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", "<Keyboard>/ctrl")
26 /// .With("Modifier", "<Keyboard>/shift")
27 /// .With("Binding", "<Keyboard>/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", "<Keyboard>/ctrl")
41 /// .With("Modifier2", "<Keyboard>/shift")
42 /// .With("Binding", "<Mouse>/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><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><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}