macos: fix configure, move-resize, and compute-size
authorChristian Hergert <christian@hergert.me>
Wed, 16 Feb 2022 10:50:51 +0000 (02:50 -0800)
committerChristian Hergert <christian@hergert.me>
Fri, 18 Feb 2022 10:50:46 +0000 (02:50 -0800)
This significantly cleans up how we handle various move-resize, compute-
size, and configure (notification of changes) in the macOS GDK backend.

Originally when prototyping this backend, there were some bits that came
over from the quartz backend and some bits which did not. It got confusing
and so this makes an attempt to knock down all that technical debt.

It is much simpler now in that the GdkMacosSurface makes requests of the
GdkMacosWindow, and the GdkMacosWindow notifies the GdkMacosSurface of
changes that happen.

User resizes are delayed until the next compute-size so that we are much
closer to the layout phase, reducing chances for in-between frames.

This also improves the situation of leaving maximized state so that a
grab and drag feels like you'd expect on other platforms.

I removed the opacity hack we had in before, because that is all coming
out anyway and it's a bit obnoxious to maintain through the async flows
here.

gdk/macos/GdkMacosWindow.c
gdk/macos/gdkmacosdisplay.c
gdk/macos/gdkmacossurface-private.h
gdk/macos/gdkmacossurface.c
gdk/macos/gdkmacostoplevelsurface-private.h
gdk/macos/gdkmacostoplevelsurface.c

index 8dda79ab4d6495715d11c897580de113c3090431..0bb7521f164416a37bf476097bb50a4470ff2561 100644 (file)
@@ -203,76 +203,20 @@ typedef NSString *CALayerContentsGravity;
     }
 }
 
--(void)windowDidUnmaximize
+-(void)setFrame:(NSRect)frame display:(BOOL)display
 {
-  NSWindowStyleMask style_mask = [self styleMask];
-
-  gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
+  NSRect contentRect = [self contentRectForFrameRect:frame];
+  GdkSurface *surface = GDK_SURFACE (gdk_surface);
+  gboolean maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
 
-  /* If we are using CSD, then we transitioned to an opaque
-   * window while we were maximized. Now we need to drop that
-   * as we are leaving maximized state.
-   */
-  if ((style_mask & NSWindowStyleMaskTitled) == 0 && [self isOpaque])
+  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, frame))
     {
-      GdkSurface *surface = GDK_SURFACE ([self gdkSurface]);
-
-      [self setOpaque:NO];
-
-      /* Force updating of various styling, regions, etc */
+      gdk_synthesize_surface_state (surface, GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
       _gdk_surface_update_size (surface);
     }
-}
 
--(void)windowDidMove:(NSNotification *)aNotification
-{
-  GdkSurface *surface = GDK_SURFACE (gdk_surface);
-  gboolean maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
-
-  /* In case the window is changed when maximized remove the maximized state */
-  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
-    [self windowDidUnmaximize];
-
-  _gdk_macos_surface_update_position (gdk_surface);
-  _gdk_macos_surface_reposition_children (gdk_surface);
-
-  [self checkSendEnterNotify];
-}
-
--(void)windowDidResize:(NSNotification *)aNotification
-{
-  NSRect content_rect;
-  GdkSurface *surface;
-  GdkDisplay *display;
-  gboolean maximized;
-
-  surface = GDK_SURFACE (gdk_surface);
-  display = gdk_surface_get_display (surface);
-
-  content_rect = [self contentRectForFrameRect:[self frame]];
-  maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
-
-  /* see same in windowDidMove */
-  if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, [self frame]))
-    [self windowDidUnmaximize];
-
-  surface->width = content_rect.size.width;
-  surface->height = content_rect.size.height;
-
-  /* Certain resize operations (e.g. going fullscreen), also move the
-   * origin of the window.
-   */
-  _gdk_macos_surface_update_position (GDK_MACOS_SURFACE (surface));
-
-  [[self contentView] setFrame:NSMakeRect (0, 0, surface->width, surface->height)];
-
-  _gdk_surface_update_size (surface);
-
-  gdk_surface_request_layout (surface);
-
-  _gdk_macos_surface_reposition_children (gdk_surface);
-
-  [self checkSendEnterNotify];
+  [super setFrame:frame display:display];
+  [[self contentView] setFrame:NSMakeRect (0, 0, contentRect.size.width, contentRect.size.height)];
 }
 
 -(id)initWithContentRect:(NSRect)contentRect
