From 77dc1a929ab99e61ff9442179d5e3cc815f89984 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Thu, 2 Mar 2023 12:47:23 -0500
Subject: [PATCH] cocoa: Implement popup windows
---
src/video/cocoa/SDL_cocoavideo.m | 2 +
src/video/cocoa/SDL_cocoawindow.h | 1 +
src/video/cocoa/SDL_cocoawindow.m | 138 +++++++++++++++++++++++++-----
3 files changed, 119 insertions(+), 22 deletions(-)
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index d849c63320a0..48df62e18a2e 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -177,6 +177,8 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device)
device->free = Cocoa_DeleteDevice;
+ device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT;
+
return device;
}
}
diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h
index 1a593ce3c363..90463207113f 100644
--- a/src/video/cocoa/SDL_cocoawindow.h
+++ b/src/video/cocoa/SDL_cocoawindow.h
@@ -131,6 +131,7 @@ typedef enum
@property(nonatomic) SDL_bool inWindowFullscreenTransition;
@property(nonatomic) NSInteger window_number;
@property(nonatomic) NSInteger flash_request;
+@property(nonatomic) SDL_Window *keyboard_focus;
@property(nonatomic) Cocoa_WindowListener *listener;
@property(nonatomic) SDL_CocoaVideoData *videodata;
#if SDL_VIDEO_OPENGL_EGL
diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m
index 5d105f5baedd..02fd42096ccd 100644
--- a/src/video/cocoa/SDL_cocoawindow.m
+++ b/src/video/cocoa/SDL_cocoawindow.m
@@ -115,12 +115,22 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
- (BOOL)canBecomeKeyWindow
{
- return YES;
+ SDL_Window *window = [self findSDLWindow];
+ if (window && !SDL_WINDOW_IS_POPUP(window)) {
+ return YES;
+ } else {
+ return NO;
+ }
}
- (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
@@ -324,13 +334,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;
}
@@ -477,6 +491,21 @@ static void Cocoa_UpdateClipCursor(SDL_Window *window)
}
}
+static void Cocoa_SetKeyboardFocus(SDL_Window *window)
+{
+ SDL_Window *topmost = window;
+ SDL_CocoaWindowData* topmost_data;
+
+ /* Find the topmost parent */
+ while (topmost->parent != NULL) {
+ topmost = topmost->parent;
+ }
+
+ topmost_data = (__bridge SDL_CocoaWindowData *)topmost->driverdata;
+ topmost_data.keyboard_focus = window;
+ SDL_SetKeyboardFocus(window);
+}
+
@implementation Cocoa_WindowListener
- (void)listen:(SDL_CocoaWindowData *)data
@@ -785,6 +814,8 @@ - (void)windowDidMove:(NSNotification *)aNotification
ScheduleContextUpdates(_data);
+ /* Get the parent-relative coordinates for child windows. */
+ SDL_GlobalToRelativeForWindow(window, x, y, &x, &y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
}
@@ -820,6 +851,7 @@ - (void)windowDidResize:(NSNotification *)aNotification
/* The window can move during a resize event, such as when maximizing
or resizing from a corner */
+ SDL_GlobalToRelativeForWindow(window, x, y, &x, &y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y);
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h);
@@ -857,7 +889,7 @@ - (void)windowDidBecomeKey:(NSNotification *)aNotification
/* We're going to get keyboard events, since we're key. */
/* This needs to be done before restoring the relative mouse mode. */
- SDL_SetKeyboardFocus(window);
+ Cocoa_SetKeyboardFocus(_data.keyboard_focus ? _data.keyboard_focus : window);
if (mouse->relative_mode && !mouse->relative_mode_warp && ![self isMovingOrFocusClickPending]) {
mouse->SetRelativeMouseMode(SDL_TRUE);
@@ -1642,13 +1674,16 @@ static int SetupWindowData(_THIS, SDL_Window *window, NSWindow *nswindow, NSView
/* Fill in the SDL window with the window data */
{
+ int x, y;
NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]];
BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
ConvertNSRect([nswindow screen], fullscreen, &rect);
- window->x = (int)rect.origin.x;
- window->y = (int)rect.origin.y;
+ x = (int)rect.origin.x;
+ y = (int)rect.origin.y;
window->w = (int)rect.size.width;
window->h = (int)rect.size.height;
+
+ SDL_GlobalToRelativeForWindow(window, x, y, &window->x, &window->y);
}
/* Set up the listener after we create the view */
@@ -1691,9 +1726,22 @@ static int SetupWindowData(_THIS, SDL_Window *window, NSWindow *nswindow, NSView
window->flags &= ~SDL_WINDOW_MINIMIZED;
}
- if ([nswindow isKeyWindow]) {
- window->flags |= SDL_WINDOW_INPUT_FOCUS;
- SDL_SetKeyboardFocus(data.window);
+ if (!SDL_WINDOW_IS_POPUP(window)) {
+ if ([nswindow isKeyWindow]) {
+ window->flags |= SDL_WINDOW_INPUT_FOCUS;
+ Cocoa_SetKeyboardFocus(data.window);
+ }
+ } else {
+ NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->driverdata).nswindow;
+ [nsparent addChildWindow:nswindow ordered:NSWindowAbove];
+
+ if (window->flags & SDL_WINDOW_TOOLTIP) {
+ [nswindow setIgnoresMouseEvents:YES];
+ } else if (window->flags & SDL_WINDOW_POPUP_MENU) {
+ if (window->parent == SDL_GetKeyboardFocus()) {
+ Cocoa_SetKeyboardFocus(window);
+ }
+ }
}
/* SDL_CocoaWindowData will be holding a strong reference to the NSWindow, and
@@ -1718,23 +1766,21 @@ int Cocoa_CreateWindow(_THIS, SDL_Window *window)
@autoreleasepool {
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->driverdata;
NSWindow *nswindow;
- SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
+ int x, y;
NSRect rect;
BOOL fullscreen;
- SDL_Rect bounds;
NSUInteger style;
NSArray *screens = [NSScreen screens];
NSScreen *screen = nil;
SDLView *contentView;
BOOL highdpi;
- Cocoa_GetDisplayBounds(_this, display, &bounds);
- rect.origin.x = window->x;
- rect.origin.y = window->y;
+ SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y);
+ rect.origin.x = x;
+ rect.origin.y = y;
rect.size.width = window->w;
rect.size.height = window->h;
fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO;
- ConvertNSRect([screens objectAtIndex:0], fullscreen, &rect);
style = GetWindowStyle(window);
@@ -1751,6 +1797,22 @@ int Cocoa_CreateWindow(_THIS, SDL_Window *window)
}
}
+ /* Constrain the popup */
+ if (SDL_WINDOW_IS_POPUP(window)) {
+ NSRect bounds = [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([screens objectAtIndex:0], fullscreen, &rect);
+
@try {
nswindow = [[SDLWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
}
@@ -1910,16 +1972,34 @@ void Cocoa_SetWindowPosition(_THIS, SDL_Window *window)
{
@autoreleasepool {
SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->driverdata;
+ NSRect bounds;
NSWindow *nswindow = windata.nswindow;
NSRect rect;
BOOL fullscreen;
Uint64 moveHack;
+ int x, y;
- rect.origin.x = window->x;
- rect.origin.y = window->y;
+ SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y);
+ rect.origin.x = x;
+ rect.origin.y = y;
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)) {
+ 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;
@@ -2023,6 +2103,20 @@ void Cocoa_HideWindow(_THIS, SDL_Window *window)
NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->driverdata).nswindow;
[nswindow orderOut:nil];
+
+ /* Transfer keyboard focus back to the parent */
+ if (window->flags & SDL_WINDOW_POPUP_MENU) {
+ if (window == SDL_GetKeyboardFocus()) {
+ SDL_Window *new_focus = window->parent;
+
+ /* Find the highest level window that isn't being hidden or destroyed. */
+ while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) {
+ new_focus = new_focus->parent;
+ }
+
+ Cocoa_SetKeyboardFocus(new_focus);
+ }
+ }
}
}