using System; using System.ComponentModel; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Utilities; using UnityEngine.Scripting; ////TODO: allow making modifier optional; maybe alter the value (e.g. 0=unpressed, 0.5=pressed without modifier, 1=pressed with modifier) namespace UnityEngine.InputSystem.Composites { /// /// A binding with an additional modifier. The bound controls only trigger when /// the modifier is pressed. /// /// /// This composite can be used to require a button to be held in order to "activate" /// another binding. This is most commonly used on keyboards to require one of the /// modifier keys (shift, ctrl, or alt) to be held in combination with another control, /// e.g. "CTRL+1". /// /// /// /// // Create a button action that triggers when CTRL+1 /// // is pressed on the keyboard. /// var action = new InputAction(type: InputActionType.Button); /// action.AddCompositeBinding("OneModifier") /// .With("Modifier", "<Keyboard>/ctrl") /// .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 on the /// keyboard has to be pressed in order for the player to be able to look around. /// /// /// /// lookAction.AddCompositeBinding("OneModifier") /// .With("Modifier", "<Keyboard>/alt") /// .With("Binding", "<Mouse>/delta") /// /// /// /// [DisplayStringFormat("{modifier}+{binding}")] [DisplayName("Binding With One Modifier")] public class OneModifierComposite : InputBindingComposite { /// /// Binding for the button that acts as a modifier, e.g. <Keyboard/ctrl. /// /// 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 modifier; /// /// Binding for the control that is gated by the modifier. The composite will assume the value /// of this control while the modifier is considered pressed (that is, has a magnitude equal to or /// greater than the button press point). /// /// 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; /// /// 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; /// /// 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 is bound to only s, then the composite requires /// to be pressed before pressing . This means that binding to, for example, /// Ctrl+B, the ctrl keys have to be pressed before pressing the B key. This is the behavior usually expected /// with keyboard shortcuts. /// /// However, when binding, for example, Ctrl+MouseDelta, it should be possible to press ctrl at any time. 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+B, it would mean that pressing B and /// only then pressing Ctrl will still trigger the binding. /// public bool overrideModifiersNeedToBePressedFirst; private int m_ValueSizeInBytes; private Type m_ValueType; private bool m_BindingIsButton; public override float EvaluateMagnitude(ref InputBindingCompositeContext context) { if (ModifierIsPressed(ref context)) return context.EvaluateMagnitude(binding); return default; } /// public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize) { if (ModifierIsPressed(ref context)) context.ReadValue(binding, buffer, bufferSize); else UnsafeUtility.MemClear(buffer, m_ValueSizeInBytes); } private bool ModifierIsPressed(ref InputBindingCompositeContext context) { var modifierDown = context.ReadValueAsButton(modifier); // When the modifiers are gating a button, we require the modifiers to be pressed *first*. if (modifierDown && m_BindingIsButton && !overrideModifiersNeedToBePressedFirst) { var timestamp = context.GetPressTime(binding); var timestamp1 = context.GetPressTime(modifier); return timestamp1 <= timestamp; } return modifierDown; } /// protected override void FinishSetup(ref InputBindingCompositeContext context) { DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton); if (!overrideModifiersNeedToBePressedFirst) overrideModifiersNeedToBePressedFirst = InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kDisableShortcutSupport); } public override object ReadValueAsObject(ref InputBindingCompositeContext context) { if (context.ReadValueAsButton(modifier)) return context.ReadValueAsObject(binding); return null; } internal static void DetermineValueTypeAndSize(ref InputBindingCompositeContext context, int part, out Type valueType, out int valueSizeInBytes, out bool isButton) { valueSizeInBytes = 0; isButton = true; Type type = null; foreach (var control in context.controls) { if (control.part != part) continue; var controlType = control.control.valueType; if (type == null || controlType.IsAssignableFrom(type)) type = controlType; else if (!type.IsAssignableFrom(controlType)) type = typeof(Object); valueSizeInBytes = Math.Max(control.control.valueSizeInBytes, valueSizeInBytes); // *All* bound controls need to be buttons for us to classify this part as a "Button" part. isButton &= control.control.isButton; } valueType = type; } } }