Color.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  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.Runtime.CompilerServices;
  8. using System.Text.Json.Serialization;
  9. using System.Text.RegularExpressions;
  10. namespace Terminal.Gui;
  11. /// <summary>
  12. /// Defines the 16 legacy color names and values that can be used to set the
  13. /// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
  14. /// </summary>
  15. /// <remarks>
  16. /// <para>
  17. /// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
  18. /// </para>
  19. /// <para>
  20. /// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured 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. /// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see <see cref="ColorName"/>).
  92. /// Used with <see cref="Attribute"/>.
  93. /// </summary>
  94. [JsonConverter (typeof (ColorJsonConverter))]
  95. public class Color : IEquatable<Color> {
  96. /// <summary>
  97. /// Initializes a new instance of the <see cref="Color"/> class.
  98. /// </summary>
  99. /// <param name="red">The red 8-bits.</param>
  100. /// <param name="green">The green 8-bits.</param>
  101. /// <param name="blue">The blue 8-bits.</param>
  102. /// <param name="alpha">Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.</param>
  103. public Color (int red, int green, int blue, int alpha = 0xFF)
  104. {
  105. R = red;
  106. G = green;
  107. B = blue;
  108. A = alpha;
  109. }
  110. /// <summary>
  111. /// Initializes a new instance of the <see cref="Color"/> class with an encoded 24-bit color value.
  112. /// </summary>
  113. /// <param name="rgba">The encoded 24-bit color value (see <see cref="Rgba"/>).</param>
  114. public Color (int rgba)
  115. {
  116. Rgba = rgba;
  117. }
  118. /// <summary>
  119. /// Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color value.
  120. /// </summary>
  121. /// <param name="colorName">The 16-color value.</param>
  122. public Color (ColorName colorName)
  123. {
  124. var c = Color.FromColorName (colorName);
  125. R = c.R;
  126. G = c.G;
  127. B = c.B;
  128. A = c.A;
  129. }
  130. /// <summary>
  131. /// Initializes a new instance of the <see cref="Color"/> color from string. See <see cref="TryParse(string, out Color)"/> for details.
  132. /// </summary>
  133. /// <param name="colorString"></param>
  134. /// <exception cref="Exception"></exception>
  135. public Color (string colorString)
  136. {
  137. if (!TryParse (colorString, out var c)) {
  138. throw new ArgumentOutOfRangeException (nameof (colorString));
  139. }
  140. R = c.R;
  141. G = c.G;
  142. B = c.B;
  143. A = c.A;
  144. }
  145. /// <summary>
  146. /// Initializes a new instance of the <see cref="Color"/>.
  147. /// </summary>
  148. public Color ()
  149. {
  150. R = 0;
  151. G = 0;
  152. B = 0;
  153. A = 0xFF;
  154. }
  155. /// <summary>
  156. /// Red color component.
  157. /// </summary>
  158. public int R { get; set; }
  159. /// <summary>
  160. /// Green color component.
  161. /// </summary>
  162. public int G { get; set; }
  163. /// <summary>
  164. /// Blue color component.
  165. /// </summary>
  166. public int B { get; set; }
  167. /// <summary>
  168. /// Alpha color component.
  169. /// </summary>
  170. /// <remarks>
  171. /// The Alpha channel is not supported by Terminal.Gui.
  172. /// </remarks>
  173. public int A { get; set; } = 0xFF; // Not currently supported; here for completeness.
  174. /// <summary>
  175. /// Gets or sets the color value encoded as ARGB32.
  176. /// <code>
  177. /// (&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;
  178. /// </code>
  179. /// </summary>
  180. public int Rgba {
  181. get => (A << 24) | (R << 16) | (G << 8) | B;
  182. set {
  183. A = (byte)((value >> 24) & 0xFF);
  184. R = (byte)((value >> 16) & 0xFF);
  185. G = (byte)((value >> 8) & 0xFF);
  186. B = (byte)(value & 0xFF);
  187. }
  188. }
  189. // TODO: Make this map configurable via ConfigurationManager
  190. // TODO: This does not need to be a Dictionary, but can be an 16 element array.
  191. /// <summary>
  192. /// Maps legacy 16-color values to the corresponding 24-bit RGB value.
  193. /// </summary>
  194. internal static ImmutableDictionary<Color, ColorName> _colorToNameMap = new Dictionary<Color, ColorName> () {
  195. // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
  196. // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
  197. { new Color (12, 12, 12),Gui.ColorName.Black },
  198. { new Color (0, 55, 218),Gui.ColorName.Blue },
  199. { new Color (19, 161, 14),Gui.ColorName.Green},
  200. { new Color (58, 150, 221),Gui.ColorName.Cyan},
  201. { new Color (197, 15, 31),Gui.ColorName.Red},
  202. { new Color (136, 23, 152),Gui.ColorName.Magenta},
  203. { new Color (128, 64, 32),Gui.ColorName.Yellow},
  204. { new Color (204, 204, 204),Gui.ColorName.Gray},
  205. { new Color (118, 118, 118),Gui.ColorName.DarkGray},
  206. { new Color (59, 120, 255),Gui.ColorName.BrightBlue},
  207. { new Color (22, 198, 12),Gui.ColorName.BrightGreen},
  208. { new Color (97, 214, 214),Gui.ColorName.BrightCyan},
  209. { new Color (231, 72, 86),Gui.ColorName.BrightRed},
  210. { new Color (180, 0, 158),Gui.ColorName.BrightMagenta },
  211. { new Color (249, 241, 165),Gui.ColorName.BrightYellow},
  212. { new Color (242, 242, 242),Gui.ColorName.White},
  213. }.ToImmutableDictionary ();
  214. /// <summary>
  215. /// Gets or sets the 24-bit color value for each of the legacy 16-color values.
  216. /// </summary>
  217. [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
  218. public static Dictionary<ColorName, string> Colors {
  219. get {
  220. // Transform _colorToNameMap into a Dictionary<ColorNames,string>
  221. return _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}");
  222. }
  223. set {
  224. // Transform Dictionary<ColorNames,string> into _colorToNameMap
  225. var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => {
  226. if (Enum.TryParse<ColorName> (kvp.Key.ToString (), ignoreCase: true, out ColorName colorName)) {
  227. return colorName;
  228. }
  229. throw new ArgumentException ($"Invalid color name: {kvp.Key}");
  230. });
  231. _colorToNameMap = newMap.ToImmutableDictionary ();
  232. }
  233. }
  234. /// <summary>
  235. /// Converts a legacy <see cref="Gui.ColorName"/> to a 24-bit <see cref="Color"/>.
  236. /// </summary>
  237. /// <param name="consoleColor">The <see cref="Color"/> to convert.</param>
  238. /// <returns></returns>
  239. private static Color FromColorName (ColorName consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key;
  240. // Iterates through the entries in the _colorNames dictionary, calculates the
  241. // Euclidean distance between the input color and each dictionary color in RGB space,
  242. // and keeps track of the closest entry found so far. The function returns a KeyValuePair
  243. // representing the closest color entry and its associated color name.
  244. internal static ColorName FindClosestColor (Color inputColor)
  245. {
  246. ColorName closestColor = Gui.ColorName.Black; // Default to Black
  247. double closestDistance = double.MaxValue;
  248. foreach (var colorEntry in _colorToNameMap) {
  249. var distance = CalculateColorDistance (inputColor, colorEntry.Key);
  250. if (distance < closestDistance) {
  251. closestDistance = distance;
  252. closestColor = colorEntry.Value;
  253. }
  254. }
  255. return closestColor;
  256. }
  257. private static double CalculateColorDistance (Color color1, Color color2)
  258. {
  259. // Calculate the Euclidean distance between two colors
  260. var deltaR = (double)color1.R - (double)color2.R;
  261. var deltaG = (double)color1.G - (double)color2.G;
  262. var deltaB = (double)color1.B - (double)color2.B;
  263. return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
  264. }
  265. /// <summary>
  266. /// Gets or sets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value.
  267. /// <see langword="get"/> will return the closest 16 color match to the true color when no exact value is found.
  268. /// </summary>
  269. /// <remarks>
  270. /// Get returns the <see cref="ColorName"/> of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
  271. /// </remarks>
  272. public ColorName ColorName {
  273. get => FindClosestColor (this);
  274. set {
  275. var c = FromColorName (value);
  276. R = c.R;
  277. G = c.G;
  278. B = c.B;
  279. A = c.A;
  280. }
  281. }
  282. #region Legacy Color Names
  283. /// <summary>
  284. /// The black color.
  285. /// </summary>
  286. public const ColorName Black = ColorName.Black;
  287. /// <summary>
  288. /// The blue color.
  289. /// </summary>
  290. public const ColorName Blue = ColorName.Blue;
  291. /// <summary>
  292. /// The green color.
  293. /// </summary>
  294. public const ColorName Green = ColorName.Green;
  295. /// <summary>
  296. /// The cyan color.
  297. /// </summary>
  298. public const ColorName Cyan = ColorName.Cyan;
  299. /// <summary>
  300. /// The red color.
  301. /// </summary>
  302. public const ColorName Red = ColorName.Red;
  303. /// <summary>
  304. /// The magenta color.
  305. /// </summary>
  306. public const ColorName Magenta = ColorName.Magenta;
  307. /// <summary>
  308. /// The yellow color.
  309. /// </summary>
  310. public const ColorName Yellow = ColorName.Yellow;
  311. /// <summary>
  312. /// The gray color.
  313. /// </summary>
  314. public const ColorName Gray = ColorName.Gray;
  315. /// <summary>
  316. /// The dark gray color.
  317. /// </summary>
  318. public const ColorName DarkGray = ColorName.DarkGray;
  319. /// <summary>
  320. /// The bright bBlue color.
  321. /// </summary>
  322. public const ColorName BrightBlue = ColorName.BrightBlue;
  323. /// <summary>
  324. /// The bright green color.
  325. /// </summary>
  326. public const ColorName BrightGreen = ColorName.BrightGreen;
  327. /// <summary>
  328. /// The bright cyan color.
  329. /// </summary>
  330. public const ColorName BrightCyan = ColorName.BrightCyan;
  331. /// <summary>
  332. /// The bright red color.
  333. /// </summary>
  334. public const ColorName BrightRed = ColorName.BrightRed;
  335. /// <summary>
  336. /// The bright magenta color.
  337. /// </summary>
  338. public const ColorName BrightMagenta = ColorName.BrightMagenta;
  339. /// <summary>
  340. /// The bright yellow color.
  341. /// </summary>
  342. public const ColorName BrightYellow = ColorName.BrightYellow;
  343. /// <summary>
  344. /// The White color.
  345. /// </summary>
  346. public const ColorName White = ColorName.White;
  347. #endregion
  348. /// <summary>
  349. /// Converts the provided string to a new <see cref="Color"/> instance.
  350. /// </summary>
  351. /// <param name="text">The text to analyze. Formats supported are
  352. /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
  353. /// <see cref="Gui.ColorName"/>.</param>
  354. /// <param name="color">The parsed value.</param>
  355. /// <returns>A boolean value indicating whether parsing was successful.</returns>
  356. /// <remarks>
  357. /// While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.
  358. /// </remarks>
  359. public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
  360. {
  361. // empty color
  362. if ((text == null) || (text.Length == 0)) {
  363. color = null;
  364. return false;
  365. }
  366. // #RRGGBB, #RGB
  367. if ((text [0] == '#') && text.Length is 7 or 4) {
  368. if (text.Length == 7) {
  369. var r = Convert.ToInt32 (text.Substring (1, 2), 16);
  370. var g = Convert.ToInt32 (text.Substring (3, 2), 16);
  371. var b = Convert.ToInt32 (text.Substring (5, 2), 16);
  372. color = new Color (r, g, b);
  373. } else {
  374. var rText = char.ToString (text [1]);
  375. var gText = char.ToString (text [2]);
  376. var bText = char.ToString (text [3]);
  377. var r = Convert.ToInt32 (rText + rText, 16);
  378. var g = Convert.ToInt32 (gText + gText, 16);
  379. var b = Convert.ToInt32 (bText + bText, 16);
  380. color = new Color (r, g, b);
  381. }
  382. return true;
  383. }
  384. // #RRGGBB, #RGBA
  385. if ((text [0] == '#') && text.Length is 8 or 5) {
  386. if (text.Length == 7) {
  387. var r = Convert.ToInt32 (text.Substring (1, 2), 16);
  388. var g = Convert.ToInt32 (text.Substring (3, 2), 16);
  389. var b = Convert.ToInt32 (text.Substring (5, 2), 16);
  390. var a = Convert.ToInt32 (text.Substring (7, 2), 16);
  391. color = new Color (a, r, g, b);
  392. } else {
  393. var rText = char.ToString (text [1]);
  394. var gText = char.ToString (text [2]);
  395. var bText = char.ToString (text [3]);
  396. var aText = char.ToString (text [4]);
  397. var r = Convert.ToInt32 (aText + aText, 16);
  398. var g = Convert.ToInt32 (rText + rText, 16);
  399. var b = Convert.ToInt32 (gText + gText, 16);
  400. var a = Convert.ToInt32 (bText + bText, 16);
  401. color = new Color (r, g, b, a);
  402. }
  403. return true;
  404. }
  405. // rgb(r,g,b)
  406. var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
  407. if (match.Success) {
  408. var r = int.Parse (match.Groups [1].Value);
  409. var g = int.Parse (match.Groups [2].Value);
  410. var b = int.Parse (match.Groups [3].Value);
  411. color = new Color (r, g, b);
  412. return true;
  413. }
  414. // rgb(r,g,b,a)
  415. match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)");
  416. if (match.Success) {
  417. var r = int.Parse (match.Groups [1].Value);
  418. var g = int.Parse (match.Groups [2].Value);
  419. var b = int.Parse (match.Groups [3].Value);
  420. var a = int.Parse (match.Groups [4].Value);
  421. color = new Color (r, g, b, a);
  422. return true;
  423. }
  424. if (Enum.TryParse<ColorName> (text, ignoreCase: true, out ColorName colorName)) {
  425. color = new Color (colorName);
  426. return true;
  427. }
  428. color = null;
  429. return false;
  430. }
  431. #region Operators
  432. /// <summary>
  433. /// Cast from int.
  434. /// </summary>
  435. /// <param name="rgba"></param>
  436. public static implicit operator Color (int rgba)
  437. {
  438. return new Color (rgba);
  439. }
  440. /// <summary>
  441. /// Cast to int.
  442. /// </summary>
  443. /// <param name="color"></param>
  444. public static explicit operator int (Color color)
  445. {
  446. return color.Rgba;
  447. }
  448. /// <summary>
  449. /// Cast from <see cref="Gui.ColorName"/>.
  450. /// </summary>
  451. /// <param name="colorName"></param>
  452. public static explicit operator Color (ColorName colorName)
  453. {
  454. return new Color (colorName);
  455. }
  456. /// <summary>
  457. /// Cast to <see cref="Gui.ColorName"/>.
  458. /// </summary>
  459. /// <param name="color"></param>
  460. public static explicit operator ColorName (Color color)
  461. {
  462. return color.ColorName;
  463. }
  464. /// <summary>
  465. /// Equality operator for two <see cref="Color"/> objects..
  466. /// </summary>
  467. /// <param name="left"></param>
  468. /// <param name="right"></param>
  469. /// <returns></returns>
  470. public static bool operator == (Color left, Color right)
  471. {
  472. if (left is null && right is null)
  473. return true;
  474. if (left is null || right is null)
  475. return false;
  476. return left.Equals (right);
  477. }
  478. /// <summary>
  479. /// Inequality operator for two <see cref="Color"/> objects.
  480. /// </summary>
  481. /// <param name="left"></param>
  482. /// <param name="right"></param>
  483. /// <returns></returns>
  484. public static bool operator != (Color left, Color right)
  485. {
  486. if (left is null && right is null)
  487. return false;
  488. if (left is null || right is null)
  489. return true;
  490. return !left.Equals (right);
  491. }
  492. /// <summary>
  493. /// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  494. /// </summary>
  495. /// <param name="left"></param>
  496. /// <param name="right"></param>
  497. /// <returns></returns>
  498. public static bool operator == (ColorName left, Color right)
  499. {
  500. return left == right.ColorName;
  501. }
  502. /// <summary>
  503. /// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  504. /// </summary>
  505. /// <param name="left"></param>
  506. /// <param name="right"></param>
  507. /// <returns></returns>
  508. public static bool operator != (ColorName left, Color right)
  509. {
  510. return left != right.ColorName;
  511. }
  512. /// <summary>
  513. /// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  514. /// </summary>
  515. /// <param name="left"></param>
  516. /// <param name="right"></param>
  517. /// <returns></returns>
  518. public static bool operator == (Color left, ColorName right)
  519. {
  520. return left.ColorName == right;
  521. }
  522. /// <summary>
  523. /// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
  524. /// </summary>
  525. /// <param name="left"></param>
  526. /// <param name="right"></param>
  527. /// <returns></returns>
  528. public static bool operator != (Color left, ColorName right)
  529. {
  530. return left.ColorName != right;
  531. }
  532. /// <inheritdoc/>
  533. public override bool Equals (object obj)
  534. {
  535. return obj is Color other && Equals (other);
  536. }
  537. /// <inheritdoc/>
  538. public bool Equals (Color other)
  539. {
  540. return
  541. R == other.R &&
  542. G == other.G &&
  543. B == other.B &&
  544. A == other.A;
  545. }
  546. /// <inheritdoc/>
  547. public override int GetHashCode ()
  548. {
  549. return HashCode.Combine (R, G, B, A);
  550. }
  551. #endregion
  552. /// <summary>
  553. /// Converts the color to a string representation.
  554. /// </summary>
  555. /// <remarks>
  556. /// <para>
  557. /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
  558. /// </para>
  559. /// <para>
  560. /// <see cref="A"/> (Alpha channel) is ignored and the returned string will not include it.
  561. /// </para>
  562. /// </remarks>
  563. /// <returns></returns>
  564. public override string ToString ()
  565. {
  566. // If Values has an exact match with a named color (in _colorNames), use that.
  567. if (_colorToNameMap.TryGetValue (this, out ColorName colorName)) {
  568. return Enum.GetName (typeof (ColorName), colorName);
  569. }
  570. // Otherwise return as an RGB hex value.
  571. return $"#{R:X2}{G:X2}{B:X2}";
  572. }
  573. }
  574. /// <summary>
  575. /// Attributes represent how text is styled when displayed in the terminal.
  576. /// </summary>
  577. /// <remarks>
  578. /// <see cref="Attribute"/> provides a platform independent representation of colors (and someday other forms of text styling).
  579. /// They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
  580. /// class to define color schemes that can be used in an application.
  581. /// </remarks>
  582. [JsonConverter (typeof (AttributeJsonConverter))]
  583. public readonly struct Attribute : IEquatable<Attribute> {
  584. /// <summary>
  585. /// Default empty attribute.
  586. /// </summary>
  587. public static readonly Attribute Default = new Attribute (Color.White, Color.Black);
  588. /// <summary>
  589. /// The <see cref="ConsoleDriver"/>-specific color value.
  590. /// </summary>
  591. [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
  592. internal int PlatformColor { get; }
  593. /// <summary>
  594. /// The foreground color.
  595. /// </summary>
  596. [JsonConverter (typeof (ColorJsonConverter))]
  597. public Color Foreground { get; private init; }
  598. /// <summary>
  599. /// The background color.
  600. /// </summary>
  601. [JsonConverter (typeof (ColorJsonConverter))]
  602. public Color Background { get; private init; }
  603. /// <summary>
  604. /// Initializes a new instance with default values.
  605. /// </summary>
  606. public Attribute ()
  607. {
  608. var d = Default;
  609. PlatformColor = -1;
  610. Foreground = d.Foreground;
  611. Background = d.Background;
  612. }
  613. /// <summary>
  614. /// Initializes a new instance with platform specific color value.
  615. /// </summary>
  616. /// <param name="platformColor">Value.</param>
  617. internal Attribute (int platformColor) : this (platformColor, Default.Foreground, Default.Background) { }
  618. /// <summary>
  619. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  620. /// </summary>
  621. /// <param name="platformColor">platform-dependent color value.</param>
  622. /// <param name="foreground">Foreground</param>
  623. /// <param name="background">Background</param>
  624. internal Attribute (int platformColor, Color foreground, Color background)
  625. {
  626. Foreground = foreground;
  627. Background = background;
  628. PlatformColor = platformColor;
  629. }
  630. /// <summary>
  631. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  632. /// </summary>
  633. /// <param name="platformColor">platform-dependent color value.</param>
  634. /// <param name="foreground">Foreground</param>
  635. /// <param name="background">Background</param>
  636. internal Attribute (int platformColor, ColorName foreground, ColorName background) : this (platformColor, new Color (foreground), new Color (background)) { }
  637. /// <summary>
  638. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  639. /// </summary>
  640. /// <param name="foreground">Foreground</param>
  641. /// <param name="background">Background</param>
  642. public Attribute (Color foreground, Color background)
  643. {
  644. Foreground = foreground;
  645. Background = background;
  646. // TODO: Once CursesDriver supports truecolor all the PlatformColor stuff goes away
  647. if (Application.Driver == null) {
  648. PlatformColor = -1;
  649. return;
  650. }
  651. var make = Application.Driver.MakeColor (foreground, background);
  652. PlatformColor = make.PlatformColor;
  653. }
  654. /// <summary>
  655. /// Initializes a new instance with a <see cref="ColorName"/> value. Both <see cref="Foreground"/> and
  656. /// <see cref="Background"/> will be set to the specified color.
  657. /// </summary>
  658. /// <param name="colorName">Value.</param>
  659. internal Attribute (ColorName colorName) : this (colorName, colorName) { }
  660. /// <summary>
  661. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  662. /// </summary>
  663. /// <param name="foregroundName">Foreground</param>
  664. /// <param name="backgroundName">Background</param>
  665. public Attribute (ColorName foregroundName, ColorName backgroundName) : this (new Color (foregroundName), new Color (backgroundName)) { }
  666. /// <summary>
  667. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  668. /// </summary>
  669. /// <param name="foregroundName">Foreground</param>
  670. /// <param name="background">Background</param>
  671. public Attribute (ColorName foregroundName, Color background) : this (new Color (foregroundName), background) { }
  672. /// <summary>
  673. /// Initializes a new instance of the <see cref="Attribute"/> struct.
  674. /// </summary>
  675. /// <param name="foreground">Foreground</param>
  676. /// <param name="backgroundName">Background</param>
  677. public Attribute (Color foreground, ColorName backgroundName) : this (foreground, new Color (backgroundName)) { }
  678. /// <summary>
  679. /// Initializes a new instance of the <see cref="Attribute"/> struct
  680. /// with the same colors for the foreground and background.
  681. /// </summary>
  682. /// <param name="color">The color.</param>
  683. public Attribute (Color color) : this (color, color) { }
  684. /// <summary>
  685. /// Compares two attributes for equality.
  686. /// </summary>
  687. /// <param name="left"></param>
  688. /// <param name="right"></param>
  689. /// <returns></returns>
  690. public static bool operator == (Attribute left, Attribute right) => left.Equals (right);
  691. /// <summary>
  692. /// Compares two attributes for inequality.
  693. /// </summary>
  694. /// <param name="left"></param>
  695. /// <param name="right"></param>
  696. /// <returns></returns>
  697. public static bool operator != (Attribute left, Attribute right) => !(left == right);
  698. /// <inheritdoc />
  699. public override bool Equals (object obj)
  700. {
  701. return obj is Attribute other && Equals (other);
  702. }
  703. /// <inheritdoc />
  704. public bool Equals (Attribute other)
  705. {
  706. return PlatformColor == other.PlatformColor &&
  707. Foreground == other.Foreground &&
  708. Background == other.Background;
  709. }
  710. /// <inheritdoc />
  711. public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
  712. /// <inheritdoc />
  713. public override string ToString ()
  714. {
  715. // Note, Unit tests are dependent on this format
  716. return $"{Foreground},{Background}";
  717. }
  718. }
  719. /// <summary>
  720. /// Defines the <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>.
  721. /// Containers such as <see cref="Window"/> and <see cref="FrameView"/> use <see cref="ColorScheme"/> to determine
  722. /// the colors used by sub-views.
  723. /// </summary>
  724. /// <remarks>
  725. /// See also: <see cref="Colors.ColorSchemes"/>.
  726. /// </remarks>
  727. [JsonConverter (typeof (ColorSchemeJsonConverter))]
  728. public class ColorScheme : IEquatable<ColorScheme> {
  729. Attribute _normal = Attribute.Default;
  730. Attribute _focus = Attribute.Default;
  731. Attribute _hotNormal = Attribute.Default;
  732. Attribute _hotFocus = Attribute.Default;
  733. Attribute _disabled = Attribute.Default;
  734. /// <summary>
  735. /// Used by <see cref="Colors.SetColorScheme(ColorScheme, string)"/> and <see cref="Colors.GetColorScheme(string)"/> to track which ColorScheme
  736. /// is being accessed.
  737. /// </summary>
  738. internal string _schemeBeingSet = "";
  739. /// <summary>
  740. /// Creates a new instance.
  741. /// </summary>
  742. public ColorScheme () : this (Attribute.Default) { }
  743. /// <summary>
  744. /// Creates a new instance, initialized with the values from <paramref name="scheme"/>.
  745. /// </summary>
  746. /// <param name="scheme">The scheme to initialize the new instance with.</param>
  747. public ColorScheme (ColorScheme scheme) : base ()
  748. {
  749. if (scheme != null) {
  750. _normal = scheme.Normal;
  751. _focus = scheme.Focus;
  752. _hotNormal = scheme.HotNormal;
  753. _disabled = scheme.Disabled;
  754. _hotFocus = scheme.HotFocus;
  755. }
  756. }
  757. /// <summary>
  758. /// Creates a new instance, initialized with the values from <paramref name="attribute"/>.
  759. /// </summary>
  760. /// <param name="attribute">The attribute to initialize the new instance with.</param>
  761. public ColorScheme (Attribute attribute)
  762. {
  763. _normal = attribute;
  764. _focus = attribute;
  765. _hotNormal = attribute;
  766. _disabled = attribute;
  767. _hotFocus = attribute;
  768. }
  769. /// <summary>
  770. /// The foreground and background color for text when the view is not focused, hot, or disabled.
  771. /// </summary>
  772. public Attribute Normal {
  773. get => _normal;
  774. set => _normal = value;
  775. }
  776. /// <summary>
  777. /// The foreground and background color for text when the view has the focus.
  778. /// </summary>
  779. public Attribute Focus {
  780. get => _focus;
  781. set => _focus = value;
  782. }
  783. /// <summary>
  784. /// The foreground and background color for text when the view is highlighted (hot).
  785. /// </summary>
  786. public Attribute HotNormal {
  787. get => _hotNormal;
  788. set => _hotNormal = value;
  789. }
  790. /// <summary>
  791. /// The foreground and background color for text when the view is highlighted (hot) and has focus.
  792. /// </summary>
  793. public Attribute HotFocus {
  794. get => _hotFocus;
  795. set => _hotFocus = value;
  796. }
  797. /// <summary>
  798. /// The default foreground and background color for text, when the view is disabled.
  799. /// </summary>
  800. public Attribute Disabled {
  801. get => _disabled;
  802. set => _disabled = value;
  803. }
  804. /// <summary>
  805. /// Compares two <see cref="ColorScheme"/> objects for equality.
  806. /// </summary>
  807. /// <param name="obj"></param>
  808. /// <returns>true if the two objects are equal</returns>
  809. public override bool Equals (object obj)
  810. {
  811. return Equals (obj as ColorScheme);
  812. }
  813. /// <summary>
  814. /// Compares two <see cref="ColorScheme"/> objects for equality.
  815. /// </summary>
  816. /// <param name="other"></param>
  817. /// <returns>true if the two objects are equal</returns>
  818. public bool Equals (ColorScheme other)
  819. {
  820. return other != null &&
  821. EqualityComparer<Attribute>.Default.Equals (_normal, other._normal) &&
  822. EqualityComparer<Attribute>.Default.Equals (_focus, other._focus) &&
  823. EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal) &&
  824. EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus) &&
  825. EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
  826. }
  827. /// <summary>
  828. /// Returns a hashcode for this instance.
  829. /// </summary>
  830. /// <returns>hashcode for this instance</returns>
  831. public override int GetHashCode ()
  832. {
  833. int hashCode = -1242460230;
  834. hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
  835. hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
  836. hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
  837. hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
  838. hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
  839. return hashCode;
  840. }
  841. /// <summary>
  842. /// Compares two <see cref="ColorScheme"/> objects for equality.
  843. /// </summary>
  844. /// <param name="left"></param>
  845. /// <param name="right"></param>
  846. /// <returns><c>true</c> if the two objects are equivalent</returns>
  847. public static bool operator == (ColorScheme left, ColorScheme right)
  848. {
  849. return EqualityComparer<ColorScheme>.Default.Equals (left, right);
  850. }
  851. /// <summary>
  852. /// Compares two <see cref="ColorScheme"/> objects for inequality.
  853. /// </summary>
  854. /// <param name="left"></param>
  855. /// <param name="right"></param>
  856. /// <returns><c>true</c> if the two objects are not equivalent</returns>
  857. public static bool operator != (ColorScheme left, ColorScheme right)
  858. {
  859. return !(left == right);
  860. }
  861. }
  862. /// <summary>
  863. /// The default <see cref="ColorScheme"/>s for the application.
  864. /// </summary>
  865. /// <remarks>
  866. /// This property can be set in a Theme to change the default <see cref="Colors"/> for the application.
  867. /// </remarks>
  868. public static class Colors {
  869. private class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
  870. public bool Equals (string x, string y)
  871. {
  872. if (x != null && y != null) {
  873. return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
  874. }
  875. return false;
  876. }
  877. public int GetHashCode (string obj)
  878. {
  879. return obj.ToLowerInvariant ().GetHashCode ();
  880. }
  881. }
  882. static Colors ()
  883. {
  884. ColorSchemes = Create ();
  885. }
  886. /// <summary>
  887. /// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
  888. /// </summary>
  889. public static Dictionary<string, ColorScheme> Create ()
  890. {
  891. // Use reflection to dynamically create the default set of ColorSchemes from the list defined
  892. // by the class.
  893. return typeof (Colors).GetProperties ()
  894. .Where (p => p.PropertyType == typeof (ColorScheme))
  895. .Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
  896. .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ());
  897. }
  898. /// <summary>
  899. /// The application Toplevel color scheme, for the default Toplevel views.
  900. /// </summary>
  901. /// <remarks>
  902. /// <para>
  903. /// This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["TopLevel"];</c>
  904. /// </para>
  905. /// </remarks>
  906. public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
  907. /// <summary>
  908. /// The base color scheme, for the default Toplevel views.
  909. /// </summary>
  910. /// <remarks>
  911. /// <para>
  912. /// This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Base"];</c>
  913. /// </para>
  914. /// </remarks>
  915. public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
  916. /// <summary>
  917. /// The dialog color scheme, for standard popup dialog boxes
  918. /// </summary>
  919. /// <remarks>
  920. /// <para>
  921. /// This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Dialog"];</c>
  922. /// </para>
  923. /// </remarks>
  924. public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
  925. /// <summary>
  926. /// The menu bar color
  927. /// </summary>
  928. /// <remarks>
  929. /// <para>
  930. /// This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Menu"];</c>
  931. /// </para>
  932. /// </remarks>
  933. public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); }
  934. /// <summary>
  935. /// The color scheme for showing errors.
  936. /// </summary>
  937. /// <remarks>
  938. /// <para>
  939. /// This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Error"];</c>
  940. /// </para>
  941. /// </remarks>
  942. public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
  943. static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null)
  944. {
  945. return ColorSchemes [schemeBeingSet];
  946. }
  947. static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
  948. {
  949. ColorSchemes [schemeBeingSet] = colorScheme;
  950. colorScheme._schemeBeingSet = schemeBeingSet;
  951. }
  952. /// <summary>
  953. /// Provides the defined <see cref="ColorScheme"/>s.
  954. /// </summary>
  955. [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
  956. [JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
  957. public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
  958. }