SDL: cocoa: Implement Cocoa popup windows

From 205ae7ec9f2e0abbf93f1b9a97593d43a11c73c1 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 2 Mar 2023 12:25:43 -0500
Subject: [PATCH] cocoa: Implement Cocoa popup windows

---
 src/video/cocoa/SDL_cocoawindow.m | 115 ++++++++++++++++++++++++++++--
 1 file changed, 108 insertions(+), 7 deletions(-)

diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index b14cd3bbf623..0f159a67f602 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -119,7 +119,12 @@ - (BOOL)canBecomeKeyWindow
 
 - (BOOL)canBecomeMainWindow
 {
-    return YES;
+    SDL_Window *window = [self findSDLWindow];
+    if (window && !SDL_WINDOW_IS_POPUP(window)) {
+        return YES;
+    } else {
+        return NO;
+    }
 }
 
 - (void)sendEvent:(NSEvent *)event
@@ -308,13 +313,17 @@ the NSWindowStyleMaskBorderless comments in SetupWindowData()! */
        minimize the window, whether there's a title bar or not */
     NSUInteger style = NSWindowStyleMaskMiniaturizable;
 
-    if (window->flags & SDL_WINDOW_BORDERLESS) {
-        style |= NSWindowStyleMaskBorderless;
+    if (!SDL_WINDOW_IS_POPUP(window)) {
+        if (window->flags & SDL_WINDOW_BORDERLESS) {
+            style |= NSWindowStyleMaskBorderless;
+        } else {
+            style |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
+        }
+        if (window->flags & SDL_WINDOW_RESIZABLE) {
+            style |= NSWindowStyleMaskResizable;
+        }
     } else {
-        style |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
-    }
-    if (window->flags & SDL_WINDOW_RESIZABLE) {
-        style |= NSWindowStyleMaskResizable;
+        style |= NSWindowStyleMaskBorderless;
     }
     return style;
 }
@@ -461,6 +470,26 @@ static void Cocoa_UpdateClipCursor(SDL_Window *window)
     }
 }
 
+static void Cocoa_RestoreChildParameters(SDL_Window *window)
+{
+    if (SDL_WINDOW_IS_POPUP(window)) {
+        SDL_CocoaWindowData *windowData, *parentData;
+        SDL_Window *w;
+        
+        windowData = (__bridge SDL_CocoaWindowData *)window->driverdata;
+        parentData = (__bridge SDL_CocoaWindowData *)window->parent->driverdata;
+        [parentData.nswindow addChildWindow:windowData.nswindow ordered:NSWindowAbove];
+        
+        if (window->flags & SDL_WINDOW_TOOLTIP) {
+            [windowData.nswindow setIgnoresMouseEvents:YES];
+        }
+        
+        for (w = window->first_child; w != NULL; w = w->next_sibling) {
+            Cocoa_RestoreChildParameters(w);
+        }
+    }
+}
+
 @implementation Cocoa_WindowListener
 
 - (void)listen:(SDL_CocoaWindowData *)data
