Color.cs 35 KB

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