@@ -418,12 +362,23 @@ typedef NSString *CALayerContentsGravity;
   windowFrame.origin.x = new_origin.x;
   windowFrame.origin.y = new_origin.y;
 
-  /* And now apply the frame to the window */
-  [self setFrameOrigin:NSMakePoint(new_origin.x, new_origin.y)];
+  [self setFrame:NSMakeRect (new_origin.x, new_origin.y,
+                             window_gdk.width, window_gdk.height)
+         display:YES];
 
   return YES;
 }
 
+-(void)windowDidMove:(NSNotification *)notification
+{
+  _gdk_macos_surface_configure ([self gdkSurface]);
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+  _gdk_macos_surface_configure ([self gdkSurface]);
+}
+
 /* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler
  * above will see the event or if it will be subjected to standard processing
  * by GDK.
@@ -435,6 +390,7 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)beginManualMove
 {
+  gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
   NSPoint initialMoveLocation;
   GdkPoint point;
   GdkMonitor *monitor;
@@ -453,6 +409,13 @@ typedef NSString *CALayerContentsGravity;
 
   initialMoveLocation = [NSEvent mouseLocation];
 
+  if (maximized)
+    [self setFrame:NSMakeRect (initialMoveLocation.x - (int)lastUnmaximizedFrame.size.width/2,
+                               initialMoveLocation.y,
+                               lastUnmaximizedFrame.size.width,
+                               lastUnmaximizedFrame.size.height)
+           display:YES];
+
   _gdk_macos_display_from_display_coords ([self gdkDisplay],
                                           initialMoveLocation.x,
                                           initialMoveLocation.y,
@@ -544,15 +507,7 @@ typedef NSString *CALayerContentsGravity;
       new_frame.size.height = min_size.height;
     }
 
-  /* We could also apply aspect ratio:
-     new_frame.size.height = new_frame.size.width / [self aspectRatio].width * [self aspectRatio].height;
-  */
-
-  [self setFrame:new_frame display:YES];
-
-  /* Let the resizing be handled by GTK. */
-  if (g_main_context_pending (NULL))
-    g_main_context_iteration (NULL, FALSE);
+  _gdk_macos_surface_user_resize ([self gdkSurface], new_frame);
 
   inTrackManualResize = NO;
 
@@ -561,49 +516,46 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)beginManualResize:(GdkSurfaceEdge)edge
 {
+  CALayerContentsGravity gravity = kCAGravityBottomLeft;
+
   if (inMove || inManualMove || inManualResize)
     return;
 
   inManualResize = YES;
   resizeEdge = edge;
 
-  if (GDK_IS_MACOS_GL_VIEW ([self contentView]))
+  switch (edge)
     {
-      CALayerContentsGravity gravity = kCAGravityBottomLeft;
+    default:
+    case GDK_SURFACE_EDGE_NORTH:
+      gravity = kCAGravityTopLeft;
+      break;
 
-      switch (edge)
-        {
-        default:
-        case GDK_SURFACE_EDGE_NORTH:
-          gravity = kCAGravityTopLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_NORTH_WEST:
-          gravity = kCAGravityTopRight;
-          break;
-
-        case GDK_SURFACE_EDGE_SOUTH_WEST:
-        case GDK_SURFACE_EDGE_WEST:
-          gravity = kCAGravityBottomRight;
-          break;
-
-        case GDK_SURFACE_EDGE_SOUTH:
-        case GDK_SURFACE_EDGE_SOUTH_EAST:
-          gravity = kCAGravityBottomLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_EAST:
-          gravity = kCAGravityBottomLeft;
-          break;
-
-        case GDK_SURFACE_EDGE_NORTH_EAST:
-          gravity = kCAGravityTopLeft;
-          break;
-        }
+    case GDK_SURFACE_EDGE_NORTH_WEST:
+      gravity = kCAGravityTopRight;
+      break;
+
+    case GDK_SURFACE_EDGE_SOUTH_WEST:
+    case GDK_SURFACE_EDGE_WEST:
+      gravity = kCAGravityBottomRight;
+      break;
+
+    case GDK_SURFACE_EDGE_SOUTH:
+    case GDK_SURFACE_EDGE_SOUTH_EAST:
+      gravity = kCAGravityBottomLeft;
+      break;
+
+    case GDK_SURFACE_EDGE_EAST:
+      gravity = kCAGravityBottomLeft;
+      break;
 
-      [[[self contentView] layer] setContentsGravity:gravity];
+    case GDK_SURFACE_EDGE_NORTH_EAST:
+      gravity = kCAGravityTopLeft;
+      break;
     }
 
+  [[[self contentView] layer] setContentsGravity:gravity];
+
   initialResizeFrame = [self frame];
   initialResizeLocation = convert_nspoint_to_screen (self, [self mouseLocationOutsideOfEventStream]);
 }
