2007-12-04 Emmanuele Bassi <ebassi@openedhand.com>

* clutter/clutter-score.[ch]: Reimplement ClutterScore using
	a N-ary tree to store the timelines. Remove clutter_score_add():
	the same functionality can be achieved by passing a NULL
	parent to clutter_score_append().

	* tests/test-score.c: Update ClutterScore test unit, and add
	debug printouts.
This commit is contained in:
Emmanuele Bassi 2007-12-04 16:26:19 +00:00
parent 094c5f77b8
commit 51a0d5a80f
9 changed files with 646 additions and 295 deletions

View File

@ -1,3 +1,13 @@
2007-12-04 Emmanuele Bassi <ebassi@openedhand.com>
* clutter/clutter-score.[ch]: Reimplement ClutterScore using
a N-ary tree to store the timelines. Remove clutter_score_add():
the same functionality can be achieved by passing a NULL
parent to clutter_score_append().
* tests/test-score.c: Update ClutterScore test unit, and add
debug printouts.
2007-12-04 Øyvind Kolås <pippin@o-hand.com> 2007-12-04 Øyvind Kolås <pippin@o-hand.com>
* clutter/clutter-shader.c: (bind_glsl_shader): use gchar instead of * clutter/clutter-shader.c: (bind_glsl_shader): use gchar instead of

View File

@ -427,12 +427,14 @@ clutter_rectangle_set_color
clutter_redraw clutter_redraw
clutter_rotate_axis_get_type clutter_rotate_axis_get_type
clutter_rotate_direction_get_type clutter_rotate_direction_get_type
clutter_score_add
clutter_score_append clutter_score_append
clutter_score_get_loop clutter_score_get_loop
clutter_score_get_timeline
clutter_score_get_type clutter_score_get_type
clutter_score_is_playing clutter_score_is_playing
clutter_score_list_timelines
clutter_score_new clutter_score_new
clutter_score_remove
clutter_score_remove_all clutter_score_remove_all
clutter_score_rewind clutter_score_rewind
clutter_score_start clutter_score_start

View File

