diff --git a/doc/cookbook/Makefile.am b/doc/cookbook/Makefile.am index 9ce15c241..15ce15a23 100644 --- a/doc/cookbook/Makefile.am +++ b/doc/cookbook/Makefile.am @@ -58,6 +58,7 @@ VIDEO_FILES = \ videos/events-mouse-scroll.ogv \ videos/textures-crossfade-two-textures.ogv \ videos/animations-complex.ogv \ + videos/animations-reuse.ogv \ $(NULL) EXTRA_DIST = \ diff --git a/doc/cookbook/animations.xml b/doc/cookbook/animations.xml index 49c9a0832..ba22a3350 100644 --- a/doc/cookbook/animations.xml +++ b/doc/cookbook/animations.xml @@ -844,7 +844,7 @@ clutter_actor_set_z_rotation_from_gravity (actor, -
+
Direction of rotation The apparent direction of an animated rotation depends on @@ -1438,4 +1438,382 @@ clutter_actor_set_z_rotation_from_gravity (actor,
+
+ Reusing a complex animation on different actors + +
+ Problem + + You want to apply the same complex animation to several + different actors. + +
+ +
+ Solution + + Instead of animating each actor separately, create a + rig: an empty container with an associated + animation, which will be animated in lieu of + animating the actor directly. Do this as follows: + + + + Initialise the stage and actors, including those + to be animated. + + + Define a ClutterContainer and a + ClutterAnimator animation to animate it. + + + When you need to animate an actor: + + + + Create an instance of the rig and its animator. + + + Reparent the actor to the rig. + + + Run the rig's animation. + + + + + + For this solution, we're using + JSON to define the + animation and the user interface elements. For more + details about this approach, see + the chapter + on ClutterScript. + + 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 the + appendix): + + + + + + + + The key point to note is how a signal handler is defined + for the button-press-event, so that the + foo_button_pressed_cb() function will trigger + the animation when a (mouse) button is pressed on each rectangle. + + The second JSON definition includes the rig + (an empty ClutterGroup) and a + ClutterAnimator to animate it. The animation moves the + container across the stage and scales it to twice its original + size. (This is the same + code as in the appendix): + + + + + a code sample should be here... but isn't + + + + + 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 + in the appendix, + but below is the most important part, the callback function: + + + + + + + + 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. + + + The signal handler has to be declared non-static and + you must use -export-dynamic as an option to the + compiler, otherwise the function isn't visible to + ClutterScript (as outlined + in this recipe). + + + This is what the animation looks like: + + + + + + + Video of a simple reusable animation + + + +
+ +
+ Discussion + + 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 + really want to animate. By animating the rig, + it appears as though the actor inside it is being animated (but + see + these caveats). 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 ClutterAnimator on different + actors (see + this + section). + + 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 ClutterAnimator + documentation for examples. + +
+ One animation vs. many + + 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. + + 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 clutter_animator_get_timeline()). + Then, if the animation is running, prevent any other actor + from being reparented to the rig. + + 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. +
+ +
+ Caveats about animating a rig instead of an actor + + 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: + + + + Animating a rig doesn't always + produce the same visual effect as animating an actor directly. + For example, compare the following cases: + + + + You rotate an actor by 180 degrees in the + y axis, then by 90 degrees in the + z axis. The actor appears to rotate in + a clockwise direction. + + + You rotate the parent container of an actor + by 180 degrees in the y axis; then rotate + the actor by 90 degrees in the z axis. + The actor appears to rotate in an + anti-clockwise direction. By + rotating the container, the "back" of the + actor faces the view point, so the actor's movement + appears reversed. See + this + recipe for more details. + + + + There may be other situations where you get similar + discrepancies. + + + + Animating a rig doesn't change an actor's properties, + but animating the actor does. + + 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 + clutter_actor_get_scale()) will not + reflect the scaling applied to their container; whereas + directly animating the actors would cause their scale + properties to change. + + + + 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. + + Note that in the sample code, the position of the actor + (x, y 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. + + Why bother to do this? Because the rig has a default + position of 0,0 (top-left of its + 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. + + +
+ +
+ +
+ Full example + + + The three separate code examples in this section + constitute a single application which implements the above + solution. + + + + <type>ClutterScript</type> JSON defining several + rectangles with signal handlers + + + a code sample should be here... but isn't + + + + + + <type>ClutterScript</type> JSON describing a "rig" + and a <type>ClutterAnimator</type> animation + + + a code sample should be here... but isn't + + + + + + Loading <type>ClutterScript</type> from JSON files + in response to events in a user interface + + + a code sample should be here... but isn't + + + + +
+ +
+ diff --git a/doc/cookbook/examples/Makefile.am b/doc/cookbook/examples/Makefile.am index 6039c690b..e1bcd81b9 100644 --- a/doc/cookbook/examples/Makefile.am +++ b/doc/cookbook/examples/Makefile.am @@ -4,6 +4,7 @@ NULL = noinst_PROGRAMS = \ animations-complex \ + animations-reuse \ animations-rotating \ text-shadow \ textures-reflection \ @@ -45,6 +46,7 @@ AM_CFLAGS = \ AM_LDFLAGS = $(CLUTTER_LIBS) -export-dynamic animations_complex_SOURCES = animations-complex.c +animations_reuse_SOURCES = animations-reuse.c animations_rotating_SOURCES = animations-rotating.c text_shadow_SOURCES = text-shadow.c textures_reflection_SOURCES = textures-reflection.c diff --git a/doc/cookbook/examples/animations-reuse-animation.json b/doc/cookbook/examples/animations-reuse-animation.json new file mode 100644 index 000000000..5f16e1ee9 --- /dev/null +++ b/doc/cookbook/examples/animations-reuse-animation.json @@ -0,0 +1,42 @@ +[ + { + "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 ] + ] + } + ] + } +] diff --git a/doc/cookbook/examples/animations-reuse-ui.json b/doc/cookbook/examples/animations-reuse-ui.json new file mode 100644 index 000000000..99f50c616 --- /dev/null +++ b/doc/cookbook/examples/animations-reuse-ui.json @@ -0,0 +1,80 @@ +[ + { + "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" } + ] + } + ] + } +] diff --git a/doc/cookbook/examples/animations-reuse.c b/doc/cookbook/examples/animations-reuse.c new file mode 100644 index 000000000..506435a21 --- /dev/null +++ b/doc/cookbook/examples/animations-reuse.c @@ -0,0 +1,103 @@ +#include +#include + +#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, &error); + + if (error != NULL) + { + g_critical ("Error loading ClutterScript file %s\n%s", filename, error->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", &rig, + "animator", &animator, + NULL); + + /* remove the button press handler from the rectangle */ + g_signal_handlers_disconnect_matched (actor, + G_SIGNAL_MATCH_FUNC, + 0, + 0, + NULL, + 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; + + clutter_init (&argc, &argv); + + script = clutter_script_new (); + load_script_from_file (script, UI_FILE); + + clutter_script_connect_signals (script, script); + + clutter_script_get_objects (script, + "stage", &stage, + NULL); + + clutter_actor_show (stage); + + clutter_main (); + + g_object_unref (script); + + return EXIT_SUCCESS; +} diff --git a/doc/cookbook/videos/animations-reuse.ogv b/doc/cookbook/videos/animations-reuse.ogv new file mode 100644 index 000000000..07b0c4e40 Binary files /dev/null and b/doc/cookbook/videos/animations-reuse.ogv differ