]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshadowvalue.c
shadow: add blur to icon-shadow (icon)
[~andy/gtk] / gtk / gtkcssshadowvalue.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Red Hat, Inc.
3  *
4  * Author: Cosimo Cecchi <cosimoc@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssshadowvalueprivate.h"
23
24 #include "gtkcairoblurprivate.h"
25 #include "gtkcssnumbervalueprivate.h"
26 #include "gtkcssrgbavalueprivate.h"
27 #include "gtkstylecontextprivate.h"
28 #include "gtksymboliccolorprivate.h"
29 #include "gtkthemingengineprivate.h"
30 #include "gtkpango.h"
31
32 struct _GtkCssValue {
33   GTK_CSS_VALUE_BASE
34   guint inset :1;
35
36   GtkCssValue *hoffset;
37   GtkCssValue *voffset;
38   GtkCssValue *radius;
39   GtkCssValue *spread;
40
41   GtkCssValue *color;
42 };
43
44 static GtkCssValue *    gtk_css_shadow_value_new (GtkCssValue *hoffset,
45                                                   GtkCssValue *voffset,
46                                                   GtkCssValue *radius,
47                                                   GtkCssValue *spread,
48                                                   gboolean     inset,
49                                                   GtkCssValue *color);
50
51 static void
52 gtk_css_value_shadow_free (GtkCssValue *shadow)
53 {
54   _gtk_css_value_unref (shadow->hoffset);
55   _gtk_css_value_unref (shadow->voffset);
56   _gtk_css_value_unref (shadow->radius);
57   _gtk_css_value_unref (shadow->spread);
58   _gtk_css_value_unref (shadow->color);
59
60   g_slice_free (GtkCssValue, shadow);
61 }
62
63 static GtkCssValue *
64 gtk_css_value_shadow_compute (GtkCssValue        *shadow,
65                               guint               property_id,
66                               GtkStyleContext    *context,
67                               GtkCssDependencies *dependencies)
68 {
69   GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
70   GtkCssDependencies child_deps;
71
72   child_deps = 0;
73   hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, context, &child_deps);
74   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
75
76   child_deps = 0;
77   voffset = _gtk_css_value_compute (shadow->voffset, property_id, context, &child_deps);
78   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
79
80   child_deps = 0;
81   radius = _gtk_css_value_compute (shadow->radius, property_id, context, &child_deps);
82   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
83
84   child_deps = 0;
85   spread = _gtk_css_value_compute (shadow->spread, property_id, context, &child_deps),
86   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
87
88   child_deps = 0;
89   color = _gtk_css_value_compute (shadow->color, property_id, context, &child_deps);
90   *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
91
92   return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
93 }
94
95 static gboolean
96 gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
97                             const GtkCssValue *shadow2)
98 {
99   return shadow1->inset == shadow2->inset
100       && _gtk_css_value_equal (shadow1->hoffset, shadow2->hoffset)
101       && _gtk_css_value_equal (shadow1->voffset, shadow2->voffset)
102       && _gtk_css_value_equal (shadow1->radius, shadow2->radius)
103       && _gtk_css_value_equal (shadow1->spread, shadow2->spread)
104       && _gtk_css_value_equal (shadow1->color, shadow2->color);
105 }
106
107 static GtkCssValue *
108 gtk_css_value_shadow_transition (GtkCssValue *start,
109                                  GtkCssValue *end,
110                                  guint        property_id,
111                                  double       progress)
112 {
113   if (start->inset != end->inset)
114     return NULL;
115
116   return gtk_css_shadow_value_new (_gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress),
117                                    _gtk_css_value_transition (start->voffset, end->voffset, property_id, progress),
118                                    _gtk_css_value_transition (start->radius, end->radius, property_id, progress),
119                                    _gtk_css_value_transition (start->spread, end->spread, property_id, progress),
120                                    start->inset,
121                                    _gtk_css_value_transition (start->color, end->color, property_id, progress));
122 }
123
124 static void
125 gtk_css_value_shadow_print (const GtkCssValue *shadow,
126                             GString           *string)
127 {
128   _gtk_css_value_print (shadow->hoffset, string);
129   g_string_append_c (string, ' ');
130   _gtk_css_value_print (shadow->voffset, string);
131   g_string_append_c (string, ' ');
132   if (_gtk_css_number_value_get (shadow->radius, 100) != 0 ||
133       _gtk_css_number_value_get (shadow->spread, 100) != 0)
134     {
135       _gtk_css_value_print (shadow->radius, string);
136       g_string_append_c (string, ' ');
137     }
138
139   if (_gtk_css_number_value_get (shadow->spread, 100) != 0)
140     {
141       _gtk_css_value_print (shadow->spread, string);
142       g_string_append_c (string, ' ');
143     }
144
145   _gtk_css_value_print (shadow->color, string);
146
147   if (shadow->inset)
148     g_string_append (string, " inset");
149
150 }
151
152 static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = {
153   gtk_css_value_shadow_free,
154   gtk_css_value_shadow_compute,
155   gtk_css_value_shadow_equal,
156   gtk_css_value_shadow_transition,
157   gtk_css_value_shadow_print
158 };
159
160 static GtkCssValue *
161 gtk_css_shadow_value_new (GtkCssValue *hoffset,
162                           GtkCssValue *voffset,
163                           GtkCssValue *radius,
164                           GtkCssValue *spread,
165                           gboolean     inset,
166                           GtkCssValue *color)
167 {
168   GtkCssValue *retval;
169
170   retval = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_SHADOW);
171
172   retval->hoffset = hoffset;
173   retval->voffset = voffset;
174   retval->radius = radius;
175   retval->spread = spread;
176   retval->inset = inset;
177   retval->color = color;
178
179   return retval;
180 }                  
181
182 GtkCssValue *
183 _gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
184 {
185   GdkRGBA transparent = { 0, 0, 0, 0 };
186
187   g_return_val_if_fail (target->class == &GTK_CSS_VALUE_SHADOW, NULL);
188
189   return gtk_css_shadow_value_new (_gtk_css_number_value_new (0, GTK_CSS_PX),
190                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
191                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
192                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
193                                    target->inset,
194                                    _gtk_css_rgba_value_new_from_rgba (&transparent));
195 }
196
197 static gboolean
198 value_is_done_parsing (GtkCssParser *parser)
199 {
200   return _gtk_css_parser_is_eof (parser) ||
201          _gtk_css_parser_begins_with (parser, ',') ||
202          _gtk_css_parser_begins_with (parser, ';') ||
203          _gtk_css_parser_begins_with (parser, '}');
204 }
205
206 GtkCssValue *
207 _gtk_css_shadow_value_parse (GtkCssParser *parser)
208 {
209   enum {
210     HOFFSET,
211     VOFFSET,
212     RADIUS,
213     SPREAD,
214     COLOR,
215     N_VALUES
216   };
217   GtkCssValue *values[N_VALUES] = { NULL, };
218   gboolean inset;
219   guint i;
220
221   inset = _gtk_css_parser_try (parser, "inset", TRUE);
222
223   do
224   {
225     if (values[HOFFSET] == NULL &&
226          _gtk_css_parser_has_number (parser))
227       {
228         values[HOFFSET] = _gtk_css_number_value_parse (parser,
229                                                        GTK_CSS_PARSE_LENGTH
230                                                        | GTK_CSS_NUMBER_AS_PIXELS);
231         if (values[HOFFSET] == NULL)
232           goto fail;
233
234         values[VOFFSET] = _gtk_css_number_value_parse (parser,
235                                                        GTK_CSS_PARSE_LENGTH
236                                                        | GTK_CSS_NUMBER_AS_PIXELS);
237         if (values[VOFFSET] == NULL)
238           goto fail;
239
240         if (_gtk_css_parser_has_number (parser))
241           {
242             values[RADIUS] = _gtk_css_number_value_parse (parser,
243                                                           GTK_CSS_PARSE_LENGTH
244                                                           | GTK_CSS_POSITIVE_ONLY
245                                                           | GTK_CSS_NUMBER_AS_PIXELS);
246             if (values[RADIUS] == NULL)
247               goto fail;
248           }
249         else
250           values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
251                                                         
252         if (_gtk_css_parser_has_number (parser))
253           {
254             values[SPREAD] = _gtk_css_number_value_parse (parser,
255                                                           GTK_CSS_PARSE_LENGTH
256                                                           | GTK_CSS_NUMBER_AS_PIXELS);
257             if (values[SPREAD] == NULL)
258               goto fail;
259           }
260         else
261           values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
262       }
263     else if (!inset && _gtk_css_parser_try (parser, "inset", TRUE))
264       {
265         if (values[HOFFSET] == NULL)
266           goto fail;
267         inset = TRUE;
268         break;
269       }
270     else if (values[COLOR] == NULL)
271       {
272         values[COLOR] = _gtk_css_symbolic_value_new (parser);
273
274         if (values[COLOR] == NULL)
275           goto fail;
276       }
277     else
278       {
279         /* We parsed everything and there's still stuff left?
280          * Pretend we didn't notice and let the normal code produce
281          * a 'junk at end of value' error */
282         goto fail;
283       }
284   }
285   while (values[HOFFSET] == NULL || !value_is_done_parsing (parser));
286
287   if (values[COLOR] == NULL)
288     values[COLOR] = _gtk_css_symbolic_value_new_take_symbolic_color (
289                       gtk_symbolic_color_ref (
290                         _gtk_symbolic_color_get_current_color ()));
291
292   return gtk_css_shadow_value_new (values[HOFFSET], values[VOFFSET],
293                                    values[RADIUS], values[SPREAD],
294                                    inset, values[COLOR]);
295
296 fail:
297   for (i = 0; i < N_VALUES; i++)
298     {
299       if (values[i])
300         _gtk_css_value_unref (values[i]);
301     }
302
303   return NULL;
304 }
305
306 static const cairo_user_data_key_t shadow_key;
307
308 static cairo_t *
309 gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
310                                     cairo_t           *cr)
311 {
312   cairo_rectangle_int_t clip_rect;
313   cairo_surface_t *surface;
314   cairo_t *blur_cr;
315   gdouble radius;
316
317   radius = _gtk_css_number_value_get (shadow->radius, 0);
318   if (radius == 0.0)
319     return cr;
320
321   gdk_cairo_get_clip_rectangle (cr, &clip_rect);
322
323   /* Create a larger surface to center the blur. */
324   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
325                                         clip_rect.width + 2 * radius,
326                                         clip_rect.height + 2 * radius);
327   cairo_surface_set_device_offset (surface, radius - clip_rect.x, radius - clip_rect.y);
328   blur_cr = cairo_create (surface);
329   cairo_set_user_data (blur_cr, &shadow_key, cairo_reference (cr), (cairo_destroy_func_t) cairo_destroy);
330
331   if (cairo_has_current_point (cr))
332     {
333       double x, y;
334       
335       cairo_get_current_point (cr, &x, &y);
336       cairo_move_to (blur_cr, x, y);
337     }
338
339   return blur_cr;
340 }
341
342 static cairo_t *
343 gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
344                                      cairo_t           *cr)
345 {
346   gdouble radius;
347   cairo_t *original_cr;
348   cairo_surface_t *surface;
349
350   radius = _gtk_css_number_value_get (shadow->radius, 0);
351   if (radius == 0.0)
352     return cr;
353
354   surface = cairo_get_target (cr);
355   original_cr = cairo_get_user_data (cr, &shadow_key);
356
357   /* Blur the surface. */
358   _gtk_cairo_blur_surface (surface, radius);
359
360   cairo_set_source_surface (original_cr, surface, 0, 0);
361   cairo_paint (original_cr);
362
363   cairo_destroy (cr);
364
365   return original_cr;
366 }
367
368 void
369 _gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
370                                     cairo_t           *cr,
371                                     PangoLayout       *layout)
372 {
373   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
374
375   if (!cairo_has_current_point (cr))
376     cairo_move_to (cr, 0, 0);
377
378   cairo_save (cr);
379
380   cairo_rel_move_to (cr, 
381                      _gtk_css_number_value_get (shadow->hoffset, 0),
382                      _gtk_css_number_value_get (shadow->voffset, 0));
383
384   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
385
386   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
387   _gtk_pango_fill_layout (cr, layout);
388
389   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
390
391   cairo_rel_move_to (cr,
392                      - _gtk_css_number_value_get (shadow->hoffset, 0),
393                      - _gtk_css_number_value_get (shadow->voffset, 0));
394   cairo_restore (cr);
395 }
396
397 void
398 _gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
399                                   cairo_t           *cr)
400 {
401   cairo_pattern_t *pattern;
402
403   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
404
405   cairo_save (cr);
406   pattern = cairo_pattern_reference (cairo_get_source (cr));
407
408   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
409
410   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
411
412   cairo_translate (cr,
413                    _gtk_css_number_value_get (shadow->hoffset, 0),
414                    _gtk_css_number_value_get (shadow->voffset, 0));
415   cairo_mask (cr, pattern);
416
417   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
418
419   cairo_restore (cr);
420   cairo_pattern_destroy (pattern);
421 }
422
423 void
424 _gtk_css_shadow_value_paint_spinner (const GtkCssValue *shadow,
425                                      cairo_t           *cr,
426                                      gdouble            radius,
427                                      gdouble            progress)
428 {
429   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
430
431   cairo_save (cr);
432
433   cairo_translate (cr,
434                    _gtk_css_number_value_get (shadow->hoffset, 0),
435                    _gtk_css_number_value_get (shadow->voffset, 0));
436   _gtk_theming_engine_paint_spinner (cr,
437                                      radius, progress,
438                                      _gtk_css_rgba_value_get_rgba (shadow->color));
439
440   cairo_restore (cr);
441 }
442
443 void
444 _gtk_css_shadow_value_paint_box (const GtkCssValue   *shadow,
445                                  cairo_t             *cr,
446                                  const GtkRoundedBox *padding_box)
447 {
448   GtkRoundedBox box, clip_box;
449   double spread, radius;
450
451   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
452
453   cairo_save (cr);
454
455   _gtk_rounded_box_path (padding_box, cr);
456   cairo_clip (cr);
457
458   box = *padding_box;
459   _gtk_rounded_box_move (&box,
460                          _gtk_css_number_value_get (shadow->hoffset, 0),
461                          _gtk_css_number_value_get (shadow->voffset, 0));
462   spread = _gtk_css_number_value_get (shadow->spread, 0);
463   _gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
464
465   clip_box = *padding_box;
466   radius = _gtk_css_number_value_get (shadow->radius, 0);
467   _gtk_rounded_box_shrink (&clip_box, -radius, -radius, -radius, -radius);
468
469   cr = gtk_css_shadow_value_start_drawing (shadow, cr);
470
471   cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
472   _gtk_rounded_box_path (&box, cr);
473   _gtk_rounded_box_clip_path (&clip_box, cr);
474
475   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
476   cairo_fill (cr);
477
478   cr = gtk_css_shadow_value_finish_drawing (shadow, cr);
479
480   cairo_restore (cr);
481 }