KeyboardShortcut.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. public KeyboardShortcut()
  53. {
  54. _allKeys = SanitizeKeys();
  55. _allKeysLookup = new HashSet<KeyCode>(_allKeys);
  56. }
  57. private static KeyCode[] SanitizeKeys(params KeyCode[] keys)
  58. {
  59. if (keys.Length == 0 || keys[0] == KeyCode.None)
  60. return new[] { KeyCode.None };
  61. return new[] { keys[0] }.Concat(keys.Skip(1).Distinct().Where(x => x != keys[0]).OrderBy(x => (int)x)).ToArray();
  62. }
  63. /// <summary>
  64. /// Main key of the key combination. It has to be pressed / let go last for the combination to be triggered.
  65. /// If the combination is empty, <see cref="KeyCode.None"/> is returned.
  66. /// </summary>
  67. public KeyCode MainKey => _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;
  68. /// <summary>
  69. /// Modifiers of the key combination, if any.
  70. /// </summary>
  71. public IEnumerable<KeyCode> Modifiers => _allKeys.Skip(1);
  72. /// <summary>
  73. /// Attempt to deserialize key combination from the string.
  74. /// </summary>
  75. public static KeyboardShortcut Deserialize(string str)
  76. {
  77. try
  78. {
  79. var parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
  80. .Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray();
  81. return new KeyboardShortcut(parts);
  82. }
  83. catch (SystemException ex)
  84. {
  85. Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message);
  86. return new KeyboardShortcut();
  87. }
  88. }
  89. /// <summary>
  90. /// Serialize the key combination into a user readable string.
  91. /// </summary>
  92. public string Serialize()
  93. {
  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. return AllKeyCodes.All(c =>
  126. {
  127. if (_allKeysLookup.Contains(c))
  128. {
  129. if (_allKeys[0] == c)
  130. return true;
  131. return Input.GetKey(c);
  132. }
  133. return !Input.GetKey(c);
  134. });
  135. }
  136. /// <inheritdoc />
  137. public override string ToString()
  138. {
  139. if (MainKey == KeyCode.None) return "Not set";
  140. return string.Join(" + ", _allKeys.Select(c => c.ToString()).ToArray());
  141. }
  142. /// <inheritdoc />
  143. public override bool Equals(object obj)
  144. {
  145. return obj is KeyboardShortcut shortcut && _allKeys.SequenceEqual(shortcut._allKeys);
  146. }
  147. /// <inheritdoc />
  148. public override int GetHashCode()
  149. {
  150. var hc = _allKeys.Length;
  151. for (var i = 0; i < _allKeys.Length; i++)
  152. hc = unchecked(hc * 31 + (int)_allKeys[i]);
  153. return hc;
  154. }
  155. }
  156. }