/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Authored By Matthew Allum * * 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * 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 entrys) * Callback id; * delay * } * * start()/stop(),remove(),remove_all() ? */ /** * SECTION:clutter-score * @short_description: Sequencing multiple #ClutterTimelines in order * * #ClutterScore is a base class for sequencing multiple timelines in order. */ #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" G_DEFINE_TYPE (ClutterScore, clutter_score, G_TYPE_OBJECT); typedef struct ClutterScoreEntry { ClutterTimeline *timeline; gulong handler_id; GSList *child_entries; ClutterScore *score; } ClutterScoreEntry; struct _ClutterScorePrivate { GSList *entries; GHashTable *running_timelines; guint paused :1; guint loop : 1; }; enum { PROP_0, PROP_LOOP }; enum { NEW_TIMELINE, STARTED, PAUSED, COMPLETED, LAST_SIGNAL }; static int score_signals[LAST_SIGNAL] = { 0 }; static void start_entry (ClutterScoreEntry *entry); /* Object */ static void clutter_score_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterScore *score; ClutterScorePrivate *priv; score = CLUTTER_SCORE(object); priv = score->priv; switch (prop_id) { case PROP_LOOP: priv->loop = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_score_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterScore *score; ClutterScorePrivate *priv; score = CLUTTER_SCORE(object); priv = score->priv; switch (prop_id) { case PROP_LOOP: g_value_set_boolean (value, priv->loop); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_score_finalize (GObject *object) { G_OBJECT_CLASS (clutter_score_parent_class)->finalize (object); } static void clutter_score_dispose (GObject *object) { ClutterScore *self = CLUTTER_SCORE(object); ClutterScorePrivate *priv; priv = self->priv; if (priv != NULL) { } G_OBJECT_CLASS (clutter_score_parent_class)->dispose (object); } static void clutter_score_class_init (ClutterScoreClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; object_class->set_property = clutter_score_set_property; object_class->get_property = clutter_score_get_property; object_class->finalize = clutter_score_finalize; object_class->dispose = clutter_score_dispose; g_type_class_add_private (klass, sizeof (ClutterScorePrivate)); /** * ClutterScore::new-timeline: * @score: the score which received the signal * @timeline: the current timeline * * The ::new-timeline signal is emitted each time a new timeline in the * score is reached. */ score_signals[NEW_TIMELINE] = g_signal_new ("new-timeline", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ClutterScoreClass, new_timeline), NULL, NULL, clutter_marshal_VOID__OBJECT, G_TYPE_NONE, 1, CLUTTER_TYPE_TIMELINE); score_signals[COMPLETED] = g_signal_new ("completed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ClutterScoreClass, completed), NULL, NULL, clutter_marshal_VOID__VOID, G_TYPE_NONE, 0); score_signals[STARTED] = g_signal_new ("started", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ClutterScoreClass, started), NULL, NULL, clutter_marshal_VOID__VOID, G_TYPE_NONE, 0); score_signals[PAUSED] = g_signal_new ("paused", G_TYPE_FROM_CLASS (object_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) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, CLUTTER_TYPE_SCORE, ClutterScorePrivate); self->priv->running_timelines = g_hash_table_new(NULL, NULL); } /** * clutter_score_set_loop: * @score: a #ClutterScore * @loop: %TRUE for enable looping * * Sets whether @score should loop. */ void clutter_score_set_loop (ClutterScore *score, gboolean loop) { g_return_if_fail (CLUTTER_IS_SCORE (score)); if (score->priv->loop != loop) { g_object_ref (score); score->priv->loop = loop; g_object_notify (G_OBJECT (score), "loop"); g_object_unref (score); } } /** * clutter_score_get_loop: * @score: a #ClutterScore * * Gets whether @score is looping * * Return value: %TRUE if the score is looping */ 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, FALSE if not. */ gboolean clutter_score_is_playing (ClutterScore *score) { g_return_val_if_fail (CLUTTER_IS_SCORE (score), FALSE); /* FIXME: paused state currently counts as playing */ return !!g_hash_table_size(score->priv->running_timelines); } static void on_timeline_finish (ClutterTimeline *timeline, ClutterScoreEntry *entry) { GSList *item; g_hash_table_remove (entry->score->priv->running_timelines, GINT_TO_POINTER(entry->handler_id)); g_signal_handler_disconnect (timeline, entry->handler_id); CLUTTER_NOTE (SCHEDULER, "completed %p %li\n", entry->timeline, entry->handler_id); for (item = entry->child_entries; item != NULL; item = item->next) { ClutterScoreEntry *child_entry = item->data; start_entry (child_entry); } if (clutter_score_is_playing (entry->score) == FALSE) { /* Score has finished - fire 'completed' signal */ /* Also check if looped etc */ CLUTTER_NOTE (SCHEDULER, "looks like we finished\n"); g_signal_emit (entry->score, score_signals[COMPLETED], 0); } } static void start_entry (ClutterScoreEntry *entry) { entry->handler_id = g_signal_connect (entry->timeline, "completed", G_CALLBACK (on_timeline_finish), entry); CLUTTER_NOTE (SCHEDULER, "started %p %li\n", entry->timeline, entry->handler_id); g_hash_table_insert (entry->score->priv->running_timelines, GINT_TO_POINTER(entry->handler_id), entry); clutter_timeline_start (entry->timeline); g_signal_emit (entry->score, score_signals[NEW_TIMELINE], 0, entry->timeline); } void on_foreach_running_timeline_start (gpointer key, gpointer value, gpointer user_data) { clutter_timeline_start (CLUTTER_TIMELINE(value)); } /** * clutter_score_start: * @score: A #ClutterScore * * Query state of a #ClutterScore instance. * * Return Value: TRUE if score is currently playing, FALSE if not. */ void clutter_score_start (ClutterScore *score) { GSList *item; ClutterScorePrivate *priv; g_return_if_fail (CLUTTER_IS_SCORE (score)); priv = score->priv; if (priv->paused) { g_hash_table_foreach (priv->running_timelines, (GHFunc)on_foreach_running_timeline_start, NULL); priv->paused = 0; } else { for (item = priv->entries; item != NULL; item = item->next) { ClutterScoreEntry *entry = item->data; start_entry (entry); } } } gboolean on_foreach_running_timeline_stop (gpointer key, gpointer value, gpointer user_data) { clutter_timeline_stop (((ClutterScoreEntry*)value)->timeline); return TRUE; } /** * clutter_score_stop: * @score: A #ClutterScore * * Stops and rewinds a playing #ClutterScore instance. * */ void clutter_score_stop (ClutterScore *score) { ClutterScorePrivate *priv; g_return_if_fail (CLUTTER_IS_SCORE (score)); priv = score->priv; g_hash_table_foreach_remove (priv->running_timelines, (GHRFunc)on_foreach_running_timeline_stop, NULL); } /** * clutter_score_rewind: * @score: A #ClutterScore * * Rewinds a #ClutterScore to inital timeline. **/ 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); } void on_foreach_running_timeline_pause (gpointer key, gpointer value, gpointer user_data) { clutter_timeline_pause (((ClutterScoreEntry*)value)->timeline); } void clutter_score_pause (ClutterScore *score) { ClutterScorePrivate *priv; g_return_if_fail (CLUTTER_IS_SCORE (score)); priv = score->priv; if (priv->paused || !clutter_score_is_playing (score)) return; g_hash_table_foreach (priv->running_timelines, (GHFunc)on_foreach_running_timeline_pause, NULL); priv->paused = 1; g_signal_emit (score, score_signals[PAUSED], 0); } static ClutterScoreEntry* find_entry (GSList *list, ClutterTimeline *timeline) { GSList *item; ClutterScoreEntry *res = NULL; if (list == NULL) return NULL; for (item = list; item != NULL && res == NULL; item = item->next) { ClutterScoreEntry *entry = item->data; g_assert (entry != NULL); if (entry->timeline == timeline) return entry; if (entry->child_entries) res = find_entry (entry->child_entries, timeline); } return res; } /** * clutter_score_append: * @score: A #ClutterScore * @timeline_existing: A #ClutterTimeline in the score * @timeline_new: A new #ClutterTimeline to start when #timeline_existing has * completed, * * Appends a new timeline to an one existing in the score. * */ void clutter_score_append (ClutterScore *score, ClutterTimeline *timeline_existing, ClutterTimeline *timeline_new) { ClutterScorePrivate *priv; ClutterScoreEntry *entry, *entry_new; priv = score->priv; /* Appends a timeline to the end of another */ if ((entry = find_entry (priv->entries, timeline_existing)) != NULL) { entry_new = g_new0(ClutterScoreEntry, 1); entry_new->timeline = g_object_ref (timeline_new); entry_new->score = score; entry->child_entries = g_slist_append (entry->child_entries, entry_new); clutter_timeline_stop (timeline_new); /* stop it */ } } /** * clutter_score_add: * @score: A #ClutterScore * @timeline: A #ClutterTimeline * * Adds a new initial timeline to start when the score is started. * */ void clutter_score_add (ClutterScore *score, ClutterTimeline *timeline) { ClutterScorePrivate *priv; ClutterScoreEntry *entry; priv = score->priv; /* Added timelines are always started first */ entry = g_new0(ClutterScoreEntry, 1); entry->timeline = g_object_ref (timeline); entry->score = score; score->priv->entries = g_slist_append (score->priv->entries, entry); clutter_timeline_stop (timeline); /* stop it */ CLUTTER_NOTE (SCHEDULER, "added timeline %p\n", entry->timeline); } static void remove_entrys (GSList *list) { GSList *item; if (list == NULL) return; for (item = list; item != NULL; item = item->next) { ClutterScoreEntry *entry = item->data; g_object_unref (entry->timeline); if (entry->child_entries) remove_entrys (entry->child_entries); g_slist_free (entry->child_entries); g_free(entry); } } void clutter_score_remove (ClutterScore *score, ClutterTimeline *timeline_parent, ClutterTimeline *timeline) { } void clutter_score_remove_all (ClutterScore *score) { clutter_score_stop (score); remove_entrys (score->priv->entries); } /** * clutter_score_new: * * Create a new #ClutterScore instance. * * Return Value: a new #ClutterScore */ ClutterScore* clutter_score_new () { return g_object_new (CLUTTER_TYPE_SCORE, NULL); }