SDL: mouse: Add internal integer mouse mode hint for sdl2-compat

From 597bfe6b2708e218161bd22d2b30020167d5c32b Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Sat, 8 Mar 2025 20:23:21 -0600
Subject: [PATCH] mouse: Add internal integer mouse mode hint for sdl2-compat

Performing this inside SDL3 dramatically simplifies sdl2-compat. See https://github.com/libsdl-org/sdl2-compat/issues/372.
---
 src/events/SDL_mouse.c   | 29 +++++++++++++++++++++++++++++
 src/events/SDL_mouse_c.h |  5 +++++
 2 files changed, 34 insertions(+)

diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index e2ce07ffd6e59..c891fdd956fdb 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -234,6 +234,13 @@ static void SDLCALL SDL_MouseRelativeCursorVisibleChanged(void *userdata, const
     SDL_SetCursor(NULL); // Update cursor visibility
 }
 
+static void SDLCALL SDL_MouseIntegerModeChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_Mouse *mouse = (SDL_Mouse *)userdata;
+
+    mouse->integer_mode = SDL_GetStringBoolean(hint, false);
+}
+
 // Public functions
 bool SDL_PreInitMouse(void)
 {
@@ -288,6 +295,9 @@ bool SDL_PreInitMouse(void)
     SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
+    SDL_AddHintCallback("SDL_MOUSE_INTEGER_MODE",
+                        SDL_MouseIntegerModeChanged, mouse);
+
     mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending
 
     mouse->cursor_shown = true;
@@ -725,12 +735,22 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL
                 y *= mouse->normal_speed_scale;
             }
         }
+        if (mouse->integer_mode) {
+            // Accumulate the fractional relative motion and only process the integer portion
+            mouse->xrel_frac = SDL_modff(mouse->xrel_frac + x, &x);
+            mouse->yrel_frac = SDL_modff(mouse->yrel_frac + y, &y);
+        }
         xrel = x;
         yrel = y;
         x = (mouse->last_x + xrel);
         y = (mouse->last_y + yrel);
         ConstrainMousePosition(mouse, window, &x, &y);
     } else {
+        if (mouse->integer_mode) {
+            // Discard the fractional component from absolute coordinates
+            x = SDL_truncf(x);
+            y = SDL_truncf(y);
+        }
         ConstrainMousePosition(mouse, window, &x, &y);
         if (mouse->has_position) {
             xrel = x - mouse->last_x;
@@ -1003,6 +1023,12 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI
         SDL_SetMouseFocus(window);
     }
 
+    // Accumulate fractional wheel motion if integer mode is enabled
+    if (mouse->integer_mode) {
+        mouse->wheel_x_frac = SDL_modff(mouse->wheel_x_frac + x, &x);
+        mouse->wheel_y_frac = SDL_modff(mouse->wheel_y_frac + y, &y);
+    }
+
     if (x == 0.0f && y == 0.0f) {
         return;
     }
@@ -1113,6 +1139,9 @@ void SDL_QuitMouse(void)
     SDL_RemoveHintCallback(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE,
                         SDL_MouseRelativeCursorVisibleChanged, mouse);
 
+    SDL_RemoveHintCallback("SDL_MOUSE_INTEGER_MODE",
+                        SDL_MouseIntegerModeChanged, mouse);
+
     for (int i = SDL_mouse_count; i--; ) {
         SDL_RemoveMouse(SDL_mice[i].instance_id, false);
     }
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index ee3e814739c1d..b2518b8879eb4 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -102,8 +102,13 @@ typedef struct
     float x_accu;
     float y_accu;
     float last_x, last_y; // the last reported x and y coordinates
+    float xrel_frac;
+    float yrel_frac;
+    float wheel_x_frac;
+    float wheel_y_frac;
     double click_motion_x;
     double click_motion_y;
+    bool integer_mode;
     bool has_position;
     bool relative_mode;
     bool relative_mode_warp_motion;