[gtk/wip/baedert/resize] Defer resize invalidation to layout phase



commit 7444773a00476e63feb94cf53aacb409dd0ab3d3
Author: Timm Bäder <mail baedert org>
Date:   Sat Jul 6 07:15:18 2019 +0200

    Defer resize invalidation to layout phase
    
    When calling queue_resize()/queue_allocate(), just record a
    per-GtkWindow list of widgets that want to be resized.

 gtk/gtkcontainer.c     | 167 +++++++++++++++++++++++++++++++++++--------------
 gtk/gtkwidget.c        | 125 +++++++++++++++++++++++++++++++++---
 gtk/gtkwidgetprivate.h |   3 +
 gtk/gtkwindow.c        |  87 ++++++++++++++++++++++++++
 gtk/gtkwindowprivate.h |  17 +++++
 5 files changed, 343 insertions(+), 56 deletions(-)
---
diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c
index bbc6968bdf..cc0c3c892b 100644
--- a/gtk/gtkcontainer.c
+++ b/gtk/gtkcontainer.c
@@ -36,7 +36,7 @@
 #include "gtkstylecontextprivate.h"
 #include "gtktypebuiltins.h"
 #include "gtkwidgetprivate.h"
-//#include "gtkwindowprivate.h"
+#include "gtkdebug.h"
 
 #include "a11y/gtkcontaineraccessibleprivate.h"
 
@@ -85,6 +85,7 @@
  * See more about implementing custom widgets at https://wiki.gnome.org/HowDoI/CustomWidgets
  */
 
