KeyboardShortcut.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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.Wrap{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 class KeyboardShortcut
  20. {
  21. static KeyboardShortcut()
  22. {
  23. TomlTypeConverter.AddConverter(
  24. typeof(KeyboardShortcut),
  25. new TypeConverter
  26. {
  27. ConvertToString = (o, type) => (o as KeyboardShortcut)?.Serialize(),
  28. ConvertToObject = (s, type) => Deserialize(s)
  29. });
  30. }
  31. /// <summary>
  32. /// All KeyCode values that can be used in a keyboard shortcut.
  33. /// </summary>
  34. public static readonly IEnumerable<KeyCode> AllKeyCodes = (KeyCode[])Enum.GetValues(typeof(KeyCode));
  35. private readonly KeyCode[] _allKeys;
  36. private readonly HashSet<KeyCode> _allKeysLookup;
  37. /// <summary>
  38. /// Create a new keyboard shortcut.
  39. /// </summary>
  40. /// <param name="mainKey">Main key to press</param>
  41. /// <param name="modifiers">Keys that should be held down before main key is registered</param>
  42. public KeyboardShortcut(KeyCode mainKey, params KeyCode[] modifiers) : this(new[] { mainKey }.Concat(modifiers).ToArray())
  43. {
  44. if (mainKey == KeyCode.None && modifiers.Any())
  45. throw new ArgumentException($"Can't set {nameof(mainKey)} to KeyCode.None if there are any {nameof(modifiers)}");
  46. }
  47. private KeyboardShortcut(KeyCode[] keys)
  48. {
  49. _allKeys = SanitizeKeys(keys);
  50. _allKeysLookup = new HashSet<KeyCode>(_allKeys);
  51. }
  52. /// <summary>
  53. /// Create a new empty shortcut.
  54. /// </summary>
  55. public KeyboardShortcut()
  56. {
  57. _allKeys = SanitizeKeys();
  58. _allKeysLookup = new HashSet<KeyCode>(_allKeys);
  59. }
  60. private static KeyCode[] SanitizeKeys(params KeyCode[] keys)
  61. {
  62. if (keys.Length == 0 || keys[0] == KeyCode.None)
  63. return new[] { KeyCode.None };
  64. return new[] { keys[0] }.Concat(keys.Skip(1).Distinct().Where(x => x != keys[0]).OrderBy(x => (int)x)).ToArray();
  65. }
  66. /// <summary>
  67. /// Main key of the key combination. It has to be pressed / let go last for the combination to be triggered.
  68. /// If the combination is empty, <see cref="KeyCode.None"/> is returned.
  69. /// </summary>
  70. public KeyCode MainKey => _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;
  71. /// <summary>
  72. /// Modifiers of the key combination, if any.
  73. /// </summary>
  74. public IEnumerable<KeyCode> Modifiers => _allKeys.Skip(1);
  75. /// <summary>
  76. /// Attempt to deserialize key combination from the string.
  77. /// </summary>
  78. public static KeyboardShortcut Deserialize(string str)
  79. {
  80. try
  81. {
  82. var parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
  83. .Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray();
  84. return new KeyboardShortcut(parts);
  85. }
  86. catch (SystemException ex)
  87. {
  88. Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message);
  89. return new KeyboardShortcut();
  90. }
  91. }
  92. /// <summary>
  93. /// Serialize the key combination into a user readable string.
  94. /// </summary>
  95. public string Serialize()
  96. {
  97. return string.Join(" + ", _allKeys.Select(x => x.ToString()).ToArray());
  98. }
  99. /// <summary>
  100. /// Check if the main key was just pressed (Input.GetKeyDown), and specified modifier keys are all pressed
  101. /// </summary>
  102. public bool IsDown()
  103. {
  104. var mainKey = MainKey;
  105. if (mainKey == KeyCode.None) return false;
  106. return Input.GetKeyDown(mainKey) && ModifierKeyTest();
  107. }
  108. /// <summary>
  109. /// Check if the main key is currently held down (Input.GetKey), and specified modifier keys are all pressed
  110. /// </summary>
  111. public bool IsPressed()
  112. {
  113. var mainKey = MainKey;
  114. if (mainKey == KeyCode.None) return false;
  115. return Input.GetKey(mainKey) && ModifierKeyTest();
  116. }
  117. /// <summary>
  118. /// Check if the main key was just lifted (Input.GetKeyUp), and specified modifier keys are all pressed.
  119. /// </summary>
  120. public bool IsUp()
  121. {
  122. var mainKey = MainKey;
  123. if (mainKey == KeyCode.None) return false;
  124. return Input.GetKeyUp(mainKey) && ModifierKeyTest();
  125. }
  126. private bool ModifierKeyTest()
  127. {
  128. return AllKeyCodes.All(c =>
  129. {
  130. if (_allKeysLookup.Contains(c))
  131. {
  132. if (_allKeys[0] == c)
  133. return true;
  134. return Input.GetKey(c);
  135. }
  136. return !Input.GetKey(c);
  137. });
  138. }
  139. /// <inheritdoc />
  140. public override string ToString()
  141. {
  142. if (MainKey == KeyCode.None) return "Not set";
  143. return string.Join(" + ", _allKeys.Select(c => c.ToString()).ToArray());
  144. }
  145. /// <inheritdoc />
  146. public override bool Equals(object obj)
  147. {
  148. return obj is KeyboardShortcut shortcut && _allKeys.SequenceEqual(shortcut._allKeys);
  149. }
  150. /// <inheritdoc />
  151. public override int GetHashCode()
  152. {
  153. var hc = _allKeys.Length;
  154. for (var i = 0; i < _allKeys.Length; i++)
  155. hc = unchecked(hc * 31 + (int)_allKeys[i]);
  156. return hc;
  157. }
  158. }
  159. }