@@ -767,7 +719,6 @@ typedef NSString *CALayerContentsGravity;
   if (state & GDK_TOPLEVEL_STATE_MAXIMIZED)
     {
       lastMaximizedFrame = newFrame;
-      [self windowDidUnmaximize];
     }
   else
     {
@@ -782,16 +733,7 @@ typedef NSString *CALayerContentsGravity;
 
 -(void)windowDidEndLiveResize:(NSNotification *)aNotification
 {
-  gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
-
   inMaximizeTransition = NO;
-
-  /* Even if this is CSD, we want to be opaque while maximized
-   * to speed up compositing by allowing the display server to
-   * avoid costly blends.
-   */
-  if (maximized)
-    [self setOpaque:YES];
 }
 
 -(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
index 8d3aa0224e42036c5cd5781415b67d2ad43e70e3..656ceb8097fb48a7d3c9943e1422b140de4d23c7 100644 (file)
@@ -185,7 +185,7 @@ gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef  center,
       g_assert (GDK_IS_MACOS_SURFACE (surface));
 
       if (GDK_IS_TOPLEVEL (surface))
-        _gdk_macos_surface_update_position (surface);
+        _gdk_macos_surface_configure (surface);
     }
 }
 
index ccb8a8317951461a60c047bdb75de9a4e6d3a4c1..0cfe0a8a2f235bd95c51a3cc87906406d4c62dcc 100644 (file)
@@ -53,6 +53,13 @@ struct _GdkMacosSurface
   int root_x;
   int root_y;
 
+  struct {
+    int root_x;
+    int root_y;
+    int width;
+    int height;
+  } next_layout;
+
   int shadow_top;
   int shadow_right;
   int shadow_bottom;
@@ -102,7 +109,6 @@ void               _gdk_macos_surface_resize                  (GdkMacosSurface
                                                                int                 width,
                                                                int                 height);
 void               _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface    *self);
-void               _gdk_macos_surface_update_position         (GdkMacosSurface    *self);
 void               _gdk_macos_surface_show                    (GdkMacosSurface    *self);
 void               _gdk_macos_surface_publish_timings         (GdkMacosSurface    *self,
                                                                gint64              predicted_presentation_time,
@@ -121,6 +127,9 @@ void               _gdk_macos_surface_move_resize             (GdkMacosSurface
                                                                int                 y,
                                                                int                 width,
                                                                int                 height);
+void               _gdk_macos_surface_configure               (GdkMacosSurface    *self);
+void               _gdk_macos_surface_user_resize             (GdkMacosSurface    *self,
+                                                               CGRect              new_frame);
 gboolean           _gdk_macos_surface_is_tracking             (GdkMacosSurface    *self,
                                                                NSTrackingArea     *area);
 void               _gdk_macos_surface_monitor_changed         (GdkMacosSurface    *self);
index c5cf05edf80d72dba19144e6b3c2d0ab267948a5..967cbd94b41450a2cea4ada2bdd2eb347e60ac62 100644 (file)
@@ -419,15 +419,6 @@ gdk_macos_surface_constructed (GObject *object)
 
   G_OBJECT_CLASS (gdk_macos_surface_parent_class)->constructed (object);
 
-  if (self->window != NULL)
-    {
-      NSRect bounds = [[self->window contentView] bounds];
-
-      GDK_SURFACE (self)->width = bounds.size.width;
-      GDK_SURFACE (self)->height = bounds.size.height;
-      _gdk_macos_surface_update_position (self);
-    }
-
   if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
     {
       g_signal_connect_object (frame_clock,
@@ -441,6 +432,9 @@ gdk_macos_surface_constructed (GObject *object)
                                self,
                                G_CONNECT_SWAPPED);
     }
+
+  if (self->window != NULL)
+    _gdk_macos_surface_configure (self);
 }
 
 static void
