2008-03-18 Emmanuele Bassi <ebassi@openedhand.com>

* clutter/clutter-marshal.list: Add signature for the
	::marker-reached signal marshaller.

	* clutter/clutter-timeline.[ch]: Add timeline marker API;
	using markers it is possible to add a unique identifier to
	a particular frame of the timeline, and receive a signal
	notification when reaching that particular marker while
	playing the timeline. (#641)

	* tests/test-timeline.c: Update the test case to check for
	the marker-reached signal emission.

	* clutter.symbols: Add new symbols.
This commit is contained in:
Emmanuele Bassi 2008-03-18 17:50:45 +00:00
parent b6cc7c1249
commit be97c496b6
6 changed files with 482 additions and 42 deletions

View File

@ -1,3 +1,19 @@
2008-03-18 Emmanuele Bassi <ebassi@openedhand.com>
* clutter/clutter-marshal.list: Add signature for the
::marker-reached signal marshaller.
* clutter/clutter-timeline.[ch]: Add timeline marker API;
using markers it is possible to add a unique identifier to
a particular frame of the timeline, and receive a signal
notification when reaching that particular marker while
playing the timeline. (#641)
* tests/test-timeline.c: Update the test case to check for
the marker-reached signal emission.
* clutter.symbols: Add new symbols.
2008-03-10 Øyvind Kolås <pippin@o-hand.com> 2008-03-10 Øyvind Kolås <pippin@o-hand.com>
* tests/test-shader.c: improved readability of fragment shader * tests/test-shader.c: improved readability of fragment shader

View File

@ -541,7 +541,10 @@ clutter_threads_enter
clutter_threads_init clutter_threads_init
clutter_threads_leave clutter_threads_leave
clutter_threads_set_lock_functions clutter_threads_set_lock_functions
clutter_timeline_add_marker_at_frame
clutter_timeline_add_marker_at_time
clutter_timeline_advance clutter_timeline_advance
clutter_timeline_advance_to_marker
clutter_timeline_clone clutter_timeline_clone
clutter_timeline_get_current_frame clutter_timeline_get_current_frame
clutter_timeline_get_delay clutter_timeline_get_delay
@ -553,9 +556,11 @@ clutter_timeline_get_n_frames
clutter_timeline_get_speed clutter_timeline_get_speed
clutter_timeline_get_type clutter_timeline_get_type
clutter_timeline_is_playing clutter_timeline_is_playing
clutter_timeline_list_markers
clutter_timeline_new clutter_timeline_new
clutter_timeline_new_for_duration clutter_timeline_new_for_duration
clutter_timeline_pause clutter_timeline_pause
clutter_timeline_remove_marker
clutter_timeline_rewind clutter_timeline_rewind
clutter_timeline_set_delay clutter_timeline_set_delay
clutter_timeline_set_direction clutter_timeline_set_direction

View File

@ -7,4 +7,5 @@ VOID:BOXED
VOID:OBJECT VOID:OBJECT
VOID:VOID VOID:VOID
VOID:OBJECT,POINTER VOID:OBJECT,POINTER
VOID:STRING,UINT
BOOLEAN:BOXED BOOLEAN:BOXED

View File

@ -76,9 +76,18 @@ struct _ClutterTimelinePrivate
GTimeVal prev_frame_timeval; GTimeVal prev_frame_timeval;
guint msecs_delta; guint msecs_delta;
GHashTable *markers_by_frame;
GHashTable *markers_by_name;
guint loop : 1; guint loop : 1;
}; };
typedef struct {
gchar *name;
guint frame_num;
GQuark quark;
} TimelineMarker;
enum enum
{ {
PROP_0, PROP_0,
@ -97,6 +106,7 @@ enum
STARTED, STARTED,
PAUSED, PAUSED,
COMPLETED, COMPLETED,
MARKER_REACHED,
LAST_SIGNAL LAST_SIGNAL
}; };
@ -163,6 +173,31 @@ timeout_remove (guint tag)
g_source_remove (tag); g_source_remove (tag);
} }
static TimelineMarker *
timeline_marker_new (const gchar *name,
guint frame_num)
{
TimelineMarker *marker = g_slice_new0 (TimelineMarker);
marker->name = g_strdup (name);
marker->quark = g_quark_from_string (marker->name);
marker->frame_num = frame_num;
return marker;
}
static void
timeline_marker_free (gpointer data)
{
if (G_LIKELY (data))
{
TimelineMarker *marker = data;
g_free (marker->name);
g_slice_free (TimelineMarker, marker);
}
}
/* Object */ /* Object */
static void static void
@ -244,6 +279,11 @@ clutter_timeline_get_property (GObject *object,
static void static void
clutter_timeline_finalize (GObject *object) clutter_timeline_finalize (GObject *object)
{ {
ClutterTimelinePrivate *priv = CLUTTER_TIMELINE (object)->priv;
g_hash_table_destroy (priv->markers_by_frame);
g_hash_table_destroy (priv->markers_by_name);
G_OBJECT_CLASS (clutter_timeline_parent_class)->finalize (object); G_OBJECT_CLASS (clutter_timeline_parent_class)->finalize (object);
} }
@ -436,18 +476,66 @@ clutter_timeline_class_init (ClutterTimelineClass *klass)
NULL, NULL, NULL, NULL,
clutter_marshal_VOID__VOID, clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0); G_TYPE_NONE, 0);
/**
* ClutterTimeline::marker-reached:
* @timeline: the #ClutterTimeline which received the signal
* @marker_name: the name of the marker reached
* @frame_num: the frame number
*
* The ::marker-reached signal is emitted each time a timeline
* reaches a marker set with clutter_timeline_add_marker_at_frame()
* or clutter_timeline_add_marker_at_time(). This signal is
* detailed with the name of the marker as well, so it is
* possible to connect a callback to the ::marker-reached signal
* for a specific marker with:
*
* <informalexample><programlisting>
* clutter_timeline_add_marker_at_frame (timeline, "foo", 24);
* clutter_timeline_add_marker_at_frame (timeline, "bar", 48);
*
* g_signal_connect (timeline, "marker-reached",
* G_CALLBACK (each_marker_reached), NULL);
* g_signal_connect (timeline, "marker-reached::foo",
* G_CALLBACK (foo_marker_reached), NULL);
* g_signal_connect (timeline, "marker-reached::bar",
* G_CALLBACK (bar_marker_reached), NULL);
* </programlisting></informalexample>
*
* In the example, the first callback will be invoked for both
* the "foo" and "bar" marker, while the second and third callbacks
* will be invoked for the "foo" or "bar" markers, respectively.
*
* Since: 0.8
*/
timeline_signals[MARKER_REACHED] =
g_signal_new ("marker-reached",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
G_STRUCT_OFFSET (ClutterTimelineClass, marker_reached),
NULL, NULL,
clutter_marshal_VOID__STRING_UINT,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_UINT);
} }
static void static void
clutter_timeline_init (ClutterTimeline *self) clutter_timeline_init (ClutterTimeline *self)
{ {
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ClutterTimelinePrivate *priv;
self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
CLUTTER_TYPE_TIMELINE, CLUTTER_TYPE_TIMELINE,
ClutterTimelinePrivate); ClutterTimelinePrivate);
self->priv->fps = clutter_get_default_frame_rate (); priv->fps = clutter_get_default_frame_rate ();
self->priv->n_frames = 0; priv->n_frames = 0;
self->priv->msecs_delta = 0; priv->msecs_delta = 0;
priv->markers_by_frame = g_hash_table_new (NULL, NULL);
priv->markers_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
timeline_marker_free);
} }
static gboolean static gboolean
@ -490,12 +578,10 @@ timeline_timeout_func (gpointer data)
priv->skipped_frames = n_frames - 1; priv->skipped_frames = n_frames - 1;
if (priv->skipped_frames) if (priv->skipped_frames)
{
CLUTTER_TIMESTAMP (SCHEDULER, CLUTTER_TIMESTAMP (SCHEDULER,
"Timeline [%p], skipping %d frames\n", "Timeline [%p], skipping %d frames\n",
timeline, timeline,
priv->skipped_frames); priv->skipped_frames);
}
priv->prev_frame_timeval = timeval; priv->prev_frame_timeval = timeval;
@ -513,9 +599,31 @@ timeline_timeout_func (gpointer data)
(priv->current_frame_num <= 0)) (priv->current_frame_num <= 0))
)) ))
{ {
gint i;
/* Fire off signal */ /* Fire off signal */
g_signal_emit (timeline, timeline_signals[NEW_FRAME], g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
0, priv->current_frame_num); priv->current_frame_num);
for (i = priv->skipped_frames; i >= 0; i--)
{
gint frame_num = priv->current_frame_num - i;
GSList *markers, *l;
markers = g_hash_table_lookup (priv->markers_by_frame,
GUINT_TO_POINTER (frame_num));
for (l = markers; l; l = l->next)
{
TimelineMarker *marker = l->data;
CLUTTER_NOTE (SCHEDULER, "Marker `%s' reached", marker->name);
g_signal_emit (timeline, timeline_signals[MARKER_REACHED],
marker->quark,
marker->name,
marker->frame_num);
}
}
/* Signal pauses timeline ? */ /* Signal pauses timeline ? */
if (!priv->timeout_id) if (!priv->timeout_id)
@ -527,26 +635,24 @@ timeline_timeout_func (gpointer data)
g_object_unref (timeline); g_object_unref (timeline);
return TRUE; return TRUE;
} }
else /* Handle loop or stop */ else
{ {
/* Handle loop or stop */
ClutterTimelineDirection saved_direction = priv->direction; ClutterTimelineDirection saved_direction = priv->direction;
guint overflow_frame_num = priv->current_frame_num; guint overflow_frame_num = priv->current_frame_num;
gint end_frame; gint end_frame;
/* In case the signal handlers want to take a peek... */ /* In case the signal handlers want to take a peek... */
if (priv->direction == CLUTTER_TIMELINE_FORWARD) if (priv->direction == CLUTTER_TIMELINE_FORWARD)
{
priv->current_frame_num = priv->n_frames; priv->current_frame_num = priv->n_frames;
}
else if (priv->direction == CLUTTER_TIMELINE_BACKWARD) else if (priv->direction == CLUTTER_TIMELINE_BACKWARD)
{
priv->current_frame_num = 0; priv->current_frame_num = 0;
}
end_frame = priv->current_frame_num; end_frame = priv->current_frame_num;
/* Fire off signal */ /* Fire off signal */
g_signal_emit (timeline, timeline_signals[NEW_FRAME], g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
0, priv->current_frame_num); priv->current_frame_num);
/* Did the signal handler modify the current_frame_num */ /* Did the signal handler modify the current_frame_num */
if (priv->current_frame_num != end_frame) if (priv->current_frame_num != end_frame)
@ -558,7 +664,6 @@ timeline_timeout_func (gpointer data)
/* Note: If the new-frame signal handler paused the timeline /* Note: If the new-frame signal handler paused the timeline
* on the last frame we will still go ahead and send the * on the last frame we will still go ahead and send the
* completed signal */ * completed signal */
CLUTTER_NOTE (SCHEDULER, CLUTTER_NOTE (SCHEDULER,
"Timeline [%p] completed (cur: %d, tot: %d, drop: %d)", "Timeline [%p] completed (cur: %d, tot: %d, drop: %d)",
timeline, timeline,
@ -577,6 +682,7 @@ timeline_timeout_func (gpointer data)
timeout_remove (priv->timeout_id); timeout_remove (priv->timeout_id);
priv->timeout_id = 0; priv->timeout_id = 0;
} }
g_signal_emit (timeline, timeline_signals[COMPLETED], 0); g_signal_emit (timeline, timeline_signals[COMPLETED], 0);
/* Again check to see if the user has manually played with /* Again check to see if the user has manually played with
@ -603,8 +709,10 @@ timeline_timeout_func (gpointer data)
/* Or if the direction changed, we try and bounce */ /* Or if the direction changed, we try and bounce */
if (priv->direction != saved_direction) if (priv->direction != saved_direction)
priv->current_frame_num = {
priv->n_frames - priv->current_frame_num; priv->current_frame_num = priv->n_frames
- priv->current_frame_num;
}
g_object_unref (timeline); g_object_unref (timeline);
return TRUE; return TRUE;
@ -612,8 +720,10 @@ timeline_timeout_func (gpointer data)
else else
{ {
clutter_timeline_rewind (timeline); clutter_timeline_rewind (timeline);
priv->prev_frame_timeval.tv_sec = 0; priv->prev_frame_timeval.tv_sec = 0;
priv->prev_frame_timeval.tv_usec = 0; priv->prev_frame_timeval.tv_usec = 0;
g_object_unref (timeline); g_object_unref (timeline);
return FALSE; return FALSE;
} }
@ -1297,3 +1407,246 @@ clutter_timeline_get_delta (ClutterTimeline *timeline,
return priv->skipped_frames + 1; return priv->skipped_frames + 1;
} }
static inline void
clutter_timeline_add_marker_internal (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num)
{
ClutterTimelinePrivate *priv = timeline->priv;
TimelineMarker *marker;
GSList *markers;
marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
if (G_UNLIKELY (marker))
{
g_warning ("A marker named `%s' already exists on frame %d",
marker->name,
marker->frame_num);
return;
}
marker = timeline_marker_new (marker_name, frame_num);
g_hash_table_insert (priv->markers_by_name, marker->name, marker);
markers = g_hash_table_lookup (priv->markers_by_frame,
GUINT_TO_POINTER (frame_num));
if (!markers)
{
markers = g_slist_prepend (NULL, marker);
g_hash_table_insert (priv->markers_by_frame,
GUINT_TO_POINTER (frame_num),
markers);
}
else
{
markers = g_slist_prepend (markers, marker);
g_hash_table_replace (priv->markers_by_frame,
GUINT_TO_POINTER (frame_num),
markers);
}
}
/**
* clutter_timeline_add_marker_at_frame:
* @timeline: a #ClutterTimeline
* @marker_name: the unique name for this marker
* @frame_num: the marker's frame
*
* Adds a named marker at @frame_num. Markers are unique string identifiers
* for a specific frame. Once @timeline reaches @frame_num, it will emit
* a ::marker-reached signal for each marker attached to that frame.
*
* A marker can be removed with clutter_timeline_remove_marker(). The
* timeline can be advanced to a marker using
* clutter_timeline_advance_to_marker().
*
* Since: 0.8
*/
void
clutter_timeline_add_marker_at_frame (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num)
{
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
g_return_if_fail (marker_name != NULL);
g_return_if_fail (frame_num <= clutter_timeline_get_n_frames (timeline));
clutter_timeline_add_marker_internal (timeline, marker_name, frame_num);
}
/**
* clutter_timeline_add_marker_at_time:
* @timeline: a #ClutterTimeline
* @marker_name: the unique name for this marker
* @msecs: position of the marker in milliseconds
*
* Time-based variant of clutter_timeline_add_marker_at_frame().
*
* Adds a named marker at @msecs.
*
* Since: 0.8
*/
void
clutter_timeline_add_marker_at_time (ClutterTimeline *timeline,
const gchar *marker_name,
guint msecs)
{
guint frame_num;
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
g_return_if_fail (marker_name != NULL);
g_return_if_fail (msecs <= clutter_timeline_get_duration (timeline));
frame_num = msecs * timeline->priv->fps / 1000;
clutter_timeline_add_marker_internal (timeline, marker_name, frame_num);
}
/**
* clutter_timeline_list_markers:
* @timeline: a #ClutterTimeline
* @frame_num: the frame number to check, or -1
* @n_markers: the number of markers returned
*
* Retrieves the list of markers at @frame_num. If @frame_num is a
* negative integer, all the markers attached to @timeline will be
* returned.
*
* Return value: a newly allocated, %NULL terminated string array
* containing the names of the markers. Use g_strfreev() when
* done.
*
* Since: 0.8
*/
gchar **
clutter_timeline_list_markers (ClutterTimeline *timeline,
gint frame_num,
guint *n_markers)
{
ClutterTimelinePrivate *priv;
gchar **retval = NULL;
gint i;
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
priv = timeline->priv;
if (frame_num < 0)
{
GList *markers, *l;
markers = g_hash_table_get_keys (priv->markers_by_name);
retval = g_new0 (gchar*, g_list_length (markers) + 1);
for (i = 0, l = markers; l != NULL; i++, l = l->next)
retval[i] = g_strdup (l->data);
g_list_free (markers);
}
else
{
GSList *markers, *l;
markers = g_hash_table_lookup (priv->markers_by_frame,
GUINT_TO_POINTER (frame_num));
retval = g_new0 (gchar*, g_slist_length (markers) + 1);
for (i = 0, l = markers; l != NULL; i++, l = l->next)
retval[i] = g_strdup (l->data);
}
if (n_markers)
*n_markers = i;
return retval;
}
/**
* clutter_timeline_advance_to_marker:
* @timeline: a #ClutterTimeline
* @marker_name: the name of the marker
*
* Advances @timeline to the frame of the given @marker_name.
*
* Since: 0.8
*/
void
clutter_timeline_advance_to_marker (ClutterTimeline *timeline,
const gchar *marker_name)
{
ClutterTimelinePrivate *priv;
TimelineMarker *marker;
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
g_return_if_fail (marker_name != NULL);
priv = timeline->priv;
marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
if (!marker)
{
g_warning ("No marker named `%s' found.", marker_name);
return;
}
clutter_timeline_advance (timeline, marker->frame_num);
}
/**
* clutter_timeline_remove_marker:
* @timeline: a #ClutterTimeline
* @marker_name: the name of the marker to remove
*
* Removes @marker_name, if found, from @timeline.
*
* Since: 0.8
*/
void
clutter_timeline_remove_marker (ClutterTimeline *timeline,
const gchar *marker_name)
{
ClutterTimelinePrivate *priv;
TimelineMarker *marker;
GSList *markers;
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
g_return_if_fail (marker_name != NULL);
priv = timeline->priv;
marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
if (!marker)
{
g_warning ("No marker named `%s' found.", marker_name);
return;
}
/* remove from the list of markers at the same frame */
markers = g_hash_table_lookup (priv->markers_by_frame,
GUINT_TO_POINTER (marker->frame_num));
if (G_LIKELY (markers))
{
markers = g_slist_remove (markers, marker);
if (!markers)
{
/* no markers left, remove the slot */
g_hash_table_remove (priv->markers_by_frame,
GUINT_TO_POINTER (marker->frame_num));
}
else
g_hash_table_replace (priv->markers_by_frame,
GUINT_TO_POINTER (marker->frame_num),
markers);
}
else
{
/* uh-oh, dangling marker; this should never happen */
g_warning ("Dangling marker %s at frame %d",
marker->name,
marker->frame_num);
}
/* this will take care of freeing the marker as well */
g_hash_table_remove (priv->markers_by_name, marker_name);
}

View File

@ -26,14 +26,12 @@
#ifndef _HAVE_CLUTTER_TIMELINE_H #ifndef _HAVE_CLUTTER_TIMELINE_H
#define _HAVE_CLUTTER_TIMELINE_H #define _HAVE_CLUTTER_TIMELINE_H
/* clutter-timeline.h */
#include <glib-object.h> #include <glib-object.h>
#include <clutter/clutter-fixed.h> #include <clutter/clutter-fixed.h>
G_BEGIN_DECLS G_BEGIN_DECLS
#define CLUTTER_TYPE_TIMELINE clutter_timeline_get_type() #define CLUTTER_TYPE_TIMELINE (clutter_timeline_get_type ())
#define CLUTTER_TIMELINE(obj) \ #define CLUTTER_TIMELINE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
@ -93,6 +91,10 @@ struct _ClutterTimelineClass
void (*new_frame) (ClutterTimeline *timeline, void (*new_frame) (ClutterTimeline *timeline,
gint frame_num); gint frame_num);
void (*marker_reached) (ClutterTimeline *timeline,
const gchar *marker_name,
gint frame_num);
/*< private >*/ /*< private >*/
void (*_clutter_timeline_1) (void); void (*_clutter_timeline_1) (void);
void (*_clutter_timeline_2) (void); void (*_clutter_timeline_2) (void);
@ -141,6 +143,20 @@ guint clutter_timeline_get_delay (ClutterTimeline *timeli
guint clutter_timeline_get_delta (ClutterTimeline *timeline, guint clutter_timeline_get_delta (ClutterTimeline *timeline,
guint *msecs); guint *msecs);
void clutter_timeline_add_marker_at_frame (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num);
void clutter_timeline_add_marker_at_time (ClutterTimeline *timeline,
const gchar *marker_name,
guint msecs);
void clutter_timeline_remove_marker (ClutterTimeline *timeline,
const gchar *marker_name);
gchar ** clutter_timeline_list_markers (ClutterTimeline *timeline,
gint frame_num,
guint *n_markers) G_GNUC_MALLOC;
void clutter_timeline_advance_to_marker (ClutterTimeline *timeline,
const gchar *marker_name);
G_END_DECLS G_END_DECLS
#endif #endif /* _HAVE_CLUTTER_TIMELINE_H */

View File

@ -38,22 +38,65 @@ timeline_3_new_frame_cb (ClutterTimeline *timeline, gint frame_no)
g_debug ("3: Doing frame %d.", frame_no); g_debug ("3: Doing frame %d.", frame_no);
} }
static void
timeline_1_marker_reached (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num)
{
g_print ("1: Marker `%s' (%d) reached\n", marker_name, frame_num);
}
static void
timeline_2_marker_reached (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num)
{
g_print ("2: Marker `%s' (%d) reached\n", marker_name, frame_num);
}
static void
timeline_3_marker_reached (ClutterTimeline *timeline,
const gchar *marker_name,
guint frame_num)
{
g_print ("2: Marker `%s' (%d) reached\n", marker_name, frame_num);
}
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {
ClutterTimeline *timeline_1; ClutterTimeline *timeline_1;
ClutterTimeline *timeline_2; ClutterTimeline *timeline_2;
ClutterTimeline *timeline_3; ClutterTimeline *timeline_3;
gchar **markers;
gsize n_markers;
clutter_init (&argc, &argv); clutter_init (&argc, &argv);
timeline_1 = clutter_timeline_new (10, 120); timeline_1 = clutter_timeline_new (10, 120);
clutter_timeline_add_marker_at_frame (timeline_1, "foo", 5);
clutter_timeline_add_marker_at_frame (timeline_1, "bar", 5);
clutter_timeline_add_marker_at_frame (timeline_1, "baz", 5);
markers = clutter_timeline_list_markers (timeline_1, 5, &n_markers);
g_assert (markers != NULL);
g_assert (n_markers == 3);
g_strfreev (markers);
timeline_2 = clutter_timeline_clone (timeline_1); timeline_2 = clutter_timeline_clone (timeline_1);
clutter_timeline_add_marker_at_frame (timeline_2, "bar", 2);
markers = clutter_timeline_list_markers (timeline_2, -1, &n_markers);
g_assert (markers != NULL);
g_assert (n_markers == 1);
g_assert (strcmp (markers[0], "bar") == 0);
g_strfreev (markers);
timeline_3 = clutter_timeline_clone (timeline_1); timeline_3 = clutter_timeline_clone (timeline_1);
clutter_timeline_set_direction (timeline_3, CLUTTER_TIMELINE_BACKWARD); clutter_timeline_set_direction (timeline_3, CLUTTER_TIMELINE_BACKWARD);
clutter_timeline_add_marker_at_frame (timeline_3, "baz", 8);
g_signal_connect (timeline_1,
"marker-reached", G_CALLBACK (timeline_1_marker_reached),
NULL);
g_signal_connect (timeline_1, g_signal_connect (timeline_1,
"new-frame", G_CALLBACK (timeline_1_new_frame_cb), "new-frame", G_CALLBACK (timeline_1_new_frame_cb),
NULL); NULL);
@ -61,6 +104,9 @@ main (int argc, char **argv)
"completed", G_CALLBACK (timeline_1_complete), "completed", G_CALLBACK (timeline_1_complete),
NULL); NULL);
g_signal_connect (timeline_2,
"marker-reached::bar", G_CALLBACK (timeline_1_marker_reached),
NULL);
g_signal_connect (timeline_2, g_signal_connect (timeline_2,
"new-frame", G_CALLBACK (timeline_2_new_frame_cb), "new-frame", G_CALLBACK (timeline_2_new_frame_cb),
NULL); NULL);
@ -68,6 +114,9 @@ main (int argc, char **argv)
"completed", G_CALLBACK (timeline_2_complete), "completed", G_CALLBACK (timeline_2_complete),
NULL); NULL);
g_signal_connect (timeline_3,
"marker-reached", G_CALLBACK (timeline_1_marker_reached),
NULL);
g_signal_connect (timeline_3, g_signal_connect (timeline_3,
"new-frame", G_CALLBACK (timeline_3_new_frame_cb), "new-frame", G_CALLBACK (timeline_3_new_frame_cb),
NULL); NULL);