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