Files
BepInEx/Runtimes/Unity/BepInEx.Unity.Mono/Configuration/KeyboardShortcut.cs
T

183 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;
using Logger = BepInEx.Logging.Logger;
namespace BepInEx.Unity.Mono.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();
/// <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;
var allModifiersPressed = allKeys.All(c => c == mainKey || Input.GetKey(c));
if (!allModifiersPressed) return false;
var 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) =>
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));
}
}