123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using BepInEx.Logging;
- using UnityEngine;
- using Logger = BepInEx.Logging.Logger;
- namespace BepInEx.Configuration
- {
- /// <summary>
- /// A keyboard shortcut that can be used in Update method to check if user presses a key combo. The shortcut is only
- /// triggered when the user presses the exact combination. For example, <c>F + LeftCtrl</c> will trigger only if user
- /// presses and holds only LeftCtrl, and then presses F. If any other keys are pressed, the shortcut will not trigger.
- ///
- /// Can be used as a value of a setting in <see cref="ConfigFile.Bind{T}(ConfigDefinition,T,ConfigDescription)"/>
- /// to allow user to change this shortcut and have the changes saved.
- ///
- /// How to use: Use <see cref="IsDown"/> in this class instead of <see cref="Input.GetKeyDown(KeyCode)"/> in the Update loop.
- /// </summary>
- public struct KeyboardShortcut
- {
- static KeyboardShortcut()
- {
- TomlTypeConverter.AddConverter(
- typeof(KeyboardShortcut),
- new TypeConverter
- {
- ConvertToString = (o, type) => ((KeyboardShortcut)o).Serialize(),
- ConvertToObject = (s, type) => Deserialize(s)
- });
- }
- /// <summary>
- /// Shortcut that never triggers.
- /// </summary>
- public static readonly KeyboardShortcut Empty = new KeyboardShortcut();
- /// <summary>
- /// All KeyCode values that can be used in a keyboard shortcut.
- /// </summary>
- public static readonly IEnumerable<KeyCode> AllKeyCodes = (KeyCode[])Enum.GetValues(typeof(KeyCode));
- // Don't block hotkeys if mouse is being pressed, e.g. when shooting and trying to strafe
- private static readonly KeyCode[] _modifierBlockKeyCodes = AllKeyCodes.Except(new[] { KeyCode.Mouse0, KeyCode.Mouse1, KeyCode.Mouse2, KeyCode.Mouse3, KeyCode.Mouse4, KeyCode.Mouse5, KeyCode.Mouse6, KeyCode.None }).ToArray();
- private readonly KeyCode[] _allKeys;
- /// <summary>
- /// Create a new keyboard shortcut.
- /// </summary>
- /// <param name="mainKey">Main key to press</param>
- /// <param name="modifiers">Keys that should be held down before main key is registered</param>
- public KeyboardShortcut(KeyCode mainKey, params KeyCode[] modifiers) : this(new[] { mainKey }.Concat(modifiers).ToArray())
- {
- if (mainKey == KeyCode.None && modifiers.Any())
- throw new ArgumentException($"Can't set {nameof(mainKey)} to KeyCode.None if there are any {nameof(modifiers)}");
- }
- private KeyboardShortcut(KeyCode[] keys)
- {
- _allKeys = SanitizeKeys(keys);
- }
- private static KeyCode[] SanitizeKeys(params KeyCode[] keys)
- {
- if (keys.Length == 0 || keys[0] == KeyCode.None)
- return new[] { KeyCode.None };
- return new[] { keys[0] }.Concat(keys.Skip(1).Distinct().Where(x => x != keys[0]).OrderBy(x => (int)x)).ToArray();
- }
- /// <summary>
- /// Main key of the key combination. It has to be pressed / let go last for the combination to be triggered.
- /// If the combination is empty, <see cref="KeyCode.None"/> is returned.
- /// </summary>
- public KeyCode MainKey => _allKeys != null && _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;
- /// <summary>
- /// Modifiers of the key combination, if any.
- /// </summary>
- public IEnumerable<KeyCode> Modifiers => _allKeys?.Skip(1) ?? Enumerable.Empty<KeyCode>();
- /// <summary>
- /// Attempt to deserialize key combination from the string.
- /// </summary>
- public static KeyboardShortcut Deserialize(string str)
- {
- try
- {
- var parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray();
- return new KeyboardShortcut(parts);
- }
- catch (SystemException ex)
- {
- Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message);
- return Empty;
- }
- }
- /// <summary>
- /// Serialize the key combination into a user readable string.
- /// </summary>
- public string Serialize()
- {
- if (_allKeys == null) return string.Empty;
- return string.Join(" + ", _allKeys.Select(x => x.ToString()).ToArray());
- }
- /// <summary>
- /// Check if the main key was just pressed (Input.GetKeyDown), and specified modifier keys are all pressed
- /// </summary>
- public bool IsDown()
- {
- var mainKey = MainKey;
- if (mainKey == KeyCode.None) return false;
- return Input.GetKeyDown(mainKey) && ModifierKeyTest();
- }
- /// <summary>
- /// Check if the main key is currently held down (Input.GetKey), and specified modifier keys are all pressed
- /// </summary>
- public bool IsPressed()
- {
- var mainKey = MainKey;
- if (mainKey == KeyCode.None) return false;
- return Input.GetKey(mainKey) && ModifierKeyTest();
- }
- /// <summary>
- /// Check if the main key was just lifted (Input.GetKeyUp), and specified modifier keys are all pressed.
- /// </summary>
- public bool IsUp()
- {
- var mainKey = MainKey;
- if (mainKey == KeyCode.None) return false;
- return Input.GetKeyUp(mainKey) && ModifierKeyTest();
- }
- private bool ModifierKeyTest()
- {
- var allKeys = _allKeys;
- var mainKey = MainKey;
- bool allModifiersPressed = allKeys.All(c => c == mainKey || Input.GetKey(c));
- if (!allModifiersPressed) return false;
- bool noOtherModifiersPressed = _modifierBlockKeyCodes.All(c => !Input.GetKey(c) || allKeys.Contains(c));
- return noOtherModifiersPressed;
- }
- /// <inheritdoc />
- public override string ToString()
- {
- if (MainKey == KeyCode.None) return "Not set";
- return string.Join(" + ", _allKeys.Select(c => c.ToString()).ToArray());
- }
- /// <inheritdoc />
- public override bool Equals(object obj)
- {
- return obj is KeyboardShortcut shortcut && MainKey == shortcut.MainKey && Modifiers.SequenceEqual(shortcut.Modifiers);
- }
- /// <inheritdoc />
- public override int GetHashCode()
- {
- if (MainKey == KeyCode.None) return 0;
- return _allKeys.Aggregate(_allKeys.Length, (current, item) => unchecked(current * 31 + (int)item));
- }
- }
- }
|