Color.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. global using Attribute = Terminal.Gui.Attribute;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.Immutable;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Linq;
  7. using System.Text.Json.Serialization;
  8. using System.Text.RegularExpressions;
  9. namespace Terminal.Gui;
  10. /// <summary>
  11. /// Defines the 16 legacy color names and values that can be used to set the
  12. /// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
  13. /// </summary>
  14. /// <remarks>
  15. /// <para>
  16. /// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
  17. /// </para>
  18. /// <para>
  19. /// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured
  20. /// using the
  21. /// <see cref="Color.Colors"/> property.
  22. /// </para>
  23. /// </remarks>
  24. public enum ColorName {
  25. /// <summary>
  26. /// The black color. ANSI escape sequence: <c>\u001b[30m</c>.
  27. /// </summary>
  28. Black,
  29. /// <summary>
  30. /// The blue color. ANSI escape sequence: <c>\u001b[34m</c>.
  31. /// </summary>
  32. Blue,
  33. /// <summary>
  34. /// The green color. ANSI escape sequence: <c>\u001b[32m</c>.
  35. /// </summary>
  36. Green,
  37. /// <summary>
  38. /// The cyan color. ANSI escape sequence: <c>\u001b[36m</c>.
  39. /// </summary>
  40. Cyan,
  41. /// <summary>
  42. /// The red color. ANSI escape sequence: <c>\u001b[31m</c>.
  43. /// </summary>
  44. Red,
  45. /// <summary>
  46. /// The magenta color. ANSI escape sequence: <c>\u001b[35m</c>.
  47. /// </summary>
  48. Magenta,
  49. /// <summary>
  50. /// The yellow color (also known as Brown). ANSI escape sequence: <c>\u001b[33m</c>.
  51. /// </summary>
  52. Yellow,
  53. /// <summary>
  54. /// The gray color (also known as White). ANSI escape sequence: <c>\u001b[37m</c>.
  55. /// </summary>
  56. Gray,
  57. /// <summary>
  58. /// The dark gray color (also known as Bright Black). ANSI escape sequence: <c>\u001b[30;1m</c>.
  59. /// </summary>
  60. DarkGray,
  61. /// <summary>
  62. /// The bright blue color. ANSI escape sequence: <c>\u001b[34;1m</c>.
  63. /// </summary>
  64. BrightBlue,
  65. /// <summary>
  66. /// The bright green color. ANSI escape sequence: <c>\u001b[32;1m</c>.
  67. /// </summary>
  68. BrightGreen,
  69. /// <summary>
  70. /// The bright cyan color. ANSI escape sequence: <c>\u001b[36;1m</c>.
  71. /// </summary>
  72. BrightCyan,
  73. /// <summary>
  74. /// The bright red color. ANSI escape sequence: <c>\u001b[31;1m</c>.
  75. /// </summary>
  76. BrightRed,
  77. /// <summary>
  78. /// The bright magenta color. ANSI escape sequence: <c>\u001b[35;1m</c>.
  79. /// </summary>
  80. BrightMagenta,
  81. /// <summary>
  82. /// The bright yellow color. ANSI escape sequence: <c>\u001b[33;1m</c>.
  83. /// </summary>
  84. BrightYellow,
  85. /// <summary>
  86. /// The White color (also known as Bright White). ANSI escape sequence: <c>\u001b[37;1m</c>.
  87. /// </summary>
  88. White
  89. }
  90. /// <summary>
  91. /// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background
  92. /// color.
  93. /// </summary>
  94. public enum AnsiColorCode {
  95. /// <summary>
  96. /// The ANSI color code for Black.
  97. /// </summary>
  98. BLACK = 30,
  99. /// <summary>
  100. /// The ANSI color code for Red.
  101. /// </summary>
  102. RED = 31,
  103. /// <summary>
  104. /// The ANSI color code for Green.
  105. /// </summary>
  106. GREEN = 32,
  107. /// <summary>
  108. /// The ANSI color code for Yellow.
  109. /// </summary>
  110. YELLOW = 33,
  111. /// <summary>
  112. /// The ANSI color code for Blue.
  113. /// </summary>
  114. BLUE = 34,
  115. /// <summary>
  116. /// The ANSI color code for Magenta.
  117. /// </summary>
  118. MAGENTA = 35,
  119. /// <summary>
  120. /// The ANSI color code for Cyan.
  121. /// </summary>
  122. CYAN = 36,
  123. /// <summary>
  124. /// The ANSI color code for White.
  125. /// </summary>
  126. WHITE = 37,
  127. /// <summary>
  128. /// The ANSI color code for Bright Black.
  129. /// </summary>
  130. BRIGHT_BLACK = 90,
  131. /// <summary>
  132. /// The ANSI color code for Bright Red.
  133. /// </summary>
  134. BRIGHT_RED = 91,
  135. /// <summary>
  136. /// The ANSI color code for Bright Green.
  137. /// </summary>
  138. BRIGHT_GREEN = 92,
  139. /// <summary>
  140. /// The ANSI color code for Bright Yellow.
  141. /// </summary>
  142. BRIGHT_YELLOW = 93,
  143. /// <summary>
  144. /// The ANSI color code for Bright Blue.
  145. /// </summary>
  146. BRIGHT_BLUE = 94,
  147. /// <summary>
  148. /// The ANSI color code for Bright Magenta.
  149. /// </summary>
  150. BRIGHT_MAGENTA = 95,
  151. /// <summary>
  152. /// The ANSI color code for Bright Cyan.
  153. /// </summary>
  154. BRIGHT_CYAN = 96,
  155. /// <summary>
  156. /// The ANSI color code for Bright White.
  157. /// </summary>
  158. BRIGHT_WHITE = 97
  159. }
  160. /// <summary>
  161. /// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see
  162. /// <see cref="ColorName"/>). Used with <see cref="Attribute"/>.
  163. /// </summary>
  164. [JsonConverter (typeof (ColorJsonConverter))]
  165. public readonly struct Color : IEquatable<Color> {
  166. // TODO: Make this map configurable via ConfigurationManager
  167. // TODO: This does not need to be a Dictionary, but can be an 16 element array.
  168. /// <summary>
  169. /// Maps legacy 16-color values to the corresponding 24-bit RGB value.
  170. /// </summary>
  171. internal static ImmutableDictionary<Color, ColorName> _colorToNameMap = new Dictionary<Color, ColorName> {
  172. // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
  173. // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
  174. { new Color (12, 12, 12), ColorName.Black },
  175. { new Color (0, 55, 218), ColorName.Blue },
  176. { new Color (19, 161, 14), ColorName.Green },
  177. { new Color (58, 150, 221), ColorName.Cyan },
  178. { new Color (197, 15, 31), ColorName.Red },
  179. { new Color (136, 23, 152), ColorName.Magenta },
  180. { new Color (128, 64, 32), ColorName.Yellow },
  181. { new Color (204, 204, 204), ColorName.Gray },
  182. { new Color (118, 118, 118), ColorName.DarkGray },
  183. { new Color (59, 120, 255), ColorName.BrightBlue },
  184. { new Color (22, 198, 12), ColorName.BrightGreen },
  185. { new Color (97, 214, 214), ColorName.BrightCyan },
  186. { new Color (231, 72, 86), ColorName.BrightRed },
  187. { new Color (180, 0, 158), ColorName.BrightMagenta },
  188. { new Color (249, 241, 165), ColorName.BrightYellow },
  189. { new Color (242, 242, 242), ColorName.White }
  190. }.ToImmutableDictionary ();
  191. /// <summary>
  192. /// Defines the 16 legacy color names and values that can be used to set the
  193. /// </summary>
  194. internal static ImmutableDictionary<ColorName, AnsiColorCode> _colorNameToAnsiColorMap = new Dictionary<ColorName, AnsiColorCode> {
  195. { ColorName.Black, AnsiColorCode.BLACK },
  196. { ColorName.Blue, AnsiColorCode.BLUE },
  197. { ColorName.Green, AnsiColorCode.GREEN },
  198. { ColorName.Cyan, AnsiColorCode.CYAN },
  199. { ColorName.Red, AnsiColorCode.RED },
  200. { ColorName.Magenta, AnsiColorCode.MAGENTA },
  201. { ColorName.Yellow, AnsiColorCode.YELLOW },
  202. { ColorName.Gray, AnsiColorCode.WHITE },
  203. { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
  204. { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
  205. { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
  206. { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
  207. { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
  208. { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
  209. { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
  210. { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
  211. }.ToImmutableDictionary ();
  212. /// <summary>
  213. /// Initializes a new instance of the <see cref="Color"/> class.
  214. /// </summary>
  215. /// <param name="red">The red 8-bits.</param>
  216. /// <param name="green">The green 8-bits.</param>
  217. /// <param name="blue">The blue 8-bits.</param>
  218. /// <param name="alpha">Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.</param>
  219. public Color (int red, int green, int blue, int alpha = 0xFF)
  220. {
  221. R = red;
  222. G = green;
  223. B = blue;
  224. A = alpha;
  225. }
  226. /// <summary>
  227. /// Initializes a new instance of the <see cref="Color"/> class with an encoded 24-bit color value.
  228. /// </summary>
  229. /// <param name="rgba">The encoded 24-bit color value (see <see cref="Rgba"/>).</param>
  230. public Color (int rgba)
  231. {
  232. A = (byte)(rgba >> 24 & 0xFF);
  233. R = (byte)(rgba >> 16 & 0xFF);
  234. G = (byte)(rgba >> 8 & 0xFF);
  235. B = (byte)(rgba & 0xFF);
  236. }
  237. /// <summary>
  238. /// Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color value.
  239. /// </summary>
  240. /// <param name="colorName">The 16-color value.</param>
  241. public Color (ColorName colorName)
  242. {
  243. var c = FromColorName (colorName);
  244. R = c.R;
  245. G = c.G;
  246. B = c.B;
  247. A = c.A;
  248. }
  249. /// <summary>
  250. /// Initializes a new instance of the <see cref="Color"/> color from string. See <see cref="TryParse(string, out Color)"/>
  251. /// for details.
  252. /// </summary>
  253. /// <param name="colorString"></param>
  254. /// <exception cref="Exception"></exception>
  255. public Color (string colorString)
  256. {
  257. if (!TryParse (colorString, out var c)) {
  258. throw new ArgumentOutOfRangeException (nameof (colorString));
  259. }
  260. R = c.R;
  261. G = c.G;
  262. B = c.B;
  263. A = c.A;
  264. }
  265. /// <summary>
  266. /// Initializes a new instance of the <see cref="Color"/>.
  267. /// </summary>
  268. public Color ()
  269. {
  270. R = 0;
  271. G = 0;
  272. B = 0;
  273. A = 0xFF;
  274. }
  275. /// <summary>
  276. /// Red color component.
  277. /// </summary>
  278. public int R { get; }
  279. /// <summary>
  280. /// Green color component.
  281. /// </summary>
  282. public int G { get; }
  283. /// <summary>
  284. /// Blue color component.
  285. /// </summary>
  286. public int B { get; }
  287. /// <summary>
  288. /// Alpha color component.
  289. /// </summary>
  290. /// <remarks>
  291. /// The Alpha channel is not supported by Terminal.Gui.
  292. /// </remarks>
  293. public int A { get; } // Not currently supported; here for completeness.
  294. /// <summary>
  295. /// Gets or sets the color value encoded as ARGB32.
  296. /// <code>
  297. /// (&lt;see cref="A"/&gt; &lt;&lt; 24) | (&lt;see cref="R"/&gt; &lt;&lt; 16) | (&lt;see cref="G"/&gt; &lt;&lt; 8) | &lt;see cref="B"/&gt;
  298. /// </code>
  299. /// </summary>
  300. [JsonIgnore]
  301. public int Rgba => A << 24 | R << 16 | G << 8 | B;
  302. /// <summary>
  303. /// Gets or sets the 24-bit color value for each of the legacy 16-color values.
  304. /// </summary>
  305. [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
  306. public static Dictionary<ColorName, string> Colors {
  307. get =>
  308. // Transform _colorToNameMap into a Dictionary<ColorNames,string>
  309. _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}");
  310. set {
  311. // Transform Dictionary<ColorNames,string> into _colorToNameMap
  312. var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => {
  313. if (Enum.TryParse<ColorName> (kvp.Key.ToString (), true, out var colorName)) {
  314. return colorName;
  315. }
  316. throw new ArgumentException ($"Invalid color name: {kvp.Key}");
  317. });
  318. _colorToNameMap = newMap.ToImmutableDictionary ();
  319. }
  320. }
  321. /// <summary>
  322. /// Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
  323. /// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
  324. /// </summary>
  325. /// <remarks>
  326. /// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded
  327. /// map.
  328. /// </remarks>
  329. [JsonIgnore]
  330. public ColorName ColorName => FindClosestColor (this);
  331. /// <summary>
  332. /// Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
  333. /// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
  334. /// </summary>
  335. /// <remarks>
  336. /// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded
  337. /// map.
  338. /// </remarks>
  339. [JsonIgnore]
  340. public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName];
  341. /// <summary>
  342. /// Converts a legacy <see cref="Gui.ColorName"/> to a 24-bit <see cref="Color"/>.
  343. /// </summary>
  344. /// <param name="colorName">The <see cref="Color"/> to convert.</param>
  345. /// <returns></returns>
  346. static Color FromColorName (ColorName colorName) => _colorToNameMap.FirstOrDefault (x => x.Value == colorName).Key;
  347. // Iterates through the entries in the _colorNames dictionary, calculates the
  348. // Euclidean distance between the input color and each dictionary color in RGB space,
  349. // and keeps track of the closest entry found so far. The function returns a KeyValuePair
  350. // representing the closest color entry and its associated color name.
  351. internal static ColorName FindClosestColor (Color inputColor)
  352. {
  353. var closestColor = ColorName.Black; // Default to Black
  354. var closestDistance = double.MaxValue;
  355. foreach (var colorEntry in _colorToNameMap) {
  356. var distance = CalculateColorDistance (inputColor, colorEntry.Key);
  357. if (distance < closestDistance) {
  358. closestDistance = distance;
  359. closestColor = colorEntry.Value;
  360. }
  361. }
  362. return closestColor;
  363. }
  364. static double CalculateColorDistance (Color color1, Color color2)
  365. {
  366. // Calculate the Euclidean distance between two colors
  367. var deltaR = color1.R - (double)color2.R;
  368. var deltaG = color1.G - (double)color2.G;
  369. var deltaB = color1.B - (double)color2.B;
  370. return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
  371. }
  372. /// <summary>
  373. /// Converts the provided string to a new <see cref="Color"/> instance.
  374. /// </summary>
  375. /// <param name="text">
  376. /// The text to analyze. Formats supported are
  377. /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
  378. /// <see cref="Gui.ColorName"/>.
  379. /// </param>
  380. /// <param name="color">The parsed value.</param>
  381. /// <returns>A boolean value indicating whether parsing was successful.</returns>
  382. /// <remarks>
  383. /// While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.
  384. /// </remarks>
  385. public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
  386. {
  387. // empty color
  388. if (string.IsNullOrEmpty (text)) {
  389. color = new Color ();
  390. return false;
  391. }
  392. // #RRGGBB, #RGB
  393. if (text [0] == '#' && text.Length is 7 or 4) {
  394. if (text.Length == 7) {
  395. var r = Convert.ToInt32 (text.Substring (1, 2), 16);
  396. var g = Convert.ToInt32 (text.Substring (3, 2), 16);
  397. var b = Convert.ToInt32 (text.Substring (5, 2), 16);
  398. color = new Color (r, g, b);
  399. } else {
  400. var rText = char.ToString (text [1]);
  401. var gText = char.ToString (text [2]);
  402. var bText = char.ToString (text [3]);
  403. var r = Convert.ToInt32 (rText + rText, 16);
  404. var g = Convert.ToInt32 (gText + gText, 16);
  405. var b = Convert.ToInt32 (bText + bText, 16);
  406. color = new Color (r, g, b);
  407. }
  408. return true;
  409. }
  410. // #RRGGBB, #RGBA
  411. if (text [0] == '#' && text.Length is 8 or 5) {
  412. if (text.Length == 7) {
  413. var r = Convert.ToInt32 (text.Substring (1, 2), 16);
  414. var g = Convert.ToInt32 (text.Substring (3, 2), 16);
  415. var b = Convert.ToInt32 (text.Substring (5, 2), 16);
  416. var a = Convert.ToInt32 (text.Substring (7, 2), 16);
  417. color = new Color (a, r, g, b);
  418. } else {
  419. var rText = char.ToString (text [1]);
  420. var gText = char.ToString (text [2]);
  421. var bText = char.ToString (text [3]);
  422. var aText = char.ToString (text [4]);
  423. var r = Convert.ToInt32 (aText + aText, 16);
  424. var g = Convert.ToInt32 (rText + rText, 16);
  425. var b = Convert.ToInt32 (gText + gText, 16);
  426. var a = Convert.ToInt32 (bText + bText, 16);
  427. color = new Color (r, g, b, a);
  428. }
  429. return true;
  430. }
  431. // rgb(r,g,b)
  432. var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
  433. if (match.Success) {
  434. var r = int.Parse (match.Groups [1].Value);
  435. var g = int.Parse (match.Groups [2].Value);
  436. var b = int.Parse (match.Groups [3].Value);
  437. color = new Color (r, g, b);
  438. return true;
  439. }
  440. // rgb(r,g,b,a)
  441. match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)");
  442. if (match.Success) {
  443. var r = int.Parse (match.Groups [1].Value);
  444. var g = int.Parse (match.Groups [2].Value);
  445. var b = int.Parse (match.Groups [3].Value);
  446. var a = int.Parse (match.Groups [4].Value);
  447. color = new Color (r, g, b, a);
  448. return true;
  449. }
  450. if (Enum.TryParse<ColorName> (text, true, out var colorName)) {
  451. color = new Color (colorName);
  452. return true;
  453. }
  454. color = new Color ();
  455. return false;
  456. }
  457. /// <summary>
  458. /// Converts the color to a string representation.
  459. /// </summary>
  460. /// <remarks>
  461. /// <para>
  462. /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
  463. /// </para>
  464. /// <para>
  465. /// <see cref="A"/> (Alpha channel) is ignored and the returned string will not include it.
  466. /// </para>
  467. /// </remarks>
  468. /// <returns></returns>
  469. public override string ToString ()
  470. {
  471. // If Values has an exact match with a named color (in _colorNames), use that.
  472. if (_colorToNameMap.TryGetValue (this, out var colorName)) {
  473. return Enum.GetName (typeof (ColorName), colorName);
  474. }
  475. // Otherwise return as an RGB hex value.
  476. return $"#{R:X2}{G:X2}{B:X2}";
  477. }
  478. #region Legacy Color Names
  479. /// <summary>
  480. /// The black color.
  481. /// </summary>
  482. public const ColorName Black = ColorName.Black;
  483. /// <summary>
  484. /// The blue color.
  485. /// </summary>
  486. public const ColorName Blue = ColorName.Blue;
  487. /// <summary>
  488. /// The green color.
  489. /// </summary>
  490. public const ColorName Green = ColorName.Green;
  491. /// <summary>
  492. /// The cyan color.
  493. /// </summary>
  494. public const ColorName Cyan = ColorName.Cyan;
  495. /// <summary>
  496. /// The red color.
  497. /// </summary>
  498. public const ColorName Red = ColorName.Red;
  499. /// <summary>
  500. /// The magenta color.
  501. /// </summary>
  502. public const ColorName Magenta = ColorName.Magenta;
  503. /// <summary>
  504. /// The yellow color.
  505. /// </summary>
  506. public const ColorName Yellow = ColorName.Yellow;
  507. /// <summary>
  508. /// The gray color.
  509. /// </summary>
  510. public const ColorName Gray = ColorName.Gray;
  511. /// <summary>
  512. /// The dark gray color.
  513. /// </summary>
  514. public const ColorName DarkGray = ColorName.DarkGray;
  515. /// <summary>
  516. /// The bright bBlue color.
  517. /// </summary>
  518. public const ColorName BrightBlue = ColorName.BrightBlue;
  519. /// <summary>
  520. /// The bright green color.
  521. /// </summary>
  522. public const ColorName BrightGreen = ColorName.BrightGreen;
  523. /// <summary>
  524. /// The bright cyan color.
  525. /// </summary>
  526. public const ColorName BrightCyan = ColorName.BrightCyan;
  527. /// <summary>
  528. /// The bright red color.
  529. /// </summary>
  530. public const ColorName BrightRed = ColorName.BrightRed;
  531. /// <summary>
  532. /// The bright magenta color.
  533. /// </summary>
  534. public const ColorName BrightMagenta = ColorName.BrightMagenta;
  535. /// <summary>
  536. /// The bright yellow color.
  537. /// </summary>
  538. public const ColorName BrightYellow = ColorName.BrightYellow;
  539. /// <summary>
  540. /// The White color.
  541. /// </summary>
  542. public const ColorName White = ColorName.White;
  543. #endregion
  544. // TODO: Verify implict/explicit are correct for below
  545. #region Operators
  546. /// <summary>
  547. /// Cast from int.
  548. /// </summary>
  549. /// <param name="rgba"></param>
  550. public static implicit operator Color (int rgba) => new (rgba);
  551. /// <summary>
  552. /// Cast to int.
  553. /// </summary>
  554. /// <param name="color"></param>
  555. public static implicit operator int (Color color) => color.Rgba;
  556. /// <summary>
  557. /// Cast from <see cref="Gui.ColorName"/>. May fail if the color is not a named color.
  558. /// </summary>
  559. /// <param name="colorName"></param>
  560. public static explicit operator Color (ColorName colorName) => new (colorName);
  561. /// <summary>
  562. /// Cast to <see cref="Gui.ColorName"/>. May fail if the color is not a named color.
  563. /// </summary>
  564. /// <param name="color"></param>
  565. public static explicit operator ColorName (Color color) => color.ColorName;
  566. /// <summary>
  567. /// Equality operator for two <see cref="Color"/> objects..
  568. /// </summary>
  569. /// <param name="left"></param>
  570. /// <param name="right"></param>
  571. /// <returns></returns>
  572. public static bool operator == (Color left, Color right) => left.Equals (right);
  573. /// <summary>
  574. /// Inequality operator for two <see cref="Color"/> objects.
  575. /// </summary>
  576. /// <param name="left"></param>
  577. /// <param name="right"></param>
  578. /// <returns></returns>
  579. public static bool operator != (Color left, Color right) => !left.Equals (right);
  580. /// <summary>
  581. /// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  582. /// </summary>
  583. /// <param name="left"></param>
  584. /// <param name="right"></param>
  585. /// <returns></returns>
  586. public static bool operator == (ColorName left, Color right) => left == right.ColorName;
  587. /// <summary>
  588. /// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  589. /// </summary>
  590. /// <param name="left"></param>
  591. /// <param name="right"></param>
  592. /// <returns></returns>
  593. public static bool operator != (ColorName left, Color right) => left != right.ColorName;
  594. /// <summary>
  595. /// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  596. /// </summary>
  597. /// <param name="left"></param>
  598. /// <param name="right"></param>
  599. /// <returns></returns>
  600. public static bool operator == (Color left, ColorName right) => left.ColorName == right;
  601. /// <summary>
  602. /// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  603. /// </summary>
  604. /// <param name="left"></param>
  605. /// <param name="right"></param>
  606. /// <returns></returns>
  607. public static bool operator != (Color left, ColorName right) => left.ColorName != right;
  608. /// <inheritdoc/>
  609. public override bool Equals (object obj) => obj is Color other && Equals (other);
  610. /// <inheritdoc/>
  611. public bool Equals (Color other) => R == other.R &&
  612. G == other.G &&
  613. B == other.B &&
  614. A == other.A;
  615. /// <inheritdoc/>
  616. public override int GetHashCode () => HashCode.Combine (R, G, B, A);
  617. #endregion
  618. }
  619. /// <summary>
  620. /// Attributes represent how text is styled when displayed in the terminal.
  621. /// </summary>
  622. /// <remarks>
  623. /// <see cref="Attribute"/> provides a platform independent representation of colors (and someday other forms of text
  624. /// styling).
  625. /// They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
  626. /// class to define color schemes that can be used in an application.
  627. /// </remarks>
  628. [JsonConverter (typeof (AttributeJsonConverter))]
  629. public readonly struct Attribute : IEquatable<Attribute> {
  630. /// <summary>
  631. /// Default empty attribute.
  632. /// </summary>
  633. public static readonly Attribute Default = new (Color.White, Color.Black);
  634. /// <summary>
  635. /// The <see cref="ConsoleDriver"/>-specific color value.
  636. /// </summary>
  637. [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
  638. internal int PlatformColor { get; }
  639. /// <summary>
  640. /// The foreground color.
  641. /// </summary>
  642. [JsonConverter (typeof (ColorJsonConverter))]
  643. public Color Foreground { get; }
  644. /// <summary>
  645. /// The background color.
  646. /// </summary>
  647. [JsonConverter (typeof (ColorJsonConverter))]
  648. public Color Background { get; }
  649. /// <summary>
  650. /// Initializes a new instance with default values.
  651. /// </summary>
  652. public Attribute ()
  653. {
  654. PlatformColor = -1;
  655. Foreground = new Color (Default.Foreground.ColorName);
  656. Background = new Color (Default.Background.ColorName);
  657. }
  658. /// <summary>
  659. /// Initializes a new instance from an existing instance.
  660. /// </summary>
  661. public Attribute (Attribute attr)
  662. {
  663. PlatformColor = -1;
  664. Foreground = new Color (attr.Foreground.ColorName);
  665. Background = new Color (attr.Background.ColorName);
  666. }
  667. /// <summary>
  668. /// Initializes a new instance with platform specific color value.
  669. /// </summary>
  670. /// <param name="platformColor">Value.</param>
  671. internal Attribute (int platformColor)
  672. {
  673. PlatformColor = platformColor;
  674. Foreground = new Color (Default.Foreground.ColorName);
  675. Background = new Color (Default.Background.ColorName);
  676. }
  677. /// <summary>
  678. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  679. /// </summary>
  680. /// <param name="platformColor">platform-dependent color value.</param>
  681. /// <param name="foreground">Foreground</param>
  682. /// <param name="background">Background</param>
  683. internal Attribute (int platformColor, Color foreground, Color background)
  684. {
  685. Foreground = foreground;
  686. Background = background;
  687. PlatformColor = platformColor;
  688. }
  689. /// <summary>
  690. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  691. /// </summary>
  692. /// <param name="platformColor">platform-dependent color value.</param>
  693. /// <param name="foreground">Foreground</param>
  694. /// <param name="background">Background</param>
  695. internal Attribute (int platformColor, ColorName foreground, ColorName background) : this (platformColor, new Color (foreground), new Color (background)) { }
  696. /// <summary>
  697. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  698. /// </summary>
  699. /// <param name="foreground">Foreground</param>
  700. /// <param name="background">Background</param>
  701. public Attribute (Color foreground, Color background)
  702. {
  703. Foreground = foreground;
  704. Background = background;
  705. // TODO: Once CursesDriver supports truecolor all the PlatformColor stuff goes away
  706. if (Application.Driver == null) {
  707. PlatformColor = -1;
  708. return;
  709. }
  710. var make = Application.Driver.MakeColor (foreground, background);
  711. PlatformColor = make.PlatformColor;
  712. }
  713. /// <summary>
  714. /// Initializes a new instance with a <see cref="ColorName"/> value. Both <see cref="Foreground"/> and
  715. /// <see cref="Background"/> will be set to the specified color.
  716. /// </summary>
  717. /// <param name="colorName">Value.</param>
  718. internal Attribute (ColorName colorName) : this (colorName, colorName) { }
  719. /// <summary>
  720. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  721. /// </summary>
  722. /// <param name="foregroundName">Foreground</param>
  723. /// <param name="backgroundName">Background</param>
  724. public Attribute (ColorName foregroundName, ColorName backgroundName) : this (new Color (foregroundName), new Color (backgroundName)) { }
  725. /// <summary>
  726. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  727. /// </summary>
  728. /// <param name="foregroundName">Foreground</param>
  729. /// <param name="background">Background</param>
  730. public Attribute (ColorName foregroundName, Color background) : this (new Color (foregroundName), background) { }
  731. /// <summary>
  732. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  733. /// </summary>
  734. /// <param name="foreground">Foreground</param>
  735. /// <param name="backgroundName">Background</param>
  736. public Attribute (Color foreground, ColorName backgroundName) : this (foreground, new Color (backgroundName)) { }
  737. /// <summary>
  738. /// Initializes a new instance of the <see cref="Attribute"/> struct
  739. /// with the same colors for the foreground and background.
  740. /// </summary>
  741. /// <param name="color">The color.</param>
  742. public Attribute (Color color) : this (color, color) { }
  743. /// <summary>
  744. /// Compares two attributes for equality.
  745. /// </summary>
  746. /// <param name="left"></param>
  747. /// <param name="right"></param>
  748. /// <returns></returns>
  749. public static bool operator == (Attribute left, Attribute right) => left.Equals (right);
  750. /// <summary>
  751. /// Compares two attributes for inequality.
  752. /// </summary>
  753. /// <param name="left"></param>
  754. /// <param name="right"></param>
  755. /// <returns></returns>
  756. public static bool operator != (Attribute left, Attribute right) => !(left == right);
  757. /// <inheritdoc/>
  758. public override bool Equals (object obj) => obj is Attribute other && Equals (other);
  759. /// <inheritdoc/>
  760. public bool Equals (Attribute other) => PlatformColor == other.PlatformColor &&
  761. Foreground == other.Foreground &&
  762. Background == other.Background;
  763. /// <inheritdoc/>
  764. public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
  765. /// <inheritdoc/>
  766. public override string ToString () =>
  767. // Note: Unit tests are dependent on this format
  768. $"[{Foreground},{Background}]";
  769. }