@@ -738,6 +767,7 @@ - (void)windowWillMove:(NSNotification *)aNotification
 - (void)windowDidMove:(NSNotification *)aNotification
 {
     int x, y;
+    SDL_Window *w;
     SDL_Window *window = _data.window;
     NSWindow *nswindow = _data.nswindow;
     BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
@@ -766,6 +796,12 @@ - (void)windowDidMove:(NSNotification *)aNotification
 
     x = (int)rect.origin.x;
     y = (int)rect.origin.y;
+    
+    /* Get the parent-relative coordinates for child windows. */
+    for (w = window->parent; w != NULL; w = w->parent) {
+        x -= w->x;
+        y -= w->y;
+    }
 
     ScheduleContextUpdates(_data);
 
@@ -1700,6 +1736,8 @@ int Cocoa_CreateWindow(_THIS, SDL_Window *window)
 {
     @autoreleasepool {
         SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->driverdata;
+        SDL_CocoaWindowData *windata;
+        SDL_Window *w;
         NSWindow *nswindow;
         SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
         NSRect rect;
@@ -1717,6 +1755,13 @@ int Cocoa_CreateWindow(_THIS, SDL_Window *window)
         rect.size.width = window->w;
         rect.size.height = window->h;
         fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
+        
+        if (window->parent) {
+            w = window->parent;
+            rect.origin.x += w->x;
+            rect.origin.y += w->y;
+        }
+        
         ConvertNSRect([screens objectAtIndex:0], fullscreen, &rect);
 
         style = GetWindowStyle(window);
@@ -1740,6 +1785,18 @@ int Cocoa_CreateWindow(_THIS, SDL_Window *window)
         @catch (NSException *e) {
             return SDL_SetError("%s", [[e reason] UTF8String]);
         }
+        
+        if (SDL_WINDOW_IS_POPUP(window)) {
+            windata = (__bridge SDL_CocoaWindowData *)window->parent->driverdata;
+            
+            [windata.nswindow addChildWindow:nswindow ordered:NSWindowAbove];
+            
+            if (window->flags & SDL_WINDOW_TOOLTIP) {
+                [nswindow setIgnoresMouseEvents:YES];
+            } else {
+                [nswindow makeKeyWindow];
+            }
+        }
 
         [nswindow setColorSpace:[NSColorSpace sRGBColorSpace]];
 
@@ -1893,7 +1950,9 @@ void Cocoa_SetWindowPosition(_THIS, SDL_Window *window)
 {
     @autoreleasepool {
         SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata;
+        NSRect bounds;
         NSWindow *nswindow = windata.nswindow;
+        SDL_Window *w;
         NSRect rect;
         BOOL fullscreen;
         Uint64 moveHack;
@@ -1903,6 +1962,26 @@ void Cocoa_SetWindowPosition(_THIS, SDL_Window *window)
         rect.size.width = window->w;
         rect.size.height = window->h;
         fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
+        
+        /* Position and constrain the popup */
+        if (SDL_WINDOW_IS_POPUP(window)) {
+            for (w = window->parent; w != NULL; w = w->parent) {
+                rect.origin.x += w->x;
+                rect.origin.y += w->y;
+            }
+            
+            bounds = [[nswindow screen] frame];
+            
+            if (rect.origin.x + rect.size.width > bounds.origin.x + bounds.size.width) {
+                rect.origin.x -= (rect.origin.x + rect.size.width) - (bounds.origin.x + bounds.size.width);
+            }
+            if (rect.origin.y + rect.size.height > bounds.origin.y + bounds.size.height) {
+                rect.origin.y -= (rect.origin.y + rect.size.height) - (bounds.origin.y + bounds.size.height);
+            }
+            rect.origin.x = SDL_max(rect.origin.x, bounds.origin.x);
+            rect.origin.y = SDL_max(rect.origin.y, bounds.origin.y);
+        }
+        
         ConvertNSRect([nswindow screen], fullscreen, &rect);
 
         moveHack = s_moveHack;
@@ -1997,6 +2076,28 @@ void Cocoa_ShowWindow(_THIS, SDL_Window *window)
             [nswindow makeKeyAndOrderFront:nil];
             [windowData.listener resumeVisibleObservation];
         }
+        
+        if (SDL_WINDOW_IS_POPUP(window)) {
+            SDL_Window *w;
+            
+            windowData = ((__bridge SDL_CocoaWindowData *)window->parent->driverdata);
+            [windowData.nswindow addChildWindow:nswindow ordered:NSWindowAbove];
+            
+            if (window->flags & SDL_WINDOW_TOOLTIP) {
+                [nswindow setIgnoresMouseEvents:YES];
+                
+                for (w = window->parent; w->parent != NULL; w = w->parent) {
+                    if (!(w->flags & SDL_WINDOW_TOOLTIP)) {
+                        break;
+                    }
+                }
+                    
+                windowData = ((__bridge SDL_CocoaWindowData *)w->driverdata);
+                [windowData.nswindow makeKeyWindow];
+            } else {
+                [nswindow makeKeyWindow];
+            }
+        }
     }
 }