Fixes #4000. Named colors as enums. (#4005)

* Add W3C color enum with the RGB as numeric value

* Add transform helper class for W3cColor enum

For the sake of backwards compatibility prioritize parsing 16 color mode color names over the W3C colors because the previous resource-based color names/values had a mix of W3C and 16 color mode RGB values.

Mechanism for choosing/prioritizing one color scheme over the other is currently only available at higher application/driver/output level.

* IColorNameResolver enable null analysis

* Remove obsolete color name related ResourceManagerTests

* Replace remains of W3CColors with direct W3C color name resolver

Temporarily breaks backwards compatibility and tests even further.

* Add ANSI 4-bit (ColorName16) color name resolver

* Add multi-standard color name resolver

Combined resolver for both ANSI 4-bit (ColorName16) and W3C colors while trying to maintain backwards compatibility for ColorPicker.

* Split conditional name resolver test cases

* Change W3C colors tests to be similar to name resolvers

* Change W3cColorsTests to W3cColorNameResolverTests

More consistent when all the tests refer to the color name resolver layer.

* Make W3cColors internal

Color name resolver is the public interface.

* W3cColors: Use Color.Argb instead of individual RGB components

* MultiStandardColorNameResolver: Substitute instead of blocking alternative W3C names

Changes color picker behavior a bit, e.g. Aqua will match to Cyan instead of jumping to Aquamarine.

* Remove leftover color string resources

* Consistent position for IColorNameResolver #nullable enable directive

* Add missing XML comments to ColorScheme.Colors.cs
This commit is contained in:
Tonttu
2025-03-29 19:18:10 +02:00
committed by GitHub
parent df0fcd3d40
commit 1856262b50
19 changed files with 1853 additions and 1970 deletions

View File

@@ -1,6 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using ColorHelper;
namespace Terminal.Gui;
@@ -40,11 +39,9 @@ internal class ColorJsonConverter : JsonConverter<Color>
// Get the color string
ReadOnlySpan<char> colorString = reader.GetString ();
// Check if the color string is a color name
if (ColorStrings.TryParseW3CColorName (colorString.ToString (), out Color color1))
if (ColorStrings.TryParseNamedColor (colorString, out Color namedColor))
{
// Return the parsed color
return new (color1);
return namedColor;
}
if (Color.TryParse (colorString, null, out Color parsedColor))

View File

@@ -0,0 +1,70 @@
#nullable enable
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Terminal.Gui;
/// <summary>
/// Color name resolver for <see cref="ColorName16"/>.
/// </summary>
public class AnsiColorNameResolver : IColorNameResolver
{
private static readonly ImmutableArray<string> AnsiColorNames = ImmutableArray.Create(Enum.GetNames<ColorName16>());
/// <inheritdoc/>
public IEnumerable<string> GetColorNames ()
{
return AnsiColorNames;
}
/// <inheritdoc/>
public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name)
{
if (Color.TryGetExactNamedColor16 (color, out ColorName16 colorName16))
{
name = Color16Name (colorName16);
return true;
}
name = null;
return false;
}
/// <inheritdoc/>
public bool TryParseColor (ReadOnlySpan<char> name, out Color color)
{
if (Enum.TryParse (name, ignoreCase: true, out ColorName16 colorName16) &&
// Any numerical value converts to undefined enum value.
Enum.IsDefined (colorName16))
{
color = new Color (colorName16);
return true;
}
color = default;
return false;
}
private static string Color16Name (ColorName16 color16)
{
return color16 switch
{
ColorName16.Black => nameof (ColorName16.Black),
ColorName16.Blue => nameof (ColorName16.Blue),
ColorName16.Green => nameof (ColorName16.Green),
ColorName16.Cyan => nameof (ColorName16.Cyan),
ColorName16.Red => nameof (ColorName16.Red),
ColorName16.Magenta => nameof (ColorName16.Magenta),
ColorName16.Yellow => nameof (ColorName16.Yellow),
ColorName16.Gray => nameof (ColorName16.Gray),
ColorName16.DarkGray => nameof (ColorName16.DarkGray),
ColorName16.BrightBlue => nameof (ColorName16.BrightBlue),
ColorName16.BrightGreen => nameof (ColorName16.BrightGreen),
ColorName16.BrightCyan => nameof (ColorName16.BrightCyan),
ColorName16.BrightRed => nameof (ColorName16.BrightRed),
ColorName16.BrightMagenta => nameof (ColorName16.BrightMagenta),
ColorName16.BrightYellow => nameof (ColorName16.BrightYellow),
ColorName16.White => nameof (ColorName16.White),
_ => throw new NotSupportedException ($"ColorName16 '{color16}' is not supported.")
};
}
}

View File

@@ -1,6 +1,5 @@
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Runtime.CompilerServices;
@@ -267,90 +266,79 @@ public readonly partial record struct Color
return text switch
{
// Null string or empty span provided
{ IsEmpty: true } when formatProvider is null => throw new ColorParseException (
in text,
"The text provided was null or empty.",
in text
),
{ IsEmpty: true } when formatProvider is null =>
throw new ColorParseException (in text, "The text provided was null or empty.", in text),
// A valid ICustomColorFormatter was specified and the text wasn't null or empty
{ IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
// Input string is only whitespace
{ Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
in text,
"The text provided consisted of only whitespace characters.",
in text
),
{ Length: > 0 } when text.IsWhiteSpace () =>
throw new ColorParseException (in text, "The text provided consisted of only whitespace characters.", in text),
// Any string too short to possibly be any supported format.
{ Length: > 0 and < 3 } => throw new ColorParseException (
in text,
"Text was too short to be any possible supported format.",
in text
),
{ Length: > 0 and < 3 } =>
throw new ColorParseException (in text, "Text was too short to be any possible supported format.", in text),
// The various hexadecimal cases
['#', ..] hexString => hexString switch
{
// #RGB
['#', var rChar, var gChar, var bChar] chars when chars [1..]
.IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
),
// The various hexadecimal cases
['#', ..] hexString => hexString switch
{
// #RGB
['#', var rChar, var gChar, var bChar] chars when chars [1..]
.IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
),
// #ARGB
['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
.IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
),
// #ARGB
['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
.IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
),
// #RRGGBB
[
'#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
var b2Char
] chars when chars [1..].IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
),
// #RRGGBB
[
'#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char
] chars when chars [1..].IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
),
// #AARRGGBB
[
'#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
var g2Char, var b1Char, var b2Char
] chars when chars [1..].IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
),
_ => throw new ColorParseException (
in hexString,
$"Color hex string {hexString} was not in a supported format",
in hexString
)
},
// #AARRGGBB
[
'#', var a1Char, var a2Char,
var r1Char, var r2Char,
var g1Char, var g2Char,
var b1Char, var b2Char
] chars when chars [1..].IsAllAsciiHexDigits () =>
new Color (
byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
),
_ => throw new ColorParseException (
in hexString,
$"Color hex string {hexString} was not in a supported format",
in hexString
)
},
// rgb(r,g,b) or rgb(r,g,b,a)
['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
// rgba(r,g,b,a) or rgba(r,g,b)
['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
// Attempt to parse as a named color from the ColorStrings resources
{ } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text.ToString (), out Color color) =>
new Color (color),
// rgb(r,g,b) or rgb(r,g,b,a)
['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
// rgba(r,g,b,a) or rgba(r,g,b)
['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
// Attempt named colors
{ } when char.IsLetter (text [0]) && ColorStrings.TryParseNamedColor (text, out Color color) => color,
// Any other input
_ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
};
@@ -585,11 +573,9 @@ public readonly partial record struct Color
[SkipLocalsInit]
public override string ToString ()
{
string? name = ColorStrings.GetW3CColorName (this);
if (name is { })
if (ColorStrings.GetColorName (this) is string colorName)
{
return name;
return colorName;
}
return $"#{R:X2}{G:X2}{B:X2}";

View File

@@ -1,7 +1,5 @@
#nullable enable
using System.Collections.Frozen;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -236,6 +234,15 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
return ColorExtensions.ColorToName16Map.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
}
/// <summary>Converts the given color value to exact named color represented by <see cref="ColorName16"/>.</summary>
/// <param name="inputColor"></param>
/// <param name="colorName16">Successfully converted named color.</param>
/// <returns>True if conversion succeeded; otherwise false.</returns>
internal static bool TryGetExactNamedColor16 (Color inputColor, out ColorName16 colorName16)
{
return ColorExtensions.ColorToName16Map.TryGetValue (inputColor, out colorName16);
}
[SkipLocalsInit]
private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); }

View File

