Merge remote branch 'elliot/cookbook-animations-looping'

* elliot/cookbook-animations-looping:
  cookbook: Recipe for "looping animations"
  cookbook: Clarify how signals are emitted during looped animation
  cookbook: First draft for looping animations recipe
  cookbook: Recipe skeleton for "looping animations"
  cookbook: Looping animation examples
This commit is contained in:
Emmanuele Bassi 2010-11-15 15:37:42 +00:00
commit 9f2c31547c
7 changed files with 645 additions and 0 deletions

View File

@ -64,6 +64,7 @@ VIDEO_FILES = \
videos/animations-reuse.ogv \
videos/animations-moving-anchors.ogv \
videos/animations-moving-depth.ogv \
videos/animations-looping.ogv \
$(NULL)
EXTRA_DIST = \

View File

@ -2305,4 +2305,402 @@ clutter_animator_start (animator);
</section>
<section id="animations-looping">
<title>Looping an animation</title>
<section>
<title>Problem</title>
<para>You want to loop an animation so it plays multiple times.</para>
</section>
<section>
<title>Solutions</title>
<para>Each <link linkend="animations-introduction-api">animation
approach</link> can be used to create a looping animation, as
described in the following sections.</para>
<para>The animation implemented in each case is a simple repeated
movement of a rectangle from the right (<code>x = 150.0</code>)
to the left (<code>x = 50.0</code>) of the stage, and back again,
looped; like this (just a few iterations):</para>
<inlinemediaobject>
<videoobject>
<videodata fileref="videos/animations-looping.ogv"/>
</videoobject>
<alt>
<para>Video showing simple looped movement of an actor</para>
</alt>
</inlinemediaobject>
<section id="animations-looping-solutions-implicit">
<title>Solution 1: looping an implicit animation</title>
<para>Implicit animations, started using
<function>clutter_actor_animate()</function>, can be looped via
their associated <type>ClutterTimeline</type>.</para>
<para>Create a <type>ClutterTimeline</type> which is
set to loop:</para>
<informalexample>
<programlisting>
ClutterTimeline *timeline = clutter_timeline_new (1000);
clutter_timeline_set_loop (timeline, TRUE);
</programlisting>
</informalexample>
<para>Use this timeline when starting an implicit animation on an
actor; in this case, to animate the actor's <varname>x</varname>
coordinate from its initial value to <code>50.0</code>:</para>
<informalexample>
<programlisting>
/* assume <varname>actor</varname> is a <type>ClutterActor</type> instance */
/* actor's initial x value is 150.0 */
clutter_actor_set_x (actor, 150.0);
/* animate the actor (starting the timeline is implicit) */
clutter_actor_animate_with_timeline (actor,
CLUTTER_LINEAR,
timeline,
"x", 50.0,
NULL);
</programlisting>
</informalexample>
<para>One further technique is to swop the timeline's
direction to create a "closed loop" animation (one which returns
to its origin at the end of each iteration). See
<link linkend="animations-looping-discussion-closed-loop">this
section</link> for details.</para>
<para><link linkend="animations-looping-example-1">The full
code example</link> shows how to run an implicit animation on
a loop.</para>
</section>
<section>
<title>Solution 2: looping with <type>ClutterAnimator</type></title>
<para>A <type>ClutterAnimator</type> animation can also be looped
via its <type>ClutterTimeline</type>. However, as
<type>ClutterAnimator</type> enables more complex animations,
you don't have to manually invert the timeline at the
end of each iteration. Instead, you can animate
an actor's properties back to their initial values
at the end of each iteration of the loop.</para>
<para>Creating the timeline and setting it to loop is the same
as for implicit animations:</para>
<informalexample>
<programlisting>
ClutterTimeline *timeline = clutter_timeline_new (2000);
clutter_timeline_set_loop (timeline, TRUE);
</programlisting>
</informalexample>
<para>Note that the timeline is twice the length of the one for
the implicit animation: this is because, unlike the implicit
animation, the movement from right to left and back again
is a <emphasis>single</emphasis> animation. By contrast, in the
implicit animation, the timeline runs forward, for the right to
left movement; and then backwards, for the left to right
movement. So rather than a 1000ms timeline running twice (once
forward, once backward for the implicit animation),
we have a 2000ms timeline running once (for
<type>ClutterAnimator</type>).</para>
<para>Next, create a <type>ClutterAnimator</type> which animates
the actor from right to left, then left to right:</para>
<informalexample>
<programlisting>
/* assume <varname>actor</varname> is a <type>ClutterActor</type> instance */
ClutterAnimator *animator = clutter_animator_new ();
/* use the looping timeline as the timeline for the animator */
clutter_animator_set_timeline (animator, timeline);
/* set positions for the actor at various points through the animation:
* at progress 0.0, x = 150.0 (right of the stage)
* at progress 0.5, x = 50.0 (left of the stage)
* at progress 1.0, x = 150.0 again (back to the right)
*/
clutter_animator_set (animator,
actor, "x", CLUTTER_LINEAR, 0.0, 150.0,
actor, "x", CLUTTER_LINEAR, 0.5, 50.0,
actor, "x", CLUTTER_LINEAR, 1.0, 150.0,
NULL);
</programlisting>
</informalexample>
<para>Finally, start the animation:</para>
<informalexample>
<programlisting>
clutter_animator_start (animator);
</programlisting>
</informalexample>
<para>See <link linkend="animations-looping-example-2">the full
example</link> for more details.</para>
</section>
<section>
<title>Solution 3: looping with <type>ClutterState</type></title>
<para>You can loop <type>ClutterState</type> animations by
creating a cycle of states which
<ulink url="http://en.wikipedia.org/wiki/Ouroboros">"swallows
its own tail"</ulink>: i.e. goes from a start state, through
intermediate state(s), back to the start state, then again
through the intermediate states(s), back to the start state,
etc., ad infinitum.</para>
<para>For the animation we're implementing, there are two states
the actor transitions between:</para>
<orderedlist>
<listitem>
<para>The actor's <varname>x</varname> value
is <code>150.0</code> (the start/end state, on the right
of the stage).</para>
</listitem>
<listitem>
<para>The actor's <varname>x</varname> value is
<code>50.0</code> (the intermediate state, on the left
of the stage).</para>
</listitem>
</orderedlist>
<para>Here is how to add those states to a
<type>ClutterState</type> instance:</para>
<informalexample>
<programlisting>
ClutterState *transitions = clutter_state_new ();
/* the duration for a transition from any state to any other is 1 second */
clutter_state_set_duration (transitions, NULL, NULL, 1000);
clutter_state_set (transitions, NULL, "right",
actor, "x", CLUTTER_LINEAR, 150.0,
NULL);
clutter_state_set (transitions, NULL, "left",
actor, "x", CLUTTER_LINEAR, 50.0,
NULL);
</programlisting>
</informalexample>
<para>You also need a handler to move the <type>ClutterState</type>
to its next state, called each time a state transition
is completed:</para>
<informalexample>
<programlisting>
/* handler to move the <type>ClutterState</type> to its next state */
static void
next_state (ClutterState *transitions,
gpointer user_data)
{
const gchar *state = clutter_state_get_state (transitions);
if (g_strcmp0 (state, "right") == 0)
clutter_state_set_state (transitions, "left");
else
clutter_state_set_state (transitions, "right");
}
</programlisting>
</informalexample>
<para>Then connect the <type>ClutterState's</type>
<code>completed</code> signal to the handler, so that each time
a state is reached, the transition to the next state begins:</para>
<informalexample>
<programlisting>
/* connect the <type>ClutterState</type> <code>completed</code> signal to the handler */
g_signal_connect (transitions,
"completed",
G_CALLBACK (next_state),
NULL);
</programlisting>
</informalexample>
<para>Finally, put the <type>ClutterState</type> into the start
state to begin the animation:</para>
<informalexample>
<programlisting>
clutter_state_warp_to_state (transitions, "right");
</programlisting>
</informalexample>
<para>See <link linkend="animations-looping-example-3">the full
example</link> for more details.</para>
</section>
</section>
<section>
<title>Discussion</title>
<para>We use two different approaches to looping in the solutions:</para>
<orderedlist>
<listitem>
<para>Setting the <type>ClutterTimeline</type> to loop
(via <function>clutter_timeline_set_loop()</function>). This
is the best approach where the timeline is explicit (for
<type>ClutterAnimator</type> and implicit animations).</para>
</listitem>
<listitem>
<para>Cycling through states in a <type>ClutterState</type>. In
this case, the timeline is implicit and we don't need to
manually control it: the loop is a consequence of cycling
repeatedly through a series of states.</para>
</listitem>
</orderedlist>
<para>The following sections cover some other aspects of looping
animations.</para>
<section>
<title>Looping a fixed number of times</title>
<para><type>ClutterTimeline</type> doesn't have any built-in
functionality to support looping a certain number of times. But
it is reasonably easy to count the number of iterations completed and
stop the animation when some limit is reached.</para>
<para>For example, you could use a static counter to keep track
of the iteration count:</para>
<informalexample>
<programlisting>
static guint counter = 0;
</programlisting>
</informalexample>
<para>Implement the looping behaviour as in the above solutions,
but use a callback function to set/reset the counter each time
the timeline completes. For example, for the
<type>ClutterAnimator</type> solution, you would connect the
<code>completed</code> signal of the timeline
to a callback function:</para>
<informalexample>
<programlisting>
g_signal_connect (timeline,
"completed",
G_CALLBACK (timeline_completed_cb),
NULL);
</programlisting>
</informalexample>
<para>And implement a callback function which resets the counter and
stops the timeline if more than two iterations have been counted:</para>
<informalexample>
<programlisting>
static void
timeline_completed_cb (ClutterTimeline *timeline,
gpointer user_data)
{
counter++;
if (counter &gt; 2)
{
counter = 0;
clutter_timeline_stop (timeline);
}
}
</programlisting>
</informalexample>
<para>Note that it's simple to count iterations and
control the timeline using <type>ClutterAnimator</type> or
<type>ClutterState</type>, as the whole animation (right to left
and back) is a discrete unit. Doing the same with implicit
animations is possible (one forward + one backward run along the
timeline is one iteration). But you will be really stretching the
implicit animation API beyond its intended use cases.</para>
</section>
<section id="animations-looping-discussion-closed-loop">
<title>Creating a "closed loop" with an implicit animation</title>
<para>When using implicit animations, at the end of the timeline
(before the next iteration of the loop), an actor's properties
"jump" back to their initial values (as they were when the timeline
started). For example, in the
<link linkend="animations-looping-solutions-implicit">earlier
solution</link>, the actor's initial <varname>x</varname> value was
<code>150.0</code>; so the default behaviour on each iteration
of the loop would be to animate the actor to <code>x = 50.0</code>
then jump it immediately back to <code>x = 150.0</code>, before
continuing the loop.</para>
<para>To prevent this happening, you can create a "closed" loop:
animate the actor's properties away from their initial values, then
back again.</para>
<para>This could be done manually, by creating two separate
animations, one the inverse of the other, and chaining them together.</para>
<para>However, a simpler solution is to run forward through the timeline
once; then invert its direction when the end of timeline is reached.
The animation continues, but in reverse. Once the backward iteration
completes, set the timeline to run forward again. Keep changing the
timeline's direction each time it completes. This
is the approach used in <link linkend="animations-looping-example-1">the
example</link>, which results in a smooth, repeated right to left,
left to right motion.</para>
<para>See <link linkend="animations-inversion">this
recipe</link> for more details about inverting a timeline.</para>
</section>
</section>
<section id="animations-looping-examples">
<title>Full examples</title>
<example id="animations-looping-example-1">
<title>Looping an implicit animation</title>
<programlisting>
<xi:include href="examples/animations-looping-implicit.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="animations-looping-example-2">
<title>Looping with <type>ClutterAnimator</type></title>
<programlisting>
<xi:include href="examples/animations-looping-animator.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="animations-looping-example-3">
<title>Looping with <type>ClutterState</type></title>
<programlisting>
<xi:include href="examples/animations-looping-state.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
</chapter>

