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
39static double gtk_slowdown = 1.0;
40
41void
42_gtk_set_slowdown (double factor)
43{
44 gtk_slowdown = factor;
45}
46
47double
48_gtk_get_slowdown (void)
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 **/
60void
61gtk_progress_tracker_init_copy (GtkProgressTracker *source,
62 GtkProgressTracker *dest)
63{
64 memcpy (dest: dest, src: source, n: 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 **/
76void
77gtk_progress_tracker_start (GtkProgressTracker *tracker,
78 guint64 duration,
79 gint64 delay,
80 double iteration_count)
81{
82 tracker->is_running = TRUE;
83 tracker->last_frame_time = 0;
84 tracker->duration = duration;
85 tracker->iteration = - delay / (double) 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 **/
95void
96gtk_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 **/
109void
110gtk_progress_tracker_advance_frame (GtkProgressTracker *tracker,
111 guint64 frame_time)
112{
113 double 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 **/
144void
145gtk_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 **/
165GtkProgressState
166gtk_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 **/
185double
186gtk_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 **/
202guint64
203gtk_progress_tracker_get_iteration_cycle (GtkProgressTracker *tracker)
204{
205 double 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 (x: 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 **/
228double
229gtk_progress_tracker_get_progress (GtkProgressTracker *tracker,
230 gboolean reversed)
231{
232 double 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 */
245static double
246ease_out_cubic (double t)
247{
248 double 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 **/
262double
263gtk_progress_tracker_get_ease_out_cubic (GtkProgressTracker *tracker,
264 gboolean reversed)
265{
266 double progress = gtk_progress_tracker_get_progress (tracker, reversed);
267 return ease_out_cubic (t: progress);
268}
269

source code of gtk/gtk/gtkprogresstracker.c