VirtualMouseInput.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. using System;
  2. using UnityEngine.InputSystem.LowLevel;
  3. using UnityEngine.UI;
  4. ////TODO: respect cursor lock mode
  5. ////TODO: investigate how driving the HW cursor behaves when FPS drops low
  6. //// (also, maybe we can add support where we turn the gamepad mouse on and off automatically based on whether the system mouse is used)
  7. ////TODO: add support for acceleration
  8. ////TODO: automatically scale mouse speed to resolution such that it stays constant regardless of resolution
  9. ////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)
  10. ////REVIEW: should we default the SW cursor position to the center of the screen?
  11. ////REVIEW: consider this for inclusion directly in the input system
  12. namespace UnityEngine.InputSystem.UI
  13. {
  14. /// <summary>
  15. /// A component that creates a virtual <see cref="Mouse"/> device and drives its input from gamepad-style inputs. This effectively
  16. /// adds a software mouse cursor.
  17. /// </summary>
  18. /// <remarks>
  19. /// This component can be used with UIs that are designed for mouse input, i.e. need to be operated with a cursor.
  20. /// By hooking up the <see cref="InputAction"/>s of this component to gamepad input and directing <see cref="cursorTransform"/>
  21. /// to the UI transform of the cursor, you can use this component to drive an on-screen cursor.
  22. ///
  23. /// Note that this component does not actually trigger UI input itself. Instead, it creates a virtual <see cref="Mouse"/>
  24. /// device which can then be picked up elsewhere (such as by <see cref="InputSystemUIInputModule"/>) where mouse/pointer input
  25. /// is expected.
  26. ///
  27. /// Also note that if there is a <see cref="Mouse"/> added by the platform, it is not impacted by this component. More specifically,
  28. /// the system mouse cursor will not be moved or otherwise used by this component.
  29. /// </remarks>
  30. /// <seealso cref="Gamepad"/>
  31. /// <seealso cref="Mouse"/>
  32. [AddComponentMenu("Input/Virtual Mouse")]
  33. public class VirtualMouseInput : MonoBehaviour
  34. {
  35. /// <summary>
  36. /// Optional transform that will be updated to correspond to the current mouse position.
  37. /// </summary>
  38. /// <value>Transform to update with mouse position.</value>
  39. /// <remarks>
  40. /// This is useful for having a UI object that directly represents the mouse cursor. Simply add both the
  41. /// <c>VirtualMouseInput</c> component and an <a href="https://docs.unity3d.com/Manual/script-Image.html">Image</a>
  42. /// component and hook the <a href="https://docs.unity3d.com/ScriptReference/RectTransform.html">RectTransform</a>
  43. /// component for the UI object into here. The object as a whole will then follow the generated mouse cursor
  44. /// motion.
  45. /// </remarks>
  46. public RectTransform cursorTransform
  47. {
  48. get => m_CursorTransform;
  49. set => m_CursorTransform = value;
  50. }
  51. /// <summary>
  52. /// How many pixels per second the cursor travels in one axis when the respective axis from
  53. /// <see cref="stickAction"/> is 1.
  54. /// </summary>
  55. /// <value>Mouse speed in pixels per second.</value>
  56. public float cursorSpeed
  57. {
  58. get => m_CursorSpeed;
  59. set => m_CursorSpeed = value;
  60. }
  61. /// <summary>
  62. /// Determines which cursor representation to use. If this is set to <see cref="CursorMode.SoftwareCursor"/>
  63. /// (the default), then <see cref="cursorGraphic"/> and <see cref="cursorTransform"/> define a software cursor
  64. /// that is made to correspond to the position of <see cref="virtualMouse"/>. If this is set to <see
  65. /// cref="CursorMode.HardwareCursorIfAvailable"/> and there is a native <see cref="Mouse"/> device present,
  66. /// the component will take over that mouse device and disable it (so as for it to not also generate position
  67. /// updates). It will then use <see cref="Mouse.WarpCursorPosition"/> to move the system mouse cursor to
  68. /// correspond to the position of the <see cref="virtualMouse"/>. In this case, <see cref="cursorGraphic"/>
  69. /// will be disabled and <see cref="cursorTransform"/> will not be updated.
  70. /// </summary>
  71. /// <value>Whether the system mouse cursor (if present) should be made to correspond with the virtual mouse position.</value>
  72. /// <remarks>
  73. /// Note that regardless of which mode is used for the cursor, mouse input is expected to be picked up from <see cref="virtualMouse"/>.
  74. ///
  75. /// Note that if <see cref="CursorMode.HardwareCursorIfAvailable"/> is used, the software cursor is still used
  76. /// if no native <see cref="Mouse"/> device is present.
  77. /// </remarks>
  78. public CursorMode cursorMode
  79. {
  80. get => m_CursorMode;
  81. set
  82. {
  83. if (m_CursorMode == value)
  84. return;
  85. // If we're turning it off, make sure we re-enable the system mouse.
  86. if (m_CursorMode == CursorMode.HardwareCursorIfAvailable && m_SystemMouse != null)
  87. {
  88. InputSystem.EnableDevice(m_SystemMouse);
  89. m_SystemMouse = null;
  90. }
  91. m_CursorMode = value;
  92. if (m_CursorMode == CursorMode.HardwareCursorIfAvailable)
  93. TryEnableHardwareCursor();
  94. else if (m_CursorGraphic != null)
  95. m_CursorGraphic.enabled = true;
  96. }
  97. }
  98. /// <summary>
  99. /// The UI graphic element that represents the mouse cursor.
  100. /// </summary>
  101. /// <value>Graphic element for the software mouse cursor.</value>
  102. /// <remarks>
  103. /// If <see cref="cursorMode"/> is set to <see cref="CursorMode.HardwareCursorIfAvailable"/>, this graphic will
  104. /// be disabled.
  105. ///
  106. /// Also, this UI component implicitly determines the <c>Canvas</c> that defines the screen area for the cursor.
  107. /// The canvas that this graphic is on will be looked up using <c>GetComponentInParent</c> and then the <c>Canvas.pixelRect</c>
  108. /// of the canvas is used as the bounds for the cursor motion range.
  109. /// </remarks>
  110. /// <seealso cref="CursorMode.SoftwareCursor"/>
  111. public Graphic cursorGraphic
  112. {
  113. get => m_CursorGraphic;
  114. set
  115. {
  116. m_CursorGraphic = value;
  117. TryFindCanvas();
  118. }
  119. }
  120. /// <summary>
  121. /// Multiplier for values received from <see cref="scrollWheelAction"/>.
  122. /// </summary>
  123. /// <value>Multiplier for scroll values.</value>
  124. public float scrollSpeed
  125. {
  126. get => m_ScrollSpeed;
  127. set => m_ScrollSpeed = value;
  128. }
  129. /// <summary>
  130. /// The virtual mouse device that the component feeds with input.
  131. /// </summary>
  132. /// <value>Instance of virtual mouse or <c>null</c>.</value>
  133. /// <remarks>
  134. /// This is only initialized after the component has been enabled for the first time. Note that
  135. /// when subsequently disabling the component, the property will continue to return the mouse device
  136. /// but the device will not be added to the system while the component is not enabled.
  137. /// </remarks>
  138. public Mouse virtualMouse => m_VirtualMouse;
  139. /// <summary>
  140. /// The Vector2 stick input that drives the mouse cursor, i.e. <see cref="Mouse.position"/> on
  141. /// <see cref="virtualMouse"/> and the <a
  142. /// href="https://docs.unity3d.com/ScriptReference/RectTransform-anchoredPosition.html">anchoredPosition</a>
  143. /// on <see cref="cursorTransform"/> (if set).
  144. /// </summary>
  145. /// <value>Stick input that drives cursor position.</value>
  146. /// <remarks>
  147. /// This should normally be bound to controls such as <see cref="Gamepad.leftStick"/> and/or
  148. /// <see cref="Gamepad.rightStick"/>.
  149. /// </remarks>
  150. public InputActionProperty stickAction
  151. {
  152. get => m_StickAction;
  153. set => SetAction(ref m_StickAction, value);
  154. }
  155. /// <summary>
  156. /// Optional button input that determines when <see cref="Mouse.leftButton"/> is pressed on
  157. /// <see cref="virtualMouse"/>.
  158. /// </summary>
  159. /// <value>Input for <see cref="Mouse.leftButton"/>.</value>
  160. public InputActionProperty leftButtonAction
  161. {
  162. get => m_LeftButtonAction;
  163. set
  164. {
  165. if (m_ButtonActionTriggeredDelegate != null)
  166. SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, false);
  167. SetAction(ref m_LeftButtonAction, value);
  168. if (m_ButtonActionTriggeredDelegate != null)
  169. SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, true);
  170. }
  171. }
  172. /// <summary>
  173. /// Optional button input that determines when <see cref="Mouse.rightButton"/> is pressed on
  174. /// <see cref="virtualMouse"/>.
  175. /// </summary>
  176. /// <value>Input for <see cref="Mouse.rightButton"/>.</value>
  177. public InputActionProperty rightButtonAction
  178. {
  179. get => m_RightButtonAction;
  180. set
  181. {
  182. if (m_ButtonActionTriggeredDelegate != null)
  183. SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, false);
  184. SetAction(ref m_RightButtonAction, value);
  185. if (m_ButtonActionTriggeredDelegate != null)
  186. SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, true);
  187. }
  188. }
  189. /// <summary>
  190. /// Optional button input that determines when <see cref="Mouse.middleButton"/> is pressed on
  191. /// <see cref="virtualMouse"/>.
  192. /// </summary>
  193. /// <value>Input for <see cref="Mouse.middleButton"/>.</value>
  194. public InputActionProperty middleButtonAction
  195. {
  196. get => m_MiddleButtonAction;
  197. set
  198. {
  199. if (m_ButtonActionTriggeredDelegate != null)
  200. SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, false);
  201. SetAction(ref m_MiddleButtonAction, value);
  202. if (m_ButtonActionTriggeredDelegate != null)
  203. SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, true);
  204. }
  205. }
  206. /// <summary>
  207. /// Optional button input that determines when <see cref="Mouse.forwardButton"/> is pressed on
  208. /// <see cref="virtualMouse"/>.
  209. /// </summary>
  210. /// <value>Input for <see cref="Mouse.forwardButton"/>.</value>
  211. public InputActionProperty forwardButtonAction
  212. {
  213. get => m_ForwardButtonAction;
  214. set
  215. {
  216. if (m_ButtonActionTriggeredDelegate != null)
  217. SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, false);
  218. SetAction(ref m_ForwardButtonAction, value);
  219. if (m_ButtonActionTriggeredDelegate != null)
  220. SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, true);
  221. }
  222. }
  223. /// <summary>
  224. /// Optional button input that determines when <see cref="Mouse.forwardButton"/> is pressed on
  225. /// <see cref="virtualMouse"/>.
  226. /// </summary>
  227. /// <value>Input for <see cref="Mouse.forwardButton"/>.</value>
  228. public InputActionProperty backButtonAction
  229. {
  230. get => m_BackButtonAction;
  231. set
  232. {
  233. if (m_ButtonActionTriggeredDelegate != null)
  234. SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, false);
  235. SetAction(ref m_BackButtonAction, value);
  236. if (m_ButtonActionTriggeredDelegate != null)
  237. SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, true);
  238. }
  239. }
  240. /// <summary>
  241. /// Optional Vector2 value input that determines the value of <see cref="Mouse.scroll"/> on
  242. /// <see cref="virtualMouse"/>.
  243. /// </summary>
  244. /// <value>Input for <see cref="Mouse.scroll"/>.</value>
  245. /// <remarks>
  246. /// In case you want to only bind vertical scrolling, simply have a <see cref="Composites.Vector2Composite"/>
  247. /// with only <c>Up</c> and <c>Down</c> bound and <c>Left</c> and <c>Right</c> deleted or bound to nothing.
  248. /// </remarks>
  249. public InputActionProperty scrollWheelAction
  250. {
  251. get => m_ScrollWheelAction;
  252. set => SetAction(ref m_ScrollWheelAction, value);
  253. }
  254. protected void OnEnable()
  255. {
  256. // Hijack system mouse, if enabled.
  257. if (m_CursorMode == CursorMode.HardwareCursorIfAvailable)
  258. TryEnableHardwareCursor();
  259. // Add mouse device.
  260. if (m_VirtualMouse == null)
  261. m_VirtualMouse = (Mouse)InputSystem.AddDevice("VirtualMouse");
  262. else if (!m_VirtualMouse.added)
  263. InputSystem.AddDevice(m_VirtualMouse);
  264. // Set initial cursor position.
  265. if (m_CursorTransform != null)
  266. {
  267. var position = m_CursorTransform.anchoredPosition;
  268. InputState.Change(m_VirtualMouse.position, position);
  269. m_SystemMouse?.WarpCursorPosition(position);
  270. }
  271. // Hook into input update.
  272. if (m_AfterInputUpdateDelegate == null)
  273. m_AfterInputUpdateDelegate = OnAfterInputUpdate;
  274. InputSystem.onAfterUpdate += m_AfterInputUpdateDelegate;
  275. // Hook into actions.
  276. if (m_ButtonActionTriggeredDelegate == null)
  277. m_ButtonActionTriggeredDelegate = OnButtonActionTriggered;
  278. SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, true);
  279. SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, true);
  280. SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, true);
  281. SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, true);
  282. SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, true);
  283. // Enable actions.
  284. m_StickAction.action?.Enable();
  285. m_LeftButtonAction.action?.Enable();
  286. m_RightButtonAction.action?.Enable();
  287. m_MiddleButtonAction.action?.Enable();
  288. m_ForwardButtonAction.action?.Enable();
  289. m_BackButtonAction.action?.Enable();
  290. m_ScrollWheelAction.action?.Enable();
  291. }
  292. protected void OnDisable()
  293. {
  294. // Remove mouse device.
  295. if (m_VirtualMouse != null && m_VirtualMouse.added)
  296. InputSystem.RemoveDevice(m_VirtualMouse);
  297. // Let go of system mouse.
  298. if (m_SystemMouse != null)
  299. {
  300. InputSystem.EnableDevice(m_SystemMouse);
  301. m_SystemMouse = null;
  302. }
  303. // Remove ourselves from input update.
  304. if (m_AfterInputUpdateDelegate != null)
  305. InputSystem.onAfterUpdate -= m_AfterInputUpdateDelegate;
  306. // Disable actions.
  307. m_StickAction.action?.Disable();
  308. m_LeftButtonAction.action?.Disable();
  309. m_RightButtonAction.action?.Disable();
  310. m_MiddleButtonAction.action?.Disable();
  311. m_ForwardButtonAction.action?.Disable();
  312. m_BackButtonAction.action?.Disable();
  313. m_ScrollWheelAction.action?.Disable();
  314. // Unhock from actions.
  315. if (m_ButtonActionTriggeredDelegate != null)
  316. {
  317. SetActionCallback(m_LeftButtonAction, m_ButtonActionTriggeredDelegate, false);
  318. SetActionCallback(m_RightButtonAction, m_ButtonActionTriggeredDelegate, false);
  319. SetActionCallback(m_MiddleButtonAction, m_ButtonActionTriggeredDelegate, false);
  320. SetActionCallback(m_ForwardButtonAction, m_ButtonActionTriggeredDelegate, false);
  321. SetActionCallback(m_BackButtonAction, m_ButtonActionTriggeredDelegate, false);
  322. }
  323. m_LastTime = default;
  324. m_LastStickValue = default;
  325. }
  326. private void TryFindCanvas()
  327. {
  328. m_Canvas = m_CursorGraphic?.GetComponentInParent<Canvas>();
  329. }
  330. private void TryEnableHardwareCursor()
  331. {
  332. var devices = InputSystem.devices;
  333. for (var i = 0; i < devices.Count; ++i)
  334. {
  335. var device = devices[i];
  336. if (device.native && device is Mouse mouse)
  337. {
  338. m_SystemMouse = mouse;
  339. break;
  340. }
  341. }
  342. if (m_SystemMouse == null)
  343. {
  344. if (m_CursorGraphic != null)
  345. m_CursorGraphic.enabled = true;
  346. return;
  347. }
  348. InputSystem.DisableDevice(m_SystemMouse);
  349. // Sync position.
  350. if (m_VirtualMouse != null)
  351. m_SystemMouse.WarpCursorPosition(m_VirtualMouse.position.ReadValue());
  352. // Turn off mouse cursor image.
  353. if (m_CursorGraphic != null)
  354. m_CursorGraphic.enabled = false;
  355. }
  356. private void UpdateMotion()
  357. {
  358. if (m_VirtualMouse == null)
  359. return;
  360. // Read current stick value.
  361. var stickAction = m_StickAction.action;
  362. if (stickAction == null)
  363. return;
  364. var stickValue = stickAction.ReadValue<Vector2>();
  365. if (Mathf.Approximately(0, stickValue.x) && Mathf.Approximately(0, stickValue.y))
  366. {
  367. // Motion has stopped.
  368. m_LastTime = default;
  369. m_LastStickValue = default;
  370. }
  371. else
  372. {
  373. var currentTime = InputState.currentTime;
  374. if (Mathf.Approximately(0, m_LastStickValue.x) && Mathf.Approximately(0, m_LastStickValue.y))
  375. {
  376. // Motion has started.
  377. m_LastTime = currentTime;
  378. }
  379. // Compute delta.
  380. var deltaTime = (float)(currentTime - m_LastTime);
  381. var delta = new Vector2(m_CursorSpeed * stickValue.x * deltaTime, m_CursorSpeed * stickValue.y * deltaTime);
  382. // Update position.
  383. var currentPosition = m_VirtualMouse.position.ReadValue();
  384. var newPosition = currentPosition + delta;
  385. ////REVIEW: for the hardware cursor, clamp to something else?
  386. // Clamp to canvas.
  387. if (m_Canvas != null)
  388. {
  389. // Clamp to canvas.
  390. var pixelRect = m_Canvas.pixelRect;
  391. newPosition.x = Mathf.Clamp(newPosition.x, pixelRect.xMin, pixelRect.xMax);
  392. newPosition.y = Mathf.Clamp(newPosition.y, pixelRect.yMin, pixelRect.yMax);
  393. }
  394. ////REVIEW: the fact we have no events on these means that actions won't have an event ID to go by; problem?
  395. InputState.Change(m_VirtualMouse.position, newPosition);
  396. InputState.Change(m_VirtualMouse.delta, delta);
  397. // Update software cursor transform, if any.
  398. if (m_CursorTransform != null &&
  399. (m_CursorMode == CursorMode.SoftwareCursor ||
  400. (m_CursorMode == CursorMode.HardwareCursorIfAvailable && m_SystemMouse == null)))
  401. m_CursorTransform.anchoredPosition = newPosition;
  402. m_LastStickValue = stickValue;
  403. m_LastTime = currentTime;
  404. // Update hardware cursor.
  405. m_SystemMouse?.WarpCursorPosition(newPosition);
  406. }
  407. // Update scroll wheel.
  408. var scrollAction = m_ScrollWheelAction.action;
  409. if (scrollAction != null)
  410. {
  411. var scrollValue = scrollAction.ReadValue<Vector2>();
  412. scrollValue.x *= m_ScrollSpeed;
  413. scrollValue.y *= m_ScrollSpeed;
  414. InputState.Change(m_VirtualMouse.scroll, scrollValue);
  415. }
  416. }
  417. [Header("Cursor")]
  418. [SerializeField] private CursorMode m_CursorMode;
  419. [SerializeField] private Graphic m_CursorGraphic;
  420. [SerializeField] private RectTransform m_CursorTransform;
  421. [Header("Motion")]
  422. [SerializeField] private float m_CursorSpeed = 400;
  423. [SerializeField] private float m_ScrollSpeed = 45;
  424. [Space(10)]
  425. [SerializeField] private InputActionProperty m_StickAction;
  426. [SerializeField] private InputActionProperty m_LeftButtonAction;
  427. [SerializeField] private InputActionProperty m_MiddleButtonAction;
  428. [SerializeField] private InputActionProperty m_RightButtonAction;
  429. [SerializeField] private InputActionProperty m_ForwardButtonAction;
  430. [SerializeField] private InputActionProperty m_BackButtonAction;
  431. [SerializeField] private InputActionProperty m_ScrollWheelAction;
  432. private Canvas m_Canvas; // Canvas that gives the motion range for the software cursor.
  433. private Mouse m_VirtualMouse;
  434. private Mouse m_SystemMouse;
  435. private Action m_AfterInputUpdateDelegate;
  436. private Action<InputAction.CallbackContext> m_ButtonActionTriggeredDelegate;
  437. private double m_LastTime;
  438. private Vector2 m_LastStickValue;
  439. private void OnButtonActionTriggered(InputAction.CallbackContext context)
  440. {
  441. if (m_VirtualMouse == null)
  442. return;
  443. // The button controls are bit controls. We can't (yet?) use InputState.Change to state
  444. // the change of those controls as the state update machinery of InputManager only supports
  445. // byte region updates. So we just grab the full state of our virtual mouse, then update
  446. // the button in there and then simply overwrite the entire state.
  447. var action = context.action;
  448. MouseButton? button = null;
  449. if (action == m_LeftButtonAction.action)
  450. button = MouseButton.Left;
  451. else if (action == m_RightButtonAction.action)
  452. button = MouseButton.Right;
  453. else if (action == m_MiddleButtonAction.action)
  454. button = MouseButton.Middle;
  455. else if (action == m_ForwardButtonAction.action)
  456. button = MouseButton.Forward;
  457. else if (action == m_BackButtonAction.action)
  458. button = MouseButton.Back;
  459. if (button != null)
  460. {
  461. var isPressed = context.control.IsPressed();
  462. m_VirtualMouse.CopyState<MouseState>(out var mouseState);
  463. mouseState.WithButton(button.Value, isPressed);
  464. InputState.Change(m_VirtualMouse, mouseState);
  465. }
  466. }
  467. private static void SetActionCallback(InputActionProperty field, Action<InputAction.CallbackContext> callback, bool install = true)
  468. {
  469. var action = field.action;
  470. if (action == null)
  471. return;
  472. // We don't need the performed callback as our mouse buttons are binary and thus
  473. // we only care about started (1) and canceled (0).
  474. if (install)
  475. {
  476. action.started += callback;
  477. action.canceled += callback;
  478. }
  479. else
  480. {
  481. action.started -= callback;
  482. action.canceled -= callback;
  483. }
  484. }
  485. private static void SetAction(ref InputActionProperty field, InputActionProperty value)
  486. {
  487. var oldValue = field;
  488. field = value;
  489. if (oldValue.reference == null)
  490. {
  491. var oldAction = oldValue.action;
  492. if (oldAction != null && oldAction.enabled)
  493. {
  494. oldAction.Disable();
  495. if (value.reference == null)
  496. value.action?.Enable();
  497. }
  498. }
  499. }
  500. private void OnAfterInputUpdate()
  501. {
  502. UpdateMotion();
  503. }
  504. /// <summary>
  505. /// Determines how the cursor for the virtual mouse is represented.
  506. /// </summary>
  507. /// <seealso cref="cursorMode"/>
  508. public enum CursorMode
  509. {
  510. /// <summary>
  511. /// The cursor is represented as a UI element. See <see cref="cursorGraphic"/>.
  512. /// </summary>
  513. SoftwareCursor,
  514. /// <summary>
  515. /// If a native <see cref="Mouse"/> device is present, its cursor will be used and driven
  516. /// by the virtual mouse using <see cref="Mouse.WarpCursorPosition"/>. The software cursor
  517. /// referenced by <see cref="cursorGraphic"/> will be disabled.
  518. ///
  519. /// Note that if no native <see cref="Mouse"/> is present, behavior will fall back to
  520. /// <see cref="SoftwareCursor"/>.
  521. /// </summary>
  522. HardwareCursorIfAvailable,
  523. }
  524. }
  525. }