Blob Blame History Raw
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>6. Reusing a complex animation on different actors</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot"><link rel="home" href="index.html" title="The Clutter Cookbook"><link rel="up" href="animations.html" title="Chapter 5. Animations"><link rel="prev" href="animations-complex.html" title="5. Creating complex animations with ClutterAnimator"><link rel="next" href="animations-moving.html" title="7. Moving actors"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">6. Reusing a complex animation on different actors</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="animations-complex.html">Prev</a> </td><th width="60%" align="center">Chapter 5. Animations</th><td width="20%" align="right"> <a accesskey="n" href="animations-moving.html">Next</a></td></tr></table><hr></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="animations-reuse"></a>6. Reusing a complex animation on different actors</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="animations-reuse-problem"></a>6.1. Problem</h3></div></div></div><p>You want to apply the same complex animation to several
      different actors.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="animations-reuse-solution"></a>6.2. Solution</h3></div></div></div><p>Instead of animating each actor separately, create a
      <span class="emphasis"><em>rig</em></span>: an empty container with an associated
      animation, which will be animated in lieu of
      animating the actor directly. Do this as follows:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>Initialise the stage and actors, including those
          to be animated.</p></li><li class="listitem"><p>Define a <span class="type">ClutterContainer</span> and a
          <span class="type">ClutterAnimator</span> animation to animate it.</p></li><li class="listitem"><p>When you need to animate an actor:</p><div class="orderedlist"><ol class="orderedlist" type="a"><li class="listitem"><p>Create an instance of the rig and its animator.</p></li><li class="listitem"><p>Reparent the actor to the rig.</p></li><li class="listitem"><p>Run the rig's animation.</p></li></ol></div></li></ol></div><p>For this solution, we're using
      <a class="ulink" href="http://json.org/" target="_top">JSON</a> to define the
      animation and the user interface elements. For more
      details about this approach, see
      <a class="link" href="script.html#script-introduction" title="1. Introduction">the chapter
      on <span class="type">ClutterScript</span></a>.</p><p>Here's an extract of the JSON definition for the stage and
      one of five rectangles placed at its left edge (the full definition
      is in <a class="link" href="animations-reuse.html#animations-reuse-example-1" title="Example 5.5. ClutterScript JSON defining several rectangles with signal handlers">the
      appendix</a>):</p><div class="informalexample"><pre class="programlisting">[
  {
    "type" : "ClutterStage",
    "id" : "stage",

    ... stage properties, signal handlers etc. ...

    "children" : [
      {
        "type" : "ClutterRectangle",
        "id" : "rect1",
        "color" : "white",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      },

      ... more children defined here ...
    ]
  }
]</pre></div><p>The key point to note is how a signal handler is defined
      for the <code class="code">button-press-event</code>, so that the
      <code class="function">foo_button_pressed_cb()</code> function will trigger
      the animation when a (mouse) button is pressed on each rectangle.</p><p>The second JSON definition includes the rig
      (an empty <span class="type">ClutterGroup</span>) and a
      <span class="type">ClutterAnimator</span> to animate it. The animation moves the
      container across the stage and scales it to twice its original
      size. (This is the <a class="link" href="animations-reuse.html#animations-reuse-example-2" title='Example 5.6. ClutterScript JSON describing a "rig" and a ClutterAnimator animation'>same
      code as in the appendix</a>):</p><div class="informalexample"><pre class="programlisting">[
  {
    "type" : "ClutterGroup",
    "id" : "rig"
  },

  {
    "type" : "ClutterAnimator",
    "id" : "animator",
    "duration" : 2000,

    "properties" : [
      {
        "object" : "rig",
        "name" : "x",
        "ease-in" : true,
        "keys" : [
          [ 0.0, "linear", 0.0 ],
          [ 1.0, "easeOutCubic", 150.0 ]
        ]
      },
      {
        "object" : "rig",
        "name" : "scale-x",
        "ease-in" : true,
        "keys" : [
          [ 0.5, "linear", 1.0 ],
          [ 1.0, "easeOutBack", 2.0 ]
        ]
      },
      {
        "object" : "rig",
        "name" : "scale-y",
        "ease-in" : true,
        "keys" : [
          [ 0.5, "linear", 1.0 ],
          [ 1.0, "easeOutBack", 2.0 ]
        ]
      }
    ]
  }
]
</pre></div><p>The remaining parts of the application code load
      the user interface definition, setting up the stage and rectangles;
      and define the callback. The full code is
      <a class="link" href="animations-reuse.html#animations-reuse-example-3" title="Example 5.7. Loading ClutterScript from JSON files in response to events in a user interface">in the appendix</a>,
      but below is the most important part, the callback function:</p><div class="informalexample"><pre class="programlisting">gboolean
