mirror of
https://github.com/brl/mutter.git
synced 2024-11-23 08:30:42 -05:00
da22150498
Text referred to three animation methods, but only provides examples for two of them; and in future there may be more/fewer than 3. So I reworded it.
509 lines
18 KiB
XML
509 lines
18 KiB
XML
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
|
|
<chapter id="animations">
|
|
<title>Animations</title>
|
|
|
|
<epigraph>
|
|
<attribution>Walt Disney</attribution>
|
|
<para>Animation can explain whatever the mind of man can conceive.</para>
|
|
</epigraph>
|
|
|
|
<section id="animations-introduction">
|
|
<title>Introduction</title>
|
|
|
|
<para>Clutter actors have a variety of <emphasis>properties</emphasis>
|
|
(position, size, rotation in 3D space, scale, opacity) which govern
|
|
their visual appearance in the UI. They may also have
|
|
<emphasis>constraints</emphasis> on how they are aligned
|
|
and/or positioned relative to each other.</para>
|
|
|
|
<para>The Clutter animation API provides a means of changing
|
|
properties and constraints as a function of time: moving, scaling,
|
|
rotating, changing opacity and colour, modifying postional
|
|
constraints, etc.</para>
|
|
|
|
<note><para>Clutter also makes it possible to animate non-visual
|
|
properties if desired.</para></note>
|
|
|
|
<section>
|
|
<title>High level overview</title>
|
|
|
|
<para>Here are the main concepts behind animation in Clutter:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>An <emphasis>animation</emphasis> changes one or more
|
|
properties of one or more actors over time: their rotation in
|
|
a particular dimension (<varname>x</varname>, <varname>y</varname>,
|
|
<varname>z</varname>), scale, size, opacity etc.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>An animation has an associated <emphasis>timeline</emphasis>.
|
|
Think of this as analogous to the "thing" you're controlling when
|
|
you watch a video on the internet: it's what you control with
|
|
the play/pause button and what is measured by the bar
|
|
showing how far through the video you are. As with the
|
|
controls on a video player, you can play/pause/skip a Clutter
|
|
timeline; you can also rewind it, loop it, and play it
|
|
backwards.</para>
|
|
<note>
|
|
<para>If a timeline is reversed, the progress along the
|
|
timeline is still measured the same way as it is in the forward
|
|
direction: so if you start from the end of the timeline and run
|
|
it backwards for 75% of its length, the progress is reported
|
|
as 0.25 (i.e. 25% of the way from the start of the
|
|
timeline).</para>
|
|
</note>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The <emphasis>duration</emphasis> of a timeline
|
|
(e.g. 500 milliseconds, 1 second, 10 seconds) specifies how
|
|
long its animation will last. The timeline can be inspected
|
|
to find out how much of it has elapsed, either as a value in
|
|
milliseconds or as a fraction (between 0 and 1) of the total
|
|
length of the timeline.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>An animation is divided into <emphasis>frames</emphasis>.
|
|
The number of frames which make up the animation isn't
|
|
constant: it depends on various factors, like how powerful
|
|
your machine is, the state of the drivers for your hardware,
|
|
and the load on he system. So you won't always get the same
|
|
number of frames in an animation of a particular duration.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The change to a property in an animation occurs over
|
|
the course of the timeline: the start value of the property
|
|
heads toward some target value. When it reaches the end of
|
|
the timeline, the property should have reached the target
|
|
value.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Exactly how the property changes over the course of the
|
|
timeline is governed by an <emphasis>alpha</emphasis>. This
|
|
is the trickiest idea to explain, so it has its own section
|
|
below.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
|
|
<section id="animations-introduction-alphas">
|
|
<title>Alphas</title>
|
|
|
|
<para>An alpha is generated for each frame of the animation.
|
|
The alpha varies between -1.0 and 2.0, and changes during the
|
|
course of the animation's timeline; ideally, the value should
|
|
start at 0.0 and reach 1.0 by the end of the timeline.</para>
|
|
|
|
<para>The alpha for any given frame of the animation is determined
|
|
by an <emphasis>alpha function</emphasis>. Usually, the alpha
|
|
function will return a value based on progress along the timeline.
|
|
However, the alpha function doesn't have to respect or pay
|
|
attention to the timeline: it can be entirely random if desired.</para>
|
|
|
|
<para>To work out the value of a property at a given frame
|
|
somewhere along the timeline for a given alpha:</para>
|
|
|
|
<orderedlist>
|
|
<listitem>
|
|
<para>Determine the difference between the start value and
|
|
the target end value for the property.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Multiply the difference by the alpha for the current
|
|
frame.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>Add the result to the start value.</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>The shape of the plot of the alpha function over time is
|
|
called its <emphasis>easing mode</emphasis>. Clutter provides
|
|
various modes ranging from <constant>CLUTTER_LINEAR</constant>
|
|
(the alpha value is equal to progress along the timeline),
|
|
to modes based on various polynomial and exponential functions,
|
|
to modes providing elastic and bounce shapes. See the
|
|
ClutterAlpha documentation for examples of the shapes produced
|
|
by these functions. There is also a good interactive demo
|
|
of the modes on
|
|
<ulink url="http://www.robertpenner.com/easing/easing_demo.html">Robert Penner's site</ulink>.
|
|
</para>
|
|
|
|
<para>Most of the time, you can use the built-in Clutter easing
|
|
modes to get the kind of animation effect you want. However,
|
|
in some cases you may want to provide your own alpha function.
|
|
Here's an example (based on the quintic ease in mode from
|
|
<filename>clutter-alpha.c</filename>):</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static gdouble
|
|
_alpha_ease_in_sextic (ClutterAlpha *alpha,
|
|
gpointer dummy G_GNUC_UNUSED)
|
|
{
|
|
ClutterTimeline *timeline = clutter_alpha_get_timeline (alpha);
|
|
gdouble p = clutter_timeline_get_progress (timeline);
|
|
|
|
return p * p * p * p * p * p;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>An alpha function just has to have a specified method
|
|
signature and return a <type>gdouble</type> value when called.
|
|
As stated above, you'd typically base the return value on the
|
|
timeline progress; the function above shows how you get the
|
|
timeline associated with the alpha, so you can apply the alpha
|
|
function to it.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Clutter's animation API</title>
|
|
|
|
<para>All of the animation approaches in Clutter use the same
|
|
basic underpinnings (as explained above), but the API provides
|
|
varying levels of abstraction and/or ease of use on top of those
|
|
underpinnings.</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><emphasis>Implicit animations</emphasis> (created using
|
|
<function>clutter_actor_animate()</function> and related
|
|
functions) are useful where you want to apply
|
|
a simple or one-off animation to an actor. They enable you
|
|
to animate one or more properties using a single easing mode;
|
|
however, you only specify the target values for the properties
|
|
you're animating, not the start values.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><emphasis>ClutterAnimator</emphasis> provides support
|
|
for declarative animations (defined using <type>ClutterScript</type>).
|
|
You can animate multiple actors with this approach, and
|
|
have more control over the easing modes used during an
|
|
animation: while implicit animations only allow a single
|
|
easing mode for all properties, <type>ClutterAnimator</type>
|
|
supports <emphasis>multiple</emphasis> easing modes for
|
|
<emphasis>each</emphasis> property; <emphasis>key frames</emphasis>
|
|
are used to indicate where in the animation each easing mode
|
|
should be applied.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><emphasis>ClutterState</emphasis> enables you to describe
|
|
<emphasis>states</emphasis>: property values across one or
|
|
more actors, plus the easing modes used to transition to
|
|
those values. It can also be combined with <type>ClutterAnimator</type>
|
|
for finer grained definition of transitions if desired.</para>
|
|
<para>States are particularly useful if you need actors to
|
|
animate between a known set of positions/sizes/opacities etc.
|
|
during their lifecycles (e.g. animating a list of items in
|
|
a menu, or for animations in a picture viewer where you
|
|
click on thumbnails to display a full view of a photograph).</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>The recipes in this section show when and where it is
|
|
appropriate to use each of these approaches.</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="animations-inversion">
|
|
<title>Inverting Animations</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want to have an animation exactly mirroring another one
|
|
that you just played.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>Reverse the direction of the <type>ClutterTimeline</type>
|
|
associated with the animation.</para>
|
|
|
|
<para>For example, here's how to invert an implicit
|
|
animation which moves an actor along the <varname>x</varname>
|
|
axis. The direction of the animation is inverted when the
|
|
movement along the <varname>x</varname> axis is completed; it is
|
|
also inverted if the mouse button is pressed on the actor.</para>
|
|
|
|
<para>First, set up the animation:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
ClutterAnimation *animation;
|
|
|
|
/*
|
|
* animate actor to x = 300.0;
|
|
* the implicit animation functions return a ClutterAnimation
|
|
* which we can use to invert the timeline
|
|
*/
|
|
animation = clutter_actor_animate (actor,
|
|
CLUTTER_EASE_IN_OUT_CUBIC,
|
|
2000,
|
|
"x", 300.0,
|
|
NULL);
|
|
|
|
/* callback for when the animation completes */
|
|
g_signal_connect (animation,
|
|
"completed",
|
|
G_CALLBACK (_animation_done_cb),
|
|
NULL);
|
|
|
|
/*
|
|
* callback for when the mouse button is pressed on the actor;
|
|
* note the animation is passed as user data, so we can
|
|
* get at the timeline
|
|
*/
|
|
g_signal_connect (actor,
|
|
"button-press-event",
|
|
G_CALLBACK (_on_click_cb),
|
|
animation);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Next, add a function for inverting the timeline:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static void
|
|
_invert_timeline (ClutterTimeline *timeline)
|
|
{
|
|
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);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Then add a function which calls <function>_invert_timeline</function>
|
|
when the animation completes. More importantly, the callback should
|
|
stop emission of the "completed" signal by the animation. This
|
|
prevents the <type>ClutterAnimation</type> underlying the implicit
|
|
animation from being unreferenced; which in turn allows it to be
|
|
inverted:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static void
|
|
_animation_done_cb (ClutterAnimation *animation,
|
|
gpointer user_data)
|
|
{
|
|
/* stop the completed signal before the ClutterAnimation is unreferenced */
|
|
g_signal_stop_emission_by_name (animation, "completed");
|
|
|
|
/* invert the timeline associated with the animation */
|
|
ClutterTimeline *timeline = clutter_animation_get_timeline (animation);
|
|
_invert_timeline (timeline);
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Finally, the click callback function uses the same
|
|
<function>_invert_timeline</function> function if the animation
|
|
is playing; but if the animation is stopped, it will
|
|
start it instead:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static void
|
|
_on_click_cb (ClutterActor *actor,
|
|
ClutterEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
ClutterAnimation *animation = (ClutterAnimation *)user_data;
|
|
|
|
ClutterTimeline *timeline = clutter_animation_get_timeline (animation);
|
|
|
|
if (clutter_timeline_is_playing (timeline))
|
|
{
|
|
_invert_timeline (timeline);
|
|
}
|
|
else
|
|
{
|
|
clutter_timeline_start (timeline);
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>If you are using <type>ClutterAnimator</type> rather than
|
|
implicit animations, <function>clutter_animator_get_timeline()</function>
|
|
enables you to get the underlying timeline; you could then use
|
|
the techniques shown above to invert it.</para>
|
|
|
|
<para><type>ClutterState</type> enables a different approach
|
|
to "inverting" an animation: rather than having a single animation
|
|
which you invert, you would define two or more
|
|
<emphasis>keys</emphasis> for an actor (or set of actors) and
|
|
transition between them.</para>
|
|
|
|
<para>For the example above, you would define two keys:
|
|
one for the actor's initial position; and a second for the actor
|
|
at <code>x = 300.0</code>. You would also define the
|
|
transition between them: 2000 milliseconds with a
|
|
<constant>CLUTTER_EASE_IN_OUT_CUBIC</constant> easing mode.</para>
|
|
|
|
<para>With the states defined, you would then use
|
|
<function>clutter_state_set_state()</function> inside callbacks to
|
|
animate the actor between the two <varname>x</varname> positions.
|
|
Behind the scenes, <type>ClutterState</type> would handle the
|
|
animations and timelines for you.</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<section id="animations-fading">
|
|
<title>Fading an actor out of or into view</title>
|
|
|
|
<section>
|
|
<title>Problem</title>
|
|
|
|
<para>You want to animate an actor so that it fades out of or into
|
|
view.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution</title>
|
|
|
|
<para>Animate the actor's opacity property.</para>
|
|
|
|
<para>You can do this using any of the approaches provided
|
|
by the animation API. Here's how to fade out an actor (until it's
|
|
completely transparent) using implicit animations:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
/* fade out actor over 4000 milliseconds */
|
|
clutter_actor_animate (actor,
|
|
CLUTTER_EASE_OUT_CUBIC,
|
|
4000,
|
|
"opacity", 0,
|
|
NULL);
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Here's an example of a rectangle fading out using this
|
|
animation:</para>
|
|
|
|
<inlinemediaobject>
|
|
<videoobject>
|
|
<videodata fileref="videos/animations-fading-out.ogv"/>
|
|
</videoobject>
|
|
<alt>
|
|
<para>Video showing an actor fading out using implicit
|
|
animations</para>
|
|
</alt>
|
|
</inlinemediaobject>
|
|
|
|
<para><constant>CLUTTER_EASE_OUT_CUBIC</constant> is one of the
|
|
Clutter easing modes; see
|
|
<link linkend="animations-introduction-alphas">the introduction</link>
|
|
for more details about what these are and how to choose one.</para>
|
|
|
|
<para>Here's an example of the transitions you could use to
|
|
fade an actor in and out using <type>ClutterState</type>:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
ClutterState *transitions = clutter_state_new ();
|
|
|
|
/* all transitions last for 2000 milliseconds */
|
|
clutter_state_set_duration (transitions, NULL, NULL, 2000);
|
|
|
|
/* transition from any state to "fade-out" state */
|
|
clutter_state_set (transitions,
|
|
NULL, /* from state (NULL means "any") */
|
|
"fade-out", /* to state */
|
|
actor, "opacity", CLUTTER_EASE_OUT_QUAD, 0,
|
|
NULL);
|
|
|
|
/* transition from any state to "fade-in" state */
|
|
clutter_state_set (transitions, NULL, "fade-in",
|
|
actor, "opacity", CLUTTER_EASE_OUT_QUAD, 255,
|
|
NULL);
|
|
|
|
/* put the actor into the "fade-out" state with no animation */
|
|
clutter_state_warp_to_state (transitions, "fade-out");
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>You would then trigger an animated state change as events
|
|
occur in the application (e.g. mouse button clicks):</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
clutter_state_set_state (transitions, "fade-in");
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>Here's an example of this animation fading in then out again:</para>
|
|
|
|
<inlinemediaobject>
|
|
<videoobject>
|
|
<videodata fileref="videos/animations-fading-in-then-out.ogv"/>
|
|
</videoobject>
|
|
<alt>
|
|
<para>Video showing an actor fading in then out using
|
|
<type>ClutterState</type></para>
|
|
</alt>
|
|
</inlinemediaobject>
|
|
|
|
<note>
|
|
<para><type>ClutterState</type> is most useful where you
|
|
need to animate an actor backwards and forwards between multiple
|
|
states (e.g. fade an actor in and out of view). Where you just
|
|
want to fade an actor in or out once,
|
|
<function>clutter_actor_animate()</function> is adequate.</para>
|
|
</note>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Discussion</title>
|
|
|
|
<para>Reducing an actor's transparency to zero does not make it
|
|
inactive: the actor will still be reactive even if it's not
|
|
visible (responding to key events, mouse clicks etc.).
|
|
To make it really "disappear", you could use
|
|
<function>clutter_actor_hide()</function> once you'd made the actor
|
|
fully transparent.</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</chapter>
|