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 "gtkcssanimatedstyleprivate.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 "gtkcsssectionprivate.h"
31 #include "gtkcssshorthandpropertyprivate.h"
32 #include "gtkcssstaticstyleprivate.h"
33 #include "gtkcssstringvalueprivate.h"
34 #include "gtkcssstylepropertyprivate.h"
35 #include "gtkcsstransitionprivate.h"
36 #include "gtkprivate.h"
37 #include "gtkstyleanimationprivate.h"
38 #include "gtkstylepropertyprivate.h"
39 #include "gtkstyleproviderprivate.h"
40 
G_DEFINE_TYPE(GtkCssAnimatedStyle,gtk_css_animated_style,GTK_TYPE_CSS_STYLE)41 G_DEFINE_TYPE (GtkCssAnimatedStyle, gtk_css_animated_style, GTK_TYPE_CSS_STYLE)
42 
43 static GtkCssValue *
44 gtk_css_animated_style_get_value (GtkCssStyle *style,
45                                   guint        id)
46 {
47   GtkCssAnimatedStyle *animated = GTK_CSS_ANIMATED_STYLE (style);
48 
49   if (animated->animated_values &&
50       id < animated->animated_values->len &&
51       g_ptr_array_index (animated->animated_values, id))
52     return g_ptr_array_index (animated->animated_values, id);
53 
54   return gtk_css_animated_style_get_intrinsic_value (animated, id);
55 }
56 
57 static GtkCssSection *
gtk_css_animated_style_get_section(GtkCssStyle * style,guint id)58 gtk_css_animated_style_get_section (GtkCssStyle *style,
59                                     guint        id)
60 {
61   GtkCssAnimatedStyle *animated = GTK_CSS_ANIMATED_STYLE (style);
62 
63   return gtk_css_style_get_section (animated->style, id);
64 }
65 
66 static gboolean
gtk_css_animated_style_is_static(GtkCssStyle * style)67 gtk_css_animated_style_is_static (GtkCssStyle *style)
68 {
69   GtkCssAnimatedStyle *animated = GTK_CSS_ANIMATED_STYLE (style);
70   GSList *list;
71 
72   for (list = animated->animations; list; list = list->next)
73     {
74       if (!_gtk_style_animation_is_static (list->data))
75         return FALSE;
76     }
77 
78   return TRUE;
79 }
80 
81 static void
gtk_css_animated_style_dispose(GObject * object)82 gtk_css_animated_style_dispose (GObject *object)
83 {
84   GtkCssAnimatedStyle *style = GTK_CSS_ANIMATED_STYLE (object);
85 
86   if (style->animated_values)
87     {
88       g_ptr_array_unref (style->animated_values);
89       style->animated_values = NULL;
90     }
91 
92   g_slist_free_full (style->animations, g_object_unref);
93   style->animations = NULL;
94 
95   G_OBJECT_CLASS (gtk_css_animated_style_parent_class)->dispose (object);
96 }
97 
98 static void
gtk_css_animated_style_finalize(GObject * object)99 gtk_css_animated_style_finalize (GObject *object)
100 {
101   GtkCssAnimatedStyle *style = GTK_CSS_ANIMATED_STYLE (object);
102 
103   g_object_unref (style->style);
104 
105   G_OBJECT_CLASS (gtk_css_animated_style_parent_class)->finalize (object);
106 }
107 
108 static void
gtk_css_animated_style_class_init(GtkCssAnimatedStyleClass * klass)109 gtk_css_animated_style_class_init (GtkCssAnimatedStyleClass *klass)
110 {
111   GObjectClass *object_class = G_OBJECT_CLASS (klass);
112   GtkCssStyleClass *style_class = GTK_CSS_STYLE_CLASS (klass);
113 
114   object_class->dispose = gtk_css_animated_style_dispose;
115   object_class->finalize = gtk_css_animated_style_finalize;
116 
117   style_class->get_value = gtk_css_animated_style_get_value;
118   style_class->get_section = gtk_css_animated_style_get_section;
119   style_class->is_static = gtk_css_animated_style_is_static;
120 }
121 
122 static void
gtk_css_animated_style_init(GtkCssAnimatedStyle * style)123 gtk_css_animated_style_init (GtkCssAnimatedStyle *style)
124 {
125 }
126 
127 void
gtk_css_animated_style_set_animated_value(GtkCssAnimatedStyle * style,guint id,GtkCssValue * value)128 gtk_css_animated_style_set_animated_value (GtkCssAnimatedStyle *style,
129                                            guint                id,
130                                            GtkCssValue         *value)
131 {
132   gtk_internal_return_if_fail (GTK_IS_CSS_ANIMATED_STYLE (style));
133   gtk_internal_return_if_fail (value != NULL);
134 
135   if (style->animated_values == NULL)
136     style->animated_values = g_ptr_array_new_with_free_func ((GDestroyNotify)_gtk_css_value_unref);
137   if (id >= style->animated_values->len)
138    g_ptr_array_set_size (style->animated_values, id + 1);
139 
140   if (g_ptr_array_index (style->animated_values, id))
141     _gtk_css_value_unref (g_ptr_array_index (style->animated_values, id));
142   g_ptr_array_index (style->animated_values, id) = _gtk_css_value_ref (value);
143 
144 }
145 
146 GtkCssValue *
gtk_css_animated_style_get_intrinsic_value(GtkCssAnimatedStyle * style,guint id)147 gtk_css_animated_style_get_intrinsic_value (GtkCssAnimatedStyle *style,
148                                             guint                id)
149 {
150   gtk_internal_return_val_if_fail (GTK_IS_CSS_ANIMATED_STYLE (style), NULL);
151 
152   return gtk_css_style_get_value (style->style, id);
153 }
154 
155 /* TRANSITIONS */
156 
157 typedef struct _TransitionInfo TransitionInfo;
158 struct _TransitionInfo {
159   guint index;                  /* index into value arrays */
160   gboolean pending;             /* TRUE if we still need to handle it */
161 };
162 
163 static void
transition_info_add(TransitionInfo infos[GTK_CSS_PROPERTY_N_PROPERTIES],GtkStyleProperty * property,guint index)164 transition_info_add (TransitionInfo    infos[GTK_CSS_PROPERTY_N_PROPERTIES],
165                      GtkStyleProperty *property,
166                      guint             index)
167 {
168   if (property == NULL)
169     {
170       guint i;
171 
172       for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++)
173         {
174           GtkCssStyleProperty *prop = _gtk_css_style_property_lookup_by_id (i);
175 
176           transition_info_add (infos, GTK_STYLE_PROPERTY (prop), index);
177         }
178     }
179   else if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
180     {
181       GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
182       guint i;
183 
184       for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
185         {
186           GtkCssStyleProperty *prop = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
187 
188           transition_info_add (infos, GTK_STYLE_PROPERTY (prop), index);
189         }
190     }
191   else if (GTK_IS_CSS_STYLE_PROPERTY (property))
192     {
193       guint id;
194 
195       if (!_gtk_css_style_property_is_animated (GTK_CSS_STYLE_PROPERTY (property)))
196         return;
197 
198       id = _gtk_css_style_property_get_id (GTK_CSS_STYLE_PROPERTY (property));
199       g_assert (id < GTK_CSS_PROPERTY_N_PROPERTIES);
200       infos[id].index = index;
201       infos[id].pending = TRUE;
202     }
203   else
204     {
205       g_assert_not_reached ();
206     }
207 }
208 
209 static void
transition_infos_set(TransitionInfo infos[GTK_CSS_PROPERTY_N_PROPERTIES],GtkCssValue * transitions)210 transition_infos_set (TransitionInfo  infos[GTK_CSS_PROPERTY_N_PROPERTIES],
211                       GtkCssValue    *transitions)
212 {
213   guint i;
214 
215   for (i = 0; i < _gtk_css_array_value_get_n_values (transitions); i++)
216     {
217       GtkStyleProperty *property;
218       GtkCssValue *prop_value;
219 
220       prop_value = _gtk_css_array_value_get_nth (transitions, i);
221       if (g_ascii_strcasecmp (_gtk_css_ident_value_get (prop_value), "all") == 0)
222         property = NULL;
223       else
224         {
225           property = _gtk_style_property_lookup (_gtk_css_ident_value_get (prop_value));
226           if (property == NULL)
227             continue;
228         }
229 
230       transition_info_add (infos, property, i);
231     }
232 }
233 
234 static GtkStyleAnimation *
gtk_css_animated_style_find_transition(GtkCssAnimatedStyle * style,guint property_id)235 gtk_css_animated_style_find_transition (GtkCssAnimatedStyle *style,
236                                         guint                property_id)
237 {
238   GSList *list;
239 
240   for (list = style->animations; list; list = list->next)
241     {
242       if (!GTK_IS_CSS_TRANSITION (list->data))
243         continue;
244 
245       if (_gtk_css_transition_get_property (list->data) == property_id)
246         return list->data;
247     }
248 
249   return NULL;
250 }
251 
252 static GSList *
gtk_css_animated_style_create_css_transitions(GSList * animations,GtkCssStyle * base_style,gint64 timestamp,GtkCssStyle * source)253 gtk_css_animated_style_create_css_transitions (GSList              *animations,
254                                                GtkCssStyle         *base_style,
255                                                gint64               timestamp,
256                                                GtkCssStyle         *source)
257 {
258   TransitionInfo transitions[GTK_CSS_PROPERTY_N_PROPERTIES] = { { 0, } };
259   GtkCssValue *durations, *delays, *timing_functions;
260   guint i;
261 
262   transition_infos_set (transitions, gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_TRANSITION_PROPERTY));
263 
264   durations = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_TRANSITION_DURATION);
265   delays = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_TRANSITION_DELAY);
266   timing_functions = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_TRANSITION_TIMING_FUNCTION);
267 
268   for (i = 0; i < GTK_CSS_PROPERTY_N_PROPERTIES; i++)
269     {
270       GtkStyleAnimation *animation;
271       GtkCssValue *start, *end;
272       double duration, delay;
273 
274       if (!transitions[i].pending)
275         continue;
276 
277       duration = _gtk_css_number_value_get (_gtk_css_array_value_get_nth (durations, transitions[i].index), 100);
278       delay = _gtk_css_number_value_get (_gtk_css_array_value_get_nth (delays, transitions[i].index), 100);
279       if (duration + delay == 0.0)
280         continue;
281 
282       if (GTK_IS_CSS_ANIMATED_STYLE (source))
283         {
284           start = gtk_css_animated_style_get_intrinsic_value (GTK_CSS_ANIMATED_STYLE (source), i);
285           end = gtk_css_style_get_value (base_style, i);
286 
287           if (_gtk_css_value_equal (start, end))
288             {
289               animation = gtk_css_animated_style_find_transition (GTK_CSS_ANIMATED_STYLE (source), i);
290               if (animation)
291                 {
292                   animation = _gtk_style_animation_advance (animation, timestamp);
293                   animations = g_slist_prepend (animations, animation);
294                 }
295 
296               continue;
297             }
298         }
299 
300       if (_gtk_css_value_equal (gtk_css_style_get_value (source, i),
301                                 gtk_css_style_get_value (base_style, i)))
302         continue;
303 
304       animation = _gtk_css_transition_new (i,
305                                            gtk_css_style_get_value (source, i),
306                                            _gtk_css_array_value_get_nth (timing_functions, i),
307                                            timestamp,
308                                            duration * G_USEC_PER_SEC,
309                                            delay * G_USEC_PER_SEC);
310       animations = g_slist_prepend (animations, animation);
311     }
312 
313   return animations;
314 }
315 
316 static GtkStyleAnimation *
gtk_css_animated_style_find_animation(GSList * animations,const char * name)317 gtk_css_animated_style_find_animation (GSList     *animations,
318                                        const char *name)
319 {
320   GSList *list;
321 
322   for (list = animations; list; list = list->next)
323     {
324       if (!GTK_IS_CSS_ANIMATION (list->data))
325         continue;
326 
327       if (g_str_equal (_gtk_css_animation_get_name (list->data), name))
328         return list->data;
329     }
330 
331   return NULL;
332 }
333 
334 static GSList *
gtk_css_animated_style_create_css_animations(GSList * animations,GtkCssStyle * base_style,GtkCssStyle * parent_style,gint64 timestamp,GtkStyleProviderPrivate * provider,GtkCssStyle * source)335 gtk_css_animated_style_create_css_animations (GSList                  *animations,
336                                               GtkCssStyle             *base_style,
337                                               GtkCssStyle             *parent_style,
338                                               gint64                   timestamp,
339                                               GtkStyleProviderPrivate *provider,
340                                               GtkCssStyle             *source)
341 {
342   GtkCssValue *durations, *delays, *timing_functions, *animation_names;
343   GtkCssValue *iteration_counts, *directions, *play_states, *fill_modes;
344   guint i;
345 
346   animation_names = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_NAME);
347   durations = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_DURATION);
348   delays = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_DELAY);
349   timing_functions = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_TIMING_FUNCTION);
350   iteration_counts = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_ITERATION_COUNT);
351   directions = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_DIRECTION);
352   play_states = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_PLAY_STATE);
353   fill_modes = gtk_css_style_get_value (base_style, GTK_CSS_PROPERTY_ANIMATION_FILL_MODE);
354 
355   for (i = 0; i < _gtk_css_array_value_get_n_values (animation_names); i++)
356     {
357       GtkStyleAnimation *animation;
358       GtkCssKeyframes *keyframes;
359       const char *name;
360 
361       name = _gtk_css_ident_value_get (_gtk_css_array_value_get_nth (animation_names, i));
362       if (g_ascii_strcasecmp (name, "none") == 0)
363         continue;
364 
365       animation = gtk_css_animated_style_find_animation (animations, name);
366       if (animation)
367         continue;
368 
369       if (GTK_IS_CSS_ANIMATED_STYLE (source))
370         animation = gtk_css_animated_style_find_animation (GTK_CSS_ANIMATED_STYLE (source)->animations, name);
371 
372       if (animation)
373         {
374           animation = _gtk_css_animation_advance_with_play_state (GTK_CSS_ANIMATION (animation),
375                                                                   timestamp,
376                                                                   _gtk_css_play_state_value_get (_gtk_css_array_value_get_nth (play_states, i)));
377         }
378       else
379         {
380           keyframes = _gtk_style_provider_private_get_keyframes (provider, name);
381           if (keyframes == NULL)
382             continue;
383 
384           keyframes = _gtk_css_keyframes_compute (keyframes, provider, base_style, parent_style);
385 
386           animation = _gtk_css_animation_new (name,
387                                               keyframes,
388                                               timestamp,
389                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (delays, i), 100) * G_USEC_PER_SEC,
390                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (durations, i), 100) * G_USEC_PER_SEC,
391                                               _gtk_css_array_value_get_nth (timing_functions, i),
392                                               _gtk_css_direction_value_get (_gtk_css_array_value_get_nth (directions, i)),
393                                               _gtk_css_play_state_value_get (_gtk_css_array_value_get_nth (play_states, i)),
394                                               _gtk_css_fill_mode_value_get (_gtk_css_array_value_get_nth (fill_modes, i)),
395                                               _gtk_css_number_value_get (_gtk_css_array_value_get_nth (iteration_counts, i), 100));
396           _gtk_css_keyframes_unref (keyframes);
397         }
398       animations = g_slist_prepend (animations, animation);
399     }
400 
401   return animations;
402 }
403 
404 /* PUBLIC API */
405 
406 static void
gtk_css_animated_style_apply_animations(GtkCssAnimatedStyle * style)407 gtk_css_animated_style_apply_animations (GtkCssAnimatedStyle *style)
408 {
409   GSList *l;
410 
411   for (l = style->animations; l; l = l->next)
412     {
413       GtkStyleAnimation *animation = l->data;
414 
415       _gtk_style_animation_apply_values (animation,
416                                          GTK_CSS_ANIMATED_STYLE (style));
417     }
418 }
419 
420 GtkCssStyle *
gtk_css_animated_style_new(GtkCssStyle * base_style,GtkCssStyle * parent_style,gint64 timestamp,GtkStyleProviderPrivate * provider,GtkCssStyle * previous_style)421 gtk_css_animated_style_new (GtkCssStyle             *base_style,
422                             GtkCssStyle             *parent_style,
423                             gint64                   timestamp,
424                             GtkStyleProviderPrivate *provider,
425                             GtkCssStyle             *previous_style)
426 {
427   GtkCssAnimatedStyle *result;
428   GSList *animations;
429 
430   gtk_internal_return_val_if_fail (GTK_IS_CSS_STYLE (base_style), NULL);
431   gtk_internal_return_val_if_fail (parent_style == NULL || GTK_IS_CSS_STYLE (parent_style), NULL);
432   gtk_internal_return_val_if_fail (GTK_IS_STYLE_PROVIDER (provider), NULL);
433   gtk_internal_return_val_if_fail (previous_style == NULL || GTK_IS_CSS_STYLE (previous_style), NULL);
434 
435   if (timestamp == 0)
436     return g_object_ref (base_style);
437 
438   animations = NULL;
439 
440   if (previous_style != NULL)
441     animations = gtk_css_animated_style_create_css_transitions (animations, base_style, timestamp, previous_style);
442   animations = gtk_css_animated_style_create_css_animations (animations, base_style, parent_style, timestamp, provider, previous_style);
443 
444   if (animations == NULL)
445     return g_object_ref (base_style);
446 
447   result = g_object_new (GTK_TYPE_CSS_ANIMATED_STYLE, NULL);
448 
449   result->style = g_object_ref (base_style);
450   result->current_time = timestamp;
451   result->animations = animations;
452 
453   gtk_css_animated_style_apply_animations (result);
454 
455   return GTK_CSS_STYLE (result);
456 }
457 
458 GtkCssStyle *
gtk_css_animated_style_new_advance(GtkCssAnimatedStyle * source,GtkCssStyle * base,gint64 timestamp)459 gtk_css_animated_style_new_advance (GtkCssAnimatedStyle *source,
460                                     GtkCssStyle         *base,
461                                     gint64               timestamp)
462 {
463   GtkCssAnimatedStyle *result;
464   GSList *l, *animations;
465 
466   gtk_internal_return_val_if_fail (GTK_IS_CSS_ANIMATED_STYLE (source), NULL);
467   gtk_internal_return_val_if_fail (GTK_IS_CSS_STYLE (base), NULL);
468 
469   if (timestamp == 0 || timestamp == source->current_time)
470     return g_object_ref (source->style);
471 
472   gtk_internal_return_val_if_fail (timestamp > source->current_time, NULL);
473 
474   animations = NULL;
475   for (l = source->animations; l; l = l->next)
476     {
477       GtkStyleAnimation *animation = l->data;
478 
479       if (_gtk_style_animation_is_finished (animation))
480         continue;
481 
482       animation = _gtk_style_animation_advance (animation, timestamp);
483       animations = g_slist_prepend (animations, animation);
484     }
485   animations = g_slist_reverse (animations);
486 
487   if (animations == NULL)
488     return g_object_ref (source->style);
489 
490   result = g_object_new (GTK_TYPE_CSS_ANIMATED_STYLE, NULL);
491 
492   result->style = g_object_ref (base);
493   result->current_time = timestamp;
494   result->animations = animations;
495 
496   gtk_css_animated_style_apply_animations (result);
497 
498   return GTK_CSS_STYLE (result);
499 }
500