1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2014 Lieven van der Heide
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 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
18 #include "config.h"
19 #include "gtkkineticscrolling.h"
20
21 #include <stdio.h>
22
23 #include "fallback-c89.c"
24
25 /*
26 * All our curves are second degree linear differential equations, and
27 * so they can always be written as linear combinations of 2 base
28 * solutions. c1 and c2 are the coefficients to these two base solutions,
29 * and are computed from the initial position and velocity.
30 *
31 * In the case of simple deceleration, the differential equation is
32 *
33 * y'' = -my'
34 *
35 * With m the resistence factor. For this we use the following 2
36 * base solutions:
37 *
38 * f1(x) = 1
39 * f2(x) = exp(-mx)
40 *
41 * In the case of overshoot, the differential equation is
42 *
43 * y'' = -my' - ky
44 *
45 * With m the resistance, and k the spring stiffness constant. We let
46 * k = m^2 / 4, so that the system is critically damped (ie, returns to its
47 * equilibrium position as quickly as possible, without oscillating), and offset
48 * the whole thing, such that the equilibrium position is at 0. This gives the
49 * base solutions
50 *
51 * f1(x) = exp(-mx / 2)
52 * f2(x) = t exp(-mx / 2)
53 */
54
55 typedef enum {
56 GTK_KINETIC_SCROLLING_PHASE_DECELERATING,
57 GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING,
58 GTK_KINETIC_SCROLLING_PHASE_FINISHED,
59 } GtkKineticScrollingPhase;
60
61 struct _GtkKineticScrolling
62 {
63 GtkKineticScrollingPhase phase;
64 gdouble lower;
65 gdouble upper;
66 gdouble overshoot_width;
67 gdouble decel_friction;
68 gdouble overshoot_friction;
69
70 gdouble c1;
71 gdouble c2;
72 gdouble equilibrium_position;
73
74 gdouble t;
75 gdouble position;
76 gdouble velocity;
77 };
78
79 static void gtk_kinetic_scrolling_init_overshoot (GtkKineticScrolling *data,
80 gdouble equilibrium_position,
81 gdouble initial_position,
82 gdouble initial_velocity);
83
84 GtkKineticScrolling *
gtk_kinetic_scrolling_new(gdouble lower,gdouble upper,gdouble overshoot_width,gdouble decel_friction,gdouble overshoot_friction,gdouble initial_position,gdouble initial_velocity)85 gtk_kinetic_scrolling_new (gdouble lower,
86 gdouble upper,
87 gdouble overshoot_width,
88 gdouble decel_friction,
89 gdouble overshoot_friction,
90 gdouble initial_position,
91 gdouble initial_velocity)
92 {
93 GtkKineticScrolling *data;
94
95 data = g_slice_new0 (GtkKineticScrolling);
96 data->lower = lower;
97 data->upper = upper;
98 data->decel_friction = decel_friction;
99 data->overshoot_friction = overshoot_friction;
100 if(initial_position < lower)
101 {
102 gtk_kinetic_scrolling_init_overshoot (data,
103 lower,
104 initial_position,
105 initial_velocity);
106 }
107 else if(initial_position > upper)
108 {
109 gtk_kinetic_scrolling_init_overshoot (data,
110 upper,
111 initial_position,
112 initial_velocity);
113 }
114 else
115 {
116 data->phase = GTK_KINETIC_SCROLLING_PHASE_DECELERATING;
117 data->c1 = initial_velocity / decel_friction + initial_position;
118 data->c2 = -initial_velocity / decel_friction;
119 data->t = 0;
120 data->position = initial_position;
121 data->velocity = initial_velocity;
122 }
123
124 return data;
125 }
126
127 GtkKineticScrollingChange
gtk_kinetic_scrolling_update_size(GtkKineticScrolling * data,gdouble lower,gdouble upper)128 gtk_kinetic_scrolling_update_size (GtkKineticScrolling *data,
129 gdouble lower,
130 gdouble upper)
131 {
132 GtkKineticScrollingChange change = GTK_KINETIC_SCROLLING_CHANGE_NONE;
133
134 if (lower != data->lower)
135 {
136 if (data->position <= lower)
137 change |= GTK_KINETIC_SCROLLING_CHANGE_LOWER;
138
139 data->lower = lower;
140 }
141
142 if (upper != data->upper)
143 {
144 if (data->position >= data->upper)
145 change |= GTK_KINETIC_SCROLLING_CHANGE_UPPER;
146
147 data->upper = upper;
148 }
149
150 if (data->phase == GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING)
151 change |= GTK_KINETIC_SCROLLING_CHANGE_IN_OVERSHOOT;
152
153 return change;
154 }
155
156 void
gtk_kinetic_scrolling_free(GtkKineticScrolling * kinetic)157 gtk_kinetic_scrolling_free (GtkKineticScrolling *kinetic)
158 {
159 g_slice_free (GtkKineticScrolling, kinetic);
160 }
161
162 static void
gtk_kinetic_scrolling_init_overshoot(GtkKineticScrolling * data,gdouble equilibrium_position,gdouble initial_position,gdouble initial_velocity)163 gtk_kinetic_scrolling_init_overshoot (GtkKineticScrolling *data,
164 gdouble equilibrium_position,
165 gdouble initial_position,
166 gdouble initial_velocity)
167 {
168 data->phase = GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING;
169 data->equilibrium_position = equilibrium_position;
170 data->c1 = initial_position - equilibrium_position;
171 data->c2 = initial_velocity + data->overshoot_friction / 2 * data->c1;
172 data->t = 0;
173 }
174
175 gboolean
gtk_kinetic_scrolling_tick(GtkKineticScrolling * data,gdouble time_delta,gdouble * position,gdouble * velocity)176 gtk_kinetic_scrolling_tick (GtkKineticScrolling *data,
177 gdouble time_delta,
178 gdouble *position,
179 gdouble *velocity)
180 {
181 switch(data->phase)
182 {
183 case GTK_KINETIC_SCROLLING_PHASE_DECELERATING:
184 {
185 gdouble last_position = data->position;
186 gdouble last_time = data->t;
187 gdouble exp_part;
188
189 data->t += time_delta;
190
191 exp_part = exp (-data->decel_friction * data->t);
192 data->position = data->c1 + data->c2 * exp_part;
193 data->velocity = -data->decel_friction * data->c2 * exp_part;
194
195 if(data->position < data->lower)
196 {
197 gtk_kinetic_scrolling_init_overshoot(data,data->lower,data->position,data->velocity);
198 }
199 else if (data->position > data->upper)
200 {
201 gtk_kinetic_scrolling_init_overshoot(data, data->upper, data->position, data->velocity);
202 }
203 else if (fabs(data->velocity) < 1 ||
204 (last_time != 0.0 && fabs(data->position - last_position) < 1))
205 {
206 data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED;
207 data->position = round(data->position);
208 data->velocity = 0;
209 }
210 break;
211 }
212
213 case GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING:
214 {
215 gdouble exp_part, pos;
216
217 data->t += time_delta;
218 exp_part = exp(-data->overshoot_friction / 2 * data->t);
219 pos = exp_part * (data->c1 + data->c2 * data->t);
220
221 if (pos < data->lower - 50 || pos > data->upper + 50)
222 {
223 pos = CLAMP (pos, data->lower - 50, data->upper + 50);
224 gtk_kinetic_scrolling_init_overshoot (data, data->equilibrium_position, pos, 0);
225 }
226 else
227 data->velocity = data->c2 * exp_part - data->overshoot_friction / 2 * pos;
228
229 data->position = pos + data->equilibrium_position;
230
231 if(fabs (pos) < 0.1)
232 {
233 data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED;
234 data->position = data->equilibrium_position;
235 data->velocity = 0;
236 }
237 break;
238 }
239
240 case GTK_KINETIC_SCROLLING_PHASE_FINISHED:
241 break;
242 }
243
244 if (position)
245 *position = data->position;
246 if (velocity)
247 *velocity = data->velocity;
248
249 return data->phase != GTK_KINETIC_SCROLLING_PHASE_FINISHED;
250 }
251
252