File size: 14,519 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
using System;
using UnityEngine.InputSystem.LowLevel;

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// Information passed to <see cref="IInputInteraction">interactions</see>
    /// when their associated controls trigger.
    /// </summary>
    /// <seealso cref="IInputInteraction.Process"/>
    public struct InputInteractionContext
    {
        /// <summary>
        /// The action associated with the binding.
        /// </summary>
        /// <remarks>
        /// If the binding is not associated with an action, this is <c>null</c>.
        /// </remarks>
        /// <seealso cref="InputBinding.action"/>
        public InputAction action => m_State.GetActionOrNull(ref m_TriggerState);

        /// <summary>
        /// The bound control that changed its state to trigger the binding associated
        /// with the interaction.
        /// </summary>
        /// <remarks>
        /// In case the binding associated with the interaction is a composite, this is
        /// one of the controls that are part of the composite.
        /// </remarks>
        /// <seealso cref="InputBinding.path"/>
        public InputControl control => m_State.GetControl(ref m_TriggerState);

        /// <summary>
        /// The phase the interaction is currently in.
        /// </summary>
        /// <remarks>
        /// Each interaction on a binding has its own phase independent of the action the binding is applied to.
        /// If an interaction gets to "drive" an action at a particular point in time, its phase will determine
        /// the phase of the action.
        /// </remarks>
        /// <seealso cref="InputAction.phase"/>
        /// <seealso cref="Started"/>
        /// <seealso cref="Waiting"/>
        /// <seealso cref="Performed"/>
        /// <seealso cref="Canceled"/>
        public InputActionPhase phase => m_TriggerState.phase;

        /// <summary>
        /// Time stamp of the input event that caused <see cref="control"/> to trigger a change in the
        /// state of <see cref="action"/>.
        /// </summary>
        /// <seealso cref="InputEvent.time"/>
        public double time => m_TriggerState.time;

        /// <summary>
        /// Timestamp of the <see cref="InputEvent"/> that caused the interaction to transition
        /// to <see cref="InputActionPhase.Started"/>.
        /// </summary>
        /// <seealso cref="InputEvent.time"/>
        public double startTime => m_TriggerState.startTime;

        /// <summary>
        /// Whether the interaction's <see cref="IInputInteraction.Process"/> method has been called because
        /// a timer set by <see cref="SetTimeout"/> has expired.
        /// </summary>
        /// <seealso cref="SetTimeout"/>
        public bool timerHasExpired
        {
            get => (m_Flags & Flags.TimerHasExpired) != 0;
            internal set
            {
                if (value)
                    m_Flags |= Flags.TimerHasExpired;
                else
                    m_Flags &= ~Flags.TimerHasExpired;
            }
        }

        /// <summary>
        /// True if the interaction is waiting for input
        /// </summary>
        /// <remarks>
        /// By default, an interaction will return this this phase after every time it has been performed
        /// (<see cref="InputActionPhase.Performed"/>). This can be changed by using <see cref="PerformedAndStayStarted"/>
        /// or <see cref="PerformedAndStayPerformed"/>.
        /// </remarks>
        /// <seealso cref="InputActionPhase.Waiting"/>
        public bool isWaiting => phase == InputActionPhase.Waiting;

        /// <summary>
        /// True if the interaction has been started.
        /// </summary>
        /// <seealso cref="InputActionPhase.Started"/>
        /// <seealso cref="Started"/>
        public bool isStarted => phase == InputActionPhase.Started;

        /// <summary>
        /// Compute the current level of control actuation.
        /// </summary>
        /// <returns>The current level of control actuation (usually [0..1]) or -1 if the control is actuated
        /// but does not support computing magnitudes.</returns>
        /// <seealso cref="ControlIsActuated"/>
        /// <seealso cref="InputControl.EvaluateMagnitude()"/>
        public float ComputeMagnitude()
        {
            return m_TriggerState.magnitude;
        }

        /// <summary>
        /// Return true if the control that triggered the interaction has been actuated beyond the given threshold.
        /// </summary>
        /// <param name="threshold">Threshold that must be reached for the control to be considered actuated. If this is zero,
        /// the threshold must be exceeded. If it is any positive value, the value must be at least matched.</param>
        /// <returns>True if the trigger control is actuated.</returns>
        /// <seealso cref="InputControlExtensions.IsActuated"/>
        /// <seealso cref="ComputeMagnitude"/>
        public bool ControlIsActuated(float threshold = 0)
        {
            return InputActionState.IsActuated(ref m_TriggerState, threshold);
        }

        /// <summary>
        /// Mark the interaction has having begun.
        /// </summary>
        /// <remarks>
        /// Note that this affects the current interaction only. There may be multiple interactions on a binding
        /// and arbitrary many interactions may concurrently be in started state. However, only one interaction
        /// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
        /// that is currently driving an action is canceled, however, the next interaction in the list that has
        /// been started will take over and continue driving the action.
        ///
        /// <example>
        /// <code>
        /// public class MyInteraction : IInputInteraction&lt;float&gt;
        /// {
        ///     public void Process(ref IInputInteractionContext context)
        ///     {
        ///         if (context.isWaiting &amp;&amp; context.ControlIsActuated())
        ///         {
        ///             // We've waited for input and got it. Start the interaction.
        ///             context.Started();
        ///         }
        ///         else if (context.isStarted &amp;&amp; !context.ControlIsActuated())
        ///         {
        ///             // Interaction has been completed.
        ///             context.Performed();
        ///         }
        ///     }
        ///
        ///     public void Reset()
        ///     {
        ///         // No reset code needed. We're not keeping any state locally in the interaction.
        ///     }
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        public void Started()
        {
            m_TriggerState.startTime = time;
            m_State.ChangePhaseOfInteraction(InputActionPhase.Started, ref m_TriggerState);
        }

        public void Performed()
        {
            if (m_TriggerState.phase == InputActionPhase.Waiting)
                m_TriggerState.startTime = time;
            m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
        }

        public void PerformedAndStayStarted()
        {
            if (m_TriggerState.phase == InputActionPhase.Waiting)
                m_TriggerState.startTime = time;
            m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
                phaseAfterPerformed: InputActionPhase.Started);
        }

        public void PerformedAndStayPerformed()
        {
            if (m_TriggerState.phase == InputActionPhase.Waiting)
                m_TriggerState.startTime = time;
            m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
                phaseAfterPerformed: InputActionPhase.Performed);
        }

        public void Canceled()
        {
            if (m_TriggerState.phase != InputActionPhase.Canceled)
                m_State.ChangePhaseOfInteraction(InputActionPhase.Canceled, ref m_TriggerState);
        }

        /// <summary>
        /// Put the interaction back into <see cref="InputActionPhase.Waiting"/> state.
        /// </summary>
        /// <seealso cref="InputAction.phase"/>
        /// <seealso cref="InputActionPhase"/>
        /// <seealso cref="Started"/>
        /// <seealso cref="Performed"/>
        /// <seealso cref="Canceled"/>
        public void Waiting()
        {
            if (m_TriggerState.phase != InputActionPhase.Waiting)
                m_State.ChangePhaseOfInteraction(InputActionPhase.Waiting, ref m_TriggerState);
        }

        /// <summary>
        /// Start a timeout that triggers within <paramref name="seconds"/>.
        /// </summary>
        /// <param name="seconds">Number of seconds before the timeout is triggered.</param>
        /// <remarks>
        /// An interaction might wait a set amount of time for something to happen and then
        /// do something depending on whether it did or did not happen. By calling this method,
        /// a timeout is installed such that in the input update that the timer expires in, the
        /// interaction's <see cref="IInputInteraction.Process"/> method is called with <see cref="timerHasExpired"/>
        /// being true.
        ///
        /// Changing the phase of the interaction while a timeout is running will implicitly cancel
        /// the timeout.
        ///
        /// <example>
        /// <code>
        /// // Let's say we're writing a Process() method for an interaction that,
        /// // after a control has been actuated, waits for 1 second for it to be
        /// // released again. If that happens, the interaction performs. If not,
        /// // it cancels.
        /// public void Process(ref InputInteractionContext context)
        /// {
        ///     // timerHasExpired will be true if we get called when our timeout
        ///     // has expired.
        ///     if (context.timerHasExpired)
        ///     {
        ///         // The user did not release the control quickly enough.
        ///         // Our interaction is not successful, so cancel.
        ///         context.Canceled();
        ///         return;
        ///     }
        ///
        ///     if (context.ControlIsActuated())
        ///     {
        ///         if (!context.isStarted)
        ///         {
        ///             // The control has been actuated. We want to give the user a max
        ///             // of 1 second to release it. So we start the interaction now and then
        ///             // set the timeout.
        ///             context.Started();
        ///             context.SetTimeout(1);
        ///         }
        ///     }
        ///     else
        ///     {
        ///         // Control has been released. If we're currently waiting for a release,
        ///         // it has come in time before out timeout expired. In other words, the
        ///         // interaction has been successfully performed. We call Performed()
        ///         // which implicitly removes our ongoing timeout.
        ///         if (context.isStarted)
        ///             context.Performed();
        ///     }
        /// }
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="timerHasExpired"/>
        public void SetTimeout(float seconds)
        {
            m_State.StartTimeout(seconds, ref m_TriggerState);
        }

        /// <summary>
        /// Override the default timeout value used by <see cref="InputAction.GetTimeoutCompletionPercentage"/>.
        /// </summary>
        /// <param name="seconds">Amount of total successive timeouts TODO</param>
        /// <exception cref="ArgumentException"></exception>
        /// <remarks>
        /// By default, timeout completion will be entirely determine by the timeout that is currently
        /// running, if any. However, some interactions (such as <see cref="Interactions.MultiTapInteraction"/>)
        /// will have to run multiple timeouts in succession. Thus, completion of a single timeout is not
        /// the same as completion of the interaction.
        ///
        /// You can use this method to account for this.
        ///
        /// Whenever a timeout completes, the timeout duration will automatically be accumulated towards
        /// the total timeout completion time.
        ///
        /// <example>
        /// <code>
        /// // Let's say we're starting our first timeout and we know that we will run three timeouts
        /// // in succession of 2 seconds each. By calling SetTotalTimeoutCompletionTime(), we can account for this.
        /// SetTotalTimeoutCompletionTime(3 * 2);
        ///
        /// // Start the first timeout. When this timeout expires, it will automatically
        /// // count one second towards the total timeout completion time.
        /// SetTimeout(2);
        /// </code>
        /// </example>
        /// </remarks>
        /// <seealso cref="InputAction.GetTimeoutCompletionPercentage"/>
        public void SetTotalTimeoutCompletionTime(float seconds)
        {
            if (seconds <= 0)
                throw new ArgumentException("Seconds must be a positive value", nameof(seconds));

            m_State.SetTotalTimeoutCompletionTime(seconds, ref m_TriggerState);
        }

        /// <summary>
        /// Read the value of the binding that triggered processing of the interaction.
        /// </summary>
        /// <typeparam name="TValue">Type of value to read from the binding. Must match the value type of the control
        /// or composite in effect for the binding.</typeparam>
        /// <returns>Value read from the binding.</returns>
        public TValue ReadValue<TValue>()
            where TValue : struct
        {
            return m_State.ReadValue<TValue>(m_TriggerState.bindingIndex, m_TriggerState.controlIndex);
        }

        internal InputActionState m_State;
        internal Flags m_Flags;
        internal InputActionState.TriggerState m_TriggerState;

        internal int mapIndex => m_TriggerState.mapIndex;

        internal int controlIndex => m_TriggerState.controlIndex;

        internal int bindingIndex => m_TriggerState.bindingIndex;

        internal int interactionIndex => m_TriggerState.interactionIndex;

        [Flags]
        internal enum Flags
        {
            TimerHasExpired = 1 << 1
        }
    }
}