+#define N_ALLOCATE_ITERATIONS 2
 
 struct _GtkContainerPrivate
 {
@@ -333,62 +334,135 @@ gtk_container_remove (GtkContainer *container,
   g_object_unref (container);
 }
 
-static gboolean
-gtk_container_needs_idle_sizer (GtkContainer *container)
+static void
+gtk_container_idle_sizer (GdkFrameClock *clock,
+                          GtkContainer  *container)
 {
   GtkContainerPrivate *priv = gtk_container_get_instance_private (container);
+  GtkWidget *widget = GTK_WIDGET (container);
+  GtkWindow *window;
+  GPtrArray *resize_widgets;
+  GPtrArray *allocate_widgets;
+  int allocate_iteration;
+  guint i;
+
+  if (!GTK_IS_WINDOW (container))
+    return;
 
-  if (priv->restyle_pending)
-    return TRUE;
 
-  return gtk_widget_needs_allocate (GTK_WIDGET (container));
-}
+  g_message ("========== %s ========", __FUNCTION__);
 
-static void
-gtk_container_idle_sizer (GdkFrameClock *clock,
-                         GtkContainer  *container)
-{
-  GtkContainerPrivate *priv = gtk_container_get_instance_private (container);
+  window = GTK_WINDOW (container);
+  resize_widgets = gtk_window_get_resize_widgets (window);
+  allocate_widgets = gtk_window_get_allocate_widgets (window);
 
-  /* We validate the style contexts in a single loop before even trying
-   * to handle resizes instead of doing validations inline.
-   * This is mostly necessary for compatibility reasons with old code,
-   * because both style_updated and size_allocate functions often change
-   * styles and so could cause infinite loops in this function.
-   *
-   * It's important to note that even an invalid style context returns
-   * sane values. So the result of an invalid style context will never be
-   * a program crash, but only a wrong layout or rendering.
-   */
-  if (priv->restyle_pending)
+  for (allocate_iteration = 0; allocate_iteration < N_ALLOCATE_ITERATIONS; allocate_iteration ++)
     {
-      priv->restyle_pending = FALSE;
-      gtk_css_node_validate (gtk_widget_get_css_node (GTK_WIDGET (container)));
-    }
+      const gboolean last_iteration = (allocate_iteration == N_ALLOCATE_ITERATIONS - 1);
+      g_message ("Iteration %d", allocate_iteration);
 
-  /* we may be invoked with a container_resize_queue of NULL, because
-   * queue_resize could have been adding an extra idle function while
-   * the queue still got processed. we better just ignore such case
-   * than trying to explicitly work around them with some extra flags,
-   * since it doesn't cause any actual harm.
-   */
-  if (gtk_widget_needs_allocate (GTK_WIDGET (container)))
-    {
-      if (GTK_IS_WINDOW (container))
-        gtk_window_check_resize (GTK_WINDOW (container));
-      else
-        g_warning ("gtk_container_idle_sizer() called on a non-window");
-    }
+      if (last_iteration)
+        gtk_window_set_last_allocate_iteration (window, TRUE);
 
-  if (!gtk_container_needs_idle_sizer (container))
-    {
-      gtk_container_stop_idle_sizer (container);
+      /* Actually mutate the widget tree */
+      for (i = 0; i < resize_widgets->len; i ++)
+        {
+          GtkWidget *w = g_ptr_array_index (resize_widgets, i);
+          old_gtk_widget_queue_resize (w);
+        }
+
+      for (i = 0; i < allocate_widgets->len; i ++)
+        {
+          GtkWidget *w = g_ptr_array_index (allocate_widgets, i);
+          old_gtk_widget_queue_allocate (w);
+        }
+
+      /* Clear for now */
+      g_ptr_array_remove_range (resize_widgets, 0, resize_widgets->len);
+      g_ptr_array_remove_range (allocate_widgets, 0, allocate_widgets->len);
+
+      /* We validate the style contexts in a single loop before even trying
+       * to handle resizes instead of doing validations inline.
+       * This is mostly necessary for compatibility reasons with old code,
+       * because both style_updated and size_allocate functions often change
+       * styles and so could cause infinite loops in this function.
+       *
+       * It's important to note that even an invalid style context returns
+       * sane values. So the result of an invalid style context will never be
+       * a program crash, but only a wrong layout or rendering.
+       */
+      if (priv->restyle_pending)
+        {
+          priv->restyle_pending = FALSE;
+          gtk_css_node_validate (gtk_widget_get_css_node (GTK_WIDGET (container)));
+        }
+
+      /* Actually do the allocate */
+      gtk_window_check_resize (GTK_WINDOW (container));
+
+      if (resize_widgets->len   == 0 &&
+          allocate_widgets->len == 0 &&
+          priv->restyle_pending == 0)
+        {
+          gtk_container_stop_idle_sizer (container);
+
+          /* We don't need a second iteration */
+          break;
+        }
+      else if (!last_iteration)
+        {
+          gdk_frame_clock_request_phase (clock,
+                                         GDK_FRAME_CLOCK_PHASE_LAYOUT);
+
+          g_message ("Second iteration caused by...");
+          for (i = 0; i < resize_widgets->len; i ++)
+            {
+              GtkWidget *w = g_ptr_array_index (resize_widgets, i);
+              g_message ("Resize:: %s %s %p",
+                         G_OBJECT_TYPE_NAME (w),
+                         gtk_css_node_get_name (gtk_widget_get_css_node (w)),
+                         w);
+            }
+
+          for (i = 0; i < allocate_widgets->len; i ++)
+            {
+              GtkWidget *w = g_ptr_array_index (allocate_widgets, i);
+              g_message ("Allocate: %s %s %p",
+                         G_OBJECT_TYPE_NAME (w),
+                         gtk_css_node_get_name (gtk_widget_get_css_node (w)),
+                         w);
+            }
+
+
+        }
     }
-  else
+
+  if (1 || GTK_DISPLAY_DEBUG_CHECK (_gtk_widget_get_display (widget), GEOMETRY))
     {
-      gdk_frame_clock_request_phase (clock,
-                                     GDK_FRAME_CLOCK_PHASE_LAYOUT);
+      for (i = 0; i < resize_widgets->len; i ++)
+        {
+          GtkWidget *w = g_ptr_array_index (resize_widgets, i);
+          g_warning ("    Resize after second iteration: %s %s %p",
+                     G_OBJECT_TYPE_NAME (w),
+                     gtk_css_node_get_name (gtk_widget_get_css_node (w)),
+                     w);
+        }
+
+      for (i = 0; i < allocate_widgets->len; i ++)
+        {
+          GtkWidget *w = g_ptr_array_index (allocate_widgets, i);
+          g_warning ("    Allocate after second iteration: %s %s %p",
+                     G_OBJECT_TYPE_NAME (w),
+                     gtk_css_node_get_name (gtk_widget_get_css_node (w)),
+                     w);
+        }
     }
+
+  /* Ignore everything after the last iteration */
+  g_ptr_array_remove_range (resize_widgets, 0, resize_widgets->len);
+  g_ptr_array_remove_range (allocate_widgets, 0, allocate_widgets->len);
+  gtk_container_stop_idle_sizer (container);
+  gtk_window_set_last_allocate_iteration (window, FALSE);
 }
 
 void
@@ -400,9 +474,6 @@ gtk_container_start_idle_sizer (GtkContainer *container)
   if (priv->resize_handler != 0)
     return;
 
-  if (!gtk_container_needs_idle_sizer (container))
-    return;
-
   clock = gtk_widget_get_frame_clock (GTK_WIDGET (container));
   if (clock == NULL)
     return;
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 42ba750375..d8f71e1bf9 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -859,6 +859,59 @@ gtk_widget_real_grab_notify (GtkWidget *widget,
     }
 }
 