View File

@ -4,6 +4,9 @@ NULL =
noinst_PROGRAMS = \
animations-complex \
animations-looping-animator \
animations-looping-implicit \
animations-looping-state \
animations-moving-animator \
animations-moving-implicit \
animations-moving-state \
@ -55,6 +58,9 @@ AM_CFLAGS = \
AM_LDFLAGS = $(CLUTTER_LIBS) -export-dynamic
animations_complex_SOURCES = animations-complex.c
animations_looping_animator_SOURCES = animations-looping-animator.c
animations_looping_implicit_SOURCES = animations-looping-implicit.c
animations_looping_state_SOURCES = animations-looping-state.c
animations_moving_animator_SOURCES = animations-moving-animator.c
animations_moving_implicit_SOURCES = animations-moving-implicit.c
animations_moving_state_SOURCES = animations-moving-state.c

View File

@ -0,0 +1,66 @@
#include <stdlib.h>
#include <clutter/clutter.h>
static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor red_color = { 0xff, 0x00, 0x00, 0xff };
static gboolean
key_pressed_cb (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
ClutterTimeline *timeline = CLUTTER_TIMELINE (user_data);
if (!clutter_timeline_is_playing (timeline))
clutter_timeline_start (timeline);
return TRUE;
}
int
main (int argc,
char *argv[])
{
ClutterActor *stage;
ClutterActor *actor;
ClutterTimeline *timeline;
ClutterAnimator *animator;
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
clutter_actor_set_size (stage, 300, 200);
clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
actor = clutter_rectangle_new_with_color (&red_color);
clutter_actor_set_size (actor, 100, 100);
clutter_actor_set_position (actor, 150, 50);
timeline = clutter_timeline_new (2000);
clutter_timeline_set_loop (timeline, TRUE);
animator = clutter_animator_new ();
clutter_animator_set_timeline (animator, timeline);
clutter_animator_set (animator,
actor, "x", CLUTTER_LINEAR, 0.0, 150.0,
actor, "x", CLUTTER_LINEAR, 0.5, 50.0,
actor, "x", CLUTTER_LINEAR, 1.0, 150.0,
NULL);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor);
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (key_pressed_cb),
timeline);
clutter_actor_show (stage);
clutter_main ();
g_object_unref (animator);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,91 @@
#include <stdlib.h>
#include <clutter/clutter.h>
static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor red_color = { 0xff, 0x00, 0x00, 0xff };
typedef struct
{
ClutterActor *actor;
ClutterTimeline *timeline;
} State;
static void
invert_timeline_cb (ClutterTimeline *timeline,
gpointer user_data)
{
ClutterTimelineDirection direction = clutter_timeline_get_direction (timeline);
if (direction == CLUTTER_TIMELINE_FORWARD)
direction = CLUTTER_TIMELINE_BACKWARD;
else
direction = CLUTTER_TIMELINE_FORWARD;
clutter_timeline_set_direction (timeline, direction);
}
static gboolean
key_pressed_cb (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
State *state = (State *) user_data;
/* only start animating if actor isn't animating already */
if (clutter_actor_get_animation (state->actor) == NULL)
clutter_actor_animate_with_timeline (state->actor,
CLUTTER_LINEAR,
state->timeline,
"x", 50.0,
NULL);
return TRUE;
}
int
main (int argc,
char *argv[])
{
State *state = g_new0 (State, 1);
ClutterActor *stage;
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
clutter_actor_set_size (stage, 300, 200);
clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
state->actor = clutter_rectangle_new_with_color (&red_color);
clutter_actor_set_size (state->actor, 100, 100);
clutter_actor_set_position (state->actor, 150, 50);
state->timeline = clutter_timeline_new (1000);
clutter_timeline_set_loop (state->timeline, TRUE);
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (key_pressed_cb),
state);
/* the animation will not emit a "completed" signal,
* as it is set to loop; but the timeline emits "completed"
* at the end of each iteration of the loop
*/
g_signal_connect (state->timeline,
"completed",
G_CALLBACK (invert_timeline_cb),
NULL);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), state->actor);
clutter_actor_show (stage);
clutter_main ();
g_object_unref (state->timeline);
g_free (state);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,83 @@
#include <stdlib.h>
#include <clutter/clutter.h>
static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor red_color = { 0xff, 0x00, 0x00, 0xff };
static void
next_state (ClutterState *transitions,
gpointer user_data)
{
const gchar *state = clutter_state_get_state (transitions);
if (g_strcmp0 (state, "right") == 0)
clutter_state_set_state (transitions, "left");
else
clutter_state_set_state (transitions, "right");
}
static gboolean
key_pressed_cb (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data)
{
ClutterState *transitions = CLUTTER_STATE (user_data);
if (!clutter_timeline_is_playing (clutter_state_get_timeline (transitions)))
next_state (transitions, NULL);
return TRUE;
}
int
main (int argc,
char *argv[])
{
ClutterActor *stage;
ClutterActor *actor;
ClutterState *transitions;
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
clutter_actor_set_size (stage, 300, 200);
clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
actor = clutter_rectangle_new_with_color (&red_color);
clutter_actor_set_position (actor, 150, 50);
clutter_actor_set_size (actor, 100, 100);
transitions = clutter_state_new ();
clutter_state_set_duration (transitions, NULL, NULL, 1000);
clutter_state_set (transitions, NULL, "right",
actor, "x", CLUTTER_LINEAR, 150.0,
NULL);
clutter_state_set (transitions, NULL, "left",
actor, "x", CLUTTER_LINEAR, 50.0,
NULL);
clutter_state_warp_to_state (transitions, "right");
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (key_pressed_cb),
transitions);
g_signal_connect (transitions,
"completed",
G_CALLBACK (next_state),
NULL);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor);
clutter_actor_show (stage);
clutter_main ();
g_object_unref (transitions);
return EXIT_SUCCESS;
}

Binary file not shown.