Browse Source

Updated color names to match ANSI spec

Tigger Kindel 1 year ago
parent
commit
30e9b363d3

+ 1 - 1
Terminal.Gui/Configuration/ThemeManager.cs

@@ -40,7 +40,7 @@ namespace Terminal.Gui;
 /// 
 /// 			},
 /// 			"HotNormal": {
-/// 				"Foreground": "Brown",
+/// 				"Foreground": "Yellow",
 /// 				"Background": "Black"
 /// 
 /// 			},

+ 1 - 1
Terminal.Gui/Configuration/ThemeScope.cs

@@ -27,7 +27,7 @@ namespace Terminal.Gui;
 /// 
 /// 		},
 /// 		"HotNormal": {
-/// 			"Foreground": "Brown",
+/// 			"Foreground": "Yellow",
 /// 			"Background": "Black"
 /// 
 /// 		},

+ 2 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -134,7 +134,7 @@ internal class CursesDriver : ConsoleDriver {
 			return Curses.COLOR_RED;
 		case ColorNames.Magenta:
 			return Curses.COLOR_MAGENTA;
-		case ColorNames.Brown:
+		case ColorNames.Yellow:
 			return Curses.COLOR_YELLOW;
 		case ColorNames.Gray:
 			return Curses.COLOR_WHITE;
@@ -174,7 +174,7 @@ internal class CursesDriver : ConsoleDriver {
 		case Curses.COLOR_MAGENTA:
 			return ColorNames.Magenta;
 		case Curses.COLOR_YELLOW:
-			return ColorNames.Brown;
+			return ColorNames.Yellow;
 		case Curses.COLOR_WHITE:
 			return ColorNames.Gray;
 		case Curses.COLOR_GRAY:

+ 58 - 28
Terminal.Gui/Drawing/Color.cs

@@ -14,77 +14,84 @@ namespace Terminal.Gui {
 	/// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
 	/// </summary>
 	/// <remarks>
-	/// 
+	/// <para>
+	/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
+	/// </para>
+	/// <para>
+	/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured using the
+	/// <see cref="Color.Colors"/> property.
+	/// </para>
 	/// </remarks>
 	public enum ColorNames {
 		/// <summary>
-		/// The black color.
+		/// The black color. ANSI escape sequence: <c>\u001b[30m</c>.
 		/// </summary>
 		Black,
 		/// <summary>
-		/// The blue color.
+		/// The blue color. ANSI escape sequence: <c>\u001b[34m</c>.
 		/// </summary>
 		Blue,
 		/// <summary>
-		/// The green color.
+		/// The green color. ANSI escape sequence: <c>\u001b[32m</c>.
 		/// </summary>
 		Green,
 		/// <summary>
-		/// The cyan color.
+		/// The cyan color. ANSI escape sequence: <c>\u001b[36m</c>.
 		/// </summary>
 		Cyan,
 		/// <summary>
-		/// The red color.
+		/// The red color. ANSI escape sequence: <c>\u001b[31m</c>.
 		/// </summary>
 		Red,
 		/// <summary>
-		/// The magenta color.
+		/// The magenta color. ANSI escape sequence: <c>\u001b[35m</c>.
 		/// </summary>
 		Magenta,
 		/// <summary>
-		/// The brown color.
+		/// The yellow color (also known as Brown). ANSI escape sequence: <c>\u001b[33m</c>.
 		/// </summary>
-		Brown,
+		Yellow,
 		/// <summary>
-		/// The gray color.
+		/// The gray color (also known as White). ANSI escape sequence: <c>\u001b[37m</c>.
 		/// </summary>
 		Gray,
 		/// <summary>
-		/// The dark gray color.
+		/// The dark gray color (also known as Bright Black). ANSI escape sequence: <c>\u001b[30;1m</c>.
 		/// </summary>
 		DarkGray,
 		/// <summary>
-		/// The bright bBlue color.
+		/// The bright blue color. ANSI escape sequence: <c>\u001b[34;1m</c>.
 		/// </summary>
 		BrightBlue,
 		/// <summary>
-		/// The bright green color.
+		/// The bright green color. ANSI escape sequence: <c>\u001b[32;1m</c>.
 		/// </summary>
 		BrightGreen,
 		/// <summary>
-		/// The bright cyan color.
+		/// The bright cyan color. ANSI escape sequence: <c>\u001b[36;1m</c>.
 		/// </summary>
 		BrightCyan,
 		/// <summary>
-		/// The bright red color.
+		/// The bright red color. ANSI escape sequence: <c>\u001b[31;1m</c>.
 		/// </summary>
 		BrightRed,
 		/// <summary>
-		/// The bright magenta color.
+		/// The bright magenta color. ANSI escape sequence: <c>\u001b[35;1m</c>.
 		/// </summary>
 		BrightMagenta,
 		/// <summary>
-		/// The bright yellow color.
+		/// The bright yellow color. ANSI escape sequence: <c>\u001b[33;1m</c>.
 		/// </summary>
 		BrightYellow,
 		/// <summary>
-		/// The White color.
+		/// The White color (also known as Bright White). ANSI escape sequence: <c>\u001b[37;1m</c>.
 		/// </summary>
 		White
 	}
 
 	/// <summary>
-	/// Represents a 24-bit color. This is used with <see cref="Attribute"/>. 
+	/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see <see cref="ColorName"/>).
+	/// Used with <see cref="Attribute"/>. 
 	/// </summary>
 	[JsonConverter (typeof (ColorJsonConverter))]
 	public class Color : IEquatable<Color> {
@@ -195,7 +202,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Maps legacy 16-color values to the corresponding 24-bit RGB value.
 		/// </summary>
-		internal static readonly ImmutableDictionary<Color, ColorNames> _colorNames = new Dictionary<Color, ColorNames> () {
+		internal static ImmutableDictionary<Color, ColorNames> _colorToNameMap = new Dictionary<Color, ColorNames> () {
 			// using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
 			// See also: https://en.wikipedia.org/wiki/ANSI_escape_code
 			{ new Color (12, 12, 12),ColorNames.Black },
@@ -204,7 +211,7 @@ namespace Terminal.Gui {
 			{ new Color (58, 150, 221),ColorNames.Cyan},
 			{ new Color (197, 15, 31),ColorNames.Red},
 			{ new Color (136, 23, 152),ColorNames.Magenta},
-			{ new Color (128, 64, 32),ColorNames.Brown},
+			{ new Color (128, 64, 32),ColorNames.Yellow},
 			{ new Color (204, 204, 204),ColorNames.Gray},
 			{ new Color (118, 118, 118),ColorNames.DarkGray},
 			{ new Color (59, 120, 255),ColorNames.BrightBlue},
@@ -216,12 +223,30 @@ namespace Terminal.Gui {
 			{ new Color (242, 242, 242),ColorNames.White},
 		}.ToImmutableDictionary ();
 
+		[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+		public static Dictionary<string, string> Colors {
+			get {
+				// Transform _colorToNameMap into a Dictionary<string,string>
+				return _colorToNameMap.ToDictionary (kvp => kvp.Value.ToString (), kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}");
+			}
+			set {
+				// Transform Dictionary<string,string> into _colorToNameMap
+				var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => {
+					if (Enum.TryParse<ColorNames> (kvp.Key, out var colorName)) {
+						return colorName;
+					}
+					throw new ArgumentException ($"Invalid color name: {kvp.Key}");
+				});
+				_colorToNameMap = newMap.ToImmutableDictionary ();
+			}
+		}
+
 		/// <summary>
 		/// Converts a legacy <see cref="ColorNames"/> to a 24-bit <see cref="Color"/>.
 		/// </summary>
 		/// <param name="consoleColor">The <see cref="Color"/> to convert.</param>
 		/// <returns></returns>
-		private static Color FromColorName (ColorNames consoleColor) => _colorNames.FirstOrDefault (x => x.Value == consoleColor).Key;
+		private static Color FromColorName (ColorNames consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key;
 
 		// Iterates through the entries in the _colorNames dictionary, calculates the
 		// Euclidean distance between the input color and each dictionary color in RGB space,
@@ -232,7 +257,7 @@ namespace Terminal.Gui {
 			ColorNames closestColor = ColorNames.Black; // Default to Black
 			double closestDistance = double.MaxValue;
 
-			foreach (var colorEntry in _colorNames) {
+			foreach (var colorEntry in _colorToNameMap) {
 				var distance = CalculateColorDistance (inputColor, colorEntry.Key);
 				if (distance < closestDistance) {
 					closestDistance = distance;
@@ -299,9 +324,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		public const ColorNames Magenta = ColorNames.Magenta;
 		/// <summary>
-		/// The brown color.
+		/// The yellow color.
 		/// </summary>
-		public const ColorNames Brown = ColorNames.Brown;
+		public const ColorNames Yellow = ColorNames.Yellow;
 		/// <summary>
 		/// The gray color.
 		/// </summary>
@@ -423,6 +448,11 @@ namespace Terminal.Gui {
 				return true;
 			}
 
+			if (Enum.TryParse<ColorNames> (text, true, out ColorNames colorName)) {
+				color = new Color (colorName);
+				return true;
+			}
+
 			color = null;
 			return false;
 		}
@@ -496,7 +526,7 @@ namespace Terminal.Gui {
 
 			if (left is null || right is null)
 				return true;
-			
+
 			return !left.Equals (right);
 		}
 
@@ -583,7 +613,7 @@ namespace Terminal.Gui {
 		public override string ToString ()
 		{
 			// If Values has an exact match with a named color (in _colorNames), use that.
-			if (_colorNames.TryGetValue (this, out ColorNames colorName)) {
+			if (_colorToNameMap.TryGetValue (this, out ColorNames colorName)) {
 				return Enum.GetName (typeof (ColorNames), colorName);
 			}
 			// Otherwise return as an RGB hex value.
@@ -751,7 +781,7 @@ namespace Terminal.Gui {
 
 		/// <inheritdoc />
 		public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
-		
+
 		/// <inheritdoc />
 		public override string ToString ()
 		{

+ 1 - 1
Terminal.Gui/Resources/config.json

@@ -54,7 +54,7 @@
                 "Background": "Cyan"
               },
               "HotNormal": {
-                "Foreground": "Brown",
+                "Foreground": "Yellow",
                 "Background": "Black"
               },
               "HotFocus": {

+ 5 - 5
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -382,7 +382,7 @@ namespace Terminal.Gui.ConfigurationTests {
                 ""Background"": ""Cyan""
               },
               ""HotNormal"": {
-                ""Foreground"": ""Brown"",
+                ""Foreground"": ""Yellow"",
                 ""Background"": ""Black""
               },
               ""HotFocus"": {
@@ -540,7 +540,7 @@ namespace Terminal.Gui.ConfigurationTests {
 							{
 								""UserDefined"": {
 									""hotNormal"": {
-										""foreground"": ""yellow"",
+										""foreground"": ""brown"",
 										""background"": ""1234""
 									}
 								}
@@ -552,7 +552,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			}";
 
 			JsonException jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
-			Assert.Equal ("Unexpected color name: yellow.", jsonException.Message);
+			Assert.Equal ("Unexpected color name: brown.", jsonException.Message);
 
 			// AbNormal is not a ColorScheme attribute
 			json = @"
@@ -621,7 +621,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			Application.Init (new FakeDriver ());
 
 			ConfigurationManager.ThrowOnJsonErrors = false;
-			// "yellow" is not a color
+			// "brown" is not a color
 			string json = @"
 			{
 				""Themes"" : [ 
@@ -631,7 +631,7 @@ namespace Terminal.Gui.ConfigurationTests {
 							{
 								""UserDefined"": {
 									""hotNormal"": {
-										""foreground"": ""yellow"",
+										""foreground"": ""brown"",
 										""background"": ""1234""
 									}
 								}

+ 2 - 2
UnitTests/Configuration/JsonConverterTests.cs

@@ -13,7 +13,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		[InlineData ("BrightMagenta", Color.BrightMagenta)]
 		[InlineData ("BrightRed", Color.BrightRed)]
 		[InlineData ("BrightYellow", Color.BrightYellow)]
-		[InlineData ("Brown", Color.Brown)]
+		[InlineData ("Yellow", Color.Yellow)]
 		[InlineData ("Cyan", Color.Cyan)]
 		[InlineData ("DarkGray", Color.DarkGray)]
 		[InlineData ("Gray", Color.Gray)]
@@ -41,7 +41,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		[InlineData (ColorNames.Gray, "Gray")]
 		[InlineData (ColorNames.Red, "Red")]
 		[InlineData (ColorNames.Magenta, "Magenta")]
-		[InlineData (ColorNames.Brown, "Brown")]
+		[InlineData (ColorNames.Yellow, "Yellow")]
 		[InlineData (ColorNames.DarkGray, "DarkGray")]
 		[InlineData (ColorNames.BrightBlue, "BrightBlue")]
 		[InlineData (ColorNames.BrightGreen, "BrightGreen")]

+ 2 - 2
UnitTests/Configuration/ThemeTests.cs

@@ -88,7 +88,7 @@ namespace Terminal.Gui.ConfigurationTests {
 				// is always White/Black
 				Normal = new Attribute (Color.Red, Color.Green),
 				Focus = new Attribute (Color.Cyan, Color.BrightCyan),
-				HotNormal = new Attribute (Color.Brown, Color.BrightYellow),
+				HotNormal = new Attribute (Color.Yellow, Color.BrightYellow),
 				HotFocus = new Attribute (Color.Green, Color.BrightGreen),
 				Disabled = new Attribute (Color.Gray, Color.DarkGray),
 			};
@@ -143,7 +143,7 @@ namespace Terminal.Gui.ConfigurationTests {
 				// is always White/Black
 				Normal = new Attribute (Color.Red, Color.Green),
 				Focus = new Attribute (Color.Cyan, Color.BrightCyan),
-				HotNormal = new Attribute (Color.Brown, Color.BrightYellow),
+				HotNormal = new Attribute (Color.Yellow, Color.BrightYellow),
 				HotFocus = new Attribute (Color.Green, Color.BrightGreen),
 				Disabled = new Attribute (Color.Gray, Color.DarkGray),
 			};

+ 12 - 12
UnitTests/Drawing/ColorTests.cs

@@ -26,12 +26,12 @@ public class ColorTests {
 	[Fact]
 	public void TestAllColors ()
 	{
-		var colorNames = Enum.GetValues (typeof (ColorNames));
-		Attribute [] attrs = new Attribute [colorNames.Length];
+		var colorNames = Enum.GetValues (typeof (ColorNames)).Cast<int> ().Distinct ().ToList();
+		Attribute [] attrs = new Attribute [colorNames.Count];
 
 		int idx = 0;
 		foreach (ColorNames bg in colorNames) {
-			attrs [idx] = new Attribute (bg, colorNames.Length - 1 - bg);
+			attrs [idx] = new Attribute (bg, colorNames.Count - 1 - bg);
 			idx++;
 		}
 		Assert.Equal (16, attrs.Length);
@@ -41,10 +41,10 @@ public class ColorTests {
 		Assert.Equal (new Attribute (Color.Cyan, Color.BrightRed), attrs [3]);
 		Assert.Equal (new Attribute (Color.Red, Color.BrightCyan), attrs [4]);
 		Assert.Equal (new Attribute (Color.Magenta, Color.BrightGreen), attrs [5]);
-		Assert.Equal (new Attribute (Color.Brown, Color.BrightBlue), attrs [6]);
+		Assert.Equal (new Attribute (Color.Yellow, Color.BrightBlue), attrs [6]);
 		Assert.Equal (new Attribute (Color.Gray, Color.DarkGray), attrs [7]);
 		Assert.Equal (new Attribute (Color.DarkGray, Color.Gray), attrs [8]);
-		Assert.Equal (new Attribute (Color.BrightBlue, Color.Brown), attrs [9]);
+		Assert.Equal (new Attribute (Color.BrightBlue, Color.Yellow), attrs [9]);
 		Assert.Equal (new Attribute (Color.BrightGreen, Color.Magenta), attrs [10]);
 		Assert.Equal (new Attribute (Color.BrightCyan, Color.Red), attrs [11]);
 		Assert.Equal (new Attribute (Color.BrightRed, Color.Cyan), attrs [12]);
@@ -52,11 +52,11 @@ public class ColorTests {
 		Assert.Equal (new Attribute (Color.BrightYellow, Color.Blue), attrs [14]);
 		Assert.Equal (new Attribute (Color.White, Color.Black), attrs [^1]);
 	}
-
+	
 	[Fact]
-	public void ColorNames_Has16Elements ()
+	public void ColorNames_HasOnly16DistinctElements ()
 	{
-		Assert.Equal (16, Enum.GetValues (typeof (ColorNames)).Length);
+		Assert.Equal (16, Enum.GetValues (typeof (ColorNames)).Cast<int> ().Distinct ().Count ());
 	}
 
 	[Fact]
@@ -68,7 +68,7 @@ public class ColorTests {
 		Assert.Equal (3, (int)ColorNames.Cyan);
 		Assert.Equal (4, (int)ColorNames.Red);
 		Assert.Equal (5, (int)ColorNames.Magenta);
-		Assert.Equal (6, (int)ColorNames.Brown);
+		Assert.Equal (6, (int)ColorNames.Yellow);
 		Assert.Equal (7, (int)ColorNames.Gray);
 		Assert.Equal (8, (int)ColorNames.DarkGray);
 		Assert.Equal (9, (int)ColorNames.BrightBlue);
@@ -307,8 +307,8 @@ public class ColorTests {
 	public void Color_ColorName_Get_ReturnsClosestColorName ()
 	{
 		// Arrange
-		var color = new Color (128, 64, 40); // Custom RGB color, closest to Brown 
-		var expectedColorName = ColorNames.Brown;
+		var color = new Color (128, 64, 40); // Custom RGB color, closest to Yellow 
+		var expectedColorName = ColorNames.Yellow;
 
 		// Act
 		var colorName = color.ColorName;
@@ -329,7 +329,7 @@ public class ColorTests {
 			(new Color(0, 255, 0), ColorNames.BrightGreen),
 			(new Color(255, 70, 8), ColorNames.BrightRed),
 			(new Color(0, 128, 128), ColorNames.Cyan),
-			(new Color(128, 64, 32), ColorNames.Brown),
+			(new Color(128, 64, 32), ColorNames.Yellow),
 		};
 
 		foreach (var testCase in testCases) {