Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Terminal.Gui/Configuration/ColorJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using ColorHelper;

namespace Terminal.Gui;

Expand Down Expand Up @@ -40,11 +39,9 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS
// 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))
Expand Down
70 changes: 70 additions & 0 deletions Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs
Original file line number Diff line number Diff line change
@@ -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.")
};
}
}
148 changes: 67 additions & 81 deletions Terminal.Gui/Drawing/Color/Color.Formatting.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -267,90 +266,79 @@ public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvi
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
),

// 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)
),

// #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
)
},

// 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),
{ 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)
),

// #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)
),

// #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 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, [])
};
Expand Down Expand Up @@ -585,11 +573,9 @@ public static bool TryParse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provi
[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}";
Expand Down
11 changes: 9 additions & 2 deletions Terminal.Gui/Drawing/Color/Color.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -236,6 +234,15 @@ internal static ColorName16 GetClosestNamedColor16 (Color inputColor)
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); }

Expand Down
13 changes: 12 additions & 1 deletion Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ public bool ContainsKey (string key)
}
}

/// <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)
Expand All @@ -193,6 +198,10 @@ public void CopyTo (KeyValuePair<string, ColorScheme?> [] array, int arrayIndex)
}
}

/// <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)
Expand All @@ -206,6 +215,7 @@ IEnumerator IEnumerable.GetEnumerator ()
return GetEnumerator ();
}

/// <inheritdoc />
public bool Remove (KeyValuePair<string, ColorScheme?> item)
{
lock (_lock)
Expand All @@ -219,6 +229,7 @@ public bool Remove (KeyValuePair<string, ColorScheme?> item)
}
}

/// <inheritdoc />
public bool Remove (string key)
{
lock (_lock)
Expand All @@ -245,7 +256,7 @@ public bool TryGetValue (string key, out ColorScheme? value)
/// <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)
Expand Down
Loading
Loading