From fe30bd0d9bad7c4d8dad8be370aa58e315b811d9 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Wed, 19 Mar 2025 22:48:20 +0200 Subject: [PATCH 01/16] Add W3C color enum with the RGB as numeric value --- Terminal.Gui/Drawing/Color/W3cColor.cs | 777 +++++++++++++++++++++++++ 1 file changed, 777 insertions(+) create mode 100644 Terminal.Gui/Drawing/Color/W3cColor.cs diff --git a/Terminal.Gui/Drawing/Color/W3cColor.cs b/Terminal.Gui/Drawing/Color/W3cColor.cs new file mode 100644 index 0000000000..a2cc07dabd --- /dev/null +++ b/Terminal.Gui/Drawing/Color/W3cColor.cs @@ -0,0 +1,777 @@ +namespace Terminal.Gui; + +/// +/// Represents the W3C color names with their RGB values. +/// +/// +/// Based on https://www.w3schools.com/colors/color_tryit.asp page. +/// +public enum W3cColor +{ + /// + /// Alice blue RGB(240, 248, 255). + /// + AliceBlue = 0xF0F8FF, + + /// + /// Antique white RGB(250, 235, 215). + /// + AntiqueWhite = 0xFAEBD7, + + /// + /// Aqua RGB(0, 255, 255). + /// + Aqua = 0x00FFFF, + + /// + /// Aquamarine RGB(127, 255, 212). + /// + Aquamarine = 0x7FFFD4, + + /// + /// Azure RGB(240, 255, 255). + /// + Azure = 0xF0FFFF, + + /// + /// Beige RGB(245, 245, 220). + /// + Beige = 0xF5F5DC, + + /// + /// Bisque RGB(255, 228, 196). + /// + Bisque = 0xFFE4C4, + + /// + /// Black RGB(0, 0, 0). + /// + Black = 0x000000, + + /// + /// Blanched almond RGB(255, 235, 205). + /// + BlanchedAlmond = 0xFFEBCD, + + /// + /// Blue RGB(0, 0, 255). + /// + Blue = 0x0000FF, + + /// + /// Blue violet RGB(138, 43, 226). + /// + BlueViolet = 0x8A2BE2, + + /// + /// Brown RGB(165, 42, 42). + /// + Brown = 0xA52A2A, + + /// + /// Burly wood RGB(222, 184, 135). + /// + BurlyWood = 0xDEB887, + + /// + /// Cadet blue RGB(95, 158, 160). + /// + CadetBlue = 0x5F9EA0, + + /// + /// Chartreuse RGB(127, 255, 0). + /// + Chartreuse = 0x7FFF00, + + /// + /// Chocolate RGB(210, 105, 30). + /// + Chocolate = 0xD2691E, + + /// + /// Coral RGB(255, 127, 80). + /// + Coral = 0xFF7F50, + + /// + /// Cornflower blue RGB(100, 149, 237). + /// + CornflowerBlue = 0x6495ED, + + /// + /// Cornsilk RGB(255, 248, 220). + /// + Cornsilk = 0xFFF8DC, + + /// + /// Crimson RGB(220, 20, 60). + /// + Crimson = 0xDC143C, + + /// + /// Cyan RGB(0, 255, 255). + /// + /// + /// Same as . + /// + Cyan = Aqua, + + /// + /// Dark blue RGB(0, 0, 139). + /// + DarkBlue = 0x00008B, + + /// + /// Dark cyan RGB(0, 139, 139). + /// + DarkCyan = 0x008B8B, + + /// + /// Dark goldenrod RGB(184, 134, 11). + /// + DarkGoldenrod = 0xB8860B, + + /// + /// Dark gray RGB(169, 169, 169). + /// + DarkGray = 0xA9A9A9, + + /// + /// Dark green RGB(0, 100, 0). + /// + DarkGreen = 0x006400, + + /// + /// Dark grey RGB(169, 169, 169). + /// + /// + /// Same as . + /// + DarkGrey = DarkGray, + + /// + /// Dark khaki RGB(189, 183, 107). + /// + DarkKhaki = 0xBDB76B, + + /// + /// Dark magenta RGB(139, 0, 139). + /// + DarkMagenta = 0x8B008B, + + /// + /// Dark olive green RGB(85, 107, 47). + /// + DarkOliveGreen = 0x556B2F, + + /// + /// Dark orange RGB(255, 140, 0). + /// + DarkOrange = 0xFF8C00, + + /// + /// Dark orchid RGB(153, 50, 204). + /// + DarkOrchid = 0x9932CC, + + /// + /// Dark red RGB(139, 0, 0). + /// + DarkRed = 0x8B0000, + + /// + /// Dark salmon RGB(233, 150, 122). + /// + DarkSalmon = 0xE9967A, + + /// + /// Dark sea green RGB(143, 188, 143). + /// + DarkSeaGreen = 0x8FBC8F, + + /// + /// Dark slate blue RGB(72, 61, 139). + /// + DarkSlateBlue = 0x483D8B, + + /// + /// Dark slate gray RGB(47, 79, 79). + /// + DarkSlateGray = 0x2F4F4F, + + /// + /// Dark slate grey RGB(47, 79, 79). + /// + /// + /// Same as . + /// + DarkSlateGrey = DarkSlateGray, + + /// + /// Dark turquoise RGB(0, 206, 209). + /// + DarkTurquoise = 0x00CED1, + + /// + /// Dark violet RGB(148, 0, 211). + /// + DarkViolet = 0x9400D3, + + /// + /// Deep pink RGB(255, 20, 147). + /// + DeepPink = 0xFF1493, + + /// + /// Deep sky blue RGB(0, 191, 255). + /// + DeepSkyBlue = 0x00BFFF, + + /// + /// Dim gray RGB(105, 105, 105). + /// + DimGray = 0x696969, + + /// + /// Dim grey RGB(105, 105, 105). + /// + /// + /// Same as . + /// + DimGrey = DimGray, + + /// + /// Dodger blue RGB(30, 144, 255). + /// + DodgerBlue = 0x1E90FF, + + /// + /// Fire brick RGB(178, 34, 34). + /// + FireBrick = 0xB22222, + + /// + /// Floral white RGB(255, 250, 240). + /// + FloralWhite = 0xFFFAF0, + + /// + /// Forest green RGB(34, 139, 34). + /// + ForestGreen = 0x228B22, + + /// + /// Fuchsia RGB(255, 0, 255). + /// + /// + /// Same as . + /// + Fuchsia = Magenta, + + /// + /// Gainsboro RGB(220, 220, 220). + /// + Gainsboro = 0xDCDCDC, + + /// + /// Ghost white RGB(248, 248, 255). + /// + GhostWhite = 0xF8F8FF, + + /// + /// Gold RGB(255, 215, 0). + /// + Gold = 0xFFD700, + + /// + /// Goldenrod RGB(218, 165, 32). + /// + Goldenrod = 0xDAA520, + + /// + /// Gray RGB(128, 128, 128). + /// + Gray = 0x808080, + + /// + /// Green RGB(0, 128, 0). + /// + Green = 0x008000, + + /// + /// Green yellow RGB(173, 255, 47). + /// + GreenYellow = 0xADFF2F, + + /// + /// Grey RGB(128, 128, 128). + /// + /// + /// Same as . + /// + Grey = Gray, + + /// + /// Honey dew RGB(240, 255, 240). + /// + HoneyDew = 0xF0FFF0, + + /// + /// Hot pink RGB(255, 105, 180). + /// + HotPink = 0xFF69B4, + + /// + /// Indian red RGB(205, 92, 92). + /// + IndianRed = 0xCD5C5C, + + /// + /// Indigo RGB(75, 0, 130). + /// + Indigo = 0x4B0082, + + /// + /// Ivory RGB(255, 255, 240). + /// + Ivory = 0xFFFFF0, + + /// + /// Khaki RGB(240, 230, 140). + /// + Khaki = 0xF0E68C, + + /// + /// Lavender RGB(230, 230, 250). + /// + Lavender = 0xE6E6FA, + + /// + /// Lavender blush RGB(255, 240, 245). + /// + LavenderBlush = 0xFFF0F5, + + /// + /// Lawn green RGB(124, 252, 0). + /// + LawnGreen = 0x7CFC00, + + /// + /// Lemon chiffon RGB(255, 250, 205). + /// + LemonChiffon = 0xFFFACD, + + /// + /// Light blue RGB(173, 216, 230). + /// + LightBlue = 0xADD8E6, + + /// + /// Light coral RGB(240, 128, 128). + /// + LightCoral = 0xF08080, + + /// + /// Light cyan RGB(224, 255, 255). + /// + LightCyan = 0xE0FFFF, + + /// + /// Light goldenrod yellow RGB(250, 250, 210). + /// + LightGoldenrodYellow = 0xFAFAD2, + + /// + /// Light gray RGB(211, 211, 211). + /// + LightGray = 0xD3D3D3, + + /// + /// Light green RGB(144, 238, 144). + /// + LightGreen = 0x90EE90, + + /// + /// Light grey RGB(211, 211, 211). + /// + /// + /// Same as . + /// + LightGrey = LightGray, + + /// + /// Light pink RGB(255, 182, 193). + /// + LightPink = 0xFFB6C1, + + /// + /// Light salmon RGB(255, 160, 122). + /// + LightSalmon = 0xFFA07A, + + /// + /// Light sea green RGB(32, 178, 170). + /// + LightSeaGreen = 0x20B2AA, + + /// + /// Light sky blue RGB(135, 206, 250). + /// + LightSkyBlue = 0x87CEFA, + + /// + /// Light slate gray RGB(119, 136, 153). + /// + LightSlateGray = 0x778899, + + /// + /// Light slate grey RGB(119, 136, 153). + /// + /// + /// Same as . + /// + LightSlateGrey = LightSlateGray, + + /// + /// Light steel blue RGB(176, 196, 222). + /// + LightSteelBlue = 0xB0C4DE, + + /// + /// Light yellow RGB(255, 255, 224). + /// + LightYellow = 0xFFFFE0, + + /// + /// Lime RGB(0, 255, 0). + /// + Lime = 0x00FF00, + + /// + /// Lime green RGB(50, 205, 50). + /// + LimeGreen = 0x32CD32, + + /// + /// Linen RGB(250, 240, 230). + /// + Linen = 0xFAF0E6, + + /// + /// Magenta RGB(255, 0, 255). + /// + Magenta = 0xFF00FF, + + /// + /// Maroon RGB(128, 0, 0). + /// + Maroon = 0x800000, + + /// + /// Medium aqua marine RGB(102, 205, 170). + /// + MediumAquaMarine = 0x66CDAA, + + /// + /// Medium blue RGB(0, 0, 205). + /// + MediumBlue = 0x0000CD, + + /// + /// Medium orchid RGB(186, 85, 211). + /// + MediumOrchid = 0xBA55D3, + + /// + /// Medium purple RGB(147, 112, 219). + /// + MediumPurple = 0x9370DB, + + /// + /// Medium sea green RGB(60, 179, 113). + /// + MediumSeaGreen = 0x3CB371, + + /// + /// Medium slate blue RGB(123, 104, 238). + /// + MediumSlateBlue = 0x7B68EE, + + /// + /// Medium spring green RGB(0, 250, 154). + /// + MediumSpringGreen = 0x00FA9A, + + /// + /// Medium turquoise RGB(72, 209, 204). + /// + MediumTurquoise = 0x48D1CC, + + /// + /// Medium violet red RGB(199, 21, 133). + /// + MediumVioletRed = 0xC71585, + + /// + /// Midnight blue RGB(25, 25, 112). + /// + MidnightBlue = 0x191970, + + /// + /// Mint cream RGB(245, 255, 250). + /// + MintCream = 0xF5FFFA, + + /// + /// Misty rose RGB(255, 228, 225). + /// + MistyRose = 0xFFE4E1, + + /// + /// Moccasin RGB(255, 228, 181). + /// + Moccasin = 0xFFE4B5, + + /// + /// Navajo white RGB(255, 222, 173). + /// + NavajoWhite = 0xFFDEAD, + + /// + /// Navy RGB(0, 0, 128). + /// + Navy = 0x000080, + + /// + /// Old lace RGB(253, 245, 230). + /// + OldLace = 0xFDF5E6, + + /// + /// Olive RGB(128, 128, 0). + /// + Olive = 0x808000, + + /// + /// Olive drab RGB(107, 142, 35). + /// + OliveDrab = 0x6B8E23, + + /// + /// Orange RGB(255, 165, 0). + /// + Orange = 0xFFA500, + + /// + /// Orange red RGB(255, 69, 0). + /// + OrangeRed = 0xFF4500, + + /// + /// Orchid RGB(218, 112, 214). + /// + Orchid = 0xDA70D6, + + /// + /// Pale goldenrod RGB(238, 232, 170). + /// + PaleGoldenrod = 0xEEE8AA, + + /// + /// Pale green RGB(152, 251, 152). + /// + PaleGreen = 0x98FB98, + + /// + /// Pale turquoise RGB(175, 238, 238). + /// + PaleTurquoise = 0xAFEEEE, + + /// + /// Pale violet red RGB(219, 112, 147). + /// + PaleVioletRed = 0xDB7093, + + /// + /// Papaya whip RGB(255, 239, 213). + /// + PapayaWhip = 0xFFEFD5, + + /// + /// Peach puff RGB(255, 218, 185). + /// + PeachPuff = 0xFFDAB9, + + /// + /// Peru RGB(205, 133, 63). + /// + Peru = 0xCD853F, + + /// + /// Pink RGB(255, 192, 203). + /// + Pink = 0xFFC0CB, + + /// + /// Plum RGB(221, 160, 221). + /// + Plum = 0xDDA0DD, + + /// + /// Powder blue RGB(176, 224, 230). + /// + PowderBlue = 0xB0E0E6, + + /// + /// Purple RGB(128, 0, 128). + /// + Purple = 0x800080, + + /// + /// Rebecca purple RGB(102, 51, 153). + /// + RebeccaPurple = 0x663399, + + /// + /// Red RGB(255, 0, 0). + /// + Red = 0xFF0000, + + /// + /// Rosy brown RGB(188, 143, 143). + /// + RosyBrown = 0xBC8F8F, + + /// + /// Royal blue RGB(65, 105, 225). + /// + RoyalBlue = 0x4169E1, + + /// + /// Saddle brown RGB(139, 69, 19). + /// + SaddleBrown = 0x8B4513, + + /// + /// Salmon RGB(250, 128, 114). + /// + Salmon = 0xFA8072, + + /// + /// Sandy brown RGB(244, 164, 96). + /// + SandyBrown = 0xF4A460, + + /// + /// Sea green RGB(46, 139, 87). + /// + SeaGreen = 0x2E8B57, + + /// + /// Sea shell RGB(255, 245, 238). + /// + SeaShell = 0xFFF5EE, + + /// + /// Sienna RGB(160, 82, 45). + /// + Sienna = 0xA0522D, + + /// + /// Silver RGB(192, 192, 192). + /// + Silver = 0xC0C0C0, + + /// + /// Sky blue RGB(135, 206, 235). + /// + SkyBlue = 0x87CEEB, + + /// + /// Slate blue RGB(106, 90, 205). + /// + SlateBlue = 0x6A5ACD, + + /// + /// Slate gray RGB(112, 128, 144). + /// + SlateGray = 0x708090, + + /// + /// Slate grey RGB(112, 128, 144). + /// + /// + /// Same as . + /// + SlateGrey = SlateGray, + + /// + /// Snow RGB(255, 250, 250). + /// + Snow = 0xFFFAFA, + + /// + /// Spring green RGB(0, 255, 127). + /// + SpringGreen = 0x00FF7F, + + /// + /// Steel blue RGB(70, 130, 180). + /// + SteelBlue = 0x4682B4, + + /// + /// Tan RGB(210, 180, 140). + /// + Tan = 0xD2B48C, + + /// + /// Teal RGB(0, 128, 128). + /// + Teal = 0x008080, + + /// + /// Thistle RGB(216, 191, 216). + /// + Thistle = 0xD8BFD8, + + /// + /// Tomato RGB(255, 99, 71). + /// + Tomato = 0xFF6347, + + /// + /// Turquoise RGB(64, 224, 208). + /// + Turquoise = 0x40E0D0, + + /// + /// Violet RGB(238, 130, 238). + /// + Violet = 0xEE82EE, + + /// + /// Wheat RGB(245, 222, 179). + /// + Wheat = 0xF5DEB3, + + /// + /// White RGB(255, 255, 255). + /// + White = 0xFFFFFF, + + /// + /// White smoke RGB(245, 245, 245). + /// + WhiteSmoke = 0xF5F5F5, + + /// + /// Yellow RGB(255, 255, 0). + /// + Yellow = 0xFFFF00, + + /// + /// Yellow green RGB(154, 205, 50). + /// + YellowGreen = 0x9ACD32 +} From 6d64a9f96f87ba4e2bc990eec8bf43463dbcae68 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sat, 22 Mar 2025 09:51:08 +0200 Subject: [PATCH 02/16] Add transform helper class for W3cColor enum For the sake of backwards compatibility prioritize parsing 16 color mode color names over the W3C colors because the previous resource-based color names/values had a mix of W3C and 16 color mode RGB values. Mechanism for choosing/prioritizing one color scheme over the other is currently only available at higher application/driver/output level. --- .../Configuration/ColorJsonConverter.cs | 12 +- .../Drawing/Color/Color.Formatting.cs | 155 +++++++++--------- Terminal.Gui/Drawing/Color/Color.cs | 10 +- Terminal.Gui/Drawing/Color/ColorStrings.cs | 98 +++++++---- Terminal.Gui/Drawing/Color/W3CColors.cs | 105 +++++++++++- .../Drawing/Color/W3cColorsTests.cs | 86 ++++++++++ 6 files changed, 351 insertions(+), 115 deletions(-) create mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index 6da4490d91..388d1fc673 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -1,6 +1,5 @@ using System.Text.Json; using System.Text.Json.Serialization; -using ColorHelper; namespace Terminal.Gui; @@ -40,11 +39,18 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS // Get the color string ReadOnlySpan colorString = reader.GetString (); + // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. + // Backwards compatibility: Color 16 RGB values were previously used as main colors. + if (ColorStrings.TryParseColor16(colorString, out Color color16)) + { + return new (color16); + } + // Check if the color string is a color name - if (ColorStrings.TryParseW3CColorName (colorString.ToString (), out Color color1)) + if (ColorStrings.TryParseW3CColorName (colorString, out Color w3cColor)) { // Return the parsed color - return new (color1); + return new (w3cColor); } if (Color.TryParse (colorString, null, out Color parsedColor)) diff --git a/Terminal.Gui/Drawing/Color/Color.Formatting.cs b/Terminal.Gui/Drawing/Color/Color.Formatting.cs index dd775fe049..5dbd378bc0 100644 --- a/Terminal.Gui/Drawing/Color/Color.Formatting.cs +++ b/Terminal.Gui/Drawing/Color/Color.Formatting.cs @@ -267,90 +267,82 @@ public static Color Parse (ReadOnlySpan text, IFormatProvider? formatProvi return text switch { // Null string or empty span provided - { IsEmpty: true } when formatProvider is null => throw new ColorParseException ( - in text, - "The text provided was null or empty.", - in text - ), + { IsEmpty: true } when formatProvider is null => + throw new ColorParseException (in text, "The text provided was null or empty.", in text), // A valid ICustomColorFormatter was specified and the text wasn't null or empty { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text), // Input string is only whitespace - { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException ( - in text, - "The text provided consisted of only whitespace characters.", - in text - ), + { Length: > 0 } when text.IsWhiteSpace () => + throw new ColorParseException (in text, "The text provided consisted of only whitespace characters.", in text), // Any string too short to possibly be any supported format. - { Length: > 0 and < 3 } => throw new ColorParseException ( - in text, - "Text was too short to be any possible supported format.", - in text - ), - - // The various hexadecimal cases - ['#', ..] hexString => hexString switch - { - // #RGB - ['#', var rChar, var gChar, var bChar] chars when chars [1..] - .IsAllAsciiHexDigits () => - new Color ( - byte.Parse ([rChar, rChar], NumberStyles.HexNumber), - byte.Parse ([gChar, gChar], NumberStyles.HexNumber), - byte.Parse ([bChar, bChar], NumberStyles.HexNumber) - ), - - // #ARGB - ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..] - .IsAllAsciiHexDigits () => - new Color ( - byte.Parse ([rChar, rChar], NumberStyles.HexNumber), - byte.Parse ([gChar, gChar], NumberStyles.HexNumber), - byte.Parse ([bChar, bChar], NumberStyles.HexNumber), - byte.Parse ([aChar, aChar], NumberStyles.HexNumber) - ), - - // #RRGGBB - [ - '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, - var b2Char - ] chars when chars [1..].IsAllAsciiHexDigits () => - new Color ( - byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), - byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), - byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber) - ), - - // #AARRGGBB - [ - '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char, - var g2Char, var b1Char, var b2Char - ] chars when chars [1..].IsAllAsciiHexDigits () => - new Color ( - byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), - byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), - byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber), - byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber) - ), - _ => throw new ColorParseException ( - in hexString, - $"Color hex string {hexString} was not in a supported format", - in hexString - ) - }, - - // rgb(r,g,b) or rgb(r,g,b,a) - ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4), - - // rgba(r,g,b,a) or rgba(r,g,b) - ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5), - - // Attempt to parse as a named color from the ColorStrings resources - { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text.ToString (), out Color color) => - new Color (color), + { Length: > 0 and < 3 } => + throw new ColorParseException (in text, "Text was too short to be any possible supported format.", in text), + // The various hexadecimal cases + ['#', ..] hexString => hexString switch + { + // #RGB + ['#', var rChar, var gChar, var bChar] chars when chars [1..] + .IsAllAsciiHexDigits () => + new Color ( + byte.Parse ([rChar, rChar], NumberStyles.HexNumber), + byte.Parse ([gChar, gChar], NumberStyles.HexNumber), + byte.Parse ([bChar, bChar], NumberStyles.HexNumber) + ), + + // #ARGB + ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..] + .IsAllAsciiHexDigits () => + new Color ( + byte.Parse ([rChar, rChar], NumberStyles.HexNumber), + byte.Parse ([gChar, gChar], NumberStyles.HexNumber), + byte.Parse ([bChar, bChar], NumberStyles.HexNumber), + byte.Parse ([aChar, aChar], NumberStyles.HexNumber) + ), + + // #RRGGBB + [ + '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char + ] chars when chars [1..].IsAllAsciiHexDigits () => + new Color ( + byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), + byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), + byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber) + ), + + // #AARRGGBB + [ + '#', var a1Char, var a2Char, + var r1Char, var r2Char, + var g1Char, var g2Char, + var b1Char, var b2Char + ] chars when chars [1..].IsAllAsciiHexDigits () => + new Color ( + byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), + byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), + byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber), + byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber) + ), + _ => throw new ColorParseException ( + in hexString, + $"Color hex string {hexString} was not in a supported format", + in hexString + ) + }, + + // rgb(r,g,b) or rgb(r,g,b,a) + ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4), + + // rgba(r,g,b,a) or rgba(r,g,b) + ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5), + // Attempt named colors + // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. + // Backwards compatibility: Color 16 RGB values were previously used as main colors. + { } when char.IsLetter (text [0]) && ColorStrings.TryParseColor16 (text, out Color color16) => color16, + { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text, out Color w3cColor) => w3cColor, // Any other input _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, []) }; @@ -585,11 +577,16 @@ public static bool TryParse (ReadOnlySpan utf8Text, IFormatProvider? provi [SkipLocalsInit] public override string ToString () { - string? name = ColorStrings.GetW3CColorName (this); + // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. + // Backwards compatibility: Color 16 RGB values were previously used as main colors. + if (ColorStrings.GetANSIColor16Name (this) is string ansiName) + { + return ansiName; + } - if (name is { }) + if (ColorStrings.GetW3CColorName (this) is string w3cName) { - return name; + return w3cName; } return $"#{R:X2}{G:X2}{B:X2}"; diff --git a/Terminal.Gui/Drawing/Color/Color.cs b/Terminal.Gui/Drawing/Color/Color.cs index d81254c73a..643cb048d7 100644 --- a/Terminal.Gui/Drawing/Color/Color.cs +++ b/Terminal.Gui/Drawing/Color/Color.cs @@ -1,7 +1,5 @@ #nullable enable using System.Collections.Frozen; -using System.Diagnostics.Contracts; -using System.Drawing; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; @@ -236,6 +234,14 @@ internal static ColorName16 GetClosestNamedColor16 (Color inputColor) return ColorExtensions.ColorToName16Map.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value; } + /// Gets the exact named color to this value. + /// + /// + internal static bool TryGetExactNamedColor16 (Color inputColor, out ColorName16 colorName16) + { + return ColorExtensions.ColorToName16Map.TryGetValue (inputColor, out colorName16); + } + [SkipLocalsInit] private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); } diff --git a/Terminal.Gui/Drawing/Color/ColorStrings.cs b/Terminal.Gui/Drawing/Color/ColorStrings.cs index 79ca9f357e..0baeccbcea 100644 --- a/Terminal.Gui/Drawing/Color/ColorStrings.cs +++ b/Terminal.Gui/Drawing/Color/ColorStrings.cs @@ -1,8 +1,5 @@ #nullable enable -using System.Collections; using System.Globalization; -using System.Resources; -using Terminal.Gui.Resources; namespace Terminal.Gui; @@ -11,8 +8,6 @@ namespace Terminal.Gui; /// public static class ColorStrings { - // PERFORMANCE: See https://stackoverflow.com/a/15521524/297526 for why GlobalResources.GetString is fast. - /// /// Gets the W3C standard string for . /// @@ -20,28 +15,58 @@ public static class ColorStrings /// if there is no standard color name for the specified color. public static string? GetW3CColorName (Color color) { - return GlobalResources.GetString ($"#{color.R:X2}{color.G:X2}{color.B:X2}", CultureInfo.CurrentUICulture); + if (W3cColors.TryNameColor (color, out string? name)) + { + return name; + } + return null; } /// - /// Returns the list of W3C standard color names. + /// Gets the ANSI 4-bit (16) color name for . /// - /// - public static IEnumerable GetW3CColorNames () + /// The color. + /// if there is no standard color name for the specified color. + public static string? GetANSIColor16Name (Color color) { - ResourceSet? resourceSet = GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true); - if (resourceSet == null) + if (Color.TryGetExactNamedColor16 (color, out ColorName16 color16)) { - yield break; + return Color16Name (color16); } + return null; + } - foreach (DictionaryEntry entry in resourceSet) + private static string Color16Name (ColorName16 color16) + { + return color16 switch { - if (entry is { Value: string colorName, Key: string key } && key.StartsWith ('#')) - { - yield return colorName; - } - } + ColorName16.Black => nameof (ColorName16.Black), + ColorName16.Blue => nameof (ColorName16.Blue), + ColorName16.Green => nameof (ColorName16.Green), + ColorName16.Cyan => nameof (ColorName16.Cyan), + ColorName16.Red => nameof (ColorName16.Red), + ColorName16.Magenta => nameof (ColorName16.Magenta), + ColorName16.Yellow => nameof (ColorName16.Yellow), + ColorName16.Gray => nameof (ColorName16.Gray), + ColorName16.DarkGray => nameof (ColorName16.DarkGray), + ColorName16.BrightBlue => nameof (ColorName16.BrightBlue), + ColorName16.BrightGreen => nameof (ColorName16.BrightGreen), + ColorName16.BrightCyan => nameof (ColorName16.BrightCyan), + ColorName16.BrightRed => nameof (ColorName16.BrightRed), + ColorName16.BrightMagenta => nameof (ColorName16.BrightMagenta), + ColorName16.BrightYellow => nameof (ColorName16.BrightYellow), + ColorName16.White => nameof (ColorName16.White), + _ => throw new NotSupportedException ($"ColorName16 '{color16}' is not supported.") + }; + } + + /// + /// Returns the list of W3C standard color names. + /// + /// + public static IEnumerable GetW3CColorNames () + { + return W3cColors.GetColorNames (); } /// @@ -50,25 +75,22 @@ public static IEnumerable GetW3CColorNames () /// The name to parse. /// If successful, the color. /// if was parsed successfully. - public static bool TryParseW3CColorName (string name, out Color color) + public static bool TryParseW3CColorName (ReadOnlySpan name, out Color color) { - foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true)!) + if (W3cColors.TryParseColor (name, out color)) { - if (entry.Value is string colorName && colorName.Equals (name, StringComparison.OrdinalIgnoreCase)) - { - return TryParseColorKey (entry.Key.ToString (), out color); - } + return true; } return TryParseColorKey (name, out color); - bool TryParseColorKey (string? key, out Color color) + static bool TryParseColorKey (ReadOnlySpan key, out Color color) { - if (key != null && key.StartsWith ('#') && key.Length == 7) + if (!key.IsEmpty && key [0] == '#' && key.Length == 7) { - if (int.TryParse (key.AsSpan (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) && - int.TryParse (key.AsSpan (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) && - int.TryParse (key.AsSpan (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b)) + if (int.TryParse (key.Slice (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) && + int.TryParse (key.Slice (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) && + int.TryParse (key.Slice (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b)) { color = new Color (r, g, b); return true; @@ -79,4 +101,22 @@ bool TryParseColorKey (string? key, out Color color) return false; } } + + /// + /// Parses and returns if name is a ANSI 4-bit standard named color. + /// + /// The name to parse. + /// If successful, the color. + /// if was parsed successfully. + public static bool TryParseColor16 (ReadOnlySpan name, out Color color) + { + if (Enum.TryParse (name, ignoreCase: true, out ColorName16 color16)) + { + color = new Color (color16); + return true; + } + + color = default; + return false; + } } diff --git a/Terminal.Gui/Drawing/Color/W3CColors.cs b/Terminal.Gui/Drawing/Color/W3CColors.cs index baa1a1101e..c594930ae7 100644 --- a/Terminal.Gui/Drawing/Color/W3CColors.cs +++ b/Terminal.Gui/Drawing/Color/W3CColors.cs @@ -1,12 +1,20 @@ -namespace Terminal.Gui; +#nullable enable + +using System.Collections.Frozen; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Terminal.Gui; /// /// Helper class that resolves w3c color names to their hex values /// Based on https://www.w3schools.com/colors/color_tryit.asp /// +[Obsolete ("Superseded by W3cColors")] public class W3CColors : IColorNameResolver { /// + [Obsolete ("Prefer W3cColors.GetColorNames()")] public IEnumerable GetColorNames () { return ColorStrings.GetW3CColorNames (); } /// @@ -15,10 +23,103 @@ public class W3CColors : IColorNameResolver /// public bool TryNameColor (Color color, out string name) { - string answer = ColorStrings.GetW3CColorName (color); + string? answer = ColorStrings.GetW3CColorName (color); name = answer ?? string.Empty; return answer != null; } } + +/// +/// Helper class for transforming to and from enum. +/// +public static class W3cColors +{ + private static readonly ImmutableArray Names; + private static readonly FrozenDictionary RgbNameMap; + + static W3cColors () + { + // Populate based on names because enums with same name are not otherwise distinguishable from each other. + string[] w3cNames = Enum.GetNames ().Order().ToArray(); + + Dictionary map = new(w3cNames.Length); + foreach (string name in w3cNames) + { + W3cColor w3c = Enum.Parse(name); + int rgb = (int)w3c; + // TODO: Collect aliases? + _ = map.TryAdd (rgb, name); + } + + Names = ImmutableArray.Create (w3cNames); + RgbNameMap = map.ToFrozenDictionary (); + } + + /// + /// Gets read-only list of the W3C colors in alphabetical order. + /// + public static IReadOnlyList GetColorNames () + { + return Names; + } + + /// + /// Tries to parse W3C color from the given name. + /// + /// + /// Contains the successfully parsed value. + /// True if parsed successfully; otherwise false. + public static bool TryParseColor (ReadOnlySpan name, out Color color) + { + if (!Enum.TryParse (name, ignoreCase: true, out W3cColor w3cColor)) + { + color = default; + return false; + } + + (byte red, byte green, byte blue) = GetRgbComponents (w3cColor); + color = new Color (red, green, blue); + return true; + } + + /// + /// Tries to match the given color RGB value to a W3C color and returns the name. + /// + /// Color to match W3C RGB value. + /// Contains name of matching W3C color. + /// True if match; otherwise false. + public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) + { + int rgb = GetRgb (color.R, color.G, color.B); + if (RgbNameMap.TryGetValue (rgb, out name)) + { + return true; + } + + name = null; + return false; + } + + private const int RgbRedShift = 16; + private const int RgbGreenShift = 8; + private const int RgbBlueShift = 0; + private const int RgbRedMask = 0xFF << RgbRedShift; + private const int RgbGreenMask = 0xFF << RgbGreenShift; + private const int RgbBlueMask = 0xFF << RgbBlueShift; + + private static (byte Red, byte Green, byte Blue) GetRgbComponents (W3cColor w3cColor) + { + int rgb = (int)w3cColor; + byte red = (byte)((rgb & RgbRedMask) >> RgbRedShift); + byte green = (byte)((rgb & RgbGreenMask) >> RgbGreenShift); + byte blue = (byte)((rgb & RgbBlueMask) >> RgbBlueShift); + return (red, green, blue); + } + + private static int GetRgb (byte red, byte green, byte blue) => + red << RgbRedShift | + green << RgbGreenShift | + blue << RgbBlueShift; +} diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs new file mode 100644 index 0000000000..cc695012e0 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs @@ -0,0 +1,86 @@ +#nullable enable + +namespace Terminal.Gui.DrawingTests; + +public class W3cColorsTests +{ + [Fact] + public void GetColorNames_NamesAreInAlphabeticalOrder () + { + List alphabeticallyOrderedNames = Enum.GetNames ().Order ().ToList (); + + Assert.Equal (alphabeticallyOrderedNames, W3cColors.GetColorNames ()); + } + + [Theory] + // TODO: Solve conflicts with ColorName16 + [InlineData (nameof (W3cColor.Aqua))] + [InlineData (nameof (W3cColor.Cyan))] + [InlineData (nameof (W3cColor.DarkGray))] + [InlineData (nameof (W3cColor.DarkGrey))] + [InlineData (nameof (W3cColor.DarkSlateGray))] + [InlineData (nameof (W3cColor.DarkSlateGrey))] + [InlineData (nameof (W3cColor.DimGray))] + [InlineData (nameof (W3cColor.DimGrey))] + [InlineData (nameof (W3cColor.Fuchsia))] + [InlineData (nameof (W3cColor.LightGray))] + [InlineData (nameof (W3cColor.LightGrey))] + [InlineData (nameof (W3cColor.LightSlateGray))] + [InlineData (nameof (W3cColor.LightSlateGrey))] + [InlineData (nameof (W3cColor.Magenta))] + [InlineData (nameof (W3cColor.SlateGray))] + [InlineData (nameof (W3cColor.SlateGrey))] + public void GetColorNames_IncludesNamesWithSameValues (string name) + { + IReadOnlyList names = W3cColors.GetColorNames (); + + Assert.True (names.Contains (name), $"W3C color names is missing '{name}'."); + } + + [Theory] + [InlineData (240, 248, 255, true, nameof(W3cColor.AliceBlue))] + [InlineData (0, 255, 255, true, nameof(W3cColor.Aqua))] + [InlineData (255, 0, 0, true, nameof(W3cColor.Red))] + [InlineData (0, 128, 0, true, nameof(W3cColor.Green))] + [InlineData (0, 0, 255, true, nameof(W3cColor.Blue))] + [InlineData (0, 255, 0, true, nameof(W3cColor.Lime))] + [InlineData (0, 0, 0, true, nameof(W3cColor.Black))] + [InlineData (255, 255, 255, true, nameof(W3cColor.White))] + [InlineData (154, 205, 50, true, nameof(W3cColor.YellowGreen))] + [InlineData (1, 2, 3, false, null)] + public void TryNameColor_ReturnsExpectedColorName(int r, int g, int b, bool expectedSuccess, string? expectedName) + { + Color inputColor = new(r, g, b); + bool actualSuccess = W3cColors.TryNameColor (inputColor, out string? actualName); + + Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + } + + [Theory] + [InlineData ("Red", true, 255, 0, 0)] + [InlineData ("red", true, 255, 0, 0)] + [InlineData ("RED", true, 255, 0, 0)] + [InlineData ("Green", true, 0, 128, 0)] + [InlineData ("green", true, 0, 128, 0)] + [InlineData ("GREEN", true, 0, 128, 0)] + [InlineData ("Blue", true, 0, 0, 255)] + [InlineData ("blue", true, 0, 0, 255)] + [InlineData ("BLUE", true, 0, 0, 255)] + [InlineData ("Nada", false, 0, 0, 0)] + // Aliases also work + // TODO: Solve conflicts with ColorName16 + [InlineData (nameof(W3cColor.Aqua), true, 0, 255, 255)] + [InlineData (nameof(W3cColor.Cyan), true, 0, 255, 255)] + [InlineData (nameof(W3cColor.DarkGray), true, 169, 169, 169)] + [InlineData (nameof(W3cColor.DarkGrey), true, 169, 169, 169)] + public void TryParseColor_ReturnsExpectedColor(string inputName, bool expectedSuccess, int r, int g, int b) + { + Color expectedColor = expectedSuccess + ? new (r, g, b) + : default; + + bool actualSuccess = W3cColors.TryParseColor (inputName, out Color actualColor); + + Assert.Equal ((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + } +} From d81495b575febf08016825ab0374c38612f27b74 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sat, 22 Mar 2025 15:44:59 +0200 Subject: [PATCH 03/16] IColorNameResolver enable null analysis --- Terminal.Gui/Drawing/Color/IColorNameResolver.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs index 5af243ee4a..f5c0fd9ba0 100644 --- a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs @@ -1,4 +1,8 @@ -namespace Terminal.Gui; +using System.Diagnostics.CodeAnalysis; + +namespace Terminal.Gui; + +#nullable enable /// /// When implemented by a class, allows mapping to @@ -20,7 +24,7 @@ public interface IColorNameResolver /// /// /// - bool TryNameColor (Color color, out string name); + bool TryNameColor (Color color, [NotNullWhen(true)]out string? name); /// /// Returns if is a recognized From 7ba196e75fbf5d90fd299dd064020ec9bbda3f9b Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 23 Mar 2025 14:33:47 +0200 Subject: [PATCH 04/16] Remove obsolete color name related ResourceManagerTests --- .../Resources/ResourceManagerTests.cs | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/Tests/UnitTests/Resources/ResourceManagerTests.cs b/Tests/UnitTests/Resources/ResourceManagerTests.cs index 1c49b3447f..15ebb0bdda 100644 --- a/Tests/UnitTests/Resources/ResourceManagerTests.cs +++ b/Tests/UnitTests/Resources/ResourceManagerTests.cs @@ -9,10 +9,6 @@ namespace Terminal.Gui.ResourcesTests; public class ResourceManagerTests { - private const string DODGER_BLUE_COLOR_KEY = "DodgerBlue"; - private const string DODGER_BLUE_COLOR_NAME = "DodgerBlue"; - private const string NO_NAMED_COLOR_KEY = "#1E80FF"; - private const string NO_NAMED_COLOR_NAME = "#1E80FF"; private const string EXISTENT_CULTURE = "pt-PT"; private const string NO_EXISTENT_CULTURE = "de-DE"; private const string NO_EXISTENT_KEY = "blabla"; @@ -50,51 +46,6 @@ public void GetObject_FallBack_To_Default_For_Not_Translated_Existent_Culture_Fi RestoreCurrentCultures (); } - [Fact] - public void GetResourceSet_FallBack_To_Default_For_No_Existent_Culture_File () - { - CultureInfo.CurrentCulture = new (NO_EXISTENT_CULTURE); - CultureInfo.CurrentUICulture = new (NO_EXISTENT_CULTURE); - - // W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames - string [] colorNames = new W3CColors ().GetColorNames ().ToArray (); - Assert.Contains (DODGER_BLUE_COLOR_NAME, colorNames); - Assert.DoesNotContain (NO_TRANSLATED_VALUE, colorNames); - - RestoreCurrentCultures (); - } - - [Fact] - public void GetResourceSet_FallBack_To_Default_For_Not_Translated_Existent_Culture_File () - { - CultureInfo.CurrentCulture = new (EXISTENT_CULTURE); - CultureInfo.CurrentUICulture = new (EXISTENT_CULTURE); - - // These aren't already translated - // ColorStrings.GetW3CColorNames method uses GetResourceSet method to retrieve color names - IEnumerable colorNames = ColorStrings.GetW3CColorNames (); - Assert.NotEmpty (colorNames); - - // W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames - colorNames = new W3CColors ().GetColorNames ().ToArray (); - Assert.Contains (DODGER_BLUE_COLOR_NAME, colorNames); - Assert.DoesNotContain (NO_TRANSLATED_VALUE, colorNames); - - // ColorStrings.TryParseW3CColorName method uses GetResourceSet method to retrieve a color value - Assert.True (ColorStrings.TryParseW3CColorName (DODGER_BLUE_COLOR_NAME, out Color color)); - Assert.Equal (DODGER_BLUE_COLOR_KEY, color.ToString ()); - - // W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames for no-named colors - colorNames = new W3CColors ().GetColorNames ().ToArray (); - Assert.DoesNotContain (NO_NAMED_COLOR_NAME, colorNames); - - // ColorStrings.TryParseW3CColorName method uses GetResourceSet method to retrieve a color value for no-named colors - Assert.True (ColorStrings.TryParseW3CColorName (NO_NAMED_COLOR_NAME, out color)); - Assert.Equal (NO_NAMED_COLOR_KEY, color.ToString ()); - - RestoreCurrentCultures (); - } - [Fact] public void GetResourceSet_With_Filter_Does_Not_Overflows_If_Key_Does_Not_Exist () { From cb109f333972297457974dfdfd94960f456af4ec Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:16:42 +0200 Subject: [PATCH 05/16] Replace remains of W3CColors with direct W3C color name resolver Temporarily breaks backwards compatibility and tests even further. --- .../Drawing/Color/IColorNameResolver.cs | 2 +- .../{W3CColors.cs => W3cColorNameResolver.cs} | 49 +++++++++---------- Terminal.Gui/Views/ColorPicker.cs | 12 ++--- Tests/UnitTests/Views/ColorPickerTests.cs | 4 +- 4 files changed, 32 insertions(+), 35 deletions(-) rename Terminal.Gui/Drawing/Color/{W3CColors.cs => W3cColorNameResolver.cs} (67%) diff --git a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs index f5c0fd9ba0..ac5b2d18e3 100644 --- a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs @@ -34,5 +34,5 @@ public interface IColorNameResolver /// /// /// - bool TryParseColor (string name, out Color color); + bool TryParseColor (ReadOnlySpan name, out Color color); } diff --git a/Terminal.Gui/Drawing/Color/W3CColors.cs b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs similarity index 67% rename from Terminal.Gui/Drawing/Color/W3CColors.cs rename to Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs index c594930ae7..f22e3f6295 100644 --- a/Terminal.Gui/Drawing/Color/W3CColors.cs +++ b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs @@ -7,28 +7,24 @@ namespace Terminal.Gui; /// -/// Helper class that resolves w3c color names to their hex values -/// Based on https://www.w3schools.com/colors/color_tryit.asp +/// W3C color name resolver. /// -[Obsolete ("Superseded by W3cColors")] -public class W3CColors : IColorNameResolver +/// +/// Color name resolver interface wrapper for . +/// +public class W3cColorNameResolver : IColorNameResolver { /// - [Obsolete ("Prefer W3cColors.GetColorNames()")] - public IEnumerable GetColorNames () { return ColorStrings.GetW3CColorNames (); } + public IEnumerable GetColorNames () => + W3cColors.GetColorNames (); /// - public bool TryParseColor (string name, out Color color) { return ColorStrings.TryParseW3CColorName (name, out color); } + public bool TryParseColor (ReadOnlySpan name, out Color color) => + W3cColors.TryParseColor (name, out color); /// - public bool TryNameColor (Color color, out string name) - { - string? answer = ColorStrings.GetW3CColorName (color); - - name = answer ?? string.Empty; - - return answer != null; - } + public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) => + W3cColors.TryNameColor (color, out name); } /// @@ -41,7 +37,8 @@ public static class W3cColors static W3cColors () { - // Populate based on names because enums with same name are not otherwise distinguishable from each other. + // Populate based on names because enums with same numerical value + // are not otherwise distinguishable from each other. string[] w3cNames = Enum.GetNames ().Order().ToArray(); Dictionary map = new(w3cNames.Length); @@ -66,14 +63,16 @@ public static IReadOnlyList GetColorNames () } /// - /// Tries to parse W3C color from the given name. + /// Converts the given W3C color name to equivalent color value. /// - /// - /// Contains the successfully parsed value. - /// True if parsed successfully; otherwise false. + /// W3C color name. + /// The successfully converted W3C color value. + /// True if the conversion succeeded; otherwise false. public static bool TryParseColor (ReadOnlySpan name, out Color color) { - if (!Enum.TryParse (name, ignoreCase: true, out W3cColor w3cColor)) + if (!Enum.TryParse (name, ignoreCase: true, out W3cColor w3cColor) || + // Any numerical value converts to undefined enum value. + !Enum.IsDefined(w3cColor)) { color = default; return false; @@ -85,11 +84,11 @@ public static bool TryParseColor (ReadOnlySpan name, out Color color) } /// - /// Tries to match the given color RGB value to a W3C color and returns the name. + /// Converts the given color value to a W3C color name. /// - /// Color to match W3C RGB value. - /// Contains name of matching W3C color. - /// True if match; otherwise false. + /// Color value to match W3C color. + /// The successfully converted W3C color name. + /// True if conversion succeeded; otherwise false. public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) { int rgb = GetRgb (color.R, color.G, color.B); diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 415fc1acd1..75889359d4 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -1,7 +1,5 @@ #nullable enable -using System; - namespace Terminal.Gui; /// @@ -34,7 +32,7 @@ public ColorPicker () private Color _selectedColor = Color.Black; // TODO: Add interface - private readonly IColorNameResolver _colorNameResolver = new W3CColors (); + private readonly IColorNameResolver _colorNameResolver = new W3cColorNameResolver (); private List _bars = new (); @@ -64,7 +62,7 @@ public void ApplyStyleChanges () Width = textFieldWidth }; tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField; - tfValue.Accepting += (s, _)=>UpdateSingleBarValueFromTextField(s); + tfValue.Accepting += (s, _) => UpdateSingleBarValueFromTextField (s); _textFields.Add (bar, tfValue); } @@ -182,7 +180,7 @@ private void CreateTextField () Add (_tfHex); _tfHex.HasFocusChanged += UpdateValueFromTextField; - _tfHex.Accepting += (_,_)=> UpdateValueFromTextField(); + _tfHex.Accepting += (_, _) => UpdateValueFromTextField (); } private void DisposeOldViews () @@ -266,7 +264,7 @@ private void SyncSubViewValues (bool syncBars) if (_tfName != null) { - _tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty; + _tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string? name) ? name : string.Empty; } if (_tfHex != null) @@ -312,7 +310,7 @@ private void UpdateValueFromName (object? sender, HasFocusEventArgs e) } // it is a leave event so update - UpdateValueFromName(); + UpdateValueFromName (); } private void UpdateValueFromName () { diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index 121696ff1d..3c06ab5a50 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -820,7 +820,7 @@ public static IEnumerable ColorPickerTestData_WithTextFields () [Fact] public void TestColorNames () { - var colors = new W3CColors (); + var colors = new W3cColorNameResolver(); Assert.Contains ("Aquamarine", colors.GetColorNames ()); Assert.DoesNotContain ("Save as", colors.GetColorNames ()); } @@ -845,7 +845,7 @@ private ColorPicker GetColorPicker (ColorModel colorModel, bool showTextFields, Application.Navigation = new (); - Application.Top = new() { Width = 20, Height = 5 }; + Application.Top = new () { Width = 20, Height = 5 }; Application.Top.Add (cp); Application.Top.LayoutSubViews (); From 7fac661e4416f8aed5119b3d6523f6393ad84718 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:12:30 +0200 Subject: [PATCH 06/16] Add ANSI 4-bit (ColorName16) color name resolver --- .../Drawing/Color/AnsiColorNameResolver.cs | 70 ++++++++++++++++ .../Color/AnsiColorNameResolverTests.cs | 82 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs create mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs diff --git a/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs b/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs new file mode 100644 index 0000000000..1b61ed0ddb --- /dev/null +++ b/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs @@ -0,0 +1,70 @@ +#nullable enable + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Terminal.Gui; + +/// +/// Color name resolver for . +/// +public class AnsiColorNameResolver : IColorNameResolver +{ + private static readonly ImmutableArray AnsiColorNames = ImmutableArray.Create(Enum.GetNames()); + + /// + public IEnumerable GetColorNames () + { + return AnsiColorNames; + } + + /// + public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) + { + if (Color.TryGetExactNamedColor16 (color, out ColorName16 colorName16)) + { + name = Color16Name (colorName16); + return true; + } + name = null; + return false; + } + + /// + public bool TryParseColor (ReadOnlySpan name, out Color color) + { + if (Enum.TryParse (name, ignoreCase: true, out ColorName16 colorName16) && + // Any numerical value converts to undefined enum value. + Enum.IsDefined (colorName16)) + { + color = new Color (colorName16); + return true; + } + color = default; + return false; + } + + private static string Color16Name (ColorName16 color16) + { + return color16 switch + { + ColorName16.Black => nameof (ColorName16.Black), + ColorName16.Blue => nameof (ColorName16.Blue), + ColorName16.Green => nameof (ColorName16.Green), + ColorName16.Cyan => nameof (ColorName16.Cyan), + ColorName16.Red => nameof (ColorName16.Red), + ColorName16.Magenta => nameof (ColorName16.Magenta), + ColorName16.Yellow => nameof (ColorName16.Yellow), + ColorName16.Gray => nameof (ColorName16.Gray), + ColorName16.DarkGray => nameof (ColorName16.DarkGray), + ColorName16.BrightBlue => nameof (ColorName16.BrightBlue), + ColorName16.BrightGreen => nameof (ColorName16.BrightGreen), + ColorName16.BrightCyan => nameof (ColorName16.BrightCyan), + ColorName16.BrightRed => nameof (ColorName16.BrightRed), + ColorName16.BrightMagenta => nameof (ColorName16.BrightMagenta), + ColorName16.BrightYellow => nameof (ColorName16.BrightYellow), + ColorName16.White => nameof (ColorName16.White), + _ => throw new NotSupportedException ($"ColorName16 '{color16}' is not supported.") + }; + } +} diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs new file mode 100644 index 0000000000..4f8a0053ba --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs @@ -0,0 +1,82 @@ +#nullable enable + +namespace Terminal.Gui.DrawingTests; + +public class AnsiColorNameResolverTests +{ + private readonly AnsiColorNameResolver _candidate; + + public AnsiColorNameResolverTests () + { + _candidate = new AnsiColorNameResolver (); + } + + [Fact] + public void GetNames_Returns16ColorNames () + { + string[] expected = Enum.GetNames(); + + string[] actual = _candidate.GetColorNames ().ToArray(); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData (0, 0, 0, true, nameof (ColorName16.Black))] + [InlineData (0, 0, 255, true, nameof (ColorName16.Blue))] + [InlineData (59, 120, 255, true, nameof (ColorName16.BrightBlue))] + [InlineData (97, 214, 214, true, nameof (ColorName16.BrightCyan))] + [InlineData (22, 198, 12, true, nameof (ColorName16.BrightGreen))] + [InlineData (180, 0, 158, true, nameof (ColorName16.BrightMagenta))] + [InlineData (231, 72, 86, true, nameof (ColorName16.BrightRed))] + [InlineData (249, 241, 165, true, nameof (ColorName16.BrightYellow))] + [InlineData (0, 255, 255, true, nameof (ColorName16.Cyan))] + [InlineData (118, 118, 118, true, nameof (ColorName16.DarkGray))] + [InlineData (128, 128, 128, true, nameof (ColorName16.Gray))] + [InlineData (0, 128, 0, true, nameof (ColorName16.Green))] + [InlineData (255, 0, 255, true, nameof (ColorName16.Magenta))] + [InlineData (255, 0, 0, true, nameof (ColorName16.Red))] + [InlineData (255, 255, 255, true, nameof (ColorName16.White))] + [InlineData (255, 255, 0, true, nameof (ColorName16.Yellow))] + // Fail + [InlineData (1, 2, 3, false, null)] + public void TryNameColor_ReturnsExpectedColorName (byte r, byte g, byte b, bool expectedSuccess, string? expectedName) + { + bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName); + + Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + } + + [Theory] + [InlineData (nameof (ColorName16.Black), true, 0, 0, 0)] + [InlineData (nameof (ColorName16.Blue), true, 0, 0, 255)] + [InlineData (nameof (ColorName16.BrightBlue), true, 59, 120, 255)] + [InlineData (nameof(ColorName16.BrightCyan), true, 97, 214, 214)] + [InlineData (nameof(ColorName16.BrightGreen), true, 22, 198, 12)] + [InlineData (nameof(ColorName16.BrightMagenta), true, 180, 0, 158)] + [InlineData (nameof(ColorName16.BrightRed), true, 231, 72, 86)] + [InlineData (nameof(ColorName16.BrightYellow), true, 249, 241, 165)] + [InlineData (nameof(ColorName16.Cyan), true, 0, 255, 255)] + [InlineData (nameof(ColorName16.DarkGray), true, 118, 118, 118)] + [InlineData (nameof(ColorName16.Gray), true, 128, 128, 128)] + [InlineData (nameof(ColorName16.Green), true, 0, 128, 0)] + [InlineData (nameof(ColorName16.Magenta), true, 255, 0, 255)] + [InlineData (nameof(ColorName16.Red), true, 255, 0, 0)] + [InlineData (nameof(ColorName16.White), true, 255, 255, 255)] + [InlineData (nameof(ColorName16.Yellow), true, 255, 255, 0)] + // Case-insensitive + [InlineData ("BRIGHTBLUE", true, 59, 120, 255)] + [InlineData ("brightblue", true, 59, 120, 255)] + // Fail + [InlineData ("brightlight", false, 0, 0, 0)] + public void TryParseColor_ReturnsExpectedColor (string inputName, bool expectedSuccess, byte r, byte g, byte b) + { + Color expectedColor = expectedSuccess + ? new(r, g, b) + : default; + + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + + Assert.Equal((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + } +} From 75d1f493cd93b32dc1963a52a036c8504739b1da Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:29:49 +0200 Subject: [PATCH 07/16] Add multi-standard color name resolver Combined resolver for both ANSI 4-bit (ColorName16) and W3C colors while trying to maintain backwards compatibility for ColorPicker. --- .../Configuration/ColorJsonConverter.cs | 13 +- .../Drawing/Color/Color.Formatting.cs | 17 +- Terminal.Gui/Drawing/Color/Color.cs | 5 +- Terminal.Gui/Drawing/Color/ColorStrings.cs | 104 +++++----- .../Color/MultiStandardColorNameResolver.cs | 174 ++++++++++++++++ Terminal.Gui/Views/ColorPicker.cs | 2 +- Tests/UnitTests/Views/ColorPickerTests.cs | 8 - .../Color/AnsiColorNameResolverTests.cs | 4 + .../MultiStandardColorNameResolverTests.cs | 187 ++++++++++++++++++ 9 files changed, 433 insertions(+), 81 deletions(-) create mode 100644 Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs create mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index 388d1fc673..c490299167 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -39,18 +39,9 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS // Get the color string ReadOnlySpan colorString = reader.GetString (); - // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. - // Backwards compatibility: Color 16 RGB values were previously used as main colors. - if (ColorStrings.TryParseColor16(colorString, out Color color16)) + if (ColorStrings.TryParseNamedColor (colorString, out Color namedColor)) { - return new (color16); - } - - // Check if the color string is a color name - if (ColorStrings.TryParseW3CColorName (colorString, out Color w3cColor)) - { - // Return the parsed color - return new (w3cColor); + return namedColor; } if (Color.TryParse (colorString, null, out Color parsedColor)) diff --git a/Terminal.Gui/Drawing/Color/Color.Formatting.cs b/Terminal.Gui/Drawing/Color/Color.Formatting.cs index 5dbd378bc0..323ae14166 100644 --- a/Terminal.Gui/Drawing/Color/Color.Formatting.cs +++ b/Terminal.Gui/Drawing/Color/Color.Formatting.cs @@ -1,6 +1,5 @@ #nullable enable using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; using System.Globalization; using System.Runtime.CompilerServices; @@ -339,10 +338,7 @@ in hexString // rgba(r,g,b,a) or rgba(r,g,b) ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5), // Attempt named colors - // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. - // Backwards compatibility: Color 16 RGB values were previously used as main colors. - { } when char.IsLetter (text [0]) && ColorStrings.TryParseColor16 (text, out Color color16) => color16, - { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text, out Color w3cColor) => w3cColor, + { } when char.IsLetter (text [0]) && ColorStrings.TryParseNamedColor (text, out Color color) => color, // Any other input _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, []) }; @@ -577,16 +573,9 @@ public static bool TryParse (ReadOnlySpan utf8Text, IFormatProvider? provi [SkipLocalsInit] public override string ToString () { - // TODO: Mechanism to choose/prioritize between ANSI and W3C colors. - // Backwards compatibility: Color 16 RGB values were previously used as main colors. - if (ColorStrings.GetANSIColor16Name (this) is string ansiName) + if (ColorStrings.GetColorName (this) is string colorName) { - return ansiName; - } - - if (ColorStrings.GetW3CColorName (this) is string w3cName) - { - return w3cName; + return colorName; } return $"#{R:X2}{G:X2}{B:X2}"; diff --git a/Terminal.Gui/Drawing/Color/Color.cs b/Terminal.Gui/Drawing/Color/Color.cs index 643cb048d7..23c74c8779 100644 --- a/Terminal.Gui/Drawing/Color/Color.cs +++ b/Terminal.Gui/Drawing/Color/Color.cs @@ -234,9 +234,10 @@ internal static ColorName16 GetClosestNamedColor16 (Color inputColor) return ColorExtensions.ColorToName16Map.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value; } - /// Gets the exact named color to this value. + /// Converts the given color value to exact named color represented by . /// - /// + /// Successfully converted named color. + /// True if conversion succeeded; otherwise false. internal static bool TryGetExactNamedColor16 (Color inputColor, out ColorName16 colorName16) { return ColorExtensions.ColorToName16Map.TryGetValue (inputColor, out colorName16); diff --git a/Terminal.Gui/Drawing/Color/ColorStrings.cs b/Terminal.Gui/Drawing/Color/ColorStrings.cs index 0baeccbcea..fc57b7a3e1 100644 --- a/Terminal.Gui/Drawing/Color/ColorStrings.cs +++ b/Terminal.Gui/Drawing/Color/ColorStrings.cs @@ -8,6 +8,10 @@ namespace Terminal.Gui; /// public static class ColorStrings { + private static readonly AnsiColorNameResolver Ansi = new(); + private static readonly W3cColorNameResolver W3c = new(); + private static readonly MultiStandardColorNameResolver Multi = new(); + /// /// Gets the W3C standard string for . /// @@ -15,7 +19,7 @@ public static class ColorStrings /// if there is no standard color name for the specified color. public static string? GetW3CColorName (Color color) { - if (W3cColors.TryNameColor (color, out string? name)) + if (W3c.TryNameColor (color, out string? name)) { return name; } @@ -29,35 +33,25 @@ public static class ColorStrings /// if there is no standard color name for the specified color. public static string? GetANSIColor16Name (Color color) { - if (Color.TryGetExactNamedColor16 (color, out ColorName16 color16)) + if (Ansi.TryNameColor (color, out string? name)) { - return Color16Name (color16); + return name; } return null; } - private static string Color16Name (ColorName16 color16) + /// + /// Gets backwards compatible color name for . + /// + /// The color. + /// Standard color name for the specified color; otherwise . + public static string? GetColorName (Color color) { - return color16 switch + if (Multi.TryNameColor (color, out string? name)) { - ColorName16.Black => nameof (ColorName16.Black), - ColorName16.Blue => nameof (ColorName16.Blue), - ColorName16.Green => nameof (ColorName16.Green), - ColorName16.Cyan => nameof (ColorName16.Cyan), - ColorName16.Red => nameof (ColorName16.Red), - ColorName16.Magenta => nameof (ColorName16.Magenta), - ColorName16.Yellow => nameof (ColorName16.Yellow), - ColorName16.Gray => nameof (ColorName16.Gray), - ColorName16.DarkGray => nameof (ColorName16.DarkGray), - ColorName16.BrightBlue => nameof (ColorName16.BrightBlue), - ColorName16.BrightGreen => nameof (ColorName16.BrightGreen), - ColorName16.BrightCyan => nameof (ColorName16.BrightCyan), - ColorName16.BrightRed => nameof (ColorName16.BrightRed), - ColorName16.BrightMagenta => nameof (ColorName16.BrightMagenta), - ColorName16.BrightYellow => nameof (ColorName16.BrightYellow), - ColorName16.White => nameof (ColorName16.White), - _ => throw new NotSupportedException ($"ColorName16 '{color16}' is not supported.") - }; + return name; + } + return null; } /// @@ -66,7 +60,7 @@ private static string Color16Name (ColorName16 color16) /// public static IEnumerable GetW3CColorNames () { - return W3cColors.GetColorNames (); + return W3c.GetColorNames (); } /// @@ -77,44 +71,64 @@ public static IEnumerable GetW3CColorNames () /// if was parsed successfully. public static bool TryParseW3CColorName (ReadOnlySpan name, out Color color) { - if (W3cColors.TryParseColor (name, out color)) + if (W3c.TryParseColor (name, out color)) { return true; } + // Backwards compatibility: Also parse #RRGGBB. + return TryParseHexColor (name, out color); + } - return TryParseColorKey (name, out color); - - static bool TryParseColorKey (ReadOnlySpan key, out Color color) + /// + /// Parses and returns if name is a ANSI 4-bit standard named color. + /// + /// The name to parse. + /// If successful, the color. + /// if was parsed successfully. + public static bool TryParseColor16 (ReadOnlySpan name, out Color color) + { + if (Ansi.TryParseColor (name, out color)) { - if (!key.IsEmpty && key [0] == '#' && key.Length == 7) - { - if (int.TryParse (key.Slice (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) && - int.TryParse (key.Slice (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) && - int.TryParse (key.Slice (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b)) - { - color = new Color (r, g, b); - return true; - } - } - - color = default (Color); - return false; + return true; } + color = default; + return false; } /// - /// Parses and returns if name is a ANSI 4-bit standard named color. + /// Parses and returns if name is either ANSI 4-bit or W3C standard named color. /// /// The name to parse. /// If successful, the color. /// if was parsed successfully. - public static bool TryParseColor16 (ReadOnlySpan name, out Color color) + public static bool TryParseNamedColor (ReadOnlySpan name, out Color color) { - if (Enum.TryParse (name, ignoreCase: true, out ColorName16 color16)) + if (Multi.TryParseColor (name, out color)) { - color = new Color (color16); return true; } + // Backwards compatibility: Also parse #RRGGBB. + if (TryParseHexColor (name, out color)) + { + return true; + } + + color = default; + return false; + } + + private static bool TryParseHexColor (ReadOnlySpan name, out Color color) + { + if (name.Length == 7 && name [0] == '#') + { + if (int.TryParse (name.Slice (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) && + int.TryParse (name.Slice (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) && + int.TryParse (name.Slice (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b)) + { + color = new Color (r, g, b); + return true; + } + } color = default; return false; diff --git a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs new file mode 100644 index 0000000000..b6db824fb7 --- /dev/null +++ b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs @@ -0,0 +1,174 @@ +using System.Collections.Frozen; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +#nullable enable + +namespace Terminal.Gui; + +/// +/// Backwards compatible(-ish) color name resolver prioritizing ANSI 4-bit (16) colors with fallback to W3C colors. +/// +public class MultiStandardColorNameResolver : IColorNameResolver +{ + private static readonly AnsiColorNameResolver Ansi = new(); + private static readonly W3cColorNameResolver W3c = new(); + private static readonly FrozenDictionary W3cBlockedColorNameHashMap; + private static readonly FrozenSet W3cBlockedColors; + private static readonly ImmutableArray CombinedColorNames; + + static MultiStandardColorNameResolver () + { + HashSet combinedNames = new(Ansi.GetColorNames()); + + HashSet w3cInconsistentColorNames = new(StringComparer.OrdinalIgnoreCase); + HashSet w3cInconsistentColors = new(); + + IEnumerable enumerableW3cNames = W3c.GetColorNames (); + IReadOnlyList w3cNames = enumerableW3cNames is IReadOnlyList alreadyReadOnlyList + ? alreadyReadOnlyList + : [.. enumerableW3cNames]; + + Dictionary> w3cColorsWithAlternativeNames = w3cNames + .GroupBy(w3cName => + { + if (!W3c.TryParseColor(w3cName, out Color w3cColor)) + { + throw new InvalidOperationException ($"W3C color name '{w3cName}' does not resolve to any W3C color."); + } + return w3cColor; + }) + .Where(g => g.Count() > 1) + .ToDictionary(g => g.Key, g => g.ToHashSet()); + + // Gather inconsistencies between ANSI and W3C, filter out problematic W3C names and + // create additional blocklists for W3C names and colors. + // Blocking and filtering is only applied to W3C because this resolver prioritizes ANSI for backwards compatibility. + // It would be a lot simpler to just prioritize W3C colors and names. + foreach (string w3cName in w3cNames) + { + if (w3cInconsistentColorNames.Contains (w3cName)) + { + // Already blocked, most likely through alternative name. + continue; + } + + if (!W3c.TryParseColor (w3cName, out Color w3cColor)) + { + // This condition is just inverted to reduce indentation. + // This should practically never happen if the W3C color name resolver is properly implemented. + throw new InvalidOperationException ($"W3C color name '{w3cName}' does not resolve to any color."); + } + + if (w3cColorsWithAlternativeNames.TryGetValue (w3cColor, out var names)) + { + bool blocked = false; + // Alternative names cause issues with ColorPicker etc. when combined with ANSI and prioritizing ANSI resolver. + // For example Aqua is not in ColorName16 but the actual color value resolves to ANSI Cyan + // so autocomplete for Aqua suddenly changes to Cyan because they happen to have same color value in both color scheme. + // Also DarkGrey would cause inconsistencies because the alternative DarkGray exists in ANSI and has different color value. + foreach (string name in names) + { + if (Ansi.TryParseColor (name, out _)) + { + w3cInconsistentColors.Add (w3cColor); + // Block all if one is inconsistent. + foreach (string inconsistentName in names) + { + w3cInconsistentColorNames.Add (inconsistentName); + } + blocked = true; + break; + } + } + + if (blocked) + { + // Already blocked continue to next W3C color name. + continue; + } + } + + // Just in case check. + // Same name, different ANSI value. + // For example both #767676 (ColorName16) and #A9A9A9 (W3C) resolve to DarkGray, + // although a bad example because it is already filtered due to also having alternative names. + if (Ansi.TryParseColor (w3cName, out Color ansiColor) && w3cColor != ansiColor) + { + w3cInconsistentColorNames.Add (w3cName); + w3cInconsistentColors.Add (w3cColor); + continue; + } + + combinedNames.Add (w3cName); + } + + // TODO: Utilize .NET 9 and later alternative lookup for matching ReadOnlySpan with string. + W3cBlockedColorNameHashMap = w3cInconsistentColorNames.ToFrozenDictionary ( + // Workaround for alternative lookup not being available in .NET 8 by matching ReadOnlySpan hash code to string hash code. + keySelector: x => string.GetHashCode (x, StringComparison.OrdinalIgnoreCase), + // String element is just for verifying hash code collision, e.g. same hash code but the name was different. + // Quite unlikely due to small data set, but still possible. + elementSelector: x => x); + W3cBlockedColors = w3cInconsistentColors.ToFrozenSet (); + CombinedColorNames = combinedNames.Order ().ToImmutableArray (); + } + + /// + public IEnumerable GetColorNames () + { + return CombinedColorNames; + } + + /// + public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) + { + if (Ansi.TryNameColor (color, out string? ansiName)) + { + name = ansiName; + return true; + } + + if (!IsBlockedW3cColor (color) && + W3c.TryNameColor (color, out string? w3cName) && + !IsBlockedW3cName (w3cName)) + { + name = w3cName; + return true; + } + + name = null; + return false; + } + + /// + public bool TryParseColor (ReadOnlySpan name, out Color color) + { + if (Ansi.TryParseColor (name, out color)) + { + return true; + } + + if (!IsBlockedW3cName (name) && + W3c.TryParseColor (name, out color) && + !IsBlockedW3cColor (color)) + { + return true; + } + + color = default; + return false; + } + + private bool IsBlockedW3cName (ReadOnlySpan name) + { + int nameHashCode = string.GetHashCode(name, StringComparison.OrdinalIgnoreCase); + return W3cBlockedColorNameHashMap.TryGetValue (nameHashCode, out string? inconsistentColorName) && + name.Equals (inconsistentColorName, StringComparison.OrdinalIgnoreCase); + } + + private bool IsBlockedW3cColor (Color color) + { + return W3cBlockedColors.Contains (color); + } +} diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 75889359d4..2bd102085c 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -32,7 +32,7 @@ public ColorPicker () private Color _selectedColor = Color.Black; // TODO: Add interface - private readonly IColorNameResolver _colorNameResolver = new W3cColorNameResolver (); + private readonly IColorNameResolver _colorNameResolver = new MultiStandardColorNameResolver (); private List _bars = new (); diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index 3c06ab5a50..38a09ac23f 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -817,14 +817,6 @@ public static IEnumerable ColorPickerTestData_WithTextFields () }; } - [Fact] - public void TestColorNames () - { - var colors = new W3cColorNameResolver(); - Assert.Contains ("Aquamarine", colors.GetColorNames ()); - Assert.DoesNotContain ("Save as", colors.GetColorNames ()); - } - private ColorBar GetColorBar (ColorPicker cp, ColorPickerPart toGet) { if (toGet <= ColorPickerPart.Bar3) diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs index 4f8a0053ba..a3ec3c540d 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs @@ -69,6 +69,10 @@ public void TryNameColor_ReturnsExpectedColorName (byte r, byte g, byte b, bool [InlineData ("brightblue", true, 59, 120, 255)] // Fail [InlineData ("brightlight", false, 0, 0, 0)] + // Existing enum numeric + [InlineData ("12", true, 231, 72, 86)] // ColorName16.BrightRed + // Non-existing enum numeric + [InlineData ("-12", false, 0, 0, 0)] public void TryParseColor_ReturnsExpectedColor (string inputName, bool expectedSuccess, byte r, byte g, byte b) { Color expectedColor = expectedSuccess diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs new file mode 100644 index 0000000000..c1151bd800 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs @@ -0,0 +1,187 @@ +#nullable enable + +namespace Terminal.Gui.DrawingTests; + +public class MultiStandardColorNameResolverTests +{ + private readonly MultiStandardColorNameResolver _candidate; + + public MultiStandardColorNameResolverTests () + { + _candidate = new MultiStandardColorNameResolver (); + } + + [Theory] + // Contains ANSI color names. + [InlineData (nameof (ColorName16.Black), true)] + [InlineData (nameof (ColorName16.White), true)] + [InlineData (nameof (ColorName16.Red), true)] + [InlineData (nameof (ColorName16.Green), true)] + [InlineData (nameof (ColorName16.Blue), true)] + [InlineData (nameof (ColorName16.Cyan), true)] + [InlineData (nameof (ColorName16.Magenta), true)] + [InlineData (nameof (ColorName16.DarkGray), true)] + [InlineData (nameof (ColorName16.BrightGreen), true)] + [InlineData (nameof (ColorName16.BrightMagenta), true)] + // Contains regular W3C color. + [InlineData (nameof (W3cColor.AliceBlue), true)] + [InlineData (nameof (W3cColor.BlanchedAlmond), true)] + [InlineData (nameof (W3cColor.CadetBlue), true)] + [InlineData (nameof (W3cColor.DarkBlue), true)] + [InlineData (nameof (W3cColor.FireBrick), true)] + [InlineData (nameof (W3cColor.Gainsboro), true)] + [InlineData (nameof (W3cColor.HoneyDew), true)] + [InlineData (nameof (W3cColor.Indigo), true)] + [InlineData (nameof (W3cColor.Khaki), true)] + [InlineData (nameof (W3cColor.Lavender), true)] + [InlineData (nameof (W3cColor.Maroon), true)] + [InlineData (nameof (W3cColor.Navy), true)] + [InlineData (nameof (W3cColor.Olive), true)] + [InlineData (nameof (W3cColor.Plum), true)] + [InlineData (nameof (W3cColor.RoyalBlue), true)] + [InlineData (nameof (W3cColor.Silver), true)] + [InlineData (nameof (W3cColor.Tomato), true)] + [InlineData (nameof (W3cColor.Violet), true)] + [InlineData (nameof (W3cColor.WhiteSmoke), true)] + [InlineData (nameof (W3cColor.YellowGreen), true)] + // Contains W3C color names that do not clash with ANSI colors. + [InlineData (nameof (W3cColor.DarkSlateGray), true)] + [InlineData (nameof (W3cColor.DarkSlateGrey), true)] + [InlineData (nameof (W3cColor.DimGray), true)] + [InlineData (nameof (W3cColor.DimGrey), true)] + [InlineData (nameof (W3cColor.LightGray), true)] + [InlineData (nameof (W3cColor.LightGrey), true)] + [InlineData (nameof (W3cColor.SlateGray), true)] + [InlineData (nameof (W3cColor.SlateGrey), true)] + // Does not contain W3C color alternative names that clash with ANSI colors. + [InlineData (nameof (W3cColor.Grey), false)] + [InlineData (nameof (W3cColor.DarkGrey), false)] + [InlineData (nameof (W3cColor.Aqua), false)] + [InlineData (nameof (W3cColor.Fuchsia), false)] + public void GetNames_ContainsExpectedNames (string name, bool shouldContain) + { + string[] names = _candidate.GetColorNames ().ToArray(); + + if (shouldContain) + { + Assert.Contains (name, names); + } + else + { + Assert.DoesNotContain (name, names); + } + } + + [Theory] + // ANSI color names + [InlineData (0, 0, 0, true, nameof (ColorName16.Black))] + [InlineData (0, 0, 255, true, nameof (ColorName16.Blue))] + [InlineData (59, 120, 255, true, nameof (ColorName16.BrightBlue))] + [InlineData (97, 214, 214, true, nameof (ColorName16.BrightCyan))] + [InlineData (22, 198, 12, true, nameof (ColorName16.BrightGreen))] + [InlineData (180, 0, 158, true, nameof (ColorName16.BrightMagenta))] + [InlineData (231, 72, 86, true, nameof (ColorName16.BrightRed))] + [InlineData (249, 241, 165, true, nameof (ColorName16.BrightYellow))] + [InlineData (0, 255, 255, true, nameof (ColorName16.Cyan))] + [InlineData (118, 118, 118, true, nameof (ColorName16.DarkGray))] + [InlineData (128, 128, 128, true, nameof (ColorName16.Gray))] + [InlineData (0, 128, 0, true, nameof (ColorName16.Green))] + [InlineData (255, 0, 255, true, nameof (ColorName16.Magenta))] + [InlineData (255, 0, 0, true, nameof (ColorName16.Red))] + [InlineData (255, 255, 255, true, nameof (ColorName16.White))] + [InlineData (255, 255, 0, true, nameof (ColorName16.Yellow))] + // W3C color names + [InlineData (240, 248, 255, true, nameof (W3cColor.AliceBlue))] + [InlineData (255, 235, 205, true, nameof (W3cColor.BlanchedAlmond))] + [InlineData (95, 158, 160, true, nameof (W3cColor.CadetBlue))] + [InlineData (0, 0, 139, true, nameof (W3cColor.DarkBlue))] + [InlineData (178, 34, 34, true, nameof (W3cColor.FireBrick))] + [InlineData (220, 220, 220, true, nameof (W3cColor.Gainsboro))] + [InlineData (240, 255, 240, true, nameof (W3cColor.HoneyDew))] + [InlineData (75, 0, 130, true, nameof (W3cColor.Indigo))] + [InlineData (240, 230, 140, true, nameof (W3cColor.Khaki))] + [InlineData (230, 230, 250, true, nameof (W3cColor.Lavender))] + [InlineData (128, 0, 0, true, nameof (W3cColor.Maroon))] + [InlineData (0, 0, 128, true, nameof (W3cColor.Navy))] + [InlineData (128, 128, 0, true, nameof (W3cColor.Olive))] + [InlineData (221, 160, 221, true, nameof (W3cColor.Plum))] + [InlineData (65, 105, 225, true, nameof (W3cColor.RoyalBlue))] + [InlineData (192, 192, 192, true, nameof (W3cColor.Silver))] + [InlineData (255, 99, 71, true, nameof (W3cColor.Tomato))] + [InlineData (238, 130, 238, true, nameof (W3cColor.Violet))] + [InlineData (245, 245, 245, true, nameof (W3cColor.WhiteSmoke))] + [InlineData (154, 205, 50, true, nameof (W3cColor.YellowGreen))] + // Blocked W3C colors + [InlineData (169, 169, 169, false, null)] // W3cColor.DarkGr(a|e)y + // Fail + [InlineData (1, 2, 3, false, null)] + public void TryNameColor_ReturnsExpectedColorNames (byte r, byte g, byte b, bool expectedSuccess, string? expectedName) + { + bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName); + + Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + } + + [Theory] + // ANSI colors. + [InlineData (nameof (ColorName16.Black), true, 0, 0, 0)] + [InlineData (nameof (ColorName16.Blue), true, 0, 0, 255)] + [InlineData (nameof (ColorName16.BrightBlue), true, 59, 120, 255)] + [InlineData (nameof (ColorName16.BrightCyan), true, 97, 214, 214)] + [InlineData (nameof (ColorName16.BrightGreen), true, 22, 198, 12)] + [InlineData (nameof (ColorName16.BrightMagenta), true, 180, 0, 158)] + [InlineData (nameof (ColorName16.BrightRed), true, 231, 72, 86)] + [InlineData (nameof (ColorName16.BrightYellow), true, 249, 241, 165)] + [InlineData (nameof (ColorName16.Cyan), true, 0, 255, 255)] + [InlineData (nameof (ColorName16.DarkGray), true, 118, 118, 118)] + [InlineData (nameof (ColorName16.Gray), true, 128, 128, 128)] + [InlineData (nameof (ColorName16.Green), true, 0, 128, 0)] + [InlineData (nameof (ColorName16.Magenta), true, 255, 0, 255)] + [InlineData (nameof (ColorName16.Red), true, 255, 0, 0)] + [InlineData (nameof (ColorName16.White), true, 255, 255, 255)] + [InlineData (nameof (ColorName16.Yellow), true, 255, 255, 0)] + // W3C colors + [InlineData (nameof (W3cColor.AliceBlue), true, 240, 248, 255)] + [InlineData (nameof (W3cColor.BlanchedAlmond), true, 255, 235, 205)] + [InlineData (nameof (W3cColor.CadetBlue), true, 95, 158, 160)] + [InlineData (nameof (W3cColor.DarkBlue), true, 0, 0, 139)] + [InlineData (nameof (W3cColor.FireBrick), true, 178, 34, 34)] + [InlineData (nameof (W3cColor.Gainsboro), true, 220, 220, 220)] + [InlineData (nameof (W3cColor.HoneyDew), true, 240, 255, 240)] + [InlineData (nameof (W3cColor.Indigo), true, 75, 0, 130)] + [InlineData (nameof (W3cColor.Khaki), true, 240, 230, 140)] + [InlineData (nameof (W3cColor.Lavender), true, 230, 230, 250)] + [InlineData (nameof (W3cColor.Maroon), true, 128, 0, 0)] + [InlineData (nameof (W3cColor.Navy), true, 0, 0, 128)] + [InlineData (nameof (W3cColor.Olive), true, 128, 128, 0)] + [InlineData (nameof (W3cColor.Plum), true, 221, 160, 221)] + [InlineData (nameof (W3cColor.RoyalBlue), true, 65, 105, 225)] + [InlineData (nameof (W3cColor.Silver), true, 192, 192, 192)] + [InlineData (nameof (W3cColor.Tomato), true, 255, 99, 71)] + [InlineData (nameof (W3cColor.Violet), true, 238, 130, 238)] + [InlineData (nameof (W3cColor.WhiteSmoke), true, 245, 245, 245)] + [InlineData (nameof (W3cColor.YellowGreen), true, 154, 205, 50)] + // Case-insensitive + [InlineData ("BRIGHTBLUE", true, 59, 120, 255)] + [InlineData ("brightblue", true, 59, 120, 255)] + [InlineData ("TOMATO", true, 255, 99, 71)] + [InlineData ("tomato", true, 255, 99, 71)] + // Not existing + [InlineData ("brightlight", false, 0, 0, 0)] + // Existing enum numeric + [InlineData ("12", true, 231, 72, 86)] // ColorName16.BrightRed + [InlineData ("16737095", true, 255, 99, 71)] // W3cColor.Tomato + // Non-existing enum numeric + [InlineData ("-12", false, 0, 0, 0)] + [InlineData ("-16737095", false, 0, 0, 0)] + public void TryParseColor_ReturnsExpectedColors (string inputName, bool expectedSuccess, byte r, byte g, byte b) + { + Color expectedColor = expectedSuccess + ? new(r, g, b) + : default; + + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + + Assert.Equal ((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + } +} From f3149284413ccf7f18298d5318f44154c08a1c83 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Mon, 24 Mar 2025 22:53:39 +0200 Subject: [PATCH 08/16] Split conditional name resolver test cases --- .../Color/AnsiColorNameResolverTests.cs | 141 ++++--- .../MultiStandardColorNameResolverTests.cs | 355 ++++++++++-------- 2 files changed, 296 insertions(+), 200 deletions(-) diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs index a3ec3c540d..4016bce4b3 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs @@ -22,65 +22,108 @@ public void GetNames_Returns16ColorNames () } [Theory] - [InlineData (0, 0, 0, true, nameof (ColorName16.Black))] - [InlineData (0, 0, 255, true, nameof (ColorName16.Blue))] - [InlineData (59, 120, 255, true, nameof (ColorName16.BrightBlue))] - [InlineData (97, 214, 214, true, nameof (ColorName16.BrightCyan))] - [InlineData (22, 198, 12, true, nameof (ColorName16.BrightGreen))] - [InlineData (180, 0, 158, true, nameof (ColorName16.BrightMagenta))] - [InlineData (231, 72, 86, true, nameof (ColorName16.BrightRed))] - [InlineData (249, 241, 165, true, nameof (ColorName16.BrightYellow))] - [InlineData (0, 255, 255, true, nameof (ColorName16.Cyan))] - [InlineData (118, 118, 118, true, nameof (ColorName16.DarkGray))] - [InlineData (128, 128, 128, true, nameof (ColorName16.Gray))] - [InlineData (0, 128, 0, true, nameof (ColorName16.Green))] - [InlineData (255, 0, 255, true, nameof (ColorName16.Magenta))] - [InlineData (255, 0, 0, true, nameof (ColorName16.Red))] - [InlineData (255, 255, 255, true, nameof (ColorName16.White))] - [InlineData (255, 255, 0, true, nameof (ColorName16.Yellow))] - // Fail - [InlineData (1, 2, 3, false, null)] - public void TryNameColor_ReturnsExpectedColorName (byte r, byte g, byte b, bool expectedSuccess, string? expectedName) + [InlineData (0, 0, 0, nameof (ColorName16.Black))] + [InlineData (0, 0, 255, nameof (ColorName16.Blue))] + [InlineData (59, 120, 255, nameof (ColorName16.BrightBlue))] + [InlineData (97, 214, 214, nameof (ColorName16.BrightCyan))] + [InlineData (22, 198, 12, nameof (ColorName16.BrightGreen))] + [InlineData (180, 0, 158, nameof (ColorName16.BrightMagenta))] + [InlineData (231, 72, 86, nameof (ColorName16.BrightRed))] + [InlineData (249, 241, 165, nameof (ColorName16.BrightYellow))] + [InlineData (0, 255, 255, nameof (ColorName16.Cyan))] + [InlineData (118, 118, 118, nameof (ColorName16.DarkGray))] + [InlineData (128, 128, 128, nameof (ColorName16.Gray))] + [InlineData (0, 128, 0, nameof (ColorName16.Green))] + [InlineData (255, 0, 255, nameof (ColorName16.Magenta))] + [InlineData (255, 0, 0, nameof (ColorName16.Red))] + [InlineData (255, 255, 255, nameof (ColorName16.White))] + [InlineData (255, 255, 0, nameof (ColorName16.Yellow))] + public void TryNameColor_ReturnsExpectedColorName (byte r, byte g, byte b, string expectedName) { + var expected = (true, expectedName); + bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName); + var actual = (actualSuccess, actualName); + + Assert.Equal (expected, actual); + } + + [Fact] + public void TryNameColor_NoMatchFails () + { + (bool, string?) expected = (false, null); + + bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName); + var actual = (actualSuccess, actualName); - Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + Assert.Equal (expected, actual); } [Theory] - [InlineData (nameof (ColorName16.Black), true, 0, 0, 0)] - [InlineData (nameof (ColorName16.Blue), true, 0, 0, 255)] - [InlineData (nameof (ColorName16.BrightBlue), true, 59, 120, 255)] - [InlineData (nameof(ColorName16.BrightCyan), true, 97, 214, 214)] - [InlineData (nameof(ColorName16.BrightGreen), true, 22, 198, 12)] - [InlineData (nameof(ColorName16.BrightMagenta), true, 180, 0, 158)] - [InlineData (nameof(ColorName16.BrightRed), true, 231, 72, 86)] - [InlineData (nameof(ColorName16.BrightYellow), true, 249, 241, 165)] - [InlineData (nameof(ColorName16.Cyan), true, 0, 255, 255)] - [InlineData (nameof(ColorName16.DarkGray), true, 118, 118, 118)] - [InlineData (nameof(ColorName16.Gray), true, 128, 128, 128)] - [InlineData (nameof(ColorName16.Green), true, 0, 128, 0)] - [InlineData (nameof(ColorName16.Magenta), true, 255, 0, 255)] - [InlineData (nameof(ColorName16.Red), true, 255, 0, 0)] - [InlineData (nameof(ColorName16.White), true, 255, 255, 255)] - [InlineData (nameof(ColorName16.Yellow), true, 255, 255, 0)] + [InlineData (nameof (ColorName16.Black), 0, 0, 0)] + [InlineData (nameof (ColorName16.Blue), 0, 0, 255)] + [InlineData (nameof (ColorName16.BrightBlue), 59, 120, 255)] + [InlineData (nameof (ColorName16.BrightCyan), 97, 214, 214)] + [InlineData (nameof (ColorName16.BrightGreen), 22, 198, 12)] + [InlineData (nameof (ColorName16.BrightMagenta), 180, 0, 158)] + [InlineData (nameof (ColorName16.BrightRed), 231, 72, 86)] + [InlineData (nameof (ColorName16.BrightYellow), 249, 241, 165)] + [InlineData (nameof (ColorName16.Cyan), 0, 255, 255)] + [InlineData (nameof (ColorName16.DarkGray), 118, 118, 118)] + [InlineData (nameof (ColorName16.Gray), 128, 128, 128)] + [InlineData (nameof (ColorName16.Green), 0, 128, 0)] + [InlineData (nameof (ColorName16.Magenta), 255, 0, 255)] + [InlineData (nameof (ColorName16.Red), 255, 0, 0)] + [InlineData (nameof (ColorName16.White), 255, 255, 255)] + [InlineData (nameof (ColorName16.Yellow), 255, 255, 0)] // Case-insensitive - [InlineData ("BRIGHTBLUE", true, 59, 120, 255)] - [InlineData ("brightblue", true, 59, 120, 255)] - // Fail - [InlineData ("brightlight", false, 0, 0, 0)] - // Existing enum numeric - [InlineData ("12", true, 231, 72, 86)] // ColorName16.BrightRed - // Non-existing enum numeric - [InlineData ("-12", false, 0, 0, 0)] - public void TryParseColor_ReturnsExpectedColor (string inputName, bool expectedSuccess, byte r, byte g, byte b) + [InlineData ("BRIGHTBLUE", 59, 120, 255)] + [InlineData ("brightblue", 59, 120, 255)] + public void TryParseColor_ReturnsExpectedColor (string inputName, byte r, byte g, byte b) + { + var expected = (true, new Color(r, g, b)); + + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("12", 231, 72, 86)] // ColorName16.BrightRed + public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b) { - Color expectedColor = expectedSuccess - ? new(r, g, b) - : default; + var expected = (true, new Color(r, g, b)); bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("brightlight")] + public void TryParseColor_FailsOnInvalidColorName (string? invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); - Assert.Equal((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("-12")] + public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); } } diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs index c1151bd800..3c6a615488 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs @@ -12,176 +12,229 @@ public MultiStandardColorNameResolverTests () } [Theory] - // Contains ANSI color names. - [InlineData (nameof (ColorName16.Black), true)] - [InlineData (nameof (ColorName16.White), true)] - [InlineData (nameof (ColorName16.Red), true)] - [InlineData (nameof (ColorName16.Green), true)] - [InlineData (nameof (ColorName16.Blue), true)] - [InlineData (nameof (ColorName16.Cyan), true)] - [InlineData (nameof (ColorName16.Magenta), true)] - [InlineData (nameof (ColorName16.DarkGray), true)] - [InlineData (nameof (ColorName16.BrightGreen), true)] - [InlineData (nameof (ColorName16.BrightMagenta), true)] - // Contains regular W3C color. - [InlineData (nameof (W3cColor.AliceBlue), true)] - [InlineData (nameof (W3cColor.BlanchedAlmond), true)] - [InlineData (nameof (W3cColor.CadetBlue), true)] - [InlineData (nameof (W3cColor.DarkBlue), true)] - [InlineData (nameof (W3cColor.FireBrick), true)] - [InlineData (nameof (W3cColor.Gainsboro), true)] - [InlineData (nameof (W3cColor.HoneyDew), true)] - [InlineData (nameof (W3cColor.Indigo), true)] - [InlineData (nameof (W3cColor.Khaki), true)] - [InlineData (nameof (W3cColor.Lavender), true)] - [InlineData (nameof (W3cColor.Maroon), true)] - [InlineData (nameof (W3cColor.Navy), true)] - [InlineData (nameof (W3cColor.Olive), true)] - [InlineData (nameof (W3cColor.Plum), true)] - [InlineData (nameof (W3cColor.RoyalBlue), true)] - [InlineData (nameof (W3cColor.Silver), true)] - [InlineData (nameof (W3cColor.Tomato), true)] - [InlineData (nameof (W3cColor.Violet), true)] - [InlineData (nameof (W3cColor.WhiteSmoke), true)] - [InlineData (nameof (W3cColor.YellowGreen), true)] - // Contains W3C color names that do not clash with ANSI colors. - [InlineData (nameof (W3cColor.DarkSlateGray), true)] - [InlineData (nameof (W3cColor.DarkSlateGrey), true)] - [InlineData (nameof (W3cColor.DimGray), true)] - [InlineData (nameof (W3cColor.DimGrey), true)] - [InlineData (nameof (W3cColor.LightGray), true)] - [InlineData (nameof (W3cColor.LightGrey), true)] - [InlineData (nameof (W3cColor.SlateGray), true)] - [InlineData (nameof (W3cColor.SlateGrey), true)] - // Does not contain W3C color alternative names that clash with ANSI colors. - [InlineData (nameof (W3cColor.Grey), false)] - [InlineData (nameof (W3cColor.DarkGrey), false)] - [InlineData (nameof (W3cColor.Aqua), false)] - [InlineData (nameof (W3cColor.Fuchsia), false)] - public void GetNames_ContainsExpectedNames (string name, bool shouldContain) + // ANSI color names. + [InlineData (nameof (ColorName16.Black))] + [InlineData (nameof (ColorName16.White))] + [InlineData (nameof (ColorName16.Red))] + [InlineData (nameof (ColorName16.Green))] + [InlineData (nameof (ColorName16.Blue))] + [InlineData (nameof (ColorName16.Cyan))] + [InlineData (nameof (ColorName16.Magenta))] + [InlineData (nameof (ColorName16.DarkGray))] + [InlineData (nameof (ColorName16.BrightGreen))] + [InlineData (nameof (ColorName16.BrightMagenta))] + // Regular W3C color. + [InlineData (nameof (W3cColor.AliceBlue))] + [InlineData (nameof (W3cColor.BlanchedAlmond))] + [InlineData (nameof (W3cColor.CadetBlue))] + [InlineData (nameof (W3cColor.DarkBlue))] + [InlineData (nameof (W3cColor.FireBrick))] + [InlineData (nameof (W3cColor.Gainsboro))] + [InlineData (nameof (W3cColor.HoneyDew))] + [InlineData (nameof (W3cColor.Indigo))] + [InlineData (nameof (W3cColor.Khaki))] + [InlineData (nameof (W3cColor.Lavender))] + [InlineData (nameof (W3cColor.Maroon))] + [InlineData (nameof (W3cColor.Navy))] + [InlineData (nameof (W3cColor.Olive))] + [InlineData (nameof (W3cColor.Plum))] + [InlineData (nameof (W3cColor.RoyalBlue))] + [InlineData (nameof (W3cColor.Silver))] + [InlineData (nameof (W3cColor.Tomato))] + [InlineData (nameof (W3cColor.Violet))] + [InlineData (nameof (W3cColor.WhiteSmoke))] + [InlineData (nameof (W3cColor.YellowGreen))] + // W3C color names that do not clash with ANSI color names. + [InlineData (nameof (W3cColor.DarkSlateGray))] + [InlineData (nameof (W3cColor.DarkSlateGrey))] + [InlineData (nameof (W3cColor.DimGray))] + [InlineData (nameof (W3cColor.DimGrey))] + [InlineData (nameof (W3cColor.LightGray))] + [InlineData (nameof (W3cColor.LightGrey))] + [InlineData (nameof (W3cColor.SlateGray))] + [InlineData (nameof (W3cColor.SlateGrey))] + + public void GetNames_ContainsCombinationOfAnsiAndW3cNames (string name) { string[] names = _candidate.GetColorNames ().ToArray(); + Assert.Contains (name, names); + } - if (shouldContain) - { - Assert.Contains (name, names); - } - else - { - Assert.DoesNotContain (name, names); - } + [Theory] + [InlineData (nameof (W3cColor.Grey))] + [InlineData (nameof (W3cColor.DarkGrey))] + [InlineData (nameof (W3cColor.Aqua))] + [InlineData (nameof (W3cColor.Fuchsia))] + public void GetNames_OmitsBlockedW3cAlternativeNames (string name) + { + string[] names = _candidate.GetColorNames ().ToArray(); + Assert.DoesNotContain (name, names); } [Theory] // ANSI color names - [InlineData (0, 0, 0, true, nameof (ColorName16.Black))] - [InlineData (0, 0, 255, true, nameof (ColorName16.Blue))] - [InlineData (59, 120, 255, true, nameof (ColorName16.BrightBlue))] - [InlineData (97, 214, 214, true, nameof (ColorName16.BrightCyan))] - [InlineData (22, 198, 12, true, nameof (ColorName16.BrightGreen))] - [InlineData (180, 0, 158, true, nameof (ColorName16.BrightMagenta))] - [InlineData (231, 72, 86, true, nameof (ColorName16.BrightRed))] - [InlineData (249, 241, 165, true, nameof (ColorName16.BrightYellow))] - [InlineData (0, 255, 255, true, nameof (ColorName16.Cyan))] - [InlineData (118, 118, 118, true, nameof (ColorName16.DarkGray))] - [InlineData (128, 128, 128, true, nameof (ColorName16.Gray))] - [InlineData (0, 128, 0, true, nameof (ColorName16.Green))] - [InlineData (255, 0, 255, true, nameof (ColorName16.Magenta))] - [InlineData (255, 0, 0, true, nameof (ColorName16.Red))] - [InlineData (255, 255, 255, true, nameof (ColorName16.White))] - [InlineData (255, 255, 0, true, nameof (ColorName16.Yellow))] + [InlineData (0, 0, 0, nameof (ColorName16.Black))] + [InlineData (0, 0, 255, nameof (ColorName16.Blue))] + [InlineData (59, 120, 255, nameof (ColorName16.BrightBlue))] + [InlineData (97, 214, 214, nameof (ColorName16.BrightCyan))] + [InlineData (22, 198, 12, nameof (ColorName16.BrightGreen))] + [InlineData (180, 0, 158, nameof (ColorName16.BrightMagenta))] + [InlineData (231, 72, 86, nameof (ColorName16.BrightRed))] + [InlineData (249, 241, 165, nameof (ColorName16.BrightYellow))] + [InlineData (0, 255, 255, nameof (ColorName16.Cyan))] + [InlineData (118, 118, 118, nameof (ColorName16.DarkGray))] + [InlineData (128, 128, 128, nameof (ColorName16.Gray))] + [InlineData (0, 128, 0, nameof (ColorName16.Green))] + [InlineData (255, 0, 255, nameof (ColorName16.Magenta))] + [InlineData (255, 0, 0, nameof (ColorName16.Red))] + [InlineData (255, 255, 255, nameof (ColorName16.White))] + [InlineData (255, 255, 0, nameof (ColorName16.Yellow))] // W3C color names - [InlineData (240, 248, 255, true, nameof (W3cColor.AliceBlue))] - [InlineData (255, 235, 205, true, nameof (W3cColor.BlanchedAlmond))] - [InlineData (95, 158, 160, true, nameof (W3cColor.CadetBlue))] - [InlineData (0, 0, 139, true, nameof (W3cColor.DarkBlue))] - [InlineData (178, 34, 34, true, nameof (W3cColor.FireBrick))] - [InlineData (220, 220, 220, true, nameof (W3cColor.Gainsboro))] - [InlineData (240, 255, 240, true, nameof (W3cColor.HoneyDew))] - [InlineData (75, 0, 130, true, nameof (W3cColor.Indigo))] - [InlineData (240, 230, 140, true, nameof (W3cColor.Khaki))] - [InlineData (230, 230, 250, true, nameof (W3cColor.Lavender))] - [InlineData (128, 0, 0, true, nameof (W3cColor.Maroon))] - [InlineData (0, 0, 128, true, nameof (W3cColor.Navy))] - [InlineData (128, 128, 0, true, nameof (W3cColor.Olive))] - [InlineData (221, 160, 221, true, nameof (W3cColor.Plum))] - [InlineData (65, 105, 225, true, nameof (W3cColor.RoyalBlue))] - [InlineData (192, 192, 192, true, nameof (W3cColor.Silver))] - [InlineData (255, 99, 71, true, nameof (W3cColor.Tomato))] - [InlineData (238, 130, 238, true, nameof (W3cColor.Violet))] - [InlineData (245, 245, 245, true, nameof (W3cColor.WhiteSmoke))] - [InlineData (154, 205, 50, true, nameof (W3cColor.YellowGreen))] - // Blocked W3C colors - [InlineData (169, 169, 169, false, null)] // W3cColor.DarkGr(a|e)y - // Fail - [InlineData (1, 2, 3, false, null)] - public void TryNameColor_ReturnsExpectedColorNames (byte r, byte g, byte b, bool expectedSuccess, string? expectedName) + [InlineData (240, 248, 255, nameof (W3cColor.AliceBlue))] + [InlineData (255, 235, 205, nameof (W3cColor.BlanchedAlmond))] + [InlineData (95, 158, 160, nameof (W3cColor.CadetBlue))] + [InlineData (0, 0, 139, nameof (W3cColor.DarkBlue))] + [InlineData (178, 34, 34, nameof (W3cColor.FireBrick))] + [InlineData (220, 220, 220, nameof (W3cColor.Gainsboro))] + [InlineData (240, 255, 240, nameof (W3cColor.HoneyDew))] + [InlineData (75, 0, 130, nameof (W3cColor.Indigo))] + [InlineData (240, 230, 140, nameof (W3cColor.Khaki))] + [InlineData (230, 230, 250, nameof (W3cColor.Lavender))] + [InlineData (128, 0, 0, nameof (W3cColor.Maroon))] + [InlineData (0, 0, 128, nameof (W3cColor.Navy))] + [InlineData (128, 128, 0, nameof (W3cColor.Olive))] + [InlineData (221, 160, 221, nameof (W3cColor.Plum))] + [InlineData (65, 105, 225, nameof (W3cColor.RoyalBlue))] + [InlineData (192, 192, 192, nameof (W3cColor.Silver))] + [InlineData (255, 99, 71, nameof (W3cColor.Tomato))] + [InlineData (238, 130, 238, nameof (W3cColor.Violet))] + [InlineData (245, 245, 245, nameof (W3cColor.WhiteSmoke))] + [InlineData (154, 205, 50, nameof (W3cColor.YellowGreen))] + public void TryNameColor_ReturnsExpectedColorNames (byte r, byte g, byte b, string expectedName) { + var expected = (true, expectedName); + bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName); + var actual = (actualSuccess, actualName); - Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + Assert.Equal (expected, actual); } [Theory] - // ANSI colors. - [InlineData (nameof (ColorName16.Black), true, 0, 0, 0)] - [InlineData (nameof (ColorName16.Blue), true, 0, 0, 255)] - [InlineData (nameof (ColorName16.BrightBlue), true, 59, 120, 255)] - [InlineData (nameof (ColorName16.BrightCyan), true, 97, 214, 214)] - [InlineData (nameof (ColorName16.BrightGreen), true, 22, 198, 12)] - [InlineData (nameof (ColorName16.BrightMagenta), true, 180, 0, 158)] - [InlineData (nameof (ColorName16.BrightRed), true, 231, 72, 86)] - [InlineData (nameof (ColorName16.BrightYellow), true, 249, 241, 165)] - [InlineData (nameof (ColorName16.Cyan), true, 0, 255, 255)] - [InlineData (nameof (ColorName16.DarkGray), true, 118, 118, 118)] - [InlineData (nameof (ColorName16.Gray), true, 128, 128, 128)] - [InlineData (nameof (ColorName16.Green), true, 0, 128, 0)] - [InlineData (nameof (ColorName16.Magenta), true, 255, 0, 255)] - [InlineData (nameof (ColorName16.Red), true, 255, 0, 0)] - [InlineData (nameof (ColorName16.White), true, 255, 255, 255)] - [InlineData (nameof (ColorName16.Yellow), true, 255, 255, 0)] + [InlineData (169, 169, 169)] // W3cColor.DarkGr(a|e)y + public void TryNameColor_OmitsBlockedW3cColors (byte r, byte g, byte b) + { + (bool, string?) expected = (false, null); + + bool actualSuccess = _candidate.TryNameColor (new Color (r, g, b), out string? actualName); + var actual = (actualSuccess, actualName); + + Assert.Equal (expected, actual); + } + + [Fact] + public void TryNameColor_NoMatchFails () + { + (bool, string?) expected = (false, null); + + bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName); + var actual = (actualSuccess, actualName); + + Assert.Equal (expected, actual); + } + + [Theory] + // ANSI colors + [InlineData (nameof (ColorName16.Black), 0, 0, 0)] + [InlineData (nameof (ColorName16.Blue), 0, 0, 255)] + [InlineData (nameof (ColorName16.BrightBlue), 59, 120, 255)] + [InlineData (nameof (ColorName16.BrightCyan), 97, 214, 214)] + [InlineData (nameof (ColorName16.BrightGreen), 22, 198, 12)] + [InlineData (nameof (ColorName16.BrightMagenta), 180, 0, 158)] + [InlineData (nameof (ColorName16.BrightRed), 231, 72, 86)] + [InlineData (nameof (ColorName16.BrightYellow), 249, 241, 165)] + [InlineData (nameof (ColorName16.Cyan), 0, 255, 255)] + [InlineData (nameof (ColorName16.DarkGray), 118, 118, 118)] + [InlineData (nameof (ColorName16.Gray), 128, 128, 128)] + [InlineData (nameof (ColorName16.Green), 0, 128, 0)] + [InlineData (nameof (ColorName16.Magenta), 255, 0, 255)] + [InlineData (nameof (ColorName16.Red), 255, 0, 0)] + [InlineData (nameof (ColorName16.White), 255, 255, 255)] + [InlineData (nameof (ColorName16.Yellow), 255, 255, 0)] // W3C colors - [InlineData (nameof (W3cColor.AliceBlue), true, 240, 248, 255)] - [InlineData (nameof (W3cColor.BlanchedAlmond), true, 255, 235, 205)] - [InlineData (nameof (W3cColor.CadetBlue), true, 95, 158, 160)] - [InlineData (nameof (W3cColor.DarkBlue), true, 0, 0, 139)] - [InlineData (nameof (W3cColor.FireBrick), true, 178, 34, 34)] - [InlineData (nameof (W3cColor.Gainsboro), true, 220, 220, 220)] - [InlineData (nameof (W3cColor.HoneyDew), true, 240, 255, 240)] - [InlineData (nameof (W3cColor.Indigo), true, 75, 0, 130)] - [InlineData (nameof (W3cColor.Khaki), true, 240, 230, 140)] - [InlineData (nameof (W3cColor.Lavender), true, 230, 230, 250)] - [InlineData (nameof (W3cColor.Maroon), true, 128, 0, 0)] - [InlineData (nameof (W3cColor.Navy), true, 0, 0, 128)] - [InlineData (nameof (W3cColor.Olive), true, 128, 128, 0)] - [InlineData (nameof (W3cColor.Plum), true, 221, 160, 221)] - [InlineData (nameof (W3cColor.RoyalBlue), true, 65, 105, 225)] - [InlineData (nameof (W3cColor.Silver), true, 192, 192, 192)] - [InlineData (nameof (W3cColor.Tomato), true, 255, 99, 71)] - [InlineData (nameof (W3cColor.Violet), true, 238, 130, 238)] - [InlineData (nameof (W3cColor.WhiteSmoke), true, 245, 245, 245)] - [InlineData (nameof (W3cColor.YellowGreen), true, 154, 205, 50)] + [InlineData (nameof (W3cColor.AliceBlue), 240, 248, 255)] + [InlineData (nameof (W3cColor.BlanchedAlmond), 255, 235, 205)] + [InlineData (nameof (W3cColor.CadetBlue), 95, 158, 160)] + [InlineData (nameof (W3cColor.DarkBlue), 0, 0, 139)] + [InlineData (nameof (W3cColor.FireBrick), 178, 34, 34)] + [InlineData (nameof (W3cColor.Gainsboro), 220, 220, 220)] + [InlineData (nameof (W3cColor.HoneyDew), 240, 255, 240)] + [InlineData (nameof (W3cColor.Indigo), 75, 0, 130)] + [InlineData (nameof (W3cColor.Khaki), 240, 230, 140)] + [InlineData (nameof (W3cColor.Lavender), 230, 230, 250)] + [InlineData (nameof (W3cColor.Maroon), 128, 0, 0)] + [InlineData (nameof (W3cColor.Navy), 0, 0, 128)] + [InlineData (nameof (W3cColor.Olive), 128, 128, 0)] + [InlineData (nameof (W3cColor.Plum), 221, 160, 221)] + [InlineData (nameof (W3cColor.RoyalBlue), 65, 105, 225)] + [InlineData (nameof (W3cColor.Silver), 192, 192, 192)] + [InlineData (nameof (W3cColor.Tomato), 255, 99, 71)] + [InlineData (nameof (W3cColor.Violet), 238, 130, 238)] + [InlineData (nameof (W3cColor.WhiteSmoke), 245, 245, 245)] + [InlineData (nameof (W3cColor.YellowGreen), 154, 205, 50)] // Case-insensitive - [InlineData ("BRIGHTBLUE", true, 59, 120, 255)] - [InlineData ("brightblue", true, 59, 120, 255)] - [InlineData ("TOMATO", true, 255, 99, 71)] - [InlineData ("tomato", true, 255, 99, 71)] - // Not existing - [InlineData ("brightlight", false, 0, 0, 0)] - // Existing enum numeric - [InlineData ("12", true, 231, 72, 86)] // ColorName16.BrightRed - [InlineData ("16737095", true, 255, 99, 71)] // W3cColor.Tomato - // Non-existing enum numeric - [InlineData ("-12", false, 0, 0, 0)] - [InlineData ("-16737095", false, 0, 0, 0)] - public void TryParseColor_ReturnsExpectedColors (string inputName, bool expectedSuccess, byte r, byte g, byte b) + [InlineData ("BRIGHTBLUE", 59, 120, 255)] + [InlineData ("brightblue", 59, 120, 255)] + [InlineData ("TOMATO", 255, 99, 71)] + [InlineData ("tomato", 255, 99, 71)] + + public void TryParseColor_ResolvesCombinationOfAnsiAndW3cColors (string inputName, byte r, byte g, byte b) { - Color expectedColor = expectedSuccess - ? new(r, g, b) - : default; + var expected = (true, new Color(r, g, b)); bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("12", 231, 72, 86)] // ColorName16.BrightRed + [InlineData ("16737095", 255, 99, 71)] // W3cColor.Tomato + public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b) + { + var expected = (true, new Color(r, g, b)); + + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("brightlight")] + public void TryParseColor_FailsOnInvalidColorName (string? invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("-12")] + [InlineData ("-16737095")] + public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); - Assert.Equal ((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + Assert.Equal (expected, actual); } } From a2d40f33b85479c922ba52d02538279436170b5e Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:17:20 +0200 Subject: [PATCH 09/16] Change W3C colors tests to be similar to name resolvers --- .../Drawing/Color/W3cColorsTests.cs | 128 +++++++++++++----- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs index cc695012e0..e3629ac280 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs @@ -13,7 +13,6 @@ public void GetColorNames_NamesAreInAlphabeticalOrder () } [Theory] - // TODO: Solve conflicts with ColorName16 [InlineData (nameof (W3cColor.Aqua))] [InlineData (nameof (W3cColor.Cyan))] [InlineData (nameof (W3cColor.DarkGray))] @@ -38,49 +37,112 @@ public void GetColorNames_IncludesNamesWithSameValues (string name) } [Theory] - [InlineData (240, 248, 255, true, nameof(W3cColor.AliceBlue))] - [InlineData (0, 255, 255, true, nameof(W3cColor.Aqua))] - [InlineData (255, 0, 0, true, nameof(W3cColor.Red))] - [InlineData (0, 128, 0, true, nameof(W3cColor.Green))] - [InlineData (0, 0, 255, true, nameof(W3cColor.Blue))] - [InlineData (0, 255, 0, true, nameof(W3cColor.Lime))] - [InlineData (0, 0, 0, true, nameof(W3cColor.Black))] - [InlineData (255, 255, 255, true, nameof(W3cColor.White))] - [InlineData (154, 205, 50, true, nameof(W3cColor.YellowGreen))] - [InlineData (1, 2, 3, false, null)] - public void TryNameColor_ReturnsExpectedColorName(int r, int g, int b, bool expectedSuccess, string? expectedName) + [InlineData (240, 248, 255, nameof (W3cColor.AliceBlue))] + [InlineData (0, 255, 255, nameof (W3cColor.Aqua))] + [InlineData (255, 0, 0, nameof (W3cColor.Red))] + [InlineData (0, 128, 0, nameof (W3cColor.Green))] + [InlineData (0, 0, 255, nameof (W3cColor.Blue))] + [InlineData (0, 255, 0, nameof (W3cColor.Lime))] + [InlineData (0, 0, 0, nameof (W3cColor.Black))] + [InlineData (255, 255, 255, nameof (W3cColor.White))] + [InlineData (154, 205, 50, nameof (W3cColor.YellowGreen))] + public void TryNameColor_ReturnsExpectedColorName (int r, int g, int b, string expectedName) { + var expected = (true, expectedName); + Color inputColor = new(r, g, b); bool actualSuccess = W3cColors.TryNameColor (inputColor, out string? actualName); + var actual = (actualSuccess, actualName); + + Assert.Equal (expected, actual); + } + + [Fact] + public void TryNameColor_NoMatchFails () + { + (bool, string?) expected = (false, null); + + bool actualSuccess = W3cColors.TryNameColor (new Color (1, 2, 3), out string? actualName); + var actual = (actualSuccess, actualName); - Assert.Equal ((expectedSuccess, expectedName), (actualSuccess, actualName)); + Assert.Equal (expected, actual); } [Theory] - [InlineData ("Red", true, 255, 0, 0)] - [InlineData ("red", true, 255, 0, 0)] - [InlineData ("RED", true, 255, 0, 0)] - [InlineData ("Green", true, 0, 128, 0)] - [InlineData ("green", true, 0, 128, 0)] - [InlineData ("GREEN", true, 0, 128, 0)] - [InlineData ("Blue", true, 0, 0, 255)] - [InlineData ("blue", true, 0, 0, 255)] - [InlineData ("BLUE", true, 0, 0, 255)] - [InlineData ("Nada", false, 0, 0, 0)] + [InlineData (nameof (W3cColor.AliceBlue), 240, 248, 255)] + [InlineData (nameof (W3cColor.BlanchedAlmond), 255, 235, 205)] + [InlineData (nameof (W3cColor.CadetBlue), 95, 158, 160)] + [InlineData (nameof (W3cColor.DarkBlue), 0, 0, 139)] + [InlineData (nameof (W3cColor.FireBrick), 178, 34, 34)] + [InlineData (nameof (W3cColor.Gainsboro), 220, 220, 220)] + [InlineData (nameof (W3cColor.HoneyDew), 240, 255, 240)] + [InlineData (nameof (W3cColor.Indigo), 75, 0, 130)] + [InlineData (nameof (W3cColor.Khaki), 240, 230, 140)] + [InlineData (nameof (W3cColor.Lavender), 230, 230, 250)] + [InlineData (nameof (W3cColor.Maroon), 128, 0, 0)] + [InlineData (nameof (W3cColor.Navy), 0, 0, 128)] + [InlineData (nameof (W3cColor.Olive), 128, 128, 0)] + [InlineData (nameof (W3cColor.Plum), 221, 160, 221)] + [InlineData (nameof (W3cColor.RoyalBlue), 65, 105, 225)] + [InlineData (nameof (W3cColor.Silver), 192, 192, 192)] + [InlineData (nameof (W3cColor.Tomato), 255, 99, 71)] + [InlineData (nameof (W3cColor.Violet), 238, 130, 238)] + [InlineData (nameof (W3cColor.WhiteSmoke), 245, 245, 245)] + [InlineData (nameof (W3cColor.YellowGreen), 154, 205, 50)] // Aliases also work - // TODO: Solve conflicts with ColorName16 - [InlineData (nameof(W3cColor.Aqua), true, 0, 255, 255)] - [InlineData (nameof(W3cColor.Cyan), true, 0, 255, 255)] - [InlineData (nameof(W3cColor.DarkGray), true, 169, 169, 169)] - [InlineData (nameof(W3cColor.DarkGrey), true, 169, 169, 169)] - public void TryParseColor_ReturnsExpectedColor(string inputName, bool expectedSuccess, int r, int g, int b) + [InlineData (nameof (W3cColor.Aqua), 0, 255, 255)] + [InlineData (nameof (W3cColor.Cyan), 0, 255, 255)] + [InlineData (nameof (W3cColor.DarkGray), 169, 169, 169)] + [InlineData (nameof (W3cColor.DarkGrey), 169, 169, 169)] + // Case-insensitive + [InlineData ("Red", 255, 0, 0)] + [InlineData ("red", 255, 0, 0)] + [InlineData ("RED", 255, 0, 0)] + public void TryParseColor_ReturnsExpectedColor (string inputName, int r, int g, int b) + { + var expected = (true, new Color(r, g, b)); + + bool actualSuccess = W3cColors.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("16737095", 255, 99, 71)] // W3cColor.Tomato + public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b) { - Color expectedColor = expectedSuccess - ? new (r, g, b) - : default; + var expected = (true, new Color(r, g, b)); bool actualSuccess = W3cColors.TryParseColor (inputName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("brightlight")] + public void TryParseColor_FailsOnInvalidColorName (string? invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = W3cColors.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); + + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("-16737095")] + public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName) + { + var expected = (false, default(Color)); + + bool actualSuccess = W3cColors.TryParseColor (invalidName, out Color actualColor); + var actual = (actualSuccess, actualColor); - Assert.Equal ((expectedSuccess, expectedColor), (actualSuccess, actualColor)); + Assert.Equal (expected, actual); } } From 551532695ac3c8d75079645fa608220d916e4082 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:29:41 +0200 Subject: [PATCH 10/16] Change W3cColorsTests to W3cColorNameResolverTests More consistent when all the tests refer to the color name resolver layer. --- .../Color/AnsiColorNameResolverTests.cs | 7 +----- .../MultiStandardColorNameResolverTests.cs | 13 ++++------- ...sTests.cs => W3cColorNameResolverTests.cs} | 22 ++++++++++--------- 3 files changed, 17 insertions(+), 25 deletions(-) rename Tests/UnitTestsParallelizable/Drawing/Color/{W3cColorsTests.cs => W3cColorNameResolverTests.cs} (84%) diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs index 4016bce4b3..76e8e34bfe 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs @@ -4,12 +4,7 @@ namespace Terminal.Gui.DrawingTests; public class AnsiColorNameResolverTests { - private readonly AnsiColorNameResolver _candidate; - - public AnsiColorNameResolverTests () - { - _candidate = new AnsiColorNameResolver (); - } + private readonly AnsiColorNameResolver _candidate = new(); [Fact] public void GetNames_Returns16ColorNames () diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs index 3c6a615488..9016a53597 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs @@ -4,12 +4,7 @@ namespace Terminal.Gui.DrawingTests; public class MultiStandardColorNameResolverTests { - private readonly MultiStandardColorNameResolver _candidate; - - public MultiStandardColorNameResolverTests () - { - _candidate = new MultiStandardColorNameResolver (); - } + private readonly MultiStandardColorNameResolver _candidate = new(); [Theory] // ANSI color names. @@ -212,9 +207,9 @@ public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byt } [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("brightlight")] + [InlineData (null)] + [InlineData ("")] + [InlineData ("brightlight")] public void TryParseColor_FailsOnInvalidColorName (string? invalidName) { var expected = (false, default(Color)); diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorNameResolverTests.cs similarity index 84% rename from Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/W3cColorNameResolverTests.cs index e3629ac280..527c0be4ea 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorsTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/W3cColorNameResolverTests.cs @@ -2,14 +2,16 @@ namespace Terminal.Gui.DrawingTests; -public class W3cColorsTests +public class W3cColorNameResolverTests { + private readonly W3cColorNameResolver _candidate = new(); + [Fact] public void GetColorNames_NamesAreInAlphabeticalOrder () { - List alphabeticallyOrderedNames = Enum.GetNames ().Order ().ToList (); + string[] alphabeticallyOrderedNames = Enum.GetNames ().Order ().ToArray (); - Assert.Equal (alphabeticallyOrderedNames, W3cColors.GetColorNames ()); + Assert.Equal (alphabeticallyOrderedNames, _candidate.GetColorNames ()); } [Theory] @@ -31,7 +33,7 @@ public void GetColorNames_NamesAreInAlphabeticalOrder () [InlineData (nameof (W3cColor.SlateGrey))] public void GetColorNames_IncludesNamesWithSameValues (string name) { - IReadOnlyList names = W3cColors.GetColorNames (); + string[] names = _candidate.GetColorNames ().ToArray(); Assert.True (names.Contains (name), $"W3C color names is missing '{name}'."); } @@ -51,7 +53,7 @@ public void TryNameColor_ReturnsExpectedColorName (int r, int g, int b, string e var expected = (true, expectedName); Color inputColor = new(r, g, b); - bool actualSuccess = W3cColors.TryNameColor (inputColor, out string? actualName); + bool actualSuccess = _candidate.TryNameColor (inputColor, out string? actualName); var actual = (actualSuccess, actualName); Assert.Equal (expected, actual); @@ -62,7 +64,7 @@ public void TryNameColor_NoMatchFails () { (bool, string?) expected = (false, null); - bool actualSuccess = W3cColors.TryNameColor (new Color (1, 2, 3), out string? actualName); + bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName); var actual = (actualSuccess, actualName); Assert.Equal (expected, actual); @@ -102,7 +104,7 @@ public void TryParseColor_ReturnsExpectedColor (string inputName, int r, int g, { var expected = (true, new Color(r, g, b)); - bool actualSuccess = W3cColors.TryParseColor (inputName, out Color actualColor); + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); var actual = (actualSuccess, actualColor); Assert.Equal (expected, actual); @@ -114,7 +116,7 @@ public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byt { var expected = (true, new Color(r, g, b)); - bool actualSuccess = W3cColors.TryParseColor (inputName, out Color actualColor); + bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor); var actual = (actualSuccess, actualColor); Assert.Equal (expected, actual); @@ -128,7 +130,7 @@ public void TryParseColor_FailsOnInvalidColorName (string? invalidName) { var expected = (false, default(Color)); - bool actualSuccess = W3cColors.TryParseColor (invalidName, out Color actualColor); + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); var actual = (actualSuccess, actualColor); Assert.Equal (expected, actual); @@ -140,7 +142,7 @@ public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName) { var expected = (false, default(Color)); - bool actualSuccess = W3cColors.TryParseColor (invalidName, out Color actualColor); + bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor); var actual = (actualSuccess, actualColor); Assert.Equal (expected, actual); From 22e608a5c01f5e997e1c50971c61011ad300a186 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:40:46 +0200 Subject: [PATCH 11/16] Make W3cColors internal Color name resolver is the public interface. --- Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs index f22e3f6295..d29fcc536a 100644 --- a/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs @@ -9,9 +9,6 @@ namespace Terminal.Gui; /// /// W3C color name resolver. /// -/// -/// Color name resolver interface wrapper for . -/// public class W3cColorNameResolver : IColorNameResolver { /// @@ -30,7 +27,7 @@ public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) => /// /// Helper class for transforming to and from enum. /// -public static class W3cColors +internal static class W3cColors { private static readonly ImmutableArray Names; private static readonly FrozenDictionary RgbNameMap; @@ -72,7 +69,7 @@ public static bool TryParseColor (ReadOnlySpan name, out Color color) { if (!Enum.TryParse (name, ignoreCase: true, out W3cColor w3cColor) || // Any numerical value converts to undefined enum value. - !Enum.IsDefined(w3cColor)) + !Enum.IsDefined (w3cColor)) { color = default; return false; From 0bf34668cf27116d6a74666705094bd24e658005 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:54:46 +0200 Subject: [PATCH 12/16] W3cColors: Use Color.Argb instead of individual RGB components --- .../Drawing/Color/W3cColorNameResolver.cs | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs index d29fcc536a..446e425726 100644 --- a/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/W3cColorNameResolver.cs @@ -30,7 +30,7 @@ public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) => internal static class W3cColors { private static readonly ImmutableArray Names; - private static readonly FrozenDictionary RgbNameMap; + private static readonly FrozenDictionary ArgbNameMap; static W3cColors () { @@ -38,17 +38,17 @@ static W3cColors () // are not otherwise distinguishable from each other. string[] w3cNames = Enum.GetNames ().Order().ToArray(); - Dictionary map = new(w3cNames.Length); + Dictionary map = new(w3cNames.Length); foreach (string name in w3cNames) { W3cColor w3c = Enum.Parse(name); - int rgb = (int)w3c; + uint argb = GetArgb(w3c); // TODO: Collect aliases? - _ = map.TryAdd (rgb, name); + _ = map.TryAdd (argb, name); } Names = ImmutableArray.Create (w3cNames); - RgbNameMap = map.ToFrozenDictionary (); + ArgbNameMap = map.ToFrozenDictionary (); } /// @@ -75,8 +75,8 @@ public static bool TryParseColor (ReadOnlySpan name, out Color color) return false; } - (byte red, byte green, byte blue) = GetRgbComponents (w3cColor); - color = new Color (red, green, blue); + uint argb = GetArgb (w3cColor); + color = new Color (argb); return true; } @@ -88,8 +88,7 @@ public static bool TryParseColor (ReadOnlySpan name, out Color color) /// True if conversion succeeded; otherwise false. public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) { - int rgb = GetRgb (color.R, color.G, color.B); - if (RgbNameMap.TryGetValue (rgb, out name)) + if (ArgbNameMap.TryGetValue (color.Argb, out name)) { return true; } @@ -98,24 +97,14 @@ public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? n return false; } - private const int RgbRedShift = 16; - private const int RgbGreenShift = 8; - private const int RgbBlueShift = 0; - private const int RgbRedMask = 0xFF << RgbRedShift; - private const int RgbGreenMask = 0xFF << RgbGreenShift; - private const int RgbBlueMask = 0xFF << RgbBlueShift; - - private static (byte Red, byte Green, byte Blue) GetRgbComponents (W3cColor w3cColor) + private static uint GetArgb (W3cColor w3cColor) { + const int alphaShift = 24; + const uint alphaMask = 0xFFU << alphaShift; + int rgb = (int)w3cColor; - byte red = (byte)((rgb & RgbRedMask) >> RgbRedShift); - byte green = (byte)((rgb & RgbGreenMask) >> RgbGreenShift); - byte blue = (byte)((rgb & RgbBlueMask) >> RgbBlueShift); - return (red, green, blue); - } - private static int GetRgb (byte red, byte green, byte blue) => - red << RgbRedShift | - green << RgbGreenShift | - blue << RgbBlueShift; + uint argb = (uint)rgb | alphaMask; + return argb; + } } From 20977cdb81dd92337ed0cef3c0e061bde3f8240f Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Thu, 27 Mar 2025 22:10:24 +0200 Subject: [PATCH 13/16] MultiStandardColorNameResolver: Substitute instead of blocking alternative W3C names Changes color picker behavior a bit, e.g. Aqua will match to Cyan instead of jumping to Aquamarine. --- .../Color/MultiStandardColorNameResolver.cs | 84 +++++++++++-------- Tests/UnitTests/Views/ColorPickerTests.cs | 12 ++- .../MultiStandardColorNameResolverTests.cs | 23 +++-- 3 files changed, 69 insertions(+), 50 deletions(-) diff --git a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs index b6db824fb7..01554bbb31 100644 --- a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs @@ -1,9 +1,9 @@ -using System.Collections.Frozen; +#nullable enable + +using System.Collections.Frozen; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -#nullable enable - namespace Terminal.Gui; /// @@ -13,16 +13,16 @@ public class MultiStandardColorNameResolver : IColorNameResolver { private static readonly AnsiColorNameResolver Ansi = new(); private static readonly W3cColorNameResolver W3c = new(); - private static readonly FrozenDictionary W3cBlockedColorNameHashMap; private static readonly FrozenSet W3cBlockedColors; private static readonly ImmutableArray CombinedColorNames; + private static readonly FrozenDictionary W3cSubstituteColors; static MultiStandardColorNameResolver () { HashSet combinedNames = new(Ansi.GetColorNames()); - HashSet w3cInconsistentColorNames = new(StringComparer.OrdinalIgnoreCase); HashSet w3cInconsistentColors = new(); + Dictionary w3cSubstituteColors = new(StringComparer.OrdinalIgnoreCase); IEnumerable enumerableW3cNames = W3c.GetColorNames (); IReadOnlyList w3cNames = enumerableW3cNames is IReadOnlyList alreadyReadOnlyList @@ -41,61 +41,66 @@ static MultiStandardColorNameResolver () .Where(g => g.Count() > 1) .ToDictionary(g => g.Key, g => g.ToHashSet()); - // Gather inconsistencies between ANSI and W3C, filter out problematic W3C names and - // create additional blocklists for W3C names and colors. + // Gather inconsistencies between ANSI and W3C, filter out or substitute problematic W3C colors and names, + // and create additional blocklist for W3C colors. // Blocking and filtering is only applied to W3C because this resolver prioritizes ANSI for backwards compatibility. // It would be a lot simpler to just prioritize W3C colors and names. foreach (string w3cName in w3cNames) { - if (w3cInconsistentColorNames.Contains (w3cName)) + if (w3cSubstituteColors.ContainsKey (w3cName)) { - // Already blocked, most likely through alternative name. + // Already dealt with alternative name. continue; } if (!W3c.TryParseColor (w3cName, out Color w3cColor)) { // This condition is just inverted to reduce indentation. - // This should practically never happen if the W3C color name resolver is properly implemented. + // Also it should practically never happen if the W3C color name resolver is properly implemented. throw new InvalidOperationException ($"W3C color name '{w3cName}' does not resolve to any color."); } if (w3cColorsWithAlternativeNames.TryGetValue (w3cColor, out var names)) { - bool blocked = false; + bool substituted = false; // Alternative names cause issues with ColorPicker etc. when combined with ANSI and prioritizing ANSI resolver. // For example Aqua is not in ColorName16 but the actual color value resolves to ANSI Cyan // so autocomplete for Aqua suddenly changes to Cyan because they happen to have same color value in both color scheme. // Also DarkGrey would cause inconsistencies because the alternative DarkGray exists in ANSI and has different color value. foreach (string name in names) { - if (Ansi.TryParseColor (name, out _)) + if (Ansi.TryParseColor (name, out Color substituteColor)) { - w3cInconsistentColors.Add (w3cColor); - // Block all if one is inconsistent. - foreach (string inconsistentName in names) + // Block the W3C color when it is inconsistent with the substitute color + // so there is no situation where W3C color -> color name -> ANSI color. + if (w3cColor != substituteColor) { - w3cInconsistentColorNames.Add (inconsistentName); + w3cInconsistentColors.Add (w3cColor); } - blocked = true; + + // Substitute all W3C alternatives to match with the ANSI color to keep colors consistent. + foreach (string alternativeName in names) + { + w3cSubstituteColors.Add (alternativeName, substituteColor); + combinedNames.Add (alternativeName); + } + substituted = true; break; } } - if (blocked) + if (substituted) { - // Already blocked continue to next W3C color name. + // Already dealt with, continue to next W3C color name. continue; } } - // Just in case check. // Same name, different ANSI value. // For example both #767676 (ColorName16) and #A9A9A9 (W3C) resolve to DarkGray, - // although a bad example because it is already filtered due to also having alternative names. + // although a bad example because it is already substituted due to also having alternative names. if (Ansi.TryParseColor (w3cName, out Color ansiColor) && w3cColor != ansiColor) { - w3cInconsistentColorNames.Add (w3cName); w3cInconsistentColors.Add (w3cColor); continue; } @@ -104,12 +109,11 @@ static MultiStandardColorNameResolver () } // TODO: Utilize .NET 9 and later alternative lookup for matching ReadOnlySpan with string. - W3cBlockedColorNameHashMap = w3cInconsistentColorNames.ToFrozenDictionary ( + W3cSubstituteColors = w3cSubstituteColors.ToFrozenDictionary ( // Workaround for alternative lookup not being available in .NET 8 by matching ReadOnlySpan hash code to string hash code. - keySelector: x => string.GetHashCode (x, StringComparison.OrdinalIgnoreCase), - // String element is just for verifying hash code collision, e.g. same hash code but the name was different. - // Quite unlikely due to small data set, but still possible. - elementSelector: x => x); + keySelector: kvp => string.GetHashCode (kvp.Key, StringComparison.OrdinalIgnoreCase), + // The string element is for detecting hash collision. + elementSelector: kvp => (kvp.Key, kvp.Value)); W3cBlockedColors = w3cInconsistentColors.ToFrozenSet (); CombinedColorNames = combinedNames.Order ().ToImmutableArray (); } @@ -130,8 +134,7 @@ public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) } if (!IsBlockedW3cColor (color) && - W3c.TryNameColor (color, out string? w3cName) && - !IsBlockedW3cName (w3cName)) + W3c.TryNameColor (color, out string? w3cName)) { name = w3cName; return true; @@ -149,8 +152,12 @@ public bool TryParseColor (ReadOnlySpan name, out Color color) return true; } - if (!IsBlockedW3cName (name) && - W3c.TryParseColor (name, out color) && + if (GetSubstituteW3cColor (name, out color)) + { + return true; + } + + if (W3c.TryParseColor (name, out color) && !IsBlockedW3cColor (color)) { return true; @@ -160,14 +167,21 @@ public bool TryParseColor (ReadOnlySpan name, out Color color) return false; } - private bool IsBlockedW3cName (ReadOnlySpan name) + private static bool GetSubstituteW3cColor (ReadOnlySpan name, out Color substituteColor) { int nameHashCode = string.GetHashCode(name, StringComparison.OrdinalIgnoreCase); - return W3cBlockedColorNameHashMap.TryGetValue (nameHashCode, out string? inconsistentColorName) && - name.Equals (inconsistentColorName, StringComparison.OrdinalIgnoreCase); + if (W3cSubstituteColors.TryGetValue (nameHashCode, out var match) && + match is (string matchName, Color matchColor) && + name.Equals (matchName, StringComparison.OrdinalIgnoreCase)) + { + substituteColor = matchColor; + return true; + } + substituteColor = default; + return false; } - private bool IsBlockedW3cColor (Color color) + private static bool IsBlockedW3cColor (Color color) { return W3cBlockedColors.Contains (color); } diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index 38a09ac23f..5de5cb0bcf 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -749,7 +749,15 @@ public void ColorPicker_TabCompleteColorName () // Auto complete the color name Application.RaiseKeyDownEvent (Key.Tab); - Assert.Equal ("Aquamarine", name.Text); + // Match cyan alternative name + Assert.Equal ("Aqua", name.Text); + + Assert.True (name.HasFocus); + + Application.RaiseKeyDownEvent (Key.Tab); + + // Resolves to cyan color + Assert.Equal ("Cyan", name.Text); // Tab out of the text field Application.RaiseKeyDownEvent (Key.Tab); @@ -757,7 +765,7 @@ public void ColorPicker_TabCompleteColorName () Assert.False (name.HasFocus); Assert.NotSame (name, cp.Focused); - Assert.Equal ("#7FFFD4", hex.Text); + Assert.Equal ("#00FFFF", hex.Text); Application.Top?.Dispose (); Application.ResetState (true); diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs index 9016a53597..0e7677d659 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs @@ -39,7 +39,11 @@ public class MultiStandardColorNameResolverTests [InlineData (nameof (W3cColor.Violet))] [InlineData (nameof (W3cColor.WhiteSmoke))] [InlineData (nameof (W3cColor.YellowGreen))] - // W3C color names that do not clash with ANSI color names. + // W3C alternatives. + [InlineData (nameof (W3cColor.Grey))] + [InlineData (nameof (W3cColor.DarkGrey))] + [InlineData (nameof (W3cColor.Aqua))] + [InlineData (nameof (W3cColor.Fuchsia))] [InlineData (nameof (W3cColor.DarkSlateGray))] [InlineData (nameof (W3cColor.DarkSlateGrey))] [InlineData (nameof (W3cColor.DimGray))] @@ -48,24 +52,12 @@ public class MultiStandardColorNameResolverTests [InlineData (nameof (W3cColor.LightGrey))] [InlineData (nameof (W3cColor.SlateGray))] [InlineData (nameof (W3cColor.SlateGrey))] - public void GetNames_ContainsCombinationOfAnsiAndW3cNames (string name) { string[] names = _candidate.GetColorNames ().ToArray(); Assert.Contains (name, names); } - [Theory] - [InlineData (nameof (W3cColor.Grey))] - [InlineData (nameof (W3cColor.DarkGrey))] - [InlineData (nameof (W3cColor.Aqua))] - [InlineData (nameof (W3cColor.Fuchsia))] - public void GetNames_OmitsBlockedW3cAlternativeNames (string name) - { - string[] names = _candidate.GetColorNames ().ToArray(); - Assert.DoesNotContain (name, names); - } - [Theory] // ANSI color names [InlineData (0, 0, 0, nameof (ColorName16.Black))] @@ -156,6 +148,11 @@ public void TryNameColor_NoMatchFails () [InlineData (nameof (ColorName16.Red), 255, 0, 0)] [InlineData (nameof (ColorName16.White), 255, 255, 255)] [InlineData (nameof (ColorName16.Yellow), 255, 255, 0)] + // W3C color name => substituted ANSI color + [InlineData (nameof (W3cColor.Fuchsia), 255, 0, 255)] // ANSI Magenta + [InlineData (nameof (W3cColor.DarkGrey), 118, 118, 118)] // ANSI Dark Gray + [InlineData (nameof (W3cColor.Grey), 128, 128, 128)] // ANSI Gray + [InlineData (nameof (W3cColor.Aqua), 0, 255, 255)] // ANSI Cyan // W3C colors [InlineData (nameof (W3cColor.AliceBlue), 240, 248, 255)] [InlineData (nameof (W3cColor.BlanchedAlmond), 255, 235, 205)] From a929229f678a60362748fc76b26c1af5d32ce221 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Fri, 28 Mar 2025 20:15:25 +0200 Subject: [PATCH 14/16] Remove leftover color string resources --- Terminal.Gui/Resources/Strings.Designer.cs | 1314 -------------------- Terminal.Gui/Resources/Strings.resx | 438 ------- 2 files changed, 1752 deletions(-) diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs index e329b718e7..e88ae567cd 100644 --- a/Terminal.Gui/Resources/Strings.Designer.cs +++ b/Terminal.Gui/Resources/Strings.Designer.cs @@ -60,1320 +60,6 @@ internal Strings() { } } - /// - /// Looks up a localized string similar to Black. - /// - internal static string _000000 { - get { - return ResourceManager.GetString("#000000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Navy. - /// - internal static string _000080 { - get { - return ResourceManager.GetString("#000080", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkBlue. - /// - internal static string _00008B { - get { - return ResourceManager.GetString("#00008B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumBlue. - /// - internal static string _0000CD { - get { - return ResourceManager.GetString("#0000CD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Blue. - /// - internal static string _0000FF { - get { - return ResourceManager.GetString("#0000FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkGreen. - /// - internal static string _006400 { - get { - return ResourceManager.GetString("#006400", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Green. - /// - internal static string _008000 { - get { - return ResourceManager.GetString("#008000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Teal. - /// - internal static string _008080 { - get { - return ResourceManager.GetString("#008080", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkCyan. - /// - internal static string _008B8B { - get { - return ResourceManager.GetString("#008B8B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DeepSkyBlue. - /// - internal static string _00BFFF { - get { - return ResourceManager.GetString("#00BFFF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkTurquoise. - /// - internal static string _00CED1 { - get { - return ResourceManager.GetString("#00CED1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumSpringGreen. - /// - internal static string _00FA9A { - get { - return ResourceManager.GetString("#00FA9A", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Lime. - /// - internal static string _00FF00 { - get { - return ResourceManager.GetString("#00FF00", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SpringGreen. - /// - internal static string _00FF7F { - get { - return ResourceManager.GetString("#00FF7F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cyan. - /// - internal static string _00FFFF { - get { - return ResourceManager.GetString("#00FFFF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightGreen. - /// - internal static string _16C60C { - get { - return ResourceManager.GetString("#16C60C", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MidnightBlue. - /// - internal static string _191970 { - get { - return ResourceManager.GetString("#191970", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DodgerBlue. - /// - internal static string _1E90FF { - get { - return ResourceManager.GetString("#1E90FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightSeaGreen. - /// - internal static string _20B2AA { - get { - return ResourceManager.GetString("#20B2AA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ForestGreen. - /// - internal static string _228B22 { - get { - return ResourceManager.GetString("#228B22", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SeaGreen. - /// - internal static string _2E8B57 { - get { - return ResourceManager.GetString("#2E8B57", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkSlateGrey. - /// - internal static string _2F4F4F { - get { - return ResourceManager.GetString("#2F4F4F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LimeGreen. - /// - internal static string _32CD32 { - get { - return ResourceManager.GetString("#32CD32", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightBlue. - /// - internal static string _3B78FF { - get { - return ResourceManager.GetString("#3B78FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumSeaGreen. - /// - internal static string _3CB371 { - get { - return ResourceManager.GetString("#3CB371", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Turquoise. - /// - internal static string _40E0D0 { - get { - return ResourceManager.GetString("#40E0D0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to RoyalBlue. - /// - internal static string _4169E1 { - get { - return ResourceManager.GetString("#4169E1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SteelBlue. - /// - internal static string _4682B4 { - get { - return ResourceManager.GetString("#4682B4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkSlateBlue. - /// - internal static string _483D8B { - get { - return ResourceManager.GetString("#483D8B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumTurquoise. - /// - internal static string _48D1CC { - get { - return ResourceManager.GetString("#48D1CC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indigo. - /// - internal static string _4B0082 { - get { - return ResourceManager.GetString("#4B0082", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkOliveGreen. - /// - internal static string _556B2F { - get { - return ResourceManager.GetString("#556B2F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CadetBlue. - /// - internal static string _5F9EA0 { - get { - return ResourceManager.GetString("#5F9EA0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightCyan. - /// - internal static string _61D6D6 { - get { - return ResourceManager.GetString("#61D6D6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CornflowerBlue. - /// - internal static string _6495ED { - get { - return ResourceManager.GetString("#6495ED", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to RebeccaPurple. - /// - internal static string _663399 { - get { - return ResourceManager.GetString("#663399", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumAquaMarine. - /// - internal static string _66CDAA { - get { - return ResourceManager.GetString("#66CDAA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DimGray. - /// - internal static string _696969 { - get { - return ResourceManager.GetString("#696969", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SlateBlue. - /// - internal static string _6A5ACD { - get { - return ResourceManager.GetString("#6A5ACD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OliveDrab. - /// - internal static string _6B8E23 { - get { - return ResourceManager.GetString("#6B8E23", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SlateGray. - /// - internal static string _708090 { - get { - return ResourceManager.GetString("#708090", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkGray. - /// - internal static string _767676 { - get { - return ResourceManager.GetString("#767676", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightSlateGrey. - /// - internal static string _778899 { - get { - return ResourceManager.GetString("#778899", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumSlateBlue. - /// - internal static string _7B68EE { - get { - return ResourceManager.GetString("#7B68EE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LawnGreen. - /// - internal static string _7CFC00 { - get { - return ResourceManager.GetString("#7CFC00", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Chartreuse. - /// - internal static string _7FFF00 { - get { - return ResourceManager.GetString("#7FFF00", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Aquamarine. - /// - internal static string _7FFFD4 { - get { - return ResourceManager.GetString("#7FFFD4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Maroon. - /// - internal static string _800000 { - get { - return ResourceManager.GetString("#800000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purple. - /// - internal static string _800080 { - get { - return ResourceManager.GetString("#800080", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Olive. - /// - internal static string _808000 { - get { - return ResourceManager.GetString("#808000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Gray. - /// - internal static string _808080 { - get { - return ResourceManager.GetString("#808080", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SkyBlue. - /// - internal static string _87CEEB { - get { - return ResourceManager.GetString("#87CEEB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightSkyBlue. - /// - internal static string _87CEFA { - get { - return ResourceManager.GetString("#87CEFA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BlueViolet. - /// - internal static string _8A2BE2 { - get { - return ResourceManager.GetString("#8A2BE2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkRed. - /// - internal static string _8B0000 { - get { - return ResourceManager.GetString("#8B0000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkMagenta. - /// - internal static string _8B008B { - get { - return ResourceManager.GetString("#8B008B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SaddleBrown. - /// - internal static string _8B4513 { - get { - return ResourceManager.GetString("#8B4513", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkSeaGreen. - /// - internal static string _8FBC8F { - get { - return ResourceManager.GetString("#8FBC8F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightGreen. - /// - internal static string _90EE90 { - get { - return ResourceManager.GetString("#90EE90", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumPurple. - /// - internal static string _9370DB { - get { - return ResourceManager.GetString("#9370DB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkViolet. - /// - internal static string _9400D3 { - get { - return ResourceManager.GetString("#9400D3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PaleGreen. - /// - internal static string _98FB98 { - get { - return ResourceManager.GetString("#98FB98", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkOrchid. - /// - internal static string _9932CC { - get { - return ResourceManager.GetString("#9932CC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to YellowGreen. - /// - internal static string _9ACD32 { - get { - return ResourceManager.GetString("#9ACD32", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Sienna. - /// - internal static string _A0522D { - get { - return ResourceManager.GetString("#A0522D", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Brown. - /// - internal static string _A52A2A { - get { - return ResourceManager.GetString("#A52A2A", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkGrey. - /// - internal static string _A9A9A9 { - get { - return ResourceManager.GetString("#A9A9A9", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightBlue. - /// - internal static string _ADD8E6 { - get { - return ResourceManager.GetString("#ADD8E6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GreenYellow. - /// - internal static string _ADFF2F { - get { - return ResourceManager.GetString("#ADFF2F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PaleTurquoise. - /// - internal static string _AFEEEE { - get { - return ResourceManager.GetString("#AFEEEE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightSteelBlue. - /// - internal static string _B0C4DE { - get { - return ResourceManager.GetString("#B0C4DE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PowderBlue. - /// - internal static string _B0E0E6 { - get { - return ResourceManager.GetString("#B0E0E6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to FireBrick. - /// - internal static string _B22222 { - get { - return ResourceManager.GetString("#B22222", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightMagenta. - /// - internal static string _B4009E { - get { - return ResourceManager.GetString("#B4009E", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkGoldenRod. - /// - internal static string _B8860B { - get { - return ResourceManager.GetString("#B8860B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumOrchid. - /// - internal static string _BA55D3 { - get { - return ResourceManager.GetString("#BA55D3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to RosyBrown. - /// - internal static string _BC8F8F { - get { - return ResourceManager.GetString("#BC8F8F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkKhaki. - /// - internal static string _BDB76B { - get { - return ResourceManager.GetString("#BDB76B", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Silver. - /// - internal static string _C0C0C0 { - get { - return ResourceManager.GetString("#C0C0C0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MediumVioletRed. - /// - internal static string _C71585 { - get { - return ResourceManager.GetString("#C71585", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IndianRed. - /// - internal static string _CD5C5C { - get { - return ResourceManager.GetString("#CD5C5C", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Peru. - /// - internal static string _CD853F { - get { - return ResourceManager.GetString("#CD853F", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Chocolate. - /// - internal static string _D2691E { - get { - return ResourceManager.GetString("#D2691E", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tan. - /// - internal static string _D2B48C { - get { - return ResourceManager.GetString("#D2B48C", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightGray. - /// - internal static string _D3D3D3 { - get { - return ResourceManager.GetString("#D3D3D3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Thistle. - /// - internal static string _D8BFD8 { - get { - return ResourceManager.GetString("#D8BFD8", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Orchid. - /// - internal static string _DA70D6 { - get { - return ResourceManager.GetString("#DA70D6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GoldenRod. - /// - internal static string _DAA520 { - get { - return ResourceManager.GetString("#DAA520", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PaleVioletRed. - /// - internal static string _DB7093 { - get { - return ResourceManager.GetString("#DB7093", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Crimson. - /// - internal static string _DC143C { - get { - return ResourceManager.GetString("#DC143C", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Gainsboro. - /// - internal static string _DCDCDC { - get { - return ResourceManager.GetString("#DCDCDC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Plum. - /// - internal static string _DDA0DD { - get { - return ResourceManager.GetString("#DDA0DD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BurlyWood. - /// - internal static string _DEB887 { - get { - return ResourceManager.GetString("#DEB887", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightCyan. - /// - internal static string _E0FFFF { - get { - return ResourceManager.GetString("#E0FFFF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Lavender. - /// - internal static string _E6E6FA { - get { - return ResourceManager.GetString("#E6E6FA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightRed. - /// - internal static string _E74856 { - get { - return ResourceManager.GetString("#E74856", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkSalmon. - /// - internal static string _E9967A { - get { - return ResourceManager.GetString("#E9967A", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Violet. - /// - internal static string _EE82EE { - get { - return ResourceManager.GetString("#EE82EE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PaleGoldenRod. - /// - internal static string _EEE8AA { - get { - return ResourceManager.GetString("#EEE8AA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightCoral. - /// - internal static string _F08080 { - get { - return ResourceManager.GetString("#F08080", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Khaki. - /// - internal static string _F0E68C { - get { - return ResourceManager.GetString("#F0E68C", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AliceBlue. - /// - internal static string _F0F8FF { - get { - return ResourceManager.GetString("#F0F8FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to HoneyDew. - /// - internal static string _F0FFF0 { - get { - return ResourceManager.GetString("#F0FFF0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Azure. - /// - internal static string _F0FFFF { - get { - return ResourceManager.GetString("#F0FFFF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SandyBrown. - /// - internal static string _F4A460 { - get { - return ResourceManager.GetString("#F4A460", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wheat. - /// - internal static string _F5DEB3 { - get { - return ResourceManager.GetString("#F5DEB3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Beige. - /// - internal static string _F5F5DC { - get { - return ResourceManager.GetString("#F5F5DC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WhiteSmoke. - /// - internal static string _F5F5F5 { - get { - return ResourceManager.GetString("#F5F5F5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MintCream. - /// - internal static string _F5FFFA { - get { - return ResourceManager.GetString("#F5FFFA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GhostWhite. - /// - internal static string _F8F8FF { - get { - return ResourceManager.GetString("#F8F8FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BrightYellow. - /// - internal static string _F9F1A5 { - get { - return ResourceManager.GetString("#F9F1A5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Salmon. - /// - internal static string _FA8072 { - get { - return ResourceManager.GetString("#FA8072", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AntiqueWhite. - /// - internal static string _FAEBD7 { - get { - return ResourceManager.GetString("#FAEBD7", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Linen. - /// - internal static string _FAF0E6 { - get { - return ResourceManager.GetString("#FAF0E6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightGoldenRodYellow. - /// - internal static string _FAFAD2 { - get { - return ResourceManager.GetString("#FAFAD2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OldLace. - /// - internal static string _FDF5E6 { - get { - return ResourceManager.GetString("#FDF5E6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Red. - /// - internal static string _FF0000 { - get { - return ResourceManager.GetString("#FF0000", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Magenta. - /// - internal static string _FF00FF { - get { - return ResourceManager.GetString("#FF00FF", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DeepPink. - /// - internal static string _FF1493 { - get { - return ResourceManager.GetString("#FF1493", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OrangeRed. - /// - internal static string _FF4500 { - get { - return ResourceManager.GetString("#FF4500", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tomato. - /// - internal static string _FF6347 { - get { - return ResourceManager.GetString("#FF6347", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to HotPink. - /// - internal static string _FF69B4 { - get { - return ResourceManager.GetString("#FF69B4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Coral. - /// - internal static string _FF7F50 { - get { - return ResourceManager.GetString("#FF7F50", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DarkOrange. - /// - internal static string _FF8C00 { - get { - return ResourceManager.GetString("#FF8C00", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightSalmon. - /// - internal static string _FFA07A { - get { - return ResourceManager.GetString("#FFA07A", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Orange. - /// - internal static string _FFA500 { - get { - return ResourceManager.GetString("#FFA500", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightPink. - /// - internal static string _FFB6C1 { - get { - return ResourceManager.GetString("#FFB6C1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pink. - /// - internal static string _FFC0CB { - get { - return ResourceManager.GetString("#FFC0CB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Gold. - /// - internal static string _FFD700 { - get { - return ResourceManager.GetString("#FFD700", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PeachPuff. - /// - internal static string _FFDAB9 { - get { - return ResourceManager.GetString("#FFDAB9", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to NavajoWhite. - /// - internal static string _FFDEAD { - get { - return ResourceManager.GetString("#FFDEAD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Moccasin. - /// - internal static string _FFE4B5 { - get { - return ResourceManager.GetString("#FFE4B5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Bisque. - /// - internal static string _FFE4C4 { - get { - return ResourceManager.GetString("#FFE4C4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MistyRose. - /// - internal static string _FFE4E1 { - get { - return ResourceManager.GetString("#FFE4E1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to BlanchedAlmond. - /// - internal static string _FFEBCD { - get { - return ResourceManager.GetString("#FFEBCD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PapayaWhip. - /// - internal static string _FFEFD5 { - get { - return ResourceManager.GetString("#FFEFD5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LavenderBlush. - /// - internal static string _FFF0F5 { - get { - return ResourceManager.GetString("#FFF0F5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SeaShell. - /// - internal static string _FFF5EE { - get { - return ResourceManager.GetString("#FFF5EE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cornsilk. - /// - internal static string _FFF8DC { - get { - return ResourceManager.GetString("#FFF8DC", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LemonChiffon. - /// - internal static string _FFFACD { - get { - return ResourceManager.GetString("#FFFACD", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to FloralWhite. - /// - internal static string _FFFAF0 { - get { - return ResourceManager.GetString("#FFFAF0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Snow. - /// - internal static string _FFFAFA { - get { - return ResourceManager.GetString("#FFFAFA", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Yellow. - /// - internal static string _FFFF00 { - get { - return ResourceManager.GetString("#FFFF00", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LightYellow. - /// - internal static string _FFFFE0 { - get { - return ResourceManager.GetString("#FFFFE0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ivory. - /// - internal static string _FFFFF0 { - get { - return ResourceManager.GetString("#FFFFF0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to White. - /// - internal static string _FFFFFF { - get { - return ResourceManager.GetString("#FFFFFF", resourceCulture); - } - } - /// /// Looks up a localized string similar to _Cancel. /// diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx index 773ee55937..e241b11f8e 100644 --- a/Terminal.Gui/Resources/Strings.resx +++ b/Terminal.Gui/Resources/Strings.resx @@ -280,444 +280,6 @@ Date Picker - - AliceBlue - - - AntiqueWhite - - - Aquamarine - - - Azure - - - Beige - - - Bisque - - - Black - - - BlanchedAlmond - - - Blue - - - BlueViolet - - - Brown - - - BurlyWood - - - CadetBlue - - - Chartreuse - - - Chocolate - - - Coral - - - CornflowerBlue - - - Cornsilk - - - Crimson - - - Cyan - - - DarkBlue - - - DarkCyan - - - DarkGoldenRod - - - DarkGrey - - - DarkGreen - - - DarkKhaki - - - DarkMagenta - - - DarkOliveGreen - - - DarkOrange - - - DarkOrchid - - - DarkRed - - - DarkSalmon - - - DarkSeaGreen - - - DarkSlateBlue - - - DarkSlateGrey - - - DarkTurquoise - - - DarkViolet - - - DeepPink - - - DeepSkyBlue - - - DimGray - - - DodgerBlue - - - FireBrick - - - FloralWhite - - - ForestGreen - - - Gainsboro - - - GhostWhite - - - Gold - - - GoldenRod - - - Gray - - - Green - - - GreenYellow - - - HoneyDew - - - HotPink - - - IndianRed - - - Indigo - - - Ivory - - - Khaki - - - Lavender - - - LavenderBlush - - - LawnGreen - - - LemonChiffon - - - LightBlue - - - LightCoral - - - LightCyan - - - LightGoldenRodYellow - - - LightGray - - - LightGreen - - - LightPink - - - LightSalmon - - - LightSeaGreen - - - LightSkyBlue - - - LightSlateGrey - - - LightSteelBlue - - - LightYellow - - - Lime - - - LimeGreen - - - Linen - - - Magenta - - - Maroon - - - MediumAquaMarine - - - MediumBlue - - - MediumOrchid - - - MediumPurple - - - MediumSeaGreen - - - MediumSlateBlue - - - MediumSpringGreen - - - MediumTurquoise - - - MediumVioletRed - - - MidnightBlue - - - MintCream - - - MistyRose - - - Moccasin - - - NavajoWhite - - - Navy - - - OldLace - - - Olive - - - OliveDrab - - - Orange - - - OrangeRed - - - Orchid - - - PaleGoldenRod - - - PaleGreen - - - PaleTurquoise - - - PaleVioletRed - - - PapayaWhip - - - PeachPuff - - - Peru - - - Pink - - - Plum - - - PowderBlue - - - Purple - - - RebeccaPurple - - - Red - - - RosyBrown - - - RoyalBlue - - - SaddleBrown - - - Salmon - - - SandyBrown - - - SeaGreen - - - SeaShell - - - Sienna - - - Silver - - - SkyBlue - - - SlateBlue - - - SlateGray - - - Snow - - - SpringGreen - - - SteelBlue - - - Tan - - - Teal - - - Thistle - - - Tomato - - - Turquoise - - - Violet - - - Wheat - - - White - - - WhiteSmoke - - - Yellow - - - YellowGreen - - - BrightBlue - - - BrightCyan - - - BrightRed - - - BrightGreen - - - BrightMagenta - - - BrightYellow - - - DarkGray - Co_lors From 69ffcf55ed784957c8af6f6d03960eddc5577381 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Fri, 28 Mar 2025 20:43:38 +0200 Subject: [PATCH 15/16] Consistent position for IColorNameResolver #nullable enable directive --- Terminal.Gui/Drawing/Color/IColorNameResolver.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs index ac5b2d18e3..cbbe70985e 100644 --- a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs @@ -1,8 +1,8 @@ -using System.Diagnostics.CodeAnalysis; +#nullable enable -namespace Terminal.Gui; +using System.Diagnostics.CodeAnalysis; -#nullable enable +namespace Terminal.Gui; /// /// When implemented by a class, allows mapping to From 1a4b32f2eca5e22613c1bad4ecf83de1e1e425b6 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sat, 29 Mar 2025 08:32:05 +0200 Subject: [PATCH 16/16] Add missing XML comments to ColorScheme.Colors.cs --- Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs b/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs index 5c3bc6dba9..96316ac431 100644 --- a/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs +++ b/Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs @@ -185,6 +185,11 @@ public bool ContainsKey (string key) } } + /// + /// Copies the elements of the to an array, starting at a particular array index. + /// + /// The one-dimensional array that is the destination of the elements copied from . + /// The zero-based index in array at which copying begins. public void CopyTo (KeyValuePair [] array, int arrayIndex) { lock (_lock) @@ -193,6 +198,10 @@ public void CopyTo (KeyValuePair [] array, int arrayIndex) } } + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator for the . public IEnumerator> GetEnumerator () { lock (_lock) @@ -206,6 +215,7 @@ IEnumerator IEnumerable.GetEnumerator () return GetEnumerator (); } + /// public bool Remove (KeyValuePair item) { lock (_lock) @@ -219,6 +229,7 @@ public bool Remove (KeyValuePair item) } } + /// public bool Remove (string key) { lock (_lock) @@ -245,7 +256,7 @@ public bool TryGetValue (string key, out ColorScheme? value) /// /// Resets the dictionary to its default values. /// - /// + /// The reset dictionary. public static Dictionary Reset () { lock (_lock)