94f6dba7390cdd8e50e8108f27cd08ba196420d3
[profile/ivi/clutter.git] / clutter / clutter-animation.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2008  Intel Corporation.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  */
24
25 /**
26  * SECTION:clutter-animation
27  * @short_description: Simple implicit animations
28  * @See_Also: #ClutterAnimatable, #ClutterInterval, #ClutterAlpha,
29  *   #ClutterTimeline
30  *
31  * #ClutterAnimation is an object providing simple, implicit animations
32  * for #GObject<!-- -->s.
33  *
34  * #ClutterAnimation instances will bind one or more #GObject properties
35  * belonging to a #GObject to a #ClutterInterval, and will then use a
36  * #ClutterAlpha to interpolate the property between the initial and final
37  * values of the interval.
38  *
39  * The duration of the animation is set using clutter_animation_set_duration().
40  * The easing mode of the animation is set using clutter_animation_set_mode().
41  *
42  * If you want to control the animation you should retrieve the
43  * #ClutterTimeline using clutter_animation_get_timeline() and then
44  * use #ClutterTimeline functions like clutter_timeline_start(),
45  * clutter_timeline_pause() or clutter_timeline_stop().
46  *
47  * A #ClutterAnimation will emit the #ClutterAnimation::completed signal
48  * when the #ClutterTimeline used by the animation is completed; unlike
49  * #ClutterTimeline, though, the #ClutterAnimation::completed will not be
50  * emitted if #ClutterAnimation:loop is set to %TRUE - that is, a looping
51  * animation never completes.
52  *
53  * If your animation depends on user control you can force its completion
54  * using clutter_animation_completed().
55  *
56  * If the #GObject instance bound to a #ClutterAnimation implements the
57  * #ClutterAnimatable interface it is possible for that instance to
58  * control the way the initial and final states are interpolated.
59  *
60  * #ClutterAnimation<!-- -->s are distinguished from #ClutterBehaviour<!-- -->s
61  * because the former can only control #GObject properties of a single
62  * #GObject instance, while the latter can control multiple properties
63  * using accessor functions inside the #ClutterBehaviour
64  * <function>alpha_notify</function> virtual function, and can control
65  * multiple #ClutterActor<!-- -->s as well.
66  *
67  * For convenience, it is possible to use the clutter_actor_animate()
68  * function call which will take care of setting up and tearing down
69  * a #ClutterAnimation instance and animate an actor between its current
70  * state and the specified final state.
71  *
72  * <refsect2 id="clutter-AnimationMode-Script">
73  *   <title>Defining ClutterAnimationMode inside ClutterScript</title>
74  *   <para>When defining a #ClutterAnimation inside a ClutterScript
75  *   file or string the #ClutterAnimation:mode can be defined either
76  *   using the #ClutterAnimationMode enumeration values through their
77  *   "nick" (the short string used inside #GEnumValue), their numeric
78  *   id, or using the following strings:</para>
79  *   <variablelist>
80  *     <varlistentry>
81  *       <term>easeInQuad, easeOutQuad, easeInOutQuad</term>
82  *       <listitem><para>Corresponding to the quadratic easing
83  *       modes</para></listitem>
84  *     </varlistentry>
85  *     <varlistentry>
86  *       <term>easeInCubic, easeOutCubic, easeInOutCubic</term>
87  *       <listitem><para>Corresponding to the cubic easing
88  *       modes</para></listitem>
89  *     </varlistentry>
90  *     <varlistentry>
91  *       <term>easeInQuart, easeOutQuart, easeInOutQuart</term>
92  *       <listitem><para>Corresponding to the quartic easing
93  *       modes</para></listitem>
94  *     </varlistentry>
95  *     <varlistentry>
96  *       <term>easeInQuint, easeOutQuint, easeInOutQuint</term>
97  *       <listitem><para>Corresponding to the quintic easing
98  *       modes</para></listitem>
99  *     </varlistentry>
100  *     <varlistentry>
101  *       <term>easeInSine, easeOutSine, easeInOutSine</term>
102  *       <listitem><para>Corresponding to the sine easing
103  *       modes</para></listitem>
104  *     </varlistentry>
105  *     <varlistentry>
106  *       <term>easeInExpo, easeOutExpo, easeInOutExpo</term>
107  *       <listitem><para>Corresponding to the exponential easing
108  *       modes</para></listitem>
109  *     </varlistentry>
110  *     <varlistentry>
111  *       <term>easeInCirc, easeOutCirc, easeInOutCirc</term>
112  *       <listitem><para>Corresponding to the circular easing
113  *       modes</para></listitem>
114  *     </varlistentry>
115  *     <varlistentry>
116  *       <term>easeInElastic, easeOutElastic, easeInOutElastic</term>
117  *       <listitem><para>Corresponding to the overshooting elastic
118  *       easing modes</para></listitem>
119  *     </varlistentry>
120  *     <varlistentry>
121  *       <term>easeInBack, easeOutBack, easeInOutBack</term>
122  *       <listitem><para>Corresponding to the overshooting cubic
123  *       easing modes</para></listitem>
124  *     </varlistentry>
125  *     <varlistentry>
126  *       <term>easeInBounce, easeOutBounce, easeInOutBounce</term>
127  *       <listitem><para>Corresponding to the bouncing easing
128  *       modes</para></listitem>
129  *     </varlistentry>
130  *   </variablelist>
131  * </refsect2>
132  *
133  * <example id="example-clutter-animation">
134  *   <title>Tweening using clutter_actor_animate()</title>
135  *   <programlisting>
136  * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../tests/interactive/test-easing.c">
137  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
138  * </xi:include>
139  *   </programlisting>
140  * </example>
141  *
142  * #ClutterAnimation is available since Clutter 1.0
143  */
144
145 #ifdef HAVE_CONFIG_H
146 #include "config.h"
147 #endif
148
149 #include <glib-object.h>
150 #include <gobject/gvaluecollector.h>
151
152 #include "clutter-alpha.h"
153 #include "clutter-animatable.h"
154 #include "clutter-animation.h"
155 #include "clutter-debug.h"
156 #include "clutter-enum-types.h"
157 #include "clutter-interval.h"
158 #include "clutter-marshal.h"
159 #include "clutter-private.h"
160 #include "clutter-scriptable.h"
161 #include "clutter-script-private.h"
162
163 #include "deprecated/clutter-animation.h"
164
165 enum
166 {
167   PROP_0,
168
169   PROP_OBJECT,
170   PROP_MODE,
171   PROP_DURATION,
172   PROP_LOOP,
173   PROP_TIMELINE,
174   PROP_ALPHA,
175
176   PROP_LAST
177 };
178
179 static GParamSpec *obj_props[PROP_LAST];
180
181 enum
182 {
183   STARTED,
184   COMPLETED,
185
186   LAST_SIGNAL
187 };
188
189 #define CLUTTER_ANIMATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_ANIMATION, ClutterAnimationPrivate))
190
191 struct _ClutterAnimationPrivate
192 {
193   GObject *object;
194
195   GHashTable *properties;
196
197   ClutterAlpha *alpha;
198   ClutterTimeline *timeline;
199
200   guint timeline_started_id;
201   guint timeline_completed_id;
202   guint timeline_frame_id;
203 };
204
205 static guint animation_signals[LAST_SIGNAL] = { 0, };
206
207 static GQuark quark_object_animation = 0;
208
209 static void clutter_scriptable_init (ClutterScriptableIface *iface);
210
211 static void                     clutter_animation_set_alpha_internal    (ClutterAnimation *animation,
212                                                                          ClutterAlpha     *alpha);
213 static ClutterAlpha *           clutter_animation_get_alpha_internal    (ClutterAnimation *animation);
214 static ClutterTimeline *        clutter_animation_get_timeline_internal (ClutterAnimation *animation);
215
216 G_DEFINE_TYPE_WITH_CODE (ClutterAnimation, clutter_animation, G_TYPE_OBJECT,
217                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
218                                                 clutter_scriptable_init));
219
220 static ClutterAlpha *
221 clutter_animation_get_alpha_internal (ClutterAnimation *animation)
222 {
223   ClutterAnimationPrivate *priv = animation->priv;
224
225   if (priv->alpha == NULL)
226     {
227       ClutterAlpha *alpha;
228
229       alpha = clutter_alpha_new ();
230       clutter_alpha_set_mode (alpha, CLUTTER_LINEAR);
231
232       priv->alpha = g_object_ref_sink (alpha);
233
234       g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_ALPHA]);
235     }
236
237   return priv->alpha;
238 }
239
240 static void
241 on_actor_destroy (ClutterActor     *actor,
242                   ClutterAnimation *animation)
243 {
244   ClutterAnimationPrivate *priv = animation->priv;
245   GObject *obj = G_OBJECT (actor);
246
247   if (obj == priv->object)
248     {
249       g_object_set_qdata (priv->object, quark_object_animation, NULL);
250       g_signal_handlers_disconnect_by_func (priv->object,
251                                             G_CALLBACK (on_actor_destroy),
252                                             animation);
253       g_object_unref (animation);
254     }
255 }
256
257 static void
258 clutter_animation_real_completed (ClutterAnimation *self)
259 {
260   ClutterAnimationPrivate *priv = self->priv;
261   ClutterAnimatable *animatable = NULL;
262   ClutterAnimation *animation;
263   ClutterTimeline *timeline;
264   ClutterTimelineDirection direction;
265   gpointer key, value;
266   GHashTableIter iter;
267
268   timeline = clutter_animation_get_timeline (self);
269   direction = clutter_timeline_get_direction (timeline);
270
271   if (CLUTTER_IS_ANIMATABLE (priv->object))
272     animatable = CLUTTER_ANIMATABLE (priv->object);
273
274   /* explicitly set the final state of the animation */
275   CLUTTER_NOTE (ANIMATION, "Set final state on object [%p]", priv->object);
276   g_hash_table_iter_init (&iter, priv->properties);
277   while (g_hash_table_iter_next (&iter, &key, &value))
278     {
279       const gchar *p_name = key;
280       ClutterInterval *interval = value;
281       GValue *p_value;
282
283       if (direction == CLUTTER_TIMELINE_FORWARD)
284         p_value = clutter_interval_peek_final_value (interval);
285       else
286         p_value = clutter_interval_peek_initial_value (interval);
287
288       if (animatable != NULL)
289         clutter_animatable_set_final_state (animatable, p_name, p_value);
290       else
291         g_object_set_property (priv->object, p_name, p_value);
292     }
293
294   /* at this point, if this animation was created by clutter_actor_animate()
295    * and friends, the animation will be attached to the object's data; since
296    * we want to allow developers to use g_signal_connect_after("completed")
297    * to concatenate a new animation, we need to remove the animation back
298    * pointer here, and unref() the animation. FIXME - we might want to
299    * provide a clutter_animation_attach()/clutter_animation_detach() pair
300    * to let the user reattach an animation
301    */
302   animation = g_object_get_qdata (priv->object, quark_object_animation);
303   if (animation == self)
304     {
305       CLUTTER_NOTE (ANIMATION, "Unsetting animation for actor [%p]",
306                     priv->object);
307
308       g_object_set_qdata (priv->object, quark_object_animation, NULL);
309       g_signal_handlers_disconnect_by_func (priv->object,
310                                             G_CALLBACK (on_actor_destroy),
311                                             animation);
312
313       CLUTTER_NOTE (ANIMATION, "Releasing the reference Animation [%p]",
314                     animation);
315       g_object_unref (animation);
316     }
317 }
318
319 static void
320 clutter_animation_finalize (GObject *gobject)
321 {
322   ClutterAnimationPrivate *priv = CLUTTER_ANIMATION (gobject)->priv;
323
324   CLUTTER_NOTE (ANIMATION,
325                 "Destroying properties table for Animation [%p]",
326                 gobject);
327   g_hash_table_destroy (priv->properties);
328
329   G_OBJECT_CLASS (clutter_animation_parent_class)->finalize (gobject);
330 }
331
332 static void
333 clutter_animation_dispose (GObject *gobject)
334 {
335   ClutterAnimationPrivate *priv = CLUTTER_ANIMATION (gobject)->priv;
336   ClutterTimeline *timeline;
337
338   if (priv->alpha != NULL)
339     timeline = clutter_alpha_get_timeline (priv->alpha);
340   else
341     timeline = priv->timeline;
342
343   if (timeline != NULL && priv->timeline_started_id != 0)
344     g_signal_handler_disconnect (timeline, priv->timeline_started_id);
345
346   if (timeline != NULL && priv->timeline_completed_id != 0)
347     g_signal_handler_disconnect (timeline, priv->timeline_completed_id);
348
349   if (timeline != NULL && priv->timeline_frame_id != 0)
350     g_signal_handler_disconnect (timeline, priv->timeline_frame_id);
351
352   priv->timeline_started_id = 0;
353   priv->timeline_completed_id = 0;
354   priv->timeline_frame_id = 0;
355
356   if (priv->alpha != NULL)
357     {
358       g_object_unref (priv->alpha);
359       priv->alpha = NULL;
360     }
361
362   if (priv->object != NULL)
363     {
364       g_object_unref (priv->object);
365       priv->object = NULL;
366     }
367
368   G_OBJECT_CLASS (clutter_animation_parent_class)->dispose (gobject);
369 }
370
371 static void
372 clutter_animation_set_property (GObject      *gobject,
373                                 guint         prop_id,
374                                 const GValue *value,
375                                 GParamSpec   *pspec)
376 {
377   ClutterAnimation *animation = CLUTTER_ANIMATION (gobject);
378
379   switch (prop_id)
380     {
381     case PROP_OBJECT:
382       clutter_animation_set_object (animation, g_value_get_object (value));
383       break;
384
385     case PROP_MODE:
386       clutter_animation_set_mode (animation, g_value_get_ulong (value));
387       break;
388
389     case PROP_DURATION:
390       clutter_animation_set_duration (animation, g_value_get_uint (value));
391       break;
392
393     case PROP_LOOP:
394       clutter_animation_set_loop (animation, g_value_get_boolean (value));
395       break;
396
397     case PROP_TIMELINE:
398       clutter_animation_set_timeline (animation, g_value_get_object (value));
399       break;
400
401     case PROP_ALPHA:
402       clutter_animation_set_alpha_internal (animation, g_value_get_object (value));
403       break;
404
405     default:
406       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
407       break;
408     }
409 }
410
411 static void
412 clutter_animation_get_property (GObject    *gobject,
413                                 guint       prop_id,
414                                 GValue     *value,
415                                 GParamSpec *pspec)
416 {
417   ClutterAnimation *animation = CLUTTER_ANIMATION (gobject);
418   ClutterAnimationPrivate *priv = animation->priv;
419
420   switch (prop_id)
421     {
422     case PROP_OBJECT:
423       g_value_set_object (value, priv->object);
424       break;
425
426     case PROP_MODE:
427       g_value_set_ulong (value, clutter_animation_get_mode (animation));
428       break;
429
430     case PROP_DURATION:
431       g_value_set_uint (value, clutter_animation_get_duration (animation));
432       break;
433
434     case PROP_LOOP:
435       g_value_set_boolean (value, clutter_animation_get_loop (animation));
436       break;
437
438     case PROP_TIMELINE:
439       g_value_set_object (value, clutter_animation_get_timeline (animation));
440       break;
441
442     case PROP_ALPHA:
443       g_value_set_object (value, clutter_animation_get_alpha_internal (animation));
444       break;
445
446     default:
447       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
448       break;
449     }
450 }
451
452 static gboolean
453 clutter_animation_parse_custom_node (ClutterScriptable *scriptable,
454                                      ClutterScript     *script,
455                                      GValue            *value,
456                                      const gchar       *name,
457                                      JsonNode          *node)
458 {
459   if (strncmp (name, "mode", 4) == 0)
460     {
461       gulong mode;
462
463       mode = _clutter_script_resolve_animation_mode (node);
464
465       g_value_init (value, G_TYPE_ULONG);
466       g_value_set_ulong (value, mode);
467
468       return TRUE;
469     }
470
471   return FALSE;
472 }
473
474 static void
475 clutter_scriptable_init (ClutterScriptableIface *iface)
476 {
477   iface->parse_custom_node = clutter_animation_parse_custom_node;
478 }
479
480 static void
481 clutter_animation_class_init (ClutterAnimationClass *klass)
482 {
483   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
484
485   quark_object_animation =
486     g_quark_from_static_string ("clutter-actor-animation");
487
488   g_type_class_add_private (klass, sizeof (ClutterAnimationPrivate));
489
490   klass->completed = clutter_animation_real_completed;
491
492   gobject_class->set_property = clutter_animation_set_property;
493   gobject_class->get_property = clutter_animation_get_property;
494   gobject_class->dispose = clutter_animation_dispose;
495   gobject_class->finalize = clutter_animation_finalize;
496
497   /**
498    * ClutterAnimation:object:
499    *
500    * The #GObject to which the animation applies.
501    *
502    * Since: 1.0
503    */
504   obj_props[PROP_OBJECT] =
505     g_param_spec_object ("object",
506                          P_("Object"),
507                          P_("Object to which the animation applies"),
508                          G_TYPE_OBJECT,
509                          CLUTTER_PARAM_READWRITE);
510
511   /**
512    * ClutterAnimation:mode:
513    *
514    * The animation mode, either a value from #ClutterAnimationMode
515    * or a value returned by clutter_alpha_register_func(). The
516    * default value is %CLUTTER_LINEAR.
517    *
518    * Since: 1.0
519    */
520   obj_props[PROP_MODE] =
521     g_param_spec_ulong ("mode",
522                         P_("Mode"),
523                         P_("The mode of the animation"),
524                         0, G_MAXULONG,
525                         CLUTTER_LINEAR,
526                         CLUTTER_PARAM_READWRITE);
527
528   /**
529    * ClutterAnimation:duration:
530    *
531    * The duration of the animation, expressed in milliseconds.
532    *
533    * Since: 1.0
534    */
535   obj_props[PROP_DURATION] =
536     g_param_spec_uint ("duration",
537                        P_("Duration"),
538                        P_("Duration of the animation, in milliseconds"),
539                        0, G_MAXUINT,
540                        0,
541                        CLUTTER_PARAM_READWRITE);
542
543   /**
544    * ClutterAnimation:loop:
545    *
546    * Whether the animation should loop.
547    *
548    * Since: 1.0
549    */
550   obj_props[PROP_LOOP] =
551     g_param_spec_boolean ("loop",
552                           P_("Loop"),
553                           P_("Whether the animation should loop"),
554                           FALSE,
555                           CLUTTER_PARAM_READWRITE);
556
557   /**
558    * ClutterAnimation:timeline:
559    *
560    * The #ClutterTimeline used by the animation.
561    *
562    * Since: 1.0
563    */
564   obj_props[PROP_TIMELINE] =
565     g_param_spec_object ("timeline",
566                          P_("Timeline"),
567                          P_("The timeline used by the animation"),
568                          CLUTTER_TYPE_TIMELINE,
569                          CLUTTER_PARAM_READWRITE);
570
571   /**
572    * ClutterAnimation:alpha:
573    *
574    * The #ClutterAlpha used by the animation.
575    *
576    * Since: 1.0
577    *
578    * Deprecated: 1.10: Use the #ClutterAnimation:timeline property and
579    *   the #ClutterTimeline:progress-mode property instead.
580    */
581   obj_props[PROP_ALPHA] =
582     g_param_spec_object ("alpha",
583                          P_("Alpha"),
584                          P_("The alpha used by the animation"),
585                          CLUTTER_TYPE_ALPHA,
586                          CLUTTER_PARAM_READWRITE | G_PARAM_DEPRECATED);
587
588   g_object_class_install_properties (gobject_class,
589                                      PROP_LAST,
590                                      obj_props);
591
592   /**
593    * ClutterAnimation::started:
594    * @animation: the animation that emitted the signal
595    *
596    * The ::started signal is emitted once the animation has been
597    * started
598    *
599    * Since: 1.0
600    */
601   animation_signals[STARTED] =
602     g_signal_new (I_("started"),
603                   G_TYPE_FROM_CLASS (klass),
604                   G_SIGNAL_RUN_LAST,
605                   G_STRUCT_OFFSET (ClutterAnimationClass, started),
606                   NULL, NULL,
607                   _clutter_marshal_VOID__VOID,
608                   G_TYPE_NONE, 0);
609
610   /**
611    * ClutterAnimation::completed:
612    * @animation: the animation that emitted the signal
613    *
614    * The ::completed signal is emitted once the animation has
615    * been completed.
616    *
617    * The @animation instance is guaranteed to be valid for the entire
618    * duration of the signal emission chain.
619    *
620    * Since: 1.0
621    */
622   animation_signals[COMPLETED] =
623     g_signal_new (I_("completed"),
624                   G_TYPE_FROM_CLASS (klass),
625                   G_SIGNAL_RUN_LAST,
626                   G_STRUCT_OFFSET (ClutterAnimationClass, completed),
627                   NULL, NULL,
628                   _clutter_marshal_VOID__VOID,
629                   G_TYPE_NONE, 0);
630 }
631
632 static void
633 clutter_animation_init (ClutterAnimation *self)
634 {
635   self->priv = CLUTTER_ANIMATION_GET_PRIVATE (self);
636
637   self->priv->properties =
638     g_hash_table_new_full (g_str_hash, g_str_equal,
639                            (GDestroyNotify) g_free,
640                            (GDestroyNotify) g_object_unref);
641 }
642
643 static inline void
644 clutter_animation_bind_property_internal (ClutterAnimation *animation,
645                                           const gchar      *property_name,
646                                           GParamSpec       *pspec,
647                                           ClutterInterval  *interval)
648 {
649   ClutterAnimationPrivate *priv = animation->priv;
650
651   if (!clutter_interval_validate (interval, pspec))
652     {
653       g_warning ("Cannot bind property '%s': the interval is out "
654                  "of bounds",
655                  property_name);
656       return;
657     }
658
659   g_hash_table_insert (priv->properties,
660                        g_strdup (property_name),
661                        g_object_ref_sink (interval));
662 }
663
664 static inline void
665 clutter_animation_update_property_internal (ClutterAnimation *animation,
666                                             const gchar      *property_name,
667                                             GParamSpec       *pspec,
668                                             ClutterInterval  *interval)
669 {
670   ClutterAnimationPrivate *priv = animation->priv;
671
672   if (!clutter_interval_validate (interval, pspec))
673     {
674       g_warning ("Cannot bind property '%s': the interval is out "
675                  "of bounds",
676                  property_name);
677       return;
678     }
679
680   g_hash_table_replace (priv->properties,
681                         g_strdup (property_name),
682                         g_object_ref_sink (interval));
683 }
684
685 static GParamSpec *
686 clutter_animation_validate_bind (ClutterAnimation *animation,
687                                  const char       *property_name,
688                                  GType             argtype)
689 {
690   ClutterAnimationPrivate *priv;
691   GParamSpec *pspec;
692   GType pspec_type;
693
694   priv = animation->priv;
695
696   if (G_UNLIKELY (!priv->object))
697     {
698       g_warning ("Cannot bind property '%s': the animation has no "
699                  "object set. You need to call clutter_animation_set_object() "
700                  "first to be able to bind a property",
701                  property_name);
702       return NULL;
703     }
704
705   if (G_UNLIKELY (clutter_animation_has_property (animation, property_name)))
706     {
707       g_warning ("Cannot bind property '%s': the animation already has "
708                  "a bound property with the same name",
709                  property_name);
710       return NULL;
711     }
712
713   if (CLUTTER_IS_ANIMATABLE (priv->object))
714     {
715       ClutterAnimatable *animatable = CLUTTER_ANIMATABLE (priv->object);
716
717       pspec = clutter_animatable_find_property (animatable, property_name);
718     }
719   else
720     {
721       GObjectClass *klass = G_OBJECT_GET_CLASS (priv->object);
722
723       pspec = g_object_class_find_property (klass, property_name);
724     }
725
726   if (pspec == NULL)
727     {
728       g_warning ("Cannot bind property '%s': objects of type '%s' have "
729                  "no such property",
730                  property_name,
731                  g_type_name (G_OBJECT_TYPE (priv->object)));
732       return NULL;
733     }
734
735   if (!(pspec->flags & G_PARAM_WRITABLE))
736     {
737       g_warning ("Cannot bind property '%s': the property is not writable",
738                  property_name);
739       return NULL;
740     }
741
742   pspec_type = G_PARAM_SPEC_VALUE_TYPE (pspec);
743
744   if (g_value_type_transformable (argtype, pspec_type))
745     return pspec;
746   else
747     {
748       g_warning ("Cannot bind property '%s': the interval value of "
749                  "type '%s' is not compatible with the property value "
750                  "of type '%s'",
751                  property_name,
752                  g_type_name (argtype),
753                  g_type_name (pspec_type));
754       return NULL;
755     }
756 }
757
758 /**
759  * clutter_animation_bind_interval:
760  * @animation: a #ClutterAnimation
761  * @property_name: the property to control
762  * @interval: (transfer full): a #ClutterInterval
763  *
764  * Binds @interval to the @property_name of the #GObject
765  * attached to @animation. The #ClutterAnimation will take
766  * ownership of the passed #ClutterInterval.  For more information
767  * about animations, see clutter_actor_animate().
768  *
769  * If you need to update the interval instance use
770  * clutter_animation_update_interval() instead.
771  *
772  * Return value: (transfer none): The animation itself.
773  * Since: 1.0
774  */
775 ClutterAnimation *
776 clutter_animation_bind_interval (ClutterAnimation *animation,
777                                  const gchar      *property_name,
778                                  ClutterInterval  *interval)
779 {
780   GParamSpec *pspec;
781
782   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
783   g_return_val_if_fail (property_name != NULL, NULL);
784   g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), NULL);
785
786   pspec = clutter_animation_validate_bind (animation, property_name,
787                                            clutter_interval_get_value_type (interval));
788   if (pspec == NULL)
789     return NULL;
790
791   clutter_animation_bind_property_internal (animation, property_name,
792                                             pspec,
793                                             interval);
794
795   return animation;
796 }
797
798
799 /**
800  * clutter_animation_bind:
801  * @animation: a #ClutterAnimation
802  * @property_name: the property to control
803  * @final: The final value of the property
804  *
805  * Adds a single property with name @property_name to the
806  * animation @animation.  For more information about animations,
807  * see clutter_actor_animate().
808  *
809  * This method returns the animation primarily to make chained
810  * calls convenient in language bindings.
811  *
812  * Return value: (transfer none): The animation itself.
813  *
814  * Since: 1.0
815  */
816 ClutterAnimation *
817 clutter_animation_bind (ClutterAnimation *animation,
818                         const gchar      *property_name,
819                         const GValue     *final)
820 {
821   ClutterAnimationPrivate *priv;
822   GParamSpec *pspec;
823   ClutterInterval *interval;
824   GType type;
825   GValue initial = G_VALUE_INIT;
826   GValue real_final = G_VALUE_INIT;
827
828   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
829   g_return_val_if_fail (property_name != NULL, NULL);
830
831   priv = animation->priv;
832
833   type = G_VALUE_TYPE (final);
834   pspec = clutter_animation_validate_bind (animation, property_name, type);
835   if (pspec == NULL)
836     return NULL;
837
838   g_value_init (&real_final, G_PARAM_SPEC_VALUE_TYPE (pspec));
839   if (!g_value_transform (final, &real_final))
840     {
841       g_value_unset (&real_final);
842       g_warning ("Unable to transform the value of type '%s' to a value "
843                  "of '%s' compatible with the property '%s'of the object "
844                  "of type '%s'",
845                  g_type_name (type),
846                  g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
847                  property_name,
848                  G_OBJECT_TYPE_NAME (priv->object));
849       return NULL;
850     }
851
852   g_value_init (&initial, G_PARAM_SPEC_VALUE_TYPE (pspec));
853
854   if (CLUTTER_IS_ANIMATABLE (priv->object))
855     clutter_animatable_get_initial_state (CLUTTER_ANIMATABLE (priv->object),
856                                           property_name,
857                                           &initial);
858   else
859     g_object_get_property (priv->object, property_name, &initial);
860
861   interval = clutter_interval_new_with_values (G_PARAM_SPEC_VALUE_TYPE (pspec),
862                                                &initial,
863                                                &real_final);
864
865   g_value_unset (&initial);
866   g_value_unset (&real_final);
867
868   clutter_animation_bind_property_internal (animation, property_name,
869                                             pspec,
870                                             interval);
871
872   return animation;
873 }
874
875
876 /**
877  * clutter_animation_unbind_property:
878  * @animation: a #ClutterAnimation
879  * @property_name: name of the property
880  *
881  * Removes @property_name from the list of animated properties.
882  *
883  * Since: 1.0
884  */
885 void
886 clutter_animation_unbind_property (ClutterAnimation *animation,
887                                    const gchar      *property_name)
888 {
889   ClutterAnimationPrivate *priv;
890
891   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
892   g_return_if_fail (property_name != NULL);
893
894   priv = animation->priv;
895
896   if (!clutter_animation_has_property (animation, property_name))
897     {
898       g_warning ("Cannot unbind property '%s': the animation has "
899                  "no bound property with that name",
900                  property_name);
901       return;
902     }
903
904   g_hash_table_remove (priv->properties, property_name);
905 }
906
907 /**
908  * clutter_animation_has_property:
909  * @animation: a #ClutterAnimation
910  * @property_name: name of the property
911  *
912  * Checks whether @animation is controlling @property_name.
913  *
914  * Return value: %TRUE if the property is animated by the
915  *   #ClutterAnimation, %FALSE otherwise
916  *
917  * Since: 1.0
918  */
919 gboolean
920 clutter_animation_has_property (ClutterAnimation *animation,
921                                 const gchar      *property_name)
922 {
923   ClutterAnimationPrivate *priv;
924
925   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), FALSE);
926   g_return_val_if_fail (property_name != NULL, FALSE);
927
928   priv = animation->priv;
929
930   return g_hash_table_lookup (priv->properties, property_name) != NULL;
931 }
932
933 /**
934  * clutter_animation_update_interval:
935  * @animation: a #ClutterAnimation
936  * @property_name: name of the property
937  * @interval: a #ClutterInterval
938  *
939  * Changes the @interval for @property_name. The #ClutterAnimation
940  * will take ownership of the passed #ClutterInterval.
941  *
942  * Since: 1.0
943  */
944 void
945 clutter_animation_update_interval (ClutterAnimation *animation,
946                                    const gchar      *property_name,
947                                    ClutterInterval  *interval)
948 {
949   ClutterAnimationPrivate *priv;
950   GParamSpec *pspec;
951   GType pspec_type, int_type;
952
953   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
954   g_return_if_fail (property_name != NULL);
955   g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
956
957   priv = animation->priv;
958
959   if (!clutter_animation_has_property (animation, property_name))
960     {
961       g_warning ("Cannot update property '%s': the animation has "
962                  "no bound property with that name",
963                  property_name);
964       return;
965     }
966
967   if (CLUTTER_IS_ANIMATABLE (priv->object))
968     {
969       ClutterAnimatable *animatable = CLUTTER_ANIMATABLE (priv->object);
970
971       pspec = clutter_animatable_find_property (animatable, property_name);
972     }
973   else
974     {
975       GObjectClass *klass = G_OBJECT_GET_CLASS (priv->object);
976
977       pspec = g_object_class_find_property (klass, property_name);
978     }
979
980   if (pspec == NULL)
981     {
982       g_warning ("Cannot update property '%s': objects of type '%s' have "
983                  "no such property",
984                  property_name,
985                  g_type_name (G_OBJECT_TYPE (priv->object)));
986       return;
987     }
988
989   pspec_type = G_PARAM_SPEC_VALUE_TYPE (pspec);
990   int_type = clutter_interval_get_value_type (interval);
991
992   if (!g_value_type_compatible (int_type, pspec_type) ||
993       !g_value_type_transformable (int_type, pspec_type))
994     {
995       g_warning ("Cannot update property '%s': the interval value of "
996                  "type '%s' is not compatible with the property value "
997                  "of type '%s'",
998                  property_name,
999                  g_type_name (int_type),
1000                  g_type_name (pspec_type));
1001       return;
1002     }
1003
1004   clutter_animation_update_property_internal (animation, property_name,
1005                                               pspec,
1006                                               interval);
1007 }
1008
1009 /**
1010  * clutter_animation_update:
1011  * @animation: a #ClutterAnimation
1012  * @property_name: name of the property
1013  * @final: The final value of the property
1014  *
1015  * Updates the @final value of the interval for @property_name
1016  *
1017  * Return value: (transfer none): The animation itself.
1018  *
1019  * Since: 1.0
1020  */
1021 ClutterAnimation *
1022 clutter_animation_update (ClutterAnimation *animation,
1023                           const gchar      *property_name,
1024                           const GValue     *final)
1025 {
1026   ClutterInterval *interval;
1027   GType int_type;
1028
1029   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
1030   g_return_val_if_fail (property_name != NULL, NULL);
1031   g_return_val_if_fail (final != NULL, NULL);
1032   g_return_val_if_fail (G_VALUE_TYPE (final) != G_TYPE_INVALID, NULL);
1033
1034   interval = clutter_animation_get_interval (animation, property_name);
1035   if (interval == NULL)
1036     {
1037       g_warning ("Cannot update property '%s': the animation has "
1038                  "no bound property with that name",
1039                  property_name);
1040       return NULL;
1041     }
1042
1043   int_type = clutter_interval_get_value_type (interval);
1044
1045   if (!g_value_type_compatible (G_VALUE_TYPE (final), int_type) ||
1046       !g_value_type_transformable (G_VALUE_TYPE (final), int_type))
1047     {
1048       g_warning ("Cannot update property '%s': the interval value of "
1049                  "type '%s' is not compatible with the property value "
1050                  "of type '%s'",
1051                  property_name,
1052                  g_type_name (int_type),
1053                  g_type_name (G_VALUE_TYPE (final)));
1054       return NULL;
1055     }
1056
1057   clutter_interval_set_final_value (interval, final);
1058
1059   return animation;
1060 }
1061
1062 /**
1063  * clutter_animation_get_interval:
1064  * @animation: a #ClutterAnimation
1065  * @property_name: name of the property
1066  *
1067  * Retrieves the #ClutterInterval associated to @property_name
1068  * inside @animation.
1069  *
1070  * Return value: (transfer none): a #ClutterInterval or %NULL if no
1071  *   property with the same name was found. The returned interval is
1072  *   owned by the #ClutterAnimation and should not be unreferenced
1073  *
1074  * Since: 1.0
1075  */
1076 ClutterInterval *
1077 clutter_animation_get_interval (ClutterAnimation *animation,
1078                                 const gchar      *property_name)
1079 {
1080   ClutterAnimationPrivate *priv;
1081
1082   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
1083   g_return_val_if_fail (property_name != NULL, NULL);
1084
1085   priv = animation->priv;
1086
1087   return g_hash_table_lookup (priv->properties, property_name);
1088 }
1089
1090 static void
1091 on_timeline_started (ClutterTimeline  *timeline,
1092                      ClutterAnimation *animation)
1093 {
1094   g_signal_emit (animation, animation_signals[STARTED], 0);
1095 }
1096
1097 static void
1098 on_timeline_completed (ClutterTimeline  *timeline,
1099                        ClutterAnimation *animation)
1100 {
1101   CLUTTER_NOTE (ANIMATION, "Timeline [%p] complete", timeline);
1102
1103   if (!clutter_animation_get_loop (animation))
1104     g_signal_emit (animation, animation_signals[COMPLETED], 0);
1105 }
1106
1107 static void
1108 on_timeline_frame (ClutterTimeline  *timeline,
1109                    gint              elapsed,
1110                    ClutterAnimation *animation)
1111 {
1112   ClutterAnimationPrivate *priv;
1113   GList *properties, *p;
1114   gdouble alpha_value;
1115   gboolean is_animatable = FALSE;
1116   ClutterAnimatable *animatable = NULL;
1117
1118   /* make sure the animation survives the notification */
1119   g_object_ref (animation);
1120
1121   priv = animation->priv;
1122
1123   if (priv->alpha != NULL)
1124     alpha_value = clutter_alpha_get_alpha (priv->alpha);
1125   else
1126     alpha_value = clutter_timeline_get_progress (priv->timeline);
1127
1128   if (CLUTTER_IS_ANIMATABLE (priv->object))
1129     {
1130       animatable = CLUTTER_ANIMATABLE (priv->object);
1131       is_animatable = TRUE;
1132     }
1133
1134   g_object_freeze_notify (priv->object);
1135
1136   properties = g_hash_table_get_keys (priv->properties);
1137   for (p = properties; p != NULL; p = p->next)
1138     {
1139       const gchar *p_name = p->data;
1140       ClutterInterval *interval;
1141       GValue value = G_VALUE_INIT;
1142       gboolean apply;
1143
1144       interval = g_hash_table_lookup (priv->properties, p_name);
1145       g_assert (CLUTTER_IS_INTERVAL (interval));
1146
1147       g_value_init (&value, clutter_interval_get_value_type (interval));
1148
1149       if (is_animatable)
1150         {
1151           apply = clutter_animatable_interpolate_value (animatable, p_name,
1152                                                         interval,
1153                                                         alpha_value,
1154                                                         &value);
1155         }
1156       else
1157         {
1158           apply = clutter_interval_compute_value (interval,
1159                                                   alpha_value,
1160                                                   &value);
1161         }
1162
1163       if (apply)
1164         {
1165           if (is_animatable)
1166             clutter_animatable_set_final_state (animatable, p_name, &value);
1167           else
1168             g_object_set_property (priv->object, p_name, &value);
1169         }
1170
1171       g_value_unset (&value);
1172     }
1173
1174   g_list_free (properties);
1175
1176   g_object_thaw_notify (priv->object);
1177
1178   g_object_unref (animation);
1179 }
1180
1181 static ClutterTimeline *
1182 clutter_animation_get_timeline_internal (ClutterAnimation *animation)
1183 {
1184   ClutterAnimationPrivate *priv = animation->priv;
1185   ClutterTimeline *timeline;
1186
1187   if (priv->timeline != NULL)
1188     return priv->timeline;
1189
1190   if (priv->alpha != NULL)
1191     {
1192       timeline = clutter_alpha_get_timeline (priv->alpha);
1193       if (timeline != NULL)
1194         return timeline;
1195     }
1196
1197   timeline = g_object_new (CLUTTER_TYPE_TIMELINE, NULL);
1198
1199   priv->timeline_started_id =
1200     g_signal_connect (timeline, "started",
1201                       G_CALLBACK (on_timeline_started),
1202                       animation);
1203
1204   priv->timeline_completed_id =
1205     g_signal_connect (timeline, "completed",
1206                       G_CALLBACK (on_timeline_completed),
1207                       animation);
1208
1209   priv->timeline_frame_id =
1210     g_signal_connect (timeline, "new-frame",
1211                       G_CALLBACK (on_timeline_frame),
1212                       animation);
1213
1214   if (priv->alpha != NULL)
1215     {
1216       clutter_alpha_set_timeline (priv->alpha, timeline);
1217
1218       /* the alpha owns the timeline now */
1219       g_object_unref (timeline);
1220     }
1221
1222   priv->timeline = timeline;
1223
1224   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_TIMELINE]);
1225
1226   return priv->timeline;
1227 }
1228
1229 static void
1230 clutter_animation_set_alpha_internal (ClutterAnimation *animation,
1231                                       ClutterAlpha     *alpha)
1232 {
1233   ClutterAnimationPrivate *priv;
1234   ClutterTimeline *timeline;
1235
1236   priv = animation->priv;
1237
1238   if (priv->alpha == alpha)
1239     return;
1240
1241   g_object_freeze_notify (G_OBJECT (animation));
1242
1243   if (priv->alpha != NULL)
1244     timeline = clutter_alpha_get_timeline (priv->alpha);
1245   else
1246     timeline = NULL;
1247
1248   /* disconnect the old timeline first */
1249   if (timeline != NULL && priv->timeline_started_id != 0)
1250     {
1251       g_signal_handler_disconnect (timeline, priv->timeline_started_id);
1252       priv->timeline_started_id = 0;
1253     }
1254
1255   if (timeline != NULL && priv->timeline_completed_id != 0)
1256     {
1257       g_signal_handler_disconnect (timeline, priv->timeline_completed_id);
1258       priv->timeline_completed_id = 0;
1259     }
1260
1261   /* then we need to disconnect the signal handler from the old alpha */
1262   if (timeline != NULL && priv->timeline_frame_id != 0)
1263     {
1264       g_signal_handler_disconnect (timeline, priv->timeline_frame_id);
1265       priv->timeline_frame_id = 0;
1266     }
1267
1268   if (priv->alpha != NULL)
1269     {
1270       /* this will take care of any reference we hold on the timeline */
1271       g_object_unref (priv->alpha);
1272       priv->alpha = NULL;
1273     }
1274
1275   if (alpha == NULL)
1276     goto out;
1277
1278   priv->alpha = g_object_ref_sink (alpha);
1279
1280   /* if the alpha has a timeline then we use it, otherwise we create one */
1281   timeline = clutter_alpha_get_timeline (priv->alpha);
1282   if (timeline != NULL)
1283     {
1284       priv->timeline_started_id =
1285         g_signal_connect (timeline, "started",
1286                           G_CALLBACK (on_timeline_started),
1287                           animation);
1288       priv->timeline_completed_id =
1289         g_signal_connect (timeline, "completed",
1290                           G_CALLBACK (on_timeline_completed),
1291                           animation);
1292       priv->timeline_frame_id =
1293         g_signal_connect (timeline, "new-frame",
1294                           G_CALLBACK (on_timeline_frame),
1295                           animation);
1296     }
1297   else
1298     {
1299       /* FIXME - add a create_timeline_internal() because this does
1300        * not look very good
1301        */
1302       (void) clutter_animation_get_timeline_internal (animation);
1303     }
1304
1305 out:
1306   /* emit all relevant notifications */
1307   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_MODE]);
1308   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_DURATION]);
1309   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_LOOP]);
1310   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_ALPHA]);
1311   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_TIMELINE]);
1312
1313   g_object_thaw_notify (G_OBJECT (animation));
1314 }
1315
1316 /**
1317  * clutter_animation_new:
1318  *
1319  * Creates a new #ClutterAnimation instance. You should set the
1320  * #GObject to be animated using clutter_animation_set_object(),
1321  * set the duration with clutter_animation_set_duration() and the
1322  * easing mode using clutter_animation_set_mode().
1323  *
1324  * Use clutter_animation_bind() or clutter_animation_bind_interval()
1325  * to define the properties to be animated. The interval and the
1326  * animated properties can be updated at runtime.
1327  *
1328  * The clutter_actor_animate() and relative family of functions provide
1329  * an easy way to animate a #ClutterActor and automatically manage the
1330  * lifetime of a #ClutterAnimation instance, so you should consider using
1331  * those functions instead of manually creating an animation.
1332  *
1333  * Return value: the newly created #ClutterAnimation. Use g_object_unref()
1334  *   to release the associated resources
1335  *
1336  * Since: 1.0
1337  */
1338 ClutterAnimation *
1339 clutter_animation_new (void)
1340 {
1341   return g_object_new (CLUTTER_TYPE_ANIMATION, NULL);
1342 }
1343
1344 /**
1345  * clutter_animation_set_object:
1346  * @animation: a #ClutterAnimation
1347  * @object: a #GObject
1348  *
1349  * Attaches @animation to @object. The #ClutterAnimation will take a
1350  * reference on @object.
1351  *
1352  * Since: 1.0
1353  */
1354 void
1355 clutter_animation_set_object (ClutterAnimation *animation,
1356                               GObject          *object)
1357 {
1358   ClutterAnimationPrivate *priv;
1359
1360   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1361   g_return_if_fail (object == NULL || G_IS_OBJECT (object));
1362
1363   priv = animation->priv;
1364
1365   if (priv->object != NULL)
1366     {
1367       g_object_set_qdata (priv->object, quark_object_animation, NULL);
1368
1369       g_object_unref (priv->object);
1370       priv->object = NULL;
1371     }
1372
1373   if (object != NULL)
1374     priv->object = g_object_ref (object);
1375
1376   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_OBJECT]);
1377 }
1378
1379 /**
1380  * clutter_animation_get_object:
1381  * @animation: a #ClutterAnimation
1382  *
1383  * Retrieves the #GObject attached to @animation.
1384  *
1385  * Return value: (transfer none): a #GObject
1386  *
1387  * Since: 1.0
1388  */
1389 GObject *
1390 clutter_animation_get_object (ClutterAnimation *animation)
1391 {
1392   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
1393
1394   return animation->priv->object;
1395 }
1396
1397 /**
1398  * clutter_animation_set_mode:
1399  * @animation: a #ClutterAnimation
1400  * @mode: an animation mode logical id
1401  *
1402  * Sets the animation @mode of @animation. The animation @mode is
1403  * a logical id, either coming from the #ClutterAnimationMode enumeration
1404  * or the return value of clutter_alpha_register_func().
1405  *
1406  * This function will also set #ClutterAnimation:alpha if needed.
1407  *
1408  * Since: 1.0
1409  */
1410 void
1411 clutter_animation_set_mode (ClutterAnimation *animation,
1412                             gulong            mode)
1413 {
1414   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1415
1416   g_object_freeze_notify (G_OBJECT (animation));
1417
1418   if (animation->priv->alpha != NULL || mode > CLUTTER_ANIMATION_LAST)
1419     {
1420       ClutterAlpha *alpha;
1421
1422       if (animation->priv->alpha == NULL)
1423         alpha = clutter_animation_get_alpha_internal (animation);
1424       else
1425         alpha = animation->priv->alpha;
1426
1427       clutter_alpha_set_mode (alpha, mode);
1428     }
1429   else
1430     {
1431       ClutterTimeline *timeline;
1432
1433       timeline = clutter_animation_get_timeline_internal (animation);
1434
1435       clutter_timeline_set_progress_mode (timeline, mode);
1436     }
1437
1438   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_MODE]);
1439
1440   g_object_thaw_notify (G_OBJECT (animation));
1441 }
1442
1443 /**
1444  * clutter_animation_get_mode:
1445  * @animation: a #ClutterAnimation
1446  *
1447  * Retrieves the animation mode of @animation, as set by
1448  * clutter_animation_set_mode().
1449  *
1450  * Return value: the mode for the animation
1451  *
1452  * Since: 1.0
1453  */
1454 gulong
1455 clutter_animation_get_mode (ClutterAnimation *animation)
1456 {
1457   ClutterTimeline *timeline;
1458
1459   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), CLUTTER_LINEAR);
1460
1461   if (animation->priv->alpha != NULL)
1462     return clutter_alpha_get_mode (animation->priv->alpha);
1463
1464   timeline = clutter_animation_get_timeline_internal (animation);
1465
1466   return clutter_timeline_get_progress_mode (timeline);
1467 }
1468
1469 /**
1470  * clutter_animation_set_duration:
1471  * @animation: a #ClutterAnimation
1472  * @msecs: the duration in milliseconds
1473  *
1474  * Sets the duration of @animation in milliseconds.
1475  *
1476  * This function will set #ClutterAnimation:alpha and
1477  * #ClutterAnimation:timeline if needed.
1478  *
1479  * Since: 1.0
1480  */
1481 void
1482 clutter_animation_set_duration (ClutterAnimation *animation,
1483                                 guint             msecs)
1484 {
1485   ClutterTimeline *timeline;
1486
1487   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1488
1489   g_object_freeze_notify (G_OBJECT (animation));
1490
1491   timeline = clutter_animation_get_timeline_internal (animation);
1492   clutter_timeline_set_duration (timeline, msecs);
1493   clutter_timeline_rewind (timeline);
1494
1495   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_DURATION]);
1496
1497   g_object_thaw_notify (G_OBJECT (animation));
1498 }
1499
1500 /**
1501  * clutter_animation_set_loop:
1502  * @animation: a #ClutterAnimation
1503  * @loop: %TRUE if the animation should loop
1504  *
1505  * Sets whether @animation should loop over itself once finished.
1506  *
1507  * A looping #ClutterAnimation will not emit the #ClutterAnimation::completed
1508  * signal when finished.
1509  *
1510  * This function will set #ClutterAnimation:alpha and
1511  * #ClutterAnimation:timeline if needed.
1512  *
1513  * Since: 1.0
1514  */
1515 void
1516 clutter_animation_set_loop (ClutterAnimation *animation,
1517                             gboolean          loop)
1518 {
1519   ClutterTimeline *timeline;
1520
1521   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1522
1523   g_object_freeze_notify (G_OBJECT (animation));
1524
1525   timeline = clutter_animation_get_timeline_internal (animation);
1526   clutter_timeline_set_repeat_count (timeline, loop ? -1 : 0);
1527
1528   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_LOOP]);
1529
1530   g_object_thaw_notify (G_OBJECT (animation));
1531 }
1532
1533 /**
1534  * clutter_animation_get_loop:
1535  * @animation: a #ClutterAnimation
1536  *
1537  * Retrieves whether @animation is looping.
1538  *
1539  * Return value: %TRUE if the animation is looping
1540  *
1541  * Since: 1.0
1542  */
1543 gboolean
1544 clutter_animation_get_loop (ClutterAnimation *animation)
1545 {
1546   ClutterTimeline *timeline;
1547
1548   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), FALSE);
1549
1550   timeline = clutter_animation_get_timeline_internal (animation);
1551
1552   return clutter_timeline_get_repeat_count (timeline) != 0;
1553 }
1554
1555 /**
1556  * clutter_animation_get_duration:
1557  * @animation: a #ClutterAnimation
1558  *
1559  * Retrieves the duration of @animation, in milliseconds.
1560  *
1561  * Return value: the duration of the animation
1562  *
1563  * Since: 1.0
1564  */
1565 guint
1566 clutter_animation_get_duration (ClutterAnimation *animation)
1567 {
1568   ClutterTimeline *timeline;
1569
1570   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), 0);
1571
1572   timeline = clutter_animation_get_timeline_internal (animation);
1573
1574   return clutter_timeline_get_duration (timeline);
1575 }
1576
1577 /**
1578  * clutter_animation_set_timeline:
1579  * @animation: a #ClutterAnimation
1580  * @timeline: (allow-none): a #ClutterTimeline, or %NULL to unset the
1581  *   current #ClutterTimeline
1582  *
1583  * Sets the #ClutterTimeline used by @animation.
1584  *
1585  * This function will take a reference on the passed @timeline.
1586  *
1587  * Since: 1.0
1588  */
1589 void
1590 clutter_animation_set_timeline (ClutterAnimation *animation,
1591                                 ClutterTimeline  *timeline)
1592 {
1593   ClutterAnimationPrivate *priv;
1594   ClutterTimeline *cur_timeline;
1595
1596   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1597   g_return_if_fail (timeline == NULL || CLUTTER_IS_TIMELINE (timeline));
1598
1599   priv = animation->priv;
1600
1601   if (priv->alpha != NULL)
1602     cur_timeline = clutter_alpha_get_timeline (priv->alpha);
1603   else
1604     cur_timeline = NULL;
1605
1606   if (cur_timeline == timeline)
1607     return;
1608
1609   g_object_freeze_notify (G_OBJECT (animation));
1610
1611   if (cur_timeline != NULL && priv->timeline_started_id != 0)
1612     g_signal_handler_disconnect (cur_timeline, priv->timeline_started_id);
1613
1614   if (cur_timeline != NULL && priv->timeline_completed_id != 0)
1615     g_signal_handler_disconnect (cur_timeline, priv->timeline_completed_id);
1616
1617   if (cur_timeline != NULL && priv->timeline_frame_id != 0)
1618     g_signal_handler_disconnect (cur_timeline, priv->timeline_frame_id);
1619
1620   priv->timeline_started_id = 0;
1621   priv->timeline_completed_id = 0;
1622   priv->timeline_frame_id = 0;
1623
1624   if (priv->alpha != NULL)
1625     clutter_alpha_set_timeline (priv->alpha, timeline);
1626   else
1627     priv->timeline = timeline;
1628
1629   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_TIMELINE]);
1630   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_DURATION]);
1631   g_object_notify_by_pspec (G_OBJECT (animation), obj_props[PROP_LOOP]);
1632
1633   if (timeline != NULL)
1634     {
1635       g_object_ref (timeline);
1636
1637       priv->timeline_started_id =
1638         g_signal_connect (timeline, "started",
1639                           G_CALLBACK (on_timeline_started),
1640                           animation);
1641       priv->timeline_completed_id =
1642         g_signal_connect (timeline, "completed",
1643                           G_CALLBACK (on_timeline_completed),
1644                           animation);
1645       priv->timeline_frame_id =
1646         g_signal_connect (timeline, "new-frame",
1647                           G_CALLBACK (on_timeline_frame),
1648                           animation);
1649     }
1650
1651   g_object_thaw_notify (G_OBJECT (animation));
1652 }
1653
1654 /**
1655  * clutter_animation_get_timeline:
1656  * @animation: a #ClutterAnimation
1657  *
1658  * Retrieves the #ClutterTimeline used by @animation
1659  *
1660  * Return value: (transfer none): the timeline used by the animation
1661  *
1662  * Since: 1.0
1663  */
1664 ClutterTimeline *
1665 clutter_animation_get_timeline (ClutterAnimation *animation)
1666 {
1667   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
1668
1669   return clutter_animation_get_timeline_internal (animation);
1670 }
1671
1672 /**
1673  * clutter_animation_set_alpha:
1674  * @animation: a #ClutterAnimation
1675  * @alpha: a #ClutterAlpha, or %NULL to unset the current #ClutterAlpha
1676  *
1677  * Sets @alpha as the #ClutterAlpha used by @animation.
1678  *
1679  * If @alpha is not %NULL, the #ClutterAnimation will take ownership
1680  * of the #ClutterAlpha instance.
1681  *
1682  * Since: 1.0
1683  *
1684  * Deprecated: 1.10: Use clutter_animation_get_timeline() and
1685  *   clutter_timeline_set_progress_mode() instead.
1686  */
1687 void
1688 clutter_animation_set_alpha (ClutterAnimation *animation,
1689                              ClutterAlpha     *alpha)
1690 {
1691   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1692   g_return_if_fail (alpha == NULL || CLUTTER_IS_ALPHA (alpha));
1693
1694   clutter_animation_set_alpha_internal (animation, alpha);
1695 }
1696
1697 /**
1698  * clutter_animation_get_alpha:
1699  * @animation: a #ClutterAnimation
1700  *
1701  * Retrieves the #ClutterAlpha used by @animation.
1702  *
1703  * Return value: (transfer none): the alpha object used by the animation
1704  *
1705  * Since: 1.0
1706  *
1707  * Deprecated: 1.10: Use clutter_animation_get_timeline() and
1708  *   clutter_timeline_get_progress_mode() instead.
1709  */
1710 ClutterAlpha *
1711 clutter_animation_get_alpha (ClutterAnimation *animation)
1712 {
1713   g_return_val_if_fail (CLUTTER_IS_ANIMATION (animation), NULL);
1714
1715   return clutter_animation_get_alpha_internal (animation);
1716 }
1717
1718 /**
1719  * clutter_animation_completed:
1720  * @animation: a #ClutterAnimation
1721  *
1722  * Emits the ::completed signal on @animation
1723  *
1724  * When using this function with a #ClutterAnimation created
1725  * by the clutter_actor_animate() family of functions, @animation
1726  * will be unreferenced and it will not be valid anymore,
1727  * unless g_object_ref() was called before calling this function
1728  * or unless a reference was taken inside a handler for the
1729  * #ClutterAnimation::completed signal
1730  *
1731  * Since: 1.0
1732  */
1733 void
1734 clutter_animation_completed (ClutterAnimation *animation)
1735 {
1736   g_return_if_fail (CLUTTER_IS_ANIMATION (animation));
1737
1738   g_signal_emit (animation, animation_signals[COMPLETED], 0);
1739 }
1740
1741 /*
1742  * starts the timeline
1743  */
1744 static void
1745 clutter_animation_start (ClutterAnimation *animation)
1746 {
1747   ClutterTimeline *timeline;
1748
1749   timeline = clutter_animation_get_timeline_internal (animation);
1750
1751   if (G_LIKELY (timeline != NULL))
1752     clutter_timeline_start (timeline);
1753   else
1754     {
1755       /* sanity check */
1756       g_warning (G_STRLOC ": no timeline found, unable to start the animation");
1757     }
1758 }
1759
1760 static void
1761 clutter_animation_setup_property (ClutterAnimation *animation,
1762                                   const gchar      *property_name,
1763                                   const GValue     *value,
1764                                   GParamSpec       *pspec,
1765                                   gboolean          is_fixed)
1766 {
1767   ClutterAnimationPrivate *priv = animation->priv;
1768   GValue real_value = G_VALUE_INIT;
1769
1770   if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
1771     {
1772       g_warning ("Cannot bind property '%s': the property is "
1773                  "construct-only",
1774                  property_name);
1775       return;
1776     }
1777
1778   if (!(pspec->flags & G_PARAM_WRITABLE))
1779     {
1780       g_warning ("Cannot bind property '%s': the property is "
1781                  "not writable",
1782                  property_name);
1783       return;
1784     }
1785
1786   /* initialize the real value that will be used to store the
1787    * final state of the animation
1788    */
1789   g_value_init (&real_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
1790
1791   /* if it's not the same type of the GParamSpec value, try to
1792    * convert it using the GValue transformation API, otherwise
1793    * just copy it
1794    */
1795   if (!g_type_is_a (G_VALUE_TYPE (value), G_VALUE_TYPE (&real_value)))
1796     {
1797       /* are these two types compatible (can be directly copied)? */
1798       if (g_value_type_compatible (G_VALUE_TYPE (value),
1799                                    G_VALUE_TYPE (&real_value)))
1800         {
1801           g_value_copy (value, &real_value);
1802           goto done;
1803         }
1804
1805       /* are these two type transformable? */
1806       if (g_value_type_transformable (G_VALUE_TYPE (value),
1807                                       G_VALUE_TYPE (&real_value)))
1808         {
1809           if (g_value_transform (value, &real_value))
1810             goto done;
1811         }
1812
1813       /* if not compatible and not transformable then we can't do much */
1814       g_warning ("%s: Unable to convert from %s to %s for "
1815                  "the property '%s' of object %s",
1816                  G_STRLOC,
1817                  g_type_name (G_VALUE_TYPE (value)),
1818                  g_type_name (G_VALUE_TYPE (&real_value)),
1819                  property_name,
1820                  G_OBJECT_TYPE_NAME (priv->object));
1821       g_value_unset (&real_value);
1822       return;
1823     }
1824   else
1825     g_value_copy (value, &real_value);
1826
1827 done:
1828   /* create an interval and bind it to the property, in case
1829    * it's not a fixed property, otherwise just set it
1830    */
1831   if (G_LIKELY (!is_fixed))
1832     {
1833       ClutterInterval *interval;
1834       GValue cur_value = G_VALUE_INIT;
1835
1836       g_value_init (&cur_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
1837
1838       if (CLUTTER_IS_ANIMATABLE (priv->object))
1839         clutter_animatable_get_initial_state (CLUTTER_ANIMATABLE (priv->object),
1840                                               property_name,
1841                                               &cur_value);
1842       else
1843         g_object_get_property (priv->object, property_name, &cur_value);
1844
1845       interval =
1846         clutter_interval_new_with_values (G_PARAM_SPEC_VALUE_TYPE (pspec),
1847                                           &cur_value,
1848                                           &real_value);
1849
1850       if (!clutter_animation_has_property (animation, property_name))
1851         clutter_animation_bind_property_internal (animation, property_name,
1852                                                   pspec,
1853                                                   interval);
1854       else
1855         clutter_animation_update_property_internal (animation, property_name,
1856                                                     pspec,
1857                                                     interval);
1858
1859       g_value_unset (&cur_value);
1860     }
1861   else
1862     {
1863       if (CLUTTER_IS_ANIMATABLE (priv->object))
1864         clutter_animatable_set_final_state (CLUTTER_ANIMATABLE (priv->object),
1865                                             property_name,
1866                                             &real_value);
1867       else
1868         g_object_set_property (priv->object, property_name, &real_value);
1869     }
1870
1871   g_value_unset (&real_value);
1872 }
1873
1874 static void
1875 clutter_animation_setupv (ClutterAnimation    *animation,
1876                           gint                 n_properties,
1877                           const gchar * const  properties[],
1878                           const GValue        *values)
1879 {
1880   ClutterAnimationPrivate *priv = animation->priv;
1881   ClutterAnimatable *animatable = NULL;
1882   GObjectClass *klass = NULL;
1883   gint i;
1884
1885   if (CLUTTER_IS_ANIMATABLE (priv->object))
1886     animatable = CLUTTER_ANIMATABLE (priv->object);
1887   else
1888     klass = G_OBJECT_GET_CLASS (priv->object);
1889
1890   for (i = 0; i < n_properties; i++)
1891     {
1892       const gchar *property_name = properties[i];
1893       GParamSpec *pspec;
1894       gboolean is_fixed = FALSE;
1895
1896       if (g_str_has_prefix (property_name, "fixed::"))
1897         {
1898           property_name += 7; /* strlen("fixed::") */
1899           is_fixed = TRUE;
1900         }
1901
1902       if (animatable != NULL)
1903         pspec = clutter_animatable_find_property (animatable, property_name);
1904       else
1905         pspec = g_object_class_find_property (klass, property_name);
1906
1907       if (pspec == NULL)
1908         {
1909           g_warning ("Cannot bind property '%s': objects of type '%s' do "
1910                      "not have this property",
1911                      property_name,
1912                      g_type_name (G_OBJECT_TYPE (priv->object)));
1913           break;
1914         }
1915
1916       clutter_animation_setup_property (animation, property_name,
1917                                         &values[i],
1918                                         pspec,
1919                                         is_fixed);
1920     }
1921 }
1922
1923 static const struct
1924 {
1925   const gchar *name;
1926   GConnectFlags flags;
1927 } signal_prefixes[] =
1928   {
1929     { "::", 0 },
1930     { "-swapped::", G_CONNECT_SWAPPED },
1931     { "-after::", G_CONNECT_AFTER },
1932     { "-swapped-after::", G_CONNECT_SWAPPED | G_CONNECT_AFTER }
1933   };
1934
1935 static gboolean
1936 clutter_animation_has_signal_prefix (const gchar *property_name,
1937                                      GConnectFlags *flags,
1938                                      int *offset)
1939 {
1940   int i;
1941
1942   if (!g_str_has_prefix (property_name, "signal"))
1943     return FALSE;
1944
1945   for (i = 0; i < G_N_ELEMENTS (signal_prefixes); i++)
1946     if (g_str_has_prefix (property_name + 6, signal_prefixes[i].name))
1947       {
1948         *offset = strlen (signal_prefixes[i].name) + 6;
1949         *flags = signal_prefixes[i].flags;
1950         return TRUE;
1951       }
1952
1953   return FALSE;
1954 }
1955
1956 static void
1957 clutter_animation_setup_valist (ClutterAnimation *animation,
1958                                 const gchar      *first_property_name,
1959                                 va_list           var_args)
1960 {
1961   ClutterAnimationPrivate *priv = animation->priv;
1962   ClutterAnimatable *animatable = NULL;
1963   GObjectClass *klass = NULL;
1964   const gchar *property_name;
1965
1966   if (CLUTTER_IS_ANIMATABLE (priv->object))
1967     animatable = CLUTTER_ANIMATABLE (priv->object);
1968   else
1969     klass = G_OBJECT_GET_CLASS (priv->object);
1970
1971   property_name = first_property_name;
1972   while (property_name != NULL)
1973     {
1974       GParamSpec *pspec;
1975       GValue final = G_VALUE_INIT;
1976       gchar *error = NULL;
1977       gboolean is_fixed = FALSE;
1978       GConnectFlags flags;
1979       int offset;
1980
1981       if (clutter_animation_has_signal_prefix (property_name,
1982                                                &flags,
1983                                                &offset))
1984         {
1985           const gchar *signal_name = property_name + offset;
1986           GCallback callback = va_arg (var_args, GCallback);
1987           gpointer  userdata = va_arg (var_args, gpointer);
1988
1989           g_signal_connect_data (animation, signal_name,
1990                                  callback, userdata,
1991                                  NULL, flags);
1992         }
1993       else
1994         {
1995           if (g_str_has_prefix (property_name, "fixed::"))
1996             {
1997               property_name += 7; /* strlen("fixed::") */
1998               is_fixed = TRUE;
1999             }
2000
2001           if (animatable != NULL)
2002             pspec = clutter_animatable_find_property (animatable,
2003                                                       property_name);
2004           else
2005             pspec = g_object_class_find_property (klass, property_name);
2006
2007           if (pspec == NULL)
2008             {
2009               g_warning ("Cannot bind property '%s': objects of type '%s' do "
2010                          "not have this property",
2011                          property_name,
2012                          g_type_name (G_OBJECT_TYPE (priv->object)));
2013               break;
2014             }
2015
2016           G_VALUE_COLLECT_INIT (&final, G_PARAM_SPEC_VALUE_TYPE (pspec),
2017                                 var_args, 0,
2018                                 &error);
2019
2020           if (error)
2021             {
2022               g_warning ("%s: %s", G_STRLOC, error);
2023               g_free (error);
2024               break;
2025             }
2026
2027           clutter_animation_setup_property (animation, property_name,
2028                                             &final,
2029                                             pspec,
2030                                             is_fixed);
2031
2032
2033           g_value_unset (&final);
2034         }
2035
2036       property_name = va_arg (var_args, gchar*);
2037     }
2038 }
2039
2040 static ClutterAnimation *
2041 animation_create_for_actor (ClutterActor *actor)
2042 {
2043   ClutterAnimation *animation;
2044   GObject *object = G_OBJECT (actor);
2045
2046   animation = g_object_get_qdata (object, quark_object_animation);
2047   if (animation == NULL)
2048     {
2049       animation = clutter_animation_new ();
2050       clutter_animation_set_object (animation, object);
2051       g_object_set_qdata (object, quark_object_animation, animation);
2052
2053       /* use the ::destroy signal to get a notification
2054        * that the actor went away mid-animation
2055        */
2056       g_signal_connect (object, "destroy",
2057                         G_CALLBACK (on_actor_destroy),
2058                         animation);
2059
2060       CLUTTER_NOTE (ANIMATION,
2061                     "Created new Animation [%p] for actor [%p]",
2062                     animation,
2063                     actor);
2064     }
2065   else
2066     {
2067       CLUTTER_NOTE (ANIMATION,
2068                     "Reusing Animation [%p] for actor [%p]",
2069                     animation,
2070                     actor);
2071     }
2072
2073   return animation;
2074 }
2075
2076 /**
2077  * clutter_actor_animate_with_alpha:
2078  * @actor: a #ClutterActor
2079  * @alpha: a #ClutterAlpha
2080  * @first_property_name: the name of a property
2081  * @...: a %NULL terminated list of property names and
2082  *   property values
2083  *
2084  * Animates the given list of properties of @actor between the current
2085  * value for each property and a new final value. The animation has a
2086  * definite behaviour given by the passed @alpha.
2087  *
2088  * See clutter_actor_animate() for further details.
2089  *
2090  * This function is useful if you want to use an existing #ClutterAlpha
2091  * to animate @actor.
2092  *
2093  * Return value: (transfer none): a #ClutterAnimation object. The object is owned by the
2094  *   #ClutterActor and should not be unreferenced with g_object_unref()
2095  *
2096  * Since: 1.0
2097  *
2098  * Deprecated: 1.10: Use clutter_actor_animate_with_timeline() instead
2099  */
2100 ClutterAnimation *
2101 clutter_actor_animate_with_alpha (ClutterActor *actor,
2102                                   ClutterAlpha *alpha,
2103                                   const gchar  *first_property_name,
2104                                   ...)
2105 {
2106   ClutterAnimation *animation;
2107   ClutterTimeline *timeline;
2108   va_list args;
2109
2110   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2111   g_return_val_if_fail (CLUTTER_IS_ALPHA (alpha), NULL);
2112   g_return_val_if_fail (first_property_name != NULL, NULL);
2113
2114   timeline = clutter_alpha_get_timeline (alpha);
2115   if (timeline == NULL)
2116     {
2117       g_warning ("The passed ClutterAlpha does not have an "
2118                  "associated ClutterTimeline.");
2119       return NULL;
2120     }
2121
2122   animation = animation_create_for_actor (actor);
2123   clutter_animation_set_alpha_internal (animation, alpha);
2124
2125   va_start (args, first_property_name);
2126   clutter_animation_setup_valist (animation, first_property_name, args);
2127   va_end (args);
2128
2129   clutter_animation_start (animation);
2130
2131   return animation;
2132 }
2133
2134 /**
2135  * clutter_actor_animate_with_timeline:
2136  * @actor: a #ClutterActor
2137  * @mode: an animation mode logical id
2138  * @timeline: a #ClutterTimeline
2139  * @first_property_name: the name of a property
2140  * @...: a %NULL terminated list of property names and
2141  *   property values
2142  *
2143  * Animates the given list of properties of @actor between the current
2144  * value for each property and a new final value. The animation has a
2145  * definite duration given by @timeline and a speed given by the @mode.
2146  *
2147  * See clutter_actor_animate() for further details.
2148  *
2149  * This function is useful if you want to use an existing timeline
2150  * to animate @actor.
2151  *
2152  * Return value: (transfer none): a #ClutterAnimation object. The object is
2153  *    owned by the #ClutterActor and should not be unreferenced with
2154  *    g_object_unref()
2155  *
2156  * Since: 1.0
2157  */
2158 ClutterAnimation *
2159 clutter_actor_animate_with_timeline (ClutterActor    *actor,
2160                                      gulong           mode,
2161                                      ClutterTimeline *timeline,
2162                                      const gchar     *first_property_name,
2163                                      ...)
2164 {
2165   ClutterAnimation *animation;
2166   va_list args;
2167
2168   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2169   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
2170   g_return_val_if_fail (first_property_name != NULL, NULL);
2171
2172   animation = animation_create_for_actor (actor);
2173   clutter_animation_set_mode (animation, mode);
2174   clutter_animation_set_timeline (animation, timeline);
2175
2176   va_start (args, first_property_name);
2177   clutter_animation_setup_valist (animation, first_property_name, args);
2178   va_end (args);
2179
2180   clutter_animation_start (animation);
2181
2182   return animation;
2183 }
2184
2185 /**
2186  * clutter_actor_animate:
2187  * @actor: a #ClutterActor
2188  * @mode: an animation mode logical id
2189  * @duration: duration of the animation, in milliseconds
2190  * @first_property_name: the name of a property
2191  * @...: a %NULL terminated list of property names and
2192  *   property values
2193  *
2194  * Animates the given list of properties of @actor between the current
2195  * value for each property and a new final value. The animation has a
2196  * definite duration and a speed given by the @mode.
2197  *
2198  * For example, this:
2199  *
2200  * |[
2201  *   clutter_actor_animate (rectangle, CLUTTER_LINEAR, 250,
2202  *                          "width", 100.0,
2203  *                          "height", 100.0,
2204  *                          NULL);
2205  * ]|
2206  *
2207  * will make width and height properties of the #ClutterActor "rectangle"
2208  * grow linearly between the current value and 100 pixels, in 250 milliseconds.
2209  *
2210  * The animation @mode is a logical id, either from the #ClutterAnimationMode
2211  * enumeration of from clutter_alpha_register_func().
2212  *
2213  * All the properties specified will be animated between the current value
2214  * and the final value. If a property should be set at the beginning of
2215  * the animation but not updated during the animation, it should be prefixed
2216  * by the "fixed::" string, for instance:
2217  *
2218  * |[
2219  *   clutter_actor_animate (actor, CLUTTER_EASE_IN_SINE, 100,
2220  *                          "rotation-angle-z", 360.0,
2221  *                          "fixed::rotation-center-z", &amp;center,
2222  *                          NULL);
2223  * ]|
2224  *
2225  * Will animate the "rotation-angle-z" property between the current value
2226  * and 360 degrees, and set the "rotation-center-z" property to the fixed
2227  * value of the #ClutterVertex "center".
2228  *
2229  * This function will implicitly create a #ClutterAnimation object which
2230  * will be assigned to the @actor and will be returned to the developer
2231  * to control the animation or to know when the animation has been
2232  * completed.
2233  *
2234  * If a name argument starts with "signal::", "signal-after::",
2235  * "signal-swapped::" or "signal-swapped-after::" the two following arguments
2236  * are used as callback function and data for a signal handler installed on
2237  * the #ClutterAnimation object for the specified signal name, for instance:
2238  *
2239  * |[
2240  *
2241  *   static void
2242  *   on_animation_completed (ClutterAnimation *animation,
2243  *                           ClutterActor     *actor)
2244  *   {
2245  *     clutter_actor_hide (actor);
2246  *   }
2247  *
2248  *   clutter_actor_animate (actor, CLUTTER_EASE_IN_CUBIC, 100,
2249  *                          "opacity", 0,
2250  *                          "signal::completed", on_animation_completed, actor,
2251  *                          NULL);
2252  * ]|
2253  *
2254  * or, to automatically destroy an actor at the end of the animation:
2255  *
2256  * |[
2257  *   clutter_actor_animate (actor, CLUTTER_EASE_IN_CUBIC, 100,
2258  *                          "opacity", 0,
2259  *                          "signal-swapped-after::completed",
2260  *                            clutter_actor_destroy,
2261  *                            actor,
2262  *                          NULL);
2263  * ]|
2264  *
2265  * The "signal::" modifier is the equivalent of using g_signal_connect();
2266  * the "signal-after::" modifier is the equivalent of using
2267  * g_signal_connect_after() or g_signal_connect_data() with the
2268  * %G_CONNECT_AFTER; the "signal-swapped::" modifier is the equivalent
2269  * of using g_signal_connect_swapped() or g_signal_connect_data() with the
2270  * %G_CONNECT_SWAPPED flah; finally, the "signal-swapped-after::" modifier
2271  * is the equivalent of using g_signal_connect_data() with both the
2272  * %G_CONNECT_AFTER and %G_CONNECT_SWAPPED flags. The clutter_actor_animate()
2273  * function will not keep track of multiple connections to the same signal,
2274  * so it is your responsability to avoid them when calling
2275  * clutter_actor_animate() multiple times on the same actor.
2276  *
2277  * Calling this function on an actor that is already being animated
2278  * will cause the current animation to change with the new final values,
2279  * the new easing mode and the new duration - that is, this code:
2280  *
2281  * |[
2282  *   clutter_actor_animate (actor, CLUTTER_LINEAR, 250,
2283  *                          "width", 100.0,
2284  *                          "height", 100.0,
2285  *                          NULL);
2286  *   clutter_actor_animate (actor, CLUTTER_EASE_IN_CUBIC, 500,
2287  *                          "x", 100.0,
2288  *                          "y", 100.0,
2289  *                          "width", 200.0,
2290  *                          NULL);
2291  * ]|
2292  *
2293  * is the equivalent of:
2294  *
2295  * |[
2296  *   clutter_actor_animate (actor, CLUTTER_EASE_IN_CUBIC, 500,
2297  *                          "x", 100.0,
2298  *                          "y", 100.0,
2299  *                          "width", 200.0,
2300  *                          "height", 100.0,
2301  *                          NULL);
2302  * ]|
2303  *
2304  * <note>Unless the animation is looping, the #ClutterAnimation created by
2305  * clutter_actor_animate() will become invalid as soon as it is
2306  * complete.</note>
2307  *
2308  * Since the created #ClutterAnimation instance attached to @actor
2309  * is guaranteed to be valid throughout the #ClutterAnimation::completed
2310  * signal emission chain, you will not be able to create a new animation
2311  * using clutter_actor_animate() on the same @actor from within the
2312  * #ClutterAnimation::completed signal handler unless you use
2313  * g_signal_connect_after() to connect the callback function, for instance:
2314  *
2315  * |[
2316  *   static void
2317  *   on_animation_completed (ClutterAnimation *animation,
2318  *                           ClutterActor     *actor)
2319  *   {
2320  *     clutter_actor_animate (actor, CLUTTER_EASE_OUT_CUBIC, 250,
2321  *                            "x", 500.0,
2322  *                            "y", 500.0,
2323  *                            NULL);
2324  *   }
2325  *
2326  *     ...
2327  *     animation = clutter_actor_animate (actor, CLUTTER_EASE_IN_CUBIC, 250,
2328  *                                        "x", 100.0,
2329  *                                        "y", 100.0,
2330  *                                        NULL);
2331  *     g_signal_connect (animation, "completed",
2332  *                       G_CALLBACK (on_animation_completed),
2333  *                       actor);
2334  *     ...
2335  * ]|
2336  *
2337  * Return value: (transfer none): a #ClutterAnimation object. The object is
2338  *   owned by the #ClutterActor and should not be unreferenced with
2339  *   g_object_unref()
2340  *
2341  * Since: 1.0
2342  */
2343 ClutterAnimation *
2344 clutter_actor_animate (ClutterActor *actor,
2345                        gulong        mode,
2346                        guint         duration,
2347                        const gchar  *first_property_name,
2348                        ...)
2349 {
2350   ClutterAnimation *animation;
2351   va_list args;
2352
2353   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2354   g_return_val_if_fail (mode != CLUTTER_CUSTOM_MODE, NULL);
2355   g_return_val_if_fail (duration > 0, NULL);
2356   g_return_val_if_fail (first_property_name != NULL, NULL);
2357
2358   animation = animation_create_for_actor (actor);
2359   clutter_animation_set_mode (animation, mode);
2360   clutter_animation_set_duration (animation, duration);
2361
2362   va_start (args, first_property_name);
2363   clutter_animation_setup_valist (animation, first_property_name, args);
2364   va_end (args);
2365
2366   clutter_animation_start (animation);
2367
2368   return animation;
2369 }
2370
2371 /**
2372  * clutter_actor_animatev:
2373  * @actor: a #ClutterActor
2374  * @mode: an animation mode logical id
2375  * @duration: duration of the animation, in milliseconds
2376  * @n_properties: number of property names and values
2377  * @properties: (array length=n_properties) (element-type utf8): a vector
2378  *    containing the property names to set
2379  * @values: (array length=n_properties): a vector containing the
2380  *    property values to set
2381  *
2382  * Animates the given list of properties of @actor between the current
2383  * value for each property and a new final value. The animation has a
2384  * definite duration and a speed given by the @mode.
2385  *
2386  * This is the vector-based variant of clutter_actor_animate(), useful
2387  * for language bindings.
2388  *
2389  * <warning>Unlike clutter_actor_animate(), this function will not
2390  * allow you to specify "signal::" names and callbacks.</warning>
2391  *
2392  * Return value: (transfer none): a #ClutterAnimation object. The object is
2393  *   owned by the #ClutterActor and should not be unreferenced with
2394  *   g_object_unref()
2395  *
2396  * Since: 1.0
2397  */
2398 ClutterAnimation *
2399 clutter_actor_animatev (ClutterActor        *actor,
2400                         gulong               mode,
2401                         guint                duration,
2402                         gint                 n_properties,
2403                         const gchar * const  properties[],
2404                         const GValue        *values)
2405 {
2406   ClutterAnimation *animation;
2407
2408   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2409   g_return_val_if_fail (mode != CLUTTER_CUSTOM_MODE, NULL);
2410   g_return_val_if_fail (duration > 0, NULL);
2411   g_return_val_if_fail (properties != NULL, NULL);
2412   g_return_val_if_fail (values != NULL, NULL);
2413
2414   animation = animation_create_for_actor (actor);
2415   clutter_animation_set_mode (animation, mode);
2416   clutter_animation_set_duration (animation, duration);
2417   clutter_animation_setupv (animation, n_properties, properties, values);
2418   clutter_animation_start (animation);
2419
2420   return animation;
2421 }
2422
2423 /**
2424  * clutter_actor_animate_with_timelinev:
2425  * @actor: a #ClutterActor
2426  * @mode: an animation mode logical id
2427  * @timeline: a #ClutterTimeline
2428  * @n_properties: number of property names and values
2429  * @properties: (array length=n_properties) (element-type utf8): a vector
2430  *    containing the property names to set
2431  * @values: (array length=n_properties): a vector containing the
2432  *    property values to set
2433  *
2434  * Animates the given list of properties of @actor between the current
2435  * value for each property and a new final value. The animation has a
2436  * definite duration given by @timeline and a speed given by the @mode.
2437  *
2438  * See clutter_actor_animate() for further details.
2439  *
2440  * This function is useful if you want to use an existing timeline
2441  * to animate @actor.
2442  *
2443  * This is the vector-based variant of clutter_actor_animate_with_timeline(),
2444  * useful for language bindings.
2445  *
2446  * <warning>Unlike clutter_actor_animate_with_timeline(), this function
2447  * will not allow you to specify "signal::" names and callbacks.</warning>
2448  *
2449  * Return value: (transfer none): a #ClutterAnimation object. The object is
2450  *    owned by the #ClutterActor and should not be unreferenced with
2451  *    g_object_unref()
2452  *
2453  * Since: 1.0
2454  */
2455 ClutterAnimation *
2456 clutter_actor_animate_with_timelinev (ClutterActor        *actor,
2457                                       gulong               mode,
2458                                       ClutterTimeline     *timeline,
2459                                       gint                 n_properties,
2460                                       const gchar * const  properties[],
2461                                       const GValue        *values)
2462 {
2463   ClutterAnimation *animation;
2464
2465   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2466   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
2467   g_return_val_if_fail (properties != NULL, NULL);
2468   g_return_val_if_fail (values != NULL, NULL);
2469
2470   animation = animation_create_for_actor (actor);
2471   clutter_animation_set_mode (animation, mode);
2472   clutter_animation_set_timeline (animation, timeline);
2473   clutter_animation_setupv (animation, n_properties, properties, values);
2474   clutter_animation_start (animation);
2475
2476   return animation;
2477 }
2478
2479 /**
2480  * clutter_actor_animate_with_alphav:
2481  * @actor: a #ClutterActor
2482  * @alpha: a #ClutterAlpha
2483  * @n_properties: number of property names and values
2484  * @properties: (array length=n_properties) (element-type utf8): a vector
2485  *    containing the property names to set
2486  * @values: (array length=n_properties): a vector containing the
2487  *    property values to set
2488  *
2489  * Animates the given list of properties of @actor between the current
2490  * value for each property and a new final value. The animation has a
2491  * definite behaviour given by the passed @alpha.
2492  *
2493  * See clutter_actor_animate() for further details.
2494  *
2495  * This function is useful if you want to use an existing #ClutterAlpha
2496  * to animate @actor.
2497  *
2498  * This is the vector-based variant of clutter_actor_animate_with_alpha(),
2499  * useful for language bindings.
2500  *
2501  * <warning>Unlike clutter_actor_animate_with_alpha(), this function will
2502  * not allow you to specify "signal::" names and callbacks.</warning>
2503  *
2504  * Return value: (transfer none): a #ClutterAnimation object. The object is owned by the
2505  *   #ClutterActor and should not be unreferenced with g_object_unref()
2506  *
2507  * Since: 1.0
2508  *
2509  * Deprecated: 1.10: Use clutter_actor_animate_with_timelinev() instead
2510  */
2511 ClutterAnimation *
2512 clutter_actor_animate_with_alphav (ClutterActor        *actor,
2513                                    ClutterAlpha        *alpha,
2514                                    gint                 n_properties,
2515                                    const gchar * const  properties[],
2516                                    const GValue        *values)
2517 {
2518   ClutterAnimation *animation;
2519   ClutterTimeline *timeline;
2520
2521   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2522   g_return_val_if_fail (CLUTTER_IS_ALPHA (alpha), NULL);
2523   g_return_val_if_fail (properties != NULL, NULL);
2524   g_return_val_if_fail (values != NULL, NULL);
2525
2526   timeline = clutter_alpha_get_timeline (alpha);
2527   if (timeline == NULL)
2528     {
2529       g_warning ("The passed ClutterAlpha does not have an "
2530                  "associated ClutterTimeline.");
2531       return NULL;
2532     }
2533
2534   animation = animation_create_for_actor (actor);
2535   clutter_animation_set_alpha_internal (animation, alpha);
2536   clutter_animation_setupv (animation, n_properties, properties, values);
2537   clutter_animation_start (animation);
2538
2539   return animation;
2540 }
2541
2542 /**
2543  * clutter_actor_get_animation:
2544  * @actor: a #ClutterActor
2545  *
2546  * Retrieves the #ClutterAnimation used by @actor, if clutter_actor_animate()
2547  * has been called on @actor.
2548  *
2549  * Return value: (transfer none): a #ClutterAnimation, or %NULL
2550  *
2551  * Since: 1.0
2552  */
2553 ClutterAnimation *
2554 clutter_actor_get_animation (ClutterActor *actor)
2555 {
2556   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
2557
2558   return g_object_get_qdata (G_OBJECT (actor), quark_object_animation);
2559 }
2560
2561 /**
2562  * clutter_actor_detach_animation:
2563  * @actor: a #ClutterActor
2564  *
2565  * Detaches the #ClutterAnimation used by @actor, if clutter_actor_animate()
2566  * has been called on @actor.
2567  *
2568  * Once the animation has been detached, it loses a reference. If it was
2569  * the only reference then the #ClutterAnimation becomes invalid.
2570  *
2571  * The #ClutterAnimation::completed signal will not be emitted.
2572  *
2573  * Since: 1.4
2574  */
2575 void
2576 clutter_actor_detach_animation (ClutterActor *actor)
2577 {
2578   ClutterAnimation *animation;
2579   ClutterAnimationPrivate *priv;
2580
2581   g_return_if_fail (CLUTTER_IS_ACTOR (actor));
2582
2583   animation = g_object_get_qdata (G_OBJECT (actor), quark_object_animation);
2584   if (animation == NULL)
2585     return;
2586
2587   priv = animation->priv;
2588
2589   g_assert (priv->object == G_OBJECT (actor));
2590
2591   /* we can't call get_timeline_internal() here because it would be
2592    * pointless to create a timeline on an animation we want to detach
2593    */
2594   if (priv->alpha != NULL)
2595     {
2596       ClutterTimeline *timeline;
2597
2598       timeline = clutter_alpha_get_timeline (priv->alpha);
2599       if (timeline != NULL)
2600         clutter_timeline_stop (timeline);
2601     }
2602
2603   /* disconnect the ::destroy handler added by animation_create_for_actor() */
2604   g_signal_handlers_disconnect_by_func (actor,
2605                                         G_CALLBACK (on_actor_destroy),
2606                                         animation);
2607
2608   clutter_animation_set_object (animation, NULL);
2609
2610   /* drop the reference on the animation */
2611   g_object_unref (animation);
2612 }