]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimagelinear.c
Change FSF Address
[~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 "gtkcssprovider.h"
27 #include "gtkstylecontextprivate.h"
28
29 G_DEFINE_TYPE (GtkCssImageLinear, _gtk_css_image_linear, GTK_TYPE_CSS_IMAGE)
30
31 static void
32 gtk_css_image_linear_get_start_end (GtkCssImageLinear *linear,
33                                     double             length,
34                                     double            *start,
35                                     double            *end)
36 {
37   GtkCssImageLinearColorStop *stop;
38   double pos;
39   guint i;
40       
41   stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, 0);
42   if (stop->offset.unit == GTK_CSS_NUMBER)
43     *start = 0;
44   else if (stop->offset.unit == GTK_CSS_PX)
45     *start = stop->offset.value / length;
46   else
47     *start = stop->offset.value / 100;
48
49   *end = *start;
50
51   for (i = 0; i < linear->stops->len; i++)
52     {
53       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
54       
55       if (stop->offset.unit == GTK_CSS_NUMBER)
56         continue;
57
58       if (stop->offset.unit == GTK_CSS_PX)
59         pos = stop->offset.value / length;
60       else
61         pos = stop->offset.value / 100;
62
63       *end = MAX (pos, *end);
64     }
65   
66   if (stop->offset.unit == GTK_CSS_NUMBER)
67     *end = MAX (*end, 1.0);
68 }
69
70 static void
71 gtk_css_image_linear_compute_start_point (double angle_in_degrees,
72                                           double width,
73                                           double height,
74                                           double *x,
75                                           double *y)
76 {
77   double angle, c, slope, perpendicular;
78   
79   angle = fmod (angle_in_degrees, 360);
80   if (angle < 0)
81     angle += 360;
82
83   if (angle == 0)
84     {
85       *x = 0;
86       *y = -height;
87       return;
88     }
89   else if (angle == 90)
90     {
91       *x = width;
92       *y = 0;
93       return;
94     }
95   else if (angle == 180)
96     {
97       *x = 0;
98       *y = height;
99       return;
100     }
101   else if (angle == 270)
102     {
103       *x = -width;
104       *y = 0;
105       return;
106     }
107
108   /* The tan() is confusing because the angle is clockwise
109    * from 'to top' */
110   perpendicular = tan (angle * G_PI / 180);
111   slope = -1 / perpendicular;
112
113   if (angle > 180)
114     width = - width;
115   if (angle < 90 || angle > 270)
116     height = - height;
117   
118   /* Compute c (of y = mx + c) of perpendicular */
119   c = height - perpendicular * width;
120
121   *x = c / (slope - perpendicular);
122   *y = perpendicular * *x + c;
123 }
124                                          
125 static void
126 gtk_css_image_linear_draw (GtkCssImage        *image,
127                            cairo_t            *cr,
128                            double              width,
129                            double              height)
130 {
131   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
132   cairo_pattern_t *pattern;
133   double x, y; /* coordinates of start point */
134   double length; /* distance in pixels for 100% */
135   double start, end; /* position of first/last point on gradient line - with gradient line being [0, 1] */
136   double offset;
137   int i, last;
138
139   g_return_if_fail (linear->is_computed);
140
141   if (linear->angle.unit == GTK_CSS_NUMBER)
142     {
143       guint side = linear->angle.value;
144
145       if (side & (1 << GTK_CSS_RIGHT))
146         x = width;
147       else if (side & (1 << GTK_CSS_LEFT))
148         x = -width;
149       else
150         x = 0;
151
152       if (side & (1 << GTK_CSS_TOP))
153         y = -height;
154       else if (side & (1 << GTK_CSS_BOTTOM))
155         y = height;
156       else
157         y = 0;
158     }
159   else
160     {
161       gtk_css_image_linear_compute_start_point (linear->angle.value,
162                                                 width, height,
163                                                 &x, &y);
164     }
165
166   length = sqrt (x * x + y * y);
167   gtk_css_image_linear_get_start_end (linear, length, &start, &end);
168   pattern = cairo_pattern_create_linear (x * (start - 0.5), y * (start - 0.5),
169                                          x * (end - 0.5),   y * (end - 0.5));
170   if (linear->repeating)
171     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
172   else
173     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
174
175   offset = start;
176   last = -1;
177   for (i = 0; i < linear->stops->len; i++)
178     {
179       GtkCssImageLinearColorStop *stop;
180       double pos, step;
181       
182       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
183
184       if (stop->offset.unit == GTK_CSS_NUMBER)
185         {
186           if (i == 0)
187             pos = 0.0;
188           else if (i + 1 == linear->stops->len)
189             pos = 1.0;
190           else
191             continue;
192         }
193       else if (stop->offset.unit == GTK_CSS_PX)
194         pos = stop->offset.value / length;
195       else
196         pos = stop->offset.value / 100;
197
198       pos = MAX (pos, offset);
199       step = (pos - offset) / (i - last);
200       for (last = last + 1; last <= i; last++)
201         {
202           stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, last);
203
204           offset += step;
205
206           cairo_pattern_add_color_stop_rgba (pattern,
207                                              (offset - start) / (end - start),
208                                              stop->color.rgba.red,
209                                              stop->color.rgba.green,
210                                              stop->color.rgba.blue,
211                                              stop->color.rgba.alpha);
212         }
213
214       offset = pos;
215       last = i;
216     }
217
218   cairo_rectangle (cr, 0, 0, width, height);
219   cairo_translate (cr, width / 2, height / 2);
220   cairo_set_source (cr, pattern);
221   cairo_fill (cr);
222
223   cairo_pattern_destroy (pattern);
224 }
225
226
227 static gboolean
228 gtk_css_image_linear_parse (GtkCssImage  *image,
229                             GtkCssParser *parser,
230                             GFile        *base)
231 {
232   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
233   guint i;
234
235   if (_gtk_css_parser_try (parser, "repeating-linear-gradient(", TRUE))
236     linear->repeating = TRUE;
237   else if (_gtk_css_parser_try (parser, "linear-gradient(", TRUE))
238     linear->repeating = FALSE;
239   else
240     {
241       _gtk_css_parser_error (parser, "Not a linear gradient");
242       return FALSE;
243     }
244
245   if (_gtk_css_parser_try (parser, "to", TRUE))
246     {
247       guint side = 0;
248
249       for (i = 0; i < 2; i++)
250         {
251           if (_gtk_css_parser_try (parser, "left", TRUE))
252             {
253               if (side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
254                 {
255                   _gtk_css_parser_error (parser, "Expected `top', `bottom' or comma");
256                   return FALSE;
257                 }
258               side |= (1 << GTK_CSS_LEFT);
259             }
260           else if (_gtk_css_parser_try (parser, "right", TRUE))
261             {
262               if (side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
263                 {
264                   _gtk_css_parser_error (parser, "Expected `top', `bottom' or comma");
265                   return FALSE;
266                 }
267               side |= (1 << GTK_CSS_RIGHT);
268             }
269           else if (_gtk_css_parser_try (parser, "top", TRUE))
270             {
271               if (side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
272                 {
273                   _gtk_css_parser_error (parser, "Expected `left', `right' or comma");
274                   return FALSE;
275                 }
276               side |= (1 << GTK_CSS_TOP);
277             }
278           else if (_gtk_css_parser_try (parser, "bottom", TRUE))
279             {
280               if (side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
281                 {
282                   _gtk_css_parser_error (parser, "Expected `left', `right' or comma");
283                   return FALSE;
284                 }
285               side |= (1 << GTK_CSS_BOTTOM);
286             }
287           else
288             break;
289         }
290
291       if (side == 0)
292         {
293           _gtk_css_parser_error (parser, "Expected side that gradient should go to");
294           return FALSE;
295         }
296
297       _gtk_css_number_init (&linear->angle, side, GTK_CSS_NUMBER);
298
299       if (!_gtk_css_parser_try (parser, ",", TRUE))
300         {
301           _gtk_css_parser_error (parser, "Expected a comma");
302           return FALSE;
303         }
304     }
305   else if (_gtk_css_parser_has_number (parser))
306     {
307       if (!_gtk_css_parser_read_number (parser,
308                                         &linear->angle,
309                                         GTK_CSS_PARSE_ANGLE))
310         return FALSE;
311
312       if (!_gtk_css_parser_try (parser, ",", TRUE))
313         {
314           _gtk_css_parser_error (parser, "Expected a comma");
315           return FALSE;
316         }
317     }
318   else
319     _gtk_css_number_init (&linear->angle, 1 << GTK_CSS_BOTTOM, GTK_CSS_NUMBER);
320
321   do {
322     GtkCssImageLinearColorStop stop;
323
324     stop.color.symbolic = _gtk_css_parser_read_symbolic_color (parser);
325     if (stop.color.symbolic == NULL)
326       return FALSE;
327
328     if (_gtk_css_parser_has_number (parser))
329       {
330         if (!_gtk_css_parser_read_number (parser,
331                                           &stop.offset,
332                                           GTK_CSS_PARSE_PERCENT
333                                           | GTK_CSS_PARSE_LENGTH))
334           return FALSE;
335       }
336     else
337       {
338         /* use NUMBER to mark as unset number */
339         _gtk_css_number_init (&stop.offset, 0, GTK_CSS_NUMBER);
340       }
341
342     g_array_append_val (linear->stops, stop);
343
344   } while (_gtk_css_parser_try (parser, ",", TRUE));
345
346   if (!_gtk_css_parser_try (parser, ")", TRUE))
347     {
348       _gtk_css_parser_error (parser, "Missing closing bracket at end of linear gradient");
349       return FALSE;
350     }
351
352   return TRUE;
353 }
354
355 static void
356 gtk_css_image_linear_print (GtkCssImage *image,
357                             GString     *string)
358 {
359   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
360   guint i;
361
362   /* XXX: Do these need to be prinatable? */
363   g_return_if_fail (!linear->is_computed);
364
365   if (linear->repeating)
366     g_string_append (string, "repeating-linear-gradient(");
367   else
368     g_string_append (string, "linear-gradient(");
369
370   if (linear->angle.unit == GTK_CSS_NUMBER)
371     {
372       guint side = linear->angle.value;
373
374       if (side != (1 << GTK_CSS_BOTTOM))
375         {
376           g_string_append (string, "to");
377
378           if (side & (1 << GTK_CSS_TOP))
379             g_string_append (string, " top");
380           else if (side & (1 << GTK_CSS_BOTTOM))
381             g_string_append (string, " bottom");
382
383           if (side & (1 << GTK_CSS_LEFT))
384             g_string_append (string, " left");
385           else if (side & (1 << GTK_CSS_RIGHT))
386             g_string_append (string, " right");
387
388           g_string_append (string, ", ");
389         }
390     }
391   else
392     {
393       _gtk_css_number_print (&linear->angle, string);
394       g_string_append (string, ", ");
395     }
396
397   for (i = 0; i < linear->stops->len; i++)
398     {
399       GtkCssImageLinearColorStop *stop;
400       char *s;
401       
402       if (i > 0)
403         g_string_append (string, ", ");
404
405       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
406
407       s = gtk_symbolic_color_to_string (stop->color.symbolic);
408       g_string_append (string, s);
409       g_free (s);
410
411       if (stop->offset.unit != GTK_CSS_NUMBER)
412         {
413           g_string_append (string, " ");
414           _gtk_css_number_print (&stop->offset, string);
415         }
416     }
417   
418   g_string_append (string, ")");
419 }
420
421 static GtkCssImage *
422 gtk_css_image_linear_compute (GtkCssImage     *image,
423                               GtkStyleContext *context)
424 {
425   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
426   GtkCssImageLinear *copy;
427   guint i;
428
429   if (linear->is_computed)
430     return g_object_ref (linear);
431
432   copy = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
433   copy->is_computed = TRUE;
434   copy->repeating = linear->repeating;
435
436   _gtk_css_number_compute (&copy->angle, &linear->angle, context);
437   
438   g_array_set_size (copy->stops, linear->stops->len);
439   for (i = 0; i < linear->stops->len; i++)
440     {
441       GtkCssImageLinearColorStop *stop, *scopy;
442
443       stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
444       scopy = &g_array_index (copy->stops, GtkCssImageLinearColorStop, i);
445               
446       if (!_gtk_style_context_resolve_color (context,
447                                              stop->color.symbolic,
448                                              &scopy->color.rgba))
449         {
450           static const GdkRGBA transparent = { 0, 0, 0, 0 };
451           scopy->color.rgba = transparent;
452         }
453       
454       _gtk_css_number_compute (&scopy->offset, &stop->offset, context);
455     }
456
457   return GTK_CSS_IMAGE (copy);
458 }
459
460 static void
461 gtk_css_image_linear_dispose (GObject *object)
462 {
463   GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (object);
464
465   if (linear->stops)
466     {
467       if (!linear->is_computed)
468         {
469           guint i;
470
471           for (i = 0; i < linear->stops->len; i++)
472             {
473               GtkCssImageLinearColorStop *stop;
474
475               stop = &g_array_index (linear->stops, GtkCssImageLinearColorStop, i);
476               
477               gtk_symbolic_color_unref (stop->color.symbolic);
478             }
479         }
480       g_array_free (linear->stops, TRUE);
481       linear->stops = NULL;
482     }
483
484   G_OBJECT_CLASS (_gtk_css_image_linear_parent_class)->dispose (object);
485 }
486
487 static void
488 _gtk_css_image_linear_class_init (GtkCssImageLinearClass *klass)
489 {
490   GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
491   GObjectClass *object_class = G_OBJECT_CLASS (klass);
492
493   image_class->draw = gtk_css_image_linear_draw;
494   image_class->parse = gtk_css_image_linear_parse;
495   image_class->print = gtk_css_image_linear_print;
496   image_class->compute = gtk_css_image_linear_compute;
497
498   object_class->dispose = gtk_css_image_linear_dispose;
499 }
500
501 static void
502 _gtk_css_image_linear_init (GtkCssImageLinear *linear)
503 {
504   linear->stops = g_array_new (FALSE, FALSE, sizeof (GtkCssImageLinearColorStop));
505
506   _gtk_css_number_init (&linear->angle, 1 << GTK_CSS_BOTTOM, GTK_CSS_NUMBER);
507 }
508