Color.cs 33 KB

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