Matthew
Allum
mallum@openedhand.com
Emmanuele
Bassi
ebassi@linux.intel.com
Creating Animations with Clutter
With Clutter using hardware accelration for graphics rendering,
complex and fast animations are possible. This chapter describes basic
techniques and the utilities Clutter provides in aiding animation
creation.
Basic Animations
The most basic way to create animations with Clutter is via the use
of clutter_threads_add_timeout(). This enables a callback function to be
called at a defined interval. The callback function can then modify actors
visual properties as to produce an animation.
Simple timeout example
Implement a rotating actor using 360 "frames"
struct RotationClosure {
ClutterActor *actor;
gdouble final_angle;
gdouble current_angle;
};
static gboolean
rotate_actor (gpointer data)
{
struct RotationClosure *clos = data;
clutter_actor_set_rotation (clos->actor, clos->current_angle, 0, 0, 0);
/* add one degree */
clos->current_angle += 1.0
/* if we reached the target angle, stop */
if (clos->current_angle == clos->final_angle)
return G_SOURCE_REMOVE;
return G_SOURCE_CONTINUE;
}
static void
rotate_actor_cleanup (gpointer data)
{
struct RotationClosure *clos = data;
g_object_unref (clos->actor);
g_free (clos);
}
...
struct RotationClosure *clos = NULL;
clos = g_new (struct RotationClosure, 1);
clos->actor = g_object_ref (an_actor);
clos->final_angle = 360.0;
clos->current_angle = 0;
clutter_threads_add_timeout_full (G_PRIORITY_DEFAULT,
1000 / 360, /* 360 updates in one second */
rotate_actor,
clos,
rotate_actor_cleanup);
Priorities
%G_PRIORITY_DEFAULT should always be used as the timeouts priority
(in case of clutter_threads_add_timeout_full()) as not to intefere with
Clutter's scheduling of repaints and input event handling.
Timelines
Using clutter_threads_add_timeout() to control an animation is
complicated and does not work in concert with the rest of the operations
Clutter must perform for each redraw cycle.
For this reason, Clutter provides #ClutterTimeline, a class that
allows scheduling animations with a definite duration. Timelines are
advanced during the redraw cycle so that animations are ready to be
performed at the right time. This also means that animations will not
affect the event processing; it also means that if the animation is too
complex it will be called with a longer delay, thus not blocking the
whole UI.
A Timeline is created with:
ClutterTimeline *timeline = clutter_timeline_new (duration_in_milliseconds);
The duration of the timeline then be modifed via the
#ClutterTimeline:duration property or by using
clutter_timeline_set_duration().
A timeline is started via clutter_timeline_start() and its
playback further manipulated by the clutter_timeline_pause(),
clutter_timeline_stop(), clutter_timeline_rewind() and
clutter_timeline_skip() functions.
By attaching a handler to the timeline's
#ClutterTimeline::new-frame signal a timeline can then be used to
drive an animation by altering an actor's visual properties. The
callback looks like:
void
on_new_frame (ClutterTimeline *timeline,
gint elapsed_msecs,
gpointer user_data)
{
}
The elapsed_msecs parameter is set to the amount
of time elapsed since the beginning of the timeline, and its value is
always between 0 and the #ClutterTimeline:duration value.
The function clutter_timeline_get_progress() can also be used to
get a normalised value of the timeline's current position between 0 and
1.
Timelines can also be played in reverse by setting the direction
using clutter_timeline_set_direction(), and can also have a one-time
delay set before they begin playing by using the function
clutter_timeline_set_delay().
Timelines can also control a pyshical simulation; the
clutter_timeline_get_delta() function allows retrieving the number of
milliseconds elapsed since the previous callback to ensure the physics
engine to be able to take the actual time elapsed between iterations
into account.
Using a Timeline to drive an animation
Rewrite the example above with a #ClutterTimeline instead of
using clutter_threads_add_timeout()
#include <clutter/clutter.h>
static void
on_new_frame (ClutterTimeline *timeline,
gint elapsed_msecs,
ClutterActor *actor)
{
gdouble angle = 360 * clutter_timeline_get_progress (timeline);
clutter_actor_set_rotation (actor, CLUTTER_Z_AXIS,
angle,
clutter_actor_get_width (actor) / 2,
clutter_actor_get_height (actor) / 2,
0);
}
...
ClutterTimeline *timeline;
/* one second */
timeline = clutter_timeline_new (1000);
/* looping indefinitely */
clutter_timeline_set_repeat_count (timeline, -1);
/* get a notification each time a new frame should be drawn */
g_signal_connect (timeline, "new-frame", G_CALLBACK (on_new_frame), actor);
/* start the timeline once control returns to the main loop */
clutter_timeline_start (timeline);
Implicit Animations
Using behaviours for simple animations of a single actor may
be too complicated, in terms of memory management and bookkeeping
of the object instances. For this reason, Clutter also provides a
simple animation API for implicit animations using properties of
an actor: clutter_actor_animate().
The clutter_actor_animate() family of functions will create
and use an implicit #ClutterAnimation instance, which will then
handle the animation of one or more #ClutterActor properties between
a range of values.
Using clutter_actor_animate()
The following example demonstrates how to use the
clutter_actor_animate() method to tween an actor between the current
position and a new set of coordinates. The animation takes 200
milliseconds to complete and uses a linear progression.
clutter_actor_animate (actor, CLUTTER_LINEAR, 200,
"x", 200.0f,
"y", 200.0f,
NULL);
The clutter_actor_animate() method returns a #ClutterAnimation
instance that can be used to start, stop and modify the animation
while it's running. The #ClutterAnimation::completed signal will
be emitted when the animation has been completed.
When the animation is complete it will be automatically
unreferenced, and disposed if nothing else is holding a reference
on it.
Animating inside an event handler
The following example demonstrates how to animate an actor
inside the signal handler for a button press event. If the user
presses the button on a new position while the animation is running,
the animation will be restarted with the new final values
updated.
static gboolean
on_button_press (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
gfloat event_x, event_y;
clutter_event_get_coords (event, &event_x, &event_y);
clutter_actor_animate (actor, CLUTTER_EASE_SINE_OUT, 500,
"x", event_x,
"y", event_y,
NULL);
return TRUE;
}
Calling clutter_actor_animate() multiple times on an
actor which is being animated will cause the animation to be updated
with the new values.
If you need to chain up multiple animations created using
clutter_actor_animate() you should connect to the
#ClutterAnimation::completed signal using g_signal_connect_after()
to have the guarantee that the current #ClutterAnimation has been
detached from the actor. The documentation for clutter_actor_animate()
has further examples.
Conclusion
Clutter provides a number of utility classes to aid animations
and complex animations can be produced by combining the various features
provided.
Of course animations can becreated outside of the Clutter animation
framework, as the framework is not expected to cover every kind of
possible animation scenario.
The animation functionality in Clutter is primarily suited to
building animations with a set or finite running time - i.e transitions
between states. For animations involving variable input (such as
touchscreen handling) physical simulations may be more suited.