Conversation
- 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.
themuffinator
left a comment
There was a problem hiding this comment.
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
SDL3_UpdateRoutedMouseDeltais 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.- 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_SyncSystemMouseToActiveCursorthe Windows branch no longer returns afterSDL_WarpMouseInWindow, so the console block that follows uses the pre-warpwindowMouseX/windowMouseY. That is a real behaviour change for console-over-menu; please make it deliberate and comment it. GetVirtualScreenExpansion's new flag. The only caller passesui_aspectCorrection.GetBool()asforceAspectCorrectfrom inside a block already guarded byui_aspectCorrection.GetBool(), so the argument is alwaystrueand 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.- 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.
Follow-up to #8
Problem
On macOS and high-DPI/Retina displays, menu mouse interaction suffered from two related issues:
Solution
src/sys/sdl3/sdl3_backend.cppto track the absolute window mouse position 1:1 for active GUIs and console, eliminating relative delta drift and cursor sticking.forceAspectCorrectsupport toidDeviceContext::GetVirtualScreenExpansion()and utilize it inidUserInterfaceLocal::ClampCursor(), ensuring the full widescreen virtual canvas bounds are correctly preserved across all menus.Validation
sdl3_input_parity.py,ui_cursor_state_safety.py) pass cleanly.