Color.cs 36 KB

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