mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* 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 <tig@users.noreply.github.com>
This commit is contained in:
@@ -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
Terminal.Gui/Drawing/ColorEventArgs.cs
Normal file
11
Terminal.Gui/Drawing/ColorEventArgs.cs
Normal file
@@ -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
Terminal.Gui/Drawing/ColorModel.cs
Normal file
25
Terminal.Gui/Drawing/ColorModel.cs
Normal file
@@ -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
Terminal.Gui/Drawing/ColorStrings.cs
Normal file
78
Terminal.Gui/Drawing/ColorStrings.cs
Normal file
@@ -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
Terminal.Gui/Drawing/IColorNameResolver.cs
Normal file
34
Terminal.Gui/Drawing/IColorNameResolver.cs
Normal file
@@ -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
Terminal.Gui/Drawing/W3CColors.cs
Normal file
24
Terminal.Gui/Drawing/W3CColors.cs
Normal file
@@ -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
Terminal.Gui/Resources/Strings.Designer.cs
generated
1251
Terminal.Gui/Resources/Strings.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -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
Terminal.Gui/Views/BBar.cs
Normal file
27
Terminal.Gui/Views/BBar.cs
Normal file
@@ -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
Terminal.Gui/Views/ColorBar.cs
Normal file
235
Terminal.Gui/Views/ColorBar.cs
Normal file
@@ -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
Terminal.Gui/Views/ColorModelStrategy.cs
Normal file
163
Terminal.Gui/Views/ColorModelStrategy.cs
Normal file
@@ -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); }
|
||||
}
|
||||
@@ -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 () { }
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>The new Thickness.</summary>
|
||||
public Color Color { get; set; }
|
||||
|
||||
/// <summary>The previous Thickness.</summary>
|
||||
public Color PreviousColor { get; set; }
|
||||
}
|
||||
|
||||
/// <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>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>Initializes a new instance of <see cref="ColorPicker"/>.</summary>
|
||||
public ColorPicker () { SetInitialProperties (); }
|
||||
|
||||
private void SetInitialProperties ()
|
||||
/// <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 ()
|
||||
{
|
||||
HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.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;
|
||||
Height = Dim.Auto ();
|
||||
Width = Dim.Auto ();
|
||||
ApplyStyleChanges ();
|
||||
}
|
||||
|
||||
// TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
|
||||
private readonly Dictionary<IColorBar, TextField> _textFields = new ();
|
||||
private readonly ColorModelStrategy _strategy = new ();
|
||||
private TextField? _tfHex;
|
||||
private Label? _lbHex;
|
||||
|
||||
private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me)
|
||||
{
|
||||
// if (CanFocus)
|
||||
{
|
||||
Cursor = new Point (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight);
|
||||
SetFocus ();
|
||||
me.Handled = true;
|
||||
}
|
||||
}
|
||||
private TextField? _tfName;
|
||||
private Label? _lbName;
|
||||
|
||||
/// <summary>Height of a color box</summary>
|
||||
public int BoxHeight
|
||||
private Color _selectedColor = Color.Black;
|
||||
|
||||
// TODO: Add interface
|
||||
private readonly IColorNameResolver _colorNameResolver = new W3CColors ();
|
||||
|
||||
private List<IColorBar> _bars = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild the user interface to reflect the new state of <see cref="Style"/>.
|
||||
/// </summary>
|
||||
public void ApplyStyleChanges ()
|
||||
{
|
||||
get => _boxHeight;
|
||||
set
|
||||
Color oldValue = _selectedColor;
|
||||
DisposeOldViews ();
|
||||
|
||||
var y = 0;
|
||||
const int textFieldWidth = 4;
|
||||
|
||||
foreach (ColorBar bar in _strategy.CreateBars (Style.ColorModel))
|
||||
{
|
||||
if (_boxHeight != value)
|
||||
bar.Y = y;
|
||||
bar.Width = Dim.Fill (Style.ShowTextFields ? textFieldWidth : 0);
|
||||
|
||||
if (Style.ShowTextFields)
|
||||
{
|
||||
_boxHeight = value;
|
||||
Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
|
||||
Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
|
||||
SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
|
||||
SetNeedsLayout ();
|
||||
var tfValue = new TextField
|
||||
{
|
||||
X = Pos.AnchorEnd (textFieldWidth),
|
||||
Y = y,
|
||||
Width = textFieldWidth
|
||||
};
|
||||
tfValue.Leave += UpdateSingleBarValueFromTextField;
|
||||
_textFields.Add (bar, tfValue);
|
||||
Add (tfValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Width of a color box</summary>
|
||||
public int BoxWidth
|
||||
{
|
||||
get => _boxWidth;
|
||||
set
|
||||
y++;
|
||||
|
||||
bar.ValueChanged += RebuildColorFromBar;
|
||||
|
||||
_bars.Add (bar);
|
||||
|
||||
Add (bar);
|
||||
}
|
||||
|
||||
if (Style.ShowColorName)
|
||||
{
|
||||
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>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>Selected color.</summary>
|
||||
public ColorName SelectedColor
|
||||
{
|
||||
get => (ColorName)_selectColorIndex;
|
||||
set
|
||||
{
|
||||
if (value == (ColorName)_selectColorIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var prev = (ColorName)_selectColorIndex;
|
||||
_selectColorIndex = (int)value;
|
||||
|
||||
ColorChanged?.Invoke (
|
||||
this,
|
||||
new ColorEventArgs { PreviousColor = new Color (prev), Color = new Color (value) }
|
||||
);
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fired when a color is picked.</summary>
|
||||
public event EventHandler<ColorEventArgs> ColorChanged;
|
||||
|
||||
|
||||
/// <summary>Moves the selected item index to the previous column.</summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool MoveLeft ()
|
||||
{
|
||||
if (Cursor.X > 0)
|
||||
{
|
||||
SelectedColor--;
|
||||
CreateNameField ();
|
||||
}
|
||||
|
||||
return true;
|
||||
CreateTextField ();
|
||||
SelectedColor = oldValue;
|
||||
|
||||
LayoutSubviews ();
|
||||
}
|
||||
|
||||
/// <summary>Moves the selected item index to the next column.</summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool MoveRight ()
|
||||
{
|
||||
if (Cursor.X < _cols - 1)
|
||||
{
|
||||
SelectedColor++;
|
||||
}
|
||||
/// <summary>
|
||||
/// Fired when color is changed.
|
||||
/// </summary>
|
||||
public event EventHandler<ColorEventArgs>? ColorChanged;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
/// <inheritdoc/>
|
||||
public override void OnDrawContent (Rectangle viewport)
|
||||
{
|
||||
base.OnDrawContent (viewport);
|
||||
Attribute normal = GetNormalColor ();
|
||||
Driver.SetAttribute (new (SelectedColor, normal.Background));
|
||||
int y = _bars.Count + (Style.ShowColorName ? 1 : 0);
|
||||
AddRune (13, y, (Rune)'■');
|
||||
}
|
||||
|
||||
Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
|
||||
var colorIndex = 0;
|
||||
/// <summary>
|
||||
/// The color selected in the picker
|
||||
/// </summary>
|
||||
public Color SelectedColor
|
||||
{
|
||||
get => _selectedColor;
|
||||
set => SetSelectedColor (value, true);
|
||||
}
|
||||
|
||||
for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
|
||||
/// <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 ()
|
||||
{
|
||||
_lbName = new ()
|
||||
{
|
||||
for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
|
||||
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;
|
||||
}
|
||||
|
||||
private void CreateTextField ()
|
||||
{
|
||||
int y = _bars.Count;
|
||||
|
||||
if (Style.ShowColorName)
|
||||
{
|
||||
y++;
|
||||
}
|
||||
|
||||
_lbHex = new ()
|
||||
{
|
||||
Text = "Hex:",
|
||||
X = 0,
|
||||
Y = y
|
||||
};
|
||||
|
||||
_tfHex = new ()
|
||||
{
|
||||
Y = y,
|
||||
X = 4,
|
||||
Width = 8
|
||||
};
|
||||
|
||||
Add (_lbHex);
|
||||
Add (_tfHex);
|
||||
|
||||
_tfHex.Leave += UpdateValueFromTextField;
|
||||
}
|
||||
|
||||
private void DisposeOldViews ()
|
||||
{
|
||||
foreach (ColorBar bar in _bars.Cast<ColorBar> ())
|
||||
{
|
||||
bar.ValueChanged -= RebuildColorFromBar;
|
||||
|
||||
if (_textFields.TryGetValue (bar, out TextField? tf))
|
||||
{
|
||||
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++;
|
||||
tf.Leave -= UpdateSingleBarValueFromTextField;
|
||||
Remove (tf);
|
||||
tf.Dispose ();
|
||||
}
|
||||
|
||||
Remove (bar);
|
||||
bar.Dispose ();
|
||||
}
|
||||
|
||||
_bars = new ();
|
||||
_textFields.Clear ();
|
||||
|
||||
if (_lbHex != null)
|
||||
{
|
||||
Remove (_lbHex);
|
||||
_lbHex.Dispose ();
|
||||
_lbHex = null;
|
||||
}
|
||||
|
||||
if (_tfHex != null)
|
||||
{
|
||||
Remove (_tfHex);
|
||||
_tfHex.Leave -= UpdateValueFromTextField;
|
||||
_tfHex.Dispose ();
|
||||
_tfHex = null;
|
||||
}
|
||||
|
||||
if (_lbName != null)
|
||||
{
|
||||
Remove (_lbName);
|
||||
_lbName.Dispose ();
|
||||
_lbName = null;
|
||||
}
|
||||
|
||||
if (_tfName != null)
|
||||
{
|
||||
Remove (_tfName);
|
||||
_tfName.Leave -= UpdateValueFromName;
|
||||
_tfName.Dispose ();
|
||||
_tfName = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildColorFromBar (object? sender, EventArgs<int> e) { SetSelectedColor (_strategy.GetColorFromBars (_bars, Style.ColorModel), false); }
|
||||
|
||||
private void SetSelectedColor (Color value, bool syncBars)
|
||||
{
|
||||
if (_selectedColor != value)
|
||||
{
|
||||
Color old = _selectedColor;
|
||||
_selectedColor = value;
|
||||
|
||||
ColorChanged?.Invoke (
|
||||
this,
|
||||
new (value));
|
||||
}
|
||||
|
||||
SyncSubViewValues (syncBars);
|
||||
}
|
||||
|
||||
private void SyncSubViewValues (bool syncBars)
|
||||
{
|
||||
if (syncBars)
|
||||
{
|
||||
_strategy.SetBarsToColor (_bars, _selectedColor, Style.ColorModel);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
|
||||
{
|
||||
kvp.Value.Text = kvp.Key.Value.ToString ();
|
||||
}
|
||||
|
||||
var colorHex = _selectedColor.ToString ($"#{SelectedColor.R:X2}{SelectedColor.G:X2}{SelectedColor.B:X2}");
|
||||
|
||||
if (_tfName != null)
|
||||
{
|
||||
_tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty;
|
||||
}
|
||||
|
||||
if (_tfHex != null)
|
||||
{
|
||||
_tfHex.Text = colorHex;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSingleBarValueFromTextField (object? sender, FocusEventArgs e)
|
||||
{
|
||||
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>Add the commands.</summary>
|
||||
private void AddCommands ()
|
||||
private void UpdateValueFromName (object? sender, FocusEventArgs e)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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++)
|
||||
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))
|
||||
{
|
||||
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);
|
||||
SelectedColor = newColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
|
||||
// value is invalid, revert the value in the text field back to current state
|
||||
SyncSubViewValues (false);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
private void UpdateValueFromTextField (object? sender, FocusEventArgs e)
|
||||
{
|
||||
if (_tfHex == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Point, Rune> p in lc.GetMap ())
|
||||
if (Color.TryParse (_tfHex.Text, out Color? newColor))
|
||||
{
|
||||
AddRune (p.Key.X, p.Key.Y, p.Value);
|
||||
SelectedColor = newColor.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// value is invalid, revert the value in the text field back to current state
|
||||
SyncSubViewValues (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
270
Terminal.Gui/Views/ColorPicker16.cs
Normal file
270
Terminal.Gui/Views/ColorPicker16.cs
Normal file
@@ -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
Terminal.Gui/Views/ColorPickerStyle.cs
Normal file
25
Terminal.Gui/Views/ColorPickerStyle.cs
Normal file
@@ -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
Terminal.Gui/Views/GBar.cs
Normal file
27
Terminal.Gui/Views/GBar.cs
Normal file
@@ -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
Terminal.Gui/Views/HueBar.cs
Normal file
21
Terminal.Gui/Views/HueBar.cs
Normal file
@@ -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
Terminal.Gui/Views/IColorBar.cs
Normal file
14
Terminal.Gui/Views/IColorBar.cs
Normal file
@@ -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
Terminal.Gui/Views/LightnessBar.cs
Normal file
29
Terminal.Gui/Views/LightnessBar.cs
Normal file
@@ -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
Terminal.Gui/Views/RBar.cs
Normal file
27
Terminal.Gui/Views/RBar.cs
Normal file
@@ -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
Terminal.Gui/Views/SaturationBar.cs
Normal file
40
Terminal.Gui/Views/SaturationBar.cs
Normal file
@@ -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
Terminal.Gui/Views/ValueBar.cs
Normal file
29
Terminal.Gui/Views/ValueBar.cs
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
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 ()
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
public override void BeginInit ()
|
||||
{
|
||||
base.BeginInit ();
|
||||
|
||||
_colors.ValueChanged += (s, e) => ColorChanged?.Invoke (this, e);
|
||||
|
||||
_stylePicker = new()
|
||||
{
|
||||
X = 0, Y = Pos.Bottom (_colors), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
|
||||
};
|
||||
_stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
|
||||
_stylePicker.SelectedItem = 1;
|
||||
|
||||
_addLayerBtn = new() { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
|
||||
|
||||
_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 (); }
|
||||
|
||||
public override void OnDrawContentComplete (Rectangle viewport)
|
||||
{
|
||||
base.OnDrawContentComplete (viewport);
|
||||
|
||||
foreach (LineCanvas canvas in Layers)
|
||||
{
|
||||
foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
|
||||
{
|
||||
foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
|
||||
if (c.Value is { })
|
||||
{
|
||||
if (c.Value is { })
|
||||
{
|
||||
Driver.SetAttribute (c.Value.Value.Attribute ?? ColorScheme.Normal);
|
||||
Driver.SetAttribute (c.Value.Value.Attribute ?? ColorScheme.Normal);
|
||||
|
||||
// TODO: #2616 - Support combining sequences that don't normalize
|
||||
AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune);
|
||||
}
|
||||
// TODO: #2616 - Support combining sequences that don't normalize
|
||||
AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// BUGBUG: Why is this not handled by a key binding???
|
||||
public override bool OnKeyDown (Key e)
|
||||
//// 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))
|
||||
{
|
||||
// BUGBUG: These should be implemented with key bindings
|
||||
if (e.KeyCode == (KeyCode.Z | KeyCode.CtrlMask))
|
||||
StraightLine pop = CurrentLayer.RemoveLastLine ();
|
||||
|
||||
if (pop != null)
|
||||
{
|
||||
StraightLine pop = _currentLayer.RemoveLastLine ();
|
||||
_undoHistory.Push (pop);
|
||||
SetNeedsDisplay ();
|
||||
|
||||
if (pop != null)
|
||||
{
|
||||
_undoHistory.Push (pop);
|
||||
SetNeedsDisplay ();
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask))
|
||||
{
|
||||
if (_undoHistory.Any ())
|
||||
{
|
||||
StraightLine pop = _undoHistory.Pop ();
|
||||
_currentLayer.AddLine (pop);
|
||||
SetNeedsDisplay ();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnMouseEvent (MouseEvent mouseEvent)
|
||||
if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask))
|
||||
{
|
||||
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
|
||||
if (_undoHistory.Any ())
|
||||
{
|
||||
if (_currentLine == null)
|
||||
{
|
||||
// Mouse pressed down
|
||||
_currentLine = new StraightLine (
|
||||
mouseEvent.Position,
|
||||
0,
|
||||
Orientation.Vertical,
|
||||
LineStyle,
|
||||
new Attribute (_currentColor, GetNormalColor ().Background)
|
||||
);
|
||||
StraightLine pop = _undoHistory.Pop ();
|
||||
CurrentLayer.AddLine (pop);
|
||||
SetNeedsDisplay ();
|
||||
|
||||
_currentLayer.AddLine (_currentLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mouse dragged
|
||||
Point start = _currentLine.Start;
|
||||
var end = mouseEvent.Position;
|
||||
var orientation = Orientation.Vertical;
|
||||
int length = end.Y - start.Y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
else
|
||||
{
|
||||
length--;
|
||||
}
|
||||
protected override bool OnMouseEvent (MouseEvent mouseEvent)
|
||||
{
|
||||
CurrentTool.OnMouseEvent (this, mouseEvent);
|
||||
|
||||
_currentLine.Length = length;
|
||||
_currentLine.Orientation = orientation;
|
||||
_currentLayer.ClearCache ();
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
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
|
||||
{
|
||||
_value = value;
|
||||
ValueChanged?.Invoke (this, value);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnDrawContent (Rectangle viewport)
|
||||
{
|
||||
base.OnDrawContent (viewport);
|
||||
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// 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 ();
|
||||
}
|
||||
rune = (Rune)'█';
|
||||
}
|
||||
|
||||
return base.OnMouseEvent (mouseEvent);
|
||||
AddRune (point.Item1, point.Item2, rune);
|
||||
}
|
||||
|
||||
internal void AddLayer ()
|
||||
Driver.SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));
|
||||
|
||||
// Square of background color
|
||||
foreach ((int, int) point in BackgroundPoints)
|
||||
{
|
||||
_currentLayer = new LineCanvas ();
|
||||
_layers.Add (_currentLayer);
|
||||
}
|
||||
// Make pattern like this when it is same color as background of control
|
||||
/*▓▒
|
||||
▒▓*/
|
||||
Rune rune;
|
||||
|
||||
internal void SetColor (Color c) { _currentColor = c; }
|
||||
if (isTransparentBg)
|
||||
{
|
||||
rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
|
||||
}
|
||||
else
|
||||
{
|
||||
rune = (Rune)'█';
|
||||
}
|
||||
|
||||
AddRune (point.Item1, point.Item2, rune);
|
||||
}
|
||||
}
|
||||
|
||||
private class ToolsView : Window
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnMouseEvent (MouseEvent mouseEvent)
|
||||
{
|
||||
private Button _addLayerBtn;
|
||||
private ColorPicker _colorPicker;
|
||||
private RadioGroup _stylePicker;
|
||||
|
||||
public ToolsView ()
|
||||
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
|
||||
{
|
||||
BorderStyle = LineStyle.Dotted;
|
||||
Border.Thickness = new Thickness (1, 2, 1, 1);
|
||||
Initialized += ToolsView_Initialized;
|
||||
}
|
||||
|
||||
public event Action AddLayer;
|
||||
|
||||
public override void BeginInit ()
|
||||
{
|
||||
base.BeginInit ();
|
||||
|
||||
_colorPicker = new ColorPicker { X = 0, Y = 0, BoxHeight = 1, BoxWidth = 2 };
|
||||
|
||||
_colorPicker.ColorChanged += (s, a) => ColorChanged?.Invoke (a.Color);
|
||||
|
||||
_stylePicker = new RadioGroup
|
||||
if (IsForegroundPoint (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;
|
||||
|
||||
_addLayerBtn = new Button { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
|
||||
|
||||
_addLayerBtn.Accept += (s, a) => AddLayer?.Invoke ();
|
||||
Add (_colorPicker, _stylePicker, _addLayerBtn);
|
||||
ClickedInForeground ();
|
||||
}
|
||||
else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
|
||||
{
|
||||
ClickedInBackground ();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<Color> ColorChanged;
|
||||
public event Action<LineStyle> SetStyle;
|
||||
return base.OnMouseEvent (mouseEvent);
|
||||
}
|
||||
|
||||
private void ToolsView_Initialized (object sender, EventArgs e)
|
||||
private bool IsForegroundPoint (int x, int y) { return ForegroundPoints.Contains ((x, y)); }
|
||||
|
||||
private bool IsBackgroundPoint (int x, int y) { return BackgroundPoints.Contains ((x, y)); }
|
||||
|
||||
private void ClickedInBackground ()
|
||||
{
|
||||
if (LineDrawing.PromptForColor ("Background", Value.Background, out Color newColor))
|
||||
{
|
||||
LayoutSubviews ();
|
||||
Value = new (Value.Foreground, newColor);
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
Width = Math.Max (_colorPicker.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
|
||||
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
UnitTests/Views/ColorPicker16Tests.cs
Normal file
79
UnitTests/Views/ColorPicker16Tests.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 colorPicker = new ColorPicker ();
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
Assert.Equal (Point.Empty, colorPicker.Cursor);
|
||||
Assert.True (colorPicker.CanFocus);
|
||||
var cp = GetColorPicker (ColorModel.HSV, false);
|
||||
|
||||
colorPicker.BeginInit ();
|
||||
colorPicker.EndInit ();
|
||||
colorPicker.LayoutSubviews ();
|
||||
Assert.Equal (new (0, 0, 32, 4), colorPicker.Frame);
|
||||
// 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]
|
||||
public void KeyBindings_Command ()
|
||||
[SetupFakeDriver]
|
||||
public void ColorPicker_RGB_KeyboardNavigation ()
|
||||
{
|
||||
var colorPicker = new ColorPicker ();
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
var cp = GetColorPicker (ColorModel.RGB, false);
|
||||
cp.Draw ();
|
||||
|
||||
Assert.True (colorPicker.NewKeyDownEvent (Key.CursorRight));
|
||||
Assert.Equal (ColorName.Blue, 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.CursorDown));
|
||||
Assert.Equal (ColorName.BrightBlue, colorPicker.SelectedColor);
|
||||
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.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
|
||||
Assert.Equal (ColorName.DarkGray, colorPicker.SelectedColor);
|
||||
Assert.IsAssignableFrom<IColorBar> (cp.Focused);
|
||||
|
||||
Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
cp.Draw ();
|
||||
|
||||
Assert.True (colorPicker.NewKeyDownEvent (Key.CursorLeft));
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
Application.OnKeyDown (Key.CursorRight);
|
||||
|
||||
Assert.True (colorPicker.NewKeyDownEvent (Key.CursorUp));
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
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]
|
||||
[AutoInitShutdown]
|
||||
public void MouseEvents ()
|
||||
[SetupFakeDriver]
|
||||
public void ColorPicker_RGB_MouseNavigation ()
|
||||
{
|
||||
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,false);
|
||||
|
||||
Assert.False (colorPicker.NewMouseEvent (new ()));
|
||||
cp.Draw ();
|
||||
|
||||
Assert.True (colorPicker.NewMouseEvent (new() { Position = new (4, 1), Flags = MouseFlags.Button1Clicked }));
|
||||
Assert.Equal (ColorName.Blue, colorPicker.SelectedColor);
|
||||
top.Dispose ();
|
||||
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]
|
||||
public void SelectedColorAndCursor ()
|
||||
[SetupFakeDriver]
|
||||
public void ColorPicker_ClickingAtEndOfBar_SetsMaxValue ()
|
||||
{
|
||||
var colorPicker = new ColorPicker ();
|
||||
colorPicker.SelectedColor = ColorName.White;
|
||||
Assert.Equal (7, colorPicker.Cursor.X);
|
||||
Assert.Equal (1, colorPicker.Cursor.Y);
|
||||
var cp = GetColorPicker (ColorModel.RGB, false);
|
||||
|
||||
colorPicker.SelectedColor = Color.Black;
|
||||
Assert.Equal (0, colorPicker.Cursor.X);
|
||||
Assert.Equal (0, colorPicker.Cursor.Y);
|
||||
cp.Draw ();
|
||||
|
||||
colorPicker.Cursor = new (7, 1);
|
||||
Assert.Equal (ColorName.White, colorPicker.SelectedColor);
|
||||
// Click at the end of the Red bar
|
||||
cp.Focused.OnMouseEvent (
|
||||
new ()
|
||||
{
|
||||
Flags = MouseFlags.Button1Pressed,
|
||||
Position = new (19, 0) // Assuming 0-based indexing
|
||||
});
|
||||
|
||||
colorPicker.Cursor = Point.Empty;
|
||||
Assert.Equal (ColorName.Black, colorPicker.SelectedColor);
|
||||
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]
|
||||
[SetupFakeDriver]
|
||||
public void ColorPicker_ClickingBeyondBar_ChangesToMaxValue ()
|
||||
{
|
||||
var cp = GetColorPicker (ColorModel.RGB, false);
|
||||
|
||||
cp.Draw ();
|
||||
|
||||
// Click beyond the bar
|
||||
cp.Focused.OnMouseEvent (
|
||||
new ()
|
||||
{
|
||||
Flags = MouseFlags.Button1Pressed,
|
||||
Position = new (21, 0) // Beyond the bar
|
||||
});
|
||||
|
||||
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]
|
||||
[SetupFakeDriver]
|
||||
public void ColorPicker_ChangeValueOnUI_UpdatesAllUIElements ()
|
||||
{
|
||||
var cp = GetColorPicker (ColorModel.RGB, true);
|
||||
|
||||
View otherView = new View () { CanFocus = true };
|
||||
|
||||
Application.Current?.Add (otherView);
|
||||
|
||||
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]
|
||||
[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 cp = new ColorPicker { Width = 20, SelectedColor = new (0, 0) };
|
||||
cp.Style.ColorModel = colorModel;
|
||||
cp.Style.ShowTextFields = showTextFields;
|
||||
cp.Style.ShowColorName = showName;
|
||||
cp.ApplyStyleChanges ();
|
||||
|
||||
Application.Current = new Toplevel () { Width = 20 ,Height = 5};
|
||||
Application.Current.Add (cp);
|
||||
Application.Current.FocusFirst (null);
|
||||
|
||||
Application.Current.LayoutSubviews ();
|
||||
|
||||
Application.Current.FocusFirst (null);
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user