script: Allow connecting signal to ClutterState states

One of the uses of a ClutterState state machine along with ClutterScript
is to provide a quick way to transition from state to state in response
to signal emitted on specific instances.

Connecting a real function, in code, to a specific signal does not
improve the ease of use of ClutterScript to define scenes.

By adding a new signal definition to the current one we can have both a
simple way to define application logic in code and in the UI definition
file.

The new syntax is trivial:

  {
    "name" : <signal name>,
    "state" : <state machine script id>,
    "target-state" : <target state>
  }

The ClutterState instance is identified by its script id, and the target
state is resolved at run-time, so it can be defined both in
ClutterScript or in code. Ideally, we should find a way to associate a
default ClutterState instance to the ClutterScript one that parses the
definition; this way we would be able to remove the "state" member, or
even "style" the behaviour of an object by replacing the ClutterState
instance.

The implementation uses a signal emission hook, to avoid knowing the
signal signature; we check the emitter of the signal against the object
that defined the signal, to avoid erroneous state changes.
This commit is contained in:
Emmanuele Bassi 2011-02-07 13:48:58 +00:00
parent 2c2bdc1d2c
commit dd8cf63a62
3 changed files with 182 additions and 53 deletions

View File

@ -560,12 +560,9 @@ parse_signals (ClutterScript *script,
for (i = 0; i < array_len; i++)
{
JsonNode *val = json_array_get_element (array, i);
SignalInfo *sinfo = NULL;
JsonObject *object;
SignalInfo *sinfo;
const gchar *name;
const gchar *handler;
const gchar *connect;
GConnectFlags flags = 0;
if (JSON_NODE_TYPE (val) != JSON_NODE_OBJECT)
{
@ -595,56 +592,97 @@ parse_signals (ClutterScript *script,
}
}
/* mandatory: "handler" */
if (!json_object_has_member (object, "handler"))
/* mandatory: "state" */
if (json_object_has_member (object, "state"))
{
_clutter_script_warn_missing_attribute (script, NULL, "handler");
continue;
const gchar *state;
const gchar *target;
state = json_object_get_string_member (object, "state");
if (state == NULL)
{
_clutter_script_warn_invalid_value (script,
"state", "string",
val);
continue;
}
target = json_object_get_string_member (object, "target-state");
if (target == NULL)
{
_clutter_script_warn_invalid_value (script,
"target-state", "string",
val);
continue;
}
CLUTTER_NOTE (SCRIPT,
"Added signal '%s' (state:%s, target:%s)",
name,
state, target);
sinfo = g_slice_new0 (SignalInfo);
sinfo->is_handler = FALSE;
sinfo->name = g_strdup (name);
sinfo->state = g_strdup (state);
sinfo->target = g_strdup (target);
}
else
/* mandatory: "handler" */
if (json_object_has_member (object, "handler"))
{
const gchar *handler;
const gchar *connect;
GConnectFlags flags = 0;
handler = json_object_get_string_member (object, "handler");
if (!handler)
if (handler == NULL)
{
_clutter_script_warn_invalid_value (script,
"handler", "string",
val);
continue;
}
/* optional: "object" */
if (json_object_has_member (object, "object"))
connect = json_object_get_string_member (object, "object");
else
connect = NULL;
/* optional: "after" */
if (json_object_has_member (object, "after"))
{
if (json_object_get_boolean_member (object, "after"))
flags |= G_CONNECT_AFTER;
}
/* optional: "swapped" */
if (json_object_has_member (object, "swapped"))
{
if (json_object_get_boolean_member (object, "swapped"))
flags |= G_CONNECT_SWAPPED;
}
CLUTTER_NOTE (SCRIPT,
"Added signal '%s' (handler:%s, object:%s, flags:%d)",
name,
handler, connect, flags);
sinfo = g_slice_new0 (SignalInfo);
sinfo->is_handler = TRUE;
sinfo->name = g_strdup (name);
sinfo->handler = g_strdup (handler);
sinfo->object = g_strdup (connect);
sinfo->flags = flags;
}
/* optional: "object" */
if (json_object_has_member (object, "object"))
connect = json_object_get_string_member (object, "object");
if (sinfo != NULL)
retval = g_list_prepend (retval, sinfo);
else
connect = NULL;
/* optional: "after" */
if (json_object_has_member (object, "after"))
{
if (json_object_get_boolean_member (object, "after"))
flags |= G_CONNECT_AFTER;
}
/* optional: "swapped" */
if (json_object_has_member (object, "swapped"))
{
if (json_object_get_boolean_member (object, "swapped"))
flags |= G_CONNECT_SWAPPED;
}
CLUTTER_NOTE (SCRIPT,
"Parsing signal '%s' (handler:%s, object:%s, flags:%d)",
name,
handler, connect, flags);
sinfo = g_slice_new0 (SignalInfo);
sinfo->name = g_strdup (name);
sinfo->handler = g_strdup (handler);
sinfo->object = g_strdup (connect);
sinfo->flags = flags;
retval = g_list_prepend (retval, sinfo);
_clutter_script_warn_missing_attribute (script,
NULL,
"handler or state");
}
return retval;

View File

@ -88,8 +88,12 @@ typedef struct {
gchar *name;
gchar *handler;
gchar *object;
gchar *state;
gchar *target;
GConnectFlags flags;
guint is_handler : 1;
} SignalInfo;
void property_info_free (gpointer data);

View File

@ -196,6 +196,7 @@
#include "clutter-behaviour.h"
#include "clutter-container.h"
#include "clutter-stage.h"
#include "clutter-state.h"
#include "clutter-texture.h"
#include "clutter-script.h"
@ -906,12 +907,47 @@ clutter_script_connect_signals (ClutterScript *script,
g_free (cd);
}
typedef struct {
ClutterState *state;
GObject *emitter;
gchar *target;
} HookData;
typedef struct {
ClutterScript *script;
ClutterScriptConnectFunc func;
gpointer user_data;
} SignalConnectData;
static void
hook_data_free (gpointer data)
{
if (G_LIKELY (data != NULL))
{
HookData *hook_data = data;
g_free (hook_data->target);
g_slice_free (HookData, hook_data);
}
}
static gboolean
clutter_script_state_change_hook (GSignalInvocationHint *ihint,
guint n_params,
const GValue *params,
gpointer user_data)
{
HookData *hook_data = user_data;
GObject *emitter;
emitter = g_value_get_object (&params[0]);
if (emitter == hook_data->emitter)
clutter_state_set_state (hook_data->state, hook_data->target);
return TRUE;
}
static void
connect_each_object (gpointer key,
gpointer value,
@ -929,24 +965,75 @@ connect_each_object (gpointer key,
for (l = oinfo->signals; l != NULL; l = l->next)
{
SignalInfo *sinfo = l->data;
GObject *connect_object = NULL;
if (sinfo->object)
connect_object = clutter_script_get_object (script, sinfo->object);
if (sinfo->is_handler)
{
GObject *connect_object = NULL;
if (sinfo->object && !connect_object)
unresolved = g_list_prepend (unresolved, sinfo);
if (sinfo->object)
connect_object = clutter_script_get_object (script, sinfo->object);
if (sinfo->object && !connect_object)
unresolved = g_list_prepend (unresolved, sinfo);
else
{
connect_data->func (script, object,
sinfo->name,
sinfo->handler,
connect_object,
sinfo->flags,
connect_data->user_data);
}
}
else
{
connect_data->func (script, object,
sinfo->name,
sinfo->handler,
connect_object,
sinfo->flags,
connect_data->user_data);
GObject *state_object;
const gchar *signal_name, *signal_detail;
gchar **components;
GQuark signal_quark;
guint signal_id;
HookData *hook_data;
signal_info_free (sinfo);
state_object = clutter_script_get_object (script, sinfo->state);
if (state_object == NULL)
continue;
components = g_strsplit (sinfo->name, "::", 2);
if (g_strv_length (components) == 2)
{
signal_name = components[0];
signal_detail = components[1];
}
else
{
signal_name = components[0];
signal_detail = NULL;
}
signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object));
if (signal_id == 0)
{
g_strfreev (components);
continue;
}
if (signal_detail != NULL)
signal_quark = g_quark_from_string (signal_detail);
else
signal_quark = 0;
hook_data = g_slice_new (HookData);
hook_data->emitter = object;
hook_data->state = CLUTTER_STATE (state_object);
hook_data->target = g_strdup (sinfo->target);
g_signal_add_emission_hook (signal_id, signal_quark,
clutter_script_state_change_hook,
hook_data,
hook_data_free);
}
signal_info_free (sinfo);
}
/* keep the unresolved signal handlers around, in case