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