Mouse and Touch Screen (Cursor)
Introduction
Gum supports reading from the mouse and touch screen for events. Both the mouse and touch screen report their actions through the Cursor class. Controls which respond to click events (such as Button and CheckBox) automatically read from the Cursor.
Runtime Compatibility
Touch support varies by runtime. Where a runtime has no real touch API, touch is mouse-emulated instead.
raylib
Partial
Single tap may not register as a click; a longer tap or a second tap works. Selection-style interactions (e.g. list items) work fine.
Silk.NET
Full
โ
MonoGame / KNI (DesktopGL)
Mouse-emulated
No real touch API on Windows; see note below.
raylib's default desktop backend (GLFW) has no real touch input. See raylib's own source comment acknowledging this. raylib's SDL backend does support real touch, but isn't what Gum's raylib runtime currently uses.
A Windows touchscreen cannot be used to test InputDevice.TouchScreen-specific behavior on MonoGame or KNI's DesktopGL backend. Tapping the screen still moves the cursor and registers clicks and drags, because Windows silently promotes untranslated touch input into synthetic mouse messages for apps that haven't opted into raw touch handling; DesktopGL's underlying SDL2 layer never wires up real touch events at all. That promoted input arrives as ordinary mouse input, so Cursor.LastInputDevice reports InputDevice.Mouse, never InputDevice.TouchScreen. MonoGame's WindowsDX backend does populate TouchPanel, but from a single mouse-derived point, not independent hardware touch tracking, so it has the same limitation.
Code that only reacts when LastInputDevice == InputDevice.TouchScreen will look correct in a Windows test (since the mouse-emulated input still drives clicks and drags) while remaining untested for real touch. Verifying that code path requires a platform whose windowing layer genuinely captures touch, such as Android or iOS.
Code Example: Accessing the Cursor
GumService contains an instance of the Cursor class. The following code shows how to access the Cursor class and create rectangles when the Cursor detects a click.
protected override void Update(GameTime gameTime)
{
GumUI.Update(gameTime);
Cursor cursor = GumUI.Cursor;
if(cursor.PrimaryClick)
{
ColoredRectangleRuntime rectangle = new ();
rectangle.AddToRoot();
rectangle.X = cursor.XRespectingGumZoomAndBounds();
rectangle.Y = cursor.YRespectingGumZoomAndBounds();
rectangle.Color = Color.Red;
}
base.Update(gameTime);
}
Cursor clicksChecking Control Over
The Cursor class reports information about what it is over which can be checked in events or in an Update call.
FrameworkElementOver
The FrameworkElementOver property returns the FrameworkElement (control) that the Cursor is over. The following code shows how to detect and display the control that the button is over.

VisualOver
The Cursor class also provides a VisualOver property. This can be used to determine the specific Visual that the cursor is over. A Visual will only be returned if the following are true:
The Visual's HasEvents is set to true
The Visual inherits from InteractiveGue
If a VisualOver is not null, then that visual consumes cursor events. If that visual happens to be the Visual for a FrameworkElement, then it passes those events to FrameworkElement. If the Visual is either not part of a FrameworkElement, or if it is a child of a FrameworkElement, then it will consume events, preventing the FrameworkElement itself from receiving events.
For information on which controls support events, see the Visual Events page.
HasCursorOver
GraphicalUiElement instances can be manually checked for whether the cursor is overlapping them. HasCursorOver method is a pure bounds check - it does not check if the GraphicalUiElement inherits from InteractiveGue, nor does it perform overlapping tests. This can be used for pure hit-tests.
The following code creates a rectangle and checks if the cursor is overlapping the rectangle:
Adjusting the Cursor for Scaled or Offset Rendering
If your game draws Gum's output through a RenderTarget2D, scales it with a SpriteBatch matrix, or otherwise transforms the rendered UI, the raw cursor position no longer lines up with what the player sees. Gum offers two ways to compensate, depending on whether all of your UI shares one coordinate space or several coexist in the same frame.
One coordinate space: Cursor.TransformMatrix
When every Gum visual is transformed the same way (for example, the entire UI is drawn into one render target and scaled to fit the window), set a single Cursor.TransformMatrix. It is applied once to the cursor position before any hit-testing, so clicks land on the visual under the mouse. See TransformMatrix for a full example.
Several coordinate spaces at once: HitTestTransformMatrix
A single cursor transform cannot serve two coordinate spaces at the same time. A common case is game UI drawn into a scaled RenderTarget2D while a separate editor or debug UI is drawn straight to the window at 1:1, with both clickable in the same frame. For this, set HitTestTransformMatrix on the root of each subtree that needs its own mapping.
HitTestTransformMatrix is a System.Numerics.Matrix3x2? on GraphicalUiElement. It is resolved by climbing to the nearest ancestor that set one, so setting it on a container covers that whole subtree, and content drawn at 1:1 simply leaves it unset (the default). Set it to the matrix that maps a raw window pixel back into the space the subtree was drawn in, which is the inverse of the scale and offset used to blit the render target. It affects hit-testing only and never changes rendering or layout.
Draw gameUi into the render target and blit it to the window just as in the TransformMatrix example, using blitOffset and blitScale for the blit destination. Any UI drawn straight to the window stays at 1:1 with no transform, so both are clickable in the same frame. Check hits with the HasCursorOver(ICursor) overload, for example gameUi.HasCursorOver(GumUI.Cursor).
Combining Root with additional interactive roots
The HasCursorOver check above is a raw bounds test โ fine for reacting to a click yourself, but it does not run hover, push, click, or drag for Forms controls (Button, Window, etc.) inside gameUi. Those only run through GumService.Default.Update(gameTime, roots). If gameUi is not part of Root but still needs real Forms interactivity, pass it alongside Root in the same call:
Call Update at most once per frame. Cursor detects a press or release by comparing this frame's raw mouse state to a snapshot taken the last time Update ran. Calling Update a second time in the same frame โ for example GumUI.Update(gameTime) for Root, then a separate GumUI.Update(gameTime, otherRoots) for a second group โ means the second call's "previous frame" snapshot is the state the first call just wrote, so no press/release edge is ever seen. Hover still works (it only checks the cursor's current position), but Push and Click silently never fire. Combine every root that needs interactivity into one list and call Update once.
For a runnable project with both coordinate spaces interactive in the same frame, see the RenderTarget screen in the Gum immediate-mode sample:
Disabling the Cursor Globally
The Cursor instance reported by GumService can be replaced with a custom implementation of the ICursor interface. A custom ICursor class can be created to modify its behavior. For example, the following implementation disables all behavior:
Future versions of Gum are likely to change the ICursor class by adding or renaming properties. Be aware that any custom ICursor implementation may need to be adjusted in response to these changes.
This DisabledCursor class can be assigned to disable all Cursor actions as shown in the following code:

DisabledCursor used to prevent UI interactionLast updated
Was this helpful?

