A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.InputSystem.LowLevel;
3using UnityEngine.InputSystem.Layouts;
4using UnityEngine.InputSystem.Utilities;
5
6////TODO: hide in UI
7
8namespace UnityEngine.InputSystem.Controls
9{
10 /// <summary>
11 /// A button that is considered pressed if the underlying state has a value in the specific range.
12 /// </summary>
13 /// <remarks>
14 /// This control is most useful for handling HID-style hat switches. Unlike <see cref="DpadControl"/>,
15 /// which by default is stored as a bitfield of four bits that each represent a direction on the pad,
16 /// these hat switches enumerate the possible directions that the switch can be moved in. For example,
17 /// the value 1 could indicate that the switch is moved to the left whereas 3 could indicate it is
18 /// moved up.
19 ///
20 /// <example>
21 /// <code>
22 /// [StructLayout(LayoutKind.Explicit, Size = 32)]
23 /// internal struct DualShock4HIDInputReport : IInputStateTypeInfo
24 /// {
25 /// [FieldOffset(0)] public byte reportId;
26 ///
27 /// [InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
28 /// [InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=7,maxValue=1,nullValue=8,wrapAtValue=7", bit = 0, sizeInBits = 4)]
29 /// [InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=1,maxValue=3", bit = 0, sizeInBits = 4)]
30 /// [InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=3,maxValue=5", bit = 0, sizeInBits = 4)]
31 /// [InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=5, maxValue=7", bit = 0, sizeInBits = 4)]
32 /// [FieldOffset(5)] public byte buttons1;
33 /// }
34 /// </code>
35 /// </example>
36 /// </remarks>
37 public class DiscreteButtonControl : ButtonControl
38 {
39 /// <summary>
40 /// Value (inclusive) at which to start considering the button to be pressed.
41 /// </summary>
42 /// <remarks>
43 /// <see cref="minValue"/> is allowed to be larger than <see cref="maxValue"/>. This indicates
44 /// a setup where the value wraps around beyond <see cref="minValue"/>, skips <see cref="nullValue"/>,
45 /// and then goes all the way up to <see cref="maxValue"/>.
46 ///
47 /// For example, if the underlying state represents a circular D-pad and enumerates its
48 /// 9 possible positions (including null state) going clock-wise from 0 to 8 and with 1
49 /// indicating that the D-pad is pressed to the left, then 1, 2, and 8 would indicate
50 /// that the "left" button is held on the D-pad. To set this up, set <see cref="minValue"/>
51 /// to 8, <see cref="maxValue"/> to 2, and <see cref="nullValue"/> to 0 (the default).
52 /// </remarks>
53 public int minValue;
54
55 /// <summary>
56 /// Value (inclusive) beyond which to stop considering the button to be pressed.
57 /// </summary>
58 public int maxValue;
59
60 /// <summary>
61 /// Value (inclusive) at which the values cut off and wrap back around. Considered to not be set if equal to <see cref="nullValue"/>.
62 /// </summary>
63 /// <remarks>
64 /// This is useful if, for example, an enumeration has more bits than are used for "on" states of controls. For example,
65 /// a bitfield of 4 bits where values 1-7 (i.e. 0001 to 0111) indicate "on" states of controls and value 8 indicating an
66 /// "off" state. In that case, set <see cref="nullValue"/> to 8 and <see cref="wrapAtValue"/> to 7.
67 /// </remarks>
68 public int wrapAtValue;
69
70 /// <summary>
71 /// Value at which the button is considered to not be pressed. Usually zero. Some devices, however,
72 /// use non-zero default states.
73 /// </summary>
74 public int nullValue;
75
76 /// <summary>
77 /// Determines the behavior of <see cref="WriteValueIntoState"/>. By default, attempting to write a <see cref="DiscreteButtonControl"/>
78 /// will result in a <c>NotSupportedException</c>.
79 /// </summary>
80 public WriteMode writeMode;
81
82 protected override void FinishSetup()
83 {
84 base.FinishSetup();
85
86 if (!stateBlock.format.IsIntegerFormat())
87 throw new NotSupportedException(
88 $"Non-integer format '{stateBlock.format}' is not supported for DiscreteButtonControl '{this}'");
89 }
90
91 public override unsafe float ReadUnprocessedValueFromState(void* statePtr)
92 {
93 var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
94 // Note that all signed data in state buffers is in excess-K format.
95 var intValue = MemoryHelpers.ReadTwosComplementMultipleBitsAsInt(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits);
96
97 var value = 0.0f;
98 if (minValue > maxValue)
99 {
100 // If no wrapping point is set, default to wrapping around exactly
101 // at the point of minValue.
102 if (wrapAtValue == nullValue)
103 wrapAtValue = minValue;
104
105 if ((intValue >= minValue && intValue <= wrapAtValue)
106 || (intValue != nullValue && intValue <= maxValue))
107 value = 1.0f;
108 }
109 else
110 {
111 value = intValue >= minValue && intValue <= maxValue ? 1.0f : 0.0f;
112 }
113
114 return Preprocess(value);
115 }
116
117 public override unsafe void WriteValueIntoState(float value, void* statePtr)
118 {
119 if (writeMode == WriteMode.WriteNullAndMaxValue)
120 {
121 var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
122 var valueToWrite = value >= pressPointOrDefault ? maxValue : nullValue;
123 MemoryHelpers.WriteIntAsTwosComplementMultipleBits(valuePtr, m_StateBlock.bitOffset, m_StateBlock.sizeInBits, valueToWrite);
124 return;
125 }
126
127 // The way these controls are usually used, the state is shared between multiple DiscreteButtons. So writing one
128 // may have unpredictable effects on the value of other buttons.
129 throw new NotSupportedException("Writing value states for DiscreteButtonControl is not supported as a single value may correspond to multiple states");
130 }
131
132 /// <summary>
133 /// How <see cref="DiscreteButtonControl.WriteValueIntoState"/> should behave.
134 /// </summary>
135 public enum WriteMode
136 {
137 /// <summary>
138 /// <see cref="DiscreteButtonControl.WriteValueIntoState"/> will throw <c>NotSupportedException</c>.
139 /// </summary>
140 WriteDisabled = 0,
141
142 /// <summary>
143 /// Write <see cref="DiscreteButtonControl.nullValue"/> for when the button is considered not pressed and
144 /// write <see cref="DiscreteButtonControl.maxValue"/> for when the button is considered pressed.
145 /// </summary>
146 WriteNullAndMaxValue = 1,
147 }
148 }
149}