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;
}
}
}