@@ -699,12 +693,21 @@ _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self)
 }
 
 void
-_gdk_macos_surface_update_position (GdkMacosSurface *self)
+_gdk_macos_surface_configure (GdkMacosSurface *self)
 {
-  GdkSurface *surface = GDK_SURFACE (self);
-  GdkDisplay *display = gdk_surface_get_display (surface);
-  NSRect frame_rect = [self->window frame];
-  NSRect content_rect = [self->window contentRectForFrameRect:frame_rect];
+  GdkMacosDisplay *display;
+  GdkSurface *surface = (GdkSurface *)self;
+  NSRect frame_rect;
+  NSRect content_rect;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+  frame_rect = [self->window frame];
+  content_rect = [self->window contentRectForFrameRect:frame_rect];
 
   _gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
                                           content_rect.origin.x,
@@ -721,6 +724,19 @@ _gdk_macos_surface_update_position (GdkMacosSurface *self)
       surface->x = self->root_x;
       surface->y = self->root_y;
     }
+
+  if (surface->width != content_rect.size.width ||
+      surface->height != content_rect.size.height)
+    {
+      surface->width = content_rect.size.width;
+      surface->height = content_rect.size.height;
+
+      _gdk_surface_update_size (surface);
+      gdk_surface_request_layout (surface);
+      gdk_surface_invalidate_rect (surface, NULL);
+    }
+
+  _gdk_macos_surface_reposition_children (self);
 }
 
 void
@@ -781,8 +797,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self)
     {
       if (gdk_surface_get_mapped (GDK_SURFACE (self)))
         {
-          _gdk_macos_surface_update_position (self);
-          gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
+          _gdk_macos_surface_configure (self);
           gdk_surface_thaw_updates (GDK_SURFACE (self));
         }
     }
@@ -882,7 +897,6 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self,
   GdkDisplay *display;
   NSRect content_rect;
   NSRect frame_rect;
-  gboolean size_changed;
 
   g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
 
@@ -906,28 +920,43 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self,
   if (y == -1)
     y = self->root_y;
 
-  size_changed = height != surface->height || width != surface->width;
-
-  if (GDK_IS_MACOS_SURFACE (surface->parent))
-    {
-      surface->x = x - GDK_MACOS_SURFACE (surface->parent)->root_x;
-      surface->y = y - GDK_MACOS_SURFACE (surface->parent)->root_y;
-    }
-  else
-    {
-      surface->x = x;
-      surface->y = y;
-    }
-
   _gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
-                                        x, y + height, &x, &y);
+                                        x, y + height,
+                                        &x, &y);
 
   content_rect = NSMakeRect (x, y, width, height);
   frame_rect = [self->window frameRectForContentRect:content_rect];
   [self->window setFrame:frame_rect display:YES];
+}
+
+void
+_gdk_macos_surface_user_resize (GdkMacosSurface *self,
+                                CGRect           new_frame)
+{
+  GdkMacosDisplay *display;
+  CGRect content_rect;
+  int root_x, root_y;
+
+  g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
+  g_return_if_fail (GDK_IS_TOPLEVEL (self));
+
+  if (GDK_SURFACE_DESTROYED (self))
+    return;
+
+  display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
+  content_rect = [self->window contentRectForFrameRect:new_frame];
+
+  _gdk_macos_display_from_display_coords (display,
+                                          new_frame.origin.x,
+                                          new_frame.origin.y + new_frame.size.height,
+                                          &root_x, &root_y);
 
-  if (size_changed)
-    gdk_surface_invalidate_rect (surface, NULL);
+  self->next_layout.root_x = root_x;
+  self->next_layout.root_y = root_y;
+  self->next_layout.width = content_rect.size.width;
+  self->next_layout.height = content_rect.size.height;
+
+  gdk_surface_request_layout (GDK_SURFACE (self));
 }
 
 gboolean
