using System;
using System.Collections.Generic;
using System.Linq;
using BepInEx.Logging;
using UnityEngine;
using Logger = BepInEx.Logging.Logger;
namespace BepInEx.Configuration
{
///
/// 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, F + LeftCtrl 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
/// to allow user to change this shortcut and have the changes saved.
///
/// How to use: Use in this class instead of in the Update loop.
///
public struct KeyboardShortcut
{
static KeyboardShortcut()
{
TomlTypeConverter.AddConverter(
typeof(KeyboardShortcut),
new TypeConverter
{
ConvertToString = (o, type) => ((KeyboardShortcut)o).Serialize(),
ConvertToObject = (s, type) => Deserialize(s)
});
}
///
/// Shortcut that never triggers.
///
public static readonly KeyboardShortcut Empty = new KeyboardShortcut();
///
/// All KeyCode values that can be used in a keyboard shortcut.
///
public static readonly IEnumerable 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;
///
/// Create a new keyboard shortcut.
///
/// Main key to press
/// Keys that should be held down before main key is registered
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();
}
///
/// 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, is returned.
///
public KeyCode MainKey => _allKeys != null && _allKeys.Length > 0 ? _allKeys[0] : KeyCode.None;
///
/// Modifiers of the key combination, if any.
///
public IEnumerable Modifiers => _allKeys?.Skip(1) ?? Enumerable.Empty();
///
/// Attempt to deserialize key combination from the string.
///
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;
}
}
///
/// Serialize the key combination into a user readable string.
///
public string Serialize()
{
if (_allKeys == null) return string.Empty;
return string.Join(" + ", _allKeys.Select(x => x.ToString()).ToArray());
}
///
/// Check if the main key was just pressed (Input.GetKeyDown), and specified modifier keys are all pressed
///
public bool IsDown()
{
var mainKey = MainKey;
if (mainKey == KeyCode.None) return false;
return Input.GetKeyDown(mainKey) && ModifierKeyTest();
}
///
/// Check if the main key is currently held down (Input.GetKey), and specified modifier keys are all pressed
///
public bool IsPressed()
{
var mainKey = MainKey;
if (mainKey == KeyCode.None) return false;
return Input.GetKey(mainKey) && ModifierKeyTest();
}
///
/// Check if the main key was just lifted (Input.GetKeyUp), and specified modifier keys are all pressed.
///
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;
}
///
public override string ToString()
{
if (MainKey == KeyCode.None) return "Not set";
return string.Join(" + ", _allKeys.Select(c => c.ToString()).ToArray());
}
///
public override bool Equals(object obj)
{
return obj is KeyboardShortcut shortcut && MainKey == shortcut.MainKey && Modifiers.SequenceEqual(shortcut.Modifiers);
}
///
public override int GetHashCode()
{
if (MainKey == KeyCode.None) return 0;
return _allKeys.Aggregate(_allKeys.Length, (current, item) => unchecked(current * 31 + (int)item));
}
}
}