@ -23,26 +23,6 @@
* Boston, MA 02111-1307, USA. * Boston, MA 02111-1307, USA.
*/ */
/*
* IDEAS:
* API;
* - add()
* + an new timeline to beginning of score
* - append (timeline_existing, timeline_new, delay)
* + appends a new timeline to an existing one
*
* ScoreEntry
* {
* Timeline *base;
* GList *next_timelines; - to start on completion of base,
* (points to score entries)
* Callback id;
* delay
* }
*
* start()/stop(),remove(),remove_all() ?
*/
/** /**
* SECTION:clutter-score * SECTION:clutter-score
* @short_description: Sequencing multiple #ClutterTimelines in order * @short_description: Sequencing multiple #ClutterTimelines in order
@ -62,34 +42,48 @@
#include "clutter-private.h" #include "clutter-private.h"
#include "clutter-debug.h" #include "clutter-debug.h"
G_DEFINE_TYPE (ClutterScore, clutter_score, G_TYPE_OBJECT); typedef struct _ClutterScoreEntry ClutterScoreEntry;
typedef struct ClutterScoreEntry struct _ClutterScoreEntry
{ {
ClutterTimeline *timeline; ClutterTimeline *timeline;
gulong handler_id; guint id;
GSList *child_entries;
ClutterScore *score; /* signal handler id */
} gulong completed_id;
ClutterScoreEntry;
ClutterScore *score;
/* pointer back to the tree structure */
GNode *node;
};
#define CLUTTER_SCORE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_SCORE, ClutterScorePrivate))
struct _ClutterScorePrivate struct _ClutterScorePrivate
{ {
GSList *entries; GNode *root;
GHashTable *running_timelines;
guint paused :1; GHashTable *running_timelines;
guint loop : 1;
guint last_id;
guint is_paused : 1;
guint loop : 1;
}; };
enum enum
{ {
PROP_0, PROP_0,
PROP_LOOP PROP_LOOP
}; };
enum enum
{ {
NEW_TIMELINE, TIMELINE_STARTED,
TIMELINE_COMPLETED,
STARTED, STARTED,
PAUSED, PAUSED,
COMPLETED, COMPLETED,
@ -97,23 +91,19 @@ enum
LAST_SIGNAL LAST_SIGNAL
}; };
static int score_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (ClutterScore, clutter_score, G_TYPE_OBJECT);
static void start_entry (ClutterScoreEntry *entry); static int score_signals[LAST_SIGNAL] = { 0 };
/* Object */ /* Object */
static void static void
clutter_score_set_property (GObject *object, clutter_score_set_property (GObject *gobject,
guint prop_id, guint prop_id,
const GValue *value, const GValue *value,
GParamSpec *pspec) GParamSpec *pspec)
{ {
ClutterScore *score; ClutterScorePrivate *priv = CLUTTER_SCORE_GET_PRIVATE (gobject);
ClutterScorePrivate *priv;
score = CLUTTER_SCORE(object);
priv = score->priv;
switch (prop_id) switch (prop_id)
{ {
@ -121,22 +111,18 @@ clutter_score_set_property (GObject *object,
priv->loop = g_value_get_boolean (value); priv->loop = g_value_get_boolean (value);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break; break;
} }
} }
static void static void
clutter_score_get_property (GObject *object, clutter_score_get_property (GObject *gobject,
guint prop_id, guint prop_id,
GValue *value, GValue *value,
GParamSpec *pspec) GParamSpec *pspec)
{ {
ClutterScore *score; ClutterScorePrivate *priv = CLUTTER_SCORE_GET_PRIVATE (gobject);
ClutterScorePrivate *priv;
score = CLUTTER_SCORE(object);
priv = score->priv;
switch (prop_id) switch (prop_id)
{ {
@ -144,7 +130,7 @@ clutter_score_get_property (GObject *object,
g_value_set_boolean (value, priv->loop); g_value_set_boolean (value, priv->loop);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break; break;
} }
} }
@ -154,7 +140,9 @@ clutter_score_finalize (GObject *object)
{ {
ClutterScorePrivate *priv = CLUTTER_SCORE (object)->priv; ClutterScorePrivate *priv = CLUTTER_SCORE (object)->priv;
g_hash_table_destroy (priv->running_timelines); if (priv->running_timelines)
g_hash_table_destroy (priv->running_timelines);
G_OBJECT_CLASS (clutter_score_parent_class)->finalize (object); G_OBJECT_CLASS (clutter_score_parent_class)->finalize (object);
} }
@ -173,70 +161,152 @@ clutter_score_dispose (GObject *object)
static void static void
clutter_score_class_init (ClutterScoreClass *klass) clutter_score_class_init (ClutterScoreClass *klass)
{ {
GObjectClass *object_class; GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
object_class = (GObjectClass*) klass; gobject_class->set_property = clutter_score_set_property;
gobject_class->get_property = clutter_score_get_property;
object_class->set_property = clutter_score_set_property; gobject_class->finalize = clutter_score_finalize;
object_class->get_property = clutter_score_get_property; gobject_class->dispose = clutter_score_dispose;
object_class->finalize = clutter_score_finalize;
object_class->dispose = clutter_score_dispose;
g_type_class_add_private (klass, sizeof (ClutterScorePrivate)); g_type_class_add_private (klass, sizeof (ClutterScorePrivate));
/**
* ClutterScore:loop:
*
* Whether the #ClutterScore should restart once finished.
*
* Since: 0.6
*/
g_object_class_install_property (gobject_class,
PROP_LOOP,
g_param_spec_boolean ("loop",
"Loop",
"Whether the score should restart once finished",
FALSE,
CLUTTER_PARAM_READWRITE));
/** /**
* ClutterScore::new-timeline: * ClutterScore::timeline-started:
* @score: the score which received the signal * @score: the score which received the signal
* @timeline: the current timeline * @timeline: the current timeline
* *
* The ::new-timeline signal is emitted each time a new timeline in the * The ::timeline-started signal is emitted each time a new timeline
* score is reached. * inside a #ClutterScore starts playing.
*
* Since: 0.6
*/ */
score_signals[NEW_TIMELINE] = score_signals[TIMELINE_STARTED] =
g_signal_new ("new-timeline", g_signal_new ("timeline-started",
G_TYPE_FROM_CLASS (object_class), G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterScoreClass, new_timeline), G_STRUCT_OFFSET (ClutterScoreClass, timeline_started),
NULL, NULL, NULL, NULL,
clutter_marshal_VOID__OBJECT, clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, G_TYPE_NONE,
1, CLUTTER_TYPE_TIMELINE); 1, CLUTTER_TYPE_TIMELINE);
/**
* ClutterScore::timeline-completed:
* @score: the score which received the signal
* @timeline: the completed timeline
*
* The ::timeline-completed signal is emitted each time a timeline
* inside a #ClutterScore terminates.
*
* Since: 0.6
*/
score_signals[TIMELINE_COMPLETED] =
g_signal_new ("timeline-completed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterScoreClass, timeline_completed),
NULL, NULL,
clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_TIMELINE);
/**
* ClutterScore::completed:
* @score: the score which received the signal
*
* The ::completed signal is emitted each time a #ClutterScore terminates.
*
* Since: 0.6
*/
score_signals[COMPLETED] = score_signals[COMPLETED] =
g_signal_new ("completed", g_signal_new ("completed",
G_TYPE_FROM_CLASS (object_class), G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterScoreClass, completed), G_STRUCT_OFFSET (ClutterScoreClass, completed),
NULL, NULL, NULL, NULL,
clutter_marshal_VOID__VOID, clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0); G_TYPE_NONE, 0);
/**
* ClutterScore::started:
* @score: the score which received the signal
*
* The ::started signal is emitted each time a #ClutterScore starts playing.
*
* Since: 0.6
*/
score_signals[STARTED] = score_signals[STARTED] =
g_signal_new ("started", g_signal_new ("started",
G_TYPE_FROM_CLASS (object_class), G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterScoreClass, started), G_STRUCT_OFFSET (ClutterScoreClass, started),
NULL, NULL, NULL, NULL,
clutter_marshal_VOID__VOID, clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0); G_TYPE_NONE, 0);
/**
* ClutterScore::paused:
* @score: the score which received the signal
*
* The ::paused signal is emitted each time a #ClutterScore
* is paused.
*
* Since: 0.6
*/
score_signals[PAUSED] = score_signals[PAUSED] =
g_signal_new ("paused", g_signal_new ("paused",
G_TYPE_FROM_CLASS (object_class), G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterScoreClass, paused), G_STRUCT_OFFSET (ClutterScoreClass, paused),
NULL, NULL, NULL, NULL,
clutter_marshal_VOID__VOID, clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0); G_TYPE_NONE, 0);
} }
static void static void
clutter_score_init (ClutterScore *self) clutter_score_init (ClutterScore *self)
{ {
self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, ClutterScorePrivate *priv;
CLUTTER_TYPE_SCORE,
ClutterScorePrivate);
self->priv->running_timelines = g_hash_table_new(NULL, NULL); self->priv = priv = CLUTTER_SCORE_GET_PRIVATE (self);
/* sentinel */
priv->root = g_node_new (NULL);
priv->running_timelines = NULL;
priv->is_paused = FALSE;
priv->loop = FALSE;
priv->last_id = 1;
}
/**
* clutter_score_new:
*
* Creates a new #ClutterScore. A #ClutterScore is an object that can
* hold multiple #ClutterTimeline<!-- -->s in a sequential order.
*
* Return value: the newly created #ClutterScore. Use g_object_unref()
* when done.
*
* Since: 0.6
*/
ClutterScore *
clutter_score_new (void)
{
return g_object_new (CLUTTER_TYPE_SCORE, NULL);
} }
/** /**
@ -282,76 +352,108 @@ clutter_score_get_loop (ClutterScore *score)
* *
* Query state of a #ClutterScore instance. * Query state of a #ClutterScore instance.
* *
* Return Value: TRUE if score is currently playing, FALSE if not. * Return Value: %TRUE if score is currently playing
*/ */
gboolean gboolean
clutter_score_is_playing (ClutterScore *score) clutter_score_is_playing (ClutterScore *score)
{ {
g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE); g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE);
/* FIXME: paused state currently counts as playing */ if (score->priv->is_paused)
return FALSE;
return !!g_hash_table_size (score->priv->running_timelines); return (g_hash_table_size (score->priv->running_timelines) != 0);
}
/* forward declaration */
static void start_entry (ClutterScoreEntry *entry);
static void
start_children_entries (GNode *node,
gpointer data)
{
ClutterScoreEntry *entry = node->data;
start_entry (entry);
} }
static void static void
on_timeline_finish (ClutterTimeline *timeline, on_timeline_finish (ClutterTimeline *timeline,
ClutterScoreEntry *entry) ClutterScoreEntry *entry)
{ {
GSList *item; ClutterScorePrivate *priv = entry->score->priv;
g_hash_table_remove (entry->score->priv->running_timelines, g_hash_table_remove (priv->running_timelines,
GINT_TO_POINTER(entry->handler_id)); GINT_TO_POINTER (entry->id));
g_signal_handler_disconnect (timeline, entry->handler_id); g_signal_handler_disconnect (timeline, entry->completed_id);
entry->completed_id = 0;
CLUTTER_NOTE (SCHEDULER, CLUTTER_NOTE (SCHEDULER, "timeline [%p] (%d) completed",
"completed %p %li\n", entry->timeline,
entry->timeline, entry->handler_id); entry->id);
for (item = entry->child_entries; item != NULL; item = item->next) g_signal_emit (entry->score, score_signals[TIMELINE_COMPLETED], 0,
entry->timeline);
/* start every child */
if (entry->node->children)
{ {
ClutterScoreEntry *child_entry = item->data; g_node_children_foreach (entry->node,
start_entry (child_entry); G_TRAVERSE_ALL,
start_children_entries,
NULL);
} }
if (clutter_score_is_playing (entry->score) == FALSE) /* score has finished - fire 'completed' signal */
if (g_hash_table_size (priv->running_timelines) == 0)
{ {
/* Score has finished - fire 'completed' signal */ CLUTTER_NOTE (SCHEDULER, "looks like we finished");
/* Also check if looped etc */
CLUTTER_NOTE (SCHEDULER, "looks like we finished\n");
g_signal_emit (entry->score, score_signals[COMPLETED], 0); g_signal_emit (entry->score, score_signals[COMPLETED], 0);
clutter_score_stop (entry->score);
if (priv->loop)
clutter_score_start (entry->score);
} }
} }
static void static void
start_entry (ClutterScoreEntry *entry) start_entry (ClutterScoreEntry *entry)
{ {
entry->handler_id = g_signal_connect (entry->timeline, ClutterScorePrivate *priv = entry->score->priv;
"completed",
G_CALLBACK (on_timeline_finish),
entry);
CLUTTER_NOTE (SCHEDULER, entry->completed_id =
"started %p %li\n", entry->timeline, entry->handler_id); g_signal_connect (entry->timeline,
"completed", G_CALLBACK (on_timeline_finish),
entry);
g_hash_table_insert (entry->score->priv->running_timelines, CLUTTER_NOTE (SCHEDULER, "timeline [%p] (%d) started",
GINT_TO_POINTER(entry->handler_id), entry->timeline,
entry->id);
if (G_UNLIKELY (priv->running_timelines == NULL))
priv->running_timelines = g_hash_table_new (NULL, NULL);
g_hash_table_insert (priv->running_timelines,
GINT_TO_POINTER (entry->id),
entry); entry);
clutter_timeline_start (entry->timeline); clutter_timeline_start (entry->timeline);
g_signal_emit (entry->score, score_signals[NEW_TIMELINE], g_signal_emit (entry->score, score_signals[TIMELINE_STARTED], 0,
0, entry->timeline); entry->timeline);
} }
void static void
on_foreach_running_timeline_start (gpointer key, foreach_running_timeline_start (gpointer key,
gpointer value, gpointer value,
gpointer user_data) gpointer user_data)
{ {
clutter_timeline_start (CLUTTER_TIMELINE(value)); ClutterScoreEntry *entry = value;
clutter_timeline_start (entry->timeline);
} }
/** /**
@ -365,36 +467,37 @@ on_foreach_running_timeline_start (gpointer key,
void void
clutter_score_start (ClutterScore *score) clutter_score_start (ClutterScore *score)
{ {
GSList *item;
ClutterScorePrivate *priv; ClutterScorePrivate *priv;
g_return_if_fail (CLUTTER_IS_SCORE (score)); g_return_if_fail (CLUTTER_IS_SCORE (score));
priv = score->priv; priv = score->priv;
if (priv->paused) if (priv->is_paused)
{ {
g_hash_table_foreach (priv->running_timelines, g_hash_table_foreach (priv->running_timelines,
(GHFunc)on_foreach_running_timeline_start, foreach_running_timeline_start,
NULL); NULL);
priv->paused = 0; priv->is_paused = FALSE;
} }
else else
{ {
for (item = priv->entries; item != NULL; item = item->next) g_node_children_foreach (priv->root,
{ G_TRAVERSE_ALL,
ClutterScoreEntry *entry = item->data; start_children_entries,
start_entry (entry); NULL);
}
} }
} }
gboolean static gboolean
on_foreach_running_timeline_stop (gpointer key, foreach_running_timeline_stop (gpointer key,
gpointer value, gpointer value,
gpointer user_data) gpointer user_data)
{ {
clutter_timeline_stop (((ClutterScoreEntry*)value)->timeline); ClutterScoreEntry *entry = value;
clutter_timeline_stop (entry->timeline);
return TRUE; return TRUE;
} }
@ -415,8 +518,10 @@ clutter_score_stop (ClutterScore *score)
priv = score->priv; priv = score->priv;
g_hash_table_foreach_remove (priv->running_timelines, g_hash_table_foreach_remove (priv->running_timelines,
(GHRFunc)on_foreach_running_timeline_stop, foreach_running_timeline_stop,
NULL); NULL);
g_hash_table_destroy (priv->running_timelines);
priv->running_timelines = NULL;
} }
/** /**
@ -440,12 +545,14 @@ clutter_score_rewind (ClutterScore *score)
clutter_score_start (score); clutter_score_start (score);
} }
void static void
on_foreach_running_timeline_pause (gpointer key, foreach_running_timeline_pause (gpointer key,
gpointer value, gpointer value,
gpointer user_data) gpointer user_data)
{ {
clutter_timeline_pause (((ClutterScoreEntry*)value)->timeline); ClutterScoreEntry *entry = value;
clutter_timeline_pause (entry->timeline);
} }
void void
@ -457,131 +564,278 @@ clutter_score_pause (ClutterScore *score)
priv = score->priv; priv = score->priv;
if (priv->paused || !clutter_score_is_playing (score)) if (!clutter_score_is_playing (score))
return; return;
g_hash_table_foreach (priv->running_timelines, g_hash_table_foreach (priv->running_timelines,
(GHFunc)on_foreach_running_timeline_pause, foreach_running_timeline_pause,
NULL); NULL);
priv->paused = 1; priv->is_paused = TRUE;
g_signal_emit (score, score_signals[PAUSED], 0); g_signal_emit (score, score_signals[PAUSED], 0);
} }
static ClutterScoreEntry* typedef enum {
find_entry (GSList *list, ClutterTimeline *timeline) FIND_BY_TIMELINE,
FIND_BY_ID,
REMOVE_BY_ID,
LIST_TIMELINES
} TraverseAction;
typedef struct {
TraverseAction action;
ClutterScore *score;
union {
ClutterTimeline *timeline;
guint id;
ClutterScoreEntry *entry;
} d;
gpointer result;
} TraverseClosure;
static gboolean
destroy_entry (GNode *node,
G_GNUC_UNUSED gpointer data)
{ {
GSList *item; ClutterScoreEntry *entry = node->data;
ClutterScoreEntry *res = NULL;
if (list == NULL) if (G_LIKELY (entry != NULL))
return NULL;
for (item = list; item != NULL && res == NULL; item = item->next)
{ {
ClutterScoreEntry *entry = item->data; if (entry->completed_id)
g_signal_handler_disconnect (entry->timeline, entry->completed_id);
g_assert (entry != NULL); g_object_unref (entry->timeline);
g_slice_free (ClutterScoreEntry, entry);
if (entry->timeline == timeline) node->data = NULL;
return entry;
if (entry->child_entries)
res = find_entry (entry->child_entries, timeline);
} }
return res; /* continue */
return FALSE;
}
static gboolean
traverse_children (GNode *node,
gpointer data)
{
TraverseClosure *closure = data;
ClutterScoreEntry *entry = node->data;
gboolean retval = FALSE;
/* root */
if (!entry)
return TRUE;
switch (closure->action)
{
case FIND_BY_TIMELINE:
if (closure->d.timeline == entry->timeline)
{
closure->result = node;
retval = TRUE;
}
break;
case FIND_BY_ID:
if (closure->d.id == entry->id)
{
closure->result = node;
retval = TRUE;
}
break;
case REMOVE_BY_ID:
if (closure->d.id == entry->id)
{
if (entry->completed_id)
g_signal_handler_disconnect (entry->timeline, entry->completed_id);
g_object_unref (entry->timeline);
g_node_traverse (node,
G_POST_ORDER,
G_TRAVERSE_ALL,
-1,
destroy_entry, NULL);
g_slice_free (ClutterScoreEntry, entry);
closure->result = node;
retval = TRUE;
}
break;
case LIST_TIMELINES:
retval = TRUE;
break;
}
return retval;
}
static GNode *
find_entry_by_timeline (ClutterScore *score,
ClutterTimeline *timeline)
{
ClutterScorePrivate *priv = score->priv;
TraverseClosure closure;
closure.action = FIND_BY_TIMELINE;
closure.score = score;
closure.d.timeline = timeline;
closure.result = NULL;
g_node_traverse (priv->root,
G_POST_ORDER,
G_TRAVERSE_ALL,
-1,
traverse_children, &closure);
if (closure.result)
return closure.result;
return NULL;
}
static GNode *
find_entry_by_id (ClutterScore *score,
guint id)
{
ClutterScorePrivate *priv = score->priv;
TraverseClosure closure;
closure.action = FIND_BY_ID;
closure.score = score;
closure.d.id = id;
closure.result = NULL;
g_node_traverse (priv->root,
G_POST_ORDER,
G_TRAVERSE_ALL,
-1,
traverse_children, &closure);
if (closure.result)
return closure.result;
return NULL;
} }
/** /**
* clutter_score_append: * clutter_score_append:
* @score: A #ClutterScore * @score: a #ClutterScore
* @timeline_existing: A #ClutterTimeline in the score * @parent: a #ClutterTimeline in the score or %NULL
* @timeline_new: A new #ClutterTimeline to start when #timeline_existing has * @timeline: a #ClutterTimeline
* completed,
* *
* Appends a new timeline to an one existing in the score. * Appends a timeline to another one existing in the score; the newly
* appended timeline will be started when @parent is complete.
* *
* If @parent is %NULL, the new #ClutterTimeline will be started when
* clutter_score_start() is called.
*
* Return value: the id of the newly added timeline, to be used with
* clutter_score_get_timeline() and clutter_score_remove().
*
* Since: 0.6
*/ */
void guint
clutter_score_append (ClutterScore *score, clutter_score_append (ClutterScore *score,
ClutterTimeline *timeline_existing, ClutterTimeline *parent,
ClutterTimeline *timeline_new) ClutterTimeline *timeline)
{ {
ClutterScorePrivate *priv; ClutterScorePrivate *priv;
ClutterScoreEntry *entry, *entry_new;
g_return_val_if_fail (CLUTTER_IS_SCORE (score), 0);
g_return_val_if_fail (parent == NULL || CLUTTER_IS_TIMELINE (parent), 0);
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
priv = score->priv; priv = score->priv;
/* Appends a timeline to the end of another */ if (!parent)
if ((entry = find_entry (priv->entries, timeline_existing)) != NULL)
{ {
entry_new = g_new0(ClutterScoreEntry, 1); ClutterScoreEntry *entry;
entry_new->timeline = g_object_ref (timeline_new);
entry_new->score = score;
entry->child_entries = g_slist_append (entry->child_entries, entry_new); entry = g_slice_new (ClutterScoreEntry);
clutter_timeline_stop (timeline_new); /* stop it */ entry->timeline = g_object_ref (timeline);
entry->id = priv->last_id;
entry->completed_id = 0;
entry->score = score;
entry->node = g_node_append_data (priv->root, entry);
priv->last_id += 1;
return entry->id;
} }
else
{
GNode *node;
ClutterScoreEntry *entry;
node = find_entry_by_timeline (score, parent);
if (G_UNLIKELY (!node))
{
g_warning ("Unable to find the parent timeline inside the score.");
return 0;
}
entry = g_slice_new (ClutterScoreEntry);
entry->timeline = g_object_ref (timeline);
entry->id = priv->last_id;
entry->completed_id = 0;
entry->score = score;
entry->node = g_node_append_data (node, entry);
priv->last_id += 1;
return entry->id;
}
return 0;
} }
/** /**
* clutter_score_add: * clutter_score_remove:
* @score: A #ClutterScore * @score: a #ClutterScore
* @timeline: A #ClutterTimeline * @id: the id of the timeline to remove
* *
* Adds a new initial timeline to start when the score is started. * Removes the #ClutterTimeline with the given id inside @score. If
* the timeline has other timelines attached to it, those are removed
* as well.
*
* Since: 0.6
*/ */
void void
clutter_score_add (ClutterScore *score, clutter_score_remove (ClutterScore *score,
ClutterTimeline *timeline) guint id)
{ {
ClutterScorePrivate *priv; ClutterScorePrivate *priv;
ClutterScoreEntry *entry; TraverseClosure closure;
g_return_if_fail (CLUTTER_IS_SCORE (score));
g_return_if_fail (id > 0);
priv = score->priv; priv = score->priv;
/* Added timelines are always started first */ closure.action = REMOVE_BY_ID;
entry = g_new0(ClutterScoreEntry, 1); closure.score = score;
entry->timeline = g_object_ref (timeline); closure.d.id = id;
entry->score = score; closure.result = NULL;
score->priv->entries = g_slist_append (score->priv->entries, entry);
clutter_timeline_stop (timeline); /* stop it */ g_node_traverse (priv->root,
G_POST_ORDER,
CLUTTER_NOTE (SCHEDULER, "added timeline %p\n", entry->timeline); G_TRAVERSE_ALL,
} -1,
traverse_children, &closure);
static void
remove_entries (GSList *list)
{
GSList *item;
if (!list)
return;
for (item = list; item != NULL; item = item->next)
{
ClutterScoreEntry *entry = item->data;
g_object_unref (entry->timeline);
if (entry->child_entries)
remove_entries (entry->child_entries);
g_slist_free (entry->child_entries);
g_free(entry);
}
}
void
clutter_score_remove (ClutterScore *score,
ClutterTimeline *timeline_parent,
ClutterTimeline *timeline)
{
if (closure.result)
g_node_destroy (closure.result);
} }
/** /**
@ -595,21 +849,66 @@ clutter_score_remove (ClutterScore *score,
void void
clutter_score_remove_all (ClutterScore *score) clutter_score_remove_all (ClutterScore *score)
{ {
ClutterScorePrivate *priv;
g_return_if_fail (CLUTTER_IS_SCORE (score));
/* this will take care of the running timelines */
clutter_score_stop (score); clutter_score_stop (score);
remove_entries (score->priv->entries);
priv = score->priv;
g_node_traverse (priv->root,
G_POST_ORDER,
G_TRAVERSE_ALL,
-1,
destroy_entry, NULL);
g_node_destroy (priv->root);
/* sentinel */
priv->root = g_node_new (NULL);
} }
/** ClutterTimeline *
* clutter_score_new: clutter_score_get_timeline (ClutterScore *score,
* guint id)
* Creates a new #ClutterScore.
*
* Return value: the newly created #ClutterScore
*
* Since: 0.6
*/
ClutterScore *
clutter_score_new (void)
{ {
return g_object_new (CLUTTER_TYPE_SCORE, NULL); GNode *node;
ClutterScoreEntry *entry;
g_return_val_if_fail (CLUTTER_IS_SCORE (score), NULL);
g_return_val_if_fail (id > 0, NULL);
node = find_entry_by_id (score, id);
if (G_UNLIKELY (!node))
return NULL;
entry = node->data;
return entry->timeline;
}
GSList *
clutter_score_list_timelines (ClutterScore *score)
{
ClutterScorePrivate *priv;
TraverseClosure closure;
GSList *retval;
g_return_val_if_fail (CLUTTER_IS_SCORE (score), NULL);
priv = score->priv;
closure.action = LIST_TIMELINES;
closure.result = NULL;
g_node_traverse (priv->root,
G_POST_ORDER,
G_TRAVERSE_ALL,
-1,
traverse_children, &closure);
retval = closure.result;
return retval;
} }

