test
This commit is contained in:
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47e42d28cda46b34cb7f8804e6db4c25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 408bd3b4ce254207beaa4e0779763fd6
|
||||
timeCreated: 1617961863
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bca227b3a59246a197db2ff52b862072
|
||||
timeCreated: 1582733677
|
File diff suppressed because it is too large
Load Diff
@@ -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:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0800c72aa1894e478bb749990855f9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -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:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e7ea3151ed73745b7924e453c67eca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e04d6d4984c7d164096e271f20932849
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd2cb6115f94b4c129408ea68717f9c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afe3cda3e244f60419474164d6ba3b0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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""
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa489f950f62d2948a1d96092e5a708a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 424b6720ff5999446843ba414e58a907
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user