We are currently migrating Bugzilla to GitHub issues.
Any changes made to the bug tracker now will be lost, so please do not post new bugs or make changes to them.
When we're done, all bug URLs will redirect to their equivalent location on the new bug tracker.

Bug 5060

Summary: CreateWindowFrom on macOS doesn't work with sub-widgets, only top level window
Product: SDL Reporter: michaeljosephmaltese
Component: videoAssignee: Ryan C. Gordon <icculus>
Status: RESOLVED FIXED QA Contact: Sam Lantinga <slouken>
Severity: enhancement    
Priority: P2 CC: icculus, michaeljosephmaltese, sheppy, zebbey
Version: HG 2.1Keywords: target-2.0.14
Hardware: x86   
OS: Mac OS X (All)   
Attachments: [PATCH] cocoa: allow calling CreateWindowFrom on an NSView

Description michaeljosephmaltese 2020-03-25 23:51:29 UTC
On Windows and X11, we can use CreateWindowFrom on arbitrary native widgets (referenced with either HWND or the xid) to embed an SDL window inside a larger widget layout.

On macOS, CreateWindowFrom only supports being called with an NSWindow* object. It should support being called with NSView* objects as well.

This is useful for e.g. games, game editors, simulations, etc.
Comment 1 michaeljosephmaltese 2020-03-25 23:57:09 UTC
Created attachment 4280 [details]
[PATCH] cocoa: allow calling CreateWindowFrom on an NSView

Here's an attempt at allowing CreateWindowFrom to work with NSViews.

It expands the role of the existing sdlContentView field (caches [nswindow contentView] for created windows, since Cocoa complains if contentview is called off the main thread) to hold the NSView for non-created windows as well. All calls to [nswindow contentView] have been replaced with references to sdlContentView.

When an NSView is passed into CreateWindowFrom, it sets sdlContentView directly, and finds the associated window with [nsview window].

Tested with a basic Qt application, and with testdraw2 and testgl2 from the tests directory.
Comment 2 michaeljosephmaltese 2020-03-26 00:04:11 UTC
Comment on attachment 4280 [details]
[PATCH] cocoa: allow calling CreateWindowFrom on an NSView

# HG changeset patch
# User Michael Maltese <michaeljosephmaltese@gmail.com>
# Date 1585179643 25200
#      Wed Mar 25 16:40:43 2020 -0700
# Node ID aaec3f2027fc1efe5473bbd8477d5860f97487ff
# Parent  389ce8cfa2a31f765c3d751c911c73abf5c60e33
cocoa: allow calling CreateWindowFrom on an NSView

This lets applications embed SDL with other widgets surrounding it.
Already possible on Windows and X11.

diff -r 389ce8cfa2a3 -r aaec3f2027fc src/video/cocoa/SDL_cocoaopengl.m
--- a/src/video/cocoa/SDL_cocoaopengl.m	Wed Mar 25 09:38:45 2020 -0700
+++ b/src/video/cocoa/SDL_cocoaopengl.m	Wed Mar 25 16:40:43 2020 -0700
@@ -97,17 +97,6 @@
         SDL_WindowData *windowdata = (SDL_WindowData *)newWindow->driverdata;
         NSView *contentview = windowdata->sdlContentView;
 
