From 89e3e3a4cc06c10c2573f3d115c5b03ee4890524 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 9 Mar 2009 21:11:40 +0000 Subject: [PATCH] [animation] Add vector variants for ::animate() Bug 1438 - Implicit Animation API could use animatev variants The clutter_actor_animate* family of functions use va_lists to handle the property/value pairs for the final state of the animation. Language bindings have problems with variadic arguments functions, and usually prefer vector-based API which allow a greater level of control and conversion from native data types. For each variadic arguments function in the clutter_actor_animate* family there should be a vector-based version that takes: - the number of property/value pairs - a constant array of constant strings - an array of GValues Most of the internal implementation can be refactored from the current one, thus both the var_args and the vector entry points share a common implementation of the code; then, both versions of the API are just loops over a list of arguments. Based on a patch by: Robert Carr --- clutter/clutter-animation.c | 418 +++++++++++++++++---- clutter/clutter-animation.h | 18 + doc/reference/clutter/clutter-sections.txt | 3 + 3 files changed, 374 insertions(+), 65 deletions(-) diff --git a/clutter/clutter-animation.c b/clutter/clutter-animation.c index 0ff2d073e..a5df67bb3 100644 --- a/clutter/clutter-animation.c +++ b/clutter/clutter-animation.c @@ -1135,7 +1135,7 @@ clutter_animation_get_alpha (ClutterAnimation *animation) static void clutter_animation_start (ClutterAnimation *animation) { - if (animation->priv->timeline) + if (G_LIKELY (animation->priv->timeline)) clutter_timeline_start (animation->priv->timeline); else { @@ -1144,7 +1144,146 @@ clutter_animation_start (ClutterAnimation *animation) } } -static inline void +static void +clutter_animation_setup_property (ClutterAnimation *animation, + const gchar *property_name, + const GValue *value, + GParamSpec *pspec) +{ + ClutterAnimationPrivate *priv = animation->priv; + gboolean is_fixed = FALSE; + GValue real_value = { 0, }; + + /* fixed properties will not be animated */ + if (g_str_has_prefix (property_name, "fixed::")) + { + is_fixed = TRUE; + property_name += 7; /* strlen("fixed::") */ + } + + if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) + { + g_warning ("Cannot bind property `%s': the property is " + "construct-only", + property_name); + return; + } + + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("Cannot bind property `%s': the property is " + "not writable", + property_name); + return; + } + + /* initialize the real value that will be used to store the + * final state of the animation + */ + g_value_init (&real_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + + /* if it's not the same type of the GParamSpec value, try to + * convert it using the GValue transformation API, otherwise + * just copy it + */ + if (!g_type_is_a (G_VALUE_TYPE (value), G_VALUE_TYPE (&real_value))) + { + if (!g_value_type_compatible (G_VALUE_TYPE (value), + G_VALUE_TYPE (&real_value)) && + !g_value_type_compatible (G_VALUE_TYPE (&real_value), + G_VALUE_TYPE (value))) + { + g_warning ("%s: Unable to convert from %s to %s for " + "the property `%s' of object %s", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (&real_value)), + property_name, + G_OBJECT_TYPE_NAME (priv->object)); + g_value_unset (&real_value); + return; + } + + if (!g_value_transform (value, &real_value)) + { + g_warning ("%s: Unable to transform from %s to %s", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (&real_value))); + g_value_unset (&real_value); + return; + } + } + else + g_value_copy (value, &real_value); + + /* create an interval and bind it to the property, in case + * it's not a fixed property, otherwise just set it + */ + if (G_LIKELY (!is_fixed)) + { + ClutterInterval *interval; + GValue cur_value = { 0, }; + + g_value_init (&cur_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_object_get_property (priv->object, property_name, &cur_value); + + interval = + clutter_interval_new_with_values (G_PARAM_SPEC_VALUE_TYPE (pspec), + &cur_value, + &real_value); + + if (!clutter_animation_has_property (animation, pspec->name)) + clutter_animation_bind_property_internal (animation, + pspec, + interval); + else + clutter_animation_update_property_internal (animation, + pspec, + interval); + + g_value_unset (&cur_value); + } + else + g_object_set_property (priv->object, property_name, &real_value); + + g_value_unset (&real_value); +} + +static void +clutter_animation_setupv (ClutterAnimation *animation, + gint n_properties, + const gchar * const properties[], + const GValue *values) +{ + ClutterAnimationPrivate *priv = animation->priv; + GObjectClass *klass; + gint i; + + klass = G_OBJECT_GET_CLASS (priv->object); + + for (i = 0; i < n_properties; i++) + { + const gchar *property_name = properties[i]; + GParamSpec *pspec; + + pspec = g_object_class_find_property (klass, property_name); + if (!pspec) + { + g_warning ("Cannot bind property `%s': objects of type `%s' do " + "not have this property", + property_name, + g_type_name (G_OBJECT_TYPE (priv->object))); + break; + } + + clutter_animation_setup_property (animation, property_name, + &values[i], + pspec); + } +} + +static void clutter_animation_setup_valist (ClutterAnimation *animation, const gchar *first_property_name, va_list var_args) @@ -1158,18 +1297,10 @@ clutter_animation_setup_valist (ClutterAnimation *animation, property_name = first_property_name; while (property_name != NULL) { + GParamSpec *pspec; GValue final = { 0, }; - GParamSpec *pspec = NULL; - gboolean is_fixed = FALSE; gchar *error = NULL; - /* fixed properties will not be animated */ - if (g_str_has_prefix (property_name, "fixed::")) - { - is_fixed = TRUE; - property_name += 7; - } - pspec = g_object_class_find_property (klass, property_name); if (!pspec) { @@ -1180,22 +1311,6 @@ clutter_animation_setup_valist (ClutterAnimation *animation, break; } - if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) - { - g_warning ("Cannot bind property `%s': the property is " - "construct-only", - property_name); - break; - } - - if (!(pspec->flags & G_PARAM_WRITABLE)) - { - g_warning ("Cannot bind property `%s': the property is " - "not writable", - property_name); - break; - } - g_value_init (&final, G_PARAM_SPEC_VALUE_TYPE (pspec)); G_VALUE_COLLECT (&final, var_args, 0, &error); if (error) @@ -1205,43 +1320,12 @@ clutter_animation_setup_valist (ClutterAnimation *animation, break; } - /* create an interval and bind it to the property, in case - * it's not a fixed property, otherwise just set it - */ - if (G_LIKELY (!is_fixed)) - { - ClutterInterval *interval; - GValue initial = { 0, }; - - g_value_init (&initial, G_PARAM_SPEC_VALUE_TYPE (pspec)); - g_object_get_property (priv->object, property_name, &initial); - - interval = - clutter_interval_new_with_values (G_PARAM_SPEC_VALUE_TYPE (pspec), - &initial, - &final); - - if (!clutter_animation_has_property (animation, pspec->name)) - clutter_animation_bind_property_internal (animation, - pspec, - interval); - else - clutter_animation_update_property_internal (animation, - pspec, - interval); - - g_value_unset (&initial); - } - else - g_object_set_property (priv->object, property_name, &final); - - g_value_unset (&final); + clutter_animation_setup_property (animation, property_name, + &final, + pspec); property_name = va_arg (var_args, gchar*); } - - /* start the animation by default */ - clutter_animation_start (animation); } /** @@ -1305,6 +1389,8 @@ clutter_actor_animate_with_alpha (ClutterActor *actor, clutter_animation_setup_valist (animation, first_property_name, args); va_end (args); + clutter_animation_start (animation); + return animation; } @@ -1364,6 +1450,8 @@ clutter_actor_animate_with_timeline (ClutterActor *actor, clutter_animation_setup_valist (animation, first_property_name, args); va_end (args); + clutter_animation_start (animation); + return animation; } @@ -1403,14 +1491,13 @@ clutter_actor_animate_with_timeline (ClutterActor *actor, * |[ * clutter_actor_animate (actor, CLUTTER_EASE_IN, 100, * "rotation-angle-z", 360, - * "fixed::rotation-center-x", 100, - * "fixed::rotation-center-y", 100, + * "fixed::rotation-center-z", &center, * NULL); * ]| * * Will animate the "rotation-angle-z" property between the current value - * and 360 degrees, and set the "rotation-center-x" and "rotation-center-y" - * to the fixed value of 100 pixels. + * and 360 degrees, and set the "rotation-center-z" property to the fixed + * value of the #ClutterVertex "center". * * This function will implicitly create a #ClutterAnimation object which * will be assigned to the @actor and will be returned to the developer @@ -1496,5 +1583,206 @@ clutter_actor_animate (ClutterActor *actor, clutter_animation_setup_valist (animation, first_property_name, args); va_end (args); + clutter_animation_start (animation); + + return animation; +} + +/** + * clutter_actor_animatev: + * @actor: a #ClutterActor + * @mode: an animation mode logical id + * @duration: duration of the animation, in milliseconds + * @n_properties: number of property names and values + * @properties: (array length=n_properties): a vector containing the + * property names to set + * @values: (array length=n_properies): a vector containing the + * property values to set + * + * Animates the given list of properties of @actor between the current + * value for each property and a new final value. The animation has a + * definite duration and a speed given by the @mode. + * + * This is the vector-based variant of clutter_actor_animate(), useful + * for language bindings. + * + * Return value: (transfer none): a #ClutterAnimation object. The object is + * owned by the #ClutterActor and should not be unreferenced with + * g_object_unref() + * + * Since: 1.0 + */ +ClutterAnimation * +clutter_actor_animatev (ClutterActor *actor, + gulong mode, + guint duration, + gint n_properties, + const gchar * const properties[], + const GValue *values) +{ + ClutterAnimation *animation; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + g_return_val_if_fail (mode != CLUTTER_CUSTOM_MODE, NULL); + g_return_val_if_fail (duration > 0, NULL); + g_return_val_if_fail (properties != NULL, NULL); + g_return_val_if_fail (values != NULL, NULL); + + animation = g_object_get_qdata (G_OBJECT (actor), quark_object_animation); + if (G_LIKELY (!animation)) + { + /* if there is no animation already attached to the actor, + * create one and set up the timeline and alpha using the + * current values for duration, mode and loop + */ + animation = clutter_animation_new (); + clutter_animation_set_timeline (animation, NULL); + clutter_animation_set_alpha (animation, NULL); + clutter_animation_set_object (animation, G_OBJECT (actor)); + + CLUTTER_NOTE (ANIMATION, "Created new Animation [%p]", animation); + } + else + CLUTTER_NOTE (ANIMATION, "Reusing Animation [%p]", animation); + + /* force the update of duration and mode using the new + * values coming from the parameters of this function + */ + clutter_animation_set_duration (animation, duration); + clutter_animation_set_mode (animation, mode); + clutter_animation_setupv (animation, n_properties, properties, values); + clutter_animation_start (animation); + + return animation; +} + +/** + * clutter_actor_animate_with_timelinev: + * @actor: a #ClutterActor + * @mode: an animation mode logical id + * @timeline: a #ClutterTimeline + * @n_properties: number of property names and values + * @properties: (array length=n_properties): a vector containing the + * property names to set + * @values: (array length=n_properies): a vector containing the + * property values to set + * + * Animates the given list of properties of @actor between the current + * value for each property and a new final value. The animation has a + * definite duration given by @timeline and a speed given by the @mode. + * + * See clutter_actor_animate() for further details. + * + * This function is useful if you want to use an existing timeline + * to animate @actor. + * + * This is the vector-based variant of clutter_actor_animate_with_timeline(), + * useful for language bindings. + * + * Return value: (transfer none): a #ClutterAnimation object. The object is + * owned by the #ClutterActor and should not be unreferenced with + * g_object_unref() + * + * Since: 1.0 + */ +ClutterAnimation * +clutter_actor_animate_with_timelinev (ClutterActor *actor, + gulong mode, + ClutterTimeline *timeline, + gint n_properties, + const gchar * const properties[], + const GValue *values) +{ + ClutterAnimation *animation; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL); + g_return_val_if_fail (properties != NULL, NULL); + g_return_val_if_fail (values != NULL, NULL); + + animation = g_object_get_qdata (G_OBJECT (actor), quark_object_animation); + if (G_LIKELY (!animation)) + { + animation = clutter_animation_new (); + CLUTTER_NOTE (ANIMATION, "Created new Animation [%p]", animation); + } + else + CLUTTER_NOTE (ANIMATION, "Reusing Animation [%p]", animation); + + clutter_animation_set_timeline (animation, timeline); + clutter_animation_set_alpha (animation, NULL); + clutter_animation_set_mode (animation, mode); + clutter_animation_set_object (animation, G_OBJECT (actor)); + clutter_animation_setupv (animation, n_properties, properties, values); + clutter_animation_start (animation); + + return animation; +} + +/** + * clutter_actor_animate_with_alphav: + * @actor: a #ClutterActor + * @alpha: a #ClutterAlpha + * @n_properties: number of property names and values + * @properties: (array length=n_properties): a vector containing the + * property names to set + * @values: (array length=n_properies): a vector containing the + * property values to set + * + * Animates the given list of properties of @actor between the current + * value for each property and a new final value. The animation has a + * definite behaviour given by the passed @alpha. + * + * See clutter_actor_animate() for further details. + * + * This function is useful if you want to use an existing #ClutterAlpha + * to animate @actor. + * + * This is the vector-based variant of clutter_actor_animate_with_alpha(), + * useful for language bindings. + * + * Return value: (transfer none): a #ClutterAnimation object. The object is owned by the + * #ClutterActor and should not be unreferenced with g_object_unref() + * + * Since: 1.0 + */ +ClutterAnimation * +clutter_actor_animate_with_alphav (ClutterActor *actor, + ClutterAlpha *alpha, + gint n_properties, + const gchar * const properties[], + const GValue *values) +{ + ClutterAnimation *animation; + ClutterTimeline *timeline; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + g_return_val_if_fail (CLUTTER_IS_ALPHA (alpha), NULL); + g_return_val_if_fail (properties != NULL, NULL); + g_return_val_if_fail (values != NULL, NULL); + + timeline = clutter_alpha_get_timeline (alpha); + if (G_UNLIKELY (!timeline)) + { + g_warning ("The passed ClutterAlpha does not have an " + "associated ClutterTimeline."); + return NULL; + } + + animation = g_object_get_qdata (G_OBJECT (actor), quark_object_animation); + if (G_LIKELY (!animation)) + { + animation = clutter_animation_new (); + CLUTTER_NOTE (ANIMATION, "Created new Animation [%p]", animation); + } + else + CLUTTER_NOTE (ANIMATION, "Reusing Animation [%p]", animation); + + clutter_animation_set_timeline (animation, timeline); + clutter_animation_set_alpha (animation, alpha); + clutter_animation_set_object (animation, G_OBJECT (actor)); + clutter_animation_setupv (animation, n_properties, properties, values); + clutter_animation_start (animation); + return animation; } diff --git a/clutter/clutter-animation.h b/clutter/clutter-animation.h index 1b7e42656..a73eeea76 100644 --- a/clutter/clutter-animation.h +++ b/clutter/clutter-animation.h @@ -144,6 +144,24 @@ ClutterAnimation * clutter_actor_animate_with_alpha (ClutterActor * const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED; +ClutterAnimation * clutter_actor_animatev (ClutterActor *actor, + gulong mode, + guint duration, + gint n_properties, + const gchar * const properties[], + const GValue *values); +ClutterAnimation * clutter_actor_animate_with_timelinev (ClutterActor *actor, + gulong mode, + ClutterTimeline *timeline, + gint n_properties, + const gchar * const properties[], + const GValue *values); +ClutterAnimation * clutter_actor_animate_with_alphav (ClutterActor *actor, + ClutterAlpha *alpha, + gint n_properties, + const gchar * const properties[], + const GValue *values); + G_END_DECLS #endif /* __CLUTTER_ANIMATION_H__ */ diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 49ca6daa6..3ed16085b 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -1424,6 +1424,9 @@ clutter_animation_get_interval clutter_actor_animate clutter_actor_animate_with_timeline clutter_actor_animate_with_alpha +clutter_actor_animatev +clutter_actor_animate_with_timelinev +clutter_actor_animate_with_alphav CLUTTER_TYPE_ANIMATION