When using the XRUIInputModule with “Enable Mouse Input = true” the mouse processing does not take multiple displays into account.
I’m having this issue when I want to render the VR mirror display on the main screen and have a second screen with UI.
I have created a quick fix by copying the package in the packages folder locally and modified the following:
MouseModel.cs now contains a property called displayIndex which is set from XRUIInputModule.cs in the
ProcessMouse() method.
m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();
XRUIInputModule.cs
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Pooling;
namespace UnityEngine.XR.Interaction.Toolkit.UI
{
/// <summary>
/// Matches the UI Model to the state of the Interactor.
/// </summary>
public interface IUIInteractor
{
/// <summary>
/// Updates the current UI Model to match the state of the Interactor.
/// </summary>
/// <param name="model">The returned model that will match this Interactor.</param>
void UpdateUIModel(ref TrackedDeviceModel model);
/// <summary>
/// Attempts to retrieve the current UI Model.
/// </summary>
/// <param name="model">The returned model that reflects the UI state of this Interactor.</param>
/// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
bool TryGetUIModel(out TrackedDeviceModel model);
}
/// <summary>
/// Matches the UI Model to the state of the Interactor with support for hover events.
/// </summary>
public interface IUIHoverInteractor : IUIInteractor
{
/// <summary>
/// The event that is called when the Interactor begins hovering over a UI element.
/// </summary>
/// <remarks>
/// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
/// do not hold a reference to it.
/// </remarks>
UIHoverEnterEvent uiHoverEntered { get; }
/// <summary>
/// The event that is called when this Interactor ends hovering over a UI element.
/// </summary>
/// <remarks>
/// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
/// do not hold a reference to it.
/// </remarks>
UIHoverExitEvent uiHoverExited { get; }
/// <summary>
/// The <see cref="XRUIInputModule"/> calls this method when the Interactor begins hovering over a UI element.
/// </summary>
/// <param name="args">Event data containing the UI element that is being hovered over.</param>
/// <remarks>
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
/// </remarks>
/// <seealso cref="OnUIHoverExited(UIHoverEventArgs)"/>
void OnUIHoverEntered(UIHoverEventArgs args);
/// <summary>
/// The <see cref="XRUIInputModule"/> calls this method when the Interactor ends hovering over a UI element.
/// </summary>
/// <param name="args">Event data containing the UI element that is no longer hovered over.</param>
/// <remarks>
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
/// </remarks>
/// <seealso cref="OnUIHoverEntered(UIHoverEventArgs)"/>
void OnUIHoverExited(UIHoverEventArgs args);
}
/// <summary>
/// Custom class for input modules that send UI input in XR.
/// </summary>
[AddComponentMenu("Event/XR UI Input Module", 11)]
[HelpURL(XRHelpURLConstants.k_XRUIInputModule)]
public partial class XRUIInputModule : UIInputModule
{
struct RegisteredInteractor
{
public IUIInteractor interactor;
public TrackedDeviceModel model;
public RegisteredInteractor(IUIInteractor interactor, int deviceIndex)
{
this.interactor = interactor;
model = new TrackedDeviceModel(deviceIndex);
}
}
struct RegisteredTouch
{
public bool isValid;
public int touchId;
public TouchModel model;
public RegisteredTouch(Touch touch, int deviceIndex)
{
touchId = touch.fingerId;
model = new TouchModel(deviceIndex);
isValid = true;
}
}
/// <summary>
/// Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
/// </summary>
/// /// <seealso cref="activeInputMode"/>
public enum ActiveInputMode
{
/// <summary>
/// Only use input polled through the built-in Unity Input Manager (Old).
/// </summary>
InputManagerBindings,
/// <summary>
/// Only use input polled from <see cref="InputActionReference"/> through the newer Input System package.
/// </summary>
InputSystemActions,
/// <summary>
/// Scan through input from both Unity Input Manager and Input System action references.
/// Note: This may cause undesired effects or may impact performance if input configuration is duplicated.
/// </summary>
Both,
}
#if !ENABLE_INPUT_SYSTEM || !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.")]
ActiveInputMode m_ActiveInputMode;
/// <summary>
/// Configures which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
/// </summary>
/// <seealso cref="ActiveInputMode"/>
public ActiveInputMode activeInputMode
{
get => m_ActiveInputMode;
set => m_ActiveInputMode = value;
}
[SerializeField, HideInInspector]
[Tooltip("The maximum distance to ray cast with tracked devices to find hit objects.")]
float m_MaxTrackedDeviceRaycastDistance = 1000f;
[Header("Input Devices")]
[SerializeField]
[Tooltip("If true, will forward 3D tracked device data to UI elements.")]
bool m_EnableXRInput = true;
/// <summary>
/// If <see langword="true"/>, will forward 3D tracked device data to UI elements.
/// </summary>
public bool enableXRInput
{
get => m_EnableXRInput;
set => m_EnableXRInput = value;
}
[SerializeField]
[Tooltip("If true, will forward 2D mouse data to UI elements.")]
bool m_EnableMouseInput = true;
/// <summary>
/// If <see langword="true"/>, will forward 2D mouse data to UI elements.
/// </summary>
public bool enableMouseInput
{
get => m_EnableMouseInput;
set => m_EnableMouseInput = value;
}
[SerializeField]
[Tooltip("If true, will forward 2D touch data to UI elements.")]
bool m_EnableTouchInput = true;
/// <summary>
/// If <see langword="true"/>, will forward 2D touch data to UI elements.
/// </summary>
public bool enableTouchInput
{
get => m_EnableTouchInput;
set => m_EnableTouchInput = value;
}
#if ENABLE_INPUT_SYSTEM
[Header("Input System UI Actions")]
#else
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Pointer input action reference, such as a mouse or single-finger touch device.")]
InputActionReference m_PointAction;
/// <summary>
/// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
/// </summary>
public InputActionReference pointAction
{
get => m_PointAction;
set => SetInputAction(ref m_PointAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Left-click input action reference, typically the left button on a mouse.")]
InputActionReference m_LeftClickAction;
/// <summary>
/// The Input System action to use to determine whether the left button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
/// </summary>
public InputActionReference leftClickAction
{
get => m_LeftClickAction;
set => SetInputAction(ref m_LeftClickAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Middle-click input action reference, typically the middle button on a mouse.")]
InputActionReference m_MiddleClickAction;
/// <summary>
/// The Input System action to use to determine whether the middle button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
/// </summary>
public InputActionReference middleClickAction
{
get => m_MiddleClickAction;
set => SetInputAction(ref m_MiddleClickAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Right-click input action reference, typically the right button on a mouse.")]
InputActionReference m_RightClickAction;
/// <summary>
/// The Input System action to use to determine whether the right button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
/// </summary>
public InputActionReference rightClickAction
{
get => m_RightClickAction;
set => SetInputAction(ref m_RightClickAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Scroll wheel input action reference, typically the scroll wheel on a mouse.")]
InputActionReference m_ScrollWheelAction;
/// <summary>
/// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
/// </summary>
public InputActionReference scrollWheelAction
{
get => m_ScrollWheelAction;
set => SetInputAction(ref m_ScrollWheelAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Navigation input action reference will change which UI element is currently selected to the one up, down, left of or right of the currently selected one.")]
InputActionReference m_NavigateAction;
/// <summary>
/// The Input System action to use to navigate the currently active UI. Must be a <see cref="Vector2Control"/> Control.
/// </summary>
public InputActionReference navigateAction
{
get => m_NavigateAction;
set => SetInputAction(ref m_NavigateAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Submit input action reference will trigger a submission of the currently selected UI in the Event System.")]
InputActionReference m_SubmitAction;
/// <summary>
/// The Input System action to use for submitting or activating a UI element. Must be a <see cref="ButtonControl"/> Control.
/// </summary>
public InputActionReference submitAction
{
get => m_SubmitAction;
set => SetInputAction(ref m_SubmitAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Cancel input action reference will trigger canceling out of the currently selected UI in the Event System.")]
InputActionReference m_CancelAction;
/// <summary>
/// The Input System action to use for cancelling or backing out of a UI element. Must be a <see cref="ButtonControl"/> Control.
/// </summary>
public InputActionReference cancelAction
{
get => m_CancelAction;
set => SetInputAction(ref m_CancelAction, value);
}
#if !ENABLE_INPUT_SYSTEM
[HideInInspector]
#endif
[SerializeField]
[Tooltip("When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned.")]
bool m_EnableBuiltinActionsAsFallback = true;
/// <summary>
/// When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned. This uses the
/// currently enabled Input System devices: <see cref="Mouse.current"/>, <see cref="Gamepad.current"/>, and <see cref="Joystick.current"/>.
/// </summary>
public bool enableBuiltinActionsAsFallback
{
get => m_EnableBuiltinActionsAsFallback;
set
{
m_EnableBuiltinActionsAsFallback = value;
m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();
}
}
#if ENABLE_LEGACY_INPUT_MANAGER
[Header("Input Manager (Old) Gamepad/Joystick Bindings")]
#else
[HideInInspector]
#endif
[SerializeField]
[Tooltip("If true, will use the Input Manager (Old) configuration to forward gamepad data to UI elements.")]
bool m_EnableGamepadInput = true;
/// <summary>
/// If <see langword="true"/>, will forward gamepad data to UI elements.
/// </summary>
public bool enableGamepadInput
{
get => m_EnableGamepadInput;
set => m_EnableGamepadInput = value;
}
#if !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("If true, will use the Input Manager (Old) configuration to forward joystick data to UI elements.")]
bool m_EnableJoystickInput = true;
/// <summary>
/// If <see langword="true"/>, will forward joystick data to UI elements.
/// </summary>
public bool enableJoystickInput
{
get => m_EnableJoystickInput;
set => m_EnableJoystickInput = value;
}
#if !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Name of the horizontal axis for gamepad/joystick UI navigation when using the old Input Manager.")]
string m_HorizontalAxis = "Horizontal";
/// <summary>
/// Name of the horizontal axis for UI navigation when using the old Input Manager.
/// </summary>
public string horizontalAxis
{
get => m_HorizontalAxis;
set => m_HorizontalAxis = value;
}
#if !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Name of the vertical axis for gamepad/joystick UI navigation when using the old Input Manager.")]
string m_VerticalAxis = "Vertical";
/// <summary>
/// Name of the vertical axis for UI navigation when using the old Input Manager.
/// </summary>
public string verticalAxis
{
get => m_VerticalAxis;
set => m_VerticalAxis = value;
}
#if !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.")]
string m_SubmitButton = "Submit";
/// <summary>
/// Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.
/// </summary>
public string submitButton
{
get => m_SubmitButton;
set => m_SubmitButton = value;
}
#if !ENABLE_LEGACY_INPUT_MANAGER
[HideInInspector]
#endif
[SerializeField]
[Tooltip("Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.")]
string m_CancelButton = "Cancel";
/// <summary>
/// Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.
/// </summary>
public string cancelButton
{
get => m_CancelButton;
set => m_CancelButton = value;
}
int m_RollingPointerId;
bool m_UseBuiltInInputSystemActions;
MouseModel m_MouseState;
NavigationModel m_NavigationState;
internal const float kPixelPerLine = 20f;
readonly List<RegisteredTouch> m_RegisteredTouches = new List<RegisteredTouch>();
readonly List<RegisteredInteractor> m_RegisteredInteractors = new List<RegisteredInteractor>();
// Reusable event args
readonly LinkedPool<UIHoverEventArgs> m_UIHoverEventArgs = new LinkedPool<UIHoverEventArgs>(() => new UIHoverEventArgs(), collectionCheck: false);
/// <summary>
/// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnEnable.html">MonoBehavior.OnEnable</a>.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
// Check active input mode is correct
#if !ENABLE_INPUT_SYSTEM && ENABLE_LEGACY_INPUT_MANAGER
m_ActiveInputMode = ActiveInputMode.InputManagerBindings;
#elif ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
m_ActiveInputMode = ActiveInputMode.InputSystemActions;
#endif
m_MouseState = new MouseModel(m_RollingPointerId++);
m_NavigationState = new NavigationModel();
m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();
if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
EnableAllActions();
}
/// <summary>
/// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDisable.html">MonoBehavior.OnDisable</a>.
/// </summary>
protected override void OnDisable()
{
RemovePointerEventData(m_MouseState.pointerId);
if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
DisableAllActions();
base.OnDisable();
}
/// <summary>
/// Register an <see cref="IUIInteractor"/> with the UI system.
/// Calling this will enable it to start interacting with UI.
/// </summary>
/// <param name="interactor">The <see cref="IUIInteractor"/> to use.</param>
public void RegisterInteractor(IUIInteractor interactor)
{
for (var i = 0; i < m_RegisteredInteractors.Count; i++)
{
if (m_RegisteredInteractors[i].interactor == interactor)
return;
}
m_RegisteredInteractors.Add(new RegisteredInteractor(interactor, m_RollingPointerId++));
}
/// <summary>
/// Unregisters an <see cref="IUIInteractor"/> with the UI system.
/// This cancels all UI Interaction and makes the <see cref="IUIInteractor"/> no longer able to affect UI.
/// </summary>
/// <param name="interactor">The <see cref="IUIInteractor"/> to stop using.</param>
public void UnregisterInteractor(IUIInteractor interactor)
{
for (var i = 0; i < m_RegisteredInteractors.Count; i++)
{
if (m_RegisteredInteractors[i].interactor == interactor)
{
var registeredInteractor = m_RegisteredInteractors[i];
registeredInteractor.interactor = null;
m_RegisteredInteractors[i] = registeredInteractor;
return;
}
}
}
/// <summary>
/// Gets an <see cref="IUIInteractor"/> from its corresponding Unity UI Pointer Id.
/// This can be used to identify individual Interactors from the underlying UI Events.
/// </summary>
/// <param name="pointerId">A unique integer representing an object that can point at UI.</param>
/// <returns>Returns the interactor associated with <paramref name="pointerId"/>.
/// Returns <see langword="null"/> if no Interactor is associated (e.g. if it's a mouse event).</returns>
public IUIInteractor GetInteractor(int pointerId)
{
for (var i = 0; i < m_RegisteredInteractors.Count; i++)
{
if (m_RegisteredInteractors[i].model.pointerId == pointerId)
{
return m_RegisteredInteractors[i].interactor;
}
}
return null;
}
/// <summary>Retrieves the UI Model for a selected <see cref="IUIInteractor"/>.</summary>
/// <param name="interactor">The <see cref="IUIInteractor"/> you want the model for.</param>
/// <param name="model">The returned model that reflects the UI state of the <paramref name="interactor"/>.</param>
/// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
public bool GetTrackedDeviceModel(IUIInteractor interactor, out TrackedDeviceModel model)
{
for (var i = 0; i < m_RegisteredInteractors.Count; i++)
{
if (m_RegisteredInteractors[i].interactor == interactor)
{
model = m_RegisteredInteractors[i].model;
return true;
}
}
model = new TrackedDeviceModel(-1);
return false;
}
/// <inheritdoc />
protected override void DoProcess()
{
if (m_EnableXRInput)
{
for (var i = 0; i < m_RegisteredInteractors.Count; i++)
{
var registeredInteractor = m_RegisteredInteractors[i];
var oldTarget = registeredInteractor.model.implementationData.pointerTarget;
// If device is removed, we send a default state to unclick any UI
if (registeredInteractor.interactor == null)
{
registeredInteractor.model.Reset(false);
ProcessTrackedDevice(ref registeredInteractor.model, true);
RemovePointerEventData(registeredInteractor.model.pointerId);
m_RegisteredInteractors.RemoveAt(i--);
}
else
{
registeredInteractor.interactor.UpdateUIModel(ref registeredInteractor.model);
ProcessTrackedDevice(ref registeredInteractor.model);
m_RegisteredInteractors[i] = registeredInteractor;
}
// If hover target changed, send event
var newTarget = registeredInteractor.model.implementationData.pointerTarget;
if (oldTarget != newTarget)
{
using (m_UIHoverEventArgs.Get(out var args))
{
args.interactorObject = registeredInteractor.interactor;
args.deviceModel = registeredInteractor.model;
if (args.interactorObject != null && args.interactorObject is IUIHoverInteractor hoverInteractor)
{
if (oldTarget != null)
{
args.uiObject = oldTarget;
hoverInteractor.OnUIHoverExited(args);
}
if (newTarget != null)
{
args.uiObject = newTarget;
hoverInteractor.OnUIHoverEntered(args);
}
}
}
}
}
}
// Touch needs to take precedence because of the mouse emulation layer
var hasTouches = false;
if (m_EnableTouchInput)
hasTouches = ProcessTouches();
if (m_EnableMouseInput && !hasTouches)
ProcessMouse();
ProcessNavigation();
}
void ProcessMouse()
{
if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
{
if (m_UseBuiltInInputSystemActions)
{
if (Mouse.current != null)
{
m_MouseState.position = Mouse.current.position.ReadValue();
m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();
m_MouseState.scrollDelta = Mouse.current.scroll.ReadValue() * (1 / kPixelPerLine);
m_MouseState.leftButtonPressed = Mouse.current.leftButton.isPressed;
m_MouseState.rightButtonPressed = Mouse.current.rightButton.isPressed;
m_MouseState.middleButtonPressed = Mouse.current.middleButton.isPressed;
}
}
else
{
if (IsActionEnabled(m_PointAction))
m_MouseState.position = m_PointAction.action.ReadValue<Vector2>();
if (IsActionEnabled(m_ScrollWheelAction))
m_MouseState.scrollDelta = m_ScrollWheelAction.action.ReadValue<Vector2>() * (1 / kPixelPerLine);
if (IsActionEnabled(m_LeftClickAction))
m_MouseState.leftButtonPressed = m_LeftClickAction.action.IsPressed();
if (IsActionEnabled(m_RightClickAction))
m_MouseState.rightButtonPressed = m_RightClickAction.action.IsPressed();
if (IsActionEnabled(m_MiddleClickAction))
m_MouseState.middleButtonPressed = m_MiddleClickAction.action.IsPressed();
}
}
if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && Input.mousePresent)
{
m_MouseState.position = Input.mousePosition;
m_MouseState.scrollDelta = Input.mouseScrollDelta;
m_MouseState.leftButtonPressed = Input.GetMouseButton(0);
m_MouseState.rightButtonPressed = Input.GetMouseButton(1);
m_MouseState.middleButtonPressed = Input.GetMouseButton(2);
}
ProcessMouseState(ref m_MouseState);
}
bool ProcessTouches()
{
var hasTouches = Input.touchCount > 0;
if (!hasTouches)
return false;
var touchCount = Input.touchCount;
for (var touchIndex = 0; touchIndex < touchCount; ++touchIndex)
{
var touch = Input.GetTouch(touchIndex);
var registeredTouchIndex = -1;
// Find if touch already exists
for (var j = 0; j < m_RegisteredTouches.Count; j++)
{
if (touch.fingerId == m_RegisteredTouches[j].touchId)
{
registeredTouchIndex = j;
break;
}
}
if (registeredTouchIndex < 0)
{
// Not found, search empty pool
for (var j = 0; j < m_RegisteredTouches.Count; j++)
{
if (!m_RegisteredTouches[j].isValid)
{
// Re-use the Id
var pointerId = m_RegisteredTouches[j].model.pointerId;
m_RegisteredTouches[j] = new RegisteredTouch(touch, pointerId);
registeredTouchIndex = j;
break;
}
}
if (registeredTouchIndex < 0)
{
// No Empty slots, add one
registeredTouchIndex = m_RegisteredTouches.Count;
m_RegisteredTouches.Add(new RegisteredTouch(touch, m_RollingPointerId++));
}
}
var registeredTouch = m_RegisteredTouches[registeredTouchIndex];
registeredTouch.model.selectPhase = touch.phase;
registeredTouch.model.position = touch.position;
m_RegisteredTouches[registeredTouchIndex] = registeredTouch;
}
for (var i = 0; i < m_RegisteredTouches.Count; i++)
{
var registeredTouch = m_RegisteredTouches[i];
ProcessTouch(ref registeredTouch.model);
if (registeredTouch.model.selectPhase == TouchPhase.Ended || registeredTouch.model.selectPhase == TouchPhase.Canceled)
registeredTouch.isValid = false;
m_RegisteredTouches[i] = registeredTouch;
}
return true;
}
void ProcessNavigation()
{
if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
{
if (m_UseBuiltInInputSystemActions)
{
if (Gamepad.current != null)
{
// Combine left stick and dpad for navigation movement
m_NavigationState.move = Gamepad.current.leftStick.ReadValue() + Gamepad.current.dpad.ReadValue();
m_NavigationState.submitButtonDown = Gamepad.current.buttonSouth.isPressed;
m_NavigationState.cancelButtonDown = Gamepad.current.buttonEast.isPressed;
}
if (Joystick.current != null)
{
// Combine main joystick and hatswitch for navigation movement
m_NavigationState.move = Joystick.current.stick.ReadValue() +
(Joystick.current.hatswitch != null ? Joystick.current.hatswitch.ReadValue() : Vector2.zero);
m_NavigationState.submitButtonDown = Joystick.current.trigger.isPressed;
// This will always be false until we can rely on a secondary button from the joystick
m_NavigationState.cancelButtonDown = false;
}
}
else
{
if (IsActionEnabled(m_NavigateAction))
m_NavigationState.move = m_NavigateAction.action.ReadValue<Vector2>();
if (IsActionEnabled(m_SubmitAction))
m_NavigationState.submitButtonDown = m_SubmitAction.action.WasPressedThisFrame();
if (IsActionEnabled(m_CancelAction))
m_NavigationState.cancelButtonDown = m_CancelAction.action.WasPressedThisFrame();
}
}
if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && (m_EnableGamepadInput || m_EnableJoystickInput) && Input.GetJoystickNames().Length > 0)
{
m_NavigationState.move = new Vector2(Input.GetAxis(m_HorizontalAxis), Input.GetAxis(m_VerticalAxis));
m_NavigationState.submitButtonDown = Input.GetButton(m_SubmitButton);
m_NavigationState.cancelButtonDown = Input.GetButton(m_CancelButton);
}
base.ProcessNavigationState(ref m_NavigationState);
}
bool InputActionReferencesAreSet()
{
return (m_PointAction != null ||
m_LeftClickAction != null ||
m_RightClickAction != null ||
m_MiddleClickAction != null ||
m_NavigateAction != null ||
m_SubmitAction != null ||
m_CancelAction != null ||
m_ScrollWheelAction != null);
}
void EnableAllActions()
{
EnableInputAction(m_PointAction);
EnableInputAction(m_LeftClickAction);
EnableInputAction(m_RightClickAction);
EnableInputAction(m_MiddleClickAction);
EnableInputAction(m_NavigateAction);
EnableInputAction(m_SubmitAction);
EnableInputAction(m_CancelAction);
EnableInputAction(m_ScrollWheelAction);
}
void DisableAllActions()
{
DisableInputAction(m_PointAction);
DisableInputAction(m_LeftClickAction);
DisableInputAction(m_RightClickAction);
DisableInputAction(m_MiddleClickAction);
DisableInputAction(m_NavigateAction);
DisableInputAction(m_SubmitAction);
DisableInputAction(m_CancelAction);
DisableInputAction(m_ScrollWheelAction);
}
static bool IsActionEnabled(InputActionReference inputAction)
{
return inputAction != null && inputAction.action != null && inputAction.action.enabled;
}
static void EnableInputAction(InputActionReference inputAction)
{
if (inputAction == null || inputAction.action == null)
return;
inputAction.action.Enable();
}
static void DisableInputAction(InputActionReference inputAction)
{
if (inputAction == null || inputAction.action == null)
return;
inputAction.action.Disable();
}
void SetInputAction(ref InputActionReference inputAction, InputActionReference value)
{
if (Application.isPlaying && inputAction != null)
inputAction.action?.Disable();
inputAction = value;
if (Application.isPlaying && isActiveAndEnabled && inputAction != null)
inputAction.action?.Enable();
}
}
}
MouseModel.cs
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Controls;
namespace UnityEngine.XR.Interaction.Toolkit.UI
{
/// <summary>
/// A series of flags to determine if a button has been pressed or released since the last time checked.
/// Useful for identifying press/release events that occur in a single frame or sample.
/// </summary>
[Flags]
public enum ButtonDeltaState
{
/// <summary>
/// No change since last time checked.
/// </summary>
NoChange = 0,
/// <summary>
/// Button pressed since last time checked.
/// </summary>
Pressed = 1 << 0,
/// <summary>
/// Button released since last time checked.
/// </summary>
Released = 1 << 1,
}
/// <summary>
/// Represents the state of a single mouse button within the Unity UI (UGUI) system. Keeps track of various book-keeping regarding clicks, drags, and presses.
/// Can be converted to and from PointerEventData for sending into Unity UI (UGUI).
/// </summary>
public struct MouseButtonModel
{
internal struct ImplementationData
{
/// <summary>
/// Used to cache whether or not the current mouse button is being dragged.
/// </summary>
/// <seealso cref="PointerEventData.dragging"/>
public bool isDragging { get; set; }
/// <summary>
/// Used to cache the last time this button was pressed.
/// </summary>
/// <seealso cref="PointerEventData.clickTime"/>
public float pressedTime { get; set; }
/// <summary>
/// The position on the screen that this button was last pressed.
/// In the same scale as <see cref="MouseModel.position"/>, and caches the same value as <see cref="PointerEventData.pressPosition"/>.
/// </summary>
/// <seealso cref="PointerEventData.pressPosition"/>
public Vector2 pressedPosition { get; set; }
/// <summary>
/// The Raycast data from the time it was pressed.
/// </summary>
/// <seealso cref="PointerEventData.pointerPressRaycast"/>
public RaycastResult pressedRaycast { get; set; }
/// <summary>
/// The last GameObject pressed on that can handle press or click events.
/// </summary>
/// <seealso cref="PointerEventData.pointerPress"/>
public GameObject pressedGameObject { get; set; }
/// <summary>
/// The last GameObject pressed on regardless of whether it can handle events or not.
/// </summary>
/// <seealso cref="PointerEventData.rawPointerPress"/>
public GameObject pressedGameObjectRaw { get; set; }
/// <summary>
/// The GameObject currently being dragged if any.
/// </summary>
/// <seealso cref="PointerEventData.pointerDrag"/>
public GameObject draggedGameObject { get; set; }
/// <summary>
/// Resets this object to its default, unused state.
/// </summary>
public void Reset()
{
isDragging = false;
pressedTime = 0f;
pressedPosition = Vector2.zero;
pressedRaycast = new RaycastResult();
pressedGameObject = pressedGameObjectRaw = draggedGameObject = null;
}
}
/// <summary>
/// Used to store the current binary state of the button. When set, will also track the changes between calls of <see cref="OnFrameFinished"/> in <see cref="lastFrameDelta"/>.
/// </summary>
public bool isDown
{
get => m_IsDown;
set
{
if (m_IsDown != value)
{
m_IsDown = value;
lastFrameDelta |= value ? ButtonDeltaState.Pressed : ButtonDeltaState.Released;
}
}
}
/// <summary>
/// A set of flags to identify the changes that have occurred between calls of <see cref="OnFrameFinished"/>.
/// </summary>
internal ButtonDeltaState lastFrameDelta { get; private set; }
/// <summary>
/// Resets this object to it's default, unused state.
/// </summary>
public void Reset()
{
lastFrameDelta = ButtonDeltaState.NoChange;
m_IsDown = false;
m_ImplementationData.Reset();
}
/// <summary>
/// Call this on each frame in order to reset properties that detect whether or not a certain condition was met this frame.
/// </summary>
public void OnFrameFinished() => lastFrameDelta = ButtonDeltaState.NoChange;
/// <summary>
/// Fills a <see cref="PointerEventData"/> with this mouse button's internally cached values.
/// </summary>
/// <param name="eventData">These objects are used to send data through the Unity UI (UGUI) system.</param>
public void CopyTo(PointerEventData eventData)
{
eventData.dragging = m_ImplementationData.isDragging;
eventData.clickTime = m_ImplementationData.pressedTime;
eventData.pressPosition = m_ImplementationData.pressedPosition;
eventData.pointerPressRaycast = m_ImplementationData.pressedRaycast;
eventData.pointerPress = m_ImplementationData.pressedGameObject;
eventData.rawPointerPress = m_ImplementationData.pressedGameObjectRaw;
eventData.pointerDrag = m_ImplementationData.draggedGameObject;
}
/// <summary>
/// Fills this object with the values from a <see cref="PointerEventData"/>.
/// </summary>
/// <param name="eventData">These objects are used to send data through the Unity UI (UGUI) system.</param>
public void CopyFrom(PointerEventData eventData)
{
m_ImplementationData.isDragging = eventData.dragging;
m_ImplementationData.pressedTime = eventData.clickTime;
m_ImplementationData.pressedPosition = eventData.pressPosition;
m_ImplementationData.pressedRaycast = eventData.pointerPressRaycast;
m_ImplementationData.pressedGameObject = eventData.pointerPress;
m_ImplementationData.pressedGameObjectRaw = eventData.rawPointerPress;
m_ImplementationData.draggedGameObject = eventData.pointerDrag;
}
bool m_IsDown;
ImplementationData m_ImplementationData;
}
struct MouseModel
{
internal struct InternalData
{
/// <summary>
/// This tracks the current GUI targets being hovered over.
/// </summary>
/// <seealso cref="PointerEventData.hovered"/>
public List<GameObject> hoverTargets { get; set; }
/// <summary>
/// Tracks the current enter/exit target being hovered over at any given moment.
/// </summary>
/// <seealso cref="PointerEventData.pointerEnter"/>
public GameObject pointerTarget { get; set; }
public void Reset()
{
pointerTarget = null;
if (hoverTargets == null)
hoverTargets = new List<GameObject>();
else
hoverTargets.Clear();
}
}
/// <summary>
/// An Id representing a unique pointer.
/// </summary>
public int pointerId { get; }
/// <summary>
/// A boolean value representing whether any mouse data has changed this frame, meaning that events should be processed.
/// </summary>
/// <remarks>
/// This only checks for changes in mouse state (<see cref="position"/>, <see cref="leftButton"/>, <see cref="rightButton"/>, <see cref="middleButton"/>, or <see cref="scrollDelta"/>).
/// </remarks>
public bool changedThisFrame { get; private set; }
Vector2 m_Position;
public Vector2 position
{
get => m_Position;
set
{
if (m_Position != value)
{
deltaPosition = value - m_Position;
m_Position = value;
changedThisFrame = true;
}
}
}
/// <summary>
/// The pixel-space change in <see cref="position"/> since the last call to <see cref="OnFrameFinished"/>.
/// </summary>
public Vector2 deltaPosition { get; private set; }
Vector2 m_ScrollDelta;
/// <summary>
/// The amount of scroll since the last call to <see cref="OnFrameFinished"/>.
/// </summary>
public Vector2 scrollDelta
{
get => m_ScrollDelta;
set
{
if (m_ScrollDelta != value)
{
m_ScrollDelta = value;
changedThisFrame = true;
}
}
}
MouseButtonModel m_LeftButton;
/// <summary>
/// Cached data and button state representing a left mouse button on a mouse.
/// Used by Unity UI (UGUI) to keep track of persistent click, press, and drag states.
/// </summary>
public MouseButtonModel leftButton
{
get => m_LeftButton;
set
{
changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
m_LeftButton = value;
}
}
/// <summary>
/// Sets the pressed state of the left mouse button.
/// </summary>
public bool leftButtonPressed
{
set
{
changedThisFrame |= m_LeftButton.isDown != value;
m_LeftButton.isDown = value;
}
}
MouseButtonModel m_RightButton;
/// <summary>
/// Cached data and button state representing a right mouse button on a mouse.
/// Unity UI (UGUI) uses this to keep track of persistent click, press, and drag states.
/// </summary>
public MouseButtonModel rightButton
{
get => m_RightButton;
set
{
changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
m_RightButton = value;
}
}
/// <summary>
/// Sets the pressed state of the right mouse button.
/// </summary>
public bool rightButtonPressed
{
set
{
changedThisFrame |= m_RightButton.isDown != value;
m_RightButton.isDown = value;
}
}
MouseButtonModel m_MiddleButton;
/// <summary>
/// Cached data and button state representing a middle mouse button on a mouse.
/// Used by Unity UI (UGUI) to keep track of persistent click, press, and drag states.
/// </summary>
public MouseButtonModel middleButton
{
get => m_MiddleButton;
set
{
changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
m_MiddleButton = value;
}
}
/// <summary>
/// Sets the pressed state of the middle mouse button.
/// </summary>
public bool middleButtonPressed
{
set
{
changedThisFrame |= m_MiddleButton.isDown != value;
m_MiddleButton.isDown = value;
}
}
public int displayIndex
{
get => m_DisplayIndex;
set
{
if (m_DisplayIndex != value)
{
m_DisplayIndex = value;
changedThisFrame = true;
}
}
}
private int m_DisplayIndex;
InternalData m_InternalData;
public MouseModel(int pointerId)
{
this.pointerId = pointerId;
changedThisFrame = false;
m_Position = Vector2.zero;
deltaPosition = Vector2.zero;
m_ScrollDelta = Vector2.zero;
m_LeftButton = new MouseButtonModel();
m_RightButton = new MouseButtonModel();
m_MiddleButton = new MouseButtonModel();
m_LeftButton.Reset();
m_RightButton.Reset();
m_MiddleButton.Reset();
m_DisplayIndex = 0;
m_InternalData = new InternalData();
m_InternalData.Reset();
}
/// <summary>
/// Call this at the end of polling for per-frame changes. This resets delta values, such as <see cref="deltaPosition"/>, <see cref="scrollDelta"/>, and <see cref="MouseButtonModel.lastFrameDelta"/>.
/// </summary>
public void OnFrameFinished()
{
changedThisFrame = false;
deltaPosition = Vector2.zero;
m_DisplayIndex = 0;
m_ScrollDelta = Vector2.zero;
m_LeftButton.OnFrameFinished();
m_RightButton.OnFrameFinished();
m_MiddleButton.OnFrameFinished();
}
public void CopyTo(PointerEventData eventData)
{
eventData.pointerId = pointerId;
eventData.position = position;
eventData.delta = deltaPosition;
eventData.scrollDelta = scrollDelta;
eventData.pointerEnter = m_InternalData.pointerTarget;
eventData.hovered.Clear();
eventData.hovered.AddRange(m_InternalData.hoverTargets);
#if UNITY_2022_3_OR_NEWER
eventData.displayIndex = displayIndex;
#endif
}
public void CopyFrom(PointerEventData eventData)
{
var hoverTargets = m_InternalData.hoverTargets;
m_InternalData.hoverTargets.Clear();
m_InternalData.hoverTargets.AddRange(eventData.hovered);
m_InternalData.hoverTargets = hoverTargets;
m_InternalData.pointerTarget = eventData.pointerEnter;
#if UNITY_2022_3_OR_NEWER
m_DisplayIndex = eventData.displayIndex;
#endif
}
}
}