#nullable enable using System.Diagnostics.CodeAnalysis; using System.Globalization; 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. /// /// /// /// /// 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; } /// /// Copy constructor. /// /// The Key to copy public Key (Key key) { KeyCode = key.KeyCode; Handled = key.Handled; } /// Constructs a new from a char. /// /// /// The key codes for the A..Z keys are encoded as values between 65 and 90 ( through /// ). While these are the same as the ASCII values for uppercase characters, they represent /// *keys*, not characters. Therefore, this constructor will store 'A'..'Z' as .. /// with the set and will store `a`..`z` as /// ... /// /// /// public Key (char ch) { switch (ch) { case >= 'A' and <= 'Z': // Upper case A..Z mean "Shift-char" so we need to add Shift KeyCode = (KeyCode)ch | KeyCode.ShiftMask; break; case >= 'a' and <= 'z': // Lower case a..z mean no shift, so we need to store as Key.A...Key.Z KeyCode = (KeyCode)(ch - 32); return; default: KeyCode = (KeyCode)ch; break; } } /// /// Constructs a new Key from a string describing the key. See /// for information on the format of the string. /// /// The string describing the key. public Key (string str) { bool result = TryParse (str, out Key key); if (!result) { throw new ArgumentException (@$"Invalid key string: {str}", nameof (str)); } KeyCode = key.KeyCode; } /// /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. /// Useful for determining if a key represents is a printable character. /// /// /// Keys with Ctrl or Alt modifiers will return . /// /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether /// is set. /// /// /// If the key is outside of the range, the returned Rune will be /// . /// /// public Rune AsRune => ToRune (KeyCode); /// /// Indicates if the current Key event has already been processed and the driver should stop notifying any other /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside /// the /// subscriber method. /// public bool Handled { get; set; } /// 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 key represents a key in the range of to /// , regardless of the . This is useful for testing if a key is /// based on these keys which are special cased. /// /// /// 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); /// Gets a value indicating whether the Shift key was pressed. /// if is shift; otherwise, . public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0; /// /// 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; private readonly KeyCode _keyCode; /// 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. public KeyCode KeyCode { get => _keyCode; init { #if DEBUG if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0) { throw new ArgumentException (@$"Invalid KeyCode: {value} is invalid.", nameof (value)); } #endif _keyCode = value; } } /// /// Helper for removing a shift modifier from a . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// var AltDelete = ControlAltDelete.NoCtrl; /// /// public Key NoAlt => new (this) { KeyCode = 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 NoCtrl => new (this) { KeyCode = 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 NoShift => new (this) { KeyCode = KeyCode & ~KeyCode.ShiftMask }; /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithAlt => new (this) { KeyCode = KeyCode | KeyCode.AltMask }; /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithCtrl => new (this) { KeyCode = KeyCode | KeyCode.CtrlMask }; /// /// Helper for specifying a shifted . /// /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel; /// /// public Key WithShift => new (this) { KeyCode = KeyCode | KeyCode.ShiftMask }; /// /// Tests if a KeyCode represents a key in the range of to , /// regardless of the . This is useful for testing if a key is based on these keys which /// are special cased. /// /// /// 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; } /// /// Converts a to a . Useful for determining if a key represents is a /// printable character. /// /// /// Keys with Ctrl or Alt modifiers will return . /// /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether /// is set. /// /// /// If the key is outside of the range, the returned Rune 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 (Rune); } // Extract the base key code KeyCode baseKey = key; if (baseKey.HasFlag (KeyCode.ShiftMask)) { baseKey &= ~KeyCode.ShiftMask; } switch (baseKey) { case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask): return new ((uint)(baseKey + 32)); case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask): return new ((uint)baseKey); case > KeyCode.Null and < KeyCode.A: return new ((uint)baseKey); } if (Enum.IsDefined (typeof (KeyCode), baseKey)) { return default (Rune); } return new ((uint)baseKey); } #region Operators /// /// Explicitly cast a to a . The conversion is lossy because properties such /// as are not encoded in . /// /// Uses . /// public static explicit operator Rune (Key kea) { return kea.AsRune; } // BUGBUG: (Tig) I do not think this cast operator is really needed. /// /// Explicitly cast to a . The conversion is lossy because properties such /// as are not encoded in . /// /// public static explicit operator uint (Key kea) { return (uint)kea.KeyCode; } /// /// Explicitly cast to a . The conversion is lossy because properties such /// as are not encoded in . /// /// public static explicit operator KeyCode (Key key) { return key.KeyCode; } /// Cast to a . /// public static implicit operator Key (KeyCode keyCode) { return new (keyCode); } /// Cast to a . /// See for more information. /// public static implicit operator Key (char ch) { return new (ch); } /// Cast to a . /// See for more information. /// public static implicit operator Key (string str) { return new (str); } /// Cast a to a . /// See for more information. /// public static implicit operator string (Key key) { return key.ToString (); } /// public override bool Equals (object? obj) { if (obj is Key other) { return other._keyCode == _keyCode && other.Handled == Handled; } return false; } bool IEquatable.Equals (Key? other) { return Equals (other); } /// public override int GetHashCode () { return _keyCode.GetHashCode (); } /// Compares two s for equality. /// /// /// public static bool operator == (Key a, Key b) { return a!.Equals (b); } /// Compares two s for not equality. /// /// /// public static bool operator != (Key? a, Key? b) { return !a!.Equals (b); } /// Compares two s for less-than. /// /// /// public static bool operator < (Key a, Key b) { return a?.KeyCode < b?.KeyCode; } /// Compares two s for greater-than. /// /// /// public static bool operator > (Key a, Key b) { return a?.KeyCode > b?.KeyCode; } /// Compares two s for greater-than-or-equal-to. /// /// /// public static bool operator <= (Key a, Key b) { return a?.KeyCode <= b?.KeyCode; } /// Compares two s for greater-than-or-equal-to. /// /// /// public static bool operator >= (Key a, Key b) { return a?.KeyCode >= b?.KeyCode; } #endregion Operators #region String conversion /// Pretty prints the Key. /// public override string ToString () { return ToString (KeyCode, Separator); } private static string GetKeyString (KeyCode key) { if (key is KeyCode.Null or KeyCode.SpecialMask) { return string.Empty; } // Extract the base key (removing modifier flags) KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) { return ((Rune)(uint)(key + 32)).ToString (); } if (key is > KeyCode.Space and < KeyCode.A) { return ((Rune)(uint)key).ToString (); } string? keyName = Enum.GetName (typeof (KeyCode), key); return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)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) { return ToString (key, Separator); } /// Formats a as a string. /// The key to format. /// The character to use as a separator between modifier keys 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) { // Same as Key.IsValid return @"Null"; } var sb = new StringBuilder (); // Extract the base key (removing modifier flags) KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; // Extract and handle modifiers var 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)); } } return TrimEndSeparator (sb.ToString (), separator); } private static string TrimEndSeparator (string input, Rune separator) { // Trim the trailing separator (+). Unless there are two separators at the end. // "+" (don't trim) // "Ctrl+" (trim) // "Ctrl++" (trim) if (input.Length > 1 && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator) { return input [..^1]; } return input; } private static readonly Dictionary _modifierDict = new (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", "X", and "120" (Unicode codepoint). /// /// The separator can be any character, not just (e.g. "Ctrl@Alt@X"). /// /// /// The parsed value. /// A boolean value indicating whether parsing was successful. /// public static bool TryParse (string text, out Key key) { if (string.IsNullOrEmpty (text)) { key = Empty; return true; } switch (text) { case "Ctrl": key = KeyCode.CtrlMask; return true; case "Alt": key = KeyCode.AltMask; return true; case "Shift": key = KeyCode.ShiftMask; return true; } key = null!; Rune separator = Separator; // Perhaps the separator was written using a different Key.Separator? Does the string // start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator. if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [4]; } else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [3]; } else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [5]; } else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [^5]; } else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [^4]; } else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase)) { separator = (Rune)text [^6]; } // Split the string into parts using the set Separator string [] parts = text.Split ((char)separator.Value); if (parts.Length is > 4) { // Invalid return false; } // e.g. "Ctrl++" if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty)) { // Invalid return false; } if ((Rune)text [^1] == separator) { parts [^1] = separator.Value.ToString (); key = (char)separator.Value; } if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2))) { parts = text.Split ((char)separator.Value); if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty)) { // Invalid return false; } } var modifiers = KeyCode.Null; for (var index = 0; index < parts.Length; index++) { if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out KeyCode 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; var 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, NumberStyles.Integer, 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 (parsedKeyCode | KeyCode.ShiftMask); return true; } key = new (parsedKeyCode | modifiers); return true; } } if (GetIsKeyCodeAtoZ (keyCode) && (keyCode & KeyCode.Space) != 0) { keyCode &= ~KeyCode.Space; } key = new (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 (parsedKeyCode | KeyCode.ShiftMask); return true; } if (GetIsKeyCodeAtoZ (parsedKeyCode) && (parsedKeyCode & KeyCode.Space) != 0) { parsedKeyCode = parsedKeyCode & ~KeyCode.Space; } key = new (parsedKeyCode | modifiers); return true; } } // if it's a number int, treat it as a unicode value if (int.TryParse ( partNotFound, NumberStyles.Number, CultureInfo.InvariantCulture, out parsedInt )) { if (!Rune.IsValid (parsedInt)) { return false; } if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) { key = new ((KeyCode)parsedInt | KeyCode.ShiftMask); return true; } key = new ((KeyCode)parsedInt); return true; } if (!Enum.TryParse (partNotFound, true, out parsedKeyCode)) { return false; } if (GetIsKeyCodeAtoZ (parsedKeyCode)) { key = new (parsedKeyCode | (modifiers & ~KeyCode.Space)); return true; } return false; } #endregion #region Standard Key Definitions /// An uninitialized The object. public new static Key Empty => new (); /// The object for the Backspace key. public static Key Backspace => new (KeyCode.Backspace); /// The object for the tab key (forwards tab key). public static Key Tab => new (KeyCode.Tab); /// The object for the return key. public static Key Enter => new (KeyCode.Enter); /// The object for the clear key. public static Key Clear => new (KeyCode.Clear); /// The object for the Escape key. public static Key Esc => new (KeyCode.Esc); /// The object for the Space bar key. public static Key Space => new (KeyCode.Space); /// The object for 0 key. public static Key D0 => new (KeyCode.D0); /// The object for 1 key. public static Key D1 => new (KeyCode.D1); /// The object for 2 key. public static Key D2 => new (KeyCode.D2); /// The object for 3 key. public static Key D3 => new (KeyCode.D3); /// The object for 4 key. public static Key D4 => new (KeyCode.D4); /// The object for 5 key. public static Key D5 => new (KeyCode.D5); /// The object for 6 key. public static Key D6 => new (KeyCode.D6); /// The object for 7 key. public static Key D7 => new (KeyCode.D7); /// The object for 8 key. public static Key D8 => new (KeyCode.D8); /// The object for 9 key. public static Key D9 => new (KeyCode.D9); /// The object for the A key (un-shifted). Use Key.A.WithShift for uppercase 'A'. public static Key A => new (KeyCode.A); /// The object for the B key (un-shifted). Use Key.B.WithShift for uppercase 'B'. public static Key B => new (KeyCode.B); /// The object for the C key (un-shifted). Use Key.C.WithShift for uppercase 'C'. public static Key C => new (KeyCode.C); /// The object for the D key (un-shifted). Use Key.D.WithShift for uppercase 'D'. public static Key D => new (KeyCode.D); /// The object for the E key (un-shifted). Use Key.E.WithShift for uppercase 'E'. public static Key E => new (KeyCode.E); /// The object for the F key (un-shifted). Use Key.F.WithShift for uppercase 'F'. public static Key F => new (KeyCode.F); /// The object for the G key (un-shifted). Use Key.G.WithShift for uppercase 'G'. public static Key G => new (KeyCode.G); /// The object for the H key (un-shifted). Use Key.H.WithShift for uppercase 'H'. public static Key H => new (KeyCode.H); /// The object for the I key (un-shifted). Use Key.I.WithShift for uppercase 'I'. public static Key I => new (KeyCode.I); /// The object for the J key (un-shifted). Use Key.J.WithShift for uppercase 'J'. public static Key J => new (KeyCode.J); /// The object for the K key (un-shifted). Use Key.K.WithShift for uppercase 'K'. public static Key K => new (KeyCode.K); /// The object for the L key (un-shifted). Use Key.L.WithShift for uppercase 'L'. public static Key L => new (KeyCode.L); /// The object for the M key (un-shifted). Use Key.M.WithShift for uppercase 'M'. public static Key M => new (KeyCode.M); /// The object for the N key (un-shifted). Use Key.N.WithShift for uppercase 'N'. public static Key N => new (KeyCode.N); /// The object for the O key (un-shifted). Use Key.O.WithShift for uppercase 'O'. public static Key O => new (KeyCode.O); /// The object for the P key (un-shifted). Use Key.P.WithShift for uppercase 'P'. public static Key P => new (KeyCode.P); /// The object for the Q key (un-shifted). Use Key.Q.WithShift for uppercase 'Q'. public static Key Q => new (KeyCode.Q); /// The object for the R key (un-shifted). Use Key.R.WithShift for uppercase 'R'. public static Key R => new (KeyCode.R); /// The object for the S key (un-shifted). Use Key.S.WithShift for uppercase 'S'. public static Key S => new (KeyCode.S); /// The object for the T key (un-shifted). Use Key.T.WithShift for uppercase 'T'. public static Key T => new (KeyCode.T); /// The object for the U key (un-shifted). Use Key.U.WithShift for uppercase 'U'. public static Key U => new (KeyCode.U); /// The object for the V key (un-shifted). Use Key.V.WithShift for uppercase 'V'. public static Key V => new (KeyCode.V); /// The object for the W key (un-shifted). Use Key.W.WithShift for uppercase 'W'. public static Key W => new (KeyCode.W); /// The object for the X key (un-shifted). Use Key.X.WithShift for uppercase 'X'. public static Key X => new (KeyCode.X); /// The object for the Y key (un-shifted). Use Key.Y.WithShift for uppercase 'Y'. public static Key Y => new (KeyCode.Y); /// The object for the Z key (un-shifted). Use Key.Z.WithShift for uppercase 'Z'. public static Key Z => new (KeyCode.Z); /// The object for the Delete key. public static Key Delete => new (KeyCode.Delete); /// The object for the Cursor up key. public static Key CursorUp => new (KeyCode.CursorUp); /// The object for Cursor down key. public static Key CursorDown => new (KeyCode.CursorDown); /// The object for Cursor left key. public static Key CursorLeft => new (KeyCode.CursorLeft); /// The object for Cursor right key. public static Key CursorRight => new (KeyCode.CursorRight); /// The object for Page Up key. public static Key PageUp => new (KeyCode.PageUp); /// The object for Page Down key. public static Key PageDown => new (KeyCode.PageDown); /// The object for Home key. public static Key Home => new (KeyCode.Home); /// The object for End key. public static Key End => new (KeyCode.End); /// The object for Insert Character key. public static Key InsertChar => new (KeyCode.Insert); /// The object for Delete Character key. public static Key DeleteChar => new (KeyCode.Delete); /// The object for Print Screen key. public static Key PrintScreen => new (KeyCode.PrintScreen); /// The object for F1 key. public static Key F1 => new (KeyCode.F1); /// The object for F2 key. public static Key F2 => new (KeyCode.F2); /// The object for F3 key. public static Key F3 => new (KeyCode.F3); /// The object for F4 key. public static Key F4 => new (KeyCode.F4); /// The object for F5 key. public static Key F5 => new (KeyCode.F5); /// The object for F6 key. public static Key F6 => new (KeyCode.F6); /// The object for F7 key. public static Key F7 => new (KeyCode.F7); /// The object for F8 key. public static Key F8 => new (KeyCode.F8); /// The object for F9 key. public static Key F9 => new (KeyCode.F9); /// The object for F10 key. public static Key F10 => new (KeyCode.F10); /// The object for F11 key. public static Key F11 => new (KeyCode.F11); /// The object for F12 key. public static Key F12 => new (KeyCode.F12); /// The object for F13 key. public static Key F13 => new (KeyCode.F13); /// The object for F14 key. public static Key F14 => new (KeyCode.F14); /// The object for F15 key. public static Key F15 => new (KeyCode.F15); /// The object for F16 key. public static Key F16 => new (KeyCode.F16); /// The object for F17 key. public static Key F17 => new (KeyCode.F17); /// The object for F18 key. public static Key F18 => new (KeyCode.F18); /// The object for F19 key. public static Key F19 => new (KeyCode.F19); /// The object for F20 key. public static Key F20 => new (KeyCode.F20); /// The object for F21 key. public static Key F21 => new (KeyCode.F21); /// The object for F22 key. public static Key F22 => new (KeyCode.F22); /// The object for F23 key. public static Key F23 => new (KeyCode.F23); /// The object for F24 key. public static Key F24 => new (KeyCode.F24); #endregion private static Rune _separator = new ('+'); /// Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static Rune Separator { get => _separator; set { if (_separator != value) { _separator = value == default (Rune) ? new ('+') : value; } } } }