Key.cs 38 KB

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