animator: Provide a ClutterScript parser
authorEmmanuele Bassi <ebassi@linux.intel.com>
Mon, 8 Feb 2010 15:52:18 +0000 (15:52 +0000)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Mon, 8 Feb 2010 15:52:18 +0000 (15:52 +0000)
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
clutter/clutter-animator.c
tests/conform/Makefile.am
tests/conform/test-animator.c [new file with mode: 0644]
tests/conform/test-conform-main.c
tests/data/Makefile.am
tests/data/test-animator-1.json [new file with mode: 0644]
tests/data/test-animator-2.json [new file with mode: 0644]

index a1d1649..3b1a150 100644 (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
index ae5a775..86f6ebd 100644 (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,
index 7b35d27..9c7c57f 100644 (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:
diff --git a/tests/conform/test-animator.c b/tests/conform/test-animator.c
new file mode 100644 (file)
index 0000000..da17e31
--- /dev/null
@@ -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);
+}
index 836cbff..0779173 100644 (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);
 
index 335f8f3..3236b26 100644 (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 = \
diff --git a/tests/data/test-animator-1.json b/tests/data/test-animator-1.json
new file mode 100644 (file)
index 0000000..2d6aab9
--- /dev/null
@@ -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 (file)
index 0000000..9059f57
--- /dev/null
@@ -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 ]
+        ]
+      }
+    ]
+  }
+]