Scheme.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. using System.Collections.Immutable;
  2. using System.Numerics;
  3. using System.Text.Json.Serialization;
  4. namespace Terminal.Gui.Drawing;
  5. /// <summary>
  6. /// Represents a theme definition that maps each <see cref="VisualRole"/> (such as <see cref="VisualRole.Focus"/>,
  7. /// <see cref="VisualRole.Disabled"/>, etc.)
  8. /// to an <see cref="Attribute"/> describing its foreground color, background color, and text style.
  9. /// <para>
  10. /// A <see cref="Scheme"/> enables consistent, semantic theming of UI elements by associating each visual state
  11. /// with a specific style.
  12. /// Each property (e.g., <see cref="Normal"/>, <see cref="Focus"/>, <see cref="Disabled"/>) is an
  13. /// <see cref="Attribute"/>.
  14. /// If a property is not explicitly set, its value is derived from other roles (typically <see cref="Normal"/>)
  15. /// using well-defined inheritance rules.
  16. /// </para>
  17. /// <para>
  18. /// <see cref="Scheme"/> objects are immutable. To update a scheme, create a new instance with the desired values.
  19. /// Use <see cref="SchemeManager"/> to manage available schemes and apply them to views.
  20. /// </para>
  21. /// <para>
  22. /// See <see href="https://gui-cs.github.io/Terminal.Gui/docs/drawing.html"/> for more information.
  23. /// </para>
  24. /// </summary>
  25. /// <remarks>
  26. /// <para>
  27. /// <b>Immutability:</b> Scheme objects are immutable. Once constructed, their properties cannot be changed. To
  28. /// modify a Scheme,
  29. /// create a new instance with the desired values (e.g., using the <see cref="Scheme(Scheme)"/> constructor).
  30. /// </para>
  31. /// <para>
  32. /// <b>Attribute Resolution Algorithm:</b>
  33. /// <br/>
  34. /// Each <see cref="Scheme"/> property corresponds to a <see cref="VisualRole"/> and is an <see cref="Attribute"/>.
  35. /// The <see cref="Normal"/> attribute must always be set.
  36. /// All other attributes are optional. If an attribute for a given <see cref="VisualRole"/> is not explicitly set,
  37. /// its value is derived using the following rules:
  38. /// <list type="number">
  39. /// <item>
  40. /// <description><b>Normal:</b> Must always be explicitly set.</description>
  41. /// </item>
  42. /// <item>
  43. /// <description>
  44. /// <b>Focus:</b> If not set, derived from <see cref="Normal"/> by swapping foreground and background
  45. /// colors.
  46. /// </description>
  47. /// </item>
  48. /// <item>
  49. /// <description>
  50. /// <b>Active:</b> If not set, derived from <see cref="Focus"/> by:
  51. /// <list type="bullet">
  52. /// <item>
  53. /// <description>
  54. /// Setting <c>Foreground</c> to <see cref="Focus"/>'s foreground with
  55. /// <c>GetHighlightColor()</c>.
  56. /// </description>
  57. /// </item>
  58. /// <item>
  59. /// <description>
  60. /// Setting <c>Background</c> to <see cref="Focus"/>'s background with
  61. /// <c>GetDimColor()</c>.
  62. /// </description>
  63. /// </item>
  64. /// <item>
  65. /// <description>Adding <see cref="TextStyle.Bold"/> to the style.</description>
  66. /// </item>
  67. /// </list>
  68. /// </description>
  69. /// </item>
  70. /// <item>
  71. /// <description>
  72. /// <b>Highlight:</b> If not set, derived from <see cref="Normal"/> by:
  73. /// <list type="bullet">
  74. /// <item>
  75. /// <description>
  76. /// Setting <c>Foreground</c> to <see cref="Normal"/>'s background with
  77. /// <c>GetHighlightColor()</c>.
  78. /// </description>
  79. /// </item>
  80. /// <item>
  81. /// <description>Setting <c>Background</c> to <see cref="Normal"/>'s background.</description>
  82. /// </item>
  83. /// <item>
  84. /// <description>
  85. /// Setting <c>Style</c> to <see cref="Editable"/>'s style with
  86. /// <see cref="TextStyle.Italic"/> added.
  87. /// </description>
  88. /// </item>
  89. /// </list>
  90. /// </description>
  91. /// </item>
  92. /// <item>
  93. /// <description>
  94. /// <b>Editable:</b> If not set, derived from <see cref="Normal"/> by:
  95. /// <list type="bullet">
  96. /// <item>
  97. /// <description>
  98. /// Setting <c>Foreground</c> to <see cref="Normal"/>'s background with
  99. /// <c>GetHighlightColor()</c>.
  100. /// </description>
  101. /// </item>
  102. /// <item>
  103. /// <description>
  104. /// Setting <c>Background</c> to <see cref="Normal"/>'s background with
  105. /// <c>GetDimColor()</c>.
  106. /// </description>
  107. /// </item>
  108. /// </list>
  109. /// </description>
  110. /// </item>
  111. /// <item>
  112. /// <description>
  113. /// <b>ReadOnly:</b> If not set, derived from <see cref="Editable"/> by adding
  114. /// <see cref="TextStyle.Faint"/> to the style.
  115. /// </description>
  116. /// </item>
  117. /// <item>
  118. /// <description>
  119. /// <b>Disabled:</b> If not set, derived from <see cref="Normal"/> by adding
  120. /// <see cref="TextStyle.Faint"/> to the style.
  121. /// </description>
  122. /// </item>
  123. /// <item>
  124. /// <description>
  125. /// <b>HotNormal:</b> If not set, derived from <see cref="Normal"/> by adding
  126. /// <see cref="TextStyle.Underline"/> to the style.
  127. /// </description>
  128. /// </item>
  129. /// <item>
  130. /// <description>
  131. /// <b>HotFocus:</b> If not set, derived from <see cref="Focus"/> by adding
  132. /// <see cref="TextStyle.Underline"/> to the style.
  133. /// </description>
  134. /// </item>
  135. /// <item>
  136. /// <description>
  137. /// <b>HotActive:</b> If not set, derived from <see cref="Active"/> by adding
  138. /// <see cref="TextStyle.Underline"/> to the style.
  139. /// </description>
  140. /// </item>
  141. /// </list>
  142. /// This algorithm ensures that every <see cref="VisualRole"/> always resolves to a valid <see cref="Attribute"/>,
  143. /// either explicitly set or derived.
  144. /// </para>
  145. /// </remarks>
  146. [JsonConverter (typeof (SchemeJsonConverter))]
  147. public record Scheme : IEqualityOperators<Scheme, Scheme, bool>
  148. {
  149. /// <summary>
  150. /// INTERNAL: Gets the hard-coded set of <see cref="Scheme"/>s. Used for generating the built-in config.json and for
  151. /// unit tests that don't depend on ConfigurationManager.
  152. /// </summary>
  153. /// <returns></returns>
  154. internal static ImmutableSortedDictionary<string, Scheme> GetHardCodedSchemes ()
  155. {
  156. return ImmutableSortedDictionary.CreateRange (
  157. StringComparer.InvariantCultureIgnoreCase,
  158. [
  159. new KeyValuePair<string, Scheme> (SchemeManager.SchemesToSchemeName (Schemes.Base)!, CreateBase ()),
  160. new (SchemeManager.SchemesToSchemeName (Schemes.Dialog)!, CreateDialog ()),
  161. new (SchemeManager.SchemesToSchemeName (Schemes.Error)!, CreateError ()),
  162. new (SchemeManager.SchemesToSchemeName (Schemes.Menu)!, CreateMenu ()),
  163. new (SchemeManager.SchemesToSchemeName (Schemes.Toplevel)!, CreateToplevel ()),
  164. ]
  165. );
  166. Scheme CreateBase ()
  167. {
  168. return new ()
  169. {
  170. Normal = new (StandardColor.LightBlue, StandardColor.RaisinBlack)
  171. };
  172. }
  173. Scheme CreateError ()
  174. {
  175. return new ()
  176. {
  177. Normal = new (StandardColor.IndianRed, StandardColor.RaisinBlack)
  178. };
  179. }
  180. Scheme CreateDialog ()
  181. {
  182. return new ()
  183. {
  184. Normal = new (StandardColor.LightSkyBlue, StandardColor.OuterSpace)
  185. };
  186. }
  187. Scheme CreateMenu ()
  188. {
  189. return new ()
  190. {
  191. Normal = new (StandardColor.Charcoal, StandardColor.LightBlue, TextStyle.Bold)
  192. };
  193. }
  194. Scheme CreateToplevel ()
  195. {
  196. return new ()
  197. {
  198. Normal = new (StandardColor.CadetBlue, StandardColor.Charcoal)
  199. };
  200. }
  201. }
  202. /// <summary>Creates a new instance set to the default attributes (see <see cref="Attribute.Default"/>).</summary>
  203. public Scheme () : this (Attribute.Default) { }
  204. /// <summary>Creates a new instance, initialized with the values from <paramref name="scheme"/>.</summary>
  205. /// <param name="scheme">The scheme to initialize the new instance with.</param>
  206. public Scheme (Scheme? scheme)
  207. {
  208. ArgumentNullException.ThrowIfNull (scheme);
  209. Normal = scheme.Normal;
  210. _hotNormal = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.HotNormal, out Attribute? hotNormal) ? hotNormal : null;
  211. _focus = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.Focus, out Attribute? focus) ? focus : null;
  212. _hotFocus = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.HotFocus, out Attribute? hotFocus) ? hotFocus : null;
  213. _active = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.Active, out Attribute? active) ? active : null;
  214. _hotActive = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.HotActive, out Attribute? hotActive) ? hotActive : null;
  215. _highlight = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.Highlight, out Attribute? highlight) ? highlight : null;
  216. _editable = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.Editable, out Attribute? editable) ? editable : null;
  217. _readOnly = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.ReadOnly, out Attribute? readOnly) ? readOnly : null;
  218. _disabled = scheme.TryGetExplicitlySetAttributeForRole (VisualRole.Disabled, out Attribute? disabled) ? disabled : null;
  219. }
  220. /// <summary>Creates a new instance, initialized with the values from <paramref name="attribute"/>.</summary>
  221. /// <param name="attribute">The attribute to initialize the new instance with.</param>
  222. public Scheme (Attribute attribute)
  223. {
  224. // Only set Normal as explicitly set
  225. Normal = attribute;
  226. }
  227. /// <summary>
  228. /// Gets the <see cref="Attribute"/> associated with a specified <see cref="VisualRole"/>,
  229. /// applying inheritance rules for attributes not explicitly set.
  230. /// </summary>
  231. /// <param name="role">The semantic <see cref="VisualRole"/> describing the element being rendered.</param>
  232. /// <returns>
  233. /// The corresponding <see cref="Attribute"/> from the <see cref="Scheme"/>, possibly derived if not explicitly
  234. /// set.
  235. /// </returns>
  236. public Attribute GetAttributeForRole (VisualRole role)
  237. {
  238. // Use a HashSet to guard against recursion cycles
  239. return GetAttributeForRoleCore (role, []);
  240. }
  241. /// <summary>
  242. /// Attempts to get the <see cref="Attribute"/> associated with a specified <see cref="VisualRole"/>. If the
  243. /// role is not explicitly set, it will return false and the out parameter will be null.
  244. /// </summary>
  245. /// <param name="role"></param>
  246. /// <param name="attribute"></param>
  247. /// <returns></returns>
  248. public bool TryGetExplicitlySetAttributeForRole (VisualRole role, out Attribute? attribute)
  249. {
  250. // Use a HashSet to guard against recursion cycles
  251. attribute = role switch
  252. {
  253. VisualRole.Normal => _normal,
  254. VisualRole.HotNormal => _hotNormal,
  255. VisualRole.Focus => _focus,
  256. VisualRole.HotFocus => _hotFocus,
  257. VisualRole.Active => _active,
  258. VisualRole.HotActive => _hotActive,
  259. VisualRole.Highlight => _highlight,
  260. VisualRole.Editable => _editable,
  261. VisualRole.ReadOnly => _readOnly,
  262. VisualRole.Disabled => _disabled,
  263. _ => null
  264. };
  265. return attribute is { };
  266. }
  267. // TODO: Provide a CWP-based API that lets devs override this algo?
  268. private Attribute GetAttributeForRoleCore (VisualRole role, HashSet<VisualRole> stack)
  269. {
  270. // Prevent infinite recursion
  271. if (!stack.Add (role))
  272. {
  273. return Normal; // fallback
  274. }
  275. Attribute? attr = Normal;
  276. if (role == VisualRole.Normal || TryGetExplicitlySetAttributeForRole (role, out attr))
  277. {
  278. return attr!.Value;
  279. }
  280. // TODO: Provide an API that lets devs override this algo?
  281. // Derivation algorithm as documented
  282. Attribute result = role switch
  283. {
  284. VisualRole.Focus =>
  285. GetAttributeForRoleCore (VisualRole.Normal, stack) with
  286. {
  287. Foreground = GetAttributeForRoleCore (VisualRole.Normal, stack).Background,
  288. Background = GetAttributeForRoleCore (VisualRole.Normal, stack).Foreground
  289. },
  290. VisualRole.Active =>
  291. GetAttributeForRoleCore (VisualRole.Focus, stack) with
  292. {
  293. Foreground = GetAttributeForRoleCore (VisualRole.Focus, stack).Foreground.GetBrighterColor (),
  294. Background = GetAttributeForRoleCore (VisualRole.Focus, stack).Background.GetDimColor (),
  295. Style = GetAttributeForRoleCore (VisualRole.Focus, stack).Style | TextStyle.Bold
  296. },
  297. VisualRole.Highlight =>
  298. GetAttributeForRoleCore (VisualRole.Normal, stack) with
  299. {
  300. Foreground = GetAttributeForRoleCore (VisualRole.Normal, stack).Background.GetBrighterColor (),
  301. Background = GetAttributeForRoleCore (VisualRole.Normal, stack).Background,
  302. Style = GetAttributeForRoleCore (VisualRole.Editable, stack).Style | TextStyle.Italic
  303. },
  304. VisualRole.Editable =>
  305. GetAttributeForRoleCore (VisualRole.Normal, stack) with
  306. {
  307. Foreground = GetAttributeForRoleCore (VisualRole.Normal, stack).Foreground,
  308. Background = GetAttributeForRoleCore (VisualRole.Normal, stack).Foreground.GetDimColor (0.5)
  309. },
  310. VisualRole.ReadOnly =>
  311. GetAttributeForRoleCore (VisualRole.Editable, stack) with
  312. {
  313. Foreground = GetAttributeForRoleCore (VisualRole.Editable, stack).Foreground.GetDimColor (0.05),
  314. },
  315. VisualRole.Disabled =>
  316. GetAttributeForRoleCore (VisualRole.Normal, stack) with
  317. {
  318. Foreground = GetAttributeForRoleCore (VisualRole.Normal, stack).Foreground.GetDimColor (0.05),
  319. },
  320. VisualRole.HotNormal =>
  321. GetAttributeForRoleCore (VisualRole.Normal, stack) with
  322. {
  323. Style = GetAttributeForRoleCore (VisualRole.Normal, stack).Style | TextStyle.Underline
  324. },
  325. VisualRole.HotFocus =>
  326. GetAttributeForRoleCore (VisualRole.Focus, stack) with
  327. {
  328. Style = GetAttributeForRoleCore (VisualRole.Focus, stack).Style | TextStyle.Underline
  329. },
  330. VisualRole.HotActive =>
  331. GetAttributeForRoleCore (VisualRole.Active, stack) with
  332. {
  333. Style = GetAttributeForRoleCore (VisualRole.Active, stack).Style | TextStyle.Underline
  334. },
  335. _ => GetAttributeForRoleCore (VisualRole.Normal, stack)
  336. };
  337. stack.Remove (role);
  338. return result;
  339. }
  340. /// <summary>
  341. /// Gets the <see cref="Attribute"/> associated with a specified <see cref="VisualRole"/> string.
  342. /// </summary>
  343. /// <param name="roleName">The name of the <see cref="VisualRole"/> describing the element being rendered.</param>
  344. /// <returns>The corresponding <see cref="Attribute"/> from the <see cref="Scheme"/>.</returns>
  345. public Attribute GetAttributeForRole (string roleName)
  346. {
  347. if (Enum.TryParse (roleName, true, out VisualRole role))
  348. {
  349. return GetAttributeForRole (role);
  350. }
  351. // If the string does not match any VisualRole, return the default Normal attribute
  352. return Normal;
  353. }
  354. // Helper method for property _get implementation
  355. private Attribute GetAttributeForRoleProperty (Attribute? explicitValue, VisualRole role)
  356. {
  357. if (explicitValue is { })
  358. {
  359. return explicitValue.Value;
  360. }
  361. return GetAttributeForRoleCore (role, []);
  362. }
  363. // Helper method for property _set implementation
  364. private Attribute? SetAttributeForRoleProperty (Attribute value, VisualRole role)
  365. {
  366. // If value is the same as the algorithm value, use null
  367. if (GetAttributeForRoleCore (role, []) == value)
  368. {
  369. return null;
  370. }
  371. return value;
  372. }
  373. private readonly Attribute? _normal;
  374. /// <summary>
  375. /// The default visual role for unfocused, unselected, enabled elements.
  376. /// The Normal attribute must always be set. All other attributes are optional, and if not explicitly
  377. /// set, will be automatically generated. See the description for <see cref="Scheme"/> for details on the
  378. /// algorithm used.
  379. /// </summary>
  380. public Attribute Normal
  381. {
  382. get => _normal!.Value;
  383. init => _normal = value;
  384. }
  385. private readonly Attribute? _hotNormal;
  386. /// <summary>
  387. /// The visual role for <see cref="Normal"/> elements with a <see cref="View.HotKey"/> indicator.
  388. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  389. /// algorithm used.
  390. /// </summary>
  391. public Attribute HotNormal
  392. {
  393. get => GetAttributeForRoleProperty (_hotNormal, VisualRole.HotNormal);
  394. init => _hotNormal = SetAttributeForRoleProperty (value, VisualRole.HotNormal);
  395. }
  396. private readonly Attribute? _focus;
  397. /// <summary>
  398. /// The visual role when the element is focused.
  399. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  400. /// algorithm used.
  401. /// </summary>
  402. public Attribute Focus
  403. {
  404. get => GetAttributeForRoleProperty (_focus, VisualRole.Focus);
  405. init => _focus = SetAttributeForRoleProperty (value, VisualRole.Focus);
  406. }
  407. private readonly Attribute? _hotFocus;
  408. /// <summary>
  409. /// The visual role for <see cref="Focus"/> elements with a <see cref="View.HotKey"/> indicator.
  410. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  411. /// algorithm used.
  412. /// </summary>
  413. public Attribute HotFocus
  414. {
  415. get => GetAttributeForRoleProperty (_hotFocus, VisualRole.HotFocus);
  416. init => _hotFocus = SetAttributeForRoleProperty (value, VisualRole.HotFocus);
  417. }
  418. private readonly Attribute? _active;
  419. /// <summary>
  420. /// The visual role for elements that are active or selected (e.g., selected item in a <see cref="ListView"/>). Also
  421. /// used
  422. /// for headers in, <see cref="HexView"/>, <see cref="CharMap"/> and <see cref="TabView"/>.
  423. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  424. /// algorithm used.
  425. /// </summary>
  426. public Attribute Active
  427. {
  428. get => GetAttributeForRoleProperty (_active, VisualRole.Active);
  429. init => _active = SetAttributeForRoleProperty (value, VisualRole.Active);
  430. }
  431. private readonly Attribute? _hotActive;
  432. /// <summary>
  433. /// The visual role for <see cref="Active"/> elements with a <see cref="View.HotKey"/> indicator.
  434. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  435. /// algorithm used.
  436. /// </summary>
  437. public Attribute HotActive
  438. {
  439. get => GetAttributeForRoleProperty (_hotActive, VisualRole.HotActive);
  440. init => _hotActive = SetAttributeForRoleProperty (value, VisualRole.HotActive);
  441. }
  442. private readonly Attribute? _highlight;
  443. /// <summary>
  444. /// The visual role for elements that are highlighted (e.g., when the mouse is inside a <see cref="Button"/>).
  445. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  446. /// algorithm used.
  447. /// </summary>
  448. public Attribute Highlight
  449. {
  450. get => GetAttributeForRoleProperty (_highlight, VisualRole.Highlight);
  451. init => _highlight = SetAttributeForRoleProperty (value, VisualRole.Highlight);
  452. }
  453. private readonly Attribute? _editable;
  454. /// <summary>
  455. /// The visual role for elements that are editable (e.g., <see cref="TextField"/> and <see cref="TextView"/>).
  456. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  457. /// algorithm used.
  458. /// </summary>
  459. public Attribute Editable
  460. {
  461. get => GetAttributeForRoleProperty (_editable, VisualRole.Editable);
  462. init => _editable = SetAttributeForRoleProperty (value, VisualRole.Editable);
  463. }
  464. private readonly Attribute? _readOnly;
  465. /// <summary>
  466. /// The visual role for elements that are normally editable but currently read-only.
  467. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  468. /// algorithm used.
  469. /// </summary>
  470. public Attribute ReadOnly
  471. {
  472. get => GetAttributeForRoleProperty (_readOnly, VisualRole.ReadOnly);
  473. init => _readOnly = SetAttributeForRoleProperty (value, VisualRole.ReadOnly);
  474. }
  475. private readonly Attribute? _disabled;
  476. /// <summary>
  477. /// The visual role for elements that are disabled and not interactable.
  478. /// If not explicitly set, will be a derived value. See the description for <see cref="Scheme"/> for details on the
  479. /// algorithm used.
  480. /// </summary>
  481. public Attribute Disabled
  482. {
  483. get => GetAttributeForRoleProperty (_disabled, VisualRole.Disabled);
  484. init => _disabled = SetAttributeForRoleProperty (value, VisualRole.Disabled);
  485. }
  486. /// <inheritdoc/>
  487. public virtual bool Equals (Scheme? other)
  488. {
  489. return other is { }
  490. && EqualityComparer<Attribute>.Default.Equals (Normal, other.Normal)
  491. && EqualityComparer<Attribute>.Default.Equals (HotNormal, other.HotNormal)
  492. && EqualityComparer<Attribute>.Default.Equals (Focus, other.Focus)
  493. && EqualityComparer<Attribute>.Default.Equals (HotFocus, other.HotFocus)
  494. && EqualityComparer<Attribute>.Default.Equals (Active, other.Active)
  495. && EqualityComparer<Attribute>.Default.Equals (HotActive, other.HotActive)
  496. && EqualityComparer<Attribute>.Default.Equals (Highlight, other.Highlight)
  497. && EqualityComparer<Attribute>.Default.Equals (Editable, other.Editable)
  498. && EqualityComparer<Attribute>.Default.Equals (ReadOnly, other.ReadOnly)
  499. && EqualityComparer<Attribute>.Default.Equals (Disabled, other.Disabled);
  500. }
  501. /// <inheritdoc/>
  502. public override int GetHashCode ()
  503. {
  504. return HashCode.Combine (
  505. HashCode.Combine (Normal, HotNormal, Focus, HotFocus, Active, HotActive, Highlight, Editable),
  506. HashCode.Combine (ReadOnly, Disabled)
  507. );
  508. }
  509. /// <inheritdoc/>
  510. public override string ToString ()
  511. {
  512. return $"Normal: {Normal}; HotNormal: {HotNormal}; Focus: {Focus}; HotFocus: {HotFocus}; "
  513. + $"Active: {Active}; HotActive: {HotActive}; Highlight: {Highlight}; Editable: {Editable}; "
  514. + $"ReadOnly: {ReadOnly}; Disabled: {Disabled}";
  515. }
  516. }