]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimagelinear.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcssimagelinear.c
1 /*
2  * Copyright © 2012 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 #include "gtkcssimagelinearprivate.h"
23
24 #include <math.h>
25
26 #include "gtkcsscolorvalueprivate.h"
27 #include "gtkcssnumbervalueprivate.h"
28 #include "gtkcssrgbavalueprivate.h"
29 #include "gtkcssprovider.h"
30
31 G_DEFINE_TYPE (GtkCssImageLinear, _gtk_css_image_linear, GTK_TYPE_CSS_IMAGE)
32
33 static void
34 gtk_css_image_linear_get_start_end (GtkCssImageLinear *linear,
35                                     double             length,
36                                     double            *start,
37                                     double            *end)
38 {
39   GtkCssImageLinearColorStop *stop;
40   double pos;
41   guint i;
42       
43   if (linear->repeating)
44     {
45       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, 0);
46       if (stop->offset == NULL)
47         *start = 0;
48       else
49         *start = _gtk_css_number_value_get (stop->offset, length) / length;
50
51       *end = *start;
52
53       for (i = 0; i < linear->stops->len; i++)
54         {
55           stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
56           
57           if (stop->offset == NULL)
58             continue;
59
60           pos = _gtk_css_number_value_get (stop->offset, length) / length;
61
62           *end = MAX (pos, *end);
63         }
64       
65       if (stop->offset == NULL)
66         *end = MAX (*end, 1.0);
67     }
68   else
69     {
70       *start = 0;
71       *end = 1;
72     }
73 }
74
75 static void
76 gtk_css_image_linear_compute_start_point (double angle_in_degrees,
77                                           double width,
78                                           double height,
79                                           double *x,
80                                           double *y)
81 {
82   double angle, c, slope, perpendicular;
83   
84   angle = fmod (angle_in_degrees, 360);
85   if (angle < 0)
86     angle += 360;
87
88   if (angle == 0)
89     {
90       *x = 0;
91       *y = -height;
92       return;
93     }
94   else if (angle == 90)
95     {
96       *x = width;
97       *y = 0;
98       return;
99     }
100   else if (angle == 180)
101     {
102       *x = 0;
103       *y = height;
104       return;
105     }
106   else if (angle == 270)
107     {
108       *x = -width;
109       *y = 0;
110       return;
111     }
112
113   /* The tan() is confusing because the angle is clockwise
114    * from 'to top' */
115   perpendicular = tan (angle * G_PI / 180);
116   slope = -1 / perpendicular;
117
118   if (angle > 180)
119     width = - width;
120   if (angle < 90 || angle > 270)
121     height = - height;
122   
123   /* Compute c (of y = mx + c) of perpendicular */
124   c = height - perpendicular * width;
125
126   *x = c / (slope - perpendicular);
127   *y = perpendicular * *x + c;
128 }
129                                          
130 static void
131 gtk_css_image_linear_draw (GtkCssImage        *image,
132                            cairo_t            *cr,
133                            double              width,
134                            double              height)
135 {
136   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
137   cairo_pattern_t *pattern;
138   double x, y; /* coordinates of start point */
139   double length; /* distance in pixels for 100% */
140   double start, end; /* position of first/last point on gradient line - with gradient line being [0, 1] */
141   double offset;
142   int i, last;
143
144   if (_gtk_css_number_value_get_unit (linear->angle) == GTK_CSS_NUMBER)
145     {
146       guint side = _gtk_css_number_value_get (linear->angle, 100);
147
148       if (side & (1 << GTK_CSS_RIGHT))
149         x = width;
150       else if (side & (1 << GTK_CSS_LEFT))
151         x = -width;
152       else
153         x = 0;
154
155       if (side & (1 << GTK_CSS_TOP))
156         y = -height;
157       else if (side & (1 << GTK_CSS_BOTTOM))
158         y = height;
159       else
160         y = 0;
161     }
162   else
163     {
164       gtk_css_image_linear_compute_start_point (_gtk_css_number_value_get (linear->angle, 100),
165                                                 width, height,
166                                                 &x, &y);
167     }
168
169   length = sqrt (x * x + y * y);
170   gtk_css_image_linear_get_start_end (linear, length, &start, &end);
171   pattern = cairo_pattern_create_linear (x * (start - 0.5), y * (start - 0.5),
172                                          x * (end - 0.5),   y * (end - 0.5));
173   if (linear->repeating)
174     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
175   else
176     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
177
178   offset = start;
179   last = -1;
180   for (i = 0; i < linear->stops->len; i++)
181     {
182       GtkCssImageLinearColorStop *stop;
183       double pos, step;
184       
185       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
186
187       if (stop->offset == NULL)
188         {
189           if (i == 0)
190             pos = 0.0;
191           else if (i + 1 == linear->stops->len)
192             pos = 1.0;
193           else
194             continue;
195         }
196       else
197         pos = _gtk_css_number_value_get (stop->offset, length) / length;
198
199       pos = MAX (pos, offset);
200       step = (pos - offset) / (i - last);
201       for (last = last + 1; last <= i; last++)
202         {
203           const GdkRGBA *rgba;
204
205           stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, last);
206
207           rgba = _gtk_css_rgba_value_get_rgba (stop->color);
208           offset += step;
209
210           cairo_pattern_add_color_stop_rgba (pattern,
211                                              (offset - start) / (end - start),
212                                              rgba->red,
213                                              rgba->green,
214                                              rgba->blue,
215                                              rgba->alpha);
216         }
217
218       offset = pos;
219       last = i;
220     }
221
222   cairo_rectangle (cr, 0, 0, width, height);
223   cairo_translate (cr, width / 2, height / 2);
224   cairo_set_source (cr, pattern);
225   cairo_fill (cr);
226
227   cairo_pattern_destroy (pattern);
228 }
229
230
231 static gboolean
232 gtk_css_image_linear_parse (GtkCssImage  *image,
233                             GtkCssParser *parser)
234 {
235   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
236   guint i;
237
238   if (_gtk_css_parser_try (parser, "repeating-linear-gradient(", TRUE))
239     linear->repeating = TRUE;
240   else if (_gtk_css_parser_try (parser, "linear-gradient(", TRUE))
241     linear->repeating = FALSE;
242   else
243     {
244       _gtk_css_parser_error (parser, "Not a linear gradient");
245       return FALSE;
246     }
247
248   if (_gtk_css_parser_try (parser, "to", TRUE))
249     {
250       guint side = 0;
251
252       for (i = 0; i < 2; i++)
253         {
254           if (_gtk_css_parser_try (parser, "left", TRUE))
255             {
256               if (side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
257                 {
258                   _gtk_css_parser_error (parser, "Expected `top', `bottom' or comma");
259                   return FALSE;
260                 }
261               side |= (1 << GTK_CSS_LEFT);
262             }
263           else if (_gtk_css_parser_try (parser, "right", TRUE))
264             {
265               if (side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
266                 {
267                   _gtk_css_parser_error (parser, "Expected `top', `bottom' or comma");
268                   return FALSE;
269                 }
270               side |= (1 << GTK_CSS_RIGHT);
271             }
272           else if (_gtk_css_parser_try (parser, "top", TRUE))
273             {
274               if (side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
275                 {
276                   _gtk_css_parser_error (parser, "Expected `left', `right' or comma");
277                   return FALSE;
278                 }
279               side |= (1 << GTK_CSS_TOP);
280             }
281           else if (_gtk_css_parser_try (parser, "bottom", TRUE))
282             {
283               if (side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
284                 {
285                   _gtk_css_parser_error (parser, "Expected `left', `right' or comma");
286                   return FALSE;
287                 }
288               side |= (1 << GTK_CSS_BOTTOM);
289             }
290           else
291             break;
292         }
293
294       if (side == 0)
295         {
296           _gtk_css_parser_error (parser, "Expected side that gradient should go to");
297           return FALSE;
298         }
299
300       linear->angle = _gtk_css_number_value_new (side, GTK_CSS_NUMBER);
301
302       if (!_gtk_css_parser_try (parser, ",", TRUE))
303         {
304           _gtk_css_parser_error (parser, "Expected a comma");
305           return FALSE;
306         }
307     }
308   else if (_gtk_css_parser_has_number (parser))
309     {
310       linear->angle = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
311       if (linear->angle == NULL)
312         return FALSE;
313
314       if (!_gtk_css_parser_try (parser, ",", TRUE))
315         {
316           _gtk_css_parser_error (parser, "Expected a comma");
317           return FALSE;
318         }
319     }
320   else
321     linear->angle = _gtk_css_number_value_new (1 << GTK_CSS_BOTTOM, GTK_CSS_NUMBER);
322
323   do {
324     GtkCssImageLinearColorStop stop;
325
326     stop.color = _gtk_css_color_value_parse (parser);
327     if (stop.color == NULL)
328       return FALSE;
329
330     if (_gtk_css_parser_has_number (parser))
331       {
332         stop.offset = _gtk_css_number_value_parse (parser,
333                                                    GTK_CSS_PARSE_PERCENT
334                                                    | GTK_CSS_PARSE_LENGTH);
335         if (stop.offset == NULL)
336           {
337             _gtk_css_value_unref (stop.color);
338             return FALSE;
339           }
340       }
341     else
342       {
343         stop.offset = NULL;
344       }
345
346     g_array_append_val (linear->stops, stop);
347
348   } while (_gtk_css_parser_try (parser, ",", TRUE));
349
350   if (!_gtk_css_parser_try (parser, ")", TRUE))
351     {
352       _gtk_css_parser_error (parser, "Missing closing bracket at end of linear gradient");
353       return FALSE;
354     }
355
356   return TRUE;
357 }
358
359 static void
360 gtk_css_image_linear_print (GtkCssImage *image,
361                             GString     *string)
362 {
363   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
364   guint i;
365
366   if (linear->repeating)
367     g_string_append (string, "repeating-linear-gradient(");
368   else
369     g_string_append (string, "linear-gradient(");
370
371   if (_gtk_css_number_value_get_unit (linear->angle) == GTK_CSS_NUMBER)
372     {
373       guint side = _gtk_css_number_value_get (linear->angle, 100);
374
375       if (side != (1 << GTK_CSS_BOTTOM))
376         {
377           g_string_append (string, "to");
378
379           if (side & (1 << GTK_CSS_TOP))
380             g_string_append (string, " top");
381           else if (side & (1 << GTK_CSS_BOTTOM))
382             g_string_append (string, " bottom");
383
384           if (side & (1 << GTK_CSS_LEFT))
385             g_string_append (string, " left");
386           else if (side & (1 << GTK_CSS_RIGHT))
387             g_string_append (string, " right");
388
389           g_string_append (string, ", ");
390         }
391     }
392   else
393     {
394       _gtk_css_value_print (linear->angle, string);
395       g_string_append (string, ", ");
396     }
397
398   for (i = 0; i < linear->stops->len; i++)
399     {
400       GtkCssImageLinearColorStop *stop;
401       
402       if (i > 0)
403         g_string_append (string, ", ");
404
405       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
406
407       _gtk_css_value_print (stop->color, string);
408
409       if (stop->offset)
410         {
411           g_string_append (string, " ");
412           _gtk_css_value_print (stop->offset, string);
413         }
414     }
415   
416   g_string_append (string, ")");
417 }
418
419 static GtkCssImage *
420 gtk_css_image_linear_compute (GtkCssImage             *image,
421                               guint                    property_id,
422                               GtkStyleProviderPrivate *provider,
423                               GtkCssComputedValues    *values,
424                               GtkCssComputedValues    *parent_values,
425                               GtkCssDependencies      *dependencies)
426 {
427   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
428   GtkCssImageLinear *copy;
429   guint i;
430
431   copy = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
432   copy->repeating = linear->repeating;
433
434   copy->angle = _gtk_css_value_compute (linear->angle, property_id, provider, values, parent_values, dependencies);
435   
436   g_array_set_size (copy->stops, linear->stops->len);
437   for (i = 0; i < linear->stops->len; i++)
438     {
439       GtkCssImageLinearColorStop *stop, *scopy;
440       GtkCssDependencies child_deps;
441
442       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
443       scopy = &g_array_index (copy->stops, GtkCssImageLinearColorStop, i);
444               
445       scopy->color = _gtk_css_value_compute (stop->color, property_id, provider, values, parent_values, &child_deps);
446       *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
447       
448       if (stop->offset)
449         {
450           scopy->offset = _gtk_css_value_compute (stop->offset, property_id, provider, values, parent_values, &child_deps);
451           *dependencies = _gtk_css_dependencies_union (*dependencies, child_deps);
452         }
453       else
454         {
455           scopy->offset = NULL;
456         }
457     }
458
459   return GTK_CSS_IMAGE (copy);
460 }
461
462 static GtkCssImage *
463 gtk_css_image_linear_transition (GtkCssImage *start_image,
464                                  GtkCssImage *end_image,
465                                  guint        property_id,
466                                  double       progress)
467 {
468   GtkCssImageLinear *start, *end, *result;
469   guint i;
470
471   start = GTK_CSS_IMAGE_LINEAR (start_image);
472
473   if (end_image == NULL)
474
475   if (!GTK_IS_CSS_IMAGE_LINEAR (end_image))
476     return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
477
478   end = GTK_CSS_IMAGE_LINEAR (end_image);
479
480   if ((start->repeating != end->repeating)
481       || (start->stops->len != end->stops->len))
482     return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
483
484   result = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
485   result->repeating = start->repeating;
486
487   result->angle = _gtk_css_value_transition (start->angle, end->angle, property_id, progress);
488   if (result->angle == NULL)
489     goto fail;
490   
491   for (i = 0; i < start->stops->len; i++)
492     {
493       GtkCssImageLinearColorStop stop, *start_stop, *end_stop;
494
495       start_stop = &g_array_index (start->stops, GtkCssImageLinearColorStop, i);
496       end_stop = &g_array_index (end->stops, GtkCssImageLinearColorStop, i);
497
498       if ((start_stop->offset != NULL) != (end_stop->offset != NULL))
499         goto fail;
500
501       if (start_stop->offset == NULL)
502         {
503           stop.offset = NULL;
504         }
505       else
506         {
507           stop.offset = _gtk_css_value_transition (start_stop->offset,
508                                                    end_stop->offset,
509                                                    property_id,
510                                                    progress);
511           if (stop.offset == NULL)
512             goto fail;
513         }
514
515       stop.color = _gtk_css_value_transition (start_stop->color,
516                                               end_stop->color,
517                                               property_id,
518                                               progress);
519       if (stop.color == NULL)
520         {
521           if (stop.offset)
522             _gtk_css_value_unref (stop.offset);
523           goto fail;
524         }
525
526       g_array_append_val (result->stops, stop);
527     }
528
529   return GTK_CSS_IMAGE (result);
530
531 fail:
532   g_object_unref (result);
533   return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
534 }
535
536 static gboolean
537 gtk_css_image_linear_equal (GtkCssImage *image1,
538                             GtkCssImage *image2)
539 {
540   GtkCssImageLinear *linear1 = GTK_CSS_IMAGE_LINEAR (image1);
541   GtkCssImageLinear *linear2 = GTK_CSS_IMAGE_LINEAR (image2);
542   guint i;
543
544   if (linear1->repeating != linear2->repeating ||
545       !_gtk_css_value_equal (linear1->angle, linear2->angle) ||
546       linear1->stops->len != linear2->stops->len)
547     return FALSE;
548
549   for (i = 0; i < linear1->stops->len; i++)
550     {
551       GtkCssImageLinearColorStop *stop1, *stop2;
552
553       stop1 = &g_array_index (linear1->stops, GtkCssImageLinearColorStop, i);
554       stop2 = &g_array_index (linear2->stops, GtkCssImageLinearColorStop, i);
555
556       if (!_gtk_css_value_equal0 (stop1->offset, stop2->offset) ||
557           !_gtk_css_value_equal (stop1->color, stop2->color))
558         return FALSE;
559     }
560
561   return TRUE;
562 }
563
564 static void
565 gtk_css_image_linear_dispose (GObject *object)
566 {
567   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (object);
568
569   if (linear->stops)
570     {
571       g_array_free (linear->stops, TRUE);
572       linear->stops = NULL;
573     }
574
575   if (linear->angle)
576     {
577       _gtk_css_value_unref (linear->angle);
578       linear->angle = NULL;
579     }
580
581   G_OBJECT_CLASS (_gtk_css_image_linear_parent_class)->dispose (object);
582 }
583
584 static void
585 _gtk_css_image_linear_class_init (GtkCssImageLinearClass *klass)
586 {
587   GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
588   GObjectClass *object_class = G_OBJECT_CLASS (klass);
589
590   image_class->draw = gtk_css_image_linear_draw;
591   image_class->parse = gtk_css_image_linear_parse;
592   image_class->print = gtk_css_image_linear_print;
593   image_class->compute = gtk_css_image_linear_compute;
594   image_class->equal = gtk_css_image_linear_equal;
595   image_class->transition = gtk_css_image_linear_transition;
596
597   object_class->dispose = gtk_css_image_linear_dispose;
598 }
599
600 static void
601 gtk_css_image_clear_color_stop (gpointer color_stop)
602 {
603   GtkCssImageLinearColorStop *stop = color_stop;
604
605   _gtk_css_value_unref (stop->color);
606   if (stop->offset)
607     _gtk_css_value_unref (stop->offset);
608 }
609
610 static void
611 _gtk_css_image_linear_init (GtkCssImageLinear *linear)
612 {
613   linear->stops = g_array_new (FALSE, FALSE, sizeof (GtkCssImageLinearColorStop));
614   g_array_set_clear_func (linear->stops, gtk_css_image_clear_color_stop);
615 }
616