File size: 11,440 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
using System;
using System.ComponentModel;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Scripting;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.InputSystem.Editor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
#endif

////TODO: protect against the control *hovering* around the press point; this should not fire the press repeatedly; probably need a zone around the press point
////TODO: also, for analog controls, we probably want a deadzone that gives just a tiny little buffer at the low end before the action starts

////REVIEW: shouldn't it use Canceled for release on PressAndRelease instead of triggering Performed again?

namespace UnityEngine.InputSystem.Interactions
{
    /// <summary>
    /// Performs the action at specific points in a button press-and-release sequence according top <see cref="behavior"/>.
    /// </summary>
    /// <remarks>
    /// By default, uses <see cref="PressBehavior.PressOnly"/> which performs the action as soon as the control crosses the
    /// button press threshold defined by <see cref="pressPoint"/>. The action then will not trigger again until the control
    /// is first released.
    ///
    /// Can be set to instead trigger on release (that is, when the control goes back below the button press threshold) using
    /// <see cref="PressBehavior.ReleaseOnly"/> or can be set to trigger on both press and release using <see cref="PressBehavior.PressAndRelease"/>).
    ///
    /// Note that using an explicit press interaction is only necessary if the goal is to either customize the press behavior
    /// of a button or when binding to controls that are not buttons as such (the press interaction compares magnitudes to
    /// <see cref="pressPoint"/> and thus any type of control that can deliver a magnitude can act as a button). The default
    /// behavior available out of the box when binding <see cref="InputActionType.Button"/> type actions to button-type controls
    /// (<see cref="UnityEngine.InputSystem.Controls.ButtonControl"/>) corresponds to using a press modifier with <see cref="behavior"/>
    /// set to <see cref="PressBehavior.PressOnly"/> and <see cref="pressPoint"/> left at default.
    /// </remarks>
    [DisplayName("Press")]
    public class PressInteraction : IInputInteraction
    {
        /// <summary>
        /// Amount of actuation required before a control is considered pressed.
        /// </summary>
        /// <remarks>
        /// If zero (default), defaults to <see cref="InputSettings.defaultButtonPressPoint"/>.
        /// </remarks>
        [Tooltip("The amount of actuation a control requires before being considered pressed. If not set, default to "
            + "'Default Press Point' in the global input settings.")]
        public float pressPoint;

        ////REVIEW: this should really be named "pressBehavior"
        /// <summary>
        /// Determines how button presses trigger the action.
        /// </summary>
        /// <remarks>
        /// By default (PressOnly), the action is performed on press.
        /// With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is
        /// performed on press and on release.
        /// </remarks>
        [Tooltip("Determines how button presses trigger the action. By default (PressOnly), the action is performed on press. "
            + "With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is performed on press and release.")]
        public PressBehavior behavior;

        private float pressPointOrDefault => pressPoint > 0 ? pressPoint : ButtonControl.s_GlobalDefaultButtonPressPoint;
        private float releasePointOrDefault => pressPointOrDefault * ButtonControl.s_GlobalDefaultButtonReleaseThreshold;
        private bool m_WaitingForRelease;

        public void Process(ref InputInteractionContext context)
        {
            var actuation = context.ComputeMagnitude();
            switch (behavior)
            {
                case PressBehavior.PressOnly:
                    if (m_WaitingForRelease)
                    {
                        if (actuation <= releasePointOrDefault)
                        {
                            m_WaitingForRelease = false;
                            if (Mathf.Approximately(0f, actuation))
                                context.Canceled();
                            else
                                context.Started();
                        }
                    }
                    else if (actuation >= pressPointOrDefault)
                    {
                        m_WaitingForRelease = true;
                        // Stay performed until release.
                        context.PerformedAndStayPerformed();
                    }
                    else if (actuation > 0 && !context.isStarted)
                    {
                        context.Started();
                    }
                    else if (Mathf.Approximately(0f, actuation) && context.isStarted)
                    {
                        context.Canceled();
                    }
                    break;

                case PressBehavior.ReleaseOnly:
                    if (m_WaitingForRelease)
                    {
                        if (actuation <= releasePointOrDefault)
                        {
                            m_WaitingForRelease = false;
                            context.Performed();
                            context.Canceled();
                        }
                    }
                    else if (actuation >= pressPointOrDefault)
                    {
                        m_WaitingForRelease = true;
                        if (!context.isStarted)
                            context.Started();
                    }
                    else
                    {
                        var started = context.isStarted;
                        if (actuation > 0 && !started)
                            context.Started();
                        else if (Mathf.Approximately(0, actuation) && started)
                            context.Canceled();
                    }
                    break;

                case PressBehavior.PressAndRelease:
                    if (m_WaitingForRelease)
                    {
                        if (actuation <= releasePointOrDefault)
                        {
                            m_WaitingForRelease = false;
                            context.Performed();
                            if (Mathf.Approximately(0, actuation))
                                context.Canceled();
                        }
                    }
                    else if (actuation >= pressPointOrDefault)
                    {
                        m_WaitingForRelease = true;
                        context.PerformedAndStayPerformed();
                    }
                    else
                    {
                        var started = context.isStarted;
                        if (actuation > 0 && !started)
                            context.Started();
                        else if (Mathf.Approximately(0, actuation) && started)
                            context.Canceled();
                    }
                    break;
            }
        }

