Color.cs 36 KB

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