Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-timeline.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By Matthew Allum  <mallum@openedhand.com>
7  *
8  * Copyright (C) 2006 OpenedHand
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22  *
23  *
24  */
25
26 /**
27  * SECTION:clutter-timeline
28  * @short_description: A class for time-based events
29  * @see_also: #ClutterAnimation, #ClutterAnimator, #ClutterState
30  *
31  * #ClutterTimeline is a base class for managing time-based event that cause
32  * Clutter to redraw a stage, such as animations.
33  *
34  * Each #ClutterTimeline instance has a duration: once a timeline has been
35  * started, using clutter_timeline_start(), it will emit a signal that can
36  * be used to update the state of the actors.
37  *
38  * It is important to note that #ClutterTimeline is not a generic API for
39  * calling closures after an interval; each Timeline is tied into the master
40  * clock used to drive the frame cycle. If you need to schedule a closure
41  * after an interval, see clutter_threads_add_timeout() instead.
42  *
43  * Users of #ClutterTimeline should connect to the #ClutterTimeline::new-frame
44  * signal, which is emitted each time a timeline is advanced during the maste
45  * clock iteration. The #ClutterTimeline::new-frame signal provides the time
46  * elapsed since the beginning of the timeline, in milliseconds. A normalized
47  * progress value can be obtained by calling clutter_timeline_get_progress().
48  * By using clutter_timeline_get_delta() it is possible to obtain the wallclock
49  * time elapsed since the last emission of the #ClutterTimeline::new-frame
50  * signal.
51  *
52  * Initial state can be set up by using the #ClutterTimeline::started signal,
53  * while final state can be set up by using the #ClutterTimeline::completed
54  * signal. The #ClutterTimeline guarantees the emission of at least a single
55  * #ClutterTimeline::new-frame signal, as well as the emission of the
56  * #ClutterTimeline::completed signal.
57  *
58  * It is possible to connect to specific points in the timeline progress by
59  * adding <emphasis>markers</emphasis> using clutter_timeline_add_marker_at_time()
60  * and connecting to the #ClutterTimeline::marker-reached signal.
61  *
62  * Timelines can be made to loop once they reach the end of their duration, by
63  * using clutter_timeline_set_repeat_count(); a looping timeline will still
64  * emit the #ClutterTimeline::completed signal once it reaches the end of its
65  * duration.
66  *
67  * Timelines have a #ClutterTimeline:direction: the default direction is
68  * %CLUTTER_TIMELINE_FORWARD, and goes from 0 to the duration; it is possible
69  * to change the direction to %CLUTTER_TIMELINE_BACKWARD, and have the timeline
70  * go from the duration to 0. The direction can be automatically reversed
71  * when reaching completion by using the #ClutterTimeline:auto-reverse property.
72  *
73  * Timelines are used in the Clutter animation framework by classes like
74  * #ClutterAnimation, #ClutterAnimator, and #ClutterState.
75  *
76  * <refsect2 id="timeline-script">
77  *  <title>Defining Timelines in ClutterScript</title>
78  *  <para>A #ClutterTimeline can be described in #ClutterScript like any
79  *  other object. Additionally, it is possible to define markers directly
80  *  inside the JSON definition by using the <emphasis>markers</emphasis>
81  *  JSON object member, such as:</para>
82  *  <informalexample><programlisting><![CDATA[
83 {
84   "type" : "ClutterTimeline",
85   "duration" : 1000,
86   "markers" : [
87     { "name" : "quarter", "time" : 250 },
88     { "name" : "half-time", "time" : 500 },
89     { "name" : "three-quarters", "time" : 750 }
90   ]
91 }
92  *  ]]></programlisting></informalexample>
93  * </refsect2>
94  */
95
96 #ifdef HAVE_CONFIG_H
97 #include "config.h"
98 #endif
99
100 #include "clutter-timeline.h"
101
102 #include "clutter-debug.h"
103 #include "clutter-easing.h"
104 #include "clutter-enum-types.h"
105 #include "clutter-main.h"
106 #include "clutter-marshal.h"
107 #include "clutter-master-clock.h"
108 #include "clutter-private.h"
109 #include "clutter-scriptable.h"
110
111 #include "deprecated/clutter-timeline.h"
112
113 static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
114
115 G_DEFINE_TYPE_WITH_CODE (ClutterTimeline, clutter_timeline, G_TYPE_OBJECT,
116                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
117                                                 clutter_scriptable_iface_init));
118
119 struct _ClutterTimelinePrivate
120 {
121   ClutterTimelineDirection direction;
122
123   guint delay_id;
124
125   /* The total length in milliseconds of this timeline */
126   guint duration;
127   guint delay;
128
129   /* The current amount of elapsed time */
130   gint64 elapsed_time;
131
132   /* The elapsed time since the last frame was fired */
133   gint64 msecs_delta;
134
135   GHashTable *markers_by_name;
136
137   /* Time we last advanced the elapsed time and showed a frame */
138   gint64 last_frame_time;
139
140   /* How many times the timeline should repeat */
141   gint repeat_count;
142
143   /* The number of times the timeline has repeated */
144   gint current_repeat;
145
146   ClutterTimelineProgressFunc progress_func;
147   gpointer progress_data;
148   GDestroyNotify progress_notify;
149   ClutterAnimationMode progress_mode;
150
151   guint is_playing         : 1;
152
153   /* If we've just started playing and haven't yet gotten
154    * a tick from the master clock
155    */
156   guint waiting_first_tick : 1;
157   guint auto_reverse       : 1;
158 };
159
160 typedef struct {
161   gchar *name;
162   guint msecs;
163   GQuark quark;
164 } TimelineMarker;
165
166 enum
167 {
168   PROP_0,
169
170   PROP_LOOP,
171   PROP_DELAY,
172   PROP_DURATION,
173   PROP_DIRECTION,
174   PROP_AUTO_REVERSE,
175   PROP_REPEAT_COUNT,
176   PROP_PROGRESS_MODE,
177
178   PROP_LAST
179 };
180
181 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
182
183 enum
184 {
185   NEW_FRAME,
186   STARTED,
187   PAUSED,
188   COMPLETED,
189   MARKER_REACHED,
190   STOPPED,
191
192   LAST_SIGNAL
193 };
194
195 static guint timeline_signals[LAST_SIGNAL] = { 0, };
196
197 static TimelineMarker *
198 timeline_marker_new (const gchar *name,
199                      guint        msecs)
200 {
201   TimelineMarker *marker = g_slice_new0 (TimelineMarker);
202
203   marker->name = g_strdup (name);
204   marker->quark = g_quark_from_string (marker->name);
205   marker->msecs = msecs;
206
207   return marker;
208 }
209
210 static void
211 timeline_marker_free (gpointer data)
212 {
213   if (G_LIKELY (data))
214     {
215       TimelineMarker *marker = data;
216
217       g_free (marker->name);
218       g_slice_free (TimelineMarker, marker);
219     }
220 }
221
222 /*< private >
223  * clutter_timeline_add_marker_internal:
224  * @timeline: a #ClutterTimeline
225  * @marker: a TimelineMarker
226  *
227  * Adds @marker into the hash table of markers for @timeline.
228  *
229  * The TimelineMarker will either be added or, in case of collisions
230  * with another existing marker, freed. In any case, this function
231  * assumes the ownership of the passed @marker.
232  */
233 static inline void
234 clutter_timeline_add_marker_internal (ClutterTimeline *timeline,
235                                       TimelineMarker  *marker)
236 {
237   ClutterTimelinePrivate *priv = timeline->priv;
238   TimelineMarker *old_marker;
239
240   /* create the hash table that will hold the markers */
241   if (G_UNLIKELY (priv->markers_by_name == NULL))
242     priv->markers_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
243                                                    NULL,
244                                                    timeline_marker_free);
245
246   old_marker = g_hash_table_lookup (priv->markers_by_name, marker->name);
247   if (old_marker != NULL)
248     {
249       g_warning ("A marker named '%s' already exists at time %d",
250                  old_marker->name,
251                  old_marker->msecs);
252       timeline_marker_free (marker);
253       return;
254     }
255
256   g_hash_table_insert (priv->markers_by_name, marker->name, marker);
257 }
258
259 static inline void
260 clutter_timeline_set_loop_internal (ClutterTimeline *timeline,
261                                     gboolean         loop)
262 {
263   gint old_repeat_count;
264
265   old_repeat_count = timeline->priv->repeat_count;
266
267   if (loop)
268     clutter_timeline_set_repeat_count (timeline, -1);
269   else
270     clutter_timeline_set_repeat_count (timeline, 0);
271
272   if (old_repeat_count != timeline->priv->repeat_count)
273     g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_LOOP]);
274 }
275
276 /* Scriptable */
277 typedef struct _ParseClosure {
278   ClutterTimeline *timeline;
279   ClutterScript *script;
280   GValue *value;
281   gboolean result;
282 } ParseClosure;
283
284 static void
285 parse_timeline_markers (JsonArray *array,
286                         guint      index_,
287                         JsonNode  *element,
288                         gpointer   data)
289 {
290   ParseClosure *clos = data;
291   JsonObject *object;
292   TimelineMarker *marker;
293   GList *markers;
294
295   if (JSON_NODE_TYPE (element) != JSON_NODE_OBJECT)
296     {
297       g_warning ("The 'markers' member of a ClutterTimeline description "
298                  "should be an array of objects, but the element %d of the "
299                  "array is of type '%s'. The element will be ignored.",
300                  index_,
301                  json_node_type_name (element));
302       return;
303     }
304
305   object = json_node_get_object (element);
306
307   if (!(json_object_has_member (object, "name") &&
308         json_object_has_member (object, "time")))
309     {
310       g_warning ("The marker definition in a ClutterTimeline description "
311                  "must be an object with the 'name' and 'time' members, "
312                  "but the element %d of the 'markers' array does not have "
313                  "either",
314                  index_);
315       return;
316     }
317
318   if (G_IS_VALUE (clos->value))
319     markers = g_value_get_pointer (clos->value);
320   else
321     {
322       g_value_init (clos->value, G_TYPE_POINTER);
323       markers = NULL;
324     }
325
326   marker = timeline_marker_new (json_object_get_string_member (object, "name"),
327                                 json_object_get_int_member (object, "time"));
328
329   markers = g_list_prepend (markers, marker);
330
331   g_value_set_pointer (clos->value, markers);
332
333   clos->result = TRUE;
334 }
335
336 static gboolean
337 clutter_timeline_parse_custom_node (ClutterScriptable *scriptable,
338                                     ClutterScript     *script,
339                                     GValue            *value,
340                                     const gchar       *name,
341                                     JsonNode          *node)
342 {
343   ParseClosure clos;
344
345   if (strcmp (name, "markers") != 0)
346     return FALSE;
347
348   if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
349     return FALSE;
350
351   clos.timeline = CLUTTER_TIMELINE (scriptable);
352   clos.script = script;
353   clos.value = value;
354   clos.result = FALSE;
355
356   json_array_foreach_element (json_node_get_array (node),
357                               parse_timeline_markers,
358                               &clos);
359
360   return clos.result;
361 }
362
363 static void
364 clutter_timeline_set_custom_property (ClutterScriptable *scriptable,
365                                       ClutterScript     *script,
366                                       const gchar       *name,
367                                       const GValue      *value)
368 {
369   if (strcmp (name, "markers") == 0)
370     {
371       ClutterTimeline *timeline = CLUTTER_TIMELINE (scriptable);
372       GList *markers = g_value_get_pointer (value);
373       GList *m;
374
375       /* the list was created through prepend() */
376       markers = g_list_reverse (markers);
377
378       for (m = markers; m != NULL; m = m->next)
379         clutter_timeline_add_marker_internal (timeline, m->data);
380
381       g_list_free (markers);
382     }
383   else
384     g_object_set_property (G_OBJECT (scriptable), name, value);
385 }
386
387
388 static void
389 clutter_scriptable_iface_init (ClutterScriptableIface *iface)
390 {
391   iface->parse_custom_node = clutter_timeline_parse_custom_node;
392   iface->set_custom_property = clutter_timeline_set_custom_property;
393 }
394
395 /* Object */
396
397 static void
398 clutter_timeline_set_property (GObject      *object,
399                                guint         prop_id,
400                                const GValue *value,
401                                GParamSpec   *pspec)
402 {
403   ClutterTimeline *timeline = CLUTTER_TIMELINE (object);
404
405   switch (prop_id)
406     {
407     case PROP_LOOP:
408       clutter_timeline_set_loop_internal (timeline, g_value_get_boolean (value));
409       break;
410
411     case PROP_DELAY:
412       clutter_timeline_set_delay (timeline, g_value_get_uint (value));
413       break;
414
415     case PROP_DURATION:
416       clutter_timeline_set_duration (timeline, g_value_get_uint (value));
417       break;
418
419     case PROP_DIRECTION:
420       clutter_timeline_set_direction (timeline, g_value_get_enum (value));
421       break;
422
423     case PROP_AUTO_REVERSE:
424       clutter_timeline_set_auto_reverse (timeline, g_value_get_boolean (value));
425       break;
426
427     case PROP_REPEAT_COUNT:
428       clutter_timeline_set_repeat_count (timeline, g_value_get_int (value));
429       break;
430
431     case PROP_PROGRESS_MODE:
432       clutter_timeline_set_progress_mode (timeline, g_value_get_enum (value));
433       break;
434
435     default:
436       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
437       break;
438     }
439 }
440
441 static void
442 clutter_timeline_get_property (GObject    *object,
443                                guint       prop_id,
444                                GValue     *value,
445                                GParamSpec *pspec)
446 {
447   ClutterTimeline *timeline = CLUTTER_TIMELINE (object);
448   ClutterTimelinePrivate *priv = timeline->priv;
449
450   switch (prop_id)
451     {
452     case PROP_LOOP:
453       g_value_set_boolean (value, priv->repeat_count != 0);
454       break;
455
456     case PROP_DELAY:
457       g_value_set_uint (value, priv->delay);
458       break;
459
460     case PROP_DURATION:
461       g_value_set_uint (value, clutter_timeline_get_duration (timeline));
462       break;
463
464     case PROP_DIRECTION:
465       g_value_set_enum (value, priv->direction);
466       break;
467
468     case PROP_AUTO_REVERSE:
469       g_value_set_boolean (value, priv->auto_reverse);
470       break;
471
472     case PROP_REPEAT_COUNT:
473       g_value_set_int (value, priv->repeat_count);
474       break;
475
476     case PROP_PROGRESS_MODE:
477       g_value_set_enum (value, priv->progress_mode);
478       break;
479
480     default:
481       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482       break;
483     }
484 }
485
486 static void
487 clutter_timeline_finalize (GObject *object)
488 {
489   ClutterTimeline *self = CLUTTER_TIMELINE (object);
490   ClutterTimelinePrivate *priv = self->priv;
491   ClutterMasterClock *master_clock;
492
493   if (priv->markers_by_name)
494     g_hash_table_destroy (priv->markers_by_name);
495
496   if (priv->is_playing)
497     {
498       master_clock = _clutter_master_clock_get_default ();
499       _clutter_master_clock_remove_timeline (master_clock, self);
500     }
501
502   G_OBJECT_CLASS (clutter_timeline_parent_class)->finalize (object);
503 }
504
505 static void
506 clutter_timeline_dispose (GObject *object)
507 {
508   ClutterTimeline *self = CLUTTER_TIMELINE(object);
509   ClutterTimelinePrivate *priv;
510
511   priv = self->priv;
512
513   if (priv->delay_id)
514     {
515       g_source_remove (priv->delay_id);
516       priv->delay_id = 0;
517     }
518
519   if (priv->progress_notify != NULL)
520     {
521       priv->progress_notify (priv->progress_data);
522       priv->progress_func = NULL;
523       priv->progress_data = NULL;
524       priv->progress_notify = NULL;
525     }
526
527   G_OBJECT_CLASS (clutter_timeline_parent_class)->dispose (object);
528 }
529
530 static void
531 clutter_timeline_class_init (ClutterTimelineClass *klass)
532 {
533   GObjectClass *object_class = G_OBJECT_CLASS (klass);
534
535   g_type_class_add_private (klass, sizeof (ClutterTimelinePrivate));
536
537   /**
538    * ClutterTimeline:loop:
539    *
540    * Whether the timeline should automatically rewind and restart.
541    *
542    * As a side effect, setting this property to %TRUE will set the
543    * #ClutterTimeline:repeat-count property to -1, while setting this
544    * property to %FALSE will set the #ClutterTimeline:repeat-count
545    * property to 0.
546    *
547    * Deprecated: 1.10: Use the #ClutterTimeline:repeat-count property instead.
548    */
549   obj_props[PROP_LOOP] =
550     g_param_spec_boolean ("loop",
551                           P_("Loop"),
552                           P_("Should the timeline automatically restart"),
553                           FALSE,
554                           CLUTTER_PARAM_READWRITE | G_PARAM_DEPRECATED);
555
556   /**
557    * ClutterTimeline:delay:
558    *
559    * A delay, in milliseconds, that should be observed by the
560    * timeline before actually starting.
561    *
562    * Since: 0.4
563    */
564   obj_props[PROP_DELAY] =
565     g_param_spec_uint ("delay",
566                        P_("Delay"),
567                        P_("Delay before start"),
568                        0, G_MAXUINT,
569                        0,
570                        CLUTTER_PARAM_READWRITE);
571
572   /**
573    * ClutterTimeline:duration:
574    *
575    * Duration of the timeline in milliseconds, depending on the
576    * ClutterTimeline:fps value.
577    *
578    * Since: 0.6
579    */
580   obj_props[PROP_DURATION] =
581     g_param_spec_uint ("duration",
582                        P_("Duration"),
583                        P_("Duration of the timeline in milliseconds"),
584                        0, G_MAXUINT,
585                        1000,
586                        CLUTTER_PARAM_READWRITE);
587
588   /**
589    * ClutterTimeline:direction:
590    *
591    * The direction of the timeline, either %CLUTTER_TIMELINE_FORWARD or
592    * %CLUTTER_TIMELINE_BACKWARD.
593    *
594    * Since: 0.6
595    */
596   obj_props[PROP_DIRECTION] =
597     g_param_spec_enum ("direction",
598                        P_("Direction"),
599                        P_("Direction of the timeline"),
600                        CLUTTER_TYPE_TIMELINE_DIRECTION,
601                        CLUTTER_TIMELINE_FORWARD,
602                        CLUTTER_PARAM_READWRITE);
603
604   /**
605    * ClutterTimeline:auto-reverse:
606    *
607    * If the direction of the timeline should be automatically reversed
608    * when reaching the end.
609    *
610    * Since: 1.6
611    */
612   obj_props[PROP_AUTO_REVERSE] =
613     g_param_spec_boolean ("auto-reverse",
614                           P_("Auto Reverse"),
615                           P_("Whether the direction should be reversed when reaching the end"),
616                           FALSE,
617                           CLUTTER_PARAM_READWRITE);
618
619   /**
620    * ClutterTimeline:repeat-count:
621    *
622    * Defines how many times the timeline should repeat.
623    *
624    * If the repeat count is 0, the timeline does not repeat.
625    *
626    * If the repeat count is set to -1, the timeline will repeat until it is
627    * stopped.
628    *
629    * Since: 1.10
630    */
631   obj_props[PROP_REPEAT_COUNT] =
632     g_param_spec_int ("repeat-count",
633                       P_("Repeat Count"),
634                       P_("How many times the timeline should repeat"),
635                       -1, G_MAXINT,
636                       0,
637                       CLUTTER_PARAM_READWRITE);
638
639   /**
640    * ClutterTimeline:progress-mode:
641    *
642    * Controls the way a #ClutterTimeline computes the normalized progress.
643    *
644    * Since: 1.10
645    */
646   obj_props[PROP_PROGRESS_MODE] =
647     g_param_spec_enum ("progress-mode",
648                        P_("Progress Mode"),
649                        P_("How the timeline should compute the progress"),
650                        CLUTTER_TYPE_ANIMATION_MODE,
651                        CLUTTER_LINEAR,
652                        CLUTTER_PARAM_READWRITE);
653
654   object_class->dispose = clutter_timeline_dispose;
655   object_class->finalize = clutter_timeline_finalize;
656   object_class->set_property = clutter_timeline_set_property;
657   object_class->get_property = clutter_timeline_get_property;
658   g_object_class_install_properties (object_class, PROP_LAST, obj_props);
659
660   /**
661    * ClutterTimeline::new-frame:
662    * @timeline: the timeline which received the signal
663    * @msecs: the elapsed time between 0 and duration
664    *
665    * The ::new-frame signal is emitted for each timeline running
666    * timeline before a new frame is drawn to give animations a chance
667    * to update the scene.
668    */
669   timeline_signals[NEW_FRAME] =
670     g_signal_new (I_("new-frame"),
671                   G_TYPE_FROM_CLASS (object_class),
672                   G_SIGNAL_RUN_LAST,
673                   G_STRUCT_OFFSET (ClutterTimelineClass, new_frame),
674                   NULL, NULL,
675                   _clutter_marshal_VOID__INT,
676                   G_TYPE_NONE,
677                   1, G_TYPE_INT);
678   /**
679    * ClutterTimeline::completed:
680    * @timeline: the #ClutterTimeline which received the signal
681    *
682    * The #ClutterTimeline::completed signal is emitted when the timeline's
683    * elapsed time reaches the value of the #ClutterTimeline:duration
684    * property.
685    *
686    * This signal will be emitted even if the #ClutterTimeline is set to be
687    * repeating.
688    *
689    * If you want to get notification on whether the #ClutterTimeline has
690    * been stopped or has finished its run, including its eventual repeats,
691    * you should use the #ClutterTimeline::stopped signal instead.
692    */
693   timeline_signals[COMPLETED] =
694     g_signal_new (I_("completed"),
695                   G_TYPE_FROM_CLASS (object_class),
696                   G_SIGNAL_RUN_LAST,
697                   G_STRUCT_OFFSET (ClutterTimelineClass, completed),
698                   NULL, NULL,
699                   _clutter_marshal_VOID__VOID,
700                   G_TYPE_NONE, 0);
701   /**
702    * ClutterTimeline::started:
703    * @timeline: the #ClutterTimeline which received the signal
704    *
705    * The ::started signal is emitted when the timeline starts its run.
706    * This might be as soon as clutter_timeline_start() is invoked or
707    * after the delay set in the ClutterTimeline:delay property has
708    * expired.
709    */
710   timeline_signals[STARTED] =
711     g_signal_new (I_("started"),
712                   G_TYPE_FROM_CLASS (object_class),
713                   G_SIGNAL_RUN_LAST,
714                   G_STRUCT_OFFSET (ClutterTimelineClass, started),
715                   NULL, NULL,
716                   _clutter_marshal_VOID__VOID,
717                   G_TYPE_NONE, 0);
718   /**
719    * ClutterTimeline::paused:
720    * @timeline: the #ClutterTimeline which received the signal
721    *
722    * The ::paused signal is emitted when clutter_timeline_pause() is invoked.
723    */
724   timeline_signals[PAUSED] =
725     g_signal_new (I_("paused"),
726                   G_TYPE_FROM_CLASS (object_class),
727                   G_SIGNAL_RUN_LAST,
728                   G_STRUCT_OFFSET (ClutterTimelineClass, paused),
729                   NULL, NULL,
730                   _clutter_marshal_VOID__VOID,
731                   G_TYPE_NONE, 0);
732   /**
733    * ClutterTimeline::marker-reached:
734    * @timeline: the #ClutterTimeline which received the signal
735    * @marker_name: the name of the marker reached
736    * @msecs: the elapsed time
737    *
738    * The ::marker-reached signal is emitted each time a timeline
739    * reaches a marker set with
740    * clutter_timeline_add_marker_at_time(). This signal is detailed
741    * with the name of the marker as well, so it is possible to connect
742    * a callback to the ::marker-reached signal for a specific marker
743    * with:
744    *
745    * <informalexample><programlisting>
746    *   clutter_timeline_add_marker_at_time (timeline, "foo", 500);
747    *   clutter_timeline_add_marker_at_time (timeline, "bar", 750);
748    *
749    *   g_signal_connect (timeline, "marker-reached",
750    *                     G_CALLBACK (each_marker_reached), NULL);
751    *   g_signal_connect (timeline, "marker-reached::foo",
752    *                     G_CALLBACK (foo_marker_reached), NULL);
753    *   g_signal_connect (timeline, "marker-reached::bar",
754    *                     G_CALLBACK (bar_marker_reached), NULL);
755    * </programlisting></informalexample>
756    *
757    * In the example, the first callback will be invoked for both
758    * the "foo" and "bar" marker, while the second and third callbacks
759    * will be invoked for the "foo" or "bar" markers, respectively.
760    *
761    * Since: 0.8
762    */
763   timeline_signals[MARKER_REACHED] =
764     g_signal_new (I_("marker-reached"),
765                   G_TYPE_FROM_CLASS (object_class),
766                   G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE |
767                   G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
768                   G_STRUCT_OFFSET (ClutterTimelineClass, marker_reached),
769                   NULL, NULL,
770                   _clutter_marshal_VOID__STRING_INT,
771                   G_TYPE_NONE, 2,
772                   G_TYPE_STRING,
773                   G_TYPE_INT);
774   /**
775    * ClutterTimeline::stopped:
776    * @timeline: the #ClutterTimeline that emitted the signal
777    * @is_finished: %TRUE if the signal was emitted at the end of the
778    *   timeline.
779    *
780    * The #ClutterTimeline::stopped signal is emitted when the timeline
781    * has been stopped, either because clutter_timeline_stop() has been
782    * called, or because it has been exhausted.
783    *
784    * This is different from the #ClutterTimeline::completed signal,
785    * which gets emitted after every repeat finishes.
786    *
787    * If the #ClutterTimeline has is marked as infinitely repeating,
788    * this signal will never be emitted.
789    *
790    * Since: 1.12
791    */
792   timeline_signals[STOPPED] =
793     g_signal_new (I_("stopped"),
794                   G_TYPE_FROM_CLASS (object_class),
795                   G_SIGNAL_RUN_LAST,
796                   G_STRUCT_OFFSET (ClutterTimelineClass, completed),
797                   NULL, NULL,
798                   _clutter_marshal_VOID__BOOLEAN,
799                   G_TYPE_NONE, 1,
800                   G_TYPE_BOOLEAN);
801 }
802
803 static void
804 clutter_timeline_init (ClutterTimeline *self)
805 {
806   ClutterTimelinePrivate *priv;
807
808   self->priv = priv =
809     G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_TIMELINE,
810                                  ClutterTimelinePrivate);
811
812   priv->progress_mode = CLUTTER_LINEAR;
813 }
814
815 struct CheckIfMarkerHitClosure
816 {
817   ClutterTimeline *timeline;
818   ClutterTimelineDirection direction;
819   gint new_time;
820   gint duration;
821   gint delta;
822 };
823
824 static gboolean
825 have_passed_time (const struct CheckIfMarkerHitClosure *data,
826                   gint msecs)
827 {
828   /* Ignore markers that are outside the duration of the timeline */
829   if (msecs < 0 || msecs > data->duration)
830     return FALSE;
831
832   if (data->direction == CLUTTER_TIMELINE_FORWARD)
833     {
834       /* We need to special case when a marker is added at the
835          beginning of the timeline */
836       if (msecs == 0 &&
837           data->delta > 0 &&
838           data->new_time - data->delta <= 0)
839         return TRUE;
840
841       /* Otherwise it's just a simple test if the time is in range of
842          the previous time and the new time */
843       return (msecs > data->new_time - data->delta
844               && msecs <= data->new_time);
845     }
846   else
847     {
848       /* We need to special case when a marker is added at the
849          end of the timeline */
850       if (msecs == data->duration &&
851           data->delta > 0 &&
852           data->new_time + data->delta >= data->duration)
853         return TRUE;
854
855       /* Otherwise it's just a simple test if the time is in range of
856          the previous time and the new time */
857       return (msecs >= data->new_time
858               && msecs < data->new_time + data->delta);
859     }
860 }
861
862 static void
863 check_if_marker_hit (const gchar *name,
864                      TimelineMarker *marker,
865                      struct CheckIfMarkerHitClosure *data)
866 {
867   if (have_passed_time (data, marker->msecs))
868     {
869       CLUTTER_NOTE (SCHEDULER, "Marker '%s' reached", name);
870
871       g_signal_emit (data->timeline, timeline_signals[MARKER_REACHED],
872                      marker->quark,
873                      name,
874                      marker->msecs);
875     }
876 }
877
878 static void
879 check_markers (ClutterTimeline *timeline,
880                gint delta)
881 {
882   ClutterTimelinePrivate *priv = timeline->priv;
883   struct CheckIfMarkerHitClosure data;
884
885   /* shortcircuit here if we don't have any marker installed */
886   if (priv->markers_by_name == NULL)
887     return;
888
889   /* store the details of the timeline so that changing them in a
890      marker signal handler won't affect which markers are hit */
891   data.timeline = timeline;
892   data.direction = priv->direction;
893   data.new_time = priv->elapsed_time;
894   data.duration = priv->duration;
895   data.delta = delta;
896
897   g_hash_table_foreach (priv->markers_by_name,
898                         (GHFunc) check_if_marker_hit,
899                         &data);
900 }
901
902 static void
903 emit_frame_signal (ClutterTimeline *timeline)
904 {
905   ClutterTimelinePrivate *priv = timeline->priv;
906
907   /* see bug https://bugzilla.gnome.org/show_bug.cgi?id=654066 */
908   gint elapsed = (gint) priv->elapsed_time;
909
910   CLUTTER_NOTE (SCHEDULER, "Emitting ::new-frame signal on timeline[%p]", timeline);
911
912   g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0, elapsed);
913 }
914
915 static gboolean
916 is_complete (ClutterTimeline *timeline)
917 {
918   ClutterTimelinePrivate *priv = timeline->priv;
919
920   return (priv->direction == CLUTTER_TIMELINE_FORWARD
921           ? priv->elapsed_time >= priv->duration
922           : priv->elapsed_time <= 0);
923 }
924
925 static void
926 set_is_playing (ClutterTimeline *timeline,
927                 gboolean         is_playing)
928 {
929   ClutterTimelinePrivate *priv = timeline->priv;
930   ClutterMasterClock *master_clock;
931
932   is_playing = !!is_playing;
933
934   if (is_playing == priv->is_playing)
935     return;
936
937   priv->is_playing = is_playing;
938
939   master_clock = _clutter_master_clock_get_default ();
940   if (priv->is_playing)
941     {
942       _clutter_master_clock_add_timeline (master_clock, timeline);
943       priv->waiting_first_tick = TRUE;
944       priv->current_repeat = 0;
945     }
946   else
947     _clutter_master_clock_remove_timeline (master_clock, timeline);
948 }
949
950 static gboolean
951 clutter_timeline_do_frame (ClutterTimeline *timeline)
952 {
953   ClutterTimelinePrivate *priv;
954
955   priv = timeline->priv;
956
957   g_object_ref (timeline);
958
959   CLUTTER_NOTE (SCHEDULER, "Timeline [%p] activated (elapsed time: %ld)\n",
960                 timeline,
961                 (long) priv->elapsed_time);
962
963   /* Advance time */
964   if (priv->direction == CLUTTER_TIMELINE_FORWARD)
965     priv->elapsed_time += priv->msecs_delta;
966   else
967     priv->elapsed_time -= priv->msecs_delta;
968
969   /* If we have not reached the end of the timeline: */
970   if (!is_complete (timeline))
971     {
972       /* Emit the signal */
973       emit_frame_signal (timeline);
974       check_markers (timeline, priv->msecs_delta);
975
976       g_object_unref (timeline);
977
978       return priv->is_playing;
979     }
980   else
981     {
982       /* Handle loop or stop */
983       ClutterTimelineDirection saved_direction = priv->direction;
984       gint elapsed_time_delta = priv->msecs_delta;
985       guint overflow_msecs = priv->elapsed_time;
986       gint end_msecs;
987
988       /* Update the current elapsed time in case the signal handlers
989        * want to take a peek. If we clamp elapsed time, then we need
990        * to correpondingly reduce elapsed_time_delta to reflect the correct
991        * range of times */
992       if (priv->direction == CLUTTER_TIMELINE_FORWARD)
993         {
994           elapsed_time_delta -= (priv->elapsed_time - priv->duration);
995           priv->elapsed_time = priv->duration;
996         }
997       else if (priv->direction == CLUTTER_TIMELINE_BACKWARD)
998         {
999           elapsed_time_delta -= - priv->elapsed_time;
1000           priv->elapsed_time = 0;
1001         }
1002
1003       end_msecs = priv->elapsed_time;
1004
1005       /* Emit the signal */
1006       emit_frame_signal (timeline);
1007       check_markers (timeline, elapsed_time_delta);
1008
1009       /* Did the signal handler modify the elapsed time? */
1010       if (priv->elapsed_time != end_msecs)
1011         {
1012           g_object_unref (timeline);
1013           return TRUE;
1014         }
1015
1016       /* Note: If the new-frame signal handler paused the timeline
1017        * on the last frame we will still go ahead and send the
1018        * completed signal */
1019       CLUTTER_NOTE (SCHEDULER,
1020                     "Timeline [%p] completed (cur: %ld, tot: %ld)",
1021                     timeline,
1022                     (long) priv->elapsed_time,
1023                     (long) priv->msecs_delta);
1024
1025       if (priv->is_playing &&
1026           (priv->repeat_count == 0 ||
1027            priv->repeat_count == priv->current_repeat))
1028         {
1029           /* We stop the timeline now, so that the completed signal handler
1030            * may choose to re-start the timeline
1031            *
1032            * XXX Perhaps we should do this earlier, and regardless of
1033            * priv->repeat_count. Are we limiting the things that could be
1034            * done in the above new-frame signal handler?
1035            */
1036           set_is_playing (timeline, FALSE);
1037           g_signal_emit (timeline, timeline_signals[STOPPED], 0, TRUE);
1038         }
1039
1040       g_signal_emit (timeline, timeline_signals[COMPLETED], 0);
1041
1042       priv->current_repeat += 1;
1043
1044       if (priv->auto_reverse)
1045         {
1046           /* :auto-reverse changes the direction of the timeline */
1047           if (priv->direction == CLUTTER_TIMELINE_FORWARD)
1048             priv->direction = CLUTTER_TIMELINE_BACKWARD;
1049           else
1050             priv->direction = CLUTTER_TIMELINE_FORWARD;
1051
1052           g_object_notify_by_pspec (G_OBJECT (timeline),
1053                                     obj_props[PROP_DIRECTION]);
1054         }
1055
1056       /* Again check to see if the user has manually played with
1057        * the elapsed time, before we finally stop or loop the timeline */
1058
1059       if (priv->elapsed_time != end_msecs &&
1060           !(/* Except allow changing time from 0 -> duration (or vice-versa)
1061                since these are considered equivalent */
1062             (priv->elapsed_time == 0 && end_msecs == priv->duration) ||
1063             (priv->elapsed_time == priv->duration && end_msecs == 0)
1064           ))
1065         {
1066           g_object_unref (timeline);
1067           return TRUE;
1068         }
1069
1070       if (priv->repeat_count != 0)
1071         {
1072           /* We try and interpolate smoothly around a loop */
1073           if (saved_direction == CLUTTER_TIMELINE_FORWARD)
1074             priv->elapsed_time = overflow_msecs - priv->duration;
1075           else
1076             priv->elapsed_time = priv->duration + overflow_msecs;
1077
1078           /* Or if the direction changed, we try and bounce */
1079           if (priv->direction != saved_direction)
1080             priv->elapsed_time = priv->duration - priv->elapsed_time;
1081
1082           /* If we have overflowed then we are changing the elapsed
1083              time without emitting the new frame signal so we need to
1084              check for markers again */
1085           check_markers (timeline,
1086                          priv->direction == CLUTTER_TIMELINE_FORWARD
1087                            ? priv->elapsed_time
1088                            : priv->duration - priv->elapsed_time);
1089
1090           g_object_unref (timeline);
1091           return TRUE;
1092         }
1093       else
1094         {
1095           clutter_timeline_rewind (timeline);
1096
1097           g_object_unref (timeline);
1098           return FALSE;
1099         }
1100     }
1101 }
1102
1103 static gboolean
1104 delay_timeout_func (gpointer data)
1105 {
1106   ClutterTimeline *timeline = data;
1107   ClutterTimelinePrivate *priv = timeline->priv;
1108
1109   priv->delay_id = 0;
1110   priv->msecs_delta = 0;
1111   set_is_playing (timeline, TRUE);
1112
1113   g_signal_emit (timeline, timeline_signals[STARTED], 0);
1114
1115   return FALSE;
1116 }
1117
1118 /**
1119  * clutter_timeline_start:
1120  * @timeline: A #ClutterTimeline
1121  *
1122  * Starts the #ClutterTimeline playing.
1123  **/
1124 void
1125 clutter_timeline_start (ClutterTimeline *timeline)
1126 {
1127   ClutterTimelinePrivate *priv;
1128
1129   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1130
1131   priv = timeline->priv;
1132
1133   if (priv->delay_id || priv->is_playing)
1134     return;
1135
1136   if (priv->duration == 0)
1137     return;
1138
1139   if (priv->delay)
1140     priv->delay_id = clutter_threads_add_timeout (priv->delay,
1141                                                   delay_timeout_func,
1142                                                   timeline);
1143   else
1144     {
1145       priv->msecs_delta = 0;
1146       set_is_playing (timeline, TRUE);
1147
1148       g_signal_emit (timeline, timeline_signals[STARTED], 0);
1149     }
1150 }
1151
1152 /**
1153  * clutter_timeline_pause:
1154  * @timeline: A #ClutterTimeline
1155  *
1156  * Pauses the #ClutterTimeline on current frame
1157  **/
1158 void
1159 clutter_timeline_pause (ClutterTimeline *timeline)
1160 {
1161   ClutterTimelinePrivate *priv;
1162
1163   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1164
1165   priv = timeline->priv;
1166
1167   if (priv->delay_id == 0 && !priv->is_playing)
1168     return;
1169
1170   if (priv->delay_id)
1171     {
1172       g_source_remove (priv->delay_id);
1173       priv->delay_id = 0;
1174     }
1175
1176   priv->msecs_delta = 0;
1177   set_is_playing (timeline, FALSE);
1178
1179   g_signal_emit (timeline, timeline_signals[PAUSED], 0);
1180 }
1181
1182 /**
1183  * clutter_timeline_stop:
1184  * @timeline: A #ClutterTimeline
1185  *
1186  * Stops the #ClutterTimeline and moves to frame 0
1187  **/
1188 void
1189 clutter_timeline_stop (ClutterTimeline *timeline)
1190 {
1191   gboolean was_playing;
1192
1193   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1194
1195   /* we check the is_playing here because pause() will return immediately
1196    * if the timeline wasn't playing, so we don't know if it was actually
1197    * stopped, and yet we still don't want to emit a ::stopped signal if
1198    * the timeline was not playing in the first place.
1199    */
1200   was_playing = timeline->priv->is_playing;
1201
1202   clutter_timeline_pause (timeline);
1203   clutter_timeline_rewind (timeline);
1204
1205   if (was_playing)
1206     g_signal_emit (timeline, timeline_signals[STOPPED], 0, FALSE);
1207 }
1208
1209 /**
1210  * clutter_timeline_set_loop:
1211  * @timeline: a #ClutterTimeline
1212  * @loop: %TRUE for enable looping
1213  *
1214  * Sets whether @timeline should loop.
1215  *
1216  * This function is equivalent to calling clutter_timeline_set_repeat_count()
1217  * with -1 if @loop is %TRUE, and with 0 if @loop is %FALSE.
1218  *
1219  * Deprecated: 1.10: Use clutter_timeline_set_repeat_count() instead.
1220  */
1221 void
1222 clutter_timeline_set_loop (ClutterTimeline *timeline,
1223                            gboolean         loop)
1224 {
1225   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1226
1227   clutter_timeline_set_loop_internal (timeline, loop);
1228 }
1229
1230 /**
1231  * clutter_timeline_get_loop:
1232  * @timeline: a #ClutterTimeline
1233  *
1234  * Gets whether @timeline is looping
1235  *
1236  * Return value: %TRUE if the timeline is looping
1237  *
1238  * Deprecated: 1.10: Use clutter_timeline_get_repeat_count() instead.
1239  */
1240 gboolean
1241 clutter_timeline_get_loop (ClutterTimeline *timeline)
1242 {
1243   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), FALSE);
1244
1245   return timeline->priv->repeat_count != 0;
1246 }
1247
1248 /**
1249  * clutter_timeline_rewind:
1250  * @timeline: A #ClutterTimeline
1251  *
1252  * Rewinds #ClutterTimeline to the first frame if its direction is
1253  * %CLUTTER_TIMELINE_FORWARD and the last frame if it is
1254  * %CLUTTER_TIMELINE_BACKWARD.
1255  */
1256 void
1257 clutter_timeline_rewind (ClutterTimeline *timeline)
1258 {
1259   ClutterTimelinePrivate *priv;
1260
1261   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1262
1263   priv = timeline->priv;
1264
1265   if (priv->direction == CLUTTER_TIMELINE_FORWARD)
1266     clutter_timeline_advance (timeline, 0);
1267   else if (priv->direction == CLUTTER_TIMELINE_BACKWARD)
1268     clutter_timeline_advance (timeline, priv->duration);
1269 }
1270
1271 /**
1272  * clutter_timeline_skip:
1273  * @timeline: A #ClutterTimeline
1274  * @msecs: Amount of time to skip
1275  *
1276  * Advance timeline by the requested time in milliseconds
1277  */
1278 void
1279 clutter_timeline_skip (ClutterTimeline *timeline,
1280                        guint            msecs)
1281 {
1282   ClutterTimelinePrivate *priv;
1283
1284   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1285
1286   priv = timeline->priv;
1287
1288   if (priv->direction == CLUTTER_TIMELINE_FORWARD)
1289     {
1290       priv->elapsed_time += msecs;
1291
1292       if (priv->elapsed_time > priv->duration)
1293         priv->elapsed_time = 1;
1294     }
1295   else if (priv->direction == CLUTTER_TIMELINE_BACKWARD)
1296     {
1297       priv->elapsed_time -= msecs;
1298
1299       if (priv->elapsed_time < 1)
1300         priv->elapsed_time = priv->duration - 1;
1301     }
1302
1303   priv->msecs_delta = 0;
1304 }
1305
1306 /**
1307  * clutter_timeline_advance:
1308  * @timeline: A #ClutterTimeline
1309  * @msecs: Time to advance to
1310  *
1311  * Advance timeline to the requested point. The point is given as a
1312  * time in milliseconds since the timeline started.
1313  *
1314  * <note><para>The @timeline will not emit the #ClutterTimeline::new-frame
1315  * signal for the given time. The first ::new-frame signal after the call to
1316  * clutter_timeline_advance() will be emit the skipped markers.
1317  * </para></note>
1318  */
1319 void
1320 clutter_timeline_advance (ClutterTimeline *timeline,
1321                           guint            msecs)
1322 {
1323   ClutterTimelinePrivate *priv;
1324
1325   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1326
1327   priv = timeline->priv;
1328
1329   priv->elapsed_time = CLAMP (msecs, 0, priv->duration);
1330 }
1331
1332 /**
1333  * clutter_timeline_get_elapsed_time:
1334  * @timeline: A #ClutterTimeline
1335  *
1336  * Request the current time position of the timeline.
1337  *
1338  * Return value: current elapsed time in milliseconds.
1339  */
1340 guint
1341 clutter_timeline_get_elapsed_time (ClutterTimeline *timeline)
1342 {
1343   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
1344
1345   return timeline->priv->elapsed_time;
1346 }
1347
1348 /**
1349  * clutter_timeline_is_playing:
1350  * @timeline: A #ClutterTimeline
1351  *
1352  * Queries state of a #ClutterTimeline.
1353  *
1354  * Return value: %TRUE if timeline is currently playing
1355  */
1356 gboolean
1357 clutter_timeline_is_playing (ClutterTimeline *timeline)
1358 {
1359   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), FALSE);
1360
1361   return timeline->priv->is_playing;
1362 }
1363
1364 /**
1365  * clutter_timeline_clone:
1366  * @timeline: #ClutterTimeline to duplicate.
1367  *
1368  * Create a new #ClutterTimeline instance which has property values
1369  * matching that of supplied timeline. The cloned timeline will not
1370  * be started and will not be positioned to the current position of
1371  * the original @timeline: you will have to start it with clutter_timeline_start().
1372  *
1373  * <note><para>The only cloned properties are:</para>
1374  * <itemizedlist>
1375  *   <listitem><simpara>#ClutterTimeline:duration</simpara></listitem>
1376  *   <listitem><simpara>#ClutterTimeline:loop</simpara></listitem>
1377  *   <listitem><simpara>#ClutterTimeline:delay</simpara></listitem>
1378  *   <listitem><simpara>#ClutterTimeline:direction</simpara></listitem>
1379  * </itemizedlist></note>
1380  *
1381  * Return value: (transfer full): a new #ClutterTimeline, cloned
1382  *   from @timeline
1383  *
1384  * Since: 0.4
1385  *
1386  * Deprecated: 1.10: Use clutter_timeline_new() or g_object_new()
1387  *   instead
1388  */
1389 ClutterTimeline *
1390 clutter_timeline_clone (ClutterTimeline *timeline)
1391 {
1392   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
1393
1394   return g_object_new (CLUTTER_TYPE_TIMELINE,
1395                        "duration", timeline->priv->duration,
1396                        "loop", timeline->priv->repeat_count != 0,
1397                        "delay", timeline->priv->delay,
1398                        "direction", timeline->priv->direction,
1399                        NULL);
1400 }
1401
1402 /**
1403  * clutter_timeline_new:
1404  * @msecs: Duration of the timeline in milliseconds
1405  *
1406  * Creates a new #ClutterTimeline with a duration of @msecs.
1407  *
1408  * Return value: the newly created #ClutterTimeline instance. Use
1409  *   g_object_unref() when done using it
1410  *
1411  * Since: 0.6
1412  */
1413 ClutterTimeline *
1414 clutter_timeline_new (guint msecs)
1415 {
1416   return g_object_new (CLUTTER_TYPE_TIMELINE,
1417                        "duration", msecs,
1418                        NULL);
1419 }
1420
1421 /**
1422  * clutter_timeline_get_delay:
1423  * @timeline: a #ClutterTimeline
1424  *
1425  * Retrieves the delay set using clutter_timeline_set_delay().
1426  *
1427  * Return value: the delay in milliseconds.
1428  *
1429  * Since: 0.4
1430  */
1431 guint
1432 clutter_timeline_get_delay (ClutterTimeline *timeline)
1433 {
1434   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
1435
1436   return timeline->priv->delay;
1437 }
1438
1439 /**
1440  * clutter_timeline_set_delay:
1441  * @timeline: a #ClutterTimeline
1442  * @msecs: delay in milliseconds
1443  *
1444  * Sets the delay, in milliseconds, before @timeline should start.
1445  *
1446  * Since: 0.4
1447  */
1448 void
1449 clutter_timeline_set_delay (ClutterTimeline *timeline,
1450                             guint            msecs)
1451 {
1452   ClutterTimelinePrivate *priv;
1453
1454   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1455
1456   priv = timeline->priv;
1457
1458   if (priv->delay != msecs)
1459     {
1460       priv->delay = msecs;
1461       g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_DELAY]);
1462     }
1463 }
1464
1465 /**
1466  * clutter_timeline_get_duration:
1467  * @timeline: a #ClutterTimeline
1468  *
1469  * Retrieves the duration of a #ClutterTimeline in milliseconds.
1470  * See clutter_timeline_set_duration().
1471  *
1472  * Return value: the duration of the timeline, in milliseconds.
1473  *
1474  * Since: 0.6
1475  */
1476 guint
1477 clutter_timeline_get_duration (ClutterTimeline *timeline)
1478 {
1479   ClutterTimelinePrivate *priv;
1480
1481   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
1482
1483   priv = timeline->priv;
1484
1485   return priv->duration;
1486 }
1487
1488 /**
1489  * clutter_timeline_set_duration:
1490  * @timeline: a #ClutterTimeline
1491  * @msecs: duration of the timeline in milliseconds
1492  *
1493  * Sets the duration of the timeline, in milliseconds. The speed
1494  * of the timeline depends on the ClutterTimeline:fps setting.
1495  *
1496  * Since: 0.6
1497  */
1498 void
1499 clutter_timeline_set_duration (ClutterTimeline *timeline,
1500                                guint            msecs)
1501 {
1502   ClutterTimelinePrivate *priv;
1503
1504   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1505   g_return_if_fail (msecs > 0);
1506
1507   priv = timeline->priv;
1508
1509   if (priv->duration != msecs)
1510     {
1511       priv->duration = msecs;
1512
1513       g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_DURATION]);
1514     }
1515 }
1516
1517 /**
1518  * clutter_timeline_get_progress:
1519  * @timeline: a #ClutterTimeline
1520  *
1521  * The position of the timeline in a normalized [-1, 2] interval.
1522  *
1523  * The return value of this function is determined by the progress
1524  * mode set using clutter_timeline_set_progress_mode(), or by the
1525  * progress function set using clutter_timeline_set_progress_func().
1526  *
1527  * Return value: the normalized current position in the timeline.
1528  *
1529  * Since: 0.6
1530  */
1531 gdouble
1532 clutter_timeline_get_progress (ClutterTimeline *timeline)
1533 {
1534   ClutterTimelinePrivate *priv;
1535
1536   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0.0);
1537
1538   priv = timeline->priv;
1539
1540   /* short-circuit linear progress */
1541   if (priv->progress_func == NULL)
1542     return (gdouble) priv->elapsed_time / (gdouble) priv->duration;
1543   else
1544     return priv->progress_func (timeline,
1545                                 (gdouble) priv->elapsed_time,
1546                                 (gdouble) priv->duration,
1547                                 priv->progress_data);
1548 }
1549
1550 /**
1551  * clutter_timeline_get_direction:
1552  * @timeline: a #ClutterTimeline
1553  *
1554  * Retrieves the direction of the timeline set with
1555  * clutter_timeline_set_direction().
1556  *
1557  * Return value: the direction of the timeline
1558  *
1559  * Since: 0.6
1560  */
1561 ClutterTimelineDirection
1562 clutter_timeline_get_direction (ClutterTimeline *timeline)
1563 {
1564   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline),
1565                         CLUTTER_TIMELINE_FORWARD);
1566
1567   return timeline->priv->direction;
1568 }
1569
1570 /**
1571  * clutter_timeline_set_direction:
1572  * @timeline: a #ClutterTimeline
1573  * @direction: the direction of the timeline
1574  *
1575  * Sets the direction of @timeline, either %CLUTTER_TIMELINE_FORWARD or
1576  * %CLUTTER_TIMELINE_BACKWARD.
1577  *
1578  * Since: 0.6
1579  */
1580 void
1581 clutter_timeline_set_direction (ClutterTimeline          *timeline,
1582                                 ClutterTimelineDirection  direction)
1583 {
1584   ClutterTimelinePrivate *priv;
1585
1586   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1587
1588   priv = timeline->priv;
1589
1590   if (priv->direction != direction)
1591     {
1592       priv->direction = direction;
1593
1594       if (priv->elapsed_time == 0)
1595         priv->elapsed_time = priv->duration;
1596
1597       g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_DIRECTION]);
1598     }
1599 }
1600
1601 /**
1602  * clutter_timeline_get_delta:
1603  * @timeline: a #ClutterTimeline
1604  *
1605  * Retrieves the amount of time elapsed since the last
1606  * ClutterTimeline::new-frame signal.
1607  *
1608  * This function is only useful inside handlers for the ::new-frame
1609  * signal, and its behaviour is undefined if the timeline is not
1610  * playing.
1611  *
1612  * Return value: the amount of time in milliseconds elapsed since the
1613  * last frame
1614  *
1615  * Since: 0.6
1616  */
1617 guint
1618 clutter_timeline_get_delta (ClutterTimeline *timeline)
1619 {
1620   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
1621
1622   if (!clutter_timeline_is_playing (timeline))
1623     return 0;
1624
1625   return timeline->priv->msecs_delta;
1626 }
1627
1628 void
1629 _clutter_timeline_advance (ClutterTimeline *timeline,
1630                            gint64           tick_time)
1631 {
1632   ClutterTimelinePrivate *priv = timeline->priv;
1633
1634   g_object_ref (timeline);
1635
1636   priv->msecs_delta = tick_time;
1637   priv->is_playing = TRUE;
1638
1639   clutter_timeline_do_frame (timeline);
1640
1641   priv->is_playing = FALSE;
1642
1643   g_object_unref (timeline);
1644 }
1645
1646 /*< private >
1647  * clutter_timeline_do_tick
1648  * @timeline: a #ClutterTimeline
1649  * @tick_time: time of advance
1650  *
1651  * Advances @timeline based on the time passed in @tick_time. This
1652  * function is called by the master clock. The @timeline will use this
1653  * interval to emit the #ClutterTimeline::new-frame signal and
1654  * eventually skip frames.
1655  */
1656 void
1657 _clutter_timeline_do_tick (ClutterTimeline *timeline,
1658                            gint64           tick_time)
1659 {
1660   ClutterTimelinePrivate *priv;
1661
1662   priv = timeline->priv;
1663
1664   /* Check the is_playing variable before performing the timeline tick.
1665    * This is necessary, as if a timeline is stopped in response to a
1666    * master-clock generated signal of a different timeline, this code can
1667    * still be reached.
1668    */
1669   if (!priv->is_playing)
1670     return;
1671
1672   if (priv->waiting_first_tick)
1673     {
1674       priv->last_frame_time = tick_time;
1675       priv->msecs_delta = 0;
1676       priv->waiting_first_tick = FALSE;
1677       clutter_timeline_do_frame (timeline);
1678     }
1679   else
1680     {
1681       gint64 msecs;
1682
1683       msecs = tick_time - priv->last_frame_time;
1684
1685       /* if the clock rolled back between ticks we need to
1686        * account for it; the best course of action, since the
1687        * clock roll back can happen by any arbitrary amount
1688        * of milliseconds, is to drop a frame here
1689        */
1690       if (msecs < 0)
1691         {
1692           priv->last_frame_time = tick_time;
1693           return;
1694         }
1695
1696       if (msecs != 0)
1697         {
1698           /* Avoid accumulating error */
1699           priv->last_frame_time += msecs;
1700           priv->msecs_delta = msecs;
1701           clutter_timeline_do_frame (timeline);
1702         }
1703     }
1704 }
1705
1706 /**
1707  * clutter_timeline_add_marker_at_time:
1708  * @timeline: a #ClutterTimeline
1709  * @marker_name: the unique name for this marker
1710  * @msecs: position of the marker in milliseconds
1711  *
1712  * Adds a named marker that will be hit when the timeline has been
1713  * running for @msecs milliseconds. Markers are unique string
1714  * identifiers for a given time. Once @timeline reaches
1715  * @msecs, it will emit a ::marker-reached signal for each marker
1716  * attached to that time.
1717  *
1718  * A marker can be removed with clutter_timeline_remove_marker(). The
1719  * timeline can be advanced to a marker using
1720  * clutter_timeline_advance_to_marker().
1721  *
1722  * Since: 0.8
1723  */
1724 void
1725 clutter_timeline_add_marker_at_time (ClutterTimeline *timeline,
1726                                      const gchar     *marker_name,
1727                                      guint            msecs)
1728 {
1729   TimelineMarker *marker;
1730
1731   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1732   g_return_if_fail (marker_name != NULL);
1733   g_return_if_fail (msecs <= clutter_timeline_get_duration (timeline));
1734
1735   marker = timeline_marker_new (marker_name, msecs);
1736   clutter_timeline_add_marker_internal (timeline, marker);
1737 }
1738
1739 struct CollectMarkersClosure
1740 {
1741   guint msecs;
1742   GArray *markers;
1743 };
1744
1745 static void
1746 collect_markers (const gchar *key,
1747                  TimelineMarker *marker,
1748                  struct CollectMarkersClosure *data)
1749 {
1750   if (marker->msecs == data->msecs)
1751     {
1752       gchar *name_copy = g_strdup (key);
1753       g_array_append_val (data->markers, name_copy);
1754     }
1755 }
1756
1757 /**
1758  * clutter_timeline_list_markers:
1759  * @timeline: a #ClutterTimeline
1760  * @msecs: the time to check, or -1
1761  * @n_markers: the number of markers returned
1762  *
1763  * Retrieves the list of markers at time @msecs. If @msecs is a
1764  * negative integer, all the markers attached to @timeline will be
1765  * returned.
1766  *
1767  * Return value: (transfer full) (array zero-terminated=1 length=n_markers):
1768  *   a newly allocated, %NULL terminated string array containing the names
1769  *   of the markers. Use g_strfreev() when done.
1770  *
1771  * Since: 0.8
1772  */
1773 gchar **
1774 clutter_timeline_list_markers (ClutterTimeline *timeline,
1775                                gint             msecs,
1776                                gsize           *n_markers)
1777 {
1778   ClutterTimelinePrivate *priv;
1779   gchar **retval = NULL;
1780   gsize i;
1781
1782   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), NULL);
1783
1784   priv = timeline->priv;
1785
1786   if (G_UNLIKELY (priv->markers_by_name == NULL))
1787     {
1788       if (n_markers)
1789         *n_markers = 0;
1790
1791       return NULL;
1792     }
1793
1794   if (msecs < 0)
1795     {
1796       GList *markers, *l;
1797
1798       markers = g_hash_table_get_keys (priv->markers_by_name);
1799       retval = g_new0 (gchar*, g_list_length (markers) + 1);
1800
1801       for (i = 0, l = markers; l != NULL; i++, l = l->next)
1802         retval[i] = g_strdup (l->data);
1803
1804       g_list_free (markers);
1805     }
1806   else
1807     {
1808       struct CollectMarkersClosure data;
1809
1810       data.msecs = msecs;
1811       data.markers = g_array_new (TRUE, FALSE, sizeof (gchar *));
1812
1813       g_hash_table_foreach (priv->markers_by_name,
1814                             (GHFunc) collect_markers,
1815                             &data);
1816
1817       i = data.markers->len;
1818       retval = (gchar **) (void *) g_array_free (data.markers, FALSE);
1819     }
1820
1821   if (n_markers)
1822     *n_markers = i;
1823
1824   return retval;
1825 }
1826
1827 /**
1828  * clutter_timeline_advance_to_marker:
1829  * @timeline: a #ClutterTimeline
1830  * @marker_name: the name of the marker
1831  *
1832  * Advances @timeline to the time of the given @marker_name.
1833  *
1834  * <note><para>Like clutter_timeline_advance(), this function will not
1835  * emit the #ClutterTimeline::new-frame for the time where @marker_name
1836  * is set, nor it will emit #ClutterTimeline::marker-reached for
1837  * @marker_name.</para></note>
1838  *
1839  * Since: 0.8
1840  */
1841 void
1842 clutter_timeline_advance_to_marker (ClutterTimeline *timeline,
1843                                     const gchar     *marker_name)
1844 {
1845   ClutterTimelinePrivate *priv;
1846   TimelineMarker *marker;
1847
1848   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1849   g_return_if_fail (marker_name != NULL);
1850
1851   priv = timeline->priv;
1852
1853   if (G_UNLIKELY (priv->markers_by_name == NULL))
1854     {
1855       g_warning ("No marker named '%s' found.", marker_name);
1856       return;
1857     }
1858
1859   marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
1860   if (!marker)
1861     {
1862       g_warning ("No marker named '%s' found.", marker_name);
1863       return;
1864     }
1865
1866   clutter_timeline_advance (timeline, marker->msecs);
1867 }
1868
1869 /**
1870  * clutter_timeline_remove_marker:
1871  * @timeline: a #ClutterTimeline
1872  * @marker_name: the name of the marker to remove
1873  *
1874  * Removes @marker_name, if found, from @timeline.
1875  *
1876  * Since: 0.8
1877  */
1878 void
1879 clutter_timeline_remove_marker (ClutterTimeline *timeline,
1880                                 const gchar     *marker_name)
1881 {
1882   ClutterTimelinePrivate *priv;
1883   TimelineMarker *marker;
1884
1885   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1886   g_return_if_fail (marker_name != NULL);
1887
1888   priv = timeline->priv;
1889
1890   if (G_UNLIKELY (priv->markers_by_name == NULL))
1891     {
1892       g_warning ("No marker named '%s' found.", marker_name);
1893       return;
1894     }
1895
1896   marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
1897   if (!marker)
1898     {
1899       g_warning ("No marker named '%s' found.", marker_name);
1900       return;
1901     }
1902
1903   /* this will take care of freeing the marker as well */
1904   g_hash_table_remove (priv->markers_by_name, marker_name);
1905 }
1906
1907 /**
1908  * clutter_timeline_has_marker:
1909  * @timeline: a #ClutterTimeline
1910  * @marker_name: the name of the marker
1911  *
1912  * Checks whether @timeline has a marker set with the given name.
1913  *
1914  * Return value: %TRUE if the marker was found
1915  *
1916  * Since: 0.8
1917  */
1918 gboolean
1919 clutter_timeline_has_marker (ClutterTimeline *timeline,
1920                              const gchar     *marker_name)
1921 {
1922   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), FALSE);
1923   g_return_val_if_fail (marker_name != NULL, FALSE);
1924
1925   if (G_UNLIKELY (timeline->priv->markers_by_name == NULL))
1926     return FALSE;
1927
1928   return NULL != g_hash_table_lookup (timeline->priv->markers_by_name,
1929                                       marker_name);
1930 }
1931
1932 /**
1933  * clutter_timeline_set_auto_reverse:
1934  * @timeline: a #ClutterTimeline
1935  * @reverse: %TRUE if the @timeline should reverse the direction
1936  *
1937  * Sets whether @timeline should reverse the direction after the
1938  * emission of the #ClutterTimeline::completed signal.
1939  *
1940  * Setting the #ClutterTimeline:auto-reverse property to %TRUE is the
1941  * equivalent of connecting a callback to the #ClutterTimeline::completed
1942  * signal and changing the direction of the timeline from that callback;
1943  * for instance, this code:
1944  *
1945  * |[
1946  * static void
1947  * reverse_timeline (ClutterTimeline *timeline)
1948  * {
1949  *   ClutterTimelineDirection dir = clutter_timeline_get_direction (timeline);
1950  *
1951  *   if (dir == CLUTTER_TIMELINE_FORWARD)
1952  *     dir = CLUTTER_TIMELINE_BACKWARD;
1953  *   else
1954  *     dir = CLUTTER_TIMELINE_FORWARD;
1955  *
1956  *   clutter_timeline_set_direction (timeline, dir);
1957  * }
1958  * ...
1959  *   timeline = clutter_timeline_new (1000);
1960  *   clutter_timeline_set_repeat_count (timeline, -1);
1961  *   g_signal_connect (timeline, "completed",
1962  *                     G_CALLBACK (reverse_timeline),
1963  *                     NULL);
1964  * ]|
1965  *
1966  * can be effectively replaced by:
1967  *
1968  * |[
1969  *   timeline = clutter_timeline_new (1000);
1970  *   clutter_timeline_set_repeat_count (timeline, -1);
1971  *   clutter_timeline_set_auto_reverse (timeline);
1972  * ]|
1973  *
1974  * Since: 1.6
1975  */
1976 void
1977 clutter_timeline_set_auto_reverse (ClutterTimeline *timeline,
1978                                    gboolean         reverse)
1979 {
1980   ClutterTimelinePrivate *priv;
1981
1982   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1983
1984   reverse = !!reverse;
1985
1986   priv = timeline->priv;
1987
1988   if (priv->auto_reverse != reverse)
1989     {
1990       priv->auto_reverse = reverse;
1991
1992       g_object_notify_by_pspec (G_OBJECT (timeline),
1993                                 obj_props[PROP_AUTO_REVERSE]);
1994     }
1995 }
1996
1997 /**
1998  * clutter_timeline_get_auto_reverse:
1999  * @timeline: a #ClutterTimeline
2000  *
2001  * Retrieves the value set by clutter_timeline_set_auto_reverse().
2002  *
2003  * Return value: %TRUE if the timeline should automatically reverse, and
2004  *   %FALSE otherwise
2005  *
2006  * Since: 1.6
2007  */
2008 gboolean
2009 clutter_timeline_get_auto_reverse (ClutterTimeline *timeline)
2010 {
2011   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), FALSE);
2012
2013   return timeline->priv->auto_reverse;
2014 }
2015
2016 /**
2017  * clutter_timeline_set_repeat_count:
2018  * @timeline: a #ClutterTimeline
2019  * @count: the number of times the timeline should repeat
2020  *
2021  * Sets the number of times the @timeline should repeat.
2022  *
2023  * If @count is 0, the timeline never repeats.
2024  *
2025  * If @count is -1, the timeline will always repeat until
2026  * it's stopped.
2027  *
2028  * Since: 1.10
2029  */
2030 void
2031 clutter_timeline_set_repeat_count (ClutterTimeline *timeline,
2032                                    gint             count)
2033 {
2034   ClutterTimelinePrivate *priv;
2035
2036   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
2037   g_return_if_fail (count >= -1);
2038
2039   priv = timeline->priv;
2040
2041   if (priv->repeat_count != count)
2042     {
2043       priv->repeat_count = count;
2044
2045       g_object_notify_by_pspec (G_OBJECT (timeline),
2046                                 obj_props[PROP_REPEAT_COUNT]);
2047     }
2048 }
2049
2050 /**
2051  * clutter_timeline_get_repeat_count:
2052  * @timeline: a #ClutterTimeline
2053  *
2054  * Retrieves the number set using clutter_timeline_set_repeat_count().
2055  *
2056  * Return value: the number of repeats
2057  *
2058  * Since: 1.10
2059  */
2060 gint
2061 clutter_timeline_get_repeat_count (ClutterTimeline *timeline)
2062 {
2063   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2064
2065   return timeline->priv->repeat_count;
2066 }
2067
2068 /**
2069  * clutter_timeline_set_progress_func:
2070  * @timeline: a #ClutterTimeline
2071  * @func: (scope notified) (allow-none): a progress function, or %NULL
2072  * @data: (closure): data to pass to @func
2073  * @notify: a function to be called when the progress function is removed
2074  *    or the timeline is disposed
2075  *
2076  * Sets a custom progress function for @timeline. The progress function will
2077  * be called by clutter_timeline_get_progress() and will be used to compute
2078  * the progress value based on the elapsed time and the total duration of the
2079  * timeline.
2080  *
2081  * If @func is not %NULL, the #ClutterTimeline:progress-mode property will
2082  * be set to %CLUTTER_CUSTOM_MODE.
2083  *
2084  * If @func is %NULL, any previously set progress function will be unset, and
2085  * the #ClutterTimeline:progress-mode property will be set to %CLUTTER_LINEAR.
2086  *
2087  * Since: 1.10
2088  */
2089 void
2090 clutter_timeline_set_progress_func (ClutterTimeline             *timeline,
2091                                     ClutterTimelineProgressFunc  func,
2092                                     gpointer                     data,
2093                                     GDestroyNotify               notify)
2094 {
2095   ClutterTimelinePrivate *priv;
2096
2097   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
2098
2099   priv = timeline->priv;
2100
2101   if (priv->progress_notify != NULL)
2102     priv->progress_notify (priv->progress_data);
2103
2104   priv->progress_func = func;
2105   priv->progress_data = data;
2106   priv->progress_notify = notify;
2107
2108   if (priv->progress_func != NULL)
2109     priv->progress_mode = CLUTTER_CUSTOM_MODE;
2110   else
2111     priv->progress_mode = CLUTTER_LINEAR;
2112
2113   g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_PROGRESS_MODE]);
2114 }
2115
2116 static gdouble
2117 clutter_timeline_progress_func (ClutterTimeline *timeline,
2118                                 gdouble          elapsed,
2119                                 gdouble          duration,
2120                                 gpointer         user_data G_GNUC_UNUSED)
2121 {
2122   ClutterTimelinePrivate *priv = timeline->priv;
2123
2124   return clutter_easing_for_mode (priv->progress_mode, elapsed, duration);
2125 }
2126
2127 /**
2128  * clutter_timeline_set_progress_mode:
2129  * @timeline: a #ClutterTimeline
2130  * @mode: the progress mode, as a #ClutterAnimationMode
2131  *
2132  * Sets the progress function using a value from the #ClutterAnimationMode
2133  * enumeration. The @mode cannot be %CLUTTER_CUSTOM_MODE or bigger than
2134  * %CLUTTER_ANIMATION_LAST.
2135  *
2136  * Since: 1.10
2137  */
2138 void
2139 clutter_timeline_set_progress_mode (ClutterTimeline      *timeline,
2140                                     ClutterAnimationMode  mode)
2141 {
2142   ClutterTimelinePrivate *priv;
2143
2144   g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
2145   g_return_if_fail (mode < CLUTTER_ANIMATION_LAST);
2146   g_return_if_fail (mode != CLUTTER_CUSTOM_MODE);
2147
2148   priv = timeline->priv;
2149
2150   if (priv->progress_mode == mode)
2151     return;
2152
2153   if (priv->progress_notify != NULL)
2154     priv->progress_notify (priv->progress_data);
2155
2156   priv->progress_mode = mode;
2157
2158   /* short-circuit linear progress */
2159   if (priv->progress_mode != CLUTTER_LINEAR)
2160     priv->progress_func = clutter_timeline_progress_func;
2161   else
2162     priv->progress_func = NULL;
2163
2164   priv->progress_data = NULL;
2165   priv->progress_notify = NULL;
2166
2167   g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_PROGRESS_MODE]);
2168 }
2169
2170 /**
2171  * clutter_timeline_get_progress_mode:
2172  * @timeline: a #ClutterTimeline
2173  *
2174  * Retrieves the progress mode set using clutter_timeline_set_progress_mode()
2175  * or clutter_timeline_set_progress_func().
2176  *
2177  * Return value: a #ClutterAnimationMode
2178  *
2179  * Since: 1.10
2180  */
2181 ClutterAnimationMode
2182 clutter_timeline_get_progress_mode (ClutterTimeline *timeline)
2183 {
2184   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), CLUTTER_LINEAR);
2185
2186   return timeline->priv->progress_mode;
2187 }
2188
2189 /**
2190  * clutter_timeline_get_duration_hint:
2191  * @timeline: a #ClutterTimeline
2192  *
2193  * Retrieves the full duration of the @timeline, taking into account the
2194  * current value of the #ClutterTimeline:repeat-count property.
2195  *
2196  * If the #ClutterTimeline:repeat-count property is set to -1, this function
2197  * will return %G_MAXINT64.
2198  *
2199  * The returned value is to be considered a hint, and it's only valid
2200  * as long as the @timeline hasn't been changed.
2201  *
2202  * Return value: the full duration of the #ClutterTimeline
2203  *
2204  * Since: 1.10
2205  */
2206 gint64
2207 clutter_timeline_get_duration_hint (ClutterTimeline *timeline)
2208 {
2209   ClutterTimelinePrivate *priv;
2210
2211   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2212
2213   priv = timeline->priv;
2214
2215   if (priv->repeat_count == 0)
2216     return priv->duration;
2217   else if (priv->repeat_count < 0)
2218     return G_MAXINT64;
2219   else
2220     return priv->repeat_count * priv->duration;
2221 }
2222
2223 /**
2224  * clutter_timeline_get_current_repeat:
2225  * @timeline: a #ClutterTimeline
2226  *
2227  * Retrieves the current repeat for a timeline.
2228  *
2229  * Repeats start at 0.
2230  *
2231  * Return value: the current repeat
2232  *
2233  * Since: 1.10
2234  */
2235 gint
2236 clutter_timeline_get_current_repeat (ClutterTimeline *timeline)
2237 {
2238   g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2239
2240   return timeline->priv->current_repeat;
2241 }