        public void Reset()
        {
            m_WaitingForRelease = false;
        }
    }

    /// <summary>
    /// Determines how to trigger an action based on button presses.
    /// </summary>
    /// <seealso cref="PressInteraction.behavior"/>
    public enum PressBehavior
    {
        /// <summary>
        /// Perform the action when the button is pressed.
        /// </summary>
        /// <remarks>
        /// Triggers <see cref="InputAction.performed"/> when a control crosses the button press threshold.
        /// </remarks>
        // ReSharper disable once UnusedMember.Global
        PressOnly = 0,

        /// <summary>
        /// Perform the action when the button is released.
        /// </summary>
        /// <remarks>
        /// Triggers <see cref="InputAction.started"/> when a control crosses the button press threshold and
        /// <see cref="InputAction.performed"/> when the control goes back below the button press threshold.
        /// </remarks>
        // ReSharper disable once UnusedMember.Global
        ReleaseOnly = 1,

        /// <summary>
        /// Perform the action when the button is pressed and when the button is released.
        /// </summary>
        /// <remarks>
        /// Triggers <see cref="InputAction.performed"/> when a control crosses the button press threshold
        /// and triggers <see cref="InputAction.performed"/> again when it goes back below the button press
        /// threshold.
        /// </remarks>
        // ReSharper disable once UnusedMember.Global
        PressAndRelease = 2,
    }

    #if UNITY_EDITOR
    /// <summary>
    /// UI that is displayed when editing <see cref="PressInteraction"/> in the editor.
    /// </summary>
    // ReSharper disable once UnusedMember.Global
    internal class PressInteractionEditor : InputParameterEditor<PressInteraction>
    {
        protected override void OnEnable()
        {
            m_PressPointSetting.Initialize("Press Point",
                "The amount of actuation a control requires before being considered pressed. If not set, default to "
                + "'Default Button Press Point' in the global input settings.",
                "Default Button Press Point",
                () => target.pressPoint, v => target.pressPoint = v,
                () => InputSystem.settings.defaultButtonPressPoint);
        }

        public override void OnGUI()
        {
            EditorGUILayout.HelpBox(s_HelpBoxText);
            target.behavior = (PressBehavior)EditorGUILayout.EnumPopup(s_PressBehaviorLabel, target.behavior);
            m_PressPointSetting.OnGUI();
        }

#if UNITY_INPUT_SYSTEM_UI_TK_ASSET_EDITOR
        public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
        {
            root.Add(new HelpBox(s_HelpBoxText.text, HelpBoxMessageType.None));

            var behaviourDropdown = new EnumField(s_PressBehaviorLabel.text, target.behavior);
            behaviourDropdown.RegisterValueChangedCallback(evt =>
            {
                target.behavior = (PressBehavior)evt.newValue;
                onChangedCallback?.Invoke();
            });
            root.Add(behaviourDropdown);

            m_PressPointSetting.OnDrawVisualElements(root, onChangedCallback);
        }

#endif

        private CustomOrDefaultSetting m_PressPointSetting;

        private static readonly GUIContent s_HelpBoxText = EditorGUIUtility.TrTextContent("Note that the 'Press' interaction is only "
            + "necessary when wanting to customize button press behavior. For default press behavior, simply set the action type to 'Button' "
            + "and use the action without interactions added to it.");

        private static readonly GUIContent s_PressBehaviorLabel = EditorGUIUtility.TrTextContent("Trigger Behavior",
            "Determines how button presses trigger the action. By default (PressOnly), the action is performed on press. "
            + "With ReleaseOnly, the action is performed on release. With PressAndRelease, the action is performed on press and "
            + "canceled on release.");
    }
    #endif
}