@@ -995,7 +1024,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self)
       g_object_unref (monitor);
     }
 
-  _gdk_surface_update_size (GDK_SURFACE (self));
+  _gdk_macos_surface_configure (self);
+
   gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
 }
 
index b0bddfa0c1a94bdb2465019a2604a0ca89f3811f..1404aa14fec2917877a4e70107bbffac8aa46a01 100644 (file)
@@ -35,6 +35,8 @@ struct _GdkMacosToplevelSurface
 {
   GdkMacosSurface    parent_instance;
   GdkToplevelLayout *layout;
+  int                last_computed_width;
+  int                last_computed_height;
   guint              decorated : 1;
 };
 
index 17b1cec7085313ea840d7a613a0320f783e60d5a..bb2a77f2563ca7df5b53ba6f472273afa6049074 100644 (file)
@@ -381,11 +381,11 @@ static gboolean
 _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
 {
   GdkMacosToplevelSurface *self = (GdkMacosToplevelSurface *)surface;
+  GdkMacosSurface *macos_surface = (GdkMacosSurface *)surface;
   GdkToplevelSize size;
   GdkDisplay *display;
   GdkMonitor *monitor;
   int bounds_width, bounds_height;
-  int width, height;
   GdkGeometry geometry;
   GdkSurfaceHints mask;
 
@@ -419,9 +419,6 @@ _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
   g_warn_if_fail (size.width > 0);
   g_warn_if_fail (size.height > 0);
 
-  width = surface->width;
-  height = surface->height;
-
   if (self->layout != NULL &&
       gdk_toplevel_layout_get_resizable (self->layout))
     {
@@ -437,16 +434,63 @@ _gdk_macos_toplevel_surface_compute_size (GdkSurface *surface)
     }
 
   if (size.shadow.is_valid)
-    _gdk_macos_surface_set_shadow (GDK_MACOS_SURFACE (surface),
+    _gdk_macos_surface_set_shadow (macos_surface,
                                    size.shadow.top,
                                    size.shadow.right,
                                    size.shadow.bottom,
                                    size.shadow.left);
 
-  gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height);
+  _gdk_macos_surface_set_geometry_hints (macos_surface, &geometry, mask);
 
-  _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask);
-  _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height);
+  if (surface->state & (GDK_TOPLEVEL_STATE_FULLSCREEN |
+                        GDK_TOPLEVEL_STATE_MAXIMIZED |
+                        GDK_TOPLEVEL_STATE_TILED |
+                        GDK_TOPLEVEL_STATE_TOP_TILED |
+                        GDK_TOPLEVEL_STATE_RIGHT_TILED |
+                        GDK_TOPLEVEL_STATE_BOTTOM_TILED |
+                        GDK_TOPLEVEL_STATE_LEFT_TILED |
+                        GDK_TOPLEVEL_STATE_MINIMIZED))
+    return FALSE;
+
+  /* If we delayed a user resize until the beginning of the frame,
+   * apply it now so we can start processing updates for it.
+   */
+  if (macos_surface->next_layout.width > 0 &&
+      macos_surface->next_layout.height > 0)
+    {
+      int root_x = macos_surface->next_layout.root_x;
+      int root_y = macos_surface->next_layout.root_y;
+      int width = macos_surface->next_layout.width;
+      int height = macos_surface->next_layout.height;
+
+      gdk_surface_constrain_size (&geometry, mask,
+                                  width, height,
+                                  &width, &height);
+
+      macos_surface->next_layout.width = 0;
+      macos_surface->next_layout.height = 0;
+
+      _gdk_macos_surface_move_resize (macos_surface,
+                                      root_x, root_y,
+                                      width, height);
+
+      return FALSE;
+    }
+
+  gdk_surface_constrain_size (&geometry, mask,
+                              size.width, size.height,
+                              &size.width, &size.height);
+
+  if ((size.width != self->last_computed_width ||
+       size.height != self->last_computed_height) &&
+      (size.width != surface->width ||
+       size.height != surface->height))
+    {
+      self->last_computed_width = size.width;
+      self->last_computed_height = size.height;
+
+      _gdk_macos_surface_resize (macos_surface, size.width, size.height);
+    }
 
   return FALSE;
 }