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
54typedef enum {
55 GTK_KINETIC_SCROLLING_PHASE_DECELERATING,
56 GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING,
57 GTK_KINETIC_SCROLLING_PHASE_FINISHED,
58} GtkKineticScrollingPhase;
59
60struct _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
78static void gtk_kinetic_scrolling_init_overshoot (GtkKineticScrolling *data,
79 double equilibrium_position,
80 double initial_position,
81 double initial_velocity);
82
83GtkKineticScrolling *
84gtk_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
126GtkKineticScrollingChange
127gtk_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
155void
156gtk_kinetic_scrolling_free (GtkKineticScrolling *kinetic)
157{
158 g_slice_free (GtkKineticScrolling, kinetic);
159}
160
161static void
162gtk_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
174gboolean
175gtk_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
250void
251gtk_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

source code of gtk/gtk/gtkkineticscrolling.c