From: Øyvind Kolås Date: Fri, 5 Feb 2010 12:32:00 +0000 (+0000) Subject: Add ClutterAnimator X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4cc269a4687f6ca4fcd88ada134cfa00e2b13a1a;p=profile%2Fivi%2Fclutter.git Add ClutterAnimator ClutterAnimator is a class for managing the animation of multiple properties of multiple actors over time with keyframing of values. The Animator class is meant to be used to effectively describe animations using the ClutterScript definition format, and to construct complex implicit animations from the ground up. Signed-off-by: Emmanuele Bassi --- diff --git a/.gitignore b/.gitignore index 6320d8d..a1d1649 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 09fa069..38f2474 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 0000000..f9fd309 --- /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 0000000..d530c84 --- /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 b13a4df..84f3644 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 fac430a..fe39276 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 38fe852..b2e8c73 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 6626369..0bf34eb 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 b954e2a..21b6a80 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 61580ab..41a88d3 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 0000000..a1c130b --- /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