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 * |
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 | equilibrium_position: lower, |
103 | initial_position, |
104 | initial_velocity); |
105 | } |
106 | else if(initial_position > upper) |
107 | { |
108 | gtk_kinetic_scrolling_init_overshoot (data, |
109 | equilibrium_position: 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 |
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 |
156 | gtk_kinetic_scrolling_free (GtkKineticScrolling *kinetic) |
157 | { |
158 | g_slice_free (GtkKineticScrolling, kinetic); |
159 | } |
160 | |
161 | static void |
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 |
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 (x: -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,equilibrium_position: data->lower,initial_position: data->position,initial_velocity: data->velocity); |
197 | } |
198 | else if (data->position > data->upper) |
199 | { |
200 | gtk_kinetic_scrolling_init_overshoot(data, equilibrium_position: data->upper, initial_position: data->position, initial_velocity: data->velocity); |
201 | } |
202 | else if (fabs(x: data->velocity) < 1 || |
203 | (last_time != 0.0 && fabs(x: data->position - last_position) < 1)) |
204 | { |
205 | gtk_kinetic_scrolling_stop (data); |
206 | } |
207 | break; |
208 | } |
209 | |
210 | case GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING: |
211 | { |
212 | double exp_part, pos; |
213 | |
214 | data->t += time_delta; |
215 | exp_part = exp(x: -data->overshoot_friction / 2 * data->t); |
216 | pos = exp_part * (data->c1 + data->c2 * data->t); |
217 | |
218 | if (pos < data->lower - 50 || pos > data->upper + 50) |
219 | { |
220 | pos = CLAMP (pos, data->lower - 50, data->upper + 50); |
221 | gtk_kinetic_scrolling_init_overshoot (data, equilibrium_position: data->equilibrium_position, initial_position: pos, initial_velocity: 0); |
222 | } |
223 | else |
224 | data->velocity = data->c2 * exp_part - data->overshoot_friction / 2 * pos; |
225 | |
226 | data->position = pos + data->equilibrium_position; |
227 | |
228 | if(fabs (x: pos) < 0.1) |
229 | { |
230 | data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED; |
231 | data->position = data->equilibrium_position; |
232 | data->velocity = 0; |
233 | } |
234 | break; |
235 | } |
236 | |
237 | case GTK_KINETIC_SCROLLING_PHASE_FINISHED: |
238 | default: |
239 | break; |
240 | } |
241 | |
242 | if (position) |
243 | *position = data->position; |
244 | if (velocity) |
245 | *velocity = data->velocity; |
246 | |
247 | return data->phase != GTK_KINETIC_SCROLLING_PHASE_FINISHED; |
248 | } |
249 | |
250 | void |
251 | gtk_kinetic_scrolling_stop (GtkKineticScrolling *data) |
252 | { |
253 | if (data->phase == GTK_KINETIC_SCROLLING_PHASE_DECELERATING) |
254 | { |
255 | data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED; |
256 | data->position = round (x: data->position); |
257 | data->velocity = 0; |
258 | } |
259 | } |
260 | |