1 /* dzl-animation.c
2 *
3 * Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
4 *
5 * This file is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License as published by the Free
7 * Software Foundation; either version 2.1 of the License, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13 * License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-animation"
20
21 #include "config.h"
22
23 #include <glib/gi18n.h>
24 #include <gobject/gvaluecollector.h>
25 #include <gdk/gdk.h>
26 #include <gtk/gtk.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "dzl-animation.h"
31 #include "dzl-frame-source.h"
32
33 #define FALLBACK_FRAME_RATE 60
34
35 typedef gdouble (*AlphaFunc) (gdouble offset);
36 typedef void (*TweenFunc) (const GValue *begin,
37 const GValue *end,
38 GValue *value,
39 gdouble offset);
40
41 typedef struct
42 {
43 gboolean is_child; /* Does GParamSpec belong to parent widget */
44 GParamSpec *pspec; /* GParamSpec of target property */
45 GValue begin; /* Begin value in animation */
46 GValue end; /* End value in animation */
47 } Tween;
48
49
50 struct _DzlAnimation
51 {
52 GInitiallyUnowned parent_instance;
53
54 gpointer target; /* Target object to animate */
55 gint64 begin_time; /* Time in which animation started */
56 gint64 end_time; /* Deadline for the animation */
57 guint duration_msec; /* Duration in milliseconds */
58 guint mode; /* Tween mode */
59 gulong tween_handler; /* GSource or signal handler */
60 gulong after_paint_handler; /* signal handler */
61 gdouble last_offset; /* Track our last offset */
62 GArray *tweens; /* Array of tweens to perform */
63 GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */
64 GDestroyNotify notify; /* Notify callback */
65 gpointer notify_data; /* Data for notify */
66 guint stop_called : 1;
67 };
68
69 G_DEFINE_TYPE (DzlAnimation, dzl_animation, G_TYPE_INITIALLY_UNOWNED)
70
71 enum {
72 PROP_0,
73 PROP_DURATION,
74 PROP_FRAME_CLOCK,
75 PROP_MODE,
76 PROP_TARGET,
77 LAST_PROP
78 };
79
80
81 enum {
82 TICK,
83 LAST_SIGNAL
84 };
85
86
87 /*
88 * Helper macros.
89 */
90 #define LAST_FUNDAMENTAL 64
91 #define TWEEN(type) \
92 static void \
93 tween_ ## type (const GValue * begin, \
94 const GValue * end, \
95 GValue * value, \
96 gdouble offset) \
97 { \
98 g ## type x = g_value_get_ ## type (begin); \
99 g ## type y = g_value_get_ ## type (end); \
100 g_value_set_ ## type (value, x + ((y - x) * offset)); \
101 }
102
103
104 /*
105 * Globals.
106 */
107 static AlphaFunc alpha_funcs[DZL_ANIMATION_LAST];
108 static gboolean debug;
109 static GParamSpec *properties[LAST_PROP];
110 static guint signals[LAST_SIGNAL];
111 static TweenFunc tween_funcs[LAST_FUNDAMENTAL];
112 static guint slow_down_factor = 1;
113
114
115 /*
116 * Tweeners for basic types.
117 */
118 TWEEN (int);
119 TWEEN (uint);
120 TWEEN (long);
121 TWEEN (ulong);
122 TWEEN (float);
123 TWEEN (double);
124
125
126 /**
127 * dzl_animation_alpha_ease_in_cubic:
128 * @offset: (in): The position within the animation; 0.0 to 1.0.
129 *
130 * An alpha function to transform the offset within the animation.
131 * @DZL_ANIMATION_CUBIC means the valu ewill be transformed into
132 * cubic acceleration (x * x * x).
133 */
134 static gdouble
dzl_animation_alpha_ease_in_cubic(gdouble offset)135 dzl_animation_alpha_ease_in_cubic (gdouble offset)
136 {
137 return offset * offset * offset;
138 }
139
140
141 static gdouble
dzl_animation_alpha_ease_out_cubic(gdouble offset)142 dzl_animation_alpha_ease_out_cubic (gdouble offset)
143 {
144 gdouble p = offset - 1.0;
145
146 return p * p * p + 1.0;
147 }
148
149 static gdouble
dzl_animation_alpha_ease_in_out_cubic(gdouble offset)150 dzl_animation_alpha_ease_in_out_cubic (gdouble offset)
151 {
152 gdouble p = offset * 2.0;
153
154 if (p < 1.0)
155 return 0.5 * p * p * p;
156 p -= 2.0;
157 return 0.5 * (p * p * p + 2.0);
158 }
159
160
161 /**
162 * dzl_animation_alpha_linear:
163 * @offset: (in): The position within the animation; 0.0 to 1.0.
164 *
165 * An alpha function to transform the offset within the animation.
166 * @DZL_ANIMATION_LINEAR means no tranformation will be made.
167 *
168 * Returns: @offset.
169 * Side effects: None.
170 */
171 static gdouble
dzl_animation_alpha_linear(gdouble offset)172 dzl_animation_alpha_linear (gdouble offset)
173 {
174 return offset;
175 }
176
177
178 /**
179 * dzl_animation_alpha_ease_in_quad:
180 * @offset: (in): The position within the animation; 0.0 to 1.0.
181 *
182 * An alpha function to transform the offset within the animation.
183 * @DZL_ANIMATION_EASE_IN_QUAD means that the value will be transformed
184 * into a quadratic acceleration.
185 *
186 * Returns: A tranformation of @offset.
187 * Side effects: None.
188 */
189 static gdouble
dzl_animation_alpha_ease_in_quad(gdouble offset)190 dzl_animation_alpha_ease_in_quad (gdouble offset)
191 {
192 return offset * offset;
193 }
194
195
196 /**
197 * dzl_animation_alpha_ease_out_quad:
198 * @offset: (in): The position within the animation; 0.0 to 1.0.
199 *
200 * An alpha function to transform the offset within the animation.
201 * @DZL_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
202 * into a quadratic deceleration.
203 *
204 * Returns: A tranformation of @offset.
205 * Side effects: None.
206 */
207 static gdouble
dzl_animation_alpha_ease_out_quad(gdouble offset)208 dzl_animation_alpha_ease_out_quad (gdouble offset)
209 {
210 return -1.0 * offset * (offset - 2.0);
211 }
212
213
214 /**
215 * dzl_animation_alpha_ease_in_out_quad:
216 * @offset: (in): The position within the animation; 0.0 to 1.0.
217 *
218 * An alpha function to transform the offset within the animation.
219 * @DZL_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
220 * into a quadratic acceleration for the first half, and quadratic
221 * deceleration the second half.
222 *
223 * Returns: A tranformation of @offset.
224 * Side effects: None.
225 */
226 static gdouble
dzl_animation_alpha_ease_in_out_quad(gdouble offset)227 dzl_animation_alpha_ease_in_out_quad (gdouble offset)
228 {
229 offset *= 2.0;
230 if (offset < 1.0)
231 return 0.5 * offset * offset;
232 offset -= 1.0;
233 return -0.5 * (offset * (offset - 2.0) - 1.0);
234 }
235
236
237 /**
238 * dzl_animation_load_begin_values:
239 * @animation: (in): A #DzlAnimation.
240 *
241 * Load the begin values for all the properties we are about to
242 * animate.
243 *
244 * Side effects: None.
245 */
246 static void
dzl_animation_load_begin_values(DzlAnimation * animation)247 dzl_animation_load_begin_values (DzlAnimation *animation)
248 {
249 GtkContainer *container;
250 Tween *tween;
251 guint i;
252
253 g_assert (DZL_IS_ANIMATION (animation));
254
255 for (i = 0; i < animation->tweens->len; i++)
256 {
257 tween = &g_array_index (animation->tweens, Tween, i);
258 g_value_reset (&tween->begin);
259 if (tween->is_child)
260 {
261 container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
262 gtk_container_child_get_property (container,
263 animation->target,
264 tween->pspec->name,
265 &tween->begin);
266 }
267 else
268 {
269 g_object_get_property (animation->target,
270 tween->pspec->name,
271 &tween->begin);
272 }
273 }
274 }
275
276
277 /**
278 * dzl_animation_unload_begin_values:
279 * @animation: (in): A #DzlAnimation.
280 *
281 * Unloads the begin values for the animation. This might be particularly
282 * useful once we support pointer types.
283 *
284 * Side effects: None.
285 */
286 static void
dzl_animation_unload_begin_values(DzlAnimation * animation)287 dzl_animation_unload_begin_values (DzlAnimation *animation)
288 {
289 Tween *tween;
290 guint i;
291
292 g_assert (DZL_IS_ANIMATION (animation));
293
294 for (i = 0; i < animation->tweens->len; i++)
295 {
296 tween = &g_array_index (animation->tweens, Tween, i);
297 g_value_reset (&tween->begin);
298 }
299 }
300
301
302 /**
303 * dzl_animation_get_offset:
304 * @animation: A #DzlAnimation.
305 * @frame_time: the time to present the frame, or 0 for current timing.
306 *
307 * Retrieves the position within the animation from 0.0 to 1.0. This
308 * value is calculated using the msec of the beginning of the animation
309 * and the current time.
310 *
311 * Returns: The offset of the animation from 0.0 to 1.0.
312 */
313 static gdouble
dzl_animation_get_offset(DzlAnimation * animation,gint64 frame_time)314 dzl_animation_get_offset (DzlAnimation *animation,
315 gint64 frame_time)
316 {
317 g_assert (DZL_IS_ANIMATION (animation));
318
319 if (frame_time == 0)
320 {
321 if (animation->frame_clock != NULL)
322 frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
323 else
324 frame_time = g_get_monotonic_time ();
325 }
326
327 frame_time = CLAMP (frame_time, animation->begin_time, animation->end_time);
328
329 /* Check end_time first in case end_time == begin_time */
330 if (frame_time == animation->end_time)
331 return 1.0;
332 else if (frame_time == animation->begin_time)
333 return 0.0;
334
335 return (frame_time - animation->begin_time) / (gdouble)(animation->duration_msec * 1000L);
336 }
337
338
339 /**
340 * dzl_animation_update_property:
341 * @animation: (in): A #DzlAnimation.
342 * @target: (in): A #GObject.
343 * @tween: (in): a #Tween containing the property.
344 * @value: (in): The new value for the property.
345 *
346 * Updates the value of a property on an object using @value.
347 *
348 * Side effects: The property of @target is updated.
349 */
350 static void
dzl_animation_update_property(DzlAnimation * animation,gpointer target,Tween * tween,const GValue * value)351 dzl_animation_update_property (DzlAnimation *animation,
352 gpointer target,
353 Tween *tween,
354 const GValue *value)
355 {
356 g_assert (DZL_IS_ANIMATION (animation));
357 g_assert (G_IS_OBJECT (target));
358 g_assert (tween);
359 g_assert (value);
360
361 g_object_set_property (target, tween->pspec->name, value);
362 }
363
364
365 /**
366 * dzl_animation_update_child_property:
367 * @animation: (in): A #DzlAnimation.
368 * @target: (in): A #GObject.
369 * @tween: (in): A #Tween containing the property.
370 * @value: (in): The new value for the property.
371 *
372 * Updates the value of the parent widget of the target to @value.
373 *
374 * Side effects: The property of @target<!-- -->'s parent widget is updated.
375 */
376 static void
dzl_animation_update_child_property(DzlAnimation * animation,gpointer target,Tween * tween,const GValue * value)377 dzl_animation_update_child_property (DzlAnimation *animation,
378 gpointer target,
379 Tween *tween,
380 const GValue *value)
381 {
382 GtkWidget *parent;
383
384 g_assert (DZL_IS_ANIMATION (animation));
385 g_assert (G_IS_OBJECT (target));
386 g_assert (tween);
387 g_assert (value);
388
389 parent = gtk_widget_get_parent (GTK_WIDGET (target));
390 gtk_container_child_set_property (GTK_CONTAINER (parent),
391 target,
392 tween->pspec->name,
393 value);
394 }
395
396
397 /**
398 * dzl_animation_get_value_at_offset:
399 * @animation: (in): A #DzlAnimation.
400 * @offset: (in): The offset in the animation from 0.0 to 1.0.
401 * @tween: (in): A #Tween containing the property.
402 * @value: (out): A #GValue in which to store the property.
403 *
404 * Retrieves a value for a particular position within the animation.
405 *
406 * Side effects: None.
407 */
408 static void
dzl_animation_get_value_at_offset(DzlAnimation * animation,gdouble offset,Tween * tween,GValue * value)409 dzl_animation_get_value_at_offset (DzlAnimation *animation,
410 gdouble offset,
411 Tween *tween,
412 GValue *value)
413 {
414 g_assert (DZL_IS_ANIMATION (animation));
415 g_assert (tween != NULL);
416 g_assert (value != NULL);
417 g_assert (value->g_type == tween->pspec->value_type);
418
419 if (value->g_type < LAST_FUNDAMENTAL)
420 {
421 /*
422 * If you hit the following assertion, you need to add a function
423 * to create the new value at the given offset.
424 */
425 g_assert (tween_funcs[value->g_type]);
426 tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
427 }
428 else
429 {
430 /*
431 * TODO: Support complex transitions.
432 */
433 if (offset >= 1.0)
434 g_value_copy (&tween->end, value);
435 }
436 }
437
438 static void
dzl_animation_set_frame_clock(DzlAnimation * animation,GdkFrameClock * frame_clock)439 dzl_animation_set_frame_clock (DzlAnimation *animation,
440 GdkFrameClock *frame_clock)
441 {
442 if (animation->frame_clock != frame_clock)
443 {
444 g_clear_object (&animation->frame_clock);
445 animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
446 }
447 }
448
449 static void
dzl_animation_set_target(DzlAnimation * animation,gpointer target)450 dzl_animation_set_target (DzlAnimation *animation,
451 gpointer target)
452 {
453 g_assert (!animation->target);
454
455 animation->target = g_object_ref (target);
456
457 if (GTK_IS_WIDGET (animation->target))
458 dzl_animation_set_frame_clock (animation,
459 gtk_widget_get_frame_clock (animation->target));
460 }
461
462
463 /**
464 * dzl_animation_tick:
465 * @animation: (in): A #DzlAnimation.
466 *
467 * Moves the object properties to the next position in the animation.
468 *
469 * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
470 * Side effects: None.
471 */
472 static gboolean
dzl_animation_tick(DzlAnimation * animation,gdouble offset)473 dzl_animation_tick (DzlAnimation *animation,
474 gdouble offset)
475 {
476 gdouble alpha;
477 GValue value = { 0 };
478 Tween *tween;
479 guint i;
480
481 g_assert (DZL_IS_ANIMATION (animation));
482
483 if (offset == animation->last_offset)
484 return offset < 1.0;
485
486 alpha = alpha_funcs[animation->mode](offset);
487
488 /*
489 * Update property values.
490 */
491 for (i = 0; i < animation->tweens->len; i++)
492 {
493 tween = &g_array_index (animation->tweens, Tween, i);
494 g_value_init (&value, tween->pspec->value_type);
495 dzl_animation_get_value_at_offset (animation, alpha, tween, &value);
496 if (!tween->is_child)
497 {
498 dzl_animation_update_property (animation,
499 animation->target,
500 tween,
501 &value);
502 }
503 else
504 {
505 dzl_animation_update_child_property (animation,
506 animation->target,
507 tween,
508 &value);
509 }
510 g_value_unset (&value);
511 }
512
513 /*
514 * Notify anyone interested in the tick signal.
515 */
516 g_signal_emit (animation, signals[TICK], 0);
517
518 /*
519 * Flush any outstanding events to the graphics server (in the case of X).
520 */
521 #if !GTK_CHECK_VERSION (3, 13, 0)
522 if (GTK_IS_WIDGET (animation->target))
523 {
524 GdkWindow *window;
525
526 if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
527 gdk_window_flush (window);
528 }
529 #endif
530
531 animation->last_offset = offset;
532
533 return offset < 1.0;
534 }
535
536
537 /**
538 * dzl_animation_timeout_cb:
539 * @user_data: (in): A #DzlAnimation.
540 *
541 * Timeout from the main loop to move to the next step of the animation.
542 *
543 * Returns: %TRUE until the animation has completed; otherwise %FALSE.
544 * Side effects: None.
545 */
546 static gboolean
dzl_animation_timeout_cb(gpointer user_data)547 dzl_animation_timeout_cb (gpointer user_data)
548 {
549 DzlAnimation *animation = user_data;
550 gboolean ret;
551 gdouble offset;
552
553 offset = dzl_animation_get_offset (animation, 0);
554
555 if (!(ret = dzl_animation_tick (animation, offset)))
556 dzl_animation_stop (animation);
557
558 return ret;
559 }
560
561
562 static gboolean
dzl_animation_widget_tick_cb(GdkFrameClock * frame_clock,DzlAnimation * animation)563 dzl_animation_widget_tick_cb (GdkFrameClock *frame_clock,
564 DzlAnimation *animation)
565 {
566 gboolean ret = G_SOURCE_REMOVE;
567
568 g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
569 g_assert (DZL_IS_ANIMATION (animation));
570
571 if (animation->tween_handler)
572 {
573 gdouble offset;
574
575 offset = dzl_animation_get_offset (animation, 0);
576
577 if (!(ret = dzl_animation_tick (animation, offset)))
578 dzl_animation_stop (animation);
579 }
580
581 return ret;
582 }
583
584
585 static void
dzl_animation_widget_after_paint_cb(GdkFrameClock * frame_clock,DzlAnimation * animation)586 dzl_animation_widget_after_paint_cb (GdkFrameClock *frame_clock,
587 DzlAnimation *animation)
588 {
589 gint64 base_time;
590 gint64 interval;
591 gint64 next_frame_time;
592 gdouble offset;
593
594 g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
595 g_assert (DZL_IS_ANIMATION (animation));
596
597 base_time = gdk_frame_clock_get_frame_time (frame_clock);
598 gdk_frame_clock_get_refresh_info (frame_clock, base_time, &interval, &next_frame_time);
599
600 offset = dzl_animation_get_offset (animation, next_frame_time);
601
602 dzl_animation_tick (animation, offset);
603 }
604
605
606 /**
607 * dzl_animation_start:
608 * @animation: (in): A #DzlAnimation.
609 *
610 * Start the animation. When the animation stops, the internal reference will
611 * be dropped and the animation may be finalized.
612 *
613 * Side effects: None.
614 */
615 void
dzl_animation_start(DzlAnimation * animation)616 dzl_animation_start (DzlAnimation *animation)
617 {
618 g_return_if_fail (DZL_IS_ANIMATION (animation));
619 g_return_if_fail (!animation->tween_handler);
620
621 g_object_ref_sink (animation);
622 dzl_animation_load_begin_values (animation);
623
624 /*
625 * We want the real current time instead of the GdkFrameClocks current time
626 * because if the clock was asleep, it could be innaccurate.
627 */
628
629 if (animation->frame_clock)
630 {
631 animation->begin_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
632 animation->end_time = animation->begin_time + (animation->duration_msec * 1000L);
633 animation->tween_handler =
634 g_signal_connect_object (animation->frame_clock,
635 "update",
636 G_CALLBACK (dzl_animation_widget_tick_cb),
637 animation,
638 0);
639 animation->after_paint_handler =
640 g_signal_connect_object (animation->frame_clock,
641 "after-paint",
642 G_CALLBACK (dzl_animation_widget_after_paint_cb),
643 animation,
644 0);
645 gdk_frame_clock_begin_updating (animation->frame_clock);
646 }
647 else
648 {
649 animation->begin_time = g_get_monotonic_time ();
650 animation->end_time = animation->begin_time + (animation->duration_msec * 1000L);
651 animation->tween_handler = dzl_frame_source_add (FALLBACK_FRAME_RATE,
652 dzl_animation_timeout_cb,
653 animation);
654 }
655 }
656
657
658 static void
dzl_animation_notify(DzlAnimation * self)659 dzl_animation_notify (DzlAnimation *self)
660 {
661 g_assert (DZL_IS_ANIMATION (self));
662
663 if (self->notify != NULL)
664 {
665 GDestroyNotify notify = self->notify;
666 gpointer data = self->notify_data;
667
668 self->notify = NULL;
669 self->notify_data = NULL;
670
671 notify (data);
672 }
673 }
674
675
676 /**
677 * dzl_animation_stop:
678 * @animation: (nullable): A #DzlAnimation.
679 *
680 * Stops a running animation. The internal reference to the animation is
681 * dropped and therefore may cause the object to finalize.
682 *
683 * As a convenience, this function accepts %NULL for @animation but
684 * does nothing if that should occur.
685 */
686 void
dzl_animation_stop(DzlAnimation * animation)687 dzl_animation_stop (DzlAnimation *animation)
688 {
689 if (animation == NULL)
690 return;
691
692 g_return_if_fail (DZL_IS_ANIMATION (animation));
693
694 if (animation->stop_called)
695 return;
696
697 animation->stop_called = TRUE;
698
699 if (animation->tween_handler)
700 {
701 if (animation->frame_clock)
702 {
703 gdk_frame_clock_end_updating (animation->frame_clock);
704 g_signal_handler_disconnect (animation->frame_clock, animation->tween_handler);
705 g_signal_handler_disconnect (animation->frame_clock, animation->after_paint_handler);
706 animation->tween_handler = 0;
707 }
708 else
709 {
710 g_source_remove (animation->tween_handler);
711 animation->tween_handler = 0;
712 }
713 dzl_animation_unload_begin_values (animation);
714 dzl_animation_notify (animation);
715 g_object_unref (animation);
716 }
717 }
718
719
720 /**
721 * dzl_animation_add_property:
722 * @animation: (in): A #DzlAnimation.
723 * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
724 * @value: (in): The new value for the property at the end of the animation.
725 *
726 * Adds a new property to the set of properties to be animated during the
727 * lifetime of the animation.
728 *
729 * Side effects: None.
730 */
731 void
dzl_animation_add_property(DzlAnimation * animation,GParamSpec * pspec,const GValue * value)732 dzl_animation_add_property (DzlAnimation *animation,
733 GParamSpec *pspec,
734 const GValue *value)
735 {
736 Tween tween = { 0 };
737 GType type;
738
739 g_return_if_fail (DZL_IS_ANIMATION (animation));
740 g_return_if_fail (pspec != NULL);
741 g_return_if_fail (value != NULL);
742 g_return_if_fail (value->g_type);
743 g_return_if_fail (animation->target);
744 g_return_if_fail (!animation->tween_handler);
745
746 type = G_TYPE_FROM_INSTANCE (animation->target);
747 tween.is_child = !g_type_is_a (type, pspec->owner_type);
748 if (tween.is_child)
749 {
750 if (!GTK_IS_WIDGET (animation->target))
751 {
752 g_critical (_("Cannot locate property %s in class %s"),
753 pspec->name, g_type_name (type));
754 return;
755 }
756 }
757
758 tween.pspec = g_param_spec_ref (pspec);
759 g_value_init (&tween.begin, pspec->value_type);
760 g_value_init (&tween.end, pspec->value_type);
761 g_value_copy (value, &tween.end);
762 g_array_append_val (animation->tweens, tween);
763 }
764
765
766 /**
767 * dzl_animation_dispose:
768 * @object: (in): A #DzlAnimation.
769 *
770 * Releases any object references the animation contains.
771 *
772 * Side effects: None.
773 */
774 static void
dzl_animation_dispose(GObject * object)775 dzl_animation_dispose (GObject *object)
776 {
777 DzlAnimation *self = DZL_ANIMATION (object);
778
779 g_clear_object (&self->target);
780 g_clear_object (&self->frame_clock);
781
782 G_OBJECT_CLASS (dzl_animation_parent_class)->dispose (object);
783 }
784
785
786 /**
787 * dzl_animation_finalize:
788 * @object: (in): A #DzlAnimation.
789 *
790 * Finalizes the object and releases any resources allocated.
791 *
792 * Side effects: None.
793 */
794 static void
dzl_animation_finalize(GObject * object)795 dzl_animation_finalize (GObject *object)
796 {
797 DzlAnimation *self = DZL_ANIMATION (object);
798 Tween *tween;
799 guint i;
800
801 for (i = 0; i < self->tweens->len; i++)
802 {
803 tween = &g_array_index (self->tweens, Tween, i);
804 g_value_unset (&tween->begin);
805 g_value_unset (&tween->end);
806 g_param_spec_unref (tween->pspec);
807 }
808
809 g_array_unref (self->tweens);
810
811 G_OBJECT_CLASS (dzl_animation_parent_class)->finalize (object);
812 }
813
814
815 /**
816 * dzl_animation_set_property:
817 * @object: (in): A #GObject.
818 * @prop_id: (in): The property identifier.
819 * @value: (in): The given property.
820 * @pspec: (in): A #ParamSpec.
821 *
822 * Set a given #GObject property.
823 */
824 static void
dzl_animation_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)825 dzl_animation_set_property (GObject *object,
826 guint prop_id,
827 const GValue *value,
828 GParamSpec *pspec)
829 {
830 DzlAnimation *animation = DZL_ANIMATION (object);
831
832 switch (prop_id)
833 {
834 case PROP_DURATION:
835 animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
836 break;
837
838 case PROP_FRAME_CLOCK:
839 dzl_animation_set_frame_clock (animation, g_value_get_object (value));
840 break;
841
842 case PROP_MODE:
843 animation->mode = g_value_get_enum (value);
844 break;
845
846 case PROP_TARGET:
847 dzl_animation_set_target (animation, g_value_get_object (value));
848 break;
849
850 default:
851 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
852 }
853 }
854
855
856 /**
857 * dzl_animation_class_init:
858 * @klass: (in): A #DzlAnimationClass.
859 *
860 * Initializes the GObjectClass.
861 *
862 * Side effects: Properties, signals, and vtables are initialized.
863 */
864 static void
dzl_animation_class_init(DzlAnimationClass * klass)865 dzl_animation_class_init (DzlAnimationClass *klass)
866 {
867 GObjectClass *object_class;
868 const gchar *slow_down_factor_env;
869
870 debug = !!g_getenv ("DZL_ANIMATION_DEBUG");
871 slow_down_factor_env = g_getenv ("DZL_ANIMATION_SLOW_DOWN_FACTOR");
872
873 if (slow_down_factor_env)
874 slow_down_factor = MAX (1, atoi (slow_down_factor_env));
875
876 object_class = G_OBJECT_CLASS (klass);
877 object_class->dispose = dzl_animation_dispose;
878 object_class->finalize = dzl_animation_finalize;
879 object_class->set_property = dzl_animation_set_property;
880
881 /**
882 * DzlAnimation:duration:
883 *
884 * The "duration" property is the total number of milliseconds that the
885 * animation should run before being completed.
886 */
887 properties[PROP_DURATION] =
888 g_param_spec_uint ("duration",
889 "Duration",
890 "The duration of the animation",
891 0,
892 G_MAXUINT,
893 250,
894 (G_PARAM_WRITABLE |
895 G_PARAM_CONSTRUCT_ONLY |
896 G_PARAM_STATIC_STRINGS));
897
898 properties[PROP_FRAME_CLOCK] =
899 g_param_spec_object ("frame-clock",
900 "Frame Clock",
901 "An optional frame-clock to synchronize with.",
902 GDK_TYPE_FRAME_CLOCK,
903 (G_PARAM_WRITABLE |
904 G_PARAM_CONSTRUCT_ONLY |
905 G_PARAM_STATIC_STRINGS));
906
907 /**
908 * DzlAnimation:mode:
909 *
910 * The "mode" property is the Alpha function that should be used to
911 * determine the offset within the animation based on the current
912 * offset in the animations duration.
913 */
914 properties[PROP_MODE] =
915 g_param_spec_enum ("mode",
916 "Mode",
917 "The animation mode",
918 DZL_TYPE_ANIMATION_MODE,
919 DZL_ANIMATION_LINEAR,
920 (G_PARAM_WRITABLE |
921 G_PARAM_CONSTRUCT_ONLY |
922 G_PARAM_STATIC_STRINGS));
923
924 /**
925 * DzlAnimation:target:
926 *
927 * The "target" property is the #GObject that should have its properties
928 * animated.
929 */
930 properties[PROP_TARGET] =
931 g_param_spec_object ("target",
932 "Target",
933 "The target of the animation",
934 G_TYPE_OBJECT,
935 (G_PARAM_WRITABLE |
936 G_PARAM_CONSTRUCT_ONLY |
937 G_PARAM_STATIC_STRINGS));
938
939 g_object_class_install_properties (object_class, LAST_PROP, properties);
940
941 /**
942 * DzlAnimation::tick:
943 *
944 * The "tick" signal is emitted on each frame in the animation.
945 */
946 signals[TICK] = g_signal_new ("tick",
947 DZL_TYPE_ANIMATION,
948 G_SIGNAL_RUN_FIRST,
949 0,
950 NULL, NULL, NULL,
951 G_TYPE_NONE,
952 0);
953
954 #define SET_ALPHA(_T, _t) \
955 alpha_funcs[DZL_ANIMATION_ ## _T] = dzl_animation_alpha_ ## _t
956
957 SET_ALPHA (LINEAR, linear);
958 SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
959 SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
960 SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
961 SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
962 SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
963 SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
964
965 #define SET_TWEEN(_T, _t) \
966 G_STMT_START { \
967 guint idx = G_TYPE_ ## _T; \
968 tween_funcs[idx] = tween_ ## _t; \
969 } G_STMT_END
970
971 SET_TWEEN (INT, int);
972 SET_TWEEN (UINT, uint);
973 SET_TWEEN (LONG, long);
974 SET_TWEEN (ULONG, ulong);
975 SET_TWEEN (FLOAT, float);
976 SET_TWEEN (DOUBLE, double);
977 }
978
979
980 /**
981 * dzl_animation_init:
982 * @animation: (in): A #DzlAnimation.
983 *
984 * Initializes the #DzlAnimation instance.
985 *
986 * Side effects: Everything.
987 */
988 static void
dzl_animation_init(DzlAnimation * animation)989 dzl_animation_init (DzlAnimation *animation)
990 {
991 animation->duration_msec = 250;
992 animation->mode = DZL_ANIMATION_EASE_IN_OUT_QUAD;
993 animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
994 animation->last_offset = -G_MINDOUBLE;
995 }
996
997
998 /**
999 * dzl_animation_mode_get_type:
1000 *
1001 * Retrieves the GType for #DzlAnimationMode.
1002 *
1003 * Returns: A GType.
1004 * Side effects: GType registered on first call.
1005 */
1006 GType
dzl_animation_mode_get_type(void)1007 dzl_animation_mode_get_type (void)
1008 {
1009 static GType type_id = 0;
1010 static const GEnumValue values[] = {
1011 { DZL_ANIMATION_LINEAR, "DZL_ANIMATION_LINEAR", "linear" },
1012 { DZL_ANIMATION_EASE_IN_QUAD, "DZL_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
1013 { DZL_ANIMATION_EASE_IN_OUT_QUAD, "DZL_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
1014 { DZL_ANIMATION_EASE_OUT_QUAD, "DZL_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
1015 { DZL_ANIMATION_EASE_IN_CUBIC, "DZL_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
1016 { DZL_ANIMATION_EASE_OUT_CUBIC, "DZL_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
1017 { DZL_ANIMATION_EASE_IN_OUT_CUBIC, "DZL_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
1018 { 0 }
1019 };
1020
1021 if (G_UNLIKELY (!type_id))
1022 type_id = g_enum_register_static ("DzlAnimationMode", values);
1023 return type_id;
1024 }
1025
1026 /**
1027 * dzl_object_animatev:
1028 * @object: A #GObject.
1029 * @mode: The animation mode.
1030 * @duration_msec: The duration in milliseconds.
1031 * @frame_clock: (nullable): The #GdkFrameClock to synchronize to.
1032 * @first_property: The first property to animate.
1033 * @args: A variadac list of arguments
1034 *
1035 * Returns: (transfer none): A #DzlAnimation.
1036 */
1037 DzlAnimation *
dzl_object_animatev(gpointer object,DzlAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,const gchar * first_property,va_list args)1038 dzl_object_animatev (gpointer object,
1039 DzlAnimationMode mode,
1040 guint duration_msec,
1041 GdkFrameClock *frame_clock,
1042 const gchar *first_property,
1043 va_list args)
1044 {
1045 DzlAnimation *animation;
1046 GObjectClass *klass;
1047 GObjectClass *pklass;
1048 const gchar *name;
1049 GParamSpec *pspec;
1050 GtkWidget *parent;
1051 GValue value = { 0 };
1052 gchar *error = NULL;
1053 GType type;
1054 GType ptype;
1055 gboolean enable_animations;
1056
1057 g_return_val_if_fail (first_property != NULL, NULL);
1058 g_return_val_if_fail (mode < DZL_ANIMATION_LAST, NULL);
1059
1060 if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
1061 frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
1062
1063 /*
1064 * If we have a frame clock, then we must be in the gtk thread and we
1065 * should check GtkSettings for disabled animations. If we are disabled,
1066 * we will just make the timeout immediate.
1067 */
1068 if (frame_clock != NULL)
1069 {
1070 g_object_get (gtk_settings_get_default (),
1071 "gtk-enable-animations", &enable_animations,
1072 NULL);
1073
1074 if (enable_animations == FALSE)
1075 duration_msec = 0;
1076 }
1077
1078 name = first_property;
1079 type = G_TYPE_FROM_INSTANCE (object);
1080 klass = G_OBJECT_GET_CLASS (object);
1081 animation = g_object_new (DZL_TYPE_ANIMATION,
1082 "duration", duration_msec,
1083 "frame-clock", frame_clock,
1084 "mode", mode,
1085 "target", object,
1086 NULL);
1087
1088 do
1089 {
1090 /*
1091 * First check for the property on the object. If that does not exist
1092 * then check if the object has a parent and look at its child
1093 * properties (if it's a GtkWidget).
1094 */
1095 if (!(pspec = g_object_class_find_property (klass, name)))
1096 {
1097 if (!g_type_is_a (type, GTK_TYPE_WIDGET))
1098 {
1099 g_critical (_("Failed to find property %s in %s"),
1100 name, g_type_name (type));
1101 goto failure;
1102 }
1103 if (!(parent = gtk_widget_get_parent (object)))
1104 {
1105 g_critical (_("Failed to find property %s in %s"),
1106 name, g_type_name (type));
1107 goto failure;
1108 }
1109 pklass = G_OBJECT_GET_CLASS (parent);
1110 ptype = G_TYPE_FROM_INSTANCE (parent);
1111 if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
1112 {
1113 g_critical (_("Failed to find property %s in %s or parent %s"),
1114 name, g_type_name (type), g_type_name (ptype));
1115 goto failure;
1116 }
1117 }
1118
1119 g_value_init (&value, pspec->value_type);
1120 G_VALUE_COLLECT (&value, args, 0, &error);
1121 if (error != NULL)
1122 {
1123 g_critical (_("Failed to retrieve va_list value: %s"), error);
1124 g_free (error);
1125 goto failure;
1126 }
1127
1128 dzl_animation_add_property (animation, pspec, &value);
1129 g_value_unset (&value);
1130 }
1131 while ((name = va_arg (args, const gchar *)));
1132
1133 dzl_animation_start (animation);
1134
1135 return animation;
1136
1137 failure:
1138 g_object_ref_sink (animation);
1139 g_object_unref (animation);
1140 return NULL;
1141 }
1142
1143 /**
1144 * dzl_object_animate:
1145 * @object: (in): A #GObject.
1146 * @mode: (in): The animation mode.
1147 * @duration_msec: (in): The duration in milliseconds.
1148 * @first_property: (in): The first property to animate.
1149 *
1150 * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
1151 * will be animated from their current value to the target value over the time period.
1152 *
1153 * Return value: (transfer none): A #DzlAnimation.
1154 * Side effects: None.
1155 */
1156 DzlAnimation*
dzl_object_animate(gpointer object,DzlAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,const gchar * first_property,...)1157 dzl_object_animate (gpointer object,
1158 DzlAnimationMode mode,
1159 guint duration_msec,
1160 GdkFrameClock *frame_clock,
1161 const gchar *first_property,
1162 ...)
1163 {
1164 DzlAnimation *animation;
1165 va_list args;
1166
1167 va_start (args, first_property);
1168 animation = dzl_object_animatev (object,
1169 mode,
1170 duration_msec,
1171 frame_clock,
1172 first_property,
1173 args);
1174 va_end (args);
1175
1176 return animation;
1177 }
1178
1179 /**
1180 * dzl_object_animate_full:
1181 *
1182 * Return value: (transfer none): A #DzlAnimation.
1183 */
1184 DzlAnimation*
dzl_object_animate_full(gpointer object,DzlAnimationMode mode,guint duration_msec,GdkFrameClock * frame_clock,GDestroyNotify notify,gpointer notify_data,const gchar * first_property,...)1185 dzl_object_animate_full (gpointer object,
1186 DzlAnimationMode mode,
1187 guint duration_msec,
1188 GdkFrameClock *frame_clock,
1189 GDestroyNotify notify,
1190 gpointer notify_data,
1191 const gchar *first_property,
1192 ...)
1193 {
1194 DzlAnimation *animation;
1195 va_list args;
1196
1197 va_start (args, first_property);
1198 animation = dzl_object_animatev (object,
1199 mode,
1200 duration_msec,
1201 frame_clock,
1202 first_property,
1203 args);
1204 va_end (args);
1205
1206 animation->notify = notify;
1207 animation->notify_data = notify_data;
1208
1209 return animation;
1210 }
1211
1212 guint
dzl_animation_calculate_duration(GdkMonitor * monitor,gdouble from_value,gdouble to_value)1213 dzl_animation_calculate_duration (GdkMonitor *monitor,
1214 gdouble from_value,
1215 gdouble to_value)
1216 {
1217 GdkRectangle geom;
1218 gdouble distance_units;
1219 gdouble distance_mm;
1220 gdouble mm_per_frame;
1221 gint height_mm;
1222 gint refresh_rate;
1223 gint n_frames;
1224 guint ret;
1225
1226 #define MM_PER_SECOND (150.0)
1227 #define MIN_FRAMES_PER_ANIM (5)
1228 #define MAX_FRAMES_PER_ANIM (500)
1229
1230 g_assert (GDK_IS_MONITOR (monitor));
1231 g_assert (from_value >= 0.0);
1232 g_assert (to_value >= 0.0);
1233
1234 /*
1235 * Get various monitor information we'll need to calculate the duration of
1236 * the animation. We need the physical space of the monitor, the refresh
1237 * rate, and geometry so that we can limit how many device units we will
1238 * traverse per-frame of the animation. Failure to deal with the physical
1239 * space results in jittery animations to the user.
1240 *
1241 * It would also be nice to take into account the acceleration curve so that
1242 * we know the max amount of jump per frame, but that is getting into
1243 * diminishing returns since we can just average it out.
1244 */
1245 height_mm = gdk_monitor_get_height_mm (monitor);
1246 gdk_monitor_get_geometry (monitor, &geom);
1247 refresh_rate = gdk_monitor_get_refresh_rate (monitor);
1248 if (refresh_rate == 0)
1249 refresh_rate = 60000;
1250
1251 /*
1252 * The goal here is to determine the number of millimeters that we need to
1253 * animate given a transition of distance_unit pixels. Since we are dealing
1254 * with physical units (mm), we don't need to take into account the device
1255 * scale underneath the widget. The equation comes out the same.
1256 */
1257
1258 distance_units = ABS (from_value - to_value);
1259 distance_mm = distance_units / (gdouble)geom.height * height_mm;
1260 mm_per_frame = MM_PER_SECOND / (refresh_rate / 1000.0);
1261 n_frames = (distance_mm / mm_per_frame) + 1;
1262
1263 ret = n_frames * (1000.0 / (refresh_rate / 1000.0));
1264 ret = CLAMP (ret,
1265 MIN_FRAMES_PER_ANIM * (1000000.0 / refresh_rate),
1266 MAX_FRAMES_PER_ANIM * (1000000.0 / refresh_rate));
1267
1268 return ret;
1269
1270 #undef MM_PER_SECOND
1271 #undef MIN_FRAMES_PER_ANIM
1272 #undef MAX_FRAMES_PER_ANIM
1273 }
1274