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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user