1 /*
2  * Gtd.
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  * SECTION:gtd-timeline
26  * @short_description: A class for time-based events
27  *
28  * #GtdTimeline is a base class for managing time-based event that cause
29  * GTK to redraw, such as animations.
30  *
31  * Each #GtdTimeline instance has a duration: once a timeline has been
32  * started, using gtd_timeline_start(), it will emit a signal that can
33  * be used to update the state of the widgets.
34  *
35  * It is important to note that #GtdTimeline is not a generic API for
36  * calling closures after an interval; each Timeline is tied into the master
37  * clock used to drive the frame cycle. If you need to schedule a closure
38  * after an interval, see gtd_threads_add_timeout() instead.
39  *
40  * Users of #GtdTimeline should connect to the #GtdTimeline::new-frame
41  * signal, which is emitted each time a timeline is advanced during the maste
42  * clock iteration. The #GtdTimeline::new-frame signal provides the time
43  * elapsed since the beginning of the timeline, in milliseconds. A normalized
44  * progress value can be obtained by calling gtd_timeline_get_progress().
45  * By using gtd_timeline_get_delta() it is possible to obtain the wallclock
46  * time elapsed since the last emission of the #GtdTimeline::new-frame
47  * signal.
48  *
49  * Initial state can be set up by using the #GtdTimeline::started signal,
50  * while final state can be set up by using the #GtdTimeline::stopped
51  * signal. The #GtdTimeline guarantees the emission of at least a single
52  * #GtdTimeline::new-frame signal, as well as the emission of the
53  * #GtdTimeline::completed signal every time the #GtdTimeline reaches
54  * its #GtdTimeline:duration.
55  *
56  * It is possible to connect to specific points in the timeline progress by
57  * adding markers using gtd_timeline_add_marker_at_time() and connecting
58  * to the #GtdTimeline::marker-reached signal.
59  *
60  * Timelines can be made to loop once they reach the end of their duration, by
61  * using gtd_timeline_set_repeat_count(); a looping timeline will still
62  * emit the #GtdTimeline::completed signal once it reaches the end of its
63  * duration at each repeat. If you want to be notified of the end of the last
64  * repeat, use the #GtdTimeline::stopped signal.
65  *
66  * Timelines have a #GtdTimeline:direction: the default direction is
67  * %GTD_TIMELINE_FORWARD, and goes from 0 to the duration; it is possible
68  * to change the direction to %GTD_TIMELINE_BACKWARD, and have the timeline
69  * go from the duration to 0. The direction can be automatically reversed
70  * when reaching completion by using the #GtdTimeline:auto-reverse property.
71  *
72  * Timelines are used in the Gtd animation framework by classes like
73  * #GtdTransition.
74  */
75 
76 #define G_LOG_DOMAIN "GtdTimeline"
77 
78 #include "gtd-timeline.h"
79 
80 #include "gtd-debug.h"
81 #include "gnome-todo.h"
82 
83 typedef struct
84 {
85   GtdTimelineDirection direction;
86 
87   GtdWidget *widget;
88   guint frame_tick_id;
89 
90   gint64 duration_us;
91   gint64 delay_us;
92 
93   /* The current amount of elapsed time */
94   gint64 elapsed_time_us;
95 
96   /* The elapsed time since the last frame was fired */
97   gint64 delta_us;
98 
99   /* Time we last advanced the elapsed time and showed a frame */
100   gint64 last_frame_time_us;
101 
102   gint64 start_us;
103 
104   /* How many times the timeline should repeat */
105   gint repeat_count;
106 
107   /* The number of times the timeline has repeated */
108   gint current_repeat;
109 
110   GtdTimelineProgressFunc progress_func;
111   gpointer progress_data;
112   GDestroyNotify progress_notify;
113   GtdEaseMode progress_mode;
114 
115   guint is_playing         : 1;
116 
117   /* If we've just started playing and haven't yet gotten
118    * a tick from the master clock
119    */
120   guint auto_reverse       : 1;
121 } GtdTimelinePrivate;
122 
123 enum
124 {
125   PROP_0,
126 
127   PROP_AUTO_REVERSE,
128   PROP_DELAY,
129   PROP_DURATION,
130   PROP_DIRECTION,
131   PROP_REPEAT_COUNT,
132   PROP_PROGRESS_MODE,
133   PROP_WIDGET,
134 
135   PROP_LAST
136 };
137 
138 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
139 
140 enum
141 {
142   NEW_FRAME,
143   STARTED,
144   PAUSED,
145   COMPLETED,
146   STOPPED,
147 
148   LAST_SIGNAL
149 };
150 
151 static guint timeline_signals[LAST_SIGNAL] = { 0, };
152 
153 static void set_is_playing (GtdTimeline *self,
154                             gboolean     is_playing);
155 
G_DEFINE_TYPE_WITH_PRIVATE(GtdTimeline,gtd_timeline,G_TYPE_OBJECT)156 G_DEFINE_TYPE_WITH_PRIVATE (GtdTimeline, gtd_timeline, G_TYPE_OBJECT)
157 
158 static inline gint64
159 us_to_ms (gint64 ms)
160 {
161   return ms / 1000;
162 }
163 
164 static inline gint64
ms_to_us(gint64 us)165 ms_to_us (gint64 us)
166 {
167   return us * 1000;
168 }
169 
170 static inline gboolean
is_waiting_for_delay(GtdTimeline * self,gint64 frame_time_us)171 is_waiting_for_delay (GtdTimeline *self,
172                       gint64       frame_time_us)
173 {
174   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
175   return priv->start_us + priv->delay_us > frame_time_us;
176 }
177 
178 static void
emit_frame_signal(GtdTimeline * self)179 emit_frame_signal (GtdTimeline *self)
180 {
181   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
182   gint64 elapsed_time_ms = us_to_ms (priv->elapsed_time_us);
183 
184   GTD_TRACE_MSG ("Emitting ::new-frame signal on timeline[%p]", self);
185 
186   g_signal_emit (self, timeline_signals[NEW_FRAME], 0, elapsed_time_ms);
187 }
188 
189 static gboolean
is_complete(GtdTimeline * self)190 is_complete (GtdTimeline *self)
191 {
192   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
193 
194   return (priv->direction == GTD_TIMELINE_FORWARD
195           ? priv->elapsed_time_us >= priv->duration_us
196           : priv->elapsed_time_us <= 0);
197 }
198 
199 static gboolean
maybe_loop_timeline(GtdTimeline * self)200 maybe_loop_timeline (GtdTimeline *self)
201 {
202   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
203   GtdTimelineDirection saved_direction = priv->direction;
204   gint64 overflow_us = priv->elapsed_time_us;
205   gint64 end_us;
206 
207   /* Update the current elapsed time in case the signal handlers
208    * want to take a peek. If we clamp elapsed time, then we need
209    * to correpondingly reduce elapsed_time_delta to reflect the correct
210    * range of times */
211   if (priv->direction == GTD_TIMELINE_FORWARD)
212     priv->elapsed_time_us = priv->duration_us;
213   else if (priv->direction == GTD_TIMELINE_BACKWARD)
214     priv->elapsed_time_us = 0;
215 
216   end_us = priv->elapsed_time_us;
217 
218   /* Emit the signal */
219   emit_frame_signal (self);
220 
221   /* Did the signal handler modify the elapsed time? */
222   if (priv->elapsed_time_us != end_us)
223     return TRUE;
224 
225   /* Note: If the new-frame signal handler paused the timeline
226    * on the last frame we will still go ahead and send the
227    * completed signal */
228   GTD_TRACE_MSG ("Timeline [%p] completed (cur: %ldµs, tot: %ldµs)",
229                 self,
230                 priv->elapsed_time_us,
231                 priv->delta_us);
232 
233   if (priv->is_playing &&
234       (priv->repeat_count == 0 ||
235        priv->repeat_count == priv->current_repeat))
236     {
237       /* We stop the timeline now, so that the completed signal handler
238        * may choose to re-start the timeline
239        */
240       set_is_playing (self, FALSE);
241 
242       g_signal_emit (self, timeline_signals[COMPLETED], 0);
243       g_signal_emit (self, timeline_signals[STOPPED], 0, TRUE);
244     }
245   else
246     {
247       g_signal_emit (self, timeline_signals[COMPLETED], 0);
248     }
249 
250   priv->current_repeat += 1;
251 
252   if (priv->auto_reverse)
253     {
254       /* :auto-reverse changes the direction of the timeline */
255       if (priv->direction == GTD_TIMELINE_FORWARD)
256         priv->direction = GTD_TIMELINE_BACKWARD;
257       else
258         priv->direction = GTD_TIMELINE_FORWARD;
259 
260       g_object_notify_by_pspec (G_OBJECT (self),
261                                 obj_props[PROP_DIRECTION]);
262     }
263 
264   /*
265    * Again check to see if the user has manually played with
266    * the elapsed time, before we finally stop or loop the timeline,
267    * except changing time from 0 -> duration (or vice-versa)
268    * since these are considered equivalent
269    */
270   if (priv->elapsed_time_us != end_us &&
271       !((priv->elapsed_time_us == 0 && end_us == priv->duration_us) ||
272         (priv->elapsed_time_us == priv->duration_us && end_us == 0)))
273     {
274       return TRUE;
275     }
276 
277   if (priv->repeat_count == 0)
278     {
279       gtd_timeline_rewind (self);
280       return FALSE;
281     }
282 
283   /* Try and interpolate smoothly around a loop */
284   if (saved_direction == GTD_TIMELINE_FORWARD)
285     priv->elapsed_time_us = overflow_us - priv->duration_us;
286   else
287     priv->elapsed_time_us = priv->duration_us + overflow_us;
288 
289   /* Or if the direction changed, we try and bounce */
290   if (priv->direction != saved_direction)
291     priv->elapsed_time_us = priv->duration_us - priv->elapsed_time_us;
292 
293   return TRUE;
294 }
295 
296 static gboolean
tick_timeline(GtdTimeline * self,gint64 tick_time_us)297 tick_timeline (GtdTimeline *self,
298                gint64       tick_time_us)
299 {
300   GtdTimelinePrivate *priv;
301   gboolean should_continue;
302   gboolean complete;
303   gint64 elapsed_us;
304 
305   priv = gtd_timeline_get_instance_private (self);
306 
307   GTD_TRACE_MSG ("Timeline [%p] ticked (elapsed_time: %ldµs, delta_us: %ldµs, "
308                  "last_frame_time: %ldµs, tick_time: %ldµs)",
309                  self,
310                  priv->elapsed_time_us,
311                  priv->delta_us,
312                  priv->last_frame_time_us,
313                  tick_time_us);
314 
315   /* Check the is_playing variable before performing the timeline tick.
316    * This is necessary, as if a timeline is stopped in response to a
317    * frame clock generated signal of a different timeline, this code can
318    * still be reached.
319    */
320   if (!priv->is_playing)
321     return FALSE;
322 
323   elapsed_us = tick_time_us - priv->last_frame_time_us;
324   priv->last_frame_time_us = tick_time_us;
325 
326   if (is_waiting_for_delay (self, tick_time_us))
327     {
328       GTD_TRACE_MSG ("- waiting for delay");
329       return G_SOURCE_CONTINUE;
330     }
331 
332   /* if the clock rolled back between ticks we need to
333    * account for it; the best course of action, since the
334    * clock roll back can happen by any arbitrary amount
335    * of milliseconds, is to drop a frame here
336    */
337   if (elapsed_us <= 0)
338     return TRUE;
339 
340   priv->delta_us = elapsed_us;
341 
342   GTD_TRACE_MSG ("Timeline [%p] activated (elapsed time: %ldµs, "
343                  "duration: %ldµs, delta_us: %ldµs)",
344                  self,
345                  priv->elapsed_time_us,
346                  priv->duration_us,
347                  priv->delta_us);
348 
349   g_object_ref (self);
350 
351   /* Advance time */
352   if (priv->direction == GTD_TIMELINE_FORWARD)
353     priv->elapsed_time_us += priv->delta_us;
354   else
355     priv->elapsed_time_us -= priv->delta_us;
356 
357   complete = is_complete (self);
358   should_continue = !complete ? priv->is_playing : maybe_loop_timeline (self);
359 
360   /* If we have not reached the end of the timeline */
361   if (!complete)
362     emit_frame_signal (self);
363 
364   g_object_unref (self);
365 
366   return should_continue;
367 }
368 
369 static gboolean
frame_tick_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)370 frame_tick_cb (GtkWidget     *widget,
371                GdkFrameClock *frame_clock,
372                gpointer       user_data)
373 {
374   GtdTimeline *self = GTD_TIMELINE (user_data);
375   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
376   gint64 frame_time_us;
377 
378   frame_time_us = gdk_frame_clock_get_frame_time (frame_clock);
379 
380   if (tick_timeline (self, frame_time_us))
381     return G_SOURCE_CONTINUE;
382 
383   priv->frame_tick_id = 0;
384   return G_SOURCE_REMOVE;
385 }
386 
387 static void
add_tick_callback(GtdTimeline * self)388 add_tick_callback (GtdTimeline *self)
389 {
390   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
391 
392   g_assert (!(priv->frame_tick_id > 0 && !priv->widget));
393 
394   if (priv->frame_tick_id == 0)
395     {
396       priv->frame_tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (priv->widget),
397                                                           frame_tick_cb,
398                                                           self,
399                                                           NULL);
400     }
401 }
402 
403 static void
remove_tick_callback(GtdTimeline * self)404 remove_tick_callback (GtdTimeline *self)
405 {
406   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
407 
408   g_assert (!(priv->frame_tick_id > 0 && !priv->widget));
409 
410   if (priv->frame_tick_id > 0)
411     {
412       gtk_widget_remove_tick_callback (GTK_WIDGET (priv->widget), priv->frame_tick_id);
413       priv->frame_tick_id = 0;
414     }
415 }
416 
417 static void
set_is_playing(GtdTimeline * self,gboolean is_playing)418 set_is_playing (GtdTimeline *self,
419                 gboolean     is_playing)
420 {
421   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
422 
423   is_playing = !!is_playing;
424 
425   if (is_playing == priv->is_playing)
426     return;
427 
428   priv->is_playing = is_playing;
429 
430   if (priv->is_playing)
431     {
432       priv->start_us = g_get_monotonic_time ();
433       priv->last_frame_time_us = priv->start_us;
434       priv->current_repeat = 0;
435 
436       add_tick_callback (self);
437     }
438   else
439     {
440       remove_tick_callback (self);
441     }
442 }
443 
444 static gdouble
timeline_progress_func(GtdTimeline * self,gdouble elapsed,gdouble duration,gpointer user_data)445 timeline_progress_func (GtdTimeline *self,
446                         gdouble      elapsed,
447                         gdouble      duration,
448                         gpointer     user_data)
449 {
450   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
451 
452   return gtd_easing_for_mode (priv->progress_mode, elapsed, duration);
453 }
454 
455 
456 /*
457  * GObject overrides
458  */
459 
460 static void
gtd_timeline_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)461 gtd_timeline_set_property (GObject      *object,
462                            guint         prop_id,
463                            const GValue *value,
464                            GParamSpec   *pspec)
465 {
466   GtdTimeline *self = GTD_TIMELINE (object);
467 
468   switch (prop_id)
469     {
470     case PROP_DELAY:
471       gtd_timeline_set_delay (self, g_value_get_uint (value));
472       break;
473 
474     case PROP_DURATION:
475       gtd_timeline_set_duration (self, g_value_get_uint (value));
476       break;
477 
478     case PROP_DIRECTION:
479       gtd_timeline_set_direction (self, g_value_get_enum (value));
480       break;
481 
482     case PROP_AUTO_REVERSE:
483       gtd_timeline_set_auto_reverse (self, g_value_get_boolean (value));
484       break;
485 
486     case PROP_REPEAT_COUNT:
487       gtd_timeline_set_repeat_count (self, g_value_get_int (value));
488       break;
489 
490     case PROP_PROGRESS_MODE:
491       gtd_timeline_set_progress_mode (self, g_value_get_enum (value));
492       break;
493 
494     case PROP_WIDGET:
495       gtd_timeline_set_widget (self, g_value_get_object (value));
496       break;
497 
498     default:
499       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
500       break;
501     }
502 }
503 
504 static void
gtd_timeline_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)505 gtd_timeline_get_property (GObject    *object,
506                            guint       prop_id,
507                            GValue     *value,
508                            GParamSpec *pspec)
509 {
510   GtdTimeline *self = GTD_TIMELINE (object);
511   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
512 
513   switch (prop_id)
514     {
515     case PROP_DELAY:
516       g_value_set_uint (value, us_to_ms (priv->delay_us));
517       break;
518 
519     case PROP_DURATION:
520       g_value_set_uint (value, gtd_timeline_get_duration (self));
521       break;
522 
523     case PROP_DIRECTION:
524       g_value_set_enum (value, priv->direction);
525       break;
526 
527     case PROP_AUTO_REVERSE:
528       g_value_set_boolean (value, priv->auto_reverse);
529       break;
530 
531     case PROP_REPEAT_COUNT:
532       g_value_set_int (value, priv->repeat_count);
533       break;
534 
535     case PROP_PROGRESS_MODE:
536       g_value_set_enum (value, priv->progress_mode);
537       break;
538 
539     case PROP_WIDGET:
540       g_value_set_object (value, priv->widget);
541       break;
542 
543     default:
544       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
545       break;
546     }
547 }
548 
549 static void
gtd_timeline_dispose(GObject * object)550 gtd_timeline_dispose (GObject *object)
551 {
552   GtdTimeline *self = GTD_TIMELINE (object);
553   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
554 
555   if (priv->progress_notify != NULL)
556     {
557       priv->progress_notify (priv->progress_data);
558       priv->progress_func = NULL;
559       priv->progress_data = NULL;
560       priv->progress_notify = NULL;
561     }
562 
563   G_OBJECT_CLASS (gtd_timeline_parent_class)->dispose (object);
564 }
565 
566 static void
gtd_timeline_class_init(GtdTimelineClass * klass)567 gtd_timeline_class_init (GtdTimelineClass *klass)
568 {
569   GObjectClass *object_class = G_OBJECT_CLASS (klass);
570 
571   /**
572    * GtdTimeline::widget:
573    *
574    * The widget the timeline is associated with. This will determine what frame
575    * clock will drive it.
576    */
577   obj_props[PROP_WIDGET] =
578     g_param_spec_object ("widget",
579                          "Widget",
580                          "Associated GtdWidget",
581                          GTD_TYPE_WIDGET,
582                          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
583   /**
584    * GtdTimeline:delay:
585    *
586    * A delay, in milliseconds, that should be observed by the
587    * timeline before actually starting.
588    */
589   obj_props[PROP_DELAY] =
590     g_param_spec_uint ("delay",
591                        "Delay",
592                        "Delay before start",
593                        0, G_MAXUINT,
594                        0,
595                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
596 
597   /**
598    * GtdTimeline:duration:
599    *
600    * Duration of the timeline in milliseconds.
601    */
602   obj_props[PROP_DURATION] =
603     g_param_spec_uint ("duration",
604                        "Duration",
605                        "Duration of the timeline in milliseconds",
606                        0, G_MAXUINT,
607                        1000,
608                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
609 
610   /**
611    * GtdTimeline:direction:GIT
612    *
613    * The direction of the timeline, either %GTD_TIMELINE_FORWARD or
614    * %GTD_TIMELINE_BACKWARD.
615    */
616   obj_props[PROP_DIRECTION] =
617     g_param_spec_enum ("direction",
618                        "Direction",
619                        "Direction of the timeline",
620                        GTD_TYPE_TIMELINE_DIRECTION,
621                        GTD_TIMELINE_FORWARD,
622                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
623 
624   /**
625    * GtdTimeline:auto-reverse:
626    *
627    * If the direction of the timeline should be automatically reversed
628    * when reaching the end.
629    */
630   obj_props[PROP_AUTO_REVERSE] =
631     g_param_spec_boolean ("auto-reverse",
632                           "Auto Reverse",
633                           "Whether the direction should be reversed when reaching the end",
634                           FALSE,
635                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
636 
637   /**
638    * GtdTimeline:repeat-count:
639    *
640    * Defines how many times the timeline should repeat.
641    *
642    * If the repeat count is 0, the timeline does not repeat.
643    *
644    * If the repeat count is set to -1, the timeline will repeat until it is
645    * stopped.
646    */
647   obj_props[PROP_REPEAT_COUNT] =
648     g_param_spec_int ("repeat-count",
649                       "Repeat Count",
650                       "How many times the timeline should repeat",
651                       -1, G_MAXINT,
652                       0,
653                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
654 
655   /**
656    * GtdTimeline:progress-mode:
657    *
658    * Controls the way a #GtdTimeline computes the normalized progress.
659    */
660   obj_props[PROP_PROGRESS_MODE] =
661     g_param_spec_enum ("progress-mode",
662                        "Progress Mode",
663                        "How the timeline should compute the progress",
664                        GTD_TYPE_EASE_MODE,
665                        GTD_EASE_LINEAR,
666                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
667 
668   object_class->dispose = gtd_timeline_dispose;
669   object_class->set_property = gtd_timeline_set_property;
670   object_class->get_property = gtd_timeline_get_property;
671   g_object_class_install_properties (object_class, PROP_LAST, obj_props);
672 
673   /**
674    * GtdTimeline::new-frame:
675    * @timeline: the timeline which received the signal
676    * @msecs: the elapsed time between 0 and duration
677    *
678    * The ::new-frame signal is emitted for each timeline running
679    * timeline before a new frame is drawn to give animations a chance
680    * to update the scene.
681    */
682   timeline_signals[NEW_FRAME] =
683     g_signal_new ("new-frame",
684                   G_TYPE_FROM_CLASS (object_class),
685                   G_SIGNAL_RUN_LAST,
686                   G_STRUCT_OFFSET (GtdTimelineClass, new_frame),
687                   NULL, NULL, NULL,
688                   G_TYPE_NONE,
689                   1, G_TYPE_INT);
690   /**
691    * GtdTimeline::completed:
692    * @timeline: the #GtdTimeline which received the signal
693    *
694    * The #GtdTimeline::completed signal is emitted when the timeline's
695    * elapsed time reaches the value of the #GtdTimeline:duration
696    * property.
697    *
698    * This signal will be emitted even if the #GtdTimeline is set to be
699    * repeating.
700    *
701    * If you want to get notification on whether the #GtdTimeline has
702    * been stopped or has finished its run, including its eventual repeats,
703    * you should use the #GtdTimeline::stopped signal instead.
704    */
705   timeline_signals[COMPLETED] =
706     g_signal_new ("completed",
707                   G_TYPE_FROM_CLASS (object_class),
708                   G_SIGNAL_RUN_LAST,
709                   G_STRUCT_OFFSET (GtdTimelineClass, completed),
710                   NULL, NULL, NULL,
711                   G_TYPE_NONE, 0);
712   /**
713    * GtdTimeline::started:
714    * @timeline: the #GtdTimeline which received the signal
715    *
716    * The ::started signal is emitted when the timeline starts its run.
717    * This might be as soon as gtd_timeline_start() is invoked or
718    * after the delay set in the GtdTimeline:delay property has
719    * expired.
720    */
721   timeline_signals[STARTED] =
722     g_signal_new ("started",
723                   G_TYPE_FROM_CLASS (object_class),
724                   G_SIGNAL_RUN_LAST,
725                   G_STRUCT_OFFSET (GtdTimelineClass, started),
726                   NULL, NULL, NULL,
727                   G_TYPE_NONE, 0);
728   /**
729    * GtdTimeline::paused:
730    * @timeline: the #GtdTimeline which received the signal
731    *
732    * The ::paused signal is emitted when gtd_timeline_pause() is invoked.
733    */
734   timeline_signals[PAUSED] =
735     g_signal_new ("paused",
736                   G_TYPE_FROM_CLASS (object_class),
737                   G_SIGNAL_RUN_LAST,
738                   G_STRUCT_OFFSET (GtdTimelineClass, paused),
739                   NULL, NULL, NULL,
740                   G_TYPE_NONE, 0);
741 
742   /**
743    * GtdTimeline::stopped:
744    * @timeline: the #GtdTimeline that emitted the signal
745    * @is_finished: %TRUE if the signal was emitted at the end of the
746    *   timeline.
747    *
748    * The #GtdTimeline::stopped signal is emitted when the timeline
749    * has been stopped, either because gtd_timeline_stop() has been
750    * called, or because it has been exhausted.
751    *
752    * This is different from the #GtdTimeline::completed signal,
753    * which gets emitted after every repeat finishes.
754    *
755    * If the #GtdTimeline has is marked as infinitely repeating,
756    * this signal will never be emitted.
757    */
758   timeline_signals[STOPPED] =
759     g_signal_new ("stopped",
760                   G_TYPE_FROM_CLASS (object_class),
761                   G_SIGNAL_RUN_LAST,
762                   G_STRUCT_OFFSET (GtdTimelineClass, stopped),
763                   NULL, NULL, NULL,
764                   G_TYPE_NONE,
765                   1,
766                   G_TYPE_BOOLEAN);
767 }
768 
769 static void
gtd_timeline_init(GtdTimeline * self)770 gtd_timeline_init (GtdTimeline *self)
771 {
772   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
773 
774   priv->progress_mode = GTD_EASE_LINEAR;
775   priv->progress_func = timeline_progress_func;
776 }
777 
778 /**
779  * gtd_timeline_new:
780  * @duration_ms: Duration of the timeline in milliseconds
781  *
782  * Creates a new #GtdTimeline with a duration of @duration_ms milli seconds.
783  *
784  * Return value: the newly created #GtdTimeline instance. Use
785  *   g_object_unref() when done using it
786  */
787 GtdTimeline *
gtd_timeline_new(guint duration_ms)788 gtd_timeline_new (guint duration_ms)
789 {
790   return g_object_new (GTD_TYPE_TIMELINE,
791                        "duration", duration_ms,
792                        NULL);
793 }
794 
795 /**
796  * gtd_timeline_new_for_widget:
797  * @widget: The #GtdWidget the timeline is associated with
798  * @duration_ms: Duration of the timeline in milliseconds
799  *
800  * Creates a new #GtdTimeline with a duration of @duration milli seconds.
801  *
802  * Return value: the newly created #GtdTimeline instance. Use
803  *   g_object_unref() when done using it
804  */
805 GtdTimeline *
gtd_timeline_new_for_widget(GtdWidget * widget,guint duration_ms)806 gtd_timeline_new_for_widget (GtdWidget *widget,
807                              guint      duration_ms)
808 {
809   return g_object_new (GTD_TYPE_TIMELINE,
810                        "duration", duration_ms,
811                        "widget", widget,
812                        NULL);
813 }
814 
815 /**
816  * gtd_timeline_set_widget:
817  * @timeline: a #GtdTimeline
818  * @widget: a #GtdWidget
819  *
820  * Set the widget the timeline is associated with.
821  */
822 void
gtd_timeline_set_widget(GtdTimeline * self,GtdWidget * widget)823 gtd_timeline_set_widget (GtdTimeline *self,
824                          GtdWidget   *widget)
825 {
826   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
827 
828   g_return_if_fail (GTD_IS_TIMELINE (self));
829 
830   if (priv->widget)
831     {
832       remove_tick_callback (self);
833       priv->widget = NULL;
834     }
835 
836   priv->widget = widget;
837 
838   if (priv->is_playing)
839     add_tick_callback (self);
840 }
841 
842 /**
843  * gtd_timeline_start:
844  * @timeline: A #GtdTimeline
845  *
846  * Starts the #GtdTimeline playing.
847  **/
848 void
gtd_timeline_start(GtdTimeline * self)849 gtd_timeline_start (GtdTimeline *self)
850 {
851   GtdTimelinePrivate *priv;
852 
853   g_return_if_fail (GTD_IS_TIMELINE (self));
854 
855   priv = gtd_timeline_get_instance_private (self);
856 
857   if (priv->is_playing)
858     return;
859 
860   if (priv->duration_us == 0)
861     return;
862 
863   priv->delta_us = 0;
864   set_is_playing (self, TRUE);
865 
866   g_signal_emit (self, timeline_signals[STARTED], 0);
867 }
868 
869 /**
870  * gtd_timeline_pause:
871  * @timeline: A #GtdTimeline
872  *
873  * Pauses the #GtdTimeline on current frame
874  **/
875 void
gtd_timeline_pause(GtdTimeline * self)876 gtd_timeline_pause (GtdTimeline *self)
877 {
878   GtdTimelinePrivate *priv;
879 
880   g_return_if_fail (GTD_IS_TIMELINE (self));
881 
882   priv = gtd_timeline_get_instance_private (self);
883 
884   if (!priv->is_playing)
885     return;
886 
887   priv->delta_us = 0;
888   set_is_playing (self, FALSE);
889 
890   g_signal_emit (self, timeline_signals[PAUSED], 0);
891 }
892 
893 /**
894  * gtd_timeline_stop:
895  * @timeline: A #GtdTimeline
896  *
897  * Stops the #GtdTimeline and moves to frame 0
898  **/
899 void
gtd_timeline_stop(GtdTimeline * self)900 gtd_timeline_stop (GtdTimeline *self)
901 {
902   GtdTimelinePrivate *priv;
903   gboolean was_playing;
904 
905   g_return_if_fail (GTD_IS_TIMELINE (self));
906 
907   priv = gtd_timeline_get_instance_private (self);
908 
909   /* we check the is_playing here because pause() will return immediately
910    * if the timeline wasn't playing, so we don't know if it was actually
911    * stopped, and yet we still don't want to emit a ::stopped signal if
912    * the timeline was not playing in the first place.
913    */
914   was_playing = priv->is_playing;
915 
916   gtd_timeline_pause (self);
917   gtd_timeline_rewind (self);
918 
919   if (was_playing)
920     g_signal_emit (self, timeline_signals[STOPPED], 0, FALSE);
921 }
922 
923 /**
924  * gtd_timeline_rewind:
925  * @timeline: A #GtdTimeline
926  *
927  * Rewinds #GtdTimeline to the first frame if its direction is
928  * %GTD_TIMELINE_FORWARD and the last frame if it is
929  * %GTD_TIMELINE_BACKWARD.
930  */
931 void
gtd_timeline_rewind(GtdTimeline * self)932 gtd_timeline_rewind (GtdTimeline *self)
933 {
934   GtdTimelinePrivate *priv;
935 
936   g_return_if_fail (GTD_IS_TIMELINE (self));
937 
938   priv = gtd_timeline_get_instance_private (self);
939 
940   if (priv->direction == GTD_TIMELINE_FORWARD)
941     gtd_timeline_advance (self, 0);
942   else if (priv->direction == GTD_TIMELINE_BACKWARD)
943     gtd_timeline_advance (self, us_to_ms (priv->duration_us));
944 }
945 
946 /**
947  * gtd_timeline_skip:
948  * @timeline: A #GtdTimeline
949  * @msecs: Amount of time to skip
950  *
951  * Advance timeline by the requested time in milliseconds
952  */
953 void
gtd_timeline_skip(GtdTimeline * self,guint msecs)954 gtd_timeline_skip (GtdTimeline *self,
955                    guint        msecs)
956 {
957   GtdTimelinePrivate *priv;
958   gint64 us;
959 
960   g_return_if_fail (GTD_IS_TIMELINE (self));
961 
962   priv = gtd_timeline_get_instance_private (self);
963   us = ms_to_us (msecs);
964 
965   if (priv->direction == GTD_TIMELINE_FORWARD)
966     {
967       priv->elapsed_time_us += us;
968 
969       if (priv->elapsed_time_us > priv->duration_us)
970         priv->elapsed_time_us = 1;
971     }
972   else if (priv->direction == GTD_TIMELINE_BACKWARD)
973     {
974       priv->elapsed_time_us -= us;
975 
976       if (priv->elapsed_time_us < 1)
977         priv->elapsed_time_us = priv->duration_us - 1;
978     }
979 
980   priv->delta_us = 0;
981 }
982 
983 /**
984  * gtd_timeline_advance:
985  * @timeline: A #GtdTimeline
986  * @msecs: Time to advance to
987  *
988  * Advance timeline to the requested point. The point is given as a
989  * time in milliseconds since the timeline started.
990  *
991  * The @timeline will not emit the #GtdTimeline::new-frame
992  * signal for the given time. The first ::new-frame signal after the call to
993  * gtd_timeline_advance() will be emit the skipped markers.
994  */
995 void
gtd_timeline_advance(GtdTimeline * self,guint msecs)996 gtd_timeline_advance (GtdTimeline *self,
997                       guint        msecs)
998 {
999   GtdTimelinePrivate *priv;
1000   gint64 us;
1001 
1002   g_return_if_fail (GTD_IS_TIMELINE (self));
1003 
1004   priv = gtd_timeline_get_instance_private (self);
1005   us = ms_to_us (msecs);
1006 
1007   priv->elapsed_time_us = MIN (us, priv->duration_us);
1008 }
1009 
1010 /**
1011  * gtd_timeline_get_elapsed_time:
1012  * @timeline: A #GtdTimeline
1013  *
1014  * Request the current time position of the timeline.
1015  *
1016  * Return value: current elapsed time in milliseconds.
1017  */
1018 guint
gtd_timeline_get_elapsed_time(GtdTimeline * self)1019 gtd_timeline_get_elapsed_time (GtdTimeline *self)
1020 {
1021   GtdTimelinePrivate *priv;
1022 
1023   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1024 
1025   priv = gtd_timeline_get_instance_private (self);
1026   return us_to_ms (priv->elapsed_time_us);
1027 }
1028 
1029 /**
1030  * gtd_timeline_is_playing:
1031  * @timeline: A #GtdTimeline
1032  *
1033  * Queries state of a #GtdTimeline.
1034  *
1035  * Return value: %TRUE if timeline is currently playing
1036  */
1037 gboolean
gtd_timeline_is_playing(GtdTimeline * self)1038 gtd_timeline_is_playing (GtdTimeline *self)
1039 {
1040   GtdTimelinePrivate *priv;
1041 
1042   g_return_val_if_fail (GTD_IS_TIMELINE (self), FALSE);
1043 
1044   priv = gtd_timeline_get_instance_private (self);
1045   return priv->is_playing;
1046 }
1047 
1048 /**
1049  * gtd_timeline_get_delay:
1050  * @timeline: a #GtdTimeline
1051  *
1052  * Retrieves the delay set using gtd_timeline_set_delay().
1053  *
1054  * Return value: the delay in milliseconds.
1055  */
1056 guint
gtd_timeline_get_delay(GtdTimeline * self)1057 gtd_timeline_get_delay (GtdTimeline *self)
1058 {
1059   GtdTimelinePrivate *priv;
1060 
1061   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1062 
1063   priv = gtd_timeline_get_instance_private (self);
1064   return us_to_ms (priv->delay_us);
1065 }
1066 
1067 /**
1068  * gtd_timeline_set_delay:
1069  * @timeline: a #GtdTimeline
1070  * @msecs: delay in milliseconds
1071  *
1072  * Sets the delay, in milliseconds, before @timeline should start.
1073  */
1074 void
gtd_timeline_set_delay(GtdTimeline * self,guint msecs)1075 gtd_timeline_set_delay (GtdTimeline *self,
1076                         guint        msecs)
1077 {
1078   GtdTimelinePrivate *priv;
1079   gint64 us;
1080 
1081   g_return_if_fail (GTD_IS_TIMELINE (self));
1082 
1083   priv = gtd_timeline_get_instance_private (self);
1084   us = ms_to_us (msecs);
1085 
1086   if (priv->delay_us != us)
1087     {
1088       priv->delay_us = us;
1089       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DELAY]);
1090     }
1091 }
1092 
1093 /**
1094  * gtd_timeline_get_duration:
1095  * @timeline: a #GtdTimeline
1096  *
1097  * Retrieves the duration of a #GtdTimeline in milliseconds.
1098  * See gtd_timeline_set_duration().
1099  *
1100  * Return value: the duration of the timeline, in milliseconds.
1101  */
1102 guint
gtd_timeline_get_duration(GtdTimeline * self)1103 gtd_timeline_get_duration (GtdTimeline *self)
1104 {
1105   GtdTimelinePrivate *priv;
1106 
1107   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1108 
1109   priv = gtd_timeline_get_instance_private (self);
1110 
1111   return us_to_ms (priv->duration_us);
1112 }
1113 
1114 /**
1115  * gtd_timeline_set_duration:
1116  * @timeline: a #GtdTimeline
1117  * @msecs: duration of the timeline in milliseconds
1118  *
1119  * Sets the duration of the timeline, in milliseconds. The speed
1120  * of the timeline depends on the GtdTimeline:fps setting.
1121  */
1122 void
gtd_timeline_set_duration(GtdTimeline * self,guint msecs)1123 gtd_timeline_set_duration (GtdTimeline *self,
1124                            guint        msecs)
1125 {
1126   GtdTimelinePrivate *priv;
1127   gint64 us;
1128 
1129   g_return_if_fail (GTD_IS_TIMELINE (self));
1130   g_return_if_fail (msecs > 0);
1131 
1132   priv = gtd_timeline_get_instance_private (self);
1133   us = ms_to_us (msecs);
1134 
1135   if (priv->duration_us != us)
1136     {
1137       priv->duration_us = us;
1138 
1139       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DURATION]);
1140     }
1141 }
1142 
1143 /**
1144  * gtd_timeline_get_progress:
1145  * @timeline: a #GtdTimeline
1146  *
1147  * The position of the timeline in a normalized [-1, 2] interval.
1148  *
1149  * The return value of this function is determined by the progress
1150  * mode set using gtd_timeline_set_progress_mode(), or by the
1151  * progress function set using gtd_timeline_set_progress_func().
1152  *
1153  * Return value: the normalized current position in the timeline.
1154  */
1155 gdouble
gtd_timeline_get_progress(GtdTimeline * self)1156 gtd_timeline_get_progress (GtdTimeline *self)
1157 {
1158   GtdTimelinePrivate *priv;
1159 
1160   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0.0);
1161 
1162   priv = gtd_timeline_get_instance_private (self);
1163 
1164   return priv->progress_func (self,
1165                               (gdouble) priv->elapsed_time_us,
1166                               (gdouble) priv->duration_us,
1167                               priv->progress_data);
1168 }
1169 
1170 /**
1171  * gtd_timeline_get_direction:
1172  * @timeline: a #GtdTimeline
1173  *
1174  * Retrieves the direction of the timeline set with
1175  * gtd_timeline_set_direction().
1176  *
1177  * Return value: the direction of the timeline
1178  */
1179 GtdTimelineDirection
gtd_timeline_get_direction(GtdTimeline * self)1180 gtd_timeline_get_direction (GtdTimeline *self)
1181 {
1182   GtdTimelinePrivate *priv;
1183 
1184   g_return_val_if_fail (GTD_IS_TIMELINE (self), GTD_TIMELINE_FORWARD);
1185 
1186   priv = gtd_timeline_get_instance_private (self);
1187   return priv->direction;
1188 }
1189 
1190 /**
1191  * gtd_timeline_set_direction:
1192  * @timeline: a #GtdTimeline
1193  * @direction: the direction of the timeline
1194  *
1195  * Sets the direction of @timeline, either %GTD_TIMELINE_FORWARD or
1196  * %GTD_TIMELINE_BACKWARD.
1197  */
1198 void
gtd_timeline_set_direction(GtdTimeline * self,GtdTimelineDirection direction)1199 gtd_timeline_set_direction (GtdTimeline          *self,
1200                             GtdTimelineDirection  direction)
1201 {
1202   GtdTimelinePrivate *priv;
1203 
1204   g_return_if_fail (GTD_IS_TIMELINE (self));
1205 
1206   priv = gtd_timeline_get_instance_private (self);
1207 
1208   if (priv->direction != direction)
1209     {
1210       priv->direction = direction;
1211 
1212       if (priv->elapsed_time_us == 0)
1213         priv->elapsed_time_us = priv->duration_us;
1214 
1215       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DIRECTION]);
1216     }
1217 }
1218 
1219 /**
1220  * gtd_timeline_get_delta:
1221  * @timeline: a #GtdTimeline
1222  *
1223  * Retrieves the amount of time elapsed since the last
1224  * GtdTimeline::new-frame signal.
1225  *
1226  * This function is only useful inside handlers for the ::new-frame
1227  * signal, and its behaviour is undefined if the timeline is not
1228  * playing.
1229  *
1230  * Return value: the amount of time in milliseconds elapsed since the
1231  * last frame
1232  */
1233 guint
gtd_timeline_get_delta(GtdTimeline * self)1234 gtd_timeline_get_delta (GtdTimeline *self)
1235 {
1236   GtdTimelinePrivate *priv;
1237 
1238   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1239 
1240   if (!gtd_timeline_is_playing (self))
1241     return 0;
1242 
1243   priv = gtd_timeline_get_instance_private (self);
1244   return us_to_ms (priv->delta_us);
1245 }
1246 
1247 /**
1248  * gtd_timeline_set_auto_reverse:
1249  * @timeline: a #GtdTimeline
1250  * @reverse: %TRUE if the @timeline should reverse the direction
1251  *
1252  * Sets whether @timeline should reverse the direction after the
1253  * emission of the #GtdTimeline::completed signal.
1254  *
1255  * Setting the #GtdTimeline:auto-reverse property to %TRUE is the
1256  * equivalent of connecting a callback to the #GtdTimeline::completed
1257  * signal and changing the direction of the timeline from that callback;
1258  * for instance, this code:
1259  *
1260  * |[
1261  * static void
1262  * reverse_timeline (GtdTimeline *self)
1263  * {
1264  *   GtdTimelineDirection dir = gtd_timeline_get_direction (self);
1265  *
1266  *   if (dir == GTD_TIMELINE_FORWARD)
1267  *     dir = GTD_TIMELINE_BACKWARD;
1268  *   else
1269  *     dir = GTD_TIMELINE_FORWARD;
1270  *
1271  *   gtd_timeline_set_direction (self, dir);
1272  * }
1273  * ...
1274  *   timeline = gtd_timeline_new (1000);
1275  *   gtd_timeline_set_repeat_count (self, -1);
1276  *   g_signal_connect (self, "completed",
1277  *                     G_CALLBACK (reverse_timeline),
1278  *                     NULL);
1279  * ]|
1280  *
1281  * can be effectively replaced by:
1282  *
1283  * |[
1284  *   timeline = gtd_timeline_new (1000);
1285  *   gtd_timeline_set_repeat_count (self, -1);
1286  *   gtd_timeline_set_auto_reverse (self);
1287  * ]|
1288  */
1289 void
gtd_timeline_set_auto_reverse(GtdTimeline * self,gboolean reverse)1290 gtd_timeline_set_auto_reverse (GtdTimeline *self,
1291                                gboolean     reverse)
1292 {
1293   GtdTimelinePrivate *priv;
1294 
1295   g_return_if_fail (GTD_IS_TIMELINE (self));
1296 
1297   reverse = !!reverse;
1298 
1299   priv = gtd_timeline_get_instance_private (self);
1300 
1301   if (priv->auto_reverse != reverse)
1302     {
1303       priv->auto_reverse = reverse;
1304       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_AUTO_REVERSE]);
1305     }
1306 }
1307 
1308 /**
1309  * gtd_timeline_get_auto_reverse:
1310  * @timeline: a #GtdTimeline
1311  *
1312  * Retrieves the value set by gtd_timeline_set_auto_reverse().
1313  *
1314  * Return value: %TRUE if the timeline should automatically reverse, and
1315  *   %FALSE otherwise
1316  */
1317 gboolean
gtd_timeline_get_auto_reverse(GtdTimeline * self)1318 gtd_timeline_get_auto_reverse (GtdTimeline *self)
1319 {
1320   GtdTimelinePrivate *priv;
1321 
1322   g_return_val_if_fail (GTD_IS_TIMELINE (self), FALSE);
1323 
1324   priv = gtd_timeline_get_instance_private (self);
1325   return priv->auto_reverse;
1326 }
1327 
1328 /**
1329  * gtd_timeline_set_repeat_count:
1330  * @timeline: a #GtdTimeline
1331  * @count: the number of times the timeline should repeat
1332  *
1333  * Sets the number of times the @timeline should repeat.
1334  *
1335  * If @count is 0, the timeline never repeats.
1336  *
1337  * If @count is -1, the timeline will always repeat until
1338  * it's stopped.
1339  */
1340 void
gtd_timeline_set_repeat_count(GtdTimeline * self,gint count)1341 gtd_timeline_set_repeat_count (GtdTimeline *self,
1342                                gint         count)
1343 {
1344   GtdTimelinePrivate *priv;
1345 
1346   g_return_if_fail (GTD_IS_TIMELINE (self));
1347   g_return_if_fail (count >= -1);
1348 
1349   priv = gtd_timeline_get_instance_private (self);
1350 
1351   if (priv->repeat_count != count)
1352     {
1353       priv->repeat_count = count;
1354       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_REPEAT_COUNT]);
1355     }
1356 }
1357 
1358 /**
1359  * gtd_timeline_get_repeat_count:
1360  * @timeline: a #GtdTimeline
1361  *
1362  * Retrieves the number set using gtd_timeline_set_repeat_count().
1363  *
1364  * Return value: the number of repeats
1365  */
1366 gint
gtd_timeline_get_repeat_count(GtdTimeline * self)1367 gtd_timeline_get_repeat_count (GtdTimeline *self)
1368 {
1369   GtdTimelinePrivate *priv;
1370 
1371   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1372 
1373   priv = gtd_timeline_get_instance_private (self);
1374   return priv->repeat_count;
1375 }
1376 
1377 /**
1378  * gtd_timeline_set_progress_func:
1379  * @timeline: a #GtdTimeline
1380  * @func: (scope notified) (allow-none): a progress function, or %NULL
1381  * @data: (closure): data to pass to @func
1382  * @notify: a function to be called when the progress function is removed
1383  *    or the timeline is disposed
1384  *
1385  * Sets a custom progress function for @timeline. The progress function will
1386  * be called by gtd_timeline_get_progress() and will be used to compute
1387  * the progress value based on the elapsed time and the total duration of the
1388  * timeline.
1389  *
1390  * If @func is not %NULL, the #GtdTimeline:progress-mode property will
1391  * be set to %GTD_CUSTOM_MODE.
1392  *
1393  * If @func is %NULL, any previously set progress function will be unset, and
1394  * the #GtdTimeline:progress-mode property will be set to %GTD_EASE_LINEAR.
1395  */
1396 void
gtd_timeline_set_progress_func(GtdTimeline * self,GtdTimelineProgressFunc func,gpointer data,GDestroyNotify notify)1397 gtd_timeline_set_progress_func (GtdTimeline             *self,
1398                                 GtdTimelineProgressFunc  func,
1399                                 gpointer                 data,
1400                                 GDestroyNotify           notify)
1401 {
1402   GtdTimelinePrivate *priv;
1403 
1404   g_return_if_fail (GTD_IS_TIMELINE (self));
1405 
1406   priv = gtd_timeline_get_instance_private (self);
1407 
1408   if (priv->progress_notify != NULL)
1409     priv->progress_notify (priv->progress_data);
1410 
1411   priv->progress_func = func;
1412   priv->progress_data = data;
1413   priv->progress_notify = notify;
1414 
1415   if (priv->progress_func != NULL)
1416     priv->progress_mode = GTD_CUSTOM_MODE;
1417   else
1418     priv->progress_mode = GTD_EASE_LINEAR;
1419 
1420   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PROGRESS_MODE]);
1421 }
1422 
1423 /**
1424  * gtd_timeline_set_progress_mode:
1425  * @timeline: a #GtdTimeline
1426  * @mode: the progress mode, as a #GtdEaseMode
1427  *
1428  * Sets the progress function using a value from the #GtdEaseMode
1429  * enumeration. The @mode cannot be %GTD_CUSTOM_MODE or bigger than
1430  * %GTD_ANIMATION_LAST.
1431  */
1432 void
gtd_timeline_set_progress_mode(GtdTimeline * self,GtdEaseMode mode)1433 gtd_timeline_set_progress_mode (GtdTimeline *self,
1434                                 GtdEaseMode  mode)
1435 {
1436   GtdTimelinePrivate *priv;
1437 
1438   g_return_if_fail (GTD_IS_TIMELINE (self));
1439   g_return_if_fail (mode < GTD_EASE_LAST);
1440   g_return_if_fail (mode != GTD_CUSTOM_MODE);
1441 
1442   priv = gtd_timeline_get_instance_private (self);
1443 
1444   if (priv->progress_mode == mode)
1445     return;
1446 
1447   if (priv->progress_notify != NULL)
1448     priv->progress_notify (priv->progress_data);
1449 
1450   priv->progress_mode = mode;
1451   priv->progress_func = timeline_progress_func;
1452   priv->progress_data = NULL;
1453   priv->progress_notify = NULL;
1454 
1455   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PROGRESS_MODE]);
1456 }
1457 
1458 /**
1459  * gtd_timeline_get_progress_mode:
1460  * @timeline: a #GtdTimeline
1461  *
1462  * Retrieves the progress mode set using gtd_timeline_set_progress_mode()
1463  * or gtd_timeline_set_progress_func().
1464  *
1465  * Return value: a #GtdEaseMode
1466  */
1467 GtdEaseMode
gtd_timeline_get_progress_mode(GtdTimeline * self)1468 gtd_timeline_get_progress_mode (GtdTimeline *self)
1469 {
1470   GtdTimelinePrivate *priv;
1471 
1472   g_return_val_if_fail (GTD_IS_TIMELINE (self), GTD_EASE_LINEAR);
1473 
1474   priv = gtd_timeline_get_instance_private (self);
1475   return priv->progress_mode;
1476 }
1477 
1478 /**
1479  * gtd_timeline_get_duration_hint:
1480  * @timeline: a #GtdTimeline
1481  *
1482  * Retrieves the full duration of the @timeline, taking into account the
1483  * current value of the #GtdTimeline:repeat-count property.
1484  *
1485  * If the #GtdTimeline:repeat-count property is set to -1, this function
1486  * will return %G_MAXINT64.
1487  *
1488  * The returned value is to be considered a hint, and it's only valid
1489  * as long as the @timeline hasn't been changed.
1490  *
1491  * Return value: the full duration of the #GtdTimeline
1492  */
1493 gint64
gtd_timeline_get_duration_hint(GtdTimeline * self)1494 gtd_timeline_get_duration_hint (GtdTimeline *self)
1495 {
1496   GtdTimelinePrivate *priv;
1497   gint64 duration_ms;
1498 
1499   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1500 
1501   priv = gtd_timeline_get_instance_private (self);
1502   duration_ms = us_to_ms (priv->duration_us);
1503 
1504   if (priv->repeat_count == 0)
1505     return duration_ms;
1506   else if (priv->repeat_count < 0)
1507     return G_MAXINT64;
1508   else
1509     return priv->repeat_count * duration_ms;
1510 }
1511 
1512 /**
1513  * gtd_timeline_get_current_repeat:
1514  * @timeline: a #GtdTimeline
1515  *
1516  * Retrieves the current repeat for a timeline.
1517  *
1518  * Repeats start at 0.
1519  *
1520  * Return value: the current repeat
1521  */
1522 gint
gtd_timeline_get_current_repeat(GtdTimeline * self)1523 gtd_timeline_get_current_repeat (GtdTimeline *self)
1524 {
1525   GtdTimelinePrivate *priv;
1526 
1527   g_return_val_if_fail (GTD_IS_TIMELINE (self), 0);
1528 
1529   priv = gtd_timeline_get_instance_private (self);
1530   return priv->current_repeat;
1531 }
1532 
1533 /**
1534  * gtd_timeline_get_widget:
1535  * @timeline: a #GtdTimeline
1536  *
1537  * Get the widget the timeline is associated with.
1538  *
1539  * Returns: (transfer none): the associated #GtdWidget
1540  */
1541 GtdWidget *
gtd_timeline_get_widget(GtdTimeline * self)1542 gtd_timeline_get_widget (GtdTimeline *self)
1543 {
1544   GtdTimelinePrivate *priv = gtd_timeline_get_instance_private (self);
1545 
1546   return priv->widget;
1547 }
1548