From 4dd11d6915f75ba62b8292c3138d4cc11a28fb21 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 8 Feb 2010 15:52:18 +0000 Subject: [PATCH] animator: Provide a ClutterScript parser The whole point of having the Animator class is that the developer can describe a complex animation using ClutterScript. Hence, ClutterAnimator should hook into the Script machinery and parse a specific description format for its keys. --- .gitignore | 2 + clutter/clutter-animator.c | 303 +++++++++++++++++++++++++++++++++++--- tests/conform/Makefile.am | 1 + tests/conform/test-animator.c | 88 +++++++++++ tests/conform/test-conform-main.c | 2 + tests/data/Makefile.am | 2 + tests/data/test-animator-1.json | 5 + tests/data/test-animator-2.json | 29 ++++ 8 files changed, 409 insertions(+), 23 deletions(-) create mode 100644 tests/conform/test-animator.c create mode 100644 tests/data/test-animator-1.json create mode 100644 tests/data/test-animator-2.json diff --git a/.gitignore b/.gitignore index a1d1649..3b1a150 100644 --- a/.gitignore +++ b/.gitignore @@ -245,6 +245,8 @@ TAGS /tests/conform/test-behaviours /tests/conform/test-cogl-sub-texture /tests/conform/test-cogl-multitexture +/tests/conform/test-animator-base +/tests/conform/test-animator-properties /tests/micro-bench/test-text-perf /tests/micro-bench/test-text /tests/micro-bench/test-picking diff --git a/clutter/clutter-animator.c b/clutter/clutter-animator.c index ae5a775..86f6ebd 100644 --- a/clutter/clutter-animator.c +++ b/clutter/clutter-animator.c @@ -54,8 +54,8 @@ #include "clutter-enum-types.h" #include "clutter-interval.h" #include "clutter-private.h" - -G_DEFINE_TYPE (ClutterAnimator, clutter_animator, G_TYPE_OBJECT); +#include "clutter-script-private.h" +#include "clutter-scriptable.h" #define CLUTTER_ANIMATOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_ANIMATOR, ClutterAnimatorPrivate)) @@ -104,6 +104,13 @@ enum PROP_DURATION }; +static void clutter_scriptable_init (ClutterScriptableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (ClutterAnimator, + clutter_animator, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE, + clutter_scriptable_init)); /** * clutter_animator_new: * @@ -270,10 +277,10 @@ object_disappeared (gpointer data, static ClutterAnimatorKey * clutter_animator_key_new (ClutterAnimator *animator, - gdouble progress, GObject *object, - guint mode, - const gchar *property_name) + const gchar *property_name, + gdouble progress, + guint mode) { ClutterAnimatorKey *animator_key; @@ -771,10 +778,16 @@ clutter_animator_get_timeline (ClutterAnimator *animator) ClutterTimeline * clutter_animator_run (ClutterAnimator *animator) { + ClutterAnimatorPrivate *priv; + g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL); - clutter_timeline_rewind (animator->priv->timeline); - clutter_timeline_start (animator->priv->timeline); - return animator->priv->timeline; + + priv = animator->priv; + + clutter_timeline_rewind (priv->timeline); + clutter_timeline_start (priv->timeline); + + return priv->timeline; } /** @@ -915,18 +928,47 @@ clutter_animator_set (ClutterAnimator *animator, va_end (args); } +static inline void +clutter_animator_set_key_internal (ClutterAnimator *animator, + ClutterAnimatorKey *key) +{ + ClutterAnimatorPrivate *priv = animator->priv; + GList *old_item; + + old_item = g_list_find_custom (priv->score, key, + sort_actor_prop_progress_func); + + /* replace the key if we already have a similar one */ + if (old_item != NULL) + { + ClutterAnimatorKey *old_key = old_item->data; + + clutter_animator_key_free (old_key); + + priv->score = g_list_remove (priv->score, old_key); + } + + priv->score = g_list_insert_sorted (priv->score, key, + sort_actor_prop_progress_func); +} + /** * clutter_animator_set_key: * @animator: a #ClutterAnimator * @object: a #GObject * @property_name: the property to specify a key for * @mode: the id of the alpha function to use - * @progress: at which stage of the animation this value applies (range 0.0-1.0) + * @progress: the normalized range at which stage of the animation this + * value applies * @value: the value property_name should have at progress. * - * As clutter_animator_set but only for a single key. + * Sets a single key in the #ClutterAnimator for the @property_name of + * @object at @progress. + * + * See also: clutter_animator_set() + * + * Return value: (transfer none): The animator instance * - * Return value: (transfer none): The animator itself. * Since: 1.2 */ ClutterAnimator * @@ -939,7 +981,6 @@ clutter_animator_set_key (ClutterAnimator *animator, { ClutterAnimatorPrivate *priv; ClutterAnimatorKey *animator_key; - GList *old_item; g_return_val_if_fail (CLUTTER_IS_ANIMATOR (animator), NULL); g_return_val_if_fail (G_IS_OBJECT (object), NULL); @@ -949,22 +990,16 @@ clutter_animator_set_key (ClutterAnimator *animator, priv = animator->priv; property_name = g_intern_string (property_name); - animator_key = clutter_animator_key_new (animator, progress, object, mode, - property_name); + animator_key = clutter_animator_key_new (animator, + object, property_name, + progress, + mode); g_value_init (&animator_key->value, G_VALUE_TYPE (value)); g_value_copy (value, &animator_key->value); - if ((old_item = g_list_find_custom (priv->score, animator_key, - sort_actor_prop_progress_func))) - { - ClutterAnimatorKey *old_key = old_item->data; - clutter_animator_key_free (old_key); - animator->priv->score = g_list_remove (animator->priv->score, old_key); - } + clutter_animator_set_key_internal (animator, animator_key); - priv->score = g_list_insert_sorted (priv->score, animator_key, - sort_actor_prop_progress_func); return animator; } @@ -1077,6 +1112,228 @@ again: } } +typedef struct _ParseClosure { + ClutterAnimator *animator; + ClutterScript *script; + + GValue *value; + + gboolean result; +} ParseClosure; + +static ClutterInterpolation +resolve_interpolation (JsonNode *node) +{ + if ((JSON_NODE_TYPE (node) != JSON_NODE_VALUE)) + return CLUTTER_INTERPOLATION_LINEAR; + + if (json_node_get_value_type (node) == G_TYPE_INT64) + { + return json_node_get_int (node); + } + else if (json_node_get_value_type (node) == G_TYPE_STRING) + { + const gchar *str = json_node_get_string (node); + gboolean res; + gint enum_value; + + res = clutter_script_enum_from_string (CLUTTER_TYPE_INTERPOLATION, + str, + &enum_value); + if (res) + return enum_value; + } + + return CLUTTER_INTERPOLATION_LINEAR; +} + +static void +parse_animator_property (JsonArray *array, + guint index_, + JsonNode *element, + gpointer data) +{ + ParseClosure *clos = data; + JsonObject *object; + JsonArray *keys; + GObject *gobject; + const gchar *id, *pname; + GObjectClass *klass; + GParamSpec *pspec; + GSList *valid_keys = NULL; + GList *k; + ClutterInterpolation interpolation = CLUTTER_INTERPOLATION_LINEAR; + gboolean ease_in = FALSE; + + if (JSON_NODE_TYPE (element) != JSON_NODE_OBJECT) + { + g_warning ("The 'properties' member of a ClutterAnimator description " + "should be an array of objects, but the element %d of the " + "array is of type '%s'. The element will be ignored.", + index_, + json_node_type_name (element)); + return; + } + + object = json_node_get_object (element); + + if (!json_object_has_member (object, "object") || + !json_object_has_member (object, "name") || + !json_object_has_member (object, "keys")) + { + g_warning ("The property description at index %d is missing one of " + "the mandatory fields: object, name and keys", + index_); + return; + } + + id = json_object_get_string_member (object, "object"); + gobject = clutter_script_get_object (clos->script, id); + if (gobject == NULL) + { + g_warning ("No object with id '%s' has been defined.", id); + return; + } + + pname = json_object_get_string_member (object, "name"); + klass = G_OBJECT_GET_CLASS (gobject); + pspec = g_object_class_find_property (klass, pname); + if (pspec == NULL) + { + g_warning ("The object of type '%s' and name '%s' has no " + "property named '%s'", + G_OBJECT_TYPE_NAME (gobject), + id, + pname); + return; + } + + if (json_object_has_member (object, "ease-in")) + ease_in = json_object_get_boolean_member (object, "ease-in"); + + if (json_object_has_member (object, "interpolation")) + { + JsonNode *node = json_object_get_member (object, "interpolation"); + + interpolation = resolve_interpolation (node); + } + + keys = json_object_get_array_member (object, "keys"); + if (keys == NULL) + { + g_warning ("The property description at index %d has an invalid " + "key field of type '%s' when an array was expected.", + index_, + json_node_type_name (json_object_get_member (object, "keys"))); + return; + } + + valid_keys = NULL; + for (k = json_array_get_elements (keys); + k != NULL; + k = k->next) + { + JsonNode *node = k->data; + JsonArray *key = json_node_get_array (node); + ClutterAnimatorKey *animator_key; + gdouble progress; + gulong mode; + GValue *value; + gboolean res; + + progress = json_array_get_double_element (key, 0); + mode = clutter_script_resolve_animation_mode (json_array_get_element (key, 1)); + + animator_key = clutter_animator_key_new (clos->animator, + gobject, + pname, + progress, + mode); + value = &animator_key->value; + res = clutter_script_parse_node (clos->script, + value, + pname, + json_array_get_element (key, 2), + pspec); + if (!res) + { + g_warning ("Unable to parse the key value for the " + "property '%s' (progress: %.2f) at index %d", + pname, + progress, + index_); + continue; + } + + animator_key->ease_in = ease_in; + animator_key->interpolation = interpolation; + + valid_keys = g_slist_prepend (valid_keys, animator_key); + } + + g_value_init (clos->value, G_TYPE_POINTER); + g_value_set_pointer (clos->value, g_slist_reverse (valid_keys)); + + clos->result = TRUE; +} + +static gboolean +clutter_animator_parse_custom_node (ClutterScriptable *scriptable, + ClutterScript *script, + GValue *value, + const gchar *name, + JsonNode *node) +{ + ParseClosure parse_closure; + + if (strcmp (name, "properties") != 0) + return FALSE; + + if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY) + return FALSE; + + parse_closure.animator = CLUTTER_ANIMATOR (scriptable); + parse_closure.script = script; + parse_closure.value = value; + parse_closure.result = FALSE; + + json_array_foreach_element (json_node_get_array (node), + parse_animator_property, + &parse_closure); + + /* we return TRUE if we had at least one key parsed */ + + return parse_closure.result; +} + +static void +clutter_animator_set_custom_property (ClutterScriptable *scriptable, + ClutterScript *script, + const gchar *name, + const GValue *value) +{ + if (strcmp (name, "properties") == 0) + { + ClutterAnimator *animator = CLUTTER_ANIMATOR (scriptable); + GSList *keys = g_value_get_pointer (value); + GSList *k; + + for (k = keys; k != NULL; k = k->next) + clutter_animator_set_key_internal (animator, k->data); + + g_slist_free (keys); + } + else + g_object_set_property (G_OBJECT (scriptable), name, value); +} + +static void +clutter_scriptable_init (ClutterScriptableIface *iface) +{ + iface->parse_custom_node = clutter_animator_parse_custom_node; + iface->set_custom_property = clutter_animator_set_custom_property; +} + static void clutter_animator_set_property (GObject *gobject, guint prop_id, diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index 7b35d27..9c7c57f 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -45,6 +45,7 @@ test_conformance_SOURCES = \ test-script-parser.c \ test-actor-destroy.c \ test-behaviours.c \ + test-animator.c \ $(NULL) # For convenience, this provides a way to easily run individual unit tests: diff --git a/tests/conform/test-animator.c b/tests/conform/test-animator.c new file mode 100644 index 0000000..da17e31 --- /dev/null +++ b/tests/conform/test-animator.c @@ -0,0 +1,88 @@ +#include + +#include "test-conform-common.h" + +void +test_animator_properties (TestConformSimpleFixture *fixture, + gconstpointer dummy) +{ + ClutterScript *script = clutter_script_new (); + GObject *animator = NULL; + GError *error = NULL; + gchar *test_file; + GList *keys; + + test_file = clutter_test_get_data_file ("test-animator-2.json"); + clutter_script_load_from_file (script, test_file, &error); + if (g_test_verbose () && error) + g_print ("Error: %s", error->message); + g_assert (error == NULL); + + animator = clutter_script_get_object (script, "animator"); + g_assert (CLUTTER_IS_ANIMATOR (animator)); + + /* get all the keys */ + keys = clutter_animator_get_keys (CLUTTER_ANIMATOR (animator), + NULL, NULL, -1.0); + g_assert_cmpint (g_list_length (keys), ==, 3); + + { + const ClutterAnimatorKey *key = g_list_nth_data (keys, 1); + GValue value = { 0, }; + + g_assert (key != NULL); + + if (g_test_verbose ()) + { + g_print ("keys[1] = \n" + ".object = %s\n" + ".progress = %.2f\n" + ".name = '%s'\n" + ".type = '%s'\n", + clutter_get_script_id (clutter_animator_key_get_object (key)), + clutter_animator_key_get_progress (key), + clutter_animator_key_get_property_name (key), + g_type_name (clutter_animator_key_get_property_type (key))); + } + + g_assert (clutter_animator_key_get_object (key) != NULL); + g_assert_cmpfloat (clutter_animator_key_get_progress (key), ==, 0.2); + g_assert_cmpstr (clutter_animator_key_get_property_name (key), ==, "x"); + + g_assert (clutter_animator_key_get_property_type (key) == G_TYPE_FLOAT); + + g_value_init (&value, G_TYPE_FLOAT); + g_assert (clutter_animator_key_get_value (key, &value)); + g_assert_cmpfloat (g_value_get_float (&value), ==, 150.0); + } + + g_list_free (keys); + g_object_unref (script); + g_free (test_file); +} + +void +test_animator_base (TestConformSimpleFixture *fixture, + gconstpointer dummy) +{ + ClutterScript *script = clutter_script_new (); + GObject *animator = NULL; + GError *error = NULL; + guint duration = 0; + gchar *test_file; + + test_file = clutter_test_get_data_file ("test-animator-1.json"); + clutter_script_load_from_file (script, test_file, &error); + if (g_test_verbose () && error) + g_print ("Error: %s", error->message); + g_assert (error == NULL); + + animator = clutter_script_get_object (script, "animator"); + g_assert (CLUTTER_IS_ANIMATOR (animator)); + + duration = clutter_animator_get_duration (CLUTTER_ANIMATOR (animator)); + g_assert_cmpint (duration, ==, 1000); + + g_object_unref (script); + g_free (test_file); +} diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index 836cbff..0779173 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -177,6 +177,8 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/script", test_script_object_property); TEST_CONFORM_SIMPLE ("/script", test_script_animation); TEST_CONFORM_SIMPLE ("/script", test_script_named_object); + TEST_CONFORM_SIMPLE ("/script", test_animator_base); + TEST_CONFORM_SIMPLE ("/script", test_animator_properties); TEST_CONFORM_SIMPLE ("/behaviours", test_behaviours); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 335f8f3..3236b26 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -8,6 +8,8 @@ json_files = \ test-script-named-object.json \ test-script-object-property.json \ test-script-single.json \ + test-animator-1.json \ + test-animator-2.json \ $(NULL) png_files = \ diff --git a/tests/data/test-animator-1.json b/tests/data/test-animator-1.json new file mode 100644 index 0000000..2d6aab9 --- /dev/null +++ b/tests/data/test-animator-1.json @@ -0,0 +1,5 @@ +{ + "type" : "ClutterAnimator", + "id" : "animator", + "duration" : 1000 +} diff --git a/tests/data/test-animator-2.json b/tests/data/test-animator-2.json new file mode 100644 index 0000000..9059f57 --- /dev/null +++ b/tests/data/test-animator-2.json @@ -0,0 +1,29 @@ +[ + { + "type" : "ClutterRectangle", + "id" : "foo", + "x" : 0, + "y" : 0, + "width" : 100, + "height" : 100 + }, + { + "type" : "ClutterAnimator", + "id" : "animator", + "duration" : 1000, + + "properties" : [ + { + "object" : "foo", + "name" : "x", + "ease-in" : true, + "interpolation" : "linear", + "keys" : [ + [ 0.0, "easeInCubic", 100.0 ], + [ 0.2, "easeOutCubic", 150.0 ], + [ 0.8, "linear", 200.0 ] + ] + } + ] + } +] -- 2.7.4