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__ */