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