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