From ac9aa06fcf97bd5ccb2b32c970eac94ffb18297e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 9 Oct 2007 13:29:03 +0000 Subject: [PATCH] 2007-10-09 Emmanuele Bassi * clutter/clutter-script-private.h: * clutter/clutter-script.h: * clutter/clutter-script.c: Add licensing information to the newly added files. * clutter/clutter-script.c: Support creating behaviours with ClutterScript. ClutterAlpha objects are implicit, but timelines can be both explicit objects using their id or implicit objects. Make the property resolution and translation more robust. Support the pixbuf property. * tests/test-script.c: Test the newly added features. * docs/reference/clutter-docs.sgml: * docs/reference/clutter-sections.txt: Add ClutterScript. --- ChangeLog | 18 ++ clutter/clutter-script-private.h | 29 ++ clutter/clutter-script.c | 454 ++++++++++++++++++++++++++--- clutter/clutter-script.h | 25 ++ doc/reference/clutter-docs.sgml | 1 + doc/reference/clutter-sections.txt | 25 ++ tests/test-script.c | 46 ++- 7 files changed, 549 insertions(+), 49 deletions(-) diff --git a/ChangeLog b/ChangeLog index 95b4cab3d..27ebedcc3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2007-10-09 Emmanuele Bassi + + * clutter/clutter-script-private.h: + * clutter/clutter-script.h: + * clutter/clutter-script.c: Add licensing information to + the newly added files. + + * clutter/clutter-script.c: Support creating behaviours with + ClutterScript. ClutterAlpha objects are implicit, but + timelines can be both explicit objects using their id or + implicit objects. Make the property resolution and translation + more robust. Support the pixbuf property. + + * tests/test-script.c: Test the newly added features. + + * docs/reference/clutter-docs.sgml: + * docs/reference/clutter-sections.txt: Add ClutterScript. + 2007-10-09 Emmanuele Bassi * clutter/clutter-fixed.h: Add deprecation guards around diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h index 7aee43308..ae5a7c7d0 100644 --- a/clutter/clutter-script-private.h +++ b/clutter/clutter-script-private.h @@ -1,3 +1,28 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + #ifndef __CLUTTER_SCRIPT_PRIVATE_H__ #define __CLUTTER_SCRIPT_PRIVATE_H__ @@ -27,6 +52,10 @@ typedef struct { GObject *clutter_script_construct_object (ClutterScript *script, ObjectInfo *info); +gboolean clutter_script_enum_from_string (GType gtype, + const gchar *string, + gint *enum_value); + G_END_DECLS #endif /* __CLUTTER_SCRIPT_PRIVATE_H__ */ diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c index 4fa30ec96..817f33a42 100644 --- a/clutter/clutter-script.c +++ b/clutter/clutter-script.c @@ -1,3 +1,91 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:clutter-script + * @short_description: Loads a scene from UI definition data + * + * #ClutterScript is an object used for loading and building parts or a + * complete scenegraph from external definition data in forms of string + * buffers or files. + * + * The UI definition format is JSON, the JavaScript Object Notation as + * described by RFC 4627. #ClutterScript can load a JSON data stream, + * parse it and build all the objects defined into it. Each object must + * have an "id" and a "type" properties defining the name to be used + * to retrieve it from #ClutterScript with clutter_script_get_object(), + * and the class type to be instanciated. Every other attribute will + * be mapped to the class properties. + * + * A simple object might be defined as: + * + * + * { + * "id" : "red-button", + * "type" : "ClutterRectangle", + * "width" : 100, + * "height" : 100, + * "color" : "0xff0000ff" + * } + * + * + * This will produce a red #ClutterRectangle, 100x100 pixels wide, and + * with a name of "red-button"; it can be retrieved by calling: + * + * + * ClutterActor *red_button; + * + * red_button = CLUTTER_ACTOR (clutter_script_get_object (script, "red-button")); + * + * + * and then manipulated with the Clutter API. + * + * Packing can be represented using the "children" member, and passing an + * array of objects or ids of objects already defined (but not packed: the + * packing rules of Clutter still apply). + * + * Behaviours and timelines can also be defined inside a UI definition + * buffer: + * + * + * { + * "id" : "rotate-behaviour", + * "type" : "ClutterBehaviourRotate", + * "angle-begin" : 0.0, + * "angle-end" : 360.0, + * "axis" : "z-axis", + * "alpha" : { + * "timeline" : { "num-frames" : 240, "fps" : 60, "loop" : true }, + * "function" : "sine" + * } + * } + * + * + * #ClutterScript is available since Clutter 0.6 + */ + #include "config.h" #include @@ -9,8 +97,10 @@ #include #include "clutter-actor.h" -#include "clutter-stage.h" +#include "clutter-alpha.h" +#include "clutter-behaviour.h" #include "clutter-container.h" +#include "clutter-stage.h" #include "clutter-script.h" #include "clutter-script-private.h" @@ -38,6 +128,8 @@ struct _ClutterScriptPrivate G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT); +static void object_info_free (gpointer data); + /* tries to map a name in camel case into the corresponding get_type() * function, e.g.: * @@ -83,6 +175,47 @@ resolve_type_lazily (const gchar *name) return gtype; } +static ClutterAlphaFunc +resolve_alpha_func (const gchar *name) +{ + static GModule *module = NULL; + ClutterAlphaFunc func; + GString *symbol_name = g_string_new (""); + char c, *symbol; + int i; + + if (!module) + module = g_module_open (NULL, 0); + + if (g_module_symbol (module, name, (gpointer) &func)) + return func; + + g_string_append (symbol_name, "clutter_"); + + for (i = 0; name[i] != '\0'; i++) + { + c = name[i]; + /* skip if uppercase, first or previous is uppercase */ + if ((c == g_ascii_toupper (c) && + i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) || + (i > 2 && name[i] == g_ascii_toupper (name[i]) && + name[i-1] == g_ascii_toupper (name[i-1]) && + name[i-2] == g_ascii_toupper (name[i-2]))) + g_string_append_c (symbol_name, '_'); + g_string_append_c (symbol_name, g_ascii_tolower (c)); + } + g_string_append (symbol_name, "_func"); + + symbol = g_string_free (symbol_name, FALSE); + + if (!g_module_symbol (module, symbol, (gpointer)&func)) + func = NULL; + + g_free (symbol); + + return func; +} + static void warn_missing_attribute (ClutterScript *script, const gchar *id, @@ -131,6 +264,53 @@ warn_invalid_value (ClutterScript *script, } } +static ClutterTimeline * +construct_timeline (ClutterScript *script, + JsonObject *object) +{ + ClutterTimeline *retval = NULL; + ObjectInfo *oinfo; + GList *members, *l; + + /* we fake an ObjectInfo so we can reuse clutter_script_construct_object() + * here; we do not save it inside the hash table, because if this had + * been a named object then we wouldn't have ended up here in the first + * place + */ + oinfo = g_slice_new0 (ObjectInfo); + oinfo->gtype = CLUTTER_TYPE_TIMELINE; + + members = json_object_get_members (object); + for (l = members; l != NULL; l = l->next) + { + const gchar *name = l->data; + JsonNode *node = json_object_get_member (object, name); + + if (JSON_NODE_TYPE (node) == JSON_NODE_VALUE) + { + PropertyInfo *pinfo = g_slice_new (PropertyInfo); + GValue value = { 0, }; + + pinfo->property_name = g_strdup (name); + + json_node_get_value (node, &value); + g_value_init (&pinfo->value, G_VALUE_TYPE (&value)); + g_value_transform (&value, &pinfo->value); + g_value_unset (&value); + + oinfo->properties = g_list_prepend (oinfo->properties, pinfo); + } + } + g_list_free (members); + + retval = CLUTTER_TIMELINE (clutter_script_construct_object (script, oinfo)); + + g_object_ref (retval); + object_info_free (oinfo); + + return retval; +} + static PropertyInfo * parse_member_to_property (ClutterScript *script, ObjectInfo *info, @@ -148,11 +328,51 @@ parse_member_to_property (ClutterScript *script, json_node_get_value (node, &value); g_value_init (&retval->value, G_VALUE_TYPE (&value)); - g_value_copy (&value, &retval->value); + g_value_transform (&value, &retval->value); g_value_unset (&value); break; case JSON_NODE_OBJECT: + if (strcmp (name, "alpha") == 0) + { + JsonObject *object = json_node_get_object (node); + ClutterAlpha *alpha = NULL; + ClutterTimeline *timeline = NULL; + ClutterAlphaFunc func = NULL; + JsonNode *val; + + retval = g_slice_new (PropertyInfo); + retval->property_name = g_strdup (name); + + alpha = clutter_alpha_new (); + + val = json_object_get_member (object, "timeline"); + if (val) + { + if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE && + json_node_get_string (val) != NULL) + { + const gchar *id = json_node_get_string (val); + + timeline = + CLUTTER_TIMELINE (clutter_script_get_object (script, id)); + } + else if (JSON_NODE_TYPE (val) == JSON_NODE_OBJECT) + timeline = construct_timeline (script, json_node_get_object (val)); + } + + val = json_object_get_member (object, "function"); + if (val && json_node_get_string (val) != NULL) + func = resolve_alpha_func (json_node_get_string (val)); + + alpha = g_object_new (CLUTTER_TYPE_ALPHA, + "timeline", timeline, + NULL); + clutter_alpha_set_func (alpha, func, NULL, NULL); + + g_value_init (&retval->value, CLUTTER_TYPE_ALPHA); + g_value_set_object (&retval->value, G_OBJECT (alpha)); + } break; case JSON_NODE_ARRAY: @@ -365,59 +585,150 @@ json_object_end (JsonParser *parser, g_hash_table_replace (priv->objects, g_strdup (oinfo->id), oinfo); } +/* the property translation sequence is split in two: the first + * half is done in parse_member_to_property(), which translates + * from JSON data types into valid property types. the second + * half is done here, where property types are translated into + * the correct type for the given property GType + */ static gboolean -translate_property (const gchar *name, - const GValue *src, - GValue *dest) +translate_property (ClutterScript *script, + GType gtype, + const gchar *name, + const GValue *src, + GValue *dest) { + gboolean retval = FALSE; + + /* colors have a parse function, so we can pass it the + * string we get from the parser + */ if (strcmp (name, "color") == 0) { ClutterColor color = { 0, }; const gchar *color_str; if (G_VALUE_HOLDS (src, G_TYPE_STRING)) - color_str = g_value_get_string (src); - else - color_str = NULL; - - clutter_color_parse (color_str, &color); + { + color_str = g_value_get_string (src); + clutter_color_parse (color_str, &color); + } g_value_init (dest, CLUTTER_TYPE_COLOR); g_value_set_boxed (dest, &color); return TRUE; } - else if (strcmp (name, "pixbuf") == 0) + + /* pixbufs are specified using the path to the file name; the + * path can be absolute or relative to the current directory. + * we need to load the pixbuf from the file and print a + * warning here in case it didn't work. + */ + if (strcmp (name, "pixbuf") == 0) { GdkPixbuf *pixbuf = NULL; - const gchar *path; + const gchar *string; + gchar *path; + GError *error; if (G_VALUE_HOLDS (src, G_TYPE_STRING)) - path = g_value_get_string (src); + string = g_value_get_string (src); else - path = NULL; + return FALSE; - if (path && g_path_is_absolute (path)) + if (g_path_is_absolute (string)) + path = g_strdup (string); + else { - GError *error = NULL; - - pixbuf = gdk_pixbuf_new_from_file (path, &error); - if (error) + if (script->priv->is_filename) { - g_warning ("Unable to open pixbuf at path `%s': %s", - path, - error->message); - g_error_free (error); + gchar *dirname; + + dirname = g_path_get_dirname (script->priv->filename); + path = g_build_filename (dirname, string, NULL); + + g_free (dirname); + } + else + { + gchar *dirname; + + dirname = g_get_current_dir (); + path = g_build_filename (dirname, string, NULL); + + g_free (dirname); } } + error = NULL; + pixbuf = gdk_pixbuf_new_from_file (path, &error); + if (error) + { + g_warning ("Unable to open pixbuf at path `%s': %s", + path, + error->message); + g_error_free (error); + g_free (path); + return FALSE; + } + + CLUTTER_NOTE (SCRIPT, "Setting pixbuf [%p] from file `%s'", + pixbuf, + path); + + g_free (path); + g_value_init (dest, GDK_TYPE_PIXBUF); g_value_set_object (dest, pixbuf); return TRUE; } - return FALSE; + CLUTTER_NOTE (SCRIPT, "Copying property `%s' to type `%s'", + name, + g_type_name (gtype)); + + /* fall back scenario */ + g_value_init (dest, gtype); + + switch (G_TYPE_FUNDAMENTAL (gtype)) + { + case G_TYPE_UINT: + g_value_set_uint (dest, (guint) g_value_get_int (src)); + retval = TRUE; + break; + case G_TYPE_UCHAR: + g_value_set_uchar (dest, (guchar) g_value_get_int (src)); + retval = TRUE; + break; + case G_TYPE_ENUM: + if (G_VALUE_HOLDS (src, G_TYPE_STRING)) + { + const gchar *string = g_value_get_string (src); + gint enum_value; + + if (clutter_script_enum_from_string (gtype, string, &enum_value)) + { + g_value_set_enum (dest, enum_value); + retval = TRUE; + } + } + else if (G_VALUE_HOLDS (src, G_TYPE_INT)) + { + g_value_set_enum (dest, g_value_get_int (src)); + retval = TRUE; + } + break; + case G_TYPE_FLAGS: + break; + default: + g_value_copy (src, dest); + retval = TRUE; + break; + } + + return retval; } static void @@ -451,10 +762,15 @@ translate_properties (ClutterScript *script, } param.name = pinfo->property_name; - if (!translate_property (param.name, &pinfo->value, ¶m.value)) + if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec), + param.name, + &pinfo->value, + ¶m.value)) { - g_value_init (¶m.value, G_PARAM_SPEC_VALUE_TYPE (pspec)); - g_value_copy (&pinfo->value, ¶m.value); + g_warning ("Unable to set property `%s' for class `%s'", + pinfo->property_name, + g_type_name (oinfo->gtype)); + continue; } g_array_append_val (parameters, param); @@ -525,10 +841,15 @@ construct_stage (ClutterScript *script, continue; } - if (!translate_property (name, &pinfo->value, &value)) + if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec), + name, + &pinfo->value, + &value)) { - g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); - g_value_copy (&pinfo->value, &value); + g_warning ("Unable to set property `%s' for class `%s'", + pinfo->property_name, + g_type_name (oinfo->gtype)); + continue; } g_object_set_property (oinfo->object, name, &value); @@ -557,33 +878,34 @@ GObject * clutter_script_construct_object (ClutterScript *script, ObjectInfo *oinfo) { - GType gtype; guint n_params, i; GParameter *params; if (oinfo->object) return oinfo->object; - gtype = resolve_type_lazily (oinfo->class_name); - if (gtype == G_TYPE_INVALID) - return NULL; + if (oinfo->gtype == G_TYPE_INVALID) + { + oinfo->gtype = resolve_type_lazily (oinfo->class_name); + if (oinfo->gtype == G_TYPE_INVALID) + return NULL; + } /* the stage is a special case: it's a singleton, it cannot * be created by the user and it's owned by the backend. hence, * we cannot follow the usual pattern here */ - if (g_type_is_a (gtype, CLUTTER_TYPE_STAGE)) + if (g_type_is_a (oinfo->gtype, CLUTTER_TYPE_STAGE)) return construct_stage (script, oinfo); - oinfo->gtype = gtype; params = NULL; translate_properties (script, oinfo, &n_params, ¶ms); CLUTTER_NOTE (SCRIPT, "Creating instance for type `%s' (params:%d)", - g_type_name (gtype), + g_type_name (oinfo->gtype), n_params); - oinfo->object = g_object_newv (gtype, n_params, params); + oinfo->object = g_object_newv (oinfo->gtype, n_params, params); for (i = 0; i < n_params; i++) { @@ -593,15 +915,13 @@ clutter_script_construct_object (ClutterScript *script, g_free (params); - if (oinfo->children) - { - if (CLUTTER_IS_CONTAINER (oinfo->object)) - add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo->children); - } + if (oinfo->children && CLUTTER_IS_CONTAINER (oinfo->object)) + add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo->children); - g_object_set_data_full (oinfo->object, "clutter-script-name", - g_strdup (oinfo->id), - g_free); + if (oinfo->id) + g_object_set_data_full (oinfo->object, "clutter-script-name", + g_strdup (oinfo->id), + g_free); return oinfo->object; } @@ -647,6 +967,7 @@ object_info_free (gpointer data) } g_list_free (oinfo->properties); + /* these are ids */ g_list_foreach (oinfo->children, (GFunc) g_free, NULL); g_list_free (oinfo->children); @@ -823,6 +1144,41 @@ clutter_script_get_objects (ClutterScript *script, return retval; } +gboolean +clutter_script_enum_from_string (GType type, + const gchar *string, + gint *enum_value) +{ + GEnumClass *eclass; + GEnumValue *ev; + gchar *endptr; + gint value; + gboolean retval = TRUE; + + g_return_val_if_fail (G_TYPE_IS_ENUM (type), 0); + g_return_val_if_fail (string != NULL, 0); + + value = strtoul (string, &endptr, 0); + if (endptr != string) /* parsed a number */ + *enum_value = value; + else + { + eclass = g_type_class_ref (type); + ev = g_enum_get_value_by_name (eclass, string); + if (!ev) + ev = g_enum_get_value_by_nick (eclass, string); + + if (ev) + *enum_value = ev->value; + else + retval = FALSE; + + g_type_class_unref (eclass); + } + + return retval; +} + gboolean clutter_script_value_from_data (ClutterScript *script, GType gtype, @@ -832,3 +1188,9 @@ clutter_script_value_from_data (ClutterScript *script, { return FALSE; } + +GQuark +clutter_script_error_quark (void) +{ + return g_quark_from_static_string ("clutter-script-error"); +} diff --git a/clutter/clutter-script.h b/clutter/clutter-script.h index f782b09af..c1ac9f1ae 100644 --- a/clutter/clutter-script.h +++ b/clutter/clutter-script.h @@ -1,3 +1,28 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + #ifndef __CLUTTER_SCRIPT_H__ #define __CLUTTER_SCRIPT_H__ diff --git a/doc/reference/clutter-docs.sgml b/doc/reference/clutter-docs.sgml index 62fea69be..81e419ed4 100644 --- a/doc/reference/clutter-docs.sgml +++ b/doc/reference/clutter-docs.sgml @@ -145,6 +145,7 @@ + diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index 97c666e4a..245cd2518 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -1046,3 +1046,28 @@ CLUTTER_BEHAVIOUR_DEPTH_GET_CLASS ClutterBehaviourDepthPrivate clutter_behaviour_depth_get_type + +
+clutter-script +ClutterScript +ClutterScript +ClutterScriptClass +clutter_script_new +ClutterScriptError +clutter_script_load_from_data +clutter_script_load_from_file +clutter_script_get_object +clutter_script_get_objects + +CLUTTER_TYPE_SCRIPT +CLUTTER_SCRIPT +CLUTTER_IS_SCRIPT +CLUTTER_SCRIPT_CLASS +CLUTTER_IS_SCRIPT_CLASS +CLUTTER_SCRIPT_GET_CLASS +CLUTTER_SCRIPT_ERROR + +ClutterScriptPrivate +clutter_script_get_type +clutter_script_error_quark +
diff --git a/tests/test-script.c b/tests/test-script.c index 2cc45d96f..c265111b9 100644 --- a/tests/test-script.c +++ b/tests/test-script.c @@ -5,6 +5,19 @@ #include +static const gchar *test_behaviour = +"{" +" \"id\" : \"rotate-behaviour\"," +" \"type\" : \"ClutterBehaviourRotate\"," +" \"angle-begin\" : 0.0," +" \"angle-end\" : 360.0," +" \"axis\" : \"z-axis\"," +" \"alpha\" : {" +" \"timeline\" : { \"num-frames\" : 300, \"fps\" : 60, \"loop\" : true }," +" \"function\" : \"sine\"" +" }" +"}"; + static const gchar *test_ui = "{" " \"Scene\" : {" @@ -43,6 +56,15 @@ static const gchar *test_ui = " \"width\" : 100," " \"height\" : 100," " \"visible\" : true," +" }," +" {" +" \"id\" : \"red-hand\"," +" \"type\" : \"ClutterTexture\"," +" \"pixbuf\" : \"redhand.png\"," +" \"x\" : 50," +" \"y\" : 50," +" \"opacity\" : 25," +" \"visible\" : true," " }" " ]" " }" @@ -52,14 +74,26 @@ int main (int argc, char *argv[]) { ClutterActor *stage; - ClutterActor *rect; + ClutterActor *texture; + ClutterBehaviour *rotate; ClutterScript *script; - GError *error; + GError *error = NULL; clutter_init (&argc, &argv); script = clutter_script_new (); - error = NULL; + g_assert (CLUTTER_IS_SCRIPT (script)); + + clutter_script_load_from_data (script, test_behaviour, -1, &error); + if (error) + { + g_print ("*** Error:\n" + "*** %s\n", error->message); + g_error_free (error); + g_object_unref (script); + return EXIT_FAILURE; + } + clutter_script_load_from_data (script, test_ui, -1, &error); if (error) { @@ -73,6 +107,12 @@ main (int argc, char *argv[]) stage = CLUTTER_ACTOR (clutter_script_get_object (script, "main-stage")); clutter_actor_show (stage); + texture = CLUTTER_ACTOR (clutter_script_get_object (script, "red-hand")); + + rotate = CLUTTER_BEHAVIOUR (clutter_script_get_object (script, "rotate-behaviour")); + clutter_behaviour_apply (rotate, texture); + clutter_timeline_start (clutter_alpha_get_timeline (clutter_behaviour_get_alpha (rotate))); + clutter_main (); g_object_unref (script);