using System;
using UnityEngine.InputSystem.LowLevel;
namespace UnityEngine.InputSystem
{
///
/// Information passed to interactions
/// when their associated controls trigger.
///
///
public struct InputInteractionContext
{
///
/// The action associated with the binding.
///
///
/// If the binding is not associated with an action, this is null.
///
///
public InputAction action => m_State.GetActionOrNull(ref m_TriggerState);
///
/// The bound control that changed its state to trigger the binding associated
/// with the interaction.
///
///
/// In case the binding associated with the interaction is a composite, this is
/// one of the controls that are part of the composite.
///
///
public InputControl control => m_State.GetControl(ref m_TriggerState);
///
/// The phase the interaction is currently in.
///
///
/// 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.
///
///
///
///
///
///
public InputActionPhase phase => m_TriggerState.phase;
///
/// Time stamp of the input event that caused to trigger a change in the
/// state of .
///
///
public double time => m_TriggerState.time;
///
/// Timestamp of the that caused the interaction to transition
/// to .
///
///
public double startTime => m_TriggerState.startTime;
///
/// Whether the interaction's method has been called because
/// a timer set by has expired.
///
///
public bool timerHasExpired
{
get => (m_Flags & Flags.TimerHasExpired) != 0;
internal set
{
if (value)
m_Flags |= Flags.TimerHasExpired;
else
m_Flags &= ~Flags.TimerHasExpired;
}
}
///
/// True if the interaction is waiting for input
///
///
/// By default, an interaction will return this this phase after every time it has been performed
/// (). This can be changed by using
/// or .
///
///
public bool isWaiting => phase == InputActionPhase.Waiting;
///
/// True if the interaction has been started.
///
///
///
public bool isStarted => phase == InputActionPhase.Started;
///
/// Compute the current level of control actuation.
///
/// The current level of control actuation (usually [0..1]) or -1 if the control is actuated
/// but does not support computing magnitudes.
///
///
public float ComputeMagnitude()
{
return m_TriggerState.magnitude;
}
///
/// Return true if the control that triggered the interaction has been actuated beyond the given 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.
/// True if the trigger control is actuated.
///
///
public bool ControlIsActuated(float threshold = 0)
{
return InputActionState.IsActuated(ref m_TriggerState, threshold);
}
///
/// Mark the interaction has having begun.
///
///
/// This affects the current interaction only. There might be multiple interactions on a binding
/// and arbitrary many interactions might 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.
///
///
///
/// public class MyInteraction : IInputInteraction<float>
/// {
/// public void Process(ref IInputInteractionContext context)
/// {
/// if (context.isWaiting && context.ControlIsActuated())
/// {
/// // We've waited for input and got it. Start the interaction.
/// context.Started();
/// }
/// else if (context.isStarted && !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.
/// }
/// }
///
///
///
public void Started()
{
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Started, ref m_TriggerState);
}
///
/// Marks the interaction as being performed and then transitions back to
/// to wait for input. This behavior is desirable for interaction events that are instant and reflect
/// a transitional interaction pattern such as or .
///
///
/// Note that this affects the current interaction only. There might be multiple interactions on a binding
/// and arbitrary many interactions might 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.
///
public void Performed()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
}
///
/// Marks the interaction as being performed and then transitions into I
/// to wait for an initial trigger condition to be true before being performed again. This behavior
/// may be desirable for interaction events that reflect transitional interaction patterns but should
/// be considered as started until a cancellation condition is true, such as releasing a button.
///
public void PerformedAndStayStarted()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
phaseAfterPerformed: InputActionPhase.Started);
}
///
/// Marks the interaction as being performed and then stays in that state waiting for an input to
/// cancel the interactions active state. This behavior is desirable for interaction events that
/// are active for a duration until a cancellation condition is true, such as or where releasing
/// the associated button cancels the interaction..
///
public void PerformedAndStayPerformed()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
phaseAfterPerformed: InputActionPhase.Performed);
}
///
/// Marks the interaction as being interrupted or aborted. This is relevant to signal that the interaction
/// pattern was not completed, for example, the user pressed and then released a button before the minimum
/// time required for a to complete.
///
///
/// This is used by most existing interactions to cancel the transitions in the interaction state machine
/// when a condition required to proceed turned false or other indirect requirements were not met, such as
/// time-based conditions.
///
public void Canceled()
{
if (m_TriggerState.phase != InputActionPhase.Canceled)
m_State.ChangePhaseOfInteraction(InputActionPhase.Canceled, ref m_TriggerState);
}
///
/// Put the interaction back into state.
///
///
///
///
///
///
public void Waiting()
{
if (m_TriggerState.phase != InputActionPhase.Waiting)
m_State.ChangePhaseOfInteraction(InputActionPhase.Waiting, ref m_TriggerState);
}
///
/// Start a timeout that triggers within .
///
/// Number of seconds before the timeout is triggered.
///
/// 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 method is called with
/// being true.
///
/// Changing the phase of the interaction while a timeout is running will implicitly cancel
/// the timeout.
///
///
///
/// // 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();
/// }
/// }
///
///
///
///
public void SetTimeout(float seconds)
{
m_State.StartTimeout(seconds, ref m_TriggerState);
}
///
/// Override the default timeout value used by .
///
/// Amount of total successive timeouts TODO
///
///
/// By default, timeout completion will be entirely determine by the timeout that is currently
/// running, if any. However, some interactions (such as )
/// 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.
///
///
///
/// // 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);
///
///
///
///
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);
}
///
/// Read the value of the binding that triggered processing of the interaction.
///
/// Type of value to read from the binding. Must match the value type of the control
/// or composite in effect for the binding.
/// Value read from the binding.
public TValue ReadValue()
where TValue : struct
{
return m_State.ReadValue(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
}
}
}