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 "gtkcsscolorvalueprivate.h"
29#include "gtkcssprovider.h"
30
31G_DEFINE_TYPE (GtkCssImageLinear, _gtk_css_image_linear, GTK_TYPE_CSS_IMAGE)
32
33static void
34gtk_css_image_linear_get_repeating_start_end (GtkCssImageLinear *linear,
35 double length,
36 double *start,
37 double *end)
38{
39 const GtkCssImageLinearColorStop *stop;
40 double pos;
41 guint i;
42
43 g_assert (linear->repeating);
44
45 stop = &linear->color_stops[0];
46 if (stop->offset == NULL)
47 *start = 0;
48 else
49 *start = _gtk_css_number_value_get (number: stop->offset, one_hundred_percent: length) / length;
50
51 *end = *start;
52
53 for (i = 0; i < linear->n_stops; i++)
54 {
55 stop = &linear->color_stops[i];
56
57 if (stop->offset == NULL)
58 continue;
59
60 pos = _gtk_css_number_value_get (number: stop->offset, one_hundred_percent: length) / length;
61
62 *end = MAX (pos, *end);
63 }
64
65 if (stop->offset == NULL)
66 *end = MAX (*end, 1.0);
67}
68
69static void
70gtk_css_image_linear_compute_start_point (double angle_in_degrees,
71 double width,
72 double height,
73 double *out_x,
74 double *out_y,
75 double *out_length)
76{
77 double angle, c, slope, perpendicular;
78 double x, y, length;
79
80 angle = fmod (x: angle_in_degrees, y: 360);
81 if (angle < 0)
82 angle += 360;
83
84 if (angle == 0)
85 {
86 *out_x = 0;
87 *out_y = -height;
88 *out_length = height;
89 return;
90 }
91 else if (angle == 90)
92 {
93 *out_x = width;
94 *out_y = 0;
95 *out_length = width;
96 return;
97 }
98 else if (angle == 180)
99 {
100 *out_x = 0;
101 *out_y = height;
102 *out_length = height;
103 return;
104 }
105 else if (angle == 270)
106 {
107 *out_x = -width;
108 *out_y = 0;
109 *out_length = width;
110 return;
111 }
112
113 /* The tan() is confusing because the angle is clockwise
114 * from 'to top' */
115 perpendicular = tan (x: 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 length = sqrt (x: x * x + y * y);
130
131 *out_x = x;
132 *out_y = y;
133 *out_length = length;
134}
135
136static void
137gtk_css_image_linear_snapshot (GtkCssImage *image,
138 GtkSnapshot *snapshot,
139 double width,
140 double height)
141{
142 GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
143 GskColorStop *stops;
144 double angle; /* actual angle of the gradiant line in degrees */
145 double x, y; /* coordinates of start point */
146 double length; /* distance in pixels for 100% */
147 double start, end; /* position of first/last point on gradient line - with gradient line being [0, 1] */
148 double offset;
149 int i, last;
150
151 if (linear->side)
152 {
153 /* special casing the regular cases here so we don't get rounding errors */
154 switch (linear->side)
155 {
156 case 1 << GTK_CSS_RIGHT:
157 angle = 90;
158 break;
159 case 1 << GTK_CSS_LEFT:
160 angle = 270;
161 break;
162 case 1 << GTK_CSS_TOP:
163 angle = 0;
164 break;
165 case 1 << GTK_CSS_BOTTOM:
166 angle = 180;
167 break;
168 default:
169 angle = atan2 (y: linear->side & 1 << GTK_CSS_TOP ? -width : width,
170 x: linear->side & 1 << GTK_CSS_LEFT ? -height : height);
171 angle = 180 * angle / G_PI + 90;
172 break;
173 }
174 }
175 else
176 {
177 angle = _gtk_css_number_value_get (number: linear->angle, one_hundred_percent: 100);
178 }
179
180 gtk_css_image_linear_compute_start_point (angle_in_degrees: angle,
181 width, height,
182 out_x: &x, out_y: &y,
183 out_length: &length);
184
185 if (linear->repeating)
186 {
187 gtk_css_image_linear_get_repeating_start_end (linear, length, start: &start, end: &end);
188
189 if (start == end)
190 {
191 /* repeating gradients with all color stops sharing the same offset
192 * get the color of the last color stop */
193 const GtkCssImageLinearColorStop *stop = &linear->color_stops[linear->n_stops - 1];
194
195 gtk_snapshot_append_color (snapshot,
196 color: gtk_css_color_value_get_rgba (color: stop->color),
197 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height));
198 return;
199 }
200 }
201 else
202 {
203 start = 0;
204 end = 1;
205 }
206
207 offset = start;
208 last = -1;
209 stops = g_newa (GskColorStop, linear->n_stops);
210
211 for (i = 0; i < linear->n_stops; i++)
212 {
213 const GtkCssImageLinearColorStop *stop = &linear->color_stops[i];
214 double pos, step;
215
216 if (stop->offset == NULL)
217 {
218 if (i == 0)
219 pos = 0.0;
220 else if (i + 1 == linear->n_stops)
221 pos = 1.0;
222 else
223 continue;
224 }
225 else
226 {
227 pos = _gtk_css_number_value_get (number: stop->offset, one_hundred_percent: length) / length;
228 pos = CLAMP (pos, 0.0, 1.0);
229 }
230
231 pos = MAX (pos, offset);
232 step = (pos - offset) / (i - last);
233 for (last = last + 1; last <= i; last++)
234 {
235 stop = &linear->color_stops[last];
236
237 offset += step;
238
239 stops[last].offset = (offset - start) / (end - start);
240 stops[last].color = *gtk_css_color_value_get_rgba (color: stop->color);
241 }
242
243 offset = pos;
244 last = i;
245 }
246
247 if (linear->repeating)
248 {
249 gtk_snapshot_append_repeating_linear_gradient (
250 snapshot,
251 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height),
252 start_point: &GRAPHENE_POINT_INIT (width / 2 + x * (start - 0.5), height / 2 + y * (start - 0.5)),
253 end_point: &GRAPHENE_POINT_INIT (width / 2 + x * (end - 0.5), height / 2 + y * (end - 0.5)),
254 stops,
255 n_stops: linear->n_stops);
256 }
257 else
258 {
259 gtk_snapshot_append_linear_gradient (
260 snapshot,
261 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height),
262 start_point: &GRAPHENE_POINT_INIT (width / 2 + x * (start - 0.5), height / 2 + y * (start - 0.5)),
263 end_point: &GRAPHENE_POINT_INIT (width / 2 + x * (end - 0.5), height / 2 + y * (end - 0.5)),
264 stops,
265 n_stops: linear->n_stops);
266 }
267}
268
269static guint
270gtk_css_image_linear_parse_color_stop (GtkCssImageLinear *self,
271 GtkCssParser *parser,
272 GArray *stop_array)
273{
274 GtkCssImageLinearColorStop stop;
275
276 stop.color = _gtk_css_color_value_parse (parser);
277 if (stop.color == NULL)
278 return 0;
279
280 if (gtk_css_number_value_can_parse (parser))
281 {
282 stop.offset = _gtk_css_number_value_parse (parser,
283 flags: GTK_CSS_PARSE_PERCENT
284 | GTK_CSS_PARSE_LENGTH);
285 if (stop.offset == NULL)
286 {
287 _gtk_css_value_unref (value: stop.color);
288 return 0;
289 }
290 }
291 else
292 {
293 stop.offset = NULL;
294 }
295
296 g_array_append_val (stop_array, stop);
297
298 return 1;
299}
300
301static guint
302gtk_css_image_linear_parse_first_arg (GtkCssImageLinear *linear,
303 GtkCssParser *parser,
304 GArray *stop_array)
305{
306 guint i;
307
308 if (gtk_css_parser_try_ident (self: parser, ident: "to"))
309 {
310 for (i = 0; i < 2; i++)
311 {
312 if (gtk_css_parser_try_ident (self: parser, ident: "left"))
313 {
314 if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
315 {
316 gtk_css_parser_error_syntax (self: parser, format: "Expected 'top', 'bottom' or comma");
317 return 0;
318 }
319 linear->side |= (1 << GTK_CSS_LEFT);
320 }
321 else if (gtk_css_parser_try_ident (self: parser, ident: "right"))
322 {
323 if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT)))
324 {
325 gtk_css_parser_error_syntax (self: parser, format: "Expected 'top', 'bottom' or comma");
326 return 0;
327 }
328 linear->side |= (1 << GTK_CSS_RIGHT);
329 }
330 else if (gtk_css_parser_try_ident (self: parser, ident: "top"))
331 {
332 if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
333 {
334 gtk_css_parser_error_syntax (self: parser, format: "Expected 'left', 'right' or comma");
335 return 0;
336 }
337 linear->side |= (1 << GTK_CSS_TOP);
338 }
339 else if (gtk_css_parser_try_ident (self: parser, ident: "bottom"))
340 {
341 if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM)))
342 {
343 gtk_css_parser_error_syntax (self: parser, format: "Expected 'left', 'right' or comma");
344 return 0;
345 }
346 linear->side |= (1 << GTK_CSS_BOTTOM);
347 }
348 else
349 break;
350 }
351
352 if (linear->side == 0)
353 {
354 gtk_css_parser_error_syntax (self: parser, format: "Expected side that gradient should go to");
355 return 0;
356 }
357
358 return 1;
359 }
360 else if (gtk_css_number_value_can_parse (parser))
361 {
362 linear->angle = _gtk_css_number_value_parse (parser, flags: GTK_CSS_PARSE_ANGLE);
363 if (linear->angle == NULL)
364 return 0;
365
366 return 1;
367 }
368 else
369 {
370 linear->side = 1 << GTK_CSS_BOTTOM;
371 if (!gtk_css_image_linear_parse_color_stop (self: linear, parser, stop_array))
372 return 0;
373
374 return 2;
375 }
376}
377
378typedef struct
379{
380 GtkCssImageLinear *self;
381 GArray *stop_array;
382} ParseData;
383
384static guint
385gtk_css_image_linear_parse_arg (GtkCssParser *parser,
386 guint arg,
387 gpointer user_data)
388{
389 ParseData *parse_data = user_data;
390 GtkCssImageLinear *self = parse_data->self;
391
392 if (arg == 0)
393 return gtk_css_image_linear_parse_first_arg (linear: self, parser, stop_array: parse_data->stop_array);
394 else
395 return gtk_css_image_linear_parse_color_stop (self, parser, stop_array: parse_data->stop_array);
396}
397
398static gboolean
399gtk_css_image_linear_parse (GtkCssImage *image,
400 GtkCssParser *parser)
401{
402 GtkCssImageLinear *self = GTK_CSS_IMAGE_LINEAR (image);
403 ParseData parse_data;
404 gboolean success;
405
406 if (gtk_css_parser_has_function (self: parser, name: "repeating-linear-gradient"))
407 self->repeating = TRUE;
408 else if (gtk_css_parser_has_function (self: parser, name: "linear-gradient"))
409 self->repeating = FALSE;
410 else
411 {
412 gtk_css_parser_error_syntax (self: parser, format: "Not a linear gradient");
413 return FALSE;
414 }
415
416 parse_data.self = self;
417 parse_data.stop_array = g_array_new (TRUE, FALSE, element_size: sizeof (GtkCssImageLinearColorStop));
418
419 success = gtk_css_parser_consume_function (self: parser, min_args: 3, G_MAXUINT, parse_func: gtk_css_image_linear_parse_arg, data: &parse_data);
420
421 if (!success)
422 {
423 g_array_free (array: parse_data.stop_array, TRUE);
424 }
425 else
426 {
427 self->n_stops = parse_data.stop_array->len;
428 self->color_stops = (GtkCssImageLinearColorStop *)g_array_free (array: parse_data.stop_array, FALSE);
429 }
430
431 return success;
432}
433
434static void
435gtk_css_image_linear_print (GtkCssImage *image,
436 GString *string)
437{
438 GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
439 guint i;
440
441 if (linear->repeating)
442 g_string_append (string, val: "repeating-linear-gradient(");
443 else
444 g_string_append (string, val: "linear-gradient(");
445
446 if (linear->side)
447 {
448 if (linear->side != (1 << GTK_CSS_BOTTOM))
449 {
450 g_string_append (string, val: "to");
451
452 if (linear->side & (1 << GTK_CSS_TOP))
453 g_string_append (string, val: " top");
454 else if (linear->side & (1 << GTK_CSS_BOTTOM))
455 g_string_append (string, val: " bottom");
456
457 if (linear->side & (1 << GTK_CSS_LEFT))
458 g_string_append (string, val: " left");
459 else if (linear->side & (1 << GTK_CSS_RIGHT))
460 g_string_append (string, val: " right");
461
462 g_string_append (string, val: ", ");
463 }
464 }
465 else
466 {
467 _gtk_css_value_print (value: linear->angle, string);
468 g_string_append (string, val: ", ");
469 }
470
471 for (i = 0; i < linear->n_stops; i++)
472 {
473 const GtkCssImageLinearColorStop *stop = &linear->color_stops[i];
474
475 if (i > 0)
476 g_string_append (string, val: ", ");
477
478 _gtk_css_value_print (value: stop->color, string);
479
480 if (stop->offset)
481 {
482 g_string_append (string, val: " ");
483 _gtk_css_value_print (value: stop->offset, string);
484 }
485 }
486
487 g_string_append (string, val: ")");
488}
489
490static GtkCssImage *
491gtk_css_image_linear_compute (GtkCssImage *image,
492 guint property_id,
493 GtkStyleProvider *provider,
494 GtkCssStyle *style,
495 GtkCssStyle *parent_style)
496{
497 GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
498 GtkCssImageLinear *copy;
499 guint i;
500
501 copy = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
502 copy->repeating = linear->repeating;
503 copy->side = linear->side;
504
505 if (linear->angle)
506 copy->angle = _gtk_css_value_compute (value: linear->angle, property_id, provider, style, parent_style);
507
508 copy->n_stops = linear->n_stops;
509 copy->color_stops = g_malloc (n_bytes: sizeof (GtkCssImageLinearColorStop) * copy->n_stops);
510 for (i = 0; i < linear->n_stops; i++)
511 {
512 const GtkCssImageLinearColorStop *stop = &linear->color_stops[i];
513 GtkCssImageLinearColorStop *scopy = &copy->color_stops[i];
514
515 scopy->color = _gtk_css_value_compute (value: stop->color, property_id, provider, style, parent_style);
516
517 if (stop->offset)
518 {
519 scopy->offset = _gtk_css_value_compute (value: stop->offset, property_id, provider, style, parent_style);
520 }
521 else
522 {
523 scopy->offset = NULL;
524 }
525 }
526
527 return GTK_CSS_IMAGE (copy);
528}
529
530static GtkCssImage *
531gtk_css_image_linear_transition (GtkCssImage *start_image,
532 GtkCssImage *end_image,
533 guint property_id,
534 double progress)
535{
536 GtkCssImageLinear *start, *end, *result;
537 guint i;
538
539 start = GTK_CSS_IMAGE_LINEAR (start_image);
540
541 if (end_image == NULL)
542 return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
543
544 if (!GTK_IS_CSS_IMAGE_LINEAR (end_image))
545 return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
546
547 end = GTK_CSS_IMAGE_LINEAR (end_image);
548
549 if ((start->repeating != end->repeating)
550 || (start->n_stops != end->n_stops))
551 return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
552
553 result = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
554 result->repeating = start->repeating;
555
556 if (start->side != end->side)
557 goto fail;
558
559 result->side = start->side;
560 if (result->side == 0)
561 result->angle = _gtk_css_value_transition (start: start->angle, end: end->angle, property_id, progress);
562 if (result->angle == NULL)
563 goto fail;
564
565 /* Maximum amountof stops */
566 result->color_stops = g_malloc (n_bytes: sizeof (GtkCssImageLinearColorStop) * start->n_stops);
567 result->n_stops = 0;
568 for (i = 0; i < start->n_stops; i++)
569 {
570 const GtkCssImageLinearColorStop *start_stop = &start->color_stops[i];
571 const GtkCssImageLinearColorStop *end_stop = &end->color_stops[i];
572 GtkCssImageLinearColorStop *stop = &result->color_stops[i];
573
574 if ((start_stop->offset != NULL) != (end_stop->offset != NULL))
575 goto fail;
576
577 if (start_stop->offset == NULL)
578 {
579 stop->offset = NULL;
580 }
581 else
582 {
583 stop->offset = _gtk_css_value_transition (start: start_stop->offset,
584 end: end_stop->offset,
585 property_id,
586 progress);
587 if (stop->offset == NULL)
588 goto fail;
589 }
590
591 stop->color = _gtk_css_value_transition (start: start_stop->color,
592 end: end_stop->color,
593 property_id,
594 progress);
595 if (stop->color == NULL)
596 {
597 if (stop->offset)
598 _gtk_css_value_unref (value: stop->offset);
599 goto fail;
600 }
601
602 result->n_stops ++;
603 }
604
605 return GTK_CSS_IMAGE (result);
606
607fail:
608 g_object_unref (object: result);
609 return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress);
610}
611
612static gboolean
613gtk_css_image_linear_equal (GtkCssImage *image1,
614 GtkCssImage *image2)
615{
616 GtkCssImageLinear *linear1 = (GtkCssImageLinear *) image1;
617 GtkCssImageLinear *linear2 = (GtkCssImageLinear *) image2;
618 guint i;
619
620 if (linear1->repeating != linear2->repeating ||
621 linear1->side != linear2->side ||
622 (linear1->side == 0 && !_gtk_css_value_equal (value1: linear1->angle, value2: linear2->angle)) ||
623 linear1->n_stops != linear2->n_stops)
624 return FALSE;
625
626 for (i = 0; i < linear1->n_stops; i++)
627 {
628 const GtkCssImageLinearColorStop *stop1 = &linear1->color_stops[i];
629 const GtkCssImageLinearColorStop *stop2 = &linear2->color_stops[i];
630
631 if (!_gtk_css_value_equal0 (value1: stop1->offset, value2: stop2->offset) ||
632 !_gtk_css_value_equal (value1: stop1->color, value2: stop2->color))
633 return FALSE;
634 }
635
636 return TRUE;
637}
638
639static void
640gtk_css_image_linear_dispose (GObject *object)
641{
642 GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (object);
643 guint i;
644
645 for (i = 0; i < linear->n_stops; i ++)
646 {
647 GtkCssImageLinearColorStop *stop = &linear->color_stops[i];
648
649 _gtk_css_value_unref (value: stop->color);
650 if (stop->offset)
651 _gtk_css_value_unref (value: stop->offset);
652 }
653 g_free (mem: linear->color_stops);
654
655 linear->side = 0;
656 if (linear->angle)
657 {
658 _gtk_css_value_unref (value: linear->angle);
659 linear->angle = NULL;
660 }
661
662 G_OBJECT_CLASS (_gtk_css_image_linear_parent_class)->dispose (object);
663}
664
665static gboolean
666gtk_css_image_linear_is_computed (GtkCssImage *image)
667{
668 GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
669 guint i;
670 gboolean computed = TRUE;
671
672 computed = !linear->angle || gtk_css_value_is_computed (value: linear->angle);
673
674 for (i = 0; i < linear->n_stops; i ++)
675 {
676 const GtkCssImageLinearColorStop *stop = &linear->color_stops[i];
677
678 if (stop->offset && !gtk_css_value_is_computed (value: stop->offset))
679 {
680 computed = FALSE;
681 break;
682 }
683
684 if (!gtk_css_value_is_computed (value: stop->color))
685 {
686 computed = FALSE;
687 break;
688 }
689 }
690
691 return computed;
692}
693
694static void
695_gtk_css_image_linear_class_init (GtkCssImageLinearClass *klass)
696{
697 GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
698 GObjectClass *object_class = G_OBJECT_CLASS (klass);
699
700 image_class->snapshot = gtk_css_image_linear_snapshot;
701 image_class->parse = gtk_css_image_linear_parse;
702 image_class->print = gtk_css_image_linear_print;
703 image_class->compute = gtk_css_image_linear_compute;
704 image_class->equal = gtk_css_image_linear_equal;
705 image_class->transition = gtk_css_image_linear_transition;
706 image_class->is_computed = gtk_css_image_linear_is_computed;
707
708 object_class->dispose = gtk_css_image_linear_dispose;
709}
710
711static void
712_gtk_css_image_linear_init (GtkCssImageLinear *linear)
713{
714}
715
716

source code of gtk/gtk/gtkcssimagelinear.c