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