1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 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 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 "gtkcsskeyframesprivate.h"
21
22#include "gtkcssarrayvalueprivate.h"
23#include "gtkcssshorthandpropertyprivate.h"
24#include "gtkcssstylepropertyprivate.h"
25#include "gtkstylepropertyprivate.h"
26
27#include "gtkprivate.h"
28
29#include <stdlib.h>
30#include <string.h>
31
32struct _GtkCssKeyframes {
33 int ref_count; /* ref count */
34 int n_keyframes; /* number of keyframes (at least 2 for 0% and 100% */
35 double *keyframe_progress; /* ordered array of n_keyframes of [0..1] */
36 int n_properties; /* number of properties used by keyframes */
37 guint *property_ids; /* ordered array of n_properties property ids */
38 GtkCssValue **values; /* 2D array: n_keyframes * n_properties of (value or NULL) for all the keyframes */
39};
40
41#define KEYFRAMES_VALUE(keyframes, k, p) ((keyframes)->values[(k) * (keyframes)->n_properties + (p)])
42
43GtkCssKeyframes *
44_gtk_css_keyframes_ref (GtkCssKeyframes *keyframes)
45{
46 g_return_val_if_fail (keyframes != NULL, NULL);
47
48 keyframes->ref_count++;
49
50 return keyframes;
51}
52
53void
54_gtk_css_keyframes_unref (GtkCssKeyframes *keyframes)
55{
56 guint k, p;
57
58 g_return_if_fail (keyframes != NULL);
59
60 keyframes->ref_count--;
61 if (keyframes->ref_count > 0)
62 return;
63
64 g_free (mem: keyframes->keyframe_progress);
65 g_free (mem: keyframes->property_ids);
66
67 for (k = 0; k < keyframes->n_keyframes; k++)
68 {
69 for (p = 0; p < keyframes->n_properties; p++)
70 {
71 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
72 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
73 }
74 }
75 g_free (mem: keyframes->values);
76
77 g_slice_free (GtkCssKeyframes, keyframes);
78}
79
80static guint
81gtk_css_keyframes_add_keyframe (GtkCssKeyframes *keyframes,
82 double progress)
83{
84 guint k, p;
85
86 for (k = 0; k < keyframes->n_keyframes; k++)
87 {
88 if (keyframes->keyframe_progress[k] == progress)
89 {
90 for (p = 0; p < keyframes->n_properties; p++)
91 {
92 if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
93 continue;
94
95 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
96 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
97
98 /* XXX: GC properties that are now unset
99 * in all keyframes?
100 */
101 }
102 return k;
103 }
104 else if (keyframes->keyframe_progress[k] > progress)
105 break;
106 }
107
108 keyframes->n_keyframes++;
109 keyframes->keyframe_progress = g_realloc (mem: keyframes->keyframe_progress, n_bytes: sizeof (double) * keyframes->n_keyframes);
110 memmove (dest: keyframes->keyframe_progress + k + 1, src: keyframes->keyframe_progress + k, n: sizeof (double) * (keyframes->n_keyframes - k - 1));
111 keyframes->keyframe_progress[k] = progress;
112
113 if (keyframes->n_properties)
114 {
115 gsize size = sizeof (GtkCssValue *) * keyframes->n_properties;
116
117 keyframes->values = g_realloc (mem: keyframes->values, n_bytes: sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
118 memmove (dest: &KEYFRAMES_VALUE (keyframes, k + 1, 0), src: &KEYFRAMES_VALUE (keyframes, k, 0), n: size * (keyframes->n_keyframes - k - 1));
119 memset (s: &KEYFRAMES_VALUE (keyframes, k, 0), c: 0, n: size);
120 }
121
122 return k;
123}
124
125static guint
126gtk_css_keyframes_lookup_property (GtkCssKeyframes *keyframes,
127 guint property_id)
128{
129 guint p;
130
131 for (p = 0; p < keyframes->n_properties; p++)
132 {
133 if (keyframes->property_ids[p] == property_id)
134 return p;
135 else if (keyframes->property_ids[p] > property_id)
136 break;
137 }
138
139 keyframes->n_properties++;
140 keyframes->property_ids = g_realloc (mem: keyframes->property_ids, n_bytes: sizeof (guint) * keyframes->n_properties);
141 memmove (dest: keyframes->property_ids + p + 1, src: keyframes->property_ids + p, n: sizeof (guint) * (keyframes->n_properties - p - 1));
142 keyframes->property_ids[p] = property_id;
143
144 if (keyframes->n_properties > 1)
145 {
146 guint old_n_properties = keyframes->n_properties - 1;
147 int k;
148
149 keyframes->values = g_realloc (mem: keyframes->values, n_bytes: sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
150
151 if (p + 1 < keyframes->n_properties)
152 {
153 memmove (dest: &KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p + 1),
154 src: &keyframes->values[(keyframes->n_keyframes - 1) * old_n_properties + p],
155 n: sizeof (GtkCssValue *) * (keyframes->n_properties - p - 1));
156 }
157 KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p) = NULL;
158
159 for (k = keyframes->n_keyframes - 2; k >= 0; k--)
160 {
161 memmove (dest: &KEYFRAMES_VALUE (keyframes, k, p + 1),
162 src: &keyframes->values[k * old_n_properties + p],
163 n: sizeof (GtkCssValue *) * old_n_properties);
164 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
165 }
166 }
167 else
168 {
169 keyframes->values = g_new0 (GtkCssValue *, keyframes->n_keyframes);
170 }
171
172 return p;
173}
174
175static GtkCssKeyframes *
176gtk_css_keyframes_alloc (void)
177{
178 GtkCssKeyframes *keyframes;
179
180 keyframes = g_slice_new0 (GtkCssKeyframes);
181 keyframes->ref_count = 1;
182
183 return keyframes;
184}
185
186static GtkCssKeyframes *
187gtk_css_keyframes_new (void)
188{
189 GtkCssKeyframes *keyframes;
190
191 keyframes = gtk_css_keyframes_alloc ();
192
193 gtk_css_keyframes_add_keyframe (keyframes, progress: 0);
194 gtk_css_keyframes_add_keyframe (keyframes, progress: 1);
195
196 return keyframes;
197}
198
199static gboolean
200keyframes_set_value (GtkCssKeyframes *keyframes,
201 guint k,
202 GtkCssStyleProperty *property,
203 GtkCssValue *value)
204{
205 guint p;
206
207 if (!_gtk_css_style_property_is_animated (property))
208 return FALSE;
209
210 p = gtk_css_keyframes_lookup_property (keyframes, property_id: _gtk_css_style_property_get_id (property));
211
212 if (KEYFRAMES_VALUE (keyframes, k, p))
213 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
214
215 KEYFRAMES_VALUE (keyframes, k, p) = _gtk_css_value_ref (value);
216
217 return TRUE;
218}
219
220static gboolean
221gtk_css_keyframes_parse_declaration (GtkCssKeyframes *keyframes,
222 guint k,
223 GtkCssParser *parser)
224{
225 GtkStyleProperty *property;
226 GtkCssValue *value;
227 char *name;
228
229 name = gtk_css_parser_consume_ident (self: parser);
230 if (name == NULL)
231 {
232 if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF))
233 gtk_css_parser_error_syntax (self: parser, format: "Expected a property name");
234 return FALSE;
235 }
236
237 property = _gtk_style_property_lookup (name);
238 if (property == NULL)
239 {
240 gtk_css_parser_error_value (self: parser, format: "No property named '%s'", name);
241 g_free (mem: name);
242 return FALSE;
243 }
244
245 g_free (mem: name);
246
247 if (!gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COLON))
248 {
249 gtk_css_parser_error_syntax (self: parser, format: "Expected a ':'");
250 return FALSE;
251 }
252
253 value = _gtk_style_property_parse_value (property, parser);
254 if (value == NULL)
255 return FALSE;
256
257 if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF))
258 {
259 gtk_css_parser_error_syntax (self: parser, format: "Junk at end of value");
260 _gtk_css_value_unref (value);
261 return FALSE;
262 }
263
264 if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
265 {
266 GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
267 gboolean animatable = FALSE;
268 guint i;
269
270 for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
271 {
272 GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, property: i);
273 GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
274
275 animatable |= keyframes_set_value (keyframes, k, property: child, value: sub);
276 }
277
278 if (!animatable)
279 gtk_css_parser_error_value (self: parser, format: "shorthand '%s' cannot be animated", _gtk_style_property_get_name (property));
280 }
281 else if (GTK_IS_CSS_STYLE_PROPERTY (property))
282 {
283 if (!keyframes_set_value (keyframes, k, GTK_CSS_STYLE_PROPERTY (property), value))
284 gtk_css_parser_error_value (self: parser, format: "Cannot animate property '%s'", _gtk_style_property_get_name (property));
285 }
286 else
287 {
288 g_assert_not_reached ();
289 }
290
291 _gtk_css_value_unref (value);
292
293 return TRUE;
294}
295
296static gboolean
297gtk_css_keyframes_parse_block (GtkCssKeyframes *keyframes,
298 guint k,
299 GtkCssParser *parser)
300{
301 if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_OPEN_CURLY))
302 {
303 gtk_css_parser_error_syntax (self: parser, format: "Expected '{'");
304 return FALSE;
305 }
306
307 gtk_css_parser_start_block (self: parser);
308
309 while (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF))
310 {
311 gtk_css_parser_start_semicolon_block (self: parser, alternative_token: GTK_CSS_TOKEN_EOF);
312 gtk_css_keyframes_parse_declaration (keyframes, k, parser);
313 gtk_css_parser_end_block (self: parser);
314 }
315
316 gtk_css_parser_end_block (self: parser);
317
318 return TRUE;
319}
320
321GtkCssKeyframes *
322_gtk_css_keyframes_parse (GtkCssParser *parser)
323{
324 GtkCssKeyframes *keyframes;
325 double progress;
326 guint k;
327
328 g_return_val_if_fail (parser != NULL, NULL);
329
330 keyframes = gtk_css_keyframes_new ();
331
332 while (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF))
333 {
334 if (gtk_css_parser_try_ident (self: parser, ident: "from"))
335 progress = 0;
336 else if (gtk_css_parser_try_ident (self: parser, ident: "to"))
337 progress = 1;
338 else if (gtk_css_parser_consume_percentage (self: parser, number: &progress))
339 {
340 if (progress < 0 || progress > 100)
341 {
342 /* XXX: should we skip over the block here? */
343 gtk_css_parser_error_value (self: parser, format: "percentages must be between 0%% and 100%%");
344 _gtk_css_keyframes_unref (keyframes);
345 return NULL;
346 }
347 progress /= 100;
348 }
349 else
350 {
351 _gtk_css_keyframes_unref (keyframes);
352 return NULL;
353 }
354
355 k = gtk_css_keyframes_add_keyframe (keyframes, progress);
356
357 if (!gtk_css_keyframes_parse_block (keyframes, k, parser))
358 {
359 _gtk_css_keyframes_unref (keyframes);
360 return NULL;
361 }
362 }
363
364 return keyframes;
365}
366
367static int
368compare_property_by_name (gconstpointer a,
369 gconstpointer b,
370 gpointer data)
371{
372 GtkCssKeyframes *keyframes = data;
373
374 return strcmp (s1: _gtk_style_property_get_name (GTK_STYLE_PROPERTY (
375 _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) a]))),
376 s2: _gtk_style_property_get_name (GTK_STYLE_PROPERTY (
377 _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) b]))));
378}
379
380void
381_gtk_css_keyframes_print (GtkCssKeyframes *keyframes,
382 GString *string)
383{
384 guint k, p;
385 guint *sorted;
386
387 g_return_if_fail (keyframes != NULL);
388 g_return_if_fail (string != NULL);
389
390 sorted = g_new (guint, keyframes->n_properties);
391 for (p = 0; p < keyframes->n_properties; p++)
392 sorted[p] = p;
393 g_qsort_with_data (pbase: sorted, total_elems: keyframes->n_properties, size: sizeof (guint), compare_func: compare_property_by_name, user_data: keyframes);
394
395 for (k = 0; k < keyframes->n_keyframes; k++)
396 {
397 /* useful for 0% and 100% which might be empty */
398 gboolean opened = FALSE;
399
400 for (p = 0; p < keyframes->n_properties; p++)
401 {
402 if (KEYFRAMES_VALUE (keyframes, k, sorted[p]) == NULL)
403 continue;
404
405 if (!opened)
406 {
407 if (keyframes->keyframe_progress[k] == 0.0)
408 g_string_append (string, val: " from {\n");
409 else if (keyframes->keyframe_progress[k] == 1.0)
410 g_string_append (string, val: " to {\n");
411 else
412 g_string_append_printf (string, format: " %g%% {\n", keyframes->keyframe_progress[k] * 100);
413 opened = TRUE;
414 }
415
416 g_string_append_printf (string, format: " %s: ", _gtk_style_property_get_name (
417 GTK_STYLE_PROPERTY (
418 _gtk_css_style_property_lookup_by_id (
419 keyframes->property_ids[sorted[p]]))));
420 _gtk_css_value_print (KEYFRAMES_VALUE (keyframes, k, sorted[p]), string);
421 g_string_append (string, val: ";\n");
422 }
423
424 if (opened)
425 g_string_append (string, val: " }\n");
426 }
427
428 g_free (mem: sorted);
429}
430
431GtkCssKeyframes *
432_gtk_css_keyframes_compute (GtkCssKeyframes *keyframes,
433 GtkStyleProvider *provider,
434 GtkCssStyle *style,
435 GtkCssStyle *parent_style)
436{
437 GtkCssKeyframes *resolved;
438 guint k, p;
439
440 g_return_val_if_fail (keyframes != NULL, NULL);
441 g_return_val_if_fail (GTK_IS_STYLE_PROVIDER (provider), NULL);
442 g_return_val_if_fail (GTK_IS_CSS_STYLE (style), NULL);
443 g_return_val_if_fail (parent_style == NULL || GTK_IS_CSS_STYLE (parent_style), NULL);
444
445 resolved = gtk_css_keyframes_alloc ();
446 resolved->n_keyframes = keyframes->n_keyframes;
447 resolved->keyframe_progress = g_memdup2 (mem: keyframes->keyframe_progress, byte_size: keyframes->n_keyframes * sizeof (double));
448 resolved->n_properties = keyframes->n_properties;
449 resolved->property_ids = g_memdup2 (mem: keyframes->property_ids, byte_size: keyframes->n_properties * sizeof (guint));
450 resolved->values = g_new0 (GtkCssValue *, resolved->n_keyframes * resolved->n_properties);
451
452 for (p = 0; p < resolved->n_properties; p++)
453 {
454 for (k = 0; k < resolved->n_keyframes; k++)
455 {
456 if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
457 continue;
458
459 KEYFRAMES_VALUE (resolved, k, p) = _gtk_css_value_compute (KEYFRAMES_VALUE (keyframes, k, p),
460 property_id: resolved->property_ids[p],
461 provider,
462 style,
463 parent_style);
464 }
465 }
466
467 return resolved;
468}
469
470guint
471_gtk_css_keyframes_get_n_properties (GtkCssKeyframes *keyframes)
472{
473 g_return_val_if_fail (keyframes != NULL, 0);
474
475 return keyframes->n_properties;
476}
477
478guint
479_gtk_css_keyframes_get_property_id (GtkCssKeyframes *keyframes,
480 guint id)
481{
482 g_return_val_if_fail (keyframes != NULL, 0);
483 g_return_val_if_fail (id < keyframes->n_properties, 0);
484
485 return keyframes->property_ids[id];
486}
487
488GtkCssValue *
489_gtk_css_keyframes_get_value (GtkCssKeyframes *keyframes,
490 guint id,
491 double progress,
492 GtkCssValue *default_value)
493{
494 GtkCssValue *start_value, *end_value, *result;
495 double start_progress, end_progress;
496 guint k;
497
498 g_return_val_if_fail (keyframes != NULL, 0);
499 g_return_val_if_fail (id < keyframes->n_properties, 0);
500
501 start_value = default_value;
502 start_progress = 0.0;
503 end_value = default_value;
504 end_progress = 1.0;
505
506 for (k = 0; k < keyframes->n_keyframes; k++)
507 {
508 if (KEYFRAMES_VALUE (keyframes, k, id) == NULL)
509 continue;
510
511 if (keyframes->keyframe_progress[k] == progress)
512 {
513 return _gtk_css_value_ref (KEYFRAMES_VALUE (keyframes, k, id));
514 }
515 else if (keyframes->keyframe_progress[k] < progress)
516 {
517 start_value = KEYFRAMES_VALUE (keyframes, k, id);
518 start_progress = keyframes->keyframe_progress[k];
519 }
520 else
521 {
522 end_value = KEYFRAMES_VALUE (keyframes, k, id);
523 end_progress = keyframes->keyframe_progress[k];
524 break;
525 }
526 }
527
528 progress = (progress - start_progress) / (end_progress - start_progress);
529
530 result = _gtk_css_value_transition (start: start_value,
531 end: end_value,
532 property_id: keyframes->property_ids[id],
533 progress);
534
535 /* XXX: Dear spec, what's the correct thing to do here? */
536 if (result == NULL)
537 return _gtk_css_value_ref (value: start_value);
538
539 return result;
540}
541
542

source code of gtk/gtk/gtkcsskeyframes.c