No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-theme-node-drawing.c: Code to draw themed elements
4 *
5 * Copyright 2009, 2010 Red Hat, Inc.
6 * Copyright 2009, 2010 Florian Mç«Żllner
7 * Copyright 2010 Intel Corporation.
8 * Copyright 2011 Quentin "Sardem FF7" Glidic
9 *
10 * Contains code derived from:
11 * rectangle.c: Rounded rectangle.
12 * Copyright 2008 litl, LLC.
13 * st-texture-frame.h: Expandible texture actor
14 * Copyright 2007 OpenedHand
15 * Copyright 2009 Intel Corporation.
16 *
17 * This program is free software; you can redistribute it and/or modify it
18 * under the terms and conditions of the GNU Lesser General Public License,
19 * version 2.1, as published by the Free Software Foundation.
20 *
21 * This program is distributed in the hope it will be useful, but WITHOUT ANY
22 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
24 * more details.
25 *
26 * You should have received a copy of the GNU Lesser General Public License
27 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 */
29
30 #include <stdlib.h>
31 #include <math.h>
32
33 #include "st-shadow.h"
34 #include "st-private.h"
35 #include "st-theme-private.h"
36 #include "st-theme-context.h"
37 #include "st-texture-cache.h"
38 #include "st-theme-node-private.h"
39
40 /****
41 * Rounded corners
42 ****/
43
44 typedef struct {
45 ClutterColor color;
46 ClutterColor border_color_1;
47 ClutterColor border_color_2;
48 guint radius;
49 guint border_width_1;
50 guint border_width_2;
51 } StCornerSpec;
52
53 static void
54 elliptical_arc (cairo_t *cr,
55 double x_center,
56 double y_center,
57 double x_radius,
58 double y_radius,
59 double angle1,
60 double angle2)
61 {
62 cairo_save (cr);
63 cairo_translate (cr, x_center, y_center);
64 cairo_scale (cr, x_radius, y_radius);
65 cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
66 cairo_restore (cr);
67 }
68
69 static CoglHandle
70 create_corner_material (StCornerSpec *corner)
71 {
72 CoglHandle texture;
73 cairo_t *cr;
74 cairo_surface_t *surface;
75 guint rowstride;
76 guint8 *data;
77 guint size;
78 guint max_border_width;
79
80 max_border_width = MAX(corner->border_width_2, corner->border_width_1);
81 size = 2 * MAX(max_border_width, corner->radius);
82 rowstride = size * 4;
83 data = g_new0 (guint8, size * rowstride);
84
85 surface = cairo_image_surface_create_for_data (data,
86 CAIRO_FORMAT_ARGB32,
87 size, size,
88 rowstride);
89 cr = cairo_create (surface);
90 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
91 cairo_scale (cr, size, size);
92
93 if (max_border_width <= corner->radius)
94 {
95 double x_radius, y_radius;
96
97 if (max_border_width != 0)
98 {
99 cairo_set_source_rgba (cr,
100 corner->border_color_1.red / 255.,
101 corner->border_color_1.green / 255.,
102 corner->border_color_1.blue / 255.,
103 corner->border_color_1.alpha / 255.);
104
105 cairo_arc (cr, 0.5, 0.5, 0.5, 0, 2 * M_PI);
106 cairo_fill (cr);
107 }
108
109 cairo_set_source_rgba (cr,
110 corner->color.red / 255.,
111 corner->color.green / 255.,
112 corner->color.blue / 255.,
113 corner->color.alpha / 255.);
114
115 x_radius = 0.5 * (1.0 - (double) corner->border_width_2 / corner->radius);
116 y_radius = 0.5 * (1.0 - (double) corner->border_width_1 / corner->radius);
117
118 /* TOPRIGHT */
119 elliptical_arc (cr,
120 0.5, 0.5,
121 x_radius, y_radius,
122 3 * M_PI / 2, 2 * M_PI);
123
124 /* BOTTOMRIGHT */
125 elliptical_arc (cr,
126 0.5, 0.5,
127 x_radius, y_radius,
128 0, M_PI / 2);
129
130 /* TOPLEFT */
131 elliptical_arc (cr,
132 0.5, 0.5,
133 x_radius, y_radius,
134 M_PI, 3 * M_PI / 2);
135
136 /* BOTTOMLEFT */
137 elliptical_arc (cr,
138 0.5, 0.5,
139 x_radius, y_radius,
140 M_PI / 2, M_PI);
141
142 cairo_fill (cr);
143 }
144 else
145 {
146 double radius;
147
148 radius = (gdouble)corner->radius / max_border_width;
149
150 cairo_set_source_rgba (cr,
151 corner->border_color_1.red / 255.,
152 corner->border_color_1.green / 255.,
153 corner->border_color_1.blue / 255.,
154 corner->border_color_1.alpha / 255.);
155
156 cairo_arc (cr, radius, radius, radius, M_PI, 3 * M_PI / 2);
157 cairo_line_to (cr, 1.0 - radius, 0.0);
158 cairo_arc (cr, 1.0 - radius, radius, radius, 3 * M_PI / 2, 2 * M_PI);
159 cairo_line_to (cr, 1.0, 1.0 - radius);
160 cairo_arc (cr, 1.0 - radius, 1.0 - radius, radius, 0, M_PI / 2);
161 cairo_line_to (cr, radius, 1.0);
162 cairo_arc (cr, radius, 1.0 - radius, radius, M_PI / 2, M_PI);
163 cairo_fill (cr);
164 }
165 cairo_destroy (cr);
166
167 cairo_surface_destroy (surface);
168
169 texture = cogl_texture_new_from_data (size, size,
170 COGL_TEXTURE_NONE,
171 CLUTTER_CAIRO_FORMAT_ARGB32,
172 COGL_PIXEL_FORMAT_ANY,
173 rowstride,
174 data);
175 g_free (data);
176 g_assert (texture != COGL_INVALID_HANDLE);
177
178 return texture;
179 }
180
181 static char *
182 corner_to_string (StCornerSpec *corner)
183 {
184 return g_strdup_printf ("st-theme-node-corner:%02x%02x%02x%02x,%02x%02x%02x%02x,%02x%02x%02x%02x,%u,%u,%u",
185 corner->color.red, corner->color.blue, corner->color.green, corner->color.alpha,
186 corner->border_color_1.red, corner->border_color_1.green, corner->border_color_1.blue, corner->border_color_1.alpha,
187 corner->border_color_2.red, corner->border_color_2.green, corner->border_color_2.blue, corner->border_color_2.alpha,
188 corner->radius,
189 corner->border_width_1,
190 corner->border_width_2);
191 }
192
193 static CoglHandle
194 load_corner (StTextureCache *cache,
195 const char *key,
196 void *datap,
197 GError **error)
198 {
199 return create_corner_material ((StCornerSpec *) datap);
200 }
201
202 /* To match the CSS specification, we want the border to look like it was
203 * drawn over the background. But actually drawing the border over the
204 * background will produce slightly bad antialiasing at the edges, so
205 * compute the effective border color instead.
206 */
207 #define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8)
208 #define MULT(c,a) NORM(c*a)
209
210 static void
211 premultiply (ClutterColor *color)
212 {
213 guint t;
214 color->red = MULT (color->red, color->alpha);
215 color->green = MULT (color->green, color->alpha);
216 color->blue = MULT (color->blue, color->alpha);
217 }
218
219 static void
220 unpremultiply (ClutterColor *color)
221 {
222 if (color->alpha != 0)
223 {
224 color->red = (color->red * 255 + 127) / color->alpha;
225 color->green = (color->green * 255 + 127) / color->alpha;
226 color->blue = (color->blue * 255 + 127) / color->alpha;
227 }
228 }
229
230 static void
231 over (const ClutterColor *source,
232 const ClutterColor *destination,
233 ClutterColor *result)
234 {
235 guint t;
236 ClutterColor src = *source;
237 ClutterColor dst = *destination;
238
239 premultiply (&src);
240 premultiply (&dst);
241
242 result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha);
243 result->red = src.red + NORM ((255 - src.alpha) * dst.red);
244 result->green = src.green + NORM ((255 - src.alpha) * dst.green);
245 result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue);
246
247 unpremultiply (result);
248 }
249
250 /*
251 * st_theme_node_reduce_border_radius:
252 * @node: a #StThemeNode
253 * @corners: (array length=4) (out): reduced corners
254 *
255 * Implements the corner overlap algorithm mentioned at
256 * http://www.w3.org/TR/css3-background/#corner-overlap
257 */
258 static void
259 st_theme_node_reduce_border_radius (StThemeNode *node,
260 guint *corners)
261 {
262 gfloat scale;
263 guint sum;
264
265 scale = 1.0;
266
267 /* top */
268 sum = node->border_radius[ST_CORNER_TOPLEFT]
269 + node->border_radius[ST_CORNER_TOPRIGHT];
270
271 if (sum > 0)
272 scale = MIN (node->alloc_width / sum, scale);
273
274 /* right */
275 sum = node->border_radius[ST_CORNER_TOPRIGHT]
276 + node->border_radius[ST_CORNER_BOTTOMRIGHT];
277
278 if (sum > 0)
279 scale = MIN (node->alloc_height / sum, scale);
280
281 /* bottom */
282 sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
283 + node->border_radius[ST_CORNER_BOTTOMRIGHT];
284
285 if (sum > 0)
286 scale = MIN (node->alloc_width / sum, scale);
287
288 /* left */
289 sum = node->border_radius[ST_CORNER_BOTTOMLEFT]
290 + node->border_radius[ST_CORNER_TOPLEFT];
291
292 if (sum > 0)
293 scale = MIN (node->alloc_height / sum, scale);
294
295 corners[ST_CORNER_TOPLEFT] = node->border_radius[ST_CORNER_TOPLEFT] * scale;
296 corners[ST_CORNER_TOPRIGHT] = node->border_radius[ST_CORNER_TOPRIGHT] * scale;
297 corners[ST_CORNER_BOTTOMLEFT] = node->border_radius[ST_CORNER_BOTTOMLEFT] * scale;
298 corners[ST_CORNER_BOTTOMRIGHT] = node->border_radius[ST_CORNER_BOTTOMRIGHT] * scale;
299 }
300
301 static void
302 st_theme_node_get_corner_border_widths (StThemeNode *node,
303 StCorner corner_id,
304 guint *border_width_1,
305 guint *border_width_2)
306 {
307 switch (corner_id)
308 {
309 case ST_CORNER_TOPLEFT:
310 if (border_width_1)
311 *border_width_1 = node->border_width[ST_SIDE_TOP];
312 if (border_width_2)
313 *border_width_2 = node->border_width[ST_SIDE_LEFT];
314 break;
315 case ST_CORNER_TOPRIGHT:
316 if (border_width_1)
317 *border_width_1 = node->border_width[ST_SIDE_TOP];
318 if (border_width_2)
319 *border_width_2 = node->border_width[ST_SIDE_RIGHT];
320 break;
321 case ST_CORNER_BOTTOMRIGHT:
322 if (border_width_1)
323 *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
324 if (border_width_2)
325 *border_width_2 = node->border_width[ST_SIDE_RIGHT];
326 break;
327 case ST_CORNER_BOTTOMLEFT:
328 if (border_width_1)
329 *border_width_1 = node->border_width[ST_SIDE_BOTTOM];
330 if (border_width_2)
331 *border_width_2 = node->border_width[ST_SIDE_LEFT];
332 break;
333 }
334 }
335
336 static CoglHandle
337 st_theme_node_lookup_corner (StThemeNode *node,
338 StCorner corner_id)
339 {
340 CoglHandle texture, material;
341 char *key;
342 StTextureCache *cache;
343 StCornerSpec corner;
344 guint radius[4];
345
346 cache = st_texture_cache_get_default ();
347
348 st_theme_node_reduce_border_radius (node, radius);
349
350 if (radius[corner_id] == 0)
351 return COGL_INVALID_HANDLE;
352
353 corner.radius = radius[corner_id];
354 corner.color = node->background_color;
355 st_theme_node_get_corner_border_widths (node, corner_id,
356 &corner.border_width_1,
357 &corner.border_width_2);
358
359 switch (corner_id)
360 {
361 case ST_CORNER_TOPLEFT:
362 over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
363 over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
364 break;
365 case ST_CORNER_TOPRIGHT:
366 over (&node->border_color[ST_SIDE_TOP], &corner.color, &corner.border_color_1);
367 over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
368 break;
369 case ST_CORNER_BOTTOMRIGHT:
370 over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
371 over (&node->border_color[ST_SIDE_RIGHT], &corner.color, &corner.border_color_2);
372 break;
373 case ST_CORNER_BOTTOMLEFT:
374 over (&node->border_color[ST_SIDE_BOTTOM], &corner.color, &corner.border_color_1);
375 over (&node->border_color[ST_SIDE_LEFT], &corner.color, &corner.border_color_2);
376 break;
377 }
378
379 if (corner.color.alpha == 0 &&
380 corner.border_color_1.alpha == 0 &&
381 corner.border_color_2.alpha == 0)
382 return COGL_INVALID_HANDLE;
383
384 key = corner_to_string (&corner);
385 texture = st_texture_cache_load (cache, key, ST_TEXTURE_CACHE_POLICY_NONE, load_corner, &corner, NULL);
386 material = _st_create_texture_material (texture);
387 cogl_handle_unref (texture);
388
389 g_free (key);
390
391 return material;
392 }
393
394 static void
395 get_background_scale (StThemeNode *node,
396 gdouble painting_area_width,
397 gdouble painting_area_height,
398 gdouble background_image_width,
399 gdouble background_image_height,
400 gdouble *scale_w,
401 gdouble *scale_h)
402 {
403 *scale_w = -1.0;
404 *scale_h = -1.0;
405
406 switch (node->background_size)
407 {
408 case ST_BACKGROUND_SIZE_AUTO:
409 *scale_w = 1.0;
410 break;
411 case ST_BACKGROUND_SIZE_CONTAIN:
412 *scale_w = MIN (painting_area_width / background_image_width,
413 painting_area_height / background_image_height);
414 break;
415 case ST_BACKGROUND_SIZE_COVER:
416 *scale_w = MAX (painting_area_width / background_image_width,
417 painting_area_height / background_image_height);
418 break;
419 case ST_BACKGROUND_SIZE_FIXED:
420 if (node->background_size_w > -1)
421 {
422 *scale_w = node->background_size_w / background_image_width;
423 if (node->background_size_h > -1)
424 *scale_h = node->background_size_h / background_image_height;
425 }
426 else if (node->background_size_h > -1)
427 *scale_w = node->background_size_h / background_image_height;
428 break;
429 }
430 if (*scale_h < 0.0)
431 *scale_h = *scale_w;
432 }
433
434 static void
435 get_background_coordinates (StThemeNode *node,
436 gdouble painting_area_width,
437 gdouble painting_area_height,
438 gdouble background_image_width,
439 gdouble background_image_height,
440 gdouble *x,
441 gdouble *y)
442 {
443 /* honor the specified position if any */
444 if (node->background_position_set)
445 {
446 *x = node->background_position_x;
447 *y = node->background_position_y;
448 }
449 else
450 {
451 /* center the background on the widget */
452 *x = (painting_area_width / 2.0) - (background_image_width / 2.0);
453 *y = (painting_area_height / 2.0) - (background_image_height / 2.0);
454 }
455 }
456
457 static void
458 get_background_position (StThemeNode *self,
459 const ClutterActorBox *allocation,
460 ClutterActorBox *result,
461 ClutterActorBox *texture_coords)
462 {
463 gdouble painting_area_width, painting_area_height;
464 gdouble background_image_width, background_image_height;
465 gdouble x1, y1;
466 gdouble scale_w, scale_h;
467
468 /* get the background image size */
469 background_image_width = cogl_texture_get_width (self->background_texture);
470 background_image_height = cogl_texture_get_height (self->background_texture);
471
472 /* get the painting area size */
473 painting_area_width = allocation->x2 - allocation->x1;
474 painting_area_height = allocation->y2 - allocation->y1;
475
476 /* scale if requested */
477 get_background_scale (self,
478 painting_area_width, painting_area_height,
479 background_image_width, background_image_height,
480 &scale_w, &scale_h);
481 background_image_width *= scale_w;
482 background_image_height *= scale_h;
483
484 /* get coordinates */
485 get_background_coordinates (self,
486 painting_area_width, painting_area_height,
487 background_image_width, background_image_height,
488 &x1, &y1);
489
490 if (self->background_repeat)
491 {
492 gdouble width = allocation->x2 - allocation->x1 + x1;
493 gdouble height = allocation->y2 - allocation->y1 + y1;
494
495 *result = *allocation;
496
497 /* reference image is at x1, y1 */
498 texture_coords->x1 = x1 / background_image_width;
499 texture_coords->y1 = y1 / background_image_height;
500 texture_coords->x2 = width / background_image_width;
501 texture_coords->y2 = height / background_image_height;
502 }
503 else
504 {
505 result->x1 = x1;
506 result->y1 = y1;
507 result->x2 = x1 + background_image_width;
508 result->y2 = y1 + background_image_height;
509
510 texture_coords->x1 = texture_coords->y1 = 0;
511 texture_coords->x2 = texture_coords->y2 = 1;
512 }
513 }
514
515 /* Use of this function marks code which doesn't support
516 * non-uniform colors.
517 */
518 static void
519 get_arbitrary_border_color (StThemeNode *node,
520 ClutterColor *color)
521 {
522 if (color)
523 st_theme_node_get_border_color (node, ST_SIDE_TOP, color);
524 }
525
526 static gboolean
527 st_theme_node_has_visible_outline (StThemeNode *node)
528 {
529 if (node->background_color.alpha > 0)
530 return TRUE;
531
532 if (node->background_gradient_end.alpha > 0)
533 return TRUE;
534
535 if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
536 node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
537 node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
538 node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
539 return TRUE;
540
541 if (node->border_width[ST_SIDE_TOP] > 0 ||
542 node->border_width[ST_SIDE_LEFT] > 0 ||
543 node->border_width[ST_SIDE_RIGHT] > 0 ||
544 node->border_width[ST_SIDE_BOTTOM] > 0)
545 return TRUE;
546
547 return FALSE;
548 }
549
550 static cairo_pattern_t *
551 create_cairo_pattern_of_background_gradient (StThemeNode *node)
552 {
553 cairo_pattern_t *pattern;
554
555 g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE,
556 NULL);
557
558 if (node->background_gradient_type == ST_GRADIENT_VERTICAL)
559 pattern = cairo_pattern_create_linear (0, 0, 0, node->alloc_height);
560 else if (node->background_gradient_type == ST_GRADIENT_HORIZONTAL)
561 pattern = cairo_pattern_create_linear (0, 0, node->alloc_width, 0);
562 else
563 {
564 gdouble cx, cy;
565
566 cx = node->alloc_width / 2.;
567 cy = node->alloc_height / 2.;
568 pattern = cairo_pattern_create_radial (cx, cy, 0, cx, cy, MIN (cx, cy));
569 }
570
571 cairo_pattern_add_color_stop_rgba (pattern, 0,
572 node->background_color.red / 255.,
573 node->background_color.green / 255.,
574 node->background_color.blue / 255.,
575 node->background_color.alpha / 255.);
576 cairo_pattern_add_color_stop_rgba (pattern, 1,
577 node->background_gradient_end.red / 255.,
578 node->background_gradient_end.green / 255.,
579 node->background_gradient_end.blue / 255.,
580 node->background_gradient_end.alpha / 255.);
581 return pattern;
582 }
583
584 static cairo_pattern_t *
585 create_cairo_pattern_of_background_image (StThemeNode *node,
586 gboolean *needs_background_fill)
587 {
588 cairo_surface_t *surface;
589 cairo_pattern_t *pattern;
590 cairo_content_t content;
591 cairo_matrix_t matrix;
592 const char *file;
593
594 StTextureCache *texture_cache;
595
596 gdouble background_image_width, background_image_height;
597 gdouble x, y;
598 gdouble scale_w, scale_h;
599
600 file = st_theme_node_get_background_image (node);
601
602 texture_cache = st_texture_cache_get_default ();
603
604 surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file);
605
606 if (surface == NULL)
607 return NULL;
608
609 g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
610
611 content = cairo_surface_get_content (surface);
612 pattern = cairo_pattern_create_for_surface (surface);
613
614 background_image_width = cairo_image_surface_get_width (surface);
615 background_image_height = cairo_image_surface_get_height (surface);
616
617 *needs_background_fill = TRUE;
618
619 cairo_matrix_init_identity (&matrix);
620
621 get_background_scale (node,
622 node->alloc_width, node->alloc_height,
623 background_image_width, background_image_height,
624 &scale_w, &scale_h);
625 if ((scale_w != 1) || (scale_h != 1))
626 cairo_matrix_scale (&matrix, 1.0/scale_w, 1.0/scale_h);
627 background_image_width *= scale_w;
628 background_image_height *= scale_h;
629
630 get_background_coordinates (node,
631 node->alloc_width, node->alloc_height,
632 background_image_width, background_image_height,
633 &x, &y);
634 cairo_matrix_translate (&matrix, -x, -y);
635
636 if (node->background_repeat)
637 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
638
639 /* If it's opaque, fills up the entire allocated
640 * area, then don't bother doing a background fill first
641 */
642 if (content != CAIRO_CONTENT_COLOR_ALPHA)
643 {
644 if (node->background_repeat ||
645 (x >= 0 &&
646 y >= 0 &&
647 background_image_width - x >= node->alloc_width &&
648 background_image_height -y >= node->alloc_height))
649 *needs_background_fill = FALSE;
650 }
651
652 cairo_pattern_set_matrix (pattern, &matrix);
653
654 return pattern;
655 }
656
657 /* fill_exterior = TRUE means that pattern is a surface pattern and
658 * we should extend the pattern with a solid fill from its edges.
659 * This is a bit of a hack; the alternative would be to make the
660 * surface of the surface pattern 1 pixel bigger and use CAIRO_EXTEND_PAD.
661 */
662 static void
663 paint_shadow_pattern_to_cairo_context (StShadow *shadow_spec,
664 cairo_pattern_t *pattern,
665 gboolean fill_exterior,
666 cairo_t *cr,
667 cairo_path_t *interior_path,
668 cairo_path_t *outline_path)
669 {
670 /* If there are borders, clip the shadow to the interior
671 * of the borders; if there is a visible outline, clip the shadow to
672 * that outline
673 */
674 cairo_path_t *path = (interior_path != NULL) ? interior_path : outline_path;
675 double x1, x2, y1, y2;
676
677 /* fill_exterior only makes sense if we're clipping the shadow - filling
678 * to the edges of the surface would be silly */
679 g_assert (!(fill_exterior && path == NULL));
680
681 cairo_save (cr);
682 if (path != NULL)
683 {
684 cairo_append_path (cr, path);
685
686 /* There's no way to invert a path in cairo, so we need bounds for
687 * the area we are drawing in order to create the "exterior" region.
688 * Pixel align to hit fast paths.
689 */
690 if (fill_exterior)
691 {
692 cairo_path_extents (cr, &x1, &y1, &x2, &y2);
693 x1 = floor (x1);
694 y1 = floor (y1);
695 x2 = ceil (x2);
696 y2 = ceil (y2);
697 }
698
699 cairo_clip (cr);
700 }
701
702 cairo_set_source_rgba (cr,
703 shadow_spec->color.red / 255.0,
704 shadow_spec->color.green / 255.0,
705 shadow_spec->color.blue / 255.0,
706 shadow_spec->color.alpha / 255.0);
707 if (fill_exterior)
708 {
709 cairo_surface_t *surface;
710 int width, height;
711 cairo_matrix_t matrix;
712
713 cairo_save (cr);
714
715 /* Start with a rectangle enclosing the bounds of the clipped
716 * region */
717 cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
718
719 /* Then subtract out the bounds of the surface in the surface
720 * pattern; we transform the context by the inverse of the
721 * pattern matrix to get to surface coordinates */
722 cairo_pattern_get_surface (pattern, &surface);
723 width = cairo_image_surface_get_width (surface);
724 height = cairo_image_surface_get_height (surface);
725
726 cairo_pattern_get_matrix (pattern, &matrix);
727 cairo_matrix_invert (&matrix);
728 cairo_transform (cr, &matrix);
729
730 cairo_rectangle (cr, 0, height, width, - height);
731 cairo_fill (cr);
732
733 cairo_restore (cr);
734 }
735
736 cairo_mask (cr, pattern);
737 cairo_restore (cr);
738 }
739
740 static void
741 paint_background_image_shadow_to_cairo_context (StThemeNode *node,
742 StShadow *shadow_spec,
743 cairo_pattern_t *pattern,
744 cairo_t *cr,
745 cairo_path_t *interior_path,
746 cairo_path_t *outline_path,
747 int x,
748 int y,
749 int width,
750 int height)
751 {
752 cairo_pattern_t *shadow_pattern;
753
754 g_assert (shadow_spec != NULL);
755 g_assert (pattern != NULL);
756
757 if (outline_path != NULL)
758 {
759 cairo_surface_t *clipped_surface;
760 cairo_pattern_t *clipped_pattern;
761 cairo_t *temp_cr;
762
763 /* Prerender the pattern to a temporary surface,
764 * so it's properly clipped before we create a shadow from it
765 */
766 clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
767 temp_cr = cairo_create (clipped_surface);
768
769 cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR);
770 cairo_paint (temp_cr);
771 cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE);
772
773 if (interior_path != NULL)
774 {
775 cairo_append_path (temp_cr, interior_path);
776 cairo_clip (temp_cr);
777 }
778
779 cairo_append_path (temp_cr, outline_path);
780 cairo_translate (temp_cr, x, y);
781 cairo_set_source (temp_cr, pattern);
782 cairo_clip (temp_cr);
783 cairo_paint (temp_cr);
784 cairo_destroy (temp_cr);
785
786 clipped_pattern = cairo_pattern_create_for_surface (clipped_surface);
787 cairo_surface_destroy (clipped_surface);
788
789 shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
790 clipped_pattern);
791 cairo_pattern_destroy (clipped_pattern);
792 }
793 else
794 {
795 shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec,
796 pattern);
797 }
798
799 paint_shadow_pattern_to_cairo_context (shadow_spec,
800 shadow_pattern, FALSE,
801 cr,
802 interior_path,
803 outline_path);
804 cairo_pattern_destroy (shadow_pattern);
805 }
806
807 /* gets the extents of a cairo_path_t; slightly inefficient, but much simpler than
808 * computing from the raw path data */
809 static void
810 path_extents (cairo_path_t *path,
811 double *x1,
812 double *y1,
813 double *x2,
814 double *y2)
815
816 {
817 cairo_surface_t *dummy = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
818 cairo_t *cr = cairo_create (dummy);
819
820 cairo_append_path (cr, path);
821 cairo_path_extents (cr, x1, y1, x2, y2);
822
823 cairo_destroy (cr);
824 cairo_surface_destroy (dummy);
825 }
826
827 static void
828 paint_inset_box_shadow_to_cairo_context (StThemeNode *node,
829 StShadow *shadow_spec,
830 cairo_t *cr,
831 cairo_path_t *shadow_outline)
832 {
833 cairo_surface_t *shadow_surface;
834 cairo_pattern_t *shadow_pattern;
835 double extents_x1, extents_y1, extents_x2, extents_y2;
836 double shrunk_extents_x1, shrunk_extents_y1, shrunk_extents_x2, shrunk_extents_y2;
837 gboolean fill_exterior;
838
839 g_assert (shadow_spec != NULL);
840 g_assert (shadow_outline != NULL);
841
842 /* Create the pattern used to create the inset shadow; as the shadow
843 * should be drawn as if everything outside the outline was opaque,
844 * we use a temporary surface to draw the background as a solid shape,
845 * which is inverted when creating the shadow pattern.
846 */
847
848 /* First we need to find the size of the temporary surface
849 */
850 path_extents (shadow_outline,
851 &extents_x1, &extents_y1, &extents_x2, &extents_y2);
852
853 /* Shrink the extents by the spread, and offset */
854 shrunk_extents_x1 = extents_x1 + shadow_spec->xoffset + shadow_spec->spread;
855 shrunk_extents_y1 = extents_y1 + shadow_spec->yoffset + shadow_spec->spread;
856 shrunk_extents_x2 = extents_x2 + shadow_spec->xoffset - shadow_spec->spread;
857 shrunk_extents_y2 = extents_y2 + shadow_spec->yoffset - shadow_spec->spread;
858
859 if (shrunk_extents_x1 >= shrunk_extents_x2 || shrunk_extents_y1 >= shrunk_extents_x2)
860 {
861 /* Shadow occupies entire area within border */
862 shadow_pattern = cairo_pattern_create_rgb (0., 0., 0.);
863 fill_exterior = FALSE;
864 }
865 else
866 {
867 /* Bounds of temporary surface */
868 int surface_x = floor (shrunk_extents_x1);
869 int surface_y = floor (shrunk_extents_y1);
870 int surface_width = ceil (shrunk_extents_x2) - surface_x;
871 int surface_height = ceil (shrunk_extents_y2) - surface_y;
872
873 /* Center of the original path */
874 double x_center = (extents_x1 + extents_x2) / 2;
875 double y_center = (extents_y1 + extents_y2) / 2;
876
877 cairo_pattern_t *pattern;
878 cairo_t *temp_cr;
879 cairo_matrix_t matrix;
880
881 shadow_surface = cairo_image_surface_create (CAIRO_FORMAT_A8, surface_width, surface_height);
882 temp_cr = cairo_create (shadow_surface);
883
884 /* Match the coordinates in the temporary context to the parent context */
885 cairo_translate (temp_cr, - surface_x, - surface_y);
886
887 /* Shadow offset */
888 cairo_translate (temp_cr, shadow_spec->xoffset, shadow_spec->yoffset);
889
890 /* Scale the path around the center to match the shrunk bounds */
891 cairo_translate (temp_cr, x_center, y_center);
892 cairo_scale (temp_cr,
893 (shrunk_extents_x2 - shrunk_extents_x1) / (extents_x2 - extents_x1),
894 (shrunk_extents_y2 - shrunk_extents_y1) / (extents_y2 - extents_y1));
895 cairo_translate (temp_cr, - x_center, - y_center);
896
897 cairo_append_path (temp_cr, shadow_outline);
898 cairo_fill (temp_cr);
899 cairo_destroy (temp_cr);
900
901 pattern = cairo_pattern_create_for_surface (shadow_surface);
902 cairo_surface_destroy (shadow_surface);
903
904 /* The pattern needs to be offset back to coordinates in the parent context */
905 cairo_matrix_init_translate (&matrix, - surface_x, - surface_y);
906 cairo_pattern_set_matrix (pattern, &matrix);
907
908 shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, pattern);
909 fill_exterior = TRUE;
910
911 cairo_pattern_destroy (pattern);
912 }
913
914 paint_shadow_pattern_to_cairo_context (shadow_spec,
915 shadow_pattern, fill_exterior,
916 cr,
917 shadow_outline,
918 NULL);
919
920 cairo_pattern_destroy (shadow_pattern);
921 }
922
923 /* In order for borders to be smoothly blended with non-solid backgrounds,
924 * we need to use cairo. This function is a slow fallback path for those
925 * cases (gradients, background images, etc).
926 */
927 static CoglHandle
928 st_theme_node_prerender_background (StThemeNode *node)
929 {
930 StBorderImage *border_image;
931 CoglHandle texture;
932 guint radius[4];
933 int i;
934 cairo_t *cr;
935 cairo_surface_t *surface;
936 StShadow *shadow_spec;
937 StShadow *box_shadow_spec;
938 cairo_pattern_t *pattern = NULL;
939 cairo_path_t *outline_path = NULL;
940 gboolean draw_solid_background = TRUE;
941 gboolean background_is_translucent;
942 gboolean interior_dirty;
943 gboolean draw_background_image_shadow = FALSE;
944 gboolean has_visible_outline;
945 ClutterColor border_color;
946 int border_width[4];
947 guint rowstride;
948 guchar *data;
949 ClutterActorBox actor_box;
950 ClutterActorBox paint_box;
951 cairo_path_t *interior_path = NULL;
952
953 border_image = st_theme_node_get_border_image (node);
954
955 shadow_spec = st_theme_node_get_background_image_shadow (node);
956 box_shadow_spec = st_theme_node_get_box_shadow (node);
957
958 actor_box.x1 = 0;
959 actor_box.x2 = node->alloc_width;
960 actor_box.y1 = 0;
961 actor_box.y2 = node->alloc_height;
962
963 /* If there's a background image shadow, we
964 * may need to create an image bigger than the nodes
965 * allocation
966 */
967 st_theme_node_get_background_paint_box (node, &actor_box, &paint_box);
968
969 /* translate the boxes so the paint box is at 0,0
970 */
971 actor_box.x1 += - paint_box.x1;
972 actor_box.x2 += - paint_box.x1;
973 actor_box.y1 += - paint_box.y1;
974 actor_box.y2 += - paint_box.y1;
975
976 paint_box.x2 += - paint_box.x1;
977 paint_box.x1 += - paint_box.x1;
978 paint_box.y2 += - paint_box.y1;
979 paint_box.y1 += - paint_box.y1;
980
981 rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32,
982 paint_box.x2 - paint_box.x1);
983 data = g_new0 (guchar, (paint_box.y2 - paint_box.y1) * rowstride);
984
985 /* We zero initialize the destination memory, so it's fully transparent
986 * by default.
987 */
988 interior_dirty = FALSE;
989
990 surface = cairo_image_surface_create_for_data (data,
991 CAIRO_FORMAT_ARGB32,
992 paint_box.x2 - paint_box.x1,
993 paint_box.y2 - paint_box.y1,
994 rowstride);
995 cr = cairo_create (surface);
996
997 /* TODO - support non-uniform border colors */
998 get_arbitrary_border_color (node, &border_color);
999
1000 st_theme_node_reduce_border_radius (node, radius);
1001
1002 for (i = 0; i < 4; i++)
1003 border_width[i] = st_theme_node_get_border_width (node, i);
1004
1005 /* Note we don't support translucent background images on top
1006 * of gradients. It's strictly either/or.
1007 */
1008 if (node->background_gradient_type != ST_GRADIENT_NONE)
1009 {
1010 pattern = create_cairo_pattern_of_background_gradient (node);
1011 draw_solid_background = FALSE;
1012
1013 /* If the gradient has any translucent areas, we need to
1014 * erase the interior region before drawing, so that we show
1015 * what's actually under the gradient and not whatever is
1016 * left over from filling the border, etc.
1017 */
1018 if (node->background_color.alpha < 255 ||
1019 node->background_gradient_end.alpha < 255)
1020 background_is_translucent = TRUE;
1021 else
1022 background_is_translucent = FALSE;
1023 }
1024 else
1025 {
1026 const char *background_image;
1027
1028 background_image = st_theme_node_get_background_image (node);
1029
1030 if (background_image != NULL)
1031 {
1032 pattern = create_cairo_pattern_of_background_image (node,
1033 &draw_solid_background);
1034 if (shadow_spec && pattern != NULL)
1035 draw_background_image_shadow = TRUE;
1036 }
1037
1038 /* We never need to clear the interior region before drawing the
1039 * background image, because it either always fills the entire area
1040 * opaquely, or we draw the solid background behind it.
1041 */
1042 background_is_translucent = FALSE;
1043 }
1044
1045 if (pattern == NULL)
1046 draw_solid_background = TRUE;
1047
1048 /* drawing the solid background implicitly clears the interior
1049 * region, so if we're going to draw a solid background before drawing
1050 * the background pattern, then we don't need to bother also clearing the
1051 * background region.
1052 */
1053 if (draw_solid_background)
1054 background_is_translucent = FALSE;
1055
1056 has_visible_outline = st_theme_node_has_visible_outline (node);
1057
1058 /* Create a path for the background's outline first */
1059 if (radius[ST_CORNER_TOPLEFT] > 0)
1060 cairo_arc (cr,
1061 actor_box.x1 + radius[ST_CORNER_TOPLEFT],
1062 actor_box.y1 + radius[ST_CORNER_TOPLEFT],
1063 radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2);
1064 else
1065 cairo_move_to (cr, actor_box.x1, actor_box.y1);
1066 cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1);
1067 if (radius[ST_CORNER_TOPRIGHT] > 0)
1068 cairo_arc (cr,
1069 actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
1070 actor_box.x1 + radius[ST_CORNER_TOPRIGHT],
1071 radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI);
1072 cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]);
1073 if (radius[ST_CORNER_BOTTOMRIGHT] > 0)
1074 cairo_arc (cr,
1075 actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
1076 actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
1077 radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2);
1078 cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2);
1079 if (radius[ST_CORNER_BOTTOMLEFT] > 0)
1080 cairo_arc (cr,
1081 actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
1082 actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
1083 radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI);
1084 cairo_close_path (cr);
1085
1086 outline_path = cairo_copy_path (cr);
1087
1088 /* If we have a solid border, we fill the outline shape with the border
1089 * color and create the inline shape for the background;
1090 * otherwise the outline shape is filled with the background
1091 * directly
1092 */
1093 if (border_image == NULL &&
1094 (border_width[ST_SIDE_TOP] > 0 ||
1095 border_width[ST_SIDE_RIGHT] > 0 ||
1096 border_width[ST_SIDE_BOTTOM] > 0 ||
1097 border_width[ST_SIDE_LEFT] > 0))
1098 {
1099 cairo_set_source_rgba (cr,
1100 border_color.red / 255.,
1101 border_color.green / 255.,
1102 border_color.blue / 255.,
1103 border_color.alpha / 255.);
1104 cairo_fill (cr);
1105
1106 /* We were sloppy when filling in the border, and now the interior
1107 * is filled with the border color, too.
1108 */
1109 interior_dirty = TRUE;
1110
1111 if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP],
1112 border_width[ST_SIDE_LEFT]))
1113 elliptical_arc (cr,
1114 actor_box.x1 + radius[ST_CORNER_TOPLEFT],
1115 actor_box.y1 + radius[ST_CORNER_TOPLEFT],
1116 radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT],
1117 radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP],
1118 M_PI, 3 * M_PI / 2);
1119 else
1120 cairo_move_to (cr,
1121 actor_box.x1 + border_width[ST_SIDE_LEFT],
1122 actor_box.y1 + border_width[ST_SIDE_TOP]);
1123
1124 cairo_line_to (cr,
1125 actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]),
1126 actor_box.y1 + border_width[ST_SIDE_TOP]);
1127
1128 if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP],
1129 border_width[ST_SIDE_RIGHT]))
1130 elliptical_arc (cr,
1131 actor_box.x2 - radius[ST_CORNER_TOPRIGHT],
1132 actor_box.y1 + radius[ST_CORNER_TOPRIGHT],
1133 radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT],
1134 radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP],
1135 3 * M_PI / 2, 2 * M_PI);
1136 else
1137 cairo_line_to (cr,
1138 actor_box.x2 - border_width[ST_SIDE_RIGHT],
1139 actor_box.y1 + border_width[ST_SIDE_TOP]);
1140
1141 cairo_line_to (cr,
1142 actor_box.x2 - border_width[ST_SIDE_RIGHT],
1143 actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM]));
1144
1145 if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM],
1146 border_width[ST_SIDE_RIGHT]))
1147 elliptical_arc (cr,
1148 actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT],
1149 actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT],
1150 radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT],
1151 radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM],
1152 0, M_PI / 2);
1153 else
1154 cairo_line_to (cr,
1155 actor_box.x2 - border_width[ST_SIDE_RIGHT],
1156 actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
1157
1158 cairo_line_to (cr,
1159 MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]),
1160 actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
1161
1162 if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM],
1163 border_width[ST_SIDE_LEFT]))
1164 elliptical_arc (cr,
1165 actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT],
1166 actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT],
1167 radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT],
1168 radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM],
1169 M_PI / 2, M_PI);
1170 else
1171 cairo_line_to (cr,
1172 actor_box.x1 + border_width[ST_SIDE_LEFT],
1173 actor_box.y2 - border_width[ST_SIDE_BOTTOM]);
1174
1175 cairo_close_path (cr);
1176
1177 interior_path = cairo_copy_path (cr);
1178
1179 /* clip drawing to the region inside of the borders
1180 */
1181 cairo_clip (cr);
1182
1183 /* But fill the pattern as if it started at the edge of outline,
1184 * behind the borders. This is similar to
1185 * background-clip: border-box; semantics.
1186 */
1187 cairo_append_path (cr, outline_path);
1188 }
1189
1190 if (interior_dirty && background_is_translucent)
1191 {
1192 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1193 cairo_fill_preserve (cr);
1194 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1195 }
1196
1197 if (draw_solid_background)
1198 {
1199 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1200
1201 cairo_set_source_rgba (cr,
1202 node->background_color.red / 255.,
1203 node->background_color.green / 255.,
1204 node->background_color.blue / 255.,
1205 node->background_color.alpha / 255.);
1206 cairo_fill_preserve (cr);
1207 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1208 }
1209
1210 if (draw_background_image_shadow)
1211 {
1212 paint_background_image_shadow_to_cairo_context (node,
1213 shadow_spec,
1214 pattern,
1215 cr,
1216 interior_path,
1217 has_visible_outline? outline_path : NULL,
1218 actor_box.x1,
1219 actor_box.y1,
1220 paint_box.x2 - paint_box.x1,
1221 paint_box.y2 - paint_box.y1);
1222 cairo_append_path (cr, outline_path);
1223 }
1224
1225 cairo_translate (cr, actor_box.x1, actor_box.y1);
1226
1227 if (pattern != NULL)
1228 {
1229 cairo_set_source (cr, pattern);
1230 cairo_fill (cr);
1231 cairo_pattern_destroy (pattern);
1232 }
1233
1234 if (box_shadow_spec && box_shadow_spec->inset)
1235 {
1236 paint_inset_box_shadow_to_cairo_context (node,
1237 box_shadow_spec,
1238 cr,
1239 interior_path ? interior_path
1240 : outline_path);
1241 }
1242
1243 if (outline_path != NULL)
1244 cairo_path_destroy (outline_path);
1245
1246 if (interior_path != NULL)
1247 cairo_path_destroy (interior_path);
1248
1249 texture = cogl_texture_new_from_data (paint_box.x2 - paint_box.x1,
1250 paint_box.y2 - paint_box.y1,
1251 COGL_TEXTURE_NONE,
1252 CLUTTER_CAIRO_FORMAT_ARGB32,
1253 COGL_PIXEL_FORMAT_ANY,
1254 rowstride,
1255 data);
1256
1257 cairo_destroy (cr);
1258 cairo_surface_destroy (surface);
1259 g_free (data);
1260
1261 return texture;
1262 }
1263
1264 void
1265 _st_theme_node_free_drawing_state (StThemeNode *node)
1266 {
1267 int corner_id;
1268
1269 if (node->background_texture != COGL_INVALID_HANDLE)
1270 cogl_handle_unref (node->background_texture);
1271 if (node->background_material != COGL_INVALID_HANDLE)
1272 cogl_handle_unref (node->background_material);
1273 if (node->background_shadow_material != COGL_INVALID_HANDLE)
1274 cogl_handle_unref (node->background_shadow_material);
1275 if (node->border_slices_texture != COGL_INVALID_HANDLE)
1276 cogl_handle_unref (node->border_slices_texture);
1277 if (node->border_slices_material != COGL_INVALID_HANDLE)
1278 cogl_handle_unref (node->border_slices_material);
1279 if (node->prerendered_texture != COGL_INVALID_HANDLE)
1280 cogl_handle_unref (node->prerendered_texture);
1281 if (node->prerendered_material != COGL_INVALID_HANDLE)
1282 cogl_handle_unref (node->prerendered_material);
1283 if (node->box_shadow_material != COGL_INVALID_HANDLE)
1284 cogl_handle_unref (node->box_shadow_material);
1285
1286 for (corner_id = 0; corner_id < 4; corner_id++)
1287 if (node->corner_material[corner_id] != COGL_INVALID_HANDLE)
1288 cogl_handle_unref (node->corner_material[corner_id]);
1289
1290 _st_theme_node_init_drawing_state (node);
1291 }
1292
1293 void
1294 _st_theme_node_init_drawing_state (StThemeNode *node)
1295 {
1296 int corner_id;
1297
1298 node->background_texture = COGL_INVALID_HANDLE;
1299 node->background_material = COGL_INVALID_HANDLE;
1300 node->background_shadow_material = COGL_INVALID_HANDLE;
1301 node->box_shadow_material = COGL_INVALID_HANDLE;
1302 node->border_slices_texture = COGL_INVALID_HANDLE;
1303 node->border_slices_material = COGL_INVALID_HANDLE;
1304 node->prerendered_texture = COGL_INVALID_HANDLE;
1305 node->prerendered_material = COGL_INVALID_HANDLE;
1306
1307 for (corner_id = 0; corner_id < 4; corner_id++)
1308 node->corner_material[corner_id] = COGL_INVALID_HANDLE;
1309 }
1310
1311 static void st_theme_node_paint_borders (StThemeNode *node,
1312 const ClutterActorBox *box,
1313 guint8 paint_opacity);
1314
1315 static void
1316 st_theme_node_render_resources (StThemeNode *node,
1317 float width,
1318 float height)
1319 {
1320 StTextureCache *texture_cache;
1321 StBorderImage *border_image;
1322 gboolean has_border;
1323 gboolean has_border_radius;
1324 gboolean has_inset_box_shadow;
1325 gboolean has_large_corners;
1326 StShadow *box_shadow_spec;
1327 StShadow *background_image_shadow_spec;
1328 const char *background_image;
1329
1330 g_return_if_fail (width > 0 && height > 0);
1331
1332 texture_cache = st_texture_cache_get_default ();
1333
1334 /* FIXME - need to separate this into things that need to be recomputed on
1335 * geometry change versus things that can be cached regardless, such as
1336 * a background image.
1337 */
1338 _st_theme_node_free_drawing_state (node);
1339
1340 node->alloc_width = width;
1341 node->alloc_height = height;
1342
1343 _st_theme_node_ensure_background (node);
1344 _st_theme_node_ensure_geometry (node);
1345
1346 box_shadow_spec = st_theme_node_get_box_shadow (node);
1347 has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
1348
1349 if (node->border_width[ST_SIDE_TOP] > 0 ||
1350 node->border_width[ST_SIDE_LEFT] > 0 ||
1351 node->border_width[ST_SIDE_RIGHT] > 0 ||
1352 node->border_width[ST_SIDE_BOTTOM] > 0)
1353 has_border = TRUE;
1354 else
1355 has_border = FALSE;
1356
1357 if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
1358 node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
1359 node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
1360 node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
1361 has_border_radius = TRUE;
1362 else
1363 has_border_radius = FALSE;
1364
1365 /* The cogl code pads each corner to the maximum border radius,
1366 * which results in overlapping corner areas if the radius
1367 * exceeds the actor's halfsize, causing rendering errors.
1368 * Fall back to cairo in these cases. */
1369 has_large_corners = FALSE;
1370
1371 if (has_border_radius) {
1372 guint border_radius[4];
1373 int corner;
1374
1375 st_theme_node_reduce_border_radius (node, border_radius);
1376
1377 for (corner = 0; corner < 4; corner ++) {
1378 if (border_radius[corner] * 2 > height ||
1379 border_radius[corner] * 2 > width) {
1380 has_large_corners = TRUE;
1381 break;
1382 }
1383 }
1384 }
1385
1386 /* Load referenced images from disk and draw anything we need with cairo now */
1387 background_image = st_theme_node_get_background_image (node);
1388 border_image = st_theme_node_get_border_image (node);
1389
1390 if (border_image)
1391 {
1392 const char *filename;
1393
1394 filename = st_border_image_get_filename (border_image);
1395
1396 node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename);
1397 }
1398
1399 if (node->border_slices_texture)
1400 node->border_slices_material = _st_create_texture_material (node->border_slices_texture);
1401 else
1402 node->border_slices_material = COGL_INVALID_HANDLE;
1403
1404 /* Use cairo to prerender the node if there is a gradient, or
1405 * background image with borders and/or rounded corners,
1406 * or large corners, since we can't do those things
1407 * easily with cogl.
1408 *
1409 * FIXME: if we could figure out ahead of time that a
1410 * background image won't overlap with the node borders,
1411 * then we could use cogl for that case.
1412 */
1413 if ((node->background_gradient_type != ST_GRADIENT_NONE)
1414 || (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
1415 || (background_image && (has_border || has_border_radius))
1416 || has_large_corners)
1417 node->prerendered_texture = st_theme_node_prerender_background (node);
1418
1419 if (node->prerendered_texture)
1420 node->prerendered_material = _st_create_texture_material (node->prerendered_texture);
1421 else
1422 node->prerendered_material = COGL_INVALID_HANDLE;
1423
1424 if (box_shadow_spec && !has_inset_box_shadow)
1425 {
1426 if (node->border_slices_texture != COGL_INVALID_HANDLE)
1427 node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
1428 node->border_slices_texture);
1429 else if (node->prerendered_texture != COGL_INVALID_HANDLE)
1430 node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
1431 node->prerendered_texture);
1432 else if (node->background_color.alpha > 0 || has_border)
1433 {
1434 CoglHandle buffer, offscreen;
1435 int texture_width = ceil (width);
1436 int texture_height = ceil (height);
1437
1438 buffer = cogl_texture_new_with_size (texture_width,
1439 texture_height,
1440 COGL_TEXTURE_NO_SLICING,
1441 COGL_PIXEL_FORMAT_ANY);
1442 offscreen = cogl_offscreen_new_to_texture (buffer);
1443
1444 if (offscreen != COGL_INVALID_HANDLE)
1445 {
1446 ClutterActorBox box = { 0, 0, width, height };
1447 CoglColor clear_color;
1448
1449 cogl_push_framebuffer (offscreen);
1450 cogl_ortho (0, width, height, 0, 0, 1.0);
1451
1452 cogl_color_set_from_4ub (&clear_color, 0, 0, 0, 0);
1453 cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
1454
1455 st_theme_node_paint_borders (node, &box, 0xFF);
1456 cogl_pop_framebuffer ();
1457 cogl_handle_unref (offscreen);
1458
1459 node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
1460 buffer);
1461 }
1462 cogl_handle_unref (buffer);
1463 }
1464 }
1465
1466 background_image_shadow_spec = st_theme_node_get_background_image_shadow (node);
1467 if (background_image != NULL && !has_border && !has_border_radius)
1468 {
1469 node->background_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image);
1470 node->background_material = _st_create_texture_material (node->background_texture);
1471
1472 if (node->background_repeat)
1473 cogl_material_set_layer_wrap_mode (node->background_material, 0, COGL_MATERIAL_WRAP_MODE_REPEAT);
1474
1475 if (background_image_shadow_spec)
1476 {
1477 node->background_shadow_material = _st_create_shadow_material (background_image_shadow_spec,
1478 node->background_texture);
1479 }
1480 }
1481
1482 node->corner_material[ST_CORNER_TOPLEFT] =
1483 st_theme_node_lookup_corner (node, ST_CORNER_TOPLEFT);
1484 node->corner_material[ST_CORNER_TOPRIGHT] =
1485 st_theme_node_lookup_corner (node, ST_CORNER_TOPRIGHT);
1486 node->corner_material[ST_CORNER_BOTTOMRIGHT] =
1487 st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMRIGHT);
1488 node->corner_material[ST_CORNER_BOTTOMLEFT] =
1489 st_theme_node_lookup_corner (node, ST_CORNER_BOTTOMLEFT);
1490 }
1491
1492 static void
1493 paint_material_with_opacity (CoglHandle material,
1494 ClutterActorBox *box,
1495 ClutterActorBox *coords,
1496 guint8 paint_opacity)
1497 {
1498 cogl_material_set_color4ub (material,
1499 paint_opacity, paint_opacity, paint_opacity, paint_opacity);
1500
1501 cogl_set_source (material);
1502
1503 if (coords)
1504 cogl_rectangle_with_texture_coords (box->x1, box->y1, box->x2, box->y2,
1505 coords->x1, coords->y1, coords->x2, coords->y2);
1506 else
1507 cogl_rectangle (box->x1, box->y1, box->x2, box->y2);
1508 }
1509
1510 static void
1511 st_theme_node_paint_borders (StThemeNode *node,
1512 const ClutterActorBox *box,
1513 guint8 paint_opacity)
1514 {
1515 float width, height;
1516 int border_width[4];
1517 guint border_radius[4];
1518 int max_border_radius = 0;
1519 int max_width_radius[4];
1520 int corner_id, side_id;
1521 ClutterColor border_color;
1522 guint8 alpha;
1523
1524 width = box->x2 - box->x1;
1525 height = box->y2 - box->y1;
1526
1527 /* TODO - support non-uniform border colors */
1528 get_arbitrary_border_color (node, &border_color);
1529
1530 for (side_id = 0; side_id < 4; side_id++)
1531 border_width[side_id] = st_theme_node_get_border_width(node, side_id);
1532
1533 st_theme_node_reduce_border_radius (node, border_radius);
1534
1535 for (corner_id = 0; corner_id < 4; corner_id++)
1536 {
1537 guint border_width_1, border_width_2;
1538
1539 st_theme_node_get_corner_border_widths (node, corner_id,
1540 &border_width_1, &border_width_2);
1541
1542 if (border_radius[corner_id] > max_border_radius)
1543 max_border_radius = border_radius[corner_id];
1544 max_width_radius[corner_id] = MAX(MAX(border_width_1, border_width_2),
1545 border_radius[corner_id]);
1546 }
1547
1548 /* borders */
1549 if (border_width[ST_SIDE_TOP] > 0 ||
1550 border_width[ST_SIDE_RIGHT] > 0 ||
1551 border_width[ST_SIDE_BOTTOM] > 0 ||
1552 border_width[ST_SIDE_LEFT] > 0)
1553 {
1554 ClutterColor effective_border;
1555 gboolean skip_corner_1, skip_corner_2;
1556 float x1, y1, x2, y2;
1557
1558 over (&border_color, &node->background_color, &effective_border);
1559 alpha = paint_opacity * effective_border.alpha / 255;
1560
1561 if (alpha > 0)
1562 {
1563 cogl_set_source_color4ub (effective_border.red,
1564 effective_border.green,
1565 effective_border.blue,
1566 alpha);
1567
1568 /* NORTH */
1569 skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
1570 skip_corner_2 = border_radius[ST_CORNER_TOPRIGHT] > 0;
1571
1572 x1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT] : 0;
1573 y1 = 0;
1574 x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_TOPRIGHT] : width;
1575 y2 = border_width[ST_SIDE_TOP];
1576 cogl_rectangle (x1, y1, x2, y2);
1577
1578 /* EAST */
1579 skip_corner_1 = border_radius[ST_CORNER_TOPRIGHT] > 0;
1580 skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
1581
1582 x1 = width - border_width[ST_SIDE_RIGHT];
1583 y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPRIGHT]
1584 : border_width[ST_SIDE_TOP];
1585 x2 = width;
1586 y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMRIGHT]
1587 : height - border_width[ST_SIDE_BOTTOM];
1588 cogl_rectangle (x1, y1, x2, y2);
1589
1590 /* SOUTH */
1591 skip_corner_1 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
1592 skip_corner_2 = border_radius[ST_CORNER_BOTTOMRIGHT] > 0;
1593
1594 x1 = skip_corner_1 ? max_width_radius[ST_CORNER_BOTTOMLEFT] : 0;
1595 y1 = height - border_width[ST_SIDE_BOTTOM];
1596 x2 = skip_corner_2 ? width - max_width_radius[ST_CORNER_BOTTOMRIGHT]
1597 : width;
1598 y2 = height;
1599 cogl_rectangle (x1, y1, x2, y2);
1600
1601 /* WEST */
1602 skip_corner_1 = border_radius[ST_CORNER_TOPLEFT] > 0;
1603 skip_corner_2 = border_radius[ST_CORNER_BOTTOMLEFT] > 0;
1604
1605 x1 = 0;
1606 y1 = skip_corner_1 ? max_width_radius[ST_CORNER_TOPLEFT]
1607 : border_width[ST_SIDE_TOP];
1608 x2 = border_width[ST_SIDE_LEFT];
1609 y2 = skip_corner_2 ? height - max_width_radius[ST_CORNER_BOTTOMLEFT]
1610 : height - border_width[ST_SIDE_BOTTOM];
1611 cogl_rectangle (x1, y1, x2, y2);
1612 }
1613 }
1614
1615 /* corners */
1616 if (max_border_radius > 0 && paint_opacity > 0)
1617 {
1618 for (corner_id = 0; corner_id < 4; corner_id++)
1619 {
1620 if (node->corner_material[corner_id] == COGL_INVALID_HANDLE)
1621 continue;
1622
1623 cogl_material_set_color4ub (node->corner_material[corner_id],
1624 paint_opacity, paint_opacity,
1625 paint_opacity, paint_opacity);
1626 cogl_set_source (node->corner_material[corner_id]);
1627
1628 switch (corner_id)
1629 {
1630 case ST_CORNER_TOPLEFT:
1631 cogl_rectangle_with_texture_coords (0, 0,
1632 max_width_radius[ST_CORNER_TOPLEFT], max_width_radius[ST_CORNER_TOPLEFT],
1633 0, 0, 0.5, 0.5);
1634 break;
1635 case ST_CORNER_TOPRIGHT:
1636 cogl_rectangle_with_texture_coords (width - max_width_radius[ST_CORNER_TOPRIGHT], 0,
1637 width, max_width_radius[ST_CORNER_TOPRIGHT],
1638 0.5, 0, 1, 0.5);
1639 break;
1640 case ST_CORNER_BOTTOMRIGHT:
1641 cogl_rectangle_with_texture_coords (width - max_width_radius[ST_CORNER_BOTTOMRIGHT], height - max_width_radius[ST_CORNER_BOTTOMRIGHT],
1642 width, height,
1643 0.5, 0.5, 1, 1);
1644 break;
1645 case ST_CORNER_BOTTOMLEFT:
1646 cogl_rectangle_with_texture_coords (0, height - max_width_radius[ST_CORNER_BOTTOMLEFT],
1647 max_width_radius[ST_CORNER_BOTTOMLEFT], height,
1648 0, 0.5, 0.5, 1);
1649 break;
1650 }
1651 }
1652 }
1653
1654 /* background color */
1655 alpha = paint_opacity * node->background_color.alpha / 255;
1656 if (alpha > 0)
1657 {
1658 cogl_set_source_color4ub (node->background_color.red,
1659 node->background_color.green,
1660 node->background_color.blue,
1661 alpha);
1662
1663 /* We add padding to each corner, so that all corners end up as if they
1664 * had a border-radius of max_border_radius, which allows us to treat
1665 * corners as uniform further on.
1666 */
1667 for (corner_id = 0; corner_id < 4; corner_id++)
1668 {
1669 float verts[8];
1670 int n_rects;
1671
1672 /* corner texture does not need padding */
1673 if (max_border_radius == border_radius[corner_id])
1674 continue;
1675
1676 n_rects = border_radius[corner_id] == 0 ? 1 : 2;
1677
1678 switch (corner_id)
1679 {
1680 case ST_CORNER_TOPLEFT:
1681 verts[0] = border_width[ST_SIDE_LEFT];
1682 verts[1] = MAX(border_radius[corner_id],
1683 border_width[ST_SIDE_TOP]);
1684 verts[2] = max_border_radius;
1685 verts[3] = max_border_radius;
1686 if (n_rects == 2)
1687 {
1688 verts[4] = MAX(border_radius[corner_id],
1689 border_width[ST_SIDE_LEFT]);
1690 verts[5] = border_width[ST_SIDE_TOP];
1691 verts[6] = max_border_radius;
1692 verts[7] = MAX(border_radius[corner_id],
1693 border_width[ST_SIDE_TOP]);
1694 }
1695 break;
1696 case ST_CORNER_TOPRIGHT:
1697 verts[0] = width - max_border_radius;
1698 verts[1] = MAX(border_radius[corner_id],
1699 border_width[ST_SIDE_TOP]);
1700 verts[2] = width - border_width[ST_SIDE_RIGHT];
1701 verts[3] = max_border_radius;
1702 if (n_rects == 2)
1703 {
1704 verts[4] = width - max_border_radius;
1705 verts[5] = border_width[ST_SIDE_TOP];
1706 verts[6] = width - MAX(border_radius[corner_id],
1707 border_width[ST_SIDE_RIGHT]);
1708 verts[7] = MAX(border_radius[corner_id],
1709 border_width[ST_SIDE_TOP]);
1710 }
1711 break;
1712 case ST_CORNER_BOTTOMRIGHT:
1713 verts[0] = width - max_border_radius;
1714 verts[1] = height - max_border_radius;
1715 verts[2] = width - border_width[ST_SIDE_RIGHT];
1716 verts[3] = height - MAX(border_radius[corner_id],
1717 border_width[ST_SIDE_BOTTOM]);
1718 if (n_rects == 2)
1719 {
1720 verts[4] = width - max_border_radius;
1721 verts[5] = height - MAX(border_radius[corner_id],
1722 border_width[ST_SIDE_BOTTOM]);
1723 verts[6] = width - MAX(border_radius[corner_id],
1724 border_width[ST_SIDE_RIGHT]);
1725 verts[7] = height - border_width[ST_SIDE_BOTTOM];
1726 }
1727 break;
1728 case ST_CORNER_BOTTOMLEFT:
1729 verts[0] = border_width[ST_SIDE_LEFT];
1730 verts[1] = height - max_border_radius;
1731 verts[2] = max_border_radius;
1732 verts[3] = height - MAX(border_radius[corner_id],
1733 border_width[ST_SIDE_BOTTOM]);
1734 if (n_rects == 2)
1735 {
1736 verts[4] = MAX(border_radius[corner_id],
1737 border_width[ST_SIDE_LEFT]);
1738 verts[5] = height - MAX(border_radius[corner_id],
1739 border_width[ST_SIDE_BOTTOM]);
1740 verts[6] = max_border_radius;
1741 verts[7] = height - border_width[ST_SIDE_BOTTOM];
1742 }
1743 break;
1744 }
1745 cogl_rectangles (verts, n_rects);
1746 }
1747
1748 /* Once we've drawn the borders and corners, if the corners are bigger
1749 * then the border width, the remaining area is shaped like
1750 *
1751 * ########
1752 * ##########
1753 * ##########
1754 * ########
1755 *
1756 * We draw it in at most 3 pieces - first the top and bottom if
1757 * necessary, then the main rectangle
1758 */
1759 if (max_border_radius > border_width[ST_SIDE_TOP])
1760 cogl_rectangle (MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
1761 border_width[ST_SIDE_TOP],
1762 width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
1763 max_border_radius);
1764 if (max_border_radius > border_width[ST_SIDE_BOTTOM])
1765 cogl_rectangle (MAX(max_border_radius, border_width[ST_SIDE_LEFT]),
1766 height - max_border_radius,
1767 width - MAX(max_border_radius, border_width[ST_SIDE_RIGHT]),
1768 height - border_width[ST_SIDE_BOTTOM]);
1769
1770 cogl_rectangle (border_width[ST_SIDE_LEFT],
1771 MAX(border_width[ST_SIDE_TOP], max_border_radius),
1772 width - border_width[ST_SIDE_RIGHT],
1773 height - MAX(border_width[ST_SIDE_BOTTOM], max_border_radius));
1774 }
1775 }
1776
1777 static void
1778 st_theme_node_paint_sliced_border_image (StThemeNode *node,
1779 const ClutterActorBox *box,
1780 guint8 paint_opacity)
1781 {
1782 gfloat ex, ey;
1783 gfloat tx1, ty1, tx2, ty2;
1784 gint border_left, border_right, border_top, border_bottom;
1785 float img_width, img_height;
1786 StBorderImage *border_image;
1787 CoglHandle material;
1788
1789 border_image = st_theme_node_get_border_image (node);
1790 g_assert (border_image != NULL);
1791
1792 st_border_image_get_borders (border_image,
1793 &border_left, &border_right, &border_top, &border_bottom);
1794
1795 img_width = cogl_texture_get_width (node->border_slices_texture);
1796 img_height = cogl_texture_get_height (node->border_slices_texture);
1797
1798 tx1 = border_left / img_width;
1799 tx2 = (img_width - border_right) / img_width;
1800 ty1 = border_top / img_height;
1801 ty2 = (img_height - border_bottom) / img_height;
1802
1803 ex = node->alloc_width - border_right;
1804 if (ex < 0)
1805 ex = border_right; /* FIXME ? */
1806
1807 ey = node->alloc_height - border_bottom;
1808 if (ey < 0)
1809 ey = border_bottom; /* FIXME ? */
1810
1811 material = node->border_slices_material;
1812 cogl_material_set_color4ub (material,
1813 paint_opacity, paint_opacity, paint_opacity, paint_opacity);
1814
1815 cogl_set_source (material);
1816
1817 {
1818 float rectangles[] =
1819 {
1820 /* top left corner */
1821 0, 0, border_left, border_top,
1822 0.0, 0.0,
1823 tx1, ty1,
1824
1825 /* top middle */
1826 border_left, 0, ex, border_top,
1827 tx1, 0.0,
1828 tx2, ty1,
1829
1830 /* top right */
1831 ex, 0, node->alloc_width, border_top,
1832 tx2, 0.0,
1833 1.0, ty1,
1834
1835 /* mid left */
1836 0, border_top, border_left, ey,
1837 0.0, ty1,
1838 tx1, ty2,
1839
1840 /* center */
1841 border_left, border_top, ex, ey,
1842 tx1, ty1,
1843 tx2, ty2,
1844
1845 /* mid right */
1846 ex, border_top, node->alloc_width, ey,
1847 tx2, ty1,
1848 1.0, ty2,
1849
1850 /* bottom left */
1851 0, ey, border_left, node->alloc_height,
1852 0.0, ty2,
1853 tx1, 1.0,
1854
1855 /* bottom center */
1856 border_left, ey, ex, node->alloc_height,
1857 tx1, ty2,
1858 tx2, 1.0,
1859
1860 /* bottom right */
1861 ex, ey, node->alloc_width, node->alloc_height,
1862 tx2, ty2,
1863 1.0, 1.0
1864 };
1865
1866 cogl_rectangles_with_texture_coords (rectangles, 9);
1867 }
1868 }
1869
1870 static void
1871 st_theme_node_paint_outline (StThemeNode *node,
1872 const ClutterActorBox *box,
1873 guint8 paint_opacity)
1874
1875 {
1876 float width, height;
1877 int outline_width;
1878 ClutterColor outline_color, effective_outline;
1879
1880 width = box->x2 - box->x1;
1881 height = box->y2 - box->y1;
1882
1883 outline_width = st_theme_node_get_outline_width (node);
1884 if (outline_width == 0)
1885 return;
1886
1887 st_theme_node_get_outline_color (node, &outline_color);
1888 over (&outline_color, &node->background_color, &effective_outline);
1889
1890 cogl_set_source_color4ub (effective_outline.red,
1891 effective_outline.green,
1892 effective_outline.blue,
1893 paint_opacity * effective_outline.alpha / 255);
1894
1895 /* The outline is drawn just outside the border, which means just
1896 * outside the allocation box. This means that in some situations
1897 * involving clip_to_allocation or the screen edges, you won't be
1898 * able to see the outline. In practice, it works well enough.
1899 */
1900
1901 /* NORTH */
1902 cogl_rectangle (-outline_width, -outline_width,
1903 width + outline_width, 0);
1904
1905 /* EAST */
1906 cogl_rectangle (width, 0,
1907 width + outline_width, height);
1908
1909 /* SOUTH */
1910 cogl_rectangle (-outline_width, height,
1911 width + outline_width, height + outline_width);
1912
1913 /* WEST */
1914 cogl_rectangle (-outline_width, 0,
1915 0, height);
1916 }
1917
1918 void
1919 st_theme_node_paint (StThemeNode *node,
1920 const ClutterActorBox *box,
1921 guint8 paint_opacity)
1922 {
1923 float width, height;
1924 ClutterActorBox allocation;
1925
1926 /* Some things take an ActorBox, some things just width/height */
1927 width = box->x2 - box->x1;
1928 height = box->y2 - box->y1;
1929 allocation.x1 = allocation.y1 = 0;
1930 allocation.x2 = width;
1931 allocation.y2 = height;
1932
1933 if (width <= 0 || height <= 0)
1934 return;
1935
1936 if (node->alloc_width != width || node->alloc_height != height)
1937 st_theme_node_render_resources (node, width, height);
1938
1939 /* Rough notes about the relationship of borders and backgrounds in CSS3;
1940 * see http://www.w3.org/TR/css3-background/ for more accurate details.
1941 *
1942 * - Things are drawn in 4 layers, from the bottom:
1943 * Background color
1944 * Background image
1945 * Border color or border image
1946 * Content
1947 * - The background color, gradient and image extend to and are clipped by
1948 * the edge of the border area, so will be rounded if the border is
1949 * rounded. (CSS3 background-clip property modifies this)
1950 * - The border image replaces what would normally be drawn by the border
1951 * - The border image is not clipped by a rounded border-radius
1952 * - The border radius rounds the background even if the border is
1953 * zero width or a border image is being used.
1954 *
1955 * Deviations from the above as implemented here:
1956 * - The combination of border image and a non-zero border radius is
1957 * not supported; the background color will be drawn with square
1958 * corners.
1959 * - The background image is drawn above the border color, not below it.
1960 * - We clip the background image to the inside edges of the border
1961 * instead of the outside edges of the border (but position the image
1962 * such that it's aligned to the outside edges)
1963 */
1964
1965 if (node->box_shadow_material)
1966 _st_paint_shadow_with_opacity (node->box_shadow,
1967 node->box_shadow_material,
1968 &allocation,
1969 paint_opacity);
1970
1971 if (node->prerendered_material != COGL_INVALID_HANDLE ||
1972 node->border_slices_material != COGL_INVALID_HANDLE)
1973 {
1974 if (node->prerendered_material != COGL_INVALID_HANDLE)
1975 {
1976 ClutterActorBox paint_box;
1977
1978 st_theme_node_get_background_paint_box (node,
1979 &allocation,
1980 &paint_box);
1981
1982 paint_material_with_opacity (node->prerendered_material,
1983 &paint_box,
1984 NULL,
1985 paint_opacity);
1986 }
1987
1988 if (node->border_slices_material != COGL_INVALID_HANDLE)
1989 st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity);
1990 }
1991 else
1992 {
1993 st_theme_node_paint_borders (node, box, paint_opacity);
1994 }
1995
1996 st_theme_node_paint_outline (node, box, paint_opacity);
1997
1998 if (node->background_texture != COGL_INVALID_HANDLE)
1999 {
2000 ClutterActorBox background_box;
2001 ClutterActorBox texture_coords;
2002 gboolean has_visible_outline;
2003
2004 /* If the node doesn't have an opaque or repeating background or
2005 * a border then we let its background image shadows leak out,
2006 * but otherwise we clip it.
2007 */
2008 has_visible_outline = st_theme_node_has_visible_outline (node);
2009
2010 get_background_position (node, &allocation, &background_box, &texture_coords);
2011
2012 if (has_visible_outline || node->background_repeat)
2013 cogl_clip_push_rectangle (allocation.x1, allocation.y1, allocation.x2, allocation.y2);
2014
2015 /* CSS based drop shadows
2016 *
2017 * Drop shadows in ST are modelled after the CSS3 box-shadow property;
2018 * see http://www.css3.info/preview/box-shadow/ for a detailed description.
2019 *
2020 * While the syntax of the property is mostly identical - we do not support
2021 * multiple shadows and allow for a more liberal placement of the color
2022 * parameter - its interpretation defers significantly in that the shadow's
2023 * shape is not determined by the bounding box, but by the CSS background
2024 * image. The drop shadows are allowed to escape the nodes allocation if
2025 * there is nothing (like a border, or the edge of the background color)
2026 * to logically confine it.
2027 */
2028 if (node->background_shadow_material != COGL_INVALID_HANDLE)
2029 _st_paint_shadow_with_opacity (node->background_image_shadow,
2030 node->background_shadow_material,
2031 &background_box,
2032 paint_opacity);
2033
2034 paint_material_with_opacity (node->background_material,
2035 &background_box,
2036 &texture_coords,
2037 paint_opacity);
2038
2039 if (has_visible_outline || node->background_repeat)
2040 cogl_clip_pop ();
2041 }
2042 }
2043
2044 /**
2045 * st_theme_node_copy_cached_paint_state:
2046 * @node: a #StThemeNode
2047 * @other: a different #StThemeNode
2048 *
2049 * Copy cached painting state from @other to @node. This function can be used to
2050 * optimize redrawing cached background images when the style on an element changess
2051 * in a way that doesn't affect background drawing. This function must only be called
2052 * if st_theme_node_paint_equal (node, other) returns %TRUE.
2053 */
2054 void
2055 st_theme_node_copy_cached_paint_state (StThemeNode *node,
2056 StThemeNode *other)
2057 {
2058 int corner_id;
2059
2060 g_return_if_fail (ST_IS_THEME_NODE (node));
2061 g_return_if_fail (ST_IS_THEME_NODE (other));
2062
2063 /* Check omitted for speed: */
2064 /* g_return_if_fail (st_theme_node_paint_equal (node, other)); */
2065
2066 _st_theme_node_free_drawing_state (node);
2067
2068 node->alloc_width = other->alloc_width;
2069 node->alloc_height = other->alloc_height;
2070
2071 if (other->background_shadow_material)
2072 node->background_shadow_material = cogl_handle_ref (other->background_shadow_material);
2073 if (other->box_shadow_material)
2074 node->box_shadow_material = cogl_handle_ref (other->box_shadow_material);
2075 if (other->background_texture)
2076 node->background_texture = cogl_handle_ref (other->background_texture);
2077 if (other->background_material)
2078 node->background_material = cogl_handle_ref (other->background_material);
2079 if (other->border_slices_texture)
2080 node->border_slices_texture = cogl_handle_ref (other->border_slices_texture);
2081 if (other->border_slices_material)
2082 node->border_slices_material = cogl_handle_ref (other->border_slices_material);
2083 if (other->prerendered_texture)
2084 node->prerendered_texture = cogl_handle_ref (other->prerendered_texture);
2085 if (other->prerendered_material)
2086 node->prerendered_material = cogl_handle_ref (other->prerendered_material);
2087 for (corner_id = 0; corner_id < 4; corner_id++)
2088 if (other->corner_material[corner_id])
2089 node->corner_material[corner_id] = cogl_handle_ref (other->corner_material[corner_id]);
2090 }
2091
2092 void
2093 st_theme_node_invalidate_paint_state (StThemeNode *node)
2094 {
2095 node->alloc_width = 0;
2096 node->alloc_height = 0;
2097 }