1 /* egg-animation.c
2 *
3 * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
4 *
5 * This file is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License as published by the Free
7 * Software Foundation; either version 2.1 of the License, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13 * License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <glib/gi18n.h>
20 #include <gobject/gvaluecollector.h>
21 #include <gdk/gdk.h>
22 #include <gtk/gtk.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "animation/egg-animation.h"
27 #include "animation/egg-frame-source.h"
28
29 #define FALLBACK_FRAME_RATE 60
30
31 typedef gdouble (*AlphaFunc) (gdouble offset);
32 typedef void (*TweenFunc) (const GValue *begin,
33 const GValue *end,
34 GValue *value,
35 gdouble offset);
36
37 typedef struct
38 {
39 gboolean is_child; /* Does GParamSpec belong to parent widget */
40 GParamSpec *pspec; /* GParamSpec of target property */
41 GValue begin; /* Begin value in animation */
42 GValue end; /* End value in animation */
43 } Tween;
44
45
46 struct _EggAnimation
47 {
48 GInitiallyUnowned parent_instance;
49
50 gpointer target; /* Target object to animate */
51 guint64 begin_msec; /* Time in which animation started */
52 guint duration_msec; /* Duration of animation */
53 guint mode; /* Tween mode */
54 gulong tween_handler; /* GSource or signal handler */
55 gulong after_paint_handler; /* signal handler */
56 gdouble last_offset; /* Track our last offset */
57 GArray *tweens; /* Array of tweens to perform */
58 GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */
59 GDestroyNotify notify; /* Notify callback */
60 gpointer notify_data; /* Data for notify */
61 };
62
63 G_DEFINE_TYPE (EggAnimation, egg_animation, G_TYPE_INITIALLY_UNOWNED)
64
65 enum {
66 PROP_0,
67 PROP_DURATION,
68 PROP_FRAME_CLOCK,
69 PROP_MODE,
70 PROP_TARGET,
71 LAST_PROP
72 };
73
74
75 enum {
76 TICK,
77 LAST_SIGNAL
78 };
79
80
81 /*
82 * Helper macros.
83 */
84 #define LAST_FUNDAMENTAL 64
85 #define TWEEN(type) \
86 static void \
87 tween_ ## type (const GValue * begin, \
88 const GValue * end, \
89 GValue * value, \
90 gdouble offset) \
91 { \
92 g ## type x = g_value_get_ ## type (begin); \
93 g ## type y = g_value_get_ ## type (end); \
94 g_value_set_ ## type (value, x + ((y - x) * offset)); \
95 }
96
97
98 /*
99 * Globals.
100 */
101 static AlphaFunc alpha_funcs[EGG_ANIMATION_LAST];
102 static gboolean debug;
103 static GParamSpec *properties[LAST_PROP];
104 static guint signals[LAST_SIGNAL];
105 static TweenFunc tween_funcs[LAST_FUNDAMENTAL];
106 static guint slow_down_factor = 1;
107
108
109 /*
110 * Tweeners for basic types.
111 */
112 TWEEN (int);
113 TWEEN (uint);
114 TWEEN (long);
115 TWEEN (ulong);
116 TWEEN (float);
117 TWEEN (double);
118
119
120 /**
121 * egg_animation_alpha_ease_in_cubic:
122 * @offset: (in): The position within the animation; 0.0 to 1.0.
123 *
124 * An alpha function to transform the offset within the animation.
125 * @EGG_ANIMATION_CUBIC means the valu ewill be transformed into
126 * cubic acceleration (x * x * x).
127 */
128 static gdouble
egg_animation_alpha_ease_in_cubic(gdouble offset)129 egg_animation_alpha_ease_in_cubic (gdouble offset)
130 {
131 return offset * offset * offset;
132 }
133
134
135 static gdouble
egg_animation_alpha_ease_out_cubic(gdouble offset)136 egg_animation_alpha_ease_out_cubic (gdouble offset)
137 {
138 gdouble p = offset - 1.0;
139
140 return p * p * p + 1.0;
141 }
142
143 static gdouble
egg_animation_alpha_ease_in_out_cubic(gdouble offset)144 egg_animation_alpha_ease_in_out_cubic (gdouble offset)
145 {
146 if (offset < .5)
147 return egg_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0;
148 else
149 return .5 + egg_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0;
150 }
151
152
153 /**
154 * egg_animation_alpha_linear:
155 * @offset: (in): The position within the animation; 0.0 to 1.0.
156 *
157 * An alpha function to transform the offset within the animation.
158 * @EGG_ANIMATION_LINEAR means no tranformation will be made.
159 *
160 * Returns: @offset.
161 * Side effects: None.
162 */
163 static gdouble
egg_animation_alpha_linear(gdouble offset)164 egg_animation_alpha_linear (gdouble offset)
165 {
166 return offset;
167 }
168
169
170 /**
171 * egg_animation_alpha_ease_in_quad:
172 * @offset: (in): The position within the animation; 0.0 to 1.0.
173 *
174 * An alpha function to transform the offset within the animation.
175 * @EGG_ANIMATION_EASE_IN_QUAD means that the value will be transformed
176 * into a quadratic acceleration.
177 *
178 * Returns: A tranformation of @offset.
179 * Side effects: None.
180 */
181 static gdouble
egg_animation_alpha_ease_in_quad(gdouble offset)182 egg_animation_alpha_ease_in_quad (gdouble offset)
183 {
184 return offset * offset;
185 }
186
187
188 /**
189 * egg_animation_alpha_ease_out_quad:
190 * @offset: (in): The position within the animation; 0.0 to 1.0.
191 *
192 * An alpha function to transform the offset within the animation.
193 * @EGG_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
194 * into a quadratic deceleration.
195 *
196 * Returns: A tranformation of @offset.
197 * Side effects: None.
198 */
199 static gdouble
egg_animation_alpha_ease_out_quad(gdouble offset)200 egg_animation_alpha_ease_out_quad (gdouble offset)
201 {
202 return -1.0 * offset * (offset - 2.0);
203 }
204
205
206 /**
207 * egg_animation_alpha_ease_in_out_quad:
208 * @offset: (in): The position within the animation; 0.0 to 1.0.
209 *
210 * An alpha function to transform the offset within the animation.
211 * @EGG_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
212 * into a quadratic acceleration for the first half, and quadratic
213 * deceleration the second half.
214 *
215 * Returns: A tranformation of @offset.
216 * Side effects: None.
217 */
218 static gdouble
egg_animation_alpha_ease_in_out_quad(gdouble offset)219 egg_animation_alpha_ease_in_out_quad (gdouble offset)
220 {
221 offset *= 2.0;
222 if (offset < 1.0)
223 return 0.5 * offset * offset;
224 offset -= 1.0;
225 return -0.5 * (offset * (offset - 2.0) - 1.0);
226 }
227
228
229 /**
230 * egg_animation_load_begin_values:
231 * @animation: (in): A #EggAnimation.
232 *
233 * Load the begin values for all the properties we are about to
234 * animate.
235 *
236 * Side effects: None.
237 */
238 static void
egg_animation_load_begin_values(EggAnimation * animation)239 egg_animation_load_begin_values (EggAnimation *animation)
240 {
241 GtkContainer *container;
242 Tween *tween;
243 guint i;
244
245 g_return_if_fail (EGG_IS_ANIMATION (animation));
246
247 for (i = 0; i < animation->tweens->len; i++)
248 {
249 tween = &g_array_index (animation->tweens, Tween, i);
250 g_value_reset (&tween->begin);
251 if (tween->is_child)
252 {
253 container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
254 gtk_container_child_get_property (container,
255 animation->target,
256 tween->pspec->name,
257 &tween->begin);
258 }
259 else
260 {
261 g_object_get_property (animation->target,
262 tween->pspec->name,
263 &tween->begin);
264 }
265 }
266 }
267
268
269 /**
270 * egg_animation_unload_begin_values:
271 * @animation: (in): A #EggAnimation.
272 *
273 * Unloads the begin values for the animation. This might be particularly
274 * useful once we support pointer types.
275 *
276 * Side effects: None.
277 */
278 static void
egg_animation_unload_begin_values(EggAnimation * animation)279 egg_animation_unload_begin_values (EggAnimation *animation)
280 {
281 Tween *tween;
282 guint i;
283
284 g_return_if_fail (EGG_IS_ANIMATION (animation));
285
286 for (i = 0; i < animation->tweens->len; i++)
287 {
288 tween = &g_array_index (animation->tweens, Tween, i);
289 g_value_reset (&tween->begin);
290 }
291 }
292
293
294 /**
295 * egg_animation_get_offset:
296 * @animation: A #EggAnimation.
297 * @frame_time: the time to present the frame, or 0 for current timing.
298 *
299 * Retrieves the position within the animation from 0.0 to 1.0. This
300 * value is calculated using the msec of the beginning of the animation
301 * and the current time.
302 *
303 * Returns: The offset of the animation from 0.0 to 1.0.
304 */
305 static gdouble
egg_animation_get_offset(EggAnimation * animation,gint64 frame_time)306 egg_animation_get_offset (EggAnimation *animation,
307 gint64 frame_time)
308 {
309 gdouble offset;
310 gint64 frame_msec;
311
312 g_return_val_if_fail (EGG_IS_ANIMATION (animation), 0.0);
313
314 if (frame_time == 0)
315 {
316 if (animation->frame_clock != NULL)
317 frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
318 else
319 frame_time = g_get_monotonic_time ();
320 }
321
322 frame_msec = frame_time / 1000L;
323
324 offset = (gdouble) (frame_msec - animation->begin_msec) /
325 (gdouble) MAX (animation->duration_msec, 1);
326
327 return CLAMP (offset, 0.0, 1.0);
328 }
329
330
331 /**
332 * egg_animation_update_property:
333 * @animation: (in): A #EggAnimation.
334 * @target: (in): A #GObject.
335 * @tween: (in): a #Tween containing the property.
336 * @value: (in): The new value for the property.
337 *
338 * Updates the value of a property on an object using @value.
339 *
340 * Side effects: The property of @target is updated.
341 */
342 static void
egg_animation_update_property(EggAnimation * animation,gpointer target,Tween * tween,const GValue * value)343 egg_animation_update_property (EggAnimation *animation,
344 gpointer target,
345 Tween *tween,
346 const GValue *value)
347 {
348 g_assert (EGG_IS_ANIMATION (animation));
349 g_assert (G_IS_OBJECT (target));
350 g_assert (tween);
351 g_assert (value);
352
353 g_object_set_property (target, tween->pspec->name, value);
354 }
355
356
357 /**
358 * egg_animation_update_child_property:
359 * @animation: (in): A #EggAnimation.
360 * @target: (in): A #GObject.
361 * @tween: (in): A #Tween containing the property.
362 * @value: (in): The new value for the property.
363 *
364 * Updates the value of the parent widget of the target to @value.
365 *
366 * Side effects: The property of @target<!-- -->'s parent widget is updated.
367 */
368 static void
egg_animation_update_child_property(EggAnimation * animation,gpointer target,Tween * tween,const GValue * value)369 egg_animation_update_child_property (EggAnimation *animation,
370 gpointer target,
371 Tween *tween,
372 const GValue *value)
373 {
374 GtkWidget *parent;
375
376 g_assert (EGG_IS_ANIMATION (animation));
377 g_assert (G_IS_OBJECT (target));
378 g_assert (tween);
379 g_assert (value);
380
381 parent = gtk_widget_get_parent (GTK_WIDGET (target));
382 gtk_container_child_set_property (GTK_CONTAINER (parent),
383 target,
384 tween->pspec->name,
385 value);
386 }
387
388
389 /**
390 * egg_animation_get_value_at_offset:
391 * @animation: (in): A #EggAnimation.
392 * @offset: (in): The offset in the animation from 0.0 to 1.0.
393 * @tween: (in): A #Tween containing the property.
394 * @value: (out): A #GValue in which to store the property.
395 *
396 * Retrieves a value for a particular position within the animation.
397 *
398 * Side effects: None.
399 */
400 static void
egg_animation_get_value_at_offset(EggAnimation * animation,gdouble offset,Tween * tween,GValue * value)401 egg_animation_get_value_at_offset (EggAnimation *animation,
402 gdouble offset,
403 Tween *tween,
404 GValue *value)
405 {
406 g_return_if_fail (EGG_IS_ANIMATION (animation));
407 g_return_if_fail (tween != NULL);
408 g_return_if_fail (value != NULL);
409 g_return_if_fail (value->g_type == tween->pspec->value_type);
410
411 if (value->g_type < LAST_FUNDAMENTAL)
412 {
413 /*
414 * If you hit the following assertion, you need to add a function
415 * to create the new value at the given offset.
416 */
417 g_assert (tween_funcs[value->g_type]);
418 tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
419 }
420 else
421 {
422 /*
423 * TODO: Support complex transitions.
424 */
425 if (offset >= 1.0)
426 g_value_copy (&tween->end, value);
427 }
428 }
429
430 static void
egg_animation_set_frame_clock(EggAnimation * animation,GdkFrameClock * frame_clock)431 egg_animation_set_frame_clock (EggAnimation *animation,
432 GdkFrameClock *frame_clock)
433 {
434 if (animation->frame_clock != frame_clock)
435 {
436 g_clear_object (&animation->frame_clock);
437 animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
438 }
439 }
440
441 static void
egg_animation_set_target(EggAnimation * animation,gpointer target)442 egg_animation_set_target (EggAnimation *animation,
443 gpointer target)
444 {
445 g_assert (!animation->target);
446
447 animation->target = g_object_ref (target);
448
449 if (GTK_IS_WIDGET (animation->target))
450 egg_animation_set_frame_clock (animation,
451 gtk_widget_get_frame_clock (animation->target));
452 }
453
454
455 /**
456 * egg_animation_tick:
457 * @animation: (in): A #EggAnimation.
458 *
459 * Moves the object properties to the next position in the animation.
460 *
461 * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
462 * Side effects: None.
463 */
464 static gboolean
egg_animation_tick(EggAnimation * animation,gdouble offset)465 egg_animation_tick (EggAnimation *animation,
466 gdouble offset)
467 {
468 gdouble alpha;
469 GValue value = { 0 };
470 Tween *tween;
471 guint i;
472
473 g_return_val_if_fail (EGG_IS_ANIMATION (animation), FALSE);
474
475 if (offset == animation->last_offset)
476 return offset < 1.0;
477
478 alpha = alpha_funcs[animation->mode](offset);
479
480 /*
481 * Update property values.
482 */
483 for (i = 0; i < animation->tweens->len; i++)
484 {
485 tween = &g_array_index (animation->tweens, Tween, i);
486 g_value_init (&value, tween->pspec->value_type);
487 egg_animation_get_value_at_offset (animation, alpha, tween, &value);
488 if (!tween->is_child)
489 {
490 egg_animation_update_property (animation,
491 animation->target,
492 tween,
493 &value);
494 }
495 else
496 {
497 egg_animation_update_child_property (animation,
498 animation->target,
499 tween,
500 &value);
501 }
502 g_value_unset (&value);
503 }
504
505 /*
506 * Notify anyone interested in the tick signal.
507 */
508 g_signal_emit (animation, signals[TICK], 0);
509
510 /*
511 * Flush any outstanding events to the graphics server (in the case of X).
512 */
513 #if !GTK_CHECK_VERSION (3, 13, 0)
514 if (GTK_IS_WIDGET (animation->target))
515 {
516 GdkWindow *window;
517
518 if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
519 gdk_window_flush (window);
520 }
521 #endif
522
523 animation->last_offset = offset;
524
525 return offset < 1.0;
526 }
527
528
529 /**
530 * egg_animation_timeout_cb:
531 * @user_data: (in): A #EggAnimation.
532 *
533 * Timeout from the main loop to move to the next step of the animation.
534 *
535 * Returns: %TRUE until the animation has completed; otherwise %FALSE.
536 * Side effects: None.
537 */
538 static gboolean
egg_animation_timeout_cb(gpointer user_data)539 egg_animation_timeout_cb (gpointer user_data)
540 {
541 EggAnimation *animation = user_data;
542 gboolean ret;
543 gdouble offset;
544
545 offset = egg_animation_get_offset (animation, 0);
546
547 if (!(ret = egg_animation_tick (animation, offset)))
548 egg_animation_stop (animation);
549
550 return ret;
551 }
552
553
554 static gboolean
egg_animation_widget_tick_cb(GdkFrameClock * frame_clock,EggAnimation * animation)555 egg_animation_widget_tick_cb (GdkFrameClock *frame_clock,
556 EggAnimation *animation)
557 {
558 gboolean ret = G_SOURCE_REMOVE;
559
560 g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
561 g_assert (EGG_IS_ANIMATION (animation));
562
563 if (animation->tween_handler)
564 {
565 gdouble offset;
566
567 offset = egg_animation_get_offset (animation, 0);
568
569 if (!(ret = egg_animation_tick (animation, offset)))
570 egg_animation_stop (animation);
571 }
572
573 return ret;
574 }
575
576
577 static void
egg_animation_widget_after_paint_cb(GdkFrameClock * frame_clock,EggAnimation * animation)578 egg_animation_widget_after_paint_cb (GdkFrameClock *frame_clock,
579 EggAnimation *animation)
580 {
581 gint64 base_time;
582 gint64 interval;
583 gint64 next_frame_time;
584 gdouble offset;
585
586 g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
587 g_assert (EGG_IS_ANIMATION (animation));
588
589 base_time = gdk_frame_clock_get_frame_time (frame_clock);
590 gdk_frame_clock_get_refresh_info (frame_clock, base_time, &interval, &next_frame_time);
591
592 offset = egg_animation_get_offset (animation, next_frame_time);
593
594 egg_animation_tick (animation, offset);
595 }
596
597
598 /**
599 * egg_animation_start:
600 * @animation: (in): A #EggAnimation.
601 *
602 * Start the animation. When the animation stops, the internal reference will
603 * be dropped and the animation may be finalized.
604 *
605 * Side effects: None.
606 */
607 void
egg_animation_start(EggAnimation * animation)608 egg_animation_start (EggAnimation *animation)
609 {
610 g_return_if_fail (EGG_IS_ANIMATION (animation));
611 g_return_if_fail (!animation->tween_handler);
612
613 g_object_ref_sink (animation);
614 egg_animation_load_begin_values (animation);
615
616 if (animation->frame_clock)
617 {
618 animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL;
619 animation->tween_handler =
620 g_signal_connect (animation->frame_clock,
621 "update",
622 G_CALLBACK (egg_animation_widget_tick_cb),
623 animation);
624 animation->after_paint_handler =
625 g_signal_connect (animation->frame_clock,
626 "after-paint",
627 G_CALLBACK (egg_animation_widget_after_paint_cb),
628 animation);
629 gdk_frame_clock_begin_updating (animation->frame_clock);
630 }
631 else
632 {
633 animation->begin_msec = g_get_monotonic_time () / 1000UL;
634 animation->tween_handler = egg_frame_source_add (FALLBACK_FRAME_RATE,
635 egg_animation_timeout_cb,
636 animation);
637 }
638 }
639
640
641 static void
egg_animation_notify(EggAnimation * self)642 egg_animation_notify (EggAnimation *self)
643 {
644 g_assert (EGG_IS_ANIMATION (self));
645
646 if (self->notify != NULL)
647 {
648 GDestroyNotify notify = self->notify;
649 gpointer data = self->notify_data;
650
651 self->notify = NULL;
652 self->notify_data = NULL;
653
654 notify (data);
655 }
656 }
657
658
659 /**
660 * egg_animation_stop:
661 * @animation: (in): A #EggAnimation.
662 *
663 * Stops a running animation. The internal reference to the animation is
664 * dropped and therefore may cause the object to finalize.
665 *
666 * Side effects: None.
667 */
668 void
egg_animation_stop(EggAnimation * animation)669 egg_animation_stop (EggAnimation *animation)
670 {
671 g_return_if_fail (EGG_IS_ANIMATION (animation));
672
673 if (animation->tween_handler)
674 {
675 if (animation->frame_clock)
676 {
677 gdk_frame_clock_end_updating (animation->frame_clock);
678 g_clear_signal_handler (&animation->tween_handler, animation->frame_clock);
679 g_clear_signal_handler (&animation->after_paint_handler, animation->frame_clock);
680 }
681 else
682 {
683 g_source_remove (animation->tween_handler);
684 animation->tween_handler = 0;
685 }
686 egg_animation_unload_begin_values (animation);
687 egg_animation_notify (animation);
688 g_object_unref (animation);
689 }
690 }
691
692
693 /**
694 * egg_animation_add_property:
695 * @animation: (in): A #EggAnimation.
696 * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
697 * @value: (in): The new value for the property at the end of the animation.
698 *
699 * Adds a new property to the set of properties to be animated during the
700 * lifetime of the animation.
701 *
702 * Side effects: None.
703 */
704 void
egg_animation_add_property(EggAnimation * animation,GParamSpec * pspec,const GValue * value)705 egg_animation_add_property (EggAnimation *animation,
706 GParamSpec *pspec,
707 const GValue *value)
708 {
709 Tween tween = { 0 };
710 GType type;
711
712 g_return_if_fail (EGG_IS_ANIMATION (animation));
713 g_return_if_fail (pspec != NULL);
714 g_return_if_fail (value != NULL);
715 g_return_if_fail (value->g_type);
716 g_return_if_fail (animation->target);
717 g_return_if_fail (!animation->tween_handler);
718
719 type = G_TYPE_FROM_INSTANCE (animation->target);
720 tween.is_child = !g_type_is_a (type, pspec->owner_type);
721 if (tween.is_child)
722 {
723 if (!GTK_IS_WIDGET (animation->target))
724 {
725 g_critical (_("Cannot locate property %s in class %s"),
726 pspec->name, g_type_name (type));
727 return;
728 }
729 }
730
731 tween.pspec = g_param_spec_ref (pspec);
732 g_value_init (&tween.begin, pspec->value_type);
733 g_value_init (&tween.end, pspec->value_type);
734 g_value_copy (value, &tween.end);
735 g_array_append_val (animation->tweens, tween);
736 }
737
738
739 /**
740 * egg_animation_dispose:
741 * @object: (in): A #EggAnimation.
742 *
743 * Releases any object references the animation contains.
744 *
745 * Side effects: None.
746 */
747 static void
egg_animation_dispose(GObject * object)748 egg_animation_dispose (GObject *object)
749 {
750 EggAnimation *self = EGG_ANIMATION (object);
751
752 g_clear_object (&self->target);
753 g_clear_object (&self->frame_clock);
754
755 G_OBJECT_CLASS (egg_animation_parent_class)->dispose (object);
756 }
757
758
759 /**
760 * egg_animation_finalize:
761 * @object: (in): A #EggAnimation.
762 *
763 * Finalizes the object and releases any resources allocated.
764 *
765 * Side effects: None.
766 */
767 static void
egg_animation_finalize(GObject * object)768 egg_animation_finalize (GObject *object)
769 {
770 EggAnimation *self = EGG_ANIMATION (object);
771 Tween *tween;
772 guint i;
773
774 for (i = 0; i < self->tweens->len; i++)
775 {
776 tween = &g_array_index (self->tweens, Tween, i);
777 g_value_unset (&tween->begin);
778 g_value_unset (&tween->end);
779 g_param_spec_unref (tween->pspec);
780 }
781
782 g_array_unref (self->tweens);
783
784 G_OBJECT_CLASS (egg_animation_parent_class)->finalize (object);
785 }
786
787
788 /**
789 * egg_animation_set_property:
790 * @object: (in): A #GObject.
791 * @prop_id: (in): The property identifier.
792 * @value: (in): The given property.
793 * @pspec: (in): A #ParamSpec.
794 *
795 * Set a given #GObject property.
796 */
797 static void
egg_animation_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)798 egg_animation_set_property (GObject *object,
799 guint prop_id,
800 const GValue *value,
801 GParamSpec *pspec)
802 {
803 EggAnimation *animation = EGG_ANIMATION (object);
804
805 switch (prop_id)
806 {
807 case PROP_DURATION:
808 animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
809 break;
810
811 case PROP_FRAME_CLOCK:
812 egg_animation_set_frame_clock (animation, g_value_get_object (value));
813 break;
814
815 case PROP_MODE:
816 animation->mode = g_value_get_enum (value);
817 break;
818
819 case PROP_TARGET:
820 egg_animation_set_target (animation, g_value_get_object (value));
821 break;
822
823 default:
824 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
825 }
826 }
827
828
829 /**
830 * egg_animation_class_init:
831 * @klass: (in): A #EggAnimationClass.
832 *
833 * Initializes the GObjectClass.
834 *
835 * Side effects: Properties, signals, and vtables are initialized.
836 */
837 static void
egg_animation_class_init(EggAnimationClass * klass)838 egg_animation_class_init (EggAnimationClass *klass)
839 {
840 GObjectClass *object_class;
841 const gchar *slow_down_factor_env;
842
843 debug = !!g_getenv ("EGG_ANIMATION_DEBUG");
844 slow_down_factor_env = g_getenv ("EGG_ANIMATION_SLOW_DOWN_FACTOR");
845
846 if (slow_down_factor_env)
847 slow_down_factor = MAX (1, atoi (slow_down_factor_env));
848
849 object_class = G_OBJECT_CLASS (klass);
850 object_class->dispose = egg_animation_dispose;
851 object_class->finalize = egg_animation_finalize;
852 object_class->set_property = egg_animation_set_property;
853
854 /**
855 * EggAnimation:duration:
856 *
857 * The "duration" property is the total number of milliseconds that the
858 * animation should run before being completed.
859 */
860 properties[PROP_DURATION] =
861 g_param_spec_uint ("duration",
862 "Duration",
863 "The duration of the animation",
864 0,
865 G_MAXUINT,
866 250,
867 (G_PARAM_WRITABLE |
868 G_PARAM_CONSTRUCT_ONLY |
869 G_PARAM_STATIC_STRINGS));
870
871 properties[PROP_FRAME_CLOCK] =
872 g_param_spec_object ("frame-clock",
873 "Frame Clock",
874 "An optional frame-clock to synchronize with.",
875 GDK_TYPE_FRAME_CLOCK,
876 (G_PARAM_WRITABLE |
877 G_PARAM_CONSTRUCT_ONLY |
878 G_PARAM_STATIC_STRINGS));
879
880 /**
881 * EggAnimation:mode:
882 *
883 * The "mode" property is the Alpha function that should be used to
884 * determine the offset within the animation based on the current
885 * offset in the animations duration.
886 */
887 properties[PROP_MODE] =
888 g_param_spec_enum ("mode",
889 "Mode",
890 "The animation mode",
891 EGG_TYPE_ANIMATION_MODE,
892 EGG_ANIMATION_LINEAR,
893 (G_PARAM_WRITABLE |
894 G_PARAM_CONSTRUCT_ONLY |
895 G_PARAM_STATIC_STRINGS));
896
897 /**
898 * EggAnimation:target:
899 *
900 * The "target" property is the #GObject that should have its properties
901 * animated.
902 */
903 properties[PROP_TARGET] =
904 g_param_spec_object ("target",
905 "Target",
906 "The target of the animation",
907 G_TYPE_OBJECT,
908 (G_PARAM_WRITABLE |
909 G_PARAM_CONSTRUCT_ONLY |
910 G_PARAM_STATIC_STRINGS));
911
912 g_object_class_install_properties (object_class, LAST_PROP, properties);
913
914 /**
915 * EggAnimation::tick:
916 *
917 * The "tick" signal is emitted on each frame in the animation.
918 */
919 signals[TICK] = g_signal_new ("tick",
920 EGG_TYPE_ANIMATION,
921 G_SIGNAL_RUN_FIRST,
922 0,
923 NULL, NULL, NULL,
924 G_TYPE_NONE,
925 0);
926
927 #define SET_ALPHA(_T, _t) \
928 alpha_funcs[EGG_ANIMATION_ ## _T] = egg_animation_alpha_ ## _t
929
930 SET_ALPHA (LINEAR, linear);
931 SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
932 SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
933 SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
934 SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
935 SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
936 SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
937
938 #define SET_TWEEN(_T, _t) \
939 G_STMT_START { \
940 guint idx = G_TYPE_ ## _T; \
941 tween_funcs[idx] = tween_ ## _t; \
942 } G_STMT_END
943
944 SET_TWEEN (INT, int);
945 SET_TWEEN (UINT, uint);
946 SET_TWEEN (LONG, long);
947 SET_TWEEN (ULONG, ulong);
948 SET_TWEEN (FLOAT, float);
949 SET_TWEEN (DOUBLE, double);
950 }
951
952
953 /**
954 * egg_animation_init:
955 * @animation: (in): A #EggAnimation.
956 *
957 * Initializes the #EggAnimation instance.
958 *
959 * Side effects: Everything.
960 */
961 static void
egg_animation_init(EggAnimation * animation)962 egg_animation_init (EggAnimation *animation)
963 {
964 animation->duration_msec = 250;
965 animation->mode = EGG_ANIMATION_EASE_IN_OUT_QUAD;
966 animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
967 animation->last_offset = -G_MINDOUBLE;
968 }
969
970
971 /**
972 * egg_animation_mode_get_type:
973 *
974 * Retrieves the GType for #EggAnimationMode.
975 *
976 * Returns: A GType.
977 * Side effects: GType registered on first call.
978 */
979 GType
egg_animation_mode_get_type(void)980 egg_animation_mode_get_type (void)
981 {
982 static GType type_id = 0;
983 static const GEnumValue values[] = {
984 { EGG_ANIMATION_LINEAR, "EGG_ANIMATION_LINEAR", "linear" },
985 { EGG_ANIMATION_EASE_IN_QUAD, "EGG_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
986 { EGG_ANIMATION_EASE_IN_OUT_QUAD, "EGG_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
987 { EGG_ANIMATION_EASE_OUT_QUAD, "EGG_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
988 { EGG_ANIMATION_EASE_IN_CUBIC, "EGG_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
989 { EGG_ANIMATION_EASE_OUT_CUBIC, "EGG_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
990 { EGG_ANIMATION_EASE_IN_OUT_CUBIC, "EGG_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
991 { 0 }
992 };
993
994 if (G_UNLIKELY (!type_id))
995 type_id = g_enum_register_static ("EggAnimationMode", values);
996 return type_id;
997 }
998
999 /**
1000 * egg_object_animatev:
1001 * @object: A #GObject.
1002 * @mode: The animation mode.
1003 * @duration_msec: The duration in milliseconds.
1004 * @frame_clock: (nullable): The #GdkFrameClock to synchronize to.
1005 * @first_property: The first property to animate.
1006 * @args: A variadac list of arguments
1007 *
1008 * Returns: (transfer none): A #EggAnimation.
1009 */
1010 EggAnimation *
egg_object_animatev(gpointer object,EggAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,const gchar * first_property,va_list args)1011 egg_object_animatev (gpointer object,
1012 EggAnimationMode mode,
1013 guint duration_msec,
1014 GdkFrameClock *frame_clock,
1015 const gchar *first_property,
1016 va_list args)
1017 {
1018 EggAnimation *animation;
1019 GObjectClass *klass;
1020 GObjectClass *pklass;
1021 const gchar *name;
1022 GParamSpec *pspec;
1023 GtkWidget *parent;
1024 GValue value = { 0 };
1025 gchar *error = NULL;
1026 GType type;
1027 GType ptype;
1028 gboolean enable_animations;
1029
1030 g_return_val_if_fail (first_property != NULL, NULL);
1031 g_return_val_if_fail (mode < EGG_ANIMATION_LAST, NULL);
1032
1033 if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
1034 frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
1035
1036 /*
1037 * If we have a frame clock, then we must be in the gtk thread and we
1038 * should check GtkSettings for disabled animations. If we are disabled,
1039 * we will just make the timeout immediate.
1040 */
1041 if (frame_clock != NULL)
1042 {
1043 g_object_get (gtk_settings_get_default (),
1044 "gtk-enable-animations", &enable_animations,
1045 NULL);
1046
1047 if (enable_animations == FALSE)
1048 duration_msec = 0;
1049 }
1050
1051 name = first_property;
1052 type = G_TYPE_FROM_INSTANCE (object);
1053 klass = G_OBJECT_GET_CLASS (object);
1054 animation = g_object_new (EGG_TYPE_ANIMATION,
1055 "duration", duration_msec,
1056 "frame-clock", frame_clock,
1057 "mode", mode,
1058 "target", object,
1059 NULL);
1060
1061 do
1062 {
1063 /*
1064 * First check for the property on the object. If that does not exist
1065 * then check if the object has a parent and look at its child
1066 * properties (if it's a GtkWidget).
1067 */
1068 if (!(pspec = g_object_class_find_property (klass, name)))
1069 {
1070 if (!g_type_is_a (type, GTK_TYPE_WIDGET))
1071 {
1072 g_critical (_("Failed to find property %s in %s"),
1073 name, g_type_name (type));
1074 goto failure;
1075 }
1076 if (!(parent = gtk_widget_get_parent (object)))
1077 {
1078 g_critical (_("Failed to find property %s in %s"),
1079 name, g_type_name (type));
1080 goto failure;
1081 }
1082 pklass = G_OBJECT_GET_CLASS (parent);
1083 ptype = G_TYPE_FROM_INSTANCE (parent);
1084 if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
1085 {
1086 g_critical (_("Failed to find property %s in %s or parent %s"),
1087 name, g_type_name (type), g_type_name (ptype));
1088 goto failure;
1089 }
1090 }
1091
1092 g_value_init (&value, pspec->value_type);
1093 G_VALUE_COLLECT (&value, args, 0, &error);
1094 if (error != NULL)
1095 {
1096 g_critical (_("Failed to retrieve va_list value: %s"), error);
1097 g_free (error);
1098 goto failure;
1099 }
1100
1101 egg_animation_add_property (animation, pspec, &value);
1102 g_value_unset (&value);
1103 }
1104 while ((name = va_arg (args, const gchar *)));
1105
1106 egg_animation_start (animation);
1107
1108 return animation;
1109
1110 failure:
1111 g_object_ref_sink (animation);
1112 g_object_unref (animation);
1113 return NULL;
1114 }
1115
1116 /**
1117 * egg_object_animate:
1118 * @object: (in): A #GObject.
1119 * @mode: (in): The animation mode.
1120 * @duration_msec: (in): The duration in milliseconds.
1121 * @first_property: (in): The first property to animate.
1122 *
1123 * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
1124 * will be animated from their current value to the target value over the time period.
1125 *
1126 * Return value: (transfer none): A #EggAnimation.
1127 * Side effects: None.
1128 */
1129 EggAnimation*
egg_object_animate(gpointer object,EggAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,const gchar * first_property,...)1130 egg_object_animate (gpointer object,
1131 EggAnimationMode mode,
1132 guint duration_msec,
1133 GdkFrameClock *frame_clock,
1134 const gchar *first_property,
1135 ...)
1136 {
1137 EggAnimation *animation;
1138 va_list args;
1139
1140 va_start (args, first_property);
1141 animation = egg_object_animatev (object,
1142 mode,
1143 duration_msec,
1144 frame_clock,
1145 first_property,
1146 args);
1147 va_end (args);
1148
1149 return animation;
1150 }
1151
1152 /**
1153 * egg_object_animate_full:
1154 *
1155 * Return value: (transfer none): A #EggAnimation.
1156 */
1157 EggAnimation*
egg_object_animate_full(gpointer object,EggAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,GDestroyNotify notify,gpointer notify_data,const gchar * first_property,...)1158 egg_object_animate_full (gpointer object,
1159 EggAnimationMode mode,
1160 guint duration_msec,
1161 GdkFrameClock *frame_clock,
1162 GDestroyNotify notify,
1163 gpointer notify_data,
1164 const gchar *first_property,
1165 ...)
1166 {
1167 EggAnimation *animation;
1168 va_list args;
1169
1170 va_start (args, first_property);
1171 animation = egg_object_animatev (object,
1172 mode,
1173 duration_msec,
1174 frame_clock,
1175 first_property,
1176 args);
1177 va_end (args);
1178
1179 animation->notify = notify;
1180 animation->notify_data = notify_data;
1181
1182 return animation;
1183 }
1184