using System; using System.ComponentModel; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; namespace UnityEngine.InputSystem.Composites { /// /// A binding with two additional modifiers modifier. The bound controls only trigger when /// both modifiers are pressed. /// /// /// This composite can be used to require two buttons to be held in order to "activate" /// another binding. This is most commonly used on keyboards to require two of the /// modifier keys (shift, ctrl, or alt) to be held in combination with another control, /// e.g. "SHIFT+CTRL+1". /// /// /// /// // Create a button action that triggers when SHIFT+CTRL+1 /// // is pressed on the keyboard. /// var action = new InputAction(type: InputActionType.Button); /// action.AddCompositeBinding("TwoModifiers") /// .With("Modifier", "<Keyboard>/ctrl") /// .With("Modifier", "<Keyboard>/shift") /// .With("Binding", "<Keyboard>/1") /// /// /// /// However, this can also be used to "gate" other types of controls. For example, a "look" /// action could be bound to mouse such that the and /// on the keyboard have to be pressed in order for the player to be able to /// look around. /// /// /// /// var action = new InputAction(); /// action.AddCompositeBinding("TwoModifiers") /// .With("Modifier1", "<Keyboard>/ctrl") /// .With("Modifier2", "<Keyboard>/shift") /// .With("Binding", "<Mouse>/delta"); /// /// /// /// [DisplayStringFormat("{modifier1}+{modifier2}+{binding}")] [DisplayName("Binding With Two Modifiers")] public class TwoModifiersComposite : InputBindingComposite { /// /// Binding for the first button that acts as a modifier, e.g. <Keyboard/leftCtrl. /// /// Part index to use with . /// /// This property is automatically assigned by the input system. /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global // ReSharper disable once UnassignedField.Global [InputControl(layout = "Button")] public int modifier1; /// /// Binding for the second button that acts as a modifier, e.g. <Keyboard/leftCtrl. /// /// Part index to use with . /// /// This property is automatically assigned by the input system. /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global // ReSharper disable once UnassignedField.Global [InputControl(layout = "Button")] public int modifier2; /// /// Binding for the control that is gated by and . /// The composite will assume the value of this button while both of the modifiers are pressed. /// /// Part index to use with . /// /// This property is automatically assigned by the input system. /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global // ReSharper disable once UnassignedField.Global [InputControl] public int binding; /// /// If set to true, the built-in logic to determine if modifiers need to be pressed first is overridden. /// Default value is false. /// /// /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// /// However, when binding, for example, Ctrl+Shift+MouseDelta, it should be possible to press ctrl and shift /// at any time and in any order. The default logic will automatically detect the difference between this binding and the button /// binding in the example above and behave accordingly. /// /// This field allows you to explicitly override this default inference and make it so that regardless of what /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+Shift+B, it would mean that pressing /// B and only then pressing Ctrl and Shift will still trigger the binding. /// /// To don't depends on the setting please consider using instead. /// [Tooltip("Obsolete please use modifiers Order. If enabled, this will override the Input Consumption setting, allowing the modifier keys to be pressed after the button and the composite will still trigger.")] [Obsolete("Use ModifiersOrder.Unordered with 'modifiersOrder' instead")] public bool overrideModifiersNeedToBePressedFirst; /// /// Determines how a modifiers keys need to be pressed in order or not. /// public enum ModifiersOrder { /// /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// Default = 0, /// /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// Ordered = 1, /// /// and/or can be pressed after /// and the composite will still trigger. The only requirement is for all of them to concurrently be in pressed state. /// Unordered = 2 } /// /// If set to Ordered or Unordered, the built-in logic to determine if modifiers need to be pressed first is overridden. /// /// /// By default, if the setting is enabled, /// if is bound to only s, then the composite requires /// both and to be pressed before pressing . /// This means that binding to, for example, Ctrl+Shift+B, the ctrl and shift keys have to be pressed, in any order, /// before pressing the B key. This is the behavior usually expected with keyboard shortcuts. /// /// If the setting is disabled, /// modifiers can be pressed after the button and the composite will still trigger. /// /// This field allows you to explicitly override this default inference and make the order mandatory or make it so that regardless of what /// is bound to, any press sequence is acceptable. For the example binding to Ctrl+Shift+B, it would mean that pressing /// B and only then pressing Ctrl and Shift will still trigger the binding. /// /// [Tooltip("By default it follows the Input Consumption setting to determine if the modifers keys need to be pressed first.")] public ModifiersOrder modifiersOrder = ModifiersOrder.Default; /// /// Type of values read from controls bound to . /// public override Type valueType => m_ValueType; /// /// Size of the largest value that may be read from the controls bound to . /// public override int valueSizeInBytes => m_ValueSizeInBytes; private int m_ValueSizeInBytes; private Type m_ValueType; private bool m_BindingIsButton; public override float EvaluateMagnitude(ref InputBindingCompositeContext context) { if (ModifiersArePressed(ref context)) return context.EvaluateMagnitude(binding); return default; } /// public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize) { if (ModifiersArePressed(ref context)) context.ReadValue(binding, buffer, bufferSize); else UnsafeUtility.MemClear(buffer, m_ValueSizeInBytes); } private bool ModifiersArePressed(ref InputBindingCompositeContext context) { var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2); // When the modifiers are gating a button, we require the modifiers to be pressed *first*. if (modifiersDown && m_BindingIsButton && modifiersOrder == ModifiersOrder.Ordered) { var timestamp = context.GetPressTime(binding); var timestamp1 = context.GetPressTime(modifier1); var timestamp2 = context.GetPressTime(modifier2); return timestamp1 <= timestamp && timestamp2 <= timestamp; } return modifiersDown; } /// protected override void FinishSetup(ref InputBindingCompositeContext context) { OneModifierComposite.DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton); if (modifiersOrder == ModifiersOrder.Default) { // Legacy. We need to reference the obsolete member here so temporarily // turn off the warning. #pragma warning disable CS0618 if (overrideModifiersNeedToBePressedFirst) #pragma warning restore CS0618 modifiersOrder = ModifiersOrder.Unordered; else modifiersOrder = InputSystem.settings.shortcutKeysConsumeInput ? ModifiersOrder.Ordered : ModifiersOrder.Unordered; } } public override object ReadValueAsObject(ref InputBindingCompositeContext context) { if (context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2)) return context.ReadValueAsObject(binding); return null; } } }