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 <math.h>
23#include <string.h>
24
25#include "gtkcssimagecrossfadeprivate.h"
26
27#include "gtkcssnumbervalueprivate.h"
28#include "gtkcssimagefallbackprivate.h"
29#include "gtkcsscolorvalueprivate.h"
30
31
32typedef struct _CrossFadeEntry CrossFadeEntry;
33
34struct _CrossFadeEntry
35{
36 double progress;
37 gboolean has_progress;
38 GtkCssImage *image;
39};
40
41G_DEFINE_TYPE (GtkCssImageCrossFade, gtk_css_image_cross_fade, GTK_TYPE_CSS_IMAGE)
42
43static void
44cross_fade_entry_clear (gpointer data)
45{
46 CrossFadeEntry *entry = data;
47
48 g_clear_object (&entry->image);
49}
50
51static void
52gtk_css_image_cross_fade_recalculate_progress (GtkCssImageCrossFade *self)
53{
54 double total_progress;
55 guint n_no_progress;
56 guint i;
57
58 total_progress = 0.0;
59 n_no_progress = 0;
60
61 for (i = 0; i < self->images->len; i++)
62 {
63 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
64
65 if (entry->has_progress)
66 total_progress += entry->progress;
67 else
68 n_no_progress++;
69 }
70
71 if (n_no_progress)
72 {
73 double progress;
74 if (total_progress >= 1.0)
75 {
76 progress = 0.0;
77 }
78 else
79 {
80 progress = (1.0 - total_progress) / n_no_progress;
81 total_progress = 1.0;
82 }
83 for (i = 0; i < self->images->len; i++)
84 {
85 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
86
87 if (!entry->has_progress)
88 entry->progress = progress;
89 }
90 }
91
92 self->total_progress = total_progress;
93}
94
95static void
96gtk_css_image_cross_fade_add (GtkCssImageCrossFade *self,
97 gboolean has_progress,
98 double progress,
99 GtkCssImage *image)
100{
101 CrossFadeEntry entry;
102
103 entry.has_progress = has_progress;
104 entry.progress = progress;
105 entry.image = image;
106 g_array_append_val (self->images, entry);
107
108 gtk_css_image_cross_fade_recalculate_progress (self);
109}
110
111static GtkCssImageCrossFade *
112gtk_css_image_cross_fade_new_empty (void)
113{
114 return g_object_new (GTK_TYPE_CSS_IMAGE_CROSS_FADE, NULL);
115}
116
117/* XXX: The following is not correct, it should actually run the
118 * CSS sizing algorithm for every child, not just query height and
119 * width independently.
120 */
121static int
122gtk_css_image_cross_fade_get_width (GtkCssImage *image)
123{
124 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
125 double sum_width, sum_progress;
126 guint i;
127
128 sum_width = 0.0;
129 sum_progress = 0.0;
130
131 for (i = 0; i < self->images->len; i++)
132 {
133 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
134 int image_width;
135
136 image_width = _gtk_css_image_get_width (image: entry->image);
137 if (image_width == 0)
138 continue;
139 sum_width += image_width * entry->progress;
140 sum_progress += entry->progress;
141 }
142
143 if (sum_progress <= 0.0)
144 return 0;
145
146 return ceil (x: sum_width / sum_progress);
147}
148
149static int
150gtk_css_image_cross_fade_get_height (GtkCssImage *image)
151{
152 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
153 double sum_height, sum_progress;
154 guint i;
155
156 sum_height = 0.0;
157 sum_progress = 0.0;
158
159 for (i = 0; i < self->images->len; i++)
160 {
161 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
162 int image_height;
163
164 image_height = _gtk_css_image_get_height (image: entry->image);
165 if (image_height == 0)
166 continue;
167 sum_height += image_height * entry->progress;
168 sum_progress += entry->progress;
169 }
170
171 if (sum_progress <= 0.0)
172 return 0;
173
174 return ceil (x: sum_height / sum_progress);
175}
176
177static gboolean
178gtk_css_image_cross_fade_equal (GtkCssImage *image1,
179 GtkCssImage *image2)
180{
181 GtkCssImageCrossFade *cross_fade1 = GTK_CSS_IMAGE_CROSS_FADE (image1);
182 GtkCssImageCrossFade *cross_fade2 = GTK_CSS_IMAGE_CROSS_FADE (image2);
183 guint i;
184
185 if (cross_fade1->images->len != cross_fade2->images->len)
186 return FALSE;
187
188 for (i = 0; i < cross_fade1->images->len; i++)
189 {
190 CrossFadeEntry *entry1 = &g_array_index (cross_fade1->images, CrossFadeEntry, i);
191 CrossFadeEntry *entry2 = &g_array_index (cross_fade2->images, CrossFadeEntry, i);
192
193 if (entry1->progress != entry2->progress ||
194 !_gtk_css_image_equal (image1: entry1->image, image2: entry2->image))
195 return FALSE;
196 }
197
198 return TRUE;
199}
200
201static gboolean
202gtk_css_image_cross_fade_is_dynamic (GtkCssImage *image)
203{
204 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
205 guint i;
206
207 for (i = 0; i < self->images->len; i++)
208 {
209 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
210
211 if (gtk_css_image_is_dynamic (image: entry->image))
212 return TRUE;
213 }
214
215 return FALSE;
216}
217
218static GtkCssImage *
219gtk_css_image_cross_fade_get_dynamic_image (GtkCssImage *image,
220 gint64 monotonic_time)
221{
222 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
223 GtkCssImageCrossFade *result;
224 guint i;
225
226 result = gtk_css_image_cross_fade_new_empty ();
227
228 for (i = 0; i < self->images->len; i++)
229 {
230 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
231
232 gtk_css_image_cross_fade_add (self: result,
233 has_progress: entry->has_progress,
234 progress: entry->progress,
235 image: gtk_css_image_get_dynamic_image (image: entry->image, monotonic_time));
236 }
237
238 return GTK_CSS_IMAGE (result);
239}
240
241static void
242gtk_css_image_cross_fade_snapshot (GtkCssImage *image,
243 GtkSnapshot *snapshot,
244 double width,
245 double height)
246{
247 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
248 double remaining;
249 guint i, n_cross_fades;
250
251 if (self->total_progress < 1.0)
252 {
253 n_cross_fades = self->images->len;
254 remaining = 1.0;
255 }
256 else
257 {
258 n_cross_fades = self->images->len - 1;
259 remaining = self->total_progress;
260 }
261
262 for (i = 0; i < n_cross_fades; i++)
263 {
264 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
265
266 gtk_snapshot_push_cross_fade (snapshot, progress: 1.0 - entry->progress / remaining);
267 remaining -= entry->progress;
268 gtk_css_image_snapshot (image: entry->image, snapshot, width, height);
269 gtk_snapshot_pop (snapshot);
270 }
271
272 if (n_cross_fades < self->images->len)
273 {
274 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, self->images->len - 1);
275 gtk_css_image_snapshot (image: entry->image, snapshot, width, height);
276 }
277
278 for (i = 0; i < n_cross_fades; i++)
279 {
280 gtk_snapshot_pop (snapshot);
281 }
282}
283
284static gboolean
285parse_progress (GtkCssParser *parser,
286 gpointer option_data,
287 gpointer user_data)
288{
289 double *progress = option_data;
290 GtkCssValue *number;
291
292 number = _gtk_css_number_value_parse (parser, flags: GTK_CSS_PARSE_PERCENT | GTK_CSS_POSITIVE_ONLY);
293 if (number == NULL)
294 return FALSE;
295 *progress = _gtk_css_number_value_get (number, one_hundred_percent: 1);
296 _gtk_css_value_unref (value: number);
297
298 if (*progress > 1.0)
299 {
300 gtk_css_parser_error_value (self: parser, format: "Percentages over 100%% are not allowed. Given value: %f", *progress);
301 return FALSE;
302 }
303
304 return TRUE;
305}
306
307static gboolean
308parse_image (GtkCssParser *parser,
309 gpointer option_data,
310 gpointer user_data)
311{
312 GtkCssImage **image = option_data;
313
314 if (_gtk_css_image_can_parse (parser))
315 {
316 *image = _gtk_css_image_new_parse (parser);
317 if (*image == NULL)
318 return FALSE;
319
320 return TRUE;
321 }
322 else if (gtk_css_color_value_can_parse (parser))
323 {
324 GtkCssValue *color;
325
326 color = _gtk_css_color_value_parse (parser);
327 if (color == NULL)
328 return FALSE;
329
330 *image = _gtk_css_image_fallback_new_for_color (color);
331
332 return TRUE;
333 }
334
335 return FALSE;
336}
337
338static guint
339gtk_css_image_cross_fade_parse_arg (GtkCssParser *parser,
340 guint arg,
341 gpointer data)
342{
343 GtkCssImageCrossFade *self = data;
344 double progress = -1.0;
345 GtkCssImage *image = NULL;
346 GtkCssParseOption options[] =
347 {
348 { (void *)gtk_css_number_value_can_parse, parse_progress, &progress },
349 { NULL, parse_image, &image },
350 };
351
352 if (!gtk_css_parser_consume_any (parser, options, G_N_ELEMENTS (options), user_data: self))
353 return 0;
354
355 g_assert (image != NULL);
356
357 if (progress < 0.0)
358 gtk_css_image_cross_fade_add (self, FALSE, progress: 0.0, image);
359 else
360 gtk_css_image_cross_fade_add (self, TRUE, progress, image);
361
362 return 1;
363}
364
365static gboolean
366gtk_css_image_cross_fade_parse (GtkCssImage *image,
367 GtkCssParser *parser)
368{
369 if (!gtk_css_parser_has_function (self: parser, name: "cross-fade"))
370 {
371 gtk_css_parser_error_syntax (self: parser, format: "Expected 'cross-fade('");
372 return FALSE;
373 }
374
375 return gtk_css_parser_consume_function (self: parser, min_args: 1, G_MAXUINT, parse_func: gtk_css_image_cross_fade_parse_arg, data: image);
376}
377
378static void
379gtk_css_image_cross_fade_print (GtkCssImage *image,
380 GString *string)
381{
382 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
383 guint i;
384
385 g_string_append (string, val: "cross-fade(");
386
387 for (i = 0; i < self->images->len; i++)
388 {
389 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
390
391 if (i > 0)
392 g_string_append_printf (string, format: ", ");
393 if (entry->has_progress)
394 g_string_append_printf (string, format: "%g%% ", entry->progress * 100.0);
395 _gtk_css_image_print (image: entry->image, string);
396 }
397
398 g_string_append (string, val: ")");
399}
400
401static GtkCssImage *
402gtk_css_image_cross_fade_compute (GtkCssImage *image,
403 guint property_id,
404 GtkStyleProvider *provider,
405 GtkCssStyle *style,
406 GtkCssStyle *parent_style)
407{
408 GtkCssImageCrossFade *self = GTK_CSS_IMAGE_CROSS_FADE (image);
409 GtkCssImageCrossFade *result;
410 guint i;
411
412 result = gtk_css_image_cross_fade_new_empty ();
413
414 for (i = 0; i < self->images->len; i++)
415 {
416 CrossFadeEntry *entry = &g_array_index (self->images, CrossFadeEntry, i);
417
418 gtk_css_image_cross_fade_add (self: result,
419 has_progress: entry->has_progress,
420 progress: entry->progress,
421 image: _gtk_css_image_compute (image: entry->image, property_id, provider, style, parent_style));
422 }
423
424 return GTK_CSS_IMAGE (result);
425}
426
427static void
428gtk_css_image_cross_fade_dispose (GObject *object)
429{
430 GtkCssImageCrossFade *cross_fade = GTK_CSS_IMAGE_CROSS_FADE (object);
431
432 g_clear_pointer (&cross_fade->images, g_array_unref);
433
434 G_OBJECT_CLASS (gtk_css_image_cross_fade_parent_class)->dispose (object);
435}
436
437static gboolean
438gtk_css_image_cross_fade_is_computed (GtkCssImage *image)
439{
440 GtkCssImageCrossFade *cross_fade = GTK_CSS_IMAGE_CROSS_FADE (image);
441 guint i;
442
443 for (i = 0; i < cross_fade->images->len; i++)
444 {
445 const CrossFadeEntry *entry = &g_array_index (cross_fade->images, CrossFadeEntry, i);
446 if (!gtk_css_image_is_computed (image: entry->image))
447 return FALSE;
448 }
449
450 return TRUE;
451}
452
453static void
454gtk_css_image_cross_fade_class_init (GtkCssImageCrossFadeClass *klass)
455{
456 GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
457 GObjectClass *object_class = G_OBJECT_CLASS (klass);
458
459 image_class->get_width = gtk_css_image_cross_fade_get_width;
460 image_class->get_height = gtk_css_image_cross_fade_get_height;
461 image_class->compute = gtk_css_image_cross_fade_compute;
462 image_class->equal = gtk_css_image_cross_fade_equal;
463 image_class->snapshot = gtk_css_image_cross_fade_snapshot;
464 image_class->is_dynamic = gtk_css_image_cross_fade_is_dynamic;
465 image_class->get_dynamic_image = gtk_css_image_cross_fade_get_dynamic_image;
466 image_class->parse = gtk_css_image_cross_fade_parse;
467 image_class->print = gtk_css_image_cross_fade_print;
468 image_class->is_computed = gtk_css_image_cross_fade_is_computed;
469
470 object_class->dispose = gtk_css_image_cross_fade_dispose;
471}
472
473static void
474gtk_css_image_cross_fade_init (GtkCssImageCrossFade *self)
475{
476 self->images = g_array_new (FALSE, FALSE, element_size: sizeof (CrossFadeEntry));
477 g_array_set_clear_func (array: self->images, clear_func: cross_fade_entry_clear);
478}
479
480GtkCssImage *
481_gtk_css_image_cross_fade_new (GtkCssImage *start,
482 GtkCssImage *end,
483 double progress)
484{
485 GtkCssImageCrossFade *self;
486
487 g_return_val_if_fail (start == NULL || GTK_IS_CSS_IMAGE (start), NULL);
488 g_return_val_if_fail (end == NULL || GTK_IS_CSS_IMAGE (end), NULL);
489
490 self = gtk_css_image_cross_fade_new_empty ();
491
492 if (start)
493 gtk_css_image_cross_fade_add (self, TRUE, progress: 1.0 - progress, g_object_ref (start));
494 if (end)
495 gtk_css_image_cross_fade_add (self, TRUE, progress, g_object_ref (end));
496
497 return GTK_CSS_IMAGE (self);
498}
499
500

source code of gtk/gtk/gtkcssimagecrossfade.c