KeyboardShortcut.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using BepInEx.Logging;
  5. using UnityEngine;
  6. using Logger = BepInEx.Logging.Logger;
  7. namespace BepInEx.Configuration
  8. {
  9. /// <summary>
  10. /// A keyboard shortcut that can be used in Update method to check if user presses a key combo. The shortcut is only
  11. /// triggered when the user presses the exact combination. For example, <c>F + LeftCtrl</c> will trigger only if user
  12. /// presses and holds only LeftCtrl, and then presses F. If any other keys are pressed, the shortcut will not trigger.
  13. ///
  14. /// Can be used as a value of a setting in <see cref="ConfigFile.GetSetting{T}(ConfigDefinition,T,ConfigDescription)"/>
  15. /// to allow user to change this shortcut and have the changes saved.
  16. ///
  17. /// How to use: Use <see cref="IsDown"/> in this class instead of <see cref="Input.GetKeyDown(KeyCode)"/> in the Update loop.
  18. /// </summary>
  19. public struct KeyboardShortcut
  20. {
  21. static KeyboardShortcut()
  22. {
  23. TomlTypeConverter.AddConverter(
  24. typeof(KeyboardShortcut),
  25. new TypeConverter
  26. {
  27. ConvertToString = (o, type) => ((KeyboardShortcut)o).Serialize(),
  28. ConvertToObject = (s, type) => Deserialize(s)
  29. });
  30. }
  31. /// <summary>
  32. /// Shortcut that never triggers.
  33. /// </summary>
  34. public static readonly KeyboardShortcut Empty = new KeyboardShortcut();
  35. /// <summary>
  36. /// All KeyCode values that can be used in a keyboard shortcut.
  37. /// </summary>
  38. public static readonly IEnumerable<KeyCode> AllKeyCodes = (KeyCode[])Enum.GetValues(typeof(KeyCode));
  39. private readonly KeyCode[] _allKeys;
  40. private readonly HashSet<KeyCode> _allKeysLookup;
  41. /// <summary>
  42. /// Create a new keyboard shortcut.
  43. /// </summary>
  44. /// <param name="mainKey">Main key to press</param>
  45. /// <param name="modifiers">Keys that should be held down before main key is registered</param>
  46. public KeyboardShortcut(KeyCode mainKey, params KeyCode[] modifiers) : this(new[] { mainKey }.Concat(modifiers).ToArray())
  47. {
  48. if (mainKey == KeyCode.None && modifiers.Any())
  49. throw new ArgumentException($"Can't set {nameof(mainKey)} to KeyCode.None if there are any {nameof(modifiers)}");
  50. }
  51. private KeyboardShortcut(KeyCode[] keys)
  52. {
  53. _allKeys = SanitizeKeys(keys);
  54. _allKeysLookup = new HashSet<KeyCode>(_allKeys);
  55. }
  56. private static KeyCode[] SanitizeKeys(params KeyCode[] keys)
  57. {
  58. if (keys.Length == 0 || keys[0] == KeyCode.None)
  59. return new[] { KeyCode.None };
  60. return new[] { keys[0] }.Concat(keys.Skip(1).Distinct().Where(x => x != keys[0]).OrderBy(x => (int)x)).ToArray();
  61. }
  62. /// <summary>
  63. /// Main key of the key combination. It has to be pressed / let go last for the combination to be triggered.
  64. /// If the combination is empty, <see cref="KeyCode.None"/> is returned.
  65. /// </summary>
  66. public KeyCode MainKey => _allKeys != null && _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;
  67. /// <summary>
  68. /// Modifiers of the key combination, if any.
  69. /// </summary>
  70. public IEnumerable<KeyCode> Modifiers => _allKeys?.Skip(1) ?? Enumerable.Empty<KeyCode>();
  71. /// <summary>
  72. /// Attempt to deserialize key combination from the string.
  73. /// </summary>
  74. public static KeyboardShortcut Deserialize(string str)
  75. {
  76. try
  77. {
  78. var parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
  79. .Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray();
  80. return new KeyboardShortcut(parts);
  81. }
  82. catch (SystemException ex)
  83. {
  84. Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message);
  85. return Empty;
  86. }
  87. }
  88. /// <summary>
  89. /// Serialize the key combination into a user readable string.
  90. /// </summary>
  91. public string Serialize()
  92. {
  93. if (_allKeys == null) return string.Empty;
  94. return string.Join(" + ", _allKeys.Select(x => x.ToString()).ToArray());
  95. }
  96. /// <summary>
  97. /// Check if the main key was just pressed (Input.GetKeyDown), and specified modifier keys are all pressed
  98. /// </summary>
  99. public bool IsDown()
  100. {
  101. var mainKey = MainKey;
  102. if (mainKey == KeyCode.None) return false;
  103. return Input.GetKeyDown(mainKey) && ModifierKeyTest();
  104. }
  105. /// <summary>
  106. /// Check if the main key is currently held down (Input.GetKey), and specified modifier keys are all pressed
  107. /// </summary>
  108. public bool IsPressed()
  109. {
  110. var mainKey = MainKey;
  111. if (mainKey == KeyCode.None) return false;
  112. return Input.GetKey(mainKey) && ModifierKeyTest();
  113. }
  114. /// <summary>
  115. /// Check if the main key was just lifted (Input.GetKeyUp), and specified modifier keys are all pressed.
  116. /// </summary>
  117. public bool IsUp()
  118. {
  119. var mainKey = MainKey;
  120. if (mainKey == KeyCode.None) return false;
  121. return Input.GetKeyUp(mainKey) && ModifierKeyTest();
  122. }
  123. private bool ModifierKeyTest()
  124. {
  125. var lookup = _allKeysLookup;
  126. var mainKey = MainKey;
  127. return AllKeyCodes.All(c =>
  128. {
  129. if (lookup.Contains(c))
  130. {
  131. if (mainKey == c)
  132. return true;
  133. return Input.GetKey(c);
  134. }
  135. return !Input.GetKey(c);
  136. });
  137. }
  138. /// <inheritdoc />
  139. public override string ToString()
  140. {
  141. if (MainKey == KeyCode.None) return "Not set";
  142. return string.Join(" + ", _allKeys.Select(c => c.ToString()).ToArray());
  143. }
  144. /// <inheritdoc />
  145. public override bool Equals(object obj)
  146. {
  147. return obj is KeyboardShortcut shortcut && MainKey == shortcut.MainKey && Modifiers.SequenceEqual(shortcut.Modifiers);
  148. }
  149. /// <inheritdoc />
  150. public override int GetHashCode()
  151. {
  152. if (MainKey == KeyCode.None) return 0;
  153. return _allKeys.Aggregate(_allKeys.Length, (current, item) => unchecked(current * 31 + (int)item));
  154. }
  155. }
  156. }