+
+static void
+gtk_widget_dequeue_resize (GtkWidget *widget)
+{
+  GtkRoot *root;
+  GtkWindow *window;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  root = gtk_widget_get_root (widget);
+
+  if (!root)
+    return;
+
+  /* TODO: This check should ultimately go away I guess. */
+  if (!GTK_IS_WINDOW (root))
+    {
+      g_warning ("%s: Not in a window...", __FUNCTION__);
+      return;
+    }
+
+  window = GTK_WINDOW (root);
+  gtk_window_remove_resize_widget (window, widget);
+}
+
+
+static void
+gtk_widget_dequeue_allocate (GtkWidget *widget)
+{
+  GtkRoot *root;
+  GtkWindow *window;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  root = gtk_widget_get_root (widget);
+
+  if (!root)
+    return;
+
+  /* TODO: This check should ultimately go away I guess. */
+  if (!GTK_IS_WINDOW (root))
+    {
+      g_warning ("%s: Not in a window...", __FUNCTION__);
+      return;
+    }
+
+  window = GTK_WINDOW (root);
+  gtk_window_remove_allocate_widget (window, widget);
+}
+
+
+
+
 static void
 gtk_widget_real_root (GtkWidget *widget)
 {
@@ -868,6 +921,10 @@ gtk_widget_real_root (GtkWidget *widget)
 static void
 gtk_widget_real_unroot (GtkWidget *widget)
 {
+
+  gtk_widget_dequeue_resize (widget);
+  gtk_widget_dequeue_allocate (widget);
+
   gtk_widget_forall (widget, (GtkCallback) gtk_widget_unroot, NULL);
 }
 
@@ -2921,6 +2978,9 @@ gtk_widget_unroot (GtkWidget *widget)
   g_assert (priv->root);
   g_assert (!priv->realized);
 
+  gtk_widget_dequeue_resize (widget);
+  gtk_widget_dequeue_allocate (widget);
+
   surface_transform_data = priv->surface_transform_data;
   if (surface_transform_data &&
       surface_transform_data->tracked_parent)
@@ -4076,12 +4136,25 @@ gtk_widget_set_alloc_needed (GtkWidget *widget);
 void
 gtk_widget_queue_allocate (GtkWidget *widget)
 {
+  GtkRoot *root;
+  GtkWindow *window;
+
   g_return_if_fail (GTK_IS_WIDGET (widget));
 
-  if (_gtk_widget_get_realized (widget))
-    gtk_widget_queue_draw (widget);
+  root = gtk_widget_get_root (widget);
 
-  gtk_widget_set_alloc_needed (widget);
+  if (!root)
+    return;
+
+  /* TODO: This check should ultimately go away I guess. */
+  if (!GTK_IS_WINDOW (root))
+    {
+      g_warning ("%s: Not in a window...", __FUNCTION__);
+      return;
+    }
+
+  window = GTK_WINDOW (root);
+  gtk_window_add_allocate_widget (window, widget);
 }
 
 static inline gboolean
@@ -4133,6 +4206,28 @@ gtk_widget_queue_resize_internal (GtkWidget *widget)
     }
 }
 
