Color.cs 33 KB

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