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.
This commit is contained in:
Alexander Larsson
2013-04-21 01:54:05 +02:00
parent 1270bcd5c3
commit 9da2f293e9

View File

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