File size: 15,920 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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;

////TODO: expose whether pen actually has eraser and which barrel buttons it has

////TODO: hook up pointerId in backend to allow identifying different pens

////REVIEW: have surface distance property to detect how far pen is when hovering?

////REVIEW: does it make sense to have orientation support for pen, too?

namespace UnityEngine.InputSystem.LowLevel
{
    /// <summary>
    /// Default state layout for pen devices.
    /// </summary>
    // IMPORTANT: Must match with PenInputState in native.
    [StructLayout(LayoutKind.Explicit, Size = 36)]
    public struct PenState : IInputStateTypeInfo
    {
        /// <summary>
        /// Format code for PenState.
        /// </summary>
        /// <value>Returns "PEN ".</value>
        /// <seealso cref="InputStateBlock.format"/>
        public static FourCC Format => new FourCC('P', 'E', 'N');

        /// <summary>
        /// Current screen-space position of the pen.
        /// </summary>
        /// <value>Screen-space position.</value>
        /// <seealso cref="Pointer.position"/>
        [InputControl(usage = "Point", dontReset = true)]
        [FieldOffset(0)]
        public Vector2 position;

        /// <summary>
        /// Screen-space motion delta.
        /// </summary>
        /// <value>Screen-space motion delta.</value>
        /// <seealso cref="Pointer.delta"/>
        [InputControl(usage = "Secondary2DMotion", layout = "Delta")]
        [FieldOffset(8)]
        public Vector2 delta;

        /// <summary>
        /// The way the pen is leaned over perpendicular to the tablet surface. X goes [-1..1] left to right
        /// (with -1 and 1 being completely flush to the surface) and Y goes [-1..1] bottom to top.
        /// </summary>
        /// <value>Amount pen is leaning over.</value>
        /// <seealso cref="Pen.tilt"/>
        [InputControl(layout = "Vector2", displayName = "Tilt", usage = "Tilt")]
        [FieldOffset(16)]
        public Vector2 tilt;

        /// <summary>
        /// Pressure with which the pen is pressed against the surface. 0 is none, 1 is full pressure.
        /// </summary>
        /// <value>Pressure with which the pen is pressed.</value>
        /// <remarks>
        /// May go beyond 1 depending on pressure calibration on the system. The maximum pressure point
        /// may be set to less than the physical maximum pressure point determined by the hardware.
        /// </remarks>
        /// <seealso cref="Pointer.pressure"/>
        [InputControl(layout = "Analog", usage = "Pressure", defaultState = 0.0f)]
        [FieldOffset(24)]
        public float pressure;

        /// <summary>
        /// Amount by which the pen is rotated around itself.
        /// </summary>
        /// <value>Rotation of the pen around itself.</value>
        /// <seealso cref="Pen.twist"/>
        [InputControl(layout = "Axis", displayName = "Twist", usage = "Twist")]
        [FieldOffset(28)]
        public float twist;

        /// <summary>
        /// Button mask for which buttons on the pen are active.
        /// </summary>
        /// <value>Bitmask for buttons on the pen.</value>
        [InputControl(name = "tip", displayName = "Tip", layout = "Button", bit = (int)PenButton.Tip, usage = "PrimaryAction")]
        [InputControl(name = "press", useStateFrom = "tip", synthetic = true, usages = new string[0])]
        [InputControl(name = "eraser", displayName = "Eraser", layout = "Button", bit = (int)PenButton.Eraser)]
        [InputControl(name = "inRange", displayName = "In Range?", layout = "Button", bit = (int)PenButton.InRange, synthetic = true)]
        [InputControl(name = "barrel1", displayName = "Barrel Button #1", layout = "Button", bit = (int)PenButton.BarrelFirst, alias = "barrelFirst", usage = "SecondaryAction")]
        [InputControl(name = "barrel2", displayName = "Barrel Button #2", layout = "Button", bit = (int)PenButton.BarrelSecond, alias = "barrelSecond")]
        [InputControl(name = "barrel3", displayName = "Barrel Button #3", layout = "Button", bit = (int)PenButton.BarrelThird, alias = "barrelThird")]
        [InputControl(name = "barrel4", displayName = "Barrel Button #4", layout = "Button", bit = (int)PenButton.BarrelFourth, alias = "barrelFourth")]
        // "Park" unused controls.
        [InputControl(name = "radius", layout = "Vector2", format = "VEC2", sizeInBits = 64, usage = "Radius", offset = InputStateBlock.AutomaticOffset)]
        [InputControl(name = "pointerId", layout = "Digital", format = "UINT", sizeInBits = 32, offset = InputStateBlock.AutomaticOffset)] ////TODO: this should be used
        [FieldOffset(32)]
        public ushort buttons;

        // Not currently used, but still needed in this struct for padding,
        // as il2cpp does not implement FieldOffset.
        [FieldOffset(34)]
        ushort displayIndex;

        /// <summary>
        /// Set or unset the bit in <see cref="buttons"/> for the given <paramref name="button"/>.
        /// </summary>
        /// <param name="button">Button whose state to set.</param>
        /// <param name="state">Whether the button is on or off.</param>
        /// <returns>Same PenState with an updated <see cref="buttons"/> mask.</returns>
        public PenState WithButton(PenButton button, bool state = true)
        {
            Debug.Assert((int)button < 16, $"Expected button < 16, so we fit into the 16 bit wide bitmask");
            var bit = 1U << (int)button;
            if (state)
                buttons |= (ushort)bit;
            else
                buttons &= (ushort)~bit;
            return this;
        }

        /// <inheritdoc />
        public FourCC format => Format;
    }
}

namespace UnityEngine.InputSystem
{
    /// <summary>
    /// Enumeration of buttons on a <see cref="Pen"/>.
    /// </summary>
    public enum PenButton
    {
        /// <summary>
        /// Button at the tip of a pen.
        /// </summary>
        /// <seealso cref="Pen.tip"/>
        Tip,

        /// <summary>
        /// Button located end of pen opposite to <see cref="Tip"/>.
        /// </summary>
        /// <remarks>
        /// Pens do not necessarily have an eraser. If a pen doesn't, the respective button
        /// does nothing and will always be unpressed.
        /// </remarks>
        /// <seealso cref="Pen.eraser"/>
        Eraser,

        /// <summary>
        /// First button on the side of the pen.
        /// </summary>
        /// <see cref="Pen.firstBarrelButton"/>
        BarrelFirst,

        /// <summary>
        /// Second button on the side of the pen.
        /// </summary>
        /// <seealso cref="Pen.secondBarrelButton"/>
        BarrelSecond,

        /// <summary>
        /// Artificial button that indicates whether the pen is in detection range or not.
        /// </summary>
        /// <remarks>
        /// Range detection may not be supported by a pen/tablet.
        /// </remarks>
        /// <seealso cref="Pen.inRange"/>
        InRange,

        /// <summary>
        /// Third button on the side of the pen.
        /// </summary>
        /// <seealso cref="Pen.thirdBarrelButton"/>
        BarrelThird,

        /// <summary>
        /// Fourth button on the side of the pen.
        /// </summary>
        /// <see cref="Pen.fourthBarrelButton"/>
        BarrelFourth,

        /// <summary>
        /// Synonym for <see cref="BarrelFirst"/>.
        /// </summary>
        Barrel1 = BarrelFirst,

        /// <summary>
        /// Synonym for <see cref="BarrelSecond"/>.
        /// </summary>
        Barrel2 = BarrelSecond,

        /// <summary>
        /// Synonym for <see cref="BarrelThird"/>.
        /// </summary>
        Barrel3 = BarrelThird,

        /// <summary>
        /// Synonym for <see cref="BarrelFourth"/>.
        /// </summary>
        Barrel4 = BarrelFourth,
    }

    /// <summary>
    /// Represents a pen/stylus input device.
    /// </summary>
    /// <remarks>
    /// Unlike mice but like touch, pens are absolute pointing devices moving across a fixed
    /// surface area.
    ///
    /// The <see cref="tip"/> acts as a button that is considered pressed as long as the pen is in contact with the
    /// tablet surface.
    /// </remarks>
    [InputControlLayout(stateType = typeof(PenState), isGenericTypeOfDevice = true)]
    public class Pen : Pointer
    {
        ////TODO: give the tip and eraser a very low press point
        /// <summary>
        /// The tip button of the pen.
        /// </summary>
        /// <value>Control representing the tip button.</value>
        /// <seealso cref="PenButton.Tip"/>
        public ButtonControl tip { get; protected set; }

        /// <summary>
        /// The eraser button of the pen, i.e. the button on the end opposite to the tip.
        /// </summary>
        /// <value>Control representing the eraser button.</value>
        /// <remarks>
        /// If the pen does not have an eraser button, this control will still be present
        /// but will not trigger.
        /// </remarks>
        /// <seealso cref="PenButton.Eraser"/>
        public ButtonControl eraser { get; protected set; }

        /// <summary>
        /// The button on the side of the pen barrel and located closer to the tip of the pen.
        /// </summary>
        /// <value>Control representing the first side button.</value>
        /// <remarks>
        /// If the pen does not have barrel buttons, this control will still be present
        /// but will not trigger.
        /// </remarks>
        /// <seealso cref="PenButton.BarrelFirst"/>
        public ButtonControl firstBarrelButton { get; protected set; }

        /// <summary>
        /// The button on the side of the pen barrel and located closer to the eraser end of the pen.
        /// </summary>
        /// <value>Control representing the second side button.</value>
        /// <remarks>
        /// If the pen does not have barrel buttons, this control will still be present
        /// but will not trigger.
        /// </remarks>
        /// <seealso cref="PenButton.BarrelSecond"/>
        public ButtonControl secondBarrelButton { get; protected set; }

        /// <summary>
        /// Third button the side of the pen barrel.
        /// </summary>
        /// <value>Control representing the third side button.</value>
        /// <remarks>
        /// If the pen does not have a third barrel buttons, this control will still be present
        /// but will not trigger.
        /// </remarks>
        /// <seealso cref="PenButton.BarrelThird"/>
        public ButtonControl thirdBarrelButton { get; protected set; }

        /// <summary>
        /// Fourth button the side of the pen barrel.
        /// </summary>
        /// <value>Control representing the fourth side button.</value>
        /// <remarks>
        /// If the pen does not have a fourth barrel buttons, this control will still be present
        /// but will not trigger.
        /// </remarks>
        /// <seealso cref="PenButton.BarrelFourth"/>
        public ButtonControl fourthBarrelButton { get; protected set; }

        /// <summary>
        /// Button control that indicates whether the pen is in range of the tablet surface or not.
        /// </summary>
        /// <remarks>
        /// This is a synthetic control (<see cref="InputControl.synthetic"/>).
        ///
        /// If range detection is not supported by the pen, this button will always be "pressed".
        /// </remarks>
        /// <seealso cref="PenButton.InRange"/>
        public ButtonControl inRange { get; protected set; }

        /// <summary>
        /// Orientation of the pen relative to the tablet surface, i.e. the amount by which it is leaning
        /// over along the X and Y axis.
        /// </summary>
        /// <value>Control presenting the amount the pen is leaning over.</value>
        /// <remarks>
        /// X axis goes from [-1..1] left to right with -1 and 1 meaning the pen is flush with the tablet surface. Y axis
        /// goes from [-1..1] bottom to top.
        /// </remarks>
        public Vector2Control tilt { get; protected set; }

        /// <summary>
        /// Rotation of the pointer around its own axis. 0 means the pointer is facing away from the user (12 'o clock position)
        /// and ~1 means the pointer has been rotated clockwise almost one full rotation.
        /// </summary>
        /// <value>Control representing the twist of the pen around itself.</value>
        /// <remarks>
        /// Twist is generally only supported by pens and even among pens, twist support is rare. An example product that
        /// supports twist is the Wacom Art Pen.
        ///
        /// The axis of rotation is the vector facing away from the pointer surface when the pointer is facing straight up
        /// (i.e. the surface normal of the pointer surface). When the pointer is tilted, the rotation axis is tilted along
        /// with it.
        /// </remarks>
        public AxisControl twist { get; protected set; }

        /// <summary>
        /// The pen that was active or connected last or <c>null</c> if there is no pen.
        /// </summary>
        public new static Pen current { get; internal set; }

        /// <summary>
        /// Return the given pen button.
        /// </summary>
        /// <param name="button">Pen button to return.</param>
        /// <exception cref="ArgumentException"><paramref name="button"/> is not a valid pen button.</exception>
        public ButtonControl this[PenButton button]
        {
            get
            {
                switch (button)
                {
                    case PenButton.Tip: return tip;
                    case PenButton.Eraser: return eraser;
                    case PenButton.BarrelFirst: return firstBarrelButton;
                    case PenButton.BarrelSecond: return secondBarrelButton;
                    case PenButton.BarrelThird: return thirdBarrelButton;
                    case PenButton.BarrelFourth: return fourthBarrelButton;
                    case PenButton.InRange: return inRange;
                    default:
                        throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(PenButton));
                }
            }
        }

        /// <summary>
        /// Make this the last used pen, i.e. <see cref="current"/>.
        /// </summary>
        /// <remarks>
        /// This is called automatically by the system when a pen is added or receives
        /// input.
        /// </remarks>
        public override void MakeCurrent()
        {
            base.MakeCurrent();
            current = this;
        }

        /// <summary>
        /// Called when the pen is removed from the system.
        /// </summary>
        protected override void OnRemoved()
        {
            base.OnRemoved();
            if (current == this)
                current = null;
        }

        /// <inheritdoc />
        protected override void FinishSetup()
        {
            tip = GetChildControl<ButtonControl>("tip");
            eraser = GetChildControl<ButtonControl>("eraser");
            firstBarrelButton = GetChildControl<ButtonControl>("barrel1");
            secondBarrelButton = GetChildControl<ButtonControl>("barrel2");
            thirdBarrelButton = GetChildControl<ButtonControl>("barrel3");
            fourthBarrelButton = GetChildControl<ButtonControl>("barrel4");
            inRange = GetChildControl<ButtonControl>("inRange");
            tilt = GetChildControl<Vector2Control>("tilt");
            twist = GetChildControl<AxisControl>("twist");
            base.FinishSetup();
        }
    }
}