-        /* This should never be nil since sdlContentView is only nil if the
-           window was created via SDL_CreateWindowFrom, and SDL doesn't allow
-           OpenGL contexts to be created in that case. However, it doesn't hurt
-           to check. */
-        if (contentview == nil) {
-            /* Prefer to access the cached content view above instead of this,
-               since as of Xcode 11 + SDK 10.15, [window contentView] causes
-               Apple's Main Thread Checker to output a warning. */
-            contentview = [windowdata->nswindow contentView];
-        }
-
         /* Now sign up for scheduled updates for the new window. */
         NSMutableArray *contexts = windowdata->nscontexts;
         @synchronized (contexts) {
@@ -362,7 +351,7 @@
 Cocoa_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
 {
     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
-    NSView *contentView = [windata->nswindow contentView];
+    NSView *contentView = windata->sdlContentView;
     NSRect viewport = [contentView bounds];
 
     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
diff -r 389ce8cfa2a3 -r aaec3f2027fc src/video/cocoa/SDL_cocoashape.m
--- a/src/video/cocoa/SDL_cocoashape.m	Wed Mar 25 09:38:45 2020 -0700
+++ b/src/video/cocoa/SDL_cocoashape.m	Wed Mar 25 16:40:43 2020 -0700
@@ -88,10 +88,10 @@
     [NSGraphicsContext setCurrentContext:data->context];
 
     [[NSColor clearColor] set];
-    NSRectFill([[windata->nswindow contentView] frame]);
+    NSRectFill([windata->sdlContentView frame]);
     data->shape = SDL_CalculateShapeTree(*shape_mode,shape);
 
-    closure.view = [windata->nswindow contentView];
+    closure.view = windata->sdlContentView;
     closure.path = [NSBezierPath bezierPath];
     closure.window = shaper->window;
     SDL_TraverseShapeTree(data->shape,&ConvertRects,&closure);
diff -r 389ce8cfa2a3 -r aaec3f2027fc src/video/cocoa/SDL_cocoawindow.h
--- a/src/video/cocoa/SDL_cocoawindow.h	Wed Mar 25 09:38:45 2020 -0700
+++ b/src/video/cocoa/SDL_cocoawindow.h	Wed Mar 25 16:40:43 2020 -0700
@@ -113,7 +113,7 @@
 {
     SDL_Window *window;
     NSWindow *nswindow;
-    NSView *sdlContentView; /* nil if window is created via CreateWindowFrom */
+    NSView *sdlContentView;
     NSMutableArray *nscontexts;
     SDL_bool created;
     SDL_bool inWindowFullscreenTransition;
diff -r 389ce8cfa2a3 -r aaec3f2027fc src/video/cocoa/SDL_cocoawindow.m
--- a/src/video/cocoa/SDL_cocoawindow.m	Wed Mar 25 09:38:45 2020 -0700
+++ b/src/video/cocoa/SDL_cocoawindow.m	Wed Mar 25 16:40:43 2020 -0700
@@ -297,15 +297,15 @@
     NSWindow *nswindow = data->nswindow;
 
     /* The view responder chain gets messed with during setStyleMask */
-    if ([[nswindow contentView] nextResponder] == data->listener) {
-        [[nswindow contentView] setNextResponder:nil];
+    if ([data->sdlContentView nextResponder] == data->listener) {
+        [data->sdlContentView setNextResponder:nil];
     }
 
     [nswindow setStyleMask:style];
 
     /* The view responder chain gets messed with during setStyleMask */
-    if ([[nswindow contentView] nextResponder] != data->listener) {
-        [[nswindow contentView] setNextResponder:data->listener];
+    if ([data->sdlContentView nextResponder] != data->listener) {
+        [data->sdlContentView setNextResponder:data->listener];
     }
 
     return SDL_TRUE;
@@ -318,7 +318,7 @@
 {
     NSNotificationCenter *center;
     NSWindow *window = data->nswindow;
-    NSView *view = [window contentView];
+    NSView *view = data->sdlContentView;
 
     _data = data;
     observingVisible = YES;
@@ -1360,7 +1360,7 @@
 @end
 
 static int
-SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, SDL_bool created)
+SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, NSView *nsview, SDL_bool created)
 { @autoreleasepool
 {
     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
@@ -1376,11 +1376,7 @@
     data->created = created;
     data->videodata = videodata;
     data->nscontexts = [[NSMutableArray alloc] init];
-
-    /* Only store this for windows created by us since the content view might
-     * get replaced from under us otherwise, and we only need it when the
-     * window is guaranteed to be created by us (OpenGL contexts). */
-    data->sdlContentView = created ? [nswindow contentView] : nil;
+    data->sdlContentView = nsview;
 
     /* Create an event listener for the window */
     data->listener = [[Cocoa_WindowListener alloc] init];
@@ -1541,7 +1537,7 @@
     [nswindow setContentView:contentView];
     [contentView release];
 
-    if (SetupWindowData(_this, window, nswindow, SDL_TRUE) < 0) {
+    if (SetupWindowData(_this, window, nswindow, contentView, SDL_TRUE) < 0) {
         [nswindow release];
         return -1;
     }
@@ -1571,7 +1567,19 @@
 Cocoa_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
 { @autoreleasepool
 {
-    NSWindow *nswindow = (NSWindow *) data;
+    NSView* nsview;
+    NSWindow *nswindow;
+
+    if ([(id)data isKindOfClass:[NSWindow class]]) {
+      nswindow = (NSWindow*)data;
+      nsview = [nswindow contentView];
+    } else if ([(id)data isKindOfClass:[NSView class]]) {
+      nsview = (NSView*)data;
+      nswindow = [nsview window];
+    } else {
+      SDL_assert(false);
+    }
+
     NSString *title;
 
     /* Query the title from the existing window */
@@ -1580,7 +1588,7 @@
         window->title = SDL_strdup([title UTF8String]);
     }
 
-    return SetupWindowData(_this, window, nswindow, SDL_FALSE);
+    return SetupWindowData(_this, window, nswindow, nsview, SDL_FALSE);
 }}
 
 void
@@ -1795,8 +1803,8 @@
     NSRect rect;
 
     /* The view responder chain gets messed with during setStyleMask */
-    if ([[nswindow contentView] nextResponder] == data->listener) {
-        [[nswindow contentView] setNextResponder:nil];
+    if ([data->sdlContentView nextResponder] == data->listener) {
+        [data->sdlContentView setNextResponder:nil];
     }
 
     if (fullscreen) {
@@ -1852,8 +1860,8 @@
     }
 
     /* The view responder chain gets messed with during setStyleMask */
-    if ([[nswindow contentView] nextResponder] != data->listener) {
-        [[nswindow contentView] setNextResponder:data->listener];
+    if ([data->sdlContentView nextResponder] != data->listener) {
+        [data->sdlContentView setNextResponder:data->listener];
     }
 
     s_moveHack = 0;
Comment 3 Eric Shepherd (:sheppy) 2020-03-26 18:20:04 UTC
This patch looks promising. I will try to come up with time to try it out myself as well.
Comment 4 Ryan C. Gordon 2020-04-07 18:31:35 UTC
This looks reasonable to me. I've put it in revision control at https://hg.libsdl.org/SDL/rev/6d22d6ce725c ... Eric, if you want to try it, just get the latest revision of SDL.

Thanks!

--ryan.
Comment 5 zeb 2020-09-16 03:38:11 UTC
I have tried the newest version from the hg repos.  The problem has not been resolved.

Here is my test code:

    # coding: utf-8

    import os
    import wx

    import sdl2
    import sdl2.ext
    # import sdl2.sdlgfx

    class MainFrame(wx.Frame):
      def __init__(self):
        wx.Frame.__init__(self, None, -1, 'MainFrame', pos=(0, 0), size = (400, 300), style = wx.DEFAULT_FRAME_STYLE)
    
        self.panelLeft = wx.Panel(self, style=wx.BORDER_SUNKEN)
        self.panelRight = wx.Panel(self, style=wx.BORDER_SUNKEN)

        sizer = wx.BoxSizer()
        sizer.Add(self.panelLeft, 1, wx.ALL|wx.EXPAND, border=10)
        sizer.Add(self.panelRight, 1, wx.ALL|wx.EXPAND, border=10)
        self.SetSizer(sizer)

        sdl2.ext.init()
        self.window = sdl2.SDL_CreateWindowFrom(self.panelRight.GetHandle())
        self.renderer = sdl2.SDL_CreateRenderer(self.window, -1, sdl2.SDL_RENDERER_ACCELERATED)
        sdl2.SDL_SetHint(sdl2.hints.SDL_HINT_RENDER_SCALE_QUALITY, b"1")

        self.Bind(wx.EVT_IDLE, self.render)

      def render(self, e):
        sdl2.SDL_SetRenderDrawColor(self.renderer, 225, 0, 0, 255)
        sdl2.SDL_RenderClear(self.renderer)
        # sdl2.sdlgfx.lineRGBA(self.renderer, 0, 0, 100, 100, 255, 255, 255, 255)
        sdl2.SDL_RenderPresent(self.renderer)

    class App(wx.App):
      def OnInit(self):
        self.mainFrame = MainFrame()
        self.mainFrame.Show(True)
        self.mainFrame.CentreOnScreen()
        self.SetTopWindow(self.mainFrame)
        return True

      def getMainFrame(self):
        return self._mainFrame

    app = App()
    app.MainLoop()

The right panel should be filled with red color, but the whole window has beed filled.
Comment 6 michaeljosephmaltese 2020-09-19 17:51:27 UTC
@zeb This issue was about CreateWindowFrom not working at all on macOS, leading to crashes or at the least a failure to create a window. That issue has been fixed.

What you're writing about is not the same issue: CreateFromWindow gives you a working SDL window (what this bug fixed), just not the one you want (not part of this but). Honestly it sounds like a wxWidgets issue not giving an NSView for the widget you want.

I'm reclosing this — if you need support for your issue, please take it to the forums. (If in the end it is determined to be an SDL issue, then please create a _new_ bug.)
Comment 7 michaeljosephmaltese 2020-09-19 17:51:51 UTC
.