slimecing

a fighting game featuring slimes and swords
Log | Files | Refs | README

VirtualMouseInput.cs (25520B)


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