summaryrefslogtreecommitdiffstats
path: root/src/st/st-theme-node-drawing.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/st/st-theme-node-drawing.c2834
1 files changed, 2834 insertions, 0 deletions
diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c
new file mode 100644
index 0000000..db43171
--- /dev/null
+++ b/src/st/st-theme-node-drawing.c
@@ -0,0 +1,2834 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-theme-node-drawing.c: Code to draw themed elements
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2009, 2010 Florian Müllner
+ * Copyright 2010 Intel Corporation.
+ * Copyright 2011 Quentin "Sardem FF7" Glidic
+ *
+ * Contains code derived from:
+ * rectangle.c: Rounded rectangle.
+ * Copyright 2008 litl, LLC.
+ * st-texture-frame.h: Expandible texture actor
+ * Copyright 2007 OpenedHand
+ * Copyright 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "st-shadow.h"
+#include "st-private.h"
+#include "st-theme-private.h"
+#include "st-theme-context.h"
+#include "st-texture-cache.h"
+#include "st-theme-node-private.h"
+
+/****
+ * Rounded corners
+ ****/
+
+typedef struct {
+ ClutterColor color;
+ ClutterColor border_color_1;
+ ClutterColor border_color_2;
+ guint radius;
+ guint border_width_1;
+ guint border_width_2;
+ float resource_scale;
+} StCornerSpec;
+
+static void
+elliptical_arc (cairo_t *cr,
+ double x_center,
+ double y_center,
+ double x_radius,
+ double y_radius,
+ double angle1,
+ double angle2)
+{
+ cairo_save (cr);
+ cairo_translate (cr, x_center, y_center);
+ cairo_scale (cr, x_radius, y_radius);
+ cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
+ cairo_restore (cr);
+}
+
+static CoglTexture *
+create_corner_material (StCornerSpec *corner)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ CoglTexture *texture;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ guint rowstride;
+ guint8 *data;
+ guint size;
+ guint logical_size;
+ guint max_border_width;
+ double device_scaling;
+
+ max_border_width = MAX(corner->border_width_2, corner->border_width_1);
+ logical_size = 2 * MAX(max_border_width, corner->radius);
+ size = ceilf (logical_size * corner->resource_scale);
+ rowstride = size * 4;
+ data = g_new0 (guint8, size * rowstride);
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ size, size,
+ rowstride);
+ device_scaling = (double) size / logical_size;
+ cairo_surface_set_device_scale (surface, device_scaling, device_scaling);
+ cr = cairo_create (surface);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_scale (cr, logical_size, logical_size);
+
+ if (max_border_width <= corner->radius)
+ {
+ double x_radius, y_radius;
+
+ if (max_border_width != 0)
+ {
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, 0.5, 0.5, 0.5, 0, 2 * M_PI);
+ cairo_fill (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ corner->color.red / 255.,
+ corner->color.green / 255.,
+ corner->color.blue / 255.,
+ corner->color.alpha / 255.);
+
+ x_radius = 0.5 * (1.0 - (double) corner->border_width_2 / corner->radius);
+ y_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
+
+ /* TOPRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 3 * M_PI / 2, 2 * M_PI);
+
+ /* BOTTOMRIGHT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ 0, M_PI / 2);
+
+ /* TOPLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI, 3 * M_PI / 2);
+
+ /* BOTTOMLEFT */
+ elliptical_arc (cr,
+ 0.5, 0.5,
+ x_radius, y_radius,
+ M_PI / 2, M_PI);
+
+ cairo_fill (cr);
+ }
+ else
+ {
+ double radius;
+
+ radius = (gdouble)corner->radius / max_border_width;
+
+ cairo_set_source_rgba (cr,
+ corner->border_color_1.red / 255.,
+ corner->border_color_1.green / 255.,
+ corner->border_color_1.blue / 255.,
+ corner->border_color_1.alpha / 255.);
+
+ cairo_arc (cr, radius, radius, radius, M_PI, 3 * M_PI / 2);
+ cairo_line_to (cr, 1.0 - radius, 0.0);
+ cairo_arc (cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, 1.0, 1.0 - radius);
+ cairo_arc (cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2);
+ cairo_line_to (cr, radius, 1.0);
+ cairo_arc (cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI);
+ cairo_fill (cr);
+ }
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (surface);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, size, size,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (data);
+
+ return texture;
+}
+
+static char *
+corner_to_string (StCornerSpec *corner)
+{
+ return g_strdup_printf ("st-theme-node-corner:%02x%02x%02x%02x,%02x%02x%02x%02x,%02x%02x%02x%02x,%u,%u,%u,%.4f",
+ corner->color.red, corner->color.blue, corner->color.green, corner->color.alpha,
+ corner->border_color_1.red, corner->border_color_1.green, corner->border_color_1.blue, corner->border_color_1.alpha,
+ corner->border_color_2.red, corner->border_color_2.green, corner->border_color_2.blue, corner->border_color_2.alpha,
+ corner->radius,
+ corner->border_width_1,
+ corner->border_width_2,
+ corner->resource_scale);
+}
+
+static CoglTexture *
+load_corner (StTextureCache *cache,
+ const char *key,
+ void *datap,
+ GError **error)
+{
+ return create_corner_material ((StCornerSpec *) datap);
+}
+
+/* To match the CSS specification, we want the border to look like it was
+ * drawn over the background. But actually drawing the border over the
+ * background will produce slightly bad antialiasing at the edges, so
+ * compute the effective border color instead.
+ */
+#define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8)
+#define MULT(c,a) NORM(c*a)
+
+static void
+premultiply (ClutterColor *color)
+{
+ guint t;
+ color->red = MULT (color->red, color->alpha);
+ color->green = MULT (color->green, color->alpha);
+ color->blue = MULT (color->blue, color->alpha);
+}
+
+static void
+unpremultiply (ClutterColor *color)
+{
+ if (color->alpha != 0)
+ {
+ color->red = MIN((color->red * 255 + 127) / color->alpha, 255);
+ color->green = MIN((color->green * 255 + 127) / color->alpha, 255);
+ color->blue = MIN((color->blue * 255 + 127) / color->alpha, 255);
+ }
+}
+
+static void
+over (const ClutterColor *source,
+ const ClutterColor *destination,
+ ClutterColor *result)
+{
+ guint t;
+ ClutterColor src = *source;
+ ClutterColor dst = *destination;
+
+ premultiply (&src);
+ premultiply (&dst);
+
+ result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha);
+ result->red = src.red + NORM ((255 - src.alpha) * dst.red);
+ result->green = src.green + NORM ((255 - src.alpha) * dst.green);
+ result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue);
+
+ unpremultiply (result);
+}
+
+/*
+ * st_theme_node_reduce_border_radius:
+ * @node: a #StThemeNode
+ * @width: The width of the box
+ * @height: The height of the box
+ * @corners: (array length=4) (out): reduced corners
+ *
+ * Implements the corner overlap algorithm mentioned at
+ * http://www.w3.org/TR/css3-background/#corner-overlap
+ */
+static void
+st_theme_node_reduce_border_radius (StThemeNode *node,
+ float width,
+ float height,
+ guint *corners)
+{
+ gfloat scale;
+ guint sum;
+
+ scale = 1.0;
+
+ /* top */
+ sum = node->border_radius[ST_CORNER_TOPLEFT]
+ + node->border_radius[ST_CORNER_TOPRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* right */
+ sum = node->border_radius[ST_CORNER_TOPRIGHT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ /* bottom */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_BOTTOMRIGHT];
+
+ if (sum > 0)
+ scale = MIN (width / sum, scale);
+
+ /* left */
+ sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
+ + node->border_radius[ST_CORNER_TOPLEFT];
+
+ if (sum > 0)
+ scale = MIN (height / sum, scale);
+
+ corners[ST_CORNER_TOPLEFT] = node->border_radius[ST_CORNER_TOPLEFT] * scale;
+ corners[ST_CORNER_TOPRIGHT] = node->border_radius[ST_CORNER_TOPRIGHT] * scale;
+ corners[ST_CORNER_BOTTOMLEFT] = node->border_radius[ST_CORNER_BOTTOMLEFT] * scale;
+ corners[ST_CORNER_BOTTOMRIGHT] = node->border_radius[ST_CORNER_BOTTOMRIGHT] * scale;
+}
+
+static void
+st_theme_node_get_corner_border_widths (StThemeNode *node,
+ StCorner corner_id,
+ guint *border_width_1,
+ guint *border_width_2)
+{
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ case ST_CORNER_TOPRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_TOP];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_RIGHT];
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ if (border_width_1)
+ *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
+ if (border_width_2)
+ *border_width_2 = node->border_width[ST_SIDE_LEFT];
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+static CoglPipeline *
+st_theme_node_lookup_corner (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ StCorner corner_id)
+{
+ CoglTexture *texture = NULL;
+ CoglPipeline *material = NULL;
+ char *key;
+ StTextureCache *cache;
+ StCornerSpec corner;
+ guint radius[4];
+
+ cache = st_texture_cache_get_default ();
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ if (radius[corner_id] == 0)
+ return NULL;
+
+ corner.radius = radius[corner_id];
+ corner.color = node->background_color;
+ corner.resource_scale = resource_scale;
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &corner.border_width_1,
+ &corner.border_width_2);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
+ over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ if (corner.color.alpha == 0 &&
+ corner.border_color_1.alpha == 0 &&
+ corner.border_color_2.alpha == 0)
+ return NULL;
+
+ key = corner_to_string (&corner);
+ texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_FOREVER, load_corner, &corner, NULL);
+
+ if (texture)
+ {
+ material = _st_create_texture_pipeline (texture);
+ cogl_object_unref (texture);
+ }
+
+ g_free (key);
+
+ return material;
+}
+
+static void
+get_background_scale (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *scale_w,
+ gdouble *scale_h)
+{
+ *scale_w = -1.0;
+ *scale_h = -1.0;
+
+ switch (node->background_size)
+ {
+ case ST_BACKGROUND_SIZE_AUTO:
+ *scale_w = 1.0f;
+ break;
+ case ST_BACKGROUND_SIZE_CONTAIN:
+ *scale_w = MIN (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_COVER:
+ *scale_w = MAX (painting_area_width / background_image_width,
+ painting_area_height / background_image_height);
+ break;
+ case ST_BACKGROUND_SIZE_FIXED:
+ if (node->background_size_w > -1)
+ {
+ *scale_w = node->background_size_w / background_image_width;
+ if (node->background_size_h > -1)
+ *scale_h = node->background_size_h / background_image_height;
+ }
+ else if (node->background_size_h > -1)
+ *scale_w = node->background_size_h / background_image_height;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ if (*scale_h < 0.0)
+ *scale_h = *scale_w;
+}
+
+static void
+get_background_coordinates (StThemeNode *node,
+ gdouble painting_area_width,
+ gdouble painting_area_height,
+ gdouble background_image_width,
+ gdouble background_image_height,
+ gdouble *x,
+ gdouble *y)
+{
+ /* honor the specified position if any */
+ if (node->background_position_set)
+ {
+ *x = node->background_position_x;
+ *y = node->background_position_y;
+ }
+ else
+ {
+ /* center the background on the widget */
+ *x = (painting_area_width / 2.0) - (background_image_width / 2.0);
+ *y = (painting_area_height / 2.0) - (background_image_height / 2.0);
+ }
+}
+
+static void
+get_background_position (StThemeNode *self,
+ const ClutterActorBox *allocation,
+ float resource_scale,
+ ClutterActorBox *result,
+ ClutterActorBox *texture_coords)
+{
+ gdouble painting_area_width, painting_area_height;
+ gdouble background_image_width, background_image_height;
+ gdouble x1, y1;
+ gdouble scale_w, scale_h;
+
+ /* get the background image size */
+ background_image_width = cogl_texture_get_width (self->background_texture);
+ background_image_height = cogl_texture_get_height (self->background_texture);
+
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ /* get the painting area size */
+ painting_area_width = allocation->x2 - allocation->x1;
+ painting_area_height = allocation->y2 - allocation->y1;
+
+ /* scale if requested */
+ get_background_scale (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ /* get coordinates */
+ get_background_coordinates (self,
+ painting_area_width, painting_area_height,
+ background_image_width, background_image_height,
+ &x1, &y1);
+
+ if (self->background_repeat)
+ {
+ gdouble width = allocation->x2 - allocation->x1 + x1;
+ gdouble height = allocation->y2 - allocation->y1 + y1;
+
+ *result = *allocation;
+
+ /* reference image is at x1, y1 */
+ texture_coords->x1 = x1 / background_image_width;
+ texture_coords->y1 = y1 / background_image_height;
+ texture_coords->x2 = width / background_image_width;
+ texture_coords->y2 = height / background_image_height;
+ }
+ else
+ {
+ result->x1 = x1;
+ result->y1 = y1;
+ result->x2 = x1 + background_image_width;
+ result->y2 = y1 + background_image_height;
+
+ texture_coords->x1 = texture_coords->y1 = 0;
+ texture_coords->x2 = texture_coords->y2 = 1;
+ }
+}
+
+/* Use of this function marks code which doesn't support
+ * non-uniform colors.
+ */
+static void
+get_arbitrary_border_color (StThemeNode *node,
+ ClutterColor *color)
+{
+ if (color)
+ st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
+}
+
+static gboolean
+st_theme_node_has_visible_outline (StThemeNode *node)
+{
+ if (node->background_color.alpha > 0)
+ return TRUE;
+
+ if (node->background_gradient_end.alpha > 0)
+ return TRUE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ return TRUE;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_gradient (StThemeNode *node,
+ float width,
+ float height)
+{
+ cairo_pattern_t *pattern;
+
+ g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE,
+ NULL);
+
+ if (node->background_gradient_type == ST_GRADIENT_VERTICAL)
+ pattern = cairo_pattern_create_linear (0, 0, 0, height);
+ else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL)
+ pattern = cairo_pattern_create_linear (0, 0, width, 0);
+ else
+ {
+ gdouble cx, cy;
+
+ cx = width / 2.;
+ cy = height / 2.;
+ pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy));
+ }
+
+ cairo_pattern_add_color_stop_rgba (pattern, 0,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_pattern_add_color_stop_rgba (pattern, 1,
+ node->background_gradient_end.red / 255.,
+ node->background_gradient_end.green / 255.,
+ node->background_gradient_end.blue / 255.,
+ node->background_gradient_end.alpha / 255.);
+ return pattern;
+}
+
+static cairo_pattern_t *
+create_cairo_pattern_of_background_image (StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale,
+ gboolean *needs_background_fill)
+{
+ cairo_surface_t *surface;
+ cairo_pattern_t *pattern;
+ cairo_content_t content;
+ cairo_matrix_t matrix;
+ GFile *file;
+
+ StTextureCache *texture_cache;
+
+ gdouble background_image_width, background_image_height;
+ gdouble x, y;
+ gdouble scale_w, scale_h;
+
+ file = st_theme_node_get_background_image (node);
+
+ texture_cache = st_texture_cache_get_default ();
+
+ surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file,
+ node->cached_scale_factor,
+ resource_scale);
+
+ if (surface == NULL)
+ return NULL;
+
+ g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+ content = cairo_surface_get_content (surface);
+ pattern = cairo_pattern_create_for_surface (surface);
+
+ background_image_width = cairo_image_surface_get_width (surface);
+ background_image_height = cairo_image_surface_get_height (surface);
+
+ *needs_background_fill = TRUE;
+
+ cairo_matrix_init_identity (&matrix);
+
+ if (resource_scale != 1.0)
+ {
+ background_image_width /= resource_scale;
+ background_image_height /= resource_scale;
+
+ cairo_matrix_scale (&matrix, resource_scale, resource_scale);
+ }
+
+ get_background_scale (node,
+ width, height,
+ background_image_width, background_image_height,
+ &scale_w, &scale_h);
+
+ if ((scale_w != 1) || (scale_h != 1))
+ cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h);
+
+ background_image_width *= scale_w;
+ background_image_height *= scale_h;
+
+ get_background_coordinates (node,
+ width, height,
+ background_image_width, background_image_height,
+ &x, &y);
+ cairo_matrix_translate (&matrix, -x, -y);
+
+ if (node->background_repeat)
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+
+ /* If it's opaque, fills up the entire allocated
+ * area, then don't bother doing a background fill first
+ */
+ if (content != CAIRO_CONTENT_COLOR_ALPHA)
+ {
+ if (node->background_repeat ||
+ (x >= 0 &&
+ y >= 0 &&
+ background_image_width - x >= width &&
+ background_image_height -y >= height))
+ *needs_background_fill = FALSE;
+ }
+
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ return pattern;
+}
+
+/* fill_exterior = TRUE means that pattern is a surface pattern and
+ * we should extend the pattern with a solid fill from its edges.
+ * This is a bit of a hack; the alternative would be to make the
+ * surface of the surface pattern 1 pixel bigger and use CAIRO_EXTEND_PAD.
+ */
+static void
+paint_shadow_pattern_to_cairo_context (StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ gboolean fill_exterior,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path)
+{
+ /* If there are borders, clip the shadow to the interior
+ * of the borders; if there is a visible outline, clip the shadow to
+ * that outline
+ */
+ cairo_path_t *path = (interior_path != NULL) ? interior_path : outline_path;
+ double x1, x2, y1, y2;
+
+ /* fill_exterior only makes sense if we're clipping the shadow - filling
+ * to the edges of the surface would be silly */
+ g_assert (!(fill_exterior && path == NULL));
+
+ cairo_save (cr);
+ if (path != NULL)
+ {
+ cairo_append_path (cr, path);
+
+ /* There's no way to invert a path in cairo, so we need bounds for
+ * the area we are drawing in order to create the "exterior" region.
+ * Pixel align to hit fast paths.
+ */
+ if (fill_exterior)
+ {
+ cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+ x1 = floor (x1);
+ y1 = floor (y1);
+ x2 = ceil (x2);
+ y2 = ceil (y2);
+ }
+
+ cairo_clip (cr);
+ }
+
+ cairo_set_source_rgba (cr,
+ shadow_spec->color.red / 255.0,
+ shadow_spec->color.green / 255.0,
+ shadow_spec->color.blue / 255.0,
+ shadow_spec->color.alpha / 255.0);
+ if (fill_exterior)
+ {
+ cairo_surface_t *surface;
+ int width, height;
+ double xscale, yscale;
+ cairo_matrix_t matrix;
+
+ cairo_save (cr);
+
+ /* Start with a rectangle enclosing the bounds of the clipped
+ * region */
+ cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+
+ /* Then subtract out the bounds of the surface in the surface
+ * pattern; we transform the context by the inverse of the
+ * pattern matrix to get to surface coordinates */
+
+ if (cairo_pattern_get_surface (pattern, &surface) != CAIRO_STATUS_SUCCESS)
+ /* Something went wrong previously */
+ goto no_surface;
+
+ cairo_surface_get_device_scale (surface, &xscale, &yscale);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+
+ cairo_pattern_get_matrix (pattern, &matrix);
+ cairo_matrix_invert (&matrix);
+ cairo_matrix_scale (&matrix, 1.0 / xscale, 1.0 / yscale);
+ cairo_transform (cr, &matrix);
+
+ cairo_rectangle (cr, 0, height, width, - height);
+ cairo_fill (cr);
+
+ no_surface:
+ cairo_restore (cr);
+ }
+
+ cairo_mask (cr, pattern);
+ cairo_restore (cr);
+}
+
+static void
+paint_background_image_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ cairo_pattern_t *pattern,
+ cairo_t *cr,
+ cairo_path_t *interior_path,
+ cairo_path_t *outline_path,
+ int x,
+ int y,
+ int width,
+ int height,
+ float resource_scale)
+{
+ cairo_pattern_t *shadow_pattern;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (pattern != NULL);
+
+ if (outline_path != NULL)
+ {
+ cairo_surface_t *clipped_surface;
+ cairo_pattern_t *clipped_pattern;
+ cairo_t *temp_cr;
+
+ /* Prerender the pattern to a temporary surface,
+ * so it's properly clipped before we create a shadow from it
+ */
+ width = ceilf (width * resource_scale);
+ height = ceilf (height * resource_scale);
+ clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cairo_surface_set_device_scale (clipped_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (clipped_surface);
+
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (temp_cr);
+ cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE);
+
+ if (interior_path != NULL)
+ {
+ cairo_append_path (temp_cr, interior_path);
+ cairo_clip (temp_cr);
+ }
+
+ cairo_append_path (temp_cr, outline_path);
+ cairo_translate (temp_cr, x, y);
+ cairo_set_source (temp_cr, pattern);
+ cairo_clip (temp_cr);
+ cairo_paint (temp_cr);
+ cairo_destroy (temp_cr);
+
+ clipped_pattern = cairo_pattern_create_for_surface (clipped_surface);
+ cairo_surface_destroy (clipped_surface);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ clipped_pattern);
+ cairo_pattern_destroy (clipped_pattern);
+ }
+ else
+ {
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
+ pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, FALSE,
+ cr,
+ interior_path,
+ outline_path);
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* gets the extents of a cairo_path_t; slightly inefficient, but much simpler than
+ * computing from the raw path data */
+static void
+path_extents (cairo_path_t *path,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+
+{
+ cairo_surface_t *dummy = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
+ cairo_t *cr = cairo_create (dummy);
+
+ cairo_append_path (cr, path);
+ cairo_path_extents (cr, x1, y1, x2, y2);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy);
+}
+
+static void
+paint_inset_box_shadow_to_cairo_context (StThemeNode *node,
+ StShadow *shadow_spec,
+ float resource_scale,
+ cairo_t *cr,
+ cairo_path_t *shadow_outline)
+{
+ cairo_surface_t *shadow_surface;
+ cairo_pattern_t *shadow_pattern;
+ double extents_x1, extents_y1, extents_x2, extents_y2;
+ double shrunk_extents_x1, shrunk_extents_y1, shrunk_extents_x2, shrunk_extents_y2;
+ gboolean fill_exterior;
+
+ g_assert (shadow_spec != NULL);
+ g_assert (shadow_outline != NULL);
+
+ /* Create the pattern used to create the inset shadow; as the shadow
+ * should be drawn as if everything outside the outline was opaque,
+ * we use a temporary surface to draw the background as a solid shape,
+ * which is inverted when creating the shadow pattern.
+ */
+
+ /* First we need to find the size of the temporary surface
+ */
+ path_extents (shadow_outline,
+ &extents_x1, &extents_y1, &extents_x2, &extents_y2);
+
+ /* Shrink the extents by the spread, and offset */
+ shrunk_extents_x1 = extents_x1 + shadow_spec->xoffset + shadow_spec->spread;
+ shrunk_extents_y1 = extents_y1 + shadow_spec->yoffset + shadow_spec->spread;
+ shrunk_extents_x2 = extents_x2 + shadow_spec->xoffset - shadow_spec->spread;
+ shrunk_extents_y2 = extents_y2 + shadow_spec->yoffset - shadow_spec->spread;
+
+ if (shrunk_extents_x1 >= shrunk_extents_x2 || shrunk_extents_y1 >= shrunk_extents_x2)
+ {
+ /* Shadow occupies entire area within border */
+ shadow_pattern = cairo_pattern_create_rgb (0., 0., 0.);
+ fill_exterior = FALSE;
+ }
+ else
+ {
+ /* Bounds of temporary surface */
+ int surface_x = floor (shrunk_extents_x1);
+ int surface_y = floor (shrunk_extents_y1);
+ int surface_width = ceil ((shrunk_extents_x2 - surface_x) * resource_scale);
+ int surface_height = ceil ((shrunk_extents_y2 - surface_y) * resource_scale);
+
+ /* Center of the original path */
+ double x_center = (extents_x1 + extents_x2) / 2;
+ double y_center = (extents_y1 + extents_y2) / 2;
+
+ cairo_pattern_t *pattern;
+ cairo_t *temp_cr;
+ cairo_matrix_t matrix;
+
+ shadow_surface = cairo_image_surface_create (CAIRO_FORMAT_A8, surface_width, surface_height);
+ cairo_surface_set_device_scale (shadow_surface, resource_scale, resource_scale);
+ temp_cr = cairo_create (shadow_surface);
+
+ /* Match the coordinates in the temporary context to the parent context */
+ cairo_translate (temp_cr, - surface_x, - surface_y);
+
+ /* Shadow offset */
+ cairo_translate (temp_cr, shadow_spec->xoffset, shadow_spec->yoffset);
+
+ /* Scale the path around the center to match the shrunk bounds */
+ cairo_translate (temp_cr, x_center, y_center);
+ cairo_scale (temp_cr,
+ (shrunk_extents_x2 - shrunk_extents_x1) / (extents_x2 - extents_x1),
+ (shrunk_extents_y2 - shrunk_extents_y1) / (extents_y2 - extents_y1));
+ cairo_translate (temp_cr, - x_center, - y_center);
+
+ cairo_append_path (temp_cr, shadow_outline);
+ cairo_fill (temp_cr);
+ cairo_destroy (temp_cr);
+
+ pattern = cairo_pattern_create_for_surface (shadow_surface);
+ cairo_surface_destroy (shadow_surface);
+
+ /* The pattern needs to be offset back to coordinates in the parent context */
+ cairo_matrix_init_translate (&matrix, - surface_x, - surface_y);
+ cairo_pattern_set_matrix (pattern, &matrix);
+
+ shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, pattern);
+ fill_exterior = TRUE;
+
+ cairo_pattern_destroy (pattern);
+ }
+
+ paint_shadow_pattern_to_cairo_context (shadow_spec,
+ shadow_pattern, fill_exterior,
+ cr,
+ shadow_outline,
+ NULL);
+
+ cairo_pattern_destroy (shadow_pattern);
+}
+
+/* In order for borders to be smoothly blended with non-solid backgrounds,
+ * we need to use cairo. This function is a slow fallback path for those
+ * cases (gradients, background images, etc).
+ */
+static CoglTexture *
+st_theme_node_prerender_background (StThemeNode *node,
+ float actor_width,
+ float actor_height,
+ float resource_scale)
+{
+ ClutterBackend *backend = clutter_get_default_backend ();
+ CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+ GError *error = NULL;
+ StBorderImage *border_image;
+ CoglTexture *texture;
+ guint radius[4];
+ int i;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ StShadow *shadow_spec;
+ StShadow *box_shadow_spec;
+ cairo_pattern_t *pattern = NULL;
+ cairo_path_t *outline_path = NULL;
+ gboolean draw_solid_background = TRUE;
+ gboolean background_is_translucent;
+ gboolean interior_dirty;
+ gboolean draw_background_image_shadow = FALSE;
+ gboolean has_visible_outline;
+ ClutterColor border_color;
+ guint border_width[4];
+ guint rowstride;
+ guchar *data;
+ ClutterActorBox actor_box;
+ ClutterActorBox paint_box;
+ cairo_path_t *interior_path = NULL;
+ float width, height;
+ int texture_width;
+ int texture_height;
+
+ border_image = st_theme_node_get_border_image (node);
+
+ shadow_spec = st_theme_node_get_background_image_shadow (node);
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ actor_box.x1 = 0;
+ actor_box.x2 = actor_width;
+ actor_box.y1 = 0;
+ actor_box.y2 = actor_height;
+
+ /* If there's a background image shadow, we
+ * may need to create an image bigger than the nodes
+ * allocation
+ */
+ st_theme_node_get_background_paint_box (node, &actor_box, &paint_box);
+
+ /* translate the boxes so the paint box is at 0,0
+ */
+ actor_box.x1 += - paint_box.x1;
+ actor_box.x2 += - paint_box.x1;
+ actor_box.y1 += - paint_box.y1;
+ actor_box.y2 += - paint_box.y1;
+
+ width = paint_box.x2 - paint_box.x1;
+ height = paint_box.y2 - paint_box.y1;
+
+ texture_width = ceilf (width * resource_scale);
+ texture_height = ceilf (height * resource_scale);
+
+ rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, texture_width);
+ data = g_new0 (guchar, texture_height * rowstride);
+
+ /* We zero initialize the destination memory, so it's fully transparent
+ * by default.
+ */
+ interior_dirty = FALSE;
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ texture_width, texture_height,
+ rowstride);
+ cairo_surface_set_device_scale (surface, resource_scale, resource_scale);
+ cr = cairo_create (surface);
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ st_theme_node_reduce_border_radius (node, width, height, radius);
+
+ for (i = 0; i < 4; i++)
+ border_width[i] = st_theme_node_get_border_width (node, i);
+
+ /* Note we don't support translucent background images on top
+ * of gradients. It's strictly either/or.
+ */
+ if (node->background_gradient_type != ST_GRADIENT_NONE)
+ {
+ pattern = create_cairo_pattern_of_background_gradient (node, width, height);
+ draw_solid_background = FALSE;
+
+ /* If the gradient has any translucent areas, we need to
+ * erase the interior region before drawing, so that we show
+ * what's actually under the gradient and not whatever is
+ * left over from filling the border, etc.
+ */
+ if (node->background_color.alpha < 255 ||
+ node->background_gradient_end.alpha < 255)
+ background_is_translucent = TRUE;
+ else
+ background_is_translucent = FALSE;
+ }
+ else
+ {
+ GFile *background_image;
+
+ background_image = st_theme_node_get_background_image (node);
+
+ if (background_image != NULL)
+ {
+ pattern = create_cairo_pattern_of_background_image (node,
+ width, height,
+ resource_scale,
+ &draw_solid_background);
+ if (shadow_spec && pattern != NULL)
+ draw_background_image_shadow = TRUE;
+ }
+
+ /* We never need to clear the interior region before drawing the
+ * background image, because it either always fills the entire area
+ * opaquely, or we draw the solid background behind it.
+ */
+ background_is_translucent = FALSE;
+ }
+
+ if (pattern == NULL)
+ draw_solid_background = TRUE;
+
+ /* drawing the solid background implicitly clears the interior
+ * region, so if we're going to draw a solid background before drawing
+ * the background pattern, then we don't need to bother also clearing the
+ * background region.
+ */
+ if (draw_solid_background)
+ background_is_translucent = FALSE;
+
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ /* Create a path for the background's outline first */
+ if (radius[ST_CORNER_TOPLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr, actor_box.x1, actor_box.y1);
+ cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1);
+ if (radius[ST_CORNER_TOPRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.x1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI);
+ cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]);
+ if (radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ cairo_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2);
+ cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2);
+ if (radius[ST_CORNER_BOTTOMLEFT] > 0)
+ cairo_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI);
+ cairo_close_path (cr);
+
+ outline_path = cairo_copy_path (cr);
+
+ /* If we have a solid border, we fill the outline shape with the border
+ * color and create the inline shape for the background;
+ * otherwise the outline shape is filled with the background
+ * directly
+ */
+ if (border_image == NULL &&
+ (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0))
+ {
+ cairo_set_source_rgba (cr,
+ border_color.red / 255.,
+ border_color.green / 255.,
+ border_color.blue / 255.,
+ border_color.alpha / 255.);
+ cairo_fill (cr);
+
+ /* We were sloppy when filling in the border, and now the interior
+ * is filled with the border color, too.
+ */
+ interior_dirty = TRUE;
+
+ if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_TOPLEFT],
+ actor_box.y1 + radius[ST_CORNER_TOPLEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP],
+ M_PI, 3 * M_PI / 2);
+ else
+ cairo_move_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]),
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
+ actor_box.y1 + radius[ST_CORNER_TOPRIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP],
+ 3 * M_PI / 2, 2 * M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y1 + border_width[ST_SIDE_TOP]);
+
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM]));
+
+ if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_RIGHT]))
+ elliptical_arc (cr,
+ actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT],
+ radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM],
+ 0, M_PI / 2);
+ else
+ cairo_line_to (cr,
+ actor_box.x2 - border_width[ST_SIDE_RIGHT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_line_to (cr,
+ MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]),
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM],
+ border_width[ST_SIDE_LEFT]))
+ elliptical_arc (cr,
+ actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
+ actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT],
+ radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM],
+ M_PI / 2, M_PI);
+ else
+ cairo_line_to (cr,
+ actor_box.x1 + border_width[ST_SIDE_LEFT],
+ actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
+
+ cairo_close_path (cr);
+
+ interior_path = cairo_copy_path (cr);
+
+ /* clip drawing to the region inside of the borders
+ */
+ cairo_clip (cr);
+
+ /* But fill the pattern as if it started at the edge of outline,
+ * behind the borders. This is similar to
+ * background-clip: border-box; semantics.
+ */
+ cairo_append_path (cr, outline_path);
+ }
+
+ if (interior_dirty && background_is_translucent)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_solid_background)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ cairo_set_source_rgba (cr,
+ node->background_color.red / 255.,
+ node->background_color.green / 255.,
+ node->background_color.blue / 255.,
+ node->background_color.alpha / 255.);
+ cairo_fill_preserve (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ }
+
+ if (draw_background_image_shadow)
+ {
+ paint_background_image_shadow_to_cairo_context (node,
+ shadow_spec,
+ pattern,
+ cr,
+ interior_path,
+ has_visible_outline? outline_path : NULL,
+ actor_box.x1,
+ actor_box.y1,
+ width, height,
+ resource_scale);
+ cairo_append_path (cr, outline_path);
+ }
+
+ cairo_translate (cr, actor_box.x1, actor_box.y1);
+
+ if (pattern != NULL)
+ {
+ cairo_set_source (cr, pattern);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pattern);
+ }
+
+ if (box_shadow_spec && box_shadow_spec->inset)
+ {
+ paint_inset_box_shadow_to_cairo_context (node,
+ box_shadow_spec,
+ resource_scale,
+ cr,
+ interior_path ? interior_path
+ : outline_path);
+ }
+
+ if (outline_path != NULL)
+ cairo_path_destroy (outline_path);
+
+ if (interior_path != NULL)
+ cairo_path_destroy (interior_path);
+
+ texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx,
+ texture_width,
+ texture_height,
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ rowstride,
+ data,
+ &error));
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_error_free (error);
+ }
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ g_free (data);
+
+ return texture;
+}
+
+static void st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity);
+
+void
+st_theme_node_invalidate_border_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->border_slices_texture);
+ cogl_clear_object (&node->border_slices_pipeline);
+}
+
+static gboolean
+st_theme_node_load_border_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->border_slices_texture == NULL)
+ {
+ StBorderImage *border_image;
+ GFile *file;
+
+ border_image = st_theme_node_get_border_image (node);
+ if (border_image == NULL)
+ goto out;
+
+ file = st_border_image_get_file (border_image);
+
+ node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ file,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->border_slices_texture == NULL)
+ goto out;
+
+ node->border_slices_pipeline = _st_create_texture_pipeline (node->border_slices_texture);
+ }
+
+ out:
+ return node->border_slices_texture != NULL;
+}
+
+void
+st_theme_node_invalidate_background_image (StThemeNode *node)
+{
+ cogl_clear_object (&node->background_texture);
+ cogl_clear_object (&node->background_pipeline);
+ cogl_clear_object (&node->background_shadow_pipeline);
+}
+
+static gboolean
+st_theme_node_load_background_image (StThemeNode *node,
+ gfloat resource_scale)
+{
+ if (node->background_texture == NULL)
+ {
+ GFile *background_image;
+ StShadow *background_image_shadow_spec;
+
+ background_image = st_theme_node_get_background_image (node);
+ if (background_image == NULL)
+ goto out;
+
+ background_image_shadow_spec = st_theme_node_get_background_image_shadow (node);
+ node->background_texture = st_texture_cache_load_file_to_cogl_texture (st_texture_cache_get_default (),
+ background_image,
+ node->cached_scale_factor,
+ resource_scale);
+ if (node->background_texture == NULL)
+ goto out;
+
+ node->background_pipeline = _st_create_texture_pipeline (node->background_texture);
+
+ if (node->background_repeat)
+ cogl_pipeline_set_layer_wrap_mode (node->background_pipeline, 0,
+ COGL_PIPELINE_WRAP_MODE_REPEAT);
+
+ if (background_image_shadow_spec)
+ {
+ node->background_shadow_pipeline = _st_create_shadow_pipeline (background_image_shadow_spec,
+ node->background_texture,
+ resource_scale);
+ }
+ }
+
+ out:
+ return node->background_texture != NULL;
+}
+
+static gboolean
+st_theme_node_invalidate_resources_for_file (StThemeNode *node,
+ GFile *file)
+{
+ StBorderImage *border_image;
+ gboolean changed = FALSE;
+ GFile *theme_file;
+
+ theme_file = st_theme_node_get_background_image (node);
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_background_image (node);
+ changed = TRUE;
+ }
+
+ border_image = st_theme_node_get_border_image (node);
+ theme_file = border_image ? st_border_image_get_file (border_image) : NULL;
+ if ((theme_file != NULL) && g_file_equal (theme_file, file))
+ {
+ st_theme_node_invalidate_border_image (node);
+ changed = TRUE;
+ }
+
+ return changed;
+}
+
+static void st_theme_node_compute_maximum_borders (StThemeNodePaintState *state);
+static void st_theme_node_prerender_shadow (StThemeNodePaintState *state);
+
+static void
+st_theme_node_render_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean has_border;
+ gboolean has_border_radius;
+ gboolean has_inset_box_shadow;
+ gboolean has_large_corners;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* FIXME - need to separate this into things that need to be recomputed on
+ * geometry change versus things that can be cached regardless, such as
+ * a background image.
+ */
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ _st_theme_node_ensure_background (node);
+ _st_theme_node_ensure_geometry (node);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+ has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
+
+ if (node->border_width[ST_SIDE_TOP] > 0 ||
+ node->border_width[ST_SIDE_LEFT] > 0 ||
+ node->border_width[ST_SIDE_RIGHT] > 0 ||
+ node->border_width[ST_SIDE_BOTTOM] > 0)
+ has_border = TRUE;
+ else
+ has_border = FALSE;
+
+ if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
+ node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
+ node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
+ has_border_radius = TRUE;
+ else
+ has_border_radius = FALSE;
+
+ /* The cogl code pads each corner to the maximum border radius,
+ * which results in overlapping corner areas if the radius
+ * exceeds the actor's halfsize, causing rendering errors.
+ * Fall back to cairo in these cases. */
+ has_large_corners = FALSE;
+
+ if (has_border_radius) {
+ guint border_radius[4];
+ int corner;
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner = 0; corner < 4; corner ++) {
+ if (border_radius[corner] * 2 > height ||
+ border_radius[corner] * 2 > width) {
+ has_large_corners = TRUE;
+ break;
+ }
+ }
+ }
+
+ state->corner_material[ST_CORNER_TOPLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPLEFT);
+ state->corner_material[ST_CORNER_TOPRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMRIGHT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMRIGHT);
+ state->corner_material[ST_CORNER_BOTTOMLEFT] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMLEFT);
+
+ /* Use cairo to prerender the node if there is a gradient, or
+ * background image with borders and/or rounded corners,
+ * or large corners, since we can't do those things
+ * easily with cogl.
+ *
+ * FIXME: if we could figure out ahead of time that a
+ * background image won't overlap with the node borders,
+ * then we could use cogl for that case.
+ */
+ if ((node->background_gradient_type != ST_GRADIENT_NONE)
+ || (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
+ || (st_theme_node_get_background_image (node) && (has_border || has_border_radius))
+ || has_large_corners)
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height,
+ resource_scale);
+
+ if (state->prerendered_texture)
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ else
+ state->prerendered_pipeline = NULL;
+
+ if (box_shadow_spec && !has_inset_box_shadow)
+ {
+ st_theme_node_compute_maximum_borders (state);
+
+ if (st_theme_node_load_border_image (node, resource_scale))
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ node->border_slices_texture,
+ state->resource_scale);
+ else if (state->prerendered_texture != NULL)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+ else if (node->background_color.alpha > 0 || has_border)
+ st_theme_node_prerender_shadow (state);
+ }
+
+ /* If we don't have cached textures yet, check whether we can cache
+ them. */
+ if (!node->cached_textures)
+ {
+ if (state->prerendered_pipeline == NULL &&
+ width >= node->box_shadow_min_width &&
+ height >= node->box_shadow_min_height)
+ {
+ st_theme_node_paint_state_copy (&node->cached_state, state);
+ node->cached_textures = TRUE;
+ }
+ }
+}
+
+static void
+st_theme_node_update_resources (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ gboolean had_prerendered_texture = FALSE;
+ gboolean had_box_shadow = FALSE;
+ StShadow *box_shadow_spec;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ /* Free handles we can't reuse */
+ had_prerendered_texture = (state->prerendered_texture != NULL);
+ cogl_clear_object (&state->prerendered_texture);
+
+ if (state->prerendered_pipeline != NULL)
+ {
+ cogl_clear_object (&state->prerendered_pipeline);
+
+ if (node->border_slices_texture == NULL &&
+ state->box_shadow_pipeline != NULL)
+ {
+ cogl_clear_object (&state->box_shadow_pipeline);
+ had_box_shadow = TRUE;
+ }
+ }
+
+ st_theme_node_paint_state_set_node (state, node);
+ state->alloc_width = width;
+ state->alloc_height = height;
+ state->resource_scale = resource_scale;
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ if (had_prerendered_texture)
+ {
+ state->prerendered_texture = st_theme_node_prerender_background (node, width, height, resource_scale);
+ state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
+ }
+ else
+ {
+ int corner_id;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (state->corner_material[corner_id] == NULL)
+ state->corner_material[corner_id] =
+ st_theme_node_lookup_corner (node, width, height, resource_scale, corner_id);
+ }
+
+ if (had_box_shadow)
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,
+ state->prerendered_texture,
+ state->resource_scale);
+}
+
+static void
+paint_material_with_opacity (CoglPipeline *material,
+ CoglFramebuffer *framebuffer,
+ ClutterActorBox *box,
+ ClutterActorBox *coords,
+ guint8 paint_opacity)
+{
+ cogl_pipeline_set_color4ub (material,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ if (coords)
+ cogl_framebuffer_draw_textured_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2,
+ coords->x1, coords->y1, coords->x2, coords->y2);
+ else
+ cogl_framebuffer_draw_rectangle (framebuffer, material,
+ box->x1, box->y1, box->x2, box->y2);
+}
+
+static void
+st_theme_node_ensure_color_pipeline (StThemeNode *node)
+{
+ static CoglPipeline *color_pipeline_template = NULL;
+
+ if (node->color_pipeline != NULL)
+ return;
+
+ if (G_UNLIKELY (color_pipeline_template == NULL))
+ {
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ color_pipeline_template = cogl_pipeline_new (ctx);
+ }
+
+ node->color_pipeline = cogl_pipeline_copy (color_pipeline_template);
+}
+
+static void
+st_theme_node_paint_borders (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ float width, height;
+ guint border_width[4];
+ guint border_radius[4];
+ guint max_border_radius = 0;
+ guint max_width_radius[4];
+ int corner_id, side_id;
+ ClutterColor border_color;
+ guint8 alpha;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ /* TODO - support non-uniform border colors */
+ get_arbitrary_border_color (node, &border_color);
+
+ for (side_id = 0; side_id < 4; side_id++)
+ border_width[side_id] = st_theme_node_get_border_width(node, side_id);
+
+ st_theme_node_reduce_border_radius (node, width, height, border_radius);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ guint border_width_1, border_width_2;
+
+ st_theme_node_get_corner_border_widths (node, corner_id,
+ &border_width_1, &border_width_2);
+
+ if (border_radius[corner_id] > max_border_radius)
+ max_border_radius = border_radius[corner_id];
+ max_width_radius[corner_id] = MAX(MAX(border_width_1, border_width_2),
+ border_radius[corner_id]);
+ }
+
+ /* borders */
+ if (border_width[ST_SIDE_TOP] > 0 ||
+ border_width[ST_SIDE_RIGHT] > 0 ||
+ border_width[ST_SIDE_BOTTOM] > 0 ||
+ border_width[ST_SIDE_LEFT] > 0)
+ {
+ ClutterColor effective_border;
+ gboolean skip_corner_1, skip_corner_2;
+ float rects[16];
+
+ over (&border_color, &node->background_color, &effective_border);
+ alpha = paint_opacity * effective_border.alpha / 255;
+
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_border.red * alpha / 255,
+ effective_border.green * alpha / 255,
+ effective_border.blue * alpha / 255,
+ alpha);
+
+ /* NORTH */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+
+ rects[0] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
+ rects[1] = 0;
+ rects[2] = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
+ rects[3] = border_width[ST_SIDE_TOP];
+
+ /* EAST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPRIGHT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[4] = width - border_width[ST_SIDE_RIGHT];
+ rects[5] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT]
+ : border_width[ST_SIDE_TOP];
+ rects[6] = width;
+ rects[7] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ /* SOUTH */
+ skip_corner_1 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
+
+ rects[8] = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
+ rects[9] = height - border_width[ST_SIDE_BOTTOM];
+ rects[10] = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
+ : width;
+ rects[11] = height;
+
+ /* WEST */
+ skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
+ skip_corner_2 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
+
+ rects[12] = 0;
+ rects[13] = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT]
+ : border_width[ST_SIDE_TOP];
+ rects[14] = border_width[ST_SIDE_LEFT];
+ rects[15] = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
+ : height - border_width[ST_SIDE_BOTTOM];
+
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ rects, 4);
+ }
+ }
+
+ /* corners */
+ if (max_border_radius > 0 && paint_opacity > 0)
+ {
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ if (state->corner_material[corner_id] == NULL)
+ continue;
+
+ cogl_pipeline_set_color4ub (state->corner_material[corner_id],
+ paint_opacity, paint_opacity,
+ paint_opacity, paint_opacity);
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id], 0, 0,
+ max_width_radius[ST_CORNER_TOPLEFT], max_width_radius[ST_CORNER_TOPLEFT],
+ 0, 0, 0.5, 0.5);
+ break;
+ case ST_CORNER_TOPRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_TOPRIGHT], 0,
+ width, max_width_radius[ST_CORNER_TOPRIGHT],
+ 0.5, 0, 1, 0.5);
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ width - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ height - max_width_radius[ST_CORNER_BOTTOMRIGHT],
+ width, height,
+ 0.5, 0.5, 1, 1);
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ cogl_framebuffer_draw_textured_rectangle (framebuffer,
+ state->corner_material[corner_id],
+ 0, height - max_width_radius[ST_CORNER_BOTTOMLEFT],
+ max_width_radius[ST_CORNER_BOTTOMLEFT], height,
+ 0, 0.5, 0.5, 1);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+ }
+
+ /* background color */
+ alpha = paint_opacity * node->background_color.alpha / 255;
+ if (alpha > 0)
+ {
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ node->background_color.red * alpha / 255,
+ node->background_color.green * alpha / 255,
+ node->background_color.blue * alpha / 255,
+ alpha);
+
+ /* We add padding to each corner, so that all corners end up as if they
+ * had a border-radius of max_border_radius, which allows us to treat
+ * corners as uniform further on.
+ */
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ {
+ float verts[8];
+ int n_rects;
+
+ /* corner texture does not need padding */
+ if (max_border_radius == border_radius[corner_id])
+ continue;
+
+ n_rects = border_radius[corner_id] == 0 ? 1 : 2;
+
+ switch (corner_id)
+ {
+ case ST_CORNER_TOPLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = max_border_radius;
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = max_border_radius;
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_TOPRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = max_border_radius;
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = border_width[ST_SIDE_TOP];
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_TOP]);
+ }
+ break;
+ case ST_CORNER_BOTTOMRIGHT:
+ verts[0] = width - max_border_radius;
+ verts[1] = height - max_border_radius;
+ verts[2] = width - border_width[ST_SIDE_RIGHT];
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = width - max_border_radius;
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = width - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_RIGHT]);
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ case ST_CORNER_BOTTOMLEFT:
+ verts[0] = border_width[ST_SIDE_LEFT];
+ verts[1] = height - max_border_radius;
+ verts[2] = max_border_radius;
+ verts[3] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ if (n_rects == 2)
+ {
+ verts[4] = MAX(border_radius[corner_id],
+ border_width[ST_SIDE_LEFT]);
+ verts[5] = height - MAX(border_radius[corner_id],
+ border_width[ST_SIDE_BOTTOM]);
+ verts[6] = max_border_radius;
+ verts[7] = height - border_width[ST_SIDE_BOTTOM];
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ cogl_framebuffer_draw_rectangles (framebuffer,
+ node->color_pipeline,
+ verts, n_rects);
+ }
+
+ /* Once we've drawn the borders and corners, if the corners are bigger
+ * then the border width, the remaining area is shaped like
+ *
+ * ########
+ * ##########
+ * ##########
+ * ########
+ *
+ * We draw it in at most 3 pieces - first the top and bottom if
+ * necessary, then the main rectangle
+ */
+ if (max_border_radius > border_width[ST_SIDE_TOP])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ border_width[ST_SIDE_TOP],
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ max_border_radius);
+ if (max_border_radius > border_width[ST_SIDE_BOTTOM])
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
+ height - max_border_radius,
+ width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
+ height - border_width[ST_SIDE_BOTTOM]);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ border_width[ST_SIDE_LEFT],
+ MAX(border_width[ST_SIDE_TOP], max_border_radius),
+ width - border_width[ST_SIDE_RIGHT],
+ height - MAX(border_width[ST_SIDE_BOTTOM], max_border_radius));
+ }
+}
+
+static void
+st_theme_node_paint_sliced_shadow (StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+{
+ StThemeNode *node = state->node;
+ guint border_radius[4];
+ CoglColor color;
+ StShadow *box_shadow_spec;
+ gfloat xoffset, yoffset;
+ gfloat width, height;
+ gfloat shadow_width, shadow_height;
+ gfloat xend, yend, top, bottom, left, right;
+ gfloat s_top, s_bottom, s_left, s_right;
+ gfloat shadow_blur_radius, x_spread_factor, y_spread_factor;
+ float rectangles[8 * 9];
+ gint idx;
+
+ if (paint_opacity == 0)
+ return;
+
+ st_theme_node_reduce_border_radius (node, box->x2 - box->x1, box->y2 - box->y1, border_radius);
+
+ box_shadow_spec = st_theme_node_get_box_shadow (node);
+
+ /* Compute input & output areas :
+ *
+ * yoffset ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * top ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * bottom ----------------------------
+ * | | | |
+ * | | | |
+ * | | | |
+ * yend ----------------------------
+ * xoffset left right xend
+ *
+ * s_top = top in offscreen's coordinates (0.0 - 1.0)
+ * s_bottom = bottom in offscreen's coordinates (0.0 - 1.0)
+ * s_left = left in offscreen's coordinates (0.0 - 1.0)
+ * s_right = right in offscreen's coordinates (0.0 - 1.0)
+ */
+ if (box_shadow_spec->blur == 0)
+ shadow_blur_radius = 0;
+ else
+ shadow_blur_radius = (5 * (box_shadow_spec->blur / 2.0)) / 2;
+
+ shadow_width = state->box_shadow_width + 2 * shadow_blur_radius;
+ shadow_height = state->box_shadow_height + 2 * shadow_blur_radius;
+
+ /* Compute input regions parameters */
+ s_top = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ s_bottom = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ s_left = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ s_right = shadow_blur_radius + box_shadow_spec->blur +
+ MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ /* Compute output regions parameters */
+ xoffset = box->x1 + box_shadow_spec->xoffset - shadow_blur_radius - box_shadow_spec->spread;
+ yoffset = box->y1 + box_shadow_spec->yoffset - shadow_blur_radius - box_shadow_spec->spread;
+ width = box->x2 - box->x1 + 2 * shadow_blur_radius;
+ height = box->y2 - box->y1 + 2 * shadow_blur_radius;
+
+ x_spread_factor = (width + 2 * box_shadow_spec->spread) / width;
+ y_spread_factor = (height + 2 * box_shadow_spec->spread) / height;
+
+ width += 2 * box_shadow_spec->spread;
+ height += 2 * box_shadow_spec->spread;
+
+ xend = xoffset + width;
+ yend = yoffset + height;
+
+ top = s_top * y_spread_factor;
+ bottom = s_bottom * y_spread_factor;
+ left = s_left * x_spread_factor;
+ right = s_right * x_spread_factor;
+
+ bottom = height - bottom;
+ right = width - right;
+
+ /* Final adjustments */
+ s_top /= shadow_height;
+ s_bottom /= shadow_height;
+ s_left /= shadow_width;
+ s_right /= shadow_width;
+
+ s_bottom = 1.0 - s_bottom;
+ s_right = 1.0 - s_right;
+
+ top += yoffset;
+ bottom += yoffset;
+ left += xoffset;
+ right += xoffset;
+
+ /* Setup pipeline */
+ cogl_color_init_from_4ub (&color,
+ box_shadow_spec->color.red * paint_opacity / 255,
+ box_shadow_spec->color.green * paint_opacity / 255,
+ box_shadow_spec->color.blue * paint_opacity / 255,
+ box_shadow_spec->color.alpha * paint_opacity / 255);
+ cogl_color_premultiply (&color);
+
+ cogl_pipeline_set_layer_combine_constant (state->box_shadow_pipeline, 0, &color);
+
+ idx = 0;
+
+ if (yoffset < top)
+ {
+ if (xoffset < left)
+ {
+ /* Top left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ }
+
+ /* Top middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+
+ if (xend > right)
+ {
+ /* Top right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = yoffset;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = top;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 0;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_top;
+ }
+ }
+
+ if (xoffset < left)
+ {
+ /* Left middle */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = top;
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ }
+
+ /* Center middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = top;
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+
+ if (xend > right)
+ {
+ /* Right middle */
+ rectangles[idx++] = right;
+ rectangles[idx++] = top;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = bottom;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_top;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = s_bottom;
+ }
+
+ if (yend > bottom)
+ {
+ if (xoffset < left)
+ {
+ /* Bottom left corner */
+ rectangles[idx++] = xoffset;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = left;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = 0;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = 1;
+ }
+
+ /* Bottom middle */
+ rectangles[idx++] = left;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = right;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_left;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = 1;
+
+ if (xend > right)
+ {
+ /* Bottom right corner */
+ rectangles[idx++] = right;
+ rectangles[idx++] = bottom;
+ rectangles[idx++] = xend;
+ rectangles[idx++] = yend;
+
+ rectangles[idx++] = s_right;
+ rectangles[idx++] = s_bottom;
+ rectangles[idx++] = 1;
+ rectangles[idx++] = 1;
+ }
+ }
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, state->box_shadow_pipeline,
+ rectangles, idx / 8);
+
+#if 0
+ /* Visual feedback on shadow's 9-slice and original offscreen buffer,
+ for debug purposes */
+ cogl_framebuffer_draw_rectangle (framebuffer, state->box_shadow_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + shadow_height);
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline, 0xff, 0x0, 0x0, 0xff);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, top, xend, top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xoffset, bottom, xend, bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ left, yoffset, left + 1, yend);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ right, yoffset, right + 1, yend);
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + shadow_width, yoffset + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + shadow_height, xend + shadow_width, yoffset + shadow_height + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset, xend + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + shadow_width, yoffset, xend + shadow_width + 1, yoffset + shadow_height);
+
+ s_top *= shadow_height;
+ s_bottom *= shadow_height;
+ s_left *= shadow_width;
+ s_right *= shadow_width;
+
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_top, xend + shadow_width, yoffset + s_top + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend, yoffset + s_bottom, xend + shadow_width, yoffset + s_bottom + 1);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_left, yoffset, xend + s_left + 1, yoffset + shadow_height);
+ cogl_framebuffer_draw_rectangle (framebuffer, node->color_pipeline,
+ xend + s_right, yoffset, xend + s_right + 1, yoffset + shadow_height);
+
+#endif
+}
+
+static void
+st_theme_node_prerender_shadow (StThemeNodePaintState *state)
+{
+ StThemeNode *node = state->node;
+ CoglContext *ctx;
+ int fb_width, fb_height;
+ CoglTexture *buffer;
+ CoglFramebuffer *offscreen = NULL;
+ GError *error = NULL;
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ /* Render offscreen */
+ fb_width = ceilf (state->box_shadow_width * state->resource_scale);
+ fb_height = ceilf (state->box_shadow_height * state->resource_scale);
+ buffer = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx, fb_width, fb_height));
+ if (buffer == NULL)
+ return;
+
+ offscreen = cogl_offscreen_new_with_texture (buffer);
+
+ if (cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error))
+ {
+ ClutterActorBox box = { 0, 0, state->box_shadow_width, state->box_shadow_height};
+
+ cogl_framebuffer_orthographic (offscreen, 0, 0,
+ fb_width, fb_height, 0, 1.0);
+ cogl_framebuffer_scale (offscreen,
+ state->resource_scale,
+ state->resource_scale, 1);
+ cogl_framebuffer_clear4f (offscreen, COGL_BUFFER_BIT_COLOR, 0, 0, 0, 0);
+
+ st_theme_node_paint_borders (state, offscreen, &box, 0xFF);
+
+ state->box_shadow_pipeline = _st_create_shadow_pipeline (st_theme_node_get_box_shadow (node),
+ buffer, state->resource_scale);
+ }
+
+ g_clear_error (&error);
+ cogl_clear_object (&offscreen);
+ cogl_clear_object (&buffer);
+}
+
+static void
+st_theme_node_compute_maximum_borders (StThemeNodePaintState *state)
+{
+ int max_borders[4], center_radius;
+ StThemeNode * node = state->node;
+
+ /* Compute maximum borders sizes */
+ max_borders[ST_SIDE_TOP] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_TOPRIGHT]);
+ max_borders[ST_SIDE_BOTTOM] = MAX (node->border_radius[ST_CORNER_BOTTOMLEFT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+ max_borders[ST_SIDE_LEFT] = MAX (node->border_radius[ST_CORNER_TOPLEFT],
+ node->border_radius[ST_CORNER_BOTTOMLEFT]);
+ max_borders[ST_SIDE_RIGHT] = MAX (node->border_radius[ST_CORNER_TOPRIGHT],
+ node->border_radius[ST_CORNER_BOTTOMRIGHT]);
+
+ center_radius = (node->box_shadow->blur > 0) ? (2 * node->box_shadow->blur + 1) : 1;
+
+ node->box_shadow_min_width = max_borders[ST_SIDE_LEFT] + max_borders[ST_SIDE_RIGHT] + center_radius;
+ node->box_shadow_min_height = max_borders[ST_SIDE_TOP] + max_borders[ST_SIDE_BOTTOM] + center_radius;
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ {
+ state->box_shadow_width = state->alloc_width;
+ state->box_shadow_height = state->alloc_height;
+ }
+ else
+ {
+ state->box_shadow_width = node->box_shadow_min_width;
+ state->box_shadow_height = node->box_shadow_min_height;
+ }
+}
+
+static void
+st_theme_node_paint_sliced_border_image (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ float width,
+ float height,
+ guint8 paint_opacity)
+{
+ gfloat ex, ey;
+ gfloat tx1, ty1, tx2, ty2;
+ gint border_left, border_right, border_top, border_bottom;
+ float img_width, img_height;
+ StBorderImage *border_image;
+ CoglPipeline *pipeline;
+
+ border_image = st_theme_node_get_border_image (node);
+ g_assert (border_image != NULL);
+
+ st_border_image_get_borders (border_image,
+ &border_left, &border_right, &border_top, &border_bottom);
+
+ img_width = cogl_texture_get_width (node->border_slices_texture);
+ img_height = cogl_texture_get_height (node->border_slices_texture);
+
+ tx1 = border_left / img_width;
+ tx2 = (img_width - border_right) / img_width;
+ ty1 = border_top / img_height;
+ ty2 = (img_height - border_bottom) / img_height;
+
+ ex = width - border_right;
+ if (ex < 0)
+ ex = border_right; /* FIXME ? */
+
+ ey = height - border_bottom;
+ if (ey < 0)
+ ey = border_bottom; /* FIXME ? */
+
+ pipeline = node->border_slices_pipeline;
+ cogl_pipeline_set_color4ub (pipeline,
+ paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+
+ {
+ float rectangles[] =
+ {
+ /* top left corner */
+ 0, 0, border_left, border_top,
+ 0.0, 0.0,
+ tx1, ty1,
+
+ /* top middle */
+ border_left, 0, ex, border_top,
+ tx1, 0.0,
+ tx2, ty1,
+
+ /* top right */
+ ex, 0, width, border_top,
+ tx2, 0.0,
+ 1.0, ty1,
+
+ /* mid left */
+ 0, border_top, border_left, ey,
+ 0.0, ty1,
+ tx1, ty2,
+
+ /* center */
+ border_left, border_top, ex, ey,
+ tx1, ty1,
+ tx2, ty2,
+
+ /* mid right */
+ ex, border_top, width, ey,
+ tx2, ty1,
+ 1.0, ty2,
+
+ /* bottom left */
+ 0, ey, border_left, height,
+ 0.0, ty2,
+ tx1, 1.0,
+
+ /* bottom center */
+ border_left, ey, ex, height,
+ tx1, ty2,
+ tx2, 1.0,
+
+ /* bottom right */
+ ex, ey, width, height,
+ tx2, ty2,
+ 1.0, 1.0
+ };
+
+ cogl_framebuffer_draw_textured_rectangles (framebuffer, pipeline, rectangles, 9);
+ }
+}
+
+static void
+st_theme_node_paint_outline (StThemeNode *node,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity)
+
+{
+ float width, height;
+ int outline_width;
+ float rects[16];
+ ClutterColor outline_color, effective_outline;
+ guint8 alpha;
+
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+
+ outline_width = st_theme_node_get_outline_width (node);
+ if (outline_width == 0)
+ return;
+
+ st_theme_node_get_outline_color (node, &outline_color);
+ over (&outline_color, &node->background_color, &effective_outline);
+
+ alpha = paint_opacity * outline_color.alpha / 255;
+
+ st_theme_node_ensure_color_pipeline (node);
+ cogl_pipeline_set_color4ub (node->color_pipeline,
+ effective_outline.red * alpha / 255,
+ effective_outline.green * alpha / 255,
+ effective_outline.blue * alpha / 255,
+ alpha);
+
+ /* The outline is drawn just outside the border, which means just
+ * outside the allocation box. This means that in some situations
+ * involving clip_to_allocation or the screen edges, you won't be
+ * able to see the outline. In practice, it works well enough.
+ */
+
+ /* NORTH */
+ rects[0] = -outline_width;
+ rects[1] = -outline_width;
+ rects[2] = width + outline_width;
+ rects[3] = 0;
+
+ /* EAST */
+ rects[4] = width;
+ rects[5] = 0;
+ rects[6] = width + outline_width;
+ rects[7] = height;
+
+ /* SOUTH */
+ rects[8] = -outline_width;
+ rects[9] = height;
+ rects[10] = width + outline_width;
+ rects[11] = height + outline_width;
+
+ /* WEST */
+ rects[12] = -outline_width;
+ rects[13] = 0;
+ rects[14] = 0;
+ rects[15] = height;
+
+ cogl_framebuffer_draw_rectangles (framebuffer, node->color_pipeline, rects, 4);
+}
+
+static gboolean
+st_theme_node_needs_new_box_shadow_for_size (StThemeNodePaintState *state,
+ StThemeNode *node,
+ float width,
+ float height,
+ float resource_scale)
+{
+ if (!node->rendered_once)
+ return TRUE;
+
+ /* The resource scale changed, so need to recompute a new box-shadow */
+ if (fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ return TRUE;
+
+ /* The allocation hasn't changed, no need to recompute a new
+ box-shadow. */
+ if (state->alloc_width == width &&
+ state->alloc_height == height)
+ return FALSE;
+
+ /* If there is no shadow, no need to recompute a new box-shadow. */
+ if (node->box_shadow_min_width == 0 ||
+ node->box_shadow_min_height == 0)
+ return FALSE;
+
+ /* If the new size is inferior to the box-shadow minimum size (we
+ already know the size has changed), we need to recompute the
+ box-shadow. */
+ if (width < node->box_shadow_min_width ||
+ height < node->box_shadow_min_height)
+ return TRUE;
+
+ /* Now checking whether the size of the node has crossed the minimum
+ box-shadow size boundary, from below to above the minimum size .
+ If that's the case, we need to recompute the box-shadow */
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+st_theme_node_paint (StThemeNode *node,
+ StThemeNodePaintState *state,
+ CoglFramebuffer *framebuffer,
+ const ClutterActorBox *box,
+ guint8 paint_opacity,
+ float resource_scale)
+{
+ float width, height;
+ ClutterActorBox allocation;
+
+ /* Some things take an ActorBox, some things just width/height */
+ width = box->x2 - box->x1;
+ height = box->y2 - box->y1;
+ allocation.x1 = allocation.y1 = 0;
+ allocation.x2 = width;
+ allocation.y2 = height;
+
+ if (width <= 0 || height <= 0 || resource_scale <= 0.0f)
+ return;
+
+ /* Check whether we need to recreate the textures of the paint
+ * state, either because :
+ * 1) the theme node associated to the paint state has changed
+ * 2) the allocation size change requires recreating textures
+ */
+ if (state->node != node ||
+ st_theme_node_needs_new_box_shadow_for_size (state, node, width, height,
+ resource_scale))
+ {
+ /* If we had the ability to cache textures on the node, then we
+ can just copy them over to the paint state and avoid all
+ rendering. We end up sharing textures a cross different
+ widgets. */
+ if (node->rendered_once && node->cached_textures &&
+ width >= node->box_shadow_min_width && height >= node->box_shadow_min_height &&
+ fabsf (resource_scale - state->resource_scale) < FLT_EPSILON)
+ st_theme_node_paint_state_copy (state, &node->cached_state);
+ else
+ st_theme_node_render_resources (state, node, width, height, resource_scale);
+
+ node->rendered_once = TRUE;
+ }
+ else if (state->alloc_width != width || state->alloc_height != height ||
+ fabsf (state->resource_scale - resource_scale) > FLT_EPSILON)
+ st_theme_node_update_resources (state, node, width, height, resource_scale);
+
+ /* Rough notes about the relationship of borders and backgrounds in CSS3;
+ * see http://www.w3.org/TR/css3-background/ for more accurate details.
+ *
+ * - Things are drawn in 4 layers, from the bottom:
+ * Background color
+ * Background image
+ * Border color or border image
+ * Content
+ * - The background color, gradient and image extend to and are clipped by
+ * the edge of the border area, so will be rounded if the border is
+ * rounded. (CSS3 background-clip property modifies this)
+ * - The border image replaces what would normally be drawn by the border
+ * - The border image is not clipped by a rounded border-radius
+ * - The border radius rounds the background even if the border is
+ * zero width or a border image is being used.
+ *
+ * Deviations from the above as implemented here:
+ * - The combination of border image and a non-zero border radius is
+ * not supported; the background color will be drawn with square
+ * corners.
+ * - The background image is drawn above the border color, not below it.
+ * - We clip the background image to the inside edges of the border
+ * instead of the outside edges of the border (but position the image
+ * such that it's aligned to the outside edges)
+ */
+
+ if (state->box_shadow_pipeline)
+ {
+ if (state->alloc_width < node->box_shadow_min_width ||
+ state->alloc_height < node->box_shadow_min_height)
+ _st_paint_shadow_with_opacity (node->box_shadow,
+ framebuffer,
+ state->box_shadow_pipeline,
+ &allocation,
+ paint_opacity);
+ else
+ st_theme_node_paint_sliced_shadow (state,
+ framebuffer,
+ &allocation,
+ paint_opacity);
+ }
+
+ if (state->prerendered_pipeline != NULL ||
+ st_theme_node_load_border_image (node, resource_scale))
+ {
+ if (state->prerendered_pipeline != NULL)
+ {
+ ClutterActorBox paint_box;
+
+ st_theme_node_get_background_paint_box (node,
+ &allocation,
+ &paint_box);
+
+ paint_material_with_opacity (state->prerendered_pipeline,
+ framebuffer,
+ &paint_box,
+ NULL,
+ paint_opacity);
+ }
+
+ if (node->border_slices_pipeline != NULL)
+ st_theme_node_paint_sliced_border_image (node, framebuffer, width, height, paint_opacity);
+ }
+ else
+ {
+ st_theme_node_paint_borders (state, framebuffer, box, paint_opacity);
+ }
+
+ st_theme_node_paint_outline (node, framebuffer, box, paint_opacity);
+
+ if (state->prerendered_pipeline == NULL &&
+ st_theme_node_load_background_image (node, resource_scale))
+ {
+ ClutterActorBox background_box;
+ ClutterActorBox texture_coords;
+ gboolean has_visible_outline;
+
+ /* If the node doesn't have an opaque or repeating background or
+ * a border then we let its background image shadows leak out,
+ * but otherwise we clip it.
+ */
+ has_visible_outline = st_theme_node_has_visible_outline (node);
+
+ get_background_position (node, &allocation, resource_scale,
+ &background_box, &texture_coords);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_push_rectangle_clip (framebuffer,
+ allocation.x1, allocation.y1,
+ allocation.x2, allocation.y2);
+
+ /* CSS based drop shadows
+ *
+ * Drop shadows in ST are modelled after the CSS3 box-shadow property;
+ * see http://www.css3.info/preview/box-shadow/ for a detailed description.
+ *
+ * While the syntax of the property is mostly identical - we do not support
+ * multiple shadows and allow for a more liberal placement of the color
+ * parameter - its interpretation defers significantly in that the shadow's
+ * shape is not determined by the bounding box, but by the CSS background
+ * image. The drop shadows are allowed to escape the nodes allocation if
+ * there is nothing (like a border, or the edge of the background color)
+ * to logically confine it.
+ */
+ if (node->background_shadow_pipeline != NULL)
+ _st_paint_shadow_with_opacity (node->background_image_shadow,
+ framebuffer,
+ node->background_shadow_pipeline,
+ &background_box,
+ paint_opacity);
+
+ paint_material_with_opacity (node->background_pipeline,
+ framebuffer,
+ &background_box,
+ &texture_coords,
+ paint_opacity);
+
+ if (has_visible_outline || node->background_repeat)
+ cogl_framebuffer_pop_clip (framebuffer);
+ }
+}
+
+static void
+st_theme_node_paint_state_node_free_internal (StThemeNodePaintState *state,
+ gboolean unref_node)
+{
+ int corner_id;
+
+ cogl_clear_object (&state->prerendered_texture);
+ cogl_clear_object (&state->prerendered_pipeline);
+ cogl_clear_object (&state->box_shadow_pipeline);
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ cogl_clear_object (&state->corner_material[corner_id]);
+
+ if (unref_node)
+ st_theme_node_paint_state_set_node (state, NULL);
+
+ st_theme_node_paint_state_init (state);
+}
+
+static void
+st_theme_node_paint_state_node_freed (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, FALSE);
+}
+
+void
+st_theme_node_paint_state_set_node (StThemeNodePaintState *state, StThemeNode *node)
+{
+ if (state->node)
+ g_object_weak_unref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+
+ state->node = node;
+ if (state->node)
+ g_object_weak_ref (G_OBJECT (state->node),
+ (GWeakNotify) st_theme_node_paint_state_node_freed,
+ state);
+}
+
+void
+st_theme_node_paint_state_free (StThemeNodePaintState *state)
+{
+ st_theme_node_paint_state_node_free_internal (state, TRUE);
+}
+
+void
+st_theme_node_paint_state_init (StThemeNodePaintState *state)
+{
+ int corner_id;
+
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1;
+ state->node = NULL;
+ state->box_shadow_pipeline = NULL;
+ state->prerendered_texture = NULL;
+ state->prerendered_pipeline = NULL;
+
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ state->corner_material[corner_id] = NULL;
+}
+
+void
+st_theme_node_paint_state_copy (StThemeNodePaintState *state,
+ StThemeNodePaintState *other)
+{
+ int corner_id;
+
+ if (state == other)
+ return;
+
+ st_theme_node_paint_state_free (state);
+
+ st_theme_node_paint_state_set_node (state, other->node);
+
+ state->alloc_width = other->alloc_width;
+ state->alloc_height = other->alloc_height;
+ state->resource_scale = other->resource_scale;
+ state->box_shadow_width = other->box_shadow_width;
+ state->box_shadow_height = other->box_shadow_height;
+
+ if (other->box_shadow_pipeline)
+ state->box_shadow_pipeline = cogl_object_ref (other->box_shadow_pipeline);
+ if (other->prerendered_texture)
+ state->prerendered_texture = cogl_object_ref (other->prerendered_texture);
+ if (other->prerendered_pipeline)
+ state->prerendered_pipeline = cogl_object_ref (other->prerendered_pipeline);
+ for (corner_id = 0; corner_id < 4; corner_id++)
+ if (other->corner_material[corner_id])
+ state->corner_material[corner_id] = cogl_object_ref (other->corner_material[corner_id]);
+}
+
+void
+st_theme_node_paint_state_invalidate (StThemeNodePaintState *state)
+{
+ state->alloc_width = 0;
+ state->alloc_height = 0;
+ state->resource_scale = -1.0f;
+}
+
+gboolean
+st_theme_node_paint_state_invalidate_for_file (StThemeNodePaintState *state,
+ GFile *file)
+{
+ if (state->node != NULL &&
+ st_theme_node_invalidate_resources_for_file (state->node, file))
+ {
+ st_theme_node_paint_state_invalidate (state);
+ return TRUE;
+ }
+
+ return FALSE;
+}