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.
This commit is contained in:
Emmanuele Bassi 2010-02-08 15:52:18 +00:00
parent 790a13c0d9
commit 4dd11d6915
8 changed files with 409 additions and 23 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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,

View File

@ -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:

View File

@ -0,0 +1,88 @@
#include <clutter/clutter.h>
#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);
}

View File

@ -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);

View File

@ -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 = \

View File

@ -0,0 +1,5 @@
{
"type" : "ClutterAnimator",
"id" : "animator",
"duration" : 1000
}

View File

@ -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 ]
]
}
]
}
]