first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:16:36 +07:00
commit a40d0921eb
17012 changed files with 2652386 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84ba8e63f115487ca072084bbebf0e55
timeCreated: 1517014866

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Command to tell the runtime to no longer send events for the given device.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct DisableDeviceCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('D', 'S', 'B', 'L'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
public FourCC typeStatic
{
get { return Type; }
}
public static DisableDeviceCommand Create()
{
return new DisableDeviceCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aad56faafe6d9d3458c93a9e43759461
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Command to re-enable a device that has been disabled with <see cref="DisableDeviceCommand"/>.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct EnableDeviceCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('E', 'N', 'B', 'L'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
public FourCC typeStatic
{
get { return Type; }
}
public static EnableDeviceCommand Create()
{
return new EnableDeviceCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d76c8b08c287594c9bf5d6c70fb8699
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Device Command that enables IME Composition within the application. Primarily handled by Keyboard devices.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = InputDeviceCommand.kBaseCommandSize + sizeof(byte))]
public unsafe struct EnableIMECompositionCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('I', 'M', 'E', 'M'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + +sizeof(uint);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
/// <summary>
/// Set to true, and if true, Input Method Editors will be used while typing.
/// </summary>
public bool imeEnabled
{
get { return m_ImeEnabled != 0; }
}
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
byte m_ImeEnabled;
public FourCC typeStatic
{
get { return Type; }
}
public static EnableIMECompositionCommand Create(bool enabled)
{
return new EnableIMECompositionCommand
{
baseCommand = new InputDeviceCommand(Type, InputDeviceCommand.kBaseCommandSize + sizeof(byte)),
m_ImeEnabled = enabled ? byte.MaxValue : (byte)0
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e0ea5a9f932f6541962b1bf7934f7c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Interface implemented by all input device command structs which reports the data format identifier of the command.
/// </summary>
public interface IInputDeviceCommandInfo
{
/// <summary>
/// The data format identifier of the device command as a <see cref="FourCC"/> code.
/// </summary>
FourCC typeStatic { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2339479d3a44fcb902e62c60f0a05af
timeCreated: 1517015727

View File

@@ -0,0 +1,58 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Device command to instruct the underlying platform to pair a user account to the targeted device.
/// </summary>
/// <remarks>
///
/// If successful, the platform should then send an <see cref="DeviceConfigurationEvent"/>
/// to signal that the device configuration has been changed. In response, a <see cref="QueryUserIdCommand"/>
/// may be sent to fetch the paired user ID from the device.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct InitiateUserAccountPairingCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('P', 'A', 'I', 'R'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "Enum values mandated by native code")]
public enum Result
{
/// <summary>
/// User pairing UI has been successfully opened.
/// </summary>
SuccessfullyInitiated = 1,
/// <summary>
/// System does not support application-invoked user pairing.
/// </summary>
ErrorNotSupported = (int)InputDeviceCommand.GenericFailure,
/// <summary>
/// There already is a pairing operation in progress and the system does not support
/// pairing multiple devices at the same time.
/// </summary>
ErrorAlreadyInProgress = -2,
}
public FourCC typeStatic
{
get { return Type; }
}
public static InitiateUserAccountPairingCommand Create()
{
return new InitiateUserAccountPairingCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b200cbcb815aa44418fe8ada8cb78696
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.InputSystem.LowLevel
{
////REVIEW: why is this passing the command by pointer instead of by ref?
/// <summary>
/// Delegate used by <see cref="InputSystem.onDeviceCommand"/>.
/// </summary>
public unsafe delegate long? InputDeviceCommandDelegate(InputDevice device, InputDeviceCommand* command);
/// <summary>
/// Delegate for executing <see cref="InputDeviceCommand"/>s inside <see cref="InputSystem.onFindLayoutForDevice"/>.
/// </summary>
/// <param name="command">Command to execute.</param>
/// <seealso cref="InputSystem.onFindLayoutForDevice"/>
/// <seealso cref="Layouts.InputDeviceFindControlLayoutDelegate"/>
public delegate long InputDeviceExecuteCommandDelegate(ref InputDeviceCommand command);
/// <summary>
/// Data header for a command send to an <see cref="InputDevice"/>.
/// </summary>
/// <remarks>
/// Commands are essentially synchronously processed events send directly
/// to a specific device. Their primary use is to expose device-specific
/// functions without having to extend the C# API used to communicate
/// between input code and backend device implementations (which may sit
/// in native code).
///
/// Like input events, device commands use <see cref="FourCC"/> codes
/// to indicate their type.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = kBaseCommandSize)]
public struct InputDeviceCommand : IInputDeviceCommandInfo
{
////TODO: Remove kBaseCommandSize
internal const int kBaseCommandSize = 8;
public const int BaseCommandSize = 8;
/// <summary>
/// Generic failure code for <see cref="InputDevice.ExecuteCommand{TCommand}"/> calls.
/// </summary>
/// <remarks>
/// Any negative return value for an <see cref="InputDevice.ExecuteCommand{TCommand}"/> call should be considered failure.
/// </remarks>
public const long GenericFailure = -1;
public const long GenericSuccess = 1;
[FieldOffset(0)] public FourCC type;
[FieldOffset(4)] public int sizeInBytes;
public int payloadSizeInBytes => sizeInBytes - kBaseCommandSize;
public unsafe void* payloadPtr
{
get
{
fixed(void* thisPtr = &this)
{
return ((byte*)thisPtr) + kBaseCommandSize;
}
}
}
public InputDeviceCommand(FourCC type, int sizeInBytes = kBaseCommandSize)
{
this.type = type;
this.sizeInBytes = sizeInBytes;
}
public static unsafe NativeArray<byte> AllocateNative(FourCC type, int payloadSize)
{
var sizeInBytes = payloadSize + kBaseCommandSize;
var buffer = new NativeArray<byte>(sizeInBytes, Allocator.Temp);
var commandPtr = (InputDeviceCommand*)NativeArrayUnsafeUtility.GetUnsafePtr(buffer);
commandPtr->type = type;
commandPtr->sizeInBytes = sizeInBytes;
return buffer;
}
public FourCC typeStatic
{
get { return new FourCC(); }
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2158c2dfcbb94a3092caa77d698d5010
timeCreated: 1517014875

View File

@@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Queries to see if this device is able to continue to send updates and state changes when the application is not if focus.
/// </summary>
/// <seealso cref="InputDevice.canRunInBackground"/>
[StructLayout(LayoutKind.Explicit, Size = InputDeviceCommand.kBaseCommandSize + sizeof(bool))]
public struct QueryCanRunInBackground : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('Q', 'R', 'I', 'B');
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(bool);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public bool canRunInBackground;
public FourCC typeStatic => Type;
public static QueryCanRunInBackground Create()
{
return new QueryCanRunInBackground
{
baseCommand = new InputDeviceCommand(Type, kSize),
canRunInBackground = false
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a10a64abdfd8f6340a5b6b54bf28812a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Query dimensions of a device.
/// </summary>
/// <remarks>
/// This is usually used to query screen dimensions from pointer devices.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct QueryDimensionsCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('D', 'I', 'M', 'S'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float) * 2;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public Vector2 outDimensions;
public FourCC typeStatic
{
get { return Type; }
}
public static QueryDimensionsCommand Create()
{
return new QueryDimensionsCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8ce1fd8f845226439f6b4ee991c9f78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
#if UNITY_EDITOR
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
////REVIEW: This mechanism sucks. We should have this conversion without the device having to support it through an IOCTL. A Pointer
//// should just inherently have this conversion mechanism on its controls that operate in screen space.
namespace UnityEngine.InputSystem.LowLevel
{
[StructLayout(LayoutKind.Explicit, Size = kSize)]
internal struct QueryEditorWindowCoordinatesCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('E', 'W', 'P', 'S');
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float) * 2;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public Vector2 inOutCoordinates;
public FourCC typeStatic => Type;
public static QueryEditorWindowCoordinatesCommand Create(Vector2 playerWindowCoordinates)
{
return new QueryEditorWindowCoordinatesCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
inOutCoordinates = playerWindowCoordinates
};
}
}
}
#endif // UNITY_EDITOR

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c764fcc1188a6e489f8c305ff4077a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Command to find out whether a device is currently enabled or not.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct QueryEnabledStateCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('Q', 'E', 'N', 'B');
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(bool);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public bool isEnabled;
public FourCC typeStatic => Type;
public static QueryEnabledStateCommand Create()
{
return new QueryEnabledStateCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e6d0cbdd012f434392878fdc293f974
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Command to query the current name of a key according to the current keyboard layout.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public unsafe struct QueryKeyNameCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('K', 'Y', 'C', 'F');
internal const int kMaxNameLength = 256;
internal const int kSize = InputDeviceCommand.kBaseCommandSize + kMaxNameLength + 4;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public int scanOrKeyCode;
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 4)]
public fixed byte nameBuffer[kMaxNameLength];
public string ReadKeyName()
{
fixed(QueryKeyNameCommand * thisPtr = &this)
{
return StringHelpers.ReadStringFromBuffer(new IntPtr(thisPtr->nameBuffer), kMaxNameLength);
}
}
public FourCC typeStatic => Type;
public static QueryKeyNameCommand Create(Key key)
{
return new QueryKeyNameCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
scanOrKeyCode = (int)key
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e49477f346c4f3a8c9e5fcdcde317ed
timeCreated: 1517018273

View File

@@ -0,0 +1,53 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Command to query the name of the current keyboard layout from a device.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = InputDeviceCommand.kBaseCommandSize + kMaxNameLength)]
public unsafe struct QueryKeyboardLayoutCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('K', 'B', 'L', 'T'); } }
internal const int kMaxNameLength = 256;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public fixed byte nameBuffer[kMaxNameLength];
/// <summary>
/// Read the current keyboard layout name from <see cref="nameBuffer"/>.
/// </summary>
/// <returns></returns>
public string ReadLayoutName()
{
fixed(QueryKeyboardLayoutCommand * thisPtr = &this)
return StringHelpers.ReadStringFromBuffer(new IntPtr(thisPtr->nameBuffer), kMaxNameLength);
}
/// <summary>
/// Write the given string to <see cref="nameBuffer"/>.
/// </summary>
/// <param name="name">Keyboard layout name.</param>
public void WriteLayoutName(string name)
{
fixed(QueryKeyboardLayoutCommand * thisPtr = &this)
StringHelpers.WriteStringToBuffer(name, new IntPtr(thisPtr->nameBuffer), kMaxNameLength);
}
public FourCC typeStatic => Type;
public static QueryKeyboardLayoutCommand Create()
{
return new QueryKeyboardLayoutCommand
{
baseCommand = new InputDeviceCommand(Type, InputDeviceCommand.kBaseCommandSize + kMaxNameLength)
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 620180a2562c422893989c28645cef24
timeCreated: 1517017122

View File

@@ -0,0 +1,143 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Query the ID and the name of the user paired to the device the command is sent to.
/// </summary>
/// <remarks>
/// This command is only supported on platforms where devices can be paired to user accounts
/// at the platform level. Currently this is the case for Xbox and PS4. On Switch, <see
/// cref="InitiateUserAccountPairingCommand"/> is supported but the platform does not store
/// associations established between devices and users that way.
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public unsafe struct QueryPairedUserAccountCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('P', 'A', 'C', 'C');
internal const int kMaxNameLength = 256;
internal const int kMaxIdLength = 256;
////REVIEW: is this too heavy to allocate on the stack?
internal const int kSize = InputDeviceCommand.kBaseCommandSize + 8 + kMaxNameLength * 2 + kMaxIdLength * 2;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1714:FlagsEnumsShouldHavePluralNames", Justification = "`Result` matches other command result names")]
[Flags]
public enum Result : long
{
// Leave bit #0 unused so as to not lead to possible confusion with GenericSuccess.
/// <summary>
/// The device is currently paired to a user account.
/// </summary>
DevicePairedToUserAccount = 1 << 1,
/// <summary>
/// The system is currently displaying a prompt for the user to select an account to
/// use the device with.
/// </summary>
UserAccountSelectionInProgress = 1 << 2,
/// <summary>
/// User account selection completed.
/// </summary>
UserAccountSelectionComplete = 1 << 3,
/// <summary>
/// The system had been displaying a prompt
/// </summary>
UserAccountSelectionCanceled = 1 << 4,
}
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
/// <summary>
/// Handle of the user account at the platform level.
/// </summary>
/// <remarks>
/// Note that this is wide enough to store a pointer and does not necessarily need to be a plain integer.
/// How the backend determines handles for user accounts is up to the backend.
///
/// Be aware that a handle is not guaranteed to be valid beyond the current application run. For stable,
/// persistent user account handles,use <see cref="id"/>.
/// </remarks>
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public ulong handle;
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 8)]
internal fixed byte nameBuffer[kMaxNameLength * 2];
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 8 + kMaxNameLength * 2)]
internal fixed byte idBuffer[kMaxNameLength * 2];
/// <summary>
/// Persistent ID of the user account the platform level.
/// </summary>
/// <remarks>
/// This ID is guaranteed to not change between application runs, device restarts, and the user
/// changing user names on the account.
///
/// Use this ID to associate persistent settings with.
/// </remarks>
public string id
{
get
{
fixed(byte* idBufferPtr = idBuffer)
return StringHelpers.ReadStringFromBuffer(new IntPtr(idBufferPtr), kMaxIdLength);
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var length = value.Length;
if (length > kMaxIdLength)
throw new ArgumentException($"ID '{value}' exceeds maximum supported length of {kMaxIdLength} characters", nameof(value));
fixed(byte* idBufferPtr = idBuffer)
{
StringHelpers.WriteStringToBuffer(value, new IntPtr(idBufferPtr), kMaxIdLength);
}
}
}
/// <summary>
/// Name of the user account at the platform level.
/// </summary>
public string name
{
get
{
fixed(byte* nameBufferPtr = nameBuffer)
return StringHelpers.ReadStringFromBuffer(new IntPtr(nameBufferPtr), kMaxNameLength);
}
set
{
if (value == null)
throw new ArgumentNullException("value");
var length = value.Length;
if (length > kMaxNameLength)
throw new ArgumentException($"Name '{value}' exceeds maximum supported length of {kMaxNameLength} characters", nameof(value));
fixed(byte* nameBufferPtr = nameBuffer)
{
StringHelpers.WriteStringToBuffer(value, new IntPtr(nameBufferPtr), kMaxNameLength);
}
}
}
public FourCC typeStatic => Type;
public static QueryPairedUserAccountCommand Create()
{
return new QueryPairedUserAccountCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c6284fecb53e48ee832bc14c64be6fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
[StructLayout(LayoutKind.Explicit, Size = kSize)]
internal struct QuerySamplingFrequencyCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('S', 'M', 'P', 'L'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public float frequency;
public FourCC typeStatic
{
get { return Type; }
}
public static QuerySamplingFrequencyCommand Create()
{
return new QuerySamplingFrequencyCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1659bf437d849824999b2b9ae9e1518b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
////TODO: remove this one; superseded by QueryPairedUserAccountCommand
namespace UnityEngine.InputSystem.LowLevel
{
[StructLayout(LayoutKind.Explicit, Size = kSize)]
internal unsafe struct QueryUserIdCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('U', 'S', 'E', 'R'); } }
public const int kMaxIdLength = 256;
internal const int kSize = InputDeviceCommand.kBaseCommandSize + kMaxIdLength * 2;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public fixed byte idBuffer[kMaxIdLength * 2];
public string ReadId()
{
fixed(QueryUserIdCommand * thisPtr = &this)
{
return StringHelpers.ReadStringFromBuffer(new IntPtr(thisPtr->idBuffer), kMaxIdLength);
}
}
public FourCC typeStatic
{
get { return Type; }
}
public static QueryUserIdCommand Create()
{
return new QueryUserIdCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c784a1510a154625903484b84c8ddaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A command to tell the runtime to reset the device to it's default state.
/// </summary>
/// <remarks>
/// This triggers an event being sent from the device that represents an empty, or untouched device.
/// </remarks>
/// <seealso cref="RequestSyncCommand"/>
[StructLayout(LayoutKind.Explicit, Size = InputDeviceCommand.kBaseCommandSize)]
public struct RequestResetCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('R', 'S', 'E', 'T');
internal const int kSize = InputDeviceCommand.kBaseCommandSize;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
public FourCC typeStatic => Type;
public static RequestResetCommand Create()
{
return new RequestResetCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90a43988da5cf85498b40180b9040a38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A command to tell the runtime to sync the device to it's last known state.
/// </summary>
/// <remarks>
/// This triggers an event from the underlying device that represents the whole, current state.
/// </remarks>
/// <seealso cref="RequestResetCommand"/>
[StructLayout(LayoutKind.Explicit, Size = InputDeviceCommand.kBaseCommandSize)]
public struct RequestSyncCommand : IInputDeviceCommandInfo
{
public static FourCC Type => new FourCC('S', 'Y', 'N', 'C');
internal const int kSize = InputDeviceCommand.kBaseCommandSize;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
public FourCC typeStatic => Type;
public static RequestSyncCommand Create()
{
return new RequestSyncCommand
{
baseCommand = new InputDeviceCommand(Type, kSize)
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0c637405fa4842cc92e389df8729bc8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Sets the position for IME dialogs. This is in pixels, from the upper left corner going down and to the right.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public unsafe struct SetIMECursorPositionCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('I', 'M', 'E', 'P'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + (sizeof(float) * 2);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
public Vector2 position
{
get { return m_Position; }
}
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
Vector2 m_Position;
public FourCC typeStatic
{
get { return Type; }
}
public static SetIMECursorPositionCommand Create(Vector2 cursorPosition)
{
return new SetIMECursorPositionCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
m_Position = cursorPosition
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6873ac51fc9d6dc4dbb6d9dc77a5eb49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
////REVIEW: switch this to interval-in-seconds instead of Hz?
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// For a device that is sampled periodically, set the frequency at which the device
/// is sampled.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = kSize)]
public struct SetSamplingFrequencyCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('S', 'S', 'P', 'L'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float);
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public float frequency;
public FourCC typeStatic
{
get { return Type; }
}
public static SetSamplingFrequencyCommand Create(float frequency)
{
return new SetSamplingFrequencyCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
frequency = frequency
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3894aabfd6ab70245859fa873e1a6552
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
[StructLayout(LayoutKind.Explicit, Size = kSize)]
internal struct WarpMousePositionCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('W', 'P', 'M', 'S'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float) * 2;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public Vector2 warpPositionInPlayerDisplaySpace;
public FourCC typeStatic
{
get { return Type; }
}
public static WarpMousePositionCommand Create(Vector2 position)
{
return new WarpMousePositionCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
warpPositionInPlayerDisplaySpace = position
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f018dacefe574045a76769f5f355f3b1
timeCreated: 1517107079

View File

@@ -0,0 +1,809 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Haptics;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Scripting;
////TODO: come up with consistent naming for buttons; (xxxButton? xxx?)
////REVIEW: should we add a gyro as a standard feature of gamepads?
////TODO: allow to be used for mouse simulation
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Default state layout for gamepads.
/// </summary>
/// <remarks>
/// Be aware that unlike some other devices such as <see cref="Mouse"/> or <see cref="Touchscreen"/>,
/// gamepad devices tend to have wildly varying state formats, i.e. forms in which they internally
/// store their input data. In practice, even on the same platform gamepads will often store
/// their data in different formats. This means that <see cref="GamepadState"/> will often <em>not</em>
/// be the format in which a particular gamepad (such as <see cref="XInput.XInputController"/>,
/// for example) stores its data.
///
/// If your gamepad data is arriving in a different format, you should extend the "Gamepad" layout and customize its Controls.
///
/// A real-world example of this is the Xbox Controller on macOS, which is supported through HID. Its layout looks like this:
///
/// <example>
/// <code>
/// {
/// "name" : "XboxGamepadOSX",
/// "extend" : "Gamepad",
/// "format" : "HID",
/// "device" : { "interface" : "HID", "product" : "Xbox.*Controller" },
/// "controls" : [
/// { "name" : "leftShoulder", "offset" : 2, "bit" : 8 },
/// { "name" : "rightShoulder", "offset" : 2, "bit" : 9 },
/// { "name" : "leftStickPress", "offset" : 2, "bit" : 14 },
/// { "name" : "rightStickPress", "offset" : 2, "bit" : 15 },
/// { "name" : "buttonSouth", "offset" : 2, "bit" : 12 },
/// { "name" : "buttonEast", "offset" : 2, "bit" : 13 },
/// { "name" : "buttonWest", "offset" : 2, "bit" : 14 },
/// { "name" : "buttonNorth", "offset" : 2, "bit" : 15 },
/// { "name" : "dpad", "offset" : 2 },
/// { "name" : "dpad/up", "offset" : 0, "bit" : 8 },
/// { "name" : "dpad/down", "offset" : 0, "bit" : 9 },
/// { "name" : "dpad/left", "offset" : 0, "bit" : 10 },
/// { "name" : "dpad/right", "offset" : 0, "bit" : 11 },
/// { "name" : "start", "offset" : 2, "bit" : 4 },
/// { "name" : "select", "offset" : 2, "bit" : 5 },
/// { "name" : "xbox", "offset" : 2, "bit" : 2, "layout" : "Button" },
/// { "name" : "leftTrigger", "offset" : 4, "format" : "BYTE" },
/// { "name" : "rightTrigger", "offset" : 5, "format" : "BYTE" },
/// { "name" : "leftStick", "offset" : 6, "format" : "VC2S" },
/// { "name" : "leftStick/x", "offset" : 0, "format" : "SHRT", "parameters" : "normalize,normalizeMin=-0.5,normalizeMax=0.5" },
/// { "name" : "leftStick/y", "offset" : 2, "format" : "SHRT", "parameters" : "invert,normalize,normalizeMin=-0.5,normalizeMax=0.5" },
/// { "name" : "rightStick", "offset" : 10, "format" : "VC2S" },
/// { "name" : "rightStick/x", "offset" : 0, "format" : "SHRT", "parameters" : "normalize,normalizeMin=-0.5,normalizeMax=0.5" },
/// { "name" : "rightStick/y", "offset" : 2, "format" : "SHRT", "parameters" : "invert,normalize,normalizeMin=-0.5,normalizeMax=0.5" }
/// ]
/// }
/// </code>
/// </example>
///
/// The same principle applies if some buttons on your Device are swapped, for example. In this case, you can remap their offsets.
///
///
///
///
/// </remarks>
/// <seealso cref="Gamepad"/>
// NOTE: Must match GamepadInputState in native.
[StructLayout(LayoutKind.Explicit, Size = 28)]
public struct GamepadState : IInputStateTypeInfo
{
public static FourCC Format => new FourCC('G', 'P', 'A', 'D');
// On Sony consoles, we use the platform defaults as the gamepad-wide short default names.
#if UNITY_PS4 || UNITY_PS5
internal const string ButtonSouthShortDisplayName = "Cross";
internal const string ButtonNorthShortDisplayName = "Triangle";
internal const string ButtonWestShortDisplayName = "Square";
internal const string ButtonEastShortDisplayName = "Circle";
#elif UNITY_SWITCH
internal const string ButtonSouthShortDisplayName = "B";
internal const string ButtonNorthShortDisplayName = "X";
internal const string ButtonWestShortDisplayName = "Y";
internal const string ButtonEastShortDisplayName = "A";
#else
internal const string ButtonSouthShortDisplayName = "A";
internal const string ButtonNorthShortDisplayName = "Y";
internal const string ButtonWestShortDisplayName = "X";
internal const string ButtonEastShortDisplayName = "B";
#endif
/// <summary>
/// Button bit mask.
/// </summary>
/// <seealso cref="GamepadButton"/>
/// <seealso cref="Gamepad.buttonSouth"/>
/// <seealso cref="Gamepad.buttonNorth"/>
/// <seealso cref="Gamepad.buttonWest"/>
/// <seealso cref="Gamepad.buttonSouth"/>
/// <seealso cref="Gamepad.leftShoulder"/>
/// <seealso cref="Gamepad.rightShoulder"/>
/// <seealso cref="Gamepad.startButton"/>
/// <seealso cref="Gamepad.selectButton"/>
/// <seealso cref="Gamepad.leftStickButton"/>
/// <seealso cref="Gamepad.rightStickButton"/>
////REVIEW: do we want the name to correspond to what's actually on the device?
[InputControl(name = "dpad", layout = "Dpad", usage = "Hatswitch", displayName = "D-Pad", format = "BIT", sizeInBits = 4, bit = 0)]
[InputControl(name = "buttonSouth", layout = "Button", bit = (uint)GamepadButton.South, usages = new[] { "PrimaryAction", "Submit" }, aliases = new[] { "a", "cross" }, displayName = "Button South", shortDisplayName = ButtonSouthShortDisplayName)]
[InputControl(name = "buttonWest", layout = "Button", bit = (uint)GamepadButton.West, usage = "SecondaryAction", aliases = new[] { "x", "square" }, displayName = "Button West", shortDisplayName = ButtonWestShortDisplayName)]
[InputControl(name = "buttonNorth", layout = "Button", bit = (uint)GamepadButton.North, aliases = new[] { "y", "triangle" }, displayName = "Button North", shortDisplayName = ButtonNorthShortDisplayName)]
[InputControl(name = "buttonEast", layout = "Button", bit = (uint)GamepadButton.East, usages = new[] { "Back", "Cancel" }, aliases = new[] { "b", "circle" }, displayName = "Button East", shortDisplayName = ButtonEastShortDisplayName)]
////FIXME: 'Press' naming is inconsistent with 'Button' naming
[InputControl(name = "leftStickPress", layout = "Button", bit = (uint)GamepadButton.LeftStick, displayName = "Left Stick Press")]
[InputControl(name = "rightStickPress", layout = "Button", bit = (uint)GamepadButton.RightStick, displayName = "Right Stick Press")]
[InputControl(name = "leftShoulder", layout = "Button", bit = (uint)GamepadButton.LeftShoulder, displayName = "Left Shoulder", shortDisplayName = "LB")]
[InputControl(name = "rightShoulder", layout = "Button", bit = (uint)GamepadButton.RightShoulder, displayName = "Right Shoulder", shortDisplayName = "RB")]
////REVIEW: seems like these two should get less ambiguous names as well
[InputControl(name = "start", layout = "Button", bit = (uint)GamepadButton.Start, usage = "Menu", displayName = "Start")]
[InputControl(name = "select", layout = "Button", bit = (uint)GamepadButton.Select, displayName = "Select")]
[FieldOffset(0)]
public uint buttons;
/// <summary>
/// A 2D vector representing the current position of the left stick on a gamepad.
/// </summary>
/// <remarks>Each axis of the 2D vector's range goes from -1 to 1. 0 represents the stick in its center position, and -1 or 1 represents the the stick pushed to its extent in each direction along the axis.</remarks>
/// <seealso cref="Gamepad.leftStick"/>
[InputControl(layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone", displayName = "Left Stick", shortDisplayName = "LS")]
[FieldOffset(4)]
public Vector2 leftStick;
/// <summary>
/// A 2D vector representing the current position of the right stick on a gamepad.
/// </summary>
/// <remarks>Each axis of the 2D vector's range goes from -1 to 1.
/// 0 represents the stick in its center position.
/// -1 or 1 represents the stick pushed to its extent in each direction along the axis.</remarks>
/// <seealso cref="Gamepad.rightStick"/>
[InputControl(layout = "Stick", usage = "Secondary2DMotion", processors = "stickDeadzone", displayName = "Right Stick", shortDisplayName = "RS")]
[FieldOffset(12)]
public Vector2 rightStick;
////REVIEW: should left and right trigger get deadzones?
/// <summary>
/// The current position of the left trigger on a gamepad.
/// </summary>
/// <remarks>The value's range goes from 0 to 1.
/// 0 represents the trigger in its neutral position.
/// 1 represents the trigger in its fully pressed position.</remarks>
/// <seealso cref="Gamepad.leftTrigger"/>
[InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Left Trigger", shortDisplayName = "LT")]
[FieldOffset(20)]
public float leftTrigger;
/// <summary>
/// The current position of the right trigger on a gamepad.
/// </summary>
/// <remarks>The value's range goes from 0 to 1.
/// 0 represents the trigger in its neutral position.
/// 1 represents the trigger in its fully pressed position.</remarks>
/// <seealso cref="Gamepad.rightTrigger"/>
[InputControl(layout = "Button", format = "FLT", usage = "SecondaryTrigger", displayName = "Right Trigger", shortDisplayName = "RT")]
[FieldOffset(24)]
public float rightTrigger;
/// <summary>
/// State format tag for GamepadState.
/// </summary>
/// <remarks> Holds the format tag for GamepadState ("GPAD")</remarks>
public FourCC format => Format;
/// <summary>
/// Create a gamepad state with the given buttons being pressed.
/// </summary>
/// <param name="buttons">Buttons to put into pressed state.</param>
/// <exception cref="ArgumentNullException"><paramref name="buttons"/> is <c>null</c>.</exception>
public GamepadState(params GamepadButton[] buttons)
: this()
{
if (buttons == null)
throw new ArgumentNullException(nameof(buttons));
foreach (var button in buttons)
{
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
var bit = 1U << (int)button;
this.buttons |= bit;
}
}
/// <summary>
/// Set the specific buttons to be pressed or unpressed.
/// </summary>
/// <param name="button">A gamepad button.</param>
/// <param name="value">Whether to set <paramref name="button"/> to be pressed or not pressed in
/// <see cref="buttons"/>.</param>
/// <returns>GamepadState with a modified <see cref="buttons"/> mask.</returns>
public GamepadState WithButton(GamepadButton button, bool value = true)
{
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
var bit = 1U << (int)button;
if (value)
buttons |= bit;
else
buttons &= ~bit;
return this;
}
}
////NOTE: The bit positions here based on the enum value are also used in native.
/// <summary>
/// Enum of common gamepad buttons.
/// </summary>
/// <remarks>
/// Can be used as an array indexer on the <see cref="Gamepad"/> class to get individual button controls.
/// </remarks>
public enum GamepadButton
{
// Dpad buttons. Important to be first in the bitfield as we'll
// point the DpadControl to it.
// IMPORTANT: Order has to match what is expected by DpadControl.
/// <summary>
/// The up button on a gamepad's dpad.
/// </summary>
DpadUp = 0,
/// <summary>
/// The down button on a gamepad's dpad.
/// </summary>
DpadDown = 1,
/// <summary>
/// The left button on a gamepad's dpad.
/// </summary>
DpadLeft = 2,
/// <summary>
/// The right button on a gamepad's dpad.
/// </summary>
DpadRight = 3,
// Face buttons. We go with a north/south/east/west naming as that
// clearly disambiguates where we expect the respective button to be.
/// <summary>
/// The upper action button on a gamepad.
/// </summary>
/// <remarks>
/// Identical to <see cref="Y"/> and <see cref="Triangle"/> which are the Xbox and PlayStation controller names for this button.
/// </remarks>
North = 4,
/// <summary>
/// The right action button on a gamepad.
/// </summary>
/// <remarks>
/// Identical to <see cref="B"/> and <see cref="Circle"/> which are the Xbox and PlayStation controller names for this button.
/// </remarks>
East = 5,
/// <summary>
/// The lower action button on a gamepad.
/// </summary>
/// <remarks>
/// Identical to <see cref="A"/> and <see cref="Cross"/> which are the Xbox and PlayStation controller names for this button.
/// </remarks>
South = 6,
/// <summary>
/// The left action button on a gamepad.
/// </summary>
/// <remarks>
/// Identical to <see cref="X"/> and <see cref="Square"/> which are the Xbox and PlayStation controller names for this button.
/// </remarks>
West = 7,
/// <summary>
/// The button pressed by pressing down the left stick on a gamepad.
/// </summary>
LeftStick = 8,
/// <summary>
/// The button pressed by pressing down the right stick on a gamepad.
/// </summary>
RightStick = 9,
/// <summary>
/// The left shoulder button on a gamepad.
/// </summary>
LeftShoulder = 10,
/// <summary>
/// The right shoulder button on a gamepad.
/// </summary>
RightShoulder = 11,
/// <summary>
/// The start button.
/// </summary>
Start = 12,
/// <summary>
/// The select button.
/// </summary>
Select = 13,
// For values that are not part of the buttons bitmask in GamepadState, assign large values that are outside
// the 32bit bit range.
/// <summary>
/// The left trigger button on a gamepad.
/// </summary>
LeftTrigger = 32,
/// <summary>
/// The right trigger button on a gamepad.
/// </summary>
RightTrigger = 33,
/// <summary>
/// The X button on an Xbox controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="West"/>, which is the generic name of this button.
/// </remarks>
X = West,
/// <summary>
/// The Y button on an Xbox controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="North"/>, which is the generic name of this button.
/// </remarks>
Y = North,
/// <summary>
/// The A button on an Xbox controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="South"/>, which is the generic name of this button.
/// </remarks>
A = South,
/// <summary>
/// The B button on an Xbox controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="East"/>, which is the generic name of this button.
/// </remarks>
B = East,
/// <summary>
/// The cross button on a PlayStation controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="South"/>, which is the generic name of this button.
/// </remarks>
Cross = South,
/// <summary>
/// The square button on a PlayStation controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="West"/>, which is the generic name of this button.
/// </remarks>
Square = West,
/// <summary>
/// The triangle button on a PlayStation controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="North"/>, which is the generic name of this button.
/// </remarks>
Triangle = North,
/// <summary>
/// The circle button on a PlayStation controller.
/// </summary>
/// <remarks>
/// Identical to <see cref="East"/>, which is the generic name of this button.
/// </remarks>
Circle = East,
}
}
namespace UnityEngine.InputSystem
{
/// <summary>
/// An Xbox-style gamepad with two sticks, a D-Pad, four face buttons, two triggers,
/// two shoulder buttons, and two menu buttons that usually sit in the midsection of the gamepad.
/// </summary>
/// <remarks>
/// The Gamepad layout provides a standardized layouts for gamepads. Generally, if a specific
/// device is represented as a Gamepad, the controls, such as the face buttons, are guaranteed
/// to be mapped correctly and consistently. If, based on the set of supported devices available
/// to the input system, this cannot be guaranteed, a given device is usually represented as a
/// generic <see cref="Joystick"/> or as just a plain <see cref="HID.HID"/> instead.
/// </remarks>
/// <example>
/// <code source="../../DocCodeSamples.Tests/GamepadExample.cs" />
/// </example>
/// <seealso cref="all"/>
/// <seealso cref="current"/>
/// <seealso cref="GamepadState"/>
/// <seealso cref="InputDevice"/>
/// <seealso cref="SetMotorSpeeds"/>
/// <seealso cref="ButtonControl.wasPressedThisFrame"/>
[InputControlLayout(stateType = typeof(GamepadState), isGenericTypeOfDevice = true)]
public class Gamepad : InputDevice, IDualMotorRumble
{
/// <summary>
/// The left face button of the gamepad.
/// </summary>
/// <remarks>
/// Control representing the X/Square face button.
/// On an Xbox controller, this is the <see cref="xButton"/> and on the PS4 controller, this is the
/// <see cref="squareButton"/>.
/// </remarks>
public ButtonControl buttonWest { get; protected set; }
/// <summary>
/// The top face button of the gamepad.
/// </summary>
/// <remarks>
/// Control representing the Y/Triangle face button.
/// On an Xbox controller, this is the <see cref="yButton"/> and on the PS4 controller, this is the
/// <see cref="triangleButton"/>.
/// </remarks>
public ButtonControl buttonNorth { get; protected set; }
/// <summary>
/// The bottom face button of the gamepad.
/// </summary>
/// <remarks>
/// Control representing the A/Cross face button.
/// On an Xbox controller, this is the <see cref="aButton"/> and on the PS4 controller, this is the
/// <see cref="crossButton"/>.
/// </remarks>
public ButtonControl buttonSouth { get; protected set; }
/// <summary>
/// The right face button of the gamepad.
/// </summary>
/// <remarks>
/// Control representing the B/Circle face button.
/// On an Xbox controller, this is the <see cref="bButton"/> and on the PS4 controller, this is the
/// <see cref="circleButton"/>.
/// </remarks>
public ButtonControl buttonEast { get; protected set; }
/// <summary>
/// The button that gets triggered when <see cref="leftStick"/> is pressed down.
/// </summary>
/// <remarks>Control representing a click with the left stick.</remarks>
public ButtonControl leftStickButton { get; protected set; }
/// <summary>
/// The button that gets triggered when <see cref="rightStick"/> is pressed down.
/// </summary>
/// <remarks>Control representing a click with the right stick.</remarks>
public ButtonControl rightStickButton { get; protected set; }
/// <summary>
/// The right button in the middle section of the gamepad (called "menu" on Xbox
/// controllers and "options" on PS4 controllers).
/// </summary>
/// <remarks>Control representing the right button in midsection.</remarks>
public ButtonControl startButton { get; protected set; }
/// <summary>
/// The left button in the middle section of the gamepad (called "view" on Xbox
/// controllers and "share" on PS4 controllers).
/// </summary>
/// <remarks>Control representing the left button in midsection.</remarks>
public ButtonControl selectButton { get; protected set; }
/// <summary>
/// The 4-way directional pad on the gamepad.
/// </summary>
/// <remarks>Control representing the d-pad.</remarks>
public DpadControl dpad { get; protected set; }
/// <summary>
/// The left shoulder/bumper button that sits on top of <see cref="leftTrigger"/>.
/// </summary>
/// <remarks>
/// Control representing the left shoulder button.
/// On Xbox controllers, this is usually called "left bumper" whereas on PS4
/// controllers, this button is referred to as "L1".
/// </remarks>
public ButtonControl leftShoulder { get; protected set; }
/// <summary>
/// The right shoulder/bumper button that sits on top of <see cref="rightTrigger"/>.
/// </summary>
/// <remarks>
/// Control representing the right shoulder button.
/// On Xbox controllers, this is usually called "right bumper" whereas on PS4
/// controllers, this button is referred to as "R1".
/// </remarks>
public ButtonControl rightShoulder { get; protected set; }
/// <summary>
/// The left thumbstick on the gamepad.
/// </summary>
/// <remarks>Control representing the left thumbstick.</remarks>
public StickControl leftStick { get; protected set; }
/// <summary>
/// The right thumbstick on the gamepad.
/// </summary>
/// <remarks>Control representing the right thumbstick.</remarks>
public StickControl rightStick { get; protected set; }
/// <summary>
/// The left trigger button sitting below <see cref="leftShoulder"/>.
/// </summary>
/// <remarks>Control representing the left trigger button.
/// On PS4 controllers, this button is referred to as "L2".
/// </remarks>
public ButtonControl leftTrigger { get; protected set; }
/// <summary>
/// The right trigger button sitting below <see cref="rightShoulder"/>.
/// </summary>
/// <remarks>Control representing the right trigger button.
/// On PS4 controllers, this button is referred to as "R2".
/// </remarks>
public ButtonControl rightTrigger { get; protected set; }
/// <summary>
/// Same as <see cref="buttonSouth"/>. Xbox-style alias.
/// </summary>
public ButtonControl aButton => buttonSouth;
/// <summary>
/// Same as <see cref="buttonEast"/>. Xbox-style alias.
/// </summary>
public ButtonControl bButton => buttonEast;
/// <summary>
/// Same as <see cref="buttonWest"/> Xbox-style alias.
/// </summary>
public ButtonControl xButton => buttonWest;
/// <summary>
/// Same as <see cref="buttonNorth"/>. Xbox-style alias.
/// </summary>
public ButtonControl yButton => buttonNorth;
/// <summary>
/// Same as <see cref="buttonNorth"/>. PS4-style alias.
/// </summary>
public ButtonControl triangleButton => buttonNorth;
/// <summary>
/// Same as <see cref="buttonWest"/>. PS4-style alias.
/// </summary>
public ButtonControl squareButton => buttonWest;
/// <summary>
/// Same as <see cref="buttonEast"/>. PS4-style alias.
/// </summary>
public ButtonControl circleButton => buttonEast;
/// <summary>
/// Same as <see cref="buttonSouth"/>. PS4-style alias.
/// </summary>
public ButtonControl crossButton => buttonSouth;
/// <summary>
/// Retrieve a gamepad button by its <see cref="GamepadButton"/> enumeration
/// constant.
/// </summary>
/// <param name="button">Button to retrieve.</param>
/// <exception cref="ArgumentException"><paramref name="button"/> is not a valid gamepad
/// button value.</exception>
public ButtonControl this[GamepadButton button]
{
get
{
switch (button)
{
case GamepadButton.North: return buttonNorth;
case GamepadButton.South: return buttonSouth;
case GamepadButton.East: return buttonEast;
case GamepadButton.West: return buttonWest;
case GamepadButton.Start: return startButton;
case GamepadButton.Select: return selectButton;
case GamepadButton.LeftShoulder: return leftShoulder;
case GamepadButton.RightShoulder: return rightShoulder;
case GamepadButton.LeftTrigger: return leftTrigger;
case GamepadButton.RightTrigger: return rightTrigger;
case GamepadButton.LeftStick: return leftStickButton;
case GamepadButton.RightStick: return rightStickButton;
case GamepadButton.DpadUp: return dpad.up;
case GamepadButton.DpadDown: return dpad.down;
case GamepadButton.DpadLeft: return dpad.left;
case GamepadButton.DpadRight: return dpad.right;
default:
throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(GamepadButton));
}
}
}
/// <summary>
/// The gamepad last used/connected by the player or <c>null</c> if there is no gamepad connected
/// to the system.
/// </summary>
/// <remarks>
/// When added, a device is automatically made current (see <see cref="InputDevice.MakeCurrent"/>), so
/// when connecting a gamepad, it will also become current. After that, it will only become current again
/// when input change on non-noisy controls (see <see cref="InputControl.noisy"/>) is received. It will also
/// be available once <see cref="all"/> is queried.
///
/// For local multiplayer scenarios (or whenever there are multiple gamepads that need to be usable
/// in a concurrent fashion), it is not recommended to rely on this property. Instead, it is recommended
/// to use <see cref="PlayerInput"/> or <see cref="Users.InputUser"/>.
/// </remarks>
public static Gamepad current { get; private set; }
/// <summary>
/// A list of gamepads currently connected to the system.
/// </summary>
/// <remarks>
/// Returns all currently connected gamepads.
///
/// Does not cause GC allocation.
///
/// Do <em>not</em> hold on to the value returned by this getter but rather query it whenever
/// you need it. Whenever the gamepad setup changes, the value returned by this getter
/// is invalidated.
///
/// Alternately, for querying a single gamepad, you can use <see cref="current"/> for example.
/// </remarks>
public new static ReadOnlyArray<Gamepad> all => new ReadOnlyArray<Gamepad>(s_Gamepads, 0, s_GamepadCount);
/// <inheritdoc />
protected override void FinishSetup()
{
////REVIEW: what's actually faster/better... storing these in properties or doing the lookup on the fly?
buttonWest = GetChildControl<ButtonControl>("buttonWest");
buttonNorth = GetChildControl<ButtonControl>("buttonNorth");
buttonSouth = GetChildControl<ButtonControl>("buttonSouth");
buttonEast = GetChildControl<ButtonControl>("buttonEast");
startButton = GetChildControl<ButtonControl>("start");
selectButton = GetChildControl<ButtonControl>("select");
leftStickButton = GetChildControl<ButtonControl>("leftStickPress");
rightStickButton = GetChildControl<ButtonControl>("rightStickPress");
dpad = GetChildControl<DpadControl>("dpad");
leftShoulder = GetChildControl<ButtonControl>("leftShoulder");
rightShoulder = GetChildControl<ButtonControl>("rightShoulder");
leftStick = GetChildControl<StickControl>("leftStick");
rightStick = GetChildControl<StickControl>("rightStick");
leftTrigger = GetChildControl<ButtonControl>("leftTrigger");
rightTrigger = GetChildControl<ButtonControl>("rightTrigger");
base.FinishSetup();
}
/// <summary>
/// Make the gamepad the <see cref="current"/> gamepad.
/// </summary>
/// <remarks>
/// This is called automatically by the system when there is input on a gamepad.
///
/// More remarks are available in <see cref="InputDevice.MakeCurrent()"/> when it comes to devices with
/// <see cref="InputControl.noisy"/> controls.
/// </remarks>
/// <example>
/// <code>
/// using System;
/// using UnityEngine;
/// using UnityEngine.InputSystem;
///
/// public class MakeCurrentGamepadExample : MonoBehaviour
/// {
/// void Update()
/// {
/// /// Make the first Gamepad always the current one
/// if (Gamepad.all.Count > 0)
/// {
/// Gamepad.all[0].MakeCurrent();
/// }
/// }
/// }
/// </code>
/// </example>
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc cref="InputDevice.OnAdded"/>
/// <summary>
/// Called when a gamepad is added to the system.
/// </summary>
/// <remarks>
/// Override this method if you want to do additional processing when a gamepad becomes connected. After this method is called, the gamepad is automatically added to the list of <see cref="all"/> gamepads.
/// </remarks>
protected override void OnAdded()
{
ArrayHelpers.AppendWithCapacity(ref s_Gamepads, ref s_GamepadCount, this);
}
/// <inheritdoc cref="InputDevice.OnRemoved"/>
/// <summary>
/// Called when the gamepad is removed from the system.
/// </summary>
/// <remarks>
/// Override this method if you want to do additional processing when a gamepad becomes disconnected. After this method is called, the gamepad is automatically removed from the list of <see cref="all"/> gamepads.
/// </remarks>
protected override void OnRemoved()
{
if (current == this)
current = null;
// Remove from `all`.
var index = ArrayHelpers.IndexOfReference(s_Gamepads, this, s_GamepadCount);
if (index != -1)
ArrayHelpers.EraseAtWithCapacity(s_Gamepads, ref s_GamepadCount, index);
else
{
Debug.Assert(false,
$"Gamepad {this} seems to not have been added but is being removed (gamepad list: {string.Join(", ", all)})"); // Put in else to not allocate on normal path.
}
}
/// <summary>
/// Pause rumble effects on the gamepad.
/// </summary>
/// <remarks>
/// It will pause rumble effects and save the current motor speeds.
/// Resume from those speeds with <see cref="ResumeHaptics"/>.
/// Some devices such as <see cref="DualShock.DualSenseGamepadHID"/> and
/// <see cref="DualShock.DualShock4GamepadHID"/> can also set the LED color when this method is called.
/// </remarks>
/// <seealso cref="IDualMotorRumble"/>
/// <example>
/// <code source="../../DocCodeSamples.Tests/GamepadHapticsExample.cs"/>
/// </example>
public virtual void PauseHaptics()
{
m_Rumble.PauseHaptics(this);
}
/// <summary>
/// Resume rumble effects on the gamepad.
/// </summary>
/// <remarks>
/// It will resume rumble effects from the previously set motor speeds, such as motor speeds saved when
/// calling <see cref="PauseHaptics"/>.
/// Some devices such as <see cref="DualShock.DualSenseGamepadHID"/> and
/// <see cref="DualShock.DualShock4GamepadHID"/> can also set the LED color when this method is called.
/// </remarks>
/// <seealso cref="IDualMotorRumble"/>
/// <example>
/// <code source="../../DocCodeSamples.Tests/GamepadHapticsExample.cs"/>
/// </example>
public virtual void ResumeHaptics()
{
m_Rumble.ResumeHaptics(this);
}
/// <summary>
/// Resets rumble effects on the gamepad by setting motor speeds to 0.
/// </summary>
/// <remarks>
/// Some devices such as <see cref="DualShock.DualSenseGamepadHID"/> and
/// <see cref="DualShock.DualShock4GamepadHID"/> can also set the LED color when this method is called.
/// </remarks>
/// <seealso cref="IDualMotorRumble"/>
/// <example>
/// <code source="../../DocCodeSamples.Tests/GamepadHapticsExample.cs"/>
/// </example>
public virtual void ResetHaptics()
{
m_Rumble.ResetHaptics(this);
}
/// <inheritdoc />
/// <example>
/// <code source="../../DocCodeSamples.Tests/GamepadHapticsExample.cs"/>
/// </example>
public virtual void SetMotorSpeeds(float lowFrequency, float highFrequency)
{
m_Rumble.SetMotorSpeeds(this, lowFrequency, highFrequency);
}
private DualMotorRumble m_Rumble;
private static int s_GamepadCount;
private static Gamepad[] s_Gamepads;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 644895dd25f14937a1d11481e2338925
timeCreated: 1506745703

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e286fc65ad3d4645b213e71b11e963e4
timeCreated: 1516644432

View File

@@ -0,0 +1,131 @@
using System;
using UnityEngine.InputSystem.LowLevel;
////REVIEW: should we keep an explicit playback status? ATM calling ResumeHaptics() will re-issue last set motor speed regardless of pause state
namespace UnityEngine.InputSystem.Haptics
{
/// <summary>
/// Common implementation of dual motor rumbling.
/// </summary>
/// <remarks>
/// This struct is meant for use in devices that implement <see cref="IDualMotorRumble"/>.
/// </remarks>
internal struct DualMotorRumble
{
/// <summary>
/// Normalized [0..1] speed of the low-frequency (usually left) motor.
/// </summary>
/// <value>Speed of left motor.</value>
public float lowFrequencyMotorSpeed { get; private set; }
/// <summary>
/// Normalized [0..1] speed of the high-frequency (usually right) motor.
/// </summary>
/// <value>Speed of right motor.</value>
public float highFrequencyMotorSpeed { get; private set; }
/// <summary>
/// Whether either of the motors is currently set to non-zero speeds.
/// </summary>
/// <value>True if the motors are currently turned on.</value>
/// <remarks>
/// Does not take pausing into account, i.e. <see cref="lowFrequencyMotorSpeed"/> and/or
/// <see cref="highFrequencyMotorSpeed"/> may be non-zero but haptics on the device
/// may actually be paused with <see cref="PauseHaptics"/>.
/// </remarks>
public bool isRumbling =>
!Mathf.Approximately(lowFrequencyMotorSpeed, 0f)
|| !Mathf.Approximately(highFrequencyMotorSpeed, 0f);
/// <summary>
/// Stops haptics by setting motor speeds to zero.
/// </summary>
/// <remarks>
/// Sets both motor speeds to zero while retaining the current values for <see cref="lowFrequencyMotorSpeed"/>
/// and <see cref="highFrequencyMotorSpeed"/>.
/// It will only send the command if <see cref="isRumbling"/> is true.
/// </remarks>
/// <param name="device">Device to send command to.</param>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
public void PauseHaptics(InputDevice device)
{
if (device == null)
throw new ArgumentNullException("device");
if (!isRumbling)
return;
var command = DualMotorRumbleCommand.Create(0f, 0f);
device.ExecuteCommand(ref command);
}
/// <summary>
/// Resume haptics by setting motor speeds to the current values of <see cref="lowFrequencyMotorSpeed"/>
/// and <see cref="highFrequencyMotorSpeed"/>.
/// </summary>
/// <remarks>
/// It will only set motor speeds if <see cref="isRumbling"/> is true
/// </remarks>
/// <param name="device">Device to send command to.</param>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
public void ResumeHaptics(InputDevice device)
{
if (device == null)
throw new ArgumentNullException("device");
if (!isRumbling)
return;
SetMotorSpeeds(device, lowFrequencyMotorSpeed, highFrequencyMotorSpeed);
}
/// <summary>
/// Reset haptics by setting motor speeds to zero.
/// </summary>
/// <remarks>
/// Sets <see cref="lowFrequencyMotorSpeed"/> and <see cref="highFrequencyMotorSpeed"/> to zero.
/// It will only set motor speeds if <see cref="isRumbling"/> is true.
/// </remarks>
/// <param name="device">Device to send command to.</param>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
public void ResetHaptics(InputDevice device)
{
if (device == null)
throw new ArgumentNullException("device");
if (!isRumbling)
return;
SetMotorSpeeds(device, 0.0f, 0.0f);
}
/// <summary>
/// Set the speed of the low-frequency (usually left) and high-frequency (usually right) motor
/// on <paramref name="device"/>. Updates <see cref="lowFrequencyMotorSpeed"/> and
/// <see cref="highFrequencyMotorSpeed"/>.
/// </summary>
/// <param name="device">Device to send command to.</param>
/// <param name="lowFrequency">Speed of the low-frequency (left) motor. Normalized [0..1] value
/// with 1 indicating maximum speed and 0 indicating the motor is turned off. Will automatically
/// be clamped into range.</param>
/// <param name="highFrequency">Speed of the high-frequency (right) motor. Normalized [0..1] value
/// with 1 indicating maximum speed and 0 indicating the motor is turned off. Will automatically
/// be clamped into range.</param>
/// <remarks>
/// Sends <see cref="DualMotorRumbleCommand"/> to <paramref name="device"/>.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
public void SetMotorSpeeds(InputDevice device, float lowFrequency, float highFrequency)
{
if (device == null)
throw new ArgumentNullException("device");
lowFrequencyMotorSpeed = Mathf.Clamp(lowFrequency, 0.0f, 1.0f);
highFrequencyMotorSpeed = Mathf.Clamp(highFrequency, 0.0f, 1.0f);
var command = DualMotorRumbleCommand.Create(lowFrequencyMotorSpeed, highFrequencyMotorSpeed);
device.ExecuteCommand(ref command);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4a1c749d40b4bf985d6b32d7db339bb
timeCreated: 1517012194

View File

@@ -0,0 +1,37 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
[StructLayout(LayoutKind.Explicit, Size = kSize)]
internal struct DualMotorRumbleCommand : IInputDeviceCommandInfo
{
public static FourCC Type { get { return new FourCC('R', 'M', 'B', 'L'); } }
internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(float) * 2;
[FieldOffset(0)]
public InputDeviceCommand baseCommand;
[FieldOffset(InputDeviceCommand.kBaseCommandSize)]
public float lowFrequencyMotorSpeed;
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 4)]
public float highFrequencyMotorSpeed;
public FourCC typeStatic
{
get { return Type; }
}
public static DualMotorRumbleCommand Create(float lowFrequency, float highFrequency)
{
return new DualMotorRumbleCommand
{
baseCommand = new InputDeviceCommand(Type, kSize),
lowFrequencyMotorSpeed = lowFrequency,
highFrequencyMotorSpeed = highFrequency
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ee46ea662a1d4d2397d0b3a45baf67ef
timeCreated: 1517015642

View File

@@ -0,0 +1,30 @@
namespace UnityEngine.InputSystem.Haptics
{
/// <summary>
/// A simple haptics interface that allows to control two motors individually.
/// </summary>
/// <remarks>
/// Dual-motor control is most common on gamepads (see <see cref="Gamepad"/>) such as
/// Xbox and PlayStation controllers.
/// </remarks>
public interface IDualMotorRumble : IHaptics
{
/// <summary>
/// Set the motor speeds of the low-frequency (usually on the left) and high-frequency
/// (usually on the right) motors.
/// </summary>
/// <param name="lowFrequency">Speed of the low-frequency (left) motor. Normalized [0..1] value
/// with 1 indicating maximum speed and 0 indicating the motor is turned off. Will automatically
/// be clamped into range.</param>
/// <param name="highFrequency">Speed of the high-frequency (right) motor. Normalized [0..1] value
/// with 1 indicating maximum speed and 0 indicating the motor is turned off. Will automatically
/// be clamped into range.</param>
/// <remarks>
/// Note that hardware will put limits on the level of control you have over the motors.
/// Rumbling the motors at maximum speed for an extended period of time may cause them to turn
/// off for some time to prevent overheating. Also, how quickly the motors react and how often
/// the speed can be updated will depend on the hardware and drivers.
/// </remarks>
void SetMotorSpeeds(float lowFrequency, float highFrequency);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6982a6ffcdbf47a6aa68fd4418622395
timeCreated: 1516644451

View File

@@ -0,0 +1,81 @@
////REVIEW: Devices usually will automatically shut down haptics if they haven't received a haptics command in some time.
//// How should we deal with that? Should haptics automatically refresh themselves periodically while they are set?
////REVIEW: Do we need a mute in addition to a pause?
namespace UnityEngine.InputSystem.Haptics
{
/// <summary>
/// Base interface for haptics on input devices.
/// </summary>
/// <remarks>
/// To support haptics, an <see cref="InputDevice"/> has to implement one or more
/// haptics interfaces.
/// </remarks>
/// <example>
/// <code>
/// class MyDevice : InputDevice, IDualMotorRumble
/// {
/// private DualMotorRumble m_Rumble;
///
/// public void SetMotorSpeeds(float lowFrequency, float highFrequency)
/// {
/// m_Rumble.SetMotorSpeeds(lowFrequency, highFrequency);
/// }
///
/// public void PauseHaptics()
/// {
/// m_Rumble.PauseHaptics();
/// }
///
/// public void ResumeHaptics()
/// {
/// m_Rumble.ResumeHaptics();
/// }
///
/// public void ResetHaptics()
/// {
/// m_Rumble.ResetHaptics();
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="InputSystem.PauseHaptics"/>
/// <seealso cref="InputSystem.ResumeHaptics"/>
/// <seealso cref="InputSystem.ResetHaptics"/>
public interface IHaptics
{
/// <summary>
/// Pause haptics playback on the device.
/// </summary>
/// <remarks>
/// This should preserve current playback settings (such as motor speed levels
/// or effect playback positions) but shut down feedback effects on the device.
///
/// If proper resumption of effects is not possible, playback should be stopped
/// and <see cref="ResumeHaptics"/> is allowed to be a no-operation.
///
/// Note that haptics playback states are not required to survive domain reloads
/// in the editor.
/// </remarks>
/// <seealso cref="ResumeHaptics"/>
void PauseHaptics();
/// <summary>
/// Resume haptics playback on the device.
/// </summary>
/// <remarks>
/// Should be called after calling <see cref="PauseHaptics"/>. Otherwise does
/// nothing.
/// </remarks>
void ResumeHaptics();
/// <summary>
/// Reset haptics playback on the device to its default state.
/// </summary>
/// <remarks>
/// This will turn off all haptics effects that may be playing on the device.
/// </remarks>
void ResetHaptics();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 179e4e4b8003406d97463b101f15eb8b
timeCreated: 1516752847

View File

@@ -0,0 +1,14 @@
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// A device that implements its own reset logic for when <see cref="InputSystem.ResetDevice"/>
/// is called.
/// </summary>
internal interface ICustomDeviceReset
{
/// <summary>
/// Reset the current device state.
/// </summary>
void Reset();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 08dab706fb9641539767891cee0c7d3c
timeCreated: 1636546307

View File

@@ -0,0 +1,7 @@
namespace UnityEngine.InputSystem.LowLevel
{
internal interface IEventMerger
{
bool MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7522ce067dfd4acf8e9e136fc7a0ab64
timeCreated: 1628242797

View File

@@ -0,0 +1,18 @@
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Gives an opportunity for device to modify event data in-place before it gets propagated to the rest of the system.
/// </summary>
/// <remarks>
/// If device also implements <see cref="IEventMerger"/> it will run first, because we don't process events ahead-of-time.
/// </remarks>
internal interface IEventPreProcessor
{
/// <summary>
/// Preprocess the event. !!! Beware !!! currently events can only shrink or stay the same size.
/// </summary>
/// <param name="currentEventPtr">The event to preprocess.</param>
/// <returns>True if event should be processed further, false if event should be skipped and ignored.</returns>
bool PreProcessEvent(InputEventPtr currentEventPtr);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0be28dce931f40f8a48953c52e03d5d7
timeCreated: 1631188213

View File

@@ -0,0 +1,15 @@
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Interface to allow custom input devices to receive callbacks when the input system is updated.
/// </summary>
/// <remarks>
/// If an <see cref="InputDevice"/> class implements the IInputUpdateCallbackReceiver interface, any instance of the
/// InputDevice will have it's <see cref="OnUpdate"/> method called whenever the input system updates. This can be used
/// to implement custom state update logic for virtual input devices which track some state in the project.
/// </remarks>
public interface IInputUpdateCallbackReceiver
{
void OnUpdate();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bb7aebed4ffd404da3399f60b272c7ee
timeCreated: 1508656424

View File

@@ -0,0 +1,44 @@
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Interface for <see cref="InputDevice"/> classes that can receive text input events.
/// </summary>
/// <remarks>
/// This interface should be implemented by devices that are meant to receive text
/// input through <see cref="TextEvent"/>.
/// </remarks>
/// <seealso cref="TextEvent"/>
/// <seealso cref="IMECompositionEvent"/>
public interface ITextInputReceiver
{
/// <summary>
/// A single, fully-formed Unicode character has been typed on the device.
/// </summary>
/// <param name="character">Character that was typed. Note that in case the character is part of
/// a surrogate pair, this method is called first with the high surrogate and then with the
/// low surrogate character.</param>
/// <remarks>
/// This method is called on a device when a <see cref="TextEvent"/> is received
/// for the device. <paramref name="character"/> is the <see cref="TextEvent.character"/>
/// from the event.
///
/// Note that this method will be called *twice* for a single <see cref="TextEvent"/>
/// in case the given UTF-32 (encoding in the event) needs to be represented as UTF-16
/// (encoding of <c>char</c> in C#) surrogate.
/// </remarks>
void OnTextInput(char character);
/// <summary>
/// Called when an IME composition is in-progress or finished.
/// </summary>
/// <param name="compositionString">The current composition.</param>
/// <seealso cref="IMECompositionEvent"/>
/// <seealso cref="Keyboard.onIMECompositionChange"/>
/// <remarks>
/// The method will be repeatedly called with the current string while composition is in progress.
/// Once composition finishes, the method will be called one more time with a blank composition
/// string.
/// </remarks>
void OnIMECompositionChanged(IMECompositionString compositionString);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b5bc678fe5c44624a0f00e148fc2670
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 250f0b2038f8466aaa09814a4b1c5f46
timeCreated: 1506738413

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1284e66e3be9424db2c22a37b5a3cf0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
using System;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem
{
/// <summary>
/// Indicates what type of change related to an <see cref="InputDevice">input device</see> occurred.
/// </summary>
/// <remarks>
/// Use <see cref="InputSystem.onDeviceChange"/> to receive notifications about changes
/// to the input device setup in the system.
///
/// <example>
/// <code>
/// InputSystem.onDeviceChange +=
/// (device, change) =>
/// {
/// switch (change)
/// {
/// case InputDeviceChange.Added:
/// Debug.Log($"Device {device} was added");
/// break;
/// case InputDeviceChange.Removed:
/// Debug.Log($"Device {device} was removed");
/// break;
/// }
/// };
/// </code>
/// </example>
/// </remarks>
public enum InputDeviceChange
{
/// <summary>
/// A new device was added to the system. This is triggered <em>after</em> the device
/// has already been added, i.e. it already appears on <see cref="InputSystem.devices"/>.
///
/// See also <see cref="InputSystem.AddDevice{TDevice}(string)"/> and <see cref="InputDevice.added"/>.
/// </summary>
Added,
/// <summary>
/// An existing device was removed from the system. This is triggered <em>after</em> the
/// device has already been removed, i.e. it already has been cleared from <see cref="InputSystem.devices"/>.
///
/// Other than when a device is removed programmatically, this happens when a device
/// is unplugged from the system. Subsequent to the notification, the system will remove
/// the <see cref="InputDevice"/> instance from its list and remove the device's
/// recorded input state.
///
/// See also <see cref="InputSystem.RemoveDevice"/>.
/// </summary>
Removed,
/// <summary>
/// A device reported by the <see cref="IInputRuntime"/> was <see cref="Removed"/> but was
/// retained by the system as <see cref="InputSystem.disconnectedDevices">disconnected</see>.
///
/// See also <see cref="InputSystem.disconnectedDevices"/>.
/// </summary>
Disconnected,
/// <summary>
/// A device that was previously retained as <see cref="Disconnected"/> has been re-discovered
/// and has been <see cref="Added"/> to the system again.
///
/// See also <see cref="InputSystem.disconnectedDevices"/>.
/// </summary>
Reconnected,
/// <summary>
/// An existing device was re-enabled after having been <see cref="Disabled"/>.
///
/// See also <see cref="InputSystem.EnableDevice"/> and <see cref="InputDevice.enabled"/>.
/// </summary>
Enabled,
/// <summary>
/// An existing device was disabled.
///
/// See also <see cref="InputSystem.DisableDevice"/> and <see cref="InputDevice.enabled"/>.
/// </summary>
Disabled,
/// <summary>
/// The usages on a device have changed.
///
/// This may signal, for example, that what was the right hand XR controller before
/// is now the left hand controller.
///
/// See also <see cref="InputSystem.SetDeviceUsage(InputDevice,string)"/> and
/// <see cref="InputControl.usages"/>.
/// </summary>
UsageChanged,
/// <summary>
/// The configuration of a device has changed.
///
/// This may signal, for example, that the layout used by the keyboard has changed or
/// that, on a console, a gamepad has changed which player ID(s) it is assigned to.
///
/// See also <see cref="DeviceConfigurationEvent"/> and <see cref="InputSystem.QueueConfigChangeEvent"/>.
/// </summary>
ConfigurationChanged,
/// <summary>
/// Device is being "soft" reset but in a way that excludes <see cref="Layouts.InputControlLayout.ControlItem.dontReset"/>
/// controls such as mouse positions. This can happen during application focus changes
/// (see <see cref="InputSettings.backgroundBehavior"/>) or when <see cref="InputSystem.ResetDevice"/>
/// is called explicitly.
///
/// This notification is sent before the actual reset happens.
/// </summary>
SoftReset,
/// <summary>
/// Device is being "hard" reset, i.e. every control is reset to its default value. This happens only
/// when explicitly forced through <see cref="InputSystem.ResetDevice"/>.
///
/// This notification is sent before the actual reset happens.
/// </summary>
HardReset,
[Obsolete("Destroyed enum has been deprecated.")]
Destroyed,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af015677ecab4644b2382c6b37c2d95c
timeCreated: 1506750675

View File

@@ -0,0 +1,417 @@
using System;
using UnityEngine.InputSystem.Utilities;
////TODO: add a 'devicePath' property that platforms can use to relay their internal device locators
//// (but do *not* take it into account when comparing descriptions for disconnected devices)
namespace UnityEngine.InputSystem.Layouts
{
/// <summary>
/// Metadata for an input device.
/// </summary>
/// <remarks>
/// Device descriptions are mainly used to determine which <see cref="InputControlLayout"/>
/// to create an actual <see cref="InputDevice"/> instance from. Each description is comprised
/// of a set of properties that each are individually optional. However, for a description
/// to be usable, at least some need to be set. Generally, the minimum viable description
/// for a device is one with <see cref="deviceClass"/> filled out.
///
/// <example>
/// <code>
/// // Device description equivalent to a generic gamepad with no
/// // further information about the device.
/// new InputDeviceDescription
/// {
/// deviceClass = "Gamepad"
/// };
/// </code>
/// </example>
///
/// Device descriptions will usually be supplied by the Unity runtime but can also be manually
/// fed into the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>. The
/// system will remember each device description it has seen regardless of whether it was
/// able to successfully create a device from the description. To query the list of descriptions
/// that for whatever reason did not result in a device being created, call <see
/// cref="InputSystem.GetUnsupportedDevices()"/>.
///
/// Whenever layout registrations in the system are changed (e.g. by calling <see
/// cref="InputSystem.RegisterLayout{T}"/> or whenever <see cref="InputSettings.supportedDevices"/>
/// is changed, the system will go through the list of unsupported devices itself and figure out
/// if there are device descriptions that now it can turn into devices. The same also applies
/// in reverse; if, for example, a layout is removed that is currently used a device, the
/// device will be removed and its description (if any) will be placed on the list of
/// unsupported devices.
/// </remarks>
/// <seealso cref="InputDevice.description"/>
/// <seealso cref="InputDeviceMatcher"/>
[Serializable]
public struct InputDeviceDescription : IEquatable<InputDeviceDescription>
{
/// <summary>
/// How we talk to the device; usually name of the underlying backend that feeds
/// state for the device (e.g. "HID" or "XInput").
/// </summary>
/// <value>Name of interface through which the device is reported.</value>
/// <see cref="InputDeviceMatcher.WithInterface"/>
public string interfaceName
{
get => m_InterfaceName;
set => m_InterfaceName = value;
}
/// <summary>
/// What the interface thinks the device classifies as.
/// </summary>
/// <value>Broad classification of device.</value>
/// <remarks>
/// If there is no layout specifically matching a device description,
/// the device class is used as as fallback. If, for example, this field
/// is set to "Gamepad", the "Gamepad" layout is used as a fallback.
/// </remarks>
/// <seealso cref="InputDeviceMatcher.WithDeviceClass"/>
public string deviceClass
{
get => m_DeviceClass;
set => m_DeviceClass = value;
}
/// <summary>
/// Name of the vendor that produced the device.
/// </summary>
/// <value>Name of manufacturer.</value>
/// <seealso cref="InputDeviceMatcher.WithManufacturer"/>
public string manufacturer
{
get => m_Manufacturer;
set => m_Manufacturer = value;
}
/// <summary>
/// Name of the product assigned by the vendor to the device.
/// </summary>
/// <value>Name of product.</value>
/// <seealso cref="InputDeviceMatcher.WithProduct"/>
public string product
{
get => m_Product;
set => m_Product = value;
}
/// <summary>
/// If available, serial number for the device.
/// </summary>
/// <value>Serial number of device.</value>
public string serial
{
get => m_Serial;
set => m_Serial = value;
}
/// <summary>
/// Version string of the device and/or driver.
/// </summary>
/// <value>Version of device and/or driver.</value>
/// <seealso cref="InputDeviceMatcher.WithVersion"/>
public string version
{
get => m_Version;
set => m_Version = value;
}
/// <summary>
/// An optional JSON string listing device-specific capabilities.
/// </summary>
/// <value>Interface-specific listing of device capabilities.</value>
/// <remarks>
/// The primary use of this field is to allow custom layout factories
/// to create layouts on the fly from in-depth device descriptions delivered
/// by external APIs.
///
/// In the case of HID, for example, this field contains a JSON representation
/// of the HID descriptor (see <see cref="HID.HID.HIDDeviceDescriptor"/>) as
/// reported by the device driver. This descriptor contains information about
/// all I/O elements on the device which can be used to determine the control
/// setup and data format used by the device.
/// </remarks>
/// <seealso cref="InputDeviceMatcher.WithCapability{T}"/>
public string capabilities
{
get => m_Capabilities;
set => m_Capabilities = value;
}
/// <summary>
/// Whether any of the properties in the description are set.
/// </summary>
/// <value>True if any of <see cref="interfaceName"/>, <see cref="deviceClass"/>,
/// <see cref="manufacturer"/>, <see cref="product"/>, <see cref="serial"/>,
/// <see cref="version"/>, or <see cref="capabilities"/> is not <c>null</c> and
/// not empty.</value>
public bool empty =>
string.IsNullOrEmpty(m_InterfaceName) &&
string.IsNullOrEmpty(m_DeviceClass) &&
string.IsNullOrEmpty(m_Manufacturer) &&
string.IsNullOrEmpty(m_Product) &&
string.IsNullOrEmpty(m_Serial) &&
string.IsNullOrEmpty(m_Version) &&
string.IsNullOrEmpty(m_Capabilities);
/// <summary>
/// Return a string representation of the description useful for
/// debugging.
/// </summary>
/// <returns>A script representation of the description.</returns>
public override string ToString()
{
var haveProduct = !string.IsNullOrEmpty(product);
var haveManufacturer = !string.IsNullOrEmpty(manufacturer);
var haveInterface = !string.IsNullOrEmpty(interfaceName);
if (haveProduct && haveManufacturer)
{
if (haveInterface)
return $"{manufacturer} {product} ({interfaceName})";
return $"{manufacturer} {product}";
}
if (haveProduct)
{
if (haveInterface)
return $"{product} ({interfaceName})";
return product;
}
if (!string.IsNullOrEmpty(deviceClass))
{
if (haveInterface)
return $"{deviceClass} ({interfaceName})";
return deviceClass;
}
// For some HIDs on Windows, we don't get a product and manufacturer string even though
// the HID is guaranteed to have a product and vendor ID. Resort to printing capabilities
// which for HIDs at least include the product and vendor ID.
if (!string.IsNullOrEmpty(capabilities))
{
const int kMaxCapabilitiesLength = 40;
var caps = capabilities;
if (capabilities.Length > kMaxCapabilitiesLength)
caps = caps.Substring(0, kMaxCapabilitiesLength) + "...";
if (haveInterface)
return $"{caps} ({interfaceName})";
return caps;
}
if (haveInterface)
return interfaceName;
return "<Empty Device Description>";
}
/// <summary>
/// Compare the description to the given <paramref name="other"/> description.
/// </summary>
/// <param name="other">Another device description.</param>
/// <returns>True if the two descriptions are equivalent.</returns>
/// <remarks>
/// Two descriptions are equivalent if all their properties are equal
/// (ignore case).
/// </remarks>
public bool Equals(InputDeviceDescription other)
{
return m_InterfaceName.InvariantEqualsIgnoreCase(other.m_InterfaceName) &&
m_DeviceClass.InvariantEqualsIgnoreCase(other.m_DeviceClass) &&
m_Manufacturer.InvariantEqualsIgnoreCase(other.m_Manufacturer) &&
m_Product.InvariantEqualsIgnoreCase(other.m_Product) &&
m_Serial.InvariantEqualsIgnoreCase(other.m_Serial) &&
m_Version.InvariantEqualsIgnoreCase(other.m_Version) &&
////REVIEW: this would ideally compare JSON contents not just the raw string
m_Capabilities.InvariantEqualsIgnoreCase(other.m_Capabilities);
}
/// <summary>
/// Compare the description to the given object.
/// </summary>
/// <param name="obj">An object.</param>
/// <returns>True if <paramref name="obj"/> is an InputDeviceDescription
/// equivalent to this one.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is InputDeviceDescription description && Equals(description);
}
/// <summary>
/// Compute a hash code for the device description.
/// </summary>
/// <returns>A hash code.</returns>
public override int GetHashCode()
{
unchecked
{
var hashCode = m_InterfaceName != null ? m_InterfaceName.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ (m_DeviceClass != null ? m_DeviceClass.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (m_Manufacturer != null ? m_Manufacturer.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (m_Product != null ? m_Product.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (m_Serial != null ? m_Serial.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (m_Version != null ? m_Version.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (m_Capabilities != null ? m_Capabilities.GetHashCode() : 0);
return hashCode;
}
}
/// <summary>
/// Compare the two device descriptions.
/// </summary>
/// <param name="left">First device description.</param>
/// <param name="right">Second device description.</param>
/// <returns>True if the two descriptions are equivalent.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public static bool operator==(InputDeviceDescription left, InputDeviceDescription right)
{
return left.Equals(right);
}
/// <summary>
/// Compare the two device descriptions for inequality.
/// </summary>
/// <param name="left">First device description.</param>
/// <param name="right">Second device description.</param>
/// <returns>True if the two descriptions are not equivalent.</returns>
/// <seealso cref="Equals(InputDeviceDescription)"/>
public static bool operator!=(InputDeviceDescription left, InputDeviceDescription right)
{
return !left.Equals(right);
}
/// <summary>
/// Return a JSON representation of the device description.
/// </summary>
/// <returns>A JSON representation of the description.</returns>
/// <remarks>
/// <example>
/// The result can be converted back into an InputDeviceDescription
/// using <see cref="FromJson"/>.
///
/// <code>
/// var description = new InputDeviceDescription
/// {
/// interfaceName = "HID",
/// product = "SomeDevice",
/// capabilities = @"
/// {
/// ""vendorId"" : 0xABA,
/// ""productId"" : 0xEFE
/// }
/// "
/// };
///
/// Debug.Log(description.ToJson());
/// // Prints
/// // {
/// // "interface" : "HID",
/// // "product" : "SomeDevice",
/// // "capabilities" : "{ \"vendorId\" : 0xABA, \"productId\" : 0xEFF }"
/// // }
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="FromJson"/>
public string ToJson()
{
var data = new DeviceDescriptionJson
{
@interface = interfaceName,
type = deviceClass,
product = product,
manufacturer = manufacturer,
serial = serial,
version = version,
capabilities = capabilities
};
return JsonUtility.ToJson(data, true);
}
/// <summary>
/// Read an InputDeviceDescription from its JSON representation.
/// </summary>
/// <param name="json">String in JSON format.</param>
/// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
/// <returns>The converted </returns>
/// <exception cref="ArgumentException">There as a parse error in <paramref name="json"/>.
/// </exception>
/// <remarks>
/// <example>
/// <code>
/// InputDeviceDescription.FromJson(@"
/// {
/// ""interface"" : ""HID"",
/// ""product"" : ""SomeDevice""
/// }
/// ");
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="ToJson"/>
public static InputDeviceDescription FromJson(string json)
{
if (json == null)
throw new ArgumentNullException(nameof(json));
var data = JsonUtility.FromJson<DeviceDescriptionJson>(json);
return new InputDeviceDescription
{
interfaceName = data.@interface,
deviceClass = data.type,
product = data.product,
manufacturer = data.manufacturer,
serial = data.serial,
version = data.version,
capabilities = data.capabilities
};
}
internal static bool ComparePropertyToDeviceDescriptor(string propertyName, JsonParser.JsonString propertyValue, string deviceDescriptor)
{
// We use JsonParser instead of JsonUtility.Parse in order to not allocate GC memory here.
var json = new JsonParser(deviceDescriptor);
if (!json.NavigateToProperty(propertyName))
{
if (propertyValue.text.isEmpty)
return true;
return false;
}
return json.CurrentPropertyHasValueEqualTo(propertyValue);
}
[SerializeField] private string m_InterfaceName;
[SerializeField] private string m_DeviceClass;
[SerializeField] private string m_Manufacturer;
[SerializeField] private string m_Product;
[SerializeField] private string m_Serial;
[SerializeField] private string m_Version;
[SerializeField] private string m_Capabilities;
private struct DeviceDescriptionJson
{
public string @interface;
public string type;
public string product;
public string serial;
public string version;
public string manufacturer;
public string capabilities;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 98406e6253bf4ccba72ff32ee85f5efa
timeCreated: 1506763077

View File

@@ -0,0 +1,683 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.Layouts
{
/// <summary>
/// Specification that can be matched against an <see cref="InputDeviceDescription"/>. This is
/// used to find which <see cref="InputControlLayout"/> to create for a device when it is discovered.
/// </summary>
/// <remarks>
/// Each matcher is basically a set of key/value pairs where each value may either be
/// a regular expression or a plain value object. The central method for testing a given matcher
/// against an <see cref="InputDeviceDescription"/> is <see cref="MatchPercentage"/>.
///
/// Various helper methods such as <see cref="WithInterface"/> or <see cref="WithCapability{TValue}"/>
/// assist with creating matchers.
///
/// <example>
/// <code>
/// // A matcher that matches a PS4 controller by name.
/// new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithManufacturer("Sony.+Entertainment") // Regular expression
/// .WithProduct("Wireless Controller"));
///
/// // A matcher that matches the same controller by PID and VID.
/// new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithCapability("vendorId", 0x54C) // Sony Entertainment.
/// .WithCapability("productId", 0x9CC)); // Wireless controller.
/// </code>
/// </example>
///
/// For each registered <see cref="InputControlLayout"/> in the system that represents
/// a device, arbitrary many matchers can be added. A matcher can be supplied either
/// at registration time or at any point after using <see cref="InputSystem.RegisterLayoutMatcher"/>.
///
/// <example>
/// <code>
/// // Supply a matcher at registration time.
/// InputSystem.RegisterLayout&lt;DualShock4GamepadHID&gt;(
/// matches: new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithCapability("vendorId", 0x54C) // Sony Entertainment.
/// .WithCapability("productId", 0x9CC)); // Wireless controller.
///
/// // Supply a matcher for an already registered layout.
/// // This can be called repeatedly and will add another matcher
/// // each time.
/// InputSystem.RegisterLayoutMatcher&lt;DualShock4GamepadHID&gt;(
/// matches: new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithManufacturer("Sony.+Entertainment")
/// .WithProduct("Wireless Controller"));
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputDeviceDescription"/>
/// <seealso cref="InputDevice.description"/>
/// <seealso cref="InputSystem.RegisterLayoutMatcher"/>
public struct InputDeviceMatcher : IEquatable<InputDeviceMatcher>
{
private KeyValuePair<InternedString, object>[] m_Patterns;
/// <summary>
/// If true, the matcher has been default-initialized and contains no
/// matching <see cref="patterns"/>.
/// </summary>
/// <value>Whether the matcher contains any matching patterns.</value>
/// <seealso cref="patterns"/>
public bool empty => m_Patterns == null;
/// <summary>
/// The list of patterns to match.
/// </summary>
/// <value>List of matching patterns.</value>
/// <remarks>
/// Each pattern is comprised of a key and a value. The key determines which part
/// of an <see cref="InputDeviceDescription"/> to match.
///
/// The value represents the expected value. This can be either a plain string
/// (matched case-insensitive) or a regular expression.
/// </remarks>
/// <seealso cref="WithInterface"/>
/// <seealso cref="WithCapability{TValue}"/>
/// <seealso cref="WithProduct"/>
/// <seealso cref="WithManufacturer"/>
/// <seealso cref="WithVersion"/>
/// <seealso cref="WithDeviceClass"/>
public IEnumerable<KeyValuePair<string, object>> patterns
{
get
{
if (m_Patterns == null)
yield break;
var count = m_Patterns.Length;
for (var i = 0; i < count; ++i)
yield return new KeyValuePair<string, object>(m_Patterns[i].Key.ToString(), m_Patterns[i].Value);
}
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match an <see cref="InputDeviceDescription.interfaceName"/>.
/// </summary>
/// <param name="pattern">String to match.</param>
/// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
/// a regular expression.</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.interfaceName"/>
public InputDeviceMatcher WithInterface(string pattern, bool supportRegex = true)
{
return With(kInterfaceKey, pattern, supportRegex);
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.deviceClass"/>.
/// </summary>
/// <param name="pattern">String to match.</param>
/// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
/// a regular expression.</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.deviceClass"/>
public InputDeviceMatcher WithDeviceClass(string pattern, bool supportRegex = true)
{
return With(kDeviceClassKey, pattern, supportRegex);
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.manufacturer"/>.
/// </summary>
/// <param name="pattern">String to match.</param>
/// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
/// a regular expression.</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.manufacturer"/>
public InputDeviceMatcher WithManufacturer(string pattern, bool supportRegex = true)
{
return With(kManufacturerKey, pattern, supportRegex);
}
/// <summary>
/// Add a pattern (simple string) to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.manufacturer"/>.
/// </summary>
/// <param name="noRegExPattern">String to match - simple keyword search in a device manufacturer string (eg "SONY").</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.manufacturer"/>
public InputDeviceMatcher WithManufacturerContains(string noRegExPattern)
{
return With(kManufacturerContainsKey, noRegExPattern, supportRegex: false);
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.product"/>.
/// </summary>
/// <param name="pattern">String to match.</param>
/// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
/// a regular expression.</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.product"/>
public InputDeviceMatcher WithProduct(string pattern, bool supportRegex = true)
{
return With(kProductKey, pattern, supportRegex);
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.version"/>.
/// </summary>
/// <param name="pattern">String to match.</param>
/// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
/// a regular expression.</param>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <seealso cref="InputDeviceDescription.version"/>
public InputDeviceMatcher WithVersion(string pattern, bool supportRegex = true)
{
return With(kVersionKey, pattern, supportRegex);
}
/// <summary>
/// Add a pattern to <see cref="patterns"/> to match an individual capability in <see cref="InputDeviceDescription.capabilities"/>.
/// </summary>
/// <param name="path">Path to the JSON property using '/' as a separator,
/// e.g. <c>"elements/count"</c>.</param>
/// <param name="value">Value to match. This can be a string, a regular expression,
/// a boolean, an integer, or a float. Floating-point numbers are matched with respect
/// for <c>Mathf.Epsilon</c>. Values are converted between types automatically as
/// needed (meaning that a bool can be compared to a string, for example).</param>
/// <typeparam name="TValue">Type of value to match.</typeparam>
/// <returns>The modified device matcher with the added pattern.</returns>
/// <remarks>
/// Capabilities are stored as JSON strings in <see cref="InputDeviceDescription.capabilities"/>.
/// A matcher has the ability to match specific properties from the JSON object
/// contained in the capabilities string.
///
/// <example>
/// <code>
/// // The description for a HID will usually have a HIDDeviceDescriptor in
/// // JSON format found on its InputDeviceDescription.capabilities. So, a
/// // real-world device description could look the equivalent of this:
/// var description = new InputDeviceDescription
/// {
/// interfaceName = "HID",
/// capabilities = new HID.HIDDeviceDescriptor
/// {
/// vendorId = 0x54C,
/// productId = 0x9CC
/// }.ToJson()
/// };
///
/// // We can create a device matcher that looks for those to properties
/// // directly in the JSON object.
/// new InputDeviceMatcher()
/// .WithCapability("vendorId", 0x54C)
/// .WithCapability("productId", 0x9CC);
/// </code>
/// </example>
///
/// Properties in nested objects can be referenced by separating properties
/// with <c>/</c> and properties in arrays can be indexed with <c>[..]</c>.
/// </remarks>
/// <seealso cref="InputDeviceDescription.capabilities"/>
public InputDeviceMatcher WithCapability<TValue>(string path, TValue value)
{
return With(new InternedString(path), value);
}
private InputDeviceMatcher With(InternedString key, object value, bool supportRegex = true)
{
// If it's a string, check whether it's a regex.
if (supportRegex && value is string str)
{
var mayBeRegex = !str.All(ch => char.IsLetterOrDigit(ch) || char.IsWhiteSpace(ch)) &&
!double.TryParse(str, out var _); // Avoid '.' in floats forcing the value to be a regex.
if (mayBeRegex)
value = new Regex(str, RegexOptions.IgnoreCase);
}
// Add to list.
var result = this;
ArrayHelpers.Append(ref result.m_Patterns, new KeyValuePair<InternedString, object>(key, value));
return result;
}
/// <summary>
/// Return the level of matching to the given <paramref name="deviceDescription"/>.
/// </summary>
/// <param name="deviceDescription">A device description.</param>
/// <returns>A score usually in the range between 0 and 1.</returns>
/// <remarks>
/// The algorithm computes a score of how well the matcher matches the given description.
/// Essentially, a matcher that matches every single property that is present (as in
/// not <c>null</c> and not an empty string) in <paramref name="deviceDescription"/> receives
/// a score of 1, a matcher that matches none a score of 0. Matches that match only a subset
/// receive a score in-between.
///
/// An exception to this are capabilities. Every single match of a capability is counted
/// as one property match and added to the score. This means that matchers that match
/// on multiple capabilities may actually achieve a score &gt;1.
///
/// <example>
/// <code>
/// var description = new InputDeviceDescription
/// {
/// interfaceName = "HID",
/// product = "MadeUpDevice",
/// capabilities = new HID.HIDDeviceDescriptor
/// {
/// vendorId = 0xABC,
/// productId = 0xDEF
/// }.ToJson()
/// };
///
/// // This matcher will achieve a score of 0.666 (2/3) as it
/// // matches two out of three available properties.
/// new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithProduct("MadeUpDevice");
///
/// // This matcher will achieve a score of 1 despite not matching
/// // 'product'. The reason is that it matches two keys in
/// // 'capabilities'.
/// new InputDeviceMatcher()
/// .WithInterface("HID")
/// .WithCapability("vendorId", 0xABC)
/// .WithCapability("productId", 0xDEF);
/// </code>
/// </example>
/// </remarks>
public float MatchPercentage(InputDeviceDescription deviceDescription)
{
if (empty)
return 0;
// Go through all patterns. Score is 0 if any of the patterns
// doesn't match.
var numPatterns = m_Patterns.Length;
for (var i = 0; i < numPatterns; ++i)
{
var key = m_Patterns[i].Key;
var pattern = m_Patterns[i].Value;
if (key == kInterfaceKey)
{
if (string.IsNullOrEmpty(deviceDescription.interfaceName)
|| !MatchSingleProperty(pattern, deviceDescription.interfaceName))
return 0;
}
else if (key == kDeviceClassKey)
{
if (string.IsNullOrEmpty(deviceDescription.deviceClass)
|| !MatchSingleProperty(pattern, deviceDescription.deviceClass))
return 0;
}
else if (key == kManufacturerKey)
{
if (string.IsNullOrEmpty(deviceDescription.manufacturer)
|| !MatchSingleProperty(pattern, deviceDescription.manufacturer))
return 0;
}
else if (key == kManufacturerContainsKey)
{
if (string.IsNullOrEmpty(deviceDescription.manufacturer)
|| !MatchSinglePropertyContains(pattern, deviceDescription.manufacturer))
return 0;
}
else if (key == kProductKey)
{
if (string.IsNullOrEmpty(deviceDescription.product)
|| !MatchSingleProperty(pattern, deviceDescription.product))
return 0;
}
else if (key == kVersionKey)
{
if (string.IsNullOrEmpty(deviceDescription.version)
|| !MatchSingleProperty(pattern, deviceDescription.version))
return 0;
}
else
{
// Capabilities match. Take the key as a path into the JSON
// object and match the value found at the given path.
if (string.IsNullOrEmpty(deviceDescription.capabilities))
return 0;
var graph = new JsonParser(deviceDescription.capabilities);
if (!graph.NavigateToProperty(key.ToString()) ||
!graph.CurrentPropertyHasValueEqualTo(new JsonParser.JsonValue { type = JsonParser.JsonValueType.Any, anyValue = pattern}))
return 0;
}
}
// All patterns matched. Our score is determined by the number of properties
// we matched against.
var propertyCountInDescription = GetNumPropertiesIn(deviceDescription);
var scorePerProperty = 1.0f / propertyCountInDescription;
return numPatterns * scorePerProperty;
}
private static bool MatchSingleProperty(object pattern, string value)
{
// String match.
if (pattern is string str)
return string.Compare(str, value, StringComparison.OrdinalIgnoreCase) == 0;
// Regex match.
if (pattern is Regex regex)
return regex.IsMatch(value);
return false;
}
private static bool MatchSinglePropertyContains(object pattern, string value)
{
// String match.
if (pattern is string str)
return value.Contains(str, StringComparison.OrdinalIgnoreCase);
return false;
}
private static int GetNumPropertiesIn(InputDeviceDescription description)
{
var count = 0;
if (!string.IsNullOrEmpty(description.interfaceName))
count += 1;
if (!string.IsNullOrEmpty(description.deviceClass))
count += 1;
if (!string.IsNullOrEmpty(description.manufacturer))
count += 1;
if (!string.IsNullOrEmpty(description.product))
count += 1;
if (!string.IsNullOrEmpty(description.version))
count += 1;
if (!string.IsNullOrEmpty(description.capabilities))
count += 1;
return count;
}
/// <summary>
/// Produce a matcher that matches the given device description verbatim.
/// </summary>
/// <param name="deviceDescription">A device description.</param>
/// <returns>A matcher that matches <paramref name="deviceDescription"/> exactly.</returns>
/// <remarks>
/// This method can be used to produce a matcher for an existing device description,
/// e.g. when writing a layout <see cref="InputControlLayout.Builder"/> that produces
/// layouts for devices on the fly.
/// </remarks>
public static InputDeviceMatcher FromDeviceDescription(InputDeviceDescription deviceDescription)
{
var matcher = new InputDeviceMatcher();
if (!string.IsNullOrEmpty(deviceDescription.interfaceName))
matcher = matcher.WithInterface(deviceDescription.interfaceName, false);
if (!string.IsNullOrEmpty(deviceDescription.deviceClass))
matcher = matcher.WithDeviceClass(deviceDescription.deviceClass, false);
if (!string.IsNullOrEmpty(deviceDescription.manufacturer))
matcher = matcher.WithManufacturer(deviceDescription.manufacturer, false);
if (!string.IsNullOrEmpty(deviceDescription.product))
matcher = matcher.WithProduct(deviceDescription.product, false);
if (!string.IsNullOrEmpty(deviceDescription.version))
matcher = matcher.WithVersion(deviceDescription.version, false);
// We don't include capabilities in this conversion.
return matcher;
}
/// <summary>
/// Return a string representation useful for debugging. Lists the
/// <see cref="patterns"/> contained in the matcher.
/// </summary>
/// <returns>A string representation of the matcher.</returns>
public override string ToString()
{
if (empty)
return "<empty>";
var result = string.Empty;
foreach (var pattern in m_Patterns)
{
if (result.Length > 0)
result += $",{pattern.Key}={pattern.Value}";
else
result += $"{pattern.Key}={pattern.Value}";
}
return result;
}
/// <summary>
/// Test whether this matcher is equivalent to the <paramref name="other"/> matcher.
/// </summary>
/// <param name="other">Another device matcher.</param>
/// <returns>True if the two matchers are equivalent.</returns>
/// <remarks>
/// Two matchers are equivalent if they contain the same number of patterns and the
/// same pattern occurs in each of the matchers. Order of the patterns does not
/// matter.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")]
public bool Equals(InputDeviceMatcher other)
{
if (m_Patterns == other.m_Patterns)
return true;
if (m_Patterns == null || other.m_Patterns == null)
return false;
if (m_Patterns.Length != other.m_Patterns.Length)
return false;
// Pattern count matches. Compare pattern by pattern. Order of patterns doesn't matter.
for (var i = 0; i < m_Patterns.Length; ++i)
{
var thisPattern = m_Patterns[i];
var foundPattern = false;
for (var n = 0; n < m_Patterns.Length; ++n)
{
var otherPattern = other.m_Patterns[n];
if (thisPattern.Key != otherPattern.Key)
continue;
if (!thisPattern.Value.Equals(otherPattern.Value))
return false;
foundPattern = true;
break;
}
if (!foundPattern)
return false;
}
return true;
}
/// <summary>
/// Compare this matcher to another.
/// </summary>
/// <param name="obj">A matcher object or <c>null</c>.</param>
/// <returns>True if the matcher is equivalent.</returns>
/// <seealso cref="Equals(InputDeviceMatcher)"/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is InputDeviceMatcher matcher && Equals(matcher);
}
/// <summary>
/// Compare two matchers for equivalence.
/// </summary>
/// <param name="left">First device matcher.</param>
/// <param name="right">Second device matcher.</param>
/// <returns>True if the two matchers are equivalent.</returns>
/// <seealso cref="Equals(InputDeviceMatcher)"/>
public static bool operator==(InputDeviceMatcher left, InputDeviceMatcher right)
{
return left.Equals(right);
}
/// <summary>
/// Compare two matchers for non-equivalence.
/// </summary>
/// <param name="left">First device matcher.</param>
/// <param name="right">Second device matcher.</param>
/// <returns>True if the two matchers are not equivalent.</returns>
/// <seealso cref="Equals(InputDeviceMatcher)"/>
public static bool operator!=(InputDeviceMatcher left, InputDeviceMatcher right)
{
return !(left == right);
}
/// <summary>
/// Compute a hash code for the device matcher.
/// </summary>
/// <returns>A hash code for the matcher.</returns>
public override int GetHashCode()
{
return m_Patterns != null ? m_Patterns.GetHashCode() : 0;
}
private static readonly InternedString kInterfaceKey = new InternedString("interface");
private static readonly InternedString kDeviceClassKey = new InternedString("deviceClass");
private static readonly InternedString kManufacturerKey = new InternedString("manufacturer");
private static readonly InternedString kManufacturerContainsKey = new InternedString("manufacturerContains");
private static readonly InternedString kProductKey = new InternedString("product");
private static readonly InternedString kVersionKey = new InternedString("version");
[Serializable]
internal struct MatcherJson
{
public string @interface;
public string[] interfaces;
public string deviceClass;
public string[] deviceClasses;
public string manufacturer;
public string manufacturerContains;
public string[] manufacturers;
public string product;
public string[] products;
public string version;
public string[] versions;
public Capability[] capabilities;
public struct Capability
{
public string path;
public string value;
}
public static MatcherJson FromMatcher(InputDeviceMatcher matcher)
{
if (matcher.empty)
return new MatcherJson();
var json = new MatcherJson();
foreach (var pattern in matcher.m_Patterns)
{
var key = pattern.Key;
var value = pattern.Value.ToString();
if (key == kInterfaceKey)
{
if (json.@interface == null)
json.@interface = value;
else
ArrayHelpers.Append(ref json.interfaces, value);
}
else if (key == kDeviceClassKey)
{
if (json.deviceClass == null)
json.deviceClass = value;
else
ArrayHelpers.Append(ref json.deviceClasses, value);
}
else if (key == kManufacturerKey)
{
if (json.manufacturer == null)
json.manufacturer = value;
else
ArrayHelpers.Append(ref json.manufacturers, value);
}
else if (key == kProductKey)
{
if (json.product == null)
json.product = value;
else
ArrayHelpers.Append(ref json.products, value);
}
else if (key == kVersionKey)
{
if (json.version == null)
json.version = value;
else
ArrayHelpers.Append(ref json.versions, value);
}
else
{
ArrayHelpers.Append(ref json.capabilities, new Capability {path = key, value = value});
}
}
return json;
}
public InputDeviceMatcher ToMatcher()
{
var matcher = new InputDeviceMatcher();
////TODO: get rid of the piecemeal array allocation and do it in one step
// Interfaces.
if (!string.IsNullOrEmpty(@interface))
matcher = matcher.WithInterface(@interface);
if (interfaces != null)
foreach (var value in interfaces)
matcher = matcher.WithInterface(value);
// Device classes.
if (!string.IsNullOrEmpty(deviceClass))
matcher = matcher.WithDeviceClass(deviceClass);
if (deviceClasses != null)
foreach (var value in deviceClasses)
matcher = matcher.WithDeviceClass(value);
// Manufacturer (string or regex)
if (!string.IsNullOrEmpty(manufacturer))
matcher = matcher.WithManufacturer(manufacturer);
if (manufacturers != null)
foreach (var value in manufacturers)
matcher = matcher.WithManufacturer(value);
// ManufacturerContains (simple string, can occur anywhere in the reported manufacturer string)
if (!string.IsNullOrEmpty(manufacturerContains))
matcher = matcher.WithManufacturerContains(manufacturerContains);
// Product.
if (!string.IsNullOrEmpty(product))
matcher = matcher.WithProduct(product);
if (products != null)
foreach (var value in products)
matcher = matcher.WithProduct(value);
// Version.
if (!string.IsNullOrEmpty(version))
matcher = matcher.WithVersion(version);
if (versions != null)
foreach (var value in versions)
matcher = matcher.WithVersion(value);
// Capabilities.
if (capabilities != null)
foreach (var value in capabilities)
////FIXME: we're turning all values into strings here
matcher = matcher.WithCapability(value.path, value.value);
return matcher;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bb362cb9a8353d44997049a67efe591
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,188 @@
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.LowLevel
{
internal struct JoystickState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('J', 'O', 'Y');
[InputControl(name = "trigger", displayName = "Trigger", layout = "Button", usages = new[] { "PrimaryTrigger", "PrimaryAction", "Submit" }, bit = (int)Button.Trigger)]
public int buttons;
[InputControl(displayName = "Stick", layout = "Stick", usage = "Primary2DMotion", processors = "stickDeadzone")]
public Vector2 stick;
public enum Button
{
// IMPORTANT: Order has to match what is expected by DpadControl.
HatSwitchUp,
HatSwitchDown,
HatSwitchLeft,
HatSwitchRight,
Trigger
}
public FourCC format => kFormat;
}
}
namespace UnityEngine.InputSystem
{
/// <summary>
/// A joystick with an arbitrary number of buttons and axes.
/// </summary>
/// <remarks>
/// Joysticks are somewhat hard to classify as there is little commonality other
/// than that there is one main stick 2D control and at least one button. From the
/// input system perspective, everything that is not a <see cref="Gamepad"/> and
/// that has at least one <see cref="stick"/> and one <see cref="trigger"/> control
/// is considered a candidate for being a joystick.
///
/// Optionally, a joystick may also have the ability to <see cref="twist"/>, i.e.
/// for the stick to rotate around its own axis, and at least one <see cref="hatswitch"/>.
///
/// Note that devices based on Joystick may have many more controls. Joystick
/// itself only defines a minimum required to separate joysticks as a concept
/// from other types of devices.
/// </remarks>
[InputControlLayout(stateType = typeof(JoystickState), isGenericTypeOfDevice = true)]
public class Joystick : InputDevice
{
/// <summary>
/// The primary trigger button of the joystick.
/// </summary>
/// <value>Control representing the primary trigger button.</value>
/// <remarks>
/// This is the <see cref="ButtonControl"/> type control on the joystick
/// that has the <see cref="CommonUsages.PrimaryTrigger"/> usage.
/// </remarks>
public ButtonControl trigger { get; protected set; }
/// <summary>
/// The 2D axis of the stick itself.
/// </summary>
/// <value>Control representing the main joystick axis.</value>
/// <remarks>
/// This is the <see cref="StickControl"/> type control on the joystick
/// that has the <see cref="CommonUsages.Primary2DMotion"/> usage.
/// </remarks>
public StickControl stick { get; protected set; }
/// <summary>
/// An optional control representing the rotation of the stick around its
/// own axis (i.e. side-to-side circular motion). If not supported, will be
/// <c>null</c>.
/// </summary>
/// <value>Control representing the twist motion of the joystick.</value>
/// <remarks>
/// This is the <see cref="AxisControl"/> type control on the joystick
/// that has the <see cref="CommonUsages.Twist"/> usage.
/// </remarks>
public AxisControl twist { get; protected set; }
/// <summary>
/// An optional control representing a four-way "hat switch" on the
/// joystick. If not supported, will be <c>null</c>.
/// </summary>
/// <value>Control representing a hatswitch on the joystick.</value>
/// <remarks>
/// Hat switches are usually thumb-operated four-way switches that operate
/// much like the "d-pad" on a gamepad (see <see cref="Gamepad.dpad"/>).
/// If present, this is the <see cref="Vector2Control"/> type control on the
/// joystick that has the <see cref="CommonUsages.Hatswitch"/> usage.
/// </remarks>
public Vector2Control hatswitch { get; protected set; }
/// <summary>
/// The joystick that was added or used last. Null if there is none.
/// </summary>
/// <value>Joystick that was added or used last.</value>
/// <remarks>
/// See <see cref="InputDevice.MakeCurrent"/> for details about when a device
/// is made current.
/// </remarks>
/// <seealso cref="all"/>
public static Joystick current { get; private set; }
/// <summary>
/// A list of joysticks currently connected to the system.
/// </summary>
/// <value>All currently connected joystick.</value>
/// <remarks>
/// Does not cause GC allocation.
///
/// Do <em>not</em> hold on to the value returned by this getter but rather query it whenever
/// you need it. Whenever the joystick setup changes, the value returned by this getter
/// is invalidated.
/// </remarks>
/// <seealso cref="current"/>
public new static ReadOnlyArray<Joystick> all => new ReadOnlyArray<Joystick>(s_Joysticks, 0, s_JoystickCount);
/// <summary>
/// Called when the joystick has been created but before it is added
/// to the system.
/// </summary>
protected override void FinishSetup()
{
// Mandatory controls.
trigger = GetChildControl<ButtonControl>("{PrimaryTrigger}");
stick = GetChildControl<StickControl>("{Primary2DMotion}");
// Optional controls.
twist = TryGetChildControl<AxisControl>("{Twist}");
hatswitch = TryGetChildControl<Vector2Control>("{Hatswitch}");
base.FinishSetup();
}
/// <summary>
/// Make the joystick the <see cref="current"/> one.
/// </summary>
/// <remarks>
/// This is called automatically by the input system when a device
/// receives input or is added to the system. See <see cref="InputDevice.MakeCurrent"/>
/// for details.
/// </remarks>
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <summary>
/// Called when the joystick is added to the system.
/// </summary>
protected override void OnAdded()
{
ArrayHelpers.AppendWithCapacity(ref s_Joysticks, ref s_JoystickCount, this);
}
/// <summary>
/// Called when the joystick is removed from the system.
/// </summary>
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
// Remove from `all`.
var index = ArrayHelpers.IndexOfReference(s_Joysticks, this, s_JoystickCount);
if (index != -1)
ArrayHelpers.EraseAtWithCapacity(s_Joysticks, ref s_JoystickCount, index);
else
{
Debug.Assert(false,
$"Joystick {this} seems to not have been added but is being removed (joystick list: {string.Join(", ", all)})"); // Put in else to not allocate on normal path.
}
}
private static int s_JoystickCount;
private static Joystick[] s_Joysticks;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e14a578f61014fccab81ed0f1bc21948
timeCreated: 1507753901

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 864a08de8a08473ebf82029d80696449
timeCreated: 1506740159

View File

@@ -0,0 +1,355 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
////TODO: option to allow to constrain mouse input to the screen area (i.e. no input once mouse leaves player window)
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Combine a single pointer with buttons and a scroll wheel.
/// </summary>
// IMPORTANT: State layout must match with MouseInputState in native.
[StructLayout(LayoutKind.Explicit, Size = 30)]
public struct MouseState : IInputStateTypeInfo
{
/// <summary>
/// Memory format identifier for MouseState.
/// </summary>
/// <value>Returns "MOUS".</value>
/// <seealso cref="InputStateBlock.format"/>
public static FourCC Format => new FourCC('M', 'O', 'U', 'S');
/// <summary>
/// Screen-space position of the mouse in pixels.
/// </summary>
/// <value>Position of mouse on screen.</value>
/// <remarks>
/// On Windows, delta originates from RAWINPUT API.
/// Note: This value might not update every frame, particularly if your project is running at a high frame rates. This value might also update at a different time than the <see cref="Pointer.delta"/>. If you need a delta value that correlates with position, you should compute it based on the previous position value.
/// </remarks>
/// <seealso cref="Pointer.position"/>
[InputControl(usage = "Point", dontReset = true)] // Mouse should stay put when we reset devices.
[FieldOffset(0)]
public Vector2 position;
/// <summary>
/// Screen-space motion delta of the mouse in pixels.
/// </summary>
/// <value>Mouse movement.</value>
/// <remarks>
/// On Windows, delta originates from RAWINPUT API.
/// Note: This value might not update every frame, particularly if your project is running at a high frame rates. This value might also update at a different time than the <see cref="Pointer.position"/>. If you need a delta value that correlates with position, you should compute it based on the previous position value.
/// </remarks>
/// <seealso cref="Pointer.delta"/>
[InputControl(usage = "Secondary2DMotion", layout = "Delta")]
[FieldOffset(8)]
public Vector2 delta;
////REVIEW: have half-axis buttons on the scroll axes? (up, down, left, right)
/// <summary>
/// Scroll-wheel delta of the mouse.
/// </summary>
/// <value>Scroll wheel delta.</value>
/// <seealso cref="Mouse.scroll"/>
[InputControl(displayName = "Scroll", layout = "Delta")]
[InputControl(name = "scroll/x", aliases = new[] { "horizontal" }, usage = "ScrollHorizontal", displayName = "Left/Right")]
[InputControl(name = "scroll/y", aliases = new[] { "vertical" }, usage = "ScrollVertical", displayName = "Up/Down", shortDisplayName = "Wheel")]
[FieldOffset(16)]
public Vector2 scroll;
/// <summary>
/// Button mask for which buttons on the mouse are currently pressed.
/// </summary>
/// <value>Button state mask.</value>
/// <seealso cref="MouseButton"/>
/// <seealso cref="Mouse.leftButton"/>
/// <seealso cref="Mouse.middleButton"/>
/// <seealso cref="Mouse.rightButton"/>
/// <seealso cref="Mouse.forwardButton"/>
/// <seealso cref="Mouse.backButton"/>
[InputControl(name = "press", useStateFrom = "leftButton", synthetic = true, usages = new string[0])]
[InputControl(name = "leftButton", layout = "Button", bit = (int)MouseButton.Left, usage = "PrimaryAction", displayName = "Left Button", shortDisplayName = "LMB")]
[InputControl(name = "rightButton", layout = "Button", bit = (int)MouseButton.Right, usage = "SecondaryAction", displayName = "Right Button", shortDisplayName = "RMB")]
[InputControl(name = "middleButton", layout = "Button", bit = (int)MouseButton.Middle, displayName = "Middle Button", shortDisplayName = "MMB")]
[InputControl(name = "forwardButton", layout = "Button", bit = (int)MouseButton.Forward, usage = "Forward", displayName = "Forward")]
[InputControl(name = "backButton", layout = "Button", bit = (int)MouseButton.Back, usage = "Back", displayName = "Back")]
[FieldOffset(24)]
// "Park" all the controls that are common to pointers but aren't use for mice such that they get
// appended to the end of device state where they will always have default values.
////FIXME: InputDeviceBuilder will get fooled and set up an incorrect state layout if we don't force this to VEC2; InputControlLayout will
//// "infer" USHT as the format which will then end up with a layout where two 4 byte float controls are "packed" into a 16bit sized parent;
//// in other words, setting VEC2 here manually should *not* be necessary
[InputControl(name = "pressure", layout = "Axis", usage = "Pressure", offset = InputStateBlock.AutomaticOffset, format = "FLT", sizeInBits = 32)]
[InputControl(name = "radius", layout = "Vector2", usage = "Radius", offset = InputStateBlock.AutomaticOffset, format = "VEC2", sizeInBits = 64)]
[InputControl(name = "pointerId", layout = "Digital", format = "BIT", sizeInBits = 1, offset = InputStateBlock.AutomaticOffset)] // Will stay at 0.
public ushort buttons;
/// <summary>
/// The index of the display that was moused.
/// </summary>
[InputControl(name = "displayIndex", layout = "Integer", displayName = "Display Index")]
[FieldOffset(26)]
public ushort displayIndex;
/// <summary>
/// Number of clicks performed in succession.
/// </summary>
/// <value>Successive click count.</value>
/// <seealso cref="Mouse.clickCount"/>
[InputControl(name = "clickCount", layout = "Integer", displayName = "Click Count", synthetic = true)]
[FieldOffset(28)]
public ushort clickCount;
/// <summary>
/// Set the button mask for the given button.
/// </summary>
/// <param name="button">Button whose state to set.</param>
/// <param name="state">Whether to set the bit on or off.</param>
/// <returns>The same MouseState with the change applied.</returns>
/// <seealso cref="buttons"/>
public MouseState WithButton(MouseButton 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;
}
/// <summary>
/// Returns <see cref="Format"/>.
/// </summary>
/// <seealso cref="InputStateBlock.format"/>
public FourCC format => Format;
}
/// <summary>
/// Button indices for <see cref="MouseState.buttons"/>.
/// </summary>
public enum MouseButton
{
/// <summary>
/// Left mouse button.
/// </summary>
/// <seealso cref="Mouse.leftButton"/>
Left,
/// <summary>
/// Right mouse button.
/// </summary>
/// <seealso cref="Mouse.rightButton"/>
Right,
/// <summary>
/// Middle mouse button.
/// </summary>
/// <seealso cref="Mouse.middleButton"/>
Middle,
/// <summary>
/// Second side button.
/// </summary>
/// <seealso cref="Mouse.forwardButton"/>
Forward,
/// <summary>
/// First side button.
/// </summary>
/// <seealso cref="Mouse.backButton"/>
Back
}
}
namespace UnityEngine.InputSystem
{
/// <summary>
/// An input device representing a mouse.
/// </summary>
/// <remarks>
/// Adds a scroll wheel and a typical 5-button setup with a left, middle, right,
/// forward and backward button.
///
/// To control cursor display and behavior, use <see cref="UnityEngine.Cursor"/>.
/// </remarks>
/// <example>
///
/// <code>
/// using UnityEngine;
/// using UnityEngine.InputSystem;
///
/// public class ExampleScript : MonoBehaviour
/// {
/// void Update()
/// {
/// // If there is a current mouse and the left button was pressed
/// if (Mouse.current != null &amp;&amp; Mouse.current.leftButton.wasPressedThisFrame)
/// {
/// // handle left mouse button being pressed
/// }
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="Pointer"/>
[InputControlLayout(stateType = typeof(MouseState), isGenericTypeOfDevice = true)]
public class Mouse : Pointer, IInputStateCallbackReceiver
{
/// <summary>
/// Control representing horizontal and vertical scroll wheels of a Mouse device.
/// </summary>
/// <remarks>
/// The <c>x</c> component corresponds to the horizontal scroll wheel, the
/// <c>y</c> component to the vertical scroll wheel. Most mice do not have
/// horizontal scroll wheels and will thus only see activity on <c>y</c>.
/// </remarks>
public DeltaControl scroll { get; protected set; }
/// <summary>
/// Control representing left button of a Mouse device.
/// </summary>
public ButtonControl leftButton { get; protected set; }
/// <summary>
/// Control representing middle button of a Mouse device.
/// </summary>
public ButtonControl middleButton { get; protected set; }
/// <summary>
/// Control representing right button of a Mouse device.
/// </summary>
public ButtonControl rightButton { get; protected set; }
/// <summary>
/// Control representing the first side button, often labeled/used as "back", of a Mouse device.
/// </summary>
/// <remarks>
/// On Windows, this corresponds to <c>RI_MOUSE_BUTTON_4</c>.
/// </remarks>
public ButtonControl backButton { get; protected set; }
/// <summary>
/// Control representing the second side button, often labeled/used as "forward", of a Mouse device.
/// </summary>
/// <remarks>
/// On Windows, this corresponds to <c>RI_MOUSE_BUTTON_5</c>.
/// </remarks>
public ButtonControl forwardButton { get; protected set; }
/// <summary>
/// Control representing the number of times any of the mouse buttons has been clicked in succession within
/// the system-defined click time threshold.
/// </summary>
public IntegerControl clickCount { get; protected set; }
/// <summary>
/// The mouse that was added or updated last or null if there is no mouse
/// connected to the system.
/// </summary>
/// <remarks>
/// To set a mouse device as current, use <see cref="Mouse.MakeCurrent"/>.
/// </remarks>
public new static Mouse current { get; private set; }
/// <summary>
/// Called when the mouse becomes the current mouse.
/// </summary>
/// <remarks>
/// This is called automatically by the system when there is input on a connected mouse.
/// </remarks>
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <summary>
/// Called when the mouse is added to the system.
/// </summary>
protected override void OnAdded()
{
base.OnAdded();
if (native && s_PlatformMouseDevice == null)
s_PlatformMouseDevice = this;
}
/// <summary>
/// Called when the device is removed from the system.
/// </summary>
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
internal static Mouse s_PlatformMouseDevice;
////REVIEW: how should we handle this being called from EditorWindow's? (where the editor window space processor will turn coordinates automatically into editor window space)
/// <summary>
/// Move the operating system's mouse cursor by performing a device command in a similar way to <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216%28v=vs.85%29.aspx?f=255&amp;MSPPError=-2147217396" >
/// DeviceIoControl</a> on Windows and <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/ioctl.2.html#//apple_ref/doc/man/2/ioctl" >ioctl</a>
/// on UNIX-like systems.
/// </summary>
/// <param name="position">New position in player window space.</param>
/// <remarks>
/// The <see cref="Pointer.position"/> property will not update immediately but rather will update in the
/// next input update.
/// </remarks>
public void WarpCursorPosition(Vector2 position)
{
var command = WarpMousePositionCommand.Create(position);
ExecuteCommand(ref command);
}
/// <inheritdoc />
protected override void FinishSetup()
{
scroll = GetChildControl<DeltaControl>("scroll");
leftButton = GetChildControl<ButtonControl>("leftButton");
middleButton = GetChildControl<ButtonControl>("middleButton");
rightButton = GetChildControl<ButtonControl>("rightButton");
forwardButton = GetChildControl<ButtonControl>("forwardButton");
backButton = GetChildControl<ButtonControl>("backButton");
displayIndex = GetChildControl<IntegerControl>("displayIndex");
clickCount = GetChildControl<IntegerControl>("clickCount");
base.FinishSetup();
}
/// <summary>
/// Implements <see cref="IInputStateCallbackReceiver.OnNextUpdate"/> for the mouse.
/// </summary>
protected new void OnNextUpdate()
{
base.OnNextUpdate();
InputState.Change(scroll, Vector2.zero);
}
/// <summary>
/// Implements <see cref="IInputStateCallbackReceiver.OnStateEvent"/> for the mouse.
/// </summary>
/// <param name="eventPtr">Pointer to an <see cref="InputEvent"/>. Makes it easier to
/// work with InputEvents and hides the unsafe operations necessary to work with them.
/// </param>
protected new unsafe void OnStateEvent(InputEventPtr eventPtr)
{
scroll.AccumulateValueInEvent(currentStatePtr, eventPtr);
base.OnStateEvent(eventPtr);
}
void IInputStateCallbackReceiver.OnNextUpdate()
{
OnNextUpdate();
}
void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr)
{
OnStateEvent(eventPtr);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d472007a1b9243feb0043606b73288cd
timeCreated: 1507591637

View File

@@ -0,0 +1,388 @@
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;
/// <summary>
/// The index of the display that was touched.
/// </summary>
[InputControl(name = "displayIndex", displayName = "Display Index", layout = "Integer")]
[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");
displayIndex = GetChildControl<IntegerControl>("displayIndex");
base.FinishSetup();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4eac2aa5bc9a4fc1b87308eec2acf8b1
timeCreated: 1508374574

View File

@@ -0,0 +1,263 @@
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Scripting;
////TODO: add capabilities indicating whether pressure is supported
////REVIEW: is there an opportunity to collapse "press" and "pressure" into one? after all, if there's any pressure, isn't the pointer pressed?
////REVIEW: should "displayIndex" be called "windowIndex"? or be part of a better thought-out multi-display API altogether?
////REVIEW: add click and clickCount controls directly to Pointer?
//// (I gave this a look but in my initial try, found it somewhat difficult to add click detection at the Pointer level due
//// to the extra state it involves)
////REVIEW: should we put lock state directly on Pointer?
////REVIEW: should pointer IDs be required to be globally unique across pointing devices?
////REVIEW: should we create new devices instead of using pointer IDs?
////FIXME: pointer deltas in EditorWindows need to be Y *down*
////REVIEW: kill EditorWindowSpace processor and add GetPositionInEditorWindowSpace() and GetDeltaInEditorWindowSpace()?
//// (if we do this, every touch control has to get this, too)
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Default state structure for pointer devices.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct PointerState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('P', 'T', 'R');
uint pointerId;
/// <summary>
/// Position of the pointer in screen space.
/// </summary>
#if UNITY_EDITOR
[InputControl(layout = "Vector2", displayName = "Position", usage = "Point", processors = "AutoWindowSpace", dontReset = true)]
#else
[InputControl(layout = "Vector2", displayName = "Position", usage = "Point", dontReset = true)]
#endif
public Vector2 position;
////REVIEW: if we have Secondary2DMotion on this, seems like this should be normalized
[InputControl(layout = "Delta", displayName = "Delta", usage = "Secondary2DMotion")]
public Vector2 delta;
[InputControl(layout = "Analog", displayName = "Pressure", usage = "Pressure", defaultState = 1f)]
public float pressure;
[InputControl(layout = "Vector2", displayName = "Radius", usage = "Radius")]
public Vector2 radius;
[InputControl(name = "press", displayName = "Press", layout = "Button", format = "BIT", bit = 0)]
public ushort buttons;
[InputControl(name = "displayIndex", layout = "Integer", displayName = "Display Index")]
public ushort displayIndex;
public FourCC format => kFormat;
}
}
namespace UnityEngine.InputSystem
{
/// <summary>
/// Base class for pointer-style devices moving on a 2D screen.
/// </summary>
/// <remarks>
/// This class abstracts over general "pointing" behavior where a pointer is moved across a 2D
/// surface. Operating at the <c>Pointer</c> level allows treating <see>Mouse</see>, <see>Pen</see>,
/// and <see>Touchscreen</see> all as pointers with a set of shared behaviors.
///
/// Note that a pointer may have "multi-point" ability as is the case with multi-touch where
/// multiple touches represent multiple concurrent "pointers". However, for any pointer device
/// with multiple pointers, only one pointer is considered "primary" and drives the pointer
/// controls present on the base class.
/// </remarks>
/// <seealso cref="Mouse"/>
/// <seealso cref="Pen"/>
/// <seealso cref="Touchscreen"/>
[InputControlLayout(stateType = typeof(PointerState), isGenericTypeOfDevice = true)]
public class Pointer : InputDevice, IInputStateCallbackReceiver
{
////REVIEW: shouldn't this be done for every touch position, too?
/// <summary>
/// The current pointer coordinates in window space.
/// </summary>
/// <value>Control representing the current position of the pointer on screen.</value>
/// <remarks>
/// Within player code, the coordinates are in the coordinate space of Unity's <c>Display</c>.
///
/// Within editor code, the coordinates are in the coordinate space of the current <c>EditorWindow</c>
/// This means that if you query the <see cref="Mouse"/> <see cref="position"/> in <c>EditorWindow.OnGUI</c>, for example,
/// the returned 2D vector will be in the coordinate space of your local GUI (same as
/// <c>Event.mousePosition</c>).
/// </remarks>
public Vector2Control position { get; protected set; }
/// <summary>
/// The current window-space motion delta of the pointer.
/// </summary>
/// <value>Control representing the motion delta of the pointer.</value>
/// <remarks>
/// Every time a pointer is moved, it generates a motion delta. This control represents
/// this motion.
///
/// Note that some pointers have the ability to generate motion deltas <em>without</em>
/// actually changing the position of the pointer. This is the case for <see cref="Mouse"/>
/// which even when, for example, bumping up against the edges of the screen or when being
/// locked in place, can generate motion. This means that activity on <c>delta</c> is not
/// necessarily correlated with activity on <see cref="position"/>.
///
/// Deltas have two special behaviors attached to them that makes them quite unique
/// among input controls.
///
/// For one, deltas will automatically reset to <c>(0,0)</c> between frames. If, for example,
/// the current delta value is <c>(12,8)</c>, then after the next <see cref="InputSystem.Update"/>,
/// the delta is automatically set to <c>(0,0)</c>. More precisely, deltas will reset as part
/// of <see cref="InputSystem.onBeforeUpdate"/>. This happens every time regardless of whether
/// there are pending motion events for the pointer or not. But because it happens in
/// <see cref="InputSystem.onBeforeUpdate"/> (that is, <em>before</em> events are processed),
/// subsequent motion deltas are incorporated normally.
///
/// Note that the resetting is visible to <see cref="InputAction"/>s. This means that when
/// binding to a delta control from an action that is not using <see cref="InputActionType.PassThrough"/>,
/// you will see the action getting cancelled at the start of every frame. With a <c>PassThrough</c>
/// actions, you will instead see it perform one extra time with a zero value.
///
/// The other special behavior of deltas is accumulation. When receiving more than one
/// motion update in a frame, deltas will not simply switch from one value to the other
/// but instead accumulate them. For example, if two events are received for a pointer
/// in a frame and one has a motion delta of <c>(1,1)</c> and the other has a motion delta
/// of <c>(2,2)</c>, then once <see cref="InputSystem.Update"/> has finished processing
/// events, the value of the delta control will be <c>(3,3)</c> and not <c>(2,2)</c>.
///
/// Note that just like resetting, accumulation is also visible to <see cref="InputAction"/>s.
/// This means that because the delta control changes value twice, the action will trigger
/// twice but the value when it is triggered the second time will be <c>(3,3)</c> and
/// not <c>(2,2)</c> even though that's the value received from the event.
/// </remarks>
/// <seealso cref="InputControlExtensions.AccumulateValueInEvent"/>
public DeltaControl delta { get; protected set; }
////REVIEW: move this down to only TouchScreen?
/// <summary>
/// Window-space radius of the pointer contact with the surface.
/// </summary>
/// <value>Control representing the horizontal and vertical extents of the pointer contact.</value>
/// <remarks>
/// Usually, only touch input has radius detection.
/// </remarks>
/// <seealso cref="TouchControl.radius"/>
public Vector2Control radius { get; protected set; }
/// <summary>
/// Normalized pressure with which the pointer is currently pressed while in contact with the pointer surface.
/// </summary>
/// <value>Control representing the pressure with which the pointer is pressed down.</value>
/// <remarks>
/// This is only meaningful for pointing devices that support pressure. Mice do not, pens usually do, and touch
/// usually does on mobile platforms.
///
/// Note that it is possible for the value to go above 1 even though it is considered normalized. The reason is
/// that calibration on the system can put the maximum pressure point below the physically supported maximum value.
/// </remarks>
public AxisControl pressure { get; protected set; }
/// <summary>
/// Whether the pointer is pressed down.
/// </summary>
/// <remarks>
/// What this means exactly depends on the nature of the pointer. For mice (<see cref="Mouse"/>), it means
/// that the left button is pressed. For pens (<see cref="Pen"/>), it means that the pen tip is touching
/// the screen/tablet surface. For touchscreens (<see cref="Touchscreen"/>), it means that there is at least
/// one finger touching the screen.
/// </remarks>
public ButtonControl press { get; protected set; }
/// <summary>
/// The index of the display the Pointer is currently on. This is useful for multiple screen setups.
/// This may not be supported on all platforms. When unsupported, this will always produce the index of the primary display i.e. zero.
/// <see href="https://docs.unity3d.com/ScriptReference/Display.html"/>
/// </summary>
public IntegerControl displayIndex { get; protected set; }
/// <summary>
/// The pointer that was added or used last by the user or <c>null</c> if there is no pointer
/// device connected to the system.
/// </summary>
/// <value>Currently active <c>Pointer</c> or <c>null</c>.</value>
public static Pointer current { get; internal set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
position = GetChildControl<Vector2Control>("position");
delta = GetChildControl<DeltaControl>("delta");
radius = GetChildControl<Vector2Control>("radius");
pressure = GetChildControl<AxisControl>("pressure");
press = GetChildControl<ButtonControl>("press");
displayIndex = GetChildControl<IntegerControl>("displayIndex");
base.FinishSetup();
}
/// <summary>
/// Called whenever the input system advances by one frame.
/// </summary>
/// <seealso cref="InputSystem.Update"/>
protected void OnNextUpdate()
{
InputState.Change(delta, Vector2.zero);
}
/// <summary>
/// Called when the pointer receives a state event.
/// </summary>
/// <param name="eventPtr">The input event.</param>
protected unsafe void OnStateEvent(InputEventPtr eventPtr)
{
////FIXME: This stuff makes pointer events too expensive; find a better way.
delta.AccumulateValueInEvent(currentStatePtr, eventPtr);
InputState.Change(this, eventPtr);
}
void IInputStateCallbackReceiver.OnNextUpdate()
{
OnNextUpdate();
}
void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr)
{
OnStateEvent(eventPtr);
}
bool IInputStateCallbackReceiver.GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
{
return false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d806525c0444400588f5a7800297b7b5
timeCreated: 1506745710

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6600059bab4cd4a4ab586e5bb07df47b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40cf321da8ee4fc428f65b23423528d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,877 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated by com.unity.inputsystem:InputLayoutCodeGenerator
// version 1.13.0
// from "Mouse" layout
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
// Suppress warnings from local variables for control references
// that we don't end up using.
#pragma warning disable CS0219
namespace UnityEngine.InputSystem
{
internal partial class FastMouse : UnityEngine.InputSystem.Mouse
{
public const string metadata = "AutoWindowSpace;Vector2;Delta;Button;Axis;Digital;Integer;Mouse;Pointer";
public FastMouse()
{
var builder = this.Setup(30, 10, 2)
.WithName("Mouse")
.WithDisplayName("Mouse")
.WithChildren(0, 14)
.WithLayout(new InternedString("Mouse"))
.WithStateBlock(new InputStateBlock { format = new FourCC(1297044819), sizeInBits = 392 });
var kVector2Layout = new InternedString("Vector2");
var kDeltaLayout = new InternedString("Delta");
var kButtonLayout = new InternedString("Button");
var kAxisLayout = new InternedString("Axis");
var kDigitalLayout = new InternedString("Digital");
var kIntegerLayout = new InternedString("Integer");
// /Mouse/position
var ctrlMouseposition = Initialize_ctrlMouseposition(kVector2Layout, this);
// /Mouse/delta
var ctrlMousedelta = Initialize_ctrlMousedelta(kDeltaLayout, this);
// /Mouse/scroll
var ctrlMousescroll = Initialize_ctrlMousescroll(kDeltaLayout, this);
// /Mouse/press
var ctrlMousepress = Initialize_ctrlMousepress(kButtonLayout, this);
// /Mouse/leftButton
var ctrlMouseleftButton = Initialize_ctrlMouseleftButton(kButtonLayout, this);
// /Mouse/rightButton
var ctrlMouserightButton = Initialize_ctrlMouserightButton(kButtonLayout, this);
// /Mouse/middleButton
var ctrlMousemiddleButton = Initialize_ctrlMousemiddleButton(kButtonLayout, this);
// /Mouse/forwardButton
var ctrlMouseforwardButton = Initialize_ctrlMouseforwardButton(kButtonLayout, this);
// /Mouse/backButton
var ctrlMousebackButton = Initialize_ctrlMousebackButton(kButtonLayout, this);
// /Mouse/pressure
var ctrlMousepressure = Initialize_ctrlMousepressure(kAxisLayout, this);
// /Mouse/radius
var ctrlMouseradius = Initialize_ctrlMouseradius(kVector2Layout, this);
// /Mouse/pointerId
var ctrlMousepointerId = Initialize_ctrlMousepointerId(kDigitalLayout, this);
// /Mouse/displayIndex
var ctrlMousedisplayIndex = Initialize_ctrlMousedisplayIndex(kIntegerLayout, this);
// /Mouse/clickCount
var ctrlMouseclickCount = Initialize_ctrlMouseclickCount(kIntegerLayout, this);
// /Mouse/position/x
var ctrlMousepositionx = Initialize_ctrlMousepositionx(kAxisLayout, ctrlMouseposition);
// /Mouse/position/y
var ctrlMousepositiony = Initialize_ctrlMousepositiony(kAxisLayout, ctrlMouseposition);
// /Mouse/delta/up
var ctrlMousedeltaup = Initialize_ctrlMousedeltaup(kAxisLayout, ctrlMousedelta);
// /Mouse/delta/down
var ctrlMousedeltadown = Initialize_ctrlMousedeltadown(kAxisLayout, ctrlMousedelta);
// /Mouse/delta/left
var ctrlMousedeltaleft = Initialize_ctrlMousedeltaleft(kAxisLayout, ctrlMousedelta);
// /Mouse/delta/right
var ctrlMousedeltaright = Initialize_ctrlMousedeltaright(kAxisLayout, ctrlMousedelta);
// /Mouse/delta/x
var ctrlMousedeltax = Initialize_ctrlMousedeltax(kAxisLayout, ctrlMousedelta);
// /Mouse/delta/y
var ctrlMousedeltay = Initialize_ctrlMousedeltay(kAxisLayout, ctrlMousedelta);
// /Mouse/scroll/up
var ctrlMousescrollup = Initialize_ctrlMousescrollup(kAxisLayout, ctrlMousescroll);
// /Mouse/scroll/down
var ctrlMousescrolldown = Initialize_ctrlMousescrolldown(kAxisLayout, ctrlMousescroll);
// /Mouse/scroll/left
var ctrlMousescrollleft = Initialize_ctrlMousescrollleft(kAxisLayout, ctrlMousescroll);
// /Mouse/scroll/right
var ctrlMousescrollright = Initialize_ctrlMousescrollright(kAxisLayout, ctrlMousescroll);
// /Mouse/scroll/x
var ctrlMousescrollx = Initialize_ctrlMousescrollx(kAxisLayout, ctrlMousescroll);
// /Mouse/scroll/y
var ctrlMousescrolly = Initialize_ctrlMousescrolly(kAxisLayout, ctrlMousescroll);
// /Mouse/radius/x
var ctrlMouseradiusx = Initialize_ctrlMouseradiusx(kAxisLayout, ctrlMouseradius);
// /Mouse/radius/y
var ctrlMouseradiusy = Initialize_ctrlMouseradiusy(kAxisLayout, ctrlMouseradius);
// Usages.
builder.WithControlUsage(0, new InternedString("Point"), ctrlMouseposition);
builder.WithControlUsage(1, new InternedString("Secondary2DMotion"), ctrlMousedelta);
builder.WithControlUsage(2, new InternedString("ScrollHorizontal"), ctrlMousescrollx);
builder.WithControlUsage(3, new InternedString("ScrollVertical"), ctrlMousescrolly);
builder.WithControlUsage(4, new InternedString("PrimaryAction"), ctrlMouseleftButton);
builder.WithControlUsage(5, new InternedString("SecondaryAction"), ctrlMouserightButton);
builder.WithControlUsage(6, new InternedString("Forward"), ctrlMouseforwardButton);
builder.WithControlUsage(7, new InternedString("Back"), ctrlMousebackButton);
builder.WithControlUsage(8, new InternedString("Pressure"), ctrlMousepressure);
builder.WithControlUsage(9, new InternedString("Radius"), ctrlMouseradius);
// Aliases.
builder.WithControlAlias(0, new InternedString("horizontal"));
builder.WithControlAlias(1, new InternedString("vertical"));
// Control getters/arrays.
this.scroll = ctrlMousescroll;
this.leftButton = ctrlMouseleftButton;
this.middleButton = ctrlMousemiddleButton;
this.rightButton = ctrlMouserightButton;
this.backButton = ctrlMousebackButton;
this.forwardButton = ctrlMouseforwardButton;
this.clickCount = ctrlMouseclickCount;
this.position = ctrlMouseposition;
this.delta = ctrlMousedelta;
this.radius = ctrlMouseradius;
this.pressure = ctrlMousepressure;
this.press = ctrlMousepress;
this.displayIndex = ctrlMousedisplayIndex;
ctrlMouseposition.x = ctrlMousepositionx;
ctrlMouseposition.y = ctrlMousepositiony;
ctrlMousedelta.up = ctrlMousedeltaup;
ctrlMousedelta.down = ctrlMousedeltadown;
ctrlMousedelta.left = ctrlMousedeltaleft;
ctrlMousedelta.right = ctrlMousedeltaright;
ctrlMousedelta.x = ctrlMousedeltax;
ctrlMousedelta.y = ctrlMousedeltay;
ctrlMousescroll.up = ctrlMousescrollup;
ctrlMousescroll.down = ctrlMousescrolldown;
ctrlMousescroll.left = ctrlMousescrollleft;
ctrlMousescroll.right = ctrlMousescrollright;
ctrlMousescroll.x = ctrlMousescrollx;
ctrlMousescroll.y = ctrlMousescrolly;
ctrlMouseradius.x = ctrlMouseradiusx;
ctrlMouseradius.y = ctrlMouseradiusy;
// State offset to control index map.
builder.WithStateOffsetToControlIndexMap(new uint[]
{
32782u, 16809999u, 33587218u, 33587219u, 33587220u, 50364432u, 50364433u, 50364437u, 67141656u, 67141657u
, 67141658u, 83918870u, 83918871u, 83918875u, 100664323u, 100664324u, 101188613u, 101712902u, 102237191u, 102761480u
, 109068300u, 117456909u, 134250505u, 167804956u, 184582173u, 201327627u
});
builder.WithControlTree(new byte[]
{
// Control tree nodes as bytes
135, 1, 1, 0, 0, 0, 0, 196, 0, 3, 0, 0, 0, 0, 135, 1, 23, 0, 0, 0, 0, 128, 0, 5, 0, 0, 0, 0, 196, 0
, 11, 0, 0, 0, 0, 64, 0, 7, 0, 0, 0, 1, 128, 0, 9, 0, 3, 0, 1, 32, 0, 255, 255, 1, 0, 1, 64, 0, 255, 255
, 2, 0, 1, 96, 0, 255, 255, 7, 0, 3, 128, 0, 255, 255, 4, 0, 3, 193, 0, 13, 0, 0, 0, 0, 196, 0, 19, 0, 0, 0
, 0, 161, 0, 15, 0, 10, 0, 4, 193, 0, 17, 0, 14, 0, 4, 145, 0, 255, 255, 18, 0, 3, 161, 0, 255, 255, 21, 0, 3, 192
, 0, 255, 255, 0, 0, 0, 193, 0, 255, 255, 24, 0, 2, 195, 0, 21, 0, 0, 0, 0, 196, 0, 255, 255, 28, 0, 1, 194, 0, 255
, 255, 26, 0, 1, 195, 0, 255, 255, 27, 0, 1, 32, 1, 25, 0, 0, 0, 0, 135, 1, 41, 0, 0, 0, 0, 240, 0, 27, 0, 0
, 0, 0, 32, 1, 39, 0, 0, 0, 0, 224, 0, 29, 0, 0, 0, 0, 240, 0, 255, 255, 41, 0, 1, 210, 0, 31, 0, 39, 0, 1
, 224, 0, 255, 255, 40, 0, 1, 203, 0, 33, 0, 0, 0, 0, 210, 0, 255, 255, 0, 0, 0, 200, 0, 35, 0, 0, 0, 0, 203, 0
, 255, 255, 0, 0, 0, 198, 0, 37, 0, 0, 0, 0, 200, 0, 255, 255, 0, 0, 0, 197, 0, 255, 255, 29, 0, 1, 198, 0, 255, 255
, 0, 0, 0, 8, 1, 255, 255, 30, 0, 1, 32, 1, 255, 255, 31, 0, 1, 128, 1, 43, 0, 0, 0, 0, 135, 1, 47, 0, 0, 0
, 0, 80, 1, 255, 255, 32, 0, 2, 128, 1, 45, 0, 34, 0, 2, 104, 1, 255, 255, 36, 0, 1, 128, 1, 255, 255, 37, 0, 1, 132
, 1, 49, 0, 0, 0, 0, 135, 1, 255, 255, 0, 0, 0, 130, 1, 51, 0, 0, 0, 0, 132, 1, 255, 255, 0, 0, 0, 129, 1, 255
, 255, 38, 0, 1, 130, 1, 255, 255, 0, 0, 0
}, new ushort[]
{
// Control tree node indicies
0, 14, 15, 1, 16, 17, 21, 18, 19, 20, 2, 22, 23, 27, 2, 22, 23, 27, 24, 25, 26, 24, 25, 26, 3, 4, 5, 6, 7, 8
, 9, 9, 10, 28, 10, 28, 29, 29, 11, 12, 12, 13
});
builder.Finish();
}
private UnityEngine.InputSystem.Controls.Vector2Control Initialize_ctrlMouseposition(InternedString kVector2Layout, InputControl parent)
{
var ctrlMouseposition = new UnityEngine.InputSystem.Controls.Vector2Control();
ctrlMouseposition.Setup()
.At(this, 0)
.WithParent(parent)
.WithChildren(14, 2)
.WithName("position")
.WithDisplayName("Position")
.WithLayout(kVector2Layout)
.WithUsages(0, 1)
.DontReset(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1447379762),
byteOffset = 0,
bitOffset = 0,
sizeInBits = 64
})
#if UNITY_EDITOR
.WithProcessor<InputProcessor<UnityEngine.Vector2>, UnityEngine.Vector2>(new UnityEngine.InputSystem.Processors.EditorWindowSpaceProcessor())
#endif
.Finish();
return ctrlMouseposition;
}
private UnityEngine.InputSystem.Controls.DeltaControl Initialize_ctrlMousedelta(InternedString kDeltaLayout, InputControl parent)
{
var ctrlMousedelta = new UnityEngine.InputSystem.Controls.DeltaControl();
ctrlMousedelta.Setup()
.At(this, 1)
.WithParent(parent)
.WithChildren(16, 6)
.WithName("delta")
.WithDisplayName("Delta")
.WithLayout(kDeltaLayout)
.WithUsages(1, 1)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1447379762),
byteOffset = 8,
bitOffset = 0,
sizeInBits = 64
})
.Finish();
return ctrlMousedelta;
}
private UnityEngine.InputSystem.Controls.DeltaControl Initialize_ctrlMousescroll(InternedString kDeltaLayout, InputControl parent)
{
var ctrlMousescroll = new UnityEngine.InputSystem.Controls.DeltaControl();
ctrlMousescroll.Setup()
.At(this, 2)
.WithParent(parent)
.WithChildren(22, 6)
.WithName("scroll")
.WithDisplayName("Scroll")
.WithLayout(kDeltaLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1447379762),
byteOffset = 16,
bitOffset = 0,
sizeInBits = 64
})
.Finish();
return ctrlMousescroll;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMousepress(InternedString kButtonLayout, InputControl parent)
{
var ctrlMousepress = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMousepress.Setup()
.At(this, 3)
.WithParent(parent)
.WithName("press")
.WithDisplayName("Press")
.WithLayout(kButtonLayout)
.IsSynthetic(true)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 0,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMousepress;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMouseleftButton(InternedString kButtonLayout, InputControl parent)
{
var ctrlMouseleftButton = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMouseleftButton.Setup()
.At(this, 4)
.WithParent(parent)
.WithName("leftButton")
.WithDisplayName("Left Button")
.WithShortDisplayName("LMB")
.WithLayout(kButtonLayout)
.WithUsages(4, 1)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 0,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMouseleftButton;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMouserightButton(InternedString kButtonLayout, InputControl parent)
{
var ctrlMouserightButton = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMouserightButton.Setup()
.At(this, 5)
.WithParent(parent)
.WithName("rightButton")
.WithDisplayName("Right Button")
.WithShortDisplayName("RMB")
.WithLayout(kButtonLayout)
.WithUsages(5, 1)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 1,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMouserightButton;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMousemiddleButton(InternedString kButtonLayout, InputControl parent)
{
var ctrlMousemiddleButton = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMousemiddleButton.Setup()
.At(this, 6)
.WithParent(parent)
.WithName("middleButton")
.WithDisplayName("Middle Button")
.WithShortDisplayName("MMB")
.WithLayout(kButtonLayout)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 2,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMousemiddleButton;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMouseforwardButton(InternedString kButtonLayout, InputControl parent)
{
var ctrlMouseforwardButton = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMouseforwardButton.Setup()
.At(this, 7)
.WithParent(parent)
.WithName("forwardButton")
.WithDisplayName("Forward")
.WithLayout(kButtonLayout)
.WithUsages(6, 1)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 3,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMouseforwardButton;
}
private UnityEngine.InputSystem.Controls.ButtonControl Initialize_ctrlMousebackButton(InternedString kButtonLayout, InputControl parent)
{
var ctrlMousebackButton = new UnityEngine.InputSystem.Controls.ButtonControl();
ctrlMousebackButton.Setup()
.At(this, 8)
.WithParent(parent)
.WithName("backButton")
.WithDisplayName("Back")
.WithLayout(kButtonLayout)
.WithUsages(7, 1)
.IsButton(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 24,
bitOffset = 4,
sizeInBits = 1
})
.WithMinAndMax(0, 1)
.Finish();
return ctrlMousebackButton;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousepressure(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousepressure = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousepressure.Setup()
.At(this, 9)
.WithParent(parent)
.WithName("pressure")
.WithDisplayName("Pressure")
.WithLayout(kAxisLayout)
.WithUsages(8, 1)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 32,
bitOffset = 0,
sizeInBits = 32
})
.WithDefaultState(1)
.Finish();
return ctrlMousepressure;
}
private UnityEngine.InputSystem.Controls.Vector2Control Initialize_ctrlMouseradius(InternedString kVector2Layout, InputControl parent)
{
var ctrlMouseradius = new UnityEngine.InputSystem.Controls.Vector2Control();
ctrlMouseradius.Setup()
.At(this, 10)
.WithParent(parent)
.WithChildren(28, 2)
.WithName("radius")
.WithDisplayName("Radius")
.WithLayout(kVector2Layout)
.WithUsages(9, 1)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1447379762),
byteOffset = 40,
bitOffset = 0,
sizeInBits = 64
})
.Finish();
return ctrlMouseradius;
}
private UnityEngine.InputSystem.Controls.IntegerControl Initialize_ctrlMousepointerId(InternedString kDigitalLayout, InputControl parent)
{
var ctrlMousepointerId = new UnityEngine.InputSystem.Controls.IntegerControl();
ctrlMousepointerId.Setup()
.At(this, 11)
.WithParent(parent)
.WithName("pointerId")
.WithDisplayName("pointerId")
.WithLayout(kDigitalLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1112101920),
byteOffset = 48,
bitOffset = 0,
sizeInBits = 1
})
.Finish();
return ctrlMousepointerId;
}
private UnityEngine.InputSystem.Controls.IntegerControl Initialize_ctrlMousedisplayIndex(InternedString kIntegerLayout, InputControl parent)
{
var ctrlMousedisplayIndex = new UnityEngine.InputSystem.Controls.IntegerControl();
ctrlMousedisplayIndex.Setup()
.At(this, 12)
.WithParent(parent)
.WithName("displayIndex")
.WithDisplayName("Display Index")
.WithLayout(kIntegerLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1431521364),
byteOffset = 26,
bitOffset = 0,
sizeInBits = 16
})
.Finish();
return ctrlMousedisplayIndex;
}
private UnityEngine.InputSystem.Controls.IntegerControl Initialize_ctrlMouseclickCount(InternedString kIntegerLayout, InputControl parent)
{
var ctrlMouseclickCount = new UnityEngine.InputSystem.Controls.IntegerControl();
ctrlMouseclickCount.Setup()
.At(this, 13)
.WithParent(parent)
.WithName("clickCount")
.WithDisplayName("Click Count")
.WithLayout(kIntegerLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1431521364),
byteOffset = 28,
bitOffset = 0,
sizeInBits = 16
})
.Finish();
return ctrlMouseclickCount;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousepositionx(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousepositionx = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousepositionx.Setup()
.At(this, 14)
.WithParent(parent)
.WithName("x")
.WithDisplayName("Position X")
.WithShortDisplayName("Position X")
.WithLayout(kAxisLayout)
.DontReset(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 0,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousepositionx;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousepositiony(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousepositiony = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousepositiony.Setup()
.At(this, 15)
.WithParent(parent)
.WithName("y")
.WithDisplayName("Position Y")
.WithShortDisplayName("Position Y")
.WithLayout(kAxisLayout)
.DontReset(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 4,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousepositiony;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltaup(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltaup = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMax = 3.402823E+38f };
ctrlMousedeltaup.Setup()
.At(this, 16)
.WithParent(parent)
.WithName("up")
.WithDisplayName("Delta Up")
.WithShortDisplayName("Delta Up")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 12,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltaup;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltadown(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltadown = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMin = -3.402823E+38f, invert = true };
ctrlMousedeltadown.Setup()
.At(this, 17)
.WithParent(parent)
.WithName("down")
.WithDisplayName("Delta Down")
.WithShortDisplayName("Delta Down")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 12,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltadown;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltaleft(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltaleft = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMin = -3.402823E+38f, invert = true };
ctrlMousedeltaleft.Setup()
.At(this, 18)
.WithParent(parent)
.WithName("left")
.WithDisplayName("Delta Left")
.WithShortDisplayName("Delta Left")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 8,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltaleft;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltaright(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltaright = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMax = 3.402823E+38f };
ctrlMousedeltaright.Setup()
.At(this, 19)
.WithParent(parent)
.WithName("right")
.WithDisplayName("Delta Right")
.WithShortDisplayName("Delta Right")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 8,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltaright;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltax(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltax = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousedeltax.Setup()
.At(this, 20)
.WithParent(parent)
.WithName("x")
.WithDisplayName("Delta X")
.WithShortDisplayName("Delta X")
.WithLayout(kAxisLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 8,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltax;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousedeltay(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousedeltay = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousedeltay.Setup()
.At(this, 21)
.WithParent(parent)
.WithName("y")
.WithDisplayName("Delta Y")
.WithShortDisplayName("Delta Y")
.WithLayout(kAxisLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 12,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousedeltay;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrollup(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrollup = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMax = 3.402823E+38f };
ctrlMousescrollup.Setup()
.At(this, 22)
.WithParent(parent)
.WithName("up")
.WithDisplayName("Scroll Up")
.WithShortDisplayName("Scroll Up")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 20,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrollup;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrolldown(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrolldown = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMin = -3.402823E+38f, invert = true };
ctrlMousescrolldown.Setup()
.At(this, 23)
.WithParent(parent)
.WithName("down")
.WithDisplayName("Scroll Down")
.WithShortDisplayName("Scroll Down")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 20,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrolldown;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrollleft(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrollleft = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMin = -3.402823E+38f, invert = true };
ctrlMousescrollleft.Setup()
.At(this, 24)
.WithParent(parent)
.WithName("left")
.WithDisplayName("Scroll Left")
.WithShortDisplayName("Scroll Left")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 16,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrollleft;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrollright(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrollright = new UnityEngine.InputSystem.Controls.AxisControl { clamp = UnityEngine.InputSystem.Controls.AxisControl.Clamp.BeforeNormalize, clampMax = 3.402823E+38f };
ctrlMousescrollright.Setup()
.At(this, 25)
.WithParent(parent)
.WithName("right")
.WithDisplayName("Scroll Right")
.WithShortDisplayName("Scroll Right")
.WithLayout(kAxisLayout)
.IsSynthetic(true)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 16,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrollright;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrollx(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrollx = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousescrollx.Setup()
.At(this, 26)
.WithParent(parent)
.WithName("x")
.WithDisplayName("Scroll Left/Right")
.WithShortDisplayName("Scroll Left/Right")
.WithLayout(kAxisLayout)
.WithUsages(2, 1)
.WithAliases(0, 1)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 16,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrollx;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMousescrolly(InternedString kAxisLayout, InputControl parent)
{
var ctrlMousescrolly = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMousescrolly.Setup()
.At(this, 27)
.WithParent(parent)
.WithName("y")
.WithDisplayName("Scroll Up/Down")
.WithShortDisplayName("Scroll Wheel")
.WithLayout(kAxisLayout)
.WithUsages(3, 1)
.WithAliases(1, 1)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 20,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMousescrolly;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMouseradiusx(InternedString kAxisLayout, InputControl parent)
{
var ctrlMouseradiusx = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMouseradiusx.Setup()
.At(this, 28)
.WithParent(parent)
.WithName("x")
.WithDisplayName("Radius X")
.WithShortDisplayName("Radius X")
.WithLayout(kAxisLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 40,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMouseradiusx;
}
private UnityEngine.InputSystem.Controls.AxisControl Initialize_ctrlMouseradiusy(InternedString kAxisLayout, InputControl parent)
{
var ctrlMouseradiusy = new UnityEngine.InputSystem.Controls.AxisControl();
ctrlMouseradiusy.Setup()
.At(this, 29)
.WithParent(parent)
.WithName("y")
.WithDisplayName("Radius Y")
.WithShortDisplayName("Radius Y")
.WithLayout(kAxisLayout)
.WithStateBlock(new InputStateBlock
{
format = new FourCC(1179407392),
byteOffset = 44,
bitOffset = 0,
sizeInBits = 32
})
.Finish();
return ctrlMouseradiusy;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a390ac9b9af0fb49b702bae14eaed84
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
using UnityEngine.InputSystem.LowLevel;
namespace UnityEngine.InputSystem
{
internal partial class FastMouse : IInputStateCallbackReceiver, IEventMerger
{
protected new void OnNextUpdate()
{
// Changing these separately seems to not result in much of a difference
// compared to just doing an InputState.Change with a complete MouseState.
InputState.Change(delta, Vector2.zero, InputState.currentUpdateType);
InputState.Change(scroll, Vector2.zero, InputState.currentUpdateType);
}
// For FastMouse, we know that our layout is MouseState so we can just go directly
// to memory.
protected new unsafe void OnStateEvent(InputEventPtr eventPtr)
{
if (eventPtr.type != StateEvent.Type)
{
base.OnStateEvent(eventPtr);
return;
}
var stateEvent = StateEvent.FromUnchecked(eventPtr);
if (stateEvent->stateFormat != MouseState.Format)
{
base.OnStateEvent(eventPtr);
return;
}
var newState = *(MouseState*)stateEvent->state;
var stateFromDevice = (MouseState*)((byte*)currentStatePtr + m_StateBlock.byteOffset);
newState.delta += stateFromDevice->delta;
newState.scroll += stateFromDevice->scroll;
InputState.Change(this, ref newState, InputState.currentUpdateType, eventPtr: eventPtr);
}
void IInputStateCallbackReceiver.OnNextUpdate()
{
OnNextUpdate();
}
void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr)
{
OnStateEvent(eventPtr);
}
internal static unsafe bool MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr)
{
if (currentEventPtr.type != StateEvent.Type || nextEventPtr.type != StateEvent.Type)
return false;
var currentEvent = StateEvent.FromUnchecked(currentEventPtr);
var nextEvent = StateEvent.FromUnchecked(nextEventPtr);
if (currentEvent->stateFormat != MouseState.Format || nextEvent->stateFormat != MouseState.Format)
return false;
var currentState = (MouseState*)currentEvent->state;
var nextState = (MouseState*)nextEvent->state;
// if buttons or clickCount changed we need to process it, so don't merge events together
if (currentState->buttons != nextState->buttons || currentState->clickCount != nextState->clickCount)
return false;
nextState->delta += currentState->delta;
nextState->scroll += currentState->scroll;
return true;
}
bool IEventMerger.MergeForward(InputEventPtr currentEventPtr, InputEventPtr nextEventPtr)
{
return MergeForward(currentEventPtr, nextEventPtr);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9c425cd71d0042cbbd24386f4e4b9ec1
timeCreated: 1604771146

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30858d79688750445a07107b96c917fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b3be39d9a3c9496a9a619d6dc185f5f3
timeCreated: 1510272259

View File

@@ -0,0 +1,791 @@
using System;
using System.Linq;
using System.Text;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
////TODO: show remote device IDs in the debugger
////TODO: remote timestamps need to be translated to local timestamps; doesn't make sense for remote events getting
//// processed on the local timeline as is when the originating timeline may be quite different
////TODO: support actions
////TODO: support input users
////TODO: text input events
////TODO: support remoting of device commands
////TODO: Reuse memory allocated for messages instead of allocating separately for each message.
////REVIEW: it seems that the various XXXMsg struct should be public; ATM doesn't seem like working with the message interface is practical
////REVIEW: the namespacing mechanism for layouts which changes base layouts means that layouts can't be played
//// around with on the editor side but will only be changed once they're updated in the player
namespace UnityEngine.InputSystem
{
/// <summary>
/// Makes the activity and data of an InputManager observable in message form.
/// </summary>
/// <remarks>
/// Can act as both the sender and receiver of these message so the flow is fully bidirectional,
/// i.e. the InputManager on either end can mirror its layouts, devices, and events over
/// to the other end. This permits streaming input not just from the player to the editor but
/// also feeding input from the editor back into the player.
///
/// Remoting sits entirely on top of the input system as an optional piece of functionality.
/// In development players and the editor, we enable it automatically but in non-development
/// players it has to be explicitly requested by the user.
///
/// To see devices and input from players in the editor, open the Input Debugger through
/// "Windows >> Input Debugger".
/// </remarks>
/// <seealso cref="InputSystem.remoting"/>
public sealed class InputRemoting : IObservable<InputRemoting.Message>, IObserver<InputRemoting.Message>
{
/// <summary>
/// Enumeration of possible types of messages exchanged between two InputRemoting instances.
/// </summary>
public enum MessageType
{
Connect,
Disconnect,
NewLayout,
NewDevice,
NewEvents,
RemoveDevice,
RemoveLayout, // Not used ATM.
ChangeUsages,
StartSending,
StopSending,
}
/// <summary>
/// A message exchanged between two InputRemoting instances.
/// </summary>
public struct Message
{
/// <summary>
/// For messages coming in, numeric ID of the sender of the message. For messages
/// going out, numeric ID of the targeted receiver of the message.
/// </summary>
public int participantId;
public MessageType type;
public byte[] data;
}
public bool sending
{
get => (m_Flags & Flags.Sending) == Flags.Sending;
private set
{
if (value)
m_Flags |= Flags.Sending;
else
m_Flags &= ~Flags.Sending;
}
}
internal InputRemoting(InputManager manager, bool startSendingOnConnect = false)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
m_LocalManager = manager;
if (startSendingOnConnect)
m_Flags |= Flags.StartSendingOnConnect;
//when listening for newly added layouts, must filter out ones we've added from remote
}
/// <summary>
/// Start sending messages for data and activity in the local input system
/// to observers.
/// </summary>
/// <seealso cref="sending"/>
/// <seealso cref="StopSending"/>
public void StartSending()
{
if (sending)
return;
////TODO: send events in bulk rather than one-by-one
m_LocalManager.onEvent += SendEvent;
m_LocalManager.onDeviceChange += SendDeviceChange;
m_LocalManager.onLayoutChange += SendLayoutChange;
sending = true;
SendInitialMessages();
}
public void StopSending()
{
if (!sending)
return;
m_LocalManager.onEvent -= SendEvent;
m_LocalManager.onDeviceChange -= SendDeviceChange;
m_LocalManager.onLayoutChange -= SendLayoutChange;
sending = false;
}
void IObserver<Message>.OnNext(Message msg)
{
switch (msg.type)
{
case MessageType.Connect:
ConnectMsg.Process(this);
break;
case MessageType.Disconnect:
DisconnectMsg.Process(this, msg);
break;
case MessageType.NewLayout:
NewLayoutMsg.Process(this, msg);
break;
case MessageType.NewDevice:
NewDeviceMsg.Process(this, msg);
break;
case MessageType.NewEvents:
NewEventsMsg.Process(this, msg);
break;
case MessageType.ChangeUsages:
ChangeUsageMsg.Process(this, msg);
break;
case MessageType.RemoveDevice:
RemoveDeviceMsg.Process(this, msg);
break;
case MessageType.StartSending:
StartSendingMsg.Process(this);
break;
case MessageType.StopSending:
StopSendingMsg.Process(this);
break;
}
}
void IObserver<Message>.OnError(Exception error)
{
}
void IObserver<Message>.OnCompleted()
{
}
public IDisposable Subscribe(IObserver<Message> observer)
{
if (observer == null)
throw new ArgumentNullException(nameof(observer));
var subscriber = new Subscriber {owner = this, observer = observer};
ArrayHelpers.Append(ref m_Subscribers, subscriber);
return subscriber;
}
private void SendInitialMessages()
{
SendAllGeneratedLayouts();
SendAllDevices();
}
private void SendAllGeneratedLayouts()
{
foreach (var entry in m_LocalManager.m_Layouts.layoutBuilders)
SendLayout(entry.Key);
}
private void SendLayout(string layoutName)
{
if (m_Subscribers == null)
return;
var message = NewLayoutMsg.Create(this, layoutName);
if (message != null)
Send(message.Value);
}
private void SendAllDevices()
{
var devices = m_LocalManager.devices;
foreach (var device in devices)
SendDevice(device);
}
private void SendDevice(InputDevice device)
{
if (m_Subscribers == null)
return;
// Don't mirror remote devices to other remotes.
if (device.remote)
return;
var newDeviceMessage = NewDeviceMsg.Create(device);
Send(newDeviceMessage);
// Send current state. We do this here in this case as the device
// may have been added some time ago and thus have already received events.
var stateEventMessage = NewEventsMsg.CreateStateEvent(device);
Send(stateEventMessage);
}
private unsafe void SendEvent(InputEventPtr eventPtr, InputDevice device)
{
if (m_Subscribers == null)
return;
////REVIEW: we probably want to have better control over this and allow producing local events
//// against remote devices which *are* indeed sent across the wire
// Don't send events that came in from remote devices.
if (device != null && device.remote)
return;
var message = NewEventsMsg.Create(eventPtr.data, 1);
Send(message);
}
private void SendDeviceChange(InputDevice device, InputDeviceChange change)
{
if (m_Subscribers == null)
return;
// Don't mirror remote devices to other remotes.
if (device.remote)
return;
Message msg;
switch (change)
{
case InputDeviceChange.Added:
msg = NewDeviceMsg.Create(device);
break;
case InputDeviceChange.Removed:
msg = RemoveDeviceMsg.Create(device);
break;
case InputDeviceChange.UsageChanged:
msg = ChangeUsageMsg.Create(device);
break;
////FIXME: This creates a double reset event in case the reset itself happens from a reset event that we are also remoting at the same time.
case InputDeviceChange.SoftReset:
msg = NewEventsMsg.CreateResetEvent(device, false);
break;
case InputDeviceChange.HardReset:
msg = NewEventsMsg.CreateResetEvent(device, true);
break;
default:
return;
}
Send(msg);
}
private void SendLayoutChange(string layout, InputControlLayoutChange change)
{
if (m_Subscribers == null)
return;
// Ignore changes made to layouts that aren't generated. We don't send those over
// the wire.
if (!m_LocalManager.m_Layouts.IsGeneratedLayout(new InternedString(layout)))
return;
// We're only interested in new generated layouts popping up or existing ones
// getting replaced.
if (change != InputControlLayoutChange.Added && change != InputControlLayoutChange.Replaced)
return;
var message = NewLayoutMsg.Create(this, layout);
if (message != null)
Send(message.Value);
}
private void Send(Message msg)
{
foreach (var subscriber in m_Subscribers)
subscriber.observer.OnNext(msg);
}
private int FindOrCreateSenderRecord(int senderId)
{
// Try to find existing.
if (m_Senders != null)
{
var senderCount = m_Senders.Length;
for (var i = 0; i < senderCount; ++i)
if (m_Senders[i].senderId == senderId)
return i;
}
// Create new.
var sender = new RemoteSender
{
senderId = senderId,
};
return ArrayHelpers.Append(ref m_Senders, sender);
}
private static InternedString BuildLayoutNamespace(int senderId)
{
return new InternedString($"Remote::{senderId}");
}
private int FindLocalDeviceId(int remoteDeviceId, int senderIndex)
{
var localDevices = m_Senders[senderIndex].devices;
if (localDevices != null)
{
var numLocalDevices = localDevices.Length;
for (var i = 0; i < numLocalDevices; ++i)
{
if (localDevices[i].remoteId == remoteDeviceId)
return localDevices[i].localId;
}
}
return InputDevice.InvalidDeviceId;
}
private InputDevice TryGetDeviceByRemoteId(int remoteDeviceId, int senderIndex)
{
var localId = FindLocalDeviceId(remoteDeviceId, senderIndex);
return m_LocalManager.TryGetDeviceById(localId);
}
internal InputManager manager => m_LocalManager;
private Flags m_Flags;
private InputManager m_LocalManager; // Input system we mirror input from and to.
private Subscriber[] m_Subscribers; // Receivers we send input to.
private RemoteSender[] m_Senders; // Senders we receive input from.
[Flags]
private enum Flags
{
Sending = 1 << 0,
StartSendingOnConnect = 1 << 1
}
// Data we keep about a unique sender that we receive input data
// from. We keep track of the layouts and devices we added to
// the local system.
[Serializable]
internal struct RemoteSender
{
public int senderId;
public InternedString[] layouts; // Each item is the unqualified name of the layout (without namespace)
public RemoteInputDevice[] devices;
}
[Serializable]
internal struct RemoteInputDevice
{
public int remoteId; // Device ID used by sender.
public int localId; // Device ID used by us in local system.
public InputDeviceDescription description;
}
internal class Subscriber : IDisposable
{
public InputRemoting owner;
public IObserver<Message> observer;
public void Dispose()
{
ArrayHelpers.Erase(ref owner.m_Subscribers, this);
}
}
private static class ConnectMsg
{
public static void Process(InputRemoting receiver)
{
if (receiver.sending)
receiver.SendInitialMessages();
else if ((receiver.m_Flags & Flags.StartSendingOnConnect) == Flags.StartSendingOnConnect)
receiver.StartSending();
}
}
private static class StartSendingMsg
{
public static void Process(InputRemoting receiver)
{
receiver.StartSending();
}
}
private static class StopSendingMsg
{
public static void Process(InputRemoting receiver)
{
receiver.StopSending();
}
}
public void RemoveRemoteDevices(int participantId)
{
var senderIndex = FindOrCreateSenderRecord(participantId);
// Remove devices added by remote.
var devices = m_Senders[senderIndex].devices;
if (devices != null)
{
foreach (var remoteDevice in devices)
{
var device = m_LocalManager.TryGetDeviceById(remoteDevice.localId);
if (device != null)
m_LocalManager.RemoveDevice(device);
}
}
ArrayHelpers.EraseAt(ref m_Senders, senderIndex);
}
private static class DisconnectMsg
{
public static void Process(InputRemoting receiver, Message msg)
{
Debug.Log("DisconnectMsg.Process");
receiver.RemoveRemoteDevices(msg.participantId);
receiver.StopSending();
}
}
// Tell remote input system that there's a new layout.
private static class NewLayoutMsg
{
[Serializable]
public struct Data
{
public string name;
public string layoutJson;
public bool isOverride;
}
public static Message? Create(InputRemoting sender, string layoutName)
{
// Try to load the layout. Ignore the layout if it couldn't
// be loaded.
InputControlLayout layout;
try
{
layout = sender.m_LocalManager.TryLoadControlLayout(new InternedString(layoutName));
if (layout == null)
{
Debug.Log(string.Format(
"Could not find layout '{0}' meant to be sent through remote connection; this should not happen",
layoutName));
return null;
}
}
catch (Exception exception)
{
Debug.Log($"Could not load layout '{layoutName}'; not sending to remote listeners (exception: {exception})");
return null;
}
var data = new Data
{
name = layoutName,
layoutJson = layout.ToJson(),
isOverride = layout.isOverride
};
return new Message
{
type = MessageType.NewLayout,
data = SerializeData(data)
};
}
public static void Process(InputRemoting receiver, Message msg)
{
var data = DeserializeData<Data>(msg.data);
var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
var internedLayoutName = new InternedString(data.name);
receiver.m_LocalManager.RegisterControlLayout(data.layoutJson, data.name, data.isOverride);
ArrayHelpers.Append(ref receiver.m_Senders[senderIndex].layouts, internedLayoutName);
}
}
// Tell remote input system that there's a new device.
private static class NewDeviceMsg
{
[Serializable]
public struct Data
{
public string name;
public string layout;
public int deviceId;
public string[] usages;
public InputDeviceDescription description;
}
public static Message Create(InputDevice device)
{
Debug.Assert(!device.remote, "Device being sent to remotes should be a local device, not a remote one");
var data = new Data
{
name = device.name,
layout = device.layout,
deviceId = device.deviceId,
description = device.description,
usages = device.usages.Select(x => x.ToString()).ToArray()
};
return new Message
{
type = MessageType.NewDevice,
data = SerializeData(data)
};
}
public static void Process(InputRemoting receiver, Message msg)
{
var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
var data = DeserializeData<Data>(msg.data);
// Make sure we haven't already seen the device.
var devices = receiver.m_Senders[senderIndex].devices;
if (devices != null)
{
foreach (var entry in devices)
if (entry.remoteId == data.deviceId)
{
Debug.LogError(string.Format(
"Already received device with id {0} (layout '{1}', description '{3}) from remote {2}",
data.deviceId,
data.layout, msg.participantId, data.description));
return;
}
}
// Create device.
InputDevice device;
try
{
////REVIEW: this gives remote devices names the same way that local devices receive them; should we make remote status visible in the name?
var internedLayoutName = new InternedString(data.layout);
device = receiver.m_LocalManager.AddDevice(internedLayoutName, data.name);
device.m_ParticipantId = msg.participantId;
}
catch (Exception exception)
{
Debug.LogError(
$"Could not create remote device '{data.description}' with layout '{data.layout}' locally (exception: {exception})");
return;
}
////FIXME: Setting this here like so means none of this is visible during onDeviceChange
device.m_Description = data.description;
device.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
foreach (var usage in data.usages)
receiver.m_LocalManager.AddDeviceUsage(device, new InternedString(usage));
// Remember it.
var record = new RemoteInputDevice
{
remoteId = data.deviceId,
localId = device.deviceId,
description = data.description,
};
ArrayHelpers.Append(ref receiver.m_Senders[senderIndex].devices, record);
}
}
// Tell remote system there's new input events.
private static class NewEventsMsg
{
public static unsafe Message CreateResetEvent(InputDevice device, bool isHardReset)
{
var resetEvent = DeviceResetEvent.Create(device.deviceId, isHardReset);
return Create((InputEvent*)UnsafeUtility.AddressOf(ref resetEvent), 1);
}
public static unsafe Message CreateStateEvent(InputDevice device)
{
using (StateEvent.From(device, out var eventPtr))
return Create(eventPtr.data, 1);
}
public static unsafe Message Create(InputEvent* events, int eventCount)
{
// Find total size of event buffer we need.
var totalSize = 0u;
var eventPtr = new InputEventPtr(events);
for (var i = 0; i < eventCount; ++i, eventPtr = eventPtr.Next())
totalSize = totalSize.AlignToMultipleOf(4) + eventPtr.sizeInBytes;
// Copy event data to buffer. Would be nice if we didn't have to do that
// but unfortunately we need a byte[] and can't just pass the 'events' IntPtr
// directly.
var data = new byte[totalSize];
fixed(byte* dataPtr = data)
{
UnsafeUtility.MemCpy(dataPtr, events, totalSize);
}
// Done.
return new Message
{
type = MessageType.NewEvents,
data = data
};
}
public static unsafe void Process(InputRemoting receiver, Message msg)
{
var manager = receiver.m_LocalManager;
fixed(byte* dataPtr = msg.data)
{
var dataEndPtr = new IntPtr(dataPtr + msg.data.Length);
var eventCount = 0;
var eventPtr = new InputEventPtr((InputEvent*)dataPtr);
var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
// Don't use IntPtr.ToInt64() function, on 32 bit systems, the pointer is first converted to Int32 and then casted to Int64
// Thus for big pointer value, you might get a negative value even though the pointer value will be less than Int64.MaxValue
while ((void*)eventPtr.data < dataEndPtr.ToPointer())
{
// Patch up device ID to refer to local device and send event.
var remoteDeviceId = eventPtr.deviceId;
var localDeviceId = receiver.FindLocalDeviceId(remoteDeviceId, senderIndex);
eventPtr.deviceId = localDeviceId;
if (localDeviceId != InputDevice.InvalidDeviceId)
{
////TODO: add API to send events in bulk rather than one by one
manager.QueueEvent(eventPtr);
}
++eventCount;
eventPtr = eventPtr.Next();
}
}
}
}
private static class ChangeUsageMsg
{
[Serializable]
public struct Data
{
public int deviceId;
public string[] usages;
}
public static Message Create(InputDevice device)
{
var data = new Data
{
deviceId = device.deviceId,
usages = device.usages.Select(x => x.ToString()).ToArray()
};
return new Message
{
type = MessageType.ChangeUsages,
data = SerializeData(data)
};
}
public static void Process(InputRemoting receiver, Message msg)
{
var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
var data = DeserializeData<Data>(msg.data);
var device = receiver.TryGetDeviceByRemoteId(data.deviceId, senderIndex);
if (device != null)
{
foreach (var deviceUsage in device.usages)
{
if (!data.usages.Contains(deviceUsage))
receiver.m_LocalManager.RemoveDeviceUsage(device, new InternedString(deviceUsage));
}
foreach (var dataUsage in data.usages)
{
var internedDataUsage = new InternedString(dataUsage);
if (!device.usages.Contains(internedDataUsage))
receiver.m_LocalManager.AddDeviceUsage(device, new InternedString(dataUsage));
}
}
}
}
private static class RemoveDeviceMsg
{
public static Message Create(InputDevice device)
{
return new Message
{
type = MessageType.RemoveDevice,
data = BitConverter.GetBytes(device.deviceId)
};
}
public static void Process(InputRemoting receiver, Message msg)
{
var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
var remoteDeviceId = BitConverter.ToInt32(msg.data, 0);
var device = receiver.TryGetDeviceByRemoteId(remoteDeviceId, senderIndex);
if (device != null)
receiver.m_LocalManager.RemoveDevice(device);
}
}
private static byte[] SerializeData<TData>(TData data)
{
var json = JsonUtility.ToJson(data);
return Encoding.UTF8.GetBytes(json);
}
private static TData DeserializeData<TData>(byte[] data)
{
var json = Encoding.UTF8.GetString(data);
return JsonUtility.FromJson<TData>(json);
}
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// State we want to take across domain reloads. We can only take some of the
// state across. Subscriptions will be lost and have to be manually restored.
[Serializable]
internal struct SerializedState
{
public int senderId;
public RemoteSender[] senders;
// We can't take these across domain reloads but we want to take them across
// InputSystem.Save/Restore.
[NonSerialized] public Subscriber[] subscribers;
}
internal SerializedState SaveState()
{
return new SerializedState
{
senders = m_Senders,
subscribers = m_Subscribers
};
}
internal void RestoreState(SerializedState state, InputManager manager)
{
m_LocalManager = manager;
m_Senders = state.senders;
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f617c1bc34684f738c0d2cb4d0650f35
timeCreated: 1510522987

View File

@@ -0,0 +1,213 @@
using System;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Networking.PlayerConnection;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.InputSystem
{
// Transports input remoting messages from and to players. Can be used to
// make input on either side fully available on the other side. I.e. player
// input can be fully debugged in the editor and editor input can conversely
// be fed into the player.
//
// NOTE: The Unity EditorConnection/PlayerConnection mechanism requires this to
// be a ScriptableObject as it will register every listeners as a persistent
// one.
[Serializable]
internal class RemoteInputPlayerConnection :
#if UNITY_EDITOR
// In the editor, we need to make sure that we get the same instance after domain reloads.
// Otherwise, callbacks we have registered before the reload will no longer be valid, because
// the object instance they point to will not deserialize to a valid object. So we use a
// ScriptableSingleton instance, which fullfills these requirements. In the player, we need to
// use a simple ScriptableObject, as ScriptableSingleton is an editor-only class.
ScriptableSingleton<RemoteInputPlayerConnection>,
#else
ScriptableObject,
#endif
IObserver<InputRemoting.Message>, IObservable<InputRemoting.Message>
{
public static readonly Guid kNewDeviceMsg = new Guid("fcd9651ded40425995dfa6aeb78f1f1c");
public static readonly Guid kNewLayoutMsg = new Guid("fccfec2b7369466d88502a9dd38505f4");
public static readonly Guid kNewEventsMsg = new Guid("53546641df1347bc8aa315278a603586");
public static readonly Guid kRemoveDeviceMsg = new Guid("e5e299b2d9e44255b8990bb71af8922d");
public static readonly Guid kChangeUsagesMsg = new Guid("b9fe706dfc854d7ca109a5e38d7db730");
public static readonly Guid kStartSendingMsg = new Guid("0d58e99045904672b3ef34b8797d23cb");
public static readonly Guid kStopSendingMsg = new Guid("548716b2534a45369ab0c9323fc8b4a8");
public void Bind(IEditorPlayerConnection connection, bool isConnected)
{
if (m_Connection != null)
{
if (m_Connection == connection)
return;
throw new InvalidOperationException("Already bound to an IEditorPlayerConnection");
}
// If there's already connections on the given IEditorPlayerConnection,
// calling RegisterConnection() will invoke the given callback for every
// already existing connection. However, it seems to do so only in the
// editor which is why we do the 'isConnected' dance below.
connection.RegisterConnection(OnConnected);
connection.RegisterDisconnection(OnDisconnected);
connection.Register(kNewDeviceMsg, OnNewDevice);
connection.Register(kNewLayoutMsg, OnNewLayout);
connection.Register(kNewEventsMsg, OnNewEvents);
connection.Register(kRemoveDeviceMsg, OnRemoveDevice);
connection.Register(kChangeUsagesMsg, OnChangeUsages);
connection.Register(kStartSendingMsg, OnStartSending);
connection.Register(kStopSendingMsg, OnStopSending);
m_Connection = connection;
if (isConnected)
OnConnected(0);
}
public IDisposable Subscribe(IObserver<InputRemoting.Message> observer)
{
if (observer == null)
throw new System.ArgumentNullException(nameof(observer));
var subscriber = new Subscriber {owner = this, observer = observer};
ArrayHelpers.Append(ref m_Subscribers, subscriber);
if (m_ConnectedIds != null)
{
foreach (var id in m_ConnectedIds)
observer.OnNext(new InputRemoting.Message { type = InputRemoting.MessageType.Connect, participantId = id });
}
return subscriber;
}
////REVIEW: given that the PlayerConnection will connect to the editor regardless, we end up
//// on this path whether input remoting is enabled or not
private void OnConnected(int id)
{
if (m_ConnectedIds != null && ArrayHelpers.Contains(m_ConnectedIds, id))
return;
ArrayHelpers.Append(ref m_ConnectedIds, id);
SendToSubscribers(InputRemoting.MessageType.Connect, new MessageEventArgs {playerId = id});
}
private void OnDisconnected(int id)
{
if (m_ConnectedIds == null || !ArrayHelpers.Contains(m_ConnectedIds, id))
return;
ArrayHelpers.Erase(ref m_ConnectedIds, id);
SendToSubscribers(InputRemoting.MessageType.Disconnect, new MessageEventArgs {playerId = id});
}
private void OnNewDevice(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.NewDevice, args);
}
private void OnNewLayout(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.NewLayout, args);
}
private void OnNewEvents(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.NewEvents, args);
}
private void OnRemoveDevice(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.RemoveDevice, args);
}
private void OnChangeUsages(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.ChangeUsages, args);
}
private void OnStartSending(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.StartSending, args);
}
private void OnStopSending(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.StopSending, args);
}
private void SendToSubscribers(InputRemoting.MessageType type, MessageEventArgs args)
{
if (m_Subscribers == null)
return;
var msg = new InputRemoting.Message
{
participantId = args.playerId,
type = type,
data = args.data
};
for (var i = 0; i < m_Subscribers.Length; ++i)
m_Subscribers[i].observer.OnNext(msg);
}
void IObserver<InputRemoting.Message>.OnNext(InputRemoting.Message msg)
{
if (m_Connection == null)
return;
////TODO: this should really be sending to a specific player in the editor (can't
//// do that through the IEditorPlayerConnection interface though)
switch (msg.type)
{
case InputRemoting.MessageType.NewDevice:
m_Connection.Send(kNewDeviceMsg, msg.data);
break;
case InputRemoting.MessageType.NewLayout:
m_Connection.Send(kNewLayoutMsg, msg.data);
break;
case InputRemoting.MessageType.NewEvents:
m_Connection.Send(kNewEventsMsg, msg.data);
break;
case InputRemoting.MessageType.ChangeUsages:
m_Connection.Send(kChangeUsagesMsg, msg.data);
break;
case InputRemoting.MessageType.RemoveDevice:
m_Connection.Send(kRemoveDeviceMsg, msg.data);
break;
}
}
void IObserver<InputRemoting.Message>.OnError(Exception error)
{
}
void IObserver<InputRemoting.Message>.OnCompleted()
{
}
[SerializeField] private IEditorPlayerConnection m_Connection;
[NonSerialized] private Subscriber[] m_Subscribers;
[SerializeField] private int[] m_ConnectedIds;
private class Subscriber : IDisposable
{
public RemoteInputPlayerConnection owner;
public IObserver<InputRemoting.Message> observer;
public void Dispose()
{
ArrayHelpers.Erase(ref owner.m_Subscribers, this);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 153c0ac2dec541bc824175008b97615d
timeCreated: 1507603633

View File

@@ -0,0 +1,697 @@
using System;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
////TODO: make the sensors return values through their device
//// (e.g. GravitySensor should itself be an InputControl returning a Vector3 value which is the gravity value)
////REVIEW: Is there a better way than having all the sensor classes?
namespace UnityEngine.InputSystem.LowLevel
{
internal struct AccelerometerState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('A', 'C', 'C', 'L');
[InputControl(displayName = "Acceleration", processors = "CompensateDirection", noisy = true)]
public Vector3 acceleration;
public FourCC format => kFormat;
}
internal struct GyroscopeState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('G', 'Y', 'R', 'O');
[InputControl(displayName = "Angular Velocity", processors = "CompensateDirection", noisy = true)]
public Vector3 angularVelocity;
public FourCC format => kFormat;
}
internal struct GravityState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('G', 'R', 'V', ' ');
[InputControl(displayName = "Gravity", processors = "CompensateDirection", noisy = true)]
public Vector3 gravity;
public FourCC format => kFormat;
}
internal struct AttitudeState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('A', 'T', 'T', 'D');
[InputControl(displayName = "Attitude", processors = "CompensateRotation", noisy = true)]
public Quaternion attitude;
public FourCC format => kFormat;
}
internal struct LinearAccelerationState : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('L', 'A', 'A', 'C');
[InputControl(displayName = "Acceleration", processors = "CompensateDirection", noisy = true)]
public Vector3 acceleration;
public FourCC format => kFormat;
}
}
namespace UnityEngine.InputSystem
{
/// <summary>
/// Base class representing any sensor kind of input device.
/// </summary>
/// <remarks>
/// Sensors represent device environmental sensors, such as <see cref="Accelerometer"/>s, <see cref="Gyroscope"/>s,
/// <see cref="GravitySensor"/>s and others.
///
/// Unlike other devices, sensor devices usually start out in a disabled state in order to reduce energy
/// consumption (i.e. preserve battery life) when the sensors are not in fact used. To enable a specific sensor,
/// call <see cref="InputSystem.EnableDevice"/> on the device instance.
///
/// <example>
/// <code>
/// // Enable the gyroscope.
/// InputSystem.EnableDevice(Gyroscope.current);
/// </code>
/// </example>
///
/// Sensors are usually sampled automatically by the platform at regular intervals. For example, if a sensor
/// is sampled at 50Hz, the platform will queue an event with an update at a rate of roughly 50 events per
/// second. The default sampling rate for a sensor is usually platform-specific. A custom sampling frequency
/// can be set through <see cref="samplingFrequency"/> but be aware that there may be limitations for how fast
/// a given sensor can be sampled.
/// </remarks>
[InputControlLayout(isGenericTypeOfDevice = true)]
public class Sensor : InputDevice
{
/// <summary>
/// The frequency (in Hertz) at which the underlying sensor will be refreshed and at which update
/// events for it will be queued.
/// </summary>
/// <value>Times per second at which the sensor is refreshed.</value>
/// <remarks>
/// Note that when setting sampling frequencies, there may be limits on the range of frequencies
/// supported by the underlying hardware/platform.
///
/// To support querying sampling frequencies, a sensor device must implement <see cref="QuerySamplingFrequencyCommand"/>.
/// To support setting frequencies, it must implemenet <see cref="SetSamplingFrequencyCommand"/>.
/// </remarks>
/// <exception cref="NotSupportedException">Thrown when reading the property and the underlying
/// sensor does not support querying of sampling frequencies.</exception>
public float samplingFrequency
{
get
{
var command = QuerySamplingFrequencyCommand.Create();
if (ExecuteCommand(ref command) >= 0)
return command.frequency;
throw new NotSupportedException($"Device '{this}' does not support querying sampling frequency");
}
set
{
////REVIEW: should this throw NotSupportedException, too?
var command = SetSamplingFrequencyCommand.Create(value);
ExecuteCommand(ref command);
}
}
}
/// <summary>
/// Input device representing an accelerometer sensor.
/// </summary>
/// <remarks>
/// An accelerometer let's you measure the acceleration of a device, and can be useful to control content by moving a device around.
/// Note that the accelerometer will report the acceleration measured on a device both due to moving the device around, and due gravity
/// pulling the device down. You can use <see cref="GravitySensor"/> and <see cref="LinearAccelerationSensor"/> to get decoupled values
/// for these.
///
/// <example>
/// <code>
/// class MyBehavior : MonoBehaviour
/// {
/// protected void OnEnable()
/// {
/// // All sensors start out disabled so they have to manually be enabled first.
/// InputSystem.EnableDevice(Accelerometer.current);
/// }
///
/// protected void OnDisable()
/// {
/// InputSystem.DisableDevice(Accelerometer.current);
/// }
///
/// protected void Update()
/// {
/// var acceleration = Accelerometer.current.acceleration.ReadValue();
/// //...
/// }
/// }
/// </code>
/// </example>
/// </remarks>
[InputControlLayout(stateType = typeof(AccelerometerState))]
public class Accelerometer : Sensor
{
public Vector3Control acceleration { get; protected set; }
/// <summary>
/// The accelerometer that was last added or had activity last.
/// </summary>
/// <value>Current accelerometer or <c>null</c>.</value>
public static Accelerometer current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
acceleration = GetChildControl<Vector3Control>("acceleration");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing a gyroscope sensor.
/// </summary>
/// <remarks>
/// A gyroscope lets you measure the angular velocity of a device, and can be useful to control content by rotating a device.
/// </remarks>
[InputControlLayout(stateType = typeof(GyroscopeState))]
public class Gyroscope : Sensor
{
public Vector3Control angularVelocity { get; protected set; }
/// <summary>
/// The gyroscope that was last added or had activity last.
/// </summary>
/// <value>Current gyroscope or <c>null</c>.</value>
public static Gyroscope current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
angularVelocity = GetChildControl<Vector3Control>("angularVelocity");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing a gravity sensor.
/// </summary>
/// <remarks>
/// A gravity sensor let's you determine the direction of the gravity vector relative to a device, and can be useful to control content by device orientation.
/// This is usually derived from a hardware <see cref="Accelerometer"/>, by subtracting the effect of linear acceleration (see <see cref="LinearAccelerationSensor"/>).
/// </remarks>
[InputControlLayout(stateType = typeof(GravityState), displayName = "Gravity")]
public class GravitySensor : Sensor
{
public Vector3Control gravity { get; protected set; }
/// <summary>
/// The gravity sensor that was last added or had activity last.
/// </summary>
/// <value>Current gravity sensor or <c>null</c>.</value>
public static GravitySensor current { get; private set; }
/// <inheritdoc />
protected override void FinishSetup()
{
gravity = GetChildControl<Vector3Control>("gravity");
base.FinishSetup();
}
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
}
//// REVIEW: Is this name good enough, possible other name RotationVector, here's how Android docs describe it. "A rotation vector sensor reports the orientation of the device relative to the East-North-Up coordinates frame."
//// This is the same as https://docs.unity3d.com/ScriptReference/Gyroscope-attitude.html
/// <summary>
/// Input device representing an attitude sensor.
/// </summary>
/// <remarks>
/// An attitude sensor let's you determine the orientation of a device, and can be useful to control content by rotating a device.
/// </remarks>
[InputControlLayout(stateType = typeof(AttitudeState), displayName = "Attitude")]
public class AttitudeSensor : Sensor
{
public QuaternionControl attitude { get; protected set; }
/// <summary>
/// The attitude sensor that was last added or had activity last.
/// </summary>
/// <value>Current attitude sensor or <c>null</c>.</value>
public static AttitudeSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
attitude = GetChildControl<QuaternionControl>("attitude");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing linear acceleration affecting the device playing the content.
/// </summary>
/// <remarks>
/// An accelerometer let's you measure the acceleration of a device, and can be useful to control content by moving a device around.
/// Linear acceleration is the acceleration of a device unaffected by gravity forces.
/// This is usually derived from a hardware <see cref="Accelerometer"/>, by subtracting the effect of gravity (see <see cref="GravitySensor"/>).
/// </remarks>
[InputControlLayout(stateType = typeof(LinearAccelerationState), displayName = "Linear Acceleration")]
public class LinearAccelerationSensor : Sensor
{
public Vector3Control acceleration { get; protected set; }
/// <summary>
/// The linear acceleration sensor that was last added or had activity last.
/// </summary>
/// <value>Current linear acceleration sensor or <c>null</c>.</value>
public static LinearAccelerationSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
acceleration = GetChildControl<Vector3Control>("acceleration");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the magnetic field affecting the device playing the content.
/// </summary>
[InputControlLayout(displayName = "Magnetic Field")]
public class MagneticFieldSensor : Sensor
{
/// <summary>
/// Strength of the magnetic field reported by the sensor.
/// </summary>
/// <value>Control representing the strength of the magnetic field.</value>
/// <remarks>
/// Values are in micro-Tesla (uT) and measure the ambient magnetic field in the X, Y and Z axis.
/// </remarks>
[InputControl(displayName = "Magnetic Field", noisy = true)]
public Vector3Control magneticField { get; protected set; }
/// <summary>
/// The linear acceleration sensor that was last added or had activity last.
/// </summary>
/// <value>Current linear acceleration sensor or <c>null</c>.</value>
public static MagneticFieldSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
magneticField = GetChildControl<Vector3Control>("magneticField");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the ambient light measured by the device playing the content.
/// </summary>
[InputControlLayout(displayName = "Light")]
public class LightSensor : Sensor
{
/// <summary>
/// Light level in SI lux units.
/// </summary>
[InputControl(displayName = "Light Level", noisy = true)]
public AxisControl lightLevel { get; protected set; }
/// <summary>
/// The light sensor that was last added or had activity last.
/// </summary>
/// <value>Current light sensor or <c>null</c>.</value>
public static LightSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
lightLevel = GetChildControl<AxisControl>("lightLevel");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the atmospheric pressure measured by the device playing the content.
/// </summary>
[InputControlLayout(displayName = "Pressure")]
public class PressureSensor : Sensor
{
/// <summary>
/// Atmospheric pressure in hPa (millibar).
/// </summary>
[InputControl(displayName = "Atmospheric Pressure", noisy = true)]
public AxisControl atmosphericPressure { get; protected set; }
/// <summary>
/// The pressure sensor that was last added or had activity last.
/// </summary>
/// <value>Current pressure sensor or <c>null</c>.</value>
public static PressureSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
atmosphericPressure = GetChildControl<AxisControl>("atmosphericPressure");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the proximity of the device playing the content to the user.
/// </summary>
/// <remarks>
/// The proximity sensor is usually used by phones to determine if the user is holding the phone to their ear or not.
/// </remarks>
[InputControlLayout(displayName = "Proximity")]
public class ProximitySensor : Sensor
{
/// <summary>
/// Proximity sensor distance measured in centimeters.
/// </summary>
[InputControl(displayName = "Distance", noisy = true)]
public AxisControl distance { get; protected set; }
/// <summary>
/// The proximity sensor that was last added or had activity last.
/// </summary>
/// <value>Current proximity sensor or <c>null</c>.</value>
public static ProximitySensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
distance = GetChildControl<AxisControl>("distance");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the ambient air humidity measured by the device playing the content.
/// </summary>
[InputControlLayout(displayName = "Humidity")]
public class HumiditySensor : Sensor
{
/// <summary>
/// Relative ambient air humidity in percent.
/// </summary>
[InputControl(displayName = "Relative Humidity", noisy = true)]
public AxisControl relativeHumidity { get; protected set; }
/// <summary>
/// The humidity sensor that was last added or had activity last.
/// </summary>
/// <value>Current humidity sensor or <c>null</c>.</value>
public static HumiditySensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
relativeHumidity = GetChildControl<AxisControl>("relativeHumidity");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the ambient air temperature measured by the device playing the content.
/// </summary>
[InputControlLayout(displayName = "Ambient Temperature")]
public class AmbientTemperatureSensor : Sensor
{
/// <summary>
/// Temperature in degree Celsius.
/// </summary>
[InputControl(displayName = "Ambient Temperature", noisy = true)]
public AxisControl ambientTemperature { get; protected set; }
/// <summary>
/// The ambient temperature sensor that was last added or had activity last.
/// </summary>
/// <value>Current ambient temperature sensor or <c>null</c>.</value>
public static AmbientTemperatureSensor current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
ambientTemperature = GetChildControl<AxisControl>("ambientTemperature");
base.FinishSetup();
}
}
/// <summary>
/// Input device representing the foot steps taken by the user as measured by the device playing the content.
/// </summary>
/// <remarks>
/// On iOS, access to the step counter must be enabled via <see cref="InputSettings.iOSSettings.motionUsage"/>.
/// </remarks>
[InputControlLayout(displayName = "Step Counter")]
public class StepCounter : Sensor
{
/// <summary>
/// The number of steps taken by the user since the last reboot while activated.
/// </summary>
[InputControl(displayName = "Step Counter", noisy = true)]
public IntegerControl stepCounter { get; protected set; }
/// <summary>
/// The step counter that was last added or had activity last.
/// </summary>
/// <value>Current step counter or <c>null</c>.</value>
public static StepCounter current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
stepCounter = GetChildControl<IntegerControl>("stepCounter");
base.FinishSetup();
}
}
/// <summary>
/// Hinge angle sensor.
/// This sensor is usually available on foldable devices.
/// Note: The step resolution for angle is device dependentent, on Android you can query the sensor resolution by querying device capabilities.
/// </summary>
[InputControlLayout(displayName = "Hinge Angle")]
public class HingeAngle : Sensor
{
/// <summary>
/// The angle in degrees on how much the device is unfolded.
/// </summary>
/// <value>0 means fully folded, 180 means fully unfolded.</value>
public AxisControl angle { get; protected set; }
/// <summary>
/// The hinge angle sensor that was last added or had activity last.
/// </summary>
/// <value>Current hinge angle sensor or <c>null</c>.</value>
public static HingeAngle current { get; private set; }
/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}
/// <inheritdoc />
protected override void FinishSetup()
{
angle = GetChildControl<AxisControl>("angle");
base.FinishSetup();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2064cdc23a1f4ec2a5b9b345e46960ed
timeCreated: 1511082527

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ade00164ed474391ba591b362f77149f
timeCreated: 1507173591

Some files were not shown because too many files have changed in this diff Show More