[gtk: 15/16] gdk/surface: Replace move_to_rect() with GdkPopupLayout based API



commit ca71119a40ac9b196700855dac6484f4e198bdb1
Author: Jonas Ådahl <jadahl gmail com>
Date:   Sun Feb 16 12:59:24 2020 +0100

    gdk/surface: Replace move_to_rect() with GdkPopupLayout based API
    
    Replace the gdk_surface_move_to_rect() API with a new GdkSurface
    method called gdk_surface_present_popup() taking a new GdkPopupLayout
    object describing how they should be laid out on screen.
    
    The layout properties provided are the same as the ones used with
    gdk_surface_move_to_rect(), except they are now set up using
    GdkPopupLayout.
    
    Calling gdk_surface_present_popup() will either show the popup at the
    position described using the popup layout object and a new unconstrained
    size, or reposition it accordingly.
    
    In some situations, such as when a popup is set to autohide, presenting
    may immediately fail, in case the grab was not granted by the display
    server.
    
    After a successful present, the result of the layout can be queried
    using the following methods:
    
     * gdk_surface_get_position() - to get the position relative to its
       parent
     * gdk_surface_get_width() - to get the current width
     * gdk_surface_get_height() - to get the current height
     * gdk_surface_get_rect_anchor() - to get the anchor point on the anchor
       rectangle the popup was effectively positioned against given
       constraints defined by the environment and the layout rules provided
       via GdkPopupLayout.
     * gdk_surface_get_surface_anchor() - the same as the one above but for
       the surface anchor.
    
    A new signal replaces the old "moved-to-rect" one -
    "popup-layout-changed". However, it is only intended to be emitted when
    the layout changes implicitly by the windowing system, for example if
    the monitor resolution changed, or the parent window moved.

 docs/reference/gdk/gdk4-sections.txt |   4 +-
 gdk/broadway/gdksurface-broadway.c   |  86 ++--
 gdk/gdk-autocleanup.h                |   1 +
 gdk/gdkpopuplayout.c                 | 281 +++++++++++++
 gdk/gdkpopuplayout.h                 | 136 ++++++
 gdk/gdksurface.c                     | 295 ++++++-------
 gdk/gdksurface.h                     |  57 +--
 gdk/gdksurfaceprivate.h              |  93 +++-
 gdk/meson.build                      |   2 +
 gdk/quartz/gdksurface-quartz.c       |  90 ++--
 gdk/wayland/gdkdevice-wayland.c      |   2 -
 gdk/wayland/gdksurface-wayland.c     | 794 ++++++++++++++++++++++-------------
 gdk/win32/gdksurface-win32.c         |  84 ++--
 gdk/x11/gdksurface-x11.c             |  86 ++--
 gtk/gtkpopover.c                     | 299 ++++++++++---
 gtk/gtktooltipwindow.c               |  62 +--
 16 files changed, 1648 insertions(+), 724 deletions(-)
---
diff --git a/docs/reference/gdk/gdk4-sections.txt b/docs/reference/gdk/gdk4-sections.txt
index 48eadea63b..9e9cb5bb24 100644
--- a/docs/reference/gdk/gdk4-sections.txt
+++ b/docs/reference/gdk/gdk4-sections.txt
@@ -205,7 +205,9 @@ gdk_surface_set_keep_above
 gdk_surface_set_keep_below
 gdk_surface_set_opacity
 gdk_surface_resize
-gdk_surface_move_to_rect
+gdk_surface_present_popup
+gdk_surface_get_popup_rect_anchor
+gdk_surface_get_popup_surface_anchor
 gdk_surface_raise
 gdk_surface_lower
 gdk_surface_restack
diff --git a/gdk/broadway/gdksurface-broadway.c b/gdk/broadway/gdksurface-broadway.c
index 0f2411f63a..af946eef71 100644
--- a/gdk/broadway/gdksurface-broadway.c
+++ b/gdk/broadway/gdksurface-broadway.c
@@ -455,18 +455,21 @@ gdk_broadway_surface_move (GdkSurface *surface,
 }
 
 static void
-gdk_broadway_surface_moved_to_rect (GdkSurface   *surface,
-                                    GdkRectangle  final_rect)
+gdk_broadway_surface_layout_popup (GdkSurface     *surface,
+                                   int             width,
+                                   int             height,
+                                   GdkPopupLayout *layout)
 {
-  GdkSurface *toplevel;
+  GdkRectangle final_rect;
   int x, y;
 
-  if (surface->surface_type == GDK_SURFACE_POPUP)
-    toplevel = surface->parent;
-  else
-    toplevel = surface->transient_for;
+  gdk_surface_layout_popup_helper (surface,
+                                   width,
+                                   height,
+                                   layout,
+                                   &final_rect);
 
-  gdk_surface_get_origin (toplevel, &x, &y);
+  gdk_surface_get_origin (surface->parent, &x, &y);
   x += final_rect.x;
   y += final_rect.y;
 
@@ -474,8 +477,10 @@ gdk_broadway_surface_moved_to_rect (GdkSurface   *surface,
       final_rect.height != surface->height)
     {
       gdk_broadway_surface_move_resize (surface,
-                                        x, y,
-                                        final_rect.width, final_rect.height);
+                                        x,
+                                        y,
+                                        final_rect.width,
+                                        final_rect.height);
     }
   else
     {
@@ -484,22 +489,49 @@ gdk_broadway_surface_moved_to_rect (GdkSurface   *surface,
 }
 
 static void
-gdk_broadway_surface_move_to_rect (GdkSurface         *surface,
-                                   const GdkRectangle *rect,
-                                   GdkGravity          rect_anchor,
-                                   GdkGravity          surface_anchor,
-                                   GdkAnchorHints      anchor_hints,
-                                   gint                rect_anchor_dx,
-                                   gint                rect_anchor_dy)
-{
-  gdk_surface_move_to_rect_helper (surface,
-                                   rect,
-                                   rect_anchor,
-                                   surface_anchor,
-                                   anchor_hints,
-                                   rect_anchor_dx,
-                                   rect_anchor_dy,
-                                   gdk_broadway_surface_moved_to_rect);
+show_popup (GdkSurface *surface)
+{
+  gdk_surface_raise (surface);
+  gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0);
+  _gdk_surface_update_viewable (surface);
+  gdk_broadway_surface_show (surface, FALSE);
+  gdk_surface_invalidate_rect (surface, NULL);
+}
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  show_popup (surface);
+}
+
+static gboolean
+gdk_broadway_surface_present_popup (GdkSurface     *surface,
+                                    int             width,
+                                    int             height,
+                                    GdkPopupLayout *layout)
+{
+  gdk_broadway_surface_layout_popup (surface, width, height, layout);
+
+  if (GDK_SURFACE_IS_MAPPED (surface))
+    return TRUE;
+
+  if (surface->autohide)
+    {
+      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
+                     surface,
+                     GDK_SEAT_CAPABILITY_ALL,
+                     TRUE,
+                     NULL, NULL,
+                     show_grabbing_popup, NULL);
+    }
+  else
+    {
+      show_popup (surface);
+    }
+
+  return GDK_SURFACE_IS_MAPPED (surface);
 }
 
 static void
@@ -1393,7 +1425,7 @@ gdk_broadway_surface_class_init (GdkBroadwaySurfaceClass *klass)
   impl_class->lower = gdk_broadway_surface_lower;
   impl_class->restack_toplevel = gdk_broadway_surface_restack_toplevel;
   impl_class->toplevel_resize = gdk_broadway_surface_toplevel_resize;
-  impl_class->move_to_rect = gdk_broadway_surface_move_to_rect;
+  impl_class->present_popup = gdk_broadway_surface_present_popup;
   impl_class->get_geometry = gdk_broadway_surface_get_geometry;
   impl_class->get_root_coords = gdk_broadway_surface_get_root_coords;
   impl_class->get_device_state = gdk_broadway_surface_get_device_state;
diff --git a/gdk/gdk-autocleanup.h b/gdk/gdk-autocleanup.h
index 513725620a..0086e21794 100644
--- a/gdk/gdk-autocleanup.h
+++ b/gdk/gdk-autocleanup.h
@@ -35,6 +35,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkGLContext, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkKeymap, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkMonitor, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkSeat, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkPopupLayout, gdk_popup_layout_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkVulkanContext, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkSurface, g_object_unref)
 