@@ -185,6 +185,11 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
}
}
/// <summary>
/// Copies the elements of the <see cref="ColorSchemes"/> to an array, starting at a particular array index.
/// </summary>
/// <param name="array">The one-dimensional array that is the destination of the elements copied from <see cref="ColorSchemes"/>.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
public void CopyTo (KeyValuePair<string, ColorScheme?> [] array, int arrayIndex)
{
lock (_lock)
@@ -193,6 +198,10 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
}
}
/// <summary>
/// Returns an enumerator that iterates through the <see cref="ColorSchemes"/>.
/// </summary>
/// <returns>An enumerator for the <see cref="ColorSchemes"/>.</returns>
public IEnumerator<KeyValuePair<string, ColorScheme?>> GetEnumerator ()
{
lock (_lock)
@@ -206,6 +215,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
return GetEnumerator ();
}
/// <inheritdoc />
public bool Remove (KeyValuePair<string, ColorScheme?> item)
{
lock (_lock)
@@ -219,6 +229,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
}
}
/// <inheritdoc />
public bool Remove (string key)
{
lock (_lock)
@@ -245,7 +256,7 @@ public sealed class Colors : INotifyCollectionChanged, IDictionary<string, Color
/// <summary>
/// Resets the <see cref="ColorSchemes"/> dictionary to its default values.
/// </summary>
/// <returns></returns>
/// <returns>The reset <see cref="ColorSchemes"/> dictionary.</returns>
public static Dictionary<string, ColorScheme?> Reset ()
{
lock (_lock)

View File

@@ -1,8 +1,5 @@
#nullable enable
using System.Collections;
using System.Globalization;
using System.Resources;
using Terminal.Gui.Resources;
namespace Terminal.Gui;
@@ -11,7 +8,9 @@ namespace Terminal.Gui;
/// </summary>
public static class ColorStrings
{
// PERFORMANCE: See https://stackoverflow.com/a/15521524/297526 for why GlobalResources.GetString is fast.
private static readonly AnsiColorNameResolver Ansi = new();
private static readonly W3cColorNameResolver W3c = new();
private static readonly MultiStandardColorNameResolver Multi = new();
/// <summary>
/// Gets the W3C standard string for <paramref name="color"/>.
@@ -20,7 +19,39 @@ public static class ColorStrings
/// <returns><see langword="null"/> if there is no standard color name for the specified color.</returns>
public static string? GetW3CColorName (Color color)
{
return GlobalResources.GetString ($"#{color.R:X2}{color.G:X2}{color.B:X2}", CultureInfo.CurrentUICulture);
if (W3c.TryNameColor (color, out string? name))
{
return name;
}
return null;
}
/// <summary>
/// Gets the ANSI 4-bit (16) color name 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? GetANSIColor16Name (Color color)
{
if (Ansi.TryNameColor (color, out string? name))
{
return name;
}
return null;
}
/// <summary>
/// Gets backwards compatible color name for <paramref name="color"/>.
/// </summary>
/// <param name="color">The color.</param>
/// <returns>Standard color name for the specified color; otherwise <see langword="null"/>.</returns>
public static string? GetColorName (Color color)
{
if (Multi.TryNameColor (color, out string? name))
{
return name;
}
return null;
}
/// <summary>
@@ -29,19 +60,7 @@ public static class ColorStrings
/// <returns></returns>
public static IEnumerable<string> GetW3CColorNames ()
{
ResourceSet? resourceSet = GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true);
if (resourceSet == null)
{
yield break;
}
foreach (DictionaryEntry entry in resourceSet)
{
if (entry is { Value: string colorName, Key: string key } && key.StartsWith ('#'))
{
yield return colorName;
}
}
return W3c.GetColorNames ();
}
/// <summary>
@@ -50,33 +69,68 @@ public static class ColorStrings
/// <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)
public static bool TryParseW3CColorName (ReadOnlySpan<char> name, out Color color)
{
foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true)!)
if (W3c.TryParseColor (name, out color))
{
if (entry.Value is string colorName && colorName.Equals (name, StringComparison.OrdinalIgnoreCase))
return true;
}
// Backwards compatibility: Also parse #RRGGBB.
return TryParseHexColor (name, out color);
}
/// <summary>
/// Parses <paramref name="name"/> and returns <paramref name="color"/> if name is a ANSI 4-bit 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 TryParseColor16 (ReadOnlySpan<char> name, out Color color)
{
if (Ansi.TryParseColor (name, out color))
{
return true;
}
color = default;
return false;
}
/// <summary>
/// Parses <paramref name="name"/> and returns <paramref name="color"/> if name is either ANSI 4-bit or 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 TryParseNamedColor (ReadOnlySpan<char> name, out Color color)
{
if (Multi.TryParseColor (name, out color))
{
return true;
}
// Backwards compatibility: Also parse #RRGGBB.
if (TryParseHexColor (name, out color))
{
return true;
}
color = default;
return false;
}
private static bool TryParseHexColor (ReadOnlySpan<char> name, out Color color)
{
if (name.Length == 7 && name [0] == '#')
{
if (int.TryParse (name.Slice (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) &&
int.TryParse (name.Slice (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) &&
int.TryParse (name.Slice (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
{
return TryParseColorKey (entry.Key.ToString (), out color);
color = new Color (r, g, b);
return true;
}
}
return TryParseColorKey (name, out color);
bool TryParseColorKey (string? key, out Color color)
{
if (key != null && key.StartsWith ('#') && key.Length == 7)
{
if (int.TryParse (key.AsSpan (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) &&
int.TryParse (key.AsSpan (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) &&
int.TryParse (key.AsSpan (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
{
color = new Color (r, g, b);
return true;
}
}
color = default (Color);
return false;
}
color = default;
return false;
}
}

View File

@@ -1,4 +1,8 @@
namespace Terminal.Gui;
#nullable enable
using System.Diagnostics.CodeAnalysis;
namespace Terminal.Gui;
/// <summary>
/// When implemented by a class, allows mapping <see cref="Color"/> to
@@ -20,7 +24,7 @@ public interface IColorNameResolver
/// <param name="color"></param>
/// <param name="name"></param>
/// <returns></returns>
bool TryNameColor (Color color, out string name);
bool TryNameColor (Color color, [NotNullWhen(true)]out string? name);
/// <summary>
/// Returns <see langword="true"/> if <paramref name="name"/> is a recognized
@@ -30,5 +34,5 @@ public interface IColorNameResolver
/// <param name="name"></param>
/// <param name="color"></param>
/// <returns></returns>
bool TryParseColor (string name, out Color color);
bool TryParseColor (ReadOnlySpan<char> name, out Color color);
}

View File

@@ -0,0 +1,188 @@
#nullable enable
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Terminal.Gui;
/// <summary>
/// Backwards compatible(-ish) color name resolver prioritizing ANSI 4-bit (16) colors with fallback to W3C colors.
/// </summary>
public class MultiStandardColorNameResolver : IColorNameResolver
{
private static readonly AnsiColorNameResolver Ansi = new();
private static readonly W3cColorNameResolver W3c = new();
private static readonly FrozenSet<Color> W3cBlockedColors;
private static readonly ImmutableArray<string> CombinedColorNames;
private static readonly FrozenDictionary<int, (string Name, Color Color)> W3cSubstituteColors;
static MultiStandardColorNameResolver ()
{
HashSet<string> combinedNames = new(Ansi.GetColorNames());
HashSet<Color> w3cInconsistentColors = new();
Dictionary<string, Color> w3cSubstituteColors = new(StringComparer.OrdinalIgnoreCase);
IEnumerable<string> enumerableW3cNames = W3c.GetColorNames ();
IReadOnlyList<string> w3cNames = enumerableW3cNames is IReadOnlyList<string> alreadyReadOnlyList
? alreadyReadOnlyList
: [.. enumerableW3cNames];
Dictionary<Color, HashSet<string>> w3cColorsWithAlternativeNames = w3cNames
.GroupBy(w3cName =>
{
if (!W3c.TryParseColor(w3cName, out Color w3cColor))
{
throw new InvalidOperationException ($"W3C color name '{w3cName}' does not resolve to any W3C color.");
}
return w3cColor;
})
.Where(g => g.Count() > 1)
.ToDictionary(g => g.Key, g => g.ToHashSet());
// Gather inconsistencies between ANSI and W3C, filter out or substitute problematic W3C colors and names,
// and create additional blocklist for W3C colors.
// Blocking and filtering is only applied to W3C because this resolver prioritizes ANSI for backwards compatibility.
// It would be a lot simpler to just prioritize W3C colors and names.
foreach (string w3cName in w3cNames)
{
if (w3cSubstituteColors.ContainsKey (w3cName))
{
// Already dealt with alternative name.
continue;
}
if (!W3c.TryParseColor (w3cName, out Color w3cColor))
{
// This condition is just inverted to reduce indentation.
// Also it should practically never happen if the W3C color name resolver is properly implemented.
throw new InvalidOperationException ($"W3C color name '{w3cName}' does not resolve to any color.");
}
if (w3cColorsWithAlternativeNames.TryGetValue (w3cColor, out var names))
{
bool substituted = false;
// Alternative names cause issues with ColorPicker etc. when combined with ANSI and prioritizing ANSI resolver.
// For example Aqua is not in ColorName16 but the actual color value resolves to ANSI Cyan
// so autocomplete for Aqua suddenly changes to Cyan because they happen to have same color value in both color scheme.
// Also DarkGrey would cause inconsistencies because the alternative DarkGray exists in ANSI and has different color value.
foreach (string name in names)
{
if (Ansi.TryParseColor (name, out Color substituteColor))
{
// Block the W3C color when it is inconsistent with the substitute color
// so there is no situation where W3C color -> color name -> ANSI color.
if (w3cColor != substituteColor)
{
w3cInconsistentColors.Add (w3cColor);
}
// Substitute all W3C alternatives to match with the ANSI color to keep colors consistent.
foreach (string alternativeName in names)
{
w3cSubstituteColors.Add (alternativeName, substituteColor);
combinedNames.Add (alternativeName);
}
substituted = true;
break;
}
}
if (substituted)
{
// Already dealt with, continue to next W3C color name.
continue;
}
}
// Same name, different ANSI value.
// For example both #767676 (ColorName16) and #A9A9A9 (W3C) resolve to DarkGray,
// although a bad example because it is already substituted due to also having alternative names.
if (Ansi.TryParseColor (w3cName, out Color ansiColor) && w3cColor != ansiColor)
{
w3cInconsistentColors.Add (w3cColor);
continue;
}
combinedNames.Add (w3cName);
}
// TODO: Utilize .NET 9 and later alternative lookup for matching ReadOnlySpan<char> with string.
W3cSubstituteColors = w3cSubstituteColors.ToFrozenDictionary (
// Workaround for alternative lookup not being available in .NET 8 by matching ReadOnlySpan<char> hash code to string hash code.
keySelector: kvp => string.GetHashCode (kvp.Key, StringComparison.OrdinalIgnoreCase),
// The string element is for detecting hash collision.
elementSelector: kvp => (kvp.Key, kvp.Value));
W3cBlockedColors = w3cInconsistentColors.ToFrozenSet ();
CombinedColorNames = combinedNames.Order ().ToImmutableArray ();
}
/// <inheritdoc/>
public IEnumerable<string> GetColorNames ()
{
return CombinedColorNames;
}
/// <inheritdoc/>
public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name)
{
if (Ansi.TryNameColor (color, out string? ansiName))
{
name = ansiName;
return true;
}
if (!IsBlockedW3cColor (color) &&
W3c.TryNameColor (color, out string? w3cName))
{
name = w3cName;
return true;
}
name = null;
return false;
}
/// <inheritdoc/>
public bool TryParseColor (ReadOnlySpan<char> name, out Color color)
{
if (Ansi.TryParseColor (name, out color))
{
return true;
}
if (GetSubstituteW3cColor (name, out color))
{
return true;
}
if (W3c.TryParseColor (name, out color) &&
!IsBlockedW3cColor (color))
{
return true;
}
color = default;
return false;
}
private static bool GetSubstituteW3cColor (ReadOnlySpan<char> name, out Color substituteColor)
{
int nameHashCode = string.GetHashCode(name, StringComparison.OrdinalIgnoreCase);
if (W3cSubstituteColors.TryGetValue (nameHashCode, out var match) &&
match is (string matchName, Color matchColor) &&
name.Equals (matchName, StringComparison.OrdinalIgnoreCase))
{
substituteColor = matchColor;
return true;
}
substituteColor = default;
return false;
}
private static bool IsBlockedW3cColor (Color color)
{
return W3cBlockedColors.Contains (color);
}
}

View File

@@ -1,24 +0,0 @@
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;
}
}

View File

@@ -0,0 +1,777 @@
namespace Terminal.Gui;
/// <summary>
/// Represents the W3C color names with their RGB values.
/// </summary>
/// <remarks>
/// Based on https://www.w3schools.com/colors/color_tryit.asp page.
/// </remarks>
public enum W3cColor
{
/// <summary>
/// Alice blue RGB(240, 248, 255).
/// </summary>
AliceBlue = 0xF0F8FF,
/// <summary>
/// Antique white RGB(250, 235, 215).
/// </summary>
AntiqueWhite = 0xFAEBD7,
/// <summary>
/// Aqua RGB(0, 255, 255).
/// </summary>
Aqua = 0x00FFFF,
/// <summary>
/// Aquamarine RGB(127, 255, 212).
/// </summary>
Aquamarine = 0x7FFFD4,
/// <summary>
/// Azure RGB(240, 255, 255).
/// </summary>
Azure = 0xF0FFFF,
/// <summary>
/// Beige RGB(245, 245, 220).
/// </summary>
Beige = 0xF5F5DC,
/// <summary>
/// Bisque RGB(255, 228, 196).
/// </summary>
Bisque = 0xFFE4C4,
/// <summary>
/// Black RGB(0, 0, 0).
/// </summary>
Black = 0x000000,
/// <summary>
/// Blanched almond RGB(255, 235, 205).
/// </summary>
BlanchedAlmond = 0xFFEBCD,
/// <summary>
/// Blue RGB(0, 0, 255).
/// </summary>
Blue = 0x0000FF,
/// <summary>
/// Blue violet RGB(138, 43, 226).
/// </summary>
BlueViolet = 0x8A2BE2,
/// <summary>
/// Brown RGB(165, 42, 42).
/// </summary>
Brown = 0xA52A2A,
/// <summary>
/// Burly wood RGB(222, 184, 135).
/// </summary>
BurlyWood = 0xDEB887,
/// <summary>
/// Cadet blue RGB(95, 158, 160).
/// </summary>
CadetBlue = 0x5F9EA0,
/// <summary>
/// Chartreuse RGB(127, 255, 0).
/// </summary>
Chartreuse = 0x7FFF00,
/// <summary>
/// Chocolate RGB(210, 105, 30).
/// </summary>
Chocolate = 0xD2691E,
/// <summary>
/// Coral RGB(255, 127, 80).
/// </summary>
Coral = 0xFF7F50,
/// <summary>
/// Cornflower blue RGB(100, 149, 237).
/// </summary>
CornflowerBlue = 0x6495ED,
/// <summary>
/// Cornsilk RGB(255, 248, 220).
/// </summary>
Cornsilk = 0xFFF8DC,
/// <summary>
/// Crimson RGB(220, 20, 60).
/// </summary>
Crimson = 0xDC143C,
/// <summary>
/// Cyan RGB(0, 255, 255).
/// </summary>
/// <remarks>
/// Same as <see cref="Aqua"/>.
/// </remarks>
Cyan = Aqua,
/// <summary>
/// Dark blue RGB(0, 0, 139).
/// </summary>
DarkBlue = 0x00008B,
/// <summary>
/// Dark cyan RGB(0, 139, 139).
/// </summary>
DarkCyan = 0x008B8B,
/// <summary>
/// Dark goldenrod RGB(184, 134, 11).
/// </summary>
DarkGoldenrod = 0xB8860B,
/// <summary>
/// Dark gray RGB(169, 169, 169).
/// </summary>
DarkGray = 0xA9A9A9,
/// <summary>
/// Dark green RGB(0, 100, 0).
/// </summary>
DarkGreen = 0x006400,
/// <summary>
/// Dark grey RGB(169, 169, 169).
/// </summary>
/// <remarks>
/// Same as <see cref="DarkGray"/>.
/// </remarks>
DarkGrey = DarkGray,
/// <summary>
/// Dark khaki RGB(189, 183, 107).
/// </summary>
DarkKhaki = 0xBDB76B,
/// <summary>
/// Dark magenta RGB(139, 0, 139).
/// </summary>
DarkMagenta = 0x8B008B,
/// <summary>
/// Dark olive green RGB(85, 107, 47).
/// </summary>
DarkOliveGreen = 0x556B2F,
/// <summary>
/// Dark orange RGB(255, 140, 0).
/// </summary>
DarkOrange = 0xFF8C00,
/// <summary>
/// Dark orchid RGB(153, 50, 204).
/// </summary>
DarkOrchid = 0x9932CC,
/// <summary>
/// Dark red RGB(139, 0, 0).
/// </summary>
DarkRed = 0x8B0000,
/// <summary>
/// Dark salmon RGB(233, 150, 122).
/// </summary>
DarkSalmon = 0xE9967A,
/// <summary>
/// Dark sea green RGB(143, 188, 143).
/// </summary>
DarkSeaGreen = 0x8FBC8F,
/// <summary>
/// Dark slate blue RGB(72, 61, 139).
/// </summary>
DarkSlateBlue = 0x483D8B,
/// <summary>
/// Dark slate gray RGB(47, 79, 79).
/// </summary>
DarkSlateGray = 0x2F4F4F,
/// <summary>
/// Dark slate grey RGB(47, 79, 79).
/// </summary>
/// <remarks>
/// Same as <see cref="DarkSlateGray"/>.
/// </remarks>
DarkSlateGrey = DarkSlateGray,
/// <summary>
/// Dark turquoise RGB(0, 206, 209).
/// </summary>
DarkTurquoise = 0x00CED1,
/// <summary>
/// Dark violet RGB(148, 0, 211).
/// </summary>
DarkViolet = 0x9400D3,
/// <summary>
/// Deep pink RGB(255, 20, 147).
/// </summary>
DeepPink = 0xFF1493,
/// <summary>
/// Deep sky blue RGB(0, 191, 255).
/// </summary>
DeepSkyBlue = 0x00BFFF,
/// <summary>
/// Dim gray RGB(105, 105, 105).
/// </summary>
DimGray = 0x696969,
/// <summary>
/// Dim grey RGB(105, 105, 105).
/// </summary>
/// <remarks>
/// Same as <see cref="DimGray"/>.
/// </remarks>
DimGrey = DimGray,
/// <summary>
/// Dodger blue RGB(30, 144, 255).
/// </summary>
DodgerBlue = 0x1E90FF,
/// <summary>
/// Fire brick RGB(178, 34, 34).
/// </summary>
FireBrick = 0xB22222,
/// <summary>
/// Floral white RGB(255, 250, 240).
/// </summary>
FloralWhite = 0xFFFAF0,
/// <summary>
/// Forest green RGB(34, 139, 34).
/// </summary>
ForestGreen = 0x228B22,
/// <summary>
/// Fuchsia RGB(255, 0, 255).
/// </summary>
/// <remarks>
/// Same as <see cref="Magenta"/>.
/// </remarks>
Fuchsia = Magenta,
/// <summary>
/// Gainsboro RGB(220, 220, 220).
/// </summary>
Gainsboro = 0xDCDCDC,
/// <summary>
/// Ghost white RGB(248, 248, 255).
/// </summary>
GhostWhite = 0xF8F8FF,
/// <summary>
/// Gold RGB(255, 215, 0).
/// </summary>
Gold = 0xFFD700,
/// <summary>
/// Goldenrod RGB(218, 165, 32).
/// </summary>
Goldenrod = 0xDAA520,
/// <summary>
/// Gray RGB(128, 128, 128).
/// </summary>
Gray = 0x808080,
/// <summary>
/// Green RGB(0, 128, 0).
/// </summary>
Green = 0x008000,
/// <summary>
/// Green yellow RGB(173, 255, 47).
/// </summary>
GreenYellow = 0xADFF2F,
/// <summary>
/// Grey RGB(128, 128, 128).
/// </summary>
/// <remarks>
/// Same as <see cref="Gray"/>.
/// </remarks>
Grey = Gray,
/// <summary>
/// Honey dew RGB(240, 255, 240).
/// </summary>
HoneyDew = 0xF0FFF0,
/// <summary>
/// Hot pink RGB(255, 105, 180).
/// </summary>
HotPink = 0xFF69B4,
/// <summary>
/// Indian red RGB(205, 92, 92).
/// </summary>
IndianRed = 0xCD5C5C,
/// <summary>
/// Indigo RGB(75, 0, 130).
/// </summary>
Indigo = 0x4B0082,
/// <summary>
/// Ivory RGB(255, 255, 240).
/// </summary>
Ivory = 0xFFFFF0,
/// <summary>
/// Khaki RGB(240, 230, 140).
/// </summary>
Khaki = 0xF0E68C,
/// <summary>
/// Lavender RGB(230, 230, 250).
/// </summary>
Lavender = 0xE6E6FA,
/// <summary>
/// Lavender blush RGB(255, 240, 245).
/// </summary>
LavenderBlush = 0xFFF0F5,
/// <summary>
/// Lawn green RGB(124, 252, 0).
/// </summary>
LawnGreen = 0x7CFC00,
/// <summary>
/// Lemon chiffon RGB(255, 250, 205).
/// </summary>
LemonChiffon = 0xFFFACD,
/// <summary>
/// Light blue RGB(173, 216, 230).
/// </summary>
LightBlue = 0xADD8E6,
/// <summary>
/// Light coral RGB(240, 128, 128).
/// </summary>
LightCoral = 0xF08080,
/// <summary>
/// Light cyan RGB(224, 255, 255).
/// </summary>
LightCyan = 0xE0FFFF,
/// <summary>
/// Light goldenrod yellow RGB(250, 250, 210).
/// </summary>
LightGoldenrodYellow = 0xFAFAD2,
/// <summary>
/// Light gray RGB(211, 211, 211).
/// </summary>
LightGray = 0xD3D3D3,
/// <summary>
/// Light green RGB(144, 238, 144).
/// </summary>
LightGreen = 0x90EE90,
/// <summary>
/// Light grey RGB(211, 211, 211).
/// </summary>
/// <remarks>
/// Same as <see cref="LightGray"/>.
/// </remarks>
LightGrey = LightGray,
/// <summary>
/// Light pink RGB(255, 182, 193).
/// </summary>
LightPink = 0xFFB6C1,
/// <summary>
/// Light salmon RGB(255, 160, 122).
/// </summary>
LightSalmon = 0xFFA07A,
/// <summary>
/// Light sea green RGB(32, 178, 170).
/// </summary>
LightSeaGreen = 0x20B2AA,
/// <summary>
/// Light sky blue RGB(135, 206, 250).
/// </summary>
LightSkyBlue = 0x87CEFA,
/// <summary>
/// Light slate gray RGB(119, 136, 153).
/// </summary>
LightSlateGray = 0x778899,
/// <summary>
/// Light slate grey RGB(119, 136, 153).
/// </summary>
/// <remarks>
/// Same as <see cref="LightSlateGray"/>.
/// </remarks>
LightSlateGrey = LightSlateGray,
/// <summary>
/// Light steel blue RGB(176, 196, 222).
/// </summary>
LightSteelBlue = 0xB0C4DE,
/// <summary>
/// Light yellow RGB(255, 255, 224).
/// </summary>
LightYellow = 0xFFFFE0,
/// <summary>
/// Lime RGB(0, 255, 0).
/// </summary>
Lime = 0x00FF00,
/// <summary>
/// Lime green RGB(50, 205, 50).
/// </summary>
LimeGreen = 0x32CD32,
/// <summary>
/// Linen RGB(250, 240, 230).
/// </summary>
Linen = 0xFAF0E6,
/// <summary>
/// Magenta RGB(255, 0, 255).
/// </summary>
Magenta = 0xFF00FF,
/// <summary>
/// Maroon RGB(128, 0, 0).
/// </summary>
Maroon = 0x800000,
/// <summary>
/// Medium aqua marine RGB(102, 205, 170).
/// </summary>
MediumAquaMarine = 0x66CDAA,
/// <summary>
/// Medium blue RGB(0, 0, 205).
/// </summary>
MediumBlue = 0x0000CD,
/// <summary>
/// Medium orchid RGB(186, 85, 211).
/// </summary>
MediumOrchid = 0xBA55D3,
/// <summary>
/// Medium purple RGB(147, 112, 219).
/// </summary>
MediumPurple = 0x9370DB,
/// <summary>
/// Medium sea green RGB(60, 179, 113).
/// </summary>
MediumSeaGreen = 0x3CB371,
/// <summary>
/// Medium slate blue RGB(123, 104, 238).
/// </summary>
MediumSlateBlue = 0x7B68EE,
/// <summary>
/// Medium spring green RGB(0, 250, 154).
/// </summary>
MediumSpringGreen = 0x00FA9A,
/// <summary>
/// Medium turquoise RGB(72, 209, 204).
/// </summary>
MediumTurquoise = 0x48D1CC,
/// <summary>
/// Medium violet red RGB(199, 21, 133).
/// </summary>
MediumVioletRed = 0xC71585,
/// <summary>
/// Midnight blue RGB(25, 25, 112).
/// </summary>
MidnightBlue = 0x191970,
/// <summary>
/// Mint cream RGB(245, 255, 250).
/// </summary>
MintCream = 0xF5FFFA,
/// <summary>
/// Misty rose RGB(255, 228, 225).
/// </summary>
MistyRose = 0xFFE4E1,
/// <summary>
/// Moccasin RGB(255, 228, 181).
/// </summary>
Moccasin = 0xFFE4B5,
/// <summary>
/// Navajo white RGB(255, 222, 173).
/// </summary>
NavajoWhite = 0xFFDEAD,
/// <summary>
/// Navy RGB(0, 0, 128).
/// </summary>
Navy = 0x000080,
/// <summary>
/// Old lace RGB(253, 245, 230).
/// </summary>
OldLace = 0xFDF5E6,
/// <summary>
/// Olive RGB(128, 128, 0).
/// </summary>
Olive = 0x808000,
/// <summary>
/// Olive drab RGB(107, 142, 35).
/// </summary>
OliveDrab = 0x6B8E23,
/// <summary>
/// Orange RGB(255, 165, 0).
/// </summary>
Orange = 0xFFA500,
/// <summary>
/// Orange red RGB(255, 69, 0).
/// </summary>
OrangeRed = 0xFF4500,
/// <summary>
/// Orchid RGB(218, 112, 214).
/// </summary>
Orchid = 0xDA70D6,
/// <summary>
/// Pale goldenrod RGB(238, 232, 170).
/// </summary>
PaleGoldenrod = 0xEEE8AA,
/// <summary>
/// Pale green RGB(152, 251, 152).
/// </summary>
PaleGreen = 0x98FB98,
/// <summary>
/// Pale turquoise RGB(175, 238, 238).
/// </summary>
PaleTurquoise = 0xAFEEEE,
/// <summary>
/// Pale violet red RGB(219, 112, 147).
/// </summary>
PaleVioletRed = 0xDB7093,
/// <summary>
/// Papaya whip RGB(255, 239, 213).
/// </summary>
PapayaWhip = 0xFFEFD5,
/// <summary>
/// Peach puff RGB(255, 218, 185).
/// </summary>
PeachPuff = 0xFFDAB9,
/// <summary>
/// Peru RGB(205, 133, 63).
/// </summary>
Peru = 0xCD853F,
/// <summary>
/// Pink RGB(255, 192, 203).
/// </summary>
Pink = 0xFFC0CB,
/// <summary>
/// Plum RGB(221, 160, 221).
/// </summary>
Plum = 0xDDA0DD,
/// <summary>
/// Powder blue RGB(176, 224, 230).
/// </summary>
PowderBlue = 0xB0E0E6,
/// <summary>
/// Purple RGB(128, 0, 128).
/// </summary>
Purple = 0x800080,
/// <summary>
/// Rebecca purple RGB(102, 51, 153).
/// </summary>
RebeccaPurple = 0x663399,
/// <summary>
/// Red RGB(255, 0, 0).
/// </summary>
Red = 0xFF0000,
/// <summary>
/// Rosy brown RGB(188, 143, 143).
/// </summary>
RosyBrown = 0xBC8F8F,
/// <summary>
/// Royal blue RGB(65, 105, 225).
/// </summary>
RoyalBlue = 0x4169E1,
/// <summary>
/// Saddle brown RGB(139, 69, 19).
/// </summary>
SaddleBrown = 0x8B4513,
/// <summary>
/// Salmon RGB(250, 128, 114).
/// </summary>
Salmon = 0xFA8072,
/// <summary>
/// Sandy brown RGB(244, 164, 96).
/// </summary>
SandyBrown = 0xF4A460,
/// <summary>
/// Sea green RGB(46, 139, 87).
/// </summary>
SeaGreen = 0x2E8B57,
/// <summary>
/// Sea shell RGB(255, 245, 238).
/// </summary>
SeaShell = 0xFFF5EE,
/// <summary>
/// Sienna RGB(160, 82, 45).
/// </summary>
Sienna = 0xA0522D,
/// <summary>
/// Silver RGB(192, 192, 192).
/// </summary>
Silver = 0xC0C0C0,
/// <summary>
/// Sky blue RGB(135, 206, 235).
/// </summary>
SkyBlue = 0x87CEEB,
/// <summary>
/// Slate blue RGB(106, 90, 205).
/// </summary>
SlateBlue = 0x6A5ACD,
/// <summary>
/// Slate gray RGB(112, 128, 144).
/// </summary>
SlateGray = 0x708090,
/// <summary>
/// Slate grey RGB(112, 128, 144).
/// </summary>
/// <remarks>
/// Same as <see cref="SlateGray"/>.
/// </remarks>
SlateGrey = SlateGray,
/// <summary>
/// Snow RGB(255, 250, 250).
/// </summary>
Snow = 0xFFFAFA,
/// <summary>
/// Spring green RGB(0, 255, 127).
/// </summary>
SpringGreen = 0x00FF7F,
/// <summary>
/// Steel blue RGB(70, 130, 180).
/// </summary>
SteelBlue = 0x4682B4,
/// <summary>
/// Tan RGB(210, 180, 140).
/// </summary>
Tan = 0xD2B48C,
/// <summary>
/// Teal RGB(0, 128, 128).
/// </summary>
Teal = 0x008080,
/// <summary>
/// Thistle RGB(216, 191, 216).
/// </summary>
Thistle = 0xD8BFD8,
/// <summary>
/// Tomato RGB(255, 99, 71).
/// </summary>
Tomato = 0xFF6347,
/// <summary>
/// Turquoise RGB(64, 224, 208).
/// </summary>
Turquoise = 0x40E0D0,
/// <summary>
/// Violet RGB(238, 130, 238).
/// </summary>
Violet = 0xEE82EE,
/// <summary>
/// Wheat RGB(245, 222, 179).
/// </summary>
Wheat = 0xF5DEB3,
/// <summary>
/// White RGB(255, 255, 255).
/// </summary>
White = 0xFFFFFF,
/// <summary>
/// White smoke RGB(245, 245, 245).
/// </summary>
WhiteSmoke = 0xF5F5F5,
/// <summary>
/// Yellow RGB(255, 255, 0).
/// </summary>
Yellow = 0xFFFF00,
/// <summary>
/// Yellow green RGB(154, 205, 50).
/// </summary>
YellowGreen = 0x9ACD32
}

View File

@@ -0,0 +1,110 @@
#nullable enable
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Terminal.Gui;
/// <summary>
/// W3C color name resolver.
/// </summary>
public class W3cColorNameResolver : IColorNameResolver
{
/// <inheritdoc/>
public IEnumerable<string> GetColorNames () =>
W3cColors.GetColorNames ();
/// <inheritdoc/>
public bool TryParseColor (ReadOnlySpan<char> name, out Color color) =>
W3cColors.TryParseColor (name, out color);
/// <inheritdoc/>
public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) =>
W3cColors.TryNameColor (color, out name);
}
/// <summary>
/// Helper class for transforming to and from <see cref="W3cColor"/> enum.
/// </summary>
internal static class W3cColors
{
private static readonly ImmutableArray<string> Names;
private static readonly FrozenDictionary<uint, string> ArgbNameMap;
static W3cColors ()
{
// Populate based on names because enums with same numerical value
// are not otherwise distinguishable from each other.
string[] w3cNames = Enum.GetNames<W3cColor> ().Order().ToArray();
Dictionary<uint, string> map = new(w3cNames.Length);
foreach (string name in w3cNames)
{
W3cColor w3c = Enum.Parse<W3cColor>(name);
uint argb = GetArgb(w3c);
// TODO: Collect aliases?
_ = map.TryAdd (argb, name);
}
Names = ImmutableArray.Create (w3cNames);
ArgbNameMap = map.ToFrozenDictionary ();
}
/// <summary>
/// Gets read-only list of the W3C colors in alphabetical order.
/// </summary>
public static IReadOnlyList<string> GetColorNames ()
{
return Names;
}
/// <summary>
/// Converts the given W3C color name to equivalent color value.
/// </summary>
/// <param name="name">W3C color name.</param>
/// <param name="color">The successfully converted W3C color value.</param>
/// <returns>True if the conversion succeeded; otherwise false.</returns>
public static bool TryParseColor (ReadOnlySpan<char> name, out Color color)
{
if (!Enum.TryParse (name, ignoreCase: true, out W3cColor w3cColor) ||
// Any numerical value converts to undefined enum value.
!Enum.IsDefined (w3cColor))
{
color = default;
return false;
}
uint argb = GetArgb (w3cColor);
color = new Color (argb);
return true;
}
/// <summary>
/// Converts the given color value to a W3C color name.
/// </summary>
/// <param name="color">Color value to match W3C color.</param>
/// <param name="name">The successfully converted W3C color name.</param>
/// <returns>True if conversion succeeded; otherwise false.</returns>
public static bool TryNameColor (Color color, [NotNullWhen (true)] out string? name)
{
if (ArgbNameMap.TryGetValue (color.Argb, out name))
{
return true;
}
name = null;
return false;
}
private static uint GetArgb (W3cColor w3cColor)
{
const int alphaShift = 24;
const uint alphaMask = 0xFFU << alphaShift;
int rgb = (int)w3cColor;
uint argb = (uint)rgb | alphaMask;
return argb;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -280,444 +280,6 @@
<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>
<data name="#3B78FF" xml:space="preserve">
<value>BrightBlue</value>
</data>
<data name="#61D6D6" xml:space="preserve">
<value>BrightCyan</value>
</data>
<data name="#E74856" xml:space="preserve">
<value>BrightRed</value>
</data>
<data name="#16C60C" xml:space="preserve">
<value>BrightGreen</value>
</data>
<data name="#B4009E" xml:space="preserve">
<value>BrightMagenta</value>
</data>
<data name="#F9F1A5" xml:space="preserve">
<value>BrightYellow</value>
</data>
<data name="#767676" xml:space="preserve">
<value>DarkGray</value>
</data>
<data name="ctxColors" xml:space="preserve">
<value>Co_lors</value>
</data>

View File

@@ -1,7 +1,5 @@
#nullable enable
using System;
namespace Terminal.Gui;
/// <summary>
@@ -34,7 +32,7 @@ public partial class ColorPicker : View
private Color _selectedColor = Color.Black;
// TODO: Add interface
private readonly IColorNameResolver _colorNameResolver = new W3CColors ();
private readonly IColorNameResolver _colorNameResolver = new MultiStandardColorNameResolver ();
private List<IColorBar> _bars = new ();
@@ -64,7 +62,7 @@ public partial class ColorPicker : View
Width = textFieldWidth
};
tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField;
tfValue.Accepting += (s, _)=>UpdateSingleBarValueFromTextField(s);
tfValue.Accepting += (s, _) => UpdateSingleBarValueFromTextField (s);
_textFields.Add (bar, tfValue);
}
@@ -182,7 +180,7 @@ public partial class ColorPicker : View
Add (_tfHex);
_tfHex.HasFocusChanged += UpdateValueFromTextField;
_tfHex.Accepting += (_,_)=> UpdateValueFromTextField();
_tfHex.Accepting += (_, _) => UpdateValueFromTextField ();
}
private void DisposeOldViews ()
@@ -266,7 +264,7 @@ public partial class ColorPicker : View
if (_tfName != null)
{
_tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty;
_tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string? name) ? name : string.Empty;
}
if (_tfHex != null)
@@ -312,7 +310,7 @@ public partial class ColorPicker : View
}
// it is a leave event so update
UpdateValueFromName();
UpdateValueFromName ();
}
private void UpdateValueFromName ()
{

View File

@@ -9,10 +9,6 @@ namespace Terminal.Gui.ResourcesTests;
public class ResourceManagerTests
{
private const string DODGER_BLUE_COLOR_KEY = "DodgerBlue";
private const string DODGER_BLUE_COLOR_NAME = "DodgerBlue";
private const string NO_NAMED_COLOR_KEY = "#1E80FF";
private const string NO_NAMED_COLOR_NAME = "#1E80FF";
private const string EXISTENT_CULTURE = "pt-PT";
private const string NO_EXISTENT_CULTURE = "de-DE";
private const string NO_EXISTENT_KEY = "blabla";
@@ -50,51 +46,6 @@ public class ResourceManagerTests
RestoreCurrentCultures ();
}
[Fact]
public void GetResourceSet_FallBack_To_Default_For_No_Existent_Culture_File ()
{
CultureInfo.CurrentCulture = new (NO_EXISTENT_CULTURE);
CultureInfo.CurrentUICulture = new (NO_EXISTENT_CULTURE);
// W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames
string [] colorNames = new W3CColors ().GetColorNames ().ToArray ();
Assert.Contains (DODGER_BLUE_COLOR_NAME, colorNames);
Assert.DoesNotContain (NO_TRANSLATED_VALUE, colorNames);
RestoreCurrentCultures ();
}
[Fact]
public void GetResourceSet_FallBack_To_Default_For_Not_Translated_Existent_Culture_File ()
{
CultureInfo.CurrentCulture = new (EXISTENT_CULTURE);
CultureInfo.CurrentUICulture = new (EXISTENT_CULTURE);
// These aren't already translated
// ColorStrings.GetW3CColorNames method uses GetResourceSet method to retrieve color names
IEnumerable<string> colorNames = ColorStrings.GetW3CColorNames ();
Assert.NotEmpty (colorNames);
// W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames
colorNames = new W3CColors ().GetColorNames ().ToArray ();
Assert.Contains (DODGER_BLUE_COLOR_NAME, colorNames);
Assert.DoesNotContain (NO_TRANSLATED_VALUE, colorNames);
// ColorStrings.TryParseW3CColorName method uses GetResourceSet method to retrieve a color value
Assert.True (ColorStrings.TryParseW3CColorName (DODGER_BLUE_COLOR_NAME, out Color color));
Assert.Equal (DODGER_BLUE_COLOR_KEY, color.ToString ());
// W3CColors.GetColorNames also calls ColorStrings.GetW3CColorNames for no-named colors
colorNames = new W3CColors ().GetColorNames ().ToArray ();
Assert.DoesNotContain (NO_NAMED_COLOR_NAME, colorNames);
// ColorStrings.TryParseW3CColorName method uses GetResourceSet method to retrieve a color value for no-named colors
Assert.True (ColorStrings.TryParseW3CColorName (NO_NAMED_COLOR_NAME, out color));
Assert.Equal (NO_NAMED_COLOR_KEY, color.ToString ());
RestoreCurrentCultures ();
}
[Fact]
public void GetResourceSet_With_Filter_Does_Not_Overflows_If_Key_Does_Not_Exist ()
{

View File

@@ -749,7 +749,15 @@ public class ColorPickerTests
// Auto complete the color name
Application.RaiseKeyDownEvent (Key.Tab);
Assert.Equal ("Aquamarine", name.Text);
// Match cyan alternative name
Assert.Equal ("Aqua", name.Text);
Assert.True (name.HasFocus);
Application.RaiseKeyDownEvent (Key.Tab);
// Resolves to cyan color
Assert.Equal ("Cyan", name.Text);
// Tab out of the text field
Application.RaiseKeyDownEvent (Key.Tab);
@@ -757,7 +765,7 @@ public class ColorPickerTests
Assert.False (name.HasFocus);
Assert.NotSame (name, cp.Focused);
Assert.Equal ("#7FFFD4", hex.Text);
Assert.Equal ("#00FFFF", hex.Text);
Application.Top?.Dispose ();
Application.ResetState (true);
@@ -817,14 +825,6 @@ public class ColorPickerTests
};
}
[Fact]
public void TestColorNames ()
{
var colors = new W3CColors ();
Assert.Contains ("Aquamarine", colors.GetColorNames ());
Assert.DoesNotContain ("Save as", colors.GetColorNames ());
}
private ColorBar GetColorBar (ColorPicker cp, ColorPickerPart toGet)
{
if (toGet <= ColorPickerPart.Bar3)
@@ -845,7 +845,7 @@ public class ColorPickerTests
Application.Navigation = new ();
Application.Top = new() { Width = 20, Height = 5 };
Application.Top = new () { Width = 20, Height = 5 };
Application.Top.Add (cp);
Application.Top.LayoutSubViews ();

View File

@@ -0,0 +1,124 @@
#nullable enable
namespace Terminal.Gui.DrawingTests;
public class AnsiColorNameResolverTests
{
private readonly AnsiColorNameResolver _candidate = new();
[Fact]
public void GetNames_Returns16ColorNames ()
{
string[] expected = Enum.GetNames<ColorName16>();
string[] actual = _candidate.GetColorNames ().ToArray();
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (0, 0, 0, nameof (ColorName16.Black))]
[InlineData (0, 0, 255, nameof (ColorName16.Blue))]
[InlineData (59, 120, 255, nameof (ColorName16.BrightBlue))]
[InlineData (97, 214, 214, nameof (ColorName16.BrightCyan))]
[InlineData (22, 198, 12, nameof (ColorName16.BrightGreen))]
[InlineData (180, 0, 158, nameof (ColorName16.BrightMagenta))]
[InlineData (231, 72, 86, nameof (ColorName16.BrightRed))]
[InlineData (249, 241, 165, nameof (ColorName16.BrightYellow))]
[InlineData (0, 255, 255, nameof (ColorName16.Cyan))]
[InlineData (118, 118, 118, nameof (ColorName16.DarkGray))]
[InlineData (128, 128, 128, nameof (ColorName16.Gray))]
[InlineData (0, 128, 0, nameof (ColorName16.Green))]
[InlineData (255, 0, 255, nameof (ColorName16.Magenta))]
[InlineData (255, 0, 0, nameof (ColorName16.Red))]
[InlineData (255, 255, 255, nameof (ColorName16.White))]
[InlineData (255, 255, 0, nameof (ColorName16.Yellow))]
public void TryNameColor_ReturnsExpectedColorName (byte r, byte g, byte b, string expectedName)
{
var expected = (true, expectedName);
bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Fact]
public void TryNameColor_NoMatchFails ()
{
(bool, string?) expected = (false, null);
bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (nameof (ColorName16.Black), 0, 0, 0)]
[InlineData (nameof (ColorName16.Blue), 0, 0, 255)]
[InlineData (nameof (ColorName16.BrightBlue), 59, 120, 255)]
[InlineData (nameof (ColorName16.BrightCyan), 97, 214, 214)]
[InlineData (nameof (ColorName16.BrightGreen), 22, 198, 12)]
[InlineData (nameof (ColorName16.BrightMagenta), 180, 0, 158)]
[InlineData (nameof (ColorName16.BrightRed), 231, 72, 86)]
[InlineData (nameof (ColorName16.BrightYellow), 249, 241, 165)]
[InlineData (nameof (ColorName16.Cyan), 0, 255, 255)]
[InlineData (nameof (ColorName16.DarkGray), 118, 118, 118)]
[InlineData (nameof (ColorName16.Gray), 128, 128, 128)]
[InlineData (nameof (ColorName16.Green), 0, 128, 0)]
[InlineData (nameof (ColorName16.Magenta), 255, 0, 255)]
[InlineData (nameof (ColorName16.Red), 255, 0, 0)]
[InlineData (nameof (ColorName16.White), 255, 255, 255)]
[InlineData (nameof (ColorName16.Yellow), 255, 255, 0)]
// Case-insensitive
[InlineData ("BRIGHTBLUE", 59, 120, 255)]
[InlineData ("brightblue", 59, 120, 255)]
public void TryParseColor_ReturnsExpectedColor (string inputName, byte r, byte g, byte b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("12", 231, 72, 86)] // ColorName16.BrightRed
public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (null)]
[InlineData ("")]
[InlineData ("brightlight")]
public void TryParseColor_FailsOnInvalidColorName (string? invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("-12")]
public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
}

