Use more stringent criteria for entering warp emulation mode

Require more than one warp to the window center within a certain timespan (currently 30ms, but can be tweaked) to better avoid erroneously entering warp emulation mode.

This also correctly resets the warp emulation mode activation if the window loses and regains focus.
This commit is contained in:
Frank Praznik 2024-08-06 12:36:26 -04:00 committed by Sam Lantinga
parent 1a57ea7fba
commit ae8065e1ec
4 changed files with 55 additions and 22 deletions

View File

@ -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:
*

View File

@ -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) {

View File

@ -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

View File

@ -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;
}