]> Pileus Git - ~andy/gtk/blob - gtk/gtktimeline.c
18d3f950f51464b8200e542bcdd5a0818d49e0d4
[~andy/gtk] / gtk / gtktimeline.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2007 Carlos Garnacho <carlos@imendio.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <gtk/gtktimeline.h>
21 #include <gtk/gtktypebuiltins.h>
22 #include <gtk/gtksettings.h>
23 #include <math.h>
24
25 #define MSECS_PER_SEC 1000
26 #define FRAME_INTERVAL(nframes) (MSECS_PER_SEC / nframes)
27 #define DEFAULT_FPS 30
28
29 typedef struct GtkTimelinePriv GtkTimelinePriv;
30
31 struct GtkTimelinePriv
32 {
33   guint duration;
34   guint fps;
35   guint source_id;
36
37   GTimer *timer;
38
39   gdouble progress;
40   gdouble last_progress;
41
42   GdkScreen *screen;
43
44   GtkTimelineProgressType progress_type;
45
46   guint animations_enabled : 1;
47   guint loop               : 1;
48   guint direction          : 1;
49 };
50
51 enum {
52   PROP_0,
53   PROP_FPS,
54   PROP_DURATION,
55   PROP_LOOP,
56   PROP_DIRECTION,
57   PROP_SCREEN
58 };
59
60 enum {
61   STARTED,
62   PAUSED,
63   FINISHED,
64   FRAME,
65   LAST_SIGNAL
66 };
67
68 static guint signals [LAST_SIGNAL] = { 0, };
69
70
71 static void  gtk_timeline_set_property  (GObject         *object,
72                                          guint            prop_id,
73                                          const GValue    *value,
74                                          GParamSpec      *pspec);
75 static void  gtk_timeline_get_property  (GObject         *object,
76                                          guint            prop_id,
77                                          GValue          *value,
78                                          GParamSpec      *pspec);
79 static void  _gtk_timeline_finalize     (GObject *object);
80
81
82 G_DEFINE_TYPE (GtkTimeline, _gtk_timeline, G_TYPE_OBJECT)
83
84
85 static void
86 _gtk_timeline_class_init (GtkTimelineClass *klass)
87 {
88   GObjectClass *object_class = G_OBJECT_CLASS (klass);
89
90   object_class->set_property = gtk_timeline_set_property;
91   object_class->get_property = gtk_timeline_get_property;
92   object_class->finalize = _gtk_timeline_finalize;
93
94   g_object_class_install_property (object_class,
95                                    PROP_FPS,
96                                    g_param_spec_uint ("fps",
97                                                       "FPS",
98                                                       "Frames per second for the timeline",
99                                                       1, G_MAXUINT,
100                                                       DEFAULT_FPS,
101                                                       G_PARAM_READWRITE));
102   g_object_class_install_property (object_class,
103                                    PROP_DURATION,
104                                    g_param_spec_uint ("duration",
105                                                       "Animation Duration",
106                                                       "Animation Duration",
107                                                       0, G_MAXUINT,
108                                                       0,
109                                                       G_PARAM_READWRITE));
110   g_object_class_install_property (object_class,
111                                    PROP_LOOP,
112                                    g_param_spec_boolean ("loop",
113                                                          "Loop",
114                                                          "Whether the timeline loops or not",
115                                                          FALSE,
116                                                          G_PARAM_READWRITE));
117   g_object_class_install_property (object_class,
118                                    PROP_SCREEN,
119                                    g_param_spec_object ("screen",
120                                                         "Screen",
121                                                         "Screen to get the settings from",
122                                                         GDK_TYPE_SCREEN,
123                                                         G_PARAM_READWRITE));
124
125   signals[STARTED] =
126     g_signal_new ("started",
127                   G_TYPE_FROM_CLASS (object_class),
128                   G_SIGNAL_RUN_LAST,
129                   G_STRUCT_OFFSET (GtkTimelineClass, started),
130                   NULL, NULL,
131                   g_cclosure_marshal_VOID__VOID,
132                   G_TYPE_NONE, 0);
133
134   signals[PAUSED] =
135     g_signal_new ("paused",
136                   G_TYPE_FROM_CLASS (object_class),
137                   G_SIGNAL_RUN_LAST,
138                   G_STRUCT_OFFSET (GtkTimelineClass, paused),
139                   NULL, NULL,
140                   g_cclosure_marshal_VOID__VOID,
141                   G_TYPE_NONE, 0);
142
143   signals[FINISHED] =
144     g_signal_new ("finished",
145                   G_TYPE_FROM_CLASS (object_class),
146                   G_SIGNAL_RUN_LAST,
147                   G_STRUCT_OFFSET (GtkTimelineClass, finished),
148                   NULL, NULL,
149                   g_cclosure_marshal_VOID__VOID,
150                   G_TYPE_NONE, 0);
151
152   signals[FRAME] =
153     g_signal_new ("frame",
154                   G_TYPE_FROM_CLASS (object_class),
155                   G_SIGNAL_RUN_LAST,
156                   G_STRUCT_OFFSET (GtkTimelineClass, frame),
157                   NULL, NULL,
158                   g_cclosure_marshal_VOID__DOUBLE,
159                   G_TYPE_NONE, 1,
160                   G_TYPE_DOUBLE);
161
162   g_type_class_add_private (klass, sizeof (GtkTimelinePriv));
163 }
164
165 static void
166 _gtk_timeline_init (GtkTimeline *timeline)
167 {
168   GtkTimelinePriv *priv;
169
170   priv = timeline->priv = G_TYPE_INSTANCE_GET_PRIVATE (timeline,
171                                                        GTK_TYPE_TIMELINE,
172                                                        GtkTimelinePriv);
173
174   priv->fps = DEFAULT_FPS;
175   priv->duration = 0.0;
176   priv->direction = GTK_TIMELINE_DIRECTION_FORWARD;
177   priv->screen = gdk_screen_get_default ();
178
179   priv->last_progress = 0;
180 }
181
182 static void
183 gtk_timeline_set_property (GObject      *object,
184                            guint         prop_id,
185                            const GValue *value,
186                            GParamSpec   *pspec)
187 {
188   GtkTimeline *timeline;
189
190   timeline = GTK_TIMELINE (object);
191
192   switch (prop_id)
193     {
194     case PROP_FPS:
195       _gtk_timeline_set_fps (timeline, g_value_get_uint (value));
196       break;
197     case PROP_DURATION:
198       _gtk_timeline_set_duration (timeline, g_value_get_uint (value));
199       break;
200     case PROP_LOOP:
201       _gtk_timeline_set_loop (timeline, g_value_get_boolean (value));
202       break;
203     case PROP_DIRECTION:
204       _gtk_timeline_set_direction (timeline, g_value_get_enum (value));
205       break;
206     case PROP_SCREEN:
207       _gtk_timeline_set_screen (timeline,
208                                 GDK_SCREEN (g_value_get_object (value)));
209       break;
210     default:
211       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
212     }
213 }
214
215 static void
216 gtk_timeline_get_property (GObject    *object,
217                            guint       prop_id,
218                            GValue     *value,
219                            GParamSpec *pspec)
220 {
221   GtkTimeline *timeline;
222   GtkTimelinePriv *priv;
223
224   timeline = GTK_TIMELINE (object);
225   priv = timeline->priv;
226
227   switch (prop_id)
228     {
229     case PROP_FPS:
230       g_value_set_uint (value, priv->fps);
231       break;
232     case PROP_DURATION:
233       g_value_set_uint (value, priv->duration);
234       break;
235     case PROP_LOOP:
236       g_value_set_boolean (value, priv->loop);
237       break;
238     case PROP_DIRECTION:
239       g_value_set_enum (value, priv->direction);
240       break;
241     case PROP_SCREEN:
242       g_value_set_object (value, priv->screen);
243       break;
244     default:
245       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
246     }
247 }
248
249 static void
250 _gtk_timeline_finalize (GObject *object)
251 {
252   GtkTimelinePriv *priv;
253   GtkTimeline *timeline;
254
255   timeline = (GtkTimeline *) object;
256   priv = timeline->priv;
257
258   if (priv->source_id)
259     {
260       g_source_remove (priv->source_id);
261       priv->source_id = 0;
262     }
263
264   if (priv->timer)
265     g_timer_destroy (priv->timer);
266
267   G_OBJECT_CLASS (_gtk_timeline_parent_class)->finalize (object);
268 }
269
270 static gdouble
271 calculate_progress (gdouble                 linear_progress,
272                     GtkTimelineProgressType progress_type)
273 {
274   gdouble progress;
275
276   progress = linear_progress;
277
278   switch (progress_type)
279     {
280     case GTK_TIMELINE_PROGRESS_LINEAR:
281       break;
282     case GTK_TIMELINE_PROGRESS_EASE_IN_OUT:
283       progress *= 2;
284
285       if (progress < 1)
286         progress = pow (progress, 3) / 2;
287       else
288         progress = (pow (progress - 2, 3) + 2) / 2;
289
290       break;
291     case GTK_TIMELINE_PROGRESS_EASE:
292       progress = (sin ((progress - 0.5) * G_PI) + 1) / 2;
293       break;
294     case GTK_TIMELINE_PROGRESS_EASE_IN:
295       progress = pow (progress, 3);
296       break;
297     case GTK_TIMELINE_PROGRESS_EASE_OUT:
298       progress = pow (progress - 1, 3) + 1;
299       break;
300     default:
301       g_warning ("Timeline progress type not implemented");
302     }
303
304   return progress;
305 }
306
307 static gboolean
308 gtk_timeline_run_frame (GtkTimeline *timeline)
309 {
310   GtkTimelinePriv *priv;
311   gdouble delta_progress, progress;
312   guint elapsed_time;
313
314   priv = timeline->priv;
315
316   elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000);
317   g_timer_start (priv->timer);
318
319   if (priv->animations_enabled)
320     {
321       delta_progress = (gdouble) elapsed_time / priv->duration;
322       progress = priv->last_progress;
323
324       if (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD)
325         progress -= delta_progress;
326       else
327         progress += delta_progress;
328
329       priv->last_progress = progress;
330
331       progress = CLAMP (progress, 0., 1.);
332     }
333   else
334     progress = (priv->direction == GTK_TIMELINE_DIRECTION_FORWARD) ? 1.0 : 0.0;
335
336   priv->progress = progress;
337   g_signal_emit (timeline, signals [FRAME], 0,
338                  calculate_progress (progress, priv->progress_type));
339
340   if ((priv->direction == GTK_TIMELINE_DIRECTION_FORWARD && progress == 1.0) ||
341       (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD && progress == 0.0))
342     {
343       if (!priv->loop)
344         {
345           if (priv->source_id)
346             {
347               g_source_remove (priv->source_id);
348               priv->source_id = 0;
349             }
350           g_timer_stop (priv->timer);
351           g_signal_emit (timeline, signals [FINISHED], 0);
352           return FALSE;
353         }
354       else
355         _gtk_timeline_rewind (timeline);
356     }
357
358   return TRUE;
359 }
360
361 /**
362  * gtk_timeline_new:
363  * @duration: duration in milliseconds for the timeline
364  *
365  * Creates a new #GtkTimeline with the specified number of frames.
366  *
367  * Return Value: the newly created #GtkTimeline
368  **/
369 GtkTimeline *
370 _gtk_timeline_new (guint duration)
371 {
372   return g_object_new (GTK_TYPE_TIMELINE,
373                        "duration", duration,
374                        NULL);
375 }
376
377 GtkTimeline *
378 _gtk_timeline_new_for_screen (guint      duration,
379                               GdkScreen *screen)
380 {
381   return g_object_new (GTK_TYPE_TIMELINE,
382                        "duration", duration,
383                        "screen", screen,
384                        NULL);
385 }
386
387 /**
388  * gtk_timeline_start:
389  * @timeline: A #GtkTimeline
390  *
391  * Runs the timeline from the current frame.
392  **/
393 void
394 _gtk_timeline_start (GtkTimeline *timeline)
395 {
396   GtkTimelinePriv *priv;
397   GtkSettings *settings;
398   gboolean enable_animations = FALSE;
399
400   g_return_if_fail (GTK_IS_TIMELINE (timeline));
401
402   priv = timeline->priv;
403
404   if (!priv->source_id)
405     {
406       if (priv->timer)
407         g_timer_continue (priv->timer);
408       else
409         priv->timer = g_timer_new ();
410
411       /* sanity check */
412       g_assert (priv->fps > 0);
413
414       if (priv->screen)
415         {
416           settings = gtk_settings_get_for_screen (priv->screen);
417           g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
418         }
419
420       priv->animations_enabled = (enable_animations == TRUE);
421
422       g_signal_emit (timeline, signals [STARTED], 0);
423
424       if (enable_animations)
425         priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
426                                                    (GSourceFunc) gtk_timeline_run_frame,
427                                                    timeline);
428       else
429         priv->source_id = gdk_threads_add_idle ((GSourceFunc) gtk_timeline_run_frame,
430                                                 timeline);
431     }
432 }
433
434 /**
435  * gtk_timeline_pause:
436  * @timeline: A #GtkTimeline
437  *
438  * Pauses the timeline.
439  **/
440 void
441 _gtk_timeline_pause (GtkTimeline *timeline)
442 {
443   GtkTimelinePriv *priv;
444
445   g_return_if_fail (GTK_IS_TIMELINE (timeline));
446
447   priv = timeline->priv;
448
449   if (priv->source_id)
450     {
451       g_timer_stop (priv->timer);
452       g_source_remove (priv->source_id);
453       priv->source_id = 0;
454       g_signal_emit (timeline, signals [PAUSED], 0);
455     }
456 }
457
458 /**
459  * gtk_timeline_rewind:
460  * @timeline: A #GtkTimeline
461  *
462  * Rewinds the timeline.
463  **/
464 void
465 _gtk_timeline_rewind (GtkTimeline *timeline)
466 {
467   GtkTimelinePriv *priv;
468
469   g_return_if_fail (GTK_IS_TIMELINE (timeline));
470
471   priv = timeline->priv;
472
473   if (_gtk_timeline_get_direction (timeline) != GTK_TIMELINE_DIRECTION_FORWARD)
474     priv->progress = priv->last_progress = 1.;
475   else
476     priv->progress = priv->last_progress = 0.;
477
478   /* reset timer */
479   if (priv->timer)
480     {
481       g_timer_start (priv->timer);
482
483       if (!priv->source_id)
484         g_timer_stop (priv->timer);
485     }
486 }
487
488 /**
489  * gtk_timeline_is_running:
490  * @timeline: A #GtkTimeline
491  *
492  * Returns whether the timeline is running or not.
493  *
494  * Return Value: %TRUE if the timeline is running
495  **/
496 gboolean
497 _gtk_timeline_is_running (GtkTimeline *timeline)
498 {
499   GtkTimelinePriv *priv;
500
501   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
502
503   priv = timeline->priv;
504
505   return (priv->source_id != 0);
506 }
507
508 /**
509  * gtk_timeline_get_fps:
510  * @timeline: A #GtkTimeline
511  *
512  * Returns the number of frames per second.
513  *
514  * Return Value: frames per second
515  **/
516 guint
517 _gtk_timeline_get_fps (GtkTimeline *timeline)
518 {
519   GtkTimelinePriv *priv;
520
521   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 1);
522
523   priv = timeline->priv;
524   return priv->fps;
525 }
526
527 /**
528  * gtk_timeline_set_fps:
529  * @timeline: A #GtkTimeline
530  * @fps: frames per second
531  *
532  * Sets the number of frames per second that
533  * the timeline will play.
534  **/
535 void
536 _gtk_timeline_set_fps (GtkTimeline *timeline,
537                       guint        fps)
538 {
539   GtkTimelinePriv *priv;
540
541   g_return_if_fail (GTK_IS_TIMELINE (timeline));
542   g_return_if_fail (fps > 0);
543
544   priv = timeline->priv;
545
546   priv->fps = fps;
547
548   if (_gtk_timeline_is_running (timeline))
549     {
550       g_source_remove (priv->source_id);
551       priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
552                                                  (GSourceFunc) gtk_timeline_run_frame,
553                                                  timeline);
554     }
555
556   g_object_notify (G_OBJECT (timeline), "fps");
557 }
558
559 /**
560  * gtk_timeline_get_loop:
561  * @timeline: A #GtkTimeline
562  *
563  * Returns whether the timeline loops to the
564  * beginning when it has reached the end.
565  *
566  * Return Value: %TRUE if the timeline loops
567  **/
568 gboolean
569 _gtk_timeline_get_loop (GtkTimeline *timeline)
570 {
571   GtkTimelinePriv *priv;
572
573   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
574
575   priv = timeline->priv;
576   return priv->loop;
577 }
578
579 /**
580  * gtk_timeline_set_loop:
581  * @timeline: A #GtkTimeline
582  * @loop: %TRUE to make the timeline loop
583  *
584  * Sets whether the timeline loops to the beginning
585  * when it has reached the end.
586  **/
587 void
588 _gtk_timeline_set_loop (GtkTimeline *timeline,
589                         gboolean     loop)
590 {
591   GtkTimelinePriv *priv;
592
593   g_return_if_fail (GTK_IS_TIMELINE (timeline));
594
595   priv = timeline->priv;
596
597   if (loop != priv->loop)
598     {
599       priv->loop = loop;
600       g_object_notify (G_OBJECT (timeline), "loop");
601     }
602 }
603
604 void
605 _gtk_timeline_set_duration (GtkTimeline *timeline,
606                             guint        duration)
607 {
608   GtkTimelinePriv *priv;
609
610   g_return_if_fail (GTK_IS_TIMELINE (timeline));
611
612   priv = timeline->priv;
613
614   if (duration != priv->duration)
615     {
616       priv->duration = duration;
617       g_object_notify (G_OBJECT (timeline), "duration");
618     }
619 }
620
621 guint
622 _gtk_timeline_get_duration (GtkTimeline *timeline)
623 {
624   GtkTimelinePriv *priv;
625
626   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
627
628   priv = timeline->priv;
629
630   return priv->duration;
631 }
632
633 /**
634  * gtk_timeline_set_direction:
635  * @timeline: A #GtkTimeline
636  * @direction: direction
637  *
638  * Sets the direction of the timeline.
639  **/
640 void
641 _gtk_timeline_set_direction (GtkTimeline          *timeline,
642                              GtkTimelineDirection  direction)
643 {
644   GtkTimelinePriv *priv;
645
646   g_return_if_fail (GTK_IS_TIMELINE (timeline));
647
648   priv = timeline->priv;
649   priv->direction = direction;
650 }
651
652 /**
653  * gtk_timeline_get_direction:
654  * @timeline: A #GtkTimeline
655  *
656  * Returns the direction of the timeline.
657  *
658  * Return Value: direction
659  **/
660 GtkTimelineDirection
661 _gtk_timeline_get_direction (GtkTimeline *timeline)
662 {
663   GtkTimelinePriv *priv;
664
665   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_DIRECTION_FORWARD);
666
667   priv = timeline->priv;
668   return priv->direction;
669 }
670
671 void
672 _gtk_timeline_set_screen (GtkTimeline *timeline,
673                           GdkScreen   *screen)
674 {
675   GtkTimelinePriv *priv;
676
677   g_return_if_fail (GTK_IS_TIMELINE (timeline));
678   g_return_if_fail (GDK_IS_SCREEN (screen));
679
680   priv = timeline->priv;
681
682   if (priv->screen)
683     g_object_unref (priv->screen);
684
685   priv->screen = g_object_ref (screen);
686
687   g_object_notify (G_OBJECT (timeline), "screen");
688 }
689
690 GdkScreen *
691 _gtk_timeline_get_screen (GtkTimeline *timeline)
692 {
693   GtkTimelinePriv *priv;
694
695   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), NULL);
696
697   priv = timeline->priv;
698   return priv->screen;
699 }
700
701 gdouble
702 _gtk_timeline_get_progress (GtkTimeline *timeline)
703 {
704   GtkTimelinePriv *priv;
705
706   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0.);
707
708   priv = timeline->priv;
709   return calculate_progress (priv->progress, priv->progress_type);
710 }
711
712 GtkTimelineProgressType
713 _gtk_timeline_get_progress_type (GtkTimeline *timeline)
714 {
715   GtkTimelinePriv *priv;
716
717   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_PROGRESS_LINEAR);
718
719   priv = timeline->priv;
720   return priv->progress_type;
721 }
722
723 void
724 _gtk_timeline_set_progress_type (GtkTimeline             *timeline,
725                                  GtkTimelineProgressType  progress_type)
726 {
727   GtkTimelinePriv *priv;
728
729   g_return_if_fail (GTK_IS_TIMELINE (timeline));
730
731   priv = timeline->priv;
732   priv->progress_type = progress_type;
733 }