diff --git a/.gitignore b/.gitignore
index 6320d8dcb..a1d164951 100644
--- a/.gitignore
+++ b/.gitignore
@@ -146,6 +146,7 @@ TAGS
/tests/interactive/test-flow-layout
/tests/interactive/test-box-layout
/tests/interactive/stamp-test-interactive
+/tests/interactive/test-animator
/tests/conform/stamp-test-conformance
/tests/conform/test-anchors
/tests/conform/test-cogl-backface-culling
diff --git a/clutter/Makefile.am b/clutter/Makefile.am
index 09fa0692e..38f247462 100644
--- a/clutter/Makefile.am
+++ b/clutter/Makefile.am
@@ -66,6 +66,7 @@ source_h = \
$(srcdir)/clutter-alpha.h \
$(srcdir)/clutter-animatable.h \
$(srcdir)/clutter-animation.h \
+ $(srcdir)/clutter-animator.h \
$(srcdir)/clutter-backend.h \
$(srcdir)/clutter-behaviour.h \
$(srcdir)/clutter-behaviour-depth.h \
@@ -138,6 +139,7 @@ source_c = \
$(srcdir)/clutter-alpha.c \
$(srcdir)/clutter-animatable.c \
$(srcdir)/clutter-animation.c \
+ $(srcdir)/clutter-animator.c \
$(srcdir)/clutter-backend.c \
$(srcdir)/clutter-behaviour.c \
$(srcdir)/clutter-behaviour-depth.c \
diff --git a/clutter/clutter-animator.c b/clutter/clutter-animator.c
new file mode 100644
index 000000000..f9fd309de
--- /dev/null
+++ b/clutter/clutter-animator.c
@@ -0,0 +1,1435 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author:
+ * Øyvind Kolås
+ */
+
+/**
+ * SECTION:clutter-animator
+ * @short_description: Multi-actor tweener
+ * @See_Also: #ClutterAnimatable, #ClutterInterval, #ClutterAlpha,
+ * #ClutterTimeline
+ *
+ * #ClutterAnimator is an object providing declarative animations for
+ * #GObject properties belonging to one or more #GObjects to
+ * #ClutterIntervals.
+ *
+ * #ClutterAnimator is used to build and describe complex animations
+ * in terms of "key frames". #ClutterAnimator is meant to be used
+ * through the #ClutterScript definition format, but it comes with a
+ * convenience C API.
+ *
+ * #ClutterAnimator is available since Clutter 1.2
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+
+#include "clutter-animator.h"
+
+#include "clutter-alpha.h"
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
+#include "clutter-interval.h"
+#include "clutter-private.h"
+
+G_DEFINE_TYPE (ClutterAnimator, clutter_animator, G_TYPE_OBJECT);
+
+#define CLUTTER_ANIMATOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_ANIMATOR, ClutterAnimatorPrivate))
+
+struct _ClutterAnimatorPrivate
+{
+ ClutterTimeline *timeline;
+ ClutterTimeline *slave_timeline;
+
+ GList *score;
+
+ GHashTable *properties;
+};
+
+struct _ClutterAnimatorKey
+{
+ GObject *object;
+ const gchar *property_name;
+ guint mode;
+
+ GValue value;
+
+ /* normalized progress, between 0.0 and 1.0 */
+ gdouble progress;
+
+ /* back-pointer to the animator which owns the key */
+ ClutterAnimator *animator;
+
+ /* interpolation mode */
+ ClutterInterpolation interpolation;
+
+ /* ease from the current object state into the animation when it starts */
+ guint ease_in : 1;
+
+ /* This key is already being destroyed and shouldn't
+ * trigger additional weak unrefs
+ */
+ guint is_inert : 1;
+
+ gint ref_count;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_DURATION
+};
+
+/**
+ * clutter_animator_new:
+ *
+ * Create a new #ClutterAnimator instance.
+ *
+ * Returns: a new #ClutterAnimator.
+ */
+ClutterAnimator *
+clutter_animator_new (void)
+{
+ return g_object_new (CLUTTER_TYPE_ANIMATOR, NULL);
+}
+
+/***/
+
+typedef struct _PropObjectKey {
+ GObject *object;
+ const gchar *property_name;
+ guint mode;
+ gdouble progress;
+} PropObjectKey;
+
+typedef struct _KeyAnimator {
+ PropObjectKey *key;
+ ClutterInterval *interval;
+ ClutterAlpha *alpha;
+
+ GList *current;
+
+ gdouble start; /* the progress of current */
+ gdouble end; /* until which progress it is valid */
+ ClutterInterpolation interpolation;
+
+ guint ease_in : 1;
+} KeyAnimator;
+
+static PropObjectKey *
+prop_actor_key_new (GObject *object,
+ const gchar *property_name)
+{
+ PropObjectKey *key = g_slice_new0 (PropObjectKey);
+
+ key->object = object;
+ key->property_name = g_intern_string (property_name);
+
+ return key;
+}
+
+static void
+prop_actor_key_free (gpointer key)
+{
+ if (key != NULL)
+ g_slice_free (PropObjectKey, key);
+}
+
+static void
+key_animator_free (gpointer key)
+{
+ if (key != NULL)
+ {
+ KeyAnimator *key_animator = key;
+
+ g_object_unref (key_animator->interval);
+ g_object_unref (key_animator->alpha);
+
+ g_slice_free (KeyAnimator, key_animator);
+ }
+}
+
+static KeyAnimator *
+key_animator_new (ClutterAnimator *animator,
+ PropObjectKey *key,
+ GType type)
+{
+ KeyAnimator *key_animator = g_slice_new (KeyAnimator);
+ ClutterInterval *interval = g_object_new (CLUTTER_TYPE_INTERVAL,
+ "value-type", type,
+ NULL);
+
+ /* we own this interval */
+ g_object_ref_sink (interval);
+
+ key_animator->interval = interval;
+ key_animator->key = key;
+ key_animator->alpha = clutter_alpha_new ();
+ clutter_alpha_set_timeline (key_animator->alpha,
+ animator->priv->slave_timeline);
+
+ /* as well as the alpha */
+ g_object_ref_sink (key_animator->alpha);
+
+ return key_animator;
+}
+
+static guint
+prop_actor_hash (gconstpointer value)
+{
+ const PropObjectKey *info = value;
+
+ return GPOINTER_TO_INT (info->property_name)
+ ^ GPOINTER_TO_INT (info->object);
+}
+
+static gboolean
+prop_actor_equal (gconstpointer a, gconstpointer b)
+{
+ const PropObjectKey *infoa = a;
+ const PropObjectKey *infob = b;
+
+ /* property name strings are interned so we can just compare pointers */
+ if (infoa->object == infob->object &&
+ (infoa->property_name == infob->property_name))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gint
+sort_actor_prop_progress_func (gconstpointer a,
+ gconstpointer b)
+{
+ const ClutterAnimatorKey *pa = a;
+ const ClutterAnimatorKey *pb = b;
+
+ if (pa->object == pb->object)
+ {
+ gint pdiff = pb->property_name - pa->property_name;
+
+ if (pdiff)
+ return pdiff;
+
+ if (pa->progress == pb->progress)
+ return 0;
+
+ if (pa->progress > pb->progress)
+ return 1;
+
+ return -1;
+ }
+
+ return pa->object - pb->object;
+}
+
+static gint
+sort_actor_prop_func (gconstpointer a,
+ gconstpointer b)
+{
+ const ClutterAnimatorKey *pa = a;
+ const ClutterAnimatorKey *pb = b;
+
+ if (pa->object == pb->object)
+ return pa->property_name - pb->property_name;
+
+ return pa->object - pb->object;
+}
+
+
+static void
+object_disappeared (gpointer data,
+ GObject *where_the_object_was)
+{
+ clutter_animator_remove_key (data, where_the_object_was, NULL, -1.0);
+}
+
+static ClutterAnimatorKey *
+clutter_animator_key_new (ClutterAnimator *animator,
+ gdouble progress,
+ GObject *object,
+ guint mode,
+ const gchar *property_name)
+{
+ ClutterAnimatorKey *animator_key;
+
+ animator_key = g_slice_new (ClutterAnimatorKey);
+
+ animator_key->ref_count = 1;
+ animator_key->animator = animator;
+ animator_key->object = object;
+ animator_key->mode = mode;
+ animator_key->progress = progress;
+ animator_key->property_name = g_intern_string (property_name);
+ animator_key->interpolation = CLUTTER_INTERPOLATION_LINEAR;
+ animator_key->ease_in = FALSE;
+ animator_key->is_inert = FALSE;
+
+ /* keep a weak reference on the animator, so that we can release the
+ * back-pointer when needed
+ */
+ g_object_weak_ref (object, object_disappeared,
+ animator_key->animator);
+
+ return animator_key;
+}
+
+static gpointer
+clutter_animator_key_copy (gpointer boxed)
+{
+ ClutterAnimatorKey *key = boxed;
+
+ if (key != NULL)
+ key->ref_count += 1;
+
+ return key;
+}
+
+static void
+clutter_animator_key_free (gpointer boxed)
+{
+ ClutterAnimatorKey *key = boxed;
+
+ if (key == NULL)
+ return;
+
+ key->ref_count -= 1;
+
+ if (key->ref_count > 0)
+ return;
+
+ if (!key->is_inert)
+ g_object_weak_unref (key->object, object_disappeared, key->animator);
+
+ g_slice_free (ClutterAnimatorKey, key);
+}
+
+static void
+clutter_animator_finalize (GObject *object)
+{
+ ClutterAnimator *animator = CLUTTER_ANIMATOR (object);
+ ClutterAnimatorPrivate *priv = animator->priv;
+
+ g_list_foreach (priv->score, (GFunc) clutter_animator_key_free, NULL);
+ g_list_free (priv->score);
+ priv->score = NULL;
+
+#if 0
+ for (; priv->score;
+ priv->score = g_list_remove (priv->score, priv->score->data))
+ {
+ clutter_animator_key_free (priv->score->data);
+ }
+#endif
+
+ g_object_unref (priv->timeline);
+ g_object_unref (priv->slave_timeline);
+
+ G_OBJECT_CLASS (clutter_animator_parent_class)->finalize (object);
+}
+
+/* XXX: this is copied and slightly modified from glib,
+ * there is only one way to do this. */
+static GList *
+list_find_custom_reverse (GList *list,
+ gconstpointer data,
+ GCompareFunc func)
+{
+ while (list)
+ {
+ if (! func (list->data, data))
+ return list;
+
+ list = list->prev;
+ }
+
+ return NULL;
+}
+
+/* Ensures that the interval provided by the animator is correct
+ * for the requested progress value.
+ */
+static void
+animation_animator_ensure_animator (ClutterAnimator *animator,
+ KeyAnimator *key_animator,
+ PropObjectKey *key,
+ gdouble progress)
+{
+
+ if (progress > key_animator->end)
+ {
+ while (progress > key_animator->end)
+ {
+ ClutterAnimatorKey *initial_key, *next_key;
+ GList *initial, *next;
+
+ initial = g_list_find_custom (key_animator->current->next,
+ key,
+ sort_actor_prop_func);
+ g_assert (initial != NULL);
+
+ initial_key = initial->data;
+
+ clutter_interval_set_initial_value (key_animator->interval,
+ &initial_key->value);
+ key_animator->current = initial;
+ key_animator->start = initial_key->progress;
+
+ next = g_list_find_custom (initial->next,
+ key,
+ sort_actor_prop_func);
+ if (next)
+ {
+ next_key = next->data;
+
+ key_animator->end = next_key->progress;
+ }
+ else
+ {
+ next_key = initial_key;
+
+ key_animator->end = 1.0;
+ }
+
+ clutter_interval_set_final_value (key_animator->interval,
+ &next_key->value);
+
+ if ((clutter_alpha_get_mode (key_animator->alpha) != next_key->mode))
+ clutter_alpha_set_mode (key_animator->alpha, next_key->mode);
+ }
+ }
+ else if (progress < key_animator->start)
+ {
+ while (progress < key_animator->start)
+ {
+ ClutterAnimatorKey *initial_key, *next_key;
+ GList *initial;
+ GList *old = key_animator->current;
+
+ initial = list_find_custom_reverse (key_animator->current->prev,
+ key,
+ sort_actor_prop_func);
+ g_assert (initial != NULL);
+
+ initial_key = initial->data;
+
+ clutter_interval_set_initial_value (key_animator->interval,
+ &initial_key->value);
+ key_animator->current = initial;
+ key_animator->end = key_animator->start;
+ key_animator->start = initial_key->progress;
+
+ if (old)
+ {
+ next_key = old->data;
+
+ key_animator->end = next_key->progress;
+ }
+ else
+ {
+ next_key = initial_key;
+
+ key_animator->end = 1.0;
+ }
+
+ clutter_interval_set_final_value (key_animator->interval,
+ &next_key->value);
+ if ((clutter_alpha_get_mode (key_animator->alpha) != next_key->mode))
+ clutter_alpha_set_mode (key_animator->alpha, next_key->mode);
+ }
+ }
+}
+
+/* XXX - this might be useful as an internal function exposed somewhere */
+static gdouble
+cubic_interpolation (const gdouble dx,
+ const gdouble prev,
+ const gdouble j,
+ const gdouble next,
+ const gdouble nextnext)
+{
+ return (((( - prev + 3 * j - 3 * next + nextnext ) * dx +
+ ( 2 * prev - 5 * j + 4 * next - nextnext ) ) * dx +
+ ( - prev + next ) ) * dx + (j + j) ) / 2.0;
+}
+
+/* try to get a floating point key value from a key for a property,
+ * failing use the closest key in that direction or the starting point.
+ */
+static gfloat
+list_try_get_rel (GList *list,
+ gint count)
+{
+ GList *iter = list;
+ GList *best = list;
+
+ if (count > 0)
+ {
+ while (count -- && iter != NULL)
+ {
+ iter = g_list_find_custom (iter->next, list->data,
+ sort_actor_prop_func);
+ if (iter != NULL)
+ best = iter;
+ }
+ }
+ else
+ {
+ while (count ++ < 0 && iter != NULL)
+ {
+ iter = list_find_custom_reverse (iter->prev, list->data,
+ sort_actor_prop_func);
+ if (iter != NULL)
+ best = iter;
+ }
+ }
+
+ return g_value_get_float (&(((ClutterAnimatorKey *)best->data)->value));
+}
+
+static void
+animation_animator_new_frame (ClutterTimeline *timeline,
+ gint msecs,
+ ClutterAnimator *animator)
+{
+ gdouble progress;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ progress = 1.0 * msecs / clutter_timeline_get_duration (timeline);
+
+ /* for each property that is managed figure out the GValue to set,
+ * avoid creating new ClutterInterval's for each interval crossed
+ */
+ g_hash_table_iter_init (&iter, animator->priv->properties);
+
+ key = value = NULL;
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ PropObjectKey *prop_actor_key = key;
+ KeyAnimator *key_animator = value;
+ ClutterAnimatorKey *start_key;
+ gdouble sub_progress;
+
+ animation_animator_ensure_animator (animator, key_animator,
+ key,
+ progress);
+ start_key = key_animator->current->data;
+
+ sub_progress = (progress - key_animator->start)
+ / (key_animator->end - key_animator->start);
+
+ /* do not change values if we're not active yet (delay) */
+ if (sub_progress >= 0.0 && sub_progress <= 1.0)
+ {
+ GValue cvalue = { 0, };
+ GType int_type;
+
+ g_value_init (&cvalue, G_VALUE_TYPE (&start_key->value));
+
+ clutter_timeline_advance (animator->priv->slave_timeline,
+ sub_progress * 10000);
+
+ sub_progress = clutter_alpha_get_alpha (key_animator->alpha);
+ int_type = clutter_interval_get_value_type (key_animator->interval);
+
+ if (key_animator->interpolation == CLUTTER_INTERPOLATION_CUBIC &&
+ int_type == G_TYPE_FLOAT)
+ {
+ gdouble prev, current, next, nextnext;
+ gdouble res;
+
+ if ((key_animator->ease_in == FALSE ||
+ (key_animator->ease_in &&
+ list_find_custom_reverse (key_animator->current->prev,
+ key_animator->current->data,
+ sort_actor_prop_func))))
+ {
+ current = g_value_get_float (&start_key->value);
+ prev = list_try_get_rel (key_animator->current, -1);
+ }
+ else
+ {
+ /* interpolated and easing in */
+ clutter_interval_get_initial_value (key_animator->interval,
+ &cvalue);
+ prev = current = g_value_get_float (&cvalue);
+ }
+
+ next = list_try_get_rel (key_animator->current, 1);
+ nextnext = list_try_get_rel (key_animator->current, 2);
+ res = cubic_interpolation (sub_progress, prev, current, next,
+ nextnext);
+
+ g_value_set_float (&cvalue, res);
+ }
+ else
+ clutter_interval_compute_value (key_animator->interval,
+ sub_progress,
+ &cvalue);
+
+ g_object_set_property (prop_actor_key->object,
+ prop_actor_key->property_name,
+ &cvalue);
+
+ g_value_unset (&cvalue);
+ }
+ }
+}
+
+static void
+animation_animator_started (ClutterTimeline *timeline,
+ ClutterAnimator *animator)
+{
+ GList *k;
+
+ /* Ensure that animators exist for all involved properties */
+ for (k = animator->priv->score; k != NULL; k = k->next)
+ {
+ ClutterAnimatorKey *key = k->data;
+ KeyAnimator *key_animator;
+ PropObjectKey *prop_actor_key;
+
+ prop_actor_key = prop_actor_key_new (key->object, key->property_name);
+ key_animator = g_hash_table_lookup (animator->priv->properties,
+ prop_actor_key);
+ if (key_animator)
+ {
+ prop_actor_key_free (prop_actor_key);
+ }
+ else
+ {
+ GObjectClass *klass = G_OBJECT_GET_CLASS (key->object);
+ GParamSpec *pspec;
+
+ pspec = g_object_class_find_property (klass, key->property_name);
+
+ key_animator = key_animator_new (animator, prop_actor_key,
+ G_PARAM_SPEC_VALUE_TYPE (pspec));
+ g_hash_table_insert (animator->priv->properties,
+ prop_actor_key,
+ key_animator);
+ }
+ }
+
+ /* initialize animator with initial list pointers */
+ {
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, animator->priv->properties);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ KeyAnimator *key_animator = value;
+ ClutterAnimatorKey *initial_key, *next_key;
+ GList *initial;
+ GList *next;
+
+ initial = g_list_find_custom (animator->priv->score,
+ key,
+ sort_actor_prop_func);
+ g_assert (initial != NULL);
+ initial_key = initial->data;
+ clutter_interval_set_initial_value (key_animator->interval,
+ &initial_key->value);
+
+ key_animator->current = initial;
+ key_animator->start = initial_key->progress;
+ key_animator->ease_in = initial_key->ease_in;
+ key_animator->interpolation = initial_key->interpolation;
+
+ if (key_animator->ease_in)
+ {
+ GValue cvalue = { 0, };
+ GType int_type;
+
+ int_type = clutter_interval_get_value_type (key_animator->interval);
+ g_value_init (&cvalue, int_type);
+
+ g_object_get_property (initial_key->object,
+ initial_key->property_name,
+ &cvalue);
+
+ clutter_interval_set_initial_value (key_animator->interval,
+ &cvalue);
+
+ g_value_unset (&cvalue);
+ }
+
+ next = g_list_find_custom (initial->next, key, sort_actor_prop_func);
+ if (next)
+ {
+ next_key = next->data;
+ key_animator->end = next_key->progress;
+ }
+ else
+ {
+ next_key = initial_key;
+ key_animator->end = 1.0;
+ }
+
+ clutter_interval_set_final_value (key_animator->interval,
+ &next_key->value);
+ if ((clutter_alpha_get_mode (key_animator->alpha) != next_key->mode))
+ clutter_alpha_set_mode (key_animator->alpha, next_key->mode);
+ }
+ }
+}
+
+/**
+ * clutter_animator_set_timeline:
+ * @animator: a #ClutterAnimator
+ * @timeline: a #ClutterTimeline
+ *
+ * Sets an external timeline that will be used for driving the animation
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_set_timeline (ClutterAnimator *animator,
+ ClutterTimeline *timeline)
+{
+ ClutterAnimatorPrivate *priv;
+
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+
+ priv = animator->priv;
+
+ if (priv->timeline != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->timeline,
+ animation_animator_new_frame,
+ animator);
+ g_signal_handlers_disconnect_by_func (priv->timeline,
+ animation_animator_started,
+ animator);
+ g_object_unref (priv->timeline);
+ }
+
+ priv->timeline = timeline;
+ if (timeline != NULL)
+ {
+ g_object_ref_sink (priv->timeline);
+
+ g_signal_connect (priv->timeline, "new-frame",
+ G_CALLBACK (animation_animator_new_frame),
+ animator);
+ g_signal_connect (priv->timeline, "started",
+ G_CALLBACK (animation_animator_started),
+ animator);
+ }
+}
+
+/**
+ * clutter_animator_get_timeline:
+ * @animator: a #ClutterAnimator
+ *
+ * Get the timeline hooked up for driving the #ClutterAnimator
+ *
+ * Return value: the #ClutterTimeline that drives the animator.
+ */
+ClutterTimeline *
+clutter_animator_get_timeline (ClutterAnimator *animator)
+{
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL);
+ return animator->priv->timeline;
+}
+
+/**
+ * clutter_animator_run:
+ * @animator: a #ClutterAnimator
+ *
+ * Start the ClutterAnimator, this is a thin wrapper that rewinds
+ * and starts the animators current timeline.
+ *
+ * Return value: the #ClutterTimeline that drives the animator.
+ */
+ClutterTimeline *
+clutter_animator_run (ClutterAnimator *animator)
+{
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL);
+ clutter_timeline_rewind (animator->priv->timeline);
+ clutter_timeline_start (animator->priv->timeline);
+ return animator->priv->timeline;
+}
+
+/**
+ * clutter_animator_set_duration:
+ * @animator: a #ClutterAnimator
+ * @duration: milliseconds a run of the animator should last.
+ *
+ * Runs the timeline of the #ClutterAnimator with a duration in msecs
+ * as specified.
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_set_duration (ClutterAnimator *animator,
+ guint duration)
+{
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+
+ clutter_timeline_set_duration (animator->priv->timeline, duration);
+}
+
+/**
+ * clutter_animator_get_duration:
+ * @animator: a #ClutterAnimator
+ *
+ * Retrieves the current duration of an animator
+ *
+ * Return value: the duration of the animation, in milliseconds
+ *
+ * Since: 1.2
+ */
+guint
+clutter_animator_get_duration (ClutterAnimator *animator)
+{
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), 0);
+
+ return clutter_timeline_get_duration (animator->priv->timeline);
+}
+
+/**
+ * clutter_animator_set:
+ * @animator: a #ClutterAnimator
+ * @first_object: a #GObject
+ * @first_property_name: the property to specify a key for
+ * @first_mode: the id of the alpha function to use
+ * @first_progress: at which stage of the animation this value applies; the
+ * range is a normalized floating point value between 0 and 1
+ * @VarArgs: the value first_property_name should have for first_object
+ * at first_progress, followed by more (object, property_name, mode,
+ * progress, value) tuples, followed by %NULL
+ *
+ * Adds multiple keys to a #ClutterAnimator, specifying the value a given
+ * property should have at a given progress of the animation. The mode
+ * specified is the mode used when going to this key from the previous key of
+ * the @property_name
+ *
+ * If a given (object, property, progress) tuple already exist the mode and
+ * value will be replaced with the new values.
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_set (ClutterAnimator *animator,
+ gpointer first_object,
+ const gchar *first_property_name,
+ guint first_mode,
+ gdouble first_progress,
+ ...)
+{
+ GObject *object;
+ const gchar *property_name;
+ guint mode;
+ gdouble progress;
+ va_list args;
+
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+
+ object = first_object;
+ property_name = first_property_name;
+ mode = first_mode;
+ progress = first_progress;
+
+ va_start (args, first_progress);
+
+ while (object != NULL)
+ {
+ GParamSpec *pspec;
+ GObjectClass *klass;
+ GValue value = { 0, };
+ gchar *error = NULL;
+
+ g_return_if_fail (object);
+ g_return_if_fail (property_name);
+
+ klass = G_OBJECT_GET_CLASS (object);
+ pspec = g_object_class_find_property (klass, property_name);
+
+ if (!pspec)
+ {
+ g_warning ("Cannot bind property '%s': object of type '%s' "
+ "do not have this property",
+ property_name, G_OBJECT_TYPE_NAME (object));
+ break;
+ }
+
+ /* FIXME - Depend on GLib 2.24 and use G_VALUE_COLLECT_INIT() */
+ g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ G_VALUE_COLLECT (&value, args, 0, &error);
+
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRLOC, error);
+ g_free (error);
+ break;
+ }
+
+ clutter_animator_set_key (animator,
+ object,
+ property_name,
+ mode,
+ progress,
+ &value);
+
+ object= va_arg (args, GObject *);
+ if (object)
+ {
+ property_name = va_arg (args, gchar*);
+ if (!property_name)
+ {
+ g_warning ("%s: expected a property name", G_STRLOC);
+ break;
+ }
+ mode = va_arg (args, guint);
+ progress = va_arg (args, gdouble);
+ }
+ }
+
+ va_end (args);
+}
+
+/**
+ * clutter_animator_set_key:
+ * @animator: a #ClutterAnimator
+ * @object: a #GObject
+ * @property_name: the property to specify a key for
+ * @mode: the id of the alpha function to use
+ * @progress: at which stage of the animation this value applies (range 0.0-1.0)
+ * @value: the value property_name should have at progress.
+ *
+ * As clutter_animator_set but only for a single key.
+ *
+ * Return value: (transfer none): The animator itself.
+ * Since: 1.2
+ */
+ClutterAnimator *
+clutter_animator_set_key (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ guint mode,
+ gdouble progress,
+ const GValue *value)
+{
+ ClutterAnimatorPrivate *priv;
+ ClutterAnimatorKey *animator_key;
+ GList *old_item;
+
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL);
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+ g_return_val_if_fail (property_name, NULL);
+ g_return_val_if_fail (value, NULL);
+
+ priv = animator->priv;
+ property_name = g_intern_string (property_name);
+
+ animator_key = clutter_animator_key_new (animator, progress, object, mode,
+ property_name);
+
+ g_value_init (&animator_key->value, G_VALUE_TYPE (value));
+ g_value_copy (value, &animator_key->value);
+
+ if ((old_item = g_list_find_custom (priv->score, animator_key,
+ sort_actor_prop_progress_func)))
+ {
+ ClutterAnimatorKey *old_key = old_item->data;
+ clutter_animator_key_free (old_key);
+ animator->priv->score = g_list_remove (animator->priv->score, old_key);
+ }
+
+ priv->score = g_list_insert_sorted (priv->score, animator_key,
+ sort_actor_prop_progress_func);
+ return animator;
+}
+
+/**
+ * clutter_animator_get_keys:
+ * @animator: a #ClutterAnimator instance
+ * @object: a #GObject to search for or NULL for all
+ * @property_name: a specific property name to query for or NULL for all
+ * @progress: a specific progress to search for or a negative value for all
+ *
+ * Returns a list of pointers to opaque structures with accessor functions
+ * that describe the keys added to an animator.
+ *
+ * Return value: (transfer container) (element-type ClutterAnimatorKey): a
+ * list of #ClutterAnimatorKeys; the contents of the list are owned
+ * by the #ClutterAnimator, but you should free the returned list when done,
+ * using g_list_free()
+ *
+ * Since: 1.2
+ */
+GList *
+clutter_animator_get_keys (ClutterAnimator *animator,
+ GObject *object,/* or NULL for all */
+ const gchar *property_name,
+ gdouble progress)
+{
+ GList *keys = NULL;
+ GList *k;
+
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL);
+
+ property_name = g_intern_string (property_name);
+
+ for (k = animator->priv->score; k; k = k->next)
+ {
+ ClutterAnimatorKey *key = k->data;
+
+ if ((object == NULL || (object == key->object)) &&
+ (property_name == NULL || ((property_name == key->property_name))) &&
+ (progress < 0 || (progress == key->progress)))
+ {
+ keys = g_list_prepend (keys, key);
+ }
+ }
+
+ return g_list_reverse (keys);
+}
+
+/**
+ * clutter_animator_remove:
+ * @object: a #GObject to search for or NULL for all
+ * @property_name: a specific property name to query for or NULL for all
+ * @progress: a specific progress to search for or a negative value for all
+ *
+ * Removes all keys matching the conditions specificed in the arguments.
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_remove_key (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ gdouble progress)
+{
+ ClutterAnimatorPrivate *priv;
+ GList *k;
+
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (property_name != NULL);
+
+ property_name = g_intern_string (property_name);
+
+ priv = animator->priv;
+
+ for (k = priv->score; k != NULL; k = k->next)
+ {
+ ClutterAnimatorKey *key = k->data;
+
+ if ((object == NULL || (object == key->object)) &&
+ (property_name == NULL || ((property_name == key->property_name))) &&
+ (progress < 0 || (progress == key->progress))
+ )
+ {
+ key->is_inert = TRUE;
+
+ clutter_animator_key_free (key);
+
+ /* FIXME: non performant since we reiterate the list many times */
+ k = priv->score = g_list_remove (priv->score, key);
+ }
+ }
+
+ if (object)
+ {
+ GHashTableIter iter;
+ gpointer key, value;
+
+again:
+ g_hash_table_iter_init (&iter, priv->properties);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ PropObjectKey *prop_actor_key = key;
+ if (prop_actor_key->object == object)
+ {
+ g_hash_table_remove (priv->properties, key);
+ goto again;
+ }
+ }
+ }
+}
+
+static void
+clutter_animator_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterAnimator *self = CLUTTER_ANIMATOR (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ clutter_animator_set_duration (self, g_value_get_uint (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_animator_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterAnimatorPrivate *priv = CLUTTER_ANIMATOR (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ g_value_set_uint (value, clutter_timeline_get_duration (priv->timeline));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_animator_class_init (ClutterAnimatorClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ g_type_class_add_private (klass, sizeof (ClutterAnimatorPrivate));
+
+ gobject_class->set_property = clutter_animator_set_property;
+ gobject_class->get_property = clutter_animator_get_property;
+ gobject_class->finalize = clutter_animator_finalize;
+
+ /**
+ * ClutterAnimator:duration:
+ *
+ * The duration of the #ClutterTimeline used by the #ClutterAnimator
+ * to drive the animation
+ *
+ * Since: 1.2
+ */
+ pspec = g_param_spec_uint ("duration",
+ "Duration",
+ "The duration of the animation",
+ 0, G_MAXUINT,
+ 2000,
+ CLUTTER_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class, PROP_DURATION, pspec);
+}
+
+static void
+clutter_animator_init (ClutterAnimator *animator)
+{
+ ClutterAnimatorPrivate *priv;
+
+ animator->priv = priv = CLUTTER_ANIMATOR_GET_PRIVATE (animator);
+
+ priv->properties = g_hash_table_new_full (prop_actor_hash,
+ prop_actor_equal,
+ prop_actor_key_free,
+ key_animator_free);
+
+ clutter_animator_set_timeline (animator, clutter_timeline_new (2000));
+
+ priv->slave_timeline = clutter_timeline_new (10000);
+ g_object_ref_sink (priv->slave_timeline);
+}
+
+
+/**
+ * clutter_animator_property_get_ease_in:
+ * @animator: a #ClutterAnimatorKey
+ * @object: a #GObject
+ * @property_name: the name of a property on object
+ *
+ * Checks if a property value is to be eased into the animation.
+ *
+ * Return value: %TRUE if the property is eased in
+ *
+ * Since: 1.2
+ */
+gboolean
+clutter_animator_property_get_ease_in (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name)
+{
+ ClutterAnimatorKey key, *initial_key;
+ GList *initial;
+
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), FALSE);
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+
+ key.object = object;
+ key.property_name = g_intern_string (property_name);
+ initial = g_list_find_custom (animator->priv->score, &key,
+ sort_actor_prop_func);
+ if (initial != NULL)
+ {
+ initial_key = initial->data;
+
+ return initial_key->ease_in;
+ }
+
+ return FALSE;
+}
+
+/**
+ * clutter_animator_property_set_ease_in:
+ * @animator: a #ClutterAnimatorKey
+ * @object: a #GObject
+ * @property_name: the name of a property on object
+ * @ease_in: we are going to be easing in this property
+ *
+ * Sets whether a property value is to be eased into the animation.
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_property_set_ease_in (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ gboolean ease_in)
+{
+ ClutterAnimatorKey key, *initial_key;
+ GList *initial;
+
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (property_name);
+
+ key.object = object;
+ key.property_name = g_intern_string (property_name);
+ initial = g_list_find_custom (animator->priv->score, &key,
+ sort_actor_prop_func);
+ if (initial)
+ {
+ initial_key = initial->data;
+ initial_key->ease_in = ease_in;
+ }
+ else
+ g_warning ("The animator has no object of type '%s' with a "
+ "property named '%s'",
+ G_OBJECT_TYPE_NAME (object),
+ property_name);
+}
+
+
+/**
+ * clutter_animator_property_set_interpolation:
+ * @animator: a #ClutterAnimatorKey
+ * @object: a #GObject
+ * @property_name: the name of a property on object
+ * @interpolation: the #ClutterInterpolation to use
+ *
+ * Get the interpolation used by animator for a property on a particular
+ * object.
+ *
+ * Returns: a ClutterInterpolation value.
+ * Since: 1.2
+ */
+ClutterInterpolation
+clutter_animator_property_get_interpolation (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ ClutterInterpolation interpolation)
+{
+ GList *initial;
+ ClutterAnimatorKey key, *initial_key;
+
+ g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator),
+ CLUTTER_INTERPOLATION_LINEAR);
+ g_return_val_if_fail (G_IS_OBJECT (object),
+ CLUTTER_INTERPOLATION_LINEAR);
+ g_return_val_if_fail (property_name,
+ CLUTTER_INTERPOLATION_LINEAR);
+
+ key.object = object;
+ key.property_name = g_intern_string (property_name);
+ initial = g_list_find_custom (animator->priv->score, &key,
+ sort_actor_prop_func);
+ if (initial)
+ {
+ initial_key = initial->data;
+
+ return initial_key->interpolation;
+ }
+
+ return CLUTTER_INTERPOLATION_LINEAR;
+}
+
+/**
+ * clutter_animator_property_set_interpolation:
+ * @animator: a #ClutterAnimatorKey
+ * @object: a #GObject
+ * @property_name: the name of a property on object
+ * @interpolation: the #ClutterInterpolation to use
+ *
+ * Set the interpolation method to use, CLUTTER_INTERPOLATION_LINEAR causes the
+ * values to linearly change between the values, CLUTTER_INTERPOLATION_CUBIC
+ * causes the values to smoothly change between the values.
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_property_set_interpolation (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ ClutterInterpolation interpolation)
+{
+ GList *initial;
+ ClutterAnimatorKey key, *initial_key;
+
+ g_return_if_fail (CLUTTER_IS_ANIMATOR (animator));
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (property_name);
+
+ key.object = object;
+ key.property_name = g_intern_string (property_name);
+ initial = g_list_find_custom (animator->priv->score, &key,
+ sort_actor_prop_func);
+ if (initial)
+ {
+ initial_key = initial->data;
+ initial_key->interpolation = interpolation;
+ }
+}
+
+GType
+clutter_animator_key_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (!our_type)
+ our_type = g_boxed_type_register_static (I_("ClutterAnimatorKey"),
+ clutter_animator_key_copy,
+ clutter_animator_key_free);
+
+ return our_type;
+}
+
+
+/**
+ * clutter_animator_key_get_object:
+ * @animator_key: a #ClutterAnimatorKey
+ *
+ * Retrieves the object a key applies to.
+ *
+ * Return value: the object an animator_key exist for.
+ *
+ * Since: 1.2
+ */
+GObject *
+clutter_animator_key_get_object (ClutterAnimatorKey *animator_key)
+{
+ g_return_val_if_fail (animator_key, NULL);
+ return animator_key->object;
+}
+
+/**
+ * clutter_animator_key_get_property_name:
+ * @animator_key: a #ClutterAnimatorKey
+ *
+ * Retrieves the name of the property a key applies to.
+ *
+ * Return value: the name of the property an animator_key exist for.
+ *
+ * Since: 1.2
+ */
+G_CONST_RETURN gchar *
+clutter_animator_key_get_property_name (ClutterAnimatorKey *animator_key)
+{
+ g_return_val_if_fail (animator_key != NULL, NULL);
+
+ return animator_key->property_name;
+}
+
+/**
+ * clutter_animator_key_get_mode:
+ * @animator_key: a #ClutterAnimatorKey
+ *
+ * Retrieves the mode of a #ClutterAnimator key, for the first key of a
+ * property for an object this represents the whether the animation is
+ * open ended and or curved for the remainding keys for the property it
+ * represents the easing mode.
+ *
+ * Return value: the mode of a #ClutterAnimatorKey
+ *
+ * Since: 1.2
+ */
+gulong
+clutter_animator_key_get_mode (ClutterAnimatorKey *animator_key)
+{
+ g_return_val_if_fail (animator_key != NULL, 0);
+
+ return animator_key->mode;
+}
+
+/**
+ * clutter_animator_key_get_progress:
+ * @animator_key: a #ClutterAnimatorKey
+ *
+ * Retrieves the progress of an clutter_animator_key
+ *
+ * Return value: the progress defined for a #ClutterAnimator key.
+ *
+ * Since: 1.2
+ */
+gdouble
+clutter_animator_key_get_progress (ClutterAnimatorKey *animator_key)
+{
+ g_return_val_if_fail (animator_key != NULL, 0.0);
+
+ return animator_key->progress;
+}
+
+/**
+ * clutter_animator_key_get_value:
+ * @animator_key: a #ClutterAnimatorKey
+ * @value: a #GValue initialized with the correct type for the animator key
+ *
+ * Retrieves a copy of the value for a #ClutterAnimatorKey.
+ *
+ * The passed in GValue needs to be already initialized for the value type.
+ *
+ * Use g_value_unset() when done
+ *
+ * Since: 1.2
+ */
+void
+clutter_animator_key_get_value (ClutterAnimatorKey *animator_key,
+ GValue *value)
+{
+ g_return_if_fail (animator_key != NULL);
+
+ g_value_copy (&animator_key->value, value);
+}
diff --git a/clutter/clutter-animator.h b/clutter/clutter-animator.h
new file mode 100644
index 000000000..d530c8432
--- /dev/null
+++ b/clutter/clutter-animator.h
@@ -0,0 +1,166 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author:
+ * Øyvind Kolås
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __CLUTTER_ANIMATOR_H__
+#define __CLUTTER_ANIMATOR_H__
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_ANIMATOR (clutter_animator_get_type ())
+#define CLUTTER_TYPE_ANIMATOR_KEY (clutter_animator_key_get_type ())
+
+#define CLUTTER_ANIMATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_ANIMATOR, ClutterAnimator))
+#define CLUTTER_ANIMATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_ANIMATOR, ClutterAnimatorClass))
+#define CLUTTER_IS_ANIMATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_ANIMATOR))
+#define CLUTTER_IS_ANIMATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_ANIMATOR))
+#define CLUTTER_ANIMATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_ANIMATOR, ClutterAnimatorClass))
+
+/* ClutterAnimator is typedef in clutter-types.h */
+
+typedef struct _ClutterAnimatorClass ClutterAnimatorClass;
+typedef struct _ClutterAnimatorPrivate ClutterAnimatorPrivate;
+
+/**
+ * ClutterAnimatorKey:
+ *
+ * A key frame inside a #ClutterAnimator
+ *
+ * Since: 1.2
+ */
+typedef struct _ClutterAnimatorKey ClutterAnimatorKey;
+
+/**
+ * ClutterInterpolation:
+ * @CLUTTER_INTERPOLATION_LINEAR:
+ * @CLUTTER_INTERPOLATION_CUBIC:
+ *
+ * The mode of interpolation between key frames
+ *
+ * Since: 1.2
+ */
+typedef enum {
+ CLUTTER_INTERPOLATION_LINEAR,
+ CLUTTER_INTERPOLATION_CUBIC
+} ClutterInterpolation;
+
+/**
+ * ClutterAnimator:
+ *
+ * The #ClutterAnimator structure contains only private data and
+ * should be accessed using the provided API
+ *
+ * Since: 1.2
+ */
+struct _ClutterAnimator
+{
+ /*< private >*/
+ GObject parent_instance;
+
+ ClutterAnimatorPrivate *priv;
+};
+
+/**
+ * ClutterAnimatorClass:
+ *
+ * The #ClutterAnimatorClass structure contains only private data
+ *
+ * Since: 1.2
+ */
+struct _ClutterAnimatorClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /* padding for future expansion */
+ gpointer _padding_dummy[16];
+};
+
+GType clutter_animator_get_type (void) G_GNUC_CONST;
+
+ClutterAnimator * clutter_animator_new (void);
+ClutterAnimator * clutter_animator_set_key (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ guint mode,
+ gdouble progress,
+ const GValue *value);
+void clutter_animator_set (ClutterAnimator *animator,
+ gpointer first_object,
+ const gchar *first_property_name,
+ guint first_mode,
+ gdouble first_progress,
+ ...);
+GList * clutter_animator_get_keys (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ gdouble progress);
+
+void clutter_animator_remove_key (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ gdouble progress);
+
+ClutterTimeline * clutter_animator_run (ClutterAnimator *animator);
+ClutterTimeline * clutter_animator_get_timeline (ClutterAnimator *animator);
+void clutter_animator_set_timeline (ClutterAnimator *animator,
+ ClutterTimeline *timeline);
+guint clutter_animator_get_duration (ClutterAnimator *animator);
+void clutter_animator_set_duration (ClutterAnimator *animator,
+ guint duration);
+
+gboolean clutter_animator_property_get_ease_in (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name);
+void clutter_animator_property_set_ease_in (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ gboolean ease_in);
+
+ClutterInterpolation clutter_animator_property_get_interpolation (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ ClutterInterpolation interpolation);
+void clutter_animator_property_set_interpolation (ClutterAnimator *animator,
+ GObject *object,
+ const gchar *property_name,
+ ClutterInterpolation interpolation);
+
+GType clutter_animator_key_get_type (void) G_GNUC_CONST;
+GObject * clutter_animator_key_get_object (ClutterAnimatorKey *animator_key);
+G_CONST_RETURN gchar *clutter_animator_key_get_property_name (ClutterAnimatorKey *animator_key);
+gulong clutter_animator_key_get_mode (ClutterAnimatorKey *animator_key);
+gdouble clutter_animator_key_get_progress (ClutterAnimatorKey *animator_key);
+void clutter_animator_key_get_value (ClutterAnimatorKey *animator_key,
+ GValue *value);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_ANIMATOR_H__ */
diff --git a/clutter/clutter-types.h b/clutter/clutter-types.h
index b13a4dfed..84f364462 100644
--- a/clutter/clutter-types.h
+++ b/clutter/clutter-types.h
@@ -44,6 +44,7 @@ typedef struct _ClutterStage ClutterStage;
typedef struct _ClutterContainer ClutterContainer; /* dummy */
typedef struct _ClutterChildMeta ClutterChildMeta;
typedef struct _ClutterLayoutMeta ClutterLayoutMeta;
+typedef struct _ClutterAnimator ClutterAnimator;
/**
* ClutterGravity:
diff --git a/clutter/clutter.h b/clutter/clutter.h
index fac430ae2..fe392763d 100644
--- a/clutter/clutter.h
+++ b/clutter/clutter.h
@@ -34,6 +34,7 @@
#include "clutter-alpha.h"
#include "clutter-animatable.h"
#include "clutter-animation.h"
+#include "clutter-animator.h"
#include "clutter-backend.h"
#include "clutter-behaviour-depth.h"
#include "clutter-behaviour-ellipse.h"
diff --git a/doc/reference/clutter/clutter-docs.xml.in b/doc/reference/clutter/clutter-docs.xml.in
index 38fe85287..b2e8c7388 100644
--- a/doc/reference/clutter/clutter-docs.xml.in
+++ b/doc/reference/clutter/clutter-docs.xml.in
@@ -15,7 +15,7 @@
- 2009
+ 2009, 2010
Intel Corporation
@@ -123,6 +123,8 @@
+
+
diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt
index 6626369a4..0bf34ebf7 100644
--- a/doc/reference/clutter/clutter-sections.txt
+++ b/doc/reference/clutter/clutter-sections.txt
@@ -2012,3 +2012,51 @@ CLUTTER_BOX_LAYOUT_GET_CLASS
ClutterBoxLayoutPrivate
clutter_box_layout_get_type
+
+
+clutter-animator
+ClutterAnimator
+ClutterAnimator
+ClutterAnimatorClass
+clutter_animator_new
+clutter_animator_set
+clutter_animator_set_key
+clutter_animator_remove_key
+clutter_animator_get_keys
+clutter_animator_run
+
+
+clutter_animator_set_timeline
+clutter_animator_get_timeline
+clutter_animator_set_duration
+clutter_animator_get_duration
+
+
+clutter_animator_property_set_ease_in
+clutter_animator_property_get_ease_in
+ClutterInterpolation
+clutter_animator_property_set_interpolation
+clutter_animator_property_get_interpolation
+
+
+ClutterAnimatorKey
+clutter_animator_key_get_object
+clutter_animator_key_get_property_name
+clutter_animator_key_get_mode
+clutter_animator_key_get_progress
+clutter_animator_key_get_value
+
+
+CLUTTER_TYPE_ANIMATOR
+CLUTTER_TYPE_ANIMATOR_KEY
+CLUTTER_ANIMATOR
+CLUTTER_ANIMATOR_CLASS
+CLUTTER_IS_ANIMATOR
+CLUTTER_IS_ANIMATOR_CLASS
+CLUTTER_ANIMATOR_GET_CLASS
+
+
+clutter_animator_get_type
+clutter_animator_key_get_type
+ClutterAnimatorPrivate
+
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types
index b954e2ad6..21b6a809d 100644
--- a/doc/reference/clutter/clutter.types
+++ b/doc/reference/clutter/clutter.types
@@ -42,3 +42,4 @@ clutter_flow_layout_get_type
clutter_box_layout_get_type
clutter_input_device_get_type
clutter_device_manager_get_type
+clutter_animator_get_type
diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am
index 61580ab1e..41a88d3b7 100644
--- a/tests/interactive/Makefile.am
+++ b/tests/interactive/Makefile.am
@@ -19,6 +19,7 @@ UNIT_TESTS = \
test-grab.c \
test-fullscreen.c \
test-shader.c \
+ test-animator.c \
test-unproject.c \
test-viewport.c \
test-fbo.c \
diff --git a/tests/interactive/test-animator.c b/tests/interactive/test-animator.c
new file mode 100644
index 000000000..a1c130bd7
--- /dev/null
+++ b/tests/interactive/test-animator.c
@@ -0,0 +1,134 @@
+#include
+#include
+#include
+#include
+
+static ClutterAnimator *animator;
+
+static ClutterActor *new_rect (gint r,
+ gint g,
+ gint b,
+ gint a)
+{
+ GError *error = NULL;
+ ClutterColor *color = clutter_color_new (r, g, b, a);
+ ClutterActor *rectangle = clutter_rectangle_new_with_color (color);
+
+ gchar *file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL);
+ rectangle = clutter_texture_new_from_file (file, &error);
+ if (rectangle == NULL)
+ g_error ("image load failed: %s", error->message);
+ g_free (file);
+
+ clutter_actor_set_size (rectangle, 128, 128);
+ clutter_color_free (color);
+ return rectangle;
+}
+
+static gboolean nuke_one (gpointer actor)
+{
+ clutter_actor_destroy (actor);
+ return FALSE;
+}
+
+#define COUNT 4
+
+static void reverse_timeline (ClutterTimeline *timeline,
+ gpointer data)
+{
+ ClutterTimelineDirection direction = clutter_timeline_get_direction (timeline);
+ if (direction == CLUTTER_TIMELINE_FORWARD)
+ clutter_timeline_set_direction (timeline, CLUTTER_TIMELINE_BACKWARD);
+ else
+ clutter_timeline_set_direction (timeline, CLUTTER_TIMELINE_FORWARD);
+ clutter_timeline_start (timeline);
+}
+
+
+G_MODULE_EXPORT gint
+test_animator_main (gint argc,
+ gchar **argv)
+{
+ ClutterActor *stage;
+ ClutterActor *rects[COUNT];
+ gint i;
+ clutter_init (&argc, &argv);
+
+ stage = clutter_stage_get_default ();
+
+ for (i=0; i