]> Pileus Git - ~andy/gtk/blob - gtk/gtkcsskeyframes.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcsskeyframes.c
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 <stdlib.h>
28 #include <string.h>
29
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 */
37 };
38
39 #define KEYFRAMES_VALUE(keyframes, k, p) ((keyframes)->values[(k) * (keyframes)->n_properties + (p)])
40
41 GtkCssKeyframes *
42 _gtk_css_keyframes_ref (GtkCssKeyframes *keyframes)
43 {
44   g_return_val_if_fail (keyframes != NULL, NULL);
45
46   keyframes->ref_count++;
47
48   return keyframes;
49 }
50
51 void
52 _gtk_css_keyframes_unref (GtkCssKeyframes *keyframes)
53 {
54   guint k, p;
55
56   g_return_if_fail (keyframes != NULL);
57
58   keyframes->ref_count--;
59   if (keyframes->ref_count > 0)
60     return;
61
62   g_free (keyframes->keyframe_progress);
63   g_free (keyframes->property_ids);
64
65   for (k = 0; k < keyframes->n_keyframes; k++)
66     {
67       for (p = 0; p < keyframes->n_properties; p++)
68         {
69           _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
70           KEYFRAMES_VALUE (keyframes, k, p) = NULL;
71         }
72     }
73   g_free (keyframes->values);
74
75   g_slice_free (GtkCssKeyframes, keyframes);
76 }
77
78 static guint
79 gtk_css_keyframes_add_keyframe (GtkCssKeyframes *keyframes,
80                                 double           progress)
81 {
82   guint k, p;
83
84   for (k = 0; k < keyframes->n_keyframes; k++)
85     {
86       if (keyframes->keyframe_progress[k] == progress)
87         {
88           for (p = 0; p < keyframes->n_properties; p++)
89             {
90               if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
91                 continue;
92
93               _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
94               KEYFRAMES_VALUE (keyframes, k, p) = NULL;
95
96               /* XXX: GC properties that are now unset
97                * in all keyframes? */
98             }
99           return k;
100         }
101       else if (keyframes->keyframe_progress[k] > progress)
102         break;
103     }
104
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;
109
110   if (keyframes->n_properties)
111     {
112       gsize size = sizeof (GtkCssValue *) * keyframes->n_properties;
113       
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);
117     }
118
119   return k;
120 }
121
122 static guint
123 gtk_css_keyframes_lookup_property (GtkCssKeyframes *keyframes,
124                                    guint            property_id)
125 {
126   guint p;
127
128   for (p = 0; p < keyframes->n_properties; p++)
129     {
130       if (keyframes->property_ids[p] == property_id)
131         return p;
132       else if (keyframes->property_ids[p] > property_id)
133         break;
134     }
135
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;
140
141   if (keyframes->n_properties > 1)
142     {
143       guint old_n_properties = keyframes->n_properties - 1;
144       int k;
145       
146       keyframes->values = g_realloc (keyframes->values, sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
147
148       if (p + 1 < keyframes->n_properties)
149         {
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));
153         }
154       KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p) = NULL;
155
156       for (k = keyframes->n_keyframes - 2; k >= 0; k--)
157         {
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;
162         }
163     }
164   else
165     {
166       keyframes->values = g_new0 (GtkCssValue *, keyframes->n_keyframes);
167     }
168
169   return p;
170 }
171
172 static GtkCssKeyframes *
173 gtk_css_keyframes_new (void)
174 {
175   GtkCssKeyframes *keyframes;
176
177   keyframes = g_slice_new0 (GtkCssKeyframes);
178   keyframes->ref_count = 1;
179
180   gtk_css_keyframes_add_keyframe (keyframes, 0);
181   gtk_css_keyframes_add_keyframe (keyframes, 1);
182
183   return keyframes;
184 }
185
186 static gboolean
187 keyframes_set_value (GtkCssKeyframes     *keyframes,
188                      guint                k,
189                      GtkCssStyleProperty *property,
190                      GtkCssValue         *value)
191 {
192   guint p;
193
194   if (!_gtk_css_style_property_is_animated (property))
195     return FALSE;
196
197   p = gtk_css_keyframes_lookup_property (keyframes, _gtk_css_style_property_get_id (property));
198   
199   if (KEYFRAMES_VALUE (keyframes, k, p))
200     _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
201
202   KEYFRAMES_VALUE (keyframes, k, p) = _gtk_css_value_ref (value);
203
204   return TRUE;
205 }
206
207 static gboolean
208 parse_declaration (GtkCssKeyframes *keyframes,
209                    guint            k,
210                    GtkCssParser    *parser)
211 {
212   GtkStyleProperty *property;
213   GtkCssValue *value;
214   char *name;
215
216   while (_gtk_css_parser_try (parser, ";", TRUE))
217     {
218       /* SKIP ALL THE THINGS! */
219     }
220
221   name = _gtk_css_parser_try_ident (parser, TRUE);
222   if (name == NULL)
223     {
224       _gtk_css_parser_error (parser, "No property name given");
225       return FALSE;
226     }
227
228   property = _gtk_style_property_lookup (name);
229   if (property == NULL)
230     {
231       /* should be GTK_CSS_PROVIDER_ERROR_NAME */
232       _gtk_css_parser_error (parser, "No property named '%s'", name);
233       g_free (name);
234       return FALSE;
235     }
236
237   g_free (name);
238
239   if (!_gtk_css_parser_try (parser, ":", TRUE))
240     {
241       _gtk_css_parser_error (parser, "Expected a ':'");
242       return FALSE;
243     }
244
245   value = _gtk_style_property_parse_value (property, parser);
246   if (value == NULL)
247     return FALSE;
248
249   if (!_gtk_css_parser_try (parser, ";", TRUE) &&
250       !_gtk_css_parser_begins_with (parser, '}'))
251     {
252       _gtk_css_parser_error (parser, "Junk at end of value");
253       _gtk_css_value_unref (value);
254       return FALSE;
255     }
256
257   if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
258     {
259       GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
260       gboolean animatable = FALSE;
261       guint i;
262
263       for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
264         {
265           GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
266           GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
267           
268           animatable |= keyframes_set_value (keyframes, k, child, sub);
269         }
270
271       if (!animatable)
272         _gtk_css_parser_error (parser, "shorthand '%s' cannot be animated", _gtk_style_property_get_name (property));
273     }
274   else if (GTK_IS_CSS_STYLE_PROPERTY (property))
275     {
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));
278     }
279   else
280     {
281       g_assert_not_reached ();
282     }
283       
284   _gtk_css_value_unref (value);
285
286   return TRUE;
287 }
288
289 static gboolean
290 parse_block (GtkCssKeyframes *keyframes,
291              guint            k,
292              GtkCssParser    *parser)
293 {
294   if (!_gtk_css_parser_try (parser, "{", TRUE))
295     {
296       _gtk_css_parser_error (parser, "Expected closing bracket after keyframes block");
297       return FALSE;
298     }
299
300   while (!_gtk_css_parser_try (parser, "}", TRUE))
301     {
302       if (!parse_declaration (keyframes, k, parser))
303         _gtk_css_parser_resync (parser, TRUE, '}');
304
305       if (_gtk_css_parser_is_eof (parser))
306         {
307           _gtk_css_parser_error (parser, "Expected closing '}' after keyframes block");
308           return FALSE;
309         }
310     }
311
312   return TRUE;
313 }
314
315 GtkCssKeyframes *
316 _gtk_css_keyframes_parse (GtkCssParser *parser)
317 {
318   GtkCssKeyframes *keyframes;
319   double progress;
320   guint k;
321
322   g_return_val_if_fail (parser != NULL, NULL);
323
324   keyframes = gtk_css_keyframes_new ();
325
326   while (!_gtk_css_parser_begins_with (parser, '}'))
327     {
328       if (_gtk_css_parser_try (parser, "from", TRUE))
329         progress = 0;
330       else if (_gtk_css_parser_try (parser, "to", TRUE))
331         progress = 1;
332       else if (_gtk_css_parser_try_double (parser, &progress) &&
333                _gtk_css_parser_try (parser, "%", TRUE))
334         {
335           if (progress < 0 || progress > 100)
336             {
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);
340               return NULL;
341             }
342           progress /= 100;
343         }
344       else
345         {
346           _gtk_css_parser_error (parser, "expected a percentage");
347           _gtk_css_keyframes_unref (keyframes);
348           return NULL;
349         }
350
351       k = gtk_css_keyframes_add_keyframe (keyframes, progress);
352
353       if (!parse_block (keyframes, k, parser))
354         {
355           _gtk_css_keyframes_unref (keyframes);
356           return NULL;
357         }
358     }
359
360   return keyframes;
361 }
362
363 static int
364 compare_property_by_name (gconstpointer a,
365                           gconstpointer b,
366                           gpointer      data)
367 {
368   GtkCssKeyframes *keyframes = data;
369
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]))));
374 }
375
376 void
377 _gtk_css_keyframes_print (GtkCssKeyframes *keyframes,
378                           GString         *string)
379 {
380   guint k, p;
381   guint *sorted;
382
383   g_return_if_fail (keyframes != NULL);
384   g_return_if_fail (string != NULL);
385
386   sorted = g_new (guint, keyframes->n_properties);
387   for (p = 0; p < keyframes->n_properties; p++)
388     sorted[p] = p;
389   g_qsort_with_data (sorted, keyframes->n_properties, sizeof (guint), compare_property_by_name, keyframes);
390
391   for (k = 0; k < keyframes->n_keyframes; k++)
392     {
393       /* useful for 0% and 100% which might be empty */
394       gboolean opened = FALSE;
395
396       for (p = 0; p < keyframes->n_properties; p++)
397         {
398           if (KEYFRAMES_VALUE (keyframes, k, sorted[p]) == NULL)
399             continue;
400
401           if (!opened)
402             {
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");
407               else
408                 g_string_append_printf (string, "  %g%% {\n", keyframes->keyframe_progress[k] * 100);
409               opened = TRUE;
410             }
411           
412           g_string_append_printf (string, "    %s: ", _gtk_style_property_get_name (
413                                                         GTK_STYLE_PROPERTY (
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");
418         }
419
420       if (opened)
421         g_string_append (string, "  }\n");
422     }
423
424   g_free (sorted);
425 }
426
427 GtkCssKeyframes *
428 _gtk_css_keyframes_compute (GtkCssKeyframes         *keyframes,
429                             GtkStyleProviderPrivate *provider,
430                             GtkCssComputedValues    *values,
431                             GtkCssComputedValues    *parent_values)
432 {
433   GtkCssKeyframes *resolved;
434   guint k, p;
435
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);
440
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);
447
448   for (p = 0; p < resolved->n_properties; p++)
449     {
450       for (k = 0; k < resolved->n_keyframes; k++)
451         {
452           if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
453             continue;
454
455           KEYFRAMES_VALUE (resolved, k, p) =  _gtk_css_value_compute (KEYFRAMES_VALUE (keyframes, k, p),
456                                                                       resolved->property_ids[p],
457                                                                       provider,
458                                                                       values,
459                                                                       parent_values,
460                                                                       NULL);
461         }
462     }
463
464   return resolved;
465 }
466
467 guint
468 _gtk_css_keyframes_get_n_properties (GtkCssKeyframes *keyframes)
469 {
470   g_return_val_if_fail (keyframes != NULL, 0);
471
472   return keyframes->n_properties;
473 }
474
475 guint
476 _gtk_css_keyframes_get_property_id (GtkCssKeyframes *keyframes,
477                                     guint            id)
478 {
479   g_return_val_if_fail (keyframes != NULL, 0);
480   g_return_val_if_fail (id < keyframes->n_properties, 0);
481
482   return keyframes->property_ids[id];
483 }
484
485 GtkCssValue *
486 _gtk_css_keyframes_get_value (GtkCssKeyframes *keyframes,
487                               guint            id,
488                               double           progress,
489                               GtkCssValue     *default_value)
490 {
491   GtkCssValue *start_value, *end_value, *result;
492   double start_progress, end_progress;
493   guint k;
494
495   g_return_val_if_fail (keyframes != NULL, 0);
496   g_return_val_if_fail (id < keyframes->n_properties, 0);
497
498   start_value = default_value;
499   start_progress = 0.0;
500   end_value = default_value;
501   end_progress = 1.0;
502
503   for (k = 0; k < keyframes->n_keyframes; k++)
504     {
505       if (KEYFRAMES_VALUE (keyframes, k, id) == NULL)
506         continue;
507
508       if (keyframes->keyframe_progress[k] == progress)
509         {
510           return _gtk_css_value_ref (KEYFRAMES_VALUE (keyframes, k, id));
511         }
512       else if (keyframes->keyframe_progress[k] < progress)
513         {
514           start_value = KEYFRAMES_VALUE (keyframes, k, id);
515           start_progress = keyframes->keyframe_progress[k];
516         }
517       else
518         {
519           end_value = KEYFRAMES_VALUE (keyframes, k, id);
520           end_progress = keyframes->keyframe_progress[k];
521           break;
522         }
523     }
524
525   progress = (progress - start_progress) / (end_progress - start_progress);
526
527   result = _gtk_css_value_transition (start_value,
528                                       end_value,
529                                       keyframes->property_ids[id],
530                                       progress);
531
532   /* XXX: Dear spec, what's the correct thing to do here? */
533   if (result == NULL)
534     return _gtk_css_value_ref (start_value);
535
536   return result;
537 }
538