View File

@ -23,41 +23,24 @@
* Boston, MA 02111-1307, USA. * Boston, MA 02111-1307, USA.
*/ */
#ifndef _HAVE_CLUTTER_SCORE_H #ifndef __CLUTTER_SCORE_H__
#define _HAVE_CLUTTER_SCORE_H #define __CLUTTER_SCORE_H__
/* clutter-score.h */
#include <glib-object.h>
#include <clutter/clutter-timeline.h> #include <clutter/clutter-timeline.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define CLUTTER_TYPE_SCORE clutter_score_get_type() #define CLUTTER_TYPE_SCORE (clutter_score_get_type ())
#define CLUTTER_SCORE(obj) \ #define CLUTTER_SCORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_SCORE, ClutterScore))
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \ #define CLUTTER_SCORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_SCORE, ClutterScoreClass))
CLUTTER_TYPE_SCORE, ClutterScore)) #define CLUTTER_IS_SCORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_SCORE))
#define CLUTTER_IS_SCORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_SCORE))
#define CLUTTER_SCORE_CLASS(klass) \ #define CLUTTER_SCORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_SCORE, ClutterScoreClass))
(G_TYPE_CHECK_CLASS_CAST ((klass), \
CLUTTER_TYPE_SCORE, ClutterScoreClass))
#define CLUTTER_IS_SCORE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
CLUTTER_TYPE_SCORE))
#define CLUTTER_IS_SCORE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
CLUTTER_TYPE_SCORE))
#define CLUTTER_SCORE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
CLUTTER_TYPE_SCORE, ClutterScoreClass))
typedef struct _ClutterScore ClutterScore; typedef struct _ClutterScore ClutterScore;
typedef struct _ClutterScoreClass ClutterScoreClass;
typedef struct _ClutterScorePrivate ClutterScorePrivate; typedef struct _ClutterScorePrivate ClutterScorePrivate;
typedef struct _ClutterScoreClass ClutterScoreClass;
struct _ClutterScore struct _ClutterScore
{ {
@ -68,13 +51,21 @@ struct _ClutterScore
struct _ClutterScoreClass struct _ClutterScoreClass
{ {
/*< private >*/
GObjectClass parent_class; GObjectClass parent_class;
void (*new_timeline) (ClutterScore *score, ClutterTimeline *timeline);
void (*started) (ClutterScore *score);
void (*completed) (ClutterScore *score);
void (*paused) (ClutterScore *score);
/*< public >*/
void (* timeline_started) (ClutterScore *score,
ClutterTimeline *timeline);
void (* timeline_completed) (ClutterScore *score,
ClutterTimeline *timeline);
void (* started) (ClutterScore *score);
void (* completed) (ClutterScore *score);
void (* paused) (ClutterScore *score);
/*< private >*/
/* padding for future expansion */
void (*_clutter_score_1) (void); void (*_clutter_score_1) (void);
void (*_clutter_score_2) (void); void (*_clutter_score_2) (void);
void (*_clutter_score_3) (void); void (*_clutter_score_3) (void);
@ -84,48 +75,27 @@ struct _ClutterScoreClass
GType clutter_score_get_type (void) G_GNUC_CONST; GType clutter_score_get_type (void) G_GNUC_CONST;
ClutterScore *clutter_score_new (void); ClutterScore * clutter_score_new (void);
void clutter_score_set_loop (ClutterScore *score,
gboolean loop);
gboolean clutter_score_get_loop (ClutterScore *score);
gboolean clutter_score_is_playing (ClutterScore *score);
void guint clutter_score_append (ClutterScore *score,
clutter_score_set_loop (ClutterScore *score, ClutterTimeline *parent,
gboolean loop); ClutterTimeline *timeline);
void clutter_score_remove (ClutterScore *score,
gboolean guint id);
clutter_score_get_loop (ClutterScore *score); void clutter_score_remove_all (ClutterScore *score);
ClutterTimeline *clutter_score_get_timeline (ClutterScore *score,
void guint id);
clutter_score_rewind (ClutterScore *score); GSList * clutter_score_list_timelines (ClutterScore *score);
gboolean
clutter_score_is_playing (ClutterScore *score);
void
clutter_score_start (ClutterScore *score);
void
clutter_score_stop (ClutterScore *score);
void
clutter_score_pause (ClutterScore *score);
void
clutter_score_append (ClutterScore *score,
ClutterTimeline *timeline_existing,
ClutterTimeline *timeline_new);
void
clutter_score_add (ClutterScore *score,
ClutterTimeline *timeline);
void
clutter_score_remove (ClutterScore *score,
ClutterTimeline *timeline_parent,
ClutterTimeline *timeline);
void
clutter_score_remove_all (ClutterScore *score);
void clutter_score_start (ClutterScore *score);
void clutter_score_stop (ClutterScore *score);
void clutter_score_pause (ClutterScore *score);
void clutter_score_rewind (ClutterScore *score);
G_END_DECLS G_END_DECLS
#endif #endif /* __CLUTTER_SCORE_H__ */

View File

@ -1,3 +1,9 @@
2007-12-04 Emmanuele Bassi <ebassi@openedhand.com>
* clutter.types:
* clutter-docs.sgml:
* clutter-sections.txt: Fix ClutterScore symbols.
2007-11-30 Emmanuele Bassi <ebassi@openedhand.com> 2007-11-30 Emmanuele Bassi <ebassi@openedhand.com>
* clutter-sections.txt: Update with the newly added API. * clutter-sections.txt: Update with the newly added API.

View File

@ -106,6 +106,7 @@
<title>Base classes</title> <title>Base classes</title>
<xi:include href="xml/clutter-timeline.xml"/> <xi:include href="xml/clutter-timeline.xml"/>
<xi:include href="xml/clutter-score.xml"/>
<xi:include href="xml/clutter-alpha.xml"/> <xi:include href="xml/clutter-alpha.xml"/>
<xi:include href="xml/clutter-behaviour.xml"/> <xi:include href="xml/clutter-behaviour.xml"/>
</chapter> </chapter>

View File

@ -1212,12 +1212,15 @@ clutter_model_iter_get_type
ClutterScore ClutterScore
ClutterScoreClass ClutterScoreClass
clutter_score_new clutter_score_new
clutter_score_add clutter_score_set_loop
clutter_score_get_loop
<SUBSECTION>
clutter_score_append clutter_score_append
clutter_score_remove clutter_score_remove
clutter_score_remove_all clutter_score_remove_all
clutter_score_set_loop clutter_score_get_timeline
clutter_score_get_loop clutter_score_list_timelines
<SUBSECTION> <SUBSECTION>
clutter_score_start clutter_score_start

View File

@ -24,3 +24,4 @@ clutter_script_get_type
clutter_scriptable_get_type clutter_scriptable_get_type
clutter_model_get_type clutter_model_get_type
clutter_model_iter_get_type clutter_model_iter_get_type
clutter_score_get_type

View File

@ -2,6 +2,38 @@
#include <stdlib.h> #include <stdlib.h>
#include <clutter/clutter.h> #include <clutter/clutter.h>
static gint level = 1;
static void
on_timeline_started (ClutterScore *score,
ClutterTimeline *timeline)
{
gint i;
for (i = 0; i < level; i++)
g_print (" ");
g_print ("Started timeline: `%s'\n",
g_object_get_data (G_OBJECT (timeline), "timeline-name"));
level += 1;
}
static void
on_timeline_completed (ClutterScore *score,
ClutterTimeline *timeline)
{
gint i;
level -= 1;
for (i = 0; i < level; i++)
g_print (" ");
g_print ("Completed timeline: `%s'\n",
g_object_get_data (G_OBJECT (timeline), "timeline-name"));
}
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {
@ -14,12 +46,38 @@ main (int argc, char **argv)
clutter_init (&argc, &argv); clutter_init (&argc, &argv);
timeline_1 = clutter_timeline_new (10, 120); timeline_1 = clutter_timeline_new (10, 120);
g_object_set_data_full (G_OBJECT (timeline_1),
"timeline-name", g_strdup ("Timeline 1"),
g_free);
timeline_2 = clutter_timeline_clone (timeline_1); timeline_2 = clutter_timeline_clone (timeline_1);
g_object_set_data_full (G_OBJECT (timeline_2),
"timeline-name", g_strdup ("Timeline 2"),
g_free);
timeline_3 = clutter_timeline_clone (timeline_1); timeline_3 = clutter_timeline_clone (timeline_1);
g_object_set_data_full (G_OBJECT (timeline_3),
"timeline-name", g_strdup ("Timeline 3"),
g_free);
timeline_4 = clutter_timeline_clone (timeline_1); timeline_4 = clutter_timeline_clone (timeline_1);
g_object_set_data_full (G_OBJECT (timeline_4),
"timeline-name", g_strdup ("Timeline 4"),
g_free);
score = clutter_score_new(); score = clutter_score_new();
clutter_score_add (score, timeline_1); g_signal_connect (score, "timeline-started",
G_CALLBACK (on_timeline_started),
NULL);
g_signal_connect (score, "timeline-completed",
G_CALLBACK (on_timeline_completed),
NULL);
g_signal_connect (score, "completed",
G_CALLBACK (clutter_main_quit),
NULL);
clutter_score_append (score, NULL, timeline_1);
clutter_score_append (score, timeline_1, timeline_2); clutter_score_append (score, timeline_1, timeline_2);
clutter_score_append (score, timeline_1, timeline_3); clutter_score_append (score, timeline_1, timeline_3);
clutter_score_append (score, timeline_3, timeline_4); clutter_score_append (score, timeline_3, timeline_4);
@ -32,6 +90,7 @@ main (int argc, char **argv)
g_object_unref (timeline_1); g_object_unref (timeline_1);
g_object_unref (timeline_2); g_object_unref (timeline_2);
g_object_unref (timeline_3); g_object_unref (timeline_3);
g_object_unref (timeline_4);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }