Key.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. #nullable enable
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. namespace Terminal.Gui;
  5. /// <summary>
  6. /// Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and
  7. /// raising keyboard events.
  8. /// </summary>
  9. /// <remarks>
  10. /// <para>
  11. /// This class provides a high-level abstraction with helper methods and properties for common keyboard
  12. /// operations. Use this class instead of the <see cref="Terminal.Gui.KeyCode"/> enumeration for keyboard input
  13. /// whenever possible.
  14. /// </para>
  15. /// <para></para>
  16. /// <para>
  17. /// The default value for <see cref="Key"/> is <see cref="KeyCode.Null"/> and can be tested using
  18. /// <see cref="Key.Empty"/>.
  19. /// </para>
  20. /// <para>
  21. /// <list type="table">
  22. /// <listheader>
  23. /// <term>Concept</term><description>Definition</description>
  24. /// </listheader>
  25. /// <item>
  26. /// <term>Testing Shift State</term>
  27. /// <description>
  28. /// The <c>Is</c> properties (<see cref="IsShift"/>,<see cref="IsCtrl"/>, <see cref="IsAlt"/>)
  29. /// test for shift state; whether the key press was modified by a shift key.
  30. /// </description>
  31. /// </item>
  32. /// <item>
  33. /// <term>Adding Shift State</term>
  34. /// <description>
  35. /// The <c>With</c> properties (<see cref="WithShift"/>,<see cref="WithCtrl"/>,
  36. /// <see cref="WithAlt"/>) return a copy of the Key with the shift modifier applied. This is useful for
  37. /// specifying a key that requires a shift modifier (e.g.
  38. /// <c>var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;</c>.
  39. /// </description>
  40. /// </item>
  41. /// <item>
  42. /// <term>Removing Shift State</term>
  43. /// <description>
  44. /// The <c>No</c> properties (<see cref="NoShift"/>,<see cref="NoCtrl"/>, <see cref="NoAlt"/>)
  45. /// return a copy of the Key with the shift modifier removed. This is useful for specifying a key that
  46. /// does not require a shift modifier (e.g. <c>var ControlDelete = ControlAltDelete.NoCtrl;</c>.
  47. /// </description>
  48. /// </item>
  49. /// <item>
  50. /// <term>Encoding of A..Z</term>
  51. /// <description>
  52. /// Lowercase alpha keys are encoded (in <see cref="Key.KeyCode"/>) as values between 65 and
  53. /// 90 corresponding to the un-shifted A to Z keys on a keyboard. Properties are provided for these
  54. /// (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the encoded values are the same
  55. /// as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted
  56. /// characters.
  57. /// </description>
  58. /// </item>
  59. /// <item>
  60. /// <term>Persistence as strings</term>
  61. /// <description>
  62. /// Keys are persisted as <c>"[Modifiers]+[Key]</c>. For example
  63. /// <c>new Key(Key.Delete).WithAlt.WithDel</c> is persisted as <c>"Ctrl+Alt+Delete"</c>. See
  64. /// <see cref="ToString()"/> and <see cref="TryParse(string, out Terminal.Gui.Key)"/> for more
  65. /// information.
  66. /// </description>
  67. /// </item>
  68. /// </list>
  69. /// </para>
  70. /// </remarks>
  71. public class Key : EventArgs, IEquatable<Key>
  72. {
  73. /// <summary>Constructs a new <see cref="Key"/></summary>
  74. public Key () : this (KeyCode.Null) { }
  75. /// <summary>Constructs a new <see cref="Key"/> from the provided Key value</summary>
  76. /// <param name="k">The key</param>
  77. public Key (KeyCode k) { KeyCode = k; }
  78. /// <summary>
  79. /// Copy constructor.
  80. /// </summary>
  81. /// <param name="key">The Key to copy</param>
  82. public Key (Key key)
  83. {
  84. KeyCode = key.KeyCode;
  85. Handled = key.Handled;
  86. }
  87. /// <summary>Constructs a new <see cref="Key"/> from a char.</summary>
  88. /// <remarks>
  89. /// <para>
  90. /// The key codes for the A..Z keys are encoded as values between 65 and 90 (<see cref="KeyCode.A"/> through
  91. /// <see cref="KeyCode.Z"/>). While these are the same as the ASCII values for uppercase characters, they represent
  92. /// *keys*, not characters. Therefore, this constructor will store 'A'..'Z' as <see cref="KeyCode.A"/>..
  93. /// <see cref="KeyCode.Z"/> with the <see cref="KeyCode.ShiftMask"/> set and will store `a`..`z` as
  94. /// <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/>.
  95. /// </para>
  96. /// </remarks>
  97. /// <param name="ch"></param>
  98. public Key (char ch)
  99. {
  100. switch (ch)
  101. {
  102. case >= 'A' and <= 'Z':
  103. // Upper case A..Z mean "Shift-char" so we need to add Shift
  104. KeyCode = (KeyCode)ch | KeyCode.ShiftMask;
  105. break;
  106. case >= 'a' and <= 'z':
  107. // Lower case a..z mean no shift, so we need to store as Key.A...Key.Z
  108. KeyCode = (KeyCode)(ch - 32);
  109. return;
  110. default:
  111. KeyCode = (KeyCode)ch;
  112. break;
  113. }
  114. }
  115. /// <summary>
  116. /// Constructs a new Key from a string describing the key. See
  117. /// <see cref="TryParse(string, out Terminal.Gui.Key)"/> for information on the format of the string.
  118. /// </summary>
  119. /// <param name="str">The string describing the key.</param>
  120. public Key (string str)
  121. {
  122. bool result = TryParse (str, out Key key);
  123. if (!result)
  124. {
  125. throw new ArgumentException (@$"Invalid key string: {str}", nameof (str));
  126. }
  127. KeyCode = key.KeyCode;
  128. }
  129. /// <summary>
  130. /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
  131. /// Useful for determining if a key represents is a printable character.
  132. /// </summary>
  133. /// <remarks>
  134. /// <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
  135. /// <para>
  136. /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
  137. /// <see cref="KeyCode.ShiftMask"/> is set.
  138. /// </para>
  139. /// <para>
  140. /// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
  141. /// <see langword="default"/>.
  142. /// </para>
  143. /// </remarks>
  144. public Rune AsRune => ToRune (KeyCode);
  145. /// <summary>
  146. /// Indicates if the current Key event has already been processed and the driver should stop notifying any other
  147. /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside
  148. /// the
  149. /// subscriber method.
  150. /// </summary>
  151. public bool Handled { get; set; }
  152. /// <summary>Gets a value indicating whether the Alt key was pressed (real or synthesized)</summary>
  153. /// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
  154. public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0;
  155. /// <summary>Gets a value indicating whether the Ctrl key was pressed.</summary>
  156. /// <value><see langword="true"/> if is ctrl; otherwise, <see langword="false"/>.</value>
  157. public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
  158. /// <summary>
  159. /// Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to
  160. /// <see cref="KeyCode.Z"/>, regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is
  161. /// based on these keys which are special cased.
  162. /// </summary>
  163. /// <remarks>
  164. /// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
  165. /// corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
  166. /// <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
  167. /// uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  168. /// </remarks>
  169. public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
  170. /// <summary>Gets a value indicating whether the Shift key was pressed.</summary>
  171. /// <value><see langword="true"/> if is shift; otherwise, <see langword="false"/>.</value>
  172. public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0;
  173. /// <summary>
  174. /// Indicates whether the <see cref="Key"/> is valid or not. Invalid keys are <see cref="Key.Empty"/>, and keys
  175. /// with only shift modifiers.
  176. /// </summary>
  177. public bool IsValid => this != Empty && NoAlt.NoShift.NoCtrl != Empty;
  178. private readonly KeyCode _keyCode;
  179. /// <summary>The encoded key value.</summary>
  180. /// <para>
  181. /// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and 90
  182. /// corresponding to the un-shifted A to Z keys on a keyboard. Enum values are provided for these (e.g.
  183. /// <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII values
  184. /// for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  185. /// </para>
  186. /// <remarks>This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.</remarks>
  187. public KeyCode KeyCode
  188. {
  189. get => _keyCode;
  190. init
  191. {
  192. #if DEBUG
  193. if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0)
  194. {
  195. throw new ArgumentException (@$"Invalid KeyCode: {value} is invalid.", nameof (value));
  196. }
  197. #endif
  198. _keyCode = value;
  199. }
  200. }
  201. /// <summary>
  202. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  203. /// <code>
  204. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  205. /// var AltDelete = ControlAltDelete.NoCtrl;
  206. /// </code>
  207. /// </summary>
  208. public Key NoAlt => new (this) { KeyCode = KeyCode & ~KeyCode.AltMask };
  209. /// <summary>
  210. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  211. /// <code>
  212. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  213. /// var AltDelete = ControlAltDelete.NoCtrl;
  214. /// </code>
  215. /// </summary>
  216. public Key NoCtrl => new (this) { KeyCode = KeyCode & ~KeyCode.CtrlMask };
  217. /// <summary>
  218. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  219. /// <code>
  220. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  221. /// var AltDelete = ControlAltDelete.NoCtrl;
  222. /// </code>
  223. /// </summary>
  224. public Key NoShift => new (this) { KeyCode = KeyCode & ~KeyCode.ShiftMask };
  225. /// <summary>
  226. /// Helper for specifying a shifted <see cref="Key"/>.
  227. /// <code>
  228. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  229. /// </code>
  230. /// </summary>
  231. public Key WithAlt => new (this) { KeyCode = KeyCode | KeyCode.AltMask };
  232. /// <summary>
  233. /// Helper for specifying a shifted <see cref="Key"/>.
  234. /// <code>
  235. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  236. /// </code>
  237. /// </summary>
  238. public Key WithCtrl => new (this) { KeyCode = KeyCode | KeyCode.CtrlMask };
  239. /// <summary>
  240. /// Helper for specifying a shifted <see cref="Key"/>.
  241. /// <code>
  242. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  243. /// </code>
  244. /// </summary>
  245. public Key WithShift => new (this) { KeyCode = KeyCode | KeyCode.ShiftMask };
  246. /// <summary>
  247. /// Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
  248. /// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which
  249. /// are special cased.
  250. /// </summary>
  251. /// <remarks>
  252. /// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
  253. /// corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
  254. /// <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
  255. /// uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  256. /// </remarks>
  257. public static bool GetIsKeyCodeAtoZ (KeyCode keyCode)
  258. {
  259. if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0)
  260. {
  261. return false;
  262. }
  263. if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z)
  264. {
  265. return true;
  266. }
  267. return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z;
  268. }
  269. /// <summary>
  270. /// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful for determining if a key represents is a
  271. /// printable character.
  272. /// </summary>
  273. /// <remarks>
  274. /// <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
  275. /// <para>
  276. /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
  277. /// <see cref="KeyCode.ShiftMask"/> is set.
  278. /// </para>
  279. /// <para>
  280. /// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
  281. /// <see langword="default"/>.
  282. /// </para>
  283. /// </remarks>
  284. /// <param name="key"></param>
  285. /// <returns>The key converted to a Rune. <see langword="default"/> if conversion is not possible.</returns>
  286. public static Rune ToRune (KeyCode key)
  287. {
  288. if (key is KeyCode.Null or KeyCode.SpecialMask
  289. || key.HasFlag (KeyCode.CtrlMask)
  290. || key.HasFlag (KeyCode.AltMask))
  291. {
  292. return default (Rune);
  293. }
  294. // Extract the base key code
  295. KeyCode baseKey = key;
  296. if (baseKey.HasFlag (KeyCode.ShiftMask))
  297. {
  298. baseKey &= ~KeyCode.ShiftMask;
  299. }
  300. switch (baseKey)
  301. {
  302. case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
  303. return new ((uint)(baseKey + 32));
  304. case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
  305. return new ((uint)baseKey);
  306. case > KeyCode.Null and < KeyCode.A:
  307. return new ((uint)baseKey);
  308. }
  309. if (Enum.IsDefined (typeof (KeyCode), baseKey))
  310. {
  311. return default (Rune);
  312. }
  313. return new ((uint)baseKey);
  314. }
  315. #region Operators
  316. /// <summary>
  317. /// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties such
  318. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  319. /// </summary>
  320. /// <remarks>Uses <see cref="AsRune"/>.</remarks>
  321. /// <param name="kea"></param>
  322. public static explicit operator Rune (Key kea) { return kea.AsRune; }
  323. // BUGBUG: (Tig) I do not think this cast operator is really needed.
  324. /// <summary>
  325. /// Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties such
  326. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  327. /// </summary>
  328. /// <param name="kea"></param>
  329. public static explicit operator uint (Key kea) { return (uint)kea.KeyCode; }
  330. /// <summary>
  331. /// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties such
  332. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  333. /// </summary>
  334. /// <param name="key"></param>
  335. public static explicit operator KeyCode (Key key) { return key.KeyCode; }
  336. /// <summary>Cast <see cref="KeyCode"/> to a <see cref="Key"/>.</summary>
  337. /// <param name="keyCode"></param>
  338. public static implicit operator Key (KeyCode keyCode) { return new (keyCode); }
  339. /// <summary>Cast <see langword="char"/> to a <see cref="Key"/>.</summary>
  340. /// <remarks>See <see cref="Key(char)"/> for more information.</remarks>
  341. /// <param name="ch"></param>
  342. public static implicit operator Key (char ch) { return new (ch); }
  343. /// <summary>Cast <see langword="string"/> to a <see cref="Key"/>.</summary>
  344. /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
  345. /// <param name="str"></param>
  346. public static implicit operator Key (string str) { return new (str); }
  347. /// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
  348. /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
  349. /// <param name="key"></param>
  350. public static implicit operator string (Key key) { return key.ToString (); }
  351. /// <inheritdoc/>
  352. public override bool Equals (object? obj)
  353. {
  354. if (obj is Key other)
  355. {
  356. return other._keyCode == _keyCode && other.Handled == Handled;
  357. }
  358. return false;
  359. }
  360. bool IEquatable<Key>.Equals (Key? other) { return Equals (other); }
  361. /// <inheritdoc/>
  362. public override int GetHashCode () { return _keyCode.GetHashCode (); }
  363. /// <summary>Compares two <see cref="Key"/>s for equality.</summary>
  364. /// <param name="a"></param>
  365. /// <param name="b"></param>
  366. /// <returns></returns>
  367. public static bool operator == (Key a, Key b) { return a!.Equals (b); }
  368. /// <summary>Compares two <see cref="Key"/>s for not equality.</summary>
  369. /// <param name="a"></param>
  370. /// <param name="b"></param>
  371. /// <returns></returns>
  372. public static bool operator != (Key? a, Key? b) { return !a!.Equals (b); }
  373. /// <summary>Compares two <see cref="Key"/>s for less-than.</summary>
  374. /// <param name="a"></param>
  375. /// <param name="b"></param>
  376. /// <returns></returns>
  377. public static bool operator < (Key a, Key b) { return a?.KeyCode < b?.KeyCode; }
  378. /// <summary>Compares two <see cref="Key"/>s for greater-than.</summary>
  379. /// <param name="a"></param>
  380. /// <param name="b"></param>
  381. /// <returns></returns>
  382. public static bool operator > (Key a, Key b) { return a?.KeyCode > b?.KeyCode; }
  383. /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
  384. /// <param name="a"></param>
  385. /// <param name="b"></param>
  386. /// <returns></returns>
  387. public static bool operator <= (Key a, Key b) { return a?.KeyCode <= b?.KeyCode; }
  388. /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
  389. /// <param name="a"></param>
  390. /// <param name="b"></param>
  391. /// <returns></returns>
  392. public static bool operator >= (Key a, Key b) { return a?.KeyCode >= b?.KeyCode; }
  393. #endregion Operators
  394. #region String conversion
  395. /// <summary>Pretty prints the Key.</summary>
  396. /// <returns></returns>
  397. public override string ToString () { return ToString (KeyCode, Separator); }
  398. private static string GetKeyString (KeyCode key)
  399. {
  400. if (key is KeyCode.Null or KeyCode.SpecialMask)
  401. {
  402. return string.Empty;
  403. }
  404. // Extract the base key (removing modifier flags)
  405. KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
  406. if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z)
  407. {
  408. return ((Rune)(uint)(key + 32)).ToString ();
  409. }
  410. if (key is > KeyCode.Space and < KeyCode.A)
  411. {
  412. return ((Rune)(uint)key).ToString ();
  413. }
  414. string? keyName = Enum.GetName (typeof (KeyCode), key);
  415. return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
  416. }
  417. /// <summary>Formats a <see cref="KeyCode"/> as a string using the default separator of '+'</summary>
  418. /// <param name="key">The key to format.</param>
  419. /// <returns>
  420. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
  421. /// name will be returned.
  422. /// </returns>
  423. public static string ToString (KeyCode key) { return ToString (key, Separator); }
  424. /// <summary>Formats a <see cref="KeyCode"/> as a string.</summary>
  425. /// <param name="key">The key to format.</param>
  426. /// <param name="separator">The character to use as a separator between modifier keys and the key itself.</param>
  427. /// <returns>
  428. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
  429. /// name will be returned.
  430. /// </returns>
  431. public static string ToString (KeyCode key, Rune separator)
  432. {
  433. if (key is KeyCode.Null)
  434. {
  435. // Same as Key.IsValid
  436. return @"Null";
  437. }
  438. var sb = new StringBuilder ();
  439. // Extract the base key (removing modifier flags)
  440. KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
  441. // Extract and handle modifiers
  442. var hasModifiers = false;
  443. if ((key & KeyCode.CtrlMask) != 0)
  444. {
  445. sb.Append ($"Ctrl{separator}");
  446. hasModifiers = true;
  447. }
  448. if ((key & KeyCode.AltMask) != 0)
  449. {
  450. sb.Append ($"Alt{separator}");
  451. hasModifiers = true;
  452. }
  453. if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key))
  454. {
  455. sb.Append ($"Shift{separator}");
  456. hasModifiers = true;
  457. }
  458. // Handle special cases and modifiers on their own
  459. if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers))
  460. {
  461. if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z)
  462. {
  463. sb.Append (baseKey & ~KeyCode.Space);
  464. }
  465. else
  466. {
  467. // Append the actual key name
  468. sb.Append (GetKeyString (baseKey));
  469. }
  470. }
  471. return TrimEndSeparator (sb.ToString (), separator);
  472. }
  473. private static string TrimEndSeparator (string input, Rune separator)
  474. {
  475. // Trim the trailing separator (+). Unless there are two separators at the end.
  476. // "+" (don't trim)
  477. // "Ctrl+" (trim)
  478. // "Ctrl++" (trim)
  479. if (input.Length > 1 && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
  480. {
  481. return input [..^1];
  482. }
  483. return input;
  484. }
  485. private static readonly Dictionary<string, KeyCode> _modifierDict =
  486. new (StringComparer.InvariantCultureIgnoreCase)
  487. {
  488. { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask }
  489. };
  490. /// <summary>Converts the provided string to a new <see cref="Key"/> instance.</summary>
  491. /// <param name="text">
  492. /// The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
  493. /// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", "X", and "120" (Unicode codepoint).
  494. /// <para>
  495. /// The separator can be any character, not just <see cref="Key.Separator"/> (e.g. "Ctrl@Alt@X").
  496. /// </para>
  497. /// </param>
  498. /// <param name="key">The parsed value.</param>
  499. /// <returns>A boolean value indicating whether parsing was successful.</returns>
  500. /// <remarks></remarks>
  501. public static bool TryParse (string text, out Key key)
  502. {
  503. if (string.IsNullOrEmpty (text))
  504. {
  505. key = Empty;
  506. return true;
  507. }
  508. switch (text)
  509. {
  510. case "Ctrl":
  511. key = KeyCode.CtrlMask;
  512. return true;
  513. case "Alt":
  514. key = KeyCode.AltMask;
  515. return true;
  516. case "Shift":
  517. key = KeyCode.ShiftMask;
  518. return true;
  519. }
  520. key = null!;
  521. Rune separator = Separator;
  522. // Perhaps the separator was written using a different Key.Separator? Does the string
  523. // start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator.
  524. if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
  525. {
  526. separator = (Rune)text [4];
  527. }
  528. else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
  529. {
  530. separator = (Rune)text [3];
  531. }
  532. else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
  533. {
  534. separator = (Rune)text [5];
  535. }
  536. else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
  537. {
  538. separator = (Rune)text [^5];
  539. }
  540. else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
  541. {
  542. separator = (Rune)text [^4];
  543. }
  544. else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
  545. {
  546. separator = (Rune)text [^6];
  547. }
  548. // Split the string into parts using the set Separator
  549. string [] parts = text.Split ((char)separator.Value);
  550. if (parts.Length is > 4)
  551. {
  552. // Invalid
  553. return false;
  554. }
  555. // e.g. "Ctrl++"
  556. if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
  557. {
  558. // Invalid
  559. return false;
  560. }
  561. if ((Rune)text [^1] == separator)
  562. {
  563. parts [^1] = separator.Value.ToString ();
  564. key = (char)separator.Value;
  565. }
  566. if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2)))
  567. {
  568. parts = text.Split ((char)separator.Value);
  569. if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
  570. {
  571. // Invalid
  572. return false;
  573. }
  574. }
  575. var modifiers = KeyCode.Null;
  576. for (var index = 0; index < parts.Length; index++)
  577. {
  578. if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out KeyCode modifier))
  579. {
  580. modifiers |= modifier;
  581. parts [index] = string.Empty; // eat it
  582. }
  583. }
  584. // we now have the modifiers
  585. string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty);
  586. var parsedKeyCode = KeyCode.Null;
  587. var parsedInt = 0;
  588. if (partNotFound.Length == 1)
  589. {
  590. var keyCode = (KeyCode)partNotFound [0];
  591. // if it's a single digit int, treat it as such
  592. if (int.TryParse (
  593. partNotFound,
  594. NumberStyles.Integer,
  595. CultureInfo.InvariantCulture,
  596. out parsedInt
  597. ))
  598. {
  599. keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt);
  600. }
  601. else if (Enum.TryParse (partNotFound, false, out parsedKeyCode))
  602. {
  603. if (parsedKeyCode != KeyCode.Null)
  604. {
  605. if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  606. {
  607. key = new (parsedKeyCode | KeyCode.ShiftMask);
  608. return true;
  609. }
  610. key = new (parsedKeyCode | modifiers);
  611. return true;
  612. }
  613. }
  614. if (GetIsKeyCodeAtoZ (keyCode) && (keyCode & KeyCode.Space) != 0)
  615. {
  616. keyCode &= ~KeyCode.Space;
  617. }
  618. key = new (keyCode | modifiers);
  619. return true;
  620. }
  621. if (Enum.TryParse (partNotFound, true, out parsedKeyCode))
  622. {
  623. if (parsedKeyCode != KeyCode.Null)
  624. {
  625. if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  626. {
  627. key = new (parsedKeyCode | KeyCode.ShiftMask);
  628. return true;
  629. }
  630. if (GetIsKeyCodeAtoZ (parsedKeyCode) && (parsedKeyCode & KeyCode.Space) != 0)
  631. {
  632. parsedKeyCode = parsedKeyCode & ~KeyCode.Space;
  633. }
  634. key = new (parsedKeyCode | modifiers);
  635. return true;
  636. }
  637. }
  638. // if it's a number int, treat it as a unicode value
  639. if (int.TryParse (
  640. partNotFound,
  641. NumberStyles.Number,
  642. CultureInfo.InvariantCulture,
  643. out parsedInt
  644. ))
  645. {
  646. if (!Rune.IsValid (parsedInt))
  647. {
  648. return false;
  649. }
  650. if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  651. {
  652. key = new ((KeyCode)parsedInt | KeyCode.ShiftMask);
  653. return true;
  654. }
  655. key = new ((KeyCode)parsedInt);
  656. return true;
  657. }
  658. if (!Enum.TryParse (partNotFound, true, out parsedKeyCode))
  659. {
  660. return false;
  661. }
  662. if (GetIsKeyCodeAtoZ (parsedKeyCode))
  663. {
  664. key = new (parsedKeyCode | (modifiers & ~KeyCode.Space));
  665. return true;
  666. }
  667. return false;
  668. }
  669. #endregion
  670. #region Standard Key Definitions
  671. /// <summary>An uninitialized The <see cref="Key"/> object.</summary>
  672. public new static Key Empty => new ();
  673. /// <summary>The <see cref="Key"/> object for the Backspace key.</summary>
  674. public static Key Backspace => new (KeyCode.Backspace);
  675. /// <summary>The <see cref="Key"/> object for the tab key (forwards tab key).</summary>
  676. public static Key Tab => new (KeyCode.Tab);
  677. /// <summary>The <see cref="Key"/> object for the return key.</summary>
  678. public static Key Enter => new (KeyCode.Enter);
  679. /// <summary>The <see cref="Key"/> object for the clear key.</summary>
  680. public static Key Clear => new (KeyCode.Clear);
  681. /// <summary>The <see cref="Key"/> object for the Escape key.</summary>
  682. public static Key Esc => new (KeyCode.Esc);
  683. /// <summary>The <see cref="Key"/> object for the Space bar key.</summary>
  684. public static Key Space => new (KeyCode.Space);
  685. /// <summary>The <see cref="Key"/> object for 0 key.</summary>
  686. public static Key D0 => new (KeyCode.D0);
  687. /// <summary>The <see cref="Key"/> object for 1 key.</summary>
  688. public static Key D1 => new (KeyCode.D1);
  689. /// <summary>The <see cref="Key"/> object for 2 key.</summary>
  690. public static Key D2 => new (KeyCode.D2);
  691. /// <summary>The <see cref="Key"/> object for 3 key.</summary>
  692. public static Key D3 => new (KeyCode.D3);
  693. /// <summary>The <see cref="Key"/> object for 4 key.</summary>
  694. public static Key D4 => new (KeyCode.D4);
  695. /// <summary>The <see cref="Key"/> object for 5 key.</summary>
  696. public static Key D5 => new (KeyCode.D5);
  697. /// <summary>The <see cref="Key"/> object for 6 key.</summary>
  698. public static Key D6 => new (KeyCode.D6);
  699. /// <summary>The <see cref="Key"/> object for 7 key.</summary>
  700. public static Key D7 => new (KeyCode.D7);
  701. /// <summary>The <see cref="Key"/> object for 8 key.</summary>
  702. public static Key D8 => new (KeyCode.D8);
  703. /// <summary>The <see cref="Key"/> object for 9 key.</summary>
  704. public static Key D9 => new (KeyCode.D9);
  705. /// <summary>The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.</summary>
  706. public static Key A => new (KeyCode.A);
  707. /// <summary>The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.</summary>
  708. public static Key B => new (KeyCode.B);
  709. /// <summary>The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.</summary>
  710. public static Key C => new (KeyCode.C);
  711. /// <summary>The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.</summary>
  712. public static Key D => new (KeyCode.D);
  713. /// <summary>The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.</summary>
  714. public static Key E => new (KeyCode.E);
  715. /// <summary>The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.</summary>
  716. public static Key F => new (KeyCode.F);
  717. /// <summary>The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.</summary>
  718. public static Key G => new (KeyCode.G);
  719. /// <summary>The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.</summary>
  720. public static Key H => new (KeyCode.H);
  721. /// <summary>The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.</summary>
  722. public static Key I => new (KeyCode.I);
  723. /// <summary>The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.</summary>
  724. public static Key J => new (KeyCode.J);
  725. /// <summary>The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.</summary>
  726. public static Key K => new (KeyCode.K);
  727. /// <summary>The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.</summary>
  728. public static Key L => new (KeyCode.L);
  729. /// <summary>The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.</summary>
  730. public static Key M => new (KeyCode.M);
  731. /// <summary>The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.</summary>
  732. public static Key N => new (KeyCode.N);
  733. /// <summary>The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.</summary>
  734. public static Key O => new (KeyCode.O);
  735. /// <summary>The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.</summary>
  736. public static Key P => new (KeyCode.P);
  737. /// <summary>The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.</summary>
  738. public static Key Q => new (KeyCode.Q);
  739. /// <summary>The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.</summary>
  740. public static Key R => new (KeyCode.R);
  741. /// <summary>The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.</summary>
  742. public static Key S => new (KeyCode.S);
  743. /// <summary>The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.</summary>
  744. public static Key T => new (KeyCode.T);
  745. /// <summary>The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.</summary>
  746. public static Key U => new (KeyCode.U);
  747. /// <summary>The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.</summary>
  748. public static Key V => new (KeyCode.V);
  749. /// <summary>The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.</summary>
  750. public static Key W => new (KeyCode.W);
  751. /// <summary>The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.</summary>
  752. public static Key X => new (KeyCode.X);
  753. /// <summary>The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.</summary>
  754. public static Key Y => new (KeyCode.Y);
  755. /// <summary>The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.</summary>
  756. public static Key Z => new (KeyCode.Z);
  757. /// <summary>The <see cref="Key"/> object for the Delete key.</summary>
  758. public static Key Delete => new (KeyCode.Delete);
  759. /// <summary>The <see cref="Key"/> object for the Cursor up key.</summary>
  760. public static Key CursorUp => new (KeyCode.CursorUp);
  761. /// <summary>The <see cref="Key"/> object for Cursor down key.</summary>
  762. public static Key CursorDown => new (KeyCode.CursorDown);
  763. /// <summary>The <see cref="Key"/> object for Cursor left key.</summary>
  764. public static Key CursorLeft => new (KeyCode.CursorLeft);
  765. /// <summary>The <see cref="Key"/> object for Cursor right key.</summary>
  766. public static Key CursorRight => new (KeyCode.CursorRight);
  767. /// <summary>The <see cref="Key"/> object for Page Up key.</summary>
  768. public static Key PageUp => new (KeyCode.PageUp);
  769. /// <summary>The <see cref="Key"/> object for Page Down key.</summary>
  770. public static Key PageDown => new (KeyCode.PageDown);
  771. /// <summary>The <see cref="Key"/> object for Home key.</summary>
  772. public static Key Home => new (KeyCode.Home);
  773. /// <summary>The <see cref="Key"/> object for End key.</summary>
  774. public static Key End => new (KeyCode.End);
  775. /// <summary>The <see cref="Key"/> object for Insert Character key.</summary>
  776. public static Key InsertChar => new (KeyCode.Insert);
  777. /// <summary>The <see cref="Key"/> object for Delete Character key.</summary>
  778. public static Key DeleteChar => new (KeyCode.Delete);
  779. /// <summary>The <see cref="Key"/> object for Print Screen key.</summary>
  780. public static Key PrintScreen => new (KeyCode.PrintScreen);
  781. /// <summary>The <see cref="Key"/> object for F1 key.</summary>
  782. public static Key F1 => new (KeyCode.F1);
  783. /// <summary>The <see cref="Key"/> object for F2 key.</summary>
  784. public static Key F2 => new (KeyCode.F2);
  785. /// <summary>The <see cref="Key"/> object for F3 key.</summary>
  786. public static Key F3 => new (KeyCode.F3);
  787. /// <summary>The <see cref="Key"/> object for F4 key.</summary>
  788. public static Key F4 => new (KeyCode.F4);
  789. /// <summary>The <see cref="Key"/> object for F5 key.</summary>
  790. public static Key F5 => new (KeyCode.F5);
  791. /// <summary>The <see cref="Key"/> object for F6 key.</summary>
  792. public static Key F6 => new (KeyCode.F6);
  793. /// <summary>The <see cref="Key"/> object for F7 key.</summary>
  794. public static Key F7 => new (KeyCode.F7);
  795. /// <summary>The <see cref="Key"/> object for F8 key.</summary>
  796. public static Key F8 => new (KeyCode.F8);
  797. /// <summary>The <see cref="Key"/> object for F9 key.</summary>
  798. public static Key F9 => new (KeyCode.F9);
  799. /// <summary>The <see cref="Key"/> object for F10 key.</summary>
  800. public static Key F10 => new (KeyCode.F10);
  801. /// <summary>The <see cref="Key"/> object for F11 key.</summary>
  802. public static Key F11 => new (KeyCode.F11);
  803. /// <summary>The <see cref="Key"/> object for F12 key.</summary>
  804. public static Key F12 => new (KeyCode.F12);
  805. /// <summary>The <see cref="Key"/> object for F13 key.</summary>
  806. public static Key F13 => new (KeyCode.F13);
  807. /// <summary>The <see cref="Key"/> object for F14 key.</summary>
  808. public static Key F14 => new (KeyCode.F14);
  809. /// <summary>The <see cref="Key"/> object for F15 key.</summary>
  810. public static Key F15 => new (KeyCode.F15);
  811. /// <summary>The <see cref="Key"/> object for F16 key.</summary>
  812. public static Key F16 => new (KeyCode.F16);
  813. /// <summary>The <see cref="Key"/> object for F17 key.</summary>
  814. public static Key F17 => new (KeyCode.F17);
  815. /// <summary>The <see cref="Key"/> object for F18 key.</summary>
  816. public static Key F18 => new (KeyCode.F18);
  817. /// <summary>The <see cref="Key"/> object for F19 key.</summary>
  818. public static Key F19 => new (KeyCode.F19);
  819. /// <summary>The <see cref="Key"/> object for F20 key.</summary>
  820. public static Key F20 => new (KeyCode.F20);
  821. /// <summary>The <see cref="Key"/> object for F21 key.</summary>
  822. public static Key F21 => new (KeyCode.F21);
  823. /// <summary>The <see cref="Key"/> object for F22 key.</summary>
  824. public static Key F22 => new (KeyCode.F22);
  825. /// <summary>The <see cref="Key"/> object for F23 key.</summary>
  826. public static Key F23 => new (KeyCode.F23);
  827. /// <summary>The <see cref="Key"/> object for F24 key.</summary>
  828. public static Key F24 => new (KeyCode.F24);
  829. #endregion
  830. private static Rune _separator = new ('+');
  831. /// <summary>Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'.</summary>
  832. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  833. public static Rune Separator
  834. {
  835. get => _separator;
  836. set
  837. {
  838. if (_separator != value)
  839. {
  840. _separator = value == default (Rune) ? new ('+') : value;
  841. }
  842. }
  843. }
  844. }