Scheme.cs 26 KB

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