using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Text.Json.Serialization; namespace Terminal.Gui; /// /// Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and raising keyboard events. /// /// /// /// This class provides a high-level abstraction with helper methods and properties for common keyboard operations. Use this class /// instead of the enumeration for keyboard input whenever possible. /// /// /// /// /// /// The default value for is and can be tested using . /// /// /// /// /// ConceptDefinition /// /// /// Testing Shift State /// /// The Is properties (,, ) test for shift state; whether the key press was modified by a shift key. /// /// /// /// Adding Shift State /// /// The With properties (,, ) return a copy of the Key with the shift modifier applied. This /// is useful for specifying a key that requires a shift modifier (e.g. var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;. /// /// /// /// Removing Shift State /// /// The No properties (,, ) return a copy of the Key with the shift modifier removed. This /// is useful for specifying a key that does not require a shift modifier (e.g. var ControlDelete = ControlAltDelete.NoCtrl;. /// /// /// /// Encoding of A..Z /// /// Lowercase alpha keys are encoded (in ) as values between 65 and 90 corresponding to /// the un-shifted A to Z keys on a keyboard. Properties are provided for these (e.g. , , etc.). /// Even though the encoded values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters. /// /// /// /// Persistence as strings /// /// Keys are persisted as "[Modifiers]+[Key]. For example new Key(Key.Delete).WithAlt.WithDel is persisted as "Ctrl+Alt+Delete". See /// and for more information. /// /// /// /// /// [JsonConverter (typeof (KeyJsonConverter))] public class Key : EventArgs, IEquatable { /// /// Constructs a new /// public Key () : this (KeyCode.Null) { } /// /// Constructs a new from the provided Key value /// /// The key public Key (KeyCode k) => KeyCode = k; /// /// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber. /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method. /// public bool Handled { get; set; } = false; /// /// The encoded key value. /// /// /// IMPORTANT: Lowercase alpha keys are encoded (in ) as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values /// are provided for these (e.g. , , etc.). Even though the values are the same as the ASCII /// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters. /// /// /// This property is the backing data for the . It is a enum value. /// [JsonInclude] [JsonConverter (typeof (KeyCodeJsonConverter))] public KeyCode KeyCode { get; init; } /// /// Enables passing the key binding scope with the event. Default is . /// public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused; /// /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. /// /// /// If the key pressed is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed. /// If the key is outside of the range, this will be . /// public Rune AsRune => ToRune (KeyCode); /// /// Converts a to a . /// /// /// If the key is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed. /// If the key is outside of the range, this will be . /// /// /// The key converted to a rune. if conversion is not possible. public static Rune ToRune (KeyCode key) { if (key is KeyCode.Null or KeyCode.SpecialMask || key.HasFlag (KeyCode.CtrlMask) || key.HasFlag (KeyCode.AltMask)) { return default; } // Extract the base key (removing modifier flags) var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; switch (baseKey) { case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask): return new Rune ((char)(baseKey + 32)); case >= KeyCode.A and <= KeyCode.Z: return new Rune ((char)baseKey); case > KeyCode.Null and < KeyCode.A: return new Rune ((char)baseKey); } if (Enum.IsDefined (typeof (KeyCode), baseKey)) { return default; } return new Rune ((char)baseKey); } /// /// Gets a value indicating whether the Shift key was pressed. /// /// if is shift; otherwise, . public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0; /// /// Gets a value indicating whether the Alt key was pressed (real or synthesized) /// /// if is alternate; otherwise, . public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0; /// /// Gets a value indicating whether the Ctrl key was pressed. /// /// if is ctrl; otherwise, . public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0; /// /// Gets a value indicating whether the KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key. /// /// /// IMPORTANT: Lowercase alpha keys are encoded in as values between 65 and 90 corresponding to /// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. , , etc.). /// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters. /// public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode); /// /// Tests if a KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key. /// /// /// IMPORTANT: Lowercase alpha keys are encoded in as values between 65 and 90 corresponding to /// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. , , etc.). /// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters. /// public static bool GetIsKeyCodeAtoZ (KeyCode keyCode) { if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0) { return false; } if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z) { return true; } return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z; } /// /// Indicates whether the is valid or not. Invalid keys are , /// and keys with only shift modifiers. /// public bool IsValid => this != Empty && (NoAlt.NoShift.NoCtrl != Empty); /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithShift => new (KeyCode | KeyCode.ShiftMask); /// /// Helper for removing a shift modifier from a . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// var AltDelete = ControlAltDelete.NoCtrl; /// /// public Key NoShift => new (KeyCode & ~KeyCode.ShiftMask); /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithCtrl => new (KeyCode | KeyCode.CtrlMask); /// /// Helper for removing a shift modifier from a . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// var AltDelete = ControlAltDelete.NoCtrl; /// /// public Key NoCtrl => new (KeyCode & ~KeyCode.CtrlMask); /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithAlt => new (KeyCode | KeyCode.AltMask); /// /// Helper for removing a shift modifier from a . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// var AltDelete = ControlAltDelete.NoCtrl; /// /// public Key NoAlt => new (KeyCode & ~KeyCode.AltMask); #region Operators /// /// Explicitly cast a to a . The conversion is lossy. /// /// /// Uses . /// /// public static explicit operator Rune (Key kea) => kea.AsRune; /// /// Explicitly cast to a . The conversion is lossy. /// /// public static explicit operator char (Key kea) => (char)kea.AsRune.Value; /// /// Explicitly cast to a . The conversion is lossy. /// /// public static explicit operator KeyCode (Key key) => key.KeyCode; /// /// Cast to a . /// /// public static implicit operator Key (KeyCode keyCode) => new (keyCode); /// /// Cast to a . /// /// public static implicit operator Key (char ch) => new ((KeyCode)ch); /// public override bool Equals (object obj) => obj is Key k && k.KeyCode == KeyCode; bool IEquatable.Equals (Key other) => Equals ((object)other); /// public override int GetHashCode () => (int)KeyCode; /// /// /// /// /// public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode; /// /// /// /// /// public static bool operator != (Key a, Key b) => a?.KeyCode != b?.KeyCode; /// /// Compares two s for less-than. /// /// /// /// public static bool operator < (Key a, Key b) => a?.KeyCode < b?.KeyCode; /// /// Compares two s for greater-than. /// /// /// /// public static bool operator > (Key a, Key b) => a?.KeyCode > b?.KeyCode; /// /// Compares two s for greater-than-or-equal-to. /// /// /// /// public static bool operator <= (Key a, Key b) => a?.KeyCode <= b?.KeyCode; /// /// Compares two s for greater-than-or-equal-to. /// /// /// /// public static bool operator >= (Key a, Key b) => a?.KeyCode >= b?.KeyCode; #endregion Operators #region String conversion /// /// Pretty prints the KeyEvent /// /// public override string ToString () => ToString (KeyCode, (Rune)'+'); static string GetKeyString (KeyCode key) { if (key is KeyCode.Null or KeyCode.SpecialMask) { return string.Empty; } // Extract the base key (removing modifier flags) var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) { return ((char)(key + 32)).ToString (); } if (key is >= KeyCode.Space and < KeyCode.A) { return ((char)key).ToString (); } string keyName = Enum.GetName (typeof (KeyCode), key); return !string.IsNullOrEmpty (keyName) ? keyName : ((char)key).ToString (); } /// /// Formats a as a string using the default separator of '+' /// /// The key to format. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned. public static string ToString (KeyCode key) => ToString (key, (Rune)'+'); /// /// Formats a as a string. /// /// The key to format. /// The character to use as a separator between modifier keys and and the key itself. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned. public static string ToString (KeyCode key, Rune separator) { if (key is KeyCode.Null || (key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask) == 0) { // Same as Key.IsValid return @"Null"; } var sb = new StringBuilder (); // Extract the base key (removing modifier flags) var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; // Extract and handle modifiers bool hasModifiers = false; if ((key & KeyCode.CtrlMask) != 0) { sb.Append ($"Ctrl{separator}"); hasModifiers = true; } if ((key & KeyCode.AltMask) != 0) { sb.Append ($"Alt{separator}"); hasModifiers = true; } if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key)) { sb.Append ($"Shift{separator}"); hasModifiers = true; } // Handle special cases and modifiers on their own if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers)) { if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) { sb.Append (baseKey & ~KeyCode.Space); } else { // Append the actual key name sb.Append (GetKeyString (baseKey)); } } string result = sb.ToString (); result = TrimEndRune (result, separator); return result; } static string TrimEndRune (string input, Rune runeToTrim) { // Convert the Rune to a string (which may be one or two chars) string runeString = runeToTrim.ToString (); if (input.EndsWith (runeString)) { // Remove the rune from the end of the string return input.Substring (0, input.Length - runeString.Length); } return input; } static readonly Dictionary _modifierDict = new (comparer: StringComparer.InvariantCultureIgnoreCase) { { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask } }; /// /// Converts the provided string to a new instance. /// /// The text to analyze. Formats supported are /// "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X", "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X". /// /// The parsed value. /// A boolean value indicating whether parsing was successful. /// /// public static bool TryParse (string text, [NotNullWhen (true)] out Key key) { if (string.IsNullOrEmpty (text)) { key = new Key (KeyCode.Null); return true; } key = null; // Split the string into parts string [] parts = text.Split ('+', '-'); if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty)) { return false; } // if it's just a shift key if (parts.Length == 1) { switch (parts [0]) { case "Ctrl": key = new Key (KeyCode.CtrlKey); return true; case "Alt": key = new Key (KeyCode.AltKey); return true; case "Shift": key = new Key (KeyCode.ShiftKey); return true; } } var modifiers = KeyCode.Null; for (int index = 0; index < parts.Length; index++) { if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out var modifier)) { modifiers |= modifier; parts [index] = string.Empty; // eat it } } // we now have the modifiers string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty); var parsedKeyCode = KeyCode.Null; int parsedInt = 0; if (partNotFound.Length == 1) { var keyCode = (KeyCode)partNotFound [0]; // if it's a single digit int, treat it as such if (int.TryParse (partNotFound, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out parsedInt)) { keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt); } else if (Enum.TryParse (partNotFound, false, out parsedKeyCode)) { if (parsedKeyCode != KeyCode.Null) { if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) { key = new Key (parsedKeyCode | KeyCode.ShiftMask); return true; } key = new Key ((KeyCode)parsedKeyCode | modifiers); return true; } } key = new Key (keyCode | modifiers); return true; } if (Enum.TryParse (partNotFound, true, out parsedKeyCode)) { if (parsedKeyCode != KeyCode.Null) { if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) { key = new Key (parsedKeyCode | KeyCode.ShiftMask); return true; } key = new Key (parsedKeyCode | modifiers); return true; } } // if it's a number int, treat it as a unicode value if (int.TryParse (partNotFound, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out parsedInt)) { if (!Rune.IsValid (parsedInt)) { return false; } if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) { key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask); return true; } key = new Key ((KeyCode)parsedInt); return true; } if (!Enum.TryParse (partNotFound, true, out parsedKeyCode)) { return false; } if (GetIsKeyCodeAtoZ (parsedKeyCode)) { key = new Key (parsedKeyCode | modifiers & ~KeyCode.Space); return true; } return false; } #endregion #region Standard Key Definitions /// /// An uninitialized The object. /// public new static readonly Key Empty = new (); /// /// The object for the Backspace key. /// public static readonly Key Backspace = new (KeyCode.Backspace); /// /// The object for the tab key (forwards tab key). /// public static readonly Key Tab = new (KeyCode.Tab); /// /// The object for the return key. /// public static readonly Key Enter = new (KeyCode.Enter); /// /// The object for the clear key. /// public static readonly Key Clear = new (KeyCode.Clear); /// /// The object for the Shift key. /// public static readonly Key Shift = new (KeyCode.ShiftKey); /// /// The object for the Ctrl key. /// public static readonly Key Ctrl = new (KeyCode.CtrlKey); /// /// The object for the Alt key. /// public static readonly Key Alt = new (KeyCode.AltKey); /// /// The object for the CapsLock key. /// public static readonly Key CapsLock = new (KeyCode.CapsLock); /// /// The object for the Escape key. /// public static readonly Key Esc = new (KeyCode.Esc); /// /// The object for the Space bar key. /// public static readonly Key Space = new (KeyCode.Space); /// /// The object for 0 key. /// public static readonly Key D0 = new (KeyCode.D0); /// /// The object for 1 key. /// public static readonly Key D1 = new (KeyCode.D1); /// /// The object for 2 key. /// public static readonly Key D2 = new (KeyCode.D2); /// /// The object for 3 key. /// public static readonly Key D3 = new (KeyCode.D3); /// /// The object for 4 key. /// public static readonly Key D4 = new (KeyCode.D4); /// /// The object for 5 key. /// public static readonly Key D5 = new (KeyCode.D5); /// /// The object for 6 key. /// public static readonly Key D6 = new (KeyCode.D6); /// /// The object for 7 key. /// public static readonly Key D7 = new (KeyCode.D7); /// /// The object for 8 key. /// public static readonly Key D8 = new (KeyCode.D8); /// /// The object for 9 key. /// public static readonly Key D9 = new (KeyCode.D9); /// /// The object for the A key (un-shifted). Use Key.A.WithShift for uppercase 'A'. /// public static readonly Key A = new (KeyCode.A); /// /// The object for the B key (un-shifted). Use Key.B.WithShift for uppercase 'B'. /// public static readonly Key B = new (KeyCode.B); /// /// The object for the C key (un-shifted). Use Key.C.WithShift for uppercase 'C'. /// public static readonly Key C = new (KeyCode.C); /// /// The object for the D key (un-shifted). Use Key.D.WithShift for uppercase 'D'. /// public static readonly Key D = new (KeyCode.D); /// /// The object for the E key (un-shifted). Use Key.E.WithShift for uppercase 'E'. /// public static readonly Key E = new (KeyCode.E); /// /// The object for the F key (un-shifted). Use Key.F.WithShift for uppercase 'F'. /// public static readonly Key F = new (KeyCode.F); /// /// The object for the G key (un-shifted). Use Key.G.WithShift for uppercase 'G'. /// public static readonly Key G = new (KeyCode.G); /// /// The object for the H key (un-shifted). Use Key.H.WithShift for uppercase 'H'. /// public static readonly Key H = new (KeyCode.H); /// /// The object for the I key (un-shifted). Use Key.I.WithShift for uppercase 'I'. /// public static readonly Key I = new (KeyCode.I); /// /// The object for the J key (un-shifted). Use Key.J.WithShift for uppercase 'J'. /// public static readonly Key J = new (KeyCode.J); /// /// The object for the K key (un-shifted). Use Key.K.WithShift for uppercase 'K'. /// public static readonly Key K = new (KeyCode.K); /// /// The object for the L key (un-shifted). Use Key.L.WithShift for uppercase 'L'. /// public static readonly Key L = new (KeyCode.L); /// /// The object for the M key (un-shifted). Use Key.M.WithShift for uppercase 'M'. /// public static readonly Key M = new (KeyCode.M); /// /// The object for the N key (un-shifted). Use Key.N.WithShift for uppercase 'N'. /// public static readonly Key N = new (KeyCode.N); /// /// The object for the O key (un-shifted). Use Key.O.WithShift for uppercase 'O'. /// public static readonly Key O = new (KeyCode.O); /// /// The object for the P key (un-shifted). Use Key.P.WithShift for uppercase 'P'. /// public static readonly Key P = new (KeyCode.P); /// /// The object for the Q key (un-shifted). Use Key.Q.WithShift for uppercase 'Q'. /// public static readonly Key Q = new (KeyCode.Q); /// /// The object for the R key (un-shifted). Use Key.R.WithShift for uppercase 'R'. /// public static readonly Key R = new (KeyCode.R); /// /// The object for the S key (un-shifted). Use Key.S.WithShift for uppercase 'S'. /// public static readonly Key S = new (KeyCode.S); /// /// The object for the T key (un-shifted). Use Key.T.WithShift for uppercase 'T'. /// public static readonly Key T = new (KeyCode.T); /// /// The object for the U key (un-shifted). Use Key.U.WithShift for uppercase 'U'. /// public static readonly Key U = new (KeyCode.U); /// /// The object for the V key (un-shifted). Use Key.V.WithShift for uppercase 'V'. /// public static readonly Key V = new (KeyCode.V); /// /// The object for the W key (un-shifted). Use Key.W.WithShift for uppercase 'W'. /// public static readonly Key W = new (KeyCode.W); /// /// The object for the X key (un-shifted). Use Key.X.WithShift for uppercase 'X'. /// public static readonly Key X = new (KeyCode.X); /// /// The object for the Y key (un-shifted). Use Key.Y.WithShift for uppercase 'Y'. /// public static readonly Key Y = new (KeyCode.Y); /// /// The object for the Z key (un-shifted). Use Key.Z.WithShift for uppercase 'Z'. /// public static readonly Key Z = new (KeyCode.Z); /// /// The object for the Delete key. /// public static readonly Key Delete = new (KeyCode.Delete); /// /// The object for the Cursor up key. /// public static readonly Key CursorUp = new (KeyCode.CursorUp); /// /// The object for Cursor down key. /// public static readonly Key CursorDown = new (KeyCode.CursorDown); /// /// The object for Cursor left key. /// public static readonly Key CursorLeft = new (KeyCode.CursorLeft); /// /// The object for Cursor right key. /// public static readonly Key CursorRight = new (KeyCode.CursorRight); /// /// The object for Page Up key. /// public static readonly Key PageUp = new (KeyCode.PageUp); /// /// The object for Page Down key. /// public static readonly Key PageDown = new (KeyCode.PageDown); /// /// The object for Home key. /// public static readonly Key Home = new (KeyCode.Home); /// /// The object for End key. /// public static readonly Key End = new (KeyCode.End); /// /// The object for Insert Character key. /// public static readonly Key InsertChar = new (KeyCode.InsertChar); /// /// The object for Delete Character key. /// public static readonly Key DeleteChar = new (KeyCode.DeleteChar); /// /// The object for Print Screen key. /// public static readonly Key PrintScreen = new (KeyCode.PrintScreen); /// /// The object for F1 key. /// public static readonly Key F1 = new (KeyCode.F1); /// /// The object for F2 key. /// public static readonly Key F2 = new (KeyCode.F2); /// /// The object for F3 key. /// public static readonly Key F3 = new (KeyCode.F3); /// /// The object for F4 key. /// public static readonly Key F4 = new (KeyCode.F4); /// /// The object for F5 key. /// public static readonly Key F5 = new (KeyCode.F5); /// /// The object for F6 key. /// public static readonly Key F6 = new (KeyCode.F6); /// /// The object for F7 key. /// public static readonly Key F7 = new (KeyCode.F7); /// /// The object for F8 key. /// public static readonly Key F8 = new (KeyCode.F8); /// /// The object for F9 key. /// public static readonly Key F9 = new (KeyCode.F9); /// /// The object for F10 key. /// public static readonly Key F10 = new (KeyCode.F10); /// /// The object for F11 key. /// public static readonly Key F11 = new (KeyCode.F11); /// /// The object for F12 key. /// public static readonly Key F12 = new (KeyCode.F12); /// /// The object for F13 key. /// public static readonly Key F13 = new (KeyCode.F13); /// /// The object for F14 key. /// public static readonly Key F14 = new (KeyCode.F14); /// /// The object for F15 key. /// public static readonly Key F15 = new (KeyCode.F15); /// /// The object for F16 key. /// public static readonly Key F16 = new (KeyCode.F16); /// /// The object for F17 key. /// public static readonly Key F17 = new (KeyCode.F17); /// /// The object for F18 key. /// public static readonly Key F18 = new (KeyCode.F18); /// /// The object for F19 key. /// public static readonly Key F19 = new (KeyCode.F19); /// /// The object for F20 key. /// public static readonly Key F20 = new (KeyCode.F20); /// /// The object for F21 key. /// public static readonly Key F21 = new (KeyCode.F21); /// /// The object for F22 key. /// public static readonly Key F22 = new (KeyCode.F22); /// /// The object for F23 key. /// public static readonly Key F23 = new (KeyCode.F23); /// /// The object for F24 key. /// public static readonly Key F24 = new (KeyCode.F24); #endregion }