diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp index da0ba2d7..9530f9e7 100644 --- a/src/core/Pad.cpp +++ b/src/core/Pad.cpp @@ -890,6 +890,11 @@ CMouseControllerState CMousePointerStateHelper::GetMouseSetUp() return state; } +#ifdef LIBRW_SDL2 +bool _InputMouseIsRelative(void); +void _InputConsumeMouseRelative(int *dx, int *dy); +#endif + void CPad::UpdateMouse() { #if defined RW_D3D9 || defined RWLIBS @@ -942,8 +947,13 @@ void CPad::UpdateMouse() double xpos = 1.0f, ypos; glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); #endif - if (xpos == 0.f) - return; + #ifdef LIBRW_SDL2 + if (!_InputMouseIsRelative() && xpos == 0) + return; + #else + if (xpos == 0.f) + return; + #endif int32 signX = 1; int32 signy = 1; @@ -958,8 +968,20 @@ void CPad::UpdateMouse() PCTempMouseControllerState.Clear(); - PCTempMouseControllerState.x = (float)(signX * (xpos - PSGLOBAL(lastMousePos.x))); - PCTempMouseControllerState.y = (float)(signy * (ypos - PSGLOBAL(lastMousePos.y))); + #ifdef LIBRW_SDL2 + if (_InputMouseIsRelative()) { + // true relative deltas from the motion events; polled positions are + // confined and stall at window/monitor edges + int dx, dy; + _InputConsumeMouseRelative(&dx, &dy); + PCTempMouseControllerState.x = (float)(signX * dx); + PCTempMouseControllerState.y = (float)(signy * dy); + } else + #endif + { + PCTempMouseControllerState.x = (float)(signX * (xpos - PSGLOBAL(lastMousePos.x))); + PCTempMouseControllerState.y = (float)(signy * (ypos - PSGLOBAL(lastMousePos.y))); + } #ifdef LIBRW_SDL2 PCTempMouseControllerState.LMB = !!(mouseState & SDL_BUTTON(1)); PCTempMouseControllerState.RMB = !!(mouseState & SDL_BUTTON(3)); diff --git a/src/skel/sdl2/sdl2.cpp b/src/skel/sdl2/sdl2.cpp index 6fe71c79..7b8bcbba 100644 --- a/src/skel/sdl2/sdl2.cpp +++ b/src/skel/sdl2/sdl2.cpp @@ -784,6 +784,24 @@ bool _InputMouseNeedsExclusive() return false; } +// Relative mouse support: the game derives deltas from polled positions, +// but confined positions stall at window/monitor edges. Accumulate the true +// deltas from the motion events instead; the game consumes them once per frame. +static bool mouseRelativeMode = false; +static int mouseRelX = 0, mouseRelY = 0; + +bool _InputMouseIsRelative(void) +{ + return mouseRelativeMode; +} + +void _InputConsumeMouseRelative(int *dx, int *dy) +{ + *dx = mouseRelX; + *dy = mouseRelY; + mouseRelX = mouseRelY = 0; +} + void psPostRWinit(void) { RwVideoMode vm; @@ -1268,9 +1286,12 @@ cursorEnterCB(int entered) { PSGLOBAL(cursorIsInWindow) = !!entered; } -void -windowFocusCB(int focused) { +void windowFocusCB(int focused) { WindowFocused = !!focused; + if (!focused) { + // deltas accumulated while unfocused must not fire on refocus + mouseRelX = mouseRelY = 0; + } } void @@ -1281,14 +1302,18 @@ windowIconifyCB(int iconified) { void inputEventHandler() { SDL_Event event; - if (SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: /* fall-through */ case SDL_KEYUP: keypressCB(event.key.keysym.sym, event.type, 0); break; - case SDL_MOUSEMOTION: cursorCB(event.motion.x, event.motion.y); break; + case SDL_MOUSEMOTION: + cursorCB(event.motion.x, event.motion.y); + mouseRelX += event.motion.xrel; + mouseRelY += event.motion.yrel; + break; case SDL_MOUSEWHEEL: scrollCB(event.wheel.x, event.wheel.y); break; // note that SDL_CONTROLLERDEVICEADDED/REMOVED exists, but it did not work for me @@ -1313,6 +1338,34 @@ void inputEventHandler() { break; } } + + // Grab the mouse during gameplay (GLFW_CURSOR_DISABLED equivalent): + // relative motion + pointer confinement, so the hidden cursor can't + // wander off to another monitor or stall at the window edge. + // The frontend needs absolute positions, so keep it free there. + bool wantRelative = gGameState == GS_PLAYING_GAME && + !FrontEndMenuManager.m_bMenuActive && !WindowIconified; + if (wantRelative != mouseRelativeMode) { + if (SDL_SetRelativeMouseMode(wantRelative ? SDL_TRUE : SDL_FALSE) == 0) { + mouseRelativeMode = wantRelative; + mouseRelX = mouseRelY = 0; + if (wantRelative) { + PSGLOBAL(cursorIsInWindow) = true; + } else { + int x, y; + SDL_GetMouseState(&x, &y); + PSGLOBAL(lastMousePos.x) = x; + PSGLOBAL(lastMousePos.y) = y; + } + } else { + // switch failed: never leave the menu with a grabbed pointer and + // garbage coordinates — release the grab and fall back to polling + SDL_SetWindowGrab(PSGLOBAL(window), SDL_FALSE); + SDL_ShowCursor(SDL_DISABLE); + mouseRelativeMode = false; + } + } + } /*