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 "gtkcssimageconicprivate.h"
23
24#include <math.h>
25
26#include "gtkcsscolorvalueprivate.h"
27#include "gtkcssnumbervalueprivate.h"
28#include "gtkcsspositionvalueprivate.h"
29#include "gtkcssprovider.h"
30
31G_DEFINE_TYPE (GtkCssImageConic, gtk_css_image_conic, GTK_TYPE_CSS_IMAGE)
32
33static void
34gtk_css_image_conic_snapshot (GtkCssImage *image,
35 GtkSnapshot *snapshot,
36 double width,
37 double height)
38{
39 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (image);
40 GskColorStop *stops;
41 int i, last;
42 double offset;
43
44 stops = g_newa (GskColorStop, self->n_stops);
45
46 last = -1;
47 offset = 0;
48 for (i = 0; i < self->n_stops; i++)
49 {
50 const GtkCssImageConicColorStop *stop = &self->color_stops[i];
51 double pos, step;
52
53 if (stop->offset == NULL)
54 {
55 if (i == 0)
56 pos = 0.0;
57 else if (i + 1 == self->n_stops)
58 pos = 1.0;
59 else
60 continue;
61 }
62 else
63 {
64 pos = _gtk_css_number_value_get (number: stop->offset, one_hundred_percent: 360) / 360;
65 pos = CLAMP (pos, 0.0, 1.0);
66 }
67
68 pos = MAX (pos, offset);
69 step = (pos - offset) / (i - last);
70 for (last = last + 1; last <= i; last++)
71 {
72 stop = &self->color_stops[last];
73
74 offset += step;
75
76 stops[last].offset = offset;
77 stops[last].color = *gtk_css_color_value_get_rgba (color: stop->color);
78 }
79
80 offset = pos;
81 last = i;
82 }
83
84 gtk_snapshot_append_conic_gradient (
85 snapshot,
86 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height),
87 center: &GRAPHENE_POINT_INIT (_gtk_css_position_value_get_x (self->center, width),
88 _gtk_css_position_value_get_y (self->center, height)),
89 rotation: _gtk_css_number_value_get (number: self->rotation, one_hundred_percent: 360),
90 stops,
91 n_stops: self->n_stops);
92}
93
94static gboolean
95parse_angles (GtkCssParser *parser,
96 gpointer option_data,
97 gpointer unused)
98{
99 GtkCssValue **angles = option_data;
100
101 angles[0] = _gtk_css_number_value_parse (parser, flags: GTK_CSS_PARSE_ANGLE | GTK_CSS_PARSE_PERCENT);
102 if (angles[0] == NULL)
103 return FALSE;
104
105 if (gtk_css_number_value_can_parse (parser))
106 {
107 angles[1] = _gtk_css_number_value_parse (parser, flags: GTK_CSS_PARSE_ANGLE | GTK_CSS_PARSE_PERCENT);
108 if (angles[1] == NULL)
109 return FALSE;
110 }
111
112 return TRUE;
113}
114
115static gboolean
116parse_color (GtkCssParser *parser,
117 gpointer option_data,
118 gpointer unused)
119{
120 GtkCssValue **color = option_data;
121
122 *color = _gtk_css_color_value_parse (parser);
123 if (*color == NULL)
124 return FALSE;
125
126 return TRUE;
127}
128
129static guint
130gtk_css_image_conic_parse_color_stop (GtkCssImageConic *self,
131 GtkCssParser *parser,
132 GArray *stop_array)
133{
134 GtkCssValue *angles[2] = { NULL, NULL };
135 GtkCssValue *color = NULL;
136 GtkCssParseOption options[] =
137 {
138 { (void *) gtk_css_number_value_can_parse, parse_angles, &angles },
139 { (void *) gtk_css_color_value_can_parse, parse_color, &color },
140 };
141
142 if (!gtk_css_parser_consume_any (parser, options, G_N_ELEMENTS (options), NULL))
143 goto fail;
144
145 if (color == NULL)
146 {
147 gtk_css_parser_error_syntax (self: parser, format: "Expected shadow value to contain a length");
148 goto fail;
149 }
150
151 g_array_append_vals (array: stop_array, data: (GtkCssImageConicColorStop[1]) {
152 { angles[0], color }
153 },
154 len: 1);
155 if (angles[1])
156 g_array_append_vals (array: stop_array, data: (GtkCssImageConicColorStop[1]) {
157 { .offset: angles[1], .color: gtk_css_value_ref (value: color) }
158 },
159 len: 1);
160
161 return 1;
162
163fail:
164 g_clear_pointer (&angles[0], gtk_css_value_unref);
165 g_clear_pointer (&angles[1], gtk_css_value_unref);
166 g_clear_pointer (&color, gtk_css_value_unref);
167 return 0;
168}
169
170static guint
171gtk_css_image_conic_parse_first_arg (GtkCssImageConic *self,
172 GtkCssParser *parser,
173 GArray *stop_array)
174{
175 gboolean nothing_parsed = TRUE;
176
177 if (gtk_css_parser_try_ident (self: parser, ident: "from"))
178 {
179 self->rotation = _gtk_css_number_value_parse (parser, flags: GTK_CSS_PARSE_ANGLE);
180 if (self->rotation == NULL)
181 return 0;
182 nothing_parsed = FALSE;
183 }
184 else
185 {
186 self->rotation = _gtk_css_number_value_new (value: 0, unit: GTK_CSS_DEG);
187 }
188
189 if (gtk_css_parser_try_ident (self: parser, ident: "at"))
190 {
191 self->center = _gtk_css_position_value_parse (parser);
192 if (self->center == NULL)
193 return 0;
194 nothing_parsed = FALSE;
195 }
196 else
197 {
198 self->center = _gtk_css_position_value_new (x: _gtk_css_number_value_new (value: 50, unit: GTK_CSS_PERCENT),
199 y: _gtk_css_number_value_new (value: 50, unit: GTK_CSS_PERCENT));
200 }
201
202 if (!nothing_parsed)
203 return 1;
204
205 return 1 + gtk_css_image_conic_parse_color_stop (self, parser, stop_array);
206}
207
208typedef struct
209{
210 GtkCssImageConic *self;
211 GArray *stop_array;
212} ParseData;
213
214static guint
215gtk_css_image_conic_parse_arg (GtkCssParser *parser,
216 guint arg,
217 gpointer user_data)
218{
219 ParseData *parse_data = user_data;
220 GtkCssImageConic *self = parse_data->self;
221
222 if (arg == 0)
223 return gtk_css_image_conic_parse_first_arg (self, parser, stop_array: parse_data->stop_array);
224 else
225 return gtk_css_image_conic_parse_color_stop (self, parser, stop_array: parse_data->stop_array);
226}
227
228static gboolean
229gtk_css_image_conic_parse (GtkCssImage *image,
230 GtkCssParser *parser)
231{
232 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (image);
233 ParseData parse_data;
234 gboolean success;
235
236 if (!gtk_css_parser_has_function (self: parser, name: "conic-gradient"))
237 {
238 gtk_css_parser_error_syntax (self: parser, format: "Not a conic gradient");
239 return FALSE;
240 }
241
242 parse_data.self = self;
243 parse_data.stop_array = g_array_new (TRUE, FALSE, element_size: sizeof (GtkCssImageConicColorStop));
244
245 success = gtk_css_parser_consume_function (self: parser, min_args: 3, G_MAXUINT, parse_func: gtk_css_image_conic_parse_arg, data: &parse_data);
246
247 if (!success)
248 {
249 g_array_free (array: parse_data.stop_array, TRUE);
250 }
251 else
252 {
253 self->n_stops = parse_data.stop_array->len;
254 self->color_stops = (GtkCssImageConicColorStop *)g_array_free (array: parse_data.stop_array, FALSE);
255 }
256
257 return success;
258}
259
260static void
261gtk_css_image_conic_print (GtkCssImage *image,
262 GString *string)
263{
264 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (image);
265 gboolean written = FALSE;
266 guint i;
267
268 g_string_append (string, val: "self-gradient(");
269
270 if (self->center)
271 {
272 GtkCssValue *compare = _gtk_css_position_value_new (x: _gtk_css_number_value_new (value: 50, unit: GTK_CSS_PERCENT),
273 y: _gtk_css_number_value_new (value: 50, unit: GTK_CSS_PERCENT));
274
275 if (!_gtk_css_value_equal (value1: self->center, value2: compare))
276 {
277 g_string_append (string, val: "from ");
278 _gtk_css_value_print (value: self->center, string);
279 written = TRUE;
280 }
281
282 gtk_css_value_unref (value: compare);
283 }
284
285 if (self->rotation && _gtk_css_number_value_get (number: self->rotation, one_hundred_percent: 360) != 0)
286 {
287 if (written)
288 g_string_append_c (string, ' ');
289 g_string_append (string, val: "at ");
290 _gtk_css_value_print (value: self->rotation, string);
291 }
292
293 if (written)
294 g_string_append (string, val: ", ");
295
296 for (i = 0; i < self->n_stops; i++)
297 {
298 const GtkCssImageConicColorStop *stop = &self->color_stops[i];
299
300 if (i > 0)
301 g_string_append (string, val: ", ");
302
303 _gtk_css_value_print (value: stop->color, string);
304
305 if (stop->offset)
306 {
307 g_string_append (string, val: " ");
308 _gtk_css_value_print (value: stop->offset, string);
309 }
310 }
311
312 g_string_append (string, val: ")");
313}
314
315static GtkCssImage *
316gtk_css_image_conic_compute (GtkCssImage *image,
317 guint property_id,
318 GtkStyleProvider *provider,
319 GtkCssStyle *style,
320 GtkCssStyle *parent_style)
321{
322 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (image);
323 GtkCssImageConic *copy;
324 guint i;
325
326 copy = g_object_new (GTK_TYPE_CSS_IMAGE_CONIC, NULL);
327
328 copy->center = _gtk_css_value_compute (value: self->center, property_id, provider, style, parent_style);
329 copy->rotation = _gtk_css_value_compute (value: self->rotation, property_id, provider, style, parent_style);
330
331 copy->n_stops = self->n_stops;
332 copy->color_stops = g_malloc (n_bytes: sizeof (GtkCssImageConicColorStop) * copy->n_stops);
333 for (i = 0; i < self->n_stops; i++)
334 {
335 const GtkCssImageConicColorStop *stop = &self->color_stops[i];
336 GtkCssImageConicColorStop *scopy = &copy->color_stops[i];
337
338 scopy->color = _gtk_css_value_compute (value: stop->color, property_id, provider, style, parent_style);
339
340 if (stop->offset)
341 {
342 scopy->offset = _gtk_css_value_compute (value: stop->offset, property_id, provider, style, parent_style);
343 }
344 else
345 {
346 scopy->offset = NULL;
347 }
348 }
349
350 return GTK_CSS_IMAGE (copy);
351}
352
353static GtkCssImage *
354gtk_css_image_conic_transition (GtkCssImage *start_image,
355 GtkCssImage *end_image,
356 guint property_id,
357 double progress)
358{
359 GtkCssImageConic *start, *end, *result;
360 guint i;
361
362 start = GTK_CSS_IMAGE_CONIC (start_image);
363
364 if (end_image == NULL)
365 return GTK_CSS_IMAGE_CLASS (gtk_css_image_conic_parent_class)->transition (start_image, end_image, property_id, progress);
366
367 if (!GTK_IS_CSS_IMAGE_CONIC (end_image))
368 return GTK_CSS_IMAGE_CLASS (gtk_css_image_conic_parent_class)->transition (start_image, end_image, property_id, progress);
369
370 end = GTK_CSS_IMAGE_CONIC (end_image);
371
372 if (start->n_stops != end->n_stops)
373 return GTK_CSS_IMAGE_CLASS (gtk_css_image_conic_parent_class)->transition (start_image, end_image, property_id, progress);
374
375 result = g_object_new (GTK_TYPE_CSS_IMAGE_CONIC, NULL);
376
377 result->center = _gtk_css_value_transition (start: start->center, end: end->center, property_id, progress);
378 if (result->center == NULL)
379 goto fail;
380
381 result->rotation = _gtk_css_value_transition (start: start->rotation, end: end->rotation, property_id, progress);
382 if (result->rotation == NULL)
383 goto fail;
384
385 result->color_stops = g_malloc (n_bytes: sizeof (GtkCssImageConicColorStop) * start->n_stops);
386 result->n_stops = 0;
387 for (i = 0; i < start->n_stops; i++)
388 {
389 const GtkCssImageConicColorStop *start_stop = &start->color_stops[i];
390 const GtkCssImageConicColorStop *end_stop = &end->color_stops[i];
391 GtkCssImageConicColorStop *stop = &result->color_stops[i];
392
393 if ((start_stop->offset != NULL) != (end_stop->offset != NULL))
394 goto fail;
395
396 if (start_stop->offset == NULL)
397 {
398 stop->offset = NULL;
399 }
400 else
401 {
402 stop->offset = _gtk_css_value_transition (start: start_stop->offset,
403 end: end_stop->offset,
404 property_id,
405 progress);
406 if (stop->offset == NULL)
407 goto fail;
408 }
409
410 stop->color = _gtk_css_value_transition (start: start_stop->color,
411 end: end_stop->color,
412 property_id,
413 progress);
414 if (stop->color == NULL)
415 {
416 if (stop->offset)
417 _gtk_css_value_unref (value: stop->offset);
418 goto fail;
419 }
420
421 result->n_stops ++;
422 }
423
424 return GTK_CSS_IMAGE (result);
425
426fail:
427 g_object_unref (object: result);
428 return GTK_CSS_IMAGE_CLASS (gtk_css_image_conic_parent_class)->transition (start_image, end_image, property_id, progress);
429}
430
431static gboolean
432gtk_css_image_conic_equal (GtkCssImage *image1,
433 GtkCssImage *image2)
434{
435 GtkCssImageConic *conic1 = (GtkCssImageConic *) image1;
436 GtkCssImageConic *conic2 = (GtkCssImageConic *) image2;
437 guint i;
438
439 if (!_gtk_css_value_equal (value1: conic1->center, value2: conic2->center) ||
440 !_gtk_css_value_equal (value1: conic1->rotation, value2: conic2->rotation))
441 return FALSE;
442
443 for (i = 0; i < conic1->n_stops; i++)
444 {
445 const GtkCssImageConicColorStop *stop1 = &conic1->color_stops[i];
446 const GtkCssImageConicColorStop *stop2 = &conic2->color_stops[i];
447
448 if (!_gtk_css_value_equal0 (value1: stop1->offset, value2: stop2->offset) ||
449 !_gtk_css_value_equal (value1: stop1->color, value2: stop2->color))
450 return FALSE;
451 }
452
453 return TRUE;
454}
455
456static void
457gtk_css_image_conic_dispose (GObject *object)
458{
459 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (object);
460 guint i;
461
462 for (i = 0; i < self->n_stops; i ++)
463 {
464 GtkCssImageConicColorStop *stop = &self->color_stops[i];
465
466 _gtk_css_value_unref (value: stop->color);
467 if (stop->offset)
468 _gtk_css_value_unref (value: stop->offset);
469 }
470 g_free (mem: self->color_stops);
471
472 g_clear_pointer (&self->center, gtk_css_value_unref);
473 g_clear_pointer (&self->rotation, gtk_css_value_unref);
474
475 G_OBJECT_CLASS (gtk_css_image_conic_parent_class)->dispose (object);
476}
477
478static gboolean
479gtk_css_image_conic_is_computed (GtkCssImage *image)
480{
481 GtkCssImageConic *self = GTK_CSS_IMAGE_CONIC (image);
482 guint i;
483 gboolean computed = TRUE;
484
485 computed = !self->center || gtk_css_value_is_computed (value: self->center);
486 computed &= !self->rotation || gtk_css_value_is_computed (value: self->rotation);
487
488 for (i = 0; i < self->n_stops; i ++)
489 {
490 const GtkCssImageConicColorStop *stop = &self->color_stops[i];
491
492 if (stop->offset && !gtk_css_value_is_computed (value: stop->offset))
493 {
494 computed = FALSE;
495 break;
496 }
497
498 if (!gtk_css_value_is_computed (value: stop->color))
499 {
500 computed = FALSE;
501 break;
502 }
503 }
504
505 return computed;
506}
507
508static void
509gtk_css_image_conic_class_init (GtkCssImageConicClass *klass)
510{
511 GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
512 GObjectClass *object_class = G_OBJECT_CLASS (klass);
513
514 image_class->snapshot = gtk_css_image_conic_snapshot;
515 image_class->parse = gtk_css_image_conic_parse;
516 image_class->print = gtk_css_image_conic_print;
517 image_class->compute = gtk_css_image_conic_compute;
518 image_class->equal = gtk_css_image_conic_equal;
519 image_class->transition = gtk_css_image_conic_transition;
520 image_class->is_computed = gtk_css_image_conic_is_computed;
521
522 object_class->dispose = gtk_css_image_conic_dispose;
523}
524
525static void
526gtk_css_image_conic_init (GtkCssImageConic *self)
527{
528}
529
530

source code of gtk/gtk/gtkcssimageconic.c