From 6d53276be2c703624b87fc2a0b79492608d3b8b4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:09:02 -0700 Subject: [PATCH] Fixes #4289 - Simplify Drawing/Color: unify named color handling under StandardColor and remove layered resolvers (#4432) * Initial plan * Delete AnsiColorNameResolver and MultiStandardColorNameResolver, add legacy 16-color names to StandardColor Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor and enhance tests for Color, Region, and Lines Refactored `Color` struct by removing unused methods and simplifying logic. Updated namespaces for better organization. Enhanced test coverage for `Color`, `Region`, and `LineCanvas` with new test cases, parameterized tests, and edge case handling. Added `StraightLineExtensionsTests`, `StraightLineTests`, and `RegionClassTests` to validate behavior under various scenarios. Improved `MergeRectangles` stability and addressed crash patterns. Removed legacy features and unused code. Enhanced documentation and optimized performance in key methods. * Improve Color struct and StandardColors functionality Enhanced the Color struct to fully support the alpha channel for rendering intent while maintaining semantic color identity. Updated TryNameColor to ignore alpha when matching colors, ensuring transparency does not affect color resolution. Expanded XML documentation to clarify alpha channel usage and future alpha blending support. Improved drawing documentation to explain the lifecycle, deferred rendering, and color support, including 24-bit true color and legacy 16-color compatibility. Added a new section on transparency and its role in rendering. Revised StandardColors implementation to use modern C# features and ensure consistent ARGB mapping. Added comprehensive tests for StandardColors and Color, covering alpha handling, color parsing, thread safety, and aliased color resolution. Removed outdated tests relying on legacy behavior. Enhanced code readability, maintainability, and test coverage to ensure correctness and backward compatibility. * Code cleanup * Code cleanup * Fix warnings. Code cleanup * Add comprehensive unit tests for ColorStrings class Introduced a new test class `ColorStringsTests` under the `DrawingTests.ColorTests` namespace to validate the functionality of the `ColorStrings` class. Key changes include: - Added tests for `GetColorName` to verify behavior for standard and non-standard colors, ignoring alpha channels, and handling known colors. - Added tests for `GetStandardColorNames` to ensure the method returns a non-empty, alphabetically sorted collection containing all `StandardColor` enum values. - Implemented tests for `TryParseStandardColorName` to validate case-insensitive parsing, hex color support, handling invalid input, and `ReadOnlySpan` compatibility. - Added tests for `TryParseNamedColor` to verify parsing of named and hex colors, handling of aliases, and `ReadOnlySpan` support. - Added round-trip tests to ensure consistency between `GetColorName`, `TryParseNamedColor`, `GetStandardColorNames`, and `TryParseStandardColorName`. These tests ensure robust validation of color parsing and naming functionality. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig --- Examples/UICatalog/Scenarios/ColorPicker.cs | 227 ++++++------ .../Drawing/Color/AnsiColorNameResolver.cs | 68 ---- Terminal.Gui/Drawing/Color/Color.cs | 84 ++--- Terminal.Gui/Drawing/Color/ColorModel.cs | 11 +- Terminal.Gui/Drawing/Color/ColorQuantizer.cs | 6 +- Terminal.Gui/Drawing/Color/ColorStrings.cs | 59 +--- .../Color/MultiStandardColorNameResolver.cs | 83 ----- Terminal.Gui/Drawing/Color/StandardColor.cs | 61 +++- Terminal.Gui/Drawing/Color/StandardColors.cs | 7 +- Terminal.Gui/Views/Color/BBar.cs | 2 - Terminal.Gui/Views/Color/ColorBar.cs | 5 +- .../Views/Color/ColorModelStrategy.cs | 73 ++-- Terminal.Gui/Views/Color/ColorPicker.Style.cs | 3 - Terminal.Gui/Views/Color/GBar.cs | 2 - Terminal.Gui/Views/Color/IColorBar.cs | 3 +- Terminal.Gui/Views/Color/LightnessBar.cs | 2 - Terminal.Gui/Views/Color/RBar.cs | 2 - Terminal.Gui/Views/Color/SaturationBar.cs | 2 - Terminal.sln.DotSettings | 1 + Tests/StressTests/ApplicationStressTests.cs | 2 +- .../Application/TimeoutTests.cs | 2 +- .../Configuration/ColorJsonConverterTests.cs | 6 +- .../Configuration/SourcesManagerTests.cs | 12 +- .../Color/AnsiColorNameResolverTests.cs | 168 --------- .../ColorClassTests.Constructors.cs} | 4 +- .../ColorClassTests.Operators.cs} | 4 +- .../ColorClassTests.ParsingAndFormatting.cs} | 4 +- .../ColorClassTests.TypeChecks.cs} | 4 +- .../ColorClassTests.cs} | 7 +- .../Drawing/Color/ColorStandardColorTests.cs | 27 -- .../Drawing/Color/ColorStringsTests.cs | 326 ++++++++++++++++++ .../MultiStandardColorNameResolverTests.cs | 156 --------- .../Color/StandardColorNameResolverTests.cs | 6 +- .../Drawing/Color/StandardColorsTests.cs | 268 ++++++++++++++ .../Drawing/{ => Lines}/LineCanvasTests.cs | 2 +- .../StraightLineExtensionsTests.cs | 2 +- .../Drawing/{ => Lines}/StraightLineTests.cs | 2 +- .../Drawing/Region/DifferenceTests.cs | 2 +- .../Drawing/Region/DrawOuterBoundaryTests.cs | 2 +- .../Drawing/Region/MergeRectanglesTests.cs | 2 +- .../{RegionTests.cs => RegionClassTests.cs} | 4 +- .../Drawing/Region/SubtractRectangleTests.cs | 2 +- docfx/docs/drawing.md | 71 ++++ 43 files changed, 951 insertions(+), 835 deletions(-) delete mode 100644 Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs delete mode 100644 Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs delete mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs rename Tests/UnitTestsParallelizable/Drawing/{ColorTests.Constructors.cs => Color/ColorClassTests.Constructors.cs} (99%) rename Tests/UnitTestsParallelizable/Drawing/{ColorTests.Operators.cs => Color/ColorClassTests.Operators.cs} (99%) rename Tests/UnitTestsParallelizable/Drawing/{ColorTests.ParsingAndFormatting.cs => Color/ColorClassTests.ParsingAndFormatting.cs} (98%) rename Tests/UnitTestsParallelizable/Drawing/{ColorTests.TypeChecks.cs => Color/ColorClassTests.TypeChecks.cs} (98%) rename Tests/UnitTestsParallelizable/Drawing/{ColorTests.cs => Color/ColorClassTests.cs} (96%) delete mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/ColorStandardColorTests.cs create mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/ColorStringsTests.cs delete mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs create mode 100644 Tests/UnitTestsParallelizable/Drawing/Color/StandardColorsTests.cs rename Tests/UnitTestsParallelizable/Drawing/{ => Lines}/LineCanvasTests.cs (99%) rename Tests/UnitTestsParallelizable/Drawing/{ => Lines}/StraightLineExtensionsTests.cs (99%) rename Tests/UnitTestsParallelizable/Drawing/{ => Lines}/StraightLineTests.cs (99%) rename Tests/UnitTestsParallelizable/Drawing/Region/{RegionTests.cs => RegionClassTests.cs} (99%) diff --git a/Examples/UICatalog/Scenarios/ColorPicker.cs b/Examples/UICatalog/Scenarios/ColorPicker.cs index 69ae48660..a058af4af 100644 --- a/Examples/UICatalog/Scenarios/ColorPicker.cs +++ b/Examples/UICatalog/Scenarios/ColorPicker.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace UICatalog.Scenarios; +namespace UICatalog.Scenarios; [ScenarioMetadata ("ColorPicker", "Color Picker and TrueColor demonstration.")] [ScenarioCategory ("Colors")] @@ -18,25 +15,25 @@ public class ColorPickers : Scenario private Label _foregroundColorLabel; /// Background ColorPicker. - private ColorPicker backgroundColorPicker; + private ColorPicker _backgroundColorPicker; /// Foreground ColorPicker. - private ColorPicker foregroundColorPicker; + private ColorPicker _foregroundColorPicker; /// Background ColorPicker. - private ColorPicker16 backgroundColorPicker16; + private ColorPicker16 _backgroundColorPicker16; /// Foreground ColorPicker. - private ColorPicker16 foregroundColorPicker16; + private ColorPicker16 _foregroundColorPicker16; - /// Setup the scenario. + /// Set up the scenario. public override void Main () { Application.Init (); Window app = new () { - Title = GetQuitKeyAndName (), + Title = GetQuitKeyAndName () }; /////////////////////////////////////// @@ -44,23 +41,23 @@ public class ColorPickers : Scenario /////////////////////////////////////// // Foreground ColorPicker. - foregroundColorPicker = new ColorPicker + _foregroundColorPicker = new () { Title = "_Foreground Color", BorderStyle = LineStyle.Single, Width = Dim.Percent (50) }; - foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged; - app.Add (foregroundColorPicker); + _foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged; + app.Add (_foregroundColorPicker); - _foregroundColorLabel = new Label + _foregroundColorLabel = new () { - X = Pos.Left (foregroundColorPicker), Y = Pos.Bottom (foregroundColorPicker) + 1 + X = Pos.Left (_foregroundColorPicker), Y = Pos.Bottom (_foregroundColorPicker) + 1 }; app.Add (_foregroundColorLabel); // Background ColorPicker. - backgroundColorPicker = new ColorPicker + _backgroundColorPicker = new () { Title = "_Background Color", X = Pos.AnchorEnd (), @@ -68,49 +65,47 @@ public class ColorPickers : Scenario BorderStyle = LineStyle.Single }; - backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged; - app.Add (backgroundColorPicker); - _backgroundColorLabel = new Label () + _backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged; + app.Add (_backgroundColorPicker); + + _backgroundColorLabel = new () { X = Pos.AnchorEnd (), - Y = Pos.Bottom (backgroundColorPicker) + 1 + Y = Pos.Bottom (_backgroundColorPicker) + 1 }; app.Add (_backgroundColorLabel); - /////////////////////////////////////// // 16 Color Pickers /////////////////////////////////////// - // Foreground ColorPicker 16. - foregroundColorPicker16 = new ColorPicker16 + _foregroundColorPicker16 = new () { Title = "_Foreground Color", BorderStyle = LineStyle.Single, Width = Dim.Percent (50), - Visible = false // We default to HSV so hide old one + Visible = false // We default to HSV so hide old one }; - foregroundColorPicker16.ColorChanged += ForegroundColor_ColorChanged; - app.Add (foregroundColorPicker16); + _foregroundColorPicker16.ColorChanged += ForegroundColor_ColorChanged; + app.Add (_foregroundColorPicker16); // Background ColorPicker 16. - backgroundColorPicker16 = new ColorPicker16 + _backgroundColorPicker16 = new () { Title = "_Background Color", X = Pos.AnchorEnd (), Width = Dim.Percent (50), BorderStyle = LineStyle.Single, - Visible = false // We default to HSV so hide old one + Visible = false // We default to HSV so hide old one }; - backgroundColorPicker16.ColorChanged += BackgroundColor_ColorChanged; - app.Add (backgroundColorPicker16); - + _backgroundColorPicker16.ColorChanged += BackgroundColor_ColorChanged; + app.Add (_backgroundColorPicker16); // Demo Label. - _demoView = new View + _demoView = new () { Title = "Color Sample", Text = "Lorem Ipsum", @@ -124,8 +119,7 @@ public class ColorPickers : Scenario }; app.Add (_demoView); - - var osColorModel = new OptionSelector () + var osColorModel = new OptionSelector { Y = Pos.Bottom (_demoView), Width = Dim.Auto (), @@ -137,87 +131,38 @@ public class ColorPickers : Scenario "H_SL", "_16 Colors" ], - Value = (int)foregroundColorPicker.Style.ColorModel, + Value = (int)_foregroundColorPicker.Style.ColorModel }; - osColorModel.ValueChanged += (_, e) => - { - // 16 colors - if (e.Value == 3) - { - - foregroundColorPicker16.Visible = true; - foregroundColorPicker.Visible = false; - - backgroundColorPicker16.Visible = true; - backgroundColorPicker.Visible = false; - - // Switching to 16 colors - ForegroundColor_ColorChanged (null, null); - BackgroundColor_ColorChanged (null, null); - } - else - { - foregroundColorPicker16.Visible = false; - foregroundColorPicker.Visible = true; - - if (e.Value is { }) - { - foregroundColorPicker.Style.ColorModel = (ColorModel)e.Value; - foregroundColorPicker.ApplyStyleChanges (); - - backgroundColorPicker16.Visible = false; - backgroundColorPicker.Visible = true; - backgroundColorPicker.Style.ColorModel = (ColorModel)e.Value; - } - - backgroundColorPicker.ApplyStyleChanges (); - - - // Switching to true colors - foregroundColorPicker.SelectedColor = foregroundColorPicker16.SelectedColor; - backgroundColorPicker.SelectedColor = backgroundColorPicker16.SelectedColor; - } - }; + osColorModel.ValueChanged += OnOsColorModelOnValueChanged; app.Add (osColorModel); // Checkbox for switching show text fields on and off - var cbShowTextFields = new CheckBox () + var cbShowTextFields = new CheckBox { Text = "Show _Text Fields", Y = Pos.Bottom (osColorModel) + 1, Width = Dim.Auto (), Height = Dim.Auto (), - CheckedState = foregroundColorPicker.Style.ShowTextFields ? CheckState.Checked : CheckState.UnChecked, + CheckedState = _foregroundColorPicker.Style.ShowTextFields ? CheckState.Checked : CheckState.UnChecked }; - cbShowTextFields.CheckedStateChanging += (_, e) => - { - foregroundColorPicker.Style.ShowTextFields = e.Result == CheckState.Checked; - foregroundColorPicker.ApplyStyleChanges (); - backgroundColorPicker.Style.ShowTextFields = e.Result == CheckState.Checked; - backgroundColorPicker.ApplyStyleChanges (); - }; + cbShowTextFields.CheckedStateChanging += OnCbShowTextFieldsOnCheckedStateChanging; app.Add (cbShowTextFields); // Checkbox for switching show text fields on and off - var cbShowName = new CheckBox () + var cbShowName = new CheckBox { Text = "Show Color _Name", Y = Pos.Bottom (cbShowTextFields) + 1, Width = Dim.Auto (), Height = Dim.Auto (), - CheckedState = foregroundColorPicker.Style.ShowColorName ? CheckState.Checked : CheckState.UnChecked, + CheckedState = _foregroundColorPicker.Style.ShowColorName ? CheckState.Checked : CheckState.UnChecked }; - cbShowName.CheckedStateChanging += (_, e) => - { - foregroundColorPicker.Style.ShowColorName = e.Result == CheckState.Checked; - foregroundColorPicker.ApplyStyleChanges (); - backgroundColorPicker.Style.ShowColorName = e.Result == CheckState.Checked; - backgroundColorPicker.ApplyStyleChanges (); - }; + cbShowName.CheckedStateChanging += OnCbShowTextFieldsOnCheckedStateChanging; + app.Add (cbShowName); var lblDriverName = new Label @@ -247,41 +192,88 @@ public class ColorPickers : Scenario }; cbUseTrueColor.CheckedStateChanging += (_, evt) => { Application.Force16Colors = evt.Result == CheckState.Checked; }; app.Add (lblDriverName, cbSupportsTrueColor, cbUseTrueColor); + // Set default colors. - foregroundColorPicker.SelectedColor = _demoView.SuperView!.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 (); - backgroundColorPicker.SelectedColor = _demoView.SuperView.GetAttributeForRole (VisualRole.Normal).Background.GetClosestNamedColor16 (); + _foregroundColorPicker.SelectedColor = _demoView.SuperView!.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 (); + _backgroundColorPicker.SelectedColor = _demoView.SuperView.GetAttributeForRole (VisualRole.Normal).Background.GetClosestNamedColor16 (); Application.Run (app); app.Dispose (); Application.Shutdown (); + + return; + + void OnCbShowTextFieldsOnCheckedStateChanging (object _, ResultEventArgs e) + { + _foregroundColorPicker.Style.ShowTextFields = e.Result == CheckState.Checked; + _foregroundColorPicker.ApplyStyleChanges (); + _backgroundColorPicker.Style.ShowTextFields = e.Result == CheckState.Checked; + _backgroundColorPicker.ApplyStyleChanges (); + } + + void OnOsColorModelOnValueChanged (object _, EventArgs e) + { + // 16 colors + if (e.Value == 3) + { + _foregroundColorPicker16.Visible = true; + _foregroundColorPicker.Visible = false; + + _backgroundColorPicker16.Visible = true; + _backgroundColorPicker.Visible = false; + + // Switching to 16 colors + ForegroundColor_ColorChanged (null, null); + BackgroundColor_ColorChanged (null, null); + } + else + { + _foregroundColorPicker16.Visible = false; + _foregroundColorPicker.Visible = true; + + if (e.Value is { }) + { + _foregroundColorPicker.Style.ColorModel = (ColorModel)e.Value; + _foregroundColorPicker.ApplyStyleChanges (); + + _backgroundColorPicker16.Visible = false; + _backgroundColorPicker.Visible = true; + _backgroundColorPicker.Style.ColorModel = (ColorModel)e.Value; + } + + _backgroundColorPicker.ApplyStyleChanges (); + + // Switching to true colors + _foregroundColorPicker.SelectedColor = _foregroundColorPicker16.SelectedColor; + _backgroundColorPicker.SelectedColor = _backgroundColorPicker16.SelectedColor; + } + } } /// Fired when background color is changed. private void BackgroundColor_ColorChanged (object sender, ResultEventArgs e) { - UpdateColorLabel (_backgroundColorLabel, - backgroundColorPicker.Visible ? - backgroundColorPicker.SelectedColor : - backgroundColorPicker16.SelectedColor - ); + UpdateColorLabel ( + _backgroundColorLabel, + _backgroundColorPicker.Visible ? _backgroundColorPicker.SelectedColor : _backgroundColorPicker16.SelectedColor + ); UpdateDemoLabel (); } /// Fired when foreground color is changed. private void ForegroundColor_ColorChanged (object sender, ResultEventArgs e) { - UpdateColorLabel (_foregroundColorLabel, - foregroundColorPicker.Visible ? - foregroundColorPicker.SelectedColor : - foregroundColorPicker16.SelectedColor - ); + UpdateColorLabel ( + _foregroundColorLabel, + _foregroundColorPicker.Visible ? _foregroundColorPicker.SelectedColor : _foregroundColorPicker16.SelectedColor + ); UpdateDemoLabel (); } /// Update a color label from his ColorPicker. private void UpdateColorLabel (Label label, Color color) { - label.ClearViewport (null); + label.ClearViewport (); label.Text = $"{color} ({(int)color}) #{color.R:X2}{color.G:X2}{color.B:X2}"; @@ -290,17 +282,14 @@ public class ColorPickers : Scenario /// Update Demo Label. private void UpdateDemoLabel () { - _demoView.SetScheme (new Scheme - { - Normal = new Attribute ( - foregroundColorPicker.Visible ? - foregroundColorPicker.SelectedColor : - foregroundColorPicker16.SelectedColor, - backgroundColorPicker.Visible ? - backgroundColorPicker.SelectedColor : - backgroundColorPicker16.SelectedColor - ) - }); + _demoView.SetScheme ( + new () + { + Normal = new ( + _foregroundColorPicker.Visible ? _foregroundColorPicker.SelectedColor : _foregroundColorPicker16.SelectedColor, + _backgroundColorPicker.Visible ? _backgroundColorPicker.SelectedColor : _backgroundColorPicker16.SelectedColor + ) + }); } public override List GetDemoKeyStrokes () @@ -310,7 +299,7 @@ public class ColorPickers : Scenario Key.B.WithAlt ]; - for (int i = 0; i < 200; i++) + for (var i = 0; i < 200; i++) { keys.Add (Key.CursorRight); } @@ -318,7 +307,7 @@ public class ColorPickers : Scenario keys.Add (Key.Tab); keys.Add (Key.Tab); - for (int i = 0; i < 200; i++) + for (var i = 0; i < 200; i++) { keys.Add (Key.CursorLeft); } @@ -326,7 +315,7 @@ public class ColorPickers : Scenario keys.Add (Key.Tab); keys.Add (Key.Tab); - for (int i = 0; i < 200; i++) + for (var i = 0; i < 200; i++) { keys.Add (Key.CursorLeft); } diff --git a/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs b/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs deleted file mode 100644 index 5a0ebc827..000000000 --- a/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; - -namespace Terminal.Gui.Drawing; - -/// -/// 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/Terminal.Gui/Drawing/Color/Color.cs b/Terminal.Gui/Drawing/Color/Color.cs index e83a05a73..61f16c6ed 100644 --- a/Terminal.Gui/Drawing/Color/Color.cs +++ b/Terminal.Gui/Drawing/Color/Color.cs @@ -11,8 +11,28 @@ namespace Terminal.Gui.Drawing; /// /// Represents a 24-bit color encoded in ARGB32 format. -/// +/// +/// The RGB components define the color identity (what color it is), while the alpha channel defines +/// rendering intent (how transparent it should be when drawn). +/// /// +/// +/// +/// When matching colors to standard color names (e.g., via ), +/// the alpha channel is ignored. This means colors with the same RGB values but different alpha values +/// will resolve to the same color name. This design supports transparency features while maintaining +/// semantic color identity. +/// +/// +/// While Terminal.Gui does not currently support alpha blending during rendering, the alpha channel +/// is used to indicate rendering intent: +/// +/// Alpha = 0: Fully transparent (don't render) +/// Alpha = 255: Fully opaque (normal rendering) +/// Other values: Reserved for future alpha blending support +/// +/// +/// /// /// /// @@ -23,8 +43,16 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar { /// The value of the alpha channel component /// - /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect - /// rendering. + /// + /// The alpha channel represents rendering intent (transparency) rather than color identity. + /// Terminal.Gui does not currently perform alpha blending, but uses this value to determine + /// whether to render the color at all (alpha = 0 means don't render). + /// + /// + /// When matching colors to standard color names, the alpha channel is ignored. For example, + /// new Color(255, 0, 0, 255) and new Color(255, 0, 0, 128) will both be + /// identified as "Red". + /// /// [JsonIgnore] [field: FieldOffset (3)] @@ -32,8 +60,8 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar /// The value of this as a in ARGB32 format. /// - /// The alpha channel is not currently supported, so the value of the alpha channel bits will not affect - /// rendering. + /// The alpha channel in the ARGB value represents rendering intent (transparency), not color identity. + /// When matching to standard color names, only the RGB components are considered. /// [JsonIgnore] [field: FieldOffset (0)] @@ -134,8 +162,6 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar /// Initializes a new instance of the with all channels set to 0. public Color () { Argb = 0u; } - // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we - // TODO: should be able to remove these from any non-Driver-specific usages. /// Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values. [ConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] public static Dictionary Colors16 @@ -203,31 +229,6 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar [MethodImpl (MethodImplOptions.AggressiveInlining)] public bool IsClosestToNamedColor16 (in ColorName16 namedColor) { return GetClosestNamedColor16 () == namedColor; } - /// - /// Determines if the closest named to /> is the provided - /// . - /// - /// - /// The color to test against the value in - /// . - /// - /// - /// The to check if this is closer - /// to than any other configured named color. - /// - /// - /// if the closest named color to is the provided value.
- /// if any other named color is closer to than - /// . - ///
- /// - /// If is equidistant from two named colors, the result of this method is not guaranteed - /// to be determinate. - /// - [Pure] - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static bool IsColorClosestToNamedColor16 (in Color color, in ColorName16 namedColor) { return color.IsClosestToNamedColor16 (in namedColor); } - /// Gets the "closest" named color to this value. /// /// @@ -240,15 +241,6 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar return ColorExtensions.ColorToName16Map!.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).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); - } - [SkipLocalsInit] private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); } @@ -297,16 +289,8 @@ public readonly partial record struct Color : ISpanParsable, IUtf8SpanPar HSL? hsl = ColorConverter.RgbToHsl (new (R, G, B)); double lNorm = hsl.L / 255.0; - double newL; - if (lNorm < 0.5) - { - newL = Math.Min (1.0, lNorm + brightenAmount); - } - else - { - newL = Math.Max (0.0, lNorm - brightenAmount); - } + double newL = lNorm < 0.5 ? Math.Min (1.0, lNorm + brightenAmount) : Math.Max (0.0, lNorm - brightenAmount); if (Math.Abs (newL - lNorm) < 0.1) { diff --git a/Terminal.Gui/Drawing/Color/ColorModel.cs b/Terminal.Gui/Drawing/Color/ColorModel.cs index 158c03236..7351daff0 100644 --- a/Terminal.Gui/Drawing/Color/ColorModel.cs +++ b/Terminal.Gui/Drawing/Color/ColorModel.cs @@ -1,23 +1,24 @@ +// ReSharper disable InconsistentNaming + namespace Terminal.Gui.Drawing; /// -/// Describes away of modelling color e.g. Hue -/// Saturation Lightness. +/// Describes a way of modelling color e.g. Hue, Saturation, and Lightness. /// public enum ColorModel { /// - /// Color modelled by storing Red, Green and Blue as (0-255) ints + /// Color modelled by storing Red, Green and Blue as (0-255) ints /// RGB, /// - /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Value (100%) + /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Value (100%) /// HSV, /// - /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Lightness (100%) + /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Lightness (100%) /// HSL } diff --git a/Terminal.Gui/Drawing/Color/ColorQuantizer.cs b/Terminal.Gui/Drawing/Color/ColorQuantizer.cs index b163d4f43..b0930b090 100644 --- a/Terminal.Gui/Drawing/Color/ColorQuantizer.cs +++ b/Terminal.Gui/Drawing/Color/ColorQuantizer.cs @@ -39,7 +39,7 @@ public class ColorQuantizer /// public void BuildPalette (Color [,] pixels) { - List allColors = new (); + List allColors = []; int width = pixels.GetLength (0); int height = pixels.GetLength (1); @@ -56,8 +56,8 @@ public class ColorQuantizer } /// - /// Returns the closest color in that matches - /// based on the color comparison algorithm defined by + /// Returns the closest color in that matches + /// based on the color comparison algorithm defined by /// /// /// diff --git a/Terminal.Gui/Drawing/Color/ColorStrings.cs b/Terminal.Gui/Drawing/Color/ColorStrings.cs index 8f70c6a9e..4a8fd3fd2 100644 --- a/Terminal.Gui/Drawing/Color/ColorStrings.cs +++ b/Terminal.Gui/Drawing/Color/ColorStrings.cs @@ -7,47 +7,16 @@ namespace Terminal.Gui.Drawing; /// public static class ColorStrings { - private static readonly AnsiColorNameResolver _ansi = new(); private static readonly StandardColorsNameResolver _standard = new(); - private static readonly MultiStandardColorNameResolver _multi = new(); /// - /// Gets the W3C+ standard string for . - /// - /// The color. - /// if there is no standard color name for the specified color. - public static string? GetStandardColorName (Color color) - { - if (_standard.TryNameColor (color, out string? name)) - { - return name; - } - return null; - } - - /// - /// Gets the ANSI 4-bit (16) color name for . - /// - /// The color. - /// if there is no standard color name for the specified color. - // ReSharper disable once InconsistentNaming - public static string? GetANSIColor16Name (Color color) - { - if (_ansi.TryNameColor (color, out string? name)) - { - return name; - } - return null; - } - - /// - /// Gets backwards compatible color name for . + /// Gets the color name for . /// /// The color. /// Standard color name for the specified color; otherwise . public static string? GetColorName (Color color) { - if (_multi.TryNameColor (color, out string? name)) + if (_standard.TryNameColor (color, out string? name)) { return name; } @@ -80,30 +49,14 @@ public static class ColorStrings } /// - /// 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)) - { - return true; - } - color = default; - return false; - } - - /// - /// Parses and returns if name is either ANSI 4-bit or W3C standard named color. + /// Parses and returns if name is a W3C+ standard named color. /// /// The name to parse. /// If successful, the color. /// if was parsed successfully. public static bool TryParseNamedColor (ReadOnlySpan name, out Color color) { - if (_multi.TryParseColor (name, out color)) + if (_standard.TryParseColor (name, out color)) { return true; } @@ -113,7 +66,7 @@ public static class ColorStrings return true; } - color = default; + color = default (Color); return false; } @@ -130,7 +83,7 @@ public static class ColorStrings } } - color = default; + color = default (Color); return false; } } diff --git a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs deleted file mode 100644 index 36ed50910..000000000 --- a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -namespace Terminal.Gui.Drawing; - -/// -/// Color name resolver prioritizing Standard (W3C+) colors with fallback to ANSI 4-bit (16) colors. -/// -public class MultiStandardColorNameResolver : IColorNameResolver -{ - private static readonly AnsiColorNameResolver _ansi = new (); - private static readonly StandardColorsNameResolver _standard = new (); - - private static readonly ImmutableArray _combinedColorNames; - private static readonly FrozenDictionary _nameToColorMap; - private static readonly FrozenDictionary _colorToNameMap; - - static MultiStandardColorNameResolver () - { - Dictionary nameToColor = new (StringComparer.OrdinalIgnoreCase); - Dictionary colorToName = new (); - - foreach (string name in _standard.GetColorNames ()) - { - if (_standard.TryParseColor (name, out Color color)) - { - if (nameToColor.TryAdd (name, color)) - { - _ = colorToName.TryAdd (color.Argb, name); - } - } - } - - foreach (string name in _ansi.GetColorNames ()) - { - if (_ansi.TryParseColor (name, out Color color)) - { - nameToColor.TryAdd (name, color); - colorToName.TryAdd (color.Argb, name); - } - } - - _nameToColorMap = nameToColor.ToFrozenDictionary (); - _colorToNameMap = colorToName.ToFrozenDictionary (); - _combinedColorNames = nameToColor.Keys.Order ().ToImmutableArray (); - } - - /// - public IEnumerable GetColorNames () => _combinedColorNames; - - /// - public bool TryParseColor (ReadOnlySpan name, out Color color) - { - if (name.StartsWith ("#") || name.StartsWith ("0x", StringComparison.OrdinalIgnoreCase)) - { - try - { - color = Color.Parse (name.ToString (), CultureInfo.InvariantCulture); - return true; - } - catch - { - color = default; - return false; - } - } - - if (_ansi.TryParseColor (name, out color)) return true; - if (_standard.TryParseColor (name, out color)) return true; - - color = default; - return false; - } - - - /// - public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) - { - return _colorToNameMap.TryGetValue (color.Argb, out name); - } -} diff --git a/Terminal.Gui/Drawing/Color/StandardColor.cs b/Terminal.Gui/Drawing/Color/StandardColor.cs index 45d586a03..0cec9da78 100644 --- a/Terminal.Gui/Drawing/Color/StandardColor.cs +++ b/Terminal.Gui/Drawing/Color/StandardColor.cs @@ -1248,5 +1248,64 @@ public enum StandardColor /// A bright yellowish-green color. /// /// - YellowGreen = 0x9ACD32 + YellowGreen = 0x9ACD32, + + // Legacy 16-color names for backwards compatibility + // These match the RGB values used in ColorName16 mapping + + /// + /// Bright Black RGB(118, 118, 118). + /// + /// A bright black (dark gray) color from the legacy 16-color palette. This is the ColorName16.DarkGray color. + /// + /// + BrightBlack = 0x767676, + + /// + /// Bright Blue RGB(59, 120, 255). + /// + /// A bright blue color from the legacy 16-color palette. + /// + /// + BrightBlue = 0x3B78FF, + + /// + /// Bright Cyan RGB(97, 214, 214). + /// + /// A bright cyan color from the legacy 16-color palette. + /// + /// + BrightCyan = 0x61D6D6, + + /// + /// Bright Green RGB(22, 198, 12). + /// + /// A bright green color from the legacy 16-color palette. + /// + /// + BrightGreen = 0x16C60C, + + /// + /// Bright Magenta RGB(180, 0, 158). + /// + /// A bright magenta color from the legacy 16-color palette. + /// + /// + BrightMagenta = 0xB4009E, + + /// + /// Bright Red RGB(231, 72, 86). + /// + /// A bright red color from the legacy 16-color palette. + /// + /// + BrightRed = 0xE74856, + + /// + /// Bright Yellow RGB(249, 241, 165). + /// + /// A bright yellow color from the legacy 16-color palette. + /// + /// + BrightYellow = 0xF9F1A5 } diff --git a/Terminal.Gui/Drawing/Color/StandardColors.cs b/Terminal.Gui/Drawing/Color/StandardColors.cs index 9f17ac1ac..1d3e99919 100644 --- a/Terminal.Gui/Drawing/Color/StandardColors.cs +++ b/Terminal.Gui/Drawing/Color/StandardColors.cs @@ -18,7 +18,7 @@ internal static class StandardColors { string [] standardNames = Enum.GetNames ().Order ().ToArray (); - return ImmutableArray.Create (standardNames); + return [.. standardNames]; } private static readonly Lazy> _argbNameMap = new ( @@ -82,7 +82,10 @@ internal static class StandardColors /// True if conversion succeeded; otherwise false. public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) { - if (_argbNameMap.Value.TryGetValue (color.Argb, out name)) + // Ignore alpha channel when matching - alpha represents transparency, not color identity + uint opaqueArgb = color.Argb | 0xFF000000; + + if (_argbNameMap.Value.TryGetValue (opaqueArgb, out name)) { return true; } diff --git a/Terminal.Gui/Views/Color/BBar.cs b/Terminal.Gui/Views/Color/BBar.cs index 11c5cc9df..2a1220832 100644 --- a/Terminal.Gui/Views/Color/BBar.cs +++ b/Terminal.Gui/Views/Color/BBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/ColorBar.cs b/Terminal.Gui/Views/Color/ColorBar.cs index c3590d955..66503d21c 100644 --- a/Terminal.Gui/Views/Color/ColorBar.cs +++ b/Terminal.Gui/Views/Color/ColorBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; namespace Terminal.Gui.Views; @@ -15,7 +13,7 @@ internal abstract class ColorBar : View, IColorBar /// protected ColorBar () { - Height = Dim.Auto(minimumContentDim: 1); + Height = Dim.Auto (minimumContentDim: 1); Width = Dim.Fill (); CanFocus = true; @@ -135,7 +133,6 @@ internal abstract class ColorBar : View, IColorBar mouseEvent.Handled = true; SetFocus (); - } return mouseEvent.Handled; diff --git a/Terminal.Gui/Views/Color/ColorModelStrategy.cs b/Terminal.Gui/Views/Color/ColorModelStrategy.cs index 0f803be18..78deb3956 100644 --- a/Terminal.Gui/Views/Color/ColorModelStrategy.cs +++ b/Terminal.Gui/Views/Color/ColorModelStrategy.cs @@ -1,5 +1,3 @@ - - using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; @@ -7,39 +5,23 @@ namespace Terminal.Gui.Views; internal class ColorModelStrategy { - public IEnumerable CreateBars (ColorModel model) - { - switch (model) + public IEnumerable CreateBars (ColorModel model) => + model switch { - case ColorModel.RGB: - return CreateRgbBars (); - case ColorModel.HSV: - return CreateHsvBars (); - case ColorModel.HSL: - return CreateHslBars (); - default: - throw new ArgumentOutOfRangeException (nameof (model), model, null); - } - } + ColorModel.RGB => CreateRgbBars (), + ColorModel.HSV => CreateHsvBars (), + ColorModel.HSL => CreateHslBars (), + _ => throw new ArgumentOutOfRangeException (nameof (model), model, null) + }; - public Color GetColorFromBars (IList bars, ColorModel model) - { - switch (model) + public Color GetColorFromBars (IList bars, ColorModel model) => + model switch { - case ColorModel.RGB: - return ToColor (new ((byte)bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value)); - case ColorModel.HSV: - return ToColor ( - ColorConverter.HsvToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value)) - ); - case ColorModel.HSL: - return ToColor ( - ColorConverter.HslToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value)) - ); - default: - throw new ArgumentOutOfRangeException (nameof (model), model, null); - } - } + ColorModel.RGB => ToColor (new ((byte)bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value)), + ColorModel.HSV => ToColor (ColorConverter.HsvToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value))), + ColorModel.HSL => ToColor (ColorConverter.HslToRgb (new (bars [0].Value, (byte)bars [1].Value, (byte)bars [2].Value))), + _ => throw new ArgumentOutOfRangeException (nameof (model), model, null) + }; public void SetBarsToColor (IList bars, Color newValue, ColorModel model) { @@ -47,6 +29,7 @@ internal class ColorModelStrategy { return; } + switch (model) { case ColorModel.RGB: @@ -75,21 +58,21 @@ internal class ColorModelStrategy } } - private IEnumerable CreateHslBars () + private static IEnumerable CreateHslBars () { - var h = new HueBar + HueBar h = new () { Text = "H:" }; yield return h; - var s = new SaturationBar + SaturationBar s = new () { Text = "S:" }; - var l = new LightnessBar + LightnessBar l = new () { Text = "L:" }; @@ -104,21 +87,21 @@ internal class ColorModelStrategy yield return l; } - private IEnumerable CreateHsvBars () + private static IEnumerable CreateHsvBars () { - var h = new HueBar + HueBar h = new () { Text = "H:" }; yield return h; - var s = new SaturationBar + SaturationBar s = new () { Text = "S:" }; - var v = new ValueBar + ValueBar v = new () { Text = "V:" }; @@ -133,19 +116,19 @@ internal class ColorModelStrategy yield return v; } - private IEnumerable CreateRgbBars () + private static IEnumerable CreateRgbBars () { - var r = new RBar + RBar r = new () { Text = "R:" }; - var g = new GBar + GBar g = new () { Text = "G:" }; - var b = new BBar + BBar b = new () { Text = "B:" }; @@ -163,5 +146,5 @@ internal class ColorModelStrategy yield return b; } - private Color ToColor (RGB rgb) { return new (rgb.R, rgb.G, rgb.B); } + private static Color ToColor (RGB rgb) { return new (rgb.R, rgb.G, rgb.B); } } diff --git a/Terminal.Gui/Views/Color/ColorPicker.Style.cs b/Terminal.Gui/Views/Color/ColorPicker.Style.cs index afab89d5a..89a9bda8f 100644 --- a/Terminal.Gui/Views/Color/ColorPicker.Style.cs +++ b/Terminal.Gui/Views/Color/ColorPicker.Style.cs @@ -1,6 +1,3 @@ - - - namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/Color/GBar.cs b/Terminal.Gui/Views/Color/GBar.cs index b9dd5b435..b3f3695db 100644 --- a/Terminal.Gui/Views/Color/GBar.cs +++ b/Terminal.Gui/Views/Color/GBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/IColorBar.cs b/Terminal.Gui/Views/Color/IColorBar.cs index 176edead9..7197c6534 100644 --- a/Terminal.Gui/Views/Color/IColorBar.cs +++ b/Terminal.Gui/Views/Color/IColorBar.cs @@ -1,5 +1,4 @@ -#nullable disable -namespace Terminal.Gui.Views; +namespace Terminal.Gui.Views; internal interface IColorBar { diff --git a/Terminal.Gui/Views/Color/LightnessBar.cs b/Terminal.Gui/Views/Color/LightnessBar.cs index f3d1e3942..cd7627388 100644 --- a/Terminal.Gui/Views/Color/LightnessBar.cs +++ b/Terminal.Gui/Views/Color/LightnessBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/Color/RBar.cs b/Terminal.Gui/Views/Color/RBar.cs index e71dd9246..f4647f227 100644 --- a/Terminal.Gui/Views/Color/RBar.cs +++ b/Terminal.Gui/Views/Color/RBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/SaturationBar.cs b/Terminal.Gui/Views/Color/SaturationBar.cs index 9be7ab7f7..2d122abcb 100644 --- a/Terminal.Gui/Views/Color/SaturationBar.cs +++ b/Terminal.Gui/Views/Color/SaturationBar.cs @@ -1,5 +1,3 @@ - - using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index 44cd244e2..7d566d39e 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -424,6 +424,7 @@ True True True + True True True True diff --git a/Tests/StressTests/ApplicationStressTests.cs b/Tests/StressTests/ApplicationStressTests.cs index cb51ac940..d06f797ce 100644 --- a/Tests/StressTests/ApplicationStressTests.cs +++ b/Tests/StressTests/ApplicationStressTests.cs @@ -5,7 +5,7 @@ using Xunit.Abstractions; namespace StressTests; -public class ApplicationStressTests (ITestOutputHelper output) +public class ApplicationStressTests { private const int NUM_INCREMENTS = 500; diff --git a/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs index d4d675491..75e3de809 100644 --- a/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/TimeoutTests.cs @@ -9,7 +9,7 @@ namespace ApplicationTests.Timeout; /// These tests verify that timeouts fire correctly, can be added/removed, /// handle exceptions properly, and work with Application.Run() calls. /// -public class TimeoutTests (ITestOutputHelper output) +public class TimeoutTests { [Fact] public void AddTimeout_Callback_Can_Add_New_Timeout () diff --git a/Tests/UnitTestsParallelizable/Configuration/ColorJsonConverterTests.cs b/Tests/UnitTestsParallelizable/Configuration/ColorJsonConverterTests.cs index f01cdc950..a2230f8e4 100644 --- a/Tests/UnitTestsParallelizable/Configuration/ColorJsonConverterTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/ColorJsonConverterTests.cs @@ -52,7 +52,7 @@ public class ColorJsonConverterTests [InlineData (ColorName16.Red, "Red")] [InlineData (ColorName16.Magenta, "Fuchsia")] // W3C+ Standard overrides [InlineData (ColorName16.Yellow, "Yellow")] - [InlineData (ColorName16.DarkGray, "DarkGray")] + [InlineData (ColorName16.DarkGray, "BrightBlack")] // Legacy ColorName16.DarkGray now serializes as BrightBlack (first alphabetical match) [InlineData (ColorName16.BrightBlue, "BrightBlue")] [InlineData (ColorName16.BrightGreen, "BrightGreen")] [InlineData (ColorName16.BrightCyan, "BrightCyan")] @@ -98,7 +98,7 @@ public class ColorJsonConverterTests [InlineData ("BrightYellow", Color.BrightYellow)] [InlineData ("Yellow", Color.Yellow)] [InlineData ("Cyan", Color.Cyan)] - [InlineData ("DarkGray", Color.DarkGray)] + [InlineData ("BrightBlack", Color.DarkGray)] // Legacy ColorName16.DarkGray is now accessible as BrightBlack [InlineData ("Gray", Color.Gray)] [InlineData ("Green", Color.Green)] [InlineData ("Magenta", Color.Magenta)] @@ -113,7 +113,7 @@ public class ColorJsonConverterTests var actualColor = JsonSerializer.Deserialize (json, JsonOptions); // Assert - Assert.Equal (new Color (expectedColor), actualColor); + Assert.Equal (new (expectedColor), actualColor); } [Fact] diff --git a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs index fd37edbd1..1bca6304e 100644 --- a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs @@ -12,7 +12,7 @@ public class SourcesManagerTests // Arrange var sourcesManager = new SourcesManager (); var stream = new MemoryStream (); - var source = "test.json"; + var source = "Load_WithNullSettingsScope_ReturnsFalse"; var location = ConfigLocations.AppCurrent; // Act @@ -37,7 +37,7 @@ public class SourcesManagerTests } """; var location = ConfigLocations.HardCoded; - var source = "stream"; + var source = "Load_WithValidStream_UpdatesSettingsScope"; var stream = new MemoryStream (); var writer = new StreamWriter (stream); @@ -69,7 +69,7 @@ public class SourcesManagerTests writer.Flush (); stream.Position = 0; - var source = "test.json"; + var source = "Load_WithInvalidJson_AddsJsonError"; var location = ConfigLocations.AppCurrent; // Act @@ -180,7 +180,7 @@ public class SourcesManagerTests var sourcesManager = new SourcesManager (); var settingsScope = new SettingsScope (); - var source = "test.json"; + var source = "Load_WithNullOrEmptyJson_ReturnsFalse"; var location = ConfigLocations.AppCurrent; // Act @@ -206,7 +206,7 @@ public class SourcesManagerTests "Application.QuitKey": "Ctrl+Z" } """; - var source = "test.json"; + var source = "Load_WithValidJson_UpdatesSettingsScope"; var location = ConfigLocations.HardCoded; // Act @@ -233,7 +233,7 @@ public class SourcesManagerTests // "Button.DefaultShadowStyle": "None" // } // """; - // var source = "test.json"; + // var source = "Update_WithValidJson_UpdatesThemeScope"; // var location = ConfigLocations.HardCoded; // // Act diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs deleted file mode 100644 index bf40a17bc..000000000 --- a/Tests/UnitTestsParallelizable/Drawing/Color/AnsiColorNameResolverTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -#nullable enable - -namespace DrawingTests; - -public class AnsiColorNameResolverTests -{ - private readonly AnsiColorNameResolver _candidate = new (); - [Fact] - public void TryNameColor_Resolves_All_ColorName16 () - { - var resolver = new AnsiColorNameResolver (); - - foreach (ColorName16 name in Enum.GetValues ()) - { - var color = new Color (name); - bool success = resolver.TryNameColor (color, out string? resultName); - - Assert.True (success, $"Expected TryNameColor to succeed for {name}"); - Assert.Equal (name.ToString (), resultName); - } - } - - [Fact] - public void TryParseColor_Resolves_All_ColorName16_Names () - { - var resolver = new AnsiColorNameResolver (); - - foreach (ColorName16 name in Enum.GetValues ()) - { - bool success = resolver.TryParseColor (name.ToString (), out Color parsed); - - Assert.True (success, $"Expected TryParseColor to succeed for {name}"); - Assert.Equal (new Color (name), parsed); - } - } - - public static IEnumerable AnsiColorName16NumericValues => - Enum.GetValues () - .Select (e => new object [] { ((int)e).ToString () }); - [Theory] - [MemberData (nameof (AnsiColorName16NumericValues))] - public void TryParseColor_Accepts_Enum_UnderlyingNumbers (string numeric) - { - var resolver = new AnsiColorNameResolver (); - - bool success = resolver.TryParseColor (numeric, out _); - - Assert.True (success, $"Expected numeric enum value '{numeric}' to resolve successfully."); - } - - - - [Fact] - public void GetNames_Returns16ColorNames () - { - string [] expected = Enum.GetNames (); - - string [] actual = _candidate.GetColorNames ().ToArray (); - - Assert.Equal (expected, actual); - } - - [Theory] - [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 (expected, actual); - } - - [Theory] - [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", 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) - { - 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")] - 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/ColorTests.Constructors.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Constructors.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/ColorTests.Constructors.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Constructors.cs index 531108ad2..a8f7af89b 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ColorTests.Constructors.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Constructors.cs @@ -1,6 +1,6 @@ -namespace DrawingTests; +namespace DrawingTests.ColorTests; -public partial class ColorTests +public partial class ColorClassTests { [Fact] public void Constructor_Empty_ReturnsColorWithZeroValue () diff --git a/Tests/UnitTestsParallelizable/Drawing/ColorTests.Operators.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Operators.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/ColorTests.Operators.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Operators.cs index de0cfeb66..4d39940b7 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ColorTests.Operators.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.Operators.cs @@ -1,9 +1,9 @@ using System.Numerics; using System.Reflection; -namespace DrawingTests; +namespace DrawingTests.ColorTests; -public partial class ColorTests +public partial class ColorClassTests { [Theory] [Trait ("Category", "Operators")] diff --git a/Tests/UnitTestsParallelizable/Drawing/ColorTests.ParsingAndFormatting.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.ParsingAndFormatting.cs similarity index 98% rename from Tests/UnitTestsParallelizable/Drawing/ColorTests.ParsingAndFormatting.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.ParsingAndFormatting.cs index 0ac9be122..e194afc52 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ColorTests.ParsingAndFormatting.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.ParsingAndFormatting.cs @@ -2,9 +2,9 @@ using System.Buffers.Binary; using System.Globalization; -namespace DrawingTests; +namespace DrawingTests.ColorTests; -public partial class ColorTests +public partial class ColorClassTests { [Fact] public void Color_ToString_WithNamedColor () diff --git a/Tests/UnitTestsParallelizable/Drawing/ColorTests.TypeChecks.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.TypeChecks.cs similarity index 98% rename from Tests/UnitTestsParallelizable/Drawing/ColorTests.TypeChecks.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.TypeChecks.cs index 5ad8f7e37..0db602c93 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ColorTests.TypeChecks.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.TypeChecks.cs @@ -1,9 +1,9 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace DrawingTests; +namespace DrawingTests.ColorTests; -public partial class ColorTests +public partial class ColorClassTests { [Fact] [Trait ("Category", "Type Checks")] diff --git a/Tests/UnitTestsParallelizable/Drawing/ColorTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.cs similarity index 96% rename from Tests/UnitTestsParallelizable/Drawing/ColorTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.cs index c1d26d5a2..cc3b3b694 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ColorTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorClassTests.cs @@ -1,8 +1,7 @@ -#nullable enable + +namespace DrawingTests.ColorTests; -namespace DrawingTests; - -public partial class ColorTests +public partial class ColorClassTests { [Theory] [CombinatorialData] diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/ColorStandardColorTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorStandardColorTests.cs deleted file mode 100644 index f69ca4d41..000000000 --- a/Tests/UnitTestsParallelizable/Drawing/Color/ColorStandardColorTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Xunit; - -namespace DrawingTests; - -public class ColorStandardColorTests -{ - [Fact] - public void ToString_Returns_Standard_Name_For_StandardColor_CadetBlue() - { - // Without the fix, this uses Color(in StandardColor) -> this((int)colorName), - // which sets A=0x00 and prevents name resolution (expects A=0xFF). - var c = new Terminal.Gui.Drawing.Color(Terminal.Gui.Drawing.StandardColor.CadetBlue); - - // Expected: named color - Assert.Equal("CadetBlue", c.ToString()); - } - - [Fact] - public void ToString_G_Prints_Opaque_ARGB_For_StandardColor_CadetBlue() - { - // Without the fix, A=0x00, so "G" prints "#005F9EA0" instead of "#FF5F9EA0". - var c = new Terminal.Gui.Drawing.Color(Terminal.Gui.Drawing.StandardColor.CadetBlue); - - // Expected: #AARRGGBB with A=FF (opaque) - Assert.Equal("#FF5F9EA0", c.ToString("G", null)); - } -} \ No newline at end of file diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/ColorStringsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/ColorStringsTests.cs new file mode 100644 index 000000000..3c15422b7 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drawing/Color/ColorStringsTests.cs @@ -0,0 +1,326 @@ +namespace DrawingTests.ColorTests; +public class ColorStringsTests +{ + [Fact] + public void GetColorName_ReturnsNameForStandardColor () + { + Color red = new (255, 0); + string? name = ColorStrings.GetColorName (red); + + Assert.Equal ("Red", name); + } + + [Fact] + public void GetColorName_ReturnsNullForNonStandardColor () + { + Color custom = new (1, 2, 3); + string? name = ColorStrings.GetColorName (custom); + + Assert.Null (name); + } + + [Fact] + public void GetColorName_IgnoresAlphaChannel () + { + Color opaqueRed = new (255, 0, 0, 255); + Color transparentRed = new (255, 0, 0, 128); + Color fullyTransparentRed = new (255, 0, 0, 0); + + string? name1 = ColorStrings.GetColorName (opaqueRed); + string? name2 = ColorStrings.GetColorName (transparentRed); + string? name3 = ColorStrings.GetColorName (fullyTransparentRed); + + Assert.Equal ("Red", name1); + Assert.Equal ("Red", name2); + Assert.Equal ("Red", name3); + } + + [Theory] + [InlineData (240, 248, 255, "AliceBlue")] + [InlineData (0, 255, 255, "Aqua")] + [InlineData (0, 0, 0, "Black")] + [InlineData (0, 0, 255, "Blue")] + [InlineData (0, 128, 0, "Green")] + [InlineData (255, 0, 0, "Red")] + [InlineData (255, 255, 255, "White")] + [InlineData (255, 255, 0, "Yellow")] + public void GetColorName_ReturnsCorrectNameForKnownColors (int r, int g, int b, string expectedName) + { + Color color = new (r, g, b); + string? name = ColorStrings.GetColorName (color); + + Assert.Equal (expectedName, name); + } + + [Fact] + public void GetStandardColorNames_ReturnsNonEmptyCollection () + { + IEnumerable names = ColorStrings.GetStandardColorNames (); + + Assert.NotNull (names); + Assert.NotEmpty (names); + } + + [Fact] + public void GetStandardColorNames_ReturnsAlphabeticallySortedNames () + { + IEnumerable names = ColorStrings.GetStandardColorNames (); + string [] namesArray = names.ToArray (); + string [] sortedNames = namesArray.OrderBy (n => n).ToArray (); + + Assert.Equal (sortedNames, namesArray); + } + + [Fact] + public void GetStandardColorNames_ContainsKnownColors () + { + IEnumerable names = ColorStrings.GetStandardColorNames (); + string [] namesArray = names.ToArray (); + + Assert.Contains ("Red", namesArray); + Assert.Contains ("Green", namesArray); + Assert.Contains ("Blue", namesArray); + Assert.Contains ("White", namesArray); + Assert.Contains ("Black", namesArray); + Assert.Contains ("AliceBlue", namesArray); + Assert.Contains ("Tomato", namesArray); + } + + [Fact] + public void GetStandardColorNames_ContainsAllStandardColorEnumValues () + { + IEnumerable names = ColorStrings.GetStandardColorNames (); + string [] namesArray = names.ToArray (); + string [] enumNames = Enum.GetNames (); + + // All enum names should be in the returned collection + foreach (string enumName in enumNames) + { + Assert.Contains (enumName, namesArray); + } + + // The counts should match + Assert.Equal (enumNames.Length, namesArray.Length); + } + + [Theory] + [InlineData ("Red")] + [InlineData ("red")] + [InlineData ("RED")] + [InlineData ("Green")] + [InlineData ("green")] + [InlineData ("Blue")] + [InlineData ("AliceBlue")] + [InlineData ("aliceblue")] + [InlineData ("ALICEBLUE")] + public void TryParseStandardColorName_ParsesValidColorNamesCaseInsensitively (string colorName) + { + bool result = ColorStrings.TryParseStandardColorName (colorName, out Color color); + + Assert.True (result); + Assert.NotEqual (default (Color), color); + } + + [Theory] + [InlineData ("Red", 255, 0, 0)] + [InlineData ("Green", 0, 128, 0)] + [InlineData ("Blue", 0, 0, 255)] + [InlineData ("White", 255, 255, 255)] + [InlineData ("Black", 0, 0, 0)] + [InlineData ("AliceBlue", 240, 248, 255)] + [InlineData ("Tomato", 255, 99, 71)] + public void TryParseStandardColorName_ParsesCorrectRgbValues (string colorName, int expectedR, int expectedG, int expectedB) + { + bool result = ColorStrings.TryParseStandardColorName (colorName, out Color color); + + Assert.True (result); + Assert.Equal (expectedR, color.R); + Assert.Equal (expectedG, color.G); + Assert.Equal (expectedB, color.B); + } + + [Theory] + [InlineData ("#FF0000", 255, 0, 0)] + [InlineData ("#00FF00", 0, 255, 0)] + [InlineData ("#0000FF", 0, 0, 255)] + [InlineData ("#FFFFFF", 255, 255, 255)] + [InlineData ("#000000", 0, 0, 0)] + [InlineData ("#F0F8FF", 240, 248, 255)] + public void TryParseStandardColorName_ParsesHexColorFormat (string hexColor, int expectedR, int expectedG, int expectedB) + { + bool result = ColorStrings.TryParseStandardColorName (hexColor, out Color color); + + Assert.True (result); + Assert.Equal (expectedR, color.R); + Assert.Equal (expectedG, color.G); + Assert.Equal (expectedB, color.B); + } + + [Theory] + [InlineData ("#ff0000")] + [InlineData ("#FF0000")] + [InlineData ("#Ff0000")] + public void TryParseStandardColorName_ParsesHexColorCaseInsensitively (string hexColor) + { + bool result = ColorStrings.TryParseStandardColorName (hexColor, out Color color); + + Assert.True (result); + Assert.Equal (255, color.R); + Assert.Equal (0, color.G); + Assert.Equal (0, color.B); + } + + [Theory] + [InlineData ("")] + [InlineData ("NotAColor")] + [InlineData ("Invalid")] + [InlineData ("123")] + [InlineData ("#FF")] + [InlineData ("#FFFF")] + [InlineData ("#FFFFFFFF")] + [InlineData ("FF0000")] + public void TryParseStandardColorName_ReturnsFalseForInvalidInput (string invalidInput) + { + bool result = ColorStrings.TryParseStandardColorName (invalidInput, out Color color); + + Assert.False (result); + Assert.Equal (default (Color), color); + } + + [Fact] + public void TryParseStandardColorName_SetsAlphaToFullyOpaque () + { + bool result = ColorStrings.TryParseStandardColorName ("Red", out Color color); + + Assert.True (result); + Assert.Equal (255, color.A); + } + + [Fact] + public void TryParseStandardColorName_WorksWithReadOnlySpan () + { + ReadOnlySpan span = "Red".AsSpan (); + bool result = ColorStrings.TryParseStandardColorName (span, out Color color); + + Assert.True (result); + Assert.Equal (255, color.R); + Assert.Equal (0, color.G); + Assert.Equal (0, color.B); + } + + [Theory] + [InlineData ("Red")] + [InlineData ("Green")] + [InlineData ("Blue")] + [InlineData ("AliceBlue")] + [InlineData ("#FF0000")] + public void TryParseNamedColor_ParsesValidColorNames (string colorName) + { + bool result = ColorStrings.TryParseNamedColor (colorName, out Color color); + + Assert.True (result); + Assert.NotEqual (default (Color), color); + } + + [Theory] + [InlineData ("Red", 255, 0, 0)] + [InlineData ("Green", 0, 128, 0)] + [InlineData ("Blue", 0, 0, 255)] + [InlineData ("#FF0000", 255, 0, 0)] + [InlineData ("#00FF00", 0, 255, 0)] + public void TryParseNamedColor_ParsesCorrectRgbValues (string colorName, int expectedR, int expectedG, int expectedB) + { + bool result = ColorStrings.TryParseNamedColor (colorName, out Color color); + + Assert.True (result); + Assert.Equal (expectedR, color.R); + Assert.Equal (expectedG, color.G); + Assert.Equal (expectedB, color.B); + } + + [Theory] + [InlineData ("")] + [InlineData ("NotAColor")] + [InlineData ("Invalid")] + [InlineData ("#ZZ0000")] + public void TryParseNamedColor_ReturnsFalseForInvalidInput (string invalidInput) + { + bool result = ColorStrings.TryParseNamedColor (invalidInput, out Color color); + + Assert.False (result); + Assert.Equal (default (Color), color); + } + + [Fact] + public void TryParseNamedColor_WorksWithReadOnlySpan () + { + ReadOnlySpan span = "Blue".AsSpan (); + bool result = ColorStrings.TryParseNamedColor (span, out Color color); + + Assert.True (result); + Assert.Equal (0, color.R); + Assert.Equal (0, color.G); + Assert.Equal (255, color.B); + } + + [Theory] + [InlineData (nameof (StandardColor.Aqua), nameof (StandardColor.Cyan))] + [InlineData (nameof (StandardColor.Fuchsia), nameof (StandardColor.Magenta))] + public void TryParseNamedColor_HandlesColorAliases (string name1, string name2) + { + bool result1 = ColorStrings.TryParseNamedColor (name1, out Color color1); + bool result2 = ColorStrings.TryParseNamedColor (name2, out Color color2); + + Assert.True (result1); + Assert.True (result2); + Assert.Equal (color1.R, color2.R); + Assert.Equal (color1.G, color2.G); + Assert.Equal (color1.B, color2.B); + } + + [Fact] + public void GetColorName_And_TryParseNamedColor_RoundTrip () + { + // Get a standard color name + Color originalColor = new (255, 0); + string? colorName = ColorStrings.GetColorName (originalColor); + + Assert.NotNull (colorName); + + // Parse it back + bool result = ColorStrings.TryParseNamedColor (colorName, out Color parsedColor); + + Assert.True (result); + Assert.Equal (originalColor.R, parsedColor.R); + Assert.Equal (originalColor.G, parsedColor.G); + Assert.Equal (originalColor.B, parsedColor.B); + } + + [Fact] + public void GetStandardColorNames_And_TryParseStandardColorName_RoundTrip () + { + // Get all standard color names + IEnumerable names = ColorStrings.GetStandardColorNames (); + + // Each name should parse successfully + foreach (string name in names) + { + bool result = ColorStrings.TryParseStandardColorName (name, out Color color); + Assert.True (result, $"Failed to parse standard color name: {name}"); + + // And should get the same name back (for non-aliases) + string? retrievedName = ColorStrings.GetColorName (color); + Assert.NotNull (retrievedName); + + // The retrieved name should be one of the valid names for this color + // (could be different if there are aliases) + Assert.True ( + ColorStrings.TryParseStandardColorName (retrievedName, out Color retrievedColor), + $"Retrieved name '{retrievedName}' should be parseable" + ); + Assert.Equal (color.R, retrievedColor.R); + Assert.Equal (color.G, retrievedColor.G); + Assert.Equal (color.B, retrievedColor.B); + } + } +} diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs deleted file mode 100644 index 6614db6ff..000000000 --- a/Tests/UnitTestsParallelizable/Drawing/Color/MultiStandardColorNameResolverTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -#nullable enable - -using System.Collections.Generic; -using Xunit.Abstractions; -using Terminal.Gui; - -namespace DrawingTests; - -public class MultiStandardColorNameResolverTests (ITestOutputHelper output) -{ - private readonly MultiStandardColorNameResolver _candidate = new (); - - public static IEnumerable StandardColors => - Enum.GetValues ().Select (sc => new object [] { sc }); - - [Theory] - [MemberData (nameof (StandardColors))] - public void TryParseColor_ResolvesAllStandardColorNames (StandardColor standardColor) - { - string name = standardColor.ToString (); - bool parsed = _candidate.TryParseColor (name, out Color actualColor); - - Assert.True (parsed, $"TryParseColor should succeed for {name}"); - - Color expectedColor = new (Terminal.Gui.Drawing.StandardColors.GetArgb (standardColor)); - - Assert.Equal (expectedColor.R, actualColor.R); - Assert.Equal (expectedColor.G, actualColor.G); - Assert.Equal (expectedColor.B, actualColor.B); - } - - [Theory] - [MemberData (nameof (StandardColors))] - public void TryNameColor_ResolvesAllStandardColors (StandardColor standardColor) - { - Color color = new (Terminal.Gui.Drawing.StandardColors.GetArgb (standardColor)); - - bool success = _candidate.TryNameColor (color, out string? resolvedName); - - if (!success) - { - output.WriteLine ($"Unmapped: {standardColor} → {color}"); - } - - Assert.True (success, $"TryNameColor should succeed for {standardColor}"); - - List expectedNames = Enum.GetNames () - .Where (name => Terminal.Gui.Drawing.StandardColors.GetArgb (Enum.Parse (name)) == color.Argb) - .ToList (); - - Assert.Contains (resolvedName, expectedNames); - } - - [Fact] - public void TryNameColor_Logs_Unmapped_StandardColors () - { - List unmapped = new (); - - foreach (StandardColor sc in Enum.GetValues ()) - { - Color color = new (Terminal.Gui.Drawing.StandardColors.GetArgb (sc)); - if (!_candidate.TryNameColor (color, out _)) - { - unmapped.Add (sc); - } - } - - output.WriteLine ("Unmapped StandardColor entries:"); - foreach (StandardColor sc in unmapped.Distinct ()) - { - output.WriteLine ($"- {sc}"); - } - - Assert.True (unmapped.Count < 10, $"Too many StandardColor values are not name-resolvable. Got {unmapped.Count}."); - } - - [Theory] - [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))] - [InlineData (nameof (StandardColor.AliceBlue))] - [InlineData (nameof (StandardColor.BlanchedAlmond))] - public void GetNames_ContainsKnownNames (string name) - { - string [] names = _candidate.GetColorNames ().ToArray (); - Assert.Contains (name, names); - } - - [Theory] - [InlineData (0, 0, 0, nameof (ColorName16.Black))] - [InlineData (0, 0, 255, nameof (ColorName16.Blue))] - [InlineData (59, 120, 255, nameof (ColorName16.BrightBlue))] - [InlineData (255, 0, 0, nameof (ColorName16.Red))] - [InlineData (255, 255, 255, nameof (ColorName16.White))] - [InlineData (240, 248, 255, nameof (StandardColor.AliceBlue))] - [InlineData (178, 34, 34, nameof (StandardColor.FireBrick))] - [InlineData (245, 245, 245, nameof (StandardColor.WhiteSmoke))] - public void TryNameColor_ReturnsExpectedColorNames (byte r, byte g, byte b, string expectedName) - { - Color color = new (r, g, b); - bool actualSuccess = _candidate.TryNameColor (color, out string? actualName); - - Assert.True (actualSuccess); - Assert.Equal (expectedName, actualName); - } - - [Fact] - public void TryNameColor_NoMatchFails () - { - Color input = new (1, 2, 3); - bool success = _candidate.TryNameColor (input, out string? actualName); - Assert.False (success); - Assert.Null (actualName); - } - - [Theory] - [InlineData ("12", 231, 72, 86)] // ColorName16.BrightRed - [InlineData ("16737095", 255, 99, 71)] // StandardColor.Tomato - [InlineData ("#FF0000", 255, 0, 0)] // Red - public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b) - { - bool success = _candidate.TryParseColor (inputName, out Color actualColor); - Assert.True (success); - Assert.Equal (r, actualColor.R); - Assert.Equal (g, actualColor.G); - Assert.Equal (b, actualColor.B); - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("brightlight")] - public void TryParseColor_FailsOnInvalidColorName (string? input) - { - bool success = _candidate.TryParseColor (input, out Color actualColor); - Assert.False (success); - Assert.Equal (default, actualColor); - } - - [Theory] - [InlineData ("-12")] - [InlineData ("-16737095")] - public void TryParseColor_FailsOnInvalidEnumNumber (string input) - { - bool success = _candidate.TryParseColor (input, out Color actualColor); - Assert.False (success); - Assert.Equal (default, actualColor); - } -} diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorNameResolverTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorNameResolverTests.cs index 0f8391d3d..35622b374 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorNameResolverTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorNameResolverTests.cs @@ -1,8 +1,6 @@ -#nullable enable +using Xunit.Abstractions; -using Xunit.Abstractions; - -namespace DrawingTests; +namespace DrawingTests.ColorTests; public class StandardColorNameResolverTests (ITestOutputHelper output) { diff --git a/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorsTests.cs new file mode 100644 index 000000000..dfea37653 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drawing/Color/StandardColorsTests.cs @@ -0,0 +1,268 @@ +#pragma warning disable xUnit1031 + +namespace DrawingTests.ColorTests; + +public class StandardColorsTests +{ + [Fact] + public void GetArgb_HandlesAllStandardColorValues () + { + foreach (StandardColor sc in Enum.GetValues ()) + { + uint argb = StandardColors.GetArgb (sc); + + // Verify alpha is always 0xFF (fully opaque) + var alpha = (byte)((argb >> 24) & 0xFF); + Assert.Equal (255, alpha); + + // Verify the RGB components match the enum value + var enumRgb = (int)sc; + uint expectedArgb = (uint)enumRgb | 0xFF000000; + Assert.Equal (expectedArgb, argb); + } + } + + [Theory] + [InlineData (StandardColor.Red, 255, 0, 0)] + [InlineData (StandardColor.Green, 0, 128, 0)] + [InlineData (StandardColor.Blue, 0, 0, 255)] + [InlineData (StandardColor.White, 255, 255, 255)] + [InlineData (StandardColor.Black, 0, 0, 0)] + [InlineData (StandardColor.AliceBlue, 240, 248, 255)] + [InlineData (StandardColor.YellowGreen, 154, 205, 50)] + public void GetArgb_ReturnsCorrectArgbWithFullAlpha (StandardColor standardColor, byte r, byte g, byte b) + { + uint argb = StandardColors.GetArgb (standardColor); + + var actualA = (byte)((argb >> 24) & 0xFF); + var actualR = (byte)((argb >> 16) & 0xFF); + var actualG = (byte)((argb >> 8) & 0xFF); + var actualB = (byte)(argb & 0xFF); + + Assert.Equal (255, actualA); + Assert.Equal (r, actualR); + Assert.Equal (g, actualG); + Assert.Equal (b, actualB); + } + + [Fact] + public void GetColorNames_ContainsAllStandardColorEnumValues () + { + string [] enumNames = Enum.GetNames ().Order ().ToArray (); + IReadOnlyList colorNames = StandardColors.GetColorNames (); + + Assert.Equal (enumNames.Length, colorNames.Count); + Assert.Equal (enumNames, colorNames); + } + + [Fact] + public void GetColorNames_IsAlphabeticallySorted () + { + IReadOnlyList colorNames = StandardColors.GetColorNames (); + string [] sortedNames = colorNames.OrderBy (n => n).ToArray (); + + Assert.Equal (sortedNames, colorNames); + } + + [Fact] + public void LazyInitialization_IsThreadSafe () + { + // Force initialization by calling GetColorNames multiple times in parallel + Task [] tasks = new Task [10]; + + for (var i = 0; i < tasks.Length; i++) + { + tasks [i] = Task.Run (() => + { + IReadOnlyList names = StandardColors.GetColorNames (); + Assert.NotNull (names); + Assert.NotEmpty (names); + } + ); + } + + Task.WaitAll (tasks); + } + + [Fact] + public void MapValueFactory_CreatesConsistentMapping () + { + // Call TryNameColor multiple times for the same color + var testColor = new Color (255, 0); + + bool result1 = StandardColors.TryNameColor (testColor, out string? name1); + bool result2 = StandardColors.TryNameColor (testColor, out string? name2); + + Assert.True (result1); + Assert.True (result2); + Assert.Equal (name1, name2); + } + + [Fact] + public void ToString_G_Prints_Opaque_ARGB_For_StandardColor_CadetBlue () + { + // Without the fix, A=0x00, so "G" prints "#005F9EA0" instead of "#FF5F9EA0". + var c = new Color (StandardColor.CadetBlue); + + // Expected: #AARRGGBB with A=FF (opaque) + Assert.Equal ("#FF5F9EA0", c.ToString ("G")); + } + + [Fact] + public void ToString_Returns_Standard_Name_For_StandardColor_CadetBlue () + { + // Without the fix, this uses Color(in StandardColor) -> this((int)colorName), + // which sets A=0x00 and prevents name resolution (expects A=0xFF). + var c = new Color (StandardColor.CadetBlue); + + // Expected: named color + Assert.Equal ("CadetBlue", c.ToString ()); + } + + [Fact] + public void TryNameColor_IgnoresAlphaChannel () + { + var opaqueRed = new Color (255, 0, 0, 255); + var transparentRed = new Color (255, 0, 0, 128); + + Assert.True (StandardColors.TryNameColor (opaqueRed, out string? name1)); + Assert.True (StandardColors.TryNameColor (transparentRed, out string? name2)); + + Assert.Equal (name1, name2); + Assert.Equal ("Red", name1); + } + + [Fact] + public void TryNameColor_ReturnsConsistentResultsForSameArgb () + { + List colors = new (); + + // Create multiple Color instances with the same ARGB values + for (var i = 0; i < 5; i++) + { + colors.Add (new (255, 0)); + } + + HashSet names = new (); + + foreach (Color color in colors) + { + StandardColors.TryNameColor (color, out string? name); + names.Add (name); + } + + // All should resolve to the same name + Assert.Single (names); + Assert.Equal ("Red", names.First ()); + } + + [Fact] + public void TryNameColor_ReturnsFalseForUnknownColor () + { + var unknownColor = new Color (1, 2, 3); + bool result = StandardColors.TryNameColor (unknownColor, out string? name); + + Assert.False (result); + Assert.Null (name); + } + + [Fact] + public void TryNameColor_ReturnsFirstAlphabeticalNameForAliasedColors () + { + // Aqua and Cyan have the same RGB values + var aqua = new Color (0, 255, 255); + Assert.True (StandardColors.TryNameColor (aqua, out string? name)); + + // Should return the alphabetically first name + Assert.Equal ("Aqua", name); + } + + [Theory] + [InlineData (nameof (StandardColor.Aqua), nameof (StandardColor.Cyan))] + [InlineData (nameof (StandardColor.Fuchsia), nameof (StandardColor.Magenta))] + [InlineData (nameof (StandardColor.DarkGray), nameof (StandardColor.DarkGrey))] + [InlineData (nameof (StandardColor.DarkSlateGray), nameof (StandardColor.DarkSlateGrey))] + [InlineData (nameof (StandardColor.DimGray), nameof (StandardColor.DimGrey))] + [InlineData (nameof (StandardColor.Gray), nameof (StandardColor.Grey))] + [InlineData (nameof (StandardColor.LightGray), nameof (StandardColor.LightGrey))] + [InlineData (nameof (StandardColor.LightSlateGray), nameof (StandardColor.LightSlateGrey))] + [InlineData (nameof (StandardColor.SlateGray), nameof (StandardColor.SlateGrey))] + public void TryParseColor_HandlesColorAliases (string name1, string name2) + { + Assert.True (StandardColors.TryParseColor (name1, out Color color1)); + Assert.True (StandardColors.TryParseColor (name2, out Color color2)); + + Assert.Equal (color1, color2); + } + + [Theory] + [InlineData (StandardColor.AmberPhosphor, 255, 191, 0)] + [InlineData (StandardColor.GreenPhosphor, 0, 255, 102)] + [InlineData (StandardColor.GuppieGreen, 173, 255, 47)] + public void TryParseColor_HandlesNonW3CColors (StandardColor color, byte r, byte g, byte b) + { + bool result = StandardColors.TryParseColor (color.ToString (), out Color parsedColor); + + Assert.True (result); + Assert.Equal (r, parsedColor.R); + Assert.Equal (g, parsedColor.G); + Assert.Equal (b, parsedColor.B); + } + + [Fact] + public void TryParseColor_IsCaseInsensitive () + { + Assert.True (StandardColors.TryParseColor ("RED", out Color upperColor)); + Assert.True (StandardColors.TryParseColor ("red", out Color lowerColor)); + Assert.True (StandardColors.TryParseColor ("Red", out Color mixedColor)); + + Assert.Equal (upperColor, lowerColor); + Assert.Equal (upperColor, mixedColor); + Assert.Equal (255, upperColor.R); + Assert.Equal (0, upperColor.G); + Assert.Equal (0, upperColor.B); + } + + [Theory] + [InlineData ("")] + [InlineData ("NotAColor")] + [InlineData ("123456")] + [InlineData ("-1")] + public void TryParseColor_ReturnsFalseForInvalidInput (string invalidInput) + { + bool result = StandardColors.TryParseColor (invalidInput, out Color color); + + Assert.False (result); + Assert.Equal (default (Color), color); + } + + [Fact] + public void TryParseColor_SetsAlphaToFullyOpaque () + { + Assert.True (StandardColors.TryParseColor ("Red", out Color color)); + + Assert.Equal (255, color.A); + } + + [Fact] + public void TryParseColor_WithEmptySpan_ReturnsFalse () + { + ReadOnlySpan emptySpan = ReadOnlySpan.Empty; + bool result = StandardColors.TryParseColor (emptySpan, out Color color); + + Assert.False (result); + Assert.Equal (default (Color), color); + } + + [Fact] + public void TryParseColor_WithReadOnlySpan_WorksCorrectly () + { + ReadOnlySpan span = "Red".AsSpan (); + bool result = StandardColors.TryParseColor (span, out Color color); + + Assert.True (result); + Assert.Equal (255, color.R); + Assert.Equal (0, color.G); + Assert.Equal (0, color.B); + } +} diff --git a/Tests/UnitTestsParallelizable/Drawing/LineCanvasTests.cs b/Tests/UnitTestsParallelizable/Drawing/Lines/LineCanvasTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/LineCanvasTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Lines/LineCanvasTests.cs index 7178ea897..1feedd9f2 100644 --- a/Tests/UnitTestsParallelizable/Drawing/LineCanvasTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Lines/LineCanvasTests.cs @@ -2,7 +2,7 @@ using UnitTests; using Xunit.Abstractions; -namespace DrawingTests; +namespace DrawingTests.Lines; /// /// Pure unit tests for that don't require Application.Driver or View context. diff --git a/Tests/UnitTestsParallelizable/Drawing/StraightLineExtensionsTests.cs b/Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineExtensionsTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/StraightLineExtensionsTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineExtensionsTests.cs index 663a96c31..fac26577f 100644 --- a/Tests/UnitTestsParallelizable/Drawing/StraightLineExtensionsTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineExtensionsTests.cs @@ -1,7 +1,7 @@ using UnitTests; using Xunit.Abstractions; -namespace DrawingTests; +namespace UnitTests.Parallelizable.Drawing.Lines; public class StraightLineExtensionsTests (ITestOutputHelper output) { diff --git a/Tests/UnitTestsParallelizable/Drawing/StraightLineTests.cs b/Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/StraightLineTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineTests.cs index b39246e0b..38d8e223f 100644 --- a/Tests/UnitTestsParallelizable/Drawing/StraightLineTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Lines/StraightLineTests.cs @@ -1,6 +1,6 @@ using Xunit.Abstractions; -namespace DrawingTests; +namespace UnitTests.Parallelizable.Drawing.Lines; public class StraightLineTests (ITestOutputHelper output) { diff --git a/Tests/UnitTestsParallelizable/Drawing/Region/DifferenceTests.cs b/Tests/UnitTestsParallelizable/Drawing/Region/DifferenceTests.cs index d229190f5..b84348996 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Region/DifferenceTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Region/DifferenceTests.cs @@ -1,6 +1,6 @@ using Xunit.Sdk; -namespace DrawingTests; +namespace DrawingTests.RegionTests; public class DifferenceTests { diff --git a/Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs b/Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs index bad986817..d399b2b2f 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using Xunit.Abstractions; -namespace DrawingTests; +namespace DrawingTests.RegionTests; /// /// Tests for . diff --git a/Tests/UnitTestsParallelizable/Drawing/Region/MergeRectanglesTests.cs b/Tests/UnitTestsParallelizable/Drawing/Region/MergeRectanglesTests.cs index b893b029d..f6f3dbfa0 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Region/MergeRectanglesTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Region/MergeRectanglesTests.cs @@ -1,4 +1,4 @@ -namespace DrawingTests; +namespace DrawingTests.RegionTests; public class MergeRectanglesTests diff --git a/Tests/UnitTestsParallelizable/Drawing/Region/RegionTests.cs b/Tests/UnitTestsParallelizable/Drawing/Region/RegionClassTests.cs similarity index 99% rename from Tests/UnitTestsParallelizable/Drawing/Region/RegionTests.cs rename to Tests/UnitTestsParallelizable/Drawing/Region/RegionClassTests.cs index c51e82a1d..46c0b19c5 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Region/RegionTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Region/RegionClassTests.cs @@ -1,6 +1,6 @@ -namespace DrawingTests; +namespace DrawingTests.RegionTests; -public class RegionTests +public class RegionClassTests { [Fact] public void Clone_CreatesExactCopy () diff --git a/Tests/UnitTestsParallelizable/Drawing/Region/SubtractRectangleTests.cs b/Tests/UnitTestsParallelizable/Drawing/Region/SubtractRectangleTests.cs index 23b9bd5aa..df67f841a 100644 --- a/Tests/UnitTestsParallelizable/Drawing/Region/SubtractRectangleTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/Region/SubtractRectangleTests.cs @@ -1,4 +1,4 @@ -namespace DrawingTests; +namespace DrawingTests.RegionTests; using Xunit; diff --git a/docfx/docs/drawing.md b/docfx/docs/drawing.md index a74edf432..f29516f9e 100644 --- a/docfx/docs/drawing.md +++ b/docfx/docs/drawing.md @@ -10,6 +10,27 @@ Terminal.Gui provides a set of APIs for formatting text, line drawing, and chara Terminal.Gui apps draw using the @Terminal.Gui.ViewBase.View.Move(System.Int32,System.Int32) and @Terminal.Gui.ViewBase.View.AddRune(System.Text.Rune) APIs. Move selects the column and row of the cell and AddRune places the specified glyph in that cell using the @Terminal.Gui.Drawing.Attribute that was most recently set via @Terminal.Gui.ViewBase.View.SetAttribute(Terminal.Gui.Drawing.Attribute). The driver caches all changed Cells and efficiently outputs them to the terminal each iteration of the Application. In other words, Terminal.Gui uses deferred rendering. +## Drawing Lifecycle + +**Drawing occurs during Application MainLoop iterations**, not immediately when draw-related methods are called. This deferred rendering approach provides better performance and ensures visual consistency. + +### MainLoop Iteration Process + +Each iteration of the @Terminal.Gui.App.Application MainLoop (throttled to a maximum rate) performs these steps in order: + +1. **Layout** - Views that need layout are measured and positioned (@Terminal.Gui.ViewBase.View.LayoutSubviews is called) +2. **Draw** - Views that need drawing update the driver's back buffer (@Terminal.Gui.ViewBase.View.Draw is called) +3. **Write** - The driver writes changed portions of the back buffer to the actual terminal +4. **Cursor** - The driver ensures the cursor is positioned correctly with appropriate visibility + +### When Drawing Actually Occurs + +- **Normal Operation**: Drawing happens automatically during MainLoop iterations when @Terminal.Gui.ViewBase.View.NeedsDraw or @Terminal.Gui.ViewBase.View.SubViewNeedsDraw is set +- **Forced Update**: @Terminal.Gui.App.Application.LayoutAndDraw can be called to immediately trigger layout and drawing outside of the normal iteration cycle +- **Testing**: Tests can call @Terminal.Gui.ViewBase.View.Draw directly to update the back buffer, then call @Terminal.Gui.Drivers.IDriver.Refresh to output to the terminal + +**Important**: Calling `View.Draw()` does not immediately update the terminal screen. It only updates the driver's back buffer. The actual terminal output occurs when the driver's `Refresh()` method is called, which happens automatically during MainLoop iterations. + ## Coordinate System for Drawing The @Terminal.Gui.ViewBase.View draw APIs all take coordinates specified in *Viewport-Relative* coordinates. That is, `0, 0` is the top-left cell visible to the user. @@ -60,6 +81,8 @@ Then, after the above steps have completed, the Mainloop will iterate through al If a View need to redraw because something changed within it's Content Area it can call @Terminal.Gui.ViewBase.View.SetNeedsDraw. If a View needs to be redrawn because something has changed the size of the Viewport, it can call @Terminal.Gui.ViewBase.View.SetNeedsLayout. +**Note**: Calling `SetNeedsDraw()` does not immediately cause drawing to occur. It marks the view as needing to be redrawn, which will happen in the next MainLoop iteration. To force immediate drawing (typically only needed in tests), call @Terminal.Gui.App.Application.LayoutAndDraw. + ## Clipping Clipping enables better performance and features like transparent margins by ensuring regions of the terminal that need to be drawn actually get drawn by the driver. Terminal.Gui supports non-rectangular clip regions with @Terminal.Gui.Drawing.Region. The driver.Clip is the application managed clip region and is managed by @Terminal.Gui.App.Application. Developers cannot change this directly, but can use @Terminal.Gui.ViewBase.View.SetClipToScreen, @Terminal.Gui.ViewBase.View.SetClip(Terminal.Gui.Drawing.Region), @Terminal.Gui.ViewBase.View.SetClipToFrame, etc... @@ -98,6 +121,54 @@ SetAttributeForRole (VisualRole.Focus); AddStr ("Red on Black Underlined."); ``` +## Color + +Terminal.Gui supports 24-bit true color (16.7 million colors) via the @Terminal.Gui.Drawing.Color struct. The @Terminal.Gui.Drawing.Color struct represents colors in ARGB32 format, with separate bytes for Alpha (transparency), Red, Green, and Blue components. + +### Standard Colors (W3C+) + +Terminal.Gui provides comprehensive support for W3C standard color names plus additional common terminal colors via the @Terminal.Gui.Drawing.StandardColor enum. This includes all standard W3C colors (like `AliceBlue`, `Red`, `Tomato`, etc.) as well as classic terminal colors (like `AmberPhosphor`, `GreenPhosphor`). + +Colors can be created from standard color names: + +```cs +var color1 = new Color(StandardColor.CornflowerBlue); +var color2 = new Color(StandardColor.Tomato); +var color3 = new Color("Red"); // Case-insensitive color name parsing +``` + +Standard colors can also be parsed from strings: + +```cs +if (Color.TryParse("CornflowerBlue", out Color color)) +{ + // Use the color +} +``` + +### Alpha Channel and Transparency + +While @Terminal.Gui.Drawing.Color supports an alpha channel for transparency (values 0-255), **terminal rendering does not currently support alpha blending**. The alpha channel is primarily used to: + +- Indicate whether a color should be rendered at all (alpha = 0 means fully transparent/don't render) +- Support future transparency features +- Enable terminal background pass-through (see [#2381](https://github.com/gui-cs/Terminal.Gui/issues/2381) and [#4229](https://github.com/gui-cs/Terminal.Gui/issues/4229)) + +**Important**: When matching colors to standard color names, the alpha channel is **ignored**. This means `Color(255, 0, 0, 255)` (opaque red) and `Color(255, 0, 0, 128)` (semi-transparent red) will both be recognized as "Red". This design decision supports the vision of enabling transparent backgrounds while still being able to identify colors semantically. + +```cs +var opaqueRed = new Color(255, 0, 0, 255); +var transparentRed = new Color(255, 0, 0, 0); + +// Both will resolve to "Red" +ColorStrings.GetColorName(opaqueRed); // Returns "Red" +ColorStrings.GetColorName(transparentRed); // Returns "Red" +``` + +### Legacy 16-Color Support + +For backwards compatibility and terminals with limited color support, Terminal.Gui maintains the legacy 16-color system via @Terminal.Gui.Drawing.ColorName16. When true color is not available or when `Application.Force16Colors` is set, Terminal.Gui will map true colors to the nearest 16-color equivalent. + ## VisualRole Represents the semantic visual role of a visual element rendered by a View (e.g., Normal text, Focused item, Active selection).