From fcdc3a8989dee335bec5788c5fbe785e41acd70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Tue, 11 May 2010 15:04:34 +0100 Subject: [PATCH 01/14] state machine: added state machine --- clutter/Makefile.am | 2 + clutter/clutter-state.c | 1310 +++++++++++++++++++++++ clutter/clutter-state.h | 156 +++ clutter/clutter.h | 1 + tests/interactive/Makefile.am | 2 + tests/interactive/test-state-animator.c | 134 +++ tests/interactive/test-state.c | 97 ++ 7 files changed, 1702 insertions(+) create mode 100644 clutter/clutter-state.c create mode 100644 clutter/clutter-state.h create mode 100644 tests/interactive/test-state-animator.c create mode 100644 tests/interactive/test-state.c diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 71ab39271..9626989f6 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -115,6 +115,7 @@ source_h = \ $(srcdir)/clutter-stage.h \ $(srcdir)/clutter-stage-manager.h \ $(srcdir)/clutter-stage-window.h \ + $(srcdir)/clutter-state.h \ $(srcdir)/clutter-texture.h \ $(srcdir)/clutter-text.h \ $(srcdir)/clutter-timeline.h \ @@ -191,6 +192,7 @@ source_c = \ $(srcdir)/clutter-stage.c \ $(srcdir)/clutter-stage-manager.c \ $(srcdir)/clutter-stage-window.c \ + $(srcdir)/clutter-state.c \ $(srcdir)/clutter-texture.c \ $(srcdir)/clutter-text.c \ $(srcdir)/clutter-timeline.c \ diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c new file mode 100644 index 000000000..77da22f1f --- /dev/null +++ b/clutter/clutter-state.c @@ -0,0 +1,1310 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Øyvind Kolås + * + * Copyright (C) 2009 Intel Corporation + * + * 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, see . + */ + +/** + * SECTION:clutter-state + * @short_description: State machine with animated transitions + * + * #ClutterState controls the tweening of properties on multiple + * actors between a set of named states. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-private.h" +#include "clutter-state.h" +#include +#include + +G_DEFINE_TYPE (ClutterState, clutter_state, G_TYPE_OBJECT); + +typedef struct StateAnimator { + const gchar *source_state_name; + ClutterAnimator *animator; +} StateAnimator; + +typedef struct State { + ClutterState *state; + const gchar *name; + GHashTable *durations; /* contains state objects */ + GList *keys; /* list of all keys pertaining to transitions + from other states to this one */ + GArray *animators; /* list of animators for transitioning from + * specific source states + */ +} State; + +struct _ClutterStatePrivate +{ + ClutterTimeline *timeline; + ClutterTimeline *slave_timeline; + const gchar *source_state_name; + const gchar *target_state_name; + State *source_state; + State *target_state; + GHashTable *states; /* contains state objects */ + + ClutterAnimator *current_animator; + guint duration; /* global fallback duration */ +}; + +#define SLAVE_TIMELINE_LENGTH 10000 + +/** + * ClutterStateKey: + * + * An opaque data structure with accessor functions. + * + */ +typedef struct _ClutterStateKey +{ + GObject *object; + const gchar *property_name; + gulong mode; + GValue value; + gdouble pre_delay; + gdouble post_delay; + + State *source_state; + State *target_state; + ClutterAlpha *alpha; + ClutterInterval *interval; + + ClutterState *state; + + guint is_inert : 1; + gint ref_count; +} _ClutterStateKey; + + +#define CLUTTER_STATE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + CLUTTER_TYPE_STATE, \ + ClutterStatePrivate)) + +/** + * clutter_state_new: + * + * Create a new #ClutterState instance. + * + * Returns: a new #ClutterState. + */ +ClutterState * +clutter_state_new (void) +{ + return g_object_new (CLUTTER_TYPE_STATE, NULL); +} + +static State * state_new (ClutterState *state, + const gchar *name); + +static gint sort_props_func (gconstpointer a, + gconstpointer b) +{ + const ClutterStateKey *pa = a; + const ClutterStateKey *pb = b; + + if (pa->object == pb->object) + { + gint propnamediff = pa->property_name-pb->property_name; + if (propnamediff == 0) + { + return pb->source_state - pa->source_state; + } + return propnamediff; + } + return pa->object - pb->object; +} + +static void object_disappeared (gpointer data, + GObject *where_the_object_was); + +static ClutterStateKey * +clutter_state_key_new (State *state, + GObject *object, + const gchar *property_name, + guint mode) +{ + ClutterStateKey *state_key; + GParamSpec *pspec; + GObjectClass *klass = G_OBJECT_GET_CLASS (object); + GValue value = {0, }; + + state_key = g_slice_new0 (ClutterStateKey); + state_key->target_state = state; + state_key->object = object; + state_key->property_name = g_intern_string (property_name); + state_key->mode = mode; + state_key->alpha = clutter_alpha_new (); + clutter_alpha_set_mode (state_key->alpha, mode); + clutter_alpha_set_timeline (state_key->alpha, + state->state->priv->slave_timeline); + + pspec = g_object_class_find_property (klass, property_name); + + state_key->interval = + g_object_new (CLUTTER_TYPE_INTERVAL, + "value-type", G_PARAM_SPEC_VALUE_TYPE (pspec), + NULL); + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + clutter_interval_set_initial_value (state_key->interval, &value); + clutter_interval_set_final_value (state_key->interval, &value); + g_value_unset (&value); + + g_object_ref_sink (state_key->alpha); + g_object_ref_sink (state_key->interval); + + g_object_weak_ref (object, object_disappeared, + state_key->target_state->state); + + return (void*)state_key; +} + +static void +clutter_state_key_free (gpointer clutter_state_key) +{ + ClutterStateKey *key = clutter_state_key; + + if (key == NULL) + return; + + key->ref_count -= 1; + + if (key->ref_count > 0) + return; + + if (!key->is_inert) + g_object_weak_unref (key->object, object_disappeared, + key->target_state->state); + g_object_unref (key->alpha); + g_object_unref (key->interval); + + g_slice_free (ClutterStateKey, key); +} + + +static void +clutter_state_remove_key_internal (ClutterState *this, + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name, + gboolean is_inert) +{ + GList *s, *state_list; + + State *source_state = NULL; + + g_return_if_fail (CLUTTER_IS_STATE (this)); + + source_state_name = g_intern_string (source_state_name); + target_state_name = g_intern_string (target_state_name); + property_name = g_intern_string (property_name); + + if (source_state_name) + source_state = g_hash_table_lookup (this->priv->states, + source_state_name); + + if (target_state_name != NULL) + { + state_list = g_list_append (NULL, (void*)target_state_name); + } + else + { + state_list = clutter_state_get_states (this); + } + + for (s = state_list; s; s=s->next) + { + State *target_state; + target_state = g_hash_table_lookup (this->priv->states, s->data); + + if (target_state) + { + GList *k; + + for (k = target_state->keys; k; k = k->next) + { + ClutterStateKey *key = k->data; + + if ((object == NULL || (object == key->object)) && + (source_state == NULL || + (source_state == key->source_state)) && + (property_name == NULL || + ((property_name == key->property_name)))) + { + key->is_inert = is_inert; + clutter_state_key_free (key); + k = target_state->keys = g_list_remove (target_state->keys, + key); + } + } + } + } + g_list_free (state_list); +} + +static void object_disappeared (gpointer data, + GObject *where_the_object_was) +{ + clutter_state_remove_key_internal (data, NULL, NULL, + (void*)where_the_object_was, NULL, TRUE); +} + + +static void state_free (gpointer data) +{ + State *state = data; + for (; state->keys; + state->keys = g_list_remove (state->keys, state->keys->data)) + clutter_state_key_free (state->keys->data); + g_array_unref (state->animators); + g_hash_table_destroy (state->durations); + g_free (state); +} + +static State *state_new (ClutterState *this, + const gchar *name) +{ + State *state; + state = g_new0 (State, 1); + state->state = this; + state->name = name; + state->animators = g_array_new (TRUE, TRUE, sizeof (StateAnimator)); + state->durations = g_hash_table_new (g_direct_hash, g_direct_equal); + return state; +} + +static void +clutter_state_finalize (GObject *object) +{ + ClutterState *state = CLUTTER_STATE (object); + g_hash_table_destroy (state->priv->states); + g_object_unref (state->priv->timeline); + g_object_unref (state->priv->slave_timeline); + + G_OBJECT_CLASS (clutter_state_parent_class)->finalize (object); +} + +static void clutter_state_completed (ClutterTimeline *timeline, + ClutterState *state) +{ + if (state->priv->current_animator) + { + clutter_animator_set_timeline (state->priv->current_animator, + NULL); + state->priv->current_animator = NULL; + } +} + +static void clutter_state_new_frame (ClutterTimeline *timeline, + gint msecs, + ClutterState *state) +{ + GList *k; + gdouble progress; + const gchar *curprop = NULL; + GObject *curobj = NULL; + gboolean found_specific = FALSE; + + if (state->priv->current_animator) + return; + + progress = 1.0 * msecs / clutter_timeline_get_duration (timeline); + + for (k = state->priv->target_state->keys; k; k = k->next) + { + ClutterStateKey *key = k->data; + GValue value = {0,}; + gdouble sub_progress; + + if ((curprop && !(curprop == key->property_name)) || + key->object != curobj) + { + curprop = key->property_name; + curobj = key->object; + found_specific = FALSE; + } + + if (!found_specific) + { + if (key->source_state && + key->source_state->name && + g_str_equal (state->priv->source_state_name, + key->source_state->name)) + { + found_specific = TRUE; + } + + if (found_specific || key->source_state == NULL) + { + sub_progress = (progress - key->pre_delay) / + (1.0 - (key->pre_delay + key->post_delay)); + + if (sub_progress >= 0.0) + { + if (sub_progress >= 1.0) + sub_progress = 1.0; + clutter_timeline_advance (state->priv->slave_timeline, + sub_progress * SLAVE_TIMELINE_LENGTH); + sub_progress = clutter_alpha_get_alpha (key->alpha); + + g_value_init (&value, + clutter_interval_get_value_type (key->interval)); + clutter_interval_compute_value (key->interval, sub_progress, + &value); + g_object_set_property (key->object, key->property_name, + &value); + g_value_unset (&value); + } + /* XXX: should the target value of the default destination be + * used even when found a specific source_state key? + */ + } + } + } +} + + +/** + * clutter_state_change_noanim: + * @state_name: a #ClutterState + * + * Change to @state_name and spend duration msecs when doing so. + * + * Return value: the #ClutterTimeline that drives the #ClutterState instance. + */ +ClutterTimeline * +clutter_state_change_noanim (ClutterState *this, + const gchar *target_state_name) +{ + ClutterStatePrivate *priv = this->priv; + State *state; + + g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); + g_return_val_if_fail (target_state_name, NULL); + + if (target_state_name == NULL) + target_state_name = "default"; + target_state_name = g_intern_string (target_state_name); + if (priv->target_state_name == NULL) + priv->target_state_name = g_intern_static_string ("default"); + + if (priv->current_animator) + { + clutter_animator_set_timeline (priv->current_animator, NULL); + priv->current_animator = NULL; + } + + priv->source_state_name = priv->target_state_name; + priv->target_state_name = target_state_name; + + clutter_timeline_set_duration (priv->timeline, 1); + + state = g_hash_table_lookup (priv->states, target_state_name); + + g_return_val_if_fail (state, NULL); + + { + ClutterAnimator *animator; + animator = clutter_state_get_animator (this, priv->source_state_name, + priv->target_state_name); + if (animator) + { + priv->current_animator = animator; + clutter_animator_set_timeline (animator, priv->timeline); + clutter_timeline_stop (priv->timeline); + clutter_timeline_rewind (priv->timeline); + clutter_timeline_start (priv->timeline); + return priv->timeline; + } + } + + if (state) + { + GList *k; + + for (k = state->keys; k; k = k->next) + { + ClutterStateKey *key = k->data; + GValue initial = {0,}; + + g_value_init (&initial, + clutter_interval_get_value_type (key->interval)); + + g_object_get_property (key->object, + key->property_name, &initial); + if (clutter_alpha_get_mode (key->alpha) != key->mode) + clutter_alpha_set_mode (key->alpha, key->mode); + clutter_interval_set_initial_value (key->interval, &initial); + clutter_interval_set_final_value (key->interval, &key->value); + + g_value_unset (&initial); + } + + priv->target_state = state; + clutter_timeline_rewind (priv->timeline); + clutter_timeline_start (priv->timeline); + } + else + { + g_warning ("Anim state '%s' not found\n", target_state_name); + } + + return priv->timeline; +} + + +/** + * clutter_state_change: + * @state_name: a #ClutterState + * + * Change to @state_name and spend duration msecs when doing so. + * + * Return value: the #ClutterTimeline that drives the #ClutterState instance. + */ +ClutterTimeline * +clutter_state_change (ClutterState *this, + const gchar *target_state_name) +{ + ClutterStatePrivate *priv = this->priv; + State *state; + + g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); + g_return_val_if_fail (target_state_name, NULL); + + + if (target_state_name == NULL) + target_state_name = "default"; + target_state_name = g_intern_string (target_state_name); + if (priv->target_state_name == NULL) + priv->target_state_name = g_intern_static_string ("default"); + + if (target_state_name == priv->target_state_name) + { + /* Avoiding transitioning if the desired state + * is already current + */ + return priv->timeline; + } + + if (priv->current_animator) + { + clutter_animator_set_timeline (priv->current_animator, NULL); + priv->current_animator = NULL; + } + + priv->source_state_name = priv->target_state_name; + priv->target_state_name = target_state_name; + + clutter_timeline_set_duration (priv->timeline, + clutter_state_get_duration (this, priv->source_state_name, + priv->target_state_name)); + + state = g_hash_table_lookup (priv->states, target_state_name); + + g_return_val_if_fail (state, NULL); + + { + ClutterAnimator *animator; + animator = clutter_state_get_animator (this, priv->source_state_name, + priv->target_state_name); + if (animator) + { + priv->current_animator = animator; + clutter_animator_set_timeline (animator, priv->timeline); + clutter_timeline_stop (priv->timeline); + clutter_timeline_rewind (priv->timeline); + clutter_timeline_start (priv->timeline); + return priv->timeline; + } + } + + if (state) + { + GList *k; + + for (k = state->keys; k; k = k->next) + { + ClutterStateKey *key = k->data; + GValue initial = {0,}; + + g_value_init (&initial, + clutter_interval_get_value_type (key->interval)); + + g_object_get_property (key->object, + key->property_name, &initial); + if (clutter_alpha_get_mode (key->alpha) != key->mode) + clutter_alpha_set_mode (key->alpha, key->mode); + clutter_interval_set_initial_value (key->interval, &initial); + clutter_interval_set_final_value (key->interval, &key->value); + + g_value_unset (&initial); + } + + priv->target_state = state; + clutter_timeline_rewind (priv->timeline); + clutter_timeline_start (priv->timeline); + } + else + { + g_warning ("Anim state '%s' not found\n", target_state_name); + } + + return priv->timeline; +} + + +/** + * clutter_state_set: + * @state: a #ClutterState instance. + * @source_state_name: the name of the source state keys are being added for. + * @target_state_name: the name of the target state keys are being added for. + * @first_object: a #GObject + * @first_property_name: the property to specify a key for + * @first_mode: the id of the alpha function to use + * @...: the value first_property_name should have in state_name, followed by + * more object,property_name,mode,value,... or NULL to terminate the varargs. + * + * Adds multiple keys to a named state of a #ClutterState instance, specifying + * the easing mode and value a given property of an object should have at a + * given progress of the animation. The mode specified is the mode used when + * going to this key from the previous key of the property_name, If a given + * object, state_name, property triple already exist the mode and value will be + * replaced with the new values for the existing key. If a the property_name is + * prefixed with "delayed::" two additional arguments are expected per key with a + * value relative to the full state time to pause before transitioning and + * after transitioning within the total transition time. + * + * Since: 1.4 + */ +void +clutter_state_set (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name, + gpointer first_object, + const gchar *first_property_name, + gulong first_mode, + ...) +{ + GObjectClass *klass; + gpointer object; + const gchar *property_name; + gulong mode; + va_list args; + + object = first_object; + + property_name = first_property_name; + mode = first_mode; + va_start (args, first_mode); + + g_return_if_fail (G_IS_OBJECT (first_object)); + g_return_if_fail (first_property_name); + + while (object != NULL) + { + GParamSpec *pspec; + GValue value = { 0, }; + gchar *error = NULL; + const gchar *real_property_name = property_name; + klass = G_OBJECT_GET_CLASS (object); + + if (g_str_has_prefix (property_name, "delayed::")) + { + real_property_name = strstr (property_name, "::") + 2; + } + + pspec = g_object_class_find_property (klass, real_property_name); + + if (!pspec) + { + g_warning ("Cannot bind property '%s': object of type '%s' " + "do not have this property", + real_property_name, G_OBJECT_TYPE_NAME (object)); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + G_VALUE_COLLECT (&value, args, 0, &error); + + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + break; + } + + if (g_str_has_prefix (property_name, "delayed::")) + { + gdouble pre_delay = va_arg (args, gdouble); + gdouble post_delay = va_arg (args, gdouble); + clutter_state_set_key (state, + source_state_name, + target_state_name, + object, + real_property_name, + mode, + &value, + pre_delay, + post_delay); + } + else + { + clutter_state_set_key (state, + source_state_name, + target_state_name, + object, + property_name, + mode, + &value, + 0.0, + 0.0); + } + + object = va_arg (args, gpointer); + if (object) + { + property_name = va_arg (args, gchar*); + mode = va_arg (args, gulong); + } + } + + va_end (args); +} + +/** + * clutter_state_set_key: + * @this: a #ClutterState instance. + * @source_state_name: the source transition to specify transition for + * @target_state_name: the name of the transition to set a key value for. + * @object: the #GObject to set a key for + * @property_name: the property to set a key for + * @mode: the id of the alpha function to use + * @value: the value for property_name of object in state_name + * @pre_delay: relative time of the transition to be idle in the beginning of the transition + * @post_delay: relative time of the transition to be idle in the end of the transition + * + * Sets one specific end key for a state_name, object, property_name + * combination. + * + * Returns: the #ClutterState instance, allowing chaining of multiple calls. + * Since: 1.4 + */ +ClutterState * +clutter_state_set_key (ClutterState *this, + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name, + guint mode, + const GValue *value, + gdouble pre_delay, + gdouble post_delay) +{ + ClutterStateKey *state_key; + GList *old_item; + State *source_state = NULL; + State *target_state; + + g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + g_return_val_if_fail (property_name, NULL); + g_return_val_if_fail (value, NULL); + + if (target_state_name == NULL) + target_state_name = "default"; + + source_state_name = g_intern_string (source_state_name); + target_state_name = g_intern_string (target_state_name); + property_name = g_intern_string (property_name); + + target_state = g_hash_table_lookup (this->priv->states, + target_state_name); + + if (!target_state) + { + target_state = state_new (this, target_state_name); + g_hash_table_insert (this->priv->states, + (gpointer)target_state_name, target_state); + } + + if (source_state_name) + { + source_state = g_hash_table_lookup (this->priv->states, + source_state_name); + if (!source_state) + { + source_state = state_new (this, source_state_name); + g_hash_table_insert (this->priv->states, + (gpointer)source_state_name, source_state); + } + } + + state_key = clutter_state_key_new (target_state, object, property_name, mode); + state_key->source_state = source_state; + + state_key->pre_delay = pre_delay; + state_key->post_delay = post_delay; + + g_value_init (&state_key->value, G_VALUE_TYPE (value)); + g_value_copy (value, &state_key->value); + + if ((old_item = g_list_find_custom (target_state->keys, state_key, + sort_props_func))) + { + ClutterStateKey *old_key = old_item->data; + clutter_state_key_free (old_key); + target_state->keys = g_list_remove (target_state->keys, old_key); + } + + target_state->keys = g_list_insert_sorted (target_state->keys, state_key, + sort_props_func); + return this; +} + +/** + * clutter_state_get_states: + * @state: a #ClutterState instance. + * + * Get a list of all the state names managed by this #ClutterState. + * + * Returns: a GList of const gchar * containing state names. + * Since: 1.4 + */ +GList * +clutter_state_get_states (ClutterState *state) +{ + return g_hash_table_get_keys (state->priv->states); +} + +/** + * clutter_state_get_keys: + * @state: a #ClutterState instance. + * @source_state_name: the source transition name to query for, or NULL for all + * source state. + * @target_state_name: the target transition name to query for, or NULL for all + * target state. + * @object: the specific object instance to list keys for, or NULL for all + * managed objects. + * @property_name: the property name to search for or NULL for all properties. + * + * Returns a list of pointers to opaque structures with accessor functions + * that describe the keys added to an animator. + * + * Return value: a GList of #ClutterAnimtorKeys. + * Since: 1.4 + */ +GList * +clutter_state_get_keys (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name) +{ + GList *s, *state_list; + GList *targets = NULL; + State *source_state = NULL; + + g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + + source_state_name = g_intern_string (source_state_name); + target_state_name = g_intern_string (target_state_name); + property_name = g_intern_string (property_name); + + if (target_state_name != NULL) + { + state_list = g_list_append (NULL, (void*)target_state_name); + } + else + { + state_list = clutter_state_get_states (state); + } + + if (source_state_name) + source_state = g_hash_table_lookup (state->priv->states, + source_state_name); + + for (s = state_list; s; s=s->next) + { + State *target_state; + target_state = g_hash_table_lookup (state->priv->states, s->data); + + if (target_state) + { + GList *k; + + for (k = target_state->keys; k; k = k->next) + { + ClutterStateKey *key = k->data; + if ((object == NULL || (object == key->object)) && + (source_state_name == NULL || source_state == key->source_state) && + (property_name == NULL || + ((property_name == key->property_name)))) + { + targets = g_list_append (targets, key); + } + } + } + } + g_list_free (state_list); + return targets; +} + + +/** + * clutter_state_remove_key: + * @state: a #ClutterState instance. + * @source_state_name: the source state name to query for, or NULL for all + * source state. + * @target_state_name: the target state name to query for, or NULL for all + * target state. + * @object: the specific object instance to list keys for, or NULL for all + * managed objects. + * @property_name: the property name to search for or NULL for all properties. + * + * Removes all keys matching the search criteria passed in arguments. + * + * Since: 1.4 + */ +void +clutter_state_remove_key (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name) +{ + clutter_state_remove_key_internal (state, source_state_name, target_state_name, + object, property_name, FALSE); +} + +/** + * clutter_state_get_timeline: + * @state: a #ClutterState + * + * Get the timeline driving the #ClutterState + * + * Return value: the #ClutterTimeline that drives the state change animations. + * + * Since: 1.4 + */ +ClutterTimeline * +clutter_state_get_timeline (ClutterState *state) +{ + g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + return state->priv->timeline; +} + +static void +clutter_state_class_init (ClutterStateClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = clutter_state_finalize; + + g_type_class_add_private (klass, sizeof (ClutterStatePrivate)); +} + +static void +clutter_state_init (ClutterState *self) +{ + ClutterStatePrivate *priv; + priv = self->priv = CLUTTER_STATE_GET_PRIVATE (self); + priv->states = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, state_free); + self->priv->source_state_name = NULL; + self->priv->target_state_name = NULL; + self->priv->duration = 1000; + priv->timeline = clutter_timeline_new (1000); + priv->slave_timeline = clutter_timeline_new (SLAVE_TIMELINE_LENGTH); + g_signal_connect (priv->timeline, "new-frame", + G_CALLBACK (clutter_state_new_frame), self); + g_signal_connect (priv->timeline, "completed", + G_CALLBACK (clutter_state_completed), self); +} + + +/** + * clutter_state_get_animator: + * @state: a #ClutterState instance. + * @source_state_name: the name of a source state + * @target_state_name: the name of a target state + * + * Retrieve a ClutterAnimation that is being used for transitioning between two + * state if any. + * + * Returns: a #ClutterAnimator instance or NULL + */ +ClutterAnimator * +clutter_state_get_animator (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name) +{ + State *target_state; + StateAnimator *animators; + gint i; + + g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + + source_state_name = g_intern_string (source_state_name); + if (source_state_name == g_intern_static_string ("default") || + source_state_name == g_intern_static_string ("")) + source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); + + target_state = g_hash_table_lookup (state->priv->states, + target_state_name); + if (!target_state) + return NULL; + + animators = (StateAnimator*)target_state->animators->data; + + for (i=0; animators[i].animator; i++) + { + if (animators[i].source_state_name == source_state_name) + return animators[i].animator; + } + return NULL; +} + +/** + * clutter_state_set_animator: + * @state: a #ClutterState instance. + * @source_state_name: the name of a source state + * @target_state_name: the name of a target state + * @animator: a #ClutterAnimator instance. + * + * Specify a ClutterAnimation to be used when transitioning between the two + * named state, this allows specifying a transition between the state that is + * more elaborate than the basic transitions other allowed by the simple + * tweening of #ClutterState. Pass NULL to unset an existing animator. + * ClutterState takes a reference on the passed in animator if any. + */ +void +clutter_state_set_animator (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name, + ClutterAnimator *animator) +{ + State *target_state; + StateAnimator *animators; + gint i; + + g_return_if_fail (CLUTTER_IS_STATE (state)); + + source_state_name = g_intern_string (source_state_name); + target_state_name = g_intern_string (target_state_name); + + target_state = g_hash_table_lookup (state->priv->states, + target_state_name); + if (!target_state) + return; + + animators = (StateAnimator*)target_state->animators->data; + for (i=0; animators[i].animator; i++) + { + if (animators[i].source_state_name == source_state_name) + { + g_object_unref (animators[i].animator); + if (animator) + animators[i].animator = g_object_ref (animator); + else /* remove the matched animator if passed NULL */ + g_array_remove_index (target_state->animators, i); + return; + } + } + + if (animator) + { + StateAnimator state_animator = + {source_state_name, g_object_ref (animator)}; + g_array_append_val (target_state->animators, state_animator); + } +} + +static gpointer +clutter_state_key_copy (gpointer boxed) +{ + ClutterStateKey *key = boxed; + + if (key != NULL) + key->ref_count += 1; + + return key; +} + +GType +clutter_state_key_get_type (void) +{ + static GType our_type = 0; + + if (!our_type) + our_type = g_boxed_type_register_static (I_("ClutterStateKey"), + clutter_state_key_copy, + clutter_state_key_free); + + return our_type; +} + + + +/** + * clutter_state_key_get_pre_delay: + * @state_key: a #ClutterStateKey + * + * Retrieve the pause before transitioning starts as a fraction of total + * transition time. + * + * Returns: the pre delay used before starting the transition. + * Since: 1.4 + */ +gdouble +clutter_state_key_get_pre_delay (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, 0.0); + return state_key->pre_delay; +} + +/** + * clutter_state_key_get_post_delay: + * @state_key: a #ClutterStateKey + * + * Retrieve the duration of the pause after transitioning is complete as + * a fraction of total transition time. + * + * Returns: the post delay, used after doing the transition. + * Since: 1.4 + */ +gdouble +clutter_state_key_get_post_delay (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, 0.0); + return state_key->post_delay; +} + +/** + * clutter_state_key_get_mode: + * @state_key: a #ClutterStateKey + * + * Retrieve the easing mode used for a clutter_state_key. + * + * Returns: the mode of a ClutterStateKey + * Since: 1.4 + */ +gulong +clutter_state_key_get_mode (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, 0); + return state_key->mode; +} + +/** + * clutter_state_key_get_value: + * @state_key: a #ClutterStateKey + * @value: a #GValue initialized with the correct type for the state_key. + * + * Get a copy of the value for a ClutterStateKey, the passed in GValue needs + * to be already initialized for the value type. + * + * Since: 1.4 + */ +void +clutter_state_key_get_value (ClutterStateKey *state_key, + GValue *value) +{ + g_return_if_fail (state_key); + g_value_copy (&state_key->value, value); +} + + +/** + * clutter_state_key_get_object: + * @state_key: a #ClutterStateKey + * + * Get the object instance this #ClutterStateKey applies to. + * + * Returns: the object this state key applies to. + * Since: 1.4 + */ +GObject * +clutter_state_key_get_object (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, NULL); + return state_key->object; +} + +/** + * clutter_state_key_get_property_name: + * @state_key: a #ClutterStateKey + * + * Get the name of the property this #ClutterStateKey applies to. + * + * Returns: the name of the property the key applies to. + * Since: 1.4 + */ +const gchar * +clutter_state_key_get_property_name (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, NULL); + return state_key->property_name; +} + +/** + * clutter_state_key_get_source_state_name: + * @state_key: a #ClutterStateKey + * + * Get the name of the source state this #ClutterStateKey contains, + * or NULL if this is the generic state key for the given property + * when transitioning to the target state. + * + * Returns: the name of the source state for this key, or NULL if + * the key is generic. + * Since: 1.4 + */ +const gchar * +clutter_state_key_get_source_state_name (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, NULL); + if (state_key->source_state) + { + return state_key->source_state->name; + } + return NULL; +} + +/** + * clutter_state_key_get_target_state_name: + * @state_key: a #ClutterStateKey + * + * Get the name of the source state this #ClutterStateKey contains, + * or NULL if this is the generic state key for the given property + * when transitioning to the target state. + * + * Returns: the name of the source state for this key, or NULL if + * the key is generic. + * Since: 1.4 + */ +const gchar * +clutter_state_key_get_target_state_name (ClutterStateKey *state_key) +{ + g_return_val_if_fail (state_key, NULL); + return state_key->target_state->name; +} + +/** + * clutter_state_set_duration: + * @state: a #ClutterState + * @source_state_name: the name of the source state to set duration for or NULL + * @target_state_name: the name of the source state to set duration for or NULL + * @duration: milliseconds for transition between source_state and target_state. + * + * If both state names are NULL the default duration for ClutterState is set, + * if only target_state_name is specified this becomes the default duration + * for transitions to this state. When both are specified the change only + * applies to this transition. + * + * Since: 1.4 + */ +void +clutter_state_set_duration (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name, + guint duration) +{ + State *target_state; + + g_return_if_fail (CLUTTER_IS_STATE (state)); + + source_state_name = g_intern_string (source_state_name); + if (source_state_name == g_intern_static_string ("default") || + source_state_name == g_intern_static_string ("")) + source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); + if (target_state_name == g_intern_static_string ("default") || + target_state_name == g_intern_static_string ("")) + target_state_name = NULL; + + if (target_state_name == NULL) + { + state->priv->duration = duration; + return; + } + target_state = g_hash_table_lookup (state->priv->states, + target_state_name); + if (target_state) + { + if (source_state_name) + g_hash_table_insert (target_state->durations, (void*)source_state_name, + GINT_TO_POINTER (duration)); + else + g_hash_table_insert (target_state->durations, NULL, + GINT_TO_POINTER (duration)); + } +} + +/* should have durations for: + * + * global: NULL, NULL + * per-state-default: NULL, state + * directly encoded: state, state + */ +guint +clutter_state_get_duration (ClutterState *state, + const gchar *source_state_name, + const gchar *target_state_name) +{ + State *target_state; + guint ret = 0; + + g_return_val_if_fail (CLUTTER_IS_STATE (state), 0); + + source_state_name = g_intern_string (source_state_name); + if (source_state_name == g_intern_static_string ("default") || + source_state_name == g_intern_static_string ("")) + source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); + if (target_state_name == g_intern_static_string ("default") || + target_state_name == g_intern_static_string ("")) + target_state_name = NULL; + + if (target_state_name == NULL) + { + return state->priv->duration; + } + target_state = g_hash_table_lookup (state->priv->states, + target_state_name); + if (target_state) + { + if (source_state_name) + ret = GPOINTER_TO_INT (g_hash_table_lookup (target_state->durations, + source_state_name)); + else + ret = GPOINTER_TO_INT (g_hash_table_lookup (target_state->durations, + NULL)); + } + if (!ret) + ret = state->priv->duration; + return ret; +} diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h new file mode 100644 index 000000000..845918e92 --- /dev/null +++ b/clutter/clutter-state.h @@ -0,0 +1,156 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Øyvind Kolås + * + * Copyright (C) 2009 Intel Corporation + * + * 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, see . + */ + +#ifndef __CLUTTER_STATE_H__ +#define __CLUTTER_STATE_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_STATE (clutter_state_get_type ()) + +#define CLUTTER_STATE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + CLUTTER_TYPE_STATE, ClutterState)) + +#define CLUTTER_STATE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + CLUTTER_TYPE_STATE, ClutterStateClass)) + +#define CLUTTER_IS_STATE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + CLUTTER_TYPE_STATE)) + +#define CLUTTER_IS_STATE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + CLUTTER_TYPE_STATE)) + +#define CLUTTER_STATE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + CLUTTER_TYPE_STATE, ClutterStateClass)) + +typedef struct _ClutterState ClutterState; +typedef struct _ClutterStatePrivate ClutterStatePrivate; +typedef struct _ClutterStateClass ClutterStateClass; +typedef struct _ClutterStateKey ClutterStateKey; + +/** + * ClutterState: + * + * The #ClutterState structure contains only private data and + * should be accessed using the provided API + * + * Since: 1.4 + */ +struct _ClutterState +{ + /*< private >*/ + GObject parent; + ClutterStatePrivate *priv; +}; + +/** + * ClutterStateClass: + * + * The #ClutterStateClass structure contains only private data + * + * Since: 1.4 + */ +struct _ClutterStateClass +{ + /*< private >*/ + GObjectClass parent_class; + + /* padding for future expansion */ + gpointer _padding_dummy[16]; +}; + +GType clutter_state_get_type (void) G_GNUC_CONST; +ClutterState *clutter_state_new (void); + + +/* XXX: clutter_state_run? + * the current (target?) transition should be a property of ClutterState + */ +ClutterTimeline * clutter_state_change (ClutterState *state, + const gchar *target_transition_name); +ClutterTimeline * clutter_state_change_noanim (ClutterState *state, + const gchar *target_transition_name); +ClutterState * clutter_state_set_key (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + GObject *object, + const gchar *property_name, + guint mode, + const GValue *value, + gdouble pre_delay, + gdouble post_delay); +void clutter_state_set_duration (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + guint duration); +guint clutter_state_get_duration (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name); +void clutter_state_set (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + gpointer first_object, + const gchar *first_property_name, + gulong first_mode, + ...); +GList * clutter_state_get_states (ClutterState *state); +GList * clutter_state_get_keys (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + GObject *object, + const gchar *property_name); +void clutter_state_remove_key (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + GObject *object, + const gchar *property_name); +ClutterTimeline * clutter_state_get_timeline (ClutterState *state); +void clutter_state_set_animator (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name, + ClutterAnimator *animator); +ClutterAnimator * clutter_state_get_animator (ClutterState *state, + const gchar *source_transition_name, + const gchar *target_transition_name); + +GType clutter_state_key_get_type (void) G_GNUC_CONST; +gdouble clutter_state_key_get_pre_delay (ClutterStateKey *state_key); +gdouble clutter_state_key_get_post_delay (ClutterStateKey *state_key); +gulong clutter_state_key_get_mode (ClutterStateKey *state_key); +void clutter_state_key_get_value (ClutterStateKey *state_key, + GValue *value); +GObject * clutter_state_key_get_object (ClutterStateKey *state_key); +const gchar * clutter_state_key_get_property_name (ClutterStateKey *state_key); +const gchar * clutter_state_key_get_source_state_name (ClutterStateKey *state_key); +const gchar * clutter_state_key_get_target_state_name (ClutterStateKey *state_key); + +G_END_DECLS + +#endif /* __CLUTTER_STATE_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index f56876173..f95869c21 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -78,6 +78,7 @@ #include "clutter-stage.h" #include "clutter-stage-manager.h" #include "clutter-stage-window.h" +#include "clutter-state.h" #include "clutter-texture.h" #include "clutter-text.h" #include "clutter-timeline.h" diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index b7e6295d3..60407cbe3 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -20,6 +20,8 @@ UNIT_TESTS = \ test-fullscreen.c \ test-shader.c \ test-animator.c \ + test-state.c \ + test-state-animator.c \ test-unproject.c \ test-viewport.c \ test-fbo.c \ diff --git a/tests/interactive/test-state-animator.c b/tests/interactive/test-state-animator.c new file mode 100644 index 000000000..f9e4134ac --- /dev/null +++ b/tests/interactive/test-state-animator.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +static ClutterState *state; +static ClutterAnimator *animator; + +static gboolean press_event (ClutterActor *actor, + ClutterEvent *event, + gpointer user_data) +{ + clutter_grab_pointer (actor); + clutter_state_change (state, "end"); + return TRUE; +} + +static gboolean release_event (ClutterActor *actor, + ClutterEvent *event, + gpointer user_data) +{ + clutter_state_change (state, "start"); + clutter_ungrab_pointer (); + return TRUE; +} + + +static ClutterActor *new_rect (gint r, + gint g, + gint b, + gint a) +{ + GError *error = NULL; + ClutterColor *color = clutter_color_new (r, g, b, a); + ClutterActor *rectangle = clutter_rectangle_new_with_color (color); + + gchar *file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL); + rectangle = clutter_texture_new_from_file (file, &error); + if (rectangle == NULL) + g_error ("image load failed: %s", error->message); + g_free (file); + + clutter_actor_set_size (rectangle, 128, 128); + clutter_color_free (color); + return rectangle; +} + +G_MODULE_EXPORT gint +test_state_animator_main (gint argc, + gchar **argv) +{ + ClutterActor *stage; + ClutterActor *rects[40]; + gint i; + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + + for (i=0; i<2; i++) + { + rects[i]=new_rect (255 *(i * 1.0/40), 50, 160, 255); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), rects[i]); + clutter_actor_set_anchor_point (rects[i], 64, 64); + clutter_actor_set_position (rects[i], 320.0, 240.0); + clutter_actor_set_opacity (rects[i], 0x70); + + clutter_actor_set_reactive (rects[i], TRUE); + g_signal_connect (rects[i], "button-press-event", G_CALLBACK (press_event), NULL); + g_signal_connect (rects[i], "button-release-event", G_CALLBACK (release_event), NULL); + } + + state = clutter_state_new (); + clutter_state_set (state, NULL, "start", + rects[0], "depth", CLUTTER_LINEAR, 0.0, + rects[0], "x", CLUTTER_LINEAR, 100.0, + rects[0], "y", CLUTTER_LINEAR, 300.0, + rects[1], "opacity", CLUTTER_LINEAR, 0x20, + rects[1], "scale-x", CLUTTER_LINEAR, 1.0, + rects[1], "scale-y", CLUTTER_LINEAR, 1.0, + NULL); + clutter_state_set (state, NULL, "end", + rects[0], "depth", CLUTTER_LINEAR, 200.0, + rects[0], "x", CLUTTER_LINEAR, 320.0, + rects[0], "y", CLUTTER_LINEAR, 240.0, + rects[1], "opacity", CLUTTER_LINEAR, 0xff, + rects[1], "scale-x", CLUTTER_LINEAR, 2.0, + rects[1], "scale-y", CLUTTER_LINEAR, 2.0, + NULL); + + animator = clutter_animator_new (); + clutter_animator_set (animator, + rects[0], "depth", -1, 0.0, 0.0, + rects[0], "depth", CLUTTER_LINEAR, 1.0, 275.0, + rects[0], "x", -1, 0.0, 0.0, + rects[0], "x", CLUTTER_LINEAR, 0.5, 200.0, + rects[0], "x", CLUTTER_LINEAR, 1.0, 320.0, + + rects[0], "y", -1, 0.0, 0.0, + rects[0], "y", CLUTTER_LINEAR, 0.3, 100.0, + rects[0], "y", CLUTTER_LINEAR, 1.0, 240.0, + + rects[1], "opacity", -1, 0.0, 0x20, + rects[1], "opacity", CLUTTER_LINEAR, 1.0, 0xff, + rects[1], "scale-x", -1, 0.0, 1.0, + rects[1], "scale-x", CLUTTER_LINEAR, 0.5, 2.0, + rects[1], "scale-x", CLUTTER_LINEAR, 1.0, 2.0, + rects[1], "scale-y", -1, 0.0, 1.0, + rects[1], "scale-y", CLUTTER_LINEAR, 0.5, 2.0, + rects[1], "scale-y", CLUTTER_LINEAR, 1.0, 2.0, + NULL); + + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[0]), "depth", TRUE); + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[0]), "x", TRUE); + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[0]), "y", TRUE); + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[1]), "opacity", TRUE); + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[1]), "scale-x", TRUE); + clutter_animator_property_set_ease_in (animator, G_OBJECT (rects[1]), "scale-y", TRUE); + + clutter_animator_property_set_interpolation (animator, G_OBJECT (rects[0]), "x", + CLUTTER_INTERPOLATION_CUBIC); + clutter_animator_property_set_interpolation (animator, G_OBJECT (rects[0]), "y", + CLUTTER_INTERPOLATION_CUBIC); + + clutter_state_set_animator (state, "start", "end", animator); + g_object_unref (animator); + + clutter_actor_show (stage); + clutter_state_change (state, "start"); + + clutter_main (); + g_object_unref (state); + + return EXIT_SUCCESS; +} diff --git a/tests/interactive/test-state.c b/tests/interactive/test-state.c new file mode 100644 index 000000000..7a7b86e26 --- /dev/null +++ b/tests/interactive/test-state.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +static ClutterState *state; + +static gboolean press_event (ClutterActor *actor, + ClutterEvent *event, + gpointer user_data) +{ + clutter_grab_pointer (actor); + clutter_state_change (state, "end"); + return TRUE; +} + +static gboolean release_event (ClutterActor *actor, + ClutterEvent *event, + gpointer user_data) +{ + clutter_state_change (state, "start"); + clutter_ungrab_pointer (); + return TRUE; +} + + +static ClutterActor *new_rect (gint r, + gint g, + gint b, + gint a) +{ + GError *error = NULL; + ClutterColor *color = clutter_color_new (r, g, b, a); + ClutterActor *rectangle = clutter_rectangle_new_with_color (color); + + gchar *file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL); + rectangle = clutter_texture_new_from_file (file, &error); + if (rectangle == NULL) + g_error ("image load failed: %s", error->message); + g_free (file); + + clutter_actor_set_size (rectangle, 128, 128); + clutter_color_free (color); + return rectangle; +} + +G_MODULE_EXPORT gint +test_state_main (gint argc, + gchar **argv) +{ + ClutterActor *stage; + ClutterActor *rects[40]; + gint i; + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + + for (i=0; i<2; i++) + { + rects[i]=new_rect (255 *(i * 1.0/40), 50, 160, 255); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), rects[i]); + clutter_actor_set_anchor_point (rects[i], 64, 64); + clutter_actor_set_position (rects[i], 320.0, 240.0); + clutter_actor_set_opacity (rects[i], 0x70); + + clutter_actor_set_reactive (rects[i], TRUE); + g_signal_connect (rects[i], "button-press-event", G_CALLBACK (press_event), NULL); + g_signal_connect (rects[i], "button-release-event", G_CALLBACK (release_event), NULL); + } + + state = clutter_state_new (); + clutter_state_set (state, NULL, "start", + rects[0], "depth", CLUTTER_LINEAR, 0.0, + rects[0], "x", CLUTTER_LINEAR, 100.0, + rects[0], "y", CLUTTER_LINEAR, 300.0, + rects[1], "opacity", CLUTTER_LINEAR, 0x20, + rects[1], "scale-x", CLUTTER_LINEAR, 1.0, + rects[1], "scale-y", CLUTTER_LINEAR, 1.0, + NULL); + clutter_state_set (state, NULL, "end", + rects[0], "depth", CLUTTER_LINEAR, 200.0, + rects[0], "x", CLUTTER_LINEAR, 320.0, + rects[0], "y", CLUTTER_LINEAR, 240.0, + rects[1], "opacity", CLUTTER_LINEAR, 0xff, + rects[1], "scale-x", CLUTTER_LINEAR, 2.0, + rects[1], "scale-y", CLUTTER_LINEAR, 2.0, + NULL); + clutter_state_set_duration (state, "start", "end", 5000); + + clutter_actor_show (stage); + clutter_state_change (state, "start"); + + clutter_main (); + g_object_unref (state); + + return EXIT_SUCCESS; +} From 8761b279a715167fc87194c14efe00f10bba2bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Wed, 12 May 2010 17:03:25 +0100 Subject: [PATCH 02/14] state-machine: add completed signal Added a completed signal to the animator --- clutter/clutter-state.c | 54 ++++++++++++++++++++++++++++++---- clutter/clutter-state.h | 6 +++- tests/interactive/test-state.c | 7 +++++ 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 77da22f1f..520c0b569 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -35,6 +35,7 @@ #include "clutter-private.h" #include "clutter-state.h" +#include "clutter-marshal.h" #include #include @@ -98,6 +99,13 @@ typedef struct _ClutterStateKey gint ref_count; } _ClutterStateKey; +enum +{ + COMPLETED, + LAST_SIGNAL +}; + +static guint state_signals[LAST_SIGNAL] = {0, }; #define CLUTTER_STATE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ @@ -309,7 +317,7 @@ clutter_state_finalize (GObject *object) } static void clutter_state_completed (ClutterTimeline *timeline, - ClutterState *state) + ClutterState *state) { if (state->priv->current_animator) { @@ -317,6 +325,7 @@ static void clutter_state_completed (ClutterTimeline *timeline, NULL); state->priv->current_animator = NULL; } + g_signal_emit (state, state_signals[COMPLETED], 0); } static void clutter_state_new_frame (ClutterTimeline *timeline, @@ -925,6 +934,15 @@ clutter_state_class_init (ClutterStateClass *klass) gobject_class->finalize = clutter_state_finalize; g_type_class_add_private (klass, sizeof (ClutterStatePrivate)); + + state_signals[COMPLETED] = + g_signal_new (I_("completed"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterStateClass, completed), + NULL, NULL, + clutter_marshal_VOID__VOID, + G_TYPE_NONE, 0); } static void @@ -1264,11 +1282,19 @@ clutter_state_set_duration (ClutterState *state, } } -/* should have durations for: +/** + * clutter_state_get_duration: + * @state: a #ClutterState + * @source_state_name: the name of the source state to set duration for or NULL + * @target_state_name: the name of the source state to set duration for or NULL * - * global: NULL, NULL - * per-state-default: NULL, state - * directly encoded: state, state + * Query the duration used for transitions between source/target state pair, + * the semantics for the query are the same as the semantics used for setting + * in #clutter_state_set_duration. + * + * Since: 1.4 + * + * Returns: the duration in ms. */ guint clutter_state_get_duration (ClutterState *state, @@ -1308,3 +1334,21 @@ clutter_state_get_duration (ClutterState *state, ret = state->priv->duration; return ret; } + +/** + * clutter_state_get_target_state: + * @state: a #ClutterState + * + * Query the currently set target-state, during a transition it will also + * return the current target. Can be useful in the completed callback. + * + * Since: 1.4 + * + * Returns: the duration in ms. + */ +const gchar * +clutter_state_get_target_state (ClutterState *state) +{ + g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + return state->priv->target_state_name; +} diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h index 845918e92..a6b1ced0d 100644 --- a/clutter/clutter-state.h +++ b/clutter/clutter-state.h @@ -82,6 +82,9 @@ struct _ClutterStateClass /*< private >*/ GObjectClass parent_class; + /*< public >*/ + void (*completed) (ClutterState *state); + /* padding for future expansion */ gpointer _padding_dummy[16]; }; @@ -97,7 +100,8 @@ ClutterTimeline * clutter_state_change (ClutterState *state, const gchar *target_transition_name); ClutterTimeline * clutter_state_change_noanim (ClutterState *state, const gchar *target_transition_name); -ClutterState * clutter_state_set_key (ClutterState *state, +const gchar * clutter_state_get_target_state (ClutterState *state); +ClutterState * clutter_state_set_key (ClutterState *state, const gchar *source_transition_name, const gchar *target_transition_name, GObject *object, diff --git a/tests/interactive/test-state.c b/tests/interactive/test-state.c index 7a7b86e26..fd4319007 100644 --- a/tests/interactive/test-state.c +++ b/tests/interactive/test-state.c @@ -23,6 +23,12 @@ static gboolean release_event (ClutterActor *actor, return TRUE; } +static void completed (ClutterState *state, + gpointer data) +{ + g_print ("Completed transitioning to state: %s\n", + clutter_state_get_target_state (state), data); +} static ClutterActor *new_rect (gint r, gint g, @@ -86,6 +92,7 @@ test_state_main (gint argc, rects[1], "scale-y", CLUTTER_LINEAR, 2.0, NULL); clutter_state_set_duration (state, "start", "end", 5000); + g_signal_connect (state, "completed", G_CALLBACK (completed), NULL); clutter_actor_show (stage); clutter_state_change (state, "start"); From 54bd5412706733e9af576ed3998d87cb138846e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 20 May 2010 15:41:29 +0100 Subject: [PATCH 03/14] state-machine: use clutter_timeline_get_progress --- clutter/clutter-state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 520c0b569..426db0343 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -341,7 +341,7 @@ static void clutter_state_new_frame (ClutterTimeline *timeline, if (state->priv->current_animator) return; - progress = 1.0 * msecs / clutter_timeline_get_duration (timeline); + progress = clutter_timeline_get_progress (timeline); for (k = state->priv->target_state->keys; k; k = k->next) { From 1dc8c0ff05f683ae6522e27965571742fc1a4dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 20 May 2010 16:13:46 +0100 Subject: [PATCH 04/14] state-machine: made clutter_state_change take a boolean animate argument Most of the time this will be TRUE, pass FALSE to change to the target state immediately. --- clutter/clutter-state.c | 102 +++--------------------- clutter/clutter-state.h | 8 +- tests/interactive/test-state-animator.c | 6 +- tests/interactive/test-state.c | 10 +-- 4 files changed, 19 insertions(+), 107 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 426db0343..7bda07e28 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -397,98 +397,10 @@ static void clutter_state_new_frame (ClutterTimeline *timeline, } -/** - * clutter_state_change_noanim: - * @state_name: a #ClutterState - * - * Change to @state_name and spend duration msecs when doing so. - * - * Return value: the #ClutterTimeline that drives the #ClutterState instance. - */ -ClutterTimeline * -clutter_state_change_noanim (ClutterState *this, - const gchar *target_state_name) -{ - ClutterStatePrivate *priv = this->priv; - State *state; - - g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); - g_return_val_if_fail (target_state_name, NULL); - - if (target_state_name == NULL) - target_state_name = "default"; - target_state_name = g_intern_string (target_state_name); - if (priv->target_state_name == NULL) - priv->target_state_name = g_intern_static_string ("default"); - - if (priv->current_animator) - { - clutter_animator_set_timeline (priv->current_animator, NULL); - priv->current_animator = NULL; - } - - priv->source_state_name = priv->target_state_name; - priv->target_state_name = target_state_name; - - clutter_timeline_set_duration (priv->timeline, 1); - - state = g_hash_table_lookup (priv->states, target_state_name); - - g_return_val_if_fail (state, NULL); - - { - ClutterAnimator *animator; - animator = clutter_state_get_animator (this, priv->source_state_name, - priv->target_state_name); - if (animator) - { - priv->current_animator = animator; - clutter_animator_set_timeline (animator, priv->timeline); - clutter_timeline_stop (priv->timeline); - clutter_timeline_rewind (priv->timeline); - clutter_timeline_start (priv->timeline); - return priv->timeline; - } - } - - if (state) - { - GList *k; - - for (k = state->keys; k; k = k->next) - { - ClutterStateKey *key = k->data; - GValue initial = {0,}; - - g_value_init (&initial, - clutter_interval_get_value_type (key->interval)); - - g_object_get_property (key->object, - key->property_name, &initial); - if (clutter_alpha_get_mode (key->alpha) != key->mode) - clutter_alpha_set_mode (key->alpha, key->mode); - clutter_interval_set_initial_value (key->interval, &initial); - clutter_interval_set_final_value (key->interval, &key->value); - - g_value_unset (&initial); - } - - priv->target_state = state; - clutter_timeline_rewind (priv->timeline); - clutter_timeline_start (priv->timeline); - } - else - { - g_warning ("Anim state '%s' not found\n", target_state_name); - } - - return priv->timeline; -} - - /** * clutter_state_change: * @state_name: a #ClutterState + * @animate: whether we should animate the transition or not. * * Change to @state_name and spend duration msecs when doing so. * @@ -496,7 +408,8 @@ clutter_state_change_noanim (ClutterState *this, */ ClutterTimeline * clutter_state_change (ClutterState *this, - const gchar *target_state_name) + const gchar *target_state_name, + gboolean animate) { ClutterStatePrivate *priv = this->priv; State *state; @@ -528,9 +441,12 @@ clutter_state_change (ClutterState *this, priv->source_state_name = priv->target_state_name; priv->target_state_name = target_state_name; - clutter_timeline_set_duration (priv->timeline, - clutter_state_get_duration (this, priv->source_state_name, - priv->target_state_name)); + if (animate) + clutter_timeline_set_duration (priv->timeline, + clutter_state_get_duration (this, priv->source_state_name, + priv->target_state_name)); + else + clutter_timeline_set_duration (priv->timeline, 1); state = g_hash_table_lookup (priv->states, target_state_name); diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h index a6b1ced0d..a647b9437 100644 --- a/clutter/clutter-state.h +++ b/clutter/clutter-state.h @@ -93,13 +93,9 @@ GType clutter_state_get_type (void) G_GNUC_CONST; ClutterState *clutter_state_new (void); -/* XXX: clutter_state_run? - * the current (target?) transition should be a property of ClutterState - */ ClutterTimeline * clutter_state_change (ClutterState *state, - const gchar *target_transition_name); -ClutterTimeline * clutter_state_change_noanim (ClutterState *state, - const gchar *target_transition_name); + const gchar *target_transition_name, + gboolean animate); const gchar * clutter_state_get_target_state (ClutterState *state); ClutterState * clutter_state_set_key (ClutterState *state, const gchar *source_transition_name, diff --git a/tests/interactive/test-state-animator.c b/tests/interactive/test-state-animator.c index f9e4134ac..bdd88a91d 100644 --- a/tests/interactive/test-state-animator.c +++ b/tests/interactive/test-state-animator.c @@ -11,7 +11,7 @@ static gboolean press_event (ClutterActor *actor, gpointer user_data) { clutter_grab_pointer (actor); - clutter_state_change (state, "end"); + clutter_state_change (state, "end", TRUE); return TRUE; } @@ -19,7 +19,7 @@ static gboolean release_event (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { - clutter_state_change (state, "start"); + clutter_state_change (state, "start", TRUE); clutter_ungrab_pointer (); return TRUE; } @@ -125,7 +125,7 @@ test_state_animator_main (gint argc, g_object_unref (animator); clutter_actor_show (stage); - clutter_state_change (state, "start"); + clutter_state_change (state, "start", TRUE); clutter_main (); g_object_unref (state); diff --git a/tests/interactive/test-state.c b/tests/interactive/test-state.c index fd4319007..064c03a4c 100644 --- a/tests/interactive/test-state.c +++ b/tests/interactive/test-state.c @@ -10,7 +10,7 @@ static gboolean press_event (ClutterActor *actor, gpointer user_data) { clutter_grab_pointer (actor); - clutter_state_change (state, "end"); + clutter_state_change (state, "end", TRUE); return TRUE; } @@ -18,16 +18,16 @@ static gboolean release_event (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { - clutter_state_change (state, "start"); + clutter_state_change (state, "start", TRUE); clutter_ungrab_pointer (); return TRUE; } -static void completed (ClutterState *state, +static void completed (ClutterState *sstate, gpointer data) { g_print ("Completed transitioning to state: %s\n", - clutter_state_get_target_state (state), data); + clutter_state_get_target_state (sstate)); } static ClutterActor *new_rect (gint r, @@ -95,7 +95,7 @@ test_state_main (gint argc, g_signal_connect (state, "completed", G_CALLBACK (completed), NULL); clutter_actor_show (stage); - clutter_state_change (state, "start"); + clutter_state_change (state, "start", TRUE); clutter_main (); g_object_unref (state); From 5c74f990d6e388aaa7041469dbed7b4cd80242c3 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 17:34:02 +0100 Subject: [PATCH 05/14] Do not include clutter.h from a Clutter header file Clutter header files should just include the needed headers. --- clutter/clutter-state.c | 10 ++++++++-- clutter/clutter-state.h | 30 ++++++++---------------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 7bda07e28..554ab965f 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -33,12 +33,18 @@ #include "config.h" #endif -#include "clutter-private.h" #include "clutter-state.h" -#include "clutter-marshal.h" + #include #include +#include "clutter-alpha.h" +#include "clutter-animator.h" +#include "clutter-enum-types.h" +#include "clutter-interval.h" +#include "clutter-marshal.h" +#include "clutter-private.h" + G_DEFINE_TYPE (ClutterState, clutter_state, G_TYPE_OBJECT); typedef struct StateAnimator { diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h index a647b9437..dee4c8f16 100644 --- a/clutter/clutter-state.h +++ b/clutter/clutter-state.h @@ -24,31 +24,17 @@ #ifndef __CLUTTER_STATE_H__ #define __CLUTTER_STATE_H__ -#include +#include +#include G_BEGIN_DECLS -#define CLUTTER_TYPE_STATE (clutter_state_get_type ()) - -#define CLUTTER_STATE(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - CLUTTER_TYPE_STATE, ClutterState)) - -#define CLUTTER_STATE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - CLUTTER_TYPE_STATE, ClutterStateClass)) - -#define CLUTTER_IS_STATE(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - CLUTTER_TYPE_STATE)) - -#define CLUTTER_IS_STATE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - CLUTTER_TYPE_STATE)) - -#define CLUTTER_STATE_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - CLUTTER_TYPE_STATE, ClutterStateClass)) +#define CLUTTER_TYPE_STATE (clutter_state_get_type ()) +#define CLUTTER_STATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_STATE, ClutterState)) +#define CLUTTER_STATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_STATE, ClutterStateClass)) +#define CLUTTER_IS_STATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_STATE)) +#define CLUTTER_IS_STATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_STATE)) +#define CLUTTER_STATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_STATE, ClutterStateClass)) typedef struct _ClutterState ClutterState; typedef struct _ClutterStatePrivate ClutterStatePrivate; From 3f36b4a8419e1dfbaaf1008e636d18c93fe2cb66 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 17:34:36 +0100 Subject: [PATCH 06/14] state: Constify StateKey accessors Since the accessors do not modify the StateKey we should constify the argument. --- clutter/clutter-state.c | 179 ++++++++++++++++++++++++++-------------- clutter/clutter-state.h | 20 ++--- 2 files changed, 126 insertions(+), 73 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 554ab965f..430c3a755 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -1008,22 +1008,22 @@ clutter_state_key_get_type (void) return our_type; } - - /** * clutter_state_key_get_pre_delay: * @state_key: a #ClutterStateKey * - * Retrieve the pause before transitioning starts as a fraction of total - * transition time. + * Retrieves the pause before transitioning starts as a fraction of + * the total transition time. + * + * Return value: the pre delay used before starting the transition. * - * Returns: the pre delay used before starting the transition. * Since: 1.4 */ gdouble -clutter_state_key_get_pre_delay (ClutterStateKey *state_key) +clutter_state_key_get_pre_delay (const ClutterStateKey *state_key) { - g_return_val_if_fail (state_key, 0.0); + g_return_val_if_fail (state_key != NULL, 0.0); + return state_key->pre_delay; } @@ -1031,16 +1031,18 @@ clutter_state_key_get_pre_delay (ClutterStateKey *state_key) * clutter_state_key_get_post_delay: * @state_key: a #ClutterStateKey * - * Retrieve the duration of the pause after transitioning is complete as - * a fraction of total transition time. + * Retrieves the duration of the pause after transitioning is complete + * as a fraction of the total transition time. + * + * Return value: the post delay, used after doing the transition. * - * Returns: the post delay, used after doing the transition. * Since: 1.4 */ gdouble -clutter_state_key_get_post_delay (ClutterStateKey *state_key) +clutter_state_key_get_post_delay (const ClutterStateKey *state_key) { - g_return_val_if_fail (state_key, 0.0); + g_return_val_if_fail (state_key != NULL, 0.0); + return state_key->post_delay; } @@ -1048,50 +1050,83 @@ clutter_state_key_get_post_delay (ClutterStateKey *state_key) * clutter_state_key_get_mode: * @state_key: a #ClutterStateKey * - * Retrieve the easing mode used for a clutter_state_key. + * Retrieves the easing mode used for @state_key. + * + * Return value: the mode of a #ClutterStateKey * - * Returns: the mode of a ClutterStateKey * Since: 1.4 */ gulong -clutter_state_key_get_mode (ClutterStateKey *state_key) +clutter_state_key_get_mode (const ClutterStateKey *state_key) { - g_return_val_if_fail (state_key, 0); + g_return_val_if_fail (state_key != NULL, 0); + return state_key->mode; } /** * clutter_state_key_get_value: * @state_key: a #ClutterStateKey - * @value: a #GValue initialized with the correct type for the state_key. + * @value: a #GValue initialized with the correct type for the @state_key * - * Get a copy of the value for a ClutterStateKey, the passed in GValue needs - * to be already initialized for the value type. + * Copies of the value for a #ClutterStateKey into @value + * + * The #GValue needs to be already initialized for the value type + * of the property or to a transformable type * * Since: 1.4 */ void -clutter_state_key_get_value (ClutterStateKey *state_key, - GValue *value) +clutter_state_key_get_value (const ClutterStateKey *state_key, + GValue *value) { - g_return_if_fail (state_key); - g_value_copy (&state_key->value, value); -} + g_return_if_fail (state_key != NULL); + g_return_if_fail (value != NULL); + g_return_if_fail (G_VALUE_TYPE (value) != G_TYPE_INVALID); + if (!g_type_is_a (G_VALUE_TYPE (&state_key->value), G_VALUE_TYPE (value))) + { + if (g_value_type_compatible (G_VALUE_TYPE (&state_key->value), + G_VALUE_TYPE (value))) + { + g_value_copy (&state_key->value, value); + return; + } + + if (g_value_type_transformable (G_VALUE_TYPE (&state_key->value), + G_VALUE_TYPE (value))) + { + if (g_value_transform (&state_key->value, value)) + return; + } + + g_warning ("%s: Unable to convert from %s to %s for the " + "property '%s' of object %s in the state key", + G_STRLOC, + g_type_name (G_VALUE_TYPE (&state_key->value)), + g_type_name (G_VALUE_TYPE (value)), + state_key->property_name, + G_OBJECT_TYPE_NAME (state_key->object)); + } + else + g_value_copy (&state_key->value, value); +} /** * clutter_state_key_get_object: * @state_key: a #ClutterStateKey * - * Get the object instance this #ClutterStateKey applies to. + * Retrieves the object instance this #ClutterStateKey applies to. + * + * Return value: (transfer none): the object this state key applies to. * - * Returns: the object this state key applies to. * Since: 1.4 */ GObject * -clutter_state_key_get_object (ClutterStateKey *state_key) +clutter_state_key_get_object (const ClutterStateKey *state_key) { g_return_val_if_fail (state_key, NULL); + return state_key->object; } @@ -1099,15 +1134,18 @@ clutter_state_key_get_object (ClutterStateKey *state_key) * clutter_state_key_get_property_name: * @state_key: a #ClutterStateKey * - * Get the name of the property this #ClutterStateKey applies to. + * Retrieves the name of the property this #ClutterStateKey applies to + * + * Return value: the name of the property. The returned string is owned + * by the #ClutterStateKey and should never be modified or freed * - * Returns: the name of the property the key applies to. * Since: 1.4 */ -const gchar * -clutter_state_key_get_property_name (ClutterStateKey *state_key) +G_CONST_RETURN gchar * +clutter_state_key_get_property_name (const ClutterStateKey *state_key) { g_return_val_if_fail (state_key, NULL); + return state_key->property_name; } @@ -1115,22 +1153,23 @@ clutter_state_key_get_property_name (ClutterStateKey *state_key) * clutter_state_key_get_source_state_name: * @state_key: a #ClutterStateKey * - * Get the name of the source state this #ClutterStateKey contains, - * or NULL if this is the generic state key for the given property - * when transitioning to the target state. + * Retrieves the name of the source state of the @state_key + * + * Return value: the name of the source state for this key, or %NULL + * if this is the generic state key for the given property when + * transitioning to the target state. The returned string is owned + * by the #ClutterStateKey and should never be modified or freed * - * Returns: the name of the source state for this key, or NULL if - * the key is generic. * Since: 1.4 */ -const gchar * -clutter_state_key_get_source_state_name (ClutterStateKey *state_key) +G_CONST_RETURN gchar * +clutter_state_key_get_source_state_name (const ClutterStateKey *state_key) { g_return_val_if_fail (state_key, NULL); - if (state_key->source_state) - { - return state_key->source_state->name; - } + + if (state_key->source_state != NULL) + return state_key->source_state->name; + return NULL; } @@ -1142,14 +1181,16 @@ clutter_state_key_get_source_state_name (ClutterStateKey *state_key) * or NULL if this is the generic state key for the given property * when transitioning to the target state. * - * Returns: the name of the source state for this key, or NULL if - * the key is generic. + * Return value: the name of the source state for this key, or NULL if + * the key is generic + * * Since: 1.4 */ -const gchar * -clutter_state_key_get_target_state_name (ClutterStateKey *state_key) +G_CONST_RETURN gchar * +clutter_state_key_get_target_state_name (const ClutterStateKey *state_key) { g_return_val_if_fail (state_key, NULL); + return state_key->target_state->name; } @@ -1207,16 +1248,20 @@ clutter_state_set_duration (ClutterState *state, /** * clutter_state_get_duration: * @state: a #ClutterState - * @source_state_name: the name of the source state to set duration for or NULL - * @target_state_name: the name of the source state to set duration for or NULL + * @source_state_name: (allow-none): the name of the source state to + * get the duration of, or %NULL + * @target_state_name: (allow-none): the name of the source state to + * get the duration of, or %NULL * - * Query the duration used for transitions between source/target state pair, - * the semantics for the query are the same as the semantics used for setting - * in #clutter_state_set_duration. + * Queries the duration used for transitions between a source and + * target state pair + * + * The semantics for the query are the same as the semantics used for + * setting the duration with clutter_state_set_duration() + * + * Return value: the duration, in milliseconds * * Since: 1.4 - * - * Returns: the duration in ms. */ guint clutter_state_get_duration (ClutterState *state, @@ -1224,7 +1269,7 @@ clutter_state_get_duration (ClutterState *state, const gchar *target_state_name) { State *target_state; - guint ret = 0; + guint ret = 0; g_return_val_if_fail (CLUTTER_IS_STATE (state), 0); @@ -1232,18 +1277,18 @@ clutter_state_get_duration (ClutterState *state, if (source_state_name == g_intern_static_string ("default") || source_state_name == g_intern_static_string ("")) source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); if (target_state_name == g_intern_static_string ("default") || target_state_name == g_intern_static_string ("")) target_state_name = NULL; if (target_state_name == NULL) - { - return state->priv->duration; - } + return state->priv->duration; + target_state = g_hash_table_lookup (state->priv->states, target_state_name); - if (target_state) + if (target_state != NULL) { if (source_state_name) ret = GPOINTER_TO_INT (g_hash_table_lookup (target_state->durations, @@ -1252,8 +1297,10 @@ clutter_state_get_duration (ClutterState *state, ret = GPOINTER_TO_INT (g_hash_table_lookup (target_state->durations, NULL)); } + if (!ret) ret = state->priv->duration; + return ret; } @@ -1261,16 +1308,22 @@ clutter_state_get_duration (ClutterState *state, * clutter_state_get_target_state: * @state: a #ClutterState * - * Query the currently set target-state, during a transition it will also - * return the current target. Can be useful in the completed callback. + * Queries the currently set target state. + * + * During a transition this function will also return the current target. + * + * This function is useful when called from handlers of the + * #ClutterState::completed signal. + * + * Return value: a string containing the target state. The returned string + * is owned by the #ClutterState and should not be modified or freed * * Since: 1.4 - * - * Returns: the duration in ms. */ -const gchar * +G_CONST_RETURN gchar * clutter_state_get_target_state (ClutterState *state) { g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + return state->priv->target_state_name; } diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h index dee4c8f16..55322b0ac 100644 --- a/clutter/clutter-state.h +++ b/clutter/clutter-state.h @@ -126,16 +126,16 @@ ClutterAnimator * clutter_state_get_animator (ClutterState *state, const gchar *source_transition_name, const gchar *target_transition_name); -GType clutter_state_key_get_type (void) G_GNUC_CONST; -gdouble clutter_state_key_get_pre_delay (ClutterStateKey *state_key); -gdouble clutter_state_key_get_post_delay (ClutterStateKey *state_key); -gulong clutter_state_key_get_mode (ClutterStateKey *state_key); -void clutter_state_key_get_value (ClutterStateKey *state_key, - GValue *value); -GObject * clutter_state_key_get_object (ClutterStateKey *state_key); -const gchar * clutter_state_key_get_property_name (ClutterStateKey *state_key); -const gchar * clutter_state_key_get_source_state_name (ClutterStateKey *state_key); -const gchar * clutter_state_key_get_target_state_name (ClutterStateKey *state_key); +GType clutter_state_key_get_type (void) G_GNUC_CONST; +gdouble clutter_state_key_get_pre_delay (const ClutterStateKey *state_key); +gdouble clutter_state_key_get_post_delay (const ClutterStateKey *state_key); +gulong clutter_state_key_get_mode (const ClutterStateKey *state_key); +void clutter_state_key_get_value (const ClutterStateKey *state_key, + GValue *value); +GObject * clutter_state_key_get_object (const ClutterStateKey *state_key); +G_CONST_RETURN gchar *clutter_state_key_get_property_name (const ClutterStateKey *state_key); +G_CONST_RETURN gchar *clutter_state_key_get_source_state_name (const ClutterStateKey *state_key); +G_CONST_RETURN gchar *clutter_state_key_get_target_state_name (const ClutterStateKey *state_key); G_END_DECLS From 9fafc93c5d241313d70b0451b2529f4fd30acd14 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 17:46:52 +0100 Subject: [PATCH 07/14] state: Clean up the header's documentation --- clutter/clutter-state.h | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/clutter/clutter-state.h b/clutter/clutter-state.h index 55322b0ac..59e61538e 100644 --- a/clutter/clutter-state.h +++ b/clutter/clutter-state.h @@ -39,13 +39,22 @@ G_BEGIN_DECLS typedef struct _ClutterState ClutterState; typedef struct _ClutterStatePrivate ClutterStatePrivate; typedef struct _ClutterStateClass ClutterStateClass; + +/** + * ClutterStateKey: + * + * ClutterStateKey is an opaque structure whose + * members cannot be accessed directly + * + * Since: 1.4 + */ typedef struct _ClutterStateKey ClutterStateKey; /** * ClutterState: * - * The #ClutterState structure contains only private data and - * should be accessed using the provided API + * The ClutterState structure contains only + * private data and should be accessed using the provided API * * Since: 1.4 */ @@ -58,8 +67,10 @@ struct _ClutterState /** * ClutterStateClass: + * @completed: class handler for the #ClutterState::completed signal * - * The #ClutterStateClass structure contains only private data + * The ClutterStateClass structure contains + * only private data * * Since: 1.4 */ @@ -69,10 +80,11 @@ struct _ClutterStateClass GObjectClass parent_class; /*< public >*/ - void (*completed) (ClutterState *state); + void (* completed) (ClutterState *state); + /*< private >*/ /* padding for future expansion */ - gpointer _padding_dummy[16]; + gpointer _padding_dummy[8]; }; GType clutter_state_get_type (void) G_GNUC_CONST; @@ -82,7 +94,6 @@ ClutterState *clutter_state_new (void); ClutterTimeline * clutter_state_change (ClutterState *state, const gchar *target_transition_name, gboolean animate); -const gchar * clutter_state_get_target_state (ClutterState *state); ClutterState * clutter_state_set_key (ClutterState *state, const gchar *source_transition_name, const gchar *target_transition_name, @@ -125,6 +136,11 @@ void clutter_state_set_animator (ClutterState *state, ClutterAnimator * clutter_state_get_animator (ClutterState *state, const gchar *source_transition_name, const gchar *target_transition_name); +G_CONST_RETURN gchar *clutter_state_get_target_state (ClutterState *state); + +/* + * ClutterStateKey + */ GType clutter_state_key_get_type (void) G_GNUC_CONST; gdouble clutter_state_key_get_pre_delay (const ClutterStateKey *state_key); From c2abb827a36db1d8db0518ea2b144bc971d23ee0 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 18:34:34 +0100 Subject: [PATCH 08/14] state: Minor coding style fixes --- clutter/clutter-state.c | 322 ++++++++++++++++++++++++---------------- 1 file changed, 194 insertions(+), 128 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 430c3a755..10f930b42 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -79,7 +79,7 @@ struct _ClutterStatePrivate #define SLAVE_TIMELINE_LENGTH 10000 -/** +/* * ClutterStateKey: * * An opaque data structure with accessor functions. @@ -121,9 +121,9 @@ static guint state_signals[LAST_SIGNAL] = {0, }; /** * clutter_state_new: * - * Create a new #ClutterState instance. + * Creates a new #ClutterState * - * Returns: a new #ClutterState. + * Return value: the newly create #ClutterState instance */ ClutterState * clutter_state_new (void) @@ -131,11 +131,9 @@ clutter_state_new (void) return g_object_new (CLUTTER_TYPE_STATE, NULL); } -static State * state_new (ClutterState *state, - const gchar *name); - -static gint sort_props_func (gconstpointer a, - gconstpointer b) +static gint +sort_props_func (gconstpointer a, + gconstpointer b) { const ClutterStateKey *pa = a; const ClutterStateKey *pb = b; @@ -144,11 +142,11 @@ static gint sort_props_func (gconstpointer a, { gint propnamediff = pa->property_name-pb->property_name; if (propnamediff == 0) - { - return pb->source_state - pa->source_state; - } + return pb->source_state - pa->source_state; + return propnamediff; } + return pa->object - pb->object; } @@ -159,41 +157,40 @@ static ClutterStateKey * clutter_state_key_new (State *state, GObject *object, const gchar *property_name, + GParamSpec *pspec, guint mode) { ClutterStateKey *state_key; - GParamSpec *pspec; - GObjectClass *klass = G_OBJECT_GET_CLASS (object); GValue value = {0, }; state_key = g_slice_new0 (ClutterStateKey); + state_key->target_state = state; state_key->object = object; state_key->property_name = g_intern_string (property_name); state_key->mode = mode; + state_key->alpha = clutter_alpha_new (); + g_object_ref_sink (state_key->alpha); clutter_alpha_set_mode (state_key->alpha, mode); clutter_alpha_set_timeline (state_key->alpha, state->state->priv->slave_timeline); - pspec = g_object_class_find_property (klass, property_name); - state_key->interval = - g_object_new (CLUTTER_TYPE_INTERVAL, - "value-type", G_PARAM_SPEC_VALUE_TYPE (pspec), - NULL); + g_object_new (CLUTTER_TYPE_INTERVAL, + "value-type", G_PARAM_SPEC_VALUE_TYPE (pspec), + NULL); + g_object_ref_sink (state_key->interval); + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); clutter_interval_set_initial_value (state_key->interval, &value); clutter_interval_set_final_value (state_key->interval, &value); g_value_unset (&value); - g_object_ref_sink (state_key->alpha); - g_object_ref_sink (state_key->interval); - g_object_weak_ref (object, object_disappeared, state_key->target_state->state); - return (void*)state_key; + return state_key; } static void @@ -219,20 +216,17 @@ clutter_state_key_free (gpointer clutter_state_key) } -static void +static inline void clutter_state_remove_key_internal (ClutterState *this, - const gchar *source_state_name, - const gchar *target_state_name, - GObject *object, - const gchar *property_name, - gboolean is_inert) + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name, + gboolean is_inert) { GList *s, *state_list; - State *source_state = NULL; - g_return_if_fail (CLUTTER_IS_STATE (this)); - source_state_name = g_intern_string (source_state_name); target_state_name = g_intern_string (target_state_name); property_name = g_intern_string (property_name); @@ -242,15 +236,11 @@ clutter_state_remove_key_internal (ClutterState *this, source_state_name); if (target_state_name != NULL) - { - state_list = g_list_append (NULL, (void*)target_state_name); - } + state_list = g_list_append (NULL, (gpointer) target_state_name); else - { - state_list = clutter_state_get_states (this); - } + state_list = clutter_state_get_states (this); - for (s = state_list; s; s=s->next) + for (s = state_list; s != NULL; s = s->next) { State *target_state; target_state = g_hash_table_lookup (this->priv->states, s->data); @@ -261,7 +251,7 @@ clutter_state_remove_key_internal (ClutterState *this, for (k = target_state->keys; k; k = k->next) { - ClutterStateKey *key = k->data; + ClutterStateKey *key = k->data; if ((object == NULL || (object == key->object)) && (source_state == NULL || @@ -269,74 +259,91 @@ clutter_state_remove_key_internal (ClutterState *this, (property_name == NULL || ((property_name == key->property_name)))) { + k = target_state->keys = + g_list_remove (target_state->keys, key); + key->is_inert = is_inert; clutter_state_key_free (key); - k = target_state->keys = g_list_remove (target_state->keys, - key); } } } } + g_list_free (state_list); } -static void object_disappeared (gpointer data, - GObject *where_the_object_was) +static void +object_disappeared (gpointer data, + GObject *where_the_object_was) { clutter_state_remove_key_internal (data, NULL, NULL, - (void*)where_the_object_was, NULL, TRUE); + (gpointer) where_the_object_was, + NULL, + TRUE); } -static void state_free (gpointer data) +static void +state_free (gpointer data) { State *state = data; + for (; state->keys; state->keys = g_list_remove (state->keys, state->keys->data)) clutter_state_key_free (state->keys->data); + g_array_unref (state->animators); g_hash_table_destroy (state->durations); g_free (state); } -static State *state_new (ClutterState *this, - const gchar *name) +static State * +state_new (ClutterState *this, + const gchar *name) { State *state; + state = g_new0 (State, 1); state->state = this; state->name = name; state->animators = g_array_new (TRUE, TRUE, sizeof (StateAnimator)); state->durations = g_hash_table_new (g_direct_hash, g_direct_equal); + return state; } static void clutter_state_finalize (GObject *object) { - ClutterState *state = CLUTTER_STATE (object); - g_hash_table_destroy (state->priv->states); - g_object_unref (state->priv->timeline); - g_object_unref (state->priv->slave_timeline); + ClutterStatePrivate *priv = CLUTTER_STATE (object)->priv; + + g_hash_table_destroy (priv->states); + + g_object_unref (priv->timeline); + g_object_unref (priv->slave_timeline); G_OBJECT_CLASS (clutter_state_parent_class)->finalize (object); } -static void clutter_state_completed (ClutterTimeline *timeline, - ClutterState *state) +static void +clutter_state_completed (ClutterTimeline *timeline, + ClutterState *state) { - if (state->priv->current_animator) + ClutterStatePrivate *priv = state->priv; + + if (priv->current_animator) { - clutter_animator_set_timeline (state->priv->current_animator, - NULL); - state->priv->current_animator = NULL; + clutter_animator_set_timeline (priv->current_animator, NULL); + priv->current_animator = NULL; } + g_signal_emit (state, state_signals[COMPLETED], 0); } -static void clutter_state_new_frame (ClutterTimeline *timeline, - gint msecs, - ClutterState *state) +static void +clutter_state_new_frame (ClutterTimeline *timeline, + gint msecs, + ClutterState *state) { GList *k; gdouble progress; @@ -423,9 +430,9 @@ clutter_state_change (ClutterState *this, g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); g_return_val_if_fail (target_state_name, NULL); - if (target_state_name == NULL) target_state_name = "default"; + target_state_name = g_intern_string (target_state_name); if (priv->target_state_name == NULL) priv->target_state_name = g_intern_static_string ("default"); @@ -448,27 +455,35 @@ clutter_state_change (ClutterState *this, priv->target_state_name = target_state_name; if (animate) - clutter_timeline_set_duration (priv->timeline, - clutter_state_get_duration (this, priv->source_state_name, - priv->target_state_name)); + { + guint duration; + + duration = clutter_state_get_duration (this, + priv->source_state_name, + priv->target_state_name); + clutter_timeline_set_duration (priv->timeline, duration); + } else clutter_timeline_set_duration (priv->timeline, 1); state = g_hash_table_lookup (priv->states, target_state_name); - g_return_val_if_fail (state, NULL); { ClutterAnimator *animator; - animator = clutter_state_get_animator (this, priv->source_state_name, - priv->target_state_name); - if (animator) + + animator = clutter_state_get_animator (this, + priv->source_state_name, + priv->target_state_name); + if (animator != NULL) { priv->current_animator = animator; clutter_animator_set_timeline (animator, priv->timeline); + clutter_timeline_stop (priv->timeline); clutter_timeline_rewind (priv->timeline); clutter_timeline_start (priv->timeline); + return priv->timeline; } } @@ -485,10 +500,10 @@ clutter_state_change (ClutterState *this, g_value_init (&initial, clutter_interval_get_value_type (key->interval)); - g_object_get_property (key->object, - key->property_name, &initial); + g_object_get_property (key->object, key->property_name, &initial); if (clutter_alpha_get_mode (key->alpha) != key->mode) clutter_alpha_set_mode (key->alpha, key->mode); + clutter_interval_set_initial_value (key->interval, &initial); clutter_interval_set_final_value (key->interval, &key->value); @@ -500,9 +515,7 @@ clutter_state_change (ClutterState *this, clutter_timeline_start (priv->timeline); } else - { - g_warning ("Anim state '%s' not found\n", target_state_name); - } + g_warning ("State '%s' not found", target_state_name); return priv->timeline; } @@ -533,12 +546,12 @@ clutter_state_change (ClutterState *this, */ void clutter_state_set (ClutterState *state, - const gchar *source_state_name, - const gchar *target_state_name, - gpointer first_object, - const gchar *first_property_name, - gulong first_mode, - ...) + const gchar *source_state_name, + const gchar *target_state_name, + gpointer first_object, + const gchar *first_property_name, + gulong first_mode, + ...) { GObjectClass *klass; gpointer object; @@ -561,27 +574,32 @@ clutter_state_set (ClutterState *state, GValue value = { 0, }; gchar *error = NULL; const gchar *real_property_name = property_name; + klass = G_OBJECT_GET_CLASS (object); if (g_str_has_prefix (property_name, "delayed::")) - { - real_property_name = strstr (property_name, "::") + 2; - } + real_property_name = strstr (property_name, "::") + 2; pspec = g_object_class_find_property (klass, real_property_name); - - if (!pspec) + if (pspec == NULL) { - g_warning ("Cannot bind property '%s': object of type '%s' " + g_warning ("Cannot bind property '%s': objects of type '%s' " "do not have this property", - real_property_name, G_OBJECT_TYPE_NAME (object)); + real_property_name, + G_OBJECT_TYPE_NAME (object)); break; } +#if GLIB_CHECK_VERSION (2, 23, 2) + G_VALUE_COLLECT_INIT (&value, G_PARAM_SPEC_VALUE_TYPE (pspec), + args, 0, + &error); +#else g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); G_VALUE_COLLECT (&value, args, 0, &error); +#endif /* GLIB_CHECK_VERSION */ - if (error) + if (error != NULL) { g_warning ("%s: %s", G_STRLOC, error); g_free (error); @@ -592,6 +610,7 @@ clutter_state_set (ClutterState *state, { gdouble pre_delay = va_arg (args, gdouble); gdouble post_delay = va_arg (args, gdouble); + clutter_state_set_key (state, source_state_name, target_state_name, @@ -616,7 +635,7 @@ clutter_state_set (ClutterState *state, } object = va_arg (args, gpointer); - if (object) + if (object != NULL) { property_name = va_arg (args, gchar*); mode = va_arg (args, gulong); @@ -655,6 +674,7 @@ clutter_state_set_key (ClutterState *this, gdouble pre_delay, gdouble post_delay) { + GParamSpec *pspec; ClutterStateKey *state_key; GList *old_item; State *source_state = NULL; @@ -668,6 +688,16 @@ clutter_state_set_key (ClutterState *this, if (target_state_name == NULL) target_state_name = "default"; + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), + property_name); + if (pspec == NULL || !(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("No writable property '%s' for object type '%s' found", + property_name, + G_OBJECT_TYPE_NAME (object)); + return NULL; + } + source_state_name = g_intern_string (source_state_name); target_state_name = g_intern_string (target_state_name); property_name = g_intern_string (property_name); @@ -694,24 +724,29 @@ clutter_state_set_key (ClutterState *this, } } - state_key = clutter_state_key_new (target_state, object, property_name, mode); - state_key->source_state = source_state; + state_key = clutter_state_key_new (target_state, + object, property_name, pspec, + mode); + state_key->source_state = source_state; state_key->pre_delay = pre_delay; state_key->post_delay = post_delay; g_value_init (&state_key->value, G_VALUE_TYPE (value)); g_value_copy (value, &state_key->value); - if ((old_item = g_list_find_custom (target_state->keys, state_key, + if ((old_item = g_list_find_custom (target_state->keys, + state_key, sort_props_func))) { ClutterStateKey *old_key = old_item->data; - clutter_state_key_free (old_key); + target_state->keys = g_list_remove (target_state->keys, old_key); + clutter_state_key_free (old_key); } - target_state->keys = g_list_insert_sorted (target_state->keys, state_key, + target_state->keys = g_list_insert_sorted (target_state->keys, + state_key, sort_props_func); return this; } @@ -827,8 +862,12 @@ clutter_state_remove_key (ClutterState *state, GObject *object, const gchar *property_name) { - clutter_state_remove_key_internal (state, source_state_name, target_state_name, - object, property_name, FALSE); + g_return_if_fail (CLUTTER_IS_STATE (state)); + + clutter_state_remove_key_internal (state, + source_state_name, target_state_name, + object, property_name, + FALSE); } /** @@ -845,6 +884,7 @@ ClutterTimeline * clutter_state_get_timeline (ClutterState *state) { g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + return state->priv->timeline; } @@ -853,36 +893,54 @@ clutter_state_class_init (ClutterStateClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - gobject_class->finalize = clutter_state_finalize; - g_type_class_add_private (klass, sizeof (ClutterStatePrivate)); + gobject_class->finalize = clutter_state_finalize; + + /** + * ClutterState::completed: + * @state: the #ClutterState that emitted the signal + * + * The ::completed signal is emitted when a #ClutterState reaches + * the target state specified by clutter_state_change() + * + * Since: 1.4 + */ state_signals[COMPLETED] = - g_signal_new (I_("completed"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ClutterStateClass, completed), - NULL, NULL, - clutter_marshal_VOID__VOID, - G_TYPE_NONE, 0); + g_signal_new (I_("completed"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterStateClass, completed), + NULL, NULL, + clutter_marshal_VOID__VOID, + G_TYPE_NONE, 0); } static void clutter_state_init (ClutterState *self) { ClutterStatePrivate *priv; + priv = self->priv = CLUTTER_STATE_GET_PRIVATE (self); + priv->states = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, state_free); + NULL, + state_free); + self->priv->source_state_name = NULL; self->priv->target_state_name = NULL; + self->priv->duration = 1000; + priv->timeline = clutter_timeline_new (1000); - priv->slave_timeline = clutter_timeline_new (SLAVE_TIMELINE_LENGTH); g_signal_connect (priv->timeline, "new-frame", - G_CALLBACK (clutter_state_new_frame), self); + G_CALLBACK (clutter_state_new_frame), + self); g_signal_connect (priv->timeline, "completed", - G_CALLBACK (clutter_state_completed), self); + G_CALLBACK (clutter_state_completed), + self); + + priv->slave_timeline = clutter_timeline_new (SLAVE_TIMELINE_LENGTH); } @@ -912,20 +970,21 @@ clutter_state_get_animator (ClutterState *state, if (source_state_name == g_intern_static_string ("default") || source_state_name == g_intern_static_string ("")) source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); - target_state = g_hash_table_lookup (state->priv->states, - target_state_name); - if (!target_state) + target_state = g_hash_table_lookup (state->priv->states, target_state_name); + if (target_state == NULL) return NULL; animators = (StateAnimator*)target_state->animators->data; - for (i=0; animators[i].animator; i++) + for (i = 0; animators[i].animator; i++) { if (animators[i].source_state_name == source_state_name) return animators[i].animator; } + return NULL; } @@ -948,7 +1007,7 @@ clutter_state_set_animator (ClutterState *state, const gchar *target_state_name, ClutterAnimator *animator) { - State *target_state; + State *target_state; StateAnimator *animators; gint i; @@ -957,26 +1016,30 @@ clutter_state_set_animator (ClutterState *state, source_state_name = g_intern_string (source_state_name); target_state_name = g_intern_string (target_state_name); - target_state = g_hash_table_lookup (state->priv->states, - target_state_name); - if (!target_state) + target_state = g_hash_table_lookup (state->priv->states, target_state_name); + if (target_state == NULL) return; - animators = (StateAnimator*)target_state->animators->data; - for (i=0; animators[i].animator; i++) + animators = (StateAnimator *) target_state->animators->data; + for (i = 0; animators[i].animator; i++) { if (animators[i].source_state_name == source_state_name) { g_object_unref (animators[i].animator); - if (animator) + + if (animator != NULL) animators[i].animator = g_object_ref (animator); - else /* remove the matched animator if passed NULL */ - g_array_remove_index (target_state->animators, i); + else + { + /* remove the matched animator if passed NULL */ + g_array_remove_index (target_state->animators, i); + } + return; } } - if (animator) + if (animator != NULL) { StateAnimator state_animator = {source_state_name, g_object_ref (animator)}; @@ -1222,6 +1285,7 @@ clutter_state_set_duration (ClutterState *state, if (source_state_name == g_intern_static_string ("default") || source_state_name == g_intern_static_string ("")) source_state_name = NULL; + target_state_name = g_intern_string (target_state_name); if (target_state_name == g_intern_static_string ("default") || target_state_name == g_intern_static_string ("")) @@ -1232,15 +1296,17 @@ clutter_state_set_duration (ClutterState *state, state->priv->duration = duration; return; } - target_state = g_hash_table_lookup (state->priv->states, - target_state_name); - if (target_state) + + target_state = g_hash_table_lookup (state->priv->states, target_state_name); + if (target_state != NULL) { - if (source_state_name) - g_hash_table_insert (target_state->durations, (void*)source_state_name, + if (source_state_name != NULL) + g_hash_table_insert (target_state->durations, + (gpointer) source_state_name, GINT_TO_POINTER (duration)); else - g_hash_table_insert (target_state->durations, NULL, + g_hash_table_insert (target_state->durations, + NULL, GINT_TO_POINTER (duration)); } } From ae3dc9bd5ebf0ea3350d458c5560e9cd6aff9fe1 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 18:57:27 +0100 Subject: [PATCH 09/14] state: Documentation and introspection annotation fixes --- clutter/clutter-state.c | 179 ++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 69 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 10f930b42..0f85367f2 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -413,11 +413,17 @@ clutter_state_new_frame (ClutterTimeline *timeline, /** * clutter_state_change: * @state_name: a #ClutterState - * @animate: whether we should animate the transition or not. + * @animate: whether we should animate the transition or not * - * Change to @state_name and spend duration msecs when doing so. + * Change the current state of #ClutterState to @state_name * - * Return value: the #ClutterTimeline that drives the #ClutterState instance. + * If @animate is %FALSE, the state transition will happen immediately; + * otherwise, the state transition will be animated over the duration + * set using clutter_state_set_duration() + * + * Return value: the #ClutterTimeline that drives the state transition + * + * Since: 1.4 */ ClutterTimeline * clutter_state_change (ClutterState *this, @@ -428,7 +434,7 @@ clutter_state_change (ClutterState *this, State *state; g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); - g_return_val_if_fail (target_state_name, NULL); + g_return_val_if_fail (target_state_name != NULL, NULL); if (target_state_name == NULL) target_state_name = "default"; @@ -439,9 +445,7 @@ clutter_state_change (ClutterState *this, if (target_state_name == priv->target_state_name) { - /* Avoiding transitioning if the desired state - * is already current - */ + /* Avoid transitioning if the desired state is already current */ return priv->timeline; } @@ -524,23 +528,29 @@ clutter_state_change (ClutterState *this, /** * clutter_state_set: * @state: a #ClutterState instance. - * @source_state_name: the name of the source state keys are being added for. - * @target_state_name: the name of the target state keys are being added for. + * @source_state_name: the name of the source state keys are being added for + * @target_state_name: the name of the target state keys are being added for * @first_object: a #GObject - * @first_property_name: the property to specify a key for + * @first_property_name: a property of @first_object to specify a key for * @first_mode: the id of the alpha function to use - * @...: the value first_property_name should have in state_name, followed by - * more object,property_name,mode,value,... or NULL to terminate the varargs. + * @Varargs: the value @first_property_name should have in @state_name, + * followed by object, property name, mode, value tuples, terminated + * by %NULL * * Adds multiple keys to a named state of a #ClutterState instance, specifying * the easing mode and value a given property of an object should have at a - * given progress of the animation. The mode specified is the mode used when - * going to this key from the previous key of the property_name, If a given - * object, state_name, property triple already exist the mode and value will be - * replaced with the new values for the existing key. If a the property_name is - * prefixed with "delayed::" two additional arguments are expected per key with a - * value relative to the full state time to pause before transitioning and - * after transitioning within the total transition time. + * given progress of the animation. + * + * The mode specified is the easing mode used when going to from the previous + * key to the specified key. + * + * If a given object, state_name, property tuple already exist then the mode + * and value will be replaced with the new specified values. + * + * If a the property_name is prefixed with "delayed::" two additional + * arguments per key are expected: a value relative to the full state time + * to pause before transitioning and a similar value to pause after + * transitioning. * * Since: 1.4 */ @@ -654,13 +664,17 @@ clutter_state_set (ClutterState *state, * @property_name: the property to set a key for * @mode: the id of the alpha function to use * @value: the value for property_name of object in state_name - * @pre_delay: relative time of the transition to be idle in the beginning of the transition - * @post_delay: relative time of the transition to be idle in the end of the transition + * @pre_delay: relative time of the transition to be idle in the beginning + * of the transition + * @post_delay: relative time of the transition to be idle in the end of + * the transition * * Sets one specific end key for a state_name, object, property_name * combination. * - * Returns: the #ClutterState instance, allowing chaining of multiple calls. + * Return value: the #ClutterState instance, allowing chaining of + * multiple calls + * * Since: 1.4 */ ClutterState * @@ -755,40 +769,53 @@ clutter_state_set_key (ClutterState *this, * clutter_state_get_states: * @state: a #ClutterState instance. * - * Get a list of all the state names managed by this #ClutterState. + * Gets a list of all the state names managed by this #ClutterState. + * + * Return value: (transfer container) (element-type utf8): a newly allocated + * #GList of state names. The contents of the returned #GList are owned + * by the #ClutterState and should not be modified or freed. Use + * g_list_free() to free the resources allocated by the returned list when + * done using it * - * Returns: a GList of const gchar * containing state names. * Since: 1.4 */ GList * clutter_state_get_states (ClutterState *state) { + g_return_val_if_fail (CLUTTER_IS_STATE (state), NULL); + return g_hash_table_get_keys (state->priv->states); } /** * clutter_state_get_keys: * @state: a #ClutterState instance. - * @source_state_name: the source transition name to query for, or NULL for all - * source state. - * @target_state_name: the target transition name to query for, or NULL for all - * target state. - * @object: the specific object instance to list keys for, or NULL for all - * managed objects. - * @property_name: the property name to search for or NULL for all properties. + * @source_state_name: (allow-none): the source transition name to query, + * or %NULL for all source states + * @target_state_name: (allow-none): the target transition name to query, + * or %NULL for all target states + * @object: (allow-none): the specific object instance to list keys for, + * or %NULL for all managed objects + * @property_name: (allow-none): the property name to search for, or %NULL + * for all properties. * * Returns a list of pointers to opaque structures with accessor functions * that describe the keys added to an animator. * - * Return value: a GList of #ClutterAnimtorKeys. + * Return value: (transfer container) (element-type ClutterStateKey): a + * newly allocated #GList of #ClutterStateKeys. The contents of + * the returned list are owned by the #ClutterState and should not be + * modified or freed. Use g_list_free() to free the resources allocated + * by the returned list when done using it + * * Since: 1.4 */ GList * clutter_state_get_keys (ClutterState *state, - const gchar *source_state_name, - const gchar *target_state_name, - GObject *object, - const gchar *property_name) + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name) { GList *s, *state_list; GList *targets = NULL; @@ -801,13 +828,9 @@ clutter_state_get_keys (ClutterState *state, property_name = g_intern_string (property_name); if (target_state_name != NULL) - { - state_list = g_list_append (NULL, (void*)target_state_name); - } + state_list = g_list_append (NULL, (gpointer) target_state_name); else - { - state_list = clutter_state_get_states (state); - } + state_list = clutter_state_get_states (state); if (source_state_name) source_state = g_hash_table_lookup (state->priv->states, @@ -825,31 +848,35 @@ clutter_state_get_keys (ClutterState *state, for (k = target_state->keys; k; k = k->next) { ClutterStateKey *key = k->data; + if ((object == NULL || (object == key->object)) && (source_state_name == NULL || source_state == key->source_state) && (property_name == NULL || ((property_name == key->property_name)))) { - targets = g_list_append (targets, key); + targets = g_list_prepend (targets, key); } } } } + g_list_free (state_list); - return targets; + + return g_list_reverse (targets); } /** * clutter_state_remove_key: * @state: a #ClutterState instance. - * @source_state_name: the source state name to query for, or NULL for all - * source state. - * @target_state_name: the target state name to query for, or NULL for all - * target state. - * @object: the specific object instance to list keys for, or NULL for all - * managed objects. - * @property_name: the property name to search for or NULL for all properties. + * @source_state_name: (allow-none): the source state name to query, + * or %NULL for all source states + * @target_state_name: (allow-none): the target state name to query, + * or %NULL for all target states + * @object: (allow-none): the specific object instance to list keys for, + * or %NULL for all managed objects + * @property_name: (allow-none): the property name to search for, + * or %NULL for all properties. * * Removes all keys matching the search criteria passed in arguments. * @@ -857,10 +884,10 @@ clutter_state_get_keys (ClutterState *state, */ void clutter_state_remove_key (ClutterState *state, - const gchar *source_state_name, - const gchar *target_state_name, - GObject *object, - const gchar *property_name) + const gchar *source_state_name, + const gchar *target_state_name, + GObject *object, + const gchar *property_name) { g_return_if_fail (CLUTTER_IS_STATE (state)); @@ -874,9 +901,11 @@ clutter_state_remove_key (ClutterState *state, * clutter_state_get_timeline: * @state: a #ClutterState * - * Get the timeline driving the #ClutterState + * Gets the timeline driving the #ClutterState * - * Return value: the #ClutterTimeline that drives the state change animations. + * Return value: (transfer none): the #ClutterTimeline that drives + * the state change animations. The returned timeline is owned + * by the #ClutterState and it should not be unreferenced directly * * Since: 1.4 */ @@ -950,10 +979,12 @@ clutter_state_init (ClutterState *self) * @source_state_name: the name of a source state * @target_state_name: the name of a target state * - * Retrieve a ClutterAnimation that is being used for transitioning between two - * state if any. + * Retrieves the #ClutterAnimator that is being used for transitioning + * between the two states, if any has been set * - * Returns: a #ClutterAnimator instance or NULL + * Return value: (transfer none): a #ClutterAnimator instance, or %NULL + * + * Since: 1.4 */ ClutterAnimator * clutter_state_get_animator (ClutterState *state, @@ -993,13 +1024,21 @@ clutter_state_get_animator (ClutterState *state, * @state: a #ClutterState instance. * @source_state_name: the name of a source state * @target_state_name: the name of a target state - * @animator: a #ClutterAnimator instance. + * @animator: (allow-none): a #ClutterAnimator instance, or %NULL to + * unset an existing #ClutterAnimator * - * Specify a ClutterAnimation to be used when transitioning between the two - * named state, this allows specifying a transition between the state that is + * Specifies a #ClutterAnimator to be used when transitioning between + * the two named states. + * + * The @animator allows specifying a transition between the state that is * more elaborate than the basic transitions other allowed by the simple - * tweening of #ClutterState. Pass NULL to unset an existing animator. - * ClutterState takes a reference on the passed in animator if any. + * tweening of properties defined in the #ClutterState keys. + * + * If @animator is %NULL it will unset an existing animator. + * + * #ClutterState will take a reference on the passed @animator, if any + * + * Since: 1.4 */ void clutter_state_set_animator (ClutterState *state, @@ -1050,12 +1089,14 @@ clutter_state_set_animator (ClutterState *state, static gpointer clutter_state_key_copy (gpointer boxed) { - ClutterStateKey *key = boxed; + if (boxed != NULL) + { + ClutterStateKey *key = boxed; - if (key != NULL) - key->ref_count += 1; + key->ref_count += 1; + } - return key; + return boxed; } GType From 84f165b460bcadf83f4174368e047d5c53f95b24 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 20 May 2010 18:58:30 +0100 Subject: [PATCH 10/14] Add State interactive tests to the ignore file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 65a694870..ba8d38d09 100644 --- a/.gitignore +++ b/.gitignore @@ -149,6 +149,8 @@ TAGS /tests/interactive/stamp-test-interactive /tests/interactive/test-animator /tests/interactive/test-stage-sizing +/tests/interactive/test-state +/tests/interactive/test-state-animator /tests/conform/stamp-test-conformance /tests/conform/test-anchors /tests/conform/test-cogl-backface-culling From 9c4467707f13e3f6f780d6f88ee1da5929e39f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Thu, 20 May 2010 23:05:16 +0100 Subject: [PATCH 11/14] state: documented data structures --- clutter/clutter-state.c | 84 ++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 0f85367f2..2ef53eb42 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -48,33 +48,37 @@ G_DEFINE_TYPE (ClutterState, clutter_state, G_TYPE_OBJECT); typedef struct StateAnimator { - const gchar *source_state_name; - ClutterAnimator *animator; + const gchar *source_state_name; /* interned string identifying entry */ + ClutterAnimator *animator; /* pointer to animator itself */ } StateAnimator; -typedef struct State { - ClutterState *state; - const gchar *name; - GHashTable *durations; /* contains state objects */ - GList *keys; /* list of all keys pertaining to transitions - from other states to this one */ - GArray *animators; /* list of animators for transitioning from - * specific source states - */ +typedef struct State +{ + const gchar *name; /* interned string for this state name */ + GHashTable *durations; /* durations for transitions from various state + names */ + GList *keys; /* list of all keys pertaining to transitions + from other states to this one */ + GArray *animators; /* list of animators for transitioning from + * specific source states */ + ClutterState *clutter_state; /* the ClutterState object this state belongs to + */ } State; struct _ClutterStatePrivate { - ClutterTimeline *timeline; - ClutterTimeline *slave_timeline; - const gchar *source_state_name; - const gchar *target_state_name; - State *source_state; - State *target_state; - GHashTable *states; /* contains state objects */ - - ClutterAnimator *current_animator; - guint duration; /* global fallback duration */ + GHashTable *states; /* contains state objects */ + guint duration; /* global fallback duration */ + ClutterTimeline *timeline; /* The timeline used for doing the + progress */ + ClutterTimeline *slave_timeline; /* a slave timeline used to compute + alphas */ + const gchar *source_state_name; /* current source state */ + State *source_state; /* current source_state */ + const gchar *target_state_name; /* current target state */ + State *target_state; /* target state name */ + ClutterAnimator *current_animator; /* !NULL if the current transition is + overriden by an animator */ }; #define SLAVE_TIMELINE_LENGTH 10000 @@ -87,22 +91,24 @@ struct _ClutterStatePrivate */ typedef struct _ClutterStateKey { - GObject *object; - const gchar *property_name; - gulong mode; - GValue value; - gdouble pre_delay; - gdouble post_delay; + GObject *object; /* an Gobject */ + const gchar *property_name;/* the name of a property */ + gulong mode; /* alpha to use */ + GValue value; /* target value */ + gdouble pre_delay; /* fraction of duration to delay before + starting */ + gdouble post_delay; /* fraction of duration to be done in */ - State *source_state; - State *target_state; - ClutterAlpha *alpha; - ClutterInterval *interval; + State *source_state; /* source state */ + State *target_state; /* target state */ - ClutterState *state; + ClutterAlpha *alpha; /* The alpha this key uses for interpolation */ + ClutterInterval *interval; /* The interval this key uses for + interpolation */ - guint is_inert : 1; - gint ref_count; + guint is_inert : 1; /* set if the key is being destroyed due to + weak reference */ + gint ref_count; /* reference count for boxed life time */ } _ClutterStateKey; enum @@ -174,7 +180,7 @@ clutter_state_key_new (State *state, g_object_ref_sink (state_key->alpha); clutter_alpha_set_mode (state_key->alpha, mode); clutter_alpha_set_timeline (state_key->alpha, - state->state->priv->slave_timeline); + state->clutter_state->priv->slave_timeline); state_key->interval = g_object_new (CLUTTER_TYPE_INTERVAL, @@ -188,7 +194,7 @@ clutter_state_key_new (State *state, g_value_unset (&value); g_object_weak_ref (object, object_disappeared, - state_key->target_state->state); + state_key->target_state->clutter_state); return state_key; } @@ -208,7 +214,7 @@ clutter_state_key_free (gpointer clutter_state_key) if (!key->is_inert) g_object_weak_unref (key->object, object_disappeared, - key->target_state->state); + key->target_state->clutter_state); g_object_unref (key->alpha); g_object_unref (key->interval); @@ -298,13 +304,13 @@ state_free (gpointer data) } static State * -state_new (ClutterState *this, +state_new (ClutterState *clutter_state, const gchar *name) { State *state; state = g_new0 (State, 1); - state->state = this; + state->clutter_state = clutter_state; state->name = name; state->animators = g_array_new (TRUE, TRUE, sizeof (StateAnimator)); state->durations = g_hash_table_new (g_direct_hash, g_direct_equal); From d156550633a4fc7580d6e06b726845fb414f7196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Fri, 21 May 2010 10:37:42 +0100 Subject: [PATCH 12/14] state: added a "target-state" property When set the ClutterState transitions to the new state. --- clutter/clutter-state.c | 87 +++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 2ef53eb42..0d1e9733d 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -111,6 +111,12 @@ typedef struct _ClutterStateKey gint ref_count; /* reference count for boxed life time */ } _ClutterStateKey; +enum +{ + PROP_0, + PROP_TARGET_STATE +}; + enum { COMPLETED, @@ -443,7 +449,7 @@ clutter_state_change (ClutterState *this, g_return_val_if_fail (target_state_name != NULL, NULL); if (target_state_name == NULL) - target_state_name = "default"; + target_state_name = g_intern_static_string ("default"); target_state_name = g_intern_string (target_state_name); if (priv->target_state_name == NULL) @@ -464,6 +470,8 @@ clutter_state_change (ClutterState *this, priv->source_state_name = priv->target_state_name; priv->target_state_name = target_state_name; + g_object_notify (G_OBJECT (this), "target-state"); + if (animate) { guint duration; @@ -562,11 +570,11 @@ clutter_state_change (ClutterState *this, */ void clutter_state_set (ClutterState *state, - const gchar *source_state_name, - const gchar *target_state_name, - gpointer first_object, - const gchar *first_property_name, - gulong first_mode, + const gchar *source_state_name, + const gchar *target_state_name, + gpointer first_object, + const gchar *first_property_name, + gulong first_mode, ...) { GObjectClass *klass; @@ -855,10 +863,12 @@ clutter_state_get_keys (ClutterState *state, { ClutterStateKey *key = k->data; - if ((object == NULL || (object == key->object)) && - (source_state_name == NULL || source_state == key->source_state) && - (property_name == NULL || - ((property_name == key->property_name)))) + if ( (object == NULL || (object == key->object)) + && (source_state_name == NULL + ||source_state == key->source_state) + && (property_name == NULL + ||(property_name == key->property_name)) + ) { targets = g_list_prepend (targets, key); } @@ -923,14 +933,54 @@ clutter_state_get_timeline (ClutterState *state) return state->priv->timeline; } + +static void +clutter_state_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterState *state = CLUTTER_STATE (object); + + switch (prop_id) + { + case PROP_TARGET_STATE: + clutter_state_change (state, g_value_get_string (value), TRUE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +clutter_state_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterState *state = CLUTTER_STATE (object); + switch (prop_id) + { + case PROP_TARGET_STATE: + g_value_set_string (value, + clutter_state_get_target_state (state)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + static void clutter_state_class_init (ClutterStateClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; g_type_class_add_private (klass, sizeof (ClutterStatePrivate)); - gobject_class->finalize = clutter_state_finalize; + gobject_class->finalize = clutter_state_finalize; + gobject_class->set_property = clutter_state_set_property; + gobject_class->get_property = clutter_state_get_property; /** * ClutterState::completed: @@ -949,6 +999,21 @@ clutter_state_class_init (ClutterStateClass *klass) NULL, NULL, clutter_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * ClutterState:target-state: + * + * The currently set target state, setting it causes the + * state machine to transition to the new state, use + * clutter_state_change() directly to directly jump to + * a state. + */ + pspec = g_param_spec_string ("target-state", + "Target State", + "Currently set state", + "default", + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_TARGET_STATE, pspec); } static void From 6ca425679ea27c7fe2bc8e455b8d55785200841e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 21 May 2010 14:13:14 +0100 Subject: [PATCH 13/14] script: Implement State deserialization It should be possible to describe ClutterState transitions using ClutterScript in a similar way as ClutterAnimator. --- .gitignore | 1 + clutter/clutter-state.c | 296 ++++++++++++++++++++++++++++-- tests/conform/Makefile.am | 1 + tests/conform/test-conform-main.c | 1 + tests/conform/test-state.c | 59 ++++++ tests/data/Makefile.am | 1 + tests/data/test-state-1.json | 33 ++++ 7 files changed, 375 insertions(+), 17 deletions(-) create mode 100644 tests/conform/test-state.c create mode 100644 tests/data/test-state-1.json diff --git a/.gitignore b/.gitignore index ba8d38d09..5aaf1eaaf 100644 --- a/.gitignore +++ b/.gitignore @@ -258,6 +258,7 @@ TAGS /tests/conform/test-cogl-path /tests/conform/test-cogl-wrap-modes /tests/conform/test-clutter-cairo-texture +/tests/conform/test-state-base /tests/conform/wrappers /tests/micro-bench/test-text-perf /tests/micro-bench/test-text diff --git a/clutter/clutter-state.c b/clutter/clutter-state.c index 0d1e9733d..95bd81a31 100644 --- a/clutter/clutter-state.c +++ b/clutter/clutter-state.c @@ -44,8 +44,8 @@ #include "clutter-interval.h" #include "clutter-marshal.h" #include "clutter-private.h" - -G_DEFINE_TYPE (ClutterState, clutter_state, G_TYPE_OBJECT); +#include "clutter-scriptable.h" +#include "clutter-script-private.h" typedef struct StateAnimator { const gchar *source_state_name; /* interned string identifying entry */ @@ -123,6 +123,8 @@ enum LAST_SIGNAL }; +static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); + static guint state_signals[LAST_SIGNAL] = {0, }; #define CLUTTER_STATE_GET_PRIVATE(obj) \ @@ -130,6 +132,10 @@ static guint state_signals[LAST_SIGNAL] = {0, }; CLUTTER_TYPE_STATE, \ ClutterStatePrivate)) +G_DEFINE_TYPE_WITH_CODE (ClutterState, clutter_state, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE, + clutter_scriptable_iface_init)); + /** * clutter_state_new: * @@ -669,6 +675,28 @@ clutter_state_set (ClutterState *state, va_end (args); } +static void +clutter_state_set_key_internal (ClutterState *state, + ClutterStateKey *key) +{ + State *target_state = key->target_state; + GList *old_item = NULL; + + if ((old_item = g_list_find_custom (target_state->keys, + key, + sort_props_func))) + { + ClutterStateKey *old_key = old_item->data; + + target_state->keys = g_list_remove (target_state->keys, old_key); + clutter_state_key_free (old_key); + } + + target_state->keys = g_list_insert_sorted (target_state->keys, + key, + sort_props_func); +} + /** * clutter_state_set_key: * @this: a #ClutterState instance. @@ -704,9 +732,8 @@ clutter_state_set_key (ClutterState *this, { GParamSpec *pspec; ClutterStateKey *state_key; - GList *old_item; - State *source_state = NULL; - State *target_state; + State *source_state = NULL; + State *target_state; g_return_val_if_fail (CLUTTER_IS_STATE (this), NULL); g_return_val_if_fail (G_IS_OBJECT (object), NULL); @@ -763,19 +790,8 @@ clutter_state_set_key (ClutterState *this, g_value_init (&state_key->value, G_VALUE_TYPE (value)); g_value_copy (value, &state_key->value); - if ((old_item = g_list_find_custom (target_state->keys, - state_key, - sort_props_func))) - { - ClutterStateKey *old_key = old_item->data; + clutter_state_set_key_internal (this, state_key); - target_state->keys = g_list_remove (target_state->keys, old_key); - clutter_state_key_free (old_key); - } - - target_state->keys = g_list_insert_sorted (target_state->keys, - state_key, - sort_props_func); return this; } @@ -1505,3 +1521,249 @@ clutter_state_get_target_state (ClutterState *state) return state->priv->target_state_name; } + +typedef struct _ParseClosure { + ClutterState *state; + ClutterScript *script; + + GValue *value; + + gboolean result; +} ParseClosure; + +static void +parse_state_transition (JsonArray *array, + guint index_, + JsonNode *element, + gpointer data) +{ + ParseClosure *clos = data; + ClutterStatePrivate *priv = clos->state->priv; + JsonObject *object; + const gchar *source_name, *target_name; + State *source_state, *target_state; + JsonArray *keys; + GSList *valid_keys = NULL; + GList *k; + + if (JSON_NODE_TYPE (element) != JSON_NODE_OBJECT) + { + g_warning ("The 'transitions' member of a ClutterState 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, "source") || + !json_object_has_member (object, "target") || + !json_object_has_member (object, "keys")) + { + g_warning ("The transition description at index %d is missing one " + "of the mandatory members: source, target and keys", + index_); + return; + } + + source_name = json_object_get_string_member (object, "source"); + target_name = json_object_get_string_member (object, "target"); + + keys = json_object_get_array_member (object, "keys"); + if (keys == NULL) + { + g_warning ("The transition description at index %d has an invalid " + "key member of type '%s' when an array was expected.", + index_, + json_node_type_name (json_object_get_member (object, "keys"))); + return; + } + + source_name = g_intern_string (source_name); + source_state = g_hash_table_lookup (priv->states, source_name); + if (source_state == NULL) + { + source_state = state_new (clos->state, source_name); + g_hash_table_insert (priv->states, (gpointer) source_name, source_state); + } + + target_name = g_intern_string (target_name); + target_state = g_hash_table_lookup (priv->states, target_name); + if (target_state == NULL) + { + target_state = state_new (clos->state, target_name); + g_hash_table_insert (priv->states, (gpointer) target_name, target_state); + } + + if (json_object_has_member (object, "duration")) + { + guint duration = json_object_get_int_member (object, "duration"); + + clutter_state_set_duration (clos->state, + source_name, + target_name, + duration); + } + + if (json_object_has_member (object, "animator")) + { + const gchar *id = json_object_get_string_member (object, "animator"); + GObject *animator; + + animator = clutter_script_get_object (clos->script, id); + if (animator == NULL) + { + g_warning ("No object with id '%s' has been defined.", id); + return; + } + + clutter_state_set_animator (clos->state, + source_name, + target_name, + CLUTTER_ANIMATOR (animator)); + } + + if (G_IS_VALUE (clos->value)) + valid_keys = g_slist_reverse (g_value_get_pointer (clos->value)); + else + g_value_init (clos->value, G_TYPE_POINTER); + + for (k = json_array_get_elements (keys); + k != NULL; + k = k->next) + { + JsonNode *node = k->data; + JsonArray *key = json_node_get_array (node); + ClutterStateKey *state_key; + GObject *gobject; + GParamSpec *pspec; + const gchar *id; + const gchar *property; + gulong mode; + gboolean res; + + id = json_array_get_string_element (key, 0); + gobject = clutter_script_get_object (clos->script, id); + if (gobject == NULL) + { + g_warning ("No object with id '%s' has been defined.", id); + continue; + } + + property = json_array_get_string_element (key, 1); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (gobject), + property); + if (pspec == NULL) + { + g_warning ("The object of type '%s' and name '%s' has no " + "property named '%s'.", + G_OBJECT_TYPE_NAME (gobject), + id, + property); + continue; + } + + mode = clutter_script_resolve_animation_mode (json_array_get_element (key, 2)); + + state_key = clutter_state_key_new (target_state, + gobject, property, pspec, + mode); + + res = clutter_script_parse_node (clos->script, + &(state_key->value), + property, + json_array_get_element (key, 3), + pspec); + if (!res) + { + g_warning ("Unable to parse the key value for the " + "property '%s' of object '%s' at index %d", + property, + id, + index_); + clutter_state_key_free (state_key); + continue; + } + + if (json_array_get_length (key) == 5) + { + state_key->pre_delay = json_array_get_double_element (key, 4); + state_key->post_delay = 0.0; + } + else if (json_array_get_length (key) == 6) + { + state_key->pre_delay = json_array_get_double_element (key, 4); + state_key->post_delay = json_array_get_double_element (key, 5); + } + else + { + state_key->pre_delay = 0.0; + state_key->post_delay = 0.0; + } + + state_key->source_state = source_state; + + valid_keys = g_slist_prepend (valid_keys, state_key); + } + + g_value_set_pointer (clos->value, g_slist_reverse (valid_keys)); + + clos->result = TRUE; +} + +static gboolean +clutter_state_parse_custom_node (ClutterScriptable *scriptable, + ClutterScript *script, + GValue *value, + const gchar *name, + JsonNode *node) +{ + ParseClosure clos; + + if (strcmp (name, "transitions") != 0) + return FALSE; + + if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY) + return FALSE; + + clos.state = CLUTTER_STATE (scriptable); + clos.script = script; + clos.value = value; + clos.result = FALSE; + + json_array_foreach_element (json_node_get_array (node), + parse_state_transition, + &clos); + + return clos.result; +} + +static void +clutter_state_set_custom_property (ClutterScriptable *scriptable, + ClutterScript *script, + const gchar *name, + const GValue *value) +{ + if (strcmp (name, "transitions") == 0) + { + ClutterState *state = CLUTTER_STATE (scriptable); + GSList *keys = g_value_get_pointer (value); + GSList *k; + + for (k = keys; k != NULL; k = k->next) + clutter_state_set_key_internal (state, k->data); + + g_slist_free (keys); + } + else + g_object_set_property (G_OBJECT (scriptable), name, value); +} + +static void +clutter_scriptable_iface_init (ClutterScriptableIface *iface) +{ + iface->parse_custom_node = clutter_state_parse_custom_node; + iface->set_custom_property = clutter_state_set_custom_property; +} diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index 5ad918d81..59ddea711 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -51,6 +51,7 @@ test_conformance_SOURCES = \ test-actor-destroy.c \ test-behaviours.c \ test-animator.c \ + test-state.c \ $(NULL) # For convenience, this provides a way to easily run individual unit tests: diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index 44e64d36d..939220d06 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -184,6 +184,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/script", test_animator_base); TEST_CONFORM_SIMPLE ("/script", test_animator_properties); TEST_CONFORM_SIMPLE ("/script", test_animator_multi_properties); + TEST_CONFORM_SIMPLE ("/script", test_state_base); TEST_CONFORM_SIMPLE ("/behaviours", test_behaviours); diff --git a/tests/conform/test-state.c b/tests/conform/test-state.c new file mode 100644 index 000000000..81244378c --- /dev/null +++ b/tests/conform/test-state.c @@ -0,0 +1,59 @@ +#include + +#include "test-conform-common.h" + +void +test_state_base (TestConformSimpleFixture *fixture G_GNUC_UNUSED, + gconstpointer dummy G_GNUC_UNUSED) +{ + ClutterScript *script = clutter_script_new (); + GObject *state = NULL; + GError *error = NULL; + gchar *test_file; + GList *states, *keys; + ClutterStateKey *state_key; + guint duration; + + test_file = clutter_test_get_data_file ("test-state-1.json"); + clutter_script_load_from_file (script, test_file, &error); + if (g_test_verbose () && error) + g_print ("Error: %s\n", error->message); + + g_free (test_file); + +#if GLIB_CHECK_VERSION (2, 20, 0) + g_assert_no_error (error); +#else + g_assert (error == NULL); +#endif + + state = clutter_script_get_object (script, "state"); + g_assert (CLUTTER_IS_STATE (state)); + + states = clutter_state_get_states (CLUTTER_STATE (state)); + g_assert (states != NULL); + + g_assert (g_list_find (states, g_intern_static_string ("clicked"))); + g_list_free (states); + + duration = clutter_state_get_duration (CLUTTER_STATE (state), "*", "clicked"); + g_assert_cmpint (duration, ==, 250); + + duration = clutter_state_get_duration (CLUTTER_STATE (state), "clicked", "*"); + g_assert_cmpint (duration, ==, 150); + + keys = clutter_state_get_keys (CLUTTER_STATE (state), "*", "clicked", + clutter_script_get_object (script, "rect"), + "opacity"); + g_assert (keys != NULL); + g_assert_cmpint (g_list_length (keys), ==, 1); + + state_key = keys->data; + g_assert (clutter_state_key_get_object (state_key) == clutter_script_get_object (script, "rect")); + g_assert (clutter_state_key_get_mode (state_key) == CLUTTER_LINEAR); + g_assert_cmpstr (clutter_state_key_get_property_name (state_key), ==, "opacity"); + + g_list_free (keys); + + g_object_unref (script); +} diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 95ff06080..2dfca3e79 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -12,6 +12,7 @@ json_files = \ test-animator-1.json \ test-animator-2.json \ test-animator-3.json \ + test-state-1.json \ $(NULL) png_files = \ diff --git a/tests/data/test-state-1.json b/tests/data/test-state-1.json new file mode 100644 index 000000000..ad4f3fa12 --- /dev/null +++ b/tests/data/test-state-1.json @@ -0,0 +1,33 @@ +[ + { + "type" : "ClutterRectangle", + "id" : "rect", + "width" : 100, + "height" : 100 + }, + { + "type" : "ClutterState", + "id" : "state", + + "transitions" : [ + { + "source" : "*", + "target" : "clicked", + "duration" : 250, + + "keys" : [ + [ "rect", "opacity", "linear", 128 ] + ] + }, + { + "source" : "clicked", + "target" : "*", + "duration" : 150, + + "keys" : [ + [ "rect", "opacity", "linear", 255 ] + ] + } + ] + } +] From 9fcbb274e01e4fb5df2d08fddfdfd3b91fe63e4e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 21 May 2010 14:22:56 +0100 Subject: [PATCH 14/14] Do not use wildcards in test-state Use named states, like in a real case. --- tests/conform/test-state.c | 6 +++--- tests/data/test-state-1.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conform/test-state.c b/tests/conform/test-state.c index 81244378c..0552ff9af 100644 --- a/tests/conform/test-state.c +++ b/tests/conform/test-state.c @@ -36,13 +36,13 @@ test_state_base (TestConformSimpleFixture *fixture G_GNUC_UNUSED, g_assert (g_list_find (states, g_intern_static_string ("clicked"))); g_list_free (states); - duration = clutter_state_get_duration (CLUTTER_STATE (state), "*", "clicked"); + duration = clutter_state_get_duration (CLUTTER_STATE (state), "base", "clicked"); g_assert_cmpint (duration, ==, 250); - duration = clutter_state_get_duration (CLUTTER_STATE (state), "clicked", "*"); + duration = clutter_state_get_duration (CLUTTER_STATE (state), "clicked", "base"); g_assert_cmpint (duration, ==, 150); - keys = clutter_state_get_keys (CLUTTER_STATE (state), "*", "clicked", + keys = clutter_state_get_keys (CLUTTER_STATE (state), "base", "clicked", clutter_script_get_object (script, "rect"), "opacity"); g_assert (keys != NULL); diff --git a/tests/data/test-state-1.json b/tests/data/test-state-1.json index ad4f3fa12..78ab5de89 100644 --- a/tests/data/test-state-1.json +++ b/tests/data/test-state-1.json @@ -11,7 +11,7 @@ "transitions" : [ { - "source" : "*", + "source" : "base", "target" : "clicked", "duration" : 250, @@ -21,7 +21,7 @@ }, { "source" : "clicked", - "target" : "*", + "target" : "base", "duration" : 150, "keys" : [