]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimagegradient.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcssimagegradient.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19
20 #include "config.h"
21
22 #define GDK_DISABLE_DEPRECATION_WARNINGS
23
24 #include "gtkcssimagegradientprivate.h"
25
26 #include "gtkcssprovider.h"
27
28 #include "deprecated/gtkgradientprivate.h"
29 #include "deprecated/gtksymboliccolorprivate.h"
30
31 G_DEFINE_TYPE (GtkCssImageGradient, _gtk_css_image_gradient, GTK_TYPE_CSS_IMAGE)
32
33 static GtkCssImage *
34 gtk_css_image_gradient_compute (GtkCssImage             *image,
35                                 guint                    property_id,
36                                 GtkStyleProviderPrivate *provider,
37                                 GtkCssComputedValues    *values,
38                                 GtkCssComputedValues    *parent_values,
39                                 GtkCssDependencies      *dependencies)
40 {
41   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
42   GtkCssImageGradient *copy;
43
44   if (gradient->pattern)
45     return g_object_ref (gradient);
46
47   copy = g_object_new (GTK_TYPE_CSS_IMAGE_GRADIENT, NULL);
48   copy->gradient = gtk_gradient_ref (gradient->gradient);
49   copy->pattern = _gtk_gradient_resolve_full (copy->gradient, provider, values, parent_values, dependencies);
50
51   return GTK_CSS_IMAGE (copy);
52 }
53
54 static cairo_pattern_t *
55 fade_pattern (cairo_pattern_t *pattern,
56               double           opacity)
57 {
58   double x0, y0, x1, y1, r0, r1;
59   cairo_pattern_t *result;
60   int i, n;
61
62   switch (cairo_pattern_get_type (pattern))
63     {
64     case CAIRO_PATTERN_TYPE_LINEAR:
65       cairo_pattern_get_linear_points (pattern, &x0, &y0, &x1, &y1);
66       result = cairo_pattern_create_linear (x0, y0, x1, y1);
67       break;
68     case CAIRO_PATTERN_TYPE_RADIAL:
69       cairo_pattern_get_radial_circles (pattern, &x0, &y0, &r0, &x1, &y1, &r1);
70       result = cairo_pattern_create_radial (x0, y0, r0, x1, y1, r1);
71       break;
72     default:
73       g_return_val_if_reached (NULL);
74     }
75
76   cairo_pattern_get_color_stop_count (pattern, &n);
77   for (i = 0; i < n; i++)
78     {
79       double o, r, g, b, a;
80
81       cairo_pattern_get_color_stop_rgba (pattern, i, &o, &r, &g, &b, &a);
82       cairo_pattern_add_color_stop_rgba (result, o, r, g, b, a * opacity);
83     }
84
85   return result;
86 }
87
88 static cairo_pattern_t *
89 transition_pattern (cairo_pattern_t *start,
90                     cairo_pattern_t *end,
91                     double           progress)
92 {
93   double sx0, sy0, sx1, sy1, sr0, sr1, ex0, ey0, ex1, ey1, er0, er1;
94   cairo_pattern_t *result;
95   int i, n;
96
97   progress = CLAMP (progress, 0.0, 1.0);
98
99   if (end == NULL)
100     return fade_pattern (start, 1.0 - progress);
101
102   g_assert (cairo_pattern_get_type (start) == cairo_pattern_get_type (end));
103
104   switch (cairo_pattern_get_type (start))
105     {
106     case CAIRO_PATTERN_TYPE_LINEAR:
107       cairo_pattern_get_linear_points (start, &sx0, &sy0, &sx1, &sy1);
108       cairo_pattern_get_linear_points (end, &ex0, &ey0, &ex1, &ey1);
109       result = cairo_pattern_create_linear ((1 - progress) * sx0 + progress * ex0,
110                                             (1 - progress) * sx1 + progress * ex1,
111                                             (1 - progress) * sy0 + progress * ey0,
112                                             (1 - progress) * sy1 + progress * ey1);
113       break;
114     case CAIRO_PATTERN_TYPE_RADIAL:
115       cairo_pattern_get_radial_circles (start, &sx0, &sy0, &sr0, &sx1, &sy1, &sr1);
116       cairo_pattern_get_radial_circles (end, &ex0, &ey0, &er0, &ex1, &ey1, &er1);
117       result = cairo_pattern_create_radial ((1 - progress) * sx0 + progress * ex0,
118                                             (1 - progress) * sy0 + progress * ey0,
119                                             (1 - progress) * sr0 + progress * er0,
120                                             (1 - progress) * sx1 + progress * ex1,
121                                             (1 - progress) * sy1 + progress * ey1,
122                                             (1 - progress) * sr1 + progress * er1);
123       break;
124     default:
125       g_return_val_if_reached (NULL);
126     }
127
128   cairo_pattern_get_color_stop_count (start, &n);
129   for (i = 0; i < n; i++)
130     {
131       double so, sr, sg, sb, sa, eo, er, eg, eb, ea;
132
133       cairo_pattern_get_color_stop_rgba (start, i, &so, &sr, &sg, &sb, &sa);
134       cairo_pattern_get_color_stop_rgba (end, i, &eo, &er, &eg, &eb, &ea);
135
136       cairo_pattern_add_color_stop_rgba (result,
137                                          (1 - progress) * so + progress * eo,
138                                          (1 - progress) * sr + progress * er,
139                                          (1 - progress) * sg + progress * eg,
140                                          (1 - progress) * sb + progress * eb,
141                                          (1 - progress) * sa + progress * ea);
142     }
143
144   return result;
145 }
146
147 static GtkCssImage *
148 gtk_css_image_gradient_transition (GtkCssImage *start_image,
149                                    GtkCssImage *end_image,
150                                    guint        property_id,
151                                    double       progress)
152 {
153   GtkGradient *start_gradient, *end_gradient, *gradient;
154   cairo_pattern_t *start_pattern, *end_pattern;
155   GtkCssImageGradient *result;
156
157   start_gradient = GTK_CSS_IMAGE_GRADIENT (start_image)->gradient;
158   start_pattern = GTK_CSS_IMAGE_GRADIENT (start_image)->pattern;
159   if (end_image == NULL)
160     {
161       end_gradient = NULL;
162       end_pattern = NULL;
163     }
164   else
165     {
166       if (!GTK_IS_CSS_IMAGE_GRADIENT (end_image))
167         return GTK_CSS_IMAGE_CLASS (_gtk_css_image_gradient_parent_class)->transition (start_image, end_image, property_id, progress);
168
169       end_gradient = GTK_CSS_IMAGE_GRADIENT (end_image)->gradient;
170       end_pattern = GTK_CSS_IMAGE_GRADIENT (end_image)->pattern;
171     }
172
173   gradient = _gtk_gradient_transition (start_gradient, end_gradient, property_id, progress);
174   if (gradient == NULL)
175     return GTK_CSS_IMAGE_CLASS (_gtk_css_image_gradient_parent_class)->transition (start_image, end_image, property_id, progress);
176
177   result = g_object_new (GTK_TYPE_CSS_IMAGE_GRADIENT, NULL);
178   result->gradient = gradient;
179   result->pattern = transition_pattern (start_pattern, end_pattern, progress);
180
181   return GTK_CSS_IMAGE (result);
182 }
183
184 static gboolean
185 gtk_css_image_gradient_draw_circle (GtkCssImageGradient *image,
186                                     cairo_t              *cr,
187                                     double               width,
188                                     double               height)
189 {
190   cairo_pattern_t *pattern = image->pattern;
191   double x0, y0, x1, y1, r0, r1;
192   GdkRGBA color0, color1;
193   double offset0, offset1;
194   int n_stops;
195
196   if (cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_RADIAL)
197     return FALSE;
198   if (cairo_pattern_get_extend (pattern) != CAIRO_EXTEND_PAD)
199     return FALSE;
200
201   cairo_pattern_get_radial_circles (pattern, &x0, &y0, &r0, &x1, &y1, &r1);
202
203   if (x0 != x1 ||
204       y0 != y1 ||
205       r0 != 0.0)
206     return FALSE;
207
208   cairo_pattern_get_color_stop_count (pattern, &n_stops);
209   if (n_stops != 2)
210     return FALSE;
211
212   cairo_pattern_get_color_stop_rgba (pattern, 0, &offset0, &color0.red, &color0.green, &color0.blue, &color0.alpha);
213   cairo_pattern_get_color_stop_rgba (pattern, 1, &offset1, &color1.red, &color1.green, &color1.blue, &color1.alpha);
214   if (offset0 != offset1)
215     return FALSE;
216
217   cairo_scale (cr, width, height);
218
219   cairo_rectangle (cr, 0, 0, 1, 1);
220   cairo_clip (cr);
221
222   gdk_cairo_set_source_rgba (cr, &color1);
223   cairo_paint (cr);
224
225   gdk_cairo_set_source_rgba (cr, &color0);
226   cairo_arc (cr, x1, y1, r1 * offset1, 0, 2 * G_PI);
227   cairo_fill (cr);
228
229   return TRUE;
230 }
231
232 static void
233 gtk_css_image_gradient_draw (GtkCssImage        *image,
234                              cairo_t            *cr,
235                              double              width,
236                              double              height)
237 {
238   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
239
240   if (!gradient->pattern)
241     {
242       g_warning ("trying to paint unresolved gradient");
243       return;
244     }
245
246   if (gtk_css_image_gradient_draw_circle (gradient, cr, width, height))
247     return;
248
249   cairo_scale (cr, width, height);
250
251   cairo_rectangle (cr, 0, 0, 1, 1);
252   cairo_set_source (cr, gradient->pattern);
253   cairo_fill (cr);
254 }
255
256 static gboolean
257 gtk_css_image_gradient_parse (GtkCssImage  *image,
258                               GtkCssParser *parser)
259 {
260   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
261
262   gradient->gradient = _gtk_gradient_parse (parser);
263
264   return gradient->gradient != NULL;
265 }
266
267 static void
268 gtk_css_image_gradient_print (GtkCssImage *image,
269                               GString     *string)
270 {
271   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
272   char *s;
273
274   s = gtk_gradient_to_string (gradient->gradient);
275   g_string_append (string, s);
276   g_free (s);
277 }
278
279 static void
280 gtk_css_image_gradient_dispose (GObject *object)
281 {
282   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (object);
283
284   if (gradient->gradient)
285     {
286       gtk_gradient_unref (gradient->gradient);
287       gradient->gradient = NULL;
288     }
289   if (gradient->pattern)
290     {
291       cairo_pattern_destroy (gradient->pattern);
292       gradient->pattern = NULL;
293     }
294
295   G_OBJECT_CLASS (_gtk_css_image_gradient_parent_class)->dispose (object);
296 }
297
298 static void
299 _gtk_css_image_gradient_class_init (GtkCssImageGradientClass *klass)
300 {
301   GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
302   GObjectClass *object_class = G_OBJECT_CLASS (klass);
303
304   image_class->compute = gtk_css_image_gradient_compute;
305   image_class->transition = gtk_css_image_gradient_transition;
306   image_class->draw = gtk_css_image_gradient_draw;
307   image_class->parse = gtk_css_image_gradient_parse;
308   image_class->print = gtk_css_image_gradient_print;
309
310   object_class->dispose = gtk_css_image_gradient_dispose;
311 }
312
313 static void
314 _gtk_css_image_gradient_init (GtkCssImageGradient *image_gradient)
315 {
316 }
317
318 GtkGradient *
319 _gtk_gradient_parse (GtkCssParser *parser)
320 {
321   GtkGradient *gradient;
322   cairo_pattern_type_t type;
323   gdouble coords[6];
324   guint i;
325
326   g_return_val_if_fail (parser != NULL, NULL);
327
328   if (!_gtk_css_parser_try (parser, "-gtk-gradient", TRUE))
329     {
330       _gtk_css_parser_error (parser,
331                              "Expected '-gtk-gradient'");
332       return NULL;
333     }
334
335   if (!_gtk_css_parser_try (parser, "(", TRUE))
336     {
337       _gtk_css_parser_error (parser,
338                              "Expected '(' after '-gtk-gradient'");
339       return NULL;
340     }
341
342   /* Parse gradient type */
343   if (_gtk_css_parser_try (parser, "linear", TRUE))
344     type = CAIRO_PATTERN_TYPE_LINEAR;
345   else if (_gtk_css_parser_try (parser, "radial", TRUE))
346     type = CAIRO_PATTERN_TYPE_RADIAL;
347   else
348     {
349       _gtk_css_parser_error (parser,
350                              "Gradient type must be 'radial' or 'linear'");
351       return NULL;
352     }
353
354   /* Parse start/stop position parameters */
355   for (i = 0; i < 2; i++)
356     {
357       if (! _gtk_css_parser_try (parser, ",", TRUE))
358         {
359           _gtk_css_parser_error (parser,
360                                  "Expected ','");
361           return NULL;
362         }
363
364       if (_gtk_css_parser_try (parser, "left", TRUE))
365         coords[i * 3] = 0;
366       else if (_gtk_css_parser_try (parser, "right", TRUE))
367         coords[i * 3] = 1;
368       else if (_gtk_css_parser_try (parser, "center", TRUE))
369         coords[i * 3] = 0.5;
370       else if (!_gtk_css_parser_try_double (parser, &coords[i * 3]))
371         {
372           _gtk_css_parser_error (parser,
373                                  "Expected a valid X coordinate");
374           return NULL;
375         }
376
377       if (_gtk_css_parser_try (parser, "top", TRUE))
378         coords[i * 3 + 1] = 0;
379       else if (_gtk_css_parser_try (parser, "bottom", TRUE))
380         coords[i * 3 + 1] = 1;
381       else if (_gtk_css_parser_try (parser, "center", TRUE))
382         coords[i * 3 + 1] = 0.5;
383       else if (!_gtk_css_parser_try_double (parser, &coords[i * 3 + 1]))
384         {
385           _gtk_css_parser_error (parser,
386                                  "Expected a valid Y coordinate");
387           return NULL;
388         }
389
390       if (type == CAIRO_PATTERN_TYPE_RADIAL)
391         {
392           /* Parse radius */
393           if (! _gtk_css_parser_try (parser, ",", TRUE))
394             {
395               _gtk_css_parser_error (parser,
396                                      "Expected ','");
397               return NULL;
398             }
399
400           if (! _gtk_css_parser_try_double (parser, &coords[(i * 3) + 2]))
401             {
402               _gtk_css_parser_error (parser,
403                                      "Expected a numer for the radius");
404               return NULL;
405             }
406         }
407     }
408
409   if (type == CAIRO_PATTERN_TYPE_LINEAR)
410     gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
411   else
412     gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
413                                         coords[3], coords[4], coords[5]);
414
415   while (_gtk_css_parser_try (parser, ",", TRUE))
416     {
417       GtkSymbolicColor *color;
418       gdouble position;
419
420       if (_gtk_css_parser_try (parser, "from", TRUE))
421         {
422           position = 0;
423
424           if (!_gtk_css_parser_try (parser, "(", TRUE))
425             {
426               gtk_gradient_unref (gradient);
427               _gtk_css_parser_error (parser,
428                                      "Expected '('");
429               return NULL;
430             }
431
432         }
433       else if (_gtk_css_parser_try (parser, "to", TRUE))
434         {
435           position = 1;
436
437           if (!_gtk_css_parser_try (parser, "(", TRUE))
438             {
439               gtk_gradient_unref (gradient);
440               _gtk_css_parser_error (parser,
441                                      "Expected '('");
442               return NULL;
443             }
444
445         }
446       else if (_gtk_css_parser_try (parser, "color-stop", TRUE))
447         {
448           if (!_gtk_css_parser_try (parser, "(", TRUE))
449             {
450               gtk_gradient_unref (gradient);
451               _gtk_css_parser_error (parser,
452                                      "Expected '('");
453               return NULL;
454             }
455
456           if (!_gtk_css_parser_try_double (parser, &position))
457             {
458               gtk_gradient_unref (gradient);
459               _gtk_css_parser_error (parser,
460                                      "Expected a valid number");
461               return NULL;
462             }
463
464           if (!_gtk_css_parser_try (parser, ",", TRUE))
465             {
466               gtk_gradient_unref (gradient);
467               _gtk_css_parser_error (parser,
468                                      "Expected a comma");
469               return NULL;
470             }
471         }
472       else
473         {
474           gtk_gradient_unref (gradient);
475           _gtk_css_parser_error (parser,
476                                  "Not a valid color-stop definition");
477           return NULL;
478         }
479
480       color = _gtk_css_symbolic_value_new (parser);
481       if (color == NULL)
482         {
483           gtk_gradient_unref (gradient);
484           return NULL;
485         }
486
487       gtk_gradient_add_color_stop (gradient, position, color);
488       gtk_symbolic_color_unref (color);
489
490       if (!_gtk_css_parser_try (parser, ")", TRUE))
491         {
492           gtk_gradient_unref (gradient);
493           _gtk_css_parser_error (parser,
494                                  "Expected ')'");
495           return NULL;
496         }
497     }
498
499   if (!_gtk_css_parser_try (parser, ")", TRUE))
500     {
501       gtk_gradient_unref (gradient);
502       _gtk_css_parser_error (parser,
503                              "Expected ')'");
504       return NULL;
505     }
506
507   return gradient;
508 }