A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.InputSystem.LowLevel;
3
4namespace UnityEngine.InputSystem
5{
6 /// <summary>
7 /// Information passed to <see cref="IInputInteraction">interactions</see>
8 /// when their associated controls trigger.
9 /// </summary>
10 /// <seealso cref="IInputInteraction.Process"/>
11 public struct InputInteractionContext
12 {
13 /// <summary>
14 /// The action associated with the binding.
15 /// </summary>
16 /// <remarks>
17 /// If the binding is not associated with an action, this is <c>null</c>.
18 /// </remarks>
19 /// <seealso cref="InputBinding.action"/>
20 public InputAction action => m_State.GetActionOrNull(ref m_TriggerState);
21
22 /// <summary>
23 /// The bound control that changed its state to trigger the binding associated
24 /// with the interaction.
25 /// </summary>
26 /// <remarks>
27 /// In case the binding associated with the interaction is a composite, this is
28 /// one of the controls that are part of the composite.
29 /// </remarks>
30 /// <seealso cref="InputBinding.path"/>
31 public InputControl control => m_State.GetControl(ref m_TriggerState);
32
33 /// <summary>
34 /// The phase the interaction is currently in.
35 /// </summary>
36 /// <remarks>
37 /// Each interaction on a binding has its own phase independent of the action the binding is applied to.
38 /// If an interaction gets to "drive" an action at a particular point in time, its phase will determine
39 /// the phase of the action.
40 /// </remarks>
41 /// <seealso cref="InputAction.phase"/>
42 /// <seealso cref="Started"/>
43 /// <seealso cref="Waiting"/>
44 /// <seealso cref="Performed"/>
45 /// <seealso cref="Canceled"/>
46 public InputActionPhase phase => m_TriggerState.phase;
47
48 /// <summary>
49 /// Time stamp of the input event that caused <see cref="control"/> to trigger a change in the
50 /// state of <see cref="action"/>.
51 /// </summary>
52 /// <seealso cref="InputEvent.time"/>
53 public double time => m_TriggerState.time;
54
55 /// <summary>
56 /// Timestamp of the <see cref="InputEvent"/> that caused the interaction to transition
57 /// to <see cref="InputActionPhase.Started"/>.
58 /// </summary>
59 /// <seealso cref="InputEvent.time"/>
60 public double startTime => m_TriggerState.startTime;
61
62 /// <summary>
63 /// Whether the interaction's <see cref="IInputInteraction.Process"/> method has been called because
64 /// a timer set by <see cref="SetTimeout"/> has expired.
65 /// </summary>
66 /// <seealso cref="SetTimeout"/>
67 public bool timerHasExpired
68 {
69 get => (m_Flags & Flags.TimerHasExpired) != 0;
70 internal set
71 {
72 if (value)
73 m_Flags |= Flags.TimerHasExpired;
74 else
75 m_Flags &= ~Flags.TimerHasExpired;
76 }
77 }
78
79 /// <summary>
80 /// True if the interaction is waiting for input
81 /// </summary>
82 /// <remarks>
83 /// By default, an interaction will return this this phase after every time it has been performed
84 /// (<see cref="InputActionPhase.Performed"/>). This can be changed by using <see cref="PerformedAndStayStarted"/>
85 /// or <see cref="PerformedAndStayPerformed"/>.
86 /// </remarks>
87 /// <seealso cref="InputActionPhase.Waiting"/>
88 public bool isWaiting => phase == InputActionPhase.Waiting;
89
90 /// <summary>
91 /// True if the interaction has been started.
92 /// </summary>
93 /// <seealso cref="InputActionPhase.Started"/>
94 /// <seealso cref="Started"/>
95 public bool isStarted => phase == InputActionPhase.Started;
96
97 /// <summary>
98 /// Compute the current level of control actuation.
99 /// </summary>
100 /// <returns>The current level of control actuation (usually [0..1]) or -1 if the control is actuated
101 /// but does not support computing magnitudes.</returns>
102 /// <seealso cref="ControlIsActuated"/>
103 /// <seealso cref="InputControl.EvaluateMagnitude()"/>
104 public float ComputeMagnitude()
105 {
106 return m_TriggerState.magnitude;
107 }
108
109 /// <summary>
110 /// Return true if the control that triggered the interaction has been actuated beyond the given threshold.
111 /// </summary>
112 /// <param name="threshold">Threshold that must be reached for the control to be considered actuated. If this is zero,
113 /// the threshold must be exceeded. If it is any positive value, the value must be at least matched.</param>
114 /// <returns>True if the trigger control is actuated.</returns>
115 /// <seealso cref="InputControlExtensions.IsActuated"/>
116 /// <seealso cref="ComputeMagnitude"/>
117 public bool ControlIsActuated(float threshold = 0)
118 {
119 return InputActionState.IsActuated(ref m_TriggerState, threshold);
120 }
121
122 /// <summary>
123 /// Mark the interaction has having begun.
124 /// </summary>
125 /// <remarks>
126 /// This affects the current interaction only. There might be multiple interactions on a binding
127 /// and arbitrary many interactions might concurrently be in started state. However, only one interaction
128 /// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
129 /// that is currently driving an action is canceled, however, the next interaction in the list that has
130 /// been started will take over and continue driving the action.
131 ///
132 /// <example>
133 /// <code>
134 /// public class MyInteraction : IInputInteraction<float>
135 /// {
136 /// public void Process(ref IInputInteractionContext context)
137 /// {
138 /// if (context.isWaiting && context.ControlIsActuated())
139 /// {
140 /// // We've waited for input and got it. Start the interaction.
141 /// context.Started();
142 /// }
143 /// else if (context.isStarted && !context.ControlIsActuated())
144 /// {
145 /// // Interaction has been completed.
146 /// context.Performed();
147 /// }
148 /// }
149 ///
150 /// public void Reset()
151 /// {
152 /// // No reset code needed. We're not keeping any state locally in the interaction.
153 /// }
154 /// }
155 /// </code>
156 /// </example>
157 /// </remarks>
158 public void Started()
159 {
160 m_TriggerState.startTime = time;
161 m_State.ChangePhaseOfInteraction(InputActionPhase.Started, ref m_TriggerState);
162 }
163
164 /// <summary>
165 /// Marks the interaction as being performed and then transitions back to <see cref="InputActionPhase.Waiting"/>
166 /// to wait for input. This behavior is desirable for interaction events that are instant and reflect
167 /// a transitional interaction pattern such as <see cref="Interactions.PressInteraction"/> or <see cref="Interactions.TapInteraction"/>.
168 /// </summary>
169 /// <remarks>
170 /// Note that this affects the current interaction only. There might be multiple interactions on a binding
171 /// and arbitrary many interactions might concurrently be in started state. However, only one interaction
172 /// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
173 /// that is currently driving an action is canceled, however, the next interaction in the list that has
174 /// been started will take over and continue driving the action.
175 /// </remarks>
176 public void Performed()
177 {
178 if (m_TriggerState.phase == InputActionPhase.Waiting)
179 m_TriggerState.startTime = time;
180 m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
181 }
182
183 /// <summary>
184 /// Marks the interaction as being performed and then transitions into I <see cref="InputActionPhase.Started"/>
185 /// to wait for an initial trigger condition to be true before being performed again. This behavior
186 /// may be desirable for interaction events that reflect transitional interaction patterns but should
187 /// be considered as started until a cancellation condition is true, such as releasing a button.
188 /// </summary>
189 public void PerformedAndStayStarted()
190 {
191 if (m_TriggerState.phase == InputActionPhase.Waiting)
192 m_TriggerState.startTime = time;
193 m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
194 phaseAfterPerformed: InputActionPhase.Started);
195 }
196
197 /// <summary>
198 /// Marks the interaction as being performed and then stays in that state waiting for an input to
199 /// cancel the interactions active state. This behavior is desirable for interaction events that
200 /// are active for a duration until a cancellation condition is true, such as <see cref="Interactions.HoldInteraction"/> or <see cref="Interactions.TapInteraction"/> where releasing
201 /// the associated button cancels the interaction..
202 /// </summary>
203 public void PerformedAndStayPerformed()
204 {
205 if (m_TriggerState.phase == InputActionPhase.Waiting)
206 m_TriggerState.startTime = time;
207 m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
208 phaseAfterPerformed: InputActionPhase.Performed);
209 }
210
211 /// <summary>
212 /// Marks the interaction as being interrupted or aborted. This is relevant to signal that the interaction
213 /// pattern was not completed, for example, the user pressed and then released a button before the minimum
214 /// time required for a <see cref="Interactions.HoldInteraction"/> to complete.
215 /// </summary>
216 /// <remarks>
217 /// This is used by most existing interactions to cancel the transitions in the interaction state machine
218 /// when a condition required to proceed turned false or other indirect requirements were not met, such as
219 /// time-based conditions.
220 /// </remarks>
221 public void Canceled()
222 {
223 if (m_TriggerState.phase != InputActionPhase.Canceled)
224 m_State.ChangePhaseOfInteraction(InputActionPhase.Canceled, ref m_TriggerState);
225 }
226
227 /// <summary>
228 /// Put the interaction back into <see cref="InputActionPhase.Waiting"/> state.
229 /// </summary>
230 /// <seealso cref="InputAction.phase"/>
231 /// <seealso cref="InputActionPhase"/>
232 /// <seealso cref="Started"/>
233 /// <seealso cref="Performed"/>
234 /// <seealso cref="Canceled"/>
235 public void Waiting()
236 {
237 if (m_TriggerState.phase != InputActionPhase.Waiting)
238 m_State.ChangePhaseOfInteraction(InputActionPhase.Waiting, ref m_TriggerState);
239 }
240
241 /// <summary>
242 /// Start a timeout that triggers within <paramref name="seconds"/>.
243 /// </summary>
244 /// <param name="seconds">Number of seconds before the timeout is triggered.</param>
245 /// <remarks>
246 /// An interaction might wait a set amount of time for something to happen and then
247 /// do something depending on whether it did or did not happen. By calling this method,
248 /// a timeout is installed such that in the input update that the timer expires in, the
249 /// interaction's <see cref="IInputInteraction.Process"/> method is called with <see cref="timerHasExpired"/>
250 /// being true.
251 ///
252 /// Changing the phase of the interaction while a timeout is running will implicitly cancel
253 /// the timeout.
254 ///
255 /// <example>
256 /// <code>
257 /// // Let's say we're writing a Process() method for an interaction that,
258 /// // after a control has been actuated, waits for 1 second for it to be
259 /// // released again. If that happens, the interaction performs. If not,
260 /// // it cancels.
261 /// public void Process(ref InputInteractionContext context)
262 /// {
263 /// // timerHasExpired will be true if we get called when our timeout
264 /// // has expired.
265 /// if (context.timerHasExpired)
266 /// {
267 /// // The user did not release the control quickly enough.
268 /// // Our interaction is not successful, so cancel.
269 /// context.Canceled();
270 /// return;
271 /// }
272 ///
273 /// if (context.ControlIsActuated())
274 /// {
275 /// if (!context.isStarted)
276 /// {
277 /// // The control has been actuated. We want to give the user a max
278 /// // of 1 second to release it. So we start the interaction now and then
279 /// // set the timeout.
280 /// context.Started();
281 /// context.SetTimeout(1);
282 /// }
283 /// }
284 /// else
285 /// {
286 /// // Control has been released. If we're currently waiting for a release,
287 /// // it has come in time before out timeout expired. In other words, the
288 /// // interaction has been successfully performed. We call Performed()
289 /// // which implicitly removes our ongoing timeout.
290 /// if (context.isStarted)
291 /// context.Performed();
292 /// }
293 /// }
294 /// </code>
295 /// </example>
296 /// </remarks>
297 /// <seealso cref="timerHasExpired"/>
298 public void SetTimeout(float seconds)
299 {
300 m_State.StartTimeout(seconds, ref m_TriggerState);
301 }
302
303 /// <summary>
304 /// Override the default timeout value used by <see cref="InputAction.GetTimeoutCompletionPercentage"/>.
305 /// </summary>
306 /// <param name="seconds">Amount of total successive timeouts TODO</param>
307 /// <exception cref="ArgumentException"></exception>
308 /// <remarks>
309 /// By default, timeout completion will be entirely determine by the timeout that is currently
310 /// running, if any. However, some interactions (such as <see cref="Interactions.MultiTapInteraction"/>)
311 /// will have to run multiple timeouts in succession. Thus, completion of a single timeout is not
312 /// the same as completion of the interaction.
313 ///
314 /// You can use this method to account for this.
315 ///
316 /// Whenever a timeout completes, the timeout duration will automatically be accumulated towards
317 /// the total timeout completion time.
318 ///
319 /// <example>
320 /// <code>
321 /// // Let's say we're starting our first timeout and we know that we will run three timeouts
322 /// // in succession of 2 seconds each. By calling SetTotalTimeoutCompletionTime(), we can account for this.
323 /// SetTotalTimeoutCompletionTime(3 * 2);
324 ///
325 /// // Start the first timeout. When this timeout expires, it will automatically
326 /// // count one second towards the total timeout completion time.
327 /// SetTimeout(2);
328 /// </code>
329 /// </example>
330 /// </remarks>
331 /// <seealso cref="InputAction.GetTimeoutCompletionPercentage"/>
332 public void SetTotalTimeoutCompletionTime(float seconds)
333 {
334 if (seconds <= 0)
335 throw new ArgumentException("Seconds must be a positive value", nameof(seconds));
336
337 m_State.SetTotalTimeoutCompletionTime(seconds, ref m_TriggerState);
338 }
339
340 /// <summary>
341 /// Read the value of the binding that triggered processing of the interaction.
342 /// </summary>
343 /// <typeparam name="TValue">Type of value to read from the binding. Must match the value type of the control
344 /// or composite in effect for the binding.</typeparam>
345 /// <returns>Value read from the binding.</returns>
346 public TValue ReadValue<TValue>()
347 where TValue : struct
348 {
349 return m_State.ReadValue<TValue>(m_TriggerState.bindingIndex, m_TriggerState.controlIndex);
350 }
351
352 internal InputActionState m_State;
353 internal Flags m_Flags;
354 internal InputActionState.TriggerState m_TriggerState;
355
356 internal int mapIndex => m_TriggerState.mapIndex;
357
358 internal int controlIndex => m_TriggerState.controlIndex;
359
360 internal int bindingIndex => m_TriggerState.bindingIndex;
361
362 internal int interactionIndex => m_TriggerState.interactionIndex;
363
364 [Flags]
365 internal enum Flags
366 {
367 TimerHasExpired = 1 << 1
368 }
369 }
370}