1 /*
2  * Copyright © 2016 Endless Mobile Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Matthew Watson <mattdangerw@gmail.com>
18  */
19 
20 #include "gtkprogresstrackerprivate.h"
21 #include "gtkprivate.h"
22 #include "gtkcsseasevalueprivate.h"
23 
24 #include <math.h>
25 #include <string.h>
26 
27 /*
28  * Progress tracker is small helper for tracking progress through gtk
29  * animations. It's a simple zero-initable struct, meant to be thrown in a
30  * widget's private data without the need for setup or teardown.
31  *
32  * Progress tracker will handle translating frame clock timestamps to a
33  * fractional progress value for interpolating between animation targets.
34  *
35  * Progress tracker will use the GTK_SLOWDOWN environment variable to control
36  * the speed of animations. This can be useful for debugging.
37  */
38 
39 static gdouble gtk_slowdown = 1.0;
40 
41 void
_gtk_set_slowdown(gdouble factor)42 _gtk_set_slowdown (gdouble factor)
43 {
44   gtk_slowdown = factor;
45 }
46 
47 gdouble
_gtk_get_slowdown(gdouble factor)48 _gtk_get_slowdown (gdouble factor)
49 {
50   return gtk_slowdown;
51 }
52 
53 /**
54  * gtk_progress_tracker_init_copy:
55  * @source: The source progress tracker
56  * @dest: The destination progress tracker
57  *
58  * Copy all progress tracker state from the source tracker to dest tracker.
59  **/
60 void
gtk_progress_tracker_init_copy(GtkProgressTracker * source,GtkProgressTracker * dest)61 gtk_progress_tracker_init_copy (GtkProgressTracker *source,
62                                 GtkProgressTracker *dest)
63 {
64   memcpy (dest, source, sizeof (GtkProgressTracker));
65 }
66 
67 /**
68  * gtk_progress_tracker_start:
69  * @tracker: The progress tracker
70  * @duration: Animation duration in us
71  * @delay: Animation delay in us
72  * @iteration_count: Number of iterations to run the animation, must be >= 0
73  *
74  * Begins tracking progress for a new animation. Clears all previous state.
75  **/
76 void
gtk_progress_tracker_start(GtkProgressTracker * tracker,guint64 duration,gint64 delay,gdouble iteration_count)77 gtk_progress_tracker_start (GtkProgressTracker *tracker,
78                             guint64 duration,
79                             gint64 delay,
80                             gdouble iteration_count)
81 {
82   tracker->is_running = TRUE;
83   tracker->last_frame_time = 0;
84   tracker->duration = duration;
85   tracker->iteration = - delay / (gdouble) MAX (duration, 1);
86   tracker->iteration_count = iteration_count;
87 }
88 
89 /**
90  * gtk_progress_tracker_finish:
91  * @tracker: The progress tracker
92  *
93  * Stops running the current animation.
94  **/
95 void
gtk_progress_tracker_finish(GtkProgressTracker * tracker)96 gtk_progress_tracker_finish (GtkProgressTracker *tracker)
97 {
98   tracker->is_running = FALSE;
99 }
100 
101 /**
102  * gtk_progress_tracker_advance_frame:
103  * @tracker: The progress tracker
104  * @frame_time: The current frame time, usually from the frame clock.
105  *
106  * Increments the progress of the animation forward a frame. If no animation has
107  * been started, does nothing.
108  **/
109 void
gtk_progress_tracker_advance_frame(GtkProgressTracker * tracker,guint64 frame_time)110 gtk_progress_tracker_advance_frame (GtkProgressTracker *tracker,
111                                     guint64             frame_time)
112 {
113   gdouble delta;
114 
115   if (!tracker->is_running)
116     return;
117 
118   if (tracker->last_frame_time == 0)
119     {
120       tracker->last_frame_time = frame_time;
121       return;
122     }
123 
124   if (frame_time < tracker->last_frame_time)
125     {
126       g_warning ("Progress tracker frame set backwards, ignoring.");
127       return;
128     }
129 
130   delta = (frame_time - tracker->last_frame_time) / gtk_slowdown / MAX (tracker->duration, 1);
131   tracker->last_frame_time = frame_time;
132   tracker->iteration += delta;
133 }
134 
135 /**
136  * gtk_progress_tracker_skip_frame:
137  * @tracker: The progress tracker
138  * @frame_time: The current frame time, usually from the frame clock.
139  *
140  * Does not update the progress of the animation forward, but records the frame
141  * to calculate future deltas. Calling this each frame will effectively pause
142  * the animation.
143  **/
144 void
gtk_progress_tracker_skip_frame(GtkProgressTracker * tracker,guint64 frame_time)145 gtk_progress_tracker_skip_frame (GtkProgressTracker *tracker,
146                                  guint64 frame_time)
147 {
148   if (!tracker->is_running)
149     return;
150 
151   tracker->last_frame_time = frame_time;
152 }
153 
154 /**
155  * gtk_progress_tracker_get_state:
156  * @tracker: The progress tracker
157  *
158  * Returns whether the tracker is before, during or after the currently started
159  * animation. The tracker will only ever be in the before state if the animation
160  * was started with a delay. If no animation has been started, returns
161  * %GTK_PROGRESS_STATE_AFTER.
162  *
163  * Returns: A GtkProgressState
164  **/
165 GtkProgressState
gtk_progress_tracker_get_state(GtkProgressTracker * tracker)166 gtk_progress_tracker_get_state (GtkProgressTracker *tracker)
167 {
168   if (!tracker->is_running || tracker->iteration > tracker->iteration_count)
169     return GTK_PROGRESS_STATE_AFTER;
170   if (tracker->iteration < 0)
171     return GTK_PROGRESS_STATE_BEFORE;
172   return GTK_PROGRESS_STATE_DURING;
173 }
174 
175 /**
176  * gtk_progress_tracker_get_iteration:
177  * @tracker: The progress tracker
178  *
179  * Returns the fractional number of cycles the animation has completed. For
180  * example, it you started an animation with iteration-count of 2 and are half
181  * way through the second animation, this returns 1.5.
182  *
183  * Returns: The current iteration.
184  **/
185 gdouble
gtk_progress_tracker_get_iteration(GtkProgressTracker * tracker)186 gtk_progress_tracker_get_iteration (GtkProgressTracker *tracker)
187 {
188   return tracker->is_running ? CLAMP (tracker->iteration, 0.0, tracker->iteration_count) : 1.0;
189 }
190 
191 /**
192  * gtk_progress_tracker_get_iteration_cycle:
193  * @tracker: The progress tracker
194  *
195  * Returns an integer index of the current iteration cycle tracker is
196  * progressing through. Handles edge cases, such as an iteration value of 2.0
197  * which could be considered the end of the second iteration of the beginning of
198  * the third, in the same way as gtk_progress_tracker_get_progress().
199  *
200  * Returns: The integer count of the current animation cycle.
201  **/
202 guint64
gtk_progress_tracker_get_iteration_cycle(GtkProgressTracker * tracker)203 gtk_progress_tracker_get_iteration_cycle (GtkProgressTracker *tracker)
204 {
205   gdouble iteration = gtk_progress_tracker_get_iteration (tracker);
206 
207   /* Some complexity here. We want an iteration of 0.0 to always map to 0 (start
208    * of the first iteration), but an iteration of 1.0 to also map to 0 (end of
209    * first iteration) and 2.0 to 1 (end of the second iteration).
210    */
211   if (iteration == 0.0)
212     return 0;
213 
214   return (guint64) ceil (iteration) - 1;
215 }
216 
217 /**
218  * gtk_progress_tracker_get_progress:
219  * @tracker: The progress tracker
220  * @reversed: If progress should be reversed.
221  *
222  * Gets the progress through the current animation iteration, from [0, 1]. Use
223  * to interpolate between animation targets. If reverse is true each iteration
224  * will begin at 1 and end at 0.
225  *
226  * Returns: The progress value.
227  **/
228 gdouble
gtk_progress_tracker_get_progress(GtkProgressTracker * tracker,gboolean reversed)229 gtk_progress_tracker_get_progress (GtkProgressTracker *tracker,
230                                    gboolean reversed)
231 {
232   gdouble progress, iteration;
233   guint64 iteration_cycle;
234 
235   iteration = gtk_progress_tracker_get_iteration (tracker);
236   iteration_cycle = gtk_progress_tracker_get_iteration_cycle (tracker);
237 
238   progress = iteration - iteration_cycle;
239   return reversed ? 1.0 - progress : progress;
240 }
241 
242 /* From clutter-easing.c, based on Robert Penner's
243  * infamous easing equations, MIT license.
244  */
245 static gdouble
ease_out_cubic(gdouble t)246 ease_out_cubic (gdouble t)
247 {
248   gdouble p = t - 1;
249   return p * p * p + 1;
250 }
251 
252 /**
253  * gtk_progress_tracker_get_ease_out_cubic:
254  * @tracker: The progress tracker
255  * @reversed: If progress should be reversed before applying the ease function.
256  *
257  * Applies a simple ease out cubic function to the result of
258  * gtk_progress_tracker_get_progress().
259  *
260  * Returns: The eased progress value.
261  **/
262 gdouble
gtk_progress_tracker_get_ease_out_cubic(GtkProgressTracker * tracker,gboolean reversed)263 gtk_progress_tracker_get_ease_out_cubic (GtkProgressTracker *tracker,
264                                          gboolean reversed)
265 {
266   gdouble progress = gtk_progress_tracker_get_progress (tracker, reversed);
267   return ease_out_cubic (progress);
268 }
269