diff --git a/gdk/gdkpopuplayout.c b/gdk/gdkpopuplayout.c
new file mode 100644
index 0000000000..7bc7704c64
--- /dev/null
+++ b/gdk/gdkpopuplayout.c
@@ -0,0 +1,281 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2020 Red Hat
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "gdkpopuplayout.h"
+
+#include "gdksurface.h"
+
+struct _GdkPopupLayout
+{
+  /* < private >*/
+  grefcount ref_count;
+
+  GdkRectangle anchor_rect;
+  GdkGravity rect_anchor;
+  GdkGravity surface_anchor;
+  GdkAnchorHints anchor_hints;
+  int dx;
+  int dy;
+
+  gboolean is_sealed;
+};
+
+G_DEFINE_BOXED_TYPE (GdkPopupLayout, gdk_popup_layout,
+                     gdk_popup_layout_ref,
+                     gdk_popup_layout_unref)
+
+/**
+ * gdk_popup_layout_new: (constructor)
+ * @anchor_rect:  (not nullable): the anchor #GdkRectangle to align @surface with
+ * @rect_anchor: the point on @anchor_rect to align with @surface's anchor point
+ * @surface_anchor: the point on @surface to align with @rect's anchor point
+ *
+ * Create a popup layout description. Used together with
+ * gdk_surface_present_popup() to describe how a popup surface should be placed
+ * and behave on-screen.
+ *
+ * @anchor_rect is relative to the top-left corner of the surface's parent.
+ * @rect_anchor and @surface_anchor determine anchor points on @anchor_rect and
+ * surface to pin together.
+ *
+ * The position of @anchor_rect's anchor point can optionally be offset using
+ * gdk_popup_layout_set_offset(), which is equivalent to offsetting the
+ * position of surface.
+ *
+ * Returns: (transfer full): newly created instance of #GdkPopupLayout
+ */
+GdkPopupLayout *
+gdk_popup_layout_new (const GdkRectangle *anchor_rect,
+                      GdkGravity          rect_anchor,
+                      GdkGravity          surface_anchor)
+{
+  GdkPopupLayout *layout;
+
+  layout = g_new0 (GdkPopupLayout, 1);
+  g_ref_count_init (&layout->ref_count);
+  layout->anchor_rect = *anchor_rect;
+  layout->rect_anchor = rect_anchor;
+  layout->surface_anchor = surface_anchor;
+
+  return layout;
+}
+
+/**
+ * gdk_popup_layout_ref:
+ * @layout: a #GdkPopupLayout
+ *
+ * Increases the reference count of @value.
+ *
+ * Returns: the same @layout
+ */
+GdkPopupLayout *
+gdk_popup_layout_ref (GdkPopupLayout *layout)
+{
+  g_ref_count_inc (&layout->ref_count);
+  return layout;
+}
+
+/**
+ * gdk_popup_layout_unref:
+ * @layout: a #GdkPopupLayout
+ *
+ * Decreases the reference count of @value.
+ */
+void
+gdk_popup_layout_unref (GdkPopupLayout *layout)
+{
+  if (g_ref_count_dec (&layout->ref_count))
+    g_free (layout);
+}
+
+/**
+ * gdk_popup_layout_copy:
+ * @layout: a #GdkPopupLayout
+ *
+ * Create a new #GdkPopupLayout and copy the contents of @layout into it.
+ *
+ * Returns: (transfer full): a copy of @layout.
+ */
+GdkPopupLayout *
+gdk_popup_layout_copy (GdkPopupLayout *layout)
+{
+  GdkPopupLayout *new_layout;
+
+  new_layout = g_new0 (GdkPopupLayout, 1);
+  g_ref_count_init (&new_layout->ref_count);
+
+  new_layout->anchor_rect = layout->anchor_rect;
+  new_layout->rect_anchor = layout->rect_anchor;
+  new_layout->surface_anchor = layout->surface_anchor;
+  new_layout->anchor_hints = layout->anchor_hints;
+  new_layout->dx = layout->dx;
+  new_layout->dy = layout->dy;
+
+  return new_layout;
+}
+
+/**
+ * gdk_popup_layout_set_anchor_rect:
+ * @layout: a #GdkPopupLayout
+ * @anchor_rect: the new anchor rectangle
+ *
+ * Set the anchor rectangle.
+ */
+void
+gdk_popup_layout_set_anchor_rect (GdkPopupLayout     *layout,
+                                  const GdkRectangle *anchor_rect)
+{
+  layout->anchor_rect = *anchor_rect;
+}
+
+/**
+ * gdk_popup_layout_get_anchor_rect:
+ * @layout: a #GdkPopupLayout
+ *
+ * Get the anchor rectangle.
+ *
+ * Returns: The anchor rectangle.
+ */
+const GdkRectangle *
+gdk_popup_layout_get_anchor_rect (GdkPopupLayout *layout)
+{
+  return &layout->anchor_rect;
+}
+
+/**
+ * gdk_popup_layout_set_rect_anchor:
+ * @layout: a #GdkPopupLayout
+ * @anchor: the new rect anchor
+ *
+ * Set the anchor on the anchor rectangle.
+ */
+void
+gdk_popup_layout_set_rect_anchor (GdkPopupLayout *layout,
+                                  GdkGravity      anchor)
+{
+  layout->rect_anchor = anchor;
+}
+
+/**
+ * gdk_popup_layout_get_rect_anchor:
+ * @layout: a #GdkPopupLayout
+ *
+ * Returns: the anchor on the anchor rectangle.
+ */
+GdkGravity
+gdk_popup_layout_get_rect_anchor (GdkPopupLayout *layout)
+{
+  return layout->rect_anchor;
+}
+
+/**
+ * gdk_popup_layout_set_surface_anchor:
+ * @layout: a #GdkPopupLayout
+ * @anchor: the new popup surface anchor
+ *
+ * Set the anchor on the popup surface.
+ */
+void
+gdk_popup_layout_set_surface_anchor (GdkPopupLayout *layout,
+                                     GdkGravity      anchor)
+{
+  layout->surface_anchor = anchor;
+}
+
+/**
+ * gdk_popup_layout_get_surface_anchor:
+ * @layout: a #GdkPopupLayout
+ *
+ * Returns: the anchor on the popup surface.
+ */
+GdkGravity
+gdk_popup_layout_get_surface_anchor (GdkPopupLayout *layout)
+{
+  return layout->surface_anchor;
+}
+
+/**
+ * gdk_popup_layout_set_anchor_hints:
+ * @layout: a #GdkPopupLayout
+ * @anchor_hints: the new #GdkAnchorHints
+ *
+ * Set new anchor hints.
+ *
+ * The set @anchor_hints determines how @surface will be moved if the anchor
+ * points cause it to move off-screen. For example, %GDK_ANCHOR_FLIP_X will
+ * replace %GDK_GRAVITY_NORTH_WEST with %GDK_GRAVITY_NORTH_EAST and vice versa
+ * if @surface extends beyond the left or right edges of the monitor.
+ */
+void
+gdk_popup_layout_set_anchor_hints (GdkPopupLayout *layout,
+                                   GdkAnchorHints  anchor_hints)
+{
+  layout->anchor_hints = anchor_hints;
+}
+
+/**
+ * gdk_popup_layout_get_anchor_hints:
+ * @layout: a #GdkPopupLayout
+ *
+ * Get the #GdkAnchorHints.
+ *
+ * Returns: the #GdkAnchorHints.
+ */
+GdkAnchorHints
+gdk_popup_layout_get_anchor_hints (GdkPopupLayout *layout)
+{
+  return layout->anchor_hints;
+}
+
+/**
+ * gdk_popup_layout_set_offset:
+ * @layout: a #GdkPopupLayout
+ * @dx: x delta to offset the anchor rectangle with
+ * @dy: y delta to offset the anchor rectangle with
+ *
+ * Offset the position of the anchor rectangle with the given delta.
+ */
+void
+gdk_popup_layout_set_offset (GdkPopupLayout *layout,
+                             int             dx,
+                             int             dy)
+{
+  layout->dx = dx;
+  layout->dy = dy;
+}
+
+/**
+ * gdk_popup_layout_get_offset:
+ * @layout: a #GdkPopupLayout
+ * @dx: a pointer to where to store the delta x coordinate
+ * @dy: a pointer to where to store the delta y coordinate
+ *
+ * Get the delta the anchor rectangle is offset with
+ */
+void
+gdk_popup_layout_get_offset (GdkPopupLayout *layout,
+                             int            *dx,
+                             int            *dy)
+{
+  if (dx)
+    *dx = layout->dx;
+  if (dy)
+    *dy = layout->dy;
+}
diff --git a/gdk/gdkpopuplayout.h b/gdk/gdkpopuplayout.h
new file mode 100644
index 0000000000..9a07c0aeea
--- /dev/null
+++ b/gdk/gdkpopuplayout.h
@@ -0,0 +1,136 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2020 Red Hat
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GDK_POPUP_LAYOUT_H__
+#define __GDK_POPUP_LAYOUT_H__
+
+#if !defined (__GDK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/gdk.h> can be included directly."
+#endif
+
+#include <gdk/gdkversionmacros.h>
+#include <gdk/gdktypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GdkAnchorHints:
+ * @GDK_ANCHOR_FLIP_X: allow flipping anchors horizontally
+ * @GDK_ANCHOR_FLIP_Y: allow flipping anchors vertically
+ * @GDK_ANCHOR_SLIDE_X: allow sliding surface horizontally
+ * @GDK_ANCHOR_SLIDE_Y: allow sliding surface vertically
+ * @GDK_ANCHOR_RESIZE_X: allow resizing surface horizontally
+ * @GDK_ANCHOR_RESIZE_Y: allow resizing surface vertically
+ * @GDK_ANCHOR_FLIP: allow flipping anchors on both axes
+ * @GDK_ANCHOR_SLIDE: allow sliding surface on both axes
+ * @GDK_ANCHOR_RESIZE: allow resizing surface on both axes
+ *
+ * Positioning hints for aligning a surface relative to a rectangle.
+ *
+ * These hints determine how the surface should be positioned in the case that
+ * the surface would fall off-screen if placed in its ideal position.
+ *
+ * For example, %GDK_ANCHOR_FLIP_X will replace %GDK_GRAVITY_NORTH_WEST with
+ * %GDK_GRAVITY_NORTH_EAST and vice versa if the surface extends beyond the left
+ * or right edges of the monitor.
+ *
+ * If %GDK_ANCHOR_SLIDE_X is set, the surface can be shifted horizontally to fit
+ * on-screen. If %GDK_ANCHOR_RESIZE_X is set, the surface can be shrunken
+ * horizontally to fit.
+ *
+ * In general, when multiple flags are set, flipping should take precedence over
+ * sliding, which should take precedence over resizing.
+ */
+typedef enum
+{
+  GDK_ANCHOR_FLIP_X   = 1 << 0,
+  GDK_ANCHOR_FLIP_Y   = 1 << 1,
+  GDK_ANCHOR_SLIDE_X  = 1 << 2,
+  GDK_ANCHOR_SLIDE_Y  = 1 << 3,
+  GDK_ANCHOR_RESIZE_X = 1 << 4,
+  GDK_ANCHOR_RESIZE_Y = 1 << 5,
+  GDK_ANCHOR_FLIP     = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+  GDK_ANCHOR_SLIDE    = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+  GDK_ANCHOR_RESIZE   = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y,
+} GdkAnchorHints;
+
+/**
+ * GdkPopupLayout:
+ */
+typedef struct _GdkPopupLayout GdkPopupLayout;
+
+#define GDK_TYPE_POPUP_LAYOUT (gdk_popup_layout_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+GType                   gdk_popup_layout_get_type               (void);
+
+GDK_AVAILABLE_IN_ALL
+GdkPopupLayout *        gdk_popup_layout_new                    (const GdkRectangle     *anchor_rect,
+                                                                 GdkGravity              rect_anchor,
+                                                                 GdkGravity              surface_anchor);
+
+GDK_AVAILABLE_IN_ALL
+GdkPopupLayout *        gdk_popup_layout_ref                    (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_unref                  (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+GdkPopupLayout *        gdk_popup_layout_copy                   (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_set_anchor_rect        (GdkPopupLayout         *layout,
+                                                                 const GdkRectangle     *anchor_rect);
+
+GDK_AVAILABLE_IN_ALL
+const GdkRectangle *    gdk_popup_layout_get_anchor_rect        (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_set_rect_anchor        (GdkPopupLayout         *layout,
+                                                                 GdkGravity              anchor);
+
+GDK_AVAILABLE_IN_ALL
+GdkGravity              gdk_popup_layout_get_rect_anchor        (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_set_surface_anchor     (GdkPopupLayout         *layout,
+                                                                 GdkGravity              anchor);
+
+GDK_AVAILABLE_IN_ALL
+GdkGravity              gdk_popup_layout_get_surface_anchor     (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_set_anchor_hints       (GdkPopupLayout         *layout,
+                                                                 GdkAnchorHints          anchor_hints);
+
+GDK_AVAILABLE_IN_ALL
+GdkAnchorHints          gdk_popup_layout_get_anchor_hints       (GdkPopupLayout         *layout);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_set_offset             (GdkPopupLayout         *layout,
+                                                                 int                     dx,
+                                                                 int                     dy);
+
+GDK_AVAILABLE_IN_ALL
+void                    gdk_popup_layout_get_offset             (GdkPopupLayout         *layout,
+                                                                 int                    *dx,
+                                                                 int                    *dy);
+
+G_END_DECLS
+
+#endif /* __GDK_POPUP_LAYOUT_H__ */
diff --git a/gdk/gdksurface.c b/gdk/gdksurface.c
index 9790be98a3..dbda1833bc 100644
--- a/gdk/gdksurface.c
+++ b/gdk/gdksurface.c
@@ -69,7 +69,7 @@
  */
 
 enum {
-  MOVED_TO_RECT,
+  POPUP_LAYOUT_CHANGED,
   SIZE_CHANGED,
   RENDER,
   EVENT,
@@ -247,36 +247,30 @@ maybe_flip_position (gint      bounds_pos,
 }
 
 void
-gdk_surface_move_to_rect_helper (GdkSurface            *surface,
-                                 const GdkRectangle    *rect,
-                                 GdkGravity             rect_anchor,
-                                 GdkGravity             surface_anchor,
-                                 GdkAnchorHints         anchor_hints,
-                                 gint                   rect_anchor_dx,
-                                 gint                   rect_anchor_dy,
-                                 GdkSurfaceMovedToRect  moved_to_rect)
-{
-  GdkSurface *toplevel;
+gdk_surface_layout_popup_helper (GdkSurface     *surface,
+                                 int             width,
+                                 int             height,
+                                 GdkPopupLayout *layout,
+                                 GdkRectangle   *out_final_rect)
+{
   GdkDisplay *display;
   GdkMonitor *monitor;
   GdkRectangle bounds;
-  GdkRectangle root_rect = *rect;
-  GdkRectangle flipped_rect;
+  GdkRectangle root_rect;
+  GdkGravity rect_anchor;
+  GdkGravity surface_anchor;
+  int rect_anchor_dx;
+  int rect_anchor_dy;
+  GdkAnchorHints anchor_hints;
   GdkRectangle final_rect;
   gboolean flipped_x;
   gboolean flipped_y;
   int x, y;
 
-  /* This implementation only works for backends that
-   * can provide root coordinates via get_root_coords.
-   * Other backends need to implement move_to_rect.
-   */
-  if (surface->surface_type == GDK_SURFACE_POPUP)
-    toplevel = surface->parent;
-  else
-    toplevel = surface->transient_for;
+  g_return_if_fail (surface->surface_type == GDK_SURFACE_POPUP);
 
-  gdk_surface_get_root_coords (toplevel,
+  root_rect = *gdk_popup_layout_get_anchor_rect (layout);
+  gdk_surface_get_root_coords (surface->parent,
                                root_rect.x,
                                root_rect.y,
                                &root_rect.x,
@@ -286,30 +280,33 @@ gdk_surface_move_to_rect_helper (GdkSurface            *surface,
   monitor = get_monitor_for_rect (display, &root_rect);
   gdk_monitor_get_workarea (monitor, &bounds);
 
-  flipped_rect.width = surface->width - surface->shadow_left - surface->shadow_right;
-  flipped_rect.height = surface->height - surface->shadow_top - surface->shadow_bottom;
-  flipped_rect.x = maybe_flip_position (bounds.x,
-                                        bounds.width,
-                                        root_rect.x,
-                                        root_rect.width,
-                                        flipped_rect.width,
-                                        get_anchor_x_sign (rect_anchor),
-                                        get_anchor_x_sign (surface_anchor),
-                                        rect_anchor_dx,
-                                        anchor_hints & GDK_ANCHOR_FLIP_X,
-                                        &flipped_x);
-  flipped_rect.y = maybe_flip_position (bounds.y,
-                                        bounds.height,
-                                        root_rect.y,
-                                        root_rect.height,
-                                        flipped_rect.height,
-                                        get_anchor_y_sign (rect_anchor),
-                                        get_anchor_y_sign (surface_anchor),
-                                        rect_anchor_dy,
-                                        anchor_hints & GDK_ANCHOR_FLIP_Y,
-                                        &flipped_y);
-
-  final_rect = flipped_rect;
+  rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
+  surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
+  gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy);
+  anchor_hints = gdk_popup_layout_get_anchor_hints (layout);
+
+  final_rect.width = width - surface->shadow_left - surface->shadow_right;
+  final_rect.height = height - surface->shadow_top - surface->shadow_bottom;
+  final_rect.x = maybe_flip_position (bounds.x,
+                                      bounds.width,
+                                      root_rect.x,
+                                      root_rect.width,
+                                      final_rect.width,
+                                      get_anchor_x_sign (rect_anchor),
+                                      get_anchor_x_sign (surface_anchor),
+                                      rect_anchor_dx,
+                                      anchor_hints & GDK_ANCHOR_FLIP_X,
+                                      &flipped_x);
+  final_rect.y = maybe_flip_position (bounds.y,
+                                      bounds.height,
+                                      root_rect.y,
+                                      root_rect.height,
+                                      final_rect.height,
+                                      get_anchor_y_sign (rect_anchor),
+                                      get_anchor_y_sign (surface_anchor),
+                                      rect_anchor_dy,
+                                      anchor_hints & GDK_ANCHOR_FLIP_Y,
+                                      &flipped_y);
 
   if (anchor_hints & GDK_ANCHOR_SLIDE_X)
     {
@@ -353,30 +350,30 @@ gdk_surface_move_to_rect_helper (GdkSurface            *surface,
         final_rect.height = bounds.y + bounds.height - final_rect.y;
     }
 
-  flipped_rect.x -= surface->shadow_left;
-  flipped_rect.y -= surface->shadow_top;
-  flipped_rect.width += surface->shadow_left + surface->shadow_right;
-  flipped_rect.height += surface->shadow_top + surface->shadow_bottom;
-
   final_rect.x -= surface->shadow_left;
   final_rect.y -= surface->shadow_top;
   final_rect.width += surface->shadow_left + surface->shadow_right;
   final_rect.height += surface->shadow_top + surface->shadow_bottom;
 
-  gdk_surface_get_origin (toplevel, &x, &y);
+  gdk_surface_get_origin (surface->parent, &x, &y);
   final_rect.x -= x;
   final_rect.y -= y;
-  flipped_rect.x -= x;
-  flipped_rect.y -= y;
 
-  moved_to_rect (surface, final_rect);
+  if (flipped_x)
+    {
+      rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
+      surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
+    }
+  if (flipped_y)
+    {
+      rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
+      surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
+    }
+
+  surface->popup.rect_anchor = rect_anchor;
+  surface->popup.surface_anchor = surface_anchor;
 
-  g_signal_emit_by_name (surface,
-                         "moved-to-rect",
-                         &flipped_rect,
-                         &final_rect,
-                         flipped_x,
-                         flipped_y);
+  *out_final_rect = final_rect;
 }
 
 static void
@@ -481,42 +478,24 @@ gdk_surface_class_init (GdkSurfaceClass *klass)
   g_object_class_install_properties (object_class, LAST_PROP, properties);
 
   /**
-   * GdkSurface::moved-to-rect:
-   * @surface: the #GdkSurface that moved
-   * @flipped_rect: (nullable): the position of @surface after any possible
-   *                flipping or %NULL if the backend can't obtain it
-   * @final_rect: (nullable): the final position of @surface or %NULL if the
-   *              backend can't obtain it
-   * @flipped_x: %TRUE if the anchors were flipped horizontally
-   * @flipped_y: %TRUE if the anchors were flipped vertically
-   *
-   * Emitted when the position of @surface is finalized after being moved to a
-   * destination rectangle.
+   * GdkSurface::popup-layout-changed
+   * @surface: the #GdkSurface that was laid out
    *
-   * @surface might be flipped over the destination rectangle in order to keep
-   * it on-screen, in which case @flipped_x and @flipped_y will be set to %TRUE
-   * accordingly.
+   * Emitted when the layout of a popup @surface has changed, e.g. if the popup
+   * layout was reactive and after the parent moved causing the popover to end
+   * up partially off-screen.
    *
-   * @flipped_rect is the ideal position of @surface after any possible
-   * flipping, but before any possible sliding. @final_rect is @flipped_rect,
-   * but possibly translated in the case that flipping is still ineffective in
-   * keeping @surface on-screen.
-   * Stability: Private
    */
-  signals[MOVED_TO_RECT] =
-    g_signal_new (g_intern_static_string ("moved-to-rect"),
+  signals[POPUP_LAYOUT_CHANGED] =
+    g_signal_new (g_intern_static_string ("popup-layout-changed"),
                   G_OBJECT_CLASS_TYPE (object_class),
                   G_SIGNAL_RUN_FIRST,
                   0,
                   NULL,
                   NULL,
-                  _gdk_marshal_VOID__POINTER_POINTER_BOOLEAN_BOOLEAN,
+                  NULL,
                   G_TYPE_NONE,
-                  4,
-                  G_TYPE_POINTER,
-                  G_TYPE_POINTER,
-                  G_TYPE_BOOLEAN,
-                  G_TYPE_BOOLEAN);
+                  0);
 
   /**
    * GdkSurface::size-changed:
@@ -805,9 +784,8 @@ gdk_surface_new_temp (GdkDisplay         *display,
  *
  * Create a new popup surface.
  *
- * The surface will be attached to @parent and can
- * be positioned relative to it using
- * gdk_surface_move_to_rect().
+ * The surface will be attached to @parent and can be positioned relative to it
+ * using gdk_surface_show_popup() or later using gdk_surface_layout_popup().
  *
  * Returns: (transfer full): a new #GdkSurface
  */
@@ -1985,14 +1963,6 @@ gdk_surface_restack (GdkSurface     *surface,
   GDK_SURFACE_GET_CLASS (surface)->restack_toplevel (surface, sibling, above);
 }
 
-static void
-grab_prepare_func (GdkSeat    *seat,
-                   GdkSurface *surface,
-                   gpointer    data)
-{
-  gdk_surface_show_internal (surface, TRUE);
-}
-
 /**
  * gdk_surface_show:
  * @surface: a #GdkSurface
@@ -2004,25 +1974,18 @@ grab_prepare_func (GdkSeat    *seat,
  * This function maps a surface so it’s visible onscreen. Its opposite
  * is gdk_surface_hide().
  *
+ * This function may not be used on a #GdkSurface with the surface type
+ * GTK_SURFACE_POPUP.
+ *
  * When implementing a #GtkWidget, you should call this function on the widget's
  * #GdkSurface as part of the “map” method.
  */
 void
 gdk_surface_show (GdkSurface *surface)
 {
-  if (surface->autohide)
-    {
-      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
-                     surface,
-                     GDK_SEAT_CAPABILITY_ALL,
-                     TRUE,
-                     NULL, NULL,
-                     grab_prepare_func, NULL);
-    }
-  else
-    {
-      gdk_surface_show_internal (surface, TRUE);
-    }
+  g_return_if_fail (surface->surface_type != GDK_SURFACE_POPUP);
+
+  gdk_surface_show_internal (surface, TRUE);
 }
 
 /**
@@ -2084,6 +2047,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS
 
   GDK_SURFACE_GET_CLASS (surface)->hide (surface);
 
+  surface->popup.rect_anchor = 0;
+  surface->popup.surface_anchor = 0;
   surface->x = 0;
   surface->y = 0;
 }
@@ -2109,52 +2074,70 @@ gdk_surface_resize (GdkSurface *surface,
 }
 
 /**
- * gdk_surface_move_to_rect:
- * @surface: the #GdkSurface to move
- * @rect: (not nullable): the destination #GdkRectangle to align @surface with
- * @rect_anchor: the point on @rect to align with @surface's anchor point
- * @surface_anchor: the point on @surface to align with @rect's anchor point
- * @anchor_hints: positioning hints to use when limited on space
- * @rect_anchor_dx: horizontal offset to shift @surface, i.e. @rect's anchor
- *                  point
- * @rect_anchor_dy: vertical offset to shift @surface, i.e. @rect's anchor point
- *
- * Moves @surface to @rect, aligning their anchor points.
- *
- * @rect is relative to the top-left corner of the surface that @surface is
- * transient for. @rect_anchor and @surface_anchor determine anchor points on
- * @rect and @surface to pin together. @rect's anchor point can optionally be
- * offset by @rect_anchor_dx and @rect_anchor_dy, which is equivalent to
- * offsetting the position of @surface.
- *
- * @anchor_hints determines how @surface will be moved if the anchor points cause
- * it to move off-screen. For example, %GDK_ANCHOR_FLIP_X will replace
- * %GDK_GRAVITY_NORTH_WEST with %GDK_GRAVITY_NORTH_EAST and vice versa if
- * @surface extends beyond the left or right edges of the monitor.
- *
- * Connect to the #GdkSurface::moved-to-rect signal to find out how it was
- * actually positioned.
+ * gdk_surface_present_popup:
+ * @surface: the popup #GdkSurface to show
+ * @width: the unconstrained popup width to layout
+ * @height: the unconstrained popup height to layout
+ * @layout: the #GdkPopupLayout object used to layout
+ *
+ * Present @surface after having processed the #GdkPopupLayout rules. If the
+ * popup was previously now showing, it will be showed, otherwise it will
+ * change position according to @layout.
+ *
+ * After calling this function, the result of the layout can be queried
+ * using gdk_surface_get_position(), gdk_surface_get_width(),
+ * gdk_surface_get_height(), gdk_surface_get_popup_rect_anchor() and
+ * gdk_surface_get_popup_surface_anchor().
+ *
+ * Presenting may have fail, for example if it was immediately hidden if the
+ * @surface was set to autohide.
+ *
+ * Returns: %FALSE if it failed to be presented, otherwise %TRUE.
  */
-void
-gdk_surface_move_to_rect (GdkSurface          *surface,
-                          const GdkRectangle *rect,
-                          GdkGravity          rect_anchor,
-                          GdkGravity          surface_anchor,
-                          GdkAnchorHints      anchor_hints,
-                          gint                rect_anchor_dx,
-                          gint                rect_anchor_dy)
+gboolean
+gdk_surface_present_popup (GdkSurface     *surface,
+                           int             width,
+                           int             height,
+                           GdkPopupLayout *layout)
 {
-  g_return_if_fail (GDK_IS_SURFACE (surface));
-  g_return_if_fail (surface->parent || surface->transient_for);
-  g_return_if_fail (rect);
-
-  GDK_SURFACE_GET_CLASS (surface)->move_to_rect (surface,
-                                                 rect,
-                                                 rect_anchor,
-                                                 surface_anchor,
-                                                 anchor_hints,
-                                                 rect_anchor_dx,
-                                                 rect_anchor_dy);
+  g_return_val_if_fail (GDK_IS_SURFACE (surface), FALSE);
+  g_return_val_if_fail (surface->parent, FALSE);
+  g_return_val_if_fail (layout, FALSE);
+  g_return_val_if_fail (!GDK_SURFACE_DESTROYED (surface), FALSE);
+  g_return_val_if_fail (width > 0 && height > 0, FALSE);
+
+  return GDK_SURFACE_GET_CLASS (surface)->present_popup (surface,
+                                                         width,
+                                                         height,
+                                                         layout);
+}
+
+/**
+ * gdk_surface_get_popup_surface_anchor:
+ * @surface: a #GdkSurface
+ *
+ * Get the current popup surface anchor. The value returned may chage after
+ * calling gdk_surface_show_popup(), gdk_surface_layout_popup() or after the
+ * "popup-layout-changed" is emitted.
+ */
+GdkGravity
+gdk_surface_get_popup_surface_anchor (GdkSurface *surface)
+{
+  return surface->popup.surface_anchor;
+}
+
+/**
+ * gdk_surface_get_popup_rect_anchor:
+ * @surface: a #GdkSurface
+ *
+ * Get the current popup anchor rectangle anchor. The value
+ * returned may chage after calling gdk_surface_show_popup(),
+ * gdk_surface_layout_popup() or after the "popup-layout-changed" is emitted.
+ */
+GdkGravity
+gdk_surface_get_popup_rect_anchor (GdkSurface *surface)
+{
+  return surface->popup.rect_anchor;
 }
 
 static void
diff --git a/gdk/gdksurface.h b/gdk/gdksurface.h
index 2d8c3d0b5f..6f50e66b7b 100644
--- a/gdk/gdksurface.h
+++ b/gdk/gdksurface.h
@@ -34,6 +34,7 @@
 #include <gdk/gdkevents.h>
 #include <gdk/gdkframeclock.h>
 #include <gdk/gdkmonitor.h>
+#include <gdk/gdkpopuplayout.h>
 
 G_BEGIN_DECLS
 
@@ -143,47 +144,6 @@ typedef enum
   GDK_FUNC_CLOSE       = 1 << 5
 } GdkWMFunction;
 
-/**
- * GdkAnchorHints:
- * @GDK_ANCHOR_FLIP_X: allow flipping anchors horizontally
- * @GDK_ANCHOR_FLIP_Y: allow flipping anchors vertically
- * @GDK_ANCHOR_SLIDE_X: allow sliding surface horizontally
- * @GDK_ANCHOR_SLIDE_Y: allow sliding surface vertically
- * @GDK_ANCHOR_RESIZE_X: allow resizing surface horizontally
- * @GDK_ANCHOR_RESIZE_Y: allow resizing surface vertically
- * @GDK_ANCHOR_FLIP: allow flipping anchors on both axes
- * @GDK_ANCHOR_SLIDE: allow sliding surface on both axes
- * @GDK_ANCHOR_RESIZE: allow resizing surface on both axes
- *
- * Positioning hints for aligning a surface relative to a rectangle.
- *
- * These hints determine how the surface should be positioned in the case that
- * the surface would fall off-screen if placed in its ideal position.
- *
- * For example, %GDK_ANCHOR_FLIP_X will replace %GDK_GRAVITY_NORTH_WEST with
- * %GDK_GRAVITY_NORTH_EAST and vice versa if the surface extends beyond the left
- * or right edges of the monitor.
- *
- * If %GDK_ANCHOR_SLIDE_X is set, the surface can be shifted horizontally to fit
- * on-screen. If %GDK_ANCHOR_RESIZE_X is set, the surface can be shrunken
- * horizontally to fit.
- *
- * In general, when multiple flags are set, flipping should take precedence over
- * sliding, which should take precedence over resizing.
- */
-typedef enum
-{
-  GDK_ANCHOR_FLIP_X   = 1 << 0,
-  GDK_ANCHOR_FLIP_Y   = 1 << 1,
-  GDK_ANCHOR_SLIDE_X  = 1 << 2,
-  GDK_ANCHOR_SLIDE_Y  = 1 << 3,
-  GDK_ANCHOR_RESIZE_X = 1 << 4,
-  GDK_ANCHOR_RESIZE_Y = 1 << 5,
-  GDK_ANCHOR_FLIP     = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
-  GDK_ANCHOR_SLIDE    = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
-  GDK_ANCHOR_RESIZE   = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
-} GdkAnchorHints;
-
 /**
  * GdkSurfaceEdge:
  * @GDK_SURFACE_EDGE_NORTH_WEST: the top left corner.
@@ -407,13 +367,14 @@ void          gdk_surface_resize                (GdkSurface     *surface,
                                                  gint           width,
                                                  gint           height);
 GDK_AVAILABLE_IN_ALL
-void          gdk_surface_move_to_rect          (GdkSurface         *surface,
-                                                 const GdkRectangle *rect,
-                                                 GdkGravity          rect_anchor,
-                                                 GdkGravity          surface_anchor,
-                                                 GdkAnchorHints      anchor_hints,
-                                                 gint                rect_anchor_dx,
-                                                 gint                rect_anchor_dy);
+gboolean      gdk_surface_present_popup         (GdkSurface     *surface,
+                                                 int             width,
+                                                 int             height,
+                                                 GdkPopupLayout *layout);
+GDK_AVAILABLE_IN_ALL
+GdkGravity    gdk_surface_get_popup_surface_anchor (GdkSurface     *surface);
+GDK_AVAILABLE_IN_ALL
+GdkGravity    gdk_surface_get_popup_rect_anchor    (GdkSurface     *surface);
 
 GDK_AVAILABLE_IN_ALL
 void          gdk_surface_raise                 (GdkSurface     *surface);
diff --git a/gdk/gdksurfaceprivate.h b/gdk/gdksurfaceprivate.h
index eafdb78c19..fa9674451d 100644
--- a/gdk/gdksurfaceprivate.h
+++ b/gdk/gdksurfaceprivate.h
@@ -73,6 +73,11 @@ struct _GdkSurface
   guint frame_clock_events_paused : 1;
   guint autohide : 1;
 
+  struct {
+    GdkGravity surface_anchor;
+    GdkGravity rect_anchor;
+  } popup;
+
   guint update_and_descendants_freeze_count;
 
   gint width, height;
@@ -116,13 +121,10 @@ struct _GdkSurfaceClass
   void         (* toplevel_resize)      (GdkSurface      *surface,
                                          gint             width,
                                          gint             height);
-  void         (* move_to_rect)         (GdkSurface       *surface,
-                                         const GdkRectangle *rect,
-                                         GdkGravity       rect_anchor,
-                                         GdkGravity       surface_anchor,
-                                         GdkAnchorHints   anchor_hints,
-                                         gint             rect_anchor_dx,
-                                         gint             rect_anchor_dy);
+  gboolean     (* present_popup)        (GdkSurface     *surface,
+                                         int             width,
+                                         int             height,
+                                         GdkPopupLayout *layout);
 
   void         (* get_geometry)         (GdkSurface       *surface,
                                          gint            *x,
@@ -255,18 +257,71 @@ struct _GdkSurfaceClass
 void gdk_surface_set_state (GdkSurface      *surface,
                             GdkSurfaceState  new_state);
 
-typedef void (* GdkSurfaceMovedToRect) (GdkSurface   *surface,
-                                        GdkRectangle  final_rect);
-
-void
-gdk_surface_move_to_rect_helper (GdkSurface            *surface,
-                                 const GdkRectangle    *rect,
-                                 GdkGravity             rect_anchor,
-                                 GdkGravity             surface_anchor,
-                                 GdkAnchorHints         anchor_hints,
-                                 gint                   rect_anchor_dx,
-                                 gint                   rect_anchor_dy,
-                                 GdkSurfaceMovedToRect  moved_to_rect);
+void gdk_surface_layout_popup_helper (GdkSurface     *surface,
+                                      int             width,
+                                      int             height,
+                                      GdkPopupLayout *layout,
+                                      GdkRectangle   *out_final_rect);
+
+static inline GdkGravity
+gdk_gravity_flip_horizontally (GdkGravity anchor)
+{
+  switch (anchor)
+    {
+    default:
+    case GDK_GRAVITY_STATIC:
+    case GDK_GRAVITY_NORTH_WEST:
+      return GDK_GRAVITY_NORTH_EAST;
+    case GDK_GRAVITY_NORTH:
+      return GDK_GRAVITY_NORTH;
+    case GDK_GRAVITY_NORTH_EAST:
+      return GDK_GRAVITY_NORTH_WEST;
+    case GDK_GRAVITY_WEST:
+      return GDK_GRAVITY_EAST;
+    case GDK_GRAVITY_CENTER:
+      return GDK_GRAVITY_CENTER;
+    case GDK_GRAVITY_EAST:
+      return GDK_GRAVITY_WEST;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return GDK_GRAVITY_SOUTH_EAST;
+    case GDK_GRAVITY_SOUTH:
+      return GDK_GRAVITY_SOUTH;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return GDK_GRAVITY_SOUTH_WEST;
+    }
+
+  g_assert_not_reached ();
+}
+
+static inline GdkGravity
+gdk_gravity_flip_vertically (GdkGravity anchor)
+{
+  switch (anchor)
+    {
+    default:
+    case GDK_GRAVITY_STATIC:
+    case GDK_GRAVITY_NORTH_WEST:
+      return GDK_GRAVITY_SOUTH_WEST;
+    case GDK_GRAVITY_NORTH:
+      return GDK_GRAVITY_SOUTH;
+    case GDK_GRAVITY_NORTH_EAST:
+      return GDK_GRAVITY_SOUTH_EAST;
+    case GDK_GRAVITY_WEST:
+      return GDK_GRAVITY_WEST;
+    case GDK_GRAVITY_CENTER:
+      return GDK_GRAVITY_CENTER;
+    case GDK_GRAVITY_EAST:
+      return GDK_GRAVITY_EAST;
+    case GDK_GRAVITY_SOUTH_WEST:
+      return GDK_GRAVITY_NORTH_WEST;
+    case GDK_GRAVITY_SOUTH:
+      return GDK_GRAVITY_NORTH;
+    case GDK_GRAVITY_SOUTH_EAST:
+      return GDK_GRAVITY_NORTH_EAST;
+    }
+
+  g_assert_not_reached ();
+}
 
 G_END_DECLS
 
diff --git a/gdk/meson.build b/gdk/meson.build
index ed41b126ea..af04354d88 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -45,6 +45,7 @@ gdk_public_sources = files([
   'gdktexture.c',
   'gdkvulkancontext.c',
   'gdksurface.c',
+  'gdkpopuplayout.c',
   'gdkprofiler.c'
 ])
 
@@ -90,6 +91,7 @@ gdk_public_headers = files([
   'gdktypes.h',
   'gdkvulkancontext.h',
   'gdksurface.h',
+  'gdkpopuplayout.h',
 ])
 install_headers(gdk_public_headers, subdir: 'gtk-4.0/gdk/')
 
diff --git a/gdk/quartz/gdksurface-quartz.c b/gdk/quartz/gdksurface-quartz.c
index cd8599e281..ae259e7d42 100644
--- a/gdk/quartz/gdksurface-quartz.c
+++ b/gdk/quartz/gdksurface-quartz.c
@@ -1245,51 +1245,83 @@ gdk_surface_quartz_toplevel_resize (GdkSurface *surface,
 }
 
 static void
-gdk_quartz_surface_moved_to_rect (GdkSurface   *surface,
-                                  GdkRectangle  final_rect)
+gdk_quartz_surface_layout_popup (GdkSurface     *surface,
+                                 int             width,
+                                 int             height,
+                                 GdkPopupLayout *layout)
 {
-  GdkSurface *toplevel;
+  GdkRectangle final_rect;
   int x, y;
 
-  if (surface->surface_type == GDK_SURFACE_POPUP)
-    toplevel = surface->parent;
-  else
-    toplevel = surface->transient_for;
+  gdk_surface_layout_popup_helper (surface,
+                                   width,
+                                   height,
+                                   layout,
+                                   &final_rect);
 
-  gdk_surface_get_origin (toplevel, &x, &y);
+  gdk_surface_get_origin (surface->parent, &x, &y);
   x += final_rect.x;
   y += final_rect.y;
 
   if (final_rect.width != surface->width ||
       final_rect.height != surface->height)
     {
-      window_quartz_move_resize (surface,
-                                 x, y,
-                                 final_rect.width, final_rect.height);
+      move_resize_window_internal (surface,
+                                   x,
+                                   y,
+                                   final_rect.width,
+                                   final_rect.height);
     }
   else
     {
-      window_quartz_resize (surface, final_rect.width, final_rect.height);
+      window_quartz_move (surface, x, y);
     }
 }
 
 static void
-gdk_quartz_surface_move_to_rect (GdkSurface         *surface,
-                                 const GdkRectangle *rect,
-                                 GdkGravity          rect_anchor,
-                                 GdkGravity          surface_anchor,
-                                 GdkAnchorHints      anchor_hints,
-                                 gint                rect_anchor_dx,
-                                 gint                rect_anchor_dy)
-{
-  gdk_surface_move_to_rect_helper (surface,
-                                   rect,
-                                   rect_anchor,
-                                   surface_anchor,
-                                   anchor_hints,
-                                   rect_anchor_dx,
-                                   rect_anchor_dy,
-                                   gdk_quartz_surface_moved_to_rect);
+show_popup (GdkSurface *surface)
+{
+  gdk_surface_raise (surface);
+  gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0);
+  _gdk_surface_update_viewable (surface);
+  gdk_quartz_surface_show (surface, FALSE);
+  gdk_surface_invalidate_rect (surface, NULL);
+}
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  show_popup (surface);
+}
+
+static gboolean
+gdk_quartz_surface_present_popup (GdkSurface     *surface,
+                                  int             width,
+                                  int             height,
+                                  GdkPopupLayout *layout)
+{
+  gdk_quartz_surface_layout_popup (surface, width, height, layout);
+
+  if (GDK_SURFACE_IS_MAPPED (surface))
+    return TRUE;
+
+  if (surface->autohide)
+    {
+      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
+                     surface,
+                     GDK_SEAT_CAPABILITY_ALL,
+                     TRUE,
+                     NULL, NULL,
+                     show_grabbing_popup, NULL);
+    }
+  else
+    {
+      show_popup (surface);
+    }
+
+  return GDK_SURFACE_IS_MAPPED (surface);
 }
 
 /* Get the toplevel ordering from NSApp and update our own list. We do
@@ -2675,7 +2707,7 @@ gdk_surface_impl_quartz_class_init (GdkSurfaceImplQuartzClass *klass)
   impl_class->lower = gdk_surface_quartz_lower;
   impl_class->restack_toplevel = gdk_surface_quartz_restack_toplevel;
   impl_class->toplevel_resize = gdk_surface_quartz_toplevel_resize;
-  impl_class->move_to_rect = gdk_surface_quartz_move_to_rect;
+  impl_class->present_popup = gdk_quartz_surface_present_popup;
   impl_class->get_geometry = gdk_surface_quartz_get_geometry;
   impl_class->get_root_coords = gdk_surface_quartz_get_root_coords;
   impl_class->get_device_state = gdk_surface_quartz_get_device_state;
diff --git a/gdk/wayland/gdkdevice-wayland.c b/gdk/wayland/gdkdevice-wayland.c
index 8ef0a3ae03..814c180386 100644
--- a/gdk/wayland/gdkdevice-wayland.c
+++ b/gdk/wayland/gdkdevice-wayland.c
@@ -4593,8 +4593,6 @@ gdk_wayland_seat_grab (GdkSeat                *seat,
   if (!gdk_surface_is_visible (surface))
     {
       gdk_wayland_seat_set_grab_surface (wayland_seat, NULL);
-      g_critical ("Surface %p has not been made visible in GdkSeatGrabPrepareFunc",
-                  surface);
       return GDK_GRAB_NOT_VIEWABLE;
     }
 
diff --git a/gdk/wayland/gdksurface-wayland.c b/gdk/wayland/gdksurface-wayland.c
index d9302c75e8..5a1b767089 100644
--- a/gdk/wayland/gdksurface-wayland.c
+++ b/gdk/wayland/gdksurface-wayland.c
@@ -52,6 +52,13 @@ static guint signals[LAST_SIGNAL];
 
 #define MAX_WL_BUFFER_SIZE (4083) /* 4096 minus header, string argument length and NUL byte */
 
+typedef enum _PopupState
+{
+  POPUP_STATE_IDLE,
+  POPUP_STATE_WAITING_FOR_CONFIGURE,
+  POPUP_STATE_WAITING_FOR_FRAME,
+} PopupState;
+
 struct _GdkWaylandSurface
 {
   GdkSurface parent_instance;
@@ -83,6 +90,8 @@ struct _GdkWaylandSurface
   EGLSurface egl_surface;
   EGLSurface dummy_egl_surface;
 
+  PopupState popup_state;
+
   unsigned int initial_configure_received : 1;
   unsigned int mapped : 1;
   unsigned int pending_commit : 1;
@@ -91,7 +100,6 @@ struct _GdkWaylandSurface
   GdkSurfaceTypeHint hint;
   GdkSurface *transient_for;
   GdkSurface *popup_parent;
-  gboolean has_layout_data;
 
   int pending_buffer_offset_x;
   int pending_buffer_offset_y;
@@ -137,13 +145,10 @@ struct _GdkWaylandSurface
   gulong parent_surface_committed_handler;
 
   struct {
-    GdkRectangle rect;
-    GdkGravity rect_anchor;
-    GdkGravity surface_anchor;
-    GdkAnchorHints anchor_hints;
-    gint rect_anchor_dx;
-    gint rect_anchor_dy;
-  } pending_move_to_rect;
+    GdkPopupLayout *layout;
+    int unconstrained_width;
+    int unconstrained_height;
+  } popup;
 
   struct {
     struct {
@@ -160,8 +165,11 @@ struct _GdkWaylandSurface
     } popup;
 
     uint32_t serial;
+    gboolean is_dirty;
   } pending;
 
+  int state_freeze_count;
+
   struct {
     GdkWaylandSurfaceExported callback;
     gpointer user_data;
@@ -199,15 +207,12 @@ static void gdk_wayland_surface_move_resize (GdkSurface *surface,
                                              gint        width,
                                              gint        height);
 
-static void calculate_moved_to_rect_result (GdkSurface    *surface,
-                                            int            x,
-                                            int            y,
-                                            int            width,
-                                            int            height,
-                                            GdkRectangle  *flipped_rect,
-                                            GdkRectangle  *final_rect,
-                                            gboolean      *flipped_x,
-                                            gboolean      *flipped_y);
+static void update_popup_layout_state (GdkSurface     *surface,
+                                       int             x,
+                                       int             y,
+                                       int             width,
+                                       int             height,
+                                       GdkPopupLayout *layout);
 
 static gboolean gdk_wayland_surface_is_exported (GdkSurface *surface);
 
@@ -223,6 +228,32 @@ gdk_wayland_surface_init (GdkWaylandSurface *impl)
   impl->shortcuts_inhibitors = g_hash_table_new (NULL, NULL);
 }
 
+static void
+gdk_wayland_surface_freeze_state (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  impl->state_freeze_count++;
+}
+
+static void
+gdk_wayland_surface_thaw_state (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  g_assert (impl->state_freeze_count > 0);
+
+  impl->state_freeze_count--;
+
+  if (impl->state_freeze_count > 0)
+    return;
+
+  if (impl->pending.is_dirty)
+    gdk_wayland_surface_configure (surface);
+
+  g_assert (!impl->display_server.xdg_popup);
+}
+
 static void
 _gdk_wayland_screen_add_orphan_dialog (GdkSurface *surface)
 {
@@ -341,6 +372,44 @@ fill_presentation_time_from_frame_time (GdkFrameTimings *timings,
     }
 }
 
+static GdkSurface *
+get_popup_toplevel (GdkSurface *surface)
+{
+  if (surface->parent)
+    return get_popup_toplevel (surface->parent);
+  else
+    return surface;
+}
+
+static void
+freeze_popup_toplevel_state (GdkSurface *surface)
+{
+  GdkSurface *toplevel;
+
+  toplevel = get_popup_toplevel (surface);
+  gdk_wayland_surface_freeze_state (toplevel);
+}
+
+static void
+thaw_popup_toplevel_state (GdkSurface *surface)
+{
+  GdkSurface *toplevel;
+
+  toplevel = get_popup_toplevel (surface);
+  gdk_wayland_surface_thaw_state (toplevel);
+}
+
+static void
+finish_pending_relayout (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  g_assert (impl->popup_state == POPUP_STATE_WAITING_FOR_FRAME);
+  impl->popup_state = POPUP_STATE_IDLE;
+
+  thaw_popup_toplevel_state (surface);
+}
+
 static void
 frame_callback (void               *data,
                 struct wl_callback *callback,
@@ -364,6 +433,18 @@ frame_callback (void               *data,
   if (!impl->awaiting_frame)
     return;
 
+  switch (impl->popup_state)
+    {
+    case POPUP_STATE_IDLE:
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+      break;
+    case POPUP_STATE_WAITING_FOR_FRAME:
+      finish_pending_relayout (surface);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
   impl->awaiting_frame = FALSE;
   if (impl->awaiting_frame_frozen)
     {
@@ -464,11 +545,8 @@ on_frame_clock_after_paint (GdkFrameClock *clock,
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  if (impl->pending_commit)
+  if (impl->pending_commit && surface->update_freeze_count == 0)
     {
-      if (surface->update_freeze_count > 0)
-        return;
-
       gdk_wayland_surface_request_frame (surface);
 
       /* From this commit forward, we can't write to the buffer,
@@ -671,6 +749,8 @@ gdk_wayland_surface_dispose (GObject *object)
 
   impl = GDK_WAYLAND_SURFACE (surface);
 
+  g_clear_object (&impl->popup_parent);
+
   if (impl->event_queue)
     {
       GdkWaylandDisplay *display_wayland =
@@ -1202,12 +1282,6 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
   int x, y, width, height;
-  GdkRectangle flipped_rect;
-  GdkRectangle final_rect;
-  gboolean flipped_x;
-  gboolean flipped_y;
-
-  g_return_if_fail (impl->transient_for);
 
   if (impl->display_server.xdg_popup)
     {
@@ -1220,6 +1294,18 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface)
                                      impl->pending.serial);
     }
 
+  switch (impl->popup_state)
+    {
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+      impl->popup_state = POPUP_STATE_WAITING_FOR_FRAME;
+      break;
+    case POPUP_STATE_IDLE:
+    case POPUP_STATE_WAITING_FOR_FRAME:
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
   x = impl->pending.popup.x;
   y = impl->pending.popup.y;
   width = impl->pending.popup.width;
@@ -1227,20 +1313,12 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface)
 
   gdk_wayland_surface_resize (surface, width, height, impl->scale);
 
-  calculate_moved_to_rect_result (surface,
-                                  x, y,
-                                  width, height,
-                                  &flipped_rect,
-                                  &final_rect,
-                                  &flipped_x,
-                                  &flipped_y);
+  update_popup_layout_state (surface,
+                             x, y,
+                             width, height,
+                             impl->popup.layout);
 
-  g_signal_emit_by_name (surface,
-                         "moved-to-rect",
-                         &flipped_rect,
-                         &final_rect,
-                         flipped_x,
-                         flipped_y);
+  gdk_surface_invalidate_rect (surface, NULL);
 }
 
 static void
@@ -1260,6 +1338,8 @@ gdk_wayland_surface_configure (GdkSurface *surface)
     gdk_wayland_surface_configure_toplevel (surface);
   else
     g_warn_if_reached ();
+
+  memset (&impl->pending, 0, sizeof (impl->pending));
 }
 
 static void
@@ -1268,8 +1348,12 @@ gdk_wayland_surface_handle_configure (GdkSurface *surface,
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
+  impl->pending.is_dirty = TRUE;
   impl->pending.serial = serial;
 
+  if (impl->state_freeze_count > 0)
+    return;
+
   gdk_wayland_surface_configure (surface);
 }
 
@@ -1776,70 +1860,28 @@ gdk_wayland_surface_announce_csd (GdkSurface *surface)
                                                 ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_CLIENT);
 }
 
-static GdkSurface *
-get_real_parent_and_translate (GdkSurface *surface,
-                               gint       *x,
-                               gint       *y)
-{
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkSurface *parent = impl->transient_for;
-
-  return parent;
-}
-
-static void
-translate_to_real_parent_surface_geometry (GdkSurface  *surface,
-                                           gint        *x,
-                                           gint        *y)
-{
-  GdkSurface *parent;
-
-  parent = get_real_parent_and_translate (surface, x, y);
-
-  *x -= parent->shadow_left;
-  *y -= parent->shadow_top;
-}
-
-static GdkSurface *
-translate_from_real_parent_surface_geometry (GdkSurface *surface,
-                                             gint       *x,
-                                             gint       *y)
-{
-  GdkSurface *parent;
-  gint dx = 0;
-  gint dy = 0;
-
-  parent = get_real_parent_and_translate (surface, &dx, &dy);
-
-  *x -= dx - parent->shadow_left;
-  *y -= dy - parent->shadow_top;
-
-  return parent;
-}
-
 static void
-calculate_popup_rect (GdkSurface   *surface,
-                      GdkGravity    rect_anchor,
-                      GdkGravity    surface_anchor,
-                      GdkRectangle *out_rect)
+calculate_popup_rect (GdkSurface     *surface,
+                      GdkPopupLayout *layout,
+                      GdkRectangle   *out_rect)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkRectangle geometry;
+  int width, height;
   GdkRectangle anchor_rect;
+  int dx, dy;
   int x = 0, y = 0;
 
-  gdk_wayland_surface_get_window_geometry (surface, &geometry);
+  width = (impl->popup.unconstrained_width -
+           (impl->margin_left + impl->margin_right));
+  height = (impl->popup.unconstrained_height -
+            (impl->margin_top + impl->margin_bottom));
 
-  anchor_rect = (GdkRectangle) {
-    .x = (impl->pending_move_to_rect.rect.x +
-          impl->pending_move_to_rect.rect_anchor_dx),
-    .y = (impl->pending_move_to_rect.rect.y +
-          impl->pending_move_to_rect.rect_anchor_dy),
-    .width = impl->pending_move_to_rect.rect.width,
-    .height = impl->pending_move_to_rect.rect.height
-  };
+  anchor_rect = *gdk_popup_layout_get_anchor_rect (layout);
+  gdk_popup_layout_get_offset (layout, &dx, &dy);
+  anchor_rect.x += dx;
+  anchor_rect.y += dy;
 
-  switch (rect_anchor)
+  switch (gdk_popup_layout_get_rect_anchor (layout))
     {
     default:
     case GDK_GRAVITY_STATIC:
@@ -1881,137 +1923,71 @@ calculate_popup_rect (GdkSurface   *surface,
       break;
     }
 
-  switch (surface_anchor)
+  switch (gdk_popup_layout_get_surface_anchor (layout))
     {
     default:
     case GDK_GRAVITY_STATIC:
     case GDK_GRAVITY_NORTH_WEST:
       break;
     case GDK_GRAVITY_NORTH:
-      x -= geometry.width / 2;
+      x -= width / 2;
       break;
     case GDK_GRAVITY_NORTH_EAST:
-      x -= geometry.width;
+      x -= width;
       break;
     case GDK_GRAVITY_WEST:
-      y -= geometry.height / 2;
+      y -= height / 2;
       break;
     case GDK_GRAVITY_CENTER:
-      x -= geometry.width / 2;
-      y -= geometry.height / 2;
+      x -= width / 2;
+      y -= height / 2;
       break;
     case GDK_GRAVITY_EAST:
-      x -= geometry.width;
-      y -= geometry.height / 2;
+      x -= width;
+      y -= height / 2;
       break;
     case GDK_GRAVITY_SOUTH_WEST:
-      y -= geometry.height;
+      y -= height;
       break;
     case GDK_GRAVITY_SOUTH:
-      x -= geometry.width / 2;
-      y -= geometry.height;
+      x -= width / 2;
+      y -= height;
       break;
     case GDK_GRAVITY_SOUTH_EAST:
-      x -= geometry.width;
-      y -= geometry.height;
+      x -= width;
+      y -= height;
       break;
     }
 
   *out_rect = (GdkRectangle) {
     .x = x,
     .y = y,
-    .width = geometry.width,
-    .height = geometry.height
+    .width = width,
+    .height = height
   };
 }
 
-static GdkGravity
-flip_anchor_horizontally (GdkGravity anchor)
-{
-  switch (anchor)
-    {
-    default:
-    case GDK_GRAVITY_STATIC:
-    case GDK_GRAVITY_NORTH_WEST:
-      return GDK_GRAVITY_NORTH_EAST;
-    case GDK_GRAVITY_NORTH:
-      return GDK_GRAVITY_NORTH;
-    case GDK_GRAVITY_NORTH_EAST:
-      return GDK_GRAVITY_NORTH_WEST;
-    case GDK_GRAVITY_WEST:
-      return GDK_GRAVITY_EAST;
-    case GDK_GRAVITY_CENTER:
-      return GDK_GRAVITY_CENTER;
-    case GDK_GRAVITY_EAST:
-      return GDK_GRAVITY_WEST;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return GDK_GRAVITY_SOUTH_EAST;
-    case GDK_GRAVITY_SOUTH:
-      return GDK_GRAVITY_SOUTH;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return GDK_GRAVITY_SOUTH_WEST;
-    }
-
-  g_assert_not_reached ();
-}
-
-static GdkGravity
-flip_anchor_vertically (GdkGravity anchor)
-{
-  switch (anchor)
-    {
-    default:
-    case GDK_GRAVITY_STATIC:
-    case GDK_GRAVITY_NORTH_WEST:
-      return GDK_GRAVITY_SOUTH_WEST;
-    case GDK_GRAVITY_NORTH:
-      return GDK_GRAVITY_SOUTH;
-    case GDK_GRAVITY_NORTH_EAST:
-      return GDK_GRAVITY_SOUTH_EAST;
-    case GDK_GRAVITY_WEST:
-      return GDK_GRAVITY_WEST;
-    case GDK_GRAVITY_CENTER:
-      return GDK_GRAVITY_CENTER;
-    case GDK_GRAVITY_EAST:
-      return GDK_GRAVITY_EAST;
-    case GDK_GRAVITY_SOUTH_WEST:
-      return GDK_GRAVITY_NORTH_WEST;
-    case GDK_GRAVITY_SOUTH:
-      return GDK_GRAVITY_NORTH;
-    case GDK_GRAVITY_SOUTH_EAST:
-      return GDK_GRAVITY_NORTH_EAST;
-    }
-
-  g_assert_not_reached ();
-}
-
 static void
-calculate_moved_to_rect_result (GdkSurface   *surface,
-                                int           x,
-                                int           y,
-                                int           width,
-                                int           height,
-                                GdkRectangle *flipped_rect,
-                                GdkRectangle *final_rect,
-                                gboolean     *flipped_x,
-                                gboolean     *flipped_y)
+update_popup_layout_state (GdkSurface     *surface,
+                           int             x,
+                           int             y,
+                           int             width,
+                           int             height,
+                           GdkPopupLayout *layout)
 {
-  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkSurface *parent;
   gint surface_x, surface_y;
   gint surface_width, surface_height;
   GdkRectangle best_rect;
+  GdkRectangle flipped_rect;
+  GdkGravity rect_anchor;
+  GdkGravity surface_anchor;
+  GdkAnchorHints anchor_hints;
 
-  parent = translate_from_real_parent_surface_geometry (surface, &x, &y);
-  *final_rect = (GdkRectangle) {
-    .x = x,
-    .y = y,
-    .width = width,
-    .height = height,
-  };
+  x += surface->parent->shadow_left;
+  y += surface->parent->shadow_top;
 
-  surface_x = parent->x + x;
-  surface_y = parent->y + y;
+  surface_x = x;
+  surface_y = y;
   surface_width = width + surface->shadow_left + surface->shadow_right;
   surface_height = height + surface->shadow_top + surface->shadow_bottom;
 
@@ -2019,79 +1995,117 @@ calculate_moved_to_rect_result (GdkSurface   *surface,
                                    surface_x, surface_y,
                                    surface_width, surface_height);
 
-  calculate_popup_rect (surface,
-                        impl->pending_move_to_rect.rect_anchor,
-                        impl->pending_move_to_rect.surface_anchor,
-                        &best_rect);
+  rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
+  surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
+  anchor_hints = gdk_popup_layout_get_anchor_hints (layout);
 
-  *flipped_rect = best_rect;
+  calculate_popup_rect (surface, layout, &best_rect);
+
+  flipped_rect = best_rect;
 
   if (x != best_rect.x &&
-      impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X)
+      anchor_hints & GDK_ANCHOR_FLIP_X)
     {
       GdkRectangle flipped_x_rect;
       GdkGravity flipped_rect_anchor;
       GdkGravity flipped_surface_anchor;
-
-      flipped_rect_anchor =
-        flip_anchor_horizontally (impl->pending_move_to_rect.rect_anchor);
-      flipped_surface_anchor =
-        flip_anchor_horizontally (impl->pending_move_to_rect.surface_anchor),
+      GdkPopupLayout *flipped_layout;
+
+      flipped_rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
+      flipped_surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
+      flipped_layout = gdk_popup_layout_copy (layout);
+      gdk_popup_layout_set_rect_anchor (flipped_layout,
+                                        flipped_rect_anchor);
+      gdk_popup_layout_set_surface_anchor (flipped_layout,
+                                           flipped_surface_anchor);
       calculate_popup_rect (surface,
-                            flipped_rect_anchor,
-                            flipped_surface_anchor,
+                            flipped_layout,
                             &flipped_x_rect);
+      gdk_popup_layout_unref (flipped_layout);
 
       if (flipped_x_rect.x == x)
-        flipped_rect->x = x;
+        flipped_rect.x = x;
     }
   if (y != best_rect.y &&
-      impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y)
+      anchor_hints & GDK_ANCHOR_FLIP_Y)
     {
       GdkRectangle flipped_y_rect;
       GdkGravity flipped_rect_anchor;
       GdkGravity flipped_surface_anchor;
-
-      flipped_rect_anchor =
-        flip_anchor_vertically (impl->pending_move_to_rect.rect_anchor);
-      flipped_surface_anchor =
-        flip_anchor_vertically (impl->pending_move_to_rect.surface_anchor),
+      GdkPopupLayout *flipped_layout;
+
+      flipped_rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
+      flipped_surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
+      flipped_layout = gdk_popup_layout_copy (layout);
+      gdk_popup_layout_set_rect_anchor (flipped_layout,
+                                        flipped_rect_anchor);
+      gdk_popup_layout_set_surface_anchor (flipped_layout,
+                                           flipped_surface_anchor);
       calculate_popup_rect (surface,
-                            flipped_rect_anchor,
-                            flipped_surface_anchor,
+                            flipped_layout,
                             &flipped_y_rect);
+      gdk_popup_layout_unref (flipped_layout);
 
       if (flipped_y_rect.y == y)
-        flipped_rect->y = y;
+        flipped_rect.y = y;
     }
 
-  *flipped_x = flipped_rect->x != best_rect.x;
-  *flipped_y = flipped_rect->y != best_rect.y;
+  if (flipped_rect.x != best_rect.x)
+    {
+      rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
+      surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
+    }
+  if (flipped_rect.y != best_rect.y)
+    {
+      rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
+      surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
+    }
+
+  surface->popup.rect_anchor = rect_anchor;
+  surface->popup.surface_anchor = surface_anchor;
 }
 
 static gpointer
-create_dynamic_positioner (GdkSurface *surface)
+create_dynamic_positioner (GdkSurface     *surface,
+                           int             width,
+                           int             height,
+                           GdkPopupLayout *layout)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkSurface *parent = surface->parent;
   GdkWaylandDisplay *display =
     GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
   GdkRectangle geometry;
   uint32_t constraint_adjustment = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE;
+  const GdkRectangle *anchor_rect;
   gint real_anchor_rect_x, real_anchor_rect_y;
   gint anchor_rect_width, anchor_rect_height;
+  int rect_anchor_dx;
+  int rect_anchor_dy;
+  GdkGravity rect_anchor;
+  GdkGravity surface_anchor;
+  GdkAnchorHints anchor_hints;
 
-  g_warn_if_fail (impl->has_layout_data);
+  geometry = (GdkRectangle) {
+    .x = impl->margin_left,
+    .y = impl->margin_top,
+    .width = width - (impl->margin_left + impl->margin_right),
+    .height = height - (impl->margin_top + impl->margin_bottom),
+  };
 
-  gdk_wayland_surface_get_window_geometry (surface, &geometry);
+  anchor_rect = gdk_popup_layout_get_anchor_rect (layout);
+  real_anchor_rect_x = anchor_rect->x - parent->shadow_left;
+  real_anchor_rect_y = anchor_rect->y - parent->shadow_top;
+
+  anchor_rect_width = anchor_rect->width;
+  anchor_rect_height = anchor_rect->height;
 
-  real_anchor_rect_x = impl->pending_move_to_rect.rect.x;
-  real_anchor_rect_y = impl->pending_move_to_rect.rect.y;
-  translate_to_real_parent_surface_geometry (surface,
-                                             &real_anchor_rect_x,
-                                             &real_anchor_rect_y);
+  gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy);
 
-  anchor_rect_width = impl->pending_move_to_rect.rect.width;
-  anchor_rect_height = impl->pending_move_to_rect.rect.height;
+  rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
+  surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
+
+  anchor_hints = gdk_popup_layout_get_anchor_hints (layout);
 
   switch (display->shell_variant)
     {
@@ -2109,27 +2123,25 @@ create_dynamic_positioner (GdkSurface *surface)
                                         real_anchor_rect_y,
                                         anchor_rect_width,
                                         anchor_rect_height);
-        xdg_positioner_set_offset (positioner,
-                                   impl->pending_move_to_rect.rect_anchor_dx,
-                                   impl->pending_move_to_rect.rect_anchor_dy);
+        xdg_positioner_set_offset (positioner, rect_anchor_dx, rect_anchor_dy);
 
-        anchor = rect_anchor_to_anchor (impl->pending_move_to_rect.rect_anchor);
+        anchor = rect_anchor_to_anchor (rect_anchor);
         xdg_positioner_set_anchor (positioner, anchor);
 
-        gravity = surface_anchor_to_gravity (impl->pending_move_to_rect.surface_anchor);
+        gravity = surface_anchor_to_gravity (surface_anchor);
         xdg_positioner_set_gravity (positioner, gravity);
 
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X)
+        if (anchor_hints & GDK_ANCHOR_FLIP_X)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y)
+        if (anchor_hints & GDK_ANCHOR_FLIP_Y)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_X)
+        if (anchor_hints & GDK_ANCHOR_SLIDE_X)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_Y)
+        if (anchor_hints & GDK_ANCHOR_SLIDE_Y)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_X)
+        if (anchor_hints & GDK_ANCHOR_RESIZE_X)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_Y)
+        if (anchor_hints & GDK_ANCHOR_RESIZE_Y)
           constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
         xdg_positioner_set_constraint_adjustment (positioner,
                                                   constraint_adjustment);
@@ -2151,26 +2163,26 @@ create_dynamic_positioner (GdkSurface *surface)
                                             anchor_rect_width,
                                             anchor_rect_height);
         zxdg_positioner_v6_set_offset (positioner,
-                                       impl->pending_move_to_rect.rect_anchor_dx,
-                                       impl->pending_move_to_rect.rect_anchor_dy);
+                                       rect_anchor_dx,
+                                       rect_anchor_dy);
 
-        anchor = rect_anchor_to_anchor_legacy (impl->pending_move_to_rect.rect_anchor);
+        anchor = rect_anchor_to_anchor_legacy (rect_anchor);
         zxdg_positioner_v6_set_anchor (positioner, anchor);
 
-        gravity = surface_anchor_to_gravity_legacy (impl->pending_move_to_rect.surface_anchor);
+        gravity = surface_anchor_to_gravity_legacy (surface_anchor);
         zxdg_positioner_v6_set_gravity (positioner, gravity);
 
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X)
+        if (anchor_hints & GDK_ANCHOR_FLIP_X)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y)
+        if (anchor_hints & GDK_ANCHOR_FLIP_Y)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_X)
+        if (anchor_hints & GDK_ANCHOR_SLIDE_X)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_Y)
+        if (anchor_hints & GDK_ANCHOR_SLIDE_Y)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_X)
+        if (anchor_hints & GDK_ANCHOR_RESIZE_X)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X;
-        if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_Y)
+        if (anchor_hints & GDK_ANCHOR_RESIZE_Y)
           constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
         zxdg_positioner_v6_set_constraint_adjustment (positioner,
                                                       constraint_adjustment);
@@ -2202,7 +2214,10 @@ can_map_grabbing_popup (GdkSurface *surface,
 static void
 gdk_wayland_surface_create_xdg_popup (GdkSurface     *surface,
                                       GdkSurface     *parent,
-                                      GdkWaylandSeat *grab_input_seat)
+                                      GdkWaylandSeat *grab_input_seat,
+                                      int             width,
+                                      int             height,
+                                      GdkPopupLayout *layout)
 {
   GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
@@ -2235,7 +2250,7 @@ gdk_wayland_surface_create_xdg_popup (GdkSurface     *surface,
 
   gdk_surface_freeze_updates (surface);
 
-  positioner = create_dynamic_positioner (surface);
+  positioner = create_dynamic_positioner (surface, width, height, layout);
 
   switch (display->shell_variant)
     {
@@ -2302,7 +2317,14 @@ gdk_wayland_surface_create_xdg_popup (GdkSurface     *surface,
   gdk_profiler_add_mark (g_get_monotonic_time (), 0, "wayland", "surface commit");
   wl_surface_commit (impl->display_server.wl_surface);
 
-  impl->popup_parent = parent;
+  if (surface->surface_type == GDK_SURFACE_POPUP)
+    {
+      g_assert (impl->popup_state == POPUP_STATE_IDLE);
+      impl->popup_state = POPUP_STATE_WAITING_FOR_CONFIGURE;
+      freeze_popup_toplevel_state (surface);
+    }
+
+  g_set_object (&impl->popup_parent, parent);
   display->current_popups = g_list_append (display->current_popups, surface);
   if (grab_input_seat)
     {
@@ -2360,10 +2382,9 @@ should_map_as_popup (GdkSurface *surface)
 }
 
 static void
-gdk_wayland_surface_map (GdkSurface *surface)
+gdk_wayland_surface_map_toplevel (GdkSurface *surface)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
-  GdkSurface *parent = NULL;
 
   if (!should_be_mapped (surface))
     return;
@@ -2371,25 +2392,7 @@ gdk_wayland_surface_map (GdkSurface *surface)
   if (impl->mapped)
     return;
 
-  if (should_map_as_popup (surface))
-    {
-      GdkWaylandSeat *grab_input_seat;
-
-      parent = surface->parent;
-      if (!parent)
-        {
-          g_warning ("Couldn't map as surface %p as popup because it doesn't have a parent",
-                     surface);
-          return;
-        }
-
-      grab_input_seat = find_grab_input_seat (surface, parent);
-      gdk_wayland_surface_create_xdg_popup (surface, parent, grab_input_seat);
-    }
-  else
-    {
-      gdk_wayland_surface_create_xdg_toplevel (surface);
-    }
+  gdk_wayland_surface_create_xdg_toplevel (surface);
 
   impl->mapped = TRUE;
 }
@@ -2400,10 +2403,12 @@ gdk_wayland_surface_show (GdkSurface *surface,
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
+  g_return_if_fail (!should_map_as_popup (surface));
+
   if (!impl->display_server.wl_surface)
     gdk_wayland_surface_create_surface (surface);
 
-  gdk_wayland_surface_map (surface);
+  gdk_wayland_surface_map_toplevel (surface);
 }
 
 static void
@@ -2517,6 +2522,23 @@ gdk_wayland_surface_hide_surface (GdkSurface *surface)
           gdk_surface_thaw_updates (surface);
         }
 
+      if (surface->surface_type == GDK_SURFACE_POPUP)
+        {
+          switch (impl->popup_state)
+            {
+            case POPUP_STATE_WAITING_FOR_CONFIGURE:
+            case POPUP_STATE_WAITING_FOR_FRAME:
+              thaw_popup_toplevel_state (surface);
+              break;
+            case POPUP_STATE_IDLE:
+              break;
+            default:
+              g_assert_not_reached ();
+            }
+
+          impl->popup_state = POPUP_STATE_IDLE;
+        }
+
       if (impl->display_server.gtk_surface)
         {
           gtk_surface1_destroy (impl->display_server.gtk_surface);
@@ -2533,6 +2555,8 @@ gdk_wayland_surface_hide_surface (GdkSurface *surface)
       if (impl->hint == GDK_SURFACE_TYPE_HINT_DIALOG && !impl->transient_for)
         display_wayland->orphan_dialogs =
           g_list_remove (display_wayland->orphan_dialogs, surface);
+
+      g_clear_pointer (&impl->popup.layout, gdk_popup_layout_unref);
     }
 
   unset_transient_for_exported (surface);
@@ -2606,41 +2630,217 @@ gdk_wayland_surface_toplevel_resize (GdkSurface *surface,
                                     impl->scale);
 }
 
-/* Avoid zero width/height as this is a protocol error */
+static gboolean
+is_fallback_relayout_possible (GdkSurface *surface)
+{
+  GList *l;
+
+  for (l = surface->children; l; l = l->next)
+    {
+      GdkSurface *child = l->data;
+
+      if (GDK_WAYLAND_SURFACE (child)->mapped)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+queue_relayout_fallback (GdkSurface     *surface,
+                         GdkPopupLayout *layout)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  if (!is_fallback_relayout_possible (surface))
+    return;
+
+  gdk_wayland_surface_hide_surface (surface);
+  gdk_surface_present_popup (surface,
+                             impl->popup.unconstrained_width,
+                             impl->popup.unconstrained_height,
+                             layout);
+}
+
+static void
+do_queue_relayout (GdkSurface     *surface,
+                   int             width,
+                   int             height,
+                   GdkPopupLayout *layout)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  g_assert (is_realized_popup (surface));
+  g_assert (impl->popup_state == POPUP_STATE_IDLE ||
+            impl->popup_state == POPUP_STATE_WAITING_FOR_FRAME);
+
+  g_clear_pointer (&impl->popup.layout, gdk_popup_layout_unref);
+  impl->popup.layout = gdk_popup_layout_copy (layout);
+  impl->popup.unconstrained_width = width;
+  impl->popup.unconstrained_height = height;
+
+  queue_relayout_fallback (surface, layout);
+}
+
+static gboolean
+is_relayout_finished (GdkSurface *surface)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  if (!impl->initial_configure_received)
+    return FALSE;
+
+  return TRUE;
+}
+
 static void
-sanitize_anchor_rect (GdkSurface   *surface,
-                      GdkRectangle *rect)
+gdk_wayland_surface_map_popup (GdkSurface     *surface,
+                               int             width,
+                               int             height,
+                               GdkPopupLayout *layout)
 {
-  gint original_width = rect->width;
-  gint original_height = rect->height;
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+  GdkSurface *parent;
+  GdkWaylandSeat *grab_input_seat;
+
+  if (!should_be_mapped (surface))
+    return;
 
-  rect->width  = MAX (1, rect->width);
-  rect->height = MAX (1, rect->height);
-  rect->x = MAX (rect->x + original_width - rect->width, 0);
-  rect->y = MAX (rect->y + original_height - rect->height, 0);
+  if (impl->mapped)
+    return;
+
+  parent = surface->parent;
+  if (!parent)
+    {
+      g_warning ("Couldn't map as surface %p as popup because it doesn't have a parent",
+                 surface);
+      return;
+    }
+
+  if (surface->autohide)
+    grab_input_seat = find_grab_input_seat (surface, parent);
+  else
+    grab_input_seat = NULL;
+  gdk_wayland_surface_create_xdg_popup (surface,
+                                        parent,
+                                        grab_input_seat,
+                                        width, height,
+                                        layout);
+
+  impl->popup.layout = gdk_popup_layout_copy (layout);
+  impl->popup.unconstrained_width = width;
+  impl->popup.unconstrained_height = height;
+  impl->mapped = TRUE;
 }
 
 static void
-gdk_wayland_surface_move_to_rect (GdkSurface         *surface,
-                                  const GdkRectangle *rect,
-                                  GdkGravity          rect_anchor,
-                                  GdkGravity          surface_anchor,
-                                  GdkAnchorHints      anchor_hints,
-                                  gint                rect_anchor_dx,
-                                  gint                rect_anchor_dy)
+show_popup (GdkSurface     *surface,
+            int             width,
+            int             height,
+            GdkPopupLayout *layout)
 {
   GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
 
-  impl->pending_move_to_rect.rect = *rect;
-  sanitize_anchor_rect (surface, &impl->pending_move_to_rect.rect);
+  if (!impl->display_server.wl_surface)
+    gdk_wayland_surface_create_surface (surface);
+
+  gdk_wayland_surface_map_popup (surface, width, height, layout);
+}
 
-  impl->pending_move_to_rect.rect_anchor = rect_anchor;
-  impl->pending_move_to_rect.surface_anchor = surface_anchor;
-  impl->pending_move_to_rect.anchor_hints = anchor_hints;
-  impl->pending_move_to_rect.rect_anchor_dx = rect_anchor_dx;
-  impl->pending_move_to_rect.rect_anchor_dy = rect_anchor_dy;
+typedef struct
+{
+  int width;
+  int height;
+  GdkPopupLayout *layout;
+} GrabPrepareData;
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  GrabPrepareData *data = user_data;
 
-  impl->has_layout_data = TRUE;
+  show_popup (surface, data->width, data->height, data->layout);
+}
+
+static void
+reposition_popup (GdkSurface     *surface,
+                  int             width,
+                  int             height,
+                  GdkPopupLayout *layout)
+{
+  GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
+
+  switch (impl->popup_state)
+    {
+    case POPUP_STATE_IDLE:
+    case POPUP_STATE_WAITING_FOR_FRAME:
+      do_queue_relayout (surface, width, height, layout);
+      break;
+    case POPUP_STATE_WAITING_FOR_CONFIGURE:
+      g_warn_if_reached ();
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+gdk_wayland_surface_present_popup (GdkSurface     *surface,
+                                   int             width,
+                                   int             height,
+                                   GdkPopupLayout *layout)
+{
+  GdkWaylandDisplay *display_wayland =
+    GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
+  GdkWaylandSurface *impl;
+
+  g_return_val_if_fail (should_map_as_popup (surface), FALSE);
+
+  impl = GDK_WAYLAND_SURFACE (surface);
+
+  if (!impl->mapped)
+    {
+      if (surface->autohide)
+        {
+          GrabPrepareData data;
+
+          data = (GrabPrepareData) {
+            .width = width,
+            .height = height,
+            .layout = layout,
+          };
+          gdk_seat_grab (gdk_display_get_default_seat (surface->display),
+                         surface,
+                         GDK_SEAT_CAPABILITY_ALL,
+                         TRUE,
+                         NULL, NULL,
+                         show_grabbing_popup, &data);
+        }
+      else
+        {
+          show_popup (surface, width, height, layout);
+        }
+    }
+  else
+    {
+      reposition_popup (surface, width, height, layout);
+    }
+
+  while (impl->display_server.xdg_popup && !is_relayout_finished (surface))
+    wl_display_dispatch_queue (display_wayland->wl_display, impl->event_queue);
+
+  if (impl->display_server.xdg_popup)
+    {
+      gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0);
+      gdk_surface_invalidate_rect (surface, NULL);
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
 }
 
 static void
@@ -3697,7 +3897,7 @@ gdk_wayland_surface_class_init (GdkWaylandSurfaceClass *klass)
   impl_class->lower = gdk_wayland_surface_lower;
   impl_class->restack_toplevel = gdk_wayland_surface_restack_toplevel;
   impl_class->toplevel_resize = gdk_wayland_surface_toplevel_resize;
-  impl_class->move_to_rect = gdk_wayland_surface_move_to_rect;
+  impl_class->present_popup = gdk_wayland_surface_present_popup;
   impl_class->get_geometry = gdk_wayland_surface_get_geometry;
   impl_class->get_root_coords = gdk_wayland_surface_get_root_coords;
   impl_class->get_device_state = gdk_wayland_surface_get_device_state;
diff --git a/gdk/win32/gdksurface-win32.c b/gdk/win32/gdksurface-win32.c
index eda9325bd5..d662b1059e 100644
--- a/gdk/win32/gdksurface-win32.c
+++ b/gdk/win32/gdksurface-win32.c
@@ -1289,18 +1289,21 @@ gdk_win32_surface_move (GdkSurface *surface,
 }
 
 static void
-gdk_win32_surface_moved_to_rect (GdkSurface   *surface,
-                                 GdkRectangle  final_rect)
+gdk_win32_surface_layout_popup (GdkSurface     *surface,
+                                int             width,
+                                int             height,
+                                GdkPopupLayout *layout)
 {
-  GdkSurface *toplevel;
+  GdkRectangle final_rect;
   int x, y;
 
-  if (surface->surface_type == GDK_SURFACE_POPUP)
-    toplevel = surface->parent;
-  else
-    toplevel = surface->transient_for;
+  gdk_surface_layout_popup_helper (surface,
+                                   width,
+                                   height,
+                                   layout,
+                                   &final_rect);
 
-  gdk_surface_get_origin (toplevel, &x, &y);
+  gdk_surface_get_origin (surface->parent, &x, &y);
   x += final_rect.x;
   y += final_rect.y;
 
@@ -1308,8 +1311,10 @@ gdk_win32_surface_moved_to_rect (GdkSurface   *surface,
       final_rect.height != surface->height)
     {
       gdk_win32_surface_move_resize (surface,
-                                     x, y,
-                                     final_rect.width, final_rect.height);
+                                     x,
+                                     y,
+                                     final_rect.width,
+                                     final_rect.height);
     }
   else
     {
@@ -1318,22 +1323,49 @@ gdk_win32_surface_moved_to_rect (GdkSurface   *surface,
 }
 
 static void
-gdk_win32_surface_move_to_rect (GdkSurface         *surface,
-                                const GdkRectangle *rect,
-                                GdkGravity          rect_anchor,
-                                GdkGravity          surface_anchor,
-                                GdkAnchorHints      anchor_hints,
-                                gint                rect_anchor_dx,
-                                gint                rect_anchor_dy)
+show_popup (GdkSurface *surface)
+{
+  gdk_surface_raise (surface);
+  gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0);
+  _gdk_surface_update_viewable (surface);
+  show_window_internal (surface, FALSE, FALSE);
+  gdk_surface_invalidate_rect (surface, NULL);
+}
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  show_popup (surface);
+}
+
+static gboolean
+gdk_win32_surface_present_popup (GdkSurface     *surface,
+                                 int             width,
+                                 int             height,
+                                 GdkPopupLayout *layout)
 {
-  gdk_surface_move_to_rect_helper (surface,
-                                   rect,
-                                   rect_anchor,
-                                   surface_anchor,
-                                   anchor_hints,
-                                   rect_anchor_dx,
-                                   rect_anchor_dy,
-                                   gdk_win32_surface_moved_to_rect);
+  gdk_win32_surface_layout_popup (surface, width, height, layout);
+
+  if (GDK_SURFACE_IS_MAPPED (surface))
+    return TRUE;
+
+  if (surface->autohide)
+    {
+      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
+                     surface,
+                     GDK_SEAT_CAPABILITY_ALL,
+                     TRUE,
+                     NULL, NULL,
+                     show_grabbing_popup, NULL);
+    }
+  else
+    {
+      show_popup (surface);
+    }
+
+  return GDK_SURFACE_IS_MAPPED (surface);
 }
 
 static void
@@ -5161,7 +5193,7 @@ gdk_win32_surface_class_init (GdkWin32SurfaceClass *klass)
   impl_class->lower = gdk_win32_surface_lower;
   impl_class->restack_toplevel = gdk_win32_surface_restack_toplevel;
   impl_class->toplevel_resize = gdk_win32_surface_toplevel_resize;
-  impl_class->move_to_rect = gdk_win32_surface_move_to_rect;
+  impl_class->present_popup = gdk_win32_surface_present_popup;
   impl_class->get_geometry = gdk_win32_surface_get_geometry;
   impl_class->get_device_state = gdk_surface_win32_get_device_state;
   impl_class->get_root_coords = gdk_win32_surface_get_root_coords;
diff --git a/gdk/x11/gdksurface-x11.c b/gdk/x11/gdksurface-x11.c
index b9e122a91a..3ddad661f6 100644
--- a/gdk/x11/gdksurface-x11.c
+++ b/gdk/x11/gdksurface-x11.c
@@ -1416,18 +1416,21 @@ gdk_x11_surface_move (GdkSurface *surface,
 }
 
 static void
-gdk_x11_surface_moved_to_rect (GdkSurface   *surface,
-                               GdkRectangle  final_rect)
+gdk_x11_surface_layout_popup (GdkSurface     *surface,
+                              int             width,
+                              int             height,
+                              GdkPopupLayout *layout)
 {
-  GdkSurface *toplevel;
+  GdkRectangle final_rect;
   int x, y;
 
-  if (surface->surface_type == GDK_SURFACE_POPUP)
-    toplevel = surface->parent;
-  else
-    toplevel = surface->transient_for;
+  gdk_surface_layout_popup_helper (surface,
+                                   width,
+                                   height,
+                                   layout,
+                                   &final_rect);
 
-  gdk_surface_get_origin (toplevel, &x, &y);
+  gdk_surface_get_origin (surface->parent, &x, &y);
   x += final_rect.x;
   y += final_rect.y;
 
@@ -1436,8 +1439,10 @@ gdk_x11_surface_moved_to_rect (GdkSurface   *surface,
     {
       gdk_x11_surface_move_resize (surface,
                                    TRUE,
-                                   x, y,
-                                   final_rect.width, final_rect.height);
+                                   x,
+                                   y,
+                                   final_rect.width,
+                                   final_rect.height);
     }
   else
     {
@@ -1446,22 +1451,49 @@ gdk_x11_surface_moved_to_rect (GdkSurface   *surface,
 }
 
 static void
-gdk_x11_surface_move_to_rect (GdkSurface         *surface,
-                              const GdkRectangle *rect,
-                              GdkGravity          rect_anchor,
-                              GdkGravity          surface_anchor,
-                              GdkAnchorHints      anchor_hints,
-                              gint                rect_anchor_dx,
-                              gint                rect_anchor_dy)
-{
-  gdk_surface_move_to_rect_helper (surface,
-                                   rect,
-                                   rect_anchor,
-                                   surface_anchor,
-                                   anchor_hints,
-                                   rect_anchor_dx,
-                                   rect_anchor_dy,
-                                   gdk_x11_surface_moved_to_rect);
+show_popup (GdkSurface *surface)
+{
+  gdk_surface_raise (surface);
+  gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0);
+  _gdk_surface_update_viewable (surface);
+  gdk_x11_surface_show (surface, FALSE);
+  gdk_surface_invalidate_rect (surface, NULL);
+}
+
+static void
+show_grabbing_popup (GdkSeat    *seat,
+                     GdkSurface *surface,
+                     gpointer    user_data)
+{
+  show_popup (surface);
+}
+
+static gboolean
+gdk_x11_surface_present_popup (GdkSurface     *surface,
+                               int             width,
+                               int             height,
+                               GdkPopupLayout *layout)
+{
+  gdk_x11_surface_layout_popup (surface, width, height, layout);
+
+  if (GDK_SURFACE_IS_MAPPED (surface))
+    return TRUE;
+
+  if (surface->autohide)
+    {
+      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
+                     surface,
+                     GDK_SEAT_CAPABILITY_ALL,
+                     TRUE,
+                     NULL, NULL,
+                     show_grabbing_popup, NULL);
+    }
+  else
+    {
+      show_popup (surface);
+    }
+
+  return GDK_SURFACE_IS_MAPPED (surface);
 }
 
 static void gdk_x11_surface_restack_toplevel (GdkSurface *surface,
@@ -4659,7 +4691,7 @@ gdk_x11_surface_class_init (GdkX11SurfaceClass *klass)
   impl_class->lower = gdk_x11_surface_lower;
   impl_class->restack_toplevel = gdk_x11_surface_restack_toplevel;
   impl_class->toplevel_resize = gdk_x11_surface_toplevel_resize;
-  impl_class->move_to_rect = gdk_x11_surface_move_to_rect;
+  impl_class->present_popup = gdk_x11_surface_present_popup;
   impl_class->get_geometry = gdk_x11_surface_get_geometry;
   impl_class->get_root_coords = gdk_x11_surface_get_root_coords;
   impl_class->get_device_state = gdk_x11_surface_get_device_state;
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index b626a3fbd6..a5285a2766 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -155,6 +155,7 @@ typedef struct {
   GtkCssNode *arrow_node;
   GskRenderNode *arrow_render_node;
 
+  GdkPopupLayout *layout;
   GdkRectangle final_rect;
   GtkPositionType final_position;
 } GtkPopoverPrivate;
@@ -221,14 +222,200 @@ gtk_popover_native_get_surface_transform (GtkNative *native,
         _gtk_css_number_value_get (style->size->padding_top, 100);
 }
 
+static gboolean
+is_gravity_facing_north (GdkGravity gravity)
+{
+  switch (gravity)
+    {
+    case GDK_GRAVITY_NORTH_EAST:
+    case GDK_GRAVITY_NORTH:
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+      return TRUE;
+    case GDK_GRAVITY_SOUTH_WEST:
+    case GDK_GRAVITY_WEST:
+    case GDK_GRAVITY_SOUTH_EAST:
+    case GDK_GRAVITY_EAST:
+    case GDK_GRAVITY_CENTER:
+    case GDK_GRAVITY_SOUTH:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+is_gravity_facing_south (GdkGravity gravity)
+{
+  switch (gravity)
+    {
+    case GDK_GRAVITY_SOUTH_WEST:
+    case GDK_GRAVITY_SOUTH_EAST:
+    case GDK_GRAVITY_SOUTH:
+      return TRUE;
+    case GDK_GRAVITY_NORTH_EAST:
+    case GDK_GRAVITY_NORTH:
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+    case GDK_GRAVITY_WEST:
+    case GDK_GRAVITY_EAST:
+    case GDK_GRAVITY_CENTER:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+is_gravity_facing_west (GdkGravity gravity)
+{
+  switch (gravity)
+    {
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+    case GDK_GRAVITY_SOUTH_WEST:
+    case GDK_GRAVITY_WEST:
+      return TRUE;
+    case GDK_GRAVITY_NORTH_EAST:
+    case GDK_GRAVITY_SOUTH_EAST:
+    case GDK_GRAVITY_EAST:
+    case GDK_GRAVITY_NORTH:
+    case GDK_GRAVITY_CENTER:
+    case GDK_GRAVITY_SOUTH:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+is_gravity_facing_east (GdkGravity gravity)
+{
+  switch (gravity)
+    {
+    case GDK_GRAVITY_NORTH_EAST:
+    case GDK_GRAVITY_SOUTH_EAST:
+    case GDK_GRAVITY_EAST:
+      return TRUE;
+    case GDK_GRAVITY_NORTH_WEST:
+    case GDK_GRAVITY_STATIC:
+    case GDK_GRAVITY_SOUTH_WEST:
+    case GDK_GRAVITY_WEST:
+    case GDK_GRAVITY_NORTH:
+    case GDK_GRAVITY_CENTER:
+    case GDK_GRAVITY_SOUTH:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+did_flip_horizontally (GdkGravity original_gravity,
+                       GdkGravity final_gravity)
+{
+  g_return_val_if_fail (original_gravity, FALSE);
+  g_return_val_if_fail (final_gravity, FALSE);
+
+  if (is_gravity_facing_east (original_gravity) &&
+      is_gravity_facing_west (final_gravity))
+    return TRUE;
+
+  if (is_gravity_facing_west (original_gravity) &&
+      is_gravity_facing_east (final_gravity))
+    return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+did_flip_vertically (GdkGravity original_gravity,
+                     GdkGravity final_gravity)
+{
+  g_return_val_if_fail (original_gravity, FALSE);
+  g_return_val_if_fail (final_gravity, FALSE);
+
+  if (is_gravity_facing_north (original_gravity) &&
+      is_gravity_facing_south (final_gravity))
+    return TRUE;
+
+  if (is_gravity_facing_south (original_gravity) &&
+      is_gravity_facing_north (final_gravity))
+    return TRUE;
+
+  return FALSE;
+}
+
 static void
-move_to_rect (GtkPopover *popover)
+update_popover_layout (GtkPopover     *popover,
+                       GdkPopupLayout *layout)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+  GdkRectangle final_rect;
+  gboolean flipped_x;
+  gboolean flipped_y;
+
+  g_clear_pointer (&priv->layout, gdk_popup_layout_unref);
+  priv->layout = layout;
+
+  final_rect = (GdkRectangle) {
+    .width = gdk_surface_get_width (priv->surface),
+    .height = gdk_surface_get_height (priv->surface),
+  };
+  gdk_surface_get_position (priv->surface,
+                            &final_rect.x,
+                            &final_rect.y);
+
+  flipped_x =
+    did_flip_horizontally (gdk_popup_layout_get_rect_anchor (layout),
+                           gdk_surface_get_popup_rect_anchor (priv->surface)) &&
+    did_flip_horizontally (gdk_popup_layout_get_surface_anchor (layout),
+                           gdk_surface_get_popup_surface_anchor (priv->surface));
+  flipped_y =
+    did_flip_vertically (gdk_popup_layout_get_rect_anchor (layout),
+                         gdk_surface_get_popup_rect_anchor (priv->surface)) &&
+    did_flip_vertically (gdk_popup_layout_get_surface_anchor (layout),
+                         gdk_surface_get_popup_surface_anchor (priv->surface));
+
+  gtk_widget_allocate (GTK_WIDGET (popover),
+                       gdk_surface_get_width (priv->surface),
+                       gdk_surface_get_height (priv->surface),
+                       -1, NULL);
+
+  priv->final_rect = final_rect;
+
+  switch (priv->position)
+    {
+    case GTK_POS_LEFT:
+      priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT;
+      break;
+    case GTK_POS_RIGHT:
+      priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT;
+      break;
+    case GTK_POS_TOP:
+      priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP;
+      break;
+    case GTK_POS_BOTTOM:
+      priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM;
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
+  gtk_widget_queue_draw (GTK_WIDGET (popover));
+}
+
+static GdkPopupLayout *
+create_popup_layout (GtkPopover *popover)
 {
   GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
   GdkRectangle rect;
   GdkGravity parent_anchor;
   GdkGravity surface_anchor;
   GdkAnchorHints anchor_hints;
+  GdkPopupLayout *layout;
 
   gtk_widget_get_surface_allocation (priv->relative_to, &rect);
   if (priv->has_pointing_to)
@@ -341,46 +528,39 @@ move_to_rect (GtkPopover *popover)
       g_assert_not_reached ();
     }
 
-  gdk_surface_move_to_rect (priv->surface,
-                            &rect,
-                            parent_anchor,
-                            surface_anchor,
-                            anchor_hints,
-                            0, 0);
+  layout = gdk_popup_layout_new (&rect,
+                                 parent_anchor,
+                                 surface_anchor);
+  gdk_popup_layout_set_anchor_hints (layout, anchor_hints);
+
+  return layout;
 }
 
 static void
-gtk_popover_move_resize (GtkPopover *popover)
+present_popup (GtkPopover *popover)
 {
   GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
   GtkRequisition req;
+  GdkPopupLayout *layout;
 
-  if (priv->surface)
-    {
-      gtk_widget_get_preferred_size (GTK_WIDGET (popover), NULL, &req);
-      gdk_surface_resize (priv->surface, req.width, req.height);
-      move_to_rect (popover);
-    }
+  layout = create_popup_layout (popover);
+  gtk_widget_get_preferred_size (GTK_WIDGET (popover), NULL, &req);
+  if (gdk_surface_present_popup (priv->surface,
+                                 req.width, req.height,
+                                 layout))
+    update_popover_layout (popover, layout);
 }
 
 static void
 gtk_popover_native_check_resize (GtkNative *native)
 {
   GtkPopover *popover = GTK_POPOVER (native);
-  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
   GtkWidget *widget = GTK_WIDGET (popover);
 
   if (!_gtk_widget_get_alloc_needed (widget))
     gtk_widget_ensure_allocate (widget);
   else if (gtk_widget_get_visible (widget))
-    {
-      gtk_popover_move_resize (popover);
-      if (priv->surface)
-        gtk_widget_allocate (GTK_WIDGET (popover),
-                             gdk_surface_get_width (priv->surface),
-                             gdk_surface_get_height (priv->surface),
-                             -1, NULL);
-    }
+    present_popup (popover);
 }
 
 
@@ -436,7 +616,8 @@ surface_state_changed (GtkWidget *widget)
 
   if (changed_mask & GDK_SURFACE_STATE_WITHDRAWN)
     {
-      if (priv->state & GDK_SURFACE_STATE_WITHDRAWN)
+      if (priv->state & GDK_SURFACE_STATE_WITHDRAWN &&
+          gtk_widget_is_visible (widget))
         gtk_widget_hide (widget);
     }
 }
@@ -467,36 +648,13 @@ surface_event (GdkSurface *surface,
 }
 
 static void
-surface_moved_to_rect (GdkSurface   *surface,
-                       GdkRectangle *flipped_rect,
-                       GdkRectangle *final_rect,
-                       gboolean      flipped_x,
-                       gboolean      flipped_y,
-                       GtkWidget    *widget)
+popup_layout_changed (GdkSurface *surface,
+                      GtkWidget  *widget)
 {
   GtkPopover *popover = GTK_POPOVER (widget);
   GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
 
-  priv->final_rect = *final_rect;
-
-  switch (priv->position)
-    {
-    case GTK_POS_LEFT:
-      priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT;
-      break;
-    case GTK_POS_RIGHT:
-      priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT;
-      break;
-    case GTK_POS_TOP:
-      priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP;
-      break;
-    case GTK_POS_BOTTOM:
-      priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM;
-      break;
-    default:
-      g_assert_not_reached ();
-      break;
-    }
+  update_popover_layout (popover, gdk_popup_layout_ref (priv->layout));
 }
 
 static void
@@ -612,7 +770,7 @@ gtk_popover_realize (GtkWidget *widget)
   g_signal_connect_swapped (priv->surface, "size-changed", G_CALLBACK (surface_size_changed), widget);
   g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget);
   g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget);
-  g_signal_connect (priv->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget);
+  g_signal_connect (priv->surface, "popup-layout-changed", G_CALLBACK (popup_layout_changed), widget);
 
   GTK_WIDGET_CLASS (gtk_popover_parent_class)->realize (widget);
 
@@ -634,7 +792,7 @@ gtk_popover_unrealize (GtkWidget *widget)
   g_signal_handlers_disconnect_by_func (priv->surface, surface_size_changed, widget);
   g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget);
   g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget);
-  g_signal_handlers_disconnect_by_func (priv->surface, surface_moved_to_rect, widget);
+  g_signal_handlers_disconnect_by_func (priv->surface, popup_layout_changed, widget);
   gdk_surface_set_widget (priv->surface, NULL);
   gdk_surface_destroy (priv->surface);
   g_clear_object (&priv->surface);
@@ -648,7 +806,7 @@ gtk_popover_show (GtkWidget *widget)
 
   _gtk_widget_set_visible_flag (widget, TRUE);
   gtk_widget_realize (widget);
-  gtk_popover_native_check_resize (GTK_NATIVE (widget));
+  present_popup (popover);
   gtk_widget_map (widget);
 
   if (priv->autohide)
@@ -683,8 +841,8 @@ surface_transform_changed_cb (GtkWidget               *widget,
   GtkPopover *popover = user_data;
   GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
 
-  move_to_rect (popover);
-  g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref);
+  if (priv->surface && gdk_surface_is_visible (priv->surface))
+    present_popup (popover);
 
   return G_SOURCE_CONTINUE;
 }
@@ -696,8 +854,7 @@ gtk_popover_map (GtkWidget *widget)
   GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
   GtkWidget *child;
 
-  gdk_surface_show (priv->surface);
-  move_to_rect (popover);
+  present_popup (popover);
 
   priv->surface_transform_changed_cb =
     gtk_widget_add_surface_transform_changed_callback (priv->relative_to,
@@ -755,6 +912,11 @@ gtk_popover_dispose (GObject *object)
 static void
 gtk_popover_finalize (GObject *object)
 {
+  GtkPopover *popover = GTK_POPOVER (object);
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  g_clear_pointer (&priv->layout, gdk_popup_layout_unref);
+
   G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
 }
 
@@ -1096,8 +1258,6 @@ gtk_popover_size_allocate (GtkWidget *widget,
   GtkAllocation child_alloc;
   int tail_height = priv->has_arrow ? TAIL_HEIGHT : 0;
 
-  gtk_popover_move_resize (popover);
-
   switch (priv->final_position)
     {
     case GTK_POS_TOP:
@@ -1414,13 +1574,16 @@ gtk_popover_new (GtkWidget *relative_to)
 }
 
 static void
-size_changed (GtkWidget   *widget,
-              int          width,
-              int          height,
-              int          baseline,
-              GtkPopover  *popover)
+relative_to_size_changed (GtkWidget  *widget,
+                          int         width,
+                          int         height,
+                          int         baseline,
+                          GtkPopover *popover)
 {
-  gtk_popover_move_resize (popover);
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  if (priv->surface && gdk_surface_is_visible (priv->surface))
+    present_popup (popover);
 }
 
 void
@@ -1487,7 +1650,9 @@ gtk_popover_set_relative_to (GtkPopover *popover,
 
   if (priv->relative_to)
     {
-      g_signal_handlers_disconnect_by_func (priv->relative_to, size_changed, popover);
+      g_signal_handlers_disconnect_by_func (priv->relative_to,
+                                            relative_to_size_changed,
+                                            popover);
       gtk_widget_unparent (GTK_WIDGET (popover));
     }
 
@@ -1496,7 +1661,7 @@ gtk_popover_set_relative_to (GtkPopover *popover,
   if (priv->relative_to)
     {
       g_signal_connect_object (priv->relative_to, "size-allocate",
-                               G_CALLBACK (size_changed), popover, 0);
+                               G_CALLBACK (relative_to_size_changed), popover, 0);
       gtk_css_node_set_parent (gtk_widget_get_css_node (GTK_WIDGET (popover)),
                                gtk_widget_get_css_node (relative_to));
       gtk_widget_set_parent (GTK_WIDGET (popover), relative_to);
diff --git a/gtk/gtktooltipwindow.c b/gtk/gtktooltipwindow.c
index a80c8034c5..73fc24e625 100644
--- a/gtk/gtktooltipwindow.c
+++ b/gtk/gtktooltipwindow.c
@@ -110,16 +110,34 @@ gtk_tooltip_window_native_get_surface_transform (GtkNative *native,
   *y = margin.top + border.top + padding.top;
 }
 
+static GdkPopupLayout *
+create_popup_layout (GtkTooltipWindow *window)
+{
+  GdkPopupLayout *layout;
+
+  layout = gdk_popup_layout_new (&window->rect,
+                                 window->rect_anchor,
+                                 window->surface_anchor);
+  gdk_popup_layout_set_anchor_hints (layout, window->anchor_hints);
+  gdk_popup_layout_set_offset (layout, window->dx, window->dy);
+
+  return layout;
+}
+
 static void
-move_to_rect (GtkTooltipWindow *window)
+relayout_popup (GtkTooltipWindow *window)
 {
-  gdk_surface_move_to_rect (window->surface,
-                            &window->rect,
-                            window->rect_anchor,
-                            window->surface_anchor,
-                            window->anchor_hints,
-                            window->dx,
-                            window->dy);
+  GdkPopupLayout *layout;
+
+  if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+    return;
+
+  layout = create_popup_layout (window);
+  gdk_surface_present_popup (window->surface,
+                             gdk_surface_get_width (window->surface),
+                             gdk_surface_get_height (window->surface),
+                             layout);
+  gdk_popup_layout_unref (layout);
 }
 
 static void
@@ -131,7 +149,8 @@ gtk_tooltip_window_move_resize (GtkTooltipWindow *window)
     {
       gtk_widget_get_preferred_size (GTK_WIDGET (window), NULL, &req);
       gdk_surface_resize (window->surface, req.width, req.height);
-      move_to_rect (window);
+
+      relayout_popup (window);
     }
 }
 
@@ -206,16 +225,6 @@ surface_event (GdkSurface *surface,
   return TRUE;
 }
 
-static void
-surface_moved_to_rect (GdkSurface   *surface,
-                       GdkRectangle *flipped_rect,
-                       GdkRectangle *final_rect,
-                       gboolean      flipped_x,
-                       gboolean      flipped_y,
-                       GtkWidget    *widget)
-{
-}
-
 static void
 gtk_tooltip_window_realize (GtkWidget *widget)
 {
@@ -234,7 +243,6 @@ gtk_tooltip_window_realize (GtkWidget *widget)
   g_signal_connect_swapped (window->surface, "size-changed", G_CALLBACK (surface_size_changed), widget);
   g_signal_connect (window->surface, "render", G_CALLBACK (surface_render), widget);
   g_signal_connect (window->surface, "event", G_CALLBACK (surface_event), widget);
-  g_signal_connect (window->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget);
 
   GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->realize (widget);
 
@@ -255,7 +263,6 @@ gtk_tooltip_window_unrealize (GtkWidget *widget)
   g_signal_handlers_disconnect_by_func (window->surface, surface_size_changed, widget);
   g_signal_handlers_disconnect_by_func (window->surface, surface_render, widget);
   g_signal_handlers_disconnect_by_func (window->surface, surface_event, widget);
-  g_signal_handlers_disconnect_by_func (window->surface, surface_moved_to_rect, widget);
   gdk_surface_set_widget (window->surface, NULL);
   gdk_surface_destroy (window->surface);
   g_clear_object (&window->surface);
@@ -277,7 +284,7 @@ surface_transform_changed_cb (GtkWidget               *widget,
 {
   GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (widget);
 
-  move_to_rect (window);
+  relayout_popup (window);
 
   return G_SOURCE_CONTINUE;
 }
@@ -287,10 +294,15 @@ static void
 gtk_tooltip_window_map (GtkWidget *widget)
 {
   GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (widget);
+  GdkPopupLayout *layout;
   GtkWidget *child;
 
-  gdk_surface_show (window->surface);
-  move_to_rect (window);
+  layout = create_popup_layout (window);
+  gdk_surface_present_popup (window->surface,
+                             gdk_surface_get_width (window->surface),
+                             gdk_surface_get_height (window->surface),
+                             layout);
+  gdk_popup_layout_unref (layout);
 
   window->surface_transform_changed_cb =
     gtk_widget_add_surface_transform_changed_callback (window->relative_to,
@@ -599,6 +611,6 @@ gtk_tooltip_window_position (GtkTooltipWindow *window,
   window->dx = dx;
   window->dy = dy;
 
-  move_to_rect (window);
+  relayout_popup (window);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]