foo_button_pressed_cb (ClutterActor *actor,
                       ClutterEvent *event,
                       gpointer      user_data)
{
  ClutterScript *ui = CLUTTER_SCRIPT (user_data);
  ClutterStage *stage = CLUTTER_STAGE (clutter_script_get_object (ui, "stage"));

  ClutterScript *script;
  ClutterActor *rig;
  ClutterAnimator *animator;

  /* load the rig and its animator from a JSON file */
  script = clutter_script_new ();

  /* use a function defined statically in this source file to load the JSON */
  load_script_from_file (script, ANIMATION_FILE);

  clutter_script_get_objects (script,
                              "rig", &amp;rig,
                              "animator", &amp;animator,
                              NULL);

  /* remove the button press handler from the rectangle */
  g_signal_handlers_disconnect_by_func (actor,
                                        G_CALLBACK (foo_button_pressed_cb),
                                        NULL);

  /* add a callback to clean up the script when the rig is destroyed */
  g_object_set_data_full (G_OBJECT (rig), "script", script, g_object_unref);

  /* add the rig to the stage */
  clutter_actor_add_child (stage, rig);

  /* place the rig at the same coordinates on the stage as the rectangle */
  clutter_actor_set_position (rig,
                              clutter_actor_get_x (actor),
                              clutter_actor_get_y (actor));

  /* put the rectangle into the top-left corner of the rig */
  g_object_ref (actor);
  clutter_actor_remove_child (clutter_actor_get_parent (actor), actor);
  clutter_actor_add_child (rig, actor);

  clutter_actor_set_position (actor, 0, 0);

  /* animate the rig */
  clutter_animator_start (animator);

  return TRUE;
}</pre></div><p>The code creates a new rig and associated animation
      at the point when the rectangle is clicked. It then positions the
      rig at the same coordinates as the rectangle, reparents
      the rectangle to the rig, and starts the rig's animation.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The signal handler has to be declared non-static and
        you must use <code class="code">-export-dynamic</code> as an option to the
        compiler, otherwise the function isn't visible to
        <span class="type">ClutterScript</span> (as outlined
        <a class="link" href="script-signals.html" title="3. Connecting to signals in ClutterScript">in this recipe</a>).</p></div><p>This is what the animation looks like:</p><p><video controls="controls" src="videos/animations-reuse.ogv"><a href="videos/animations-reuse.ogv">
          <p>Video of a simple reusable animation</p>
        </a></video></p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="animations-reuse-discussion"></a>6.3. Discussion</h3></div></div></div><p>The above solution reparents an actor to be animated
      into a rig (an empty placeholder). The rig is a container
      which acts as a temporary parent for the actor we
      <span class="emphasis"><em>really</em></span> want to animate. By animating the rig,
      it appears as though the actor inside it is being animated (but
      <a class="link" href="animations-reuse.html#animations-reuse-discussion-rig-not-actor" title="6.3.2. Caveats about animating a rig instead of an actor">see
      these caveats</a>). This means the same animation can be
      easily applied to different actors: create an
      instance of the rig, reparent an actor to it, then
      run the rig's animation. This is simpler than creating
      a separate animation for each actor individually, or
      reusing a single <span class="type">ClutterAnimator</span> on different
      actors (see
      <a class="link" href="animations-reuse.html#animations-reuse-discussion-one-or-many" title="6.3.1. One animation vs. many">this
      section</a>).</p><p>Using JSON enhances the animation's reusability (it's even
      potentially reusable in another application), makes the code
      simpler (an animation can be loaded directly from the script),
      and makes refactoring easier (the animation can be modified
      without recompiling the application code). However, it also puts
      some minor limitations on the animation's reusability; namely, you
      can only set absolute property values in a JSON animation
      definition. This makes JSON less useful in cases where
      you need to animate properties relative to their starting
      values: for example, "move 50 pixels along the x axis" or
      "rotate by 10 degrees more on the z axis". (This type of animation
      is probably less portable anyway.) In such cases, the programmable
      API may be a better option: see the <span class="type">ClutterAnimator</span>
      documentation for examples.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="animations-reuse-discussion-one-or-many"></a>6.3.1. One animation vs. many</h4></div></div></div><p>In the sample code, a new instance of the rig and its
        animation are created for each actor. One side effect of this
        is that all of the actors can animate simultaneously with the
        "same" animation. If you don't want this behaviour, but still
        want to use a rig approach, you could create a single instance
        of the rig and its animation. Then, you could reparent each actor
        to it in turn.</p><p>To ensure that the rig only animates one actor (or group
        of actors) at a time, you could track whether the rig is
        currently animating (e.g. by examining the animation's
        timeline with <code class="function">clutter_animator_get_timeline()</code>).
        Then, if the animation is running, prevent any other actor
        from being reparented to the rig.</p><p>Note that you would also need to "reset" the rig each time the
        animation completed (move it back to the right start values for
        its properties), ready to animate the next actor.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="animations-reuse-discussion-rig-not-actor"></a>6.3.2. Caveats about animating a rig instead of an actor</h4></div></div></div><p>There are a few issues to be aware of in cases
        where you animate a rig with contained actors, rather than
        animating the actor directly:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p>Animating a rig doesn't <span class="emphasis"><em>always</em></span>
            produce the same visual effect as animating an actor directly.
            For example, compare the following cases:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><p>You rotate an actor by 180 degrees in the
                <code class="code">y</code> axis, then by 90 degrees in the
                <code class="code">z</code> axis. The actor appears to rotate in
                a <span class="emphasis"><em>clockwise</em></span> direction.</p></li><li class="listitem"><p>You rotate the parent container of an actor
                by 180 degrees in the <code class="code">y</code> axis; then rotate
                the actor by 90 degrees in the <code class="code">z</code> axis.
                The actor appears to rotate in an
                <span class="emphasis"><em>anti-clockwise</em></span> direction. By
                rotating the container, the "back" of the
                actor faces the view point, so the actor's movement
                appears reversed. See
                <a class="link" href="animations-rotating.html#animations-rotating-discussion-direction" title="4.3.2. Direction of rotation">this
                recipe</a> for more details.</p></li></ul></div><p>There may be other situations where you get similar
            discrepancies.</p></li><li class="listitem"><p>Animating a rig doesn't change an actor's properties,
            but animating the actor does.</p><p>When you animate a container rather than the actor
            directly, the reported properties of the actor may not
            reflect its visual appearance. For example, if you apply
            a scale animation to a container, the final scale of
            actors inside it (as returned by
            <code class="function">clutter_actor_get_scale()</code>) will not
            reflect the scaling applied to their container; whereas
            directly animating the actors would cause their scale
            properties to change.</p></li><li class="listitem"><p>Reparenting an actor to a rig can cause the actor
            to "jump" to the rig's position, unless you align the
            actor to the rig first.</p><p>Note that in the sample code, the position of the actor
            (<code class="code">x</code>, <code class="code">y</code> coordinates) is copied to
            the rig before the reparenting happens. The actor is then
            reparented to the rig, and positioned in the rig's
            top-left corner. So the actor appears to be in the same
            position, but is now actually inside a rig at the actor's old
            position.</p><p>Why bother to do this? Because the rig has a default
            position of <code class="code">0,0</code> (top-left of <span class="emphasis"><em>its</em></span>
            container, the stage). If you reparent the actor to the rig,
            without first copying the actor's position to the rig, the
            actor appears to "jump" to the rig's position.</p></li></ul></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="animations-reuse-examples"></a>6.4. Full example</h3></div></div></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The three separate code examples in this section
        constitute a single application which implements the above
        solution.</p></div><div class="example"><a name="animations-reuse-example-1"></a><p class="title"><b>Example 5.5. <span class="type">ClutterScript</span> JSON defining several
        rectangles with signal handlers</b></p><div class="example-contents"><pre class="programlisting">[
  {
    "type" : "ClutterStage",
    "id" : "stage",
    "width" : 300,
    "height" : 200,
    "color" : "#333355ff",

    "signals" : [
      { "name" : "destroy", "handler" : "clutter_main_quit" }
    ],

    "children" : [
      {
        "type" : "ClutterRectangle",
        "id" : "rect1",
        "color" : "white",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      },

      {
        "type" : "ClutterRectangle",
        "id" : "rect2",
        "color" : "blue",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      },

      {
        "type" : "ClutterRectangle",
        "id" : "rect3",
        "color" : "green",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      },

      {
        "type" : "ClutterRectangle",
        "id" : "rect4",
        "color" : "red",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      },

      {
        "type" : "ClutterRectangle",
        "id" : "rect5",
        "color" : "grey",
        "width" : 50,
        "height" : 50,
        "y" : 50,
        "reactive" : true,
        "signals" : [
          { "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
        ]
      }
    ]
  }
]
</pre></div></div><br class="example-break"><div class="example"><a name="animations-reuse-example-2"></a><p class="title"><b>Example 5.6. <span class="type">ClutterScript</span> JSON describing a "rig"
        and a <span class="type">ClutterAnimator</span> animation</b></p><div class="example-contents"><pre class="programlisting">[
  {
    "type" : "ClutterGroup",
    "id" : "rig"
  },

  {
    "type" : "ClutterAnimator",
    "id" : "animator",
    "duration" : 2000,

    "properties" : [
      {
        "object" : "rig",
        "name" : "x",
        "ease-in" : true,
        "keys" : [
          [ 0.0, "linear", 0.0 ],
          [ 1.0, "easeOutCubic", 150.0 ]
        ]
      },
      {
        "object" : "rig",
        "name" : "scale-x",
        "ease-in" : true,
        "keys" : [
          [ 0.5, "linear", 1.0 ],
          [ 1.0, "easeOutBack", 2.0 ]
        ]
      },
      {
        "object" : "rig",
        "name" : "scale-y",
        "ease-in" : true,
        "keys" : [
          [ 0.5, "linear", 1.0 ],
          [ 1.0, "easeOutBack", 2.0 ]
        ]
      }
    ]
  }
]
</pre></div></div><br class="example-break"><div class="example"><a name="animations-reuse-example-3"></a><p class="title"><b>Example 5.7. Loading <span class="type">ClutterScript</span> from JSON files
        in response to events in a user interface</b></p><div class="example-contents"><pre class="programlisting">#include &lt;stdlib.h&gt;
#include &lt;clutter/clutter.h&gt;

#define UI_FILE "animations-reuse-ui.json"
#define ANIMATION_FILE "animations-reuse-animation.json"

static gboolean
load_script_from_file (ClutterScript *script,
                       gchar         *filename)
{
  GError *error = NULL;

  clutter_script_load_from_file (script, filename, &amp;error);

  if (error != NULL)
    {
      g_critical ("Error loading ClutterScript file %s\n%s", filename, error-&gt;message);
      g_error_free (error);
      exit (EXIT_FAILURE);
    }

  return TRUE;
}

gboolean
foo_button_pressed_cb (ClutterActor *actor,
                       ClutterEvent *event,
                       gpointer      user_data)
{
  ClutterScript *ui = CLUTTER_SCRIPT (user_data);
  ClutterStage *stage = CLUTTER_STAGE (clutter_script_get_object (ui, "stage"));

  ClutterScript *script;
  ClutterActor *rig;
  ClutterAnimator *animator;

  /* load the rig and its animator from a JSON file */
  script = clutter_script_new ();

  /* use a function defined statically in this source file to load the JSON */
  load_script_from_file (script, ANIMATION_FILE);

  clutter_script_get_objects (script,
                              "rig", &amp;rig,
                              "animator", &amp;animator,
                              NULL);

  /* remove the button press handler from the rectangle */
  g_signal_handlers_disconnect_by_func (actor,
                                        G_CALLBACK (foo_button_pressed_cb),
                                        NULL);

  /* add a callback to clean up the script when the rig is destroyed */
  g_object_set_data_full (G_OBJECT (rig), "script", script, g_object_unref);

  /* add the rig to the stage */
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), rig);

  /* place the rig at the same coordinates on the stage as the rectangle */
  clutter_actor_set_position (rig,
                              clutter_actor_get_x (actor),
                              clutter_actor_get_y (actor));

  /* put the rectangle into the top-left corner of the rig */
  clutter_actor_reparent (actor, rig);

  clutter_actor_set_position (actor, 0, 0);

  /* animate the rig */
  clutter_animator_start (animator);

  return TRUE;
}

int
main (int argc, char *argv[])
{
  ClutterScript *script;
  ClutterActor *stage;

  if (clutter_init (&amp;argc, &amp;argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  script = clutter_script_new ();
  load_script_from_file (script, UI_FILE);

  clutter_script_connect_signals (script, script);

  clutter_script_get_objects (script,
                              "stage", &amp;stage,
                              NULL);

  clutter_actor_show (stage);

  clutter_main ();

  g_object_unref (script);

  return EXIT_SUCCESS;
}
</pre></div></div><br class="example-break"></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="animations-complex.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="animations.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="animations-moving.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">5. Creating complex animations with
    <span class="type">ClutterAnimator</span> </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> 7. Moving actors</td></tr></table></div></body></html>