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,
}
}
}
|