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