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