5c398c18ad
This time, in Clutter core. The ObjC standard library provides a type called 'id', which obviously requires any library to either drop the useful shadowed variable warning or stop using 'id' as a variable name. Yes, it's almost unbearably stupid. Well, at least it's not 'index' in string.h, or 'y2' in math.h.
1144 lines
27 KiB
C
1144 lines
27 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* Copyright (C) 2007 OpenedHand
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-score
|
|
* @short_description: Controller for multiple timelines
|
|
*
|
|
* #ClutterScore is a base class for sequencing multiple timelines in order.
|
|
* Using #ClutterScore it is possible to start multiple timelines at the
|
|
* same time or launch multiple timelines when a particular timeline has
|
|
* emitted the ClutterTimeline::completed signal.
|
|
*
|
|
* Each time a #ClutterTimeline is started and completed, a signal will be
|
|
* emitted.
|
|
*
|
|
* For example, this code will start two #ClutterTimeline<!-- -->s after
|
|
* a third timeline terminates:
|
|
*
|
|
* |[
|
|
* ClutterTimeline *timeline_1, *timeline_2, *timeline_3;
|
|
* ClutterScore *score;
|
|
*
|
|
* timeline_1 = clutter_timeline_new_for_duration (1000);
|
|
* timeline_2 = clutter_timeline_new_for_duration (500);
|
|
* timeline_3 = clutter_timeline_new_for_duration (500);
|
|
*
|
|
* score = clutter_score_new ();
|
|
*
|
|
* clutter_score_append (score, NULL, timeline_1);
|
|
* clutter_score_append (score, timeline_1, timeline_2);
|
|
* clutter_score_append (score, timeline_1, timeline_3);
|
|
*
|
|
* clutter_score_start (score);
|
|
* ]|
|
|
*
|
|
* A #ClutterScore takes a reference on the timelines it manages,
|
|
* so timelines can be safely unreferenced after being appended.
|
|
*
|
|
* New timelines can be appended to the #ClutterScore using
|
|
* clutter_score_append() and removed using clutter_score_remove().
|
|
*
|
|
* Timelines can also be appended to a specific marker on the
|
|
* parent timeline, using clutter_score_append_at_marker().
|
|
*
|
|
* The score can be cleared using clutter_score_remove_all().
|
|
*
|
|
* The list of timelines can be retrieved using
|
|
* clutter_score_list_timelines().
|
|
*
|
|
* The score state is controlled using clutter_score_start(),
|
|
* clutter_score_pause(), clutter_score_stop() and clutter_score_rewind().
|
|
* The state can be queried using clutter_score_is_playing().
|
|
*
|
|
* #ClutterScore is available since Clutter 0.6
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-score.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-marshal.h"
|
|
#include "clutter-private.h"
|
|
#include "clutter-debug.h"
|
|
|
|
typedef struct _ClutterScoreEntry ClutterScoreEntry;
|
|
|
|
struct _ClutterScoreEntry
|
|
{
|
|
/* the entry unique id */
|
|
gulong id;
|
|
|
|
ClutterTimeline *timeline;
|
|
ClutterTimeline *parent;
|
|
|
|
/* the optional marker on the parent */
|
|
gchar *marker;
|
|
|
|
/* signal handlers id */
|
|
gulong complete_id;
|
|
gulong marker_id;
|
|
|
|
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
|
|
{
|
|
GNode *root;
|
|
|
|
GHashTable *running_timelines;
|
|
|
|
gulong last_id;
|
|
|
|
guint is_paused : 1;
|
|
guint loop : 1;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_LOOP
|
|
};
|
|
|
|
enum
|
|
{
|
|
TIMELINE_STARTED,
|
|
TIMELINE_COMPLETED,
|
|
|
|
STARTED,
|
|
PAUSED,
|
|
COMPLETED,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static inline void clutter_score_clear (ClutterScore *score);
|
|
|
|
G_DEFINE_TYPE (ClutterScore, clutter_score, G_TYPE_OBJECT);
|
|
|
|
static int score_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
/* Object */
|
|
|
|
static void
|
|
clutter_score_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterScorePrivate *priv = CLUTTER_SCORE_GET_PRIVATE (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LOOP:
|
|
priv->loop = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_score_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterScorePrivate *priv = CLUTTER_SCORE_GET_PRIVATE (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_LOOP:
|
|
g_value_set_boolean (value, priv->loop);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_score_finalize (GObject *object)
|
|
{
|
|
ClutterScore *score = CLUTTER_SCORE (object);
|
|
|
|
clutter_score_stop (score);
|
|
clutter_score_clear (score);
|
|
|
|
G_OBJECT_CLASS (clutter_score_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
clutter_score_class_init (ClutterScoreClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->set_property = clutter_score_set_property;
|
|
gobject_class->get_property = clutter_score_get_property;
|
|
gobject_class->finalize = clutter_score_finalize;
|
|
|
|
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::timeline-started:
|
|
* @score: the score which received the signal
|
|
* @timeline: the current timeline
|
|
*
|
|
* The ::timeline-started signal is emitted each time a new timeline
|
|
* inside a #ClutterScore starts playing.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
score_signals[TIMELINE_STARTED] =
|
|
g_signal_new ("timeline-started",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterScoreClass, timeline_started),
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE,
|
|
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] =
|
|
g_signal_new ("completed",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterScoreClass, completed),
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__VOID,
|
|
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] =
|
|
g_signal_new ("started",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterScoreClass, started),
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__VOID,
|
|
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] =
|
|
g_signal_new ("paused",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterScoreClass, paused),
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
clutter_score_init (ClutterScore *self)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* clutter_score_set_loop:
|
|
* @score: a #ClutterScore
|
|
* @loop: %TRUE for enable looping
|
|
*
|
|
* Sets whether @score should loop. A looping #ClutterScore will start
|
|
* from its initial state after the ::complete signal has been fired.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_set_loop (ClutterScore *score,
|
|
gboolean loop)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
if (score->priv->loop != loop)
|
|
{
|
|
score->priv->loop = loop;
|
|
|
|
g_object_notify (G_OBJECT (score), "loop");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_score_get_loop:
|
|
* @score: a #ClutterScore
|
|
*
|
|
* Gets whether @score is looping
|
|
*
|
|
* Return value: %TRUE if the score is looping
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
gboolean
|
|
clutter_score_get_loop (ClutterScore *score)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE);
|
|
|
|
return score->priv->loop;
|
|
}
|
|
|
|
/**
|
|
* clutter_score_is_playing:
|
|
* @score: A #ClutterScore
|
|
*
|
|
* Query state of a #ClutterScore instance.
|
|
*
|
|
* Return Value: %TRUE if score is currently playing
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
gboolean
|
|
clutter_score_is_playing (ClutterScore *score)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE);
|
|
|
|
if (score->priv->is_paused)
|
|
return FALSE;
|
|
|
|
return score->priv->running_timelines
|
|
&& g_hash_table_size (score->priv->running_timelines) != 0;
|
|
}
|
|
|
|
/* destroy_entry:
|
|
* @node: a #GNode
|
|
*
|
|
* Frees the #ClutterScoreEntry attached to @node.
|
|
*/
|
|
static gboolean
|
|
destroy_entry (GNode *node,
|
|
G_GNUC_UNUSED gpointer data)
|
|
{
|
|
ClutterScoreEntry *entry = node->data;
|
|
|
|
if (G_LIKELY (entry != NULL))
|
|
{
|
|
if (entry->marker_id)
|
|
{
|
|
g_signal_handler_disconnect (entry->parent, entry->marker_id);
|
|
entry->marker_id = 0;
|
|
}
|
|
|
|
if (entry->complete_id)
|
|
{
|
|
g_signal_handler_disconnect (entry->timeline, entry->complete_id);
|
|
entry->complete_id = 0;
|
|
}
|
|
|
|
g_object_unref (entry->timeline);
|
|
g_free (entry->marker);
|
|
g_slice_free (ClutterScoreEntry, entry);
|
|
|
|
node->data = NULL;
|
|
}
|
|
|
|
/* continue */
|
|
return FALSE;
|
|
}
|
|
|
|
typedef enum {
|
|
FIND_BY_TIMELINE,
|
|
FIND_BY_ID,
|
|
REMOVE_BY_ID,
|
|
LIST_TIMELINES
|
|
} TraverseAction;
|
|
|
|
typedef struct {
|
|
TraverseAction action;
|
|
|
|
ClutterScore *score;
|
|
|
|
/* parameters */
|
|
union {
|
|
ClutterTimeline *timeline;
|
|
gulong id;
|
|
ClutterScoreEntry *entry;
|
|
} d;
|
|
|
|
gpointer result;
|
|
} TraverseClosure;
|
|
|
|
/* multi-purpose traversal function for the N-ary tree used by the score */
|
|
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)
|
|
{
|
|
/* Destroy all the child entries of this node */
|
|
g_node_traverse (node,
|
|
G_POST_ORDER,
|
|
G_TRAVERSE_ALL,
|
|
-1,
|
|
destroy_entry, NULL);
|
|
|
|
/* Keep track of this node so that it will be destroyed
|
|
further up */
|
|
closure->result = node;
|
|
|
|
retval = TRUE;
|
|
}
|
|
break;
|
|
|
|
case LIST_TIMELINES:
|
|
closure->result = g_slist_prepend (closure->result, entry->timeline);
|
|
retval = FALSE;
|
|
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,
|
|
gulong 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;
|
|
}
|
|
|
|
/* forward declaration */
|
|
static void start_entry (ClutterScoreEntry *entry);
|
|
|
|
static void
|
|
start_children_entries (GNode *node,
|
|
gpointer data)
|
|
{
|
|
ClutterScoreEntry *entry = node->data;
|
|
|
|
/* If data is NULL, start all entries that have no marker, otherwise
|
|
only start entries that have the same marker */
|
|
if (data == NULL
|
|
? entry->marker == NULL
|
|
: (entry->marker && !strcmp (data, entry->marker)))
|
|
start_entry (entry);
|
|
}
|
|
|
|
static void
|
|
on_timeline_marker (ClutterTimeline *timeline,
|
|
const gchar *marker_name,
|
|
gint frame_num,
|
|
ClutterScoreEntry *entry)
|
|
{
|
|
GNode *parent;
|
|
CLUTTER_NOTE (SCHEDULER, "timeline [%p] marker ('%s') reached",
|
|
entry->timeline,
|
|
entry->marker);
|
|
|
|
parent = find_entry_by_timeline (entry->score, timeline);
|
|
if (!parent)
|
|
return;
|
|
|
|
/* start every child */
|
|
if (parent->children)
|
|
{
|
|
g_node_children_foreach (parent,
|
|
G_TRAVERSE_ALL,
|
|
start_children_entries,
|
|
(gpointer) marker_name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_timeline_completed (ClutterTimeline *timeline,
|
|
ClutterScoreEntry *entry)
|
|
{
|
|
ClutterScorePrivate *priv = entry->score->priv;
|
|
|
|
g_hash_table_remove (priv->running_timelines,
|
|
GUINT_TO_POINTER (entry->id));
|
|
|
|
g_signal_handler_disconnect (timeline, entry->complete_id);
|
|
entry->complete_id = 0;
|
|
|
|
CLUTTER_NOTE (SCHEDULER, "timeline [%p] ('%lu') completed",
|
|
entry->timeline,
|
|
entry->id);
|
|
|
|
g_signal_emit (entry->score, score_signals[TIMELINE_COMPLETED], 0,
|
|
entry->timeline);
|
|
|
|
/* start every child */
|
|
if (entry->node->children)
|
|
{
|
|
g_node_children_foreach (entry->node,
|
|
G_TRAVERSE_ALL,
|
|
start_children_entries,
|
|
NULL);
|
|
}
|
|
|
|
/* score has finished - fire 'completed' signal */
|
|
if (g_hash_table_size (priv->running_timelines) == 0)
|
|
{
|
|
CLUTTER_NOTE (SCHEDULER, "looks like we finished");
|
|
|
|
g_signal_emit (entry->score, score_signals[COMPLETED], 0);
|
|
|
|
clutter_score_stop (entry->score);
|
|
|
|
if (priv->loop)
|
|
clutter_score_start (entry->score);
|
|
}
|
|
}
|
|
|
|
static void
|
|
start_entry (ClutterScoreEntry *entry)
|
|
{
|
|
ClutterScorePrivate *priv = entry->score->priv;
|
|
|
|
/* timelines attached to a marker might already be playing when we
|
|
* end up here from the ::completed handler, so we need to perform
|
|
* this check to avoid restarting those timelines
|
|
*/
|
|
if (clutter_timeline_is_playing (entry->timeline))
|
|
return;
|
|
|
|
entry->complete_id = g_signal_connect (entry->timeline,
|
|
"completed",
|
|
G_CALLBACK (on_timeline_completed),
|
|
entry);
|
|
|
|
CLUTTER_NOTE (SCHEDULER, "timeline [%p] ('%lu') started",
|
|
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,
|
|
GUINT_TO_POINTER (entry->id),
|
|
entry);
|
|
|
|
clutter_timeline_start (entry->timeline);
|
|
|
|
g_signal_emit (entry->score, score_signals[TIMELINE_STARTED], 0,
|
|
entry->timeline);
|
|
}
|
|
|
|
enum
|
|
{
|
|
ACTION_START,
|
|
ACTION_PAUSE,
|
|
ACTION_STOP
|
|
};
|
|
|
|
static void
|
|
foreach_running_timeline (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
ClutterScoreEntry *entry = value;
|
|
gint action = GPOINTER_TO_INT (user_data);
|
|
|
|
switch (action)
|
|
{
|
|
case ACTION_START:
|
|
clutter_timeline_start (entry->timeline);
|
|
break;
|
|
|
|
case ACTION_PAUSE:
|
|
clutter_timeline_pause (entry->timeline);
|
|
break;
|
|
|
|
case ACTION_STOP:
|
|
if (entry->complete_id)
|
|
{
|
|
g_signal_handler_disconnect (entry->timeline, entry->complete_id);
|
|
entry->complete_id = 0;
|
|
}
|
|
clutter_timeline_stop (entry->timeline);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_score_start:
|
|
* @score: A #ClutterScore
|
|
*
|
|
* Starts the score.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_start (ClutterScore *score)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
priv = score->priv;
|
|
|
|
if (priv->is_paused)
|
|
{
|
|
g_hash_table_foreach (priv->running_timelines,
|
|
foreach_running_timeline,
|
|
GINT_TO_POINTER (ACTION_START));
|
|
priv->is_paused = FALSE;
|
|
}
|
|
else
|
|
{
|
|
g_signal_emit (score, score_signals[STARTED], 0);
|
|
g_node_children_foreach (priv->root,
|
|
G_TRAVERSE_ALL,
|
|
start_children_entries,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_score_stop:
|
|
* @score: A #ClutterScore
|
|
*
|
|
* Stops and rewinds a playing #ClutterScore instance.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_stop (ClutterScore *score)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
priv = score->priv;
|
|
|
|
if (priv->running_timelines)
|
|
{
|
|
g_hash_table_foreach (priv->running_timelines,
|
|
foreach_running_timeline,
|
|
GINT_TO_POINTER (ACTION_STOP));
|
|
g_hash_table_destroy (priv->running_timelines);
|
|
priv->running_timelines = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_score_pause:
|
|
* @score: a #ClutterScore
|
|
*
|
|
* Pauses a playing score @score.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_pause (ClutterScore *score)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
priv = score->priv;
|
|
|
|
if (!clutter_score_is_playing (score))
|
|
return;
|
|
|
|
g_hash_table_foreach (priv->running_timelines,
|
|
foreach_running_timeline,
|
|
GINT_TO_POINTER (ACTION_PAUSE));
|
|
|
|
priv->is_paused = TRUE;
|
|
|
|
g_signal_emit (score, score_signals[PAUSED], 0);
|
|
}
|
|
|
|
/**
|
|
* clutter_score_rewind:
|
|
* @score: A #ClutterScore
|
|
*
|
|
* Rewinds a #ClutterScore to its initial state.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_rewind (ClutterScore *score)
|
|
{
|
|
gboolean was_playing;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
was_playing = clutter_score_is_playing (score);
|
|
|
|
clutter_score_stop (score);
|
|
|
|
if (was_playing)
|
|
clutter_score_start (score);
|
|
}
|
|
|
|
static inline void
|
|
clutter_score_clear (ClutterScore *score)
|
|
{
|
|
ClutterScorePrivate *priv = score->priv;
|
|
|
|
g_node_traverse (priv->root,
|
|
G_POST_ORDER,
|
|
G_TRAVERSE_ALL,
|
|
-1,
|
|
destroy_entry, NULL);
|
|
g_node_destroy (priv->root);
|
|
}
|
|
|
|
/**
|
|
* clutter_score_append:
|
|
* @score: a #ClutterScore
|
|
* @parent: (allow-none): a #ClutterTimeline in the score, or %NULL
|
|
* @timeline: a #ClutterTimeline
|
|
*
|
|
* 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.
|
|
*
|
|
* #ClutterScore will take a reference on @timeline.
|
|
*
|
|
* Return value: the id of the #ClutterTimeline inside the score, or
|
|
* 0 on failure. The returned id can be used with clutter_score_remove()
|
|
* or clutter_score_get_timeline().
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
gulong
|
|
clutter_score_append (ClutterScore *score,
|
|
ClutterTimeline *parent,
|
|
ClutterTimeline *timeline)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
ClutterScoreEntry *entry;
|
|
|
|
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;
|
|
|
|
if (!parent)
|
|
{
|
|
entry = g_slice_new (ClutterScoreEntry);
|
|
entry->timeline = g_object_ref (timeline);
|
|
entry->parent = NULL;
|
|
entry->id = priv->last_id;
|
|
entry->marker = NULL;
|
|
entry->marker_id = 0;
|
|
entry->complete_id = 0;
|
|
entry->score = score;
|
|
|
|
entry->node = g_node_append_data (priv->root, entry);
|
|
}
|
|
else
|
|
{
|
|
GNode *node;
|
|
|
|
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->parent = parent;
|
|
entry->id = priv->last_id;
|
|
entry->marker = NULL;
|
|
entry->marker_id = 0;
|
|
entry->complete_id = 0;
|
|
entry->score = score;
|
|
|
|
entry->node = g_node_append_data (node, entry);
|
|
}
|
|
|
|
priv->last_id += 1;
|
|
|
|
return entry->id;
|
|
}
|
|
|
|
/**
|
|
* clutter_score_append_at_marker:
|
|
* @score: a #ClutterScore
|
|
* @parent: the parent #ClutterTimeline
|
|
* @marker_name: the name of the marker to use
|
|
* @timeline: the #ClutterTimeline to append
|
|
*
|
|
* Appends @timeline at the given @marker_name on the @parent
|
|
* #ClutterTimeline.
|
|
*
|
|
* If you want to append @timeline at the end of @parent, use
|
|
* clutter_score_append().
|
|
*
|
|
* The #ClutterScore will take a reference on @timeline.
|
|
*
|
|
* Return value: the id of the #ClutterTimeline inside the score, or
|
|
* 0 on failure. The returned id can be used with clutter_score_remove()
|
|
* or clutter_score_get_timeline().
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
gulong
|
|
clutter_score_append_at_marker (ClutterScore *score,
|
|
ClutterTimeline *parent,
|
|
const gchar *marker_name,
|
|
ClutterTimeline *timeline)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
GNode *node;
|
|
ClutterScoreEntry *entry;
|
|
gchar *marker_reached_signal;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_SCORE (score), 0);
|
|
g_return_val_if_fail (CLUTTER_IS_TIMELINE (parent), 0);
|
|
g_return_val_if_fail (marker_name != NULL, 0);
|
|
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
|
|
|
|
if (!clutter_timeline_has_marker (parent, marker_name))
|
|
{
|
|
g_warning ("The parent timeline has no marker '%s'", marker_name);
|
|
return 0;
|
|
}
|
|
|
|
priv = score->priv;
|
|
|
|
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->parent = parent;
|
|
entry->marker = g_strdup (marker_name);
|
|
entry->id = priv->last_id;
|
|
entry->score = score;
|
|
entry->complete_id = 0;
|
|
|
|
marker_reached_signal = g_strdup_printf ("marker-reached::%s", marker_name);
|
|
entry->marker_id = g_signal_connect (entry->parent,
|
|
marker_reached_signal,
|
|
G_CALLBACK (on_timeline_marker),
|
|
entry);
|
|
|
|
entry->node = g_node_append_data (node, entry);
|
|
|
|
g_free (marker_reached_signal);
|
|
|
|
priv->last_id += 1;
|
|
|
|
return entry->id;
|
|
}
|
|
|
|
/**
|
|
* clutter_score_remove:
|
|
* @score: a #ClutterScore
|
|
* @id_: the id of the timeline to remove
|
|
*
|
|
* 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
|
|
clutter_score_remove (ClutterScore *score,
|
|
gulong id_)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
TraverseClosure closure;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
g_return_if_fail (id_ > 0);
|
|
|
|
priv = score->priv;
|
|
|
|
closure.action = REMOVE_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)
|
|
g_node_destroy (closure.result);
|
|
}
|
|
|
|
/**
|
|
* clutter_score_remove_all:
|
|
* @score: a #ClutterScore
|
|
*
|
|
* Removes all the timelines inside @score.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_score_remove_all (ClutterScore *score)
|
|
{
|
|
ClutterScorePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCORE (score));
|
|
|
|
priv = score->priv;
|
|
|
|
/* this will take care of the running timelines */
|
|
clutter_score_stop (score);
|
|
|
|
/* destroy all the contents of the tree */
|
|
clutter_score_clear (score);
|
|
|
|
/* recreate the sentinel */
|
|
priv->root = g_node_new (NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_score_get_timeline:
|
|
* @score: a #ClutterScore
|
|
* @id_: the id of the timeline
|
|
*
|
|
* Retrieves the #ClutterTimeline for @id_ inside @score.
|
|
*
|
|
* Return value: (transfer none): the requested timeline, or %NULL. This
|
|
* function does not increase the reference count on the returned
|
|
* #ClutterTimeline
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
ClutterTimeline *
|
|
clutter_score_get_timeline (ClutterScore *score,
|
|
gulong id_)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* clutter_score_list_timelines:
|
|
* @score: a #ClutterScore
|
|
*
|
|
* Retrieves a list of all the #ClutterTimelines managed by @score.
|
|
*
|
|
* Return value: (transfer container) (element-type Clutter.Timeline): a
|
|
* #GSList containing all the timelines in the score. This function does
|
|
* not increase the reference count of the returned timelines. Use
|
|
* g_slist_free() on the returned list to deallocate its resources.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
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;
|
|
}
|