+void
+old_gtk_widget_queue_resize (GtkWidget *widget)
+{
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  if (_gtk_widget_get_realized (widget))
+    gtk_widget_queue_draw (widget);
+
+  gtk_widget_queue_resize_internal (widget);
+}
+
+void
+old_gtk_widget_queue_allocate (GtkWidget *widget)
+{
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  if (_gtk_widget_get_realized (widget))
+    gtk_widget_queue_draw (widget);
+
+  gtk_widget_set_alloc_needed (widget);
+}
+
 /**
  * gtk_widget_queue_resize:
  * @widget: a #GtkWidget
@@ -4151,12 +4246,25 @@ gtk_widget_queue_resize_internal (GtkWidget *widget)
 void
 gtk_widget_queue_resize (GtkWidget *widget)
 {
+  GtkRoot *root;
+  GtkWindow *window;
+
   g_return_if_fail (GTK_IS_WIDGET (widget));
 
-  if (_gtk_widget_get_realized (widget))
-    gtk_widget_queue_draw (widget);
+  root = gtk_widget_get_root (widget);
 
-  gtk_widget_queue_resize_internal (widget);
+  if (!root)
+    return;
+
+  /* TODO: This check should ultimately go away I guess. */
+  if (!GTK_IS_WINDOW (root))
+    {
+      g_warning ("%s: Not in a window...", __FUNCTION__);
+      return;
+    }
+
+  window = GTK_WINDOW (root);
+  gtk_window_add_resize_widget (window, widget);
 }
 
 /**
@@ -4171,7 +4279,8 @@ gtk_widget_queue_resize_no_redraw (GtkWidget *widget)
 {
   g_return_if_fail (GTK_IS_WIDGET (widget));
 
-  gtk_widget_queue_resize_internal (widget);
+  /* TODO: Remove this entire function */
+  gtk_widget_queue_resize (widget);
 }
 
 /**
@@ -11498,7 +11607,7 @@ gtk_widget_set_alloc_needed (GtkWidget *widget)
 
       if (GTK_IS_ROOT (widget))
         {
-          gtk_container_start_idle_sizer (GTK_CONTAINER (widget));
+          /*gtk_container_start_idle_sizer (GTK_CONTAINER (widget));*/
           break;
         }
 
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index 26df307cc2..4ecc160996 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -367,6 +367,9 @@ guint             gtk_widget_add_surface_transform_changed_callback (GtkWidget
 void              gtk_widget_remove_surface_transform_changed_callback (GtkWidget *widget,
                                                                         guint      id);
 
+void              old_gtk_widget_queue_resize   (GtkWidget *widget);
+void              old_gtk_widget_queue_allocate (GtkWidget *widget);
+
 
 /* inline getters */
 
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index dab50be990..2be4ac06bc 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -266,6 +266,8 @@ typedef struct
   guint    hide_on_close             : 1;
   guint    in_emit_close_request     : 1;
 
+  guint    last_allocate_iteration : 1;
+
   GdkSurfaceTypeHint type_hint;
 
   GtkGesture *click_gesture;
@@ -283,6 +285,10 @@ typedef struct
   GList *foci;
 
   GtkConstraintSolver *constraint_solver;
+
+  GPtrArray *resize_widgets;
+  GPtrArray *allocate_widgets;
+
 } GtkWindowPrivate;
 
 #ifdef GDK_WINDOWING_X11
@@ -1836,6 +1842,9 @@ gtk_window_init (GtkWindow *window)
   priv->has_user_ref_count = TRUE;
   gtk_window_update_debugging ();
 
+  priv->resize_widgets = g_ptr_array_new ();
+  priv->allocate_widgets = g_ptr_array_new ();
+
 #ifdef GDK_WINDOWING_X11
   g_signal_connect (gtk_settings_get_for_display (priv->display),
                     "notify::gtk-application-prefer-dark-theme",
@@ -4720,6 +4729,9 @@ gtk_window_finalize (GObject *object)
       priv->keys_changed_handler = 0;
     }
 
+  g_ptr_array_free (priv->resize_widgets, TRUE);
+  g_ptr_array_free (priv->allocate_widgets, TRUE);
+
   g_signal_handlers_disconnect_by_func (gdk_display_get_default_seat (priv->display),
                                         device_removed_cb,
                                         window);
@@ -9521,3 +9533,78 @@ gtk_window_maybe_update_cursor (GtkWindow *window,
         break;
     }
 }
+
+void
+gtk_window_add_resize_widget (GtkWindow *window,
+                              GtkWidget *widget)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  if (g_ptr_array_find (priv->resize_widgets, widget, NULL))
+    return;
+
+  if (priv->last_allocate_iteration)
+    g_critical ("Resize of %s %p caused N+1 allocate iterations.", G_OBJECT_TYPE_NAME (widget), widget);
+
+  g_ptr_array_add (priv->resize_widgets, widget);
+  gtk_container_start_idle_sizer (GTK_CONTAINER (window));
+}
+
+void
+gtk_window_remove_resize_widget (GtkWindow *window,
+                                 GtkWidget *widget)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  g_ptr_array_remove_fast (priv->resize_widgets, widget);
+}
+
+void
+gtk_window_add_allocate_widget (GtkWindow *window,
+                              GtkWidget *widget)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  if (g_ptr_array_find (priv->allocate_widgets, widget, NULL))
+    return;
+
+  if (priv->last_allocate_iteration)
+    g_critical ("Allocate of %s %p caused N+1 allocate iterations.", G_OBJECT_TYPE_NAME (widget), widget);
+
+  g_ptr_array_add (priv->allocate_widgets, widget);
+  gtk_container_start_idle_sizer (GTK_CONTAINER (window));
+}
+
+void
+gtk_window_remove_allocate_widget (GtkWindow *window,
+                                 GtkWidget *widget)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  g_ptr_array_remove_fast (priv->allocate_widgets, widget);
+}
+
+GPtrArray *
+gtk_window_get_resize_widgets (GtkWindow *window)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  return priv->resize_widgets;
+}
+
+GPtrArray *
+gtk_window_get_allocate_widgets (GtkWindow *window)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  return priv->allocate_widgets;
+}
+
+void
+gtk_window_set_last_allocate_iteration (GtkWindow *window,
+                                        gboolean   is_last)
+{
+  GtkWindowPrivate *priv = gtk_window_get_instance_private (window);
+
+  priv->last_allocate_iteration = is_last;
+}
diff --git a/gtk/gtkwindowprivate.h b/gtk/gtkwindowprivate.h
index 941fe9c8f1..e3161b3e42 100644
--- a/gtk/gtkwindowprivate.h
+++ b/gtk/gtkwindowprivate.h
@@ -164,6 +164,23 @@ GtkWidget *      gtk_window_pick_popover (GtkWindow   *window,
                                           double       y,
                                           GtkPickFlags flags);
 
+GPtrArray * gtk_window_get_resize_widgets (GtkWindow *window);
+void             gtk_window_add_resize_widget    (GtkWindow *window,
+                                                  GtkWidget *widget);
+void             gtk_window_remove_resize_widget (GtkWindow *window,
+                                                  GtkWidget *widget);
+
+GPtrArray * gtk_window_get_allocate_widgets (GtkWindow *window);
+void             gtk_window_add_allocate_widget    (GtkWindow *window,
+                                                    GtkWidget *widget);
+void             gtk_window_remove_allocate_widget (GtkWindow *window,
+                                                    GtkWidget *widget);
+
+void             gtk_window_set_last_allocate_iteration (GtkWindow *window,
+                                                         gboolean   is_last);
+
+
+
 G_END_DECLS
 
 #endif /* __GTK_WINDOW_PRIVATE_H__ */


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