Color.cs 37 KB

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