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);