diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 5ea981b7b..5019058bf 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -54,7 +54,7 @@ namespace Terminal.Gui { public static bool UseSystemConsole { get; set; } = false; /// - /// Gets or sets whether will be forced to output only the 16 colors defined in . + /// Gets or sets whether will be forced to output only the 16 colors defined in . /// The default is , meaning 24-bit (TrueColor) colors will be output as long as the selected /// supports TrueColor. /// diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index 29624582c..48ddea113 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -31,7 +31,7 @@ namespace Terminal.Gui { var colorString = reader.GetString (); // Check if the color string is a color name - if (Enum.TryParse (colorString, ignoreCase: true, out ColorNames color)) { + if (Enum.TryParse (colorString, ignoreCase: true, out ColorName color)) { // Return the parsed color return new Color(color); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index f8c2ea792..b5fd61084 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -88,7 +88,7 @@ internal class CursesDriver : ConsoleDriver { /// and the background color is stored in the least significant 4 bits. /// The Terminal.GUi Color values are converted to curses color encoding before being encoded. /// - private Attribute MakeColor (ColorNames foregroundName, ColorNames backgroundName) + private Attribute MakeColor (ColorName foregroundName, ColorName backgroundName) { if (!RunningUnitTests) { return MakeColor (ColorNameToCursesColorNumber (foregroundName), ColorNameToCursesColorNumber (backgroundName)); @@ -119,80 +119,80 @@ internal class CursesDriver : ConsoleDriver { } } - static short ColorNameToCursesColorNumber (ColorNames color) + static short ColorNameToCursesColorNumber (ColorName color) { switch (color) { - case ColorNames.Black: + case ColorName.Black: return Curses.COLOR_BLACK; - case ColorNames.Blue: + case ColorName.Blue: return Curses.COLOR_BLUE; - case ColorNames.Green: + case ColorName.Green: return Curses.COLOR_GREEN; - case ColorNames.Cyan: + case ColorName.Cyan: return Curses.COLOR_CYAN; - case ColorNames.Red: + case ColorName.Red: return Curses.COLOR_RED; - case ColorNames.Magenta: + case ColorName.Magenta: return Curses.COLOR_MAGENTA; - case ColorNames.Yellow: + case ColorName.Yellow: return Curses.COLOR_YELLOW; - case ColorNames.Gray: + case ColorName.Gray: return Curses.COLOR_WHITE; - case ColorNames.DarkGray: + case ColorName.DarkGray: return Curses.COLOR_GRAY; - case ColorNames.BrightBlue: + case ColorName.BrightBlue: return Curses.COLOR_BLUE | Curses.COLOR_GRAY; - case ColorNames.BrightGreen: + case ColorName.BrightGreen: return Curses.COLOR_GREEN | Curses.COLOR_GRAY; - case ColorNames.BrightCyan: + case ColorName.BrightCyan: return Curses.COLOR_CYAN | Curses.COLOR_GRAY; - case ColorNames.BrightRed: + case ColorName.BrightRed: return Curses.COLOR_RED | Curses.COLOR_GRAY; - case ColorNames.BrightMagenta: + case ColorName.BrightMagenta: return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY; - case ColorNames.BrightYellow: + case ColorName.BrightYellow: return Curses.COLOR_YELLOW | Curses.COLOR_GRAY; - case ColorNames.White: + case ColorName.White: return Curses.COLOR_WHITE | Curses.COLOR_GRAY; } throw new ArgumentException ("Invalid color code"); } - static ColorNames CursesColorNumberToColorName (short color) + static ColorName CursesColorNumberToColorName (short color) { switch (color) { case Curses.COLOR_BLACK: - return ColorNames.Black; + return ColorName.Black; case Curses.COLOR_BLUE: - return ColorNames.Blue; + return ColorName.Blue; case Curses.COLOR_GREEN: - return ColorNames.Green; + return ColorName.Green; case Curses.COLOR_CYAN: - return ColorNames.Cyan; + return ColorName.Cyan; case Curses.COLOR_RED: - return ColorNames.Red; + return ColorName.Red; case Curses.COLOR_MAGENTA: - return ColorNames.Magenta; + return ColorName.Magenta; case Curses.COLOR_YELLOW: - return ColorNames.Yellow; + return ColorName.Yellow; case Curses.COLOR_WHITE: - return ColorNames.Gray; + return ColorName.Gray; case Curses.COLOR_GRAY: - return ColorNames.DarkGray; + return ColorName.DarkGray; case Curses.COLOR_BLUE | Curses.COLOR_GRAY: - return ColorNames.BrightBlue; + return ColorName.BrightBlue; case Curses.COLOR_GREEN | Curses.COLOR_GRAY: - return ColorNames.BrightGreen; + return ColorName.BrightGreen; case Curses.COLOR_CYAN | Curses.COLOR_GRAY: - return ColorNames.BrightCyan; + return ColorName.BrightCyan; case Curses.COLOR_RED | Curses.COLOR_GRAY: - return ColorNames.BrightRed; + return ColorName.BrightRed; case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY: - return ColorNames.BrightMagenta; + return ColorName.BrightMagenta; case Curses.COLOR_YELLOW | Curses.COLOR_GRAY: - return ColorNames.BrightYellow; + return ColorName.BrightYellow; case Curses.COLOR_WHITE | Curses.COLOR_GRAY: - return ColorNames.White; + return ColorName.White; } throw new ArgumentException ("Invalid curses color code"); } @@ -675,7 +675,7 @@ internal class CursesDriver : ConsoleDriver { Curses.UseDefaultColors (); } - CurrentAttribute = MakeColor (ColorNames.White, ColorNames.Black); + CurrentAttribute = MakeColor (ColorName.White, ColorName.Black); TerminalResized = terminalResized; diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 65a830dca..b824663c4 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -2,19 +2,12 @@ // FakeDriver.cs: A fake ConsoleDriver for unit tests. // using System; -using System.Buffers; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading; using System.Text; // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -using Unix.Terminal; -using static Terminal.Gui.WindowsConsole; -using System.Drawing; namespace Terminal.Gui; /// @@ -80,9 +73,7 @@ public class FakeDriver : ConsoleDriver { Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; FakeConsole.Clear (); ResizeScreen (); - // Call InitializeColorSchemes before UpdateOffScreen as it references Colors CurrentAttribute = new Attribute (Color.White, Color.Black); - //InitializeColorSchemes (); ClearContents (); } diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs index 8d8a3a852..facac2a71 100644 --- a/Terminal.Gui/Drawing/Color.cs +++ b/Terminal.Gui/Drawing/Color.cs @@ -8,1054 +8,1054 @@ using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using System.Text.RegularExpressions; -namespace Terminal.Gui { +namespace Terminal.Gui; +/// +/// Defines the 16 legacy color names and values that can be used to set the +/// foreground and background colors in Terminal.Gui apps. Used with . +/// +/// +/// +/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors. +/// +/// +/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured using the +/// property. +/// +/// +public enum ColorName { /// - /// Defines the 16 legacy color names and values that can be used to set the - /// foreground and background colors in Terminal.Gui apps. Used with . + /// The black color. ANSI escape sequence: \u001b[30m. /// - /// - /// - /// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors. - /// - /// - /// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured using the - /// property. - /// - /// - public enum ColorNames { - /// - /// The black color. ANSI escape sequence: \u001b[30m. - /// - Black, - /// - /// The blue color. ANSI escape sequence: \u001b[34m. - /// - Blue, - /// - /// The green color. ANSI escape sequence: \u001b[32m. - /// - Green, - /// - /// The cyan color. ANSI escape sequence: \u001b[36m. - /// - Cyan, - /// - /// The red color. ANSI escape sequence: \u001b[31m. - /// - Red, - /// - /// The magenta color. ANSI escape sequence: \u001b[35m. - /// - Magenta, - /// - /// The yellow color (also known as Brown). ANSI escape sequence: \u001b[33m. - /// - Yellow, - /// - /// The gray color (also known as White). ANSI escape sequence: \u001b[37m. - /// - Gray, - /// - /// The dark gray color (also known as Bright Black). ANSI escape sequence: \u001b[30;1m. - /// - DarkGray, - /// - /// The bright blue color. ANSI escape sequence: \u001b[34;1m. - /// - BrightBlue, - /// - /// The bright green color. ANSI escape sequence: \u001b[32;1m. - /// - BrightGreen, - /// - /// The bright cyan color. ANSI escape sequence: \u001b[36;1m. - /// - BrightCyan, - /// - /// The bright red color. ANSI escape sequence: \u001b[31;1m. - /// - BrightRed, - /// - /// The bright magenta color. ANSI escape sequence: \u001b[35;1m. - /// - BrightMagenta, - /// - /// The bright yellow color. ANSI escape sequence: \u001b[33;1m. - /// - BrightYellow, - /// - /// The White color (also known as Bright White). ANSI escape sequence: \u001b[37;1m. - /// - White + Black, + /// + /// The blue color. ANSI escape sequence: \u001b[34m. + /// + Blue, + /// + /// The green color. ANSI escape sequence: \u001b[32m. + /// + Green, + /// + /// The cyan color. ANSI escape sequence: \u001b[36m. + /// + Cyan, + /// + /// The red color. ANSI escape sequence: \u001b[31m. + /// + Red, + /// + /// The magenta color. ANSI escape sequence: \u001b[35m. + /// + Magenta, + /// + /// The yellow color (also known as Brown). ANSI escape sequence: \u001b[33m. + /// + Yellow, + /// + /// The gray color (also known as White). ANSI escape sequence: \u001b[37m. + /// + Gray, + /// + /// The dark gray color (also known as Bright Black). ANSI escape sequence: \u001b[30;1m. + /// + DarkGray, + /// + /// The bright blue color. ANSI escape sequence: \u001b[34;1m. + /// + BrightBlue, + /// + /// The bright green color. ANSI escape sequence: \u001b[32;1m. + /// + BrightGreen, + /// + /// The bright cyan color. ANSI escape sequence: \u001b[36;1m. + /// + BrightCyan, + /// + /// The bright red color. ANSI escape sequence: \u001b[31;1m. + /// + BrightRed, + /// + /// The bright magenta color. ANSI escape sequence: \u001b[35;1m. + /// + BrightMagenta, + /// + /// The bright yellow color. ANSI escape sequence: \u001b[33;1m. + /// + BrightYellow, + /// + /// The White color (also known as Bright White). ANSI escape sequence: \u001b[37;1m. + /// + White +} + +/// +/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see ). +/// Used with . +/// +[JsonConverter (typeof (ColorJsonConverter))] +public class Color : IEquatable { + + /// + /// Initializes a new instance of the class. + /// + /// The red 8-bits. + /// The green 8-bits. + /// The blue 8-bits. + /// Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui. + public Color (int red, int green, int blue, int alpha = 0xFF) + { + R = red; + G = green; + B = blue; + A = alpha; } /// - /// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see ). - /// Used with . + /// Initializes a new instance of the class with an encoded 24-bit color value. /// - [JsonConverter (typeof (ColorJsonConverter))] - public class Color : IEquatable { + /// The encoded 24-bit color value (see ). + public Color (int rgba) + { + Rgba = rgba; + } - /// - /// Initializes a new instance of the class. - /// - /// The red 8-bits. - /// The green 8-bits. - /// The blue 8-bits. - /// Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui. - public Color (int red, int green, int blue, int alpha = 0xFF) - { - R = red; - G = green; - B = blue; - A = alpha; + /// + /// Initializes a new instance of the color from a legacy 16-color value. + /// + /// The 16-color value. + public Color (ColorName colorName) + { + var c = Color.FromColorName (colorName); + R = c.R; + G = c.G; + B = c.B; + A = c.A; + } + + /// + /// Initializes a new instance of the color from string. See for details. + /// + /// + /// + public Color (string colorString) + { + if (!TryParse (colorString, out var c)) { + throw new ArgumentOutOfRangeException (nameof (colorString)); } + R = c.R; + G = c.G; + B = c.B; + A = c.A; + } - /// - /// Initializes a new instance of the class with an encoded 24-bit color value. - /// - /// The encoded 24-bit color value (see ). - public Color (int rgba) - { - Rgba = rgba; + /// + /// Initializes a new instance of the . + /// + public Color () + { + R = 0; + G = 0; + B = 0; + A = 0xFF; + } + + /// + /// Red color component. + /// + public int R { get; set; } + /// + /// Green color component. + /// + public int G { get; set; } + /// + /// Blue color component. + /// + public int B { get; set; } + + /// + /// Alpha color component. + /// + /// + /// The Alpha channel is not supported by Terminal.Gui. + /// + public int A { get; set; } = 0xFF; // Not currently supported; here for completeness. + + /// + /// Gets or sets the color value encoded as ARGB32. + /// + /// (<see cref="A"/> << 24) | (<see cref="R"/> << 16) | (<see cref="G"/> << 8) | <see cref="B"/> + /// + /// + public int Rgba { + get => (A << 24) | (R << 16) | (G << 8) | B; + set { + A = (byte)((value >> 24) & 0xFF); + R = (byte)((value >> 16) & 0xFF); + G = (byte)((value >> 8) & 0xFF); + B = (byte)(value & 0xFF); } + } - /// - /// Initializes a new instance of the color from a legacy 16-color value. - /// - /// The 16-color value. - public Color (ColorNames colorName) - { - var c = Color.FromColorName (colorName); - R = c.R; - G = c.G; - B = c.B; - A = c.A; - } - - /// - /// Initializes a new instance of the color from string. See for details. - /// - /// - /// - public Color (string colorString) - { - if (!TryParse (colorString, out var c)) { - throw new ArgumentOutOfRangeException (nameof (colorString)); - } - R = c.R; - G = c.G; - B = c.B; - A = c.A; - } - - /// - /// Initializes a new instance of the . - /// - public Color () - { - R = 0; - G = 0; - B = 0; - A = 0xFF; - } - - /// - /// Red color component. - /// - public int R { get; set; } - /// - /// Green color component. - /// - public int G { get; set; } - /// - /// Blue color component. - /// - public int B { get; set; } - - /// - /// Alpha color component. - /// - /// - /// The Alpha channel is not supported by Terminal.Gui. - /// - public int A { get; set; } = 0xFF; // Not currently supported; here for completeness. - - /// - /// Gets or sets the color value encoded as ARGB32. - /// - /// (<see cref="A"/> << 24) | (<see cref="R"/> << 16) | (<see cref="G"/> << 8) | <see cref="B"/> - /// - /// - public int Rgba { - get => (A << 24) | (R << 16) | (G << 8) | B; - set { - A = (byte)((value >> 24) & 0xFF); - R = (byte)((value >> 16) & 0xFF); - G = (byte)((value >> 8) & 0xFF); - B = (byte)(value & 0xFF); - } - } - - // TODO: Make this map configurable via ConfigurationManager - // TODO: This does not need to be a Dictionary, but can be an 16 element array. - /// - /// Maps legacy 16-color values to the corresponding 24-bit RGB value. - /// - internal static ImmutableDictionary _colorToNameMap = new Dictionary () { + // TODO: Make this map configurable via ConfigurationManager + // TODO: This does not need to be a Dictionary, but can be an 16 element array. + /// + /// Maps legacy 16-color values to the corresponding 24-bit RGB value. + /// + internal static ImmutableDictionary _colorToNameMap = new Dictionary () { // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png // See also: https://en.wikipedia.org/wiki/ANSI_escape_code - { new Color (12, 12, 12),ColorNames.Black }, - { new Color (0, 55, 218),ColorNames.Blue }, - { new Color (19, 161, 14),ColorNames.Green}, - { new Color (58, 150, 221),ColorNames.Cyan}, - { new Color (197, 15, 31),ColorNames.Red}, - { new Color (136, 23, 152),ColorNames.Magenta}, - { new Color (128, 64, 32),ColorNames.Yellow}, - { new Color (204, 204, 204),ColorNames.Gray}, - { new Color (118, 118, 118),ColorNames.DarkGray}, - { new Color (59, 120, 255),ColorNames.BrightBlue}, - { new Color (22, 198, 12),ColorNames.BrightGreen}, - { new Color (97, 214, 214),ColorNames.BrightCyan}, - { new Color (231, 72, 86),ColorNames.BrightRed}, - { new Color (180, 0, 158),ColorNames.BrightMagenta }, - { new Color (249, 241, 165),ColorNames.BrightYellow}, - { new Color (242, 242, 242),ColorNames.White}, + { new Color (12, 12, 12),Gui.ColorName.Black }, + { new Color (0, 55, 218),Gui.ColorName.Blue }, + { new Color (19, 161, 14),Gui.ColorName.Green}, + { new Color (58, 150, 221),Gui.ColorName.Cyan}, + { new Color (197, 15, 31),Gui.ColorName.Red}, + { new Color (136, 23, 152),Gui.ColorName.Magenta}, + { new Color (128, 64, 32),Gui.ColorName.Yellow}, + { new Color (204, 204, 204),Gui.ColorName.Gray}, + { new Color (118, 118, 118),Gui.ColorName.DarkGray}, + { new Color (59, 120, 255),Gui.ColorName.BrightBlue}, + { new Color (22, 198, 12),Gui.ColorName.BrightGreen}, + { new Color (97, 214, 214),Gui.ColorName.BrightCyan}, + { new Color (231, 72, 86),Gui.ColorName.BrightRed}, + { new Color (180, 0, 158),Gui.ColorName.BrightMagenta }, + { new Color (249, 241, 165),Gui.ColorName.BrightYellow}, + { new Color (242, 242, 242),Gui.ColorName.White}, }.ToImmutableDictionary (); - [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] - //[JsonConverter (typeof (DictionaryJsonConverter))] - public static Dictionary Colors { - get { - // Transform _colorToNameMap into a Dictionary - return _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}"); - } - set { - // Transform Dictionary into _colorToNameMap - var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => { - if (Enum.TryParse (kvp.Key.ToString(), ignoreCase: true, out ColorNames colorName)) { - return colorName; - } - throw new ArgumentException ($"Invalid color name: {kvp.Key}"); - }); - _colorToNameMap = newMap.ToImmutableDictionary (); - } + /// + /// Gets or sets the 24-bit color value for each of the legacy 16-color values. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] + public static Dictionary Colors { + get { + // Transform _colorToNameMap into a Dictionary + return _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}"); } - - /// - /// Converts a legacy to a 24-bit . - /// - /// The to convert. - /// - private static Color FromColorName (ColorNames consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key; - - // Iterates through the entries in the _colorNames dictionary, calculates the - // Euclidean distance between the input color and each dictionary color in RGB space, - // and keeps track of the closest entry found so far. The function returns a KeyValuePair - // representing the closest color entry and its associated color name. - internal static ColorNames FindClosestColor (Color inputColor) - { - ColorNames closestColor = ColorNames.Black; // Default to Black - double closestDistance = double.MaxValue; - - foreach (var colorEntry in _colorToNameMap) { - var distance = CalculateColorDistance (inputColor, colorEntry.Key); - if (distance < closestDistance) { - closestDistance = distance; - closestColor = colorEntry.Value; + set { + // Transform Dictionary into _colorToNameMap + var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => { + if (Enum.TryParse (kvp.Key.ToString (), ignoreCase: true, out ColorName colorName)) { + return colorName; } - } - - return closestColor; + throw new ArgumentException ($"Invalid color name: {kvp.Key}"); + }); + _colorToNameMap = newMap.ToImmutableDictionary (); } + } - private static double CalculateColorDistance (Color color1, Color color2) - { - // Calculate the Euclidean distance between two colors - var deltaR = (double)color1.R - (double)color2.R; - var deltaG = (double)color1.G - (double)color2.G; - var deltaB = (double)color1.B - (double)color2.B; + /// + /// Converts a legacy to a 24-bit . + /// + /// The to convert. + /// + private static Color FromColorName (ColorName consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key; - return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB); - } + // Iterates through the entries in the _colorNames dictionary, calculates the + // Euclidean distance between the input color and each dictionary color in RGB space, + // and keeps track of the closest entry found so far. The function returns a KeyValuePair + // representing the closest color entry and its associated color name. + internal static ColorName FindClosestColor (Color inputColor) + { + ColorName closestColor = Gui.ColorName.Black; // Default to Black + double closestDistance = double.MaxValue; - /// - /// Gets or sets the using a legacy 16-color value. - /// - /// - /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded map. - /// - public ColorNames ColorName { - get => FindClosestColor (this); - set { - - var c = FromColorName (value); - R = c.R; - G = c.G; - B = c.B; - A = c.A; + foreach (var colorEntry in _colorToNameMap) { + var distance = CalculateColorDistance (inputColor, colorEntry.Key); + if (distance < closestDistance) { + closestDistance = distance; + closestColor = colorEntry.Value; } } - #region Legacy Color Names - /// - /// - /// The black color. - /// - public const ColorNames Black = ColorNames.Black; + return closestColor; + } - /// - /// The blue color. - /// - public const ColorNames Blue = ColorNames.Blue; - /// - /// The green color. - /// - public const ColorNames Green = ColorNames.Green; - /// - /// The cyan color. - /// - public const ColorNames Cyan = ColorNames.Cyan; - /// - /// The red color. - /// - public const ColorNames Red = ColorNames.Red; - /// - /// The magenta color. - /// - public const ColorNames Magenta = ColorNames.Magenta; - /// - /// The yellow color. - /// - public const ColorNames Yellow = ColorNames.Yellow; - /// - /// The gray color. - /// - public const ColorNames Gray = ColorNames.Gray; - /// - /// The dark gray color. - /// - public const ColorNames DarkGray = ColorNames.DarkGray; - /// - /// The bright bBlue color. - /// - public const ColorNames BrightBlue = ColorNames.BrightBlue; - /// - /// The bright green color. - /// - public const ColorNames BrightGreen = ColorNames.BrightGreen; - /// - /// The bright cyan color. - /// - public const ColorNames BrightCyan = ColorNames.BrightCyan; - /// - /// The bright red color. - /// - public const ColorNames BrightRed = ColorNames.BrightRed; - /// - /// The bright magenta color. - /// - public const ColorNames BrightMagenta = ColorNames.BrightMagenta; - /// - /// The bright yellow color. - /// - public const ColorNames BrightYellow = ColorNames.BrightYellow; - /// - /// The White color. - /// - public const ColorNames White = ColorNames.White; - #endregion + private static double CalculateColorDistance (Color color1, Color color2) + { + // Calculate the Euclidean distance between two colors + var deltaR = (double)color1.R - (double)color2.R; + var deltaG = (double)color1.G - (double)color2.G; + var deltaB = (double)color1.B - (double)color2.B; - /// - /// Converts the provided string to a new instance. - /// - /// The text to analyze. Formats supported are - /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the - /// . - /// The parsed value. - /// A boolean value indicating whether parsing was successful. - /// - /// While supports the alpha channel , Terminal.Gui does not. - /// - public static bool TryParse (string text, [NotNullWhen (true)] out Color color) - { - // empty color - if ((text == null) || (text.Length == 0)) { - color = null; - return false; - } + return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB); + } - // #RRGGBB, #RGB - if ((text [0] == '#') && text.Length is 7 or 4) { - if (text.Length == 7) { - var r = Convert.ToInt32 (text.Substring (1, 2), 16); - var g = Convert.ToInt32 (text.Substring (3, 2), 16); - var b = Convert.ToInt32 (text.Substring (5, 2), 16); - color = new Color (r, g, b); - } else { - var rText = char.ToString (text [1]); - var gText = char.ToString (text [2]); - var bText = char.ToString (text [3]); + /// + /// Gets or sets the using a legacy 16-color value. + /// + /// + /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded map. + /// + public ColorName ColorName { + get => FindClosestColor (this); + set { - var r = Convert.ToInt32 (rText + rText, 16); - var g = Convert.ToInt32 (gText + gText, 16); - var b = Convert.ToInt32 (bText + bText, 16); - color = new Color (r, g, b); - } - return true; - } + var c = FromColorName (value); + R = c.R; + G = c.G; + B = c.B; + A = c.A; + } + } - // #RRGGBB, #RGBA - if ((text [0] == '#') && text.Length is 8 or 5) { - if (text.Length == 7) { - var r = Convert.ToInt32 (text.Substring (1, 2), 16); - var g = Convert.ToInt32 (text.Substring (3, 2), 16); - var b = Convert.ToInt32 (text.Substring (5, 2), 16); - var a = Convert.ToInt32 (text.Substring (7, 2), 16); - color = new Color (a, r, g, b); - } else { - var rText = char.ToString (text [1]); - var gText = char.ToString (text [2]); - var bText = char.ToString (text [3]); - var aText = char.ToString (text [4]); + #region Legacy Color Names + /// + /// + /// The black color. + /// + public const ColorName Black = ColorName.Black; - var r = Convert.ToInt32 (aText + aText, 16); - var g = Convert.ToInt32 (rText + rText, 16); - var b = Convert.ToInt32 (gText + gText, 16); - var a = Convert.ToInt32 (bText + bText, 16); - color = new Color (r, g, b, a); - } - return true; - } - - // rgb(r,g,b) - var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)"); - if (match.Success) { - var r = int.Parse (match.Groups [1].Value); - var g = int.Parse (match.Groups [2].Value); - var b = int.Parse (match.Groups [3].Value); - color = new Color (r, g, b); - return true; - } - - // rgb(r,g,b,a) - match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)"); - if (match.Success) { - var r = int.Parse (match.Groups [1].Value); - var g = int.Parse (match.Groups [2].Value); - var b = int.Parse (match.Groups [3].Value); - var a = int.Parse (match.Groups [4].Value); - color = new Color (r, g, b, a); - return true; - } - - if (Enum.TryParse (text, ignoreCase: true, out ColorNames colorName)) { - color = new Color (colorName); - return true; - } + /// + /// The blue color. + /// + public const ColorName Blue = ColorName.Blue; + /// + /// The green color. + /// + public const ColorName Green = ColorName.Green; + /// + /// The cyan color. + /// + public const ColorName Cyan = ColorName.Cyan; + /// + /// The red color. + /// + public const ColorName Red = ColorName.Red; + /// + /// The magenta color. + /// + public const ColorName Magenta = ColorName.Magenta; + /// + /// The yellow color. + /// + public const ColorName Yellow = ColorName.Yellow; + /// + /// The gray color. + /// + public const ColorName Gray = ColorName.Gray; + /// + /// The dark gray color. + /// + public const ColorName DarkGray = ColorName.DarkGray; + /// + /// The bright bBlue color. + /// + public const ColorName BrightBlue = ColorName.BrightBlue; + /// + /// The bright green color. + /// + public const ColorName BrightGreen = ColorName.BrightGreen; + /// + /// The bright cyan color. + /// + public const ColorName BrightCyan = ColorName.BrightCyan; + /// + /// The bright red color. + /// + public const ColorName BrightRed = ColorName.BrightRed; + /// + /// The bright magenta color. + /// + public const ColorName BrightMagenta = ColorName.BrightMagenta; + /// + /// The bright yellow color. + /// + public const ColorName BrightYellow = ColorName.BrightYellow; + /// + /// The White color. + /// + public const ColorName White = ColorName.White; + #endregion + /// + /// Converts the provided string to a new instance. + /// + /// The text to analyze. Formats supported are + /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the + /// . + /// The parsed value. + /// A boolean value indicating whether parsing was successful. + /// + /// While supports the alpha channel , Terminal.Gui does not. + /// + public static bool TryParse (string text, [NotNullWhen (true)] out Color color) + { + // empty color + if ((text == null) || (text.Length == 0)) { color = null; return false; } - #region Operators - /// - /// Cast from int. - /// - /// - public static implicit operator Color (int rgba) - { - return new Color (rgba); - } + // #RRGGBB, #RGB + if ((text [0] == '#') && text.Length is 7 or 4) { + if (text.Length == 7) { + var r = Convert.ToInt32 (text.Substring (1, 2), 16); + var g = Convert.ToInt32 (text.Substring (3, 2), 16); + var b = Convert.ToInt32 (text.Substring (5, 2), 16); + color = new Color (r, g, b); + } else { + var rText = char.ToString (text [1]); + var gText = char.ToString (text [2]); + var bText = char.ToString (text [3]); - /// - /// Cast to int. - /// - /// - public static explicit operator int (Color color) - { - return color.Rgba; - } - - /// - /// Cast from . - /// - /// - public static explicit operator Color (ColorNames colorName) - { - return new Color (colorName); - } - - /// - /// Cast to . - /// - /// - public static explicit operator ColorNames (Color color) - { - return color.ColorName; - } - - - /// - /// Equality operator for two objects.. - /// - /// - /// - /// - public static bool operator == (Color left, Color right) - { - if (left is null && right is null) - return true; - - if (left is null || right is null) - return false; - - return left.Equals (right); - } - - - /// - /// Inequality operator for two objects. - /// - /// - /// - /// - public static bool operator != (Color left, Color right) - { - if (left is null && right is null) - return false; - - if (left is null || right is null) - return true; - - return !left.Equals (right); - } - - /// - /// Equality operator for and objects. - /// - /// - /// - /// - public static bool operator == (ColorNames left, Color right) - { - return left == right.ColorName; - } - - /// - /// Inequality operator for and objects. - /// - /// - /// - /// - public static bool operator != (ColorNames left, Color right) - { - return left != right.ColorName; - } - - /// - /// Equality operator for and objects. - /// - /// - /// - /// - public static bool operator == (Color left, ColorNames right) - { - return left.ColorName == right; - } - - /// - /// Inequality operator for and objects. - /// - /// - /// - /// - public static bool operator != (Color left, ColorNames right) - { - return left.ColorName != right; - } - - - /// - public override bool Equals (object obj) - { - return obj is Color other && Equals (other); - } - - /// - public bool Equals (Color other) - { - return - R == other.R && - G == other.G && - B == other.B && - A == other.A; - } - - /// - public override int GetHashCode () - { - return HashCode.Combine (R, G, B, A); - } - #endregion - - /// - /// Converts the color to a string representation. - /// - /// - /// - /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string. - /// - /// - /// (Alpha channel) is ignored and the returned string will not include it. - /// - /// - /// - public override string ToString () - { - // If Values has an exact match with a named color (in _colorNames), use that. - if (_colorToNameMap.TryGetValue (this, out ColorNames colorName)) { - return Enum.GetName (typeof (ColorNames), colorName); + var r = Convert.ToInt32 (rText + rText, 16); + var g = Convert.ToInt32 (gText + gText, 16); + var b = Convert.ToInt32 (bText + bText, 16); + color = new Color (r, g, b); } - // Otherwise return as an RGB hex value. - return $"#{R:X2}{G:X2}{B:X2}"; + return true; } + + // #RRGGBB, #RGBA + if ((text [0] == '#') && text.Length is 8 or 5) { + if (text.Length == 7) { + var r = Convert.ToInt32 (text.Substring (1, 2), 16); + var g = Convert.ToInt32 (text.Substring (3, 2), 16); + var b = Convert.ToInt32 (text.Substring (5, 2), 16); + var a = Convert.ToInt32 (text.Substring (7, 2), 16); + color = new Color (a, r, g, b); + } else { + var rText = char.ToString (text [1]); + var gText = char.ToString (text [2]); + var bText = char.ToString (text [3]); + var aText = char.ToString (text [4]); + + var r = Convert.ToInt32 (aText + aText, 16); + var g = Convert.ToInt32 (rText + rText, 16); + var b = Convert.ToInt32 (gText + gText, 16); + var a = Convert.ToInt32 (bText + bText, 16); + color = new Color (r, g, b, a); + } + return true; + } + + // rgb(r,g,b) + var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)"); + if (match.Success) { + var r = int.Parse (match.Groups [1].Value); + var g = int.Parse (match.Groups [2].Value); + var b = int.Parse (match.Groups [3].Value); + color = new Color (r, g, b); + return true; + } + + // rgb(r,g,b,a) + match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)"); + if (match.Success) { + var r = int.Parse (match.Groups [1].Value); + var g = int.Parse (match.Groups [2].Value); + var b = int.Parse (match.Groups [3].Value); + var a = int.Parse (match.Groups [4].Value); + color = new Color (r, g, b, a); + return true; + } + + if (Enum.TryParse (text, ignoreCase: true, out ColorName colorName)) { + color = new Color (colorName); + return true; + } + + color = null; + return false; + } + + #region Operators + /// + /// Cast from int. + /// + /// + public static implicit operator Color (int rgba) + { + return new Color (rgba); } /// - /// Attributes represent how text is styled when displayed in the terminal. + /// Cast to int. /// - /// - /// provides a platform independent representation of colors (and someday other forms of text styling). - /// They encode both the foreground and the background color and are used in the - /// class to define color schemes that can be used in an application. - /// - [JsonConverter (typeof (AttributeJsonConverter))] - public readonly struct Attribute : IEquatable { - - /// - /// Default empty attribute. - /// - public static readonly Attribute Default = new Attribute (Color.White, Color.Black); - - /// - /// The -specific color value. - /// - [JsonIgnore (Condition = JsonIgnoreCondition.Always)] - internal int PlatformColor { get; } - - /// - /// The foreground color. - /// - [JsonConverter (typeof (ColorJsonConverter))] - public Color Foreground { get; private init; } - - /// - /// The background color. - /// - [JsonConverter (typeof (ColorJsonConverter))] - public Color Background { get; private init; } - - /// - /// Initializes a new instance with default values. - /// - public Attribute () - { - var d = Default; - PlatformColor = -1; - Foreground = d.Foreground; - Background = d.Background; - } - - /// - /// Initializes a new instance with platform specific color value. - /// - /// Value. - internal Attribute (int platformColor) : this (platformColor, Default.Foreground, Default.Background) { } - - /// - /// Initializes a new instance of the struct. - /// - /// platform-dependent color value. - /// Foreground - /// Background - internal Attribute (int platformColor, Color foreground, Color background) - { - Foreground = foreground; - Background = background; - PlatformColor = platformColor; - } - - /// - /// Initializes a new instance of the struct. - /// - /// platform-dependent color value. - /// Foreground - /// Background - internal Attribute (int platformColor, ColorNames foreground, ColorNames background) : this (platformColor, (Color)foreground, (Color)background) { } - - /// - /// Initializes a new instance of the struct. - /// - /// Foreground - /// Background - public Attribute (Color foreground, Color background) - { - Foreground = foreground; - Background = background; - - if (Application.Driver == null) { - PlatformColor = -1; - return; - } - - var make = Application.Driver.MakeAttribute (foreground, background); - PlatformColor = make.PlatformColor; - } - - /// - /// Initializes a new instance with a value. Both and - /// will be set to the specified color. - /// - /// Value. - internal Attribute (ColorNames colorName) : this (colorName, colorName) { } - - /// - /// Initializes a new instance of the struct. - /// - /// Foreground - /// Background - public Attribute (ColorNames foregroundName, ColorNames backgroundName) : this (new Color (foregroundName), new Color (backgroundName)) { } - - - /// - /// Initializes a new instance of the struct. - /// - /// Foreground - /// Background - public Attribute (ColorNames foregroundName, Color background) : this (new Color (foregroundName), background) { } - - /// - /// Initializes a new instance of the struct. - /// - /// Foreground - /// Background - public Attribute (Color foreground, ColorNames backgroundName) : this (foreground, new Color (backgroundName)) { } - - /// - /// Initializes a new instance of the struct - /// with the same colors for the foreground and background. - /// - /// The color. - public Attribute (Color color) : this (color, color) { } - - - /// - /// Compares two attributes for equality. - /// - /// - /// - /// - public static bool operator == (Attribute left, Attribute right) => left.Equals (right); - - /// - /// Compares two attributes for inequality. - /// - /// - /// - /// - public static bool operator != (Attribute left, Attribute right) => !(left == right); - - /// - public override bool Equals (object obj) - { - return obj is Attribute other && Equals (other); - } - - /// - public bool Equals (Attribute other) - { - return PlatformColor == other.PlatformColor && - Foreground == other.Foreground && - Background == other.Background; - } - - /// - public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background); - - /// - public override string ToString () - { - // Note, Unit tests are dependent on this format - return $"{Foreground},{Background}"; - } + /// + public static explicit operator int (Color color) + { + return color.Rgba; } /// - /// Defines the s for common visible elements in a . - /// Containers such as and use to determine - /// the colors used by sub-views. + /// Cast from . /// - /// - /// See also: . - /// - [JsonConverter (typeof (ColorSchemeJsonConverter))] - public class ColorScheme : IEquatable { - Attribute _normal = Attribute.Default; - Attribute _focus = Attribute.Default; - Attribute _hotNormal = Attribute.Default; - Attribute _hotFocus = Attribute.Default; - Attribute _disabled = Attribute.Default; - - /// - /// Used by and to track which ColorScheme - /// is being accessed. - /// - internal string _schemeBeingSet = ""; - - /// - /// Creates a new instance. - /// - public ColorScheme () : this (Attribute.Default) { } - - /// - /// Creates a new instance, initialized with the values from . - /// - /// The scheme to initialize the new instance with. - public ColorScheme (ColorScheme scheme) : base () - { - if (scheme != null) { - _normal = scheme.Normal; - _focus = scheme.Focus; - _hotNormal = scheme.HotNormal; - _disabled = scheme.Disabled; - _hotFocus = scheme.HotFocus; - } - } - - /// - /// Creates a new instance, initialized with the values from . - /// - /// The attribute to initialize the new instance with. - public ColorScheme (Attribute attribute) - { - _normal = attribute; - _focus = attribute; - _hotNormal = attribute; - _disabled = attribute; - _hotFocus = attribute; - } - - /// - /// The foreground and background color for text when the view is not focused, hot, or disabled. - /// - public Attribute Normal { - get => _normal; - set => _normal = value; - } - - /// - /// The foreground and background color for text when the view has the focus. - /// - public Attribute Focus { - get => _focus; - set => _focus = value; - } - - /// - /// The foreground and background color for text when the view is highlighted (hot). - /// - public Attribute HotNormal { - get => _hotNormal; - set => _hotNormal = value; - } - - /// - /// The foreground and background color for text when the view is highlighted (hot) and has focus. - /// - public Attribute HotFocus { - get => _hotFocus; - set => _hotFocus = value; - } - - /// - /// The default foreground and background color for text, when the view is disabled. - /// - public Attribute Disabled { - get => _disabled; - set => _disabled = value; - } - - /// - /// Compares two objects for equality. - /// - /// - /// true if the two objects are equal - public override bool Equals (object obj) - { - return Equals (obj as ColorScheme); - } - - /// - /// Compares two objects for equality. - /// - /// - /// true if the two objects are equal - public bool Equals (ColorScheme other) - { - return other != null && - EqualityComparer.Default.Equals (_normal, other._normal) && - EqualityComparer.Default.Equals (_focus, other._focus) && - EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) && - EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) && - EqualityComparer.Default.Equals (_disabled, other._disabled); - } - - /// - /// Returns a hashcode for this instance. - /// - /// hashcode for this instance - public override int GetHashCode () - { - int hashCode = -1242460230; - hashCode = hashCode * -1521134295 + _normal.GetHashCode (); - hashCode = hashCode * -1521134295 + _focus.GetHashCode (); - hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode (); - hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode (); - hashCode = hashCode * -1521134295 + _disabled.GetHashCode (); - return hashCode; - } - - /// - /// Compares two objects for equality. - /// - /// - /// - /// true if the two objects are equivalent - public static bool operator == (ColorScheme left, ColorScheme right) - { - return EqualityComparer.Default.Equals (left, right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// - /// true if the two objects are not equivalent - public static bool operator != (ColorScheme left, ColorScheme right) - { - return !(left == right); - } + /// + public static explicit operator Color (ColorName colorName) + { + return new Color (colorName); } /// - /// The default s for the application. + /// Cast to . /// - /// - /// This property can be set in a Theme to change the default for the application. - /// - public static class Colors { - private class SchemeNameComparerIgnoreCase : IEqualityComparer { - public bool Equals (string x, string y) - { - if (x != null && y != null) { - return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase); - } - return false; - } - - public int GetHashCode (string obj) - { - return obj.ToLowerInvariant ().GetHashCode (); - } - } - - static Colors () - { - ColorSchemes = Create (); - } - - /// - /// Creates a new dictionary of new objects. - /// - public static Dictionary Create () - { - // Use reflection to dynamically create the default set of ColorSchemes from the list defined - // by the class. - return typeof (Colors).GetProperties () - .Where (p => p.PropertyType == typeof (ColorScheme)) - .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) - .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ()); - } - - /// - /// The application Toplevel color scheme, for the default Toplevel views. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"]; - /// - /// - public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The base color scheme, for the default Toplevel views. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"]; - /// - /// - public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The dialog color scheme, for standard popup dialog boxes - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"]; - /// - /// - public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The menu bar color - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"]; - /// - /// - public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The color scheme for showing errors. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"]; - /// - /// - public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } - - static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) - { - return ColorSchemes [schemeBeingSet]; - } - - static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) - { - ColorSchemes [schemeBeingSet] = colorScheme; - colorScheme._schemeBeingSet = schemeBeingSet; - } - - /// - /// Provides the defined s. - /// - [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] - [JsonConverter (typeof (DictionaryJsonConverter))] - public static Dictionary ColorSchemes { get; private set; } + /// + public static explicit operator ColorName (Color color) + { + return color.ColorName; } + + /// + /// Equality operator for two objects.. + /// + /// + /// + /// + public static bool operator == (Color left, Color right) + { + if (left is null && right is null) + return true; + + if (left is null || right is null) + return false; + + return left.Equals (right); + } + + + /// + /// Inequality operator for two objects. + /// + /// + /// + /// + public static bool operator != (Color left, Color right) + { + if (left is null && right is null) + return false; + + if (left is null || right is null) + return true; + + return !left.Equals (right); + } + + /// + /// Equality operator for and objects. + /// + /// + /// + /// + public static bool operator == (ColorName left, Color right) + { + return left == right.ColorName; + } + + /// + /// Inequality operator for and objects. + /// + /// + /// + /// + public static bool operator != (ColorName left, Color right) + { + return left != right.ColorName; + } + + /// + /// Equality operator for and objects. + /// + /// + /// + /// + public static bool operator == (Color left, ColorName right) + { + return left.ColorName == right; + } + + /// + /// Inequality operator for and objects. + /// + /// + /// + /// + public static bool operator != (Color left, ColorName right) + { + return left.ColorName != right; + } + + + /// + public override bool Equals (object obj) + { + return obj is Color other && Equals (other); + } + + /// + public bool Equals (Color other) + { + return + R == other.R && + G == other.G && + B == other.B && + A == other.A; + } + + /// + public override int GetHashCode () + { + return HashCode.Combine (R, G, B, A); + } + #endregion + + /// + /// Converts the color to a string representation. + /// + /// + /// + /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string. + /// + /// + /// (Alpha channel) is ignored and the returned string will not include it. + /// + /// + /// + public override string ToString () + { + // If Values has an exact match with a named color (in _colorNames), use that. + if (_colorToNameMap.TryGetValue (this, out ColorName colorName)) { + return Enum.GetName (typeof (ColorName), colorName); + } + // Otherwise return as an RGB hex value. + return $"#{R:X2}{G:X2}{B:X2}"; + } +} + +/// +/// Attributes represent how text is styled when displayed in the terminal. +/// +/// +/// provides a platform independent representation of colors (and someday other forms of text styling). +/// They encode both the foreground and the background color and are used in the +/// class to define color schemes that can be used in an application. +/// +[JsonConverter (typeof (AttributeJsonConverter))] +public readonly struct Attribute : IEquatable { + + /// + /// Default empty attribute. + /// + public static readonly Attribute Default = new Attribute (Color.White, Color.Black); + + /// + /// The -specific color value. + /// + [JsonIgnore (Condition = JsonIgnoreCondition.Always)] + internal int PlatformColor { get; } + + /// + /// The foreground color. + /// + [JsonConverter (typeof (ColorJsonConverter))] + public Color Foreground { get; private init; } + + /// + /// The background color. + /// + [JsonConverter (typeof (ColorJsonConverter))] + public Color Background { get; private init; } + + /// + /// Initializes a new instance with default values. + /// + public Attribute () + { + var d = Default; + PlatformColor = -1; + Foreground = d.Foreground; + Background = d.Background; + } + + /// + /// Initializes a new instance with platform specific color value. + /// + /// Value. + internal Attribute (int platformColor) : this (platformColor, Default.Foreground, Default.Background) { } + + /// + /// Initializes a new instance of the struct. + /// + /// platform-dependent color value. + /// Foreground + /// Background + internal Attribute (int platformColor, Color foreground, Color background) + { + Foreground = foreground; + Background = background; + PlatformColor = platformColor; + } + + /// + /// Initializes a new instance of the struct. + /// + /// platform-dependent color value. + /// Foreground + /// Background + internal Attribute (int platformColor, ColorName foreground, ColorName background) : this (platformColor, (Color)foreground, (Color)background) { } + + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background + public Attribute (Color foreground, Color background) + { + Foreground = foreground; + Background = background; + + if (Application.Driver == null) { + PlatformColor = -1; + return; + } + + var make = Application.Driver.MakeAttribute (foreground, background); + PlatformColor = make.PlatformColor; + } + + /// + /// Initializes a new instance with a value. Both and + /// will be set to the specified color. + /// + /// Value. + internal Attribute (ColorName colorName) : this (colorName, colorName) { } + + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background + public Attribute (ColorName foregroundName, ColorName backgroundName) : this (new Color (foregroundName), new Color (backgroundName)) { } + + + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background + public Attribute (ColorName foregroundName, Color background) : this (new Color (foregroundName), background) { } + + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background + public Attribute (Color foreground, ColorName backgroundName) : this (foreground, new Color (backgroundName)) { } + + /// + /// Initializes a new instance of the struct + /// with the same colors for the foreground and background. + /// + /// The color. + public Attribute (Color color) : this (color, color) { } + + + /// + /// Compares two attributes for equality. + /// + /// + /// + /// + public static bool operator == (Attribute left, Attribute right) => left.Equals (right); + + /// + /// Compares two attributes for inequality. + /// + /// + /// + /// + public static bool operator != (Attribute left, Attribute right) => !(left == right); + + /// + public override bool Equals (object obj) + { + return obj is Attribute other && Equals (other); + } + + /// + public bool Equals (Attribute other) + { + return PlatformColor == other.PlatformColor && + Foreground == other.Foreground && + Background == other.Background; + } + + /// + public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background); + + /// + public override string ToString () + { + // Note, Unit tests are dependent on this format + return $"{Foreground},{Background}"; + } +} + +/// +/// Defines the s for common visible elements in a . +/// Containers such as and use to determine +/// the colors used by sub-views. +/// +/// +/// See also: . +/// +[JsonConverter (typeof (ColorSchemeJsonConverter))] +public class ColorScheme : IEquatable { + Attribute _normal = Attribute.Default; + Attribute _focus = Attribute.Default; + Attribute _hotNormal = Attribute.Default; + Attribute _hotFocus = Attribute.Default; + Attribute _disabled = Attribute.Default; + + /// + /// Used by and to track which ColorScheme + /// is being accessed. + /// + internal string _schemeBeingSet = ""; + + /// + /// Creates a new instance. + /// + public ColorScheme () : this (Attribute.Default) { } + + /// + /// Creates a new instance, initialized with the values from . + /// + /// The scheme to initialize the new instance with. + public ColorScheme (ColorScheme scheme) : base () + { + if (scheme != null) { + _normal = scheme.Normal; + _focus = scheme.Focus; + _hotNormal = scheme.HotNormal; + _disabled = scheme.Disabled; + _hotFocus = scheme.HotFocus; + } + } + + /// + /// Creates a new instance, initialized with the values from . + /// + /// The attribute to initialize the new instance with. + public ColorScheme (Attribute attribute) + { + _normal = attribute; + _focus = attribute; + _hotNormal = attribute; + _disabled = attribute; + _hotFocus = attribute; + } + + /// + /// The foreground and background color for text when the view is not focused, hot, or disabled. + /// + public Attribute Normal { + get => _normal; + set => _normal = value; + } + + /// + /// The foreground and background color for text when the view has the focus. + /// + public Attribute Focus { + get => _focus; + set => _focus = value; + } + + /// + /// The foreground and background color for text when the view is highlighted (hot). + /// + public Attribute HotNormal { + get => _hotNormal; + set => _hotNormal = value; + } + + /// + /// The foreground and background color for text when the view is highlighted (hot) and has focus. + /// + public Attribute HotFocus { + get => _hotFocus; + set => _hotFocus = value; + } + + /// + /// The default foreground and background color for text, when the view is disabled. + /// + public Attribute Disabled { + get => _disabled; + set => _disabled = value; + } + + /// + /// Compares two objects for equality. + /// + /// + /// true if the two objects are equal + public override bool Equals (object obj) + { + return Equals (obj as ColorScheme); + } + + /// + /// Compares two objects for equality. + /// + /// + /// true if the two objects are equal + public bool Equals (ColorScheme other) + { + return other != null && + EqualityComparer.Default.Equals (_normal, other._normal) && + EqualityComparer.Default.Equals (_focus, other._focus) && + EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) && + EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) && + EqualityComparer.Default.Equals (_disabled, other._disabled); + } + + /// + /// Returns a hashcode for this instance. + /// + /// hashcode for this instance + public override int GetHashCode () + { + int hashCode = -1242460230; + hashCode = hashCode * -1521134295 + _normal.GetHashCode (); + hashCode = hashCode * -1521134295 + _focus.GetHashCode (); + hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode (); + hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode (); + hashCode = hashCode * -1521134295 + _disabled.GetHashCode (); + return hashCode; + } + + /// + /// Compares two objects for equality. + /// + /// + /// + /// true if the two objects are equivalent + public static bool operator == (ColorScheme left, ColorScheme right) + { + return EqualityComparer.Default.Equals (left, right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// + /// true if the two objects are not equivalent + public static bool operator != (ColorScheme left, ColorScheme right) + { + return !(left == right); + } +} + +/// +/// The default s for the application. +/// +/// +/// This property can be set in a Theme to change the default for the application. +/// +public static class Colors { + private class SchemeNameComparerIgnoreCase : IEqualityComparer { + public bool Equals (string x, string y) + { + if (x != null && y != null) { + return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase); + } + return false; + } + + public int GetHashCode (string obj) + { + return obj.ToLowerInvariant ().GetHashCode (); + } + } + + static Colors () + { + ColorSchemes = Create (); + } + + /// + /// Creates a new dictionary of new objects. + /// + public static Dictionary Create () + { + // Use reflection to dynamically create the default set of ColorSchemes from the list defined + // by the class. + return typeof (Colors).GetProperties () + .Where (p => p.PropertyType == typeof (ColorScheme)) + .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) + .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ()); + } + + /// + /// The application Toplevel color scheme, for the default Toplevel views. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"]; + /// + /// + public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The base color scheme, for the default Toplevel views. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"]; + /// + /// + public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The dialog color scheme, for standard popup dialog boxes + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"]; + /// + /// + public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The menu bar color + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"]; + /// + /// + public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The color scheme for showing errors. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"]; + /// + /// + public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } + + static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) + { + return ColorSchemes [schemeBeingSet]; + } + + static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) + { + ColorSchemes [schemeBeingSet] = colorScheme; + colorScheme._schemeBeingSet = schemeBeingSet; + } + + /// + /// Provides the defined s. + /// + [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] + [JsonConverter (typeof (DictionaryJsonConverter))] + public static Dictionary ColorSchemes { get; private set; } } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 71308543b..f41aa5b68 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -81,7 +81,7 @@ namespace Terminal.Gui { set { var colorIndex = value.Y * _cols + value.X; - SelectedColor = (ColorNames)colorIndex; + SelectedColor = (ColorName)colorIndex; } } @@ -93,13 +93,13 @@ namespace Terminal.Gui { /// /// Selected color. /// - public ColorNames SelectedColor { + public ColorName SelectedColor { get { - return (ColorNames)_selectColorIndex; + return (ColorName)_selectColorIndex; } set { - ColorNames prev = (ColorNames)_selectColorIndex; + ColorName prev = (ColorName)_selectColorIndex; _selectColorIndex = (int)value; ColorChanged?.Invoke (this, new ColorEventArgs () { PreviousColor = new Color (prev), @@ -160,7 +160,7 @@ namespace Terminal.Gui { for (var y = 0; y < (Bounds.Height / BoxHeight); y++) { for (var x = 0; x < (Bounds.Width / BoxWidth); x++) { var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols; - Driver.SetAttribute (new Attribute ((ColorNames)foregroundColorIndex, (ColorNames)colorIndex)); + Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex)); var selected = x == Cursor.X && y == Cursor.Y; DrawColorBox (x, y, selected); colorIndex++; diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index 908e7cd7b..4ca1594a7 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -10,10 +10,10 @@ namespace UICatalog.Scenarios { var vx = 30; var x = 30; var y = 14; - var colors = System.Enum.GetValues (typeof (ColorNames)); + var colors = System.Enum.GetValues (typeof (ColorName)); - foreach (ColorNames bg in colors) { + foreach (ColorName bg in colors) { Attribute attr = new Attribute (bg, colors.Length - 1 - bg); var vl = new Label (bg.ToString (), TextDirection.TopBottom_LeftRight) { X = vx, @@ -34,7 +34,7 @@ namespace UICatalog.Scenarios { }; Win.Add (hl); vx++; - foreach (ColorNames fg in colors) { + foreach (ColorName fg in colors) { var c = new Attribute (fg, bg); var t = x.ToString (); var l = new Label (x, y, t [t.Length - 1].ToString ()) { diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 06e54232a..be945b7db 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -96,7 +96,7 @@ namespace UICatalog.Scenarios { about.Text = "Housing Expenditures by income thirds 1996-2003"; - var fore = graphView.ColorScheme.Normal.Foreground == new Color(ColorNames.Black) ? new Color(ColorNames.White) : graphView.ColorScheme.Normal.Foreground; + var fore = graphView.ColorScheme.Normal.Foreground == new Color(ColorName.Black) ? new Color(ColorName.White) : graphView.ColorScheme.Normal.Foreground; var black = new Attribute (fore, Color.Black); var cyan = new Attribute (Color.BrightCyan, Color.Black); var magenta = new Attribute (Color.BrightMagenta, Color.Black); diff --git a/UICatalog/Scenarios/InvertColors.cs b/UICatalog/Scenarios/InvertColors.cs index 01726fde0..8105f98eb 100644 --- a/UICatalog/Scenarios/InvertColors.cs +++ b/UICatalog/Scenarios/InvertColors.cs @@ -13,7 +13,7 @@ namespace UICatalog.Scenarios { Win.ColorScheme = Colors.TopLevel; List