]> Pileus Git - ~andy/gtk/blob - gtk/gtkcsscomputedvalues.c
css: Huge refactoring to avoid computing wrong values
[~andy/gtk] / gtk / gtkcsscomputedvalues.c
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 "gtkcsscomputedvaluesprivate.h"
23
24 #include "gtkcssanimationprivate.h"
25 #include "gtkcssarrayvalueprivate.h"
26 #include "gtkcssenumvalueprivate.h"
27 #include "gtkcssinheritvalueprivate.h"
28 #include "gtkcssinitialvalueprivate.h"
29 #include "gtkcssnumbervalueprivate.h"
30 #include "gtkcssshorthandpropertyprivate.h"
31 #include "gtkcssstringvalueprivate.h"
32 #include "gtkcssstylepropertyprivate.h"
33 #include "gtkcsstransitionprivate.h"
34 #include "gtkstyleanimationprivate.h"
35 #include "gtkstylepropertiesprivate.h"
36 #include "gtkstylepropertyprivate.h"
37 #include "gtkstyleproviderprivate.h"
38
39 G_DEFINE_TYPE (GtkCssComputedValues, _gtk_css_computed_values, G_TYPE_OBJECT)
40
41 static void
42 gtk_css_computed_values_dispose (GObject *object)
43 {
44   GtkCssComputedValues *values = GTK_CSS_COMPUTED_VALUES (object);
45
46   if (values->values)
47     {
48       g_ptr_array_unref (values->values);
49       values->values = NULL;
50     }
51   if (values->sections)
52     {
53       g_ptr_array_unref (values->sections);
54       values->sections = NULL;
55     }
56   if (values->animated_values)
57     {
58       g_ptr_array_unref (values->animated_values);
59       values->animated_values = NULL;
60     }
61
62   g_slist_free_full (values->animations, g_object_unref);
63   values->animations = NULL;
64
65   G_OBJECT_CLASS (_gtk_css_computed_values_parent_class)->dispose (object);
66 }
67
68 static void
69 gtk_css_computed_values_finalize (GObject *object)
70 {
71   GtkCssComputedValues *values = GTK_CSS_COMPUTED_VALUES (object);
72
73   _gtk_bitmask_free (values->depends_on_parent);
74   _gtk_bitmask_free (values->equals_parent);
75   _gtk_bitmask_free (values->depends_on_color);
76   _gtk_bitmask_free (values->depends_on_font_size);
77
78   G_OBJECT_CLASS (_gtk_css_computed_values_parent_class)->finalize (object);
79 }
80
81 static void
82 _gtk_css_computed_values_class_init (GtkCssComputedValuesClass *klass)
83 {
84   GObjectClass *object_class = G_OBJECT_CLASS (klass);
85
86   object_class->dispose = gtk_css_computed_values_dispose;
87   object_class->finalize = gtk_css_computed_values_finalize;
88 }
89
90 static void
91 _gtk_css_computed_values_init (GtkCssComputedValues *values)
92 {
93   values->depends_on_parent = _gtk_bitmask_new ();
94   values->equals_parent = _gtk_bitmask_new ();
95   values->depends_on_color = _gtk_bitmask_new ();
96   values->depends_on_font_size = _gtk_bitmask_new ();
97 }
98
99 GtkCssComputedValues *
100 _gtk_css_computed_values_new (void)
101 {
102   return g_object_new (GTK_TYPE_CSS_COMPUTED_VALUES, NULL);
103 }
104
105 static void
106 maybe_unref_section (gpointer section)
107 {
108   if (section)
109     gtk_css_section_unref (section);
110 }
111
112 void
113 _gtk_css_computed_values_compute_value (GtkCssComputedValues    *values,
114                                         GtkStyleProviderPrivate *provider,
115                                         GtkCssComputedValues    *parent_values,
116                                         guint                    id,
117                                         GtkCssValue             *specified,
118                                         GtkCssSection           *section)
119 {
120   GtkCssDependencies dependencies;
121   GtkCssValue *value;
122
123   g_return_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values));
124   g_return_if_fail (GTK_IS_STYLE_PROVIDER_PRIVATE (provider));
125   g_return_if_fail (parent_values == NULL || GTK_IS_CSS_COMPUTED_VALUES (parent_values));
126
127   /* http://www.w3.org/TR/css3-cascade/#cascade
128    * Then, for every element, the value for each property can be found
129    * by following this pseudo-algorithm:
130    * 1) Identify all declarations that apply to the element
131    */
132   if (specified == NULL)
133     {
134       GtkCssStyleProperty *prop = _gtk_css_style_property_lookup_by_id (id);
135
136       if (_gtk_css_style_property_is_inherit (prop))
137         specified = _gtk_css_inherit_value_new ();
138       else
139         specified = _gtk_css_initial_value_new ();
140     }
141   else
142     _gtk_css_value_ref (specified);
143
144   value = _gtk_css_value_compute (specified, id, provider, values, parent_values, &dependencies);
145
146   _gtk_css_computed_values_set_value (values, id, value, dependencies, section);
147
148   _gtk_css_value_unref (value);
149   _gtk_css_value_unref (specified);
150 }
151
152 void
153 _gtk_css_computed_values_set_animated_value (GtkCssComputedValues *values,
154                                              guint                 id,
155                                              GtkCssValue          *value)
156 {
157   g_return_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values));
158   g_return_if_fail (value != NULL);
159
160   if (values->animated_values == NULL)
161     values->animated_values = g_ptr_array_new_with_free_func ((GDestroyNotify)_gtk_css_value_unref);
162   if (id >= values->animated_values->len)
163    g_ptr_array_set_size (values->animated_values, id + 1);
164
165   if (g_ptr_array_index (values->animated_values, id))
166     _gtk_css_value_unref (g_ptr_array_index (values->animated_values, id));
167   g_ptr_array_index (values->animated_values, id) = _gtk_css_value_ref (value);
168
169 }
170
171 void
172 _gtk_css_computed_values_set_value (GtkCssComputedValues *values,
173                                     guint                 id,
174                                     GtkCssValue          *value,
175                                     GtkCssDependencies    dependencies,
176                                     GtkCssSection        *section)
177 {
178   g_return_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values));
179
180   if (values->values == NULL)
181     values->values = g_ptr_array_new_with_free_func ((GDestroyNotify)_gtk_css_value_unref);
182   if (id >= values->values->len)
183    g_ptr_array_set_size (values->values, id + 1);
184
185   if (g_ptr_array_index (values->values, id))
186     _gtk_css_value_unref (g_ptr_array_index (values->values, id));
187   g_ptr_array_index (values->values, id) = _gtk_css_value_ref (value);
188
189   if (dependencies & (GTK_CSS_DEPENDS_ON_PARENT | GTK_CSS_EQUALS_PARENT))
190     values->depends_on_parent = _gtk_bitmask_set (values->depends_on_parent, id, TRUE);
191   if (dependencies & (GTK_CSS_EQUALS_PARENT))
192     values->equals_parent = _gtk_bitmask_set (values->equals_parent, id, TRUE);
193   if (dependencies & (GTK_CSS_DEPENDS_ON_COLOR))
194     values->depends_on_color = _gtk_bitmask_set (values->depends_on_color, id, TRUE);
195   if (dependencies & (GTK_CSS_DEPENDS_ON_FONT_SIZE))
196     values->depends_on_font_size = _gtk_bitmask_set (values->depends_on_font_size, id, TRUE);
197
198   if (section)
199     {
200       if (values->sections == NULL)
201         values->sections = g_ptr_array_new_with_free_func (maybe_unref_section);
202       if (values->sections->len <= id)
203         g_ptr_array_set_size (values->sections, id + 1);
204
205       g_ptr_array_index (values->sections, id) = gtk_css_section_ref (section);
206     }
207 }
208
209 GtkCssValue *
210 _gtk_css_computed_values_get_value (GtkCssComputedValues *values,
211                                     guint                 id)
212 {
213   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
214
215   if (values->animated_values &&
216       id < values->animated_values->len &&
217       g_ptr_array_index (values->animated_values, id))
218     return g_ptr_array_index (values->animated_values, id);
219
220   return _gtk_css_computed_values_get_intrinsic_value (values, id);
221 }
222
223 GtkCssValue *
224 _gtk_css_computed_values_get_intrinsic_value (GtkCssComputedValues *values,
225                                               guint                 id)
226 {
227   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
228
229   if (values->values == NULL ||
230       id >= values->values->len)
231     return NULL;
232
233   return g_ptr_array_index (values->values, id);
234 }
235
236 GtkCssSection *
237 _gtk_css_computed_values_get_section (GtkCssComputedValues *values,
238                                       guint                 id)
239 {
240   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
241
242   if (values->sections == NULL ||
243       id >= values->sections->len)
244     return NULL;
245
246   return g_ptr_array_index (values->sections, id);
247 }
248
249 GtkBitmask *
250 _gtk_css_computed_values_get_difference (GtkCssComputedValues *values,
251                                          GtkCssComputedValues *other)
252 {
253   GtkBitmask *result;
254   guint i, len;
255
256   len = MIN (values->values->len, other->values->len);
257   result = _gtk_bitmask_new ();
258   if (values->values->len != other->values->len)
259     result = _gtk_bitmask_invert_range (result, len, MAX (values->values->len, other->values->len));
260   
261   for (i = 0; i < len; i++)
262     {
263       if (!_gtk_css_value_equal (g_ptr_array_index (values->values, i),
264                                  g_ptr_array_index (other->values, i)))
265         result = _gtk_bitmask_set (result, i, TRUE);
266     }
267
268   return result;
269 }
270
271 /* TRANSITIONS */
272
273 typedef struct _TransitionInfo TransitionInfo;
274 struct _TransitionInfo {
275   guint index;                  /* index into value arrays */
276   gboolean pending;             /* TRUE if we still need to handle it */
277 };
278
279 static void
280 transition_info_add (TransitionInfo    infos[GTK_CSS_PROPERTY_N_PROPERTIES],
281                      GtkStyleProperty *property,
282                      guint             index)
283 {
284   if (property == NULL)
285     {
286       guint i;
287
288       for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++)
289         {
290           GtkCssStyleProperty *prop = _gtk_css_style_property_lookup_by_id (i);
291
292           transition_info_add (infos, GTK_STYLE_PROPERTY (prop), index);
293         }
294     }
295   else if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
296     {
297       GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
298       guint i;
299
300       for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
301         {
302           GtkCssStyleProperty *prop = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
303
304           transition_info_add (infos, GTK_STYLE_PROPERTY (prop), index);
305         }
306     }
307   else if (GTK_IS_CSS_STYLE_PROPERTY (property))
308     {
309       guint id;
310       
311       if (!_gtk_css_style_property_is_animated (GTK_CSS_STYLE_PROPERTY (property)))
312         return;
313
314       id = _gtk_css_style_property_get_id (GTK_CSS_STYLE_PROPERTY (property));
315       g_assert (id < GTK_CSS_PROPERTY_N_PROPERTIES);
316       infos[id].index = index;
317       infos[id].pending = TRUE;
318     }
319   else
320     {
321       g_assert_not_reached ();
322     }
323 }
324
325 static void
326 transition_infos_set (TransitionInfo  infos[GTK_CSS_PROPERTY_N_PROPERTIES],
327                       GtkCssValue    *transitions)
328 {
329   guint i;
330
331   for (i = 0; i < _gtk_css_array_value_get_n_values (transitions); i++)
332     {
333       GtkStyleProperty *property;
334       GtkCssValue *prop_value;
335
336       prop_value = _gtk_css_array_value_get_nth (transitions, i);
337       if (g_ascii_strcasecmp (_gtk_css_ident_value_get (prop_value), "all") == 0)
338         property = NULL;
339       else
340         {
341           property = _gtk_style_property_lookup (_gtk_css_ident_value_get (prop_value));
342           if (property == NULL)
343             continue;
344         }
345       
346       transition_info_add (infos, property, i);
347     }
348 }
349
350 static GtkStyleAnimation *
351 gtk_css_computed_values_find_transition (GtkCssComputedValues *values,
352                                          guint                 property_id)
353 {
354   GSList *list;
355
356   for (list = values->animations; list; list = list->next)
357     {
358       if (!GTK_IS_CSS_TRANSITION (list->data))
359         continue;
360
361       if (_gtk_css_transition_get_property (list->data) == property_id)
362         return list->data;
363     }
364
365   return NULL;
366 }
367
368 static void
369 gtk_css_computed_values_create_css_transitions (GtkCssComputedValues *values,
370                                                 gint64                timestamp,
371                                                 GtkCssComputedValues *source)
372 {
373   TransitionInfo transitions[GTK_CSS_PROPERTY_N_PROPERTIES] = { { 0, } };
374   GtkCssValue *durations, *delays, *timing_functions;
375   guint i;
376
377   transition_infos_set (transitions, _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_TRANSITION_PROPERTY));
378
379   durations = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_TRANSITION_DURATION);
380   delays = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_TRANSITION_DELAY);
381   timing_functions = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_TRANSITION_TIMING_FUNCTION);
382
383   for (i = 0; i < GTK_CSS_PROPERTY_N_PROPERTIES; i++)
384     {
385       GtkStyleAnimation *animation;
386       GtkCssValue *start, *end;
387       double duration, delay;
388
389       if (!transitions[i].pending)
390         continue;
391
392       duration = _gtk_css_number_value_get (_gtk_css_array_value_get_nth (durations, transitions[i].index), 100);
393       delay = _gtk_css_number_value_get (_gtk_css_array_value_get_nth (delays, transitions[i].index), 100);
394       if (duration + delay == 0.0)
395         continue;
396
397       start = _gtk_css_computed_values_get_intrinsic_value (source, i);
398       end = _gtk_css_computed_values_get_intrinsic_value (values, i);
399       if (_gtk_css_value_equal (start, end))
400         {
401           animation = gtk_css_computed_values_find_transition (GTK_CSS_COMPUTED_VALUES (source), i);
402           if (animation)
403             values->animations = g_slist_prepend (values->animations, g_object_ref (animation));
404         }
405       else
406         {
407           animation = _gtk_css_transition_new (i,
408                                                start,
409                                                end,
410                                                _gtk_css_array_value_get_nth (timing_functions, i),
411                                                timestamp + delay * G_USEC_PER_SEC,
412                                                timestamp + (delay + duration) * G_USEC_PER_SEC);
413           values->animations = g_slist_prepend (values->animations, animation);
414         }
415     }
416 }
417
418 static GtkStyleAnimation *
419 gtk_css_computed_values_find_animation (GtkCssComputedValues *values,
420                                         const char           *name)
421 {
422   GSList *list;
423
424   for (list = values->animations; list; list = list->next)
425     {
426       if (!GTK_IS_CSS_ANIMATION (list->data))
427         continue;
428
429       if (g_str_equal (_gtk_css_animation_get_name (list->data), name))
430         return list->data;
431     }
432
433   return NULL;
434 }
435
436 static void
437 gtk_css_computed_values_create_css_animations (GtkCssComputedValues    *values,
438                                                GtkCssComputedValues    *parent_values,
439                                                gint64                   timestamp,
440                                                GtkStyleProviderPrivate *provider,
441                                                GtkCssComputedValues    *source)
442 {
443   GtkCssValue *durations, *delays, *timing_functions, *animations;
444   GtkCssValue *iteration_counts, *directions, *play_states, *fill_modes;
445   guint i;
446
447   animations = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_NAME);
448   durations = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_DURATION);
449   delays = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_DELAY);
450   timing_functions = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_TIMING_FUNCTION);
451   iteration_counts = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_ITERATION_COUNT);
452   directions = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_DIRECTION);
453   play_states = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_PLAY_STATE);
454   fill_modes = _gtk_css_computed_values_get_value (values, GTK_CSS_PROPERTY_ANIMATION_FILL_MODE);
455
456   for (i = 0; i < _gtk_css_array_value_get_n_values (animations); i++)
457     {
458       GtkStyleAnimation *animation;
459       GtkCssKeyframes *keyframes;
460       const char *name;
461       
462       name = _gtk_css_ident_value_get (_gtk_css_array_value_get_nth (animations, i));
463       if (g_ascii_strcasecmp (name, "none") == 0)
464         continue;
465
466       animation = gtk_css_computed_values_find_animation (values, name);
467       if (animation)
468         continue;
469
470       if (source)
471         animation = gtk_css_computed_values_find_animation (source, name);
472
473       if (animation)
474         {
475           animation = _gtk_css_animation_copy (GTK_CSS_ANIMATION (animation),
476                                                timestamp,
477                                                _gtk_css_play_state_value_get (_gtk_css_array_value_get_nth (play_states, i)));
478         }
479       else
480         {
481           keyframes = _gtk_style_provider_private_get_keyframes (provider, name);
482           if (keyframes == NULL)
483             continue;
484
485           keyframes = _gtk_css_keyframes_compute (keyframes, provider, values, parent_values);
486
487           animation = _gtk_css_animation_new (name,
488                                               keyframes,
489                                               timestamp,
490                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (delays, i), 100) * G_USEC_PER_SEC,
491                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (durations, i), 100) * G_USEC_PER_SEC,
492                                               _gtk_css_array_value_get_nth (timing_functions, i),
493                                               _gtk_css_direction_value_get (_gtk_css_array_value_get_nth (directions, i)),
494                                               _gtk_css_play_state_value_get (_gtk_css_array_value_get_nth (play_states, i)),
495                                               _gtk_css_fill_mode_value_get (_gtk_css_array_value_get_nth (fill_modes, i)),
496                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (iteration_counts, i), 100));
497         }
498       values->animations = g_slist_prepend (values->animations, animation);
499     }
500 }
501
502 /* PUBLIC API */
503
504 void
505 _gtk_css_computed_values_create_animations (GtkCssComputedValues    *values,
506                                             GtkCssComputedValues    *parent_values,
507                                             gint64                   timestamp,
508                                             GtkStyleProviderPrivate *provider,
509                                             GtkCssComputedValues    *source)
510 {
511   if (source != NULL)
512     gtk_css_computed_values_create_css_transitions (values, timestamp, source);
513   gtk_css_computed_values_create_css_animations (values, parent_values, timestamp, provider, source);
514 }
515
516 GtkBitmask *
517 _gtk_css_computed_values_advance (GtkCssComputedValues *values,
518                                   gint64                timestamp)
519 {
520   GtkBitmask *changed;
521   GPtrArray *old_computed_values;
522   GSList *list;
523   guint i;
524
525   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
526   g_return_val_if_fail (timestamp >= values->current_time, NULL);
527
528   values->current_time = timestamp;
529   old_computed_values = values->animated_values;
530   values->animated_values = NULL;
531
532   list = values->animations;
533   while (list)
534     {
535       GtkStyleAnimation *animation = list->data;
536       
537       list = list->next;
538
539       _gtk_style_animation_set_values (animation,
540                                        timestamp,
541                                        GTK_CSS_COMPUTED_VALUES (values));
542       
543       if (_gtk_style_animation_is_finished (animation, timestamp))
544         {
545           values->animations = g_slist_remove (values->animations, animation);
546           g_object_unref (animation);
547         }
548     }
549
550   /* figure out changes */
551   changed = _gtk_bitmask_new ();
552
553   for (i = 0; i < GTK_CSS_PROPERTY_N_PROPERTIES; i++)
554     {
555       GtkCssValue *old_animated, *new_animated;
556
557       old_animated = old_computed_values && i < old_computed_values->len ? g_ptr_array_index (old_computed_values, i) : NULL;
558       new_animated = values->animated_values && i < values->animated_values->len ? g_ptr_array_index (values->animated_values, i) : NULL;
559
560       if (!_gtk_css_value_equal0 (old_animated, new_animated))
561         changed = _gtk_bitmask_set (changed, i, TRUE);
562     }
563
564   if (old_computed_values)
565     g_ptr_array_unref (old_computed_values);
566
567   return changed;
568 }
569
570 gboolean
571 _gtk_css_computed_values_is_static (GtkCssComputedValues *values)
572 {
573   GSList *list;
574
575   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), TRUE);
576
577   for (list = values->animations; list; list = list->next)
578     {
579       if (!_gtk_style_animation_is_static (list->data, values->current_time))
580         return FALSE;
581     }
582
583   return TRUE;
584 }
585
586 void
587 _gtk_css_computed_values_cancel_animations (GtkCssComputedValues *values)
588 {
589   g_return_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values));
590
591   if (values->animated_values)
592     {
593       g_ptr_array_unref (values->animated_values);
594       values->animated_values = NULL;
595     }
596
597   g_slist_free_full (values->animations, g_object_unref);
598   values->animations = NULL;
599 }
600