using System; using System.Collections.Generic; using System.Text; using Avalonia.Utilities; namespace Avalonia.Input { /// /// Defines a keyboard input combination. /// public sealed class KeyGesture : IEquatable { private static readonly Dictionary s_keySynonyms = new Dictionary { { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma } }; public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None) { Key = key; KeyModifiers = modifiers; } public bool Equals(KeyGesture? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Key == other.Key && KeyModifiers == other.KeyModifiers; } public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; return obj is KeyGesture gesture && Equals(gesture); } public override int GetHashCode() { unchecked { return ((int)Key * 397) ^ (int)KeyModifiers; } } public static bool operator ==(KeyGesture? left, KeyGesture? right) { return Equals(left, right); } public static bool operator !=(KeyGesture? left, KeyGesture? right) { return !Equals(left, right); } public Key Key { get; } public KeyModifiers KeyModifiers { get; } public static KeyGesture Parse(string gesture) { // string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture var key = Key.None; var keyModifiers = KeyModifiers.None; var cstart = 0; for (var c = 0; c <= gesture.Length; c++) { var ch = c == gesture.Length ? '\0' : gesture[c]; bool isLast = c == gesture.Length; if (isLast || (ch == '+' && cstart != c)) { var partSpan = gesture.AsSpan(cstart, c - cstart).Trim(); if (isLast) { key = ParseKey(partSpan.ToString()); } else { keyModifiers |= ParseModifier(partSpan); } cstart = c + 1; } } return new KeyGesture(key, keyModifiers); } public override string ToString() { var s = StringBuilderCache.Acquire(); static void Plus(StringBuilder s) { if (s.Length > 0) { s.Append("+"); } } if (KeyModifiers.HasAllFlags(KeyModifiers.Control)) { s.Append("Ctrl"); } if (KeyModifiers.HasAllFlags(KeyModifiers.Shift)) { Plus(s); s.Append("Shift"); } if (KeyModifiers.HasAllFlags(KeyModifiers.Alt)) { Plus(s); s.Append("Alt"); } if (KeyModifiers.HasAllFlags(KeyModifiers.Meta)) { Plus(s); s.Append("Cmd"); } Plus(s); s.Append(Key); return StringBuilderCache.GetStringAndRelease(s); } public bool Matches(KeyEventArgs keyEvent) => keyEvent != null && keyEvent.KeyModifiers == KeyModifiers && ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); // TODO: Move that to external key parser private static Key ParseKey(string key) { if (s_keySynonyms.TryGetValue(key.ToLower(), out Key rv)) return rv; return EnumHelper.Parse(key, true); } private static KeyModifiers ParseModifier(ReadOnlySpan modifier) { if (modifier.Equals("ctrl".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return KeyModifiers.Control; } if (modifier.Equals("cmd".AsSpan(), StringComparison.OrdinalIgnoreCase) || modifier.Equals("win".AsSpan(), StringComparison.OrdinalIgnoreCase) || modifier.Equals("⌘".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return KeyModifiers.Meta; } return EnumHelper.Parse(modifier.ToString(), true); } private Key ResolveNumPadOperationKey(Key key) { switch (key) { case Key.Add: return Key.OemPlus; case Key.Subtract: return Key.OemMinus; case Key.Decimal: return Key.OemPeriod; default: return key; } } } }