[gtk+/wip/simple-draw] GtkViewport: Add offscreen surface based scrolling



commit 9da2f293e9eb948de0e70ae48ade4500d9f4b5cd
Author: Alexander Larsson <alexl redhat com>
Date:   Sun Apr 21 01:54:05 2013 +0200

    GtkViewport: Add offscreen surface based scrolling
    
    Rather than scroll via XCopyArea (which we no longer do) we keep an
    offscreen buffer of the scrolled area with some extra space outside
    the visible area and when we expose the viewport we just blit the
    offscreen to the right place.

 gtk/gtkviewport.c | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 223 insertions(+), 16 deletions(-)
---
diff --git a/gtk/gtkviewport.c b/gtk/gtkviewport.c
index 0469c5b..4781589 100644
--- a/gtk/gtkviewport.c
+++ b/gtk/gtkviewport.c
@@ -66,6 +66,13 @@ struct _GtkViewportPrivate
   GdkWindow      *bin_window;
   GdkWindow      *view_window;
 
+  int backing_surface_x;
+  int backing_surface_y;
+  int backing_surface_w;
+  int backing_surface_h;
+  cairo_surface_t *backing_surface;
+  cairo_region_t *backing_surface_dirty;
+
   /* GtkScrollablePolicy needs to be checked when
    * driving the scrollable adjustment values */
   guint hscroll_policy : 1;
@@ -649,6 +656,39 @@ gtk_viewport_get_view_window (GtkViewport *viewport)
   return viewport->priv->view_window;
 }
 
+static gboolean
+gtk_viewport_bin_window_update_handler (GdkWindow *window,
+                                       cairo_region_t *region)
+{
+  gpointer widget;
+  GtkViewport *viewport;
+  GtkViewportPrivate *priv;
+  cairo_rectangle_int_t r;
+
+  gdk_window_get_user_data (window, &widget);
+  viewport = GTK_VIEWPORT (widget);
+  priv = viewport->priv;
+
+  if (priv->backing_surface_dirty == NULL)
+    priv->backing_surface_dirty = cairo_region_create ();
+
+  cairo_region_translate (region,
+                         -priv->backing_surface_x,
+                         -priv->backing_surface_y);
+  cairo_region_union (priv->backing_surface_dirty, region);
+  cairo_region_translate (region,
+                         priv->backing_surface_x,
+                         priv->backing_surface_y);
+
+  r.x = 0;
+  r.y = 0;
+  r.width = priv->backing_surface_w;
+  r.height = priv->backing_surface_h;
+  cairo_region_intersect_rectangle (priv->backing_surface_dirty, &r);
+
+  return TRUE;
+}
+
 static void
 gtk_viewport_realize (GtkWidget *widget)
 {
@@ -713,6 +753,8 @@ gtk_viewport_realize (GtkWidget *widget)
 
   priv->bin_window = gdk_window_new (priv->view_window, &attributes, attributes_mask);
   gtk_widget_register_window (widget, priv->bin_window);
+  gdk_window_set_update_handler (priv->bin_window,
+                                gtk_viewport_bin_window_update_handler);
 
   child = gtk_bin_get_child (bin);
   if (child)
@@ -750,7 +792,10 @@ gtk_viewport_draw (GtkWidget *widget,
   GtkViewport *viewport = GTK_VIEWPORT (widget);
   GtkViewportPrivate *priv = viewport->priv;
   GtkStyleContext *context;
-  int x, y;
+  cairo_t *backing_cr;
+  GtkWidget *child;
+  int x, y, bin_x, bin_y, new_surf_x, new_surf_y;
+  cairo_rectangle_int_t view_pos;
 
   context = gtk_widget_get_style_context (widget);
 
@@ -766,29 +811,147 @@ gtk_viewport_draw (GtkWidget *widget,
 
       gtk_style_context_restore (context);
     }
-  
-  if (gtk_cairo_should_draw_window (cr, priv->view_window))
+
+  if (priv->backing_surface &&
+      /* Don't use backing surface if rendering elsewhere */
+      cairo_surface_get_type (priv->backing_surface) == cairo_surface_get_type (cairo_get_target (cr)))
     {
-      /* This is a cute hack to ensure the contents of bin_window are
-       * restricted to where they are visible. We only need to do this
-       * clipping when called via gtk_widget_draw() and not in expose
-       * events. And when that happens every window (including this one)
-       * should be drawn.
-       */
+      gdk_window_get_position (priv->bin_window, &bin_x, &bin_y);
+      view_pos.x = -bin_x;
+      view_pos.y = -bin_y;
+      view_pos.width = gdk_window_get_width (priv->view_window);
+      view_pos.height = gdk_window_get_height (priv->view_window);
+
+      /* Reposition so all is visible visible */
+      if (priv->backing_surface)
+       {
+         cairo_rectangle_int_t r;
+         cairo_region_t *copy_region;
+         if (view_pos.x < priv->backing_surface_x ||
+             view_pos.x + view_pos.width >
+             priv->backing_surface_x + priv->backing_surface_w ||
+             view_pos.y < priv->backing_surface_y ||
+             view_pos.y + view_pos.height >
+             priv->backing_surface_y + priv->backing_surface_h)
+           {
+             new_surf_x = priv->backing_surface_x;
+             if (view_pos.x < priv->backing_surface_x)
+               new_surf_x = view_pos.x - (priv->backing_surface_w - view_pos.width);
+             else if (view_pos.x + view_pos.width >
+                      priv->backing_surface_x + priv->backing_surface_w)
+               new_surf_x = view_pos.x;
+
+             new_surf_y = priv->backing_surface_y;
+             if (view_pos.y < priv->backing_surface_y)
+               new_surf_y = view_pos.y - (priv->backing_surface_h - view_pos.height);
+             else if (view_pos.y + view_pos.height >
+                      priv->backing_surface_y + priv->backing_surface_h)
+               new_surf_y = view_pos.y;
+
+             r.x = 0;
+             r.y = 0;
+             r.width = priv->backing_surface_w;
+             r.height = priv->backing_surface_h;
+             copy_region = cairo_region_create_rectangle (&r);
+
+             if (priv->backing_surface_dirty)
+               {
+                 cairo_region_subtract (copy_region, priv->backing_surface_dirty);
+                 cairo_region_destroy (priv->backing_surface_dirty);
+                 priv->backing_surface_dirty = NULL;
+               }
+
+             cairo_region_translate (copy_region,
+                                     priv->backing_surface_x - new_surf_x,
+                                     priv->backing_surface_y - new_surf_y);
+             cairo_region_intersect_rectangle (copy_region, &r);
+
+             backing_cr = cairo_create (priv->backing_surface);
+             gdk_cairo_region (backing_cr, copy_region);
+             cairo_clip (backing_cr);
+             cairo_push_group (backing_cr);
+             cairo_set_source_surface (backing_cr, priv->backing_surface,
+                                       priv->backing_surface_x - new_surf_x,
+                                       priv->backing_surface_y - new_surf_y);
+             cairo_paint (backing_cr);
+             cairo_pop_group_to_source (backing_cr);
+             cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+             cairo_paint (backing_cr);
+             cairo_destroy (backing_cr);
+
+             priv->backing_surface_x = new_surf_x;
+             priv->backing_surface_y = new_surf_y;
+
+             cairo_region_xor_rectangle (copy_region, &r);
+             priv->backing_surface_dirty = copy_region;
+           }
+       }
+
+      if (priv->backing_surface_dirty &&
+         !cairo_region_is_empty (priv->backing_surface_dirty))
+       {
+         backing_cr = cairo_create (priv->backing_surface);
+         gdk_cairo_region (backing_cr, priv->backing_surface_dirty);
+         cairo_clip (backing_cr);
+         cairo_translate (backing_cr,
+                          -priv->backing_surface_x,
+                          -priv->backing_surface_y);
+         cairo_set_source_rgba (backing_cr,
+                                0, 0, 0, 0);
+         cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+         cairo_paint (backing_cr);
+         cairo_set_operator (backing_cr, CAIRO_OPERATOR_OVER);
+         gtk_render_background (context, backing_cr,
+                                0,0,
+                                gdk_window_get_width (priv->bin_window),
+                                gdk_window_get_height (priv->bin_window));
+         child = gtk_bin_get_child (GTK_BIN (widget));
+         if (child && gtk_widget_get_visible (child)) {
+           if (!gtk_widget_get_has_window (child))
+             {
+               GtkAllocation child_allocation;
+               gtk_widget_get_allocation (child, &child_allocation);
+               cairo_translate (backing_cr,
+                                child_allocation.x,
+                                child_allocation.y);
+             }
+
+           gtk_widget_draw (child, backing_cr);
+         }
+
+         cairo_destroy (backing_cr);
+       }
+
+      if (priv->backing_surface_dirty)
+       {
+         cairo_region_destroy (priv->backing_surface_dirty);
+         priv->backing_surface_dirty = NULL;
+       }
+
+      if (gtk_cairo_should_draw_window (cr, priv->view_window))
+       {
+         gdk_window_get_position (priv->view_window, &x, &y);
+         cairo_set_source_surface (cr, priv->backing_surface,
+                                   priv->backing_surface_x + bin_x + x,
+                                   priv->backing_surface_y + bin_y + y);
+         cairo_rectangle (cr, x, y,
+                          gdk_window_get_width (priv->view_window),
+                          gdk_window_get_height (priv->view_window));
+         cairo_fill (cr);
+       }
+    }
+  else
+    {
+      /* Don't use backing_surface */
       gdk_window_get_position (priv->view_window, &x, &y);
-      cairo_rectangle (cr, x, y, 
+      cairo_rectangle (cr, x, y,
                        gdk_window_get_width (priv->view_window),
                        gdk_window_get_height (priv->view_window));
       cairo_clip (cr);
-    }
-
-  if (gtk_cairo_should_draw_window (cr, priv->bin_window))
-    {
       gdk_window_get_position (priv->bin_window, &x, &y);
       gtk_render_background (context, cr, x, y,
                              gdk_window_get_width (priv->bin_window),
                              gdk_window_get_height (priv->bin_window));
-
       GTK_WIDGET_CLASS (gtk_viewport_parent_class)->draw (widget, cr);
     }
 
@@ -823,6 +986,7 @@ gtk_viewport_size_allocate (GtkWidget     *widget,
   GtkAdjustment *vadjustment = priv->vadjustment;
   GtkAllocation child_allocation;
   GtkWidget *child;
+  int surface_w, surface_h;
 
   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
 
@@ -843,7 +1007,7 @@ gtk_viewport_size_allocate (GtkWidget     *widget,
 
   viewport_set_hadjustment_values (viewport);
   viewport_set_vadjustment_values (viewport);
-  
+
   child_allocation.x = 0;
   child_allocation.y = 0;
   child_allocation.width = gtk_adjustment_get_upper (hadjustment);
@@ -851,6 +1015,7 @@ gtk_viewport_size_allocate (GtkWidget     *widget,
   if (gtk_widget_get_realized (widget))
     {
       GtkAllocation view_allocation;
+      cairo_rectangle_int_t rect;
 
       gdk_window_move_resize (gtk_widget_get_window (widget),
                              allocation->x + border_width,
@@ -869,6 +1034,48 @@ gtk_viewport_size_allocate (GtkWidget     *widget,
                               - gtk_adjustment_get_value (vadjustment),
                               child_allocation.width,
                               child_allocation.height);
+
+      surface_w = view_allocation.width;
+      if (child_allocation.width > view_allocation.width)
+       surface_w = MIN (surface_w + 64, child_allocation.width);
+
+      surface_h = view_allocation.height;
+      if (child_allocation.height > view_allocation.height)
+       surface_h = MIN (surface_h + 64, child_allocation.height);
+
+      if (priv->backing_surface != NULL &&
+         (priv->backing_surface_w < view_allocation.width ||
+          priv->backing_surface_w > surface_w + 32 ||
+          priv->backing_surface_h < view_allocation.height ||
+          priv->backing_surface_h > surface_h + 32))
+       {
+         cairo_surface_destroy (priv->backing_surface);
+         priv->backing_surface = NULL;
+         if (priv->backing_surface_dirty)
+           cairo_region_destroy (priv->backing_surface_dirty);
+         priv->backing_surface_dirty = NULL;
+       }
+
+      if (priv->backing_surface == NULL &&
+         (view_allocation.width < child_allocation.width ||
+          view_allocation.height < child_allocation.height))
+       {
+         priv->backing_surface_x = gtk_adjustment_get_value (hadjustment);
+         priv->backing_surface_y = gtk_adjustment_get_value (vadjustment);
+         priv->backing_surface_w = surface_w;
+         priv->backing_surface_h = surface_h;
+         priv->backing_surface_dirty = cairo_region_create ();
+         priv->backing_surface =
+           gdk_window_create_similar_surface (priv->bin_window,
+                                              CAIRO_CONTENT_COLOR_ALPHA,
+                                              surface_w, surface_h);
+         rect.x = 0;
+         rect.y = 0;
+         rect.width = surface_w;
+         rect.height = surface_h;
+         cairo_region_union_rectangle (priv->backing_surface_dirty,
+                                       &rect);
+       }
     }
 
   child = gtk_bin_get_child (bin);


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