View File

@@ -0,0 +1,232 @@
#nullable enable
namespace Terminal.Gui.DrawingTests;
public class MultiStandardColorNameResolverTests
{
private readonly MultiStandardColorNameResolver _candidate = new();
[Theory]
// ANSI color names.
[InlineData (nameof (ColorName16.Black))]
[InlineData (nameof (ColorName16.White))]
[InlineData (nameof (ColorName16.Red))]
[InlineData (nameof (ColorName16.Green))]
[InlineData (nameof (ColorName16.Blue))]
[InlineData (nameof (ColorName16.Cyan))]
[InlineData (nameof (ColorName16.Magenta))]
[InlineData (nameof (ColorName16.DarkGray))]
[InlineData (nameof (ColorName16.BrightGreen))]
[InlineData (nameof (ColorName16.BrightMagenta))]
// Regular W3C color.
[InlineData (nameof (W3cColor.AliceBlue))]
[InlineData (nameof (W3cColor.BlanchedAlmond))]
[InlineData (nameof (W3cColor.CadetBlue))]
[InlineData (nameof (W3cColor.DarkBlue))]
[InlineData (nameof (W3cColor.FireBrick))]
[InlineData (nameof (W3cColor.Gainsboro))]
[InlineData (nameof (W3cColor.HoneyDew))]
[InlineData (nameof (W3cColor.Indigo))]
[InlineData (nameof (W3cColor.Khaki))]
[InlineData (nameof (W3cColor.Lavender))]
[InlineData (nameof (W3cColor.Maroon))]
[InlineData (nameof (W3cColor.Navy))]
[InlineData (nameof (W3cColor.Olive))]
[InlineData (nameof (W3cColor.Plum))]
[InlineData (nameof (W3cColor.RoyalBlue))]
[InlineData (nameof (W3cColor.Silver))]
[InlineData (nameof (W3cColor.Tomato))]
[InlineData (nameof (W3cColor.Violet))]
[InlineData (nameof (W3cColor.WhiteSmoke))]
[InlineData (nameof (W3cColor.YellowGreen))]
// W3C alternatives.
[InlineData (nameof (W3cColor.Grey))]
[InlineData (nameof (W3cColor.DarkGrey))]
[InlineData (nameof (W3cColor.Aqua))]
[InlineData (nameof (W3cColor.Fuchsia))]
[InlineData (nameof (W3cColor.DarkSlateGray))]
[InlineData (nameof (W3cColor.DarkSlateGrey))]
[InlineData (nameof (W3cColor.DimGray))]
[InlineData (nameof (W3cColor.DimGrey))]
[InlineData (nameof (W3cColor.LightGray))]
[InlineData (nameof (W3cColor.LightGrey))]
[InlineData (nameof (W3cColor.SlateGray))]
[InlineData (nameof (W3cColor.SlateGrey))]
public void GetNames_ContainsCombinationOfAnsiAndW3cNames (string name)
{
string[] names = _candidate.GetColorNames ().ToArray();
Assert.Contains (name, names);
}
[Theory]
// ANSI color names
[InlineData (0, 0, 0, nameof (ColorName16.Black))]
[InlineData (0, 0, 255, nameof (ColorName16.Blue))]
[InlineData (59, 120, 255, nameof (ColorName16.BrightBlue))]
[InlineData (97, 214, 214, nameof (ColorName16.BrightCyan))]
[InlineData (22, 198, 12, nameof (ColorName16.BrightGreen))]
[InlineData (180, 0, 158, nameof (ColorName16.BrightMagenta))]
[InlineData (231, 72, 86, nameof (ColorName16.BrightRed))]
[InlineData (249, 241, 165, nameof (ColorName16.BrightYellow))]
[InlineData (0, 255, 255, nameof (ColorName16.Cyan))]
[InlineData (118, 118, 118, nameof (ColorName16.DarkGray))]
[InlineData (128, 128, 128, nameof (ColorName16.Gray))]
[InlineData (0, 128, 0, nameof (ColorName16.Green))]
[InlineData (255, 0, 255, nameof (ColorName16.Magenta))]
[InlineData (255, 0, 0, nameof (ColorName16.Red))]
[InlineData (255, 255, 255, nameof (ColorName16.White))]
[InlineData (255, 255, 0, nameof (ColorName16.Yellow))]
// W3C color names
[InlineData (240, 248, 255, nameof (W3cColor.AliceBlue))]
[InlineData (255, 235, 205, nameof (W3cColor.BlanchedAlmond))]
[InlineData (95, 158, 160, nameof (W3cColor.CadetBlue))]
[InlineData (0, 0, 139, nameof (W3cColor.DarkBlue))]
[InlineData (178, 34, 34, nameof (W3cColor.FireBrick))]
[InlineData (220, 220, 220, nameof (W3cColor.Gainsboro))]
[InlineData (240, 255, 240, nameof (W3cColor.HoneyDew))]
[InlineData (75, 0, 130, nameof (W3cColor.Indigo))]
[InlineData (240, 230, 140, nameof (W3cColor.Khaki))]
[InlineData (230, 230, 250, nameof (W3cColor.Lavender))]
[InlineData (128, 0, 0, nameof (W3cColor.Maroon))]
[InlineData (0, 0, 128, nameof (W3cColor.Navy))]
[InlineData (128, 128, 0, nameof (W3cColor.Olive))]
[InlineData (221, 160, 221, nameof (W3cColor.Plum))]
[InlineData (65, 105, 225, nameof (W3cColor.RoyalBlue))]
[InlineData (192, 192, 192, nameof (W3cColor.Silver))]
[InlineData (255, 99, 71, nameof (W3cColor.Tomato))]
[InlineData (238, 130, 238, nameof (W3cColor.Violet))]
[InlineData (245, 245, 245, nameof (W3cColor.WhiteSmoke))]
[InlineData (154, 205, 50, nameof (W3cColor.YellowGreen))]
public void TryNameColor_ReturnsExpectedColorNames (byte r, byte g, byte b, string expectedName)
{
var expected = (true, expectedName);
bool actualSuccess = _candidate.TryNameColor(new Color(r, g, b), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (169, 169, 169)] // W3cColor.DarkGr(a|e)y
public void TryNameColor_OmitsBlockedW3cColors (byte r, byte g, byte b)
{
(bool, string?) expected = (false, null);
bool actualSuccess = _candidate.TryNameColor (new Color (r, g, b), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Fact]
public void TryNameColor_NoMatchFails ()
{
(bool, string?) expected = (false, null);
bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Theory]
// ANSI colors
[InlineData (nameof (ColorName16.Black), 0, 0, 0)]
[InlineData (nameof (ColorName16.Blue), 0, 0, 255)]
[InlineData (nameof (ColorName16.BrightBlue), 59, 120, 255)]
[InlineData (nameof (ColorName16.BrightCyan), 97, 214, 214)]
[InlineData (nameof (ColorName16.BrightGreen), 22, 198, 12)]
[InlineData (nameof (ColorName16.BrightMagenta), 180, 0, 158)]
[InlineData (nameof (ColorName16.BrightRed), 231, 72, 86)]
[InlineData (nameof (ColorName16.BrightYellow), 249, 241, 165)]
[InlineData (nameof (ColorName16.Cyan), 0, 255, 255)]
[InlineData (nameof (ColorName16.DarkGray), 118, 118, 118)]
[InlineData (nameof (ColorName16.Gray), 128, 128, 128)]
[InlineData (nameof (ColorName16.Green), 0, 128, 0)]
[InlineData (nameof (ColorName16.Magenta), 255, 0, 255)]
[InlineData (nameof (ColorName16.Red), 255, 0, 0)]
[InlineData (nameof (ColorName16.White), 255, 255, 255)]
[InlineData (nameof (ColorName16.Yellow), 255, 255, 0)]
// W3C color name => substituted ANSI color
[InlineData (nameof (W3cColor.Fuchsia), 255, 0, 255)] // ANSI Magenta
[InlineData (nameof (W3cColor.DarkGrey), 118, 118, 118)] // ANSI Dark Gray
[InlineData (nameof (W3cColor.Grey), 128, 128, 128)] // ANSI Gray
[InlineData (nameof (W3cColor.Aqua), 0, 255, 255)] // ANSI Cyan
// W3C colors
[InlineData (nameof (W3cColor.AliceBlue), 240, 248, 255)]
[InlineData (nameof (W3cColor.BlanchedAlmond), 255, 235, 205)]
[InlineData (nameof (W3cColor.CadetBlue), 95, 158, 160)]
[InlineData (nameof (W3cColor.DarkBlue), 0, 0, 139)]
[InlineData (nameof (W3cColor.FireBrick), 178, 34, 34)]
[InlineData (nameof (W3cColor.Gainsboro), 220, 220, 220)]
[InlineData (nameof (W3cColor.HoneyDew), 240, 255, 240)]
[InlineData (nameof (W3cColor.Indigo), 75, 0, 130)]
[InlineData (nameof (W3cColor.Khaki), 240, 230, 140)]
[InlineData (nameof (W3cColor.Lavender), 230, 230, 250)]
[InlineData (nameof (W3cColor.Maroon), 128, 0, 0)]
[InlineData (nameof (W3cColor.Navy), 0, 0, 128)]
[InlineData (nameof (W3cColor.Olive), 128, 128, 0)]
[InlineData (nameof (W3cColor.Plum), 221, 160, 221)]
[InlineData (nameof (W3cColor.RoyalBlue), 65, 105, 225)]
[InlineData (nameof (W3cColor.Silver), 192, 192, 192)]
[InlineData (nameof (W3cColor.Tomato), 255, 99, 71)]
[InlineData (nameof (W3cColor.Violet), 238, 130, 238)]
[InlineData (nameof (W3cColor.WhiteSmoke), 245, 245, 245)]
[InlineData (nameof (W3cColor.YellowGreen), 154, 205, 50)]
// Case-insensitive
[InlineData ("BRIGHTBLUE", 59, 120, 255)]
[InlineData ("brightblue", 59, 120, 255)]
[InlineData ("TOMATO", 255, 99, 71)]
[InlineData ("tomato", 255, 99, 71)]
public void TryParseColor_ResolvesCombinationOfAnsiAndW3cColors (string inputName, byte r, byte g, byte b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("12", 231, 72, 86)] // ColorName16.BrightRed
[InlineData ("16737095", 255, 99, 71)] // W3cColor.Tomato
public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (null)]
[InlineData ("")]
[InlineData ("brightlight")]
public void TryParseColor_FailsOnInvalidColorName (string? invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("-12")]
[InlineData ("-16737095")]
public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
}

View File

@@ -0,0 +1,150 @@
#nullable enable
namespace Terminal.Gui.DrawingTests;
public class W3cColorNameResolverTests
{
private readonly W3cColorNameResolver _candidate = new();
[Fact]
public void GetColorNames_NamesAreInAlphabeticalOrder ()
{
string[] alphabeticallyOrderedNames = Enum.GetNames<W3cColor> ().Order ().ToArray ();
Assert.Equal (alphabeticallyOrderedNames, _candidate.GetColorNames ());
}
[Theory]
[InlineData (nameof (W3cColor.Aqua))]
[InlineData (nameof (W3cColor.Cyan))]
[InlineData (nameof (W3cColor.DarkGray))]
[InlineData (nameof (W3cColor.DarkGrey))]
[InlineData (nameof (W3cColor.DarkSlateGray))]
[InlineData (nameof (W3cColor.DarkSlateGrey))]
[InlineData (nameof (W3cColor.DimGray))]
[InlineData (nameof (W3cColor.DimGrey))]
[InlineData (nameof (W3cColor.Fuchsia))]
[InlineData (nameof (W3cColor.LightGray))]
[InlineData (nameof (W3cColor.LightGrey))]
[InlineData (nameof (W3cColor.LightSlateGray))]
[InlineData (nameof (W3cColor.LightSlateGrey))]
[InlineData (nameof (W3cColor.Magenta))]
[InlineData (nameof (W3cColor.SlateGray))]
[InlineData (nameof (W3cColor.SlateGrey))]
public void GetColorNames_IncludesNamesWithSameValues (string name)
{
string[] names = _candidate.GetColorNames ().ToArray();
Assert.True (names.Contains (name), $"W3C color names is missing '{name}'.");
}
[Theory]
[InlineData (240, 248, 255, nameof (W3cColor.AliceBlue))]
[InlineData (0, 255, 255, nameof (W3cColor.Aqua))]
[InlineData (255, 0, 0, nameof (W3cColor.Red))]
[InlineData (0, 128, 0, nameof (W3cColor.Green))]
[InlineData (0, 0, 255, nameof (W3cColor.Blue))]
[InlineData (0, 255, 0, nameof (W3cColor.Lime))]
[InlineData (0, 0, 0, nameof (W3cColor.Black))]
[InlineData (255, 255, 255, nameof (W3cColor.White))]
[InlineData (154, 205, 50, nameof (W3cColor.YellowGreen))]
public void TryNameColor_ReturnsExpectedColorName (int r, int g, int b, string expectedName)
{
var expected = (true, expectedName);
Color inputColor = new(r, g, b);
bool actualSuccess = _candidate.TryNameColor (inputColor, out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Fact]
public void TryNameColor_NoMatchFails ()
{
(bool, string?) expected = (false, null);
bool actualSuccess = _candidate.TryNameColor (new Color (1, 2, 3), out string? actualName);
var actual = (actualSuccess, actualName);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (nameof (W3cColor.AliceBlue), 240, 248, 255)]
[InlineData (nameof (W3cColor.BlanchedAlmond), 255, 235, 205)]
[InlineData (nameof (W3cColor.CadetBlue), 95, 158, 160)]
[InlineData (nameof (W3cColor.DarkBlue), 0, 0, 139)]
[InlineData (nameof (W3cColor.FireBrick), 178, 34, 34)]
[InlineData (nameof (W3cColor.Gainsboro), 220, 220, 220)]
[InlineData (nameof (W3cColor.HoneyDew), 240, 255, 240)]
[InlineData (nameof (W3cColor.Indigo), 75, 0, 130)]
[InlineData (nameof (W3cColor.Khaki), 240, 230, 140)]
[InlineData (nameof (W3cColor.Lavender), 230, 230, 250)]
[InlineData (nameof (W3cColor.Maroon), 128, 0, 0)]
[InlineData (nameof (W3cColor.Navy), 0, 0, 128)]
[InlineData (nameof (W3cColor.Olive), 128, 128, 0)]
[InlineData (nameof (W3cColor.Plum), 221, 160, 221)]
[InlineData (nameof (W3cColor.RoyalBlue), 65, 105, 225)]
[InlineData (nameof (W3cColor.Silver), 192, 192, 192)]
[InlineData (nameof (W3cColor.Tomato), 255, 99, 71)]
[InlineData (nameof (W3cColor.Violet), 238, 130, 238)]
[InlineData (nameof (W3cColor.WhiteSmoke), 245, 245, 245)]
[InlineData (nameof (W3cColor.YellowGreen), 154, 205, 50)]
// Aliases also work
[InlineData (nameof (W3cColor.Aqua), 0, 255, 255)]
[InlineData (nameof (W3cColor.Cyan), 0, 255, 255)]
[InlineData (nameof (W3cColor.DarkGray), 169, 169, 169)]
[InlineData (nameof (W3cColor.DarkGrey), 169, 169, 169)]
// Case-insensitive
[InlineData ("Red", 255, 0, 0)]
[InlineData ("red", 255, 0, 0)]
[InlineData ("RED", 255, 0, 0)]
public void TryParseColor_ReturnsExpectedColor (string inputName, int r, int g, int b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("16737095", 255, 99, 71)] // W3cColor.Tomato
public void TryParseColor_ResolvesValidEnumNumber (string inputName, byte r, byte g, byte b)
{
var expected = (true, new Color(r, g, b));
bool actualSuccess = _candidate.TryParseColor (inputName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (null)]
[InlineData ("")]
[InlineData ("brightlight")]
public void TryParseColor_FailsOnInvalidColorName (string? invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
[Theory]
[InlineData ("-16737095")]
public void TryParseColor_FailsOnInvalidEnumNumber (string invalidName)
{
var expected = (false, default(Color));
bool actualSuccess = _candidate.TryParseColor (invalidName, out Color actualColor);
var actual = (actualSuccess, actualColor);
Assert.Equal (expected, actual);
}
}