sdl2-compat: Accumulate scaled relative motion

From 033474e4b02c19e44afe9274b6e756d124e3455c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 17 Nov 2025 21:57:55 -0800
Subject: [PATCH] Accumulate scaled relative motion

Fixes https://github.com/libsdl-org/sdl2-compat/issues/466
---
 src/sdl2_compat.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index ad9334e..f057cc4 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -1231,6 +1231,8 @@ static SDL_mutex *EventWatchListMutex = NULL;
 static SDL2_LogOutputFunction LogOutputFunction2 = NULL;
 static EventFilterWrapperData *EventWatchers2 = NULL;
 static SDL2_bool relative_mouse_mode = SDL2_FALSE;
+static float residual_motion_x = 0.0f;
+static float residual_motion_y = 0.0f;
 static SDL_JoystickID *joystick_instance_list = NULL;
 static int num_joystick_instances = 0;
 static SDL_JoystickID *joystick_list = NULL;
@@ -2283,6 +2285,11 @@ static SDL2_Event *Event3to2(const SDL_Event *event3, SDL2_Event *event2)
     case SDL_EVENT_DROP_COMPLETE:
         event2->drop.windowID = event3->drop.windowID;
         break;
+    case SDL_EVENT_WINDOW_MOUSE_ENTER:
+        /* Reset accumulated fractional mouse data when mouse focus changes */
+        residual_motion_x = 0.0f;
+        residual_motion_y = 0.0f;
+        break;
     case SDL_EVENT_MOUSE_MOTION:
         renderer = SDL3_GetRenderer(SDL3_GetWindowFromID(event3->motion.windowID));
         if (renderer) {
@@ -2291,7 +2298,11 @@ static SDL2_Event *Event3to2(const SDL_Event *event3, SDL2_Event *event2)
             if (mode != SDL_LOGICAL_PRESENTATION_DISABLED) {
                 SDL3_memcpy(&cvtevent3, event3, sizeof (SDL_Event));
                 SDL3_ConvertEventToRenderCoordinates(renderer, &cvtevent3);
-                if (!SDL3_GetBooleanProperty(SDL3_GetRendererProperties(renderer), PROP_RENDERER_RELATIVE_SCALING, true)) {
+                if (SDL3_GetBooleanProperty(SDL3_GetRendererProperties(renderer), PROP_RENDERER_RELATIVE_SCALING, true)) {
+                    /* Accumulate scaled relative motion */
+                    residual_motion_x = SDL3_modff(residual_motion_x + cvtevent3.motion.xrel, &cvtevent3.motion.xrel);
+                    residual_motion_y = SDL3_modff(residual_motion_y + cvtevent3.motion.yrel, &cvtevent3.motion.yrel);
+                } else {
                     /* Undo the relative scaling that SDL_ConvertEventToRenderCoordinates() performed */
                     cvtevent3.motion.xrel = event3->motion.xrel;
                     cvtevent3.motion.yrel = event3->motion.yrel;
@@ -6979,6 +6990,9 @@ static void PostInitSubsystem(SDL_InitFlags new_flags)
         /* These are potentially noisy and have no SDL2 equivalent */
         SDL3_SetEventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE, false);
         SDL3_SetEventEnabled(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE, false);
+
+        residual_motion_x = 0.0f;
+        residual_motion_y = 0.0f;
     }
 
     if (new_flags & SDL_INIT_VIDEO) {