Color.Formatting.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. #nullable enable
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Diagnostics.Contracts;
  4. using System.Globalization;
  5. using System.Runtime.CompilerServices;
  6. namespace Terminal.Gui;
  7. public readonly partial record struct Color {
  8. /// <summary>
  9. /// Converts the provided <see langword="string" /> to a new <see cref="Color" /> value.
  10. /// </summary>
  11. /// <param name="text">
  12. /// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
  13. /// "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
  14. /// </param>
  15. /// <param name="formatProvider">
  16. /// If specified and not <see langword="null" />, will be passed to <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
  17. /// </param>
  18. /// <returns>
  19. /// A <see cref="Color" /> value equivalent to <paramref name="text" />, if parsing was successful.
  20. /// </returns>
  21. /// <remarks>
  22. /// While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
  23. /// </remarks>
  24. /// <exception cref="ArgumentNullException">If <paramref name="text" /> is <see langword="null" />.</exception>
  25. /// <exception cref="ArgumentException">
  26. /// If <paramref name="text" /> is an empty string or consists of only whitespace characters.
  27. /// </exception>
  28. /// <exception cref="ColorParseException">
  29. /// If thrown by <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
  30. /// </exception>
  31. [Pure]
  32. [SkipLocalsInit]
  33. public static Color Parse (string? text, IFormatProvider? formatProvider = null)
  34. {
  35. ArgumentException.ThrowIfNullOrWhiteSpace (text, nameof (text));
  36. if ( text is { Length: < 3 } && formatProvider is null ) {
  37. throw new ColorParseException (text, reason: "Provided text is too short to be any known color format.", badValue: text);
  38. }
  39. return Parse (text.AsSpan (), formatProvider ?? CultureInfo.InvariantCulture);
  40. }
  41. /// <summary>
  42. /// Converts the provided <see cref="ReadOnlySpan{T}" /> of <see langword="char" /> to a new <see cref="Color" /> value.
  43. /// </summary>
  44. /// <param name="text">
  45. /// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
  46. /// "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
  47. /// </param>
  48. /// <param name="formatProvider">
  49. /// Optional <see cref="IFormatProvider" /> to provide parsing services for the input text.
  50. /// <br />
  51. /// Defaults to <see cref="CultureInfo.InvariantCulture" /> if <see langword="null" />.
  52. /// <br />
  53. /// If not null, must implement <see cref="ICustomColorFormatter" /> or will be ignored and <see cref="CultureInfo.InvariantCulture" /> will
  54. /// be used.
  55. /// </param>
  56. /// <returns>
  57. /// A <see cref="Color" /> value equivalent to <paramref name="text" />, if parsing was successful.
  58. /// </returns>
  59. /// <remarks>
  60. /// While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
  61. /// </remarks>
  62. /// <exception cref="ArgumentException">
  63. /// with an inner <see cref="FormatException" /> if <paramref name="text" /> was unable to be successfully parsed as a <see cref="Color" />,
  64. /// for any reason.
  65. /// </exception>
  66. [Pure]
  67. [SkipLocalsInit]
  68. public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
  69. {
  70. return text switch {
  71. // Null string or empty span provided
  72. { IsEmpty: true } when formatProvider is null => throw new ColorParseException (in text, "The text provided was null or empty.", in text),
  73. // A valid ICustomColorFormatter was specified and the text wasn't null or empty
  74. { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
  75. // Input string is only whitespace
  76. { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (in text, "The text provided consisted of only whitespace characters.", in text),
  77. // Any string too short to possibly be any supported format.
  78. { Length: > 0 and < 4 } => throw new ColorParseException (in text, "Text was too short to be any possible supported format.", in text),
  79. // The various hexadecimal cases
  80. ['#', ..] hexString => hexString switch {
  81. // #RGB
  82. ['#', var rChar, var gChar, var bChar] chars when chars [1..].IsAllAsciiHexDigits () =>
  83. new Color (byte.Parse ([rChar, rChar], NumberStyles.HexNumber), byte.Parse ([gChar, gChar], NumberStyles.HexNumber), byte.Parse ([bChar, bChar], NumberStyles.HexNumber)),
  84. // #ARGB
  85. ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..].IsAllAsciiHexDigits () =>
  86. 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)),
  87. // #RRGGBB
  88. ['#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char] chars when chars [1..].IsAllAsciiHexDigits () =>
  89. new Color (byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)),
  90. // #AARRGGBB
  91. ['#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char] chars when chars [1..].IsAllAsciiHexDigits () =>
  92. 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)),
  93. _ => throw new ColorParseException (in hexString, $"Color hex string {hexString} was not in a supported format", in hexString)
  94. },
  95. // rgb(r,g,b) or rgb(r,g,b,a)
  96. ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
  97. // rgba(r,g,b,a) or rgba(r,g,b)
  98. ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
  99. // Attempt to parse as a named color from the ColorName enum
  100. { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) => new Color (colorName),
  101. // Any other input
  102. _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
  103. };
  104. [Pure]
  105. [SkipLocalsInit]
  106. static Color ParseRgbaFormat (in ReadOnlySpan<char> originalString, in int startIndex)
  107. {
  108. ReadOnlySpan<char> valuesSubstring = originalString [startIndex..^1];
  109. Span<Range> valueRanges = stackalloc Range [4];
  110. int rangeCount = valuesSubstring.Split (valueRanges, ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  111. switch ( rangeCount ) {
  112. case 3:
  113. {
  114. // rgba(r,g,b)
  115. ParseRgbValues (in valuesSubstring, in valueRanges, in originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan);
  116. return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
  117. }
  118. case 4:
  119. {
  120. // rgba(r,g,b,a)
  121. ParseRgbValues (in valuesSubstring, in valueRanges, in originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan);
  122. ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
  123. if ( !aSpan.IsAllAsciiDigits () ) {
  124. throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in aSpan, nameof (A));
  125. }
  126. return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
  127. }
  128. default:
  129. throw new ColorParseException (in originalString, $"Wrong number of values. Expected 3 or 4 decimal integers. Got {rangeCount}.", in originalString);
  130. }
  131. [Pure]
  132. [SkipLocalsInit]
  133. static void ParseRgbValues (in ReadOnlySpan<char> valuesString, in Span<Range> valueComponentRanges, in ReadOnlySpan<char> originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan)
  134. {
  135. rSpan = valuesString [valueComponentRanges [0]];
  136. if ( !rSpan.IsAllAsciiDigits () ) {
  137. throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in rSpan, nameof (R));
  138. }
  139. gSpan = valuesString [valueComponentRanges [1]];
  140. if ( !gSpan.IsAllAsciiDigits () ) {
  141. throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in gSpan, nameof (G));
  142. }
  143. bSpan = valuesString [valueComponentRanges [2]];
  144. if ( !bSpan.IsAllAsciiDigits () ) {
  145. throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in bSpan, nameof (B));
  146. }
  147. }
  148. }
  149. }
  150. /// <inheritdoc />
  151. [Pure]
  152. [SkipLocalsInit]
  153. public static Color Parse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)
  154. {
  155. return Parse (Encoding.UTF8.GetString (utf8Text), provider);
  156. }
  157. /// <inheritdoc cref="object.ToString" />
  158. /// <summary>
  159. /// Returns a <see langword="string" /> representation of the current <see cref="Color" /> value, according to the provided
  160. /// <paramref name="formatString" /> and optional <paramref name="formatProvider" />.
  161. /// </summary>
  162. /// <param name="formatString">
  163. /// A format string that will be passed to <see cref="string.Format(System.IFormatProvider?,string,object?[])" />.<para/>
  164. /// See remarks for parameters passed to that method.
  165. /// </param>
  166. /// <param name="formatProvider">
  167. /// An optional <see cref="IFormatProvider" /> to use when formatting the <see cref="Color" /> using custom format strings not specified for
  168. /// this method. Provides this instance as <see cref="Argb" />.
  169. /// <br />
  170. /// If this parameter is not null, the specified <see cref="IFormatProvider" /> will be used instead of the custom formatting provided by the
  171. /// <see cref="Color" /> type.<para/>
  172. /// See remarks for defined format strings.
  173. /// </param>
  174. /// <remarks>
  175. /// Pre-defined format strings for this method, if a custom <paramref name="formatProvider" /> is not supplied are: <list type="bullet">
  176. /// <listheader>
  177. /// <term>Value</term> <description>Result</description>
  178. /// </listheader> <item>
  179. /// <term>g or null or empty string</term> <description>
  180. /// General/default format - Returns a named <see cref="Color" /> if there is a match, or a 24-bit/3-byte/6-hex digit string in
  181. /// "#RRGGBB" format.
  182. /// </description>
  183. /// </item> <item>
  184. /// <term>G</term> <description>
  185. /// Extended general format - Returns a named <see cref="Color" /> if there is a match, or a 32-bit/4-byte/8-hex digit string in
  186. /// "#AARRGGBB" format.
  187. /// </description>
  188. /// </item> <item>
  189. /// <term>d</term> <description>
  190. /// Decimal format - Returns a 3-component decimal representation of the <see cref="Color" /> in "rgb(R,G,B)" format.
  191. /// </description>
  192. /// </item> <item>
  193. /// <term>D</term> <description>
  194. /// Extended decimal format - Returns a 4-component decimal representation of the <see cref="Color" /> in "rgba(R,G,B,A)" format.
  195. /// </description>
  196. /// </item>
  197. /// </list>
  198. /// <para>
  199. /// If <paramref name="formatProvider" /> is provided and is a non-null <see cref="ICustomColorFormatter" />, the following behaviors are
  200. /// available, for the specified values of <paramref name="formatString" />: <list type="bullet">
  201. /// <listheader>
  202. /// <term>Value</term> <description>Result</description>
  203. /// </listheader> <item>
  204. /// <term>null or empty string</term> <description>
  205. /// Calls <see cref="ICustomColorFormatter.Format(string?,byte,byte,byte,byte)" /> on the provided <paramref name="formatProvider" />
  206. /// with the null string, and <see cref="R" />, <see cref="G" />, <see cref="B" />, and <see cref="A" /> as typed arguments of type
  207. /// <see cref="Byte" />.
  208. /// </description>
  209. /// </item> <item>
  210. /// <term>All other values</term> <description>
  211. /// Calls <see cref="string.Format{TArg0}" /> with the provided <paramref name="formatProvider" /> and
  212. /// <paramref name="formatString" /> (parsed as a <see cref="CompositeFormat" />), with the value of <see cref="Argb" /> as the sole
  213. /// <see langword="uint" />-typed argument.
  214. /// </description>
  215. /// </item>
  216. /// </list>
  217. /// </para>
  218. /// </remarks>
  219. [SkipLocalsInit]
  220. public string ToString ([StringSyntax (StringSyntaxAttribute.CompositeFormat)] string? formatString, IFormatProvider? formatProvider = null)
  221. {
  222. return (formatString, formatProvider) switch {
  223. // Null or empty string and null formatProvider - Revert to 'g' case behavior
  224. (null or { Length: 0 }, null) => ToString (),
  225. // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
  226. (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
  227. // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
  228. (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) => string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
  229. // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
  230. (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) => $"#{R:X2}{G:X2}{B:X2}",
  231. // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
  232. ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
  233. // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
  234. (['g'], null) => ToString (),
  235. // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
  236. (['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
  237. // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
  238. (['d'], null) => $"rgb({R:D},{G:D},{B:D})",
  239. // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
  240. (['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
  241. // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
  242. ({ }, _) => string.Format (formatProvider ?? CultureInfo.InvariantCulture, CompositeFormat.Parse (formatString), R, G, B, A),
  243. _ => throw new InvalidOperationException ($"Unable to create string from Color with value {Argb}, using format string {formatString}")
  244. } ?? throw new InvalidOperationException ($"Unable to create string from Color with value {Argb}, using format string {formatString}");
  245. }
  246. /// <inheritdoc />
  247. /// <remarks>
  248. /// <para>
  249. /// This method should be used only when absolutely necessary, because it <b>always</b> has more overhead than
  250. /// <see cref="ToString(string?,System.IFormatProvider?)" />, as this method results in an intermediate allocation of one or more instances
  251. /// of <see langword="string" /> and a copy of that string to <paramref name="destination" /> if formatting was successful.
  252. /// <br />
  253. /// When possible, use <see cref="ToString(string?,System.IFormatProvider?)" />, which attempts to avoid intermediate allocations.
  254. /// </para>
  255. /// <para>
  256. /// This method only returns <see langword="true" /> and with its output written to <paramref name="destination" /> if the formatted
  257. /// string, <i>in its entirety</i>, will fit in <paramref name="destination" />. If the resulting formatted string is too large to fit in
  258. /// <paramref name="destination" />, the result will be false and <paramref name="destination" /> will be unaltered.
  259. /// </para>
  260. /// <para>
  261. /// The resulting formatted string may be <b>shorter</b> than <paramref name="destination" />. When this method returns
  262. /// <see langword="true" />, use <paramref name="charsWritten" /> when handling the value of <paramref name="destination" />.
  263. /// </para>
  264. /// </remarks>
  265. [Pure]
  266. [SkipLocalsInit]
  267. public bool TryFormat (Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
  268. {
  269. // TODO: This can probably avoid a string allocation with a little more work
  270. try {
  271. string formattedString = ToString (format.ToString (), provider);
  272. if ( formattedString.Length <= destination.Length ) {
  273. formattedString.CopyTo (destination);
  274. charsWritten = formattedString.Length;
  275. return true;
  276. }
  277. }
  278. catch {
  279. destination.Clear ();
  280. charsWritten = 0;
  281. return false;
  282. }
  283. destination.Clear ();
  284. charsWritten = 0;
  285. return false;
  286. }
  287. /// <inheritdoc />
  288. /// <remarks>
  289. /// Use of this method involves a stack allocation of <paramref name="utf8Destination" />.Length * 2 bytes. Use of the overload taking a char
  290. /// span is recommended.
  291. /// </remarks>
  292. [SkipLocalsInit]
  293. public bool TryFormat (Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
  294. {
  295. Span<char> charDestination = stackalloc char [utf8Destination.Length * 2];
  296. if ( TryFormat (charDestination, out int charsWritten, format, provider) ) {
  297. Encoding.UTF8.GetBytes (charDestination, utf8Destination);
  298. bytesWritten = charsWritten / 2;
  299. return true;
  300. }
  301. utf8Destination.Clear ();
  302. bytesWritten = 0;
  303. return false;
  304. }
  305. /// <summary>
  306. /// Converts the provided <see langword="string" /> to a new <see cref="Color" /> value.
  307. /// </summary>
  308. /// <param name="text">
  309. /// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
  310. /// "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor" /> string values.
  311. /// </param>
  312. /// <param name="formatProvider">
  313. /// Optional <see cref="IFormatProvider" /> to provide formatting services for the input text.
  314. /// <br />
  315. /// Defaults to <see cref="CultureInfo.InvariantCulture" /> if <see langword="null" />.
  316. /// </param>
  317. /// <param name="result">
  318. /// The parsed value, if successful, or <see langword="default" />(<see cref="Color" />), if unsuccessful.
  319. /// </param>
  320. /// <returns>A <see langword="bool" /> value indicating whether parsing was successful.</returns>
  321. /// <remarks>
  322. /// While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
  323. /// </remarks>
  324. [Pure]
  325. [SkipLocalsInit]
  326. public static bool TryParse (string? text, IFormatProvider? formatProvider, out Color result)
  327. {
  328. return TryParse (text.AsSpan (), formatProvider ?? CultureInfo.InvariantCulture, out result);
  329. }
  330. /// <summary>
  331. /// Converts the provided <see cref="ReadOnlySpan{T}" /> of <see langword="char" /> to a new <see cref="Color" /> value.
  332. /// </summary>
  333. /// <param name="text">
  334. /// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
  335. /// "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor" /> string values.
  336. /// </param>
  337. /// <param name="formatProvider">
  338. /// If specified and not <see langword="null" />, will be passed to <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
  339. /// </param>
  340. /// <param name="color">
  341. /// The parsed value, if successful, or <see langword="default" />(<see cref="Color" />), if unsuccessful.
  342. /// </param>
  343. /// <returns>A <see langword="bool" /> value indicating whether parsing was successful.</returns>
  344. /// <remarks>
  345. /// While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
  346. /// </remarks>
  347. [Pure]
  348. [SkipLocalsInit]
  349. public static bool TryParse (ReadOnlySpan<char> text, IFormatProvider? formatProvider, out Color color)
  350. {
  351. try {
  352. Color c = Parse (text, formatProvider);
  353. color = c;
  354. return true;
  355. }
  356. catch ( ColorParseException ) {
  357. color = default;
  358. return false;
  359. }
  360. }
  361. /// <inheritdoc />
  362. [Pure]
  363. [SkipLocalsInit]
  364. public static bool TryParse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out Color result)
  365. {
  366. return TryParse (Encoding.UTF8.GetString (utf8Text), provider, out result);
  367. }
  368. /// <summary>Converts the color to a string representation.</summary>
  369. /// <remarks>
  370. /// <para>
  371. /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
  372. /// </para>
  373. /// <para>
  374. /// <see cref="A" /> (Alpha channel) is ignored and the returned string will not include it for this overload.
  375. /// </para>
  376. /// </remarks>
  377. /// <returns>The string representation of this value in #RRGGBB format.</returns>
  378. [Pure]
  379. [SkipLocalsInit]
  380. public override string ToString ()
  381. {
  382. // If Values has an exact match with a named color (in _colorNames), use that.
  383. return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
  384. ? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
  385. : // Otherwise return as an RGB hex value.
  386. $"#{R:X2}{G:X2}{B:X2}";
  387. }
  388. /// <summary>Converts the provided string to a new <see cref="Color" /> instance.</summary>
  389. /// <param name="text">
  390. /// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
  391. /// "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
  392. /// </param>
  393. /// <param name="color">The parsed value.</param>
  394. /// <returns>A boolean value indicating whether parsing was successful.</returns>
  395. /// <remarks>
  396. /// While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
  397. /// </remarks>
  398. public static bool TryParse (string text, [NotNullWhen (true)] out Color? color)
  399. {
  400. if ( TryParse (text.AsSpan (), null, out Color c) ) {
  401. color = c;
  402. return true;
  403. }
  404. color = null;
  405. return false;
  406. }
  407. }