// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; [assembly: InternalsVisibleTo("Microsoft.MixedReality.Toolkit.Services.InputSimulation.Editor")] namespace Microsoft.MixedReality.Toolkit.Input { /// /// Identifier of a key combination or mouse button for generic input binding. /// /// /// This encodes either a KeyCode with optional modifiers or a mouse button index. /// [System.Serializable] public struct KeyBinding { /// /// The type of value encoded in the property. /// public enum KeyType : int { None = 0, Mouse = 1, Key = 2, } /// /// Enum for interpreting the mouse button integer index. /// public enum MouseButton : int { Left = 0, Right = 1, Middle = 2, Button3 = 3, Button4 = 4, Button5 = 5, Button6 = 6, Button7 = 7, } // Array of names to use for a combined enum selection. internal static readonly string[] AllCodeNames; // Maps (KeyType, code) combination onto the contiguous index used for enums. // Value can be used as index in AllCodeNames array. internal static readonly Dictionary, int> KeyBindingToEnumMap; // Maps enum index to a KeyBinding, for assignment after selecting an enum value. internal static readonly Dictionary> EnumToKeyBindingMap; // Static constructor to initialize static fields static KeyBinding() { KeyCode[] KeyCodeValues = (KeyCode[])Enum.GetValues(typeof(KeyCode)); MouseButton[] MouseButtonValues = (MouseButton[])Enum.GetValues(typeof(MouseButton)); // Build maps for converting between int enum value and KeyBinding values { KeyBindingToEnumMap = new Dictionary, int>(); EnumToKeyBindingMap = new Dictionary>(); List names = new List(); int index = 0; Action AddEnumValue = (bindingType, code) => { var kb = new KeyBinding() { bindingType = bindingType, code = code }; names.Add(kb.ToString()); EnumToKeyBindingMap[index] = Tuple.Create(bindingType, code); KeyBindingToEnumMap[Tuple.Create(bindingType, code)] = index; ++index; }; AddEnumValue(KeyType.None, 0); foreach (MouseButton mb in MouseButtonValues) { AddEnumValue(KeyType.Mouse, (int)mb); } foreach (KeyCode kc in KeyCodeValues) { AddEnumValue(KeyType.Key, (int)kc); } AllCodeNames = names.ToArray(); } } [SerializeField] private KeyType bindingType; /// /// Type of input this binding maps to. /// public KeyType BindingType => bindingType; // Internal binding code. // This can be a KeyCode or mouse button index, depending on the bindingType; [SerializeField] private int code; public override string ToString() { string s = ""; s += bindingType.ToString(); switch (bindingType) { case KeyType.Key: s += ": " + ((KeyCode)code).ToString(); break; case KeyType.Mouse: s += ": " + ((MouseButton)code).ToString(); break; } return s; } /// /// Try to convert the binding to a KeyCode. /// /// True if the binding is a keyboard key public bool TryGetKeyCode(out KeyCode keyCode) { keyCode = (KeyCode)code; return bindingType == KeyType.Key; } /// /// Try to convert the binding to a mouse button. /// /// True if the binding is a mouse button public bool TryGetMouseButton(out int mouseButton) { mouseButton = code; return bindingType == KeyType.Mouse; } /// /// Try to convert the binding to a mouse button. /// /// True if the binding is a mouse button public bool TryGetMouseButton(out MouseButton mouseButton) { if (TryGetMouseButton(out int iMouseButton)) { mouseButton = (MouseButton)iMouseButton; return true; } mouseButton = MouseButton.Left; return false; } /// /// Create a default empty binding. /// public static KeyBinding Unbound() { KeyBinding kb = new KeyBinding(); kb.bindingType = KeyType.None; kb.code = 0; return kb; } /// /// Create a binding for a keyboard key. /// public static KeyBinding FromKey(KeyCode keyCode) { KeyBinding kb = new KeyBinding(); kb.bindingType = KeyType.Key; kb.code = (int)keyCode; return kb; } /// /// Create a binding for a mouse button. /// public static KeyBinding FromMouseButton(int mouseButton) { KeyBinding kb = new KeyBinding(); kb.bindingType = KeyType.Mouse; kb.code = mouseButton; return kb; } /// /// Create a binding for a mouse button. /// public static KeyBinding FromMouseButton(MouseButton mouseButton) { return FromMouseButton((int)mouseButton); } } /// /// Utility class to poll input for key bindings and to simulate key presses /// Need to add mechanisms to poll and simulate input axis: https://github.com/microsoft/MixedRealityToolkit-Unity/issues/7659 /// public static class KeyInputSystem { private static bool isSimulated; public static bool SimulatingInput => isSimulated; private static HashSet SimulatedMouseDownSet; private static HashSet SimulatedKeyDownSet; private static HashSet SimulatedMouseSet; private static HashSet SimulatedKeySet; private static HashSet SimulatedMouseUpSet; private static HashSet SimulatedKeyUpSet; /// /// Starts the key input simulation. Inputs can now be simulated via and /// public static void StartKeyInputStimulation() { ResetKeyInputSimulation(); isSimulated = true; } /// /// Stops the key input simulation /// public static void StopKeyInputSimulation() { isSimulated = false; } /// /// Resets the key input simulation. All keys will not trigger , , or /// public static void ResetKeyInputSimulation() { SimulatedMouseDownSet = new HashSet(); SimulatedKeyDownSet = new HashSet(); SimulatedMouseSet = new HashSet(); SimulatedKeySet = new HashSet(); SimulatedMouseUpSet = new HashSet(); SimulatedKeyUpSet = new HashSet(); } /// /// Presses a key. and will be true for the keybinding. /// will no longer be true for the keybinding. /// public static void PressKey(KeyBinding kb) { if (kb.TryGetMouseButton(out int mouseButton)) { SimulatedMouseDownSet.Add(mouseButton); SimulatedMouseSet.Add(mouseButton); SimulatedMouseUpSet.Remove(mouseButton); } if (kb.TryGetKeyCode(out KeyCode keyCode)) { SimulatedKeyDownSet.Add(keyCode); SimulatedKeySet.Add(keyCode); SimulatedKeyUpSet.Remove(keyCode); } } /// /// Releases a key. will be true for the keybinding. /// and will no longer be true for the keybinding. /// public static void ReleaseKey(KeyBinding kb) { if (kb.TryGetMouseButton(out int mouseButton)) { SimulatedMouseDownSet.Remove(mouseButton); SimulatedMouseSet.Remove(mouseButton); SimulatedMouseUpSet.Add(mouseButton); } if (kb.TryGetKeyCode(out KeyCode keyCode)) { SimulatedKeyDownSet.Remove(keyCode); SimulatedKeySet.Remove(keyCode); SimulatedKeyUpSet.Add(keyCode); } } /// /// Advances the Key press simulation by 1 frame. Keybindings will no longer trigger or /// public static void AdvanceSimulation() { // keys that were just pressed are no longer trigger GetKeyDown SimulatedMouseDownSet = new HashSet(); SimulatedKeyDownSet = new HashSet(); // keys that were just released are no longer trigger GetKeyUp SimulatedMouseUpSet = new HashSet(); SimulatedKeyUpSet = new HashSet(); } /// /// Test if the key is currently pressed. /// /// True if the bound key is currently pressed public static bool GetKey(KeyBinding kb) { if (kb.TryGetMouseButton(out int mouseButton)) { if (isSimulated) return SimulatedMouseSet.Contains(mouseButton); else return UnityEngine.Input.GetMouseButton(mouseButton); } if (kb.TryGetKeyCode(out KeyCode keyCode)) { if (isSimulated) return SimulatedKeySet.Contains(keyCode); else return UnityEngine.Input.GetKey(keyCode); } return false; } /// /// Test if the key has been pressed since the last frame. /// /// True if the bound key was pressed since the last frame public static bool GetKeyDown(KeyBinding kb) { if (kb.TryGetMouseButton(out int mouseButton)) { if (isSimulated) return SimulatedMouseDownSet.Contains(mouseButton); else return UnityEngine.Input.GetMouseButtonDown(mouseButton); } if (kb.TryGetKeyCode(out KeyCode keyCode)) { if (isSimulated) return SimulatedKeyDownSet.Contains(keyCode); else return UnityEngine.Input.GetKeyDown(keyCode); } return false; } /// /// Test if the key has been released since the last frame. /// /// True if the bound key was released since the last frame public static bool GetKeyUp(KeyBinding kb) { if (kb.TryGetMouseButton(out int mouseButton)) { if (isSimulated) return SimulatedMouseUpSet.Contains(mouseButton); else return UnityEngine.Input.GetMouseButtonUp(mouseButton); } if (kb.TryGetKeyCode(out KeyCode keyCode)) { if (isSimulated) return SimulatedKeyUpSet.Contains(keyCode); else return UnityEngine.Input.GetKeyUp(keyCode); } return false; } } }