diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index ad3f711020..87c920f981 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -515,6 +515,7 @@ gtk_private_h_sources = \
gtkprintoperation-private.h \
gtkprintutils.h \
gtkprivate.h \
+ gtkpixelcacheprivate.h \
gtkquery.h \
gtkrbtree.h \
gtkrecentchooserdefault.h \
@@ -796,6 +797,7 @@ gtk_base_c_sources = \
gtkprivate.c \
gtkprivatetypebuiltins.c \
gtkprogressbar.c \
+ gtkpixelcache.c \
gtkradioaction.c \
gtkradiobutton.c \
gtkradiomenuitem.c \
diff --git a/gtk/gtkpixelcache.c b/gtk/gtkpixelcache.c
new file mode 100644
index 0000000000..1a8630819c
--- /dev/null
+++ b/gtk/gtkpixelcache.c
@@ -0,0 +1,341 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ */
+
+#include "config.h"
+
+#include "gtkpixelcacheprivate.h"
+
+/* The extra size of the offscreen surface we allocate
+ to make scrolling more efficient */
+#define EXTRA_SIZE 64
+
+/* When resizing viewport to smaller we allow this extra
+ size to avoid constantly reallocating when resizing */
+#define ALLOW_LARGER_SIZE 32
+
+struct _GtkPixelCache {
+ cairo_surface_t *surface;
+
+ /* Valid if surface != NULL */
+ int surface_x;
+ int surface_y;
+ int surface_w;
+ int surface_h;
+
+ /* may be null if not dirty */
+ cairo_region_t *surface_dirty;
+};
+
+GtkPixelCache *
+_gtk_pixel_cache_new ()
+{
+ GtkPixelCache *cache;
+
+ cache = g_new0 (GtkPixelCache, 1);
+
+ return cache;
+}
+
+void
+_gtk_pixel_cache_free (GtkPixelCache *cache)
+{
+ if (cache == NULL)
+ return;
+
+ if (cache->surface != NULL)
+ cairo_surface_destroy (cache->surface);
+
+ if (cache->surface_dirty != NULL)
+ cairo_region_destroy (cache->surface_dirty);
+
+ g_free (cache);
+}
+
+/* Region is in canvas coordinates */
+void
+_gtk_pixel_cache_invalidate (GtkPixelCache *cache,
+ cairo_region_t *region)
+{
+ cairo_rectangle_int_t r;
+ cairo_region_t *free_region = NULL;
+
+ if (cache->surface == NULL ||
+ (region != NULL && cairo_region_is_empty (region)))
+ return;
+
+ if (region == NULL)
+ {
+ r.x = cache->surface_x;
+ r.y = cache->surface_y;
+ r.width = cache->surface_w;
+ r.height = cache->surface_h;
+
+ free_region = region =
+ cairo_region_create_rectangle (&r);
+ }
+
+ if (cache->surface_dirty == NULL)
+ {
+ cache->surface_dirty = cairo_region_copy (region);
+ cairo_region_translate (cache->surface_dirty,
+ -cache->surface_x,
+ -cache->surface_y);
+ }
+ else
+ {
+ cairo_region_translate (region,
+ -cache->surface_x,
+ -cache->surface_y);
+ cairo_region_union (cache->surface_dirty, region);
+ cairo_region_translate (region,
+ cache->surface_x,
+ cache->surface_y);
+ }
+
+ if (free_region)
+ cairo_region_destroy (free_region);
+
+ r.x = 0;
+ r.y = 0;
+ r.width = cache->surface_w;
+ r.height = cache->surface_h;
+
+ cairo_region_intersect_rectangle (cache->surface_dirty, &r);
+}
+
+static void
+_gtk_pixel_cache_create_surface_if_needed (GtkPixelCache *cache,
+ GdkWindow *window,
+ cairo_rectangle_int_t *view_rect,
+ cairo_rectangle_int_t *canvas_rect)
+{
+ cairo_rectangle_int_t rect;
+ int surface_w, surface_h;
+ cairo_content_t content;
+ cairo_pattern_t *bg;
+ double red, green, blue, alpha;
+
+ content = CAIRO_CONTENT_COLOR_ALPHA;
+ bg = gdk_window_get_background_pattern (window);
+ if (bg != NULL &&
+ cairo_pattern_get_type (bg) == CAIRO_PATTERN_TYPE_SOLID &&
+ cairo_pattern_get_rgba (bg, &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS &&
+ alpha == 1.0)
+ content = CAIRO_CONTENT_COLOR;
+
+ surface_w = view_rect->width;
+ if (canvas_rect->width > surface_w)
+ surface_w = MIN (surface_w + EXTRA_SIZE, canvas_rect->width);
+
+ surface_h = view_rect->height;
+ if (canvas_rect->height > surface_h)
+ surface_h = MIN (surface_h + EXTRA_SIZE, canvas_rect->height);
+
+ /* If current surface can't fit view_rect or is too large, kill it */
+ if (cache->surface != NULL &&
+ (cairo_surface_get_content (cache->surface) != content ||
+ cache->surface_w < view_rect->width ||
+ cache->surface_w > surface_w + ALLOW_LARGER_SIZE ||
+ cache->surface_h < view_rect->height ||
+ cache->surface_h > surface_h + ALLOW_LARGER_SIZE))
+ {
+ cairo_surface_destroy (cache->surface);
+ cache->surface = NULL;
+ if (cache->surface_dirty)
+ cairo_region_destroy (cache->surface_dirty);
+ cache->surface_dirty = NULL;
+ }
+
+ /* Don't allocate a surface if view >= canvas, as we won't
+ be scrolling then anyway */
+ if (cache->surface == NULL &&
+ (view_rect->width < canvas_rect->width ||
+ view_rect->height < canvas_rect->height))
+ {
+ cache->surface_x = -canvas_rect->x;
+ cache->surface_y = -canvas_rect->y;
+ cache->surface_w = surface_w;
+ cache->surface_h = surface_h;
+
+ cache->surface =
+ gdk_window_create_similar_surface (window, content,
+ surface_w, surface_h);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = surface_w;
+ rect.height = surface_h;
+ cache->surface_dirty =
+ cairo_region_create_rectangle (&rect);
+ }
+}
+
+void
+_gtk_pixel_cache_set_position (GtkPixelCache *cache,
+ cairo_rectangle_int_t *view_rect,
+ cairo_rectangle_int_t *canvas_rect)
+{
+ cairo_rectangle_int_t r, view_pos;
+ cairo_region_t *copy_region;
+ int new_surf_x, new_surf_y;
+ cairo_t *backing_cr;
+
+ if (cache->surface == NULL)
+ return;
+
+ /* Position of view inside canvas */
+ view_pos.x = -canvas_rect->x;
+ view_pos.y = -canvas_rect->y;
+ view_pos.width = view_rect->width;
+ view_pos.height = view_rect->height;
+
+ /* Reposition so all is visible */
+ if (view_pos.x < cache->surface_x ||
+ view_pos.x + view_pos.width >
+ cache->surface_x + cache->surface_w ||
+ view_pos.y < cache->surface_y ||
+ view_pos.y + view_pos.height >
+ cache->surface_y + cache->surface_h)
+ {
+ new_surf_x = cache->surface_x;
+ if (view_pos.x < cache->surface_x)
+ new_surf_x = MAX (view_pos.x + view_pos.width - cache->surface_w, 0);
+ else if (view_pos.x + view_pos.width >
+ cache->surface_x + cache->surface_w)
+ new_surf_x = MIN (view_pos.x, canvas_rect->width - cache->surface_w);
+
+ new_surf_y = cache->surface_y;
+ if (view_pos.y < cache->surface_y)
+ new_surf_y = MAX (view_pos.y + view_pos.height - cache->surface_h, 0);
+ else if (view_pos.y + view_pos.height >
+ cache->surface_y + cache->surface_h)
+ new_surf_y = MIN (view_pos.y, canvas_rect->height - cache->surface_h);
+
+ r.x = 0;
+ r.y = 0;
+ r.width = cache->surface_w;
+ r.height = cache->surface_h;
+ copy_region = cairo_region_create_rectangle (&r);
+
+ if (cache->surface_dirty)
+ {
+ cairo_region_subtract (copy_region, cache->surface_dirty);
+ cairo_region_destroy (cache->surface_dirty);
+ cache->surface_dirty = NULL;
+ }
+
+ cairo_region_translate (copy_region,
+ cache->surface_x - new_surf_x,
+ cache->surface_y - new_surf_y);
+ cairo_region_intersect_rectangle (copy_region, &r);
+
+ backing_cr = cairo_create (cache->surface);
+ gdk_cairo_region (backing_cr, copy_region);
+ cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+ cairo_clip (backing_cr);
+ cairo_push_group (backing_cr);
+ cairo_set_source_surface (backing_cr, cache->surface,
+ cache->surface_x - new_surf_x,
+ cache->surface_y - new_surf_y);
+ cairo_paint (backing_cr);
+ cairo_pop_group_to_source (backing_cr);
+ cairo_paint (backing_cr);
+ cairo_destroy (backing_cr);
+
+ cache->surface_x = new_surf_x;
+ cache->surface_y = new_surf_y;
+
+ cairo_region_xor_rectangle (copy_region, &r);
+ cache->surface_dirty = copy_region;
+ }
+}
+
+void
+_gtk_pixel_cache_repaint (GtkPixelCache *cache,
+ GtkPixelCacheDrawFunc draw,
+ cairo_rectangle_int_t *view_rect,
+ cairo_rectangle_int_t *canvas_rect,
+ gpointer user_data)
+{
+ cairo_t *backing_cr;
+
+ if (cache->surface &&
+ cache->surface_dirty &&
+ !cairo_region_is_empty (cache->surface_dirty))
+ {
+ backing_cr = cairo_create (cache->surface);
+ gdk_cairo_region (backing_cr, cache->surface_dirty);
+ cairo_clip (backing_cr);
+ cairo_translate (backing_cr,
+ -cache->surface_x - canvas_rect->x - view_rect->x,
+ -cache->surface_y - canvas_rect->y - view_rect->y);
+ cairo_set_source_rgba (backing_cr,
+ 0.0, 0, 0, 0.0);
+ cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (backing_cr);
+
+ cairo_set_operator (backing_cr, CAIRO_OPERATOR_OVER);
+
+ draw (backing_cr, user_data);
+
+ cairo_destroy (backing_cr);
+ }
+
+ if (cache->surface_dirty)
+ {
+ cairo_region_destroy (cache->surface_dirty);
+ cache->surface_dirty = NULL;
+ }
+}
+
+void
+_gtk_pixel_cache_draw (GtkPixelCache *cache,
+ cairo_t *cr,
+ GdkWindow *window,
+ /* View position in widget coords */
+ cairo_rectangle_int_t *view_rect,
+ /* Size and position of canvas in view coords */
+ cairo_rectangle_int_t *canvas_rect,
+ GtkPixelCacheDrawFunc draw,
+ gpointer user_data)
+{
+ _gtk_pixel_cache_create_surface_if_needed (cache, window,
+ view_rect, canvas_rect);
+ _gtk_pixel_cache_set_position (cache, view_rect, canvas_rect);
+ _gtk_pixel_cache_repaint (cache, draw, view_rect, canvas_rect, user_data);
+
+ if (cache->surface &&
+ /* Don't use backing surface if rendering elsewhere */
+ cairo_surface_get_type (cache->surface) == cairo_surface_get_type (cairo_get_target (cr)))
+ {
+ cairo_save (cr);
+ cairo_set_source_surface (cr, cache->surface,
+ cache->surface_x + view_rect->x + canvas_rect->x,
+ cache->surface_y + view_rect->y + canvas_rect->y);
+ cairo_rectangle (cr, view_rect->x, view_rect->y,
+ view_rect->width, view_rect->height);
+ cairo_fill (cr);
+ cairo_restore (cr);
+ }
+ else
+ {
+ cairo_rectangle (cr,
+ view_rect->x, view_rect->y,
+ view_rect->width, view_rect->height);
+ cairo_clip (cr);
+ draw (cr, user_data);
+ }
+}
diff --git a/gtk/gtkpixelcacheprivate.h b/gtk/gtkpixelcacheprivate.h
new file mode 100644
index 0000000000..2b916d0b9c
--- /dev/null
+++ b/gtk/gtkpixelcacheprivate.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2013 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Alexander Larsson
+ */
+
+#ifndef __GTK_PIXEL_CACHE_PRIVATE_H__
+#define __GTK_PIXEL_CACHE_PRIVATE_H__
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+typedef struct _GtkPixelCache GtkPixelCache;
+
+typedef void (*GtkPixelCacheDrawFunc) (cairo_t *cr,
+ gpointer user_data);
+
+GtkPixelCache *_gtk_pixel_cache_new (void);
+void _gtk_pixel_cache_free (GtkPixelCache *cache);
+void _gtk_pixel_cache_invalidate (GtkPixelCache *cache,
+ cairo_region_t *region);
+void _gtk_pixel_cache_draw (GtkPixelCache *cache,
+ cairo_t *cr,
+ GdkWindow *window,
+ cairo_rectangle_int_t *view_rect,
+ cairo_rectangle_int_t *canvas_rect,
+ GtkPixelCacheDrawFunc draw,
+ gpointer user_data);
+
+
+G_END_DECLS
+
+#endif /* __GTK_PIXEL_CACHE_PRIVATE_H__ */