A game about forced loneliness, made by TACStudios
1using System; 2using System.ComponentModel; 3using Unity.Collections.LowLevel.Unsafe; 4using UnityEngine.InputSystem.Layouts; 5using UnityEngine.InputSystem.Utilities; 6 7namespace UnityEngine.InputSystem.Composites 8{ 9 /// <summary> 10 /// A binding with two additional modifiers modifier. The bound controls only trigger when 11 /// both modifiers are pressed. 12 /// </summary> 13 /// <remarks> 14 /// This composite can be used to require two buttons to be held in order to "activate" 15 /// another binding. This is most commonly used on keyboards to require two of the 16 /// modifier keys (shift, ctrl, or alt) to be held in combination with another control, 17 /// e.g. "SHIFT+CTRL+1". 18 /// 19 /// <example> 20 /// <code> 21 /// // Create a button action that triggers when SHIFT+CTRL+1 22 /// // is pressed on the keyboard. 23 /// var action = new InputAction(type: InputActionType.Button); 24 /// action.AddCompositeBinding("TwoModifiers") 25 /// .With("Modifier", "&lt;Keyboard&gt;/ctrl") 26 /// .With("Modifier", "&lt;Keyboard&gt;/shift") 27 /// .With("Binding", "&lt;Keyboard&gt;/1") 28 /// </code> 29 /// </example> 30 /// 31 /// However, this can also be used to "gate" other types of controls. For example, a "look" 32 /// action could be bound to mouse <see cref="Pointer.delta"/> such that the <see cref="Keyboard.ctrlKey"/> and 33 /// <see cref="Keyboard.shiftKey"/> on the keyboard have to be pressed in order for the player to be able to 34 /// look around. 35 /// 36 /// <example> 37 /// <code> 38 /// var action = new InputAction(); 39 /// action.AddCompositeBinding("TwoModifiers") 40 /// .With("Modifier1", "&lt;Keyboard&gt;/ctrl") 41 /// .With("Modifier2", "&lt;Keyboard&gt;/shift") 42 /// .With("Binding", "&lt;Mouse&gt;/delta"); 43 /// </code> 44 /// </example> 45 /// </remarks> 46 /// <seealso cref="OneModifierComposite"/> 47 [DisplayStringFormat("{modifier1}+{modifier2}+{binding}")] 48 [DisplayName("Binding With Two Modifiers")] 49 public class TwoModifiersComposite : InputBindingComposite 50 { 51 /// <summary> 52 /// Binding for the first button that acts as a modifier, e.g. <c>&lt;Keyboard/leftCtrl</c>. 53 /// </summary> 54 /// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value> 55 /// <remarks> 56 /// This property is automatically assigned by the input system. 57 /// </remarks> 58 // ReSharper disable once MemberCanBePrivate.Global 59 // ReSharper disable once FieldCanBeMadeReadOnly.Global 60 // ReSharper disable once UnassignedField.Global 61 [InputControl(layout = "Button")] public int modifier1; 62 63 /// <summary> 64 /// Binding for the second button that acts as a modifier, e.g. <c>&lt;Keyboard/leftCtrl</c>. 65 /// </summary> 66 /// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value> 67 /// <remarks> 68 /// This property is automatically assigned by the input system. 69 /// </remarks> 70 // ReSharper disable once MemberCanBePrivate.Global 71 // ReSharper disable once FieldCanBeMadeReadOnly.Global 72 // ReSharper disable once UnassignedField.Global 73 [InputControl(layout = "Button")] public int modifier2; 74 75 /// <summary> 76 /// Binding for the control that is gated by <see cref="modifier1"/> and <see cref="modifier2"/>. 77 /// The composite will assume the value of this button while both of the modifiers are pressed. 78 /// </summary> 79 /// <value>Part index to use with <see cref="InputBindingCompositeContext.ReadValue{T}(int)"/>.</value> 80 /// <remarks> 81 /// This property is automatically assigned by the input system. 82 /// </remarks> 83 // ReSharper disable once MemberCanBePrivate.Global 84 // ReSharper disable once FieldCanBeMadeReadOnly.Global 85 // ReSharper disable once UnassignedField.Global 86 [InputControl] public int binding; 87 88 /// <summary> 89 /// If set to <c>true</c>, the built-in logic to determine if modifiers need to be pressed first is overridden. 90 /// Default value is <c>false</c>. 91 /// </summary> 92 /// <remarks> 93 /// By default, if <see cref="binding"/> is bound to only <see cref="Controls.ButtonControl"/>s, then the composite requires 94 /// both <see cref="modifier1"/> and <see cref="modifier2"/> to be pressed <em>before</em> pressing <see cref="binding"/>. 95 /// This means that binding to, for example, <c>Ctrl+Shift+B</c>, the <c>ctrl</c> and <c>shift</c> keys have to be pressed 96 /// before pressing the <c>B</c> key. This is the behavior usually expected with keyboard shortcuts. 97 /// 98 /// However, when binding, for example, <c>Ctrl+Shift+MouseDelta</c>, it should be possible to press <c>ctrl</c> and <c>shift</c> 99 /// at any time and in any order. The default logic will automatically detect the difference between this binding and the button 100 /// binding in the example above and behave accordingly. 101 /// 102 /// This field allows you to explicitly override this default inference and make it so that regardless of what <see cref="binding"/> 103 /// is bound to, any press sequence is acceptable. For the example binding to <c>Ctrl+Shift+B</c>, it would mean that pressing 104 /// <c>B</c> and only then pressing <c>Ctrl</c> and <c>Shift</c> will still trigger the binding. 105 /// </remarks> 106 public bool overrideModifiersNeedToBePressedFirst; 107 108 /// <summary> 109 /// Type of values read from controls bound to <see cref="binding"/>. 110 /// </summary> 111 public override Type valueType => m_ValueType; 112 113 /// <summary> 114 /// Size of the largest value that may be read from the controls bound to <see cref="binding"/>. 115 /// </summary> 116 public override int valueSizeInBytes => m_ValueSizeInBytes; 117 118 private int m_ValueSizeInBytes; 119 private Type m_ValueType; 120 private bool m_BindingIsButton; 121 122 public override float EvaluateMagnitude(ref InputBindingCompositeContext context) 123 { 124 if (ModifiersArePressed(ref context)) 125 return context.EvaluateMagnitude(binding); 126 return default; 127 } 128 129 /// <inheritdoc/> 130 public override unsafe void ReadValue(ref InputBindingCompositeContext context, void* buffer, int bufferSize) 131 { 132 if (ModifiersArePressed(ref context)) 133 context.ReadValue(binding, buffer, bufferSize); 134 else 135 UnsafeUtility.MemClear(buffer, m_ValueSizeInBytes); 136 } 137 138 private bool ModifiersArePressed(ref InputBindingCompositeContext context) 139 { 140 var modifiersDown = context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2); 141 142 // When the modifiers are gating a button, we require the modifiers to be pressed *first*. 143 if (modifiersDown && m_BindingIsButton && !overrideModifiersNeedToBePressedFirst) 144 { 145 var timestamp = context.GetPressTime(binding); 146 var timestamp1 = context.GetPressTime(modifier1); 147 var timestamp2 = context.GetPressTime(modifier2); 148 149 return timestamp1 <= timestamp && timestamp2 <= timestamp; 150 } 151 152 return modifiersDown; 153 } 154 155 /// <inheritdoc/> 156 protected override void FinishSetup(ref InputBindingCompositeContext context) 157 { 158 OneModifierComposite.DetermineValueTypeAndSize(ref context, binding, out m_ValueType, out m_ValueSizeInBytes, out m_BindingIsButton); 159 160 if (!overrideModifiersNeedToBePressedFirst) 161 overrideModifiersNeedToBePressedFirst = !InputSystem.settings.shortcutKeysConsumeInput; 162 } 163 164 public override object ReadValueAsObject(ref InputBindingCompositeContext context) 165 { 166 if (context.ReadValueAsButton(modifier1) && context.ReadValueAsButton(modifier2)) 167 return context.ReadValueAsObject(binding); 168 return null; 169 } 170 } 171}