#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
{
///
/// A component that creates a virtual device and drives its input from gamepad-style inputs. This effectively
/// adds a software mouse cursor.
///
///
/// 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 s of this component to gamepad input and directing
/// 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
/// device which can then be picked up elsewhere (such as by ) where mouse/pointer input
/// is expected.
///
/// Also note that if there is a 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 .
///
///
///
[AddComponentMenu("Input/Virtual Mouse")]
[HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#virtual-mouse-cursor-control")]
public class VirtualMouseInput : MonoBehaviour
{
///
/// Optional transform that will be updated to correspond to the current mouse position.
///
/// Transform to update with mouse position.
///
/// This is useful for having a UI object that directly represents the mouse cursor. Simply add both the
/// VirtualMouseInput component and an Image
/// component and hook the RectTransform
/// component for the UI object into here. The object as a whole will then follow the generated mouse cursor
/// motion.
///
public RectTransform cursorTransform
{
get => m_CursorTransform;
set => m_CursorTransform = value;
}
///
/// How many pixels per second the cursor travels in one axis when the respective axis from
/// is 1.
///
/// Mouse speed in pixels per second.
public float cursorSpeed
{
get => m_CursorSpeed;
set => m_CursorSpeed = value;
}
///
/// Determines which cursor representation to use. If this is set to
/// (the default), then and define a software cursor
/// that is made to correspond to the position of . If this is set to and there is a native 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 to move the system mouse cursor to
/// correspond to the position of the . In this case,
/// will be disabled and will not be updated.
///
/// Whether the system mouse cursor (if present) should be made to correspond with the virtual mouse position.
///
/// Note that regardless of which mode is used for the cursor, mouse input is expected to be picked up from .
///
/// Note that if is used, the software cursor is still used
/// if no native device is present.
///
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;
}
}
///
/// The UI graphic element that represents the mouse cursor.
///
/// Graphic element for the software mouse cursor.
///
/// If is set to , this graphic will
/// be disabled.
///
/// Also, this UI component implicitly determines the Canvas that defines the screen area for the cursor.
/// The canvas that this graphic is on will be looked up using GetComponentInParent and then the Canvas.pixelRect
/// of the canvas is used as the bounds for the cursor motion range.
///
///
public Graphic cursorGraphic
{
get => m_CursorGraphic;
set
{
m_CursorGraphic = value;
TryFindCanvas();
}
}
///
/// Multiplier for values received from .
///
/// Multiplier for scroll values.
public float scrollSpeed
{
get => m_ScrollSpeed;
set => m_ScrollSpeed = value;
}
///
/// The virtual mouse device that the component feeds with input.
///
/// Instance of virtual mouse or null.
///
/// 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.
///
public Mouse virtualMouse => m_VirtualMouse;
///
/// The Vector2 stick input that drives the mouse cursor, i.e. on
/// and the anchoredPosition
/// on (if set).
///
/// Stick input that drives cursor position.
///
/// This should normally be bound to controls such as and/or
/// .
///
public InputActionProperty stickAction
{
get => m_StickAction;
set => SetAction(ref m_StickAction, value);
}
///
/// Optional button input that determines when is pressed on
/// .
///
/// Input for .
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);
}
}
///
/// Optional button input that determines when is pressed on
/// .
///
/// Input for .
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);
}
}
///
/// Optional button input that determines when is pressed on
/// .
///
/// Input for .
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);
}
}
///
/// Optional button input that determines when is pressed on
/// .
///
/// Input for .
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);
}
}
///
/// Optional button input that determines when is pressed on
/// .
///
/// Input for .
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);
}
}
///
/// Optional Vector2 value input that determines the value of on
/// .
///
/// Input for .
///
/// In case you want to only bind vertical scrolling, simply have a
/// with only Up and Down bound and Left and Right deleted or bound to nothing.
///
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