1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org> |
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 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 | |
18 | #include "config.h" |
19 | |
20 | #include "gtkcsscolorvalueprivate.h" |
21 | |
22 | #include "gtkcssstylepropertyprivate.h" |
23 | #include "gtkprivate.h" |
24 | #include "gtkstylepropertyprivate.h" |
25 | |
26 | #include "gdk/gdkhslaprivate.h" |
27 | #include "gdk/gdkrgbaprivate.h" |
28 | |
29 | typedef enum { |
30 | COLOR_TYPE_LITERAL, |
31 | COLOR_TYPE_NAME, |
32 | COLOR_TYPE_SHADE, |
33 | COLOR_TYPE_ALPHA, |
34 | COLOR_TYPE_MIX, |
35 | COLOR_TYPE_CURRENT_COLOR |
36 | } ColorType; |
37 | |
38 | struct _GtkCssValue |
39 | { |
40 | GTK_CSS_VALUE_BASE |
41 | ColorType type; |
42 | GtkCssValue *last_value; |
43 | |
44 | union |
45 | { |
46 | char *name; |
47 | GdkRGBA rgba; |
48 | |
49 | struct |
50 | { |
51 | GtkCssValue *color; |
52 | double factor; |
53 | } shade, alpha; |
54 | |
55 | struct |
56 | { |
57 | GtkCssValue *color1; |
58 | GtkCssValue *color2; |
59 | double factor; |
60 | } mix; |
61 | } sym_col; |
62 | }; |
63 | |
64 | static void |
65 | gtk_css_value_color_free (GtkCssValue *color) |
66 | { |
67 | if (color->last_value) |
68 | _gtk_css_value_unref (value: color->last_value); |
69 | |
70 | switch (color->type) |
71 | { |
72 | case COLOR_TYPE_NAME: |
73 | g_free (mem: color->sym_col.name); |
74 | break; |
75 | case COLOR_TYPE_SHADE: |
76 | _gtk_css_value_unref (value: color->sym_col.shade.color); |
77 | break; |
78 | case COLOR_TYPE_ALPHA: |
79 | _gtk_css_value_unref (value: color->sym_col.alpha.color); |
80 | break; |
81 | case COLOR_TYPE_MIX: |
82 | _gtk_css_value_unref (value: color->sym_col.mix.color1); |
83 | _gtk_css_value_unref (value: color->sym_col.mix.color2); |
84 | break; |
85 | case COLOR_TYPE_LITERAL: |
86 | case COLOR_TYPE_CURRENT_COLOR: |
87 | default: |
88 | break; |
89 | } |
90 | |
91 | g_slice_free (GtkCssValue, color); |
92 | } |
93 | |
94 | static GtkCssValue * |
95 | gtk_css_value_color_get_fallback (guint property_id, |
96 | GtkStyleProvider *provider, |
97 | GtkCssStyle *style, |
98 | GtkCssStyle *parent_style) |
99 | { |
100 | switch (property_id) |
101 | { |
102 | case GTK_CSS_PROPERTY_BACKGROUND_IMAGE: |
103 | case GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE: |
104 | case GTK_CSS_PROPERTY_TEXT_SHADOW: |
105 | case GTK_CSS_PROPERTY_ICON_SHADOW: |
106 | case GTK_CSS_PROPERTY_BOX_SHADOW: |
107 | return gtk_css_color_value_new_transparent (); |
108 | case GTK_CSS_PROPERTY_COLOR: |
109 | case GTK_CSS_PROPERTY_BACKGROUND_COLOR: |
110 | case GTK_CSS_PROPERTY_BORDER_TOP_COLOR: |
111 | case GTK_CSS_PROPERTY_BORDER_RIGHT_COLOR: |
112 | case GTK_CSS_PROPERTY_BORDER_BOTTOM_COLOR: |
113 | case GTK_CSS_PROPERTY_BORDER_LEFT_COLOR: |
114 | case GTK_CSS_PROPERTY_OUTLINE_COLOR: |
115 | case GTK_CSS_PROPERTY_CARET_COLOR: |
116 | case GTK_CSS_PROPERTY_SECONDARY_CARET_COLOR: |
117 | return _gtk_css_value_compute (value: _gtk_css_style_property_get_initial_value (property: _gtk_css_style_property_lookup_by_id (id: property_id)), |
118 | property_id, |
119 | provider, |
120 | style, |
121 | parent_style); |
122 | case GTK_CSS_PROPERTY_ICON_PALETTE: |
123 | return _gtk_css_value_ref (value: style->core->color); |
124 | default: |
125 | if (property_id < GTK_CSS_PROPERTY_N_PROPERTIES) |
126 | g_warning ("No fallback color defined for property '%s'" , |
127 | _gtk_style_property_get_name (GTK_STYLE_PROPERTY (_gtk_css_style_property_lookup_by_id (property_id)))); |
128 | return gtk_css_color_value_new_transparent (); |
129 | } |
130 | } |
131 | |
132 | static GtkCssValue * |
133 | gtk_css_value_color_compute (GtkCssValue *value, |
134 | guint property_id, |
135 | GtkStyleProvider *provider, |
136 | GtkCssStyle *style, |
137 | GtkCssStyle *parent_style) |
138 | { |
139 | GtkCssValue *resolved; |
140 | |
141 | /* The computed value of the ‘currentColor’ keyword is the computed |
142 | * value of the ‘color’ property. If the ‘currentColor’ keyword is |
143 | * set on the ‘color’ property itself, it is treated as ‘color: inherit’. |
144 | */ |
145 | if (property_id == GTK_CSS_PROPERTY_COLOR) |
146 | { |
147 | GtkCssValue *current; |
148 | |
149 | if (parent_style) |
150 | current = parent_style->core->color; |
151 | else |
152 | current = NULL; |
153 | |
154 | resolved = _gtk_css_color_value_resolve (color: value, |
155 | provider, |
156 | current, |
157 | NULL); |
158 | } |
159 | else if (value->type == COLOR_TYPE_LITERAL) |
160 | { |
161 | resolved = _gtk_css_value_ref (value); |
162 | } |
163 | else |
164 | { |
165 | GtkCssValue *current = style->core->color; |
166 | |
167 | resolved = _gtk_css_color_value_resolve (color: value, |
168 | provider, |
169 | current, |
170 | NULL); |
171 | } |
172 | |
173 | if (resolved == NULL) |
174 | return gtk_css_value_color_get_fallback (property_id, provider, style, parent_style); |
175 | |
176 | return resolved; |
177 | } |
178 | |
179 | static gboolean |
180 | gtk_css_value_color_equal (const GtkCssValue *value1, |
181 | const GtkCssValue *value2) |
182 | { |
183 | if (value1->type != value2->type) |
184 | return FALSE; |
185 | |
186 | switch (value1->type) |
187 | { |
188 | case COLOR_TYPE_LITERAL: |
189 | return gdk_rgba_equal (p1: &value1->sym_col.rgba, p2: &value2->sym_col.rgba); |
190 | case COLOR_TYPE_NAME: |
191 | return g_str_equal (v1: value1->sym_col.name, v2: value2->sym_col.name); |
192 | case COLOR_TYPE_SHADE: |
193 | return value1->sym_col.shade.factor == value2->sym_col.shade.factor && |
194 | _gtk_css_value_equal (value1: value1->sym_col.shade.color, |
195 | value2: value2->sym_col.shade.color); |
196 | case COLOR_TYPE_ALPHA: |
197 | return value1->sym_col.alpha.factor == value2->sym_col.alpha.factor && |
198 | _gtk_css_value_equal (value1: value1->sym_col.alpha.color, |
199 | value2: value2->sym_col.alpha.color); |
200 | case COLOR_TYPE_MIX: |
201 | return value1->sym_col.mix.factor == value2->sym_col.mix.factor && |
202 | _gtk_css_value_equal (value1: value1->sym_col.mix.color1, |
203 | value2: value2->sym_col.mix.color1) && |
204 | _gtk_css_value_equal (value1: value1->sym_col.mix.color2, |
205 | value2: value2->sym_col.mix.color2); |
206 | case COLOR_TYPE_CURRENT_COLOR: |
207 | return TRUE; |
208 | default: |
209 | g_assert_not_reached (); |
210 | return FALSE; |
211 | } |
212 | } |
213 | |
214 | static GtkCssValue * |
215 | gtk_css_value_color_transition (GtkCssValue *start, |
216 | GtkCssValue *end, |
217 | guint property_id, |
218 | double progress) |
219 | { |
220 | return _gtk_css_color_value_new_mix (color1: start, color2: end, factor: progress); |
221 | } |
222 | |
223 | static void |
224 | gtk_css_value_color_print (const GtkCssValue *value, |
225 | GString *string) |
226 | { |
227 | switch (value->type) |
228 | { |
229 | case COLOR_TYPE_LITERAL: |
230 | { |
231 | char *s = gdk_rgba_to_string (rgba: &value->sym_col.rgba); |
232 | g_string_append (string, val: s); |
233 | g_free (mem: s); |
234 | } |
235 | break; |
236 | case COLOR_TYPE_NAME: |
237 | g_string_append (string, val: "@" ); |
238 | g_string_append (string, val: value->sym_col.name); |
239 | break; |
240 | case COLOR_TYPE_SHADE: |
241 | { |
242 | char factor[G_ASCII_DTOSTR_BUF_SIZE]; |
243 | |
244 | g_string_append (string, val: "shade(" ); |
245 | _gtk_css_value_print (value: value->sym_col.shade.color, string); |
246 | g_string_append (string, val: ", " ); |
247 | g_ascii_dtostr (buffer: factor, buf_len: sizeof (factor), d: value->sym_col.shade.factor); |
248 | g_string_append (string, val: factor); |
249 | g_string_append (string, val: ")" ); |
250 | } |
251 | break; |
252 | case COLOR_TYPE_ALPHA: |
253 | { |
254 | char factor[G_ASCII_DTOSTR_BUF_SIZE]; |
255 | |
256 | g_string_append (string, val: "alpha(" ); |
257 | _gtk_css_value_print (value: value->sym_col.alpha.color, string); |
258 | g_string_append (string, val: ", " ); |
259 | g_ascii_dtostr (buffer: factor, buf_len: sizeof (factor), d: value->sym_col.alpha.factor); |
260 | g_string_append (string, val: factor); |
261 | g_string_append (string, val: ")" ); |
262 | } |
263 | break; |
264 | case COLOR_TYPE_MIX: |
265 | { |
266 | char factor[G_ASCII_DTOSTR_BUF_SIZE]; |
267 | |
268 | g_string_append (string, val: "mix(" ); |
269 | _gtk_css_value_print (value: value->sym_col.mix.color1, string); |
270 | g_string_append (string, val: ", " ); |
271 | _gtk_css_value_print (value: value->sym_col.mix.color2, string); |
272 | g_string_append (string, val: ", " ); |
273 | g_ascii_dtostr (buffer: factor, buf_len: sizeof (factor), d: value->sym_col.mix.factor); |
274 | g_string_append (string, val: factor); |
275 | g_string_append (string, val: ")" ); |
276 | } |
277 | break; |
278 | case COLOR_TYPE_CURRENT_COLOR: |
279 | g_string_append (string, val: "currentColor" ); |
280 | break; |
281 | default: |
282 | g_assert_not_reached (); |
283 | } |
284 | } |
285 | |
286 | static const GtkCssValueClass GTK_CSS_VALUE_COLOR = { |
287 | "GtkCssColorValue" , |
288 | gtk_css_value_color_free, |
289 | gtk_css_value_color_compute, |
290 | gtk_css_value_color_equal, |
291 | gtk_css_value_color_transition, |
292 | NULL, |
293 | NULL, |
294 | gtk_css_value_color_print |
295 | }; |
296 | |
297 | static void |
298 | apply_alpha (const GdkRGBA *in, |
299 | GdkRGBA *out, |
300 | double factor) |
301 | { |
302 | *out = *in; |
303 | |
304 | out->alpha = CLAMP (in->alpha * factor, 0, 1); |
305 | } |
306 | |
307 | static void |
308 | apply_shade (const GdkRGBA *in, |
309 | GdkRGBA *out, |
310 | double factor) |
311 | { |
312 | GdkHSLA hsla; |
313 | |
314 | _gdk_hsla_init_from_rgba (hsla: &hsla, rgba: in); |
315 | _gdk_hsla_shade (dest: &hsla, src: &hsla, factor); |
316 | |
317 | _gdk_rgba_init_from_hsla (rgba: out, hsla: &hsla); |
318 | } |
319 | |
320 | static inline double |
321 | transition (double start, |
322 | double end, |
323 | double progress) |
324 | { |
325 | return start + (end - start) * progress; |
326 | } |
327 | |
328 | static void |
329 | apply_mix (const GdkRGBA *in1, |
330 | const GdkRGBA *in2, |
331 | GdkRGBA *out, |
332 | double factor) |
333 | { |
334 | out->alpha = CLAMP (transition (in1->alpha, in2->alpha, factor), 0, 1); |
335 | |
336 | if (out->alpha <= 0.0) |
337 | { |
338 | out->red = out->green = out->blue = 0.0; |
339 | } |
340 | else |
341 | { |
342 | out->red = CLAMP (transition (in1->red * in1->alpha, in2->red * in2->alpha, factor), 0, 1) / out->alpha; |
343 | out->green = CLAMP (transition (in1->green * in1->alpha, in2->green * in2->alpha, factor), 0, 1) / out->alpha; |
344 | out->blue = CLAMP (transition (in1->blue * in1->alpha, in2->blue * in2->alpha, factor), 0, 1) / out->alpha; |
345 | } |
346 | } |
347 | |
348 | GtkCssValue * |
349 | _gtk_css_color_value_resolve (GtkCssValue *color, |
350 | GtkStyleProvider *provider, |
351 | GtkCssValue *current, |
352 | GSList *cycle_list) |
353 | { |
354 | GtkCssValue *value; |
355 | |
356 | gtk_internal_return_val_if_fail (color != NULL, NULL); |
357 | |
358 | switch (color->type) |
359 | { |
360 | case COLOR_TYPE_LITERAL: |
361 | return _gtk_css_value_ref (value: color); |
362 | case COLOR_TYPE_NAME: |
363 | { |
364 | GtkCssValue *named; |
365 | GSList cycle = { color, cycle_list }; |
366 | |
367 | /* If color exists in cycle_list, we're currently resolving it. |
368 | * So we've detected a cycle. */ |
369 | if (g_slist_find (list: cycle_list, data: color)) |
370 | return NULL; |
371 | |
372 | named = gtk_style_provider_get_color (provider, name: color->sym_col.name); |
373 | if (named == NULL) |
374 | return NULL; |
375 | |
376 | value = _gtk_css_color_value_resolve (color: named, provider, current, cycle_list: &cycle); |
377 | if (value == NULL) |
378 | return NULL; |
379 | } |
380 | |
381 | break; |
382 | case COLOR_TYPE_SHADE: |
383 | { |
384 | const GdkRGBA *c; |
385 | GtkCssValue *val; |
386 | GdkRGBA shade; |
387 | |
388 | val = _gtk_css_color_value_resolve (color: color->sym_col.shade.color, provider, current, cycle_list); |
389 | if (val == NULL) |
390 | return NULL; |
391 | c = gtk_css_color_value_get_rgba (color: val); |
392 | |
393 | apply_shade (in: c, out: &shade, factor: color->sym_col.shade.factor); |
394 | |
395 | value = _gtk_css_color_value_new_literal (color: &shade); |
396 | _gtk_css_value_unref (value: val); |
397 | } |
398 | |
399 | break; |
400 | case COLOR_TYPE_ALPHA: |
401 | { |
402 | const GdkRGBA *c; |
403 | GtkCssValue *val; |
404 | GdkRGBA alpha; |
405 | |
406 | val = _gtk_css_color_value_resolve (color: color->sym_col.alpha.color, provider, current, cycle_list); |
407 | if (val == NULL) |
408 | return NULL; |
409 | c = gtk_css_color_value_get_rgba (color: val); |
410 | |
411 | apply_alpha (in: c, out: &alpha, factor: color->sym_col.alpha.factor); |
412 | |
413 | value = _gtk_css_color_value_new_literal (color: &alpha); |
414 | _gtk_css_value_unref (value: val); |
415 | } |
416 | break; |
417 | |
418 | case COLOR_TYPE_MIX: |
419 | { |
420 | const GdkRGBA *color1, *color2; |
421 | GtkCssValue *val1, *val2; |
422 | GdkRGBA res; |
423 | |
424 | val1 = _gtk_css_color_value_resolve (color: color->sym_col.mix.color1, provider, current, cycle_list); |
425 | if (val1 == NULL) |
426 | return NULL; |
427 | color1 = gtk_css_color_value_get_rgba (color: val1); |
428 | |
429 | val2 = _gtk_css_color_value_resolve (color: color->sym_col.mix.color2, provider, current, cycle_list); |
430 | if (val2 == NULL) |
431 | return NULL; |
432 | color2 = gtk_css_color_value_get_rgba (color: val2); |
433 | |
434 | apply_mix (in1: color1, in2: color2, out: &res, factor: color->sym_col.mix.factor); |
435 | |
436 | value = _gtk_css_color_value_new_literal (color: &res); |
437 | _gtk_css_value_unref (value: val1); |
438 | _gtk_css_value_unref (value: val2); |
439 | } |
440 | |
441 | break; |
442 | case COLOR_TYPE_CURRENT_COLOR: |
443 | if (current) |
444 | { |
445 | return _gtk_css_value_ref (value: current); |
446 | } |
447 | else |
448 | { |
449 | GtkCssValue *initial = _gtk_css_style_property_get_initial_value (property: _gtk_css_style_property_lookup_by_id (id: GTK_CSS_PROPERTY_COLOR)); |
450 | |
451 | if (initial->class == >K_CSS_VALUE_COLOR) |
452 | { |
453 | return _gtk_css_color_value_resolve (color: initial, |
454 | provider, |
455 | NULL, |
456 | cycle_list); |
457 | } |
458 | else |
459 | { |
460 | return _gtk_css_value_ref (value: initial); |
461 | } |
462 | } |
463 | break; |
464 | default: |
465 | value = NULL; |
466 | g_assert_not_reached (); |
467 | } |
468 | |
469 | if (color->last_value != NULL && |
470 | _gtk_css_value_equal (value1: color->last_value, value2: value)) |
471 | { |
472 | _gtk_css_value_unref (value); |
473 | value = _gtk_css_value_ref (value: color->last_value); |
474 | } |
475 | else |
476 | { |
477 | if (color->last_value != NULL) |
478 | _gtk_css_value_unref (value: color->last_value); |
479 | color->last_value = _gtk_css_value_ref (value); |
480 | } |
481 | |
482 | return value; |
483 | } |
484 | |
485 | static GtkCssValue transparent_black_singleton = { >K_CSS_VALUE_COLOR, 1, TRUE, COLOR_TYPE_LITERAL, NULL, |
486 | .sym_col.rgba = {0, 0, 0, 0} }; |
487 | static GtkCssValue white_singleton = { >K_CSS_VALUE_COLOR, 1, TRUE, COLOR_TYPE_LITERAL, NULL, |
488 | .sym_col.rgba = {1, 1, 1, 1} }; |
489 | |
490 | |
491 | GtkCssValue * |
492 | gtk_css_color_value_new_transparent (void) |
493 | { |
494 | return _gtk_css_value_ref (value: &transparent_black_singleton); |
495 | } |
496 | |
497 | GtkCssValue * |
498 | gtk_css_color_value_new_white (void) |
499 | { |
500 | return _gtk_css_value_ref (value: &white_singleton); |
501 | } |
502 | |
503 | GtkCssValue * |
504 | _gtk_css_color_value_new_literal (const GdkRGBA *color) |
505 | { |
506 | GtkCssValue *value; |
507 | |
508 | g_return_val_if_fail (color != NULL, NULL); |
509 | |
510 | if (gdk_rgba_equal (p1: color, p2: &white_singleton.sym_col.rgba)) |
511 | return _gtk_css_value_ref (value: &white_singleton); |
512 | |
513 | if (gdk_rgba_equal (p1: color, p2: &transparent_black_singleton.sym_col.rgba)) |
514 | return _gtk_css_value_ref (value: &transparent_black_singleton); |
515 | |
516 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_COLOR); |
517 | value->type = COLOR_TYPE_LITERAL; |
518 | value->is_computed = TRUE; |
519 | value->sym_col.rgba = *color; |
520 | |
521 | return value; |
522 | } |
523 | |
524 | GtkCssValue * |
525 | _gtk_css_color_value_new_name (const char *name) |
526 | { |
527 | GtkCssValue *value; |
528 | |
529 | gtk_internal_return_val_if_fail (name != NULL, NULL); |
530 | |
531 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_COLOR); |
532 | value->type = COLOR_TYPE_NAME; |
533 | value->sym_col.name = g_strdup (str: name); |
534 | |
535 | return value; |
536 | } |
537 | |
538 | GtkCssValue * |
539 | _gtk_css_color_value_new_shade (GtkCssValue *color, |
540 | double factor) |
541 | { |
542 | GtkCssValue *value; |
543 | |
544 | gtk_internal_return_val_if_fail (color->class == >K_CSS_VALUE_COLOR, NULL); |
545 | |
546 | if (color->type == COLOR_TYPE_LITERAL) |
547 | { |
548 | GdkRGBA c; |
549 | |
550 | apply_shade (in: &color->sym_col.rgba, out: &c, factor); |
551 | |
552 | return _gtk_css_color_value_new_literal (color: &c); |
553 | } |
554 | |
555 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_COLOR); |
556 | value->type = COLOR_TYPE_SHADE; |
557 | value->sym_col.shade.color = _gtk_css_value_ref (value: color); |
558 | value->sym_col.shade.factor = factor; |
559 | |
560 | return value; |
561 | } |
562 | |
563 | GtkCssValue * |
564 | _gtk_css_color_value_new_alpha (GtkCssValue *color, |
565 | double factor) |
566 | { |
567 | GtkCssValue *value; |
568 | |
569 | gtk_internal_return_val_if_fail (color->class == >K_CSS_VALUE_COLOR, NULL); |
570 | |
571 | if (color->type == COLOR_TYPE_LITERAL) |
572 | { |
573 | GdkRGBA c; |
574 | |
575 | apply_alpha (in: &color->sym_col.rgba, out: &c, factor); |
576 | |
577 | return _gtk_css_color_value_new_literal (color: &c); |
578 | } |
579 | |
580 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_COLOR); |
581 | value->type = COLOR_TYPE_ALPHA; |
582 | value->sym_col.alpha.color = _gtk_css_value_ref (value: color); |
583 | value->sym_col.alpha.factor = factor; |
584 | |
585 | return value; |
586 | } |
587 | |
588 | GtkCssValue * |
589 | _gtk_css_color_value_new_mix (GtkCssValue *color1, |
590 | GtkCssValue *color2, |
591 | double factor) |
592 | { |
593 | GtkCssValue *value; |
594 | |
595 | gtk_internal_return_val_if_fail (color1->class == >K_CSS_VALUE_COLOR, NULL); |
596 | gtk_internal_return_val_if_fail (color2->class == >K_CSS_VALUE_COLOR, NULL); |
597 | |
598 | if (color1->type == COLOR_TYPE_LITERAL && |
599 | color2->type == COLOR_TYPE_LITERAL) |
600 | { |
601 | GdkRGBA result; |
602 | |
603 | apply_mix (in1: &color1->sym_col.rgba, in2: &color2->sym_col.rgba, out: &result, factor); |
604 | |
605 | return _gtk_css_color_value_new_literal (color: &result); |
606 | |
607 | } |
608 | |
609 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_COLOR); |
610 | value->type = COLOR_TYPE_MIX; |
611 | value->sym_col.mix.color1 = _gtk_css_value_ref (value: color1); |
612 | value->sym_col.mix.color2 = _gtk_css_value_ref (value: color2); |
613 | value->sym_col.mix.factor = factor; |
614 | |
615 | return value; |
616 | } |
617 | |
618 | GtkCssValue * |
619 | _gtk_css_color_value_new_current_color (void) |
620 | { |
621 | static GtkCssValue current_color = { >K_CSS_VALUE_COLOR, 1, FALSE, COLOR_TYPE_CURRENT_COLOR, NULL, }; |
622 | |
623 | return _gtk_css_value_ref (value: ¤t_color); |
624 | } |
625 | |
626 | typedef struct |
627 | { |
628 | GtkCssValue *color; |
629 | GtkCssValue *color2; |
630 | double value; |
631 | } ColorFunctionData; |
632 | |
633 | static guint |
634 | parse_color_mix (GtkCssParser *parser, |
635 | guint arg, |
636 | gpointer data_) |
637 | { |
638 | ColorFunctionData *data = data_; |
639 | |
640 | switch (arg) |
641 | { |
642 | case 0: |
643 | data->color = _gtk_css_color_value_parse (parser); |
644 | if (data->color == NULL) |
645 | return 0; |
646 | return 1; |
647 | |
648 | case 1: |
649 | data->color2 = _gtk_css_color_value_parse (parser); |
650 | if (data->color2 == NULL) |
651 | return 0; |
652 | return 1; |
653 | |
654 | case 2: |
655 | if (!gtk_css_parser_consume_number (self: parser, number: &data->value)) |
656 | return 0; |
657 | return 1; |
658 | |
659 | default: |
660 | g_return_val_if_reached (0); |
661 | } |
662 | } |
663 | |
664 | static guint |
665 | parse_color_number (GtkCssParser *parser, |
666 | guint arg, |
667 | gpointer data_) |
668 | { |
669 | ColorFunctionData *data = data_; |
670 | |
671 | switch (arg) |
672 | { |
673 | case 0: |
674 | data->color = _gtk_css_color_value_parse (parser); |
675 | if (data->color == NULL) |
676 | return 0; |
677 | return 1; |
678 | |
679 | case 1: |
680 | if (!gtk_css_parser_consume_number (self: parser, number: &data->value)) |
681 | return 0; |
682 | return 1; |
683 | |
684 | default: |
685 | g_return_val_if_reached (0); |
686 | } |
687 | } |
688 | |
689 | gboolean |
690 | gtk_css_color_value_can_parse (GtkCssParser *parser) |
691 | { |
692 | /* This is way too generous, but meh... */ |
693 | return gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_IDENT) |
694 | || gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_AT_KEYWORD) |
695 | || gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_HASH_ID) |
696 | || gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_HASH_UNRESTRICTED) |
697 | || gtk_css_parser_has_function (self: parser, name: "lighter" ) |
698 | || gtk_css_parser_has_function (self: parser, name: "darker" ) |
699 | || gtk_css_parser_has_function (self: parser, name: "shade" ) |
700 | || gtk_css_parser_has_function (self: parser, name: "alpha" ) |
701 | || gtk_css_parser_has_function (self: parser, name: "mix" ) |
702 | || gtk_css_parser_has_function (self: parser, name: "hsl" ) |
703 | || gtk_css_parser_has_function (self: parser, name: "hsla" ) |
704 | || gtk_css_parser_has_function (self: parser, name: "rgb" ) |
705 | || gtk_css_parser_has_function (self: parser, name: "rgba" ); |
706 | } |
707 | |
708 | GtkCssValue * |
709 | _gtk_css_color_value_parse (GtkCssParser *parser) |
710 | { |
711 | ColorFunctionData data = { NULL, }; |
712 | GtkCssValue *value; |
713 | GdkRGBA rgba; |
714 | |
715 | if (gtk_css_parser_try_ident (self: parser, ident: "currentColor" )) |
716 | { |
717 | return _gtk_css_color_value_new_current_color (); |
718 | } |
719 | else if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_AT_KEYWORD)) |
720 | { |
721 | const GtkCssToken *token = gtk_css_parser_get_token (self: parser); |
722 | |
723 | value = _gtk_css_color_value_new_name (name: token->string.string); |
724 | gtk_css_parser_consume_token (self: parser); |
725 | |
726 | return value; |
727 | } |
728 | else if (gtk_css_parser_has_function (self: parser, name: "lighter" )) |
729 | { |
730 | if (gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_color_number, data: &data)) |
731 | value = _gtk_css_color_value_new_shade (color: data.color, factor: 1.3); |
732 | else |
733 | value = NULL; |
734 | |
735 | g_clear_pointer (&data.color, gtk_css_value_unref); |
736 | return value; |
737 | } |
738 | else if (gtk_css_parser_has_function (self: parser, name: "darker" )) |
739 | { |
740 | if (gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_color_number, data: &data)) |
741 | value = _gtk_css_color_value_new_shade (color: data.color, factor: 0.7); |
742 | else |
743 | value = NULL; |
744 | |
745 | g_clear_pointer (&data.color, gtk_css_value_unref); |
746 | return value; |
747 | } |
748 | else if (gtk_css_parser_has_function (self: parser, name: "shade" )) |
749 | { |
750 | if (gtk_css_parser_consume_function (self: parser, min_args: 2, max_args: 2, parse_func: parse_color_number, data: &data)) |
751 | value = _gtk_css_color_value_new_shade (color: data.color, factor: data.value); |
752 | else |
753 | value = NULL; |
754 | |
755 | g_clear_pointer (&data.color, gtk_css_value_unref); |
756 | return value; |
757 | } |
758 | else if (gtk_css_parser_has_function (self: parser, name: "alpha" )) |
759 | { |
760 | if (gtk_css_parser_consume_function (self: parser, min_args: 2, max_args: 2, parse_func: parse_color_number, data: &data)) |
761 | value = _gtk_css_color_value_new_alpha (color: data.color, factor: data.value); |
762 | else |
763 | value = NULL; |
764 | |
765 | g_clear_pointer (&data.color, gtk_css_value_unref); |
766 | return value; |
767 | } |
768 | else if (gtk_css_parser_has_function (self: parser, name: "mix" )) |
769 | { |
770 | if (gtk_css_parser_consume_function (self: parser, min_args: 3, max_args: 3, parse_func: parse_color_mix, data: &data)) |
771 | value = _gtk_css_color_value_new_mix (color1: data.color, color2: data.color2, factor: data.value); |
772 | else |
773 | value = NULL; |
774 | |
775 | g_clear_pointer (&data.color, gtk_css_value_unref); |
776 | g_clear_pointer (&data.color2, gtk_css_value_unref); |
777 | return value; |
778 | } |
779 | |
780 | if (gdk_rgba_parser_parse (parser, rgba: &rgba)) |
781 | return _gtk_css_color_value_new_literal (color: &rgba); |
782 | else |
783 | return NULL; |
784 | } |
785 | |
786 | const GdkRGBA * |
787 | gtk_css_color_value_get_rgba (const GtkCssValue *color) |
788 | { |
789 | g_assert (color->class == >K_CSS_VALUE_COLOR); |
790 | g_assert (color->type == COLOR_TYPE_LITERAL); |
791 | |
792 | return &color->sym_col.rgba; |
793 | } |
794 | |