diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 96637815ea..1c20b7a0f6 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -2309,7 +2309,8 @@ extern "C" { * A variable controlling whether warping a hidden mouse cursor will activate * relative mouse mode. * - * When this hint is set and the mouse cursor is hidden, SDL will emulate + * When this hint is set, the mouse cursor is hidden, and multiple warps to + * the window center occur within a short time period, SDL will emulate * mouse warps using relative mouse mode. This can provide smoother and more * reliable mouse motion for some older games, which continuously calculate * the distance travelled by the mouse pointer and warp it back to the center @@ -2318,9 +2319,8 @@ extern "C" { * Note that relative mouse mode may have different mouse acceleration * behavior than pointer warps. * - * If your game or application needs to warp the mouse cursor while hidden for - * other purposes, such as drawing a software cursor, it should disable this - * hint. + * If your application needs to repeatedly warp the hidden mouse cursor at a + * high-frequency for other purposes, it should disable this hint. * * The variable can be set to the following values: * diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 7842f2a636..06ff779562 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -33,6 +33,8 @@ /* #define DEBUG_MOUSE */ +#define WARP_EMULATION_THRESHOLD_NS SDL_MS_TO_NS(30) + typedef struct SDL_MouseInstance { SDL_MouseID instance_id; @@ -1271,22 +1273,53 @@ void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, SDL_bool } } -static void SDL_EnableWarpEmulation(SDL_Mouse *mouse) +void SDL_DisableMouseWarpEmulation() { - if (!mouse->cursor_shown && mouse->warp_emulation_hint && !mouse->warp_emulation_prohibited) { - if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) { - mouse->warp_emulation_active = SDL_TRUE; + SDL_Mouse *mouse = SDL_GetMouse(); + + if (mouse->warp_emulation_active) { + SDL_SetRelativeMouseMode(SDL_FALSE); + } + + mouse->warp_emulation_prohibited = SDL_TRUE; +} + +static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_shown && !mouse->warp_emulation_active) { + if (!window) { + window = mouse->focus; } - /* Disable attempts at enabling warp emulation until further notice. */ - mouse->warp_emulation_prohibited = SDL_TRUE; + if (window) { + const float cx = window->w / 2.f; + const float cy = window->h / 2.f; + if (x >= SDL_floorf(cx) && x <= SDL_ceilf(cx) && + y >= SDL_floorf(cy) && y <= SDL_ceilf(cy)) { + + /* Require two consecutive warps to the center within a certain timespan to enter warp emulation mode. */ + const Uint64 now = SDL_GetTicksNS(); + if (now - mouse->last_center_warp_time_ns < WARP_EMULATION_THRESHOLD_NS) { + if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) { + mouse->warp_emulation_active = SDL_TRUE; + } + } + + mouse->last_center_warp_time_ns = now; + return; + } + } + + mouse->last_center_warp_time_ns = 0; } } void SDL_WarpMouseInWindow(SDL_Window *window, float x, float y) { SDL_Mouse *mouse = SDL_GetMouse(); - SDL_EnableWarpEmulation(mouse); + SDL_MaybeEnableWarpEmulation(window, x, y); SDL_PerformWarpMouseInWindow(window, x, y, mouse->warp_emulation_active); } @@ -1317,16 +1350,9 @@ int SDL_SetRelativeMouseMode(SDL_bool enabled) SDL_Mouse *mouse = SDL_GetMouse(); SDL_Window *focusWindow = SDL_GetKeyboardFocus(); - if (enabled) { - if (mouse->warp_emulation_active) { - mouse->warp_emulation_active = SDL_FALSE; - } - - /* If the app has used relative mode before, it probably shouldn't - * also be emulating it using repeated mouse warps, so disable - * mouse warp emulation by default. - */ - mouse->warp_emulation_prohibited = SDL_TRUE; + if (!enabled) { + /* If warps were being emulated, reset the flag. */ + mouse->warp_emulation_active = SDL_FALSE; } if (enabled == mouse->relative_mode) { @@ -1701,7 +1727,6 @@ int SDL_ShowCursor(void) if (mouse->warp_emulation_active) { SDL_SetRelativeMouseMode(SDL_FALSE); mouse->warp_emulation_active = SDL_FALSE; - mouse->warp_emulation_prohibited = SDL_FALSE; } if (!mouse->cursor_shown) { diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index fd6456f91f..3b8d89a432 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -97,6 +97,7 @@ typedef struct SDL_bool warp_emulation_hint; SDL_bool warp_emulation_active; SDL_bool warp_emulation_prohibited; + Uint64 last_center_warp_time_ns; int relative_mode_clip_interval; SDL_bool enable_normal_speed_scale; float normal_speed_scale; @@ -183,6 +184,7 @@ extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, S extern int SDL_SetRelativeMouseMode(SDL_bool enabled); extern SDL_bool SDL_GetRelativeMouseMode(void); extern void SDL_UpdateRelativeMouseMode(void); +extern void SDL_DisableMouseWarpEmulation(void); /* TODO RECONNECT: Set mouse state to "zero" */ #if 0 diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index cc2f70976b..f4fc4d286f 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -3786,6 +3786,12 @@ int SDL_SetWindowRelativeMouseMode(SDL_Window *window, SDL_bool enabled) { CHECK_WINDOW_MAGIC(window, -1); + /* If the app toggles relative mode directly, it probably shouldn't + * also be emulating it using repeated mouse warps, so disable + * mouse warp emulation by default. + */ + SDL_DisableMouseWarpEmulation(); + if (enabled == SDL_GetWindowRelativeMouseMode(window)) { return 0; }