Skip to content

Fix macOS and high-DPI menu cursor boundaries and sticking - #173

Open
jm2 wants to merge 1 commit into
themuffinator:mainfrom
jm2:fix/macos-mouse-and-cursor-boundary
Open

jm2 wants to merge 1 commit into
themuffinator:mainfrom
jm2:fix/macos-mouse-and-cursor-boundary

Conversation

@jm2

@jm2 jm2 commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #8

Problem

On macOS and high-DPI/Retina displays, menu mouse interaction suffered from two related issues:

  1. Relative mouse deltas accumulated error and clamping mismatch against window boundaries, causing the menu cursor to stick against edges or hesitate when changing directions.
  2. In widescreen aspect ratios (e.g. 16:9, 16:10), the cursor was constrained to a virtual 4:3 boundary box, preventing interaction with UI elements located on the outer wings/edges of the screen.

Solution

  • Update SDL3 mouse routing in src/sys/sdl3/sdl3_backend.cpp to track the absolute window mouse position 1:1 for active GUIs and console, eliminating relative delta drift and cursor sticking.
  • Add forceAspectCorrect support to idDeviceContext::GetVirtualScreenExpansion() and utilize it in idUserInterfaceLocal::ClampCursor(), ensuring the full widescreen virtual canvas bounds are correctly preserved across all menus.

Validation

  • Verified on macOS (Retina/high-DPI) in fullscreen and windowed modes across standard and widescreen aspect ratios.
  • Confirmed full reachability to all corners and interactive elements without sticking or boundary walls.
  • Input parity tests (sdl3_input_parity.py, ui_cursor_state_safety.py) pass cleanly.

- Switch menu mouse routing in SDL3 backend to direct 1:1 active GUI and console cursor updates, eliminating relative delta drift and invisible wall boundary stalls.
- Add forceAspectCorrect support to idDeviceContext::GetVirtualScreenExpansion and pass it in idUserInterfaceLocal::ClampCursor so widescreen aspect expansion is consistently preserved across menu screens.
Repository owner deleted a comment from chatgpt-codex-connector Bot Sep 21, 2026

@themuffinator themuffinator left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core idea — drive the menu cursor from the absolute window position instead of accumulating relative deltas — is the right shape, and I would like this to land. But as written it regresses the controller/mouse handoff, and a couple of other things need tightening first.

Blocking: SE_MOUSE 0, 0 breaks the controller-to-mouse handoff

idWindow::HandleEvent decides that the player has gone back to the mouse by looking for a non-zero motion delta:

const bool mouseMoved = event->evType == SE_MOUSE && ( event->evValue != 0 || event->evValue2 != 0 );

(src/ui/Window.cpp:1215). When mouseMoved or mousePressed is seen while gui->ControllerNavigation() is set, the menu clears controller focus and hands the pointer back.

This PR queues Sys_QueEvent( eventTime, SE_MOUSE, 0, 0, 0, NULL ) for every routed motion, so mouseMoved is now always false. Once the player touches a controller, moving the mouse can no longer reclaim focus — which is exactly the behaviour reported in #145 and fixed in #152. A click still recovers it, so it will look intermittent rather than broken.

If the cursor position is being set directly, the event still has to carry the fact that the mouse moved. Either keep a real delta in the event while using the absolute position for placement, or give idWindow an explicit signal for "absolute cursor move" rather than overloading a zero delta, which Window.cpp already reads as "did not move".

Please also address

  1. SDL3_UpdateRoutedMouseDelta is now dead. [[maybe_unused]] hides it rather than resolving it. If the relative path is gone, delete the function and its tracking state; if it is still needed for gameplay, say where.
  2. Console and GUI now both receive the cursor. Previously the console took priority and returned. Both are now updated in the same call, and in SDL3_SyncSystemMouseToActiveCursor the Windows branch no longer returns after SDL_WarpMouseInWindow, so the console block that follows uses the pre-warp windowMouseX/windowMouseY. That is a real behaviour change for console-over-menu; please make it deliberate and comment it.
  3. GetVirtualScreenExpansion's new flag. The only caller passes ui_aspectCorrection.GetBool() as forceAspectCorrect from inside a block already guarded by ui_aspectCorrection.GetBool(), so the argument is always true and the parameter never varies. If the rule is "cursor clamping always uses the full widescreen canvas, regardless of the device context's current aspect mode", write that rule directly — a defaulted bool plus a second overload is a lot of surface for one fixed behaviour.
  4. Evidence. This is a change to shared input routing on every platform for a symptom seen on macOS/Retina. A short before/after on Windows, windowed and fullscreen, at 16:9 and 4:3 — reaching all four corners and the outer-wing controls, plus a controller-then-mouse handoff check — would let me merge it without an Apple machine.

The widescreen clamp half (reaching controls on the outer wings) is a genuine bug and I am happy to take that on its own if you want to split it out; it is independent of the delta-versus-absolute question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants