From 9da2f293e9eb948de0e70ae48ade4500d9f4b5cd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sun, 21 Apr 2013 01:54:05 +0200 Subject: [PATCH] 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 0469c5bce4..47815898ea 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);