Color.cs 32 KB

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