Initial commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:02:30 +07:00
commit 0a84b9d75e
15481 changed files with 2009655 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
#if UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;
namespace UnityEngine.InputSystem.UI
{
internal class BaseInputOverride : BaseInput
{
public override string compositionString { get; }
}
}
#endif

View File

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

View File

@@ -0,0 +1,21 @@
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;
namespace UnityEngine.InputSystem.UI
{
// AxisEventData has no ToString. But that's the only thing we add so keeping
// it internal.
internal class ExtendedAxisEventData : AxisEventData
{
public ExtendedAxisEventData(EventSystem eventSystem)
: base(eventSystem)
{
}
public override string ToString()
{
return $"MoveDir: {moveDir}\nMoveVector: {moveVector}";
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 408bd3b4ce254207beaa4e0779763fd6
timeCreated: 1617961863

View File

@@ -0,0 +1,261 @@
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using System.Text;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem.UI
{
/// <summary>
/// An extension to <c>PointerEventData</c> which makes additional data about the input event available.
/// </summary>
/// <remarks>
/// Instances of this class are sent instead of <see cref="PointerEventData"/> by <see cref="InputSystemUIInputModule"/>
/// for all pointer-type input.
///
/// The <see cref="PointerEventData.pointerId"/> property will generally correspond to the <see cref="InputDevice.deviceId"/>
/// of <see cref="device"/>. An exception to this are touches as each <see cref="Touchscreen"/> may generate several pointers
/// (one for each active finger).
/// </remarks>
public class ExtendedPointerEventData : PointerEventData
{
public ExtendedPointerEventData(EventSystem eventSystem)
: base(eventSystem)
{
}
/// <summary>
/// The <see cref="InputControl"/> that generated the pointer input.
/// The device associated with this control should be the same as this event's device.
/// </summary>
/// <seealso cref="device"/>
public InputControl control { get; set; }
/// <summary>
/// The <see cref="InputDevice"/> that generated the pointer input.
/// </summary>
/// <seealso cref="Pointer"/>
/// <seealso cref="Touchscreen"/>
/// <seealso cref="Mouse"/>
/// <seealso cref="Pen"/>
public InputDevice device { get; set; }
/// <summary>
/// For <see cref="UIPointerType.Touch"/> type pointer input, this is the touch ID as reported by the
/// <see cref="Touchscreen"/> device.
/// </summary>
/// <remarks>
/// For pointer input that is not coming from touch, this will be 0 (which is not considered a valid touch ID
/// by the input system).
///
/// Note that for touch input, <see cref="PointerEventData.pointerId"/> will be a combination of the
/// device ID of <see cref="device"/> and the touch ID to generate a unique pointer ID even if there
/// are multiple touchscreens.
/// </remarks>
/// <seealso cref="TouchControl.touchId"/>
public int touchId { get; set; }
/// <summary>
/// Type of pointer that generated the input.
/// </summary>
public UIPointerType pointerType { get; set; }
public int uiToolkitPointerId { get; set; }
/// <summary>
/// For <see cref="UIPointerType.Tracked"/> type pointer input, this is the world-space position of
/// the <see cref="TrackedDevice"/>.
/// </summary>
/// <seealso cref="InputSystemUIInputModule.trackedDevicePosition"/>
public Vector3 trackedDevicePosition { get; set; }
/// <summary>
/// For <see cref="UIPointerType.Tracked"/> type pointer input, this is the world-space orientation of
/// the <see cref="TrackedDevice"/>.
/// </summary>
/// <seealso cref="InputSystemUIInputModule.trackedDeviceOrientation"/>
public Quaternion trackedDeviceOrientation { get; set; }
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(base.ToString());
stringBuilder.AppendLine("button: " + button); // Defined in PointerEventData but PointerEventData.ToString() does not include it.
stringBuilder.AppendLine("clickTime: " + clickTime); // Same here.
stringBuilder.AppendLine("clickCount: " + clickCount); // Same here.
stringBuilder.AppendLine("device: " + device);
stringBuilder.AppendLine("pointerType: " + pointerType);
stringBuilder.AppendLine("touchId: " + touchId);
stringBuilder.AppendLine("pressPosition: " + pressPosition);
stringBuilder.AppendLine("trackedDevicePosition: " + trackedDevicePosition);
stringBuilder.AppendLine("trackedDeviceOrientation: " + trackedDeviceOrientation);
#if UNITY_2021_1_OR_NEWER
stringBuilder.AppendLine("pressure" + pressure);
stringBuilder.AppendLine("radius: " + radius);
stringBuilder.AppendLine("azimuthAngle: " + azimuthAngle);
stringBuilder.AppendLine("altitudeAngle: " + altitudeAngle);
stringBuilder.AppendLine("twist: " + twist);
#endif
#if UNITY_2022_3_OR_NEWER
stringBuilder.AppendLine("displayIndex: " + displayIndex);
#endif
return stringBuilder.ToString();
}
internal static int MakePointerIdForTouch(int deviceId, int touchId)
{
unchecked
{
return (deviceId << 24) + touchId;
}
}
internal static int TouchIdFromPointerId(int pointerId)
{
return pointerId & 0xff;
}
////TODO: add pressure and tilt support (probably add after 1.0; probably should have separate actions)
/*
/// <summary>
/// If supported by the input device, this is the pressure level of the pointer contact. This is generally
/// only supported by <see cref="Pen"/> devices as well as by <see cref="Touchscreen"/>s on phones. If not
/// supported, this will be 1.
/// </summary>
/// <seealso cref="Pointer.pressure"/>
public float pressure { get; set; }
/// <summary>
/// If the pointer input is coming from a <see cref="Pen"/>, this is pen's <see cref="Pen.tilt"/>.
/// </summary>
public Vector2 tilt { get; set; }
*/
internal void ReadDeviceState()
{
if (control.parent is Pen pen)
{
uiToolkitPointerId = GetPenPointerId(pen);
#if UNITY_2021_1_OR_NEWER
pressure = pen.pressure.magnitude;
azimuthAngle = (pen.tilt.value.x + 1) * Mathf.PI / 2;
altitudeAngle = (pen.tilt.value.y + 1) * Mathf.PI / 2;
twist = pen.twist.value * Mathf.PI * 2;
#endif
#if UNITY_2022_3_OR_NEWER
displayIndex = pen.displayIndex.ReadValue();
#endif
}
else if (control.parent is TouchControl touchControl)
{
uiToolkitPointerId = GetTouchPointerId(touchControl);
#if UNITY_2021_1_OR_NEWER
pressure = touchControl.pressure.magnitude;
radius = touchControl.radius.value;
#endif
#if UNITY_2022_3_OR_NEWER
displayIndex = touchControl.displayIndex.ReadValue();
#endif
}
else if (control.parent is Touchscreen touchscreen)
{
uiToolkitPointerId = GetTouchPointerId(touchscreen.primaryTouch);
#if UNITY_2021_1_OR_NEWER
pressure = touchscreen.pressure.magnitude;
radius = touchscreen.radius.value;
#endif
#if UNITY_2022_3_OR_NEWER
displayIndex = touchscreen.displayIndex.ReadValue();
#endif
}
else
{
uiToolkitPointerId = UIElements.PointerId.mousePointerId;
}
}
private static int GetPenPointerId(Pen pen)
{
var n = 0;
foreach (var otherDevice in InputSystem.devices)
if (otherDevice is Pen otherPen)
{
if (pen == otherPen)
{
return UIElements.PointerId.penPointerIdBase +
Mathf.Min(n, UIElements.PointerId.penPointerCount - 1);
}
n++;
}
return UIElements.PointerId.penPointerIdBase;
}
private static int GetTouchPointerId(TouchControl touchControl)
{
var i = ((Touchscreen)touchControl.device).touches.IndexOfReference(touchControl);
return UIElements.PointerId.touchPointerIdBase +
Mathf.Clamp(i, 0, UIElements.PointerId.touchPointerCount - 1);
}
}
/// <summary>
/// General type of pointer that generated a <see cref="PointerEventData"/> pointer event.
/// </summary>
public enum UIPointerType
{
None,
/// <summary>
/// A <see cref="Mouse"/> or <see cref="Pen"/> or other general <see cref="Pointer"/>.
/// </summary>
MouseOrPen,
/// <summary>
/// A <see cref="Touchscreen"/>.
/// </summary>
Touch,
/// <summary>
/// A <see cref="TrackedDevice"/>.
/// </summary>
Tracked,
}
/// <summary>
/// Determine how the UI behaves in the presence of multiple pointer devices.
/// </summary>
/// <remarks>
/// While running, an application may, for example, have both a <see cref="Mouse"/> and a <see cref="Touchscreen"/> device
/// and both may end up getting bound to the actions of <see cref="InputSystemUIInputModule"/> and thus both may route
/// input into the UI. When this happens, the pointer behavior decides how the UI input module resolves the ambiguity.
/// </remarks>
public enum UIPointerBehavior
{
/// <summary>
/// Any input that isn't <see cref="Touchscreen"/> or <see cref="TrackedDevice"/> input is
/// treated as a single unified pointer.
///
/// This is the default behavior based on the expectation that mice and pens will generally drive a single on-screen
/// cursor whereas touch and tracked devices have an inherent ability to generate multiple pointers.
///
/// Note that when input from touch or tracked devices is received, the combined pointer for mice and pens (if it exists)
/// will be removed. If it was over UI objects, <c>IPointerExitHandler</c>s will be invoked.
/// </summary>
SingleMouseOrPenButMultiTouchAndTrack,
/// <summary>
/// All input is unified to a single pointer. This means that all input from all pointing devices (<see cref="Mouse"/>,
/// <see cref="Pen"/>, <see cref="Touchscreen"/>, and <see cref="TrackedDevice"/>) is routed into a single pointer
/// instance. There is only one position on screen which can be controlled from any of these devices.
/// </summary>
SingleUnifiedPointer,
/// <summary>
/// Any pointing device, whether it's <see cref="Mouse"/>, <see cref="Pen"/>, <see cref="Touchscreen"/>,
/// or <see cref="TrackedDevice"/> input, is treated as its own independent pointer and arbitrary many
/// such pointers can be active at any one time.
/// </summary>
AllPointersAsIs,
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bca227b3a59246a197db2ff52b862072
timeCreated: 1582733677

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01614664b831546d2ae94a42149d80ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3028dc075ba8c584d9bc7d1e0255e038, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,222 @@
#if UNITY_INPUT_SYSTEM_ENABLE_UI && UNITY_EDITOR
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.EventSystems;
using UnityEngine.InputSystem.Editor;
////TODO: add button to automatically set up gamepad mouse cursor support
namespace UnityEngine.InputSystem.UI.Editor
{
[CustomEditor(typeof(InputSystemUIInputModule))]
[InitializeOnLoad]
internal class InputSystemUIInputModuleEditor : UnityEditor.Editor
{
static InputSystemUIInputModuleEditor()
{
#if UNITY_6000_0_OR_NEWER && ENABLE_INPUT_SYSTEM
InputModuleComponentFactory.SetInputModuleComponentOverride(
go => ObjectFactory.AddComponent<InputSystemUIInputModule>(go));
#endif
}
private static InputActionReference GetActionReferenceFromAssets(InputActionReference[] actions, params string[] actionNames)
{
foreach (var actionName in actionNames)
{
foreach (var action in actions)
{
if (action.action != null && string.Compare(action.action.name, actionName, StringComparison.InvariantCultureIgnoreCase) == 0)
return action;
}
}
return null;
}
private static InputActionReference[] GetAllAssetReferencesFromAssetDatabase(InputActionAsset actions)
{
if (actions == null)
return null;
var path = AssetDatabase.GetAssetPath(actions);
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
return assets.Where(asset => asset is InputActionReference)
.Cast<InputActionReference>()
.OrderBy(x => x.name)
.ToArray();
}
private static readonly string[] s_ActionNames =
{
"Point",
"LeftClick",
"MiddleClick",
"RightClick",
"ScrollWheel",
"Move",
"Submit",
"Cancel",
"TrackedDevicePosition",
"TrackedDeviceOrientation"
};
private static readonly string[] s_ActionNiceNames =
{
"Point",
"Left Click",
"Middle Click",
"Right Click",
"Scroll Wheel",
"Move",
"Submit",
"Cancel",
"Tracked Position",
"Tracked Orientation"
};
private SerializedProperty[] m_ReferenceProperties;
private SerializedProperty m_ActionsAsset;
private InputActionReference[] m_AvailableActionReferencesInAssetDatabase;
private string[] m_AvailableActionsInAssetNames;
private bool m_AdvancedFoldoutState;
private string MakeActionReferenceNameUsableInGenericMenu(string name)
{
// Ugly hack: GenericMenu interprets "/" as a submenu path. But luckily, "/" is not the only slash we have in Unicode.
return name.Replace("/", "\uFF0F");
}
public void OnEnable()
{
var numActions = s_ActionNames.Length;
m_ReferenceProperties = new SerializedProperty[numActions];
for (var i = 0; i < numActions; i++)
m_ReferenceProperties[i] = serializedObject.FindProperty($"m_{s_ActionNames[i]}Action");
m_ActionsAsset = serializedObject.FindProperty("m_ActionsAsset");
m_AvailableActionReferencesInAssetDatabase = GetAllAssetReferencesFromAssetDatabase(m_ActionsAsset.objectReferenceValue as InputActionAsset);
m_AvailableActionsInAssetNames = new[] { "None" }
.Concat(m_AvailableActionReferencesInAssetDatabase?.Select(x => MakeActionReferenceNameUsableInGenericMenu(x.name)) ?? new string[0]).ToArray();
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.InputSystemUIInputModule).Send();
}
public static void ReassignActions(InputSystemUIInputModule module, InputActionAsset action)
{
module.actionsAsset = action;
var assets = GetAllAssetReferencesFromAssetDatabase(action);
if (assets != null)
{
module.point = GetActionReferenceFromAssets(assets, module.point?.action?.name, "Point", "MousePosition", "Mouse Position");
module.leftClick = GetActionReferenceFromAssets(assets, module.leftClick?.action?.name, "Click", "LeftClick", "Left Click");
module.rightClick = GetActionReferenceFromAssets(assets, module.rightClick?.action?.name, "RightClick", "Right Click", "ContextClick", "Context Click", "ContextMenu", "Context Menu");
module.middleClick = GetActionReferenceFromAssets(assets, module.middleClick?.action?.name, "MiddleClick", "Middle Click");
module.scrollWheel = GetActionReferenceFromAssets(assets, module.scrollWheel?.action?.name, "ScrollWheel", "Scroll Wheel", "Scroll", "Wheel");
module.move = GetActionReferenceFromAssets(assets, module.move?.action?.name, "Navigate", "Move");
module.submit = GetActionReferenceFromAssets(assets, module.submit?.action?.name, "Submit");
module.cancel = GetActionReferenceFromAssets(assets, module.cancel?.action?.name, "Cancel", "Esc", "Escape");
module.trackedDevicePosition = GetActionReferenceFromAssets(assets, module.trackedDevicePosition?.action?.name, "TrackedDevicePosition", "Position");
module.trackedDeviceOrientation = GetActionReferenceFromAssets(assets, module.trackedDeviceOrientation?.action?.name, "TrackedDeviceOrientation", "Orientation");
}
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ActionsAsset);
if (EditorGUI.EndChangeCheck())
{
var actions = m_ActionsAsset.objectReferenceValue as InputActionAsset;
if (actions != null)
{
serializedObject.ApplyModifiedProperties();
ReassignActions(target as InputSystemUIInputModule, actions);
serializedObject.Update();
}
// reinitialize action types
OnEnable();
}
var numActions = s_ActionNames.Length;
if ((m_AvailableActionReferencesInAssetDatabase != null && m_AvailableActionReferencesInAssetDatabase.Length > 0) || m_ActionsAsset.objectReferenceValue == null)
{
for (var i = 0; i < numActions; i++)
{
// find the input action reference from the asset that matches the input action reference from the
// InputSystemUIInputModule that is currently selected. Note we can't use reference equality of the
// two InputActionReference objects here because in ReassignActions above, we create new instances
// every time it runs.
var index = IndexOfInputActionInAsset(
((InputActionReference)m_ReferenceProperties[i]?.objectReferenceValue)?.action);
EditorGUI.BeginChangeCheck();
index = EditorGUILayout.Popup(s_ActionNiceNames[i], index, m_AvailableActionsInAssetNames);
if (EditorGUI.EndChangeCheck())
m_ReferenceProperties[i].objectReferenceValue =
index > 0 ? m_AvailableActionReferencesInAssetDatabase[index - 1] : null;
}
}
else
{
// Somehow we have an asset but no asset references from the database, pull out references manually and show them in read only UI
EditorGUILayout.HelpBox("Showing fields as read-only because current action asset seems to be created by a script and assigned programmatically.", MessageType.Info);
EditorGUI.BeginDisabledGroup(true);
for (var i = 0; i < numActions; i++)
{
var retrievedName = "None";
if (m_ReferenceProperties[i].objectReferenceValue != null &&
(m_ReferenceProperties[i].objectReferenceValue is InputActionReference reference))
retrievedName = MakeActionReferenceNameUsableInGenericMenu(reference.ToDisplayName());
EditorGUILayout.Popup(s_ActionNiceNames[i], 0, new[] {retrievedName});
}
EditorGUI.EndDisabledGroup();
}
m_AdvancedFoldoutState = EditorGUILayout.Foldout(m_AdvancedFoldoutState, new GUIContent("Advanced"), true);
if (m_AdvancedFoldoutState)
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_CursorLockBehavior"),
EditorGUIUtility.TrTextContent("Cursor Lock Behavior",
$"Controls the origin point of UI raycasts when the cursor is locked. {InputSystemUIInputModule.CursorLockBehavior.OutsideScreen} " +
$"is the default behavior and will force the raycast to miss all objects. {InputSystemUIInputModule.CursorLockBehavior.ScreenCenter} " +
$"will cast the ray from the center of the screen."));
if (GUI.changed)
serializedObject.ApplyModifiedProperties();
}
private int IndexOfInputActionInAsset(InputAction inputAction)
{
// return 0 instead of -1 here because the zero-th index refers to the 'None' binding.
if (inputAction == null)
return 0;
if (m_AvailableActionReferencesInAssetDatabase == null)
return 0;
var index = 0;
for (var j = 0; j < m_AvailableActionReferencesInAssetDatabase.Length; j++)
{
if (m_AvailableActionReferencesInAssetDatabase[j].action != null &&
m_AvailableActionReferencesInAssetDatabase[j].action == inputAction)
{
index = j + 1;
break;
}
}
return index;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,74 @@
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;
namespace UnityEngine.InputSystem.UI
{
/// <summary>
/// A modified EventSystem class, which allows multiple players to have their own instances of a UI,
/// each with it's own selection.
/// </summary>
/// <remarks>
/// You can use the <see cref="playerRoot"/> property to specify a part of the hierarchy belonging to the current player.
/// Mouse selection will ignore any game objects not within this hierarchy, and all other navigation, using keyboard or
/// gamepad for example, will be constrained to game objects under that hierarchy.
/// </remarks>
[HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#multiplayer-uis")]
public class MultiplayerEventSystem : EventSystem
{
[Tooltip("If set, only process mouse and navigation events for any game objects which are children of this game object.")]
[SerializeField] private GameObject m_PlayerRoot;
/// <summary>
/// The root object of the UI hierarchy that belongs to the given player.
/// </summary>
/// <remarks>
/// This can either be an entire <c>Canvas</c> or just part of the hierarchy of
/// a specific <c>Canvas</c>.
/// </remarks>
public GameObject playerRoot
{
get => m_PlayerRoot;
set
{
m_PlayerRoot = value;
InitializePlayerRoot();
}
}
protected override void OnEnable()
{
base.OnEnable();
InitializePlayerRoot();
}
protected override void OnDisable()
{
base.OnDisable();
}
private void InitializePlayerRoot()
{
if (m_PlayerRoot == null) return;
var inputModule = GetComponent<InputSystemUIInputModule>();
if (inputModule != null)
inputModule.localMultiPlayerRoot = m_PlayerRoot;
}
protected override void Update()
{
var originalCurrent = current;
current = this; // in order to avoid reimplementing half of the EventSystem class, just temporarily assign this EventSystem to be the globally current one
try
{
base.Update();
}
finally
{
current = originalCurrent;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b16c6f78230fa4964a222622d8aae332
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 6a237171e2346e3458db37eb0f90d42b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
#if UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;
namespace UnityEngine.InputSystem.UI
{
internal struct NavigationModel
{
public Vector2 move;
public int consecutiveMoveCount;
public MoveDirection lastMoveDirection;
public float lastMoveTime;
public AxisEventData eventData;
public void Reset()
{
move = Vector2.zero;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,305 @@
#if UNITY_INPUT_SYSTEM_ENABLE_UI
using UnityEngine.EventSystems;
namespace UnityEngine.InputSystem.UI
{
// A pointer is identified by a single unique integer ID. It has an associated position and the ability to press
// on up to three buttons. It can also scroll.
//
// There's a single ExtendedPointerEventData instance allocated for the pointer which is used to retain the pointer's
// event state. As part of that state is specific to button presses, each button retains a partial copy of press-specific
// event information.
//
// A pointer can operate in 2D (mouse, pen, touch) or 3D (tracked). For 3D, screen-space 2D positions are derived
// via raycasts based on world-space position and orientation.
internal struct PointerModel
{
public bool changedThisFrame;
public UIPointerType pointerType => eventData.pointerType;
public Vector2 screenPosition
{
get => m_ScreenPosition;
set
{
if (m_ScreenPosition != value)
{
m_ScreenPosition = value;
changedThisFrame = true;
}
}
}
public Vector3 worldPosition
{
get => m_WorldPosition;
set
{
if (m_WorldPosition != value)
{
m_WorldPosition = value;
changedThisFrame = true;
}
}
}
public Quaternion worldOrientation
{
get => m_WorldOrientation;
set
{
if (m_WorldOrientation != value)
{
m_WorldOrientation = value;
changedThisFrame = true;
}
}
}
public Vector2 scrollDelta
{
get => m_ScrollDelta;
set
{
if (m_ScrollDelta != value)
{
changedThisFrame = true;
m_ScrollDelta = value;
}
}
}
public float pressure
{
get => m_Pressure;
set
{
if (m_Pressure != value)
{
changedThisFrame = true;
m_Pressure = value;
}
}
}
public float azimuthAngle
{
get => m_AzimuthAngle;
set
{
if (m_AzimuthAngle != value)
{
changedThisFrame = true;
m_AzimuthAngle = value;
}
}
}
public float altitudeAngle
{
get => m_AltitudeAngle;
set
{
if (m_AltitudeAngle != value)
{
changedThisFrame = true;
m_AltitudeAngle = value;
}
}
}
public float twist
{
get => m_Twist;
set
{
if (m_Twist != value)
{
changedThisFrame = true;
m_Twist = value;
}
}
}
public Vector2 radius
{
get => m_Radius;
set
{
if (m_Radius != value)
{
changedThisFrame = true;
m_Radius = value;
}
}
}
public ButtonState leftButton;
public ButtonState rightButton;
public ButtonState middleButton;
public ExtendedPointerEventData eventData;
public PointerModel(ExtendedPointerEventData eventData)
{
this.eventData = eventData;
changedThisFrame = false;
leftButton = default; leftButton.OnEndFrame();
rightButton = default; rightButton.OnEndFrame();
middleButton = default; middleButton.OnEndFrame();
m_ScreenPosition = default;
m_ScrollDelta = default;
m_WorldOrientation = default;
m_WorldPosition = default;
m_Pressure = default;
m_AzimuthAngle = default;
m_AltitudeAngle = default;
m_Twist = default;
m_Radius = default;
}
public void OnFrameFinished()
{
changedThisFrame = false;
scrollDelta = default;
leftButton.OnEndFrame();
rightButton.OnEndFrame();
middleButton.OnEndFrame();
}
private Vector2 m_ScreenPosition;
private Vector2 m_ScrollDelta;
private Vector3 m_WorldPosition;
private Quaternion m_WorldOrientation;
private float m_Pressure;
private float m_AzimuthAngle;
private float m_AltitudeAngle;
private float m_Twist;
private Vector2 m_Radius;
public void CopyTouchOrPenStateFrom(PointerEventData eventData)
{
#if UNITY_2021_1_OR_NEWER
pressure = eventData.pressure;
azimuthAngle = eventData.azimuthAngle;
altitudeAngle = eventData.altitudeAngle;
twist = eventData.twist;
radius = eventData.radius;
#endif
}
// State related to pressing and releasing individual bodies. Retains those parts of
// PointerInputEvent that are specific to presses and releases.
public struct ButtonState
{
private bool m_IsPressed;
private PointerEventData.FramePressState m_FramePressState;
private float m_PressTime;
public bool isPressed
{
get => m_IsPressed;
set
{
if (m_IsPressed != value)
{
m_IsPressed = value;
if (m_FramePressState == PointerEventData.FramePressState.NotChanged && value)
m_FramePressState = PointerEventData.FramePressState.Pressed;
else if (m_FramePressState == PointerEventData.FramePressState.NotChanged && !value)
m_FramePressState = PointerEventData.FramePressState.Released;
else if (m_FramePressState == PointerEventData.FramePressState.Pressed && !value)
m_FramePressState = PointerEventData.FramePressState.PressedAndReleased;
}
}
}
/// <summary>
/// When we "release" a button other than through user interaction (e.g. through focus switching),
/// we don't want this to count as an actual release that ends up clicking. This flag will cause
/// generated events to have <c>eligibleForClick</c> to be false.
/// </summary>
public bool ignoreNextClick
{
get => m_IgnoreNextClick;
set => m_IgnoreNextClick = value;
}
public float pressTime
{
get => m_PressTime;
set => m_PressTime = value;
}
public bool clickedOnSameGameObject
{
get => m_ClickedOnSameGameObject;
set => m_ClickedOnSameGameObject = value;
}
public bool wasPressedThisFrame => m_FramePressState == PointerEventData.FramePressState.Pressed ||
m_FramePressState == PointerEventData.FramePressState.PressedAndReleased;
public bool wasReleasedThisFrame => m_FramePressState == PointerEventData.FramePressState.Released ||
m_FramePressState == PointerEventData.FramePressState.PressedAndReleased;
private RaycastResult m_PressRaycast;
private GameObject m_PressObject;
private GameObject m_RawPressObject;
private GameObject m_LastPressObject;
private GameObject m_DragObject;
private Vector2 m_PressPosition;
private float m_ClickTime; // On Time.unscaledTime timeline, NOT input event time.
private int m_ClickCount;
private bool m_Dragging;
private bool m_ClickedOnSameGameObject;
private bool m_IgnoreNextClick;
public void CopyPressStateTo(PointerEventData eventData)
{
eventData.pointerPressRaycast = m_PressRaycast;
eventData.pressPosition = m_PressPosition;
eventData.clickCount = m_ClickCount;
eventData.clickTime = m_ClickTime;
// We can't set lastPress directly. Old input module uses three different event instances, one for each
// button. We share one instance and just switch press states. Set pointerPress twice to get the lastPress
// we need.
//
// NOTE: This does *NOT* quite work as stated in the docs. pointerPress is nulled out on button release which
// will set lastPress as a side-effect. This means that lastPress will only be non-null while no press is
// going on and will *NOT* refer to the last pressed object when a new object has been pressed on.
eventData.pointerPress = m_LastPressObject;
eventData.pointerPress = m_PressObject;
eventData.rawPointerPress = m_RawPressObject;
eventData.pointerDrag = m_DragObject;
eventData.dragging = m_Dragging;
if (ignoreNextClick)
eventData.eligibleForClick = false;
}
public void CopyPressStateFrom(PointerEventData eventData)
{
m_PressRaycast = eventData.pointerPressRaycast;
m_PressObject = eventData.pointerPress;
m_RawPressObject = eventData.rawPointerPress;
m_LastPressObject = eventData.lastPress;
m_PressPosition = eventData.pressPosition;
m_ClickTime = eventData.clickTime;
m_ClickCount = eventData.clickCount;
m_DragObject = eventData.pointerDrag;
m_Dragging = eventData.dragging;
}
public void OnEndFrame()
{
m_FramePressState = PointerEventData.FramePressState.NotChanged;
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,57 @@
#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_ENABLE_UI
using System;
using UnityEditor;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Editor;
namespace UnityEngine.InputSystem.UI.Editor
{
// The only purpose of the Input System suppying a custom editor for the UI StandaloneInputModule is to guide users to using
// the Input System's InputSystemUIInputModule instead.
[CustomEditor(typeof(StandaloneInputModule))]
internal class StandaloneInputModuleModuleEditor : UnityEditor.Editor
{
SerializedProperty enableNativePlatformBackendsForNewInputSystem;
SerializedProperty disableOldInputManagerSupport;
public void OnEnable()
{
var allPlayerSettings = Resources.FindObjectsOfTypeAll<PlayerSettings>();
if (allPlayerSettings.Length > 0)
{
var playerSettings = Resources.FindObjectsOfTypeAll<PlayerSettings>()[0];
var so = new SerializedObject(playerSettings);
enableNativePlatformBackendsForNewInputSystem = so.FindProperty("enableNativePlatformBackendsForNewInputSystem");
disableOldInputManagerSupport = so.FindProperty("disableOldInputManagerSupport");
}
}
public override void OnInspectorGUI()
{
// We assume that if these properties don't exist (ie are null), then that's because the new Input System has become the default.
if (enableNativePlatformBackendsForNewInputSystem == null || enableNativePlatformBackendsForNewInputSystem.boolValue)
{
if (disableOldInputManagerSupport == null || disableOldInputManagerSupport.boolValue)
EditorGUILayout.HelpBox("You are using StandaloneInputModule, which uses the old InputManager. You are using the new InputSystem, and have the old InputManager disabled. StandaloneInputModule will not work. Click the button below to replace this component with a InputSystemUIInputModule, which uses the new InputSystem.", MessageType.Error);
else
EditorGUILayout.HelpBox("You are using StandaloneInputModule, which uses the old InputManager. You also have the new InputSystem enabled in your project. Click the button below to replace this component with a InputSystemUIInputModule, which uses the new InputSystem (recommended).", MessageType.Info);
if (GUILayout.Button("Replace with InputSystemUIInputModule"))
{
var go = ((StandaloneInputModule)target).gameObject;
Undo.DestroyObjectImmediate(target);
Undo.AddComponent<InputSystemUIInputModule>(go);
return;
}
GUILayout.Space(10);
}
base.OnInspectorGUI();
}
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.StandaloneInputModule).Send();
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,274 @@
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace UnityEngine.InputSystem.UI
{
/// <summary>
/// Raycasting implementation for use with <see cref="TrackedDevice"/>s.
/// </summary>
/// <remarks>
/// This component needs to be added alongside the <c>Canvas</c> component. Usually, raycasting is
/// performed by the <c>GraphicRaycaster</c> component found there but for 3D raycasting necessary for
/// tracked devices, this component is required.
/// </remarks>
[AddComponentMenu("Event/Tracked Device Raycaster")]
[RequireComponent(typeof(Canvas))]
public class TrackedDeviceRaycaster : BaseRaycaster
{
private struct RaycastHitData
{
public RaycastHitData(Graphic graphic, Vector3 worldHitPosition, Vector2 screenPosition, float distance)
{
this.graphic = graphic;
this.worldHitPosition = worldHitPosition;
this.screenPosition = screenPosition;
this.distance = distance;
}
public Graphic graphic { get; }
public Vector3 worldHitPosition { get; }
public Vector2 screenPosition { get; }
public float distance { get; }
}
public override Camera eventCamera
{
get
{
var myCanvas = canvas;
return myCanvas != null ? myCanvas.worldCamera : null;
}
}
public LayerMask blockingMask
{
get => m_BlockingMask;
set => m_BlockingMask = value;
}
public bool checkFor3DOcclusion
{
get => m_CheckFor3DOcclusion;
set => m_CheckFor3DOcclusion = value;
}
public bool checkFor2DOcclusion
{
get => m_CheckFor2DOcclusion;
set => m_CheckFor2DOcclusion = value;
}
public bool ignoreReversedGraphics
{
get => m_IgnoreReversedGraphics;
set => m_IgnoreReversedGraphics = value;
}
public float maxDistance
{
get => m_MaxDistance;
set => m_MaxDistance = value;
}
protected override void OnEnable()
{
base.OnEnable();
s_Instances.AppendWithCapacity(this);
}
protected override void OnDisable()
{
var index = s_Instances.IndexOfReference(this);
if (index != -1)
s_Instances.RemoveAtByMovingTailWithCapacity(index);
base.OnDisable();
}
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked)
PerformRaycast(trackedEventData, resultAppendList);
}
// Cached instances for raycasts hits to minimize GC.
[NonSerialized] private List<RaycastHitData> m_RaycastResultsCache = new List<RaycastHitData>();
internal void PerformRaycast(ExtendedPointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (canvas == null)
return;
if (eventCamera == null)
return;
var ray = new Ray(eventData.trackedDevicePosition, eventData.trackedDeviceOrientation * Vector3.forward);
var hitDistance = m_MaxDistance;
#if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS
if (m_CheckFor3DOcclusion)
{
if (Physics.Raycast(ray, out var hit, maxDistance: hitDistance, layerMask: m_BlockingMask))
hitDistance = hit.distance;
}
#endif
#if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS2D
if (m_CheckFor2DOcclusion)
{
var raycastDistance = hitDistance;
var hits = Physics2D.GetRayIntersection(ray, raycastDistance, m_BlockingMask);
if (hits.collider != null)
hitDistance = hits.distance;
}
#endif
m_RaycastResultsCache.Clear();
SortedRaycastGraphics(canvas, ray, m_RaycastResultsCache);
// Now that we have a list of sorted hits, process any extra settings and filters.
for (var i = 0; i < m_RaycastResultsCache.Count; i++)
{
var validHit = true;
var hitData = m_RaycastResultsCache[i];
var go = hitData.graphic.gameObject;
if (m_IgnoreReversedGraphics)
{
var forward = ray.direction;
var goDirection = go.transform.rotation * Vector3.forward;
validHit = Vector3.Dot(forward, goDirection) > 0;
}
validHit &= hitData.distance < hitDistance;
if (validHit)
{
var castResult = new RaycastResult
{
gameObject = go,
module = this,
distance = hitData.distance,
index = resultAppendList.Count,
depth = hitData.graphic.depth,
worldPosition = hitData.worldHitPosition,
screenPosition = hitData.screenPosition,
};
resultAppendList.Add(castResult);
}
}
}
internal static InlinedArray<TrackedDeviceRaycaster> s_Instances;
private static readonly List<RaycastHitData> s_SortedGraphics = new List<RaycastHitData>();
private void SortedRaycastGraphics(Canvas canvas, Ray ray, List<RaycastHitData> results)
{
var graphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
s_SortedGraphics.Clear();
for (var i = 0; i < graphics.Count; ++i)
{
var graphic = graphics[i];
if (graphic.depth == -1)
continue;
Vector3 worldPos;
float distance;
if (RayIntersectsRectTransform(graphic.rectTransform, ray, out worldPos, out distance))
{
Vector2 screenPos = eventCamera.WorldToScreenPoint(worldPos);
// mask/image intersection - See Unity docs on eventAlphaThreshold for when this does anything
if (graphic.Raycast(screenPos, eventCamera))
{
s_SortedGraphics.Add(new RaycastHitData(graphic, worldPos, screenPos, distance));
}
}
}
s_SortedGraphics.Sort((g1, g2) => g2.graphic.depth.CompareTo(g1.graphic.depth));
results.AddRange(s_SortedGraphics);
}
private static bool RayIntersectsRectTransform(RectTransform transform, Ray ray, out Vector3 worldPosition, out float distance)
{
var corners = new Vector3[4];
transform.GetWorldCorners(corners);
var plane = new Plane(corners[0], corners[1], corners[2]);
float enter;
if (plane.Raycast(ray, out enter))
{
var intersection = ray.GetPoint(enter);
var bottomEdge = corners[3] - corners[0];
var leftEdge = corners[1] - corners[0];
var bottomDot = Vector3.Dot(intersection - corners[0], bottomEdge);
var leftDot = Vector3.Dot(intersection - corners[0], leftEdge);
// If the intersection is right of the left edge and above the bottom edge.
if (leftDot >= 0 && bottomDot >= 0)
{
var topEdge = corners[1] - corners[2];
var rightEdge = corners[3] - corners[2];
var topDot = Vector3.Dot(intersection - corners[2], topEdge);
var rightDot = Vector3.Dot(intersection - corners[2], rightEdge);
//If the intersection is left of the right edge, and below the top edge
if (topDot >= 0 && rightDot >= 0)
{
worldPosition = intersection;
distance = enter;
return true;
}
}
}
worldPosition = Vector3.zero;
distance = 0;
return false;
}
[FormerlySerializedAs("ignoreReversedGraphics")]
[SerializeField]
private bool m_IgnoreReversedGraphics;
[FormerlySerializedAs("checkFor2DOcclusion")]
[SerializeField]
private bool m_CheckFor2DOcclusion;
[FormerlySerializedAs("checkFor3DOcclusion")]
[SerializeField]
private bool m_CheckFor3DOcclusion;
[Tooltip("Maximum distance (in 3D world space) that rays are traced to find a hit.")]
[SerializeField] private float m_MaxDistance = 1000;
[SerializeField]
private LayerMask m_BlockingMask;
[NonSerialized]
private Canvas m_Canvas;
private Canvas canvas
{
get
{
if (m_Canvas != null)
return m_Canvas;
m_Canvas = GetComponent<Canvas>();
return m_Canvas;
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,21 @@
using UnityEngine.InputSystem;
////FIXME: This should be UnityEngine.InputSystem.UI
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
public
#else
internal
#endif
static class UISupport
{
public static void Initialize()
{
InputSystem.RegisterLayout(@"
{
""name"" : ""VirtualMouse"",
""extend"" : ""Mouse""
}
");
}
}

View File

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

View File

@@ -0,0 +1,629 @@
#if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using System;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEngine.InputSystem.Editor;
#endif
////TODO: respect cursor lock mode
////TODO: investigate how driving the HW cursor behaves when FPS drops low
//// (also, maybe we can add support where we turn the gamepad mouse on and off automatically based on whether the system mouse is used)
////TODO: add support for acceleration
////TODO: automatically scale mouse speed to resolution such that it stays constant regardless of resolution
////TODO: make it work with PlayerInput such that it will automatically look up actions in the actual PlayerInput instance it is used with (based on the action IDs it has)
////REVIEW: should we default the SW cursor position to the center of the screen?
////REVIEW: consider this for inclusion directly in the input system
namespace UnityEngine.InputSystem.UI
{
/// <summary>
/// A component that creates a virtual <see cref="Mouse"/> device and drives its input from gamepad-style inputs. This effectively
/// adds a software mouse cursor.
/// </summary>
/// <remarks>
/// This component can be used with UIs that are designed for mouse input, i.e. need to be operated with a cursor.
/// By hooking up the <see cref="InputAction"/>s of this component to gamepad input and directing <see cref="cursorTransform"/>
/// to the UI transform of the cursor, you can use this component to drive an on-screen cursor.
///
/// Note that this component does not actually trigger UI input itself. Instead, it creates a virtual <see cref="Mouse"/>
/// device which can then be picked up elsewhere (such as by <see cref="InputSystemUIInputModule"/>) where mouse/pointer input
/// is expected.
///
/// Also note that if there is a <see cref="Mouse"/> added by the platform, it is not impacted by this component. More specifically,
/// the system mouse cursor will not be moved or otherwise used by this component.
///
/// Input from the component is visible in the same frame as the source input on its actions by virtue of using <see cref="InputState.Change"/>.
/// </remarks>
/// <seealso cref="Gamepad"/>
/// <seealso cref="Mouse"/>
[AddComponentMenu("Input/Virtual Mouse")]
[HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#virtual-mouse-cursor-control")]
public class VirtualMouseInput : MonoBehaviour
{
/// <summary>
/// Optional transform that will be updated to correspond to the current mouse position.
/// </summary>
/// <value>Transform to update with mouse position.</value>
/// <remarks>
/// This is useful for having a UI object that directly represents the mouse cursor. Simply add both the
/// <c>VirtualMouseInput</c> component and an <a href="https://docs.unity3d.com/Manual/script-Image.html">Image</a>
/// component and hook the <a href="https://docs.unity3d.com/ScriptReference/RectTransform.html">RectTransform</a>
/// component for the UI object into here. The object as a whole will then follow the generated mouse cursor
/// motion.
/// </remarks>
public RectTransform cursorTransform
{
get => m_CursorTransform;
set => m_CursorTransform = value;
}
/// <summary>
/// How many pixels per second the cursor travels in one axis when the respective axis from
/// <see cref="stickAction"/> is 1.
/// </summary>
/// <value>Mouse speed in pixels per second.</value>
public float cursorSpeed
{
get => m_CursorSpeed;
set => m_CursorSpeed = value;
}
/// <summary>
/// Determines which cursor representation to use. If this is set to <see cref="CursorMode.SoftwareCursor"/>
/// (the default), then <see cref="cursorGraphic"/> and <see cref="cursorTransform"/> define a software cursor
/// that is made to correspond to the position of <see cref="virtualMouse"/>. If this is set to <see
/// cref="CursorMode.HardwareCursorIfAvailable"/> and there is a native <see cref="Mouse"/> device present,
/// the component will take over that mouse device and disable it (so as for it to not also generate position
/// updates). It will then use <see cref="Mouse.WarpCursorPosition"/> to move the system mouse cursor to
/// correspond to the position of the <see cref="virtualMouse"/>. In this case, <see cref="cursorGraphic"/>
/// will be disabled and <see cref="cursorTransform"/> will not be updated.
/// </summary>
/// <value>Whether the system mouse cursor (if present) should be made to correspond with the virtual mouse position.</value>
/// <remarks>
/// Note that regardless of which mode is used for the cursor, mouse input is expected to be picked up from <see cref="virtualMouse"/>.
///
/// Note that if <see cref="CursorMode.HardwareCursorIfAvailable"/> is used, the software cursor is still used
/// if no native <see cref="Mouse"/> device is present.
/// </remarks>
public CursorMode cursorMode
{
get => m_CursorMode;
set
{
if (m_CursorMode == value)
return;
// If we're turning it off, make sure we re-enable the system mouse.
if (m_CursorMode == CursorMode.HardwareCursorIfAvailable && m_SystemMouse != null)
{
InputSystem.EnableDevice(m_SystemMouse);
m_SystemMouse = null;
}
m_CursorMode = value;
if (m_CursorMode == CursorMode.HardwareCursorIfAvailable)
TryEnableHardwareCursor();
else if (m_CursorGraphic != null)
m_CursorGraphic.enabled = true;
}
}
/// <summary>
/// The UI graphic element that represents the mouse cursor.
/// </summary>
/// <value>Graphic element for the software mouse cursor.</value>
/// <remarks>
/// If <see cref="cursorMode"/> is set to <see cref="CursorMode.HardwareCursorIfAvailable"/>, this graphic will
/// be disabled.
///
/// Also, this UI component implicitly determines the <c>Canvas</c> that defines the screen area for the cursor.
/// The canvas that this graphic is on will be looked up using <c>GetComponentInParent</c> and then the <c>Canvas.pixelRect</c>
/// of the canvas is used as the bounds for the cursor motion range.
/// </remarks>
/// <seealso cref="CursorMode.SoftwareCursor"/>
public Graphic cursorGraphic
{
get => m_CursorGraphic;
set
{
m_CursorGraphic = value;
TryFindCanvas();
}
}
/// <summary>
/// Multiplier for values received from <see cref="scrollWheelAction"/>.
/// </summary>
/// <value>Multiplier for scroll values.</value>
public float scrollSpeed
{
get => m_ScrollSpeed;
set => m_ScrollSpeed = value;
}
/// <summary>
/// The virtual mouse device that the component feeds with input.
/// </summary>
/// <value>Instance of virtual mouse or <c>null</c>.</value>
/// <remarks>
/// This is only initialized after the component has been enabled for the first time. Note that
/// when subsequently disabling the component, the property will continue to return the mouse device
/// but the device will not be added to the system while the component is not enabled.
/// </remarks>
public Mouse virtualMouse => m_VirtualMouse;
/// <summary>
/// The Vector2 stick input that drives the mouse cursor, i.e. <see cref="Pointer.position"/> on
/// <see cref="virtualMouse"/> and the <a
/// href="https://docs.unity3d.com/ScriptReference/RectTransform-anchoredPosition.html">anchoredPosition</a>
/// on <see cref="cursorTransform"/> (if set).
/// </summary>
/// <value>Stick input that drives cursor position.</value>
/// <remarks>
/// This should normally be bound to controls such as <see cref="Gamepad.leftStick"/> and/or
/// <see cref="Gamepad.rightStick"/>.
/// </remarks>
public InputActionProperty stickAction
{
get => m_StickAction;
set => SetAction(ref m_StickAction, value);
}
/// <summary>
/// Optional button input that determines when <see cref="Mouse.leftButton"/> is pressed on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.leftButton"/>.</value>
public InputActionProperty leftButtonAction
{
get => m_LeftButtonAction;
set
{
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, false);
SetAction(ref m_LeftButtonAction, value);
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, true);
}
}
/// <summary>
/// Optional button input that determines when <see cref="Mouse.rightButton"/> is pressed on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.rightButton"/>.</value>
public InputActionProperty rightButtonAction
{
get => m_RightButtonAction;
set
{
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, false);
SetAction(ref m_RightButtonAction, value);
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, true);
}
}
/// <summary>
/// Optional button input that determines when <see cref="Mouse.middleButton"/> is pressed on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.middleButton"/>.</value>
public InputActionProperty middleButtonAction
{
get => m_MiddleButtonAction;
set
{
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, false);
SetAction(ref m_MiddleButtonAction, value);
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, true);
}
}
/// <summary>
/// Optional button input that determines when <see cref="Mouse.forwardButton"/> is pressed on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.forwardButton"/>.</value>
public InputActionProperty forwardButtonAction
{
get => m_ForwardButtonAction;
set
{
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, false);
SetAction(ref m_ForwardButtonAction, value);
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, true);
}
}
/// <summary>
/// Optional button input that determines when <see cref="Mouse.forwardButton"/> is pressed on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.forwardButton"/>.</value>
public InputActionProperty backButtonAction
{
get => m_BackButtonAction;
set
{
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, false);
SetAction(ref m_BackButtonAction, value);
if (m_ButtonActionTriggeredDelegate != null)
SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, true);
}
}
/// <summary>
/// Optional Vector2 value input that determines the value of <see cref="Mouse.scroll"/> on
/// <see cref="virtualMouse"/>.
/// </summary>
/// <value>Input for <see cref="Mouse.scroll"/>.</value>
/// <remarks>
/// In case you want to only bind vertical scrolling, simply have a <see cref="Composites.Vector2Composite"/>
/// with only <c>Up</c> and <c>Down</c> bound and <c>Left</c> and <c>Right</c> deleted or bound to nothing.
/// </remarks>
public InputActionProperty scrollWheelAction
{
get => m_ScrollWheelAction;
set => SetAction(ref m_ScrollWheelAction, value);
}
protected void OnEnable()
{
// Hijack system mouse, if enabled.
if (m_CursorMode == CursorMode.HardwareCursorIfAvailable)
TryEnableHardwareCursor();
// Add mouse device.
if (m_VirtualMouse == null)
m_VirtualMouse = (Mouse)InputSystem.AddDevice("VirtualMouse");
else if (!m_VirtualMouse.added)
InputSystem.AddDevice(m_VirtualMouse);
// Set initial cursor position.
if (m_CursorTransform != null)
{
var position = m_CursorTransform.anchoredPosition;
InputState.Change(m_VirtualMouse.position, position);
m_SystemMouse?.WarpCursorPosition(position);
}
// Hook into input update.
if (m_AfterInputUpdateDelegate == null)
m_AfterInputUpdateDelegate = OnAfterInputUpdate;
InputSystem.onAfterUpdate += m_AfterInputUpdateDelegate;
// Hook into actions.
if (m_ButtonActionTriggeredDelegate == null)
m_ButtonActionTriggeredDelegate = OnButtonActionTriggered;
SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, true);
SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, true);
SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, true);
SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, true);
SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, true);
// Enable actions.
m_StickAction.action?.Enable();
m_LeftButtonAction.action?.Enable();
m_RightButtonAction.action?.Enable();
m_MiddleButtonAction.action?.Enable();
m_ForwardButtonAction.action?.Enable();
m_BackButtonAction.action?.Enable();
m_ScrollWheelAction.action?.Enable();
}
protected void OnDisable()
{
// Remove mouse device.
if (m_VirtualMouse != null && m_VirtualMouse.added)
InputSystem.RemoveDevice(m_VirtualMouse);
// Let go of system mouse.
if (m_SystemMouse != null)
{
InputSystem.EnableDevice(m_SystemMouse);
m_SystemMouse = null;
}
// Remove ourselves from input update.
if (m_AfterInputUpdateDelegate != null)
InputSystem.onAfterUpdate -= m_AfterInputUpdateDelegate;
// Disable actions.
m_StickAction.action?.Disable();
m_LeftButtonAction.action?.Disable();
m_RightButtonAction.action?.Disable();
m_MiddleButtonAction.action?.Disable();
m_ForwardButtonAction.action?.Disable();
m_BackButtonAction.action?.Disable();
m_ScrollWheelAction.action?.Disable();
// Unhock from actions.
if (m_ButtonActionTriggeredDelegate != null)
{
SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, false);
SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, false);
SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, false);
SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, false);
SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, false);
}
m_LastTime = default;
m_LastStickValue = default;
}
private void TryFindCanvas()
{
m_Canvas = m_CursorGraphic?.GetComponentInParent<Canvas>();
}
private void TryEnableHardwareCursor()
{
var devices = InputSystem.devices;
for (var i = 0; i < devices.Count; ++i)
{
var device = devices[i];
if (device.native && device is Mouse mouse)
{
m_SystemMouse = mouse;
break;
}
}
if (m_SystemMouse == null)
{
if (m_CursorGraphic != null)
m_CursorGraphic.enabled = true;
return;
}
InputSystem.DisableDevice(m_SystemMouse);
// Sync position.
if (m_VirtualMouse != null)
m_SystemMouse.WarpCursorPosition(m_VirtualMouse.position.value);
// Turn off mouse cursor image.
if (m_CursorGraphic != null)
m_CursorGraphic.enabled = false;
}
private void UpdateMotion()
{
if (m_VirtualMouse == null)
return;
// Read current stick value.
var stickAction = m_StickAction.action;
if (stickAction == null)
return;
var stickValue = stickAction.ReadValue<Vector2>();
if (Mathf.Approximately(0, stickValue.x) && Mathf.Approximately(0, stickValue.y))
{
// Motion has stopped.
m_LastTime = default;
m_LastStickValue = default;
}
else
{
var currentTime = InputState.currentTime;
if (Mathf.Approximately(0, m_LastStickValue.x) && Mathf.Approximately(0, m_LastStickValue.y))
{
// Motion has started.
m_LastTime = currentTime;
}
// Compute delta.
var deltaTime = (float)(currentTime - m_LastTime);
var delta = new Vector2(m_CursorSpeed * stickValue.x * deltaTime, m_CursorSpeed * stickValue.y * deltaTime);
// Update position.
var currentPosition = m_VirtualMouse.position.value;
var newPosition = currentPosition + delta;
////REVIEW: for the hardware cursor, clamp to something else?
// Clamp to canvas.
if (m_Canvas != null)
{
// Clamp to canvas.
var pixelRect = m_Canvas.pixelRect;
newPosition.x = Mathf.Clamp(newPosition.x, pixelRect.xMin, pixelRect.xMax);
newPosition.y = Mathf.Clamp(newPosition.y, pixelRect.yMin, pixelRect.yMax);
}
////REVIEW: the fact we have no events on these means that actions won't have an event ID to go by; problem?
InputState.Change(m_VirtualMouse.position, newPosition);
InputState.Change(m_VirtualMouse.delta, delta);
// Update software cursor transform, if any.
if (m_CursorTransform != null &&
(m_CursorMode == CursorMode.SoftwareCursor ||
(m_CursorMode == CursorMode.HardwareCursorIfAvailable && m_SystemMouse == null)))
m_CursorTransform.anchoredPosition = newPosition;
m_LastStickValue = stickValue;
m_LastTime = currentTime;
// Update hardware cursor.
m_SystemMouse?.WarpCursorPosition(newPosition);
}
// Update scroll wheel.
var scrollAction = m_ScrollWheelAction.action;
if (scrollAction != null)
{
var scrollValue = scrollAction.ReadValue<Vector2>();
scrollValue.x *= m_ScrollSpeed;
scrollValue.y *= m_ScrollSpeed;
InputState.Change(m_VirtualMouse.scroll, scrollValue);
}
}
[Header("Cursor")]
[Tooltip("Whether the component should set the cursor position of the hardware mouse cursor, if one is available. If so, "
+ "the software cursor pointed (to by 'Cursor Graphic') will be hidden.")]
[SerializeField] private CursorMode m_CursorMode;
[Tooltip("The graphic that represents the software cursor. This is hidden if a hardware cursor (see 'Cursor Mode') is used.")]
[SerializeField] private Graphic m_CursorGraphic;
[Tooltip("The transform for the software cursor. Will only be set if a software cursor is used (see 'Cursor Mode'). Moving the cursor "
+ "updates the anchored position of the transform.")]
[SerializeField] private RectTransform m_CursorTransform;
[Header("Motion")]
[Tooltip("Speed in pixels per second with which to move the cursor. Scaled by the input from 'Stick Action'.")]
[SerializeField] private float m_CursorSpeed = 400;
[Tooltip("Scale factor to apply to 'Scroll Wheel Action' when setting the mouse 'scrollWheel' control.")]
[SerializeField] private float m_ScrollSpeed = 45;
[Space(10)]
[Tooltip("Vector2 action that moves the cursor left/right (X) and up/down (Y) on screen.")]
[SerializeField] private InputActionProperty m_StickAction;
[Tooltip("Button action that triggers a left-click on the mouse.")]
[SerializeField] private InputActionProperty m_LeftButtonAction;
[Tooltip("Button action that triggers a middle-click on the mouse.")]
[SerializeField] private InputActionProperty m_MiddleButtonAction;
[Tooltip("Button action that triggers a right-click on the mouse.")]
[SerializeField] private InputActionProperty m_RightButtonAction;
[Tooltip("Button action that triggers a forward button (button #4) click on the mouse.")]
[SerializeField] private InputActionProperty m_ForwardButtonAction;
[Tooltip("Button action that triggers a back button (button #5) click on the mouse.")]
[SerializeField] private InputActionProperty m_BackButtonAction;
[Tooltip("Vector2 action that feeds into the mouse 'scrollWheel' action (scaled by 'Scroll Speed').")]
[SerializeField] private InputActionProperty m_ScrollWheelAction;
private Canvas m_Canvas; // Canvas that gives the motion range for the software cursor.
private Mouse m_VirtualMouse;
private Mouse m_SystemMouse;
private Action m_AfterInputUpdateDelegate;
private Action<InputAction.CallbackContext> m_ButtonActionTriggeredDelegate;
private double m_LastTime;
private Vector2 m_LastStickValue;
private void OnButtonActionTriggered(InputAction.CallbackContext context)
{
if (m_VirtualMouse == null)
return;
// The button controls are bit controls. We can't (yet?) use InputState.Change to state
// the change of those controls as the state update machinery of InputManager only supports
// byte region updates. So we just grab the full state of our virtual mouse, then update
// the button in there and then simply overwrite the entire state.
var action = context.action;
MouseButton? button = null;
if (action == m_LeftButtonAction.action)
button = MouseButton.Left;
else if (action == m_RightButtonAction.action)
button = MouseButton.Right;
else if (action == m_MiddleButtonAction.action)
button = MouseButton.Middle;
else if (action == m_ForwardButtonAction.action)
button = MouseButton.Forward;
else if (action == m_BackButtonAction.action)
button = MouseButton.Back;
if (button != null)
{
var isPressed = context.control.IsPressed();
m_VirtualMouse.CopyState<MouseState>(out var mouseState);
mouseState.WithButton(button.Value, isPressed);
InputState.Change(m_VirtualMouse, mouseState);
}
}
private static void SetActionCallback(InputActionProperty field, Action<InputAction.CallbackContext> callback, bool install = true)
{
var action = field.action;
if (action == null)
return;
// We don't need the performed callback as our mouse buttons are binary and thus
// we only care about started (1) and canceled (0).
if (install)
{
action.started += callback;
action.canceled += callback;
}
else
{
action.started -= callback;
action.canceled -= callback;
}
}
private static void SetAction(ref InputActionProperty field, InputActionProperty value)
{
var oldValue = field;
field = value;
if (oldValue.reference == null)
{
var oldAction = oldValue.action;
if (oldAction != null && oldAction.enabled)
{
oldAction.Disable();
if (value.reference == null)
value.action?.Enable();
}
}
}
private void OnAfterInputUpdate()
{
UpdateMotion();
}
/// <summary>
/// Determines how the cursor for the virtual mouse is represented.
/// </summary>
/// <seealso cref="cursorMode"/>
public enum CursorMode
{
/// <summary>
/// The cursor is represented as a UI element. See <see cref="cursorGraphic"/>.
/// </summary>
SoftwareCursor,
/// <summary>
/// If a native <see cref="Mouse"/> device is present, its cursor will be used and driven
/// by the virtual mouse using <see cref="Mouse.WarpCursorPosition"/>. The software cursor
/// referenced by <see cref="cursorGraphic"/> will be disabled.
///
/// Note that if no native <see cref="Mouse"/> is present, behavior will fall back to
/// <see cref="SoftwareCursor"/>.
/// </summary>
HardwareCursorIfAvailable,
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(VirtualMouseInput))]
private class VirtualMouseInputEditor : UnityEditor.Editor
{
public void OnDisable()
{
new InputComponentEditorAnalytic(InputSystemComponent.VirtualMouseInput).Send();
new VirtualMouseInputEditorAnalytic(this).Send();
}
}
#endif
}
}
#endif // PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI

View File

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