浏览代码

Fixes #2800 - Color picker (supporting hsl, hsv and rgb) (#3604)

* Readonly HSL view

* Make it possible to move between bars by moving to subview

* Basically working and with mouse support

* Fix HSL to work properly with double values instead of color matching

* Fix Value on ColorPicker to match HSL values

* Fix color spectrum

* Add Swatch and better sync with text box

* Work on jitter

* ColorPicker HSL working

* More keybindings

* Add ColorModel

* Support both HSL and HSV

* Add RGB

* Better mouse handling

* WIP: AttributeView and integrate into LineDrawing
(does not currently work properly)

* Fix color picking

* Add concept of an ITool

* Add ColorPickerStyle

* Fix selected cell rendering

* Add first test for ColorPicker2

* Add more RGB tests

* Improve ColorPicker2 setup process

* Tests and fixes for keyboard changing value R

* Fix margin on bars when no textfields

* Add mouse test

* Add tests for with text field

* Add more tests and fix bug sync component text field change with hex text field

(WIP - failing tests)

* Fix tests and fix clicking in a bar label area possibly not selecting

* Move AttributeView to LineDrawing and adjust to have a 'transparent pattern' too

* Render triangle in dark gray if background is black

* Add ColorChanged event

* Resharper Cleanup

* Xml comments and public/private adjustments

* Explore replacing diagram test with fragile Subview diving

* Migrate ColorPicker_DefaultBoot to sub asserts

* Port other tests

* Replace ColorPicker with new view

* Fix ColorPicker size to match scenarios size assumptions

* Split to separate files and ignore invalid test for ColorPicker

* Ignore also in mouse version of AllViews_Enter_Leave_Events

* Remove bool _updating from ColorPicker

Now instead we are more selective about what we update when and do so deterministically

* Typo fix

* Fix ReSharper bad renames in comments for "Value"

* Refactor to single implementation of 'prompt for color' logic

- Now called PromptForColor
- Shared by LineDrawing and ProgressBarStyles scenarios

* Sum runes instead of Length

* Hide ColorBar and SetValueWithoutRaisingEvent from public API

* Move ColorEventArgs to Drawing folder

* Move ColorModel to Drawing folder

* First try at Dim.Auto for ColorPicker

* Remove explicit width/height setting in most scenarios

* Remove explicit heights

* Fixed build/test issues.
Illustrated test best practice.

* WIP: Start working on test changes and add new options to ColorPickers scenario (Color Model and show textfields).

* Fix for R indicator arrow sometimes 'falling off' the drawn area.

* Add nullable enable

* Test fixes and refactor for avoiding Begin

* Make ColorEventArgs inherit from EventArgs<Color>

* Fix Dispose not being called on bars when switching color models

* Remove 'oldColor' from test now it is not supported

* Add initial stab at ColorPickerStyle.ShowName

* Use AppendAutocomplete for color names

* Implemented resoruce based colorname resolver

* Update GetTextField to support getting the color names field
Change style setting to ShowColorName

* Color name updates when navigating away from the named color

* Restore old color picker as ColorPicker16

* Add test that shows 'Save as' is currently considered a named color ><

* Fix GetW3CColorNames

* Removed dupe colors

* Revert to old color pickers

* Nullability question marks for everyone!

---------

Co-authored-by: Tig <[email protected]>
Thomas Nind 11 月之前
父节点
当前提交
38f84f7424

+ 2 - 1
Terminal.Gui/Drawing/Color.cs

@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections.Frozen;
 using System.Diagnostics.Contracts;
+using System.Drawing;
 using System.Globalization;
 using System.Numerics;
 using System.Runtime.CompilerServices;
@@ -331,4 +332,4 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
     public const ColorName White = ColorName.White;
 
     #endregion
-}
+}

+ 11 - 0
Terminal.Gui/Drawing/ColorEventArgs.cs

@@ -0,0 +1,11 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>Event arguments for the <see cref="Color"/> events.</summary>
+public class ColorEventArgs : EventArgs<Color>
+{
+    /// <summary>Initializes a new instance of <see cref="ColorEventArgs"/>
+    /// <paramref name="newColor"/>The value that is being changed to.</summary>
+    public ColorEventArgs (Color newColor) :base(newColor) { }
+}

+ 25 - 0
Terminal.Gui/Drawing/ColorModel.cs

@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Describes away of modelling color e.g. Hue
+/// Saturation Lightness.
+/// </summary>
+public enum ColorModel
+{
+    /// <summary>
+    /// Color modelled by storing Red, Green and Blue as (0-255) ints
+    /// </summary>
+    RGB,
+
+    /// <summary>
+    /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Value (100%)
+    /// </summary>
+    HSV,
+
+    /// <summary>
+    /// Color modelled by storing Hue (360 degrees), Saturation (100%) and Lightness (100%)
+    /// </summary>
+    HSL
+}

+ 78 - 0
Terminal.Gui/Drawing/ColorStrings.cs

@@ -0,0 +1,78 @@
+#nullable enable
+using System.Collections;
+using System.Globalization;
+using System.Resources;
+using Terminal.Gui.Resources;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Provides a mapping between <see cref="Color"/> and the W3C standard color name strings.
+/// </summary>
+public static class ColorStrings
+{
+    private static readonly ResourceManager _resourceManager = new (typeof (Strings));
+
+    /// <summary>
+    ///     Gets the W3C standard string for <paramref name="color"/>.
+    /// </summary>
+    /// <param name="color">The color.</param>
+    /// <returns><see langword="null"/> if there is no standard color name for the specified color.</returns>
+    public static string? GetW3CColorName (Color color)
+    {
+        // Fetch the color name from the resource file
+        return _resourceManager.GetString ($"#{color.R:X2}{color.G:X2}{color.B:X2}", CultureInfo.CurrentCulture);
+    }
+
+    /// <summary>
+    ///     Returns the list of W3C standard color names.
+    /// </summary>
+    /// <returns></returns>
+    public static IEnumerable<string> GetW3CColorNames ()
+    {
+        foreach (DictionaryEntry entry in _resourceManager.GetResourceSet (CultureInfo.CurrentCulture, true, true)!)
+        {
+            string keyName = entry.Key.ToString () ?? string.Empty;
+
+            if (entry.Value is string colorName && keyName.StartsWith ('#'))
+            {
+                yield return colorName;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Parses <paramref name="name"/> and returns <paramref name="color"/> if name is a W3C standard named color.
+    /// </summary>
+    /// <param name="name">The name to parse.</param>
+    /// <param name="color">If successful, the color.</param>
+    /// <returns><see langword="true"/> if <paramref name="name"/> was parsed successfully.</returns>
+    public static bool TryParseW3CColorName (string name, out Color color)
+    {
+        // Iterate through all resource entries to find the matching color name
+        foreach (DictionaryEntry entry in _resourceManager.GetResourceSet (CultureInfo.CurrentCulture, true, true)!)
+        {
+            if (entry.Value is string colorName && colorName.Equals (name, StringComparison.OrdinalIgnoreCase))
+            {
+                // Parse the key to extract the color components
+                string key = entry.Key.ToString () ?? string.Empty;
+
+                if (key.StartsWith ("#") && key.Length == 7)
+                {
+                    if (int.TryParse (key.Substring (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r)
+                        && int.TryParse (key.Substring (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g)
+                        && int.TryParse (key.Substring (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
+                    {
+                        color = new (r, g, b);
+
+                        return true;
+                    }
+                }
+            }
+        }
+
+        color = default (Color);
+
+        return false;
+    }
+}

+ 34 - 0
Terminal.Gui/Drawing/IColorNameResolver.cs

@@ -0,0 +1,34 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     When implemented by a class, allows mapping <see cref="Color"/> to
+///     human understandable name (e.g. w3c color names) and vice versa.
+/// </summary>
+public interface IColorNameResolver
+{
+    /// <summary>
+    ///     Returns the names of all known colors.
+    /// </summary>
+    /// <returns></returns>
+    IEnumerable<string> GetColorNames ();
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if <paramref name="color"/> is a recognized
+    ///     color. In which case <paramref name="name"/> will be the name of the color and
+    ///     return value will be true otherwise false.
+    /// </summary>
+    /// <param name="color"></param>
+    /// <param name="name"></param>
+    /// <returns></returns>
+    bool TryNameColor (Color color, out string name);
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if <paramref name="name"/> is a recognized
+    ///     color. In which case <paramref name="color"/> will be the color the name corresponds
+    ///     to otherwise returns false.
+    /// </summary>
+    /// <param name="name"></param>
+    /// <param name="color"></param>
+    /// <returns></returns>
+    bool TryParseColor (string name, out Color color);
+}

+ 24 - 0
Terminal.Gui/Drawing/W3CColors.cs

@@ -0,0 +1,24 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Helper class that resolves w3c color names to their hex values
+///     Based on https://www.w3schools.com/colors/color_tryit.asp
+/// </summary>
+public class W3CColors : IColorNameResolver
+{
+    /// <inheritdoc/>
+    public IEnumerable<string> GetColorNames () { return ColorStrings.GetW3CColorNames (); }
+
+    /// <inheritdoc/>
+    public bool TryParseColor (string name, out Color color) { return ColorStrings.TryParseW3CColorName (name, out color); }
+
+    /// <inheritdoc/>
+    public bool TryNameColor (Color color, out string name)
+    {
+        string answer = ColorStrings.GetW3CColorName (color);
+
+        name = answer ?? string.Empty;
+
+        return answer != null;
+    }
+}

+ 1251 - 0
Terminal.Gui/Resources/Strings.Designer.cs

@@ -60,6 +60,1257 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Black.
+        /// </summary>
+        internal static string _000000 {
+            get {
+                return ResourceManager.GetString("#000000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Navy.
+        /// </summary>
+        internal static string _000080 {
+            get {
+                return ResourceManager.GetString("#000080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkBlue.
+        /// </summary>
+        internal static string _00008B {
+            get {
+                return ResourceManager.GetString("#00008B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumBlue.
+        /// </summary>
+        internal static string _0000CD {
+            get {
+                return ResourceManager.GetString("#0000CD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Blue.
+        /// </summary>
+        internal static string _0000FF {
+            get {
+                return ResourceManager.GetString("#0000FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGreen.
+        /// </summary>
+        internal static string _006400 {
+            get {
+                return ResourceManager.GetString("#006400", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Green.
+        /// </summary>
+        internal static string _008000 {
+            get {
+                return ResourceManager.GetString("#008000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Teal.
+        /// </summary>
+        internal static string _008080 {
+            get {
+                return ResourceManager.GetString("#008080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkCyan.
+        /// </summary>
+        internal static string _008B8B {
+            get {
+                return ResourceManager.GetString("#008B8B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DeepSkyBlue.
+        /// </summary>
+        internal static string _00BFFF {
+            get {
+                return ResourceManager.GetString("#00BFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkTurquoise.
+        /// </summary>
+        internal static string _00CED1 {
+            get {
+                return ResourceManager.GetString("#00CED1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSpringGreen.
+        /// </summary>
+        internal static string _00FA9A {
+            get {
+                return ResourceManager.GetString("#00FA9A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Lime.
+        /// </summary>
+        internal static string _00FF00 {
+            get {
+                return ResourceManager.GetString("#00FF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SpringGreen.
+        /// </summary>
+        internal static string _00FF7F {
+            get {
+                return ResourceManager.GetString("#00FF7F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cyan.
+        /// </summary>
+        internal static string _00FFFF {
+            get {
+                return ResourceManager.GetString("#00FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MidnightBlue.
+        /// </summary>
+        internal static string _191970 {
+            get {
+                return ResourceManager.GetString("#191970", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DodgerBlue.
+        /// </summary>
+        internal static string _1E90FF {
+            get {
+                return ResourceManager.GetString("#1E90FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSeaGreen.
+        /// </summary>
+        internal static string _20B2AA {
+            get {
+                return ResourceManager.GetString("#20B2AA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to ForestGreen.
+        /// </summary>
+        internal static string _228B22 {
+            get {
+                return ResourceManager.GetString("#228B22", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SeaGreen.
+        /// </summary>
+        internal static string _2E8B57 {
+            get {
+                return ResourceManager.GetString("#2E8B57", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSlateGrey.
+        /// </summary>
+        internal static string _2F4F4F {
+            get {
+                return ResourceManager.GetString("#2F4F4F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LimeGreen.
+        /// </summary>
+        internal static string _32CD32 {
+            get {
+                return ResourceManager.GetString("#32CD32", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSeaGreen.
+        /// </summary>
+        internal static string _3CB371 {
+            get {
+                return ResourceManager.GetString("#3CB371", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Turquoise.
+        /// </summary>
+        internal static string _40E0D0 {
+            get {
+                return ResourceManager.GetString("#40E0D0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RoyalBlue.
+        /// </summary>
+        internal static string _4169E1 {
+            get {
+                return ResourceManager.GetString("#4169E1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SteelBlue.
+        /// </summary>
+        internal static string _4682B4 {
+            get {
+                return ResourceManager.GetString("#4682B4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSlateBlue.
+        /// </summary>
+        internal static string _483D8B {
+            get {
+                return ResourceManager.GetString("#483D8B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumTurquoise.
+        /// </summary>
+        internal static string _48D1CC {
+            get {
+                return ResourceManager.GetString("#48D1CC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Indigo.
+        /// </summary>
+        internal static string _4B0082 {
+            get {
+                return ResourceManager.GetString("#4B0082", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOliveGreen.
+        /// </summary>
+        internal static string _556B2F {
+            get {
+                return ResourceManager.GetString("#556B2F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CadetBlue.
+        /// </summary>
+        internal static string _5F9EA0 {
+            get {
+                return ResourceManager.GetString("#5F9EA0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CornflowerBlue.
+        /// </summary>
+        internal static string _6495ED {
+            get {
+                return ResourceManager.GetString("#6495ED", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RebeccaPurple.
+        /// </summary>
+        internal static string _663399 {
+            get {
+                return ResourceManager.GetString("#663399", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumAquaMarine.
+        /// </summary>
+        internal static string _66CDAA {
+            get {
+                return ResourceManager.GetString("#66CDAA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DimGray.
+        /// </summary>
+        internal static string _696969 {
+            get {
+                return ResourceManager.GetString("#696969", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SlateBlue.
+        /// </summary>
+        internal static string _6A5ACD {
+            get {
+                return ResourceManager.GetString("#6A5ACD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OliveDrab.
+        /// </summary>
+        internal static string _6B8E23 {
+            get {
+                return ResourceManager.GetString("#6B8E23", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SlateGray.
+        /// </summary>
+        internal static string _708090 {
+            get {
+                return ResourceManager.GetString("#708090", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSlateGrey.
+        /// </summary>
+        internal static string _778899 {
+            get {
+                return ResourceManager.GetString("#778899", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumSlateBlue.
+        /// </summary>
+        internal static string _7B68EE {
+            get {
+                return ResourceManager.GetString("#7B68EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LawnGreen.
+        /// </summary>
+        internal static string _7CFC00 {
+            get {
+                return ResourceManager.GetString("#7CFC00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Chartreuse.
+        /// </summary>
+        internal static string _7FFF00 {
+            get {
+                return ResourceManager.GetString("#7FFF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Aquamarine.
+        /// </summary>
+        internal static string _7FFFD4 {
+            get {
+                return ResourceManager.GetString("#7FFFD4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Maroon.
+        /// </summary>
+        internal static string _800000 {
+            get {
+                return ResourceManager.GetString("#800000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Purple.
+        /// </summary>
+        internal static string _800080 {
+            get {
+                return ResourceManager.GetString("#800080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Olive.
+        /// </summary>
+        internal static string _808000 {
+            get {
+                return ResourceManager.GetString("#808000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gray.
+        /// </summary>
+        internal static string _808080 {
+            get {
+                return ResourceManager.GetString("#808080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SkyBlue.
+        /// </summary>
+        internal static string _87CEEB {
+            get {
+                return ResourceManager.GetString("#87CEEB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSkyBlue.
+        /// </summary>
+        internal static string _87CEFA {
+            get {
+                return ResourceManager.GetString("#87CEFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BlueViolet.
+        /// </summary>
+        internal static string _8A2BE2 {
+            get {
+                return ResourceManager.GetString("#8A2BE2", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkRed.
+        /// </summary>
+        internal static string _8B0000 {
+            get {
+                return ResourceManager.GetString("#8B0000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkMagenta.
+        /// </summary>
+        internal static string _8B008B {
+            get {
+                return ResourceManager.GetString("#8B008B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SaddleBrown.
+        /// </summary>
+        internal static string _8B4513 {
+            get {
+                return ResourceManager.GetString("#8B4513", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSeaGreen.
+        /// </summary>
+        internal static string _8FBC8F {
+            get {
+                return ResourceManager.GetString("#8FBC8F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGreen.
+        /// </summary>
+        internal static string _90EE90 {
+            get {
+                return ResourceManager.GetString("#90EE90", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumPurple.
+        /// </summary>
+        internal static string _9370DB {
+            get {
+                return ResourceManager.GetString("#9370DB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkViolet.
+        /// </summary>
+        internal static string _9400D3 {
+            get {
+                return ResourceManager.GetString("#9400D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleGreen.
+        /// </summary>
+        internal static string _98FB98 {
+            get {
+                return ResourceManager.GetString("#98FB98", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOrchid.
+        /// </summary>
+        internal static string _9932CC {
+            get {
+                return ResourceManager.GetString("#9932CC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to YellowGreen.
+        /// </summary>
+        internal static string _9ACD32 {
+            get {
+                return ResourceManager.GetString("#9ACD32", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Sienna.
+        /// </summary>
+        internal static string _A0522D {
+            get {
+                return ResourceManager.GetString("#A0522D", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Brown.
+        /// </summary>
+        internal static string _A52A2A {
+            get {
+                return ResourceManager.GetString("#A52A2A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGrey.
+        /// </summary>
+        internal static string _A9A9A9 {
+            get {
+                return ResourceManager.GetString("#A9A9A9", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightBlue.
+        /// </summary>
+        internal static string _ADD8E6 {
+            get {
+                return ResourceManager.GetString("#ADD8E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GreenYellow.
+        /// </summary>
+        internal static string _ADFF2F {
+            get {
+                return ResourceManager.GetString("#ADFF2F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleTurquoise.
+        /// </summary>
+        internal static string _AFEEEE {
+            get {
+                return ResourceManager.GetString("#AFEEEE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSteelBlue.
+        /// </summary>
+        internal static string _B0C4DE {
+            get {
+                return ResourceManager.GetString("#B0C4DE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PowderBlue.
+        /// </summary>
+        internal static string _B0E0E6 {
+            get {
+                return ResourceManager.GetString("#B0E0E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to FireBrick.
+        /// </summary>
+        internal static string _B22222 {
+            get {
+                return ResourceManager.GetString("#B22222", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkGoldenRod.
+        /// </summary>
+        internal static string _B8860B {
+            get {
+                return ResourceManager.GetString("#B8860B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumOrchid.
+        /// </summary>
+        internal static string _BA55D3 {
+            get {
+                return ResourceManager.GetString("#BA55D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to RosyBrown.
+        /// </summary>
+        internal static string _BC8F8F {
+            get {
+                return ResourceManager.GetString("#BC8F8F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkKhaki.
+        /// </summary>
+        internal static string _BDB76B {
+            get {
+                return ResourceManager.GetString("#BDB76B", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Silver.
+        /// </summary>
+        internal static string _C0C0C0 {
+            get {
+                return ResourceManager.GetString("#C0C0C0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MediumVioletRed.
+        /// </summary>
+        internal static string _C71585 {
+            get {
+                return ResourceManager.GetString("#C71585", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to IndianRed.
+        /// </summary>
+        internal static string _CD5C5C {
+            get {
+                return ResourceManager.GetString("#CD5C5C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Peru.
+        /// </summary>
+        internal static string _CD853F {
+            get {
+                return ResourceManager.GetString("#CD853F", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Chocolate.
+        /// </summary>
+        internal static string _D2691E {
+            get {
+                return ResourceManager.GetString("#D2691E", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tan.
+        /// </summary>
+        internal static string _D2B48C {
+            get {
+                return ResourceManager.GetString("#D2B48C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGray.
+        /// </summary>
+        internal static string _D3D3D3 {
+            get {
+                return ResourceManager.GetString("#D3D3D3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Thistle.
+        /// </summary>
+        internal static string _D8BFD8 {
+            get {
+                return ResourceManager.GetString("#D8BFD8", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Orchid.
+        /// </summary>
+        internal static string _DA70D6 {
+            get {
+                return ResourceManager.GetString("#DA70D6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GoldenRod.
+        /// </summary>
+        internal static string _DAA520 {
+            get {
+                return ResourceManager.GetString("#DAA520", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleVioletRed.
+        /// </summary>
+        internal static string _DB7093 {
+            get {
+                return ResourceManager.GetString("#DB7093", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Crimson.
+        /// </summary>
+        internal static string _DC143C {
+            get {
+                return ResourceManager.GetString("#DC143C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gainsboro.
+        /// </summary>
+        internal static string _DCDCDC {
+            get {
+                return ResourceManager.GetString("#DCDCDC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Plum.
+        /// </summary>
+        internal static string _DDA0DD {
+            get {
+                return ResourceManager.GetString("#DDA0DD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BurlyWood.
+        /// </summary>
+        internal static string _DEB887 {
+            get {
+                return ResourceManager.GetString("#DEB887", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightCyan.
+        /// </summary>
+        internal static string _E0FFFF {
+            get {
+                return ResourceManager.GetString("#E0FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Lavender.
+        /// </summary>
+        internal static string _E6E6FA {
+            get {
+                return ResourceManager.GetString("#E6E6FA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkSalmon.
+        /// </summary>
+        internal static string _E9967A {
+            get {
+                return ResourceManager.GetString("#E9967A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Violet.
+        /// </summary>
+        internal static string _EE82EE {
+            get {
+                return ResourceManager.GetString("#EE82EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PaleGoldenRod.
+        /// </summary>
+        internal static string _EEE8AA {
+            get {
+                return ResourceManager.GetString("#EEE8AA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightCoral.
+        /// </summary>
+        internal static string _F08080 {
+            get {
+                return ResourceManager.GetString("#F08080", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Khaki.
+        /// </summary>
+        internal static string _F0E68C {
+            get {
+                return ResourceManager.GetString("#F0E68C", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to AliceBlue.
+        /// </summary>
+        internal static string _F0F8FF {
+            get {
+                return ResourceManager.GetString("#F0F8FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to HoneyDew.
+        /// </summary>
+        internal static string _F0FFF0 {
+            get {
+                return ResourceManager.GetString("#F0FFF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Azure.
+        /// </summary>
+        internal static string _F0FFFF {
+            get {
+                return ResourceManager.GetString("#F0FFFF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SandyBrown.
+        /// </summary>
+        internal static string _F4A460 {
+            get {
+                return ResourceManager.GetString("#F4A460", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Wheat.
+        /// </summary>
+        internal static string _F5DEB3 {
+            get {
+                return ResourceManager.GetString("#F5DEB3", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Beige.
+        /// </summary>
+        internal static string _F5F5DC {
+            get {
+                return ResourceManager.GetString("#F5F5DC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to WhiteSmoke.
+        /// </summary>
+        internal static string _F5F5F5 {
+            get {
+                return ResourceManager.GetString("#F5F5F5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MintCream.
+        /// </summary>
+        internal static string _F5FFFA {
+            get {
+                return ResourceManager.GetString("#F5FFFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to GhostWhite.
+        /// </summary>
+        internal static string _F8F8FF {
+            get {
+                return ResourceManager.GetString("#F8F8FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Salmon.
+        /// </summary>
+        internal static string _FA8072 {
+            get {
+                return ResourceManager.GetString("#FA8072", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to AntiqueWhite.
+        /// </summary>
+        internal static string _FAEBD7 {
+            get {
+                return ResourceManager.GetString("#FAEBD7", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Linen.
+        /// </summary>
+        internal static string _FAF0E6 {
+            get {
+                return ResourceManager.GetString("#FAF0E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightGoldenRodYellow.
+        /// </summary>
+        internal static string _FAFAD2 {
+            get {
+                return ResourceManager.GetString("#FAFAD2", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OldLace.
+        /// </summary>
+        internal static string _FDF5E6 {
+            get {
+                return ResourceManager.GetString("#FDF5E6", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Red.
+        /// </summary>
+        internal static string _FF0000 {
+            get {
+                return ResourceManager.GetString("#FF0000", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Magenta.
+        /// </summary>
+        internal static string _FF00FF {
+            get {
+                return ResourceManager.GetString("#FF00FF", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DeepPink.
+        /// </summary>
+        internal static string _FF1493 {
+            get {
+                return ResourceManager.GetString("#FF1493", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to OrangeRed.
+        /// </summary>
+        internal static string _FF4500 {
+            get {
+                return ResourceManager.GetString("#FF4500", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Tomato.
+        /// </summary>
+        internal static string _FF6347 {
+            get {
+                return ResourceManager.GetString("#FF6347", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to HotPink.
+        /// </summary>
+        internal static string _FF69B4 {
+            get {
+                return ResourceManager.GetString("#FF69B4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Coral.
+        /// </summary>
+        internal static string _FF7F50 {
+            get {
+                return ResourceManager.GetString("#FF7F50", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to DarkOrange.
+        /// </summary>
+        internal static string _FF8C00 {
+            get {
+                return ResourceManager.GetString("#FF8C00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightSalmon.
+        /// </summary>
+        internal static string _FFA07A {
+            get {
+                return ResourceManager.GetString("#FFA07A", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Orange.
+        /// </summary>
+        internal static string _FFA500 {
+            get {
+                return ResourceManager.GetString("#FFA500", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightPink.
+        /// </summary>
+        internal static string _FFB6C1 {
+            get {
+                return ResourceManager.GetString("#FFB6C1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Pink.
+        /// </summary>
+        internal static string _FFC0CB {
+            get {
+                return ResourceManager.GetString("#FFC0CB", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Gold.
+        /// </summary>
+        internal static string _FFD700 {
+            get {
+                return ResourceManager.GetString("#FFD700", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PeachPuff.
+        /// </summary>
+        internal static string _FFDAB9 {
+            get {
+                return ResourceManager.GetString("#FFDAB9", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to NavajoWhite.
+        /// </summary>
+        internal static string _FFDEAD {
+            get {
+                return ResourceManager.GetString("#FFDEAD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Moccasin.
+        /// </summary>
+        internal static string _FFE4B5 {
+            get {
+                return ResourceManager.GetString("#FFE4B5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Bisque.
+        /// </summary>
+        internal static string _FFE4C4 {
+            get {
+                return ResourceManager.GetString("#FFE4C4", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to MistyRose.
+        /// </summary>
+        internal static string _FFE4E1 {
+            get {
+                return ResourceManager.GetString("#FFE4E1", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to BlanchedAlmond.
+        /// </summary>
+        internal static string _FFEBCD {
+            get {
+                return ResourceManager.GetString("#FFEBCD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to PapayaWhip.
+        /// </summary>
+        internal static string _FFEFD5 {
+            get {
+                return ResourceManager.GetString("#FFEFD5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LavenderBlush.
+        /// </summary>
+        internal static string _FFF0F5 {
+            get {
+                return ResourceManager.GetString("#FFF0F5", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to SeaShell.
+        /// </summary>
+        internal static string _FFF5EE {
+            get {
+                return ResourceManager.GetString("#FFF5EE", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Cornsilk.
+        /// </summary>
+        internal static string _FFF8DC {
+            get {
+                return ResourceManager.GetString("#FFF8DC", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LemonChiffon.
+        /// </summary>
+        internal static string _FFFACD {
+            get {
+                return ResourceManager.GetString("#FFFACD", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to FloralWhite.
+        /// </summary>
+        internal static string _FFFAF0 {
+            get {
+                return ResourceManager.GetString("#FFFAF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Snow.
+        /// </summary>
+        internal static string _FFFAFA {
+            get {
+                return ResourceManager.GetString("#FFFAFA", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Yellow.
+        /// </summary>
+        internal static string _FFFF00 {
+            get {
+                return ResourceManager.GetString("#FFFF00", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to LightYellow.
+        /// </summary>
+        internal static string _FFFFE0 {
+            get {
+                return ResourceManager.GetString("#FFFFE0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Ivory.
+        /// </summary>
+        internal static string _FFFFF0 {
+            get {
+                return ResourceManager.GetString("#FFFFF0", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to White.
+        /// </summary>
+        internal static string _FFFFFF {
+            get {
+                return ResourceManager.GetString("#FFFFFF", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to _Cancel.
         /// </summary>

+ 527 - 110
Terminal.Gui/Resources/Strings.resx

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!-- 
+    <!-- 
     Microsoft ResX Schema 
     
     Version 2.0
@@ -59,225 +59,642 @@
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
-  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
-    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
-    <xsd:element name="root" msdata:IsDataSet="true">
-      <xsd:complexType>
-        <xsd:choice maxOccurs="unbounded">
-          <xsd:element name="metadata">
+    <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+        <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+        <xsd:element name="root" msdata:IsDataSet="true">
             <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" />
-              </xsd:sequence>
-              <xsd:attribute name="name" use="required" type="xsd:string" />
-              <xsd:attribute name="type" type="xsd:string" />
-              <xsd:attribute name="mimetype" type="xsd:string" />
-              <xsd:attribute ref="xml:space" />
+                <xsd:choice maxOccurs="unbounded">
+                    <xsd:element name="metadata">
+                        <xsd:complexType>
+                            <xsd:sequence>
+                                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+                            </xsd:sequence>
+                            <xsd:attribute name="name" use="required" type="xsd:string" />
+                            <xsd:attribute name="type" type="xsd:string" />
+                            <xsd:attribute name="mimetype" type="xsd:string" />
+                            <xsd:attribute ref="xml:space" />
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="assembly">
+                        <xsd:complexType>
+                            <xsd:attribute name="alias" type="xsd:string" />
+                            <xsd:attribute name="name" type="xsd:string" />
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="data">
+                        <xsd:complexType>
+                            <xsd:sequence>
+                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+                            </xsd:sequence>
+                            <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+                            <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+                            <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+                            <xsd:attribute ref="xml:space" />
+                        </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="resheader">
+                        <xsd:complexType>
+                            <xsd:sequence>
+                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                            </xsd:sequence>
+                            <xsd:attribute name="name" type="xsd:string" use="required" />
+                        </xsd:complexType>
+                    </xsd:element>
+                </xsd:choice>
             </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="assembly">
-            <xsd:complexType>
-              <xsd:attribute name="alias" type="xsd:string" />
-              <xsd:attribute name="name" type="xsd:string" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="data">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
-              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
-              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
-              <xsd:attribute ref="xml:space" />
-            </xsd:complexType>
-          </xsd:element>
-          <xsd:element name="resheader">
-            <xsd:complexType>
-              <xsd:sequence>
-                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
-              </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" use="required" />
-            </xsd:complexType>
-          </xsd:element>
-        </xsd:choice>
-      </xsd:complexType>
-    </xsd:element>
-  </xsd:schema>
-  <resheader name="resmimetype">
-    <value>text/microsoft-resx</value>
-  </resheader>
-  <resheader name="version">
-    <value>2.0</value>
-  </resheader>
-  <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </resheader>
-  <data name="ctxSelectAll" xml:space="preserve">
+        </xsd:element>
+    </xsd:schema>
+    <resheader name="resmimetype">
+        <value>text/microsoft-resx</value>
+    </resheader>
+    <resheader name="version">
+        <value>2.0</value>
+    </resheader>
+    <resheader name="reader">
+        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    </resheader>
+    <resheader name="writer">
+        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    </resheader>
+    <data name="ctxSelectAll" xml:space="preserve">
     <value>_Select All</value>
   </data>
-  <data name="ctxDeleteAll" xml:space="preserve">
+    <data name="ctxDeleteAll" xml:space="preserve">
     <value>_Delete All</value>
   </data>
-  <data name="ctxCopy" xml:space="preserve">
+    <data name="ctxCopy" xml:space="preserve">
     <value>_Copy</value>
   </data>
-  <data name="ctxCut" xml:space="preserve">
+    <data name="ctxCut" xml:space="preserve">
     <value>Cu_t</value>
   </data>
-  <data name="ctxPaste" xml:space="preserve">
+    <data name="ctxPaste" xml:space="preserve">
     <value>_Paste</value>
   </data>
-  <data name="ctxUndo" xml:space="preserve">
+    <data name="ctxUndo" xml:space="preserve">
     <value>_Undo</value>
   </data>
-  <data name="ctxRedo" xml:space="preserve">
+    <data name="ctxRedo" xml:space="preserve">
     <value>_Redo</value>
   </data>
-  <data name="fdDirectory" xml:space="preserve">
+    <data name="fdDirectory" xml:space="preserve">
     <value>Directory</value>
   </data>
-  <data name="fdFile" xml:space="preserve">
+    <data name="fdFile" xml:space="preserve">
     <value>File</value>
   </data>
-  <data name="fdSave" xml:space="preserve">
+    <data name="fdSave" xml:space="preserve">
     <value>Save</value>
   </data>
-  <data name="fdSaveAs" xml:space="preserve">
+    <data name="fdSaveAs" xml:space="preserve">
     <value>Save as</value>
   </data>
-  <data name="fdOpen" xml:space="preserve">
+    <data name="fdOpen" xml:space="preserve">
     <value>Open</value>
   </data>
-  <data name="fdSelectFolder" xml:space="preserve">
+    <data name="fdSelectFolder" xml:space="preserve">
     <value>Select folder</value>
   </data>
-  <data name="fdSelectMixed" xml:space="preserve">
+    <data name="fdSelectMixed" xml:space="preserve">
     <value>Select Mixed</value>
   </data>
-  <data name="wzBack" xml:space="preserve">
+    <data name="wzBack" xml:space="preserve">
     <value>_Back</value>
   </data>
-  <data name="wzFinish" xml:space="preserve">
+    <data name="wzFinish" xml:space="preserve">
     <value>Fi_nish</value>
   </data>
-  <data name="wzNext" xml:space="preserve">
+    <data name="wzNext" xml:space="preserve">
     <value>_Next...</value>
   </data>
-  <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
+    <data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
     <value>Directory already exists with that name</value>
     <comment>When trying to save a file with a name already taken by a directory</comment>
   </data>
-  <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
+    <data name="fdDirectoryMustExistFeedback" xml:space="preserve">
     <value>Must select an existing directory</value>
   </data>
-  <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
+    <data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
     <value>File already exists with that name</value>
   </data>
-  <data name="fdFileMustExistFeedback" xml:space="preserve">
+    <data name="fdFileMustExistFeedback" xml:space="preserve">
     <value>Must select an existing file</value>
     <comment>When trying to save a directory with a name already used by a file</comment>
   </data>
-  <data name="fdFilename" xml:space="preserve">
+    <data name="fdFilename" xml:space="preserve">
     <value>Filename</value>
   </data>
-  <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
+    <data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
     <value>Must select an existing file or directory</value>
   </data>
-  <data name="fdModified" xml:space="preserve">
+    <data name="fdModified" xml:space="preserve">
     <value>Modified</value>
   </data>
-  <data name="fdPathCaption" xml:space="preserve">
+    <data name="fdPathCaption" xml:space="preserve">
     <value>Enter Path</value>
   </data>
-  <data name="fdSearchCaption" xml:space="preserve">
+    <data name="fdSearchCaption" xml:space="preserve">
     <value>Enter Search</value>
   </data>
-  <data name="fdSize" xml:space="preserve">
+    <data name="fdSize" xml:space="preserve">
     <value>Size</value>
   </data>
-  <data name="fdType" xml:space="preserve">
+    <data name="fdType" xml:space="preserve">
     <value>Type</value>
   </data>
-  <data name="fdWrongFileTypeFeedback" xml:space="preserve">
+    <data name="fdWrongFileTypeFeedback" xml:space="preserve">
     <value>Wrong file type</value>
     <comment>When trying to open/save a file that does not match the provided filter (e.g. csv)</comment>
   </data>
-  <data name="fdAnyFiles" xml:space="preserve">
+    <data name="fdAnyFiles" xml:space="preserve">
     <value>Any Files</value>
     <comment>Describes an AllowedType that matches anything</comment>
   </data>
-  <data name="fdDeleteBody" xml:space="preserve">
+    <data name="fdDeleteBody" xml:space="preserve">
     <value>Are you sure you want to delete '{0}'? This operation is permanent</value>
   </data>
-  <data name="fdDeleteFailedTitle" xml:space="preserve">
+    <data name="fdDeleteFailedTitle" xml:space="preserve">
     <value>Delete Failed</value>
   </data>
-  <data name="fdDeleteTitle" xml:space="preserve">
+    <data name="fdDeleteTitle" xml:space="preserve">
     <value>Delete {0}</value>
   </data>
-  <data name="fdNewFailed" xml:space="preserve">
+    <data name="fdNewFailed" xml:space="preserve">
     <value>New Failed</value>
   </data>
-  <data name="fdNewTitle" xml:space="preserve">
+    <data name="fdNewTitle" xml:space="preserve">
     <value>New Folder</value>
   </data>
-  <data name="btnNo" xml:space="preserve">
+    <data name="btnNo" xml:space="preserve">
     <value>_No</value>
   </data>
-  <data name="fdRenameFailedTitle" xml:space="preserve">
+    <data name="fdRenameFailedTitle" xml:space="preserve">
     <value>Rename Failed</value>
   </data>
-  <data name="fdRenamePrompt" xml:space="preserve">
+    <data name="fdRenamePrompt" xml:space="preserve">
     <value>Name:</value>
   </data>
-  <data name="fdRenameTitle" xml:space="preserve">
+    <data name="fdRenameTitle" xml:space="preserve">
     <value>Rename</value>
   </data>
-  <data name="btnYes" xml:space="preserve">
+    <data name="btnYes" xml:space="preserve">
     <value>_Yes</value>
   </data>
-  <data name="fdExisting" xml:space="preserve">
+    <data name="fdExisting" xml:space="preserve">
     <value>Existing</value>
   </data>
-  <data name="btnOpen" xml:space="preserve">
+    <data name="btnOpen" xml:space="preserve">
     <value>O_pen</value>
   </data>
-  <data name="btnSave" xml:space="preserve">
+    <data name="btnSave" xml:space="preserve">
     <value>_Save</value>
   </data>
-  <data name="btnSaveAs" xml:space="preserve">
+    <data name="btnSaveAs" xml:space="preserve">
     <value>Save _as</value>
   </data>
-  <data name="btnOk" xml:space="preserve">
+    <data name="btnOk" xml:space="preserve">
     <value>_OK</value>
   </data>
-  <data name="btnCancel" xml:space="preserve">
+    <data name="btnCancel" xml:space="preserve">
     <value>_Cancel</value>
   </data>
-  <data name="fdCtxDelete" xml:space="preserve">
+    <data name="fdCtxDelete" xml:space="preserve">
     <value>_Delete</value>
   </data>
-  <data name="fdCtxHide" xml:space="preserve">
+    <data name="fdCtxHide" xml:space="preserve">
     <value>_Hide {0}</value>
   </data>
-  <data name="fdCtxNew" xml:space="preserve">
+    <data name="fdCtxNew" xml:space="preserve">
     <value>_New</value>
   </data>
-  <data name="fdCtxRename" xml:space="preserve">
+    <data name="fdCtxRename" xml:space="preserve">
     <value>_Rename</value>
   </data>
-  <data name="fdCtxSortAsc" xml:space="preserve">
+    <data name="fdCtxSortAsc" xml:space="preserve">
     <value>_Sort {0} ASC</value>
   </data>
-  <data name="fdCtxSortDesc" xml:space="preserve">
+    <data name="fdCtxSortDesc" xml:space="preserve">
     <value>_Sort {0} DESC</value>
   </data>
-  <data name="dpTitle" xml:space="preserve">
+    <data name="dpTitle" xml:space="preserve">
     <value>Date Picker</value>
   </data>
+    <data name="#F0F8FF" xml:space="preserve">
+    <value>AliceBlue</value>
+  </data>
+    <data name="#FAEBD7" xml:space="preserve">
+    <value>AntiqueWhite</value>
+  </data>
+    <data name="#7FFFD4" xml:space="preserve">
+    <value>Aquamarine</value>
+  </data>
+    <data name="#F0FFFF" xml:space="preserve">
+    <value>Azure</value>
+  </data>
+    <data name="#F5F5DC" xml:space="preserve">
+    <value>Beige</value>
+  </data>
+    <data name="#FFE4C4" xml:space="preserve">
+    <value>Bisque</value>
+  </data>
+    <data name="#000000" xml:space="preserve">
+    <value>Black</value>
+  </data>
+    <data name="#FFEBCD" xml:space="preserve">
+    <value>BlanchedAlmond</value>
+  </data>
+    <data name="#0000FF" xml:space="preserve">
+    <value>Blue</value>
+  </data>
+    <data name="#8A2BE2" xml:space="preserve">
+    <value>BlueViolet</value>
+  </data>
+    <data name="#A52A2A" xml:space="preserve">
+    <value>Brown</value>
+  </data>
+    <data name="#DEB887" xml:space="preserve">
+    <value>BurlyWood</value>
+  </data>
+    <data name="#5F9EA0" xml:space="preserve">
+    <value>CadetBlue</value>
+  </data>
+    <data name="#7FFF00" xml:space="preserve">
+    <value>Chartreuse</value>
+  </data>
+    <data name="#D2691E" xml:space="preserve">
+    <value>Chocolate</value>
+  </data>
+    <data name="#FF7F50" xml:space="preserve">
+    <value>Coral</value>
+  </data>
+    <data name="#6495ED" xml:space="preserve">
+    <value>CornflowerBlue</value>
+  </data>
+    <data name="#FFF8DC" xml:space="preserve">
+    <value>Cornsilk</value>
+  </data>
+    <data name="#DC143C" xml:space="preserve">
+    <value>Crimson</value>
+  </data>
+    <data name="#00FFFF" xml:space="preserve">
+    <value>Cyan</value>
+  </data>
+    <data name="#00008B" xml:space="preserve">
+    <value>DarkBlue</value>
+  </data>
+    <data name="#008B8B" xml:space="preserve">
+    <value>DarkCyan</value>
+  </data>
+    <data name="#B8860B" xml:space="preserve">
+    <value>DarkGoldenRod</value>
+  </data>
+    <data name="#A9A9A9" xml:space="preserve">
+    <value>DarkGrey</value>
+  </data>
+    <data name="#006400" xml:space="preserve">
+    <value>DarkGreen</value>
+  </data>
+    <data name="#BDB76B" xml:space="preserve">
+    <value>DarkKhaki</value>
+  </data>
+    <data name="#8B008B" xml:space="preserve">
+    <value>DarkMagenta</value>
+  </data>
+    <data name="#556B2F" xml:space="preserve">
+    <value>DarkOliveGreen</value>
+  </data>
+    <data name="#FF8C00" xml:space="preserve">
+    <value>DarkOrange</value>
+  </data>
+    <data name="#9932CC" xml:space="preserve">
+    <value>DarkOrchid</value>
+  </data>
+    <data name="#8B0000" xml:space="preserve">
+    <value>DarkRed</value>
+  </data>
+    <data name="#E9967A" xml:space="preserve">
+    <value>DarkSalmon</value>
+  </data>
+    <data name="#8FBC8F" xml:space="preserve">
+    <value>DarkSeaGreen</value>
+  </data>
+    <data name="#483D8B" xml:space="preserve">
+    <value>DarkSlateBlue</value>
+  </data>
+    <data name="#2F4F4F" xml:space="preserve">
+    <value>DarkSlateGrey</value>
+  </data>
+    <data name="#00CED1" xml:space="preserve">
+    <value>DarkTurquoise</value>
+  </data>
+    <data name="#9400D3" xml:space="preserve">
+    <value>DarkViolet</value>
+  </data>
+    <data name="#FF1493" xml:space="preserve">
+    <value>DeepPink</value>
+  </data>
+    <data name="#00BFFF" xml:space="preserve">
+    <value>DeepSkyBlue</value>
+  </data>
+    <data name="#696969" xml:space="preserve">
+    <value>DimGray</value>
+  </data>
+    <data name="#1E90FF" xml:space="preserve">
+    <value>DodgerBlue</value>
+  </data>
+    <data name="#B22222" xml:space="preserve">
+    <value>FireBrick</value>
+  </data>
+    <data name="#FFFAF0" xml:space="preserve">
+    <value>FloralWhite</value>
+  </data>
+    <data name="#228B22" xml:space="preserve">
+    <value>ForestGreen</value>
+  </data>
+    <data name="#DCDCDC" xml:space="preserve">
+    <value>Gainsboro</value>
+  </data>
+    <data name="#F8F8FF" xml:space="preserve">
+    <value>GhostWhite</value>
+  </data>
+    <data name="#FFD700" xml:space="preserve">
+    <value>Gold</value>
+  </data>
+    <data name="#DAA520" xml:space="preserve">
+    <value>GoldenRod</value>
+  </data>
+    <data name="#808080" xml:space="preserve">
+    <value>Gray</value>
+  </data>
+    <data name="#008000" xml:space="preserve">
+    <value>Green</value>
+  </data>
+    <data name="#ADFF2F" xml:space="preserve">
+    <value>GreenYellow</value>
+  </data>
+    <data name="#F0FFF0" xml:space="preserve">
+    <value>HoneyDew</value>
+  </data>
+    <data name="#FF69B4" xml:space="preserve">
+    <value>HotPink</value>
+  </data>
+    <data name="#CD5C5C" xml:space="preserve">
+    <value>IndianRed</value>
+  </data>
+    <data name="#4B0082" xml:space="preserve">
+    <value>Indigo</value>
+  </data>
+    <data name="#FFFFF0" xml:space="preserve">
+    <value>Ivory</value>
+  </data>
+    <data name="#F0E68C" xml:space="preserve">
+    <value>Khaki</value>
+  </data>
+    <data name="#E6E6FA" xml:space="preserve">
+    <value>Lavender</value>
+  </data>
+    <data name="#FFF0F5" xml:space="preserve">
+    <value>LavenderBlush</value>
+  </data>
+    <data name="#7CFC00" xml:space="preserve">
+    <value>LawnGreen</value>
+  </data>
+    <data name="#FFFACD" xml:space="preserve">
+    <value>LemonChiffon</value>
+  </data>
+    <data name="#ADD8E6" xml:space="preserve">
+    <value>LightBlue</value>
+  </data>
+    <data name="#F08080" xml:space="preserve">
+    <value>LightCoral</value>
+  </data>
+    <data name="#E0FFFF" xml:space="preserve">
+    <value>LightCyan</value>
+  </data>
+    <data name="#FAFAD2" xml:space="preserve">
+    <value>LightGoldenRodYellow</value>
+  </data>
+    <data name="#D3D3D3" xml:space="preserve">
+    <value>LightGray</value>
+  </data>
+    <data name="#90EE90" xml:space="preserve">
+    <value>LightGreen</value>
+  </data>
+    <data name="#FFB6C1" xml:space="preserve">
+    <value>LightPink</value>
+  </data>
+    <data name="#FFA07A" xml:space="preserve">
+    <value>LightSalmon</value>
+  </data>
+    <data name="#20B2AA" xml:space="preserve">
+    <value>LightSeaGreen</value>
+  </data>
+    <data name="#87CEFA" xml:space="preserve">
+    <value>LightSkyBlue</value>
+  </data>
+    <data name="#778899" xml:space="preserve">
+    <value>LightSlateGrey</value>
+  </data>
+    <data name="#B0C4DE" xml:space="preserve">
+    <value>LightSteelBlue</value>
+  </data>
+    <data name="#FFFFE0" xml:space="preserve">
+    <value>LightYellow</value>
+  </data>
+    <data name="#00FF00" xml:space="preserve">
+    <value>Lime</value>
+  </data>
+    <data name="#32CD32" xml:space="preserve">
+    <value>LimeGreen</value>
+  </data>
+    <data name="#FAF0E6" xml:space="preserve">
+    <value>Linen</value>
+  </data>
+    <data name="#FF00FF" xml:space="preserve">
+    <value>Magenta</value>
+  </data>
+    <data name="#800000" xml:space="preserve">
+    <value>Maroon</value>
+  </data>
+    <data name="#66CDAA" xml:space="preserve">
+    <value>MediumAquaMarine</value>
+  </data>
+    <data name="#0000CD" xml:space="preserve">
+    <value>MediumBlue</value>
+  </data>
+    <data name="#BA55D3" xml:space="preserve">
+    <value>MediumOrchid</value>
+  </data>
+    <data name="#9370DB" xml:space="preserve">
+    <value>MediumPurple</value>
+  </data>
+    <data name="#3CB371" xml:space="preserve">
+    <value>MediumSeaGreen</value>
+  </data>
+    <data name="#7B68EE" xml:space="preserve">
+    <value>MediumSlateBlue</value>
+  </data>
+    <data name="#00FA9A" xml:space="preserve">
+    <value>MediumSpringGreen</value>
+  </data>
+    <data name="#48D1CC" xml:space="preserve">
+    <value>MediumTurquoise</value>
+  </data>
+    <data name="#C71585" xml:space="preserve">
+    <value>MediumVioletRed</value>
+  </data>
+    <data name="#191970" xml:space="preserve">
+    <value>MidnightBlue</value>
+  </data>
+    <data name="#F5FFFA" xml:space="preserve">
+    <value>MintCream</value>
+  </data>
+    <data name="#FFE4E1" xml:space="preserve">
+    <value>MistyRose</value>
+  </data>
+    <data name="#FFE4B5" xml:space="preserve">
+    <value>Moccasin</value>
+  </data>
+    <data name="#FFDEAD" xml:space="preserve">
+    <value>NavajoWhite</value>
+  </data>
+    <data name="#000080" xml:space="preserve">
+    <value>Navy</value>
+  </data>
+    <data name="#FDF5E6" xml:space="preserve">
+    <value>OldLace</value>
+  </data>
+    <data name="#808000" xml:space="preserve">
+    <value>Olive</value>
+  </data>
+    <data name="#6B8E23" xml:space="preserve">
+    <value>OliveDrab</value>
+  </data>
+    <data name="#FFA500" xml:space="preserve">
+    <value>Orange</value>
+  </data>
+    <data name="#FF4500" xml:space="preserve">
+    <value>OrangeRed</value>
+  </data>
+    <data name="#DA70D6" xml:space="preserve">
+    <value>Orchid</value>
+  </data>
+    <data name="#EEE8AA" xml:space="preserve">
+    <value>PaleGoldenRod</value>
+  </data>
+    <data name="#98FB98" xml:space="preserve">
+    <value>PaleGreen</value>
+  </data>
+    <data name="#AFEEEE" xml:space="preserve">
+    <value>PaleTurquoise</value>
+  </data>
+    <data name="#DB7093" xml:space="preserve">
+    <value>PaleVioletRed</value>
+  </data>
+    <data name="#FFEFD5" xml:space="preserve">
+    <value>PapayaWhip</value>
+  </data>
+    <data name="#FFDAB9" xml:space="preserve">
+    <value>PeachPuff</value>
+  </data>
+    <data name="#CD853F" xml:space="preserve">
+    <value>Peru</value>
+  </data>
+    <data name="#FFC0CB" xml:space="preserve">
+    <value>Pink</value>
+  </data>
+    <data name="#DDA0DD" xml:space="preserve">
+    <value>Plum</value>
+  </data>
+    <data name="#B0E0E6" xml:space="preserve">
+    <value>PowderBlue</value>
+  </data>
+    <data name="#800080" xml:space="preserve">
+    <value>Purple</value>
+  </data>
+    <data name="#663399" xml:space="preserve">
+    <value>RebeccaPurple</value>
+  </data>
+    <data name="#FF0000" xml:space="preserve">
+    <value>Red</value>
+  </data>
+    <data name="#BC8F8F" xml:space="preserve">
+    <value>RosyBrown</value>
+  </data>
+    <data name="#4169E1" xml:space="preserve">
+    <value>RoyalBlue</value>
+  </data>
+    <data name="#8B4513" xml:space="preserve">
+    <value>SaddleBrown</value>
+  </data>
+    <data name="#FA8072" xml:space="preserve">
+    <value>Salmon</value>
+  </data>
+    <data name="#F4A460" xml:space="preserve">
+    <value>SandyBrown</value>
+  </data>
+    <data name="#2E8B57" xml:space="preserve">
+    <value>SeaGreen</value>
+  </data>
+    <data name="#FFF5EE" xml:space="preserve">
+    <value>SeaShell</value>
+  </data>
+    <data name="#A0522D" xml:space="preserve">
+    <value>Sienna</value>
+  </data>
+    <data name="#C0C0C0" xml:space="preserve">
+    <value>Silver</value>
+  </data>
+    <data name="#87CEEB" xml:space="preserve">
+    <value>SkyBlue</value>
+  </data>
+    <data name="#6A5ACD" xml:space="preserve">
+    <value>SlateBlue</value>
+  </data>
+    <data name="#708090" xml:space="preserve">
+    <value>SlateGray</value>
+  </data>
+    <data name="#FFFAFA" xml:space="preserve">
+    <value>Snow</value>
+  </data>
+    <data name="#00FF7F" xml:space="preserve">
+    <value>SpringGreen</value>
+  </data>
+    <data name="#4682B4" xml:space="preserve">
+    <value>SteelBlue</value>
+  </data>
+    <data name="#D2B48C" xml:space="preserve">
+    <value>Tan</value>
+  </data>
+    <data name="#008080" xml:space="preserve">
+    <value>Teal</value>
+  </data>
+    <data name="#D8BFD8" xml:space="preserve">
+    <value>Thistle</value>
+  </data>
+    <data name="#FF6347" xml:space="preserve">
+    <value>Tomato</value>
+  </data>
+    <data name="#40E0D0" xml:space="preserve">
+    <value>Turquoise</value>
+  </data>
+    <data name="#EE82EE" xml:space="preserve">
+    <value>Violet</value>
+  </data>
+    <data name="#F5DEB3" xml:space="preserve">
+    <value>Wheat</value>
+  </data>
+    <data name="#FFFFFF" xml:space="preserve">
+    <value>White</value>
+  </data>
+    <data name="#F5F5F5" xml:space="preserve">
+    <value>WhiteSmoke</value>
+  </data>
+    <data name="#FFFF00" xml:space="preserve">
+    <value>Yellow</value>
+  </data>
+    <data name="#9ACD32" xml:space="preserve">
+    <value>YellowGreen</value>
+  </data>
 </root>

+ 27 - 0
Terminal.Gui/Views/BBar.cs

@@ -0,0 +1,27 @@
+#nullable enable
+
+using ColorHelper;
+
+namespace Terminal.Gui;
+
+internal class BBar : ColorBar
+{
+    public GBar? GBar { get; set; }
+    public RBar? RBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (RBar == null || GBar == null)
+        {
+            throw new ($"{nameof (BBar)} has not been set up correctly before drawing");
+        }
+
+        var rgb = new RGB ((byte)RBar.Value, (byte)GBar.Value, (byte)(MaxValue * fraction));
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 255;
+}

+ 235 - 0
Terminal.Gui/Views/ColorBar.cs

@@ -0,0 +1,235 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     A bar representing a single component of a <see cref="Color"/> e.g.
+///     the Red portion of a <see cref="ColorModel.RGB"/>.
+/// </summary>
+internal abstract class ColorBar : View, IColorBar
+{
+    /// <summary>
+    ///     Creates a new instance of the <see cref="ColorBar"/> class.
+    /// </summary>
+    protected ColorBar ()
+    {
+        Height = 1;
+        Width = Dim.Fill ();
+        CanFocus = true;
+
+        AddCommand (Command.Left, _ => Adjust (-1));
+        AddCommand (Command.Right, _ => Adjust (1));
+
+        AddCommand (Command.LeftExtend, _ => Adjust (-MaxValue / 20));
+        AddCommand (Command.RightExtend, _ => Adjust (MaxValue / 20));
+
+        AddCommand (Command.LeftHome, _ => SetZero ());
+        AddCommand (Command.RightEnd, _ => SetMax ());
+
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+        KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
+        KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
+        KeyBindings.Add (Key.Home, Command.LeftHome);
+        KeyBindings.Add (Key.End, Command.RightEnd);
+    }
+
+    /// <summary>
+    ///     X coordinate that the bar starts at excluding any label.
+    /// </summary>
+    private int _barStartsAt;
+
+    /// <summary>
+    ///     0-1 for how much of the color element is present currently (HSL)
+    /// </summary>
+    private int _value;
+
+    /// <summary>
+    ///     The amount of <see cref="Value"/> represented by each cell width on the bar
+    ///     Can be less than 1 e.g. if Saturation (0-100) and width > 100
+    /// </summary>
+    private double _cellValue = 1d;
+
+    /// <summary>
+    ///     Last known width of the bar as passed to <see cref="DrawBar"/>.
+    /// </summary>
+    private int _barWidth;
+
+    /// <summary>
+    ///     The currently selected amount of the color component stored by this class e.g.
+    ///     the amount of Hue in a <see cref="ColorModel.HSL"/>.
+    /// </summary>
+    public int Value
+    {
+        get => _value;
+        set
+        {
+            int clampedValue = Math.Clamp (value, 0, MaxValue);
+
+            if (_value != clampedValue)
+            {
+                _value = clampedValue;
+                OnValueChanged ();
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    void IColorBar.SetValueWithoutRaisingEvent (int v)
+    {
+        _value = v;
+        SetNeedsDisplay ();
+    }
+
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
+    {
+        base.OnDrawContent (viewport);
+
+        var xOffset = 0;
+
+        if (!string.IsNullOrWhiteSpace (Text))
+        {
+            Move (0, 0);
+            Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+            Driver.AddStr (Text);
+
+            // TODO: is there a better method than this? this is what it is in TableView
+            xOffset = Text.EnumerateRunes ().Sum (c => c.GetColumns ());
+        }
+
+        _barWidth = viewport.Width - xOffset;
+        _barStartsAt = xOffset;
+
+        DrawBar (xOffset, 0, _barWidth);
+    }
+
+    /// <summary>
+    ///     Event fired when <see cref="Value"/> is changed to a new value
+    /// </summary>
+    public event EventHandler<EventArgs<int>>? ValueChanged;
+
+    /// <inheritdoc/>
+    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        {
+            if (mouseEvent.Position.X >= _barStartsAt)
+            {
+                double v = MaxValue * ((double)mouseEvent.Position.X - _barStartsAt) / (_barWidth - 1);
+                Value = Math.Clamp ((int)v, 0, MaxValue);
+            }
+
+            mouseEvent.Handled = true;
+            FocusFirst (null);
+
+            return true;
+        }
+
+        return base.OnMouseEvent (mouseEvent);
+    }
+
+    /// <summary>
+    ///     When overriden in a derived class, returns the <see cref="Color"/> to
+    ///     render at <paramref name="fraction"/> proportion of the full bars width.
+    ///     e.g. 0.5 fraction of Saturation is 50% because Saturation goes from 0-100.
+    /// </summary>
+    /// <param name="fraction"></param>
+    /// <returns></returns>
+    protected abstract Color GetColor (double fraction);
+
+    /// <summary>
+    ///     The maximum value allowed for this component e.g. Saturation allows up to 100 as it
+    ///     is a percentage while Hue allows up to 360 as it is measured in degrees.
+    /// </summary>
+    protected abstract int MaxValue { get; }
+
+    /// <summary>
+    ///     The last drawn location in View's viewport where the Triangle appeared.
+    ///     Used exclusively for tests.
+    /// </summary>
+    internal int TrianglePosition { get; private set; }
+
+    private bool? Adjust (int delta)
+    {
+        var change = (int)(delta * _cellValue);
+
+        // Ensure that the change is at least 1 or -1 if delta is non-zero
+        if (change == 0 && delta != 0)
+        {
+            change = delta > 0 ? 1 : -1;
+        }
+
+        Value += change;
+
+        return true;
+    }
+
+    private void DrawBar (int xOffset, int yOffset, int width)
+    {
+        // Each 1 unit of X in the bar corresponds to this much of Value
+        _cellValue = (double)MaxValue / (width - 1);
+
+        for (var x = 0; x < width; x++)
+        {
+            double fraction = (double)x / (width - 1);
+            Color color = GetColor (fraction);
+
+            // Adjusted isSelectedCell calculation
+            double cellBottomThreshold = (x - 1) * _cellValue;
+            double cellTopThreshold = x * _cellValue;
+
+            if (x == width - 1)
+            {
+                cellTopThreshold = MaxValue;
+            }
+
+            bool isSelectedCell = Value > cellBottomThreshold && Value <= cellTopThreshold;
+
+            // Check the brightness of the background color
+            double brightness = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B) / 255;
+
+            Color triangleColor = Color.Black;
+
+            if (brightness < 0.15) // Threshold to determine if the color is too close to black
+            {
+                triangleColor = Color.DarkGray;
+            }
+
+            if (isSelectedCell)
+            {
+                // Draw the triangle at the closest position
+                Application.Driver?.SetAttribute (new (triangleColor, color));
+                AddRune (x + xOffset, yOffset, new ('▲'));
+
+                // Record for tests
+                TrianglePosition = x + xOffset;
+            }
+            else
+            {
+                Application.Driver?.SetAttribute (new (color, color));
+                AddRune (x + xOffset, yOffset, new ('█'));
+            }
+        }
+    }
+
+    private void OnValueChanged ()
+    {
+        ValueChanged?.Invoke (this, new (in _value));
+        SetNeedsDisplay ();
+    }
+
+    private bool? SetMax ()
+    {
+        Value = MaxValue;
+
+        return true;
+    }
+
+    private bool? SetZero ()
+    {
+        Value = 0;
+
+        return true;
+    }
+}

+ 163 - 0
Terminal.Gui/Views/ColorModelStrategy.cs

@@ -0,0 +1,163 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class ColorModelStrategy
+{
+    public IEnumerable<ColorBar> CreateBars (ColorModel model)
+    {
+        switch (model)
+        {
+            case ColorModel.RGB:
+                return CreateRgbBars ();
+            case ColorModel.HSV:
+                return CreateHsvBars ();
+            case ColorModel.HSL:
+                return CreateHslBars ();
+            default:
+                throw new ArgumentOutOfRangeException (nameof (model), model, null);
+        }
+    }
+
+    public Color GetColorFromBars (IList<IColorBar> bars, ColorModel model)
+    {
+        switch (model)
+        {
+            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);
+        }
+    }
+
+    public void SetBarsToColor (IList<IColorBar> bars, Color newValue, ColorModel model)
+    {
+        switch (model)
+        {
+            case ColorModel.RGB:
+                bars [0].SetValueWithoutRaisingEvent (newValue.R);
+                bars [1].SetValueWithoutRaisingEvent (newValue.G);
+                bars [2].SetValueWithoutRaisingEvent (newValue.B);
+
+                break;
+            case ColorModel.HSV:
+                HSV newHsv = ColorConverter.RgbToHsv (new (newValue.R, newValue.G, newValue.B));
+                bars [0].SetValueWithoutRaisingEvent (newHsv.H);
+                bars [1].SetValueWithoutRaisingEvent (newHsv.S);
+                bars [2].SetValueWithoutRaisingEvent (newHsv.V);
+
+                break;
+            case ColorModel.HSL:
+
+                HSL newHsl = ColorConverter.RgbToHsl (new (newValue.R, newValue.G, newValue.B));
+                bars [0].SetValueWithoutRaisingEvent (newHsl.H);
+                bars [1].SetValueWithoutRaisingEvent (newHsl.S);
+                bars [2].SetValueWithoutRaisingEvent (newHsl.L);
+
+                break;
+            default:
+                throw new ArgumentOutOfRangeException (nameof (model), model, null);
+        }
+    }
+
+    private IEnumerable<ColorBar> CreateHslBars ()
+    {
+        var h = new HueBar
+        {
+            Text = "H:"
+        };
+
+        yield return h;
+
+        var s = new SaturationBar
+        {
+            Text = "S:"
+        };
+
+        var l = new LightnessBar
+        {
+            Text = "L:"
+        };
+
+        s.HBar = h;
+        s.LBar = l;
+
+        l.HBar = h;
+        l.SBar = s;
+
+        yield return s;
+        yield return l;
+    }
+
+    private IEnumerable<ColorBar> CreateHsvBars ()
+    {
+        var h = new HueBar
+        {
+            Text = "H:"
+        };
+
+        yield return h;
+
+        var s = new SaturationBar
+        {
+            Text = "S:"
+        };
+
+        var v = new ValueBar
+        {
+            Text = "V:"
+        };
+
+        s.HBar = h;
+        s.VBar = v;
+
+        v.HBar = h;
+        v.SBar = s;
+
+        yield return s;
+        yield return v;
+    }
+
+    private IEnumerable<ColorBar> CreateRgbBars ()
+    {
+        var r = new RBar
+        {
+            Text = "R:"
+        };
+
+        var g = new GBar
+        {
+            Text = "G:"
+        };
+
+        var b = new BBar
+        {
+            Text = "B:"
+        };
+        r.GBar = g;
+        r.BBar = b;
+
+        g.RBar = r;
+        g.BBar = b;
+
+        b.RBar = r;
+        b.GBar = g;
+
+        yield return r;
+        yield return g;
+        yield return b;
+    }
+
+    private Color ToColor (RGB rgb) { return new (rgb.R, rgb.G, rgb.B); }
+}

+ 233 - 194
Terminal.Gui/Views/ColorPicker.cs

@@ -1,279 +1,318 @@
-namespace Terminal.Gui;
+#nullable enable
 
-/// <summary>Event arguments for the <see cref="Color"/> events.</summary>
-public class ColorEventArgs : EventArgs
-{
-    /// <summary>Initializes a new instance of <see cref="ColorEventArgs"/></summary>
-    public ColorEventArgs () { }
-
-    /// <summary>The new Thickness.</summary>
-    public Color Color { get; set; }
-
-    /// <summary>The previous Thickness.</summary>
-    public Color PreviousColor { get; set; }
-}
+namespace Terminal.Gui;
 
-/// <summary>The <see cref="ColorPicker"/> <see cref="View"/> Color picker.</summary>
+/// <summary>
+///     True color picker using HSL
+/// </summary>
 public class ColorPicker : View
 {
-    /// <summary>Columns of color boxes</summary>
-    private readonly int _cols = 8;
+    /// <summary>
+    ///     Creates a new instance of <see cref="ColorPicker"/>. Use
+    ///     <see cref="Style"/> to change color model. Use <see cref="SelectedColor"/>
+    ///     to change initial <see cref="Color"/>.
+    /// </summary>
+    public ColorPicker ()
+    {
+        CanFocus = true;
+        Height = Dim.Auto ();
+        Width = Dim.Auto ();
+        ApplyStyleChanges ();
+    }
+
+    private readonly Dictionary<IColorBar, TextField> _textFields = new ();
+    private readonly ColorModelStrategy _strategy = new ();
+    private TextField? _tfHex;
+    private Label? _lbHex;
+
+    private TextField? _tfName;
+    private Label? _lbName;
 
-    /// <summary>Rows of color boxes</summary>
-    private readonly int _rows = 2;
+    private Color _selectedColor = Color.Black;
 
-    private int _boxHeight = 2;
-    private int _boxWidth = 4;
-    private int _selectColorIndex = (int)Color.Black;
+    // TODO: Add interface
+    private readonly IColorNameResolver _colorNameResolver = new W3CColors ();
 
-    /// <summary>Initializes a new instance of <see cref="ColorPicker"/>.</summary>
-    public ColorPicker () { SetInitialProperties (); }
+    private List<IColorBar> _bars = new ();
 
-    private void SetInitialProperties ()
+    /// <summary>
+    ///     Rebuild the user interface to reflect the new state of <see cref="Style"/>.
+    /// </summary>
+    public void ApplyStyleChanges ()
     {
-        HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
+        Color oldValue = _selectedColor;
+        DisposeOldViews ();
 
-        CanFocus = true;
-        AddCommands ();
-        AddKeyBindings ();
+        var y = 0;
+        const int textFieldWidth = 4;
 
-        Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-        Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-        SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+        foreach (ColorBar bar in _strategy.CreateBars (Style.ColorModel))
+        {
+            bar.Y = y;
+            bar.Width = Dim.Fill (Style.ShowTextFields ? textFieldWidth : 0);
 
-        MouseClick += ColorPicker_MouseClick;
-    }
+            if (Style.ShowTextFields)
+            {
+                var tfValue = new TextField
+                {
+                    X = Pos.AnchorEnd (textFieldWidth),
+                    Y = y,
+                    Width = textFieldWidth
+                };
+                tfValue.Leave += UpdateSingleBarValueFromTextField;
+                _textFields.Add (bar, tfValue);
+                Add (tfValue);
+            }
 
-    // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
+            y++;
 
-    private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
-    {
-       // if (CanFocus)
+            bar.ValueChanged += RebuildColorFromBar;
+
+            _bars.Add (bar);
+
+            Add (bar);
+        }
+
+        if (Style.ShowColorName)
         {
-            Cursor = new Point (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight);
-            SetFocus ();
-            me.Handled = true;
+            CreateNameField ();
         }
+
+        CreateTextField ();
+        SelectedColor = oldValue;
+
+        LayoutSubviews ();
     }
 
-    /// <summary>Height of a color box</summary>
-    public int BoxHeight
+    /// <summary>
+    ///     Fired when color is changed.
+    /// </summary>
+    public event EventHandler<ColorEventArgs>? ColorChanged;
+
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
     {
-        get => _boxHeight;
-        set
-        {
-            if (_boxHeight != value)
-            {
-                _boxHeight = value;
-                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
-                SetNeedsLayout ();
-            }
-        }
+        base.OnDrawContent (viewport);
+        Attribute normal = GetNormalColor ();
+        Driver.SetAttribute (new (SelectedColor, normal.Background));
+        int y = _bars.Count + (Style.ShowColorName ? 1 : 0);
+        AddRune (13, y, (Rune)'■');
+    }
+
+    /// <summary>
+    ///     The color selected in the picker
+    /// </summary>
+    public Color SelectedColor
+    {
+        get => _selectedColor;
+        set => SetSelectedColor (value, true);
     }
 
-    /// <summary>Width of a color box</summary>
-    public int BoxWidth
+    /// <summary>
+    ///     Style settings for the color picker.  After making changes ensure you call
+    ///     <see cref="ApplyStyleChanges"/>.
+    /// </summary>
+    public ColorPickerStyle Style { get; set; } = new ();
+
+    private void CreateNameField ()
     {
-        get => _boxWidth;
-        set
+        _lbName = new ()
         {
-            if (_boxWidth != value)
-            {
-                _boxWidth = value;
-                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
-                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
-                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
-                SetNeedsLayout ();
-            }
-        }
+            Text = "Name:",
+            X = 0,
+            Y = 3
+        };
+
+        _tfName = new ()
+        {
+            Y = 3,
+            X = 6,
+            Width = 20 // width of "LightGoldenRodYellow" - the longest w3c color name
+        };
+
+        Add (_lbName);
+        Add (_tfName);
+
+        var auto = new AppendAutocomplete (_tfName);
+
+        auto.SuggestionGenerator = new SingleWordSuggestionGenerator
+        {
+            AllSuggestions = _colorNameResolver.GetColorNames ().ToList ()
+        };
+        _tfName.Autocomplete = auto;
+
+        _tfName.Leave += UpdateValueFromName;
     }
 
-    /// <summary>Cursor for the selected color.</summary>
-    public Point Cursor
+    private void CreateTextField ()
     {
-        get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
-        set
+        int y = _bars.Count;
+
+        if (Style.ShowColorName)
         {
-            int colorIndex = value.Y * _cols + value.X;
-            SelectedColor = (ColorName)colorIndex;
+            y++;
         }
+
+        _lbHex = new ()
+        {
+            Text = "Hex:",
+            X = 0,
+            Y = y
+        };
+
+        _tfHex = new ()
+        {
+            Y = y,
+            X = 4,
+            Width = 8
+        };
+
+        Add (_lbHex);
+        Add (_tfHex);
+
+        _tfHex.Leave += UpdateValueFromTextField;
     }
 
-    /// <summary>Selected color.</summary>
-    public ColorName SelectedColor
+    private void DisposeOldViews ()
     {
-        get => (ColorName)_selectColorIndex;
-        set
+        foreach (ColorBar bar in _bars.Cast<ColorBar> ())
         {
-            if (value == (ColorName)_selectColorIndex)
+            bar.ValueChanged -= RebuildColorFromBar;
+
+            if (_textFields.TryGetValue (bar, out TextField? tf))
             {
-                return;
+                tf.Leave -= UpdateSingleBarValueFromTextField;
+                Remove (tf);
+                tf.Dispose ();
             }
-            var prev = (ColorName)_selectColorIndex;
-            _selectColorIndex = (int)value;
 
-            ColorChanged?.Invoke (
-                                  this,
-                                  new ColorEventArgs { PreviousColor = new Color (prev), Color = new Color (value) }
-                                 );
-            SetNeedsDisplay ();
+            Remove (bar);
+            bar.Dispose ();
         }
-    }
 
-    /// <summary>Fired when a color is picked.</summary>
-    public event EventHandler<ColorEventArgs> ColorChanged;
+        _bars = new ();
+        _textFields.Clear ();
 
-
-    /// <summary>Moves the selected item index to the previous column.</summary>
-    /// <returns></returns>
-    public virtual bool MoveLeft ()
-    {
-        if (Cursor.X > 0)
+        if (_lbHex != null)
         {
-            SelectedColor--;
+            Remove (_lbHex);
+            _lbHex.Dispose ();
+            _lbHex = null;
         }
 
-        return true;
-    }
+        if (_tfHex != null)
+        {
+            Remove (_tfHex);
+            _tfHex.Leave -= UpdateValueFromTextField;
+            _tfHex.Dispose ();
+            _tfHex = null;
+        }
 
-    /// <summary>Moves the selected item index to the next column.</summary>
-    /// <returns></returns>
-    public virtual bool MoveRight ()
-    {
-        if (Cursor.X < _cols - 1)
+        if (_lbName != null)
         {
-            SelectedColor++;
+            Remove (_lbName);
+            _lbName.Dispose ();
+            _lbName = null;
         }
 
-        return true;
+        if (_tfName != null)
+        {
+            Remove (_tfName);
+            _tfName.Leave -= UpdateValueFromName;
+            _tfName.Dispose ();
+            _tfName = null;
+        }
     }
 
-    /// <summary>Moves the selected item index to the previous row.</summary>
-    /// <returns></returns>
-    public virtual bool MoveUp ()
+    private void RebuildColorFromBar (object? sender, EventArgs<int> e) { SetSelectedColor (_strategy.GetColorFromBars (_bars, Style.ColorModel), false); }
+
+    private void SetSelectedColor (Color value, bool syncBars)
     {
-        if (Cursor.Y > 0)
+        if (_selectedColor != value)
         {
-            SelectedColor -= _cols;
+            Color old = _selectedColor;
+            _selectedColor = value;
+
+            ColorChanged?.Invoke (
+                                  this,
+                                  new (value));
         }
 
-        return true;
+        SyncSubViewValues (syncBars);
     }
 
-    /// <summary>Moves the selected item index to the next row.</summary>
-    /// <returns></returns>
-    public virtual bool MoveDown ()
+    private void SyncSubViewValues (bool syncBars)
     {
-        if (Cursor.Y < _rows - 1)
+        if (syncBars)
         {
-            SelectedColor += _cols;
+            _strategy.SetBarsToColor (_bars, _selectedColor, Style.ColorModel);
         }
 
-        return true;
-    }
-
-    ///<inheritdoc/>
-    public override void OnDrawContent (Rectangle viewport)
-    {
-        base.OnDrawContent (viewport);
+        foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
+        {
+            kvp.Value.Text = kvp.Key.Value.ToString ();
+        }
 
-        Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
-        var colorIndex = 0;
+        var colorHex = _selectedColor.ToString ($"#{SelectedColor.R:X2}{SelectedColor.G:X2}{SelectedColor.B:X2}");
 
-        for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
+        if (_tfName != null)
         {
-            for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
-            {
-                int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
-                Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
-                bool selected = x == Cursor.X && y == Cursor.Y;
-                DrawColorBox (x, y, selected);
-                colorIndex++;
-            }
+            _tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty;
         }
-    }
-
 
-    /// <summary>Add the commands.</summary>
-    private void AddCommands ()
-    {
-        AddCommand (Command.Left, () => MoveLeft ());
-        AddCommand (Command.Right, () => MoveRight ());
-        AddCommand (Command.LineUp, () => MoveUp ());
-        AddCommand (Command.LineDown, () => MoveDown ());
+        if (_tfHex != null)
+        {
+            _tfHex.Text = colorHex;
+        }
     }
 
-    /// <summary>Add the KeyBindinds.</summary>
-    private void AddKeyBindings ()
+    private void UpdateSingleBarValueFromTextField (object? sender, FocusEventArgs e)
     {
-        KeyBindings.Add (Key.CursorLeft, Command.Left);
-        KeyBindings.Add (Key.CursorRight, Command.Right);
-        KeyBindings.Add (Key.CursorUp, Command.LineUp);
-        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+        foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
+        {
+            if (kvp.Value == sender)
+            {
+                if (int.TryParse (kvp.Value.Text, out int v))
+                {
+                    kvp.Key.Value = v;
+                }
+            }
+        }
     }
 
-    /// <summary>Draw a box for one color.</summary>
-    /// <param name="x">X location.</param>
-    /// <param name="y">Y location</param>
-    /// <param name="selected"></param>
-    private void DrawColorBox (int x, int y, bool selected)
+    private void UpdateValueFromName (object? sender, FocusEventArgs e)
     {
-        var index = 0;
-
-        for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++)
+        if (_tfName == null)
         {
-            for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
-            {
-                Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-                Driver.AddRune ((Rune)' ');
-                index++;
-            }
+            return;
         }
 
-        if (selected)
+        if (_colorNameResolver.TryParseColor (_tfName.Text, out Color newColor))
+        {
+            SelectedColor = newColor;
+        }
+        else
         {
-            DrawFocusRect (new (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+            // value is invalid, revert the value in the text field back to current state
+            SyncSubViewValues (false);
         }
     }
 
-    private void DrawFocusRect (Rectangle rect)
+    private void UpdateValueFromTextField (object? sender, FocusEventArgs e)
     {
-        var lc = new LineCanvas ();
-
-        if (rect.Width == 1)
+        if (_tfHex == null)
         {
-            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+            return;
         }
-        else if (rect.Height == 1)
+
+        if (Color.TryParse (_tfHex.Text, out Color? newColor))
         {
-            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+            SelectedColor = newColor.Value;
         }
         else
         {
-            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-
-            lc.AddLine (
-                        rect.Location with { Y = rect.Location.Y + rect.Height - 1 },
-                        rect.Width,
-                        Orientation.Horizontal,
-                        LineStyle.Dotted
-                       );
-
-            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-
-            lc.AddLine (
-                        rect.Location with { X = rect.Location.X + rect.Width - 1 },
-                        rect.Height,
-                        Orientation.Vertical,
-                        LineStyle.Dotted
-                       );
-        }
-
-        foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
-        {
-            AddRune (p.Key.X, p.Key.Y, p.Value);
+            // value is invalid, revert the value in the text field back to current state
+            SyncSubViewValues (false);
         }
     }
 }

+ 270 - 0
Terminal.Gui/Views/ColorPicker16.cs

@@ -0,0 +1,270 @@
+namespace Terminal.Gui;
+
+/// <summary>The <see cref="ColorPicker16"/> <see cref="View"/> Color picker.</summary>
+public class ColorPicker16 : View
+{
+    /// <summary>Initializes a new instance of <see cref="ColorPicker16"/>.</summary>
+    public ColorPicker16 () { SetInitialProperties (); }
+
+    /// <summary>Columns of color boxes</summary>
+    private readonly int _cols = 8;
+
+    /// <summary>Rows of color boxes</summary>
+    private readonly int _rows = 2;
+
+    private int _boxHeight = 2;
+    private int _boxWidth = 4;
+    private int _selectColorIndex = (int)Color.Black;
+
+    /// <summary>Height of a color box</summary>
+    public int BoxHeight
+    {
+        get => _boxHeight;
+        set
+        {
+            if (_boxHeight != value)
+            {
+                _boxHeight = value;
+                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+                SetNeedsLayout ();
+            }
+        }
+    }
+
+    /// <summary>Width of a color box</summary>
+    public int BoxWidth
+    {
+        get => _boxWidth;
+        set
+        {
+            if (_boxWidth != value)
+            {
+                _boxWidth = value;
+                Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+                Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+                SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+                SetNeedsLayout ();
+            }
+        }
+    }
+
+    /// <summary>Fired when a color is picked.</summary>
+    public event EventHandler<ColorEventArgs> ColorChanged;
+
+    /// <summary>Cursor for the selected color.</summary>
+    public Point Cursor
+    {
+        get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
+        set
+        {
+            int colorIndex = value.Y * _cols + value.X;
+            SelectedColor = (ColorName)colorIndex;
+        }
+    }
+
+    /// <summary>Moves the selected item index to the next row.</summary>
+    /// <returns></returns>
+    public virtual bool MoveDown ()
+    {
+        if (Cursor.Y < _rows - 1)
+        {
+            SelectedColor += _cols;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the previous column.</summary>
+    /// <returns></returns>
+    public virtual bool MoveLeft ()
+    {
+        if (Cursor.X > 0)
+        {
+            SelectedColor--;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the next column.</summary>
+    /// <returns></returns>
+    public virtual bool MoveRight ()
+    {
+        if (Cursor.X < _cols - 1)
+        {
+            SelectedColor++;
+        }
+
+        return true;
+    }
+
+    /// <summary>Moves the selected item index to the previous row.</summary>
+    /// <returns></returns>
+    public virtual bool MoveUp ()
+    {
+        if (Cursor.Y > 0)
+        {
+            SelectedColor -= _cols;
+        }
+
+        return true;
+    }
+
+    ///<inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
+    {
+        base.OnDrawContent (viewport);
+
+        Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
+        var colorIndex = 0;
+
+        for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
+        {
+            for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
+            {
+                int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
+
+                if (foregroundColorIndex > 15 || colorIndex > 15)
+                {
+                    continue;
+                }
+
+                Driver.SetAttribute (new ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+                bool selected = x == Cursor.X && y == Cursor.Y;
+                DrawColorBox (x, y, selected);
+                colorIndex++;
+            }
+        }
+    }
+
+    /// <summary>Selected color.</summary>
+    public ColorName SelectedColor
+    {
+        get => (ColorName)_selectColorIndex;
+        set
+        {
+            if (value == (ColorName)_selectColorIndex)
+            {
+                return;
+            }
+
+            _selectColorIndex = (int)value;
+
+            ColorChanged?.Invoke (
+                                  this,
+                                  new (value)
+                                 );
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>Add the commands.</summary>
+    private void AddCommands ()
+    {
+        AddCommand (Command.Left, () => MoveLeft ());
+        AddCommand (Command.Right, () => MoveRight ());
+        AddCommand (Command.LineUp, () => MoveUp ());
+        AddCommand (Command.LineDown, () => MoveDown ());
+    }
+
+    /// <summary>Add the KeyBindinds.</summary>
+    private void AddKeyBindings ()
+    {
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+        KeyBindings.Add (Key.CursorUp, Command.LineUp);
+        KeyBindings.Add (Key.CursorDown, Command.LineDown);
+    }
+
+    // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
+
+    private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
+    {
+        // if (CanFocus)
+        {
+            Cursor = new (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight);
+            SetFocus ();
+            me.Handled = true;
+        }
+    }
+
+    /// <summary>Draw a box for one color.</summary>
+    /// <param name="x">X location.</param>
+    /// <param name="y">Y location</param>
+    /// <param name="selected"></param>
+    private void DrawColorBox (int x, int y, bool selected)
+    {
+        var index = 0;
+
+        for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++)
+        {
+            for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++)
+            {
+                Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
+                Driver.AddRune ((Rune)' ');
+                index++;
+            }
+        }
+
+        if (selected)
+        {
+            DrawFocusRect (new (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+        }
+    }
+
+    private void DrawFocusRect (Rectangle rect)
+    {
+        var lc = new LineCanvas ();
+
+        if (rect.Width == 1)
+        {
+            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+        }
+        else if (rect.Height == 1)
+        {
+            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+        }
+        else
+        {
+            lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+
+            lc.AddLine (
+                        rect.Location with { Y = rect.Location.Y + rect.Height - 1 },
+                        rect.Width,
+                        Orientation.Horizontal,
+                        LineStyle.Dotted
+                       );
+
+            lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+
+            lc.AddLine (
+                        rect.Location with { X = rect.Location.X + rect.Width - 1 },
+                        rect.Height,
+                        Orientation.Vertical,
+                        LineStyle.Dotted
+                       );
+        }
+
+        foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
+        {
+            AddRune (p.Key.X, p.Key.Y, p.Value);
+        }
+    }
+
+    private void SetInitialProperties ()
+    {
+        HighlightStyle = HighlightStyle.PressedOutside | HighlightStyle.Pressed;
+
+        CanFocus = true;
+        AddCommands ();
+        AddKeyBindings ();
+
+        Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
+        Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
+        SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
+
+        MouseClick += ColorPicker_MouseClick;
+    }
+}

+ 25 - 0
Terminal.Gui/Views/ColorPickerStyle.cs

@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Contains style settings for <see cref="ColorPicker"/> e.g. which <see cref="ColorModel"/>
+/// to use.
+/// </summary>
+public class ColorPickerStyle
+{
+    /// <summary>
+    ///     The color model for picking colors by RGB, HSV, etc.
+    /// </summary>
+    public ColorModel ColorModel { get; set; } = ColorModel.HSV;
+
+    /// <summary>
+    ///     True to put the numerical value of bars on the right of the color bar
+    /// </summary>
+    public bool ShowTextFields { get; set; } = true;
+
+    /// <summary>
+    ///     True to show an editable text field indicating the w3c/console color name of selected color.
+    /// </summary>
+    public bool ShowColorName { get; set; } = false;
+}

+ 27 - 0
Terminal.Gui/Views/GBar.cs

@@ -0,0 +1,27 @@
+#nullable enable
+
+using ColorHelper;
+
+namespace Terminal.Gui;
+
+internal class GBar : ColorBar
+{
+    public BBar? BBar { get; set; }
+    public RBar? RBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (RBar == null || BBar == null)
+        {
+            throw new ($"{nameof (GBar)} has not been set up correctly before drawing");
+        }
+
+        var rgb = new RGB ((byte)RBar.Value, (byte)(MaxValue * fraction), (byte)BBar.Value);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 255;
+}

+ 21 - 0
Terminal.Gui/Views/HueBar.cs

@@ -0,0 +1,21 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class HueBar : ColorBar
+{
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        var hsl = new HSL ((int)(MaxValue * fraction), 100, 50);
+        RGB rgb = ColorConverter.HslToRgb (hsl);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 360;
+}

+ 14 - 0
Terminal.Gui/Views/IColorBar.cs

@@ -0,0 +1,14 @@
+namespace Terminal.Gui;
+
+internal interface IColorBar
+{
+    int Value { get; set; }
+
+    /// <summary>
+    ///     Update the value of <see cref="Value"/> and reflect
+    ///     changes in UI state but do not raise a value changed
+    ///     event (to avoid circular events).
+    /// </summary>
+    /// <param name="v"></param>
+    internal void SetValueWithoutRaisingEvent (int v);
+}

+ 29 - 0
Terminal.Gui/Views/LightnessBar.cs

@@ -0,0 +1,29 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class LightnessBar : ColorBar
+{
+    public HueBar? HBar { get; set; }
+    public SaturationBar? SBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (HBar == null || SBar == null)
+        {
+            throw new ($"{nameof (LightnessBar)} has not been set up correctly before drawing");
+        }
+
+        var hsl = new HSL (HBar.Value, (byte)SBar.Value, (byte)(MaxValue * fraction));
+        RGB rgb = ColorConverter.HslToRgb (hsl);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 100;
+}

+ 27 - 0
Terminal.Gui/Views/RBar.cs

@@ -0,0 +1,27 @@
+#nullable enable
+
+using ColorHelper;
+
+namespace Terminal.Gui;
+
+internal class RBar : ColorBar
+{
+    public BBar? BBar { get; set; }
+    public GBar? GBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (GBar == null || BBar == null)
+        {
+            throw new ($"{nameof (RBar)} has not been set up correctly before drawing");
+        }
+
+        var rgb = new RGB ((byte)(MaxValue * fraction), (byte)GBar.Value, (byte)BBar.Value);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 255;
+}

+ 40 - 0
Terminal.Gui/Views/SaturationBar.cs

@@ -0,0 +1,40 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class SaturationBar : ColorBar
+{
+    public HueBar? HBar { get; set; }
+
+    // Should only have either LBar or VBar not both
+    public LightnessBar? LBar { get; set; }
+    public ValueBar? VBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (LBar != null && HBar != null)
+        {
+            var hsl = new HSL (HBar.Value, (byte)(MaxValue * fraction), (byte)LBar.Value);
+            RGB rgb = ColorConverter.HslToRgb (hsl);
+
+            return new (rgb.R, rgb.G, rgb.B);
+        }
+
+        if (VBar != null && HBar != null)
+        {
+            var hsv = new HSV (HBar.Value, (byte)(MaxValue * fraction), (byte)VBar.Value);
+            RGB rgb = ColorConverter.HsvToRgb (hsv);
+
+            return new (rgb.R, rgb.G, rgb.B);
+        }
+
+        throw new ($"{nameof (SaturationBar)} requires either Lightness or Value to render");
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 100;
+}

+ 29 - 0
Terminal.Gui/Views/ValueBar.cs

@@ -0,0 +1,29 @@
+#nullable enable
+
+using ColorHelper;
+using ColorConverter = ColorHelper.ColorConverter;
+
+namespace Terminal.Gui;
+
+internal class ValueBar : ColorBar
+{
+    public HueBar? HBar { get; set; }
+    public SaturationBar? SBar { get; set; }
+
+    /// <inheritdoc/>
+    protected override Color GetColor (double fraction)
+    {
+        if (HBar == null || SBar == null)
+        {
+            throw new ($"{nameof (ValueBar)} has not been set up correctly before drawing");
+        }
+
+        var hsv = new HSV (HBar.Value, (byte)SBar.Value, (byte)(MaxValue * fraction));
+        RGB rgb = ColorConverter.HsvToRgb (hsv);
+
+        return new (rgb.R, rgb.G, rgb.B);
+    }
+
+    /// <inheritdoc/>
+    protected override int MaxValue => 100;
+}

+ 2 - 2
UICatalog/Scenarios/AdornmentEditor.cs

@@ -8,7 +8,7 @@ namespace UICatalog.Scenarios;
 /// </summary>
 public class AdornmentEditor : View
 {
-    private readonly ColorPicker _backgroundColorPicker = new ()
+    private readonly ColorPicker16 _backgroundColorPicker = new ()
     {
         Title = "_BG",
         BoxWidth = 1,
@@ -18,7 +18,7 @@ public class AdornmentEditor : View
         Enabled = false
     };
 
-    private readonly ColorPicker _foregroundColorPicker = new ()
+    private readonly ColorPicker16 _foregroundColorPicker = new ()
     {
         Title = "_FG",
         BoxWidth = 1,

+ 2 - 2
UICatalog/Scenarios/Adornments.cs

@@ -38,7 +38,7 @@ public class Adornments : Scenario
         app.Add (window);
 
         var tf1 = new TextField { Width = 10, Text = "TextField" };
-        var color = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd () };
+        var color = new ColorPicker16 { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd () };
         color.BorderStyle = LineStyle.RoundedDotted;
 
         color.ColorChanged += (s, e) =>
@@ -47,7 +47,7 @@ public class Adornments : Scenario
                                   {
                                       Normal = new (
                                                     color.SuperView.ColorScheme.Normal.Foreground,
-                                                    e.Color
+                                                    e.CurrentValue
                                                    )
                                   };
                               };

+ 158 - 11
UICatalog/Scenarios/ColorPicker.cs

@@ -23,6 +23,12 @@ public class ColorPickers : Scenario
     /// <summary>Foreground ColorPicker.</summary>
     private ColorPicker foregroundColorPicker;
 
+    /// <summary>Background ColorPicker.</summary>
+    private ColorPicker16 backgroundColorPicker16;
+
+    /// <summary>Foreground ColorPicker.</summary>
+    private ColorPicker16 foregroundColorPicker16;
+
     /// <summary>Setup the scenario.</summary>
     public override void Main ()
     {
@@ -33,8 +39,16 @@ public class ColorPickers : Scenario
             Title = GetQuitKeyAndName (),
         };
 
+        ///////////////////////////////////////
+        // True Color Pickers
+        ///////////////////////////////////////
+
         // Foreground ColorPicker.
-        foregroundColorPicker = new ColorPicker { Title = "Foreground Color", BorderStyle = LineStyle.Single };
+        foregroundColorPicker = new ColorPicker {
+            Title = "Foreground Color",
+            BorderStyle = LineStyle.Single,
+            Width = Dim.Percent (50)
+        };
         foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged;
         app.Add (foregroundColorPicker);
 
@@ -49,9 +63,8 @@ public class ColorPickers : Scenario
         {
             Title = "Background Color",
             X = Pos.AnchorEnd (),
-            BoxHeight = 1,
-            BoxWidth = 4,
-            BorderStyle = LineStyle.Single,
+            Width = Dim.Percent (50),
+            BorderStyle = LineStyle.Single
         };
 
         backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged;
@@ -64,6 +77,37 @@ public class ColorPickers : Scenario
 
         app.Add (_backgroundColorLabel);
 
+
+        ///////////////////////////////////////
+        // 16 Color Pickers
+        ///////////////////////////////////////
+
+
+        // Foreground ColorPicker 16.
+        foregroundColorPicker16 = new ColorPicker16
+        {
+            Title = "Foreground Color",
+            BorderStyle = LineStyle.Single,
+            Width = Dim.Percent (50),
+            Visible = false  // We default to HSV so hide old one
+        };
+        foregroundColorPicker16.ColorChanged += ForegroundColor_ColorChanged;
+        app.Add (foregroundColorPicker16);
+
+        // Background ColorPicker 16.
+        backgroundColorPicker16 = new ColorPicker16
+        {
+            Title = "Background Color",
+            X = Pos.AnchorEnd (),
+            Width = Dim.Percent (50),
+            BorderStyle = LineStyle.Single,
+            Visible = false  // We default to HSV so hide old one
+        };
+
+        backgroundColorPicker16.ColorChanged += BackgroundColor_ColorChanged;
+        app.Add (backgroundColorPicker16);
+
+
         // Demo Label.
         _demoView = new View
         {
@@ -79,6 +123,98 @@ public class ColorPickers : Scenario
         };
         app.Add (_demoView);
 
+
+        // Radio for switching color models
+        var rgColorModel = new RadioGroup ()
+        {
+            Y = Pos.Bottom (_demoView),
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            RadioLabels = new []
+            {
+                "RGB",
+                "HSV",
+                "HSL",
+                "16 Colors"
+            },
+            SelectedItem = (int)foregroundColorPicker.Style.ColorModel,
+        };
+
+        rgColorModel.SelectedItemChanged += (_, e) =>
+                                            {
+                                                // 16 colors
+                                                if (e.SelectedItem == 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;
+                                                    foregroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
+                                                    foregroundColorPicker.ApplyStyleChanges ();
+
+                                                    backgroundColorPicker16.Visible = false;
+                                                    backgroundColorPicker.Visible = true;
+                                                    backgroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
+                                                    backgroundColorPicker.ApplyStyleChanges ();
+
+
+                                                    // Switching to true colors
+                                                    foregroundColorPicker.SelectedColor = foregroundColorPicker16.SelectedColor;
+                                                    backgroundColorPicker.SelectedColor = backgroundColorPicker16.SelectedColor;
+                                                }
+                                            };
+
+        app.Add (rgColorModel);
+
+        // Checkbox for switching show text fields on and off
+        var cbShowTextFields = new CheckBox ()
+        {
+            Text = "Show Text Fields",
+            Y = Pos.Bottom (rgColorModel)+1,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            CheckedState = foregroundColorPicker.Style.ShowTextFields ? CheckState.Checked: CheckState.UnChecked,
+        };
+
+        cbShowTextFields.CheckedStateChanging += (_, e) =>
+                                                {
+                                                    foregroundColorPicker.Style.ShowTextFields = e.NewValue == CheckState.Checked;
+                                                    foregroundColorPicker.ApplyStyleChanges ();
+                                                    backgroundColorPicker.Style.ShowTextFields = e.NewValue == CheckState.Checked;
+                                                    backgroundColorPicker.ApplyStyleChanges ();
+                                                };
+        app.Add (cbShowTextFields);
+
+        // Checkbox for switching show text fields on and off
+        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,
+        };
+
+        cbShowName.CheckedStateChanging += (_, e) =>
+                                           {
+                                               foregroundColorPicker.Style.ShowColorName = e.NewValue == CheckState.Checked;
+                                               foregroundColorPicker.ApplyStyleChanges ();
+                                               backgroundColorPicker.Style.ShowColorName = e.NewValue == CheckState.Checked;
+                                               backgroundColorPicker.ApplyStyleChanges ();
+                                           };
+        app.Add (cbShowName);
+
         // Set default colors.
         foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.GetClosestNamedColor ();
         backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.GetClosestNamedColor ();
@@ -92,25 +228,32 @@ public class ColorPickers : Scenario
     /// <summary>Fired when background color is changed.</summary>
     private void BackgroundColor_ColorChanged (object sender, EventArgs e)
     {
-        UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker);
+        UpdateColorLabel (_backgroundColorLabel,
+                          backgroundColorPicker.Visible ?
+                              backgroundColorPicker.SelectedColor :
+                              backgroundColorPicker16.SelectedColor
+                          );
         UpdateDemoLabel ();
     }
 
     /// <summary>Fired when foreground color is changed.</summary>
     private void ForegroundColor_ColorChanged (object sender, EventArgs e)
     {
-        UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker);
+        UpdateColorLabel (_foregroundColorLabel,
+                          foregroundColorPicker.Visible ?
+                                 foregroundColorPicker.SelectedColor :
+                                 foregroundColorPicker16.SelectedColor
+                          );
         UpdateDemoLabel ();
     }
 
     /// <summary>Update a color label from his ColorPicker.</summary>
-    private void UpdateColorLabel (Label label, ColorPicker colorPicker)
+    private void UpdateColorLabel (Label label, Color color)
     {
         label.Clear ();
-        var color = new Color (colorPicker.SelectedColor);
 
         label.Text =
-            $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}";
+            $"{color} ({(int)color}) #{color.R:X2}{color.G:X2}{color.B:X2}";
     }
 
     /// <summary>Update Demo Label.</summary>
@@ -119,8 +262,12 @@ public class ColorPickers : Scenario
         _demoView.ColorScheme = new ColorScheme
         {
             Normal = new Attribute (
-                                    foregroundColorPicker.SelectedColor,
-                                    backgroundColorPicker.SelectedColor
+                                    foregroundColorPicker.Visible ?
+                                        foregroundColorPicker.SelectedColor :
+                                        foregroundColorPicker16.SelectedColor,
+                                    backgroundColorPicker.Visible ?
+                                        backgroundColorPicker.SelectedColor :
+                                        backgroundColorPicker16.SelectedColor
                                    )
         };
     }

+ 2 - 2
UICatalog/Scenarios/ContentScrolling.cs

@@ -326,7 +326,7 @@ public class ContentScrolling : Scenario
         // Add demo views to show that things work correctly
         var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test TextField" };
 
-        var colorPicker = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (), Y = 10 };
+        var colorPicker = new ColorPicker16 { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (), Y = 10 };
         colorPicker.BorderStyle = LineStyle.RoundedDotted;
 
         colorPicker.ColorChanged += (s, e) =>
@@ -335,7 +335,7 @@ public class ContentScrolling : Scenario
                                         {
                                             Normal = new (
                                                           colorPicker.SuperView.ColorScheme.Normal.Foreground,
-                                                          e.Color
+                                                          e.CurrentValue
                                                          )
                                         };
                                     };

+ 375 - 156
UICatalog/Scenarios/LineDrawing.cs

@@ -1,10 +1,104 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
 
+public interface ITool
+{
+    void OnMouseEvent (DrawingArea area, MouseEvent mouseEvent);
+}
+
+internal class DrawLineTool : ITool
+{
+    private StraightLine _currentLine;
+    public LineStyle LineStyle { get; set; } = LineStyle.Single;
+
+    /// <inheritdoc/>
+    public void OnMouseEvent (DrawingArea area, MouseEvent mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        {
+            if (_currentLine == null)
+            {
+                // Mouse pressed down
+                _currentLine = new (
+                                    mouseEvent.Position,
+                                    0,
+                                    Orientation.Vertical,
+                                    LineStyle,
+                                    area.CurrentAttribute
+                                   );
+
+                area.CurrentLayer.AddLine (_currentLine);
+            }
+            else
+            {
+                // Mouse dragged
+                Point start = _currentLine.Start;
+                Point end = mouseEvent.Position;
+                var orientation = Orientation.Vertical;
+                int length = end.Y - start.Y;
+
+                // if line is wider than it is tall switch to horizontal
+                if (Math.Abs (start.X - end.X) > Math.Abs (start.Y - end.Y))
+                {
+                    orientation = Orientation.Horizontal;
+                    length = end.X - start.X;
+                }
+
+                if (length > 0)
+                {
+                    length++;
+                }
+                else
+                {
+                    length--;
+                }
+
+                _currentLine.Length = length;
+                _currentLine.Orientation = orientation;
+                area.CurrentLayer.ClearCache ();
+                area.SetNeedsDisplay ();
+            }
+        }
+        else
+        {
+            // Mouse released
+            if (_currentLine != null)
+            {
+                if (_currentLine.Length == 0)
+                {
+                    _currentLine.Length = 1;
+                }
+
+                if (_currentLine.Style == LineStyle.None)
+                {
+                    // Treat none as eraser
+                    int idx = area.Layers.IndexOf (area.CurrentLayer);
+                    area.Layers.Remove (area.CurrentLayer);
+
+                    area.CurrentLayer = new (
+                                             area.CurrentLayer.Lines.Exclude (
+                                                                              _currentLine.Start,
+                                                                              _currentLine.Length,
+                                                                              _currentLine.Orientation
+                                                                             )
+                                            );
+
+                    area.Layers.Insert (idx, area.CurrentLayer);
+                }
+
+                _currentLine = null;
+                area.ClearUndo ();
+                area.SetNeedsDisplay ();
+            }
+        }
+    }
+}
+
 [ScenarioMetadata ("Line Drawing", "Demonstrates LineCanvas.")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("Drawing")]
@@ -18,12 +112,14 @@ public class LineDrawing : Scenario
 
         var tools = new ToolsView { Title = "Tools", X = Pos.Right (canvas) - 20, Y = 2 };
 
-        tools.ColorChanged += c => canvas.SetColor (c);
-        tools.SetStyle += b => canvas.LineStyle = b;
+        tools.ColorChanged += (s, e) => canvas.SetAttribute (e);
+        tools.SetStyle += b => canvas.CurrentTool = new DrawLineTool { LineStyle = b };
         tools.AddLayer += () => canvas.AddLayer ();
 
         win.Add (canvas);
         win.Add (tools);
+        tools.CurrentColor = canvas.GetNormalColor ();
+        canvas.CurrentAttribute = tools.CurrentColor;
 
         win.KeyDown += (s, e) => { e.Handled = canvas.OnKeyDown (e); };
 
@@ -32,206 +128,329 @@ public class LineDrawing : Scenario
         Application.Shutdown ();
     }
 
-    private class DrawingArea : View
+    public static bool PromptForColor (string title, Color current, out Color newColor)
     {
-        private readonly List<LineCanvas> _layers = new ();
-        private readonly Stack<StraightLine> _undoHistory = new ();
-        private Color _currentColor = new (Color.White);
-        private LineCanvas _currentLayer;
-        private StraightLine _currentLine;
-        public DrawingArea () { AddLayer (); }
-        public LineStyle LineStyle { get; set; }
+        var accept = false;
 
-        public override void OnDrawContentComplete (Rectangle viewport)
+        var d = new Dialog
         {
-            base.OnDrawContentComplete (viewport);
+            Title = title,
+            Height = 7
+        };
 
-            foreach (LineCanvas canvas in _layers)
-            {
-                foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
-                {
-                    if (c.Value is { })
-                    {
-                        Driver.SetAttribute (c.Value.Value.Attribute ?? ColorScheme.Normal);
+        var btnOk = new Button
+        {
+            X = Pos.Center () - 5,
+            Y = 4,
+            Text = "Ok",
+            Width = Dim.Auto (),
+            IsDefault = true
+        };
+
+        btnOk.Accept += (s, e) =>
+                        {
+                            accept = true;
+                            e.Handled = true;
+                            Application.RequestStop ();
+                        };
+
+        var btnCancel = new Button
+        {
+            X = Pos.Center () + 5,
+            Y = 4,
+            Text = "Cancel",
+            Width = Dim.Auto ()
+        };
+
+        btnCancel.Accept += (s, e) =>
+                            {
+                                e.Handled = true;
+                                Application.RequestStop ();
+                            };
+
+        d.Add (btnOk);
+        d.Add (btnCancel);
+
+        /* Does not work
+        d.AddButton (btnOk);
+        d.AddButton (btnCancel);
+        */
+        var cp = new ColorPicker
+        {
+            SelectedColor = current,
+            Width = Dim.Fill ()
+        };
 
-                        // TODO: #2616 - Support combining sequences that don't normalize
-                        AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune);
-                    }
-                }
-            }
-        }
+        d.Add (cp);
+
+        Application.Run (d);
+        d.Dispose ();
+        newColor = cp.SelectedColor;
+
+        return accept;
+    }
+}
+
+public class ToolsView : Window
+{
+    private Button _addLayerBtn;
+    private readonly AttributeView _colors;
+    private RadioGroup _stylePicker;
+
+    public Attribute CurrentColor
+    {
+        get => _colors.Value;
+        set => _colors.Value = value;
+    }
+
+    public ToolsView ()
+    {
+        BorderStyle = LineStyle.Dotted;
+        Border.Thickness = new (1, 2, 1, 1);
+        Initialized += ToolsView_Initialized;
+        _colors = new ();
+    }
+
+    public event Action AddLayer;
 
-        //// BUGBUG: Why is this not handled by a key binding???
-        public override bool OnKeyDown (Key e)
+    public override void BeginInit ()
+    {
+        base.BeginInit ();
+
+        _colors.ValueChanged += (s, e) => ColorChanged?.Invoke (this, e);
+
+        _stylePicker = new()
         {
-            // BUGBUG: These should be implemented with key bindings
-            if (e.KeyCode == (KeyCode.Z | KeyCode.CtrlMask))
-            {
-                StraightLine pop = _currentLayer.RemoveLastLine ();
+            X = 0, Y = Pos.Bottom (_colors), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
+        };
+        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
+        _stylePicker.SelectedItem = 1;
 
-                if (pop != null)
-                {
-                    _undoHistory.Push (pop);
-                    SetNeedsDisplay ();
+        _addLayerBtn = new() { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
 
-                    return true;
-                }
-            }
+        _addLayerBtn.Accept += (s, a) => AddLayer?.Invoke ();
+        Add (_colors, _stylePicker, _addLayerBtn);
+    }
+
+    public event EventHandler<Attribute> ColorChanged;
+    public event Action<LineStyle> SetStyle;
+
+    private void ToolsView_Initialized (object sender, EventArgs e)
+    {
+        LayoutSubviews ();
+
+        Width = Math.Max (_colors.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
+
+        Height = _colors.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical;
+        SuperView.LayoutSubviews ();
+    }
+}
+
+public class DrawingArea : View
+{
+    public readonly List<LineCanvas> Layers = new ();
+    private readonly Stack<StraightLine> _undoHistory = new ();
+    public Attribute CurrentAttribute { get; set; }
+    public LineCanvas CurrentLayer { get; set; }
+
+    public ITool CurrentTool { get; set; } = new DrawLineTool ();
+    public DrawingArea () { AddLayer (); }
 
-            if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask))
+    public override void OnDrawContentComplete (Rectangle viewport)
+    {
+        base.OnDrawContentComplete (viewport);
+
+        foreach (LineCanvas canvas in Layers)
+        {
+            foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
             {
-                if (_undoHistory.Any ())
+                if (c.Value is { })
                 {
-                    StraightLine pop = _undoHistory.Pop ();
-                    _currentLayer.AddLine (pop);
-                    SetNeedsDisplay ();
+                    Driver.SetAttribute (c.Value.Value.Attribute ?? ColorScheme.Normal);
 
-                    return true;
+                    // TODO: #2616 - Support combining sequences that don't normalize
+                    AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune);
                 }
             }
-
-            return false;
         }
+    }
 
-        protected override bool OnMouseEvent (MouseEvent mouseEvent)
+    //// BUGBUG: Why is this not handled by a key binding???
+    public override bool OnKeyDown (Key e)
+    {
+        // BUGBUG: These should be implemented with key bindings
+        if (e.KeyCode == (KeyCode.Z | KeyCode.CtrlMask))
         {
-            if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+            StraightLine pop = CurrentLayer.RemoveLastLine ();
+
+            if (pop != null)
             {
-                if (_currentLine == null)
-                {
-                    // Mouse pressed down
-                    _currentLine = new StraightLine (
-                                                     mouseEvent.Position,
-                                                     0,
-                                                     Orientation.Vertical,
-                                                     LineStyle,
-                                                     new Attribute (_currentColor, GetNormalColor ().Background)
-                                                    );
-
-                    _currentLayer.AddLine (_currentLine);
-                }
-                else
-                {
-                    // Mouse dragged
-                    Point start = _currentLine.Start;
-                    var end = mouseEvent.Position;
-                    var orientation = Orientation.Vertical;
-                    int length = end.Y - start.Y;
-
-                    // if line is wider than it is tall switch to horizontal
-                    if (Math.Abs (start.X - end.X) > Math.Abs (start.Y - end.Y))
-                    {
-                        orientation = Orientation.Horizontal;
-                        length = end.X - start.X;
-                    }
-
-                    if (length > 0)
-                    {
-                        length++;
-                    }
-                    else
-                    {
-                        length--;
-                    }
-
-                    _currentLine.Length = length;
-                    _currentLine.Orientation = orientation;
-                    _currentLayer.ClearCache ();
-                    SetNeedsDisplay ();
-                }
+                _undoHistory.Push (pop);
+                SetNeedsDisplay ();
+
+                return true;
             }
-            else
+        }
+
+        if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask))
+        {
+            if (_undoHistory.Any ())
             {
-                // Mouse released
-                if (_currentLine != null)
-                {
-                    if (_currentLine.Length == 0)
-                    {
-                        _currentLine.Length = 1;
-                    }
-
-                    if (_currentLine.Style == LineStyle.None)
-                    {
-                        // Treat none as eraser
-                        int idx = _layers.IndexOf (_currentLayer);
-                        _layers.Remove (_currentLayer);
-
-                        _currentLayer = new LineCanvas (
-                                                        _currentLayer.Lines.Exclude (
-                                                                                     _currentLine.Start,
-                                                                                     _currentLine.Length,
-                                                                                     _currentLine.Orientation
-                                                                                    )
-                                                       );
-
-                        _layers.Insert (idx, _currentLayer);
-                    }
-
-                    _currentLine = null;
-                    _undoHistory.Clear ();
-                    SetNeedsDisplay ();
-                }
-            }
+                StraightLine pop = _undoHistory.Pop ();
+                CurrentLayer.AddLine (pop);
+                SetNeedsDisplay ();
 
-            return base.OnMouseEvent (mouseEvent);
+                return true;
+            }
         }
 
-        internal void AddLayer ()
+        return false;
+    }
+
+    protected override bool OnMouseEvent (MouseEvent mouseEvent)
+    {
+        CurrentTool.OnMouseEvent (this, mouseEvent);
+
+        return base.OnMouseEvent (mouseEvent);
+    }
+
+    internal void AddLayer ()
+    {
+        CurrentLayer = new ();
+        Layers.Add (CurrentLayer);
+    }
+
+    internal void SetAttribute (Attribute a) { CurrentAttribute = a; }
+
+    public void ClearUndo () { _undoHistory.Clear (); }
+}
+
+public class AttributeView : View
+{
+    public event EventHandler<Attribute> ValueChanged;
+    private Attribute _value;
+
+    public Attribute Value
+    {
+        get => _value;
+        set
         {
-            _currentLayer = new LineCanvas ();
-            _layers.Add (_currentLayer);
+            _value = value;
+            ValueChanged?.Invoke (this, value);
         }
+    }
 
-        internal void SetColor (Color c) { _currentColor = c; }
+    private static readonly HashSet<(int, int)> ForegroundPoints = new()
+    {
+        (0, 0), (1, 0), (2, 0),
+        (0, 1), (1, 1), (2, 1)
+    };
+
+    private static readonly HashSet<(int, int)> BackgroundPoints = new()
+    {
+        (3, 1),
+        (1, 2), (2, 2), (3, 2)
+    };
+
+    public AttributeView ()
+    {
+        Width = 4;
+        Height = 3;
     }
 
-    private class ToolsView : Window
+    /// <inheritdoc/>
+    public override void OnDrawContent (Rectangle viewport)
     {
-        private Button _addLayerBtn;
-        private ColorPicker _colorPicker;
-        private RadioGroup _stylePicker;
+        base.OnDrawContent (viewport);
 
-        public ToolsView ()
+        Color fg = Value.Foreground;
+        Color bg = Value.Background;
+
+        bool isTransparentFg = fg == GetNormalColor ().Background;
+        bool isTransparentBg = bg == GetNormalColor ().Background;
+
+        Driver.SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg));
+
+        // Square of foreground color
+        foreach ((int, int) point in ForegroundPoints)
         {
-            BorderStyle = LineStyle.Dotted;
-            Border.Thickness = new Thickness (1, 2, 1, 1);
-            Initialized += ToolsView_Initialized;
+            // Make pattern like this when it is same color as background of control
+            /*▓▒
+              ▒▓*/
+            Rune rune;
+
+            if (isTransparentFg)
+            {
+                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
+            }
+            else
+            {
+                rune = (Rune)'█';
+            }
+
+            AddRune (point.Item1, point.Item2, rune);
         }
 
-        public event Action AddLayer;
+        Driver.SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));
 
-        public override void BeginInit ()
+        // Square of background color
+        foreach ((int, int) point in BackgroundPoints)
         {
-            base.BeginInit ();
+            // Make pattern like this when it is same color as background of control
+            /*▓▒
+              ▒▓*/
+            Rune rune;
 
-            _colorPicker = new ColorPicker { X = 0, Y = 0, BoxHeight = 1, BoxWidth = 2 };
+            if (isTransparentBg)
+            {
+                rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
+            }
+            else
+            {
+                rune = (Rune)'█';
+            }
 
-            _colorPicker.ColorChanged += (s, a) => ColorChanged?.Invoke (a.Color);
+            AddRune (point.Item1, point.Item2, rune);
+        }
+    }
 
-            _stylePicker = new RadioGroup
+    /// <inheritdoc/>
+    protected override bool OnMouseEvent (MouseEvent mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
+        {
+            if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
+            {
+                ClickedInForeground ();
+            }
+            else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
             {
-                X = 0, Y = Pos.Bottom (_colorPicker), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
-            };
-            _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
-            _stylePicker.SelectedItem = 1;
+                ClickedInBackground ();
+            }
+        }
 
-            _addLayerBtn = new Button { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
+        return base.OnMouseEvent (mouseEvent);
+    }
 
-            _addLayerBtn.Accept += (s, a) => AddLayer?.Invoke ();
-            Add (_colorPicker, _stylePicker, _addLayerBtn);
-        }
+    private bool IsForegroundPoint (int x, int y) { return ForegroundPoints.Contains ((x, y)); }
 
-        public event Action<Color> ColorChanged;
-        public event Action<LineStyle> SetStyle;
+    private bool IsBackgroundPoint (int x, int y) { return BackgroundPoints.Contains ((x, y)); }
 
-        private void ToolsView_Initialized (object sender, EventArgs e)
+    private void ClickedInBackground ()
+    {
+        if (LineDrawing.PromptForColor ("Background", Value.Background, out Color newColor))
         {
-            LayoutSubviews ();
-
-            Width = Math.Max (_colorPicker.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
+            Value = new (Value.Foreground, newColor);
+            SetNeedsDisplay ();
+        }
+    }
 
-            Height = _colorPicker.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical;
-            SuperView.LayoutSubviews ();
+    private void ClickedInForeground ()
+    {
+        if (LineDrawing.PromptForColor ("Foreground", Value.Foreground, out Color newColor))
+        {
+            Value = new (newColor, Value.Background);
+            SetNeedsDisplay ();
         }
     }
 }

+ 17 - 44
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -63,55 +63,25 @@ public class ProgressBarStyles : Scenario
 
         #region ColorPicker
 
-        ColorName ChooseColor (string text, ColorName colorName)
-        {
-            var colorPicker = new ColorPicker { Title = text, SelectedColor = colorName };
-
-            var dialog = new Dialog { Title = text };
-
-            dialog.Initialized += (sender, args) =>
-                                     {
-                                         // TODO: Replace with Dim.Auto
-                                         dialog.X = _pbList.Frame.X;
-                                         dialog.Y = _pbList.Frame.Height;
-                                     };
-
-            dialog.LayoutComplete += (sender, args) =>
-                                    {
-                                        dialog.Viewport = Rectangle.Empty with
-                                        {
-                                            Width = colorPicker.Frame.Width,
-                                            Height = colorPicker.Frame.Height
-                                        };
-                                        Application.Top.LayoutSubviews ();
-                                    };
-
-            dialog.Add (colorPicker);
-            colorPicker.ColorChanged += (s, e) => { dialog.RequestStop (); };
-            Application.Run (dialog);
-            dialog.Dispose ();
-
-            ColorName retColor = colorPicker.SelectedColor;
-            colorPicker.Dispose ();
-
-            return retColor;
-        }
 
         var fgColorPickerBtn = new Button
         {
             Text = "Foreground HotNormal Color",
             X = Pos.Center (),
-            Y = Pos.Align (Alignment.Start),
+            Y = Pos.Align (Alignment.Start)
         };
         container.Add (fgColorPickerBtn);
 
         fgColorPickerBtn.Accept += (s, e) =>
                                     {
-                                        ColorName newColor = ChooseColor (
-                                                                          fgColorPickerBtn.Text,
-                                                                          editor.ViewToEdit.ColorScheme.HotNormal.Foreground
-                                                                                .GetClosestNamedColor ()
-                                                                         );
+                                        if (!LineDrawing.PromptForColor (
+                                                                         fgColorPickerBtn.Text,
+                                                                         editor.ViewToEdit.ColorScheme.HotNormal.Foreground,
+                                                                         out var newColor
+                                                                        ))
+                                        {
+                                            return;
+                                        }
 
                                         var cs = new ColorScheme (editor.ViewToEdit.ColorScheme)
                                         {
@@ -134,11 +104,14 @@ public class ProgressBarStyles : Scenario
 
         bgColorPickerBtn.Accept += (s, e) =>
                                     {
-                                        ColorName newColor = ChooseColor (
-                                                                          fgColorPickerBtn.Text,
-                                                                          editor.ViewToEdit.ColorScheme.HotNormal.Background
-                                                                                .GetClosestNamedColor ()
-                                                                         );
+                                        if (!LineDrawing.PromptForColor (
+                                                                         fgColorPickerBtn.Text,
+                                                                         editor.ViewToEdit.ColorScheme.HotNormal.Background
+                                                                        , out var newColor))
+
+                                        {
+                                            return;
+                                        }
 
                                         var cs = new ColorScheme (editor.ViewToEdit.ColorScheme)
                                         {

+ 3 - 3
UICatalog/Scenarios/Shortcuts.cs

@@ -318,17 +318,17 @@ public class Shortcuts : Scenario
             CanFocus = false
         };
 
-        var bgColor = new ColorPicker ()
+        var bgColor = new ColorPicker16 ()
         {
-            CanFocus = false,
             BoxHeight = 1,
             BoxWidth = 1,
+            CanFocus = false
         };
         bgColor.ColorChanged += (o, args) =>
                                 {
                                     Application.Top.ColorScheme = new ColorScheme (Application.Top.ColorScheme)
                                     {
-                                        Normal = new Attribute (Application.Top.ColorScheme.Normal.Foreground, args.Color),
+                                        Normal = new Attribute (Application.Top.ColorScheme.Normal.Foreground, args.CurrentValue),
                                     };
                                 };
         hShortcutBG.CommandView = bgColor;

+ 79 - 0
UnitTests/Views/ColorPicker16Tests.cs

@@ -0,0 +1,79 @@
+namespace Terminal.Gui.ViewsTests;
+
+public class ColorPicker16Tests
+{
+    [Fact]
+    public void Constructors ()
+    {
+        var colorPicker = new ColorPicker16 ();
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        Assert.Equal (Point.Empty, colorPicker.Cursor);
+        Assert.True (colorPicker.CanFocus);
+
+        colorPicker.BeginInit ();
+        colorPicker.EndInit ();
+        colorPicker.LayoutSubviews ();
+        Assert.Equal (new (0, 0, 32, 4), colorPicker.Frame);
+    }
+
+    [Fact]
+    public void KeyBindings_Command ()
+    {
+        var colorPicker = new ColorPicker16 ();
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorRight));
+        Assert.Equal (ColorName.Blue, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorDown));
+        Assert.Equal (ColorName.BrightBlue, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
+        Assert.Equal (ColorName.DarkGray, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+
+        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void MouseEvents ()
+    {
+        var colorPicker = new ColorPicker16 { X = 0, Y = 0, Height = 4, Width = 32 };
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        var top = new Toplevel ();
+        top.Add (colorPicker);
+        Application.Begin (top);
+
+        Assert.False (colorPicker.NewMouseEvent (new ()));
+
+        Assert.True (colorPicker.NewMouseEvent (new () { Position = new (4, 1), Flags = MouseFlags.Button1Clicked }));
+        Assert.Equal (ColorName.Blue, colorPicker.SelectedColor);
+        top.Dispose ();
+    }
+
+    [Fact]
+    public void SelectedColorAndCursor ()
+    {
+        var colorPicker = new ColorPicker16 ();
+        colorPicker.SelectedColor = ColorName.White;
+        Assert.Equal (7, colorPicker.Cursor.X);
+        Assert.Equal (1, colorPicker.Cursor.Y);
+
+        colorPicker.SelectedColor = Color.Black;
+        Assert.Equal (0, colorPicker.Cursor.X);
+        Assert.Equal (0, colorPicker.Cursor.Y);
+
+        colorPicker.Cursor = new (7, 1);
+        Assert.Equal (ColorName.White, colorPicker.SelectedColor);
+
+        colorPicker.Cursor = Point.Empty;
+        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+    }
+}

+ 735 - 48
UnitTests/Views/ColorPickerTests.cs

@@ -1,79 +1,766 @@
-namespace Terminal.Gui.ViewsTests;
+using Xunit.Abstractions;
+using Color = Terminal.Gui.Color;
+
+namespace Terminal.Gui.ViewsTests;
 
 public class ColorPickerTests
 {
     [Fact]
-    public void Constructors ()
+    [SetupFakeDriver]
+    public void ColorPicker_Construct_DefaultValue ()
+    {
+        var cp = GetColorPicker (ColorModel.HSV, false);
+
+        // Should be only a single text field (Hex) because ShowTextFields is false
+        Assert.Single (cp.Subviews.OfType<TextField> ());
+
+        cp.Draw ();
+
+        // All bars should be at 0 with the triangle at 0 (+2 because of "H:", "S:" etc)
+        var h = GetColorBar (cp, ColorPickerPart.Bar1);
+        Assert.Equal ("H:", h.Text);
+        Assert.Equal (2, h.TrianglePosition);
+        Assert.IsType<HueBar> (h);
+
+        var s = GetColorBar (cp, ColorPickerPart.Bar2);
+        Assert.Equal ("S:", s.Text);
+        Assert.Equal (2, s.TrianglePosition);
+        Assert.IsType<SaturationBar> (s);
+
+        var v = GetColorBar (cp, ColorPickerPart.Bar3);
+        Assert.Equal ("V:", v.Text);
+        Assert.Equal (2, v.TrianglePosition);
+        Assert.IsType<ValueBar> (v);
+
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+        Assert.Equal ("#000000", hex.Text);
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_RGB_KeyboardNavigation ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, false);
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (2, r.TrianglePosition);
+        Assert.IsType<RBar> (r);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.IsType<GBar> (g);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.IsType<BBar> (b);
+        Assert.Equal ("#000000", hex.Text);
+
+        Assert.IsAssignableFrom<IColorBar> (cp.Focused);
+
+        cp.Draw ();
+
+        Application.OnKeyDown (Key.CursorRight);
+
+        cp.Draw ();
+
+        Assert.Equal (3, r.TrianglePosition);
+        Assert.Equal ("#0F0000", hex.Text);
+
+        Application.OnKeyDown (Key.CursorRight);
+
+        cp.Draw ();
+
+        Assert.Equal (4, r.TrianglePosition);
+        Assert.Equal ("#1E0000", hex.Text);
+
+        // Use cursor to move the triangle all the way to the right
+        for (int i = 0; i < 1000; i++)
+        {
+            Application.OnKeyDown (Key.CursorRight);
+        }
+
+        cp.Draw ();
+
+        // 20 width and TrianglePosition is 0 indexed
+        // Meaning we are asserting that triangle is at end
+        Assert.Equal (19, r.TrianglePosition);
+        Assert.Equal ("#FF0000", hex.Text);
+
+        Application.Current.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_RGB_MouseNavigation ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB,false);
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (2, r.TrianglePosition);
+        Assert.IsType<RBar> (r);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.IsType<GBar> (g);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.IsType<BBar> (b);
+        Assert.Equal ("#000000", hex.Text);
+
+        Assert.IsAssignableFrom<IColorBar> (cp.Focused);
+
+        cp.Focused.OnMouseEvent (
+                                 new ()
+                                 {
+                                     Flags = MouseFlags.Button1Pressed,
+                                     Position = new (3, 0)
+                                 });
+
+        cp.Draw ();
+
+        Assert.Equal (3, r.TrianglePosition);
+        Assert.Equal ("#0F0000", hex.Text);
+
+        cp.Focused.OnMouseEvent (
+                                  new ()
+                                  {
+                                      Flags = MouseFlags.Button1Pressed,
+                                      Position = new (4, 0)
+                                  });
+
+        cp.Draw ();
+
+        Assert.Equal (4, r.TrianglePosition);
+        Assert.Equal ("#1E0000", hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+
+    public static IEnumerable<object []> ColorPickerTestData ()
+    {
+        yield return new object []
+        {
+            new Color(255, 0),
+            "R:", 19, "G:", 2, "B:", 2, "#FF0000"
+        };
+
+        yield return new object []
+        {
+            new Color(0, 255),
+            "R:", 2, "G:", 19, "B:", 2, "#00FF00"
+        };
+
+        yield return new object []
+        {
+            new Color(0, 0, 255),
+            "R:", 2, "G:", 2, "B:", 19, "#0000FF"
+        };
+
+        yield return new object []
+        {
+            new Color(125, 125, 125),
+            "R:", 11, "G:", 11, "B:", 11, "#7D7D7D"
+        };
+    }
+
+    [Theory]
+    [SetupFakeDriver]
+    [MemberData (nameof (ColorPickerTestData))]
+    public void ColorPicker_RGB_NoText (Color c, string expectedR, int expectedRTriangle, string expectedG, int expectedGTriangle, string expectedB, int expectedBTriangle, string expectedHex)
+    {
+        var cp = GetColorPicker (ColorModel.RGB, false);
+        cp.SelectedColor = c;
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal (expectedR, r.Text);
+        Assert.Equal (expectedRTriangle, r.TrianglePosition);
+        Assert.Equal (expectedG, g.Text);
+        Assert.Equal (expectedGTriangle, g.TrianglePosition);
+        Assert.Equal (expectedB, b.Text);
+        Assert.Equal (expectedBTriangle, b.TrianglePosition);
+        Assert.Equal (expectedHex, hex.Text);
+
+        Application.Current.Dispose ();
+    }
+
+    public static IEnumerable<object []> ColorPickerTestData_WithTextFields ()
+    {
+        yield return new object []
+        {
+            new Color(255, 0),
+            "R:", 15, 255, "G:", 2, 0, "B:", 2, 0, "#FF0000"
+        };
+
+        yield return new object []
+        {
+            new Color(0, 255),
+            "R:", 2, 0, "G:", 15, 255, "B:", 2, 0, "#00FF00"
+        };
+
+        yield return new object []
+        {
+            new Color(0, 0, 255),
+            "R:", 2, 0, "G:", 2, 0, "B:", 15, 255, "#0000FF"
+        };
+
+        yield return new object []
+        {
+            new Color(125, 125, 125),
+            "R:", 9, 125, "G:", 9, 125, "B:", 9, 125, "#7D7D7D"
+        };
+    }
+
+    [Theory]
+    [SetupFakeDriver]
+    [MemberData (nameof (ColorPickerTestData_WithTextFields))]
+    public void ColorPicker_RGB_NoText_WithTextFields (Color c, string expectedR, int expectedRTriangle, int expectedRValue, string expectedG, int expectedGTriangle, int expectedGValue, string expectedB, int expectedBTriangle, int expectedBValue, string expectedHex)
+    {
+        var cp = GetColorPicker (ColorModel.RGB, true);
+        cp.SelectedColor = c;
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+        var rTextField = GetTextField (cp, ColorPickerPart.Bar1);
+        var gTextField = GetTextField (cp, ColorPickerPart.Bar2);
+        var bTextField = GetTextField (cp, ColorPickerPart.Bar3);
+
+        Assert.Equal (expectedR, r.Text);
+        Assert.Equal (expectedRTriangle, r.TrianglePosition);
+        Assert.Equal (expectedRValue.ToString (), rTextField.Text);
+        Assert.Equal (expectedG, g.Text);
+        Assert.Equal (expectedGTriangle, g.TrianglePosition);
+        Assert.Equal (expectedGValue.ToString (), gTextField.Text);
+        Assert.Equal (expectedB, b.Text);
+        Assert.Equal (expectedBTriangle, b.TrianglePosition);
+        Assert.Equal (expectedBValue.ToString (), bTextField.Text);
+        Assert.Equal (expectedHex, hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_ClickingAtEndOfBar_SetsMaxValue ()
     {
-        var colorPicker = new ColorPicker ();
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
-        Assert.Equal (Point.Empty, colorPicker.Cursor);
-        Assert.True (colorPicker.CanFocus);
+        var cp = GetColorPicker (ColorModel.RGB, false);
 
-        colorPicker.BeginInit ();
-        colorPicker.EndInit ();
-        colorPicker.LayoutSubviews ();
-        Assert.Equal (new (0, 0, 32, 4), colorPicker.Frame);
+        cp.Draw ();
+
+        // Click at the end of the Red bar
+        cp.Focused.OnMouseEvent (
+                                 new ()
+                                 {
+                                     Flags = MouseFlags.Button1Pressed,
+                                     Position = new (19, 0) // Assuming 0-based indexing
+                                 });
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (19, r.TrianglePosition);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("#FF0000", hex.Text);
+
+        Application.Current?.Dispose ();
     }
 
     [Fact]
-    public void KeyBindings_Command ()
+    [SetupFakeDriver]
+    public void ColorPicker_ClickingBeyondBar_ChangesToMaxValue ()
     {
-        var colorPicker = new ColorPicker ();
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        var cp = GetColorPicker (ColorModel.RGB, false);
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorRight));
-        Assert.Equal (ColorName.Blue, colorPicker.SelectedColor);
+        cp.Draw ();
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorDown));
-        Assert.Equal (ColorName.BrightBlue, colorPicker.SelectedColor);
+        // Click beyond the bar
+        cp.Focused.OnMouseEvent (
+                                 new ()
+                                 {
+                                     Flags = MouseFlags.Button1Pressed,
+                                     Position = new (21, 0) // Beyond the bar
+                                 });
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
-        Assert.Equal (ColorName.DarkGray, colorPicker.SelectedColor);
+        cp.Draw ();
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (19, r.TrianglePosition);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("#FF0000", hex.Text);
 
-        Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        Application.Current?.Dispose ();
     }
 
     [Fact]
-    [AutoInitShutdown]
-    public void MouseEvents ()
+    [SetupFakeDriver]
+    public void ColorPicker_ChangeValueOnUI_UpdatesAllUIElements ()
     {
-        var colorPicker = new ColorPicker { X = 0, Y = 0, Height = 4, Width = 32 };
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
-        var top = new Toplevel ();
-        top.Add (colorPicker);
-        Application.Begin (top);
+        var cp = GetColorPicker (ColorModel.RGB, true);
+
+        View otherView = new View () { CanFocus = true };
 
-        Assert.False (colorPicker.NewMouseEvent (new ()));
+        Application.Current?.Add (otherView);
 
-        Assert.True (colorPicker.NewMouseEvent (new() { Position = new (4, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (ColorName.Blue, colorPicker.SelectedColor);
-        top.Dispose ();
+        cp.Draw ();
+
+        // Change value using text field
+        TextField rBarTextField = cp.Subviews.OfType<TextField> ().First (tf => tf.Text == "0");
+
+        rBarTextField.Text = "128";
+        //rBarTextField.OnLeave (cp); // OnLeave should be protected virtual. Don't call it.
+        otherView.SetFocus (); // Remove focus from the color picker
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+        var rTextField = GetTextField (cp, ColorPickerPart.Bar1);
+        var gTextField = GetTextField (cp, ColorPickerPart.Bar2);
+        var bTextField = GetTextField (cp, ColorPickerPart.Bar3);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (9, r.TrianglePosition);
+        Assert.Equal ("128", rTextField.Text);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("0", gTextField.Text);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("0", bTextField.Text);
+        Assert.Equal ("#800000", hex.Text);
+
+        Application.Current?.Dispose ();
     }
 
     [Fact]
-    public void SelectedColorAndCursor ()
+    [SetupFakeDriver]
+    public void ColorPicker_InvalidHexInput_DoesNotChangeColor ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, true);
+
+        cp.Draw ();
+
+        // Enter invalid hex value
+        TextField hexField = cp.Subviews.OfType<TextField> ().First (tf => tf.Text == "#000000");
+        hexField.Text = "#ZZZZZZ";
+        hexField.OnLeave (cp);
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (2, r.TrianglePosition);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("#000000", hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_ClickingDifferentBars_ChangesFocus ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, false);
+
+        cp.Draw ();
+
+        // Click on Green bar
+        cp.Subviews.OfType<GBar> ()
+          .Single ()
+          .OnMouseEvent (
+                         new ()
+                         {
+                             Flags = MouseFlags.Button1Pressed,
+                             Position = new (0, 1)
+                         });
+
+        cp.Draw ();
+
+        Assert.IsAssignableFrom<GBar> (cp.Focused);
+
+        // Click on Blue bar
+        cp.Subviews.OfType<BBar> ()
+          .Single ()
+          .OnMouseEvent (
+                         new ()
+                         {
+                             Flags = MouseFlags.Button1Pressed,
+                             Position = new (0, 2)
+                         });
+
+        cp.Draw ();
+
+        Assert.IsAssignableFrom<BBar> (cp.Focused);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_SwitchingColorModels_ResetsBars ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, false);
+        cp.SelectedColor = new (255, 0);
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (19, r.TrianglePosition);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("#FF0000", hex.Text);
+
+        // Switch to HSV
+        cp.Style.ColorModel = ColorModel.HSV;
+        cp.ApplyStyleChanges ();
+
+        cp.Draw ();
+
+        var h = GetColorBar (cp, ColorPickerPart.Bar1);
+        var s = GetColorBar (cp, ColorPickerPart.Bar2);
+        var v = GetColorBar (cp, ColorPickerPart.Bar3);
+
+        Assert.Equal ("H:", h.Text);
+        Assert.Equal (2, h.TrianglePosition);
+        Assert.Equal ("S:", s.Text);
+        Assert.Equal (19, s.TrianglePosition);
+        Assert.Equal ("V:", v.Text);
+        Assert.Equal (19, v.TrianglePosition);
+        Assert.Equal ("#FF0000", hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_SyncBetweenTextFieldAndBars ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, true);
+
+        cp.Draw ();
+
+        // Change value using the bar
+        RBar rBar = cp.Subviews.OfType<RBar> ().First ();
+        rBar.Value = 128;
+
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+        var rTextField = GetTextField (cp, ColorPickerPart.Bar1);
+        var gTextField = GetTextField (cp, ColorPickerPart.Bar2);
+        var bTextField = GetTextField (cp, ColorPickerPart.Bar3);
+
+        Assert.Equal ("R:", r.Text);
+        Assert.Equal (9, r.TrianglePosition);
+        Assert.Equal ("128", rTextField.Text);
+        Assert.Equal ("G:", g.Text);
+        Assert.Equal (2, g.TrianglePosition);
+        Assert.Equal ("0", gTextField.Text);
+        Assert.Equal ("B:", b.Text);
+        Assert.Equal (2, b.TrianglePosition);
+        Assert.Equal ("0", bTextField.Text);
+        Assert.Equal ("#800000", hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    enum ColorPickerPart
+    {
+        Bar1 = 0,
+        Bar2 = 1,
+        Bar3 = 2,
+        ColorName = 3,
+        Hex = 4,
+    }
+
+    private TextField GetTextField (ColorPicker cp, ColorPickerPart toGet)
+    {
+        var hasBarValueTextFields = cp.Style.ShowTextFields;
+        var hasColorNameTextField = cp.Style.ShowColorName;
+
+        switch (toGet)
+        {
+            case ColorPickerPart.Bar1:
+            case ColorPickerPart.Bar2:
+            case ColorPickerPart.Bar3:
+                if (!hasBarValueTextFields)
+                {
+                    throw new NotSupportedException ("Corresponding Style option is not enabled");
+                }
+
+                return cp.Subviews.OfType<TextField> ().ElementAt ((int)toGet);
+            case ColorPickerPart.ColorName:
+                if (!hasColorNameTextField)
+                {
+                    throw new NotSupportedException ("Corresponding Style option is not enabled");
+                }
+
+                return cp.Subviews.OfType<TextField> ().ElementAt (hasBarValueTextFields  ? (int)toGet : (int)toGet -3);
+            case ColorPickerPart.Hex:
+
+                int offset = hasBarValueTextFields ? 0 : 3;
+                offset += hasColorNameTextField ? 0 : 1;
+
+                return cp.Subviews.OfType<TextField> ().ElementAt ((int)toGet - offset);
+            default:
+                throw new ArgumentOutOfRangeException (nameof (toGet), toGet, null);
+        }
+    }
+
+    private ColorBar GetColorBar (ColorPicker cp, ColorPickerPart toGet)
+    {
+        if (toGet <= ColorPickerPart.Bar3)
+        {
+            return cp.Subviews.OfType<ColorBar> ().ElementAt ((int)toGet);
+        }
+        throw new NotSupportedException ("ColorPickerPart must be a bar");
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_ChangedEvent_Fires ()
+    {
+        Color newColor = default;
+        var count = 0;
+
+        var cp = new ColorPicker ();
+
+        cp.ColorChanged += (s, e) =>
+        {
+            count++;
+            newColor = e.CurrentValue;
+
+            Assert.Equal (cp.SelectedColor, e.CurrentValue);
+        };
+
+        cp.SelectedColor = new (1, 2, 3);
+        Assert.Equal (1, count);
+        Assert.Equal (new (1, 2, 3), newColor);
+
+        cp.SelectedColor = new (2, 3, 4);
+
+        Assert.Equal (2, count);
+        Assert.Equal (new (2, 3, 4), newColor);
+
+        // Set to same value
+        cp.SelectedColor = new (2, 3, 4);
+
+        // Should have no effect
+        Assert.Equal (2, count);
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_DisposesOldViews_OnModelChange ()
+    {
+        var cp = GetColorPicker (ColorModel.HSL,true);
+
+        var b1 = GetColorBar (cp, ColorPickerPart.Bar1);
+        var b2 = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b3 = GetColorBar (cp, ColorPickerPart.Bar3);
+
+        var tf1 = GetTextField (cp, ColorPickerPart.Bar1);
+        var tf2 = GetTextField (cp, ColorPickerPart.Bar2);
+        var tf3 = GetTextField (cp, ColorPickerPart.Bar3);
+
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        Assert.All (new View [] { b1, b2, b3, tf1, tf2, tf3,hex }, b => Assert.False (b.WasDisposed));
+
+        cp.Style.ColorModel = ColorModel.RGB;
+        cp.ApplyStyleChanges ();
+
+        var b1After = GetColorBar (cp, ColorPickerPart.Bar1);
+        var b2After = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b3After = GetColorBar (cp, ColorPickerPart.Bar3);
+
+        var tf1After = GetTextField (cp, ColorPickerPart.Bar1);
+        var tf2After = GetTextField (cp, ColorPickerPart.Bar2);
+        var tf3After = GetTextField (cp, ColorPickerPart.Bar3);
+
+        var hexAfter = GetTextField (cp, ColorPickerPart.Hex);
+
+        // Old bars should be disposed
+        Assert.All (new View [] { b1, b2, b3, tf1, tf2, tf3,hex }, b => Assert.True (b.WasDisposed));
+        Assert.NotSame (hex,hexAfter);
+
+        Assert.NotSame (b1,b1After);
+        Assert.NotSame (b2, b2After);
+        Assert.NotSame (b3, b3After);
+
+        Assert.NotSame (tf1, tf1After);
+        Assert.NotSame (tf2, tf2After);
+        Assert.NotSame (tf3, tf3After);
+
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_TabCompleteColorName()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, true,true);
+        cp.Draw ();
+
+        var r = GetColorBar (cp, ColorPickerPart.Bar1);
+        var g = GetColorBar (cp, ColorPickerPart.Bar2);
+        var b = GetColorBar (cp, ColorPickerPart.Bar3);
+        var name = GetTextField (cp, ColorPickerPart.ColorName);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        name.FocusFirst (TabBehavior.TabStop);
+
+        Assert.True (name.HasFocus);
+        Assert.Same (name, cp.Focused);
+
+        name.Text = "";
+        Assert.Empty (name.Text);
+
+        Application.OnKeyDown (Key.A);
+        Application.OnKeyDown (Key.Q);
+
+        Assert.Equal ("aq",name.Text);
+
+
+        // Auto complete the color name
+        Application.OnKeyDown (Key.Tab);
+
+        Assert.Equal ("Aquamarine", name.Text);
+
+        // Tab out of the text field
+        Application.OnKeyDown (Key.Tab);
+
+        Assert.False (name.HasFocus);
+        Assert.NotSame (name, cp.Focused);
+
+        Assert.Equal ("#7FFFD4", hex.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    [SetupFakeDriver]
+    public void ColorPicker_EnterHexFor_ColorName ()
+    {
+        var cp = GetColorPicker (ColorModel.RGB, true, true);
+        cp.Draw ();
+
+        var name = GetTextField (cp, ColorPickerPart.ColorName);
+        var hex = GetTextField (cp, ColorPickerPart.Hex);
+
+        hex.FocusFirst (TabBehavior.TabStop);
+
+        Assert.True (hex.HasFocus);
+        Assert.Same (hex, cp.Focused);
+
+        hex.Text = "";
+        name.Text = "";
+
+        Assert.Empty (hex.Text);
+        Assert.Empty (name.Text);
+
+        Application.OnKeyDown ('#');
+        Assert.Empty (name.Text);
+        //7FFFD4
+
+        Assert.Equal ("#", hex.Text);
+        Application.OnKeyDown ('7');
+        Application.OnKeyDown ('F');
+        Application.OnKeyDown ('F');
+        Application.OnKeyDown ('F');
+        Application.OnKeyDown ('D');
+        Assert.Empty (name.Text);
+
+        Application.OnKeyDown ('4');
+
+        // Tab out of the text field
+        Application.OnKeyDown (Key.Tab);
+        Assert.False (hex.HasFocus);
+        Assert.NotSame (hex, cp.Focused);
+
+        // Color name should be recognised as a known string and populated
+        Assert.Equal ("#7FFFD4", hex.Text);
+        Assert.Equal("Aquamarine", name.Text);
+
+        Application.Current?.Dispose ();
+    }
+
+    [Fact]
+    public void TestColorNames ()
+    {
+        var colors = new W3CColors ();
+        Assert.Contains ("Aquamarine", colors.GetColorNames ());
+        Assert.DoesNotContain ("Save as",colors.GetColorNames ());
+    }
+    private ColorPicker GetColorPicker (ColorModel colorModel, bool showTextFields, bool showName = false)
     {
-        var colorPicker = new ColorPicker ();
-        colorPicker.SelectedColor = ColorName.White;
-        Assert.Equal (7, colorPicker.Cursor.X);
-        Assert.Equal (1, colorPicker.Cursor.Y);
+        var cp = new ColorPicker { Width = 20, SelectedColor = new (0, 0) };
+        cp.Style.ColorModel = colorModel;
+        cp.Style.ShowTextFields = showTextFields;
+        cp.Style.ShowColorName = showName;
+        cp.ApplyStyleChanges ();
 
-        colorPicker.SelectedColor = Color.Black;
-        Assert.Equal (0, colorPicker.Cursor.X);
-        Assert.Equal (0, colorPicker.Cursor.Y);
+        Application.Current = new Toplevel () { Width = 20 ,Height = 5};
+        Application.Current.Add (cp);
+        Application.Current.FocusFirst (null);
 
-        colorPicker.Cursor = new (7, 1);
-        Assert.Equal (ColorName.White, colorPicker.SelectedColor);
+        Application.Current.LayoutSubviews ();
 
-        colorPicker.Cursor = Point.Empty;
-        Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
+        Application.Current.FocusFirst (null);
+        return cp;
     }
 }