SDL: Implemented SDL_SetWindowMouseRect() on macOS

From 4db546b092e288caaf8c10188f0b82258bf5c08b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 8 Nov 2021 20:35:12 -0800
Subject: [PATCH] Implemented SDL_SetWindowMouseRect() on macOS

---
 src/video/cocoa/SDL_cocoavideo.m      |   1 +
 src/video/cocoa/SDL_cocoawindow.h     |   2 +
 src/video/cocoa/SDL_cocoawindow.m     | 131 +++++++++++++++++++-------
 src/video/windows/SDL_windowswindow.c |   2 +-
 src/video/windows/SDL_windowswindow.h |   2 +-
 5 files changed, 104 insertions(+), 34 deletions(-)

diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index 8f943b4b82..1e78a3127d 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -103,6 +103,7 @@
     device->SetWindowGammaRamp = Cocoa_SetWindowGammaRamp;
     device->GetWindowGammaRamp = Cocoa_GetWindowGammaRamp;
     device->GetWindowICCProfile = Cocoa_GetWindowICCProfile;
+    device->SetWindowMouseRect = Cocoa_SetWindowMouseRect;
     device->SetWindowMouseGrab = Cocoa_SetWindowMouseGrab;
     device->SetWindowKeyboardGrab = Cocoa_SetWindowKeyboardGrab;
     device->DestroyWindow = Cocoa_DestroyWindow;
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index 03034714d4..f7b3626aa4 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -124,6 +124,7 @@ struct SDL_WindowData
     SDL_bool created;
     SDL_bool inWindowFullscreenTransition;
     NSInteger flash_request;
+    SDL_Rect mouse_rect;
     Cocoa_WindowListener *listener;
     struct SDL_VideoData *videodata;
 #if SDL_VIDEO_OPENGL_EGL
@@ -154,6 +155,7 @@ extern void Cocoa_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDispl
 extern int Cocoa_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp);
 extern void* Cocoa_GetWindowICCProfile(_THIS, SDL_Window * window, size_t * size);
 extern int Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
+extern int Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect);
 extern void Cocoa_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void Cocoa_DestroyWindow(_THIS, SDL_Window * window);
 extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info);
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index b2dfb8ebb6..6f82109900 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -322,6 +322,67 @@ static void ConvertNSRect(NSScreen *screen, BOOL fullscreen, NSRect *r)
     return SDL_TRUE;
 }
 
+static SDL_bool
+ShouldAdjustCoordinatesForGrab(SDL_Window * window)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+
+    if (!data || [data->listener isMovingOrFocusClickPending]) {
+        return SDL_FALSE;
+    }
+
+    if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) {
+        return SDL_FALSE;
+    }
+
+    if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) || (data->mouse_rect.w > 0 && data->mouse_rect.h > 0)) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+static SDL_bool
+AdjustCoordinatesForGrab(SDL_Window * window, int x, int y, CGPoint *adjusted)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+
+    if (data->mouse_rect.w > 0 && data->mouse_rect.h > 0) {
+        SDL_Rect window_rect;
+        SDL_Rect mouse_rect;
+
+        window_rect.x = 0;
+        window_rect.y = 0;
+        window_rect.w = window->w;
+        window_rect.h = window->h;
+
+        if (SDL_IntersectRect(&data->mouse_rect, &window_rect, &mouse_rect)) {
+            int left = window->x + mouse_rect.x;
+            int right = left + mouse_rect.w - 1;
+            int top = window->y + mouse_rect.y;
+            int bottom = top + mouse_rect.h - 1;
+            if (x < left || x > right || y < top || y > bottom) {
+                adjusted->x = SDL_clamp(x, left, right);
+                adjusted->y = SDL_clamp(y, top, bottom);
+                return SDL_TRUE;
+            }
+            return SDL_FALSE;
+        }
+    }
+
+    if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) {
+        int left = window->x;
+        int right = left + window->w - 1;
+        int top = window->y;
+        int bottom = top + window->h - 1;
+        if (x < left || x > right || y < top || y > bottom) {
+            adjusted->x = SDL_clamp(x, left, right);
+            adjusted->y = SDL_clamp(y, top, bottom);
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
 
 @implementation Cocoa_WindowListener
 
@@ -1156,28 +1217,12 @@ - (void)mouseMoved:(NSEvent *)theEvent
     x = (int)point.x;
     y = (int)(window->h - point.y);
 
-    if (window->flags & SDL_WINDOW_MOUSE_GRABBED) {
-        if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
-            if (x < 0) {
-                x = 0;
-            } else if (x >= window->w) {
-                x = window->w - 1;
-            }
-            if (y < 0) {
-                y = 0;
-            } else if (y >= window->h) {
-                y = window->h - 1;
-            }
-
-            CGPoint cgpoint;
-            cgpoint.x = window->x + x;
-            cgpoint.y = window->y + y;
-
-            CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
-            CGAssociateMouseAndMouseCursorPosition(YES);
-
-            Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
-        }
+    CGPoint cgpoint;
+    if (ShouldAdjustCoordinatesForGrab(window) &&
+        AdjustCoordinatesForGrab(window, window->x + x, window->y + y, &cgpoint)) {
+        Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
+        CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
+        CGAssociateMouseAndMouseCursorPosition(YES);
     }
 
     SDL_SendMouseMotion(window, mouseID, 0, x, y);
@@ -2048,27 +2093,49 @@ - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
     return 0;
 }
 
+int
+Cocoa_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+
+    if (rect) {
+        SDL_memcpy(&data->mouse_rect, rect, sizeof(*rect));
+    } else {
+        SDL_zero(data->mouse_rect);
+    }
+
+    /* Move the cursor to the nearest point in the mouse rect */
+    if (ShouldAdjustCoordinatesForGrab(window)) {
+        int x, y;
+        CGPoint cgpoint;
+
+        SDL_GetGlobalMouseState(&x, &y);
+        if (AdjustCoordinatesForGrab(window, x, y, &cgpoint)) {
+            Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
+            CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
+        }
+    }
+    return 0;
+}
+
 void
 Cocoa_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed)
 {
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
 
     /* Move the cursor to the nearest point in the window */
-    if (grabbed && data && ![data->listener isMovingOrFocusClickPending]) {
+    if (ShouldAdjustCoordinatesForGrab(window)) {
         int x, y;
         CGPoint cgpoint;
 
-        SDL_GetMouseState(&x, &y);
-        cgpoint.x = window->x + x;
-        cgpoint.y = window->y + y;
-
-        Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
-
-        DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y);
-        CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
+        SDL_GetGlobalMouseState(&x, &y);
+        if (AdjustCoordinatesForGrab(window, x, y, &cgpoint)) {
+            Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y);
+            CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
+        }
     }
 
-    if ( data && (window->flags & SDL_WINDOW_FULLSCREEN) ) {
+    if (data && (window->flags & SDL_WINDOW_FULLSCREEN)) {
         if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS)
             && ![data->listener isInFullscreenSpace]) {
             /* OpenGL is rendering to the window, so make it visible! */
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 1448eb206a..2e1a52578d 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -815,7 +815,7 @@ void WIN_UngrabKeyboard(SDL_Window *window)
 }
 
 int
-WIN_SetWindowMouseRect(_THIS, SDL_Window * window, SDL_Rect * rect)
+WIN_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect)
 {
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
 
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index 19c6eb9648..3c341d5073 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -82,7 +82,7 @@ extern void WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay
 extern int WIN_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp);
 extern void* WIN_GetWindowICCProfile(_THIS, SDL_Window * window, size_t * size);
 extern int WIN_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
-extern int WIN_SetWindowMouseRect(_THIS, SDL_Window * window, SDL_Rect * rect);
+extern int WIN_SetWindowMouseRect(_THIS, SDL_Window * window, const SDL_Rect * rect);
 extern void WIN_SetWindowMouseGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void WIN_SetWindowKeyboardGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void WIN_DestroyWindow(_THIS, SDL_Window * window);