1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Red Hat, Inc.
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.
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.
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/>.
20 #include "gtkcsskeyframesprivate.h"
22 #include "gtkcssarrayvalueprivate.h"
23 #include "gtkcssshorthandpropertyprivate.h"
24 #include "gtkcssstylepropertyprivate.h"
25 #include "gtkstylepropertyprivate.h"
30 struct _GtkCssKeyframes {
31 int ref_count; /* ref count */
32 int n_keyframes; /* number of keyframes (at least 2 for 0% and 100% */
33 double *keyframe_progress; /* ordered array of n_keyframes of [0..1] */
34 int n_properties; /* number of properties used by keyframes */
35 guint *property_ids; /* ordered array of n_properties property ids */
36 GtkCssValue **values; /* 2D array: n_keyframes * n_properties of (value or NULL) for all the keyframes */
39 #define KEYFRAMES_VALUE(keyframes, k, p) ((keyframes)->values[(k) * (keyframes)->n_properties + (p)])
42 _gtk_css_keyframes_ref (GtkCssKeyframes *keyframes)
44 g_return_val_if_fail (keyframes != NULL, NULL);
46 keyframes->ref_count++;
52 _gtk_css_keyframes_unref (GtkCssKeyframes *keyframes)
56 g_return_if_fail (keyframes != NULL);
58 keyframes->ref_count--;
59 if (keyframes->ref_count > 0)
62 g_free (keyframes->keyframe_progress);
63 g_free (keyframes->property_ids);
65 for (k = 0; k < keyframes->n_keyframes; k++)
67 for (p = 0; p < keyframes->n_properties; p++)
69 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
70 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
73 g_free (keyframes->values);
75 g_slice_free (GtkCssKeyframes, keyframes);
79 gtk_css_keyframes_add_keyframe (GtkCssKeyframes *keyframes,
84 for (k = 0; k < keyframes->n_keyframes; k++)
86 if (keyframes->keyframe_progress[k] == progress)
88 for (p = 0; p < keyframes->n_properties; p++)
90 if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
93 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
94 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
96 /* XXX: GC properties that are now unset
97 * in all keyframes? */
101 else if (keyframes->keyframe_progress[k] > progress)
105 keyframes->n_keyframes++;
106 keyframes->keyframe_progress = g_realloc (keyframes->keyframe_progress, sizeof (double) * keyframes->n_keyframes);
107 memmove (keyframes->keyframe_progress + k + 1, keyframes->keyframe_progress + k, sizeof (double) * (keyframes->n_keyframes - k - 1));
108 keyframes->keyframe_progress[k] = progress;
110 if (keyframes->n_properties)
112 gsize size = sizeof (GtkCssValue *) * keyframes->n_properties;
114 keyframes->values = g_realloc (keyframes->values, sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
115 memmove (&KEYFRAMES_VALUE (keyframes, k + 1, 0), &KEYFRAMES_VALUE (keyframes, k, 0), size * (keyframes->n_keyframes - k - 1));
116 memset (&KEYFRAMES_VALUE (keyframes, k, 0), 0, size);
123 gtk_css_keyframes_lookup_property (GtkCssKeyframes *keyframes,
128 for (p = 0; p < keyframes->n_properties; p++)
130 if (keyframes->property_ids[p] == property_id)
132 else if (keyframes->property_ids[p] > property_id)
136 keyframes->n_properties++;
137 keyframes->property_ids = g_realloc (keyframes->property_ids, sizeof (guint) * keyframes->n_properties);
138 memmove (keyframes->property_ids + p + 1, keyframes->property_ids + p, sizeof (guint) * (keyframes->n_properties - p - 1));
139 keyframes->property_ids[p] = property_id;
141 if (keyframes->n_properties > 1)
143 guint old_n_properties = keyframes->n_properties - 1;
146 keyframes->values = g_realloc (keyframes->values, sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
148 if (p + 1 < keyframes->n_properties)
150 memmove (&KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p + 1),
151 &keyframes->values[(keyframes->n_keyframes - 1) * old_n_properties + p],
152 sizeof (GtkCssValue *) * (keyframes->n_properties - p - 1));
154 KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p) = NULL;
156 for (k = keyframes->n_keyframes - 2; k >= 0; k--)
158 memmove (&KEYFRAMES_VALUE (keyframes, k, p + 1),
159 &keyframes->values[k * old_n_properties + p],
160 sizeof (GtkCssValue *) * old_n_properties);
161 KEYFRAMES_VALUE (keyframes, k, p) = NULL;
166 keyframes->values = g_new0 (GtkCssValue *, keyframes->n_keyframes);
172 static GtkCssKeyframes *
173 gtk_css_keyframes_new (void)
175 GtkCssKeyframes *keyframes;
177 keyframes = g_slice_new0 (GtkCssKeyframes);
178 keyframes->ref_count = 1;
180 gtk_css_keyframes_add_keyframe (keyframes, 0);
181 gtk_css_keyframes_add_keyframe (keyframes, 1);
187 keyframes_set_value (GtkCssKeyframes *keyframes,
189 GtkCssStyleProperty *property,
194 if (!_gtk_css_style_property_is_animated (property))
197 p = gtk_css_keyframes_lookup_property (keyframes, _gtk_css_style_property_get_id (property));
199 if (KEYFRAMES_VALUE (keyframes, k, p))
200 _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
202 KEYFRAMES_VALUE (keyframes, k, p) = _gtk_css_value_ref (value);
208 parse_declaration (GtkCssKeyframes *keyframes,
210 GtkCssParser *parser)
212 GtkStyleProperty *property;
216 while (_gtk_css_parser_try (parser, ";", TRUE))
218 /* SKIP ALL THE THINGS! */
221 name = _gtk_css_parser_try_ident (parser, TRUE);
224 _gtk_css_parser_error (parser, "No property name given");
228 property = _gtk_style_property_lookup (name);
229 if (property == NULL)
231 /* should be GTK_CSS_PROVIDER_ERROR_NAME */
232 _gtk_css_parser_error (parser, "No property named '%s'", name);
239 if (!_gtk_css_parser_try (parser, ":", TRUE))
241 _gtk_css_parser_error (parser, "Expected a ':'");
245 value = _gtk_style_property_parse_value (property, parser);
249 if (!_gtk_css_parser_try (parser, ";", TRUE) &&
250 !_gtk_css_parser_begins_with (parser, '}'))
252 _gtk_css_parser_error (parser, "Junk at end of value");
253 _gtk_css_value_unref (value);
257 if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
259 GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
260 gboolean animatable = FALSE;
263 for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
265 GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
266 GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
268 animatable |= keyframes_set_value (keyframes, k, child, sub);
272 _gtk_css_parser_error (parser, "shorthand '%s' cannot be animated", _gtk_style_property_get_name (property));
274 else if (GTK_IS_CSS_STYLE_PROPERTY (property))
276 if (!keyframes_set_value (keyframes, k, GTK_CSS_STYLE_PROPERTY (property), value))
277 _gtk_css_parser_error (parser, "Cannot animate property '%s'", _gtk_style_property_get_name (property));
281 g_assert_not_reached ();
284 _gtk_css_value_unref (value);
290 parse_block (GtkCssKeyframes *keyframes,
292 GtkCssParser *parser)
294 if (!_gtk_css_parser_try (parser, "{", TRUE))
296 _gtk_css_parser_error (parser, "Expected closing bracket after keyframes block");
300 while (!_gtk_css_parser_try (parser, "}", TRUE))
302 if (!parse_declaration (keyframes, k, parser))
303 _gtk_css_parser_resync (parser, TRUE, '}');
305 if (_gtk_css_parser_is_eof (parser))
307 _gtk_css_parser_error (parser, "Expected closing '}' after keyframes block");
316 _gtk_css_keyframes_parse (GtkCssParser *parser)
318 GtkCssKeyframes *keyframes;
322 g_return_val_if_fail (parser != NULL, NULL);
324 keyframes = gtk_css_keyframes_new ();
326 while (!_gtk_css_parser_begins_with (parser, '}'))
328 if (_gtk_css_parser_try (parser, "from", TRUE))
330 else if (_gtk_css_parser_try (parser, "to", TRUE))
332 else if (_gtk_css_parser_try_double (parser, &progress) &&
333 _gtk_css_parser_try (parser, "%", TRUE))
335 if (progress < 0 || progress > 100)
337 /* XXX: should we skip over the block here? */
338 _gtk_css_parser_error (parser, "percentages must be between 0%% and 100%%");
339 _gtk_css_keyframes_unref (keyframes);
346 _gtk_css_parser_error (parser, "expected a percentage");
347 _gtk_css_keyframes_unref (keyframes);
351 k = gtk_css_keyframes_add_keyframe (keyframes, progress);
353 if (!parse_block (keyframes, k, parser))
355 _gtk_css_keyframes_unref (keyframes);
364 compare_property_by_name (gconstpointer a,
368 GtkCssKeyframes *keyframes = data;
370 return strcmp (_gtk_style_property_get_name (GTK_STYLE_PROPERTY (
371 _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) a]))),
372 _gtk_style_property_get_name (GTK_STYLE_PROPERTY (
373 _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) b]))));
377 _gtk_css_keyframes_print (GtkCssKeyframes *keyframes,
383 g_return_if_fail (keyframes != NULL);
384 g_return_if_fail (string != NULL);
386 sorted = g_new (guint, keyframes->n_properties);
387 for (p = 0; p < keyframes->n_properties; p++)
389 g_qsort_with_data (sorted, keyframes->n_properties, sizeof (guint), compare_property_by_name, keyframes);
391 for (k = 0; k < keyframes->n_keyframes; k++)
393 /* useful for 0% and 100% which might be empty */
394 gboolean opened = FALSE;
396 for (p = 0; p < keyframes->n_properties; p++)
398 if (KEYFRAMES_VALUE (keyframes, k, sorted[p]) == NULL)
403 if (keyframes->keyframe_progress[k] == 0.0)
404 g_string_append (string, " from {\n");
405 else if (keyframes->keyframe_progress[k] == 1.0)
406 g_string_append (string, " to {\n");
408 g_string_append_printf (string, " %g%% {\n", keyframes->keyframe_progress[k] * 100);
412 g_string_append_printf (string, " %s: ", _gtk_style_property_get_name (
414 _gtk_css_style_property_lookup_by_id (
415 keyframes->property_ids[sorted[p]]))));
416 _gtk_css_value_print (KEYFRAMES_VALUE (keyframes, k, sorted[p]), string);
417 g_string_append (string, ";\n");
421 g_string_append (string, " }\n");
428 _gtk_css_keyframes_compute (GtkCssKeyframes *keyframes,
429 GtkStyleProviderPrivate *provider,
430 GtkCssComputedValues *values,
431 GtkCssComputedValues *parent_values)
433 GtkCssKeyframes *resolved;
436 g_return_val_if_fail (keyframes != NULL, NULL);
437 g_return_val_if_fail (GTK_IS_STYLE_PROVIDER_PRIVATE (provider), NULL);
438 g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
439 g_return_val_if_fail (parent_values == NULL || GTK_IS_CSS_COMPUTED_VALUES (parent_values), NULL);
441 resolved = gtk_css_keyframes_new ();
442 resolved->n_keyframes = keyframes->n_keyframes;
443 resolved->keyframe_progress = g_memdup (keyframes->keyframe_progress, keyframes->n_keyframes * sizeof (double));
444 resolved->n_properties = keyframes->n_properties;
445 resolved->property_ids = g_memdup (keyframes->property_ids, keyframes->n_properties * sizeof (guint));
446 resolved->values = g_new0 (GtkCssValue *, resolved->n_keyframes * resolved->n_properties);
448 for (p = 0; p < resolved->n_properties; p++)
450 for (k = 0; k < resolved->n_keyframes; k++)
452 if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
455 KEYFRAMES_VALUE (resolved, k, p) = _gtk_css_value_compute (KEYFRAMES_VALUE (keyframes, k, p),
456 resolved->property_ids[p],
468 _gtk_css_keyframes_get_n_properties (GtkCssKeyframes *keyframes)
470 g_return_val_if_fail (keyframes != NULL, 0);
472 return keyframes->n_properties;
476 _gtk_css_keyframes_get_property_id (GtkCssKeyframes *keyframes,
479 g_return_val_if_fail (keyframes != NULL, 0);
480 g_return_val_if_fail (id < keyframes->n_properties, 0);
482 return keyframes->property_ids[id];
486 _gtk_css_keyframes_get_value (GtkCssKeyframes *keyframes,
489 GtkCssValue *default_value)
491 GtkCssValue *start_value, *end_value, *result;
492 double start_progress, end_progress;
495 g_return_val_if_fail (keyframes != NULL, 0);
496 g_return_val_if_fail (id < keyframes->n_properties, 0);
498 start_value = default_value;
499 start_progress = 0.0;
500 end_value = default_value;
503 for (k = 0; k < keyframes->n_keyframes; k++)
505 if (KEYFRAMES_VALUE (keyframes, k, id) == NULL)
508 if (keyframes->keyframe_progress[k] == progress)
510 return _gtk_css_value_ref (KEYFRAMES_VALUE (keyframes, k, id));
512 else if (keyframes->keyframe_progress[k] < progress)
514 start_value = KEYFRAMES_VALUE (keyframes, k, id);
515 start_progress = keyframes->keyframe_progress[k];
519 end_value = KEYFRAMES_VALUE (keyframes, k, id);
520 end_progress = keyframes->keyframe_progress[k];
525 progress = (progress - start_progress) / (end_progress - start_progress);
527 result = _gtk_css_value_transition (start_value,
529 keyframes->property_ids[id],
532 /* XXX: Dear spec, what's the correct thing to do here? */
534 return _gtk_css_value_ref (start_value);