]> Pileus Git - ~andy/gtk/blob - gtk/gtktimeline.c
b4c034d700137e76f161d1fa5ef24f44f1477d3b
[~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   GtkTimelinePriv *priv;
190
191   timeline = GTK_TIMELINE (object);
192   priv = timeline->priv;
193
194   switch (prop_id)
195     {
196     case PROP_FPS:
197       gtk_timeline_set_fps (timeline, g_value_get_uint (value));
198       break;
199     case PROP_DURATION:
200       gtk_timeline_set_duration (timeline, g_value_get_uint (value));
201       break;
202     case PROP_LOOP:
203       gtk_timeline_set_loop (timeline, g_value_get_boolean (value));
204       break;
205     case PROP_DIRECTION:
206       gtk_timeline_set_direction (timeline, g_value_get_enum (value));
207       break;
208     case PROP_SCREEN:
209       gtk_timeline_set_screen (timeline,
210                                GDK_SCREEN (g_value_get_object (value)));
211       break;
212     default:
213       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214     }
215 }
216
217 static void
218 gtk_timeline_get_property (GObject    *object,
219                            guint       prop_id,
220                            GValue     *value,
221                            GParamSpec *pspec)
222 {
223   GtkTimeline *timeline;
224   GtkTimelinePriv *priv;
225
226   timeline = GTK_TIMELINE (object);
227   priv = timeline->priv;
228
229   switch (prop_id)
230     {
231     case PROP_FPS:
232       g_value_set_uint (value, priv->fps);
233       break;
234     case PROP_DURATION:
235       g_value_set_uint (value, priv->duration);
236       break;
237     case PROP_LOOP:
238       g_value_set_boolean (value, priv->loop);
239       break;
240     case PROP_DIRECTION:
241       g_value_set_enum (value, priv->direction);
242       break;
243     case PROP_SCREEN:
244       g_value_set_object (value, priv->screen);
245       break;
246     default:
247       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
248     }
249 }
250
251 static void
252 gtk_timeline_finalize (GObject *object)
253 {
254   GtkTimelinePriv *priv;
255   GtkTimeline *timeline;
256
257   timeline = (GtkTimeline *) object;
258   priv = timeline->priv;
259
260   if (priv->source_id)
261     {
262       g_source_remove (priv->source_id);
263       priv->source_id = 0;
264     }
265
266   if (priv->timer)
267     g_timer_destroy (priv->timer);
268
269   G_OBJECT_CLASS (gtk_timeline_parent_class)->finalize (object);
270 }
271
272 gdouble
273 calculate_progress (gdouble                 linear_progress,
274                     GtkTimelineProgressType progress_type)
275 {
276   gdouble progress;
277
278   progress = linear_progress;
279
280   switch (progress_type)
281     {
282     case GTK_TIMELINE_PROGRESS_LINEAR:
283       break;
284     case GTK_TIMELINE_PROGRESS_EASE_IN_OUT:
285       progress *= 2;
286
287       if (progress < 1)
288         progress = pow (progress, 3) / 2;
289       else
290         progress = (pow (progress - 2, 3) + 2) / 2;
291
292       break;
293     case GTK_TIMELINE_PROGRESS_EASE:
294       progress = (sinf ((progress - 0.5) * G_PI) + 1) / 2;
295       break;
296     case GTK_TIMELINE_PROGRESS_EASE_IN:
297       progress = pow (progress, 3);
298       break;
299     case GTK_TIMELINE_PROGRESS_EASE_OUT:
300       progress = pow (progress - 1, 3) + 1;
301       break;
302     default:
303       g_warning ("Timeline progress type not implemented");
304     }
305
306   return progress;
307 }
308
309 static gboolean
310 gtk_timeline_run_frame (GtkTimeline *timeline)
311 {
312   GtkTimelinePriv *priv;
313   gdouble delta_progress, progress;
314   guint elapsed_time;
315
316   priv = timeline->priv;
317
318   elapsed_time = (guint) (g_timer_elapsed (priv->timer, NULL) * 1000);
319   g_timer_start (priv->timer);
320
321   if (priv->animations_enabled)
322     {
323       delta_progress = (gdouble) elapsed_time / priv->duration;
324       progress = priv->last_progress;
325
326       if (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD)
327         progress -= delta_progress;
328       else
329         progress += delta_progress;
330
331       priv->last_progress = progress;
332
333       progress = CLAMP (progress, 0., 1.);
334     }
335   else
336     progress = (priv->direction == GTK_TIMELINE_DIRECTION_FORWARD) ? 1.0 : 0.0;
337
338   priv->progress = progress;
339   g_signal_emit (timeline, signals [FRAME], 0,
340                  calculate_progress (progress, priv->progress_type));
341
342   if ((priv->direction == GTK_TIMELINE_DIRECTION_FORWARD && progress == 1.0) ||
343       (priv->direction == GTK_TIMELINE_DIRECTION_BACKWARD && progress == 0.0))
344     {
345       if (!priv->loop)
346         {
347           if (priv->source_id)
348             {
349               g_source_remove (priv->source_id);
350               priv->source_id = 0;
351             }
352           g_timer_stop (priv->timer);
353           g_signal_emit (timeline, signals [FINISHED], 0);
354           return FALSE;
355         }
356       else
357         gtk_timeline_rewind (timeline);
358     }
359
360   return TRUE;
361 }
362
363 /**
364  * gtk_timeline_new:
365  * @duration: duration in milliseconds for the timeline
366  *
367  * Creates a new #GtkTimeline with the specified number of frames.
368  *
369  * Return Value: the newly created #GtkTimeline
370  **/
371 GtkTimeline *
372 gtk_timeline_new (guint duration)
373 {
374   return g_object_new (GTK_TYPE_TIMELINE,
375                        "duration", duration,
376                        NULL);
377 }
378
379 GtkTimeline *
380 gtk_timeline_new_for_screen (guint      duration,
381                              GdkScreen *screen)
382 {
383   return g_object_new (GTK_TYPE_TIMELINE,
384                        "duration", duration,
385                        "screen", screen,
386                        NULL);
387 }
388
389 /**
390  * gtk_timeline_start:
391  * @timeline: A #GtkTimeline
392  *
393  * Runs the timeline from the current frame.
394  **/
395 void
396 gtk_timeline_start (GtkTimeline *timeline)
397 {
398   GtkTimelinePriv *priv;
399   GtkSettings *settings;
400   gboolean enable_animations = FALSE;
401
402   g_return_if_fail (GTK_IS_TIMELINE (timeline));
403
404   priv = timeline->priv;
405
406   if (!priv->source_id)
407     {
408       if (priv->timer)
409         g_timer_continue (priv->timer);
410       else
411         priv->timer = g_timer_new ();
412
413       /* sanity check */
414       g_assert (priv->fps > 0);
415
416       if (priv->screen)
417         {
418           settings = gtk_settings_get_for_screen (priv->screen);
419           g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
420         }
421
422       priv->animations_enabled = (enable_animations == TRUE);
423
424       g_signal_emit (timeline, signals [STARTED], 0);
425
426       if (enable_animations)
427         priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
428                                                    (GSourceFunc) gtk_timeline_run_frame,
429                                                    timeline);
430       else
431         priv->source_id = gdk_threads_add_idle ((GSourceFunc) gtk_timeline_run_frame,
432                                                 timeline);
433     }
434 }
435
436 /**
437  * gtk_timeline_pause:
438  * @timeline: A #GtkTimeline
439  *
440  * Pauses the timeline.
441  **/
442 void
443 gtk_timeline_pause (GtkTimeline *timeline)
444 {
445   GtkTimelinePriv *priv;
446
447   g_return_if_fail (GTK_IS_TIMELINE (timeline));
448
449   priv = timeline->priv;
450
451   if (priv->source_id)
452     {
453       g_timer_stop (priv->timer);
454       g_source_remove (priv->source_id);
455       priv->source_id = 0;
456       g_signal_emit (timeline, signals [PAUSED], 0);
457     }
458 }
459
460 /**
461  * gtk_timeline_rewind:
462  * @timeline: A #GtkTimeline
463  *
464  * Rewinds the timeline.
465  **/
466 void
467 gtk_timeline_rewind (GtkTimeline *timeline)
468 {
469   GtkTimelinePriv *priv;
470
471   g_return_if_fail (GTK_IS_TIMELINE (timeline));
472
473   priv = timeline->priv;
474
475   if (gtk_timeline_get_direction(timeline) != GTK_TIMELINE_DIRECTION_FORWARD)
476     priv->progress = priv->last_progress = 1.;
477   else
478     priv->progress = priv->last_progress = 0.;
479
480   /* reset timer */
481   if (priv->timer)
482     {
483       g_timer_start (priv->timer);
484
485       if (!priv->source_id)
486         g_timer_stop (priv->timer);
487     }
488 }
489
490 /**
491  * gtk_timeline_is_running:
492  * @timeline: A #GtkTimeline
493  *
494  * Returns whether the timeline is running or not.
495  *
496  * Return Value: %TRUE if the timeline is running
497  **/
498 gboolean
499 gtk_timeline_is_running (GtkTimeline *timeline)
500 {
501   GtkTimelinePriv *priv;
502
503   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
504
505   priv = timeline->priv;
506
507   return (priv->source_id != 0);
508 }
509
510 /**
511  * gtk_timeline_get_fps:
512  * @timeline: A #GtkTimeline
513  *
514  * Returns the number of frames per second.
515  *
516  * Return Value: frames per second
517  **/
518 guint
519 gtk_timeline_get_fps (GtkTimeline *timeline)
520 {
521   GtkTimelinePriv *priv;
522
523   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 1);
524
525   priv = timeline->priv;
526   return priv->fps;
527 }
528
529 /**
530  * gtk_timeline_set_fps:
531  * @timeline: A #GtkTimeline
532  * @fps: frames per second
533  *
534  * Sets the number of frames per second that
535  * the timeline will play.
536  **/
537 void
538 gtk_timeline_set_fps (GtkTimeline *timeline,
539                       guint        fps)
540 {
541   GtkTimelinePriv *priv;
542
543   g_return_if_fail (GTK_IS_TIMELINE (timeline));
544   g_return_if_fail (fps > 0);
545
546   priv = timeline->priv;
547
548   priv->fps = fps;
549
550   if (gtk_timeline_is_running (timeline))
551     {
552       g_source_remove (priv->source_id);
553       priv->source_id = gdk_threads_add_timeout (FRAME_INTERVAL (priv->fps),
554                                                  (GSourceFunc) gtk_timeline_run_frame,
555                                                  timeline);
556     }
557
558   g_object_notify (G_OBJECT (timeline), "fps");
559 }
560
561 /**
562  * gtk_timeline_get_loop:
563  * @timeline: A #GtkTimeline
564  *
565  * Returns whether the timeline loops to the
566  * beginning when it has reached the end.
567  *
568  * Return Value: %TRUE if the timeline loops
569  **/
570 gboolean
571 gtk_timeline_get_loop (GtkTimeline *timeline)
572 {
573   GtkTimelinePriv *priv;
574
575   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
576
577   priv = timeline->priv;
578   return priv->loop;
579 }
580
581 /**
582  * gtk_timeline_set_loop:
583  * @timeline: A #GtkTimeline
584  * @loop: %TRUE to make the timeline loop
585  *
586  * Sets whether the timeline loops to the beginning
587  * when it has reached the end.
588  **/
589 void
590 gtk_timeline_set_loop (GtkTimeline *timeline,
591                        gboolean     loop)
592 {
593   GtkTimelinePriv *priv;
594
595   g_return_if_fail (GTK_IS_TIMELINE (timeline));
596
597   priv = timeline->priv;
598
599   if (loop != priv->loop)
600     {
601       priv->loop = loop;
602       g_object_notify (G_OBJECT (timeline), "loop");
603     }
604 }
605
606 void
607 gtk_timeline_set_duration (GtkTimeline *timeline,
608                            guint        duration)
609 {
610   GtkTimelinePriv *priv;
611
612   g_return_if_fail (GTK_IS_TIMELINE (timeline));
613
614   priv = timeline->priv;
615
616   if (duration != priv->duration)
617     {
618       priv->duration = duration;
619       g_object_notify (G_OBJECT (timeline), "duration");
620     }
621 }
622
623 guint
624 gtk_timeline_get_duration (GtkTimeline *timeline)
625 {
626   GtkTimelinePriv *priv;
627
628   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
629
630   priv = timeline->priv;
631
632   return priv->duration;
633 }
634
635 /**
636  * gtk_timeline_set_direction:
637  * @timeline: A #GtkTimeline
638  * @direction: direction
639  *
640  * Sets the direction of the timeline.
641  **/
642 void
643 gtk_timeline_set_direction (GtkTimeline          *timeline,
644                             GtkTimelineDirection  direction)
645 {
646   GtkTimelinePriv *priv;
647
648   g_return_if_fail (GTK_IS_TIMELINE (timeline));
649
650   priv = timeline->priv;
651   priv->direction = direction;
652 }
653
654 /**
655  * gtk_timeline_get_direction:
656  * @timeline: A #GtkTimeline
657  *
658  * Returns the direction of the timeline.
659  *
660  * Return Value: direction
661  **/
662 GtkTimelineDirection
663 gtk_timeline_get_direction (GtkTimeline *timeline)
664 {
665   GtkTimelinePriv *priv;
666
667   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_DIRECTION_FORWARD);
668
669   priv = timeline->priv;
670   return priv->direction;
671 }
672
673 void
674 gtk_timeline_set_screen (GtkTimeline *timeline,
675                          GdkScreen   *screen)
676 {
677   GtkTimelinePriv *priv;
678
679   g_return_if_fail (GTK_IS_TIMELINE (timeline));
680   g_return_if_fail (GDK_IS_SCREEN (screen));
681
682   priv = timeline->priv;
683
684   if (priv->screen)
685     g_object_unref (priv->screen);
686
687   priv->screen = g_object_ref (screen);
688
689   g_object_notify (G_OBJECT (timeline), "screen");
690 }
691
692 GdkScreen *
693 gtk_timeline_get_screen (GtkTimeline *timeline)
694 {
695   GtkTimelinePriv *priv;
696
697   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), NULL);
698
699   priv = timeline->priv;
700   return priv->screen;
701 }
702
703 gdouble
704 gtk_timeline_get_progress (GtkTimeline *timeline)
705 {
706   GtkTimelinePriv *priv;
707
708   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0.);
709
710   priv = timeline->priv;
711   return calculate_progress (priv->progress, priv->progress_type);
712 }
713
714 GtkTimelineProgressType
715 gtk_timeline_get_progress_type (GtkTimeline *timeline)
716 {
717   GtkTimelinePriv *priv;
718
719   g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_PROGRESS_LINEAR);
720
721   priv = timeline->priv;
722   return priv->progress_type;
723 }
724
725 void
726 gtk_timeline_set_progress_type (GtkTimeline             *timeline,
727                                 GtkTimelineProgressType  progress_type)
728 {
729   GtkTimelinePriv *priv;
730
731   g_return_if_fail (GTK_IS_TIMELINE (timeline));
732
733   priv = timeline->priv;
734   priv->progress_type = progress_type;
735 }
736
737 #define __GTK_TIMELINE_C__
738 #include "gtkaliasdef.c"