File size: 7,224 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
using System;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;

////TODO: hide in UI

namespace UnityEngine.InputSystem.Controls
{
    /// <summary>
    /// A button that is considered pressed if the underlying state has a value in the specific range.
    /// </summary>
    /// <remarks>
    /// This control is most useful for handling HID-style hat switches. Unlike <see cref="DpadControl"/>,
    /// which by default is stored as a bitfield of four bits that each represent a direction on the pad,
    /// these hat switches enumerate the possible directions that the switch can be moved in. For example,
    /// the value 1 could indicate that the switch is moved to the left whereas 3 could indicate it is
    /// moved up.
    ///
    /// <example>
    /// <code>
    /// [StructLayout(LayoutKind.Explicit, Size = 32)]
    /// internal struct DualShock4HIDInputReport : IInputStateTypeInfo
    /// {
    ///     [FieldOffset(0)] public byte reportId;
    ///
    ///     [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
    ///     [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 0, sizeInBits = 4)]
    ///     [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 0, sizeInBits = 4)]
    ///     [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 0, sizeInBits = 4)]
    ///     [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=5, maxValue=7", bit = 0, sizeInBits = 4)]
    ///     [FieldOffset(5)] public byte buttons1;
    /// }
    /// </code>
    /// </example>
    /// </remarks>
    public class DiscreteButtonControl : ButtonControl
    {
        /// <summary>
        /// Value (inclusive) at which to start considering the button to be pressed.
        /// </summary>
        /// <remarks>
        /// <see cref="minValue"/> is allowed to be larger than <see cref="maxValue"/>. This indicates
        /// a setup where the value wraps around beyond <see cref="minValue"/>, skips <see cref="nullValue"/>,
        /// and then goes all the way up to <see cref="maxValue"/>.
        ///
        /// For example, if the underlying state represents a circular D-pad and enumerates its
        /// 9 possible positions (including null state) going clock-wise from 0 to 8 and with 1
        /// indicating that the D-pad is pressed to the left, then 1, 2, and 8 would indicate
        /// that the "left" button is held on the D-pad. To set this up, set <see cref="minValue"/>
        /// to 8, <see cref="maxValue"/> to 2, and <see cref="nullValue"/> to 0 (the default).
        /// </remarks>
        public int minValue;

        /// <summary>
        /// Value (inclusive) beyond which to stop considering the button to be pressed.
        /// </summary>
        public int maxValue;

        /// <summary>
        /// Value (inclusive) at which the values cut off and wrap back around. Considered to not be set if equal to <see cref="nullValue"/>.
        /// </summary>
        /// <remarks>
        /// This is useful if, for example, an enumeration has more bits than are used for "on" states of controls. For example,
        /// a bitfield of 4 bits where values 1-7 (i.e. 0001 to 0111) indicate "on" states of controls and value 8 indicating an
        /// "off" state. In that case, set <see cref="nullValue"/> to 8 and <see cref="wrapAtValue"/> to 7.
        /// </remarks>
        public int wrapAtValue;

        /// <summary>
        /// Value at which the button is considered to not be pressed. Usually zero. Some devices, however,
        /// use non-zero default states.
        /// </summary>
        public int nullValue;

        /// <summary>
        /// Determines the behavior of <see cref="WriteValueIntoState"/>. By default, attempting to write a <see cref="DiscreteButtonControl"/>
        /// will result in a <c>NotSupportedException</c>.
        /// </summary>
        public WriteMode writeMode;

        protected override void FinishSetup()
        {
            base.FinishSetup();

            if (!stateBlock.format.IsIntegerFormat())
                throw new NotSupportedException(
                    $"Non-integer format '{stateBlock.format}' is not supported for DiscreteButtonControl '{this}'");
        }

        public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
        {
            var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
            // Note that all signed data in state buffers is in excess-K format.
            var intValue = MemoryHelpers.ReadTwosComplementMultipleBitsAsInt(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits);

            var value = 0.0f;
            if (minValue > maxValue)
            {
                // If no wrapping point is set, default to wrapping around exactly
                // at the point of minValue.
                if (wrapAtValue == nullValue)
                    wrapAtValue = minValue;

                if ((intValue >= minValue && intValue <= wrapAtValue)
                    || (intValue != nullValue && intValue <= maxValue))
                    value = 1.0f;
            }
            else
            {
                value = intValue >= minValue && intValue <= maxValue ? 1.0f : 0.0f;
            }

            return Preprocess(value);
        }

        public override unsafe void WriteValueIntoState(float value, void* statePtr)
        {
            if (writeMode == WriteMode.WriteNullAndMaxValue)
            {
                var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
                var valueToWrite = value >= pressPointOrDefault ? maxValue : nullValue;
                MemoryHelpers.WriteIntAsTwosComplementMultipleBits(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits, valueToWrite);
                return;
            }

            // The way these controls are usually used, the state is shared between multiple DiscreteButtons. So writing one
            // may have unpredictable effects on the value of other buttons.
            throw new NotSupportedException("Writing value states for DiscreteButtonControl is not supported as a single value may correspond to multiple states");
        }

        /// <summary>
        /// How <see cref="DiscreteButtonControl.WriteValueIntoState"/> should behave.
        /// </summary>
        public enum WriteMode
        {
            /// <summary>
            /// <see cref="DiscreteButtonControl.WriteValueIntoState"/> will throw <c>NotSupportedException</c>.
            /// </summary>
            WriteDisabled = 0,

            /// <summary>
            /// Write <see cref="DiscreteButtonControl.nullValue"/> for when the button is considered not pressed and
            /// write <see cref="DiscreteButtonControl.maxValue"/> for when the button is considered pressed.
            /// </summary>
            WriteNullAndMaxValue = 1,
        }
    }
}