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