OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <cmath> | |
6 | |
7 #include "base/lazy_instance.h" | |
8 #include "ui/gfx/android/scroller.h" | |
9 #include "ui/gfx/android/view_configuration.h" | |
10 | |
11 namespace gfx { | |
12 namespace { | |
13 | |
14 const base::TimeDelta kDefaultDuration = base::TimeDelta::FromMilliseconds(250); | |
aelias_OOO_until_Jul13
2014/02/21 02:42:35
Hmm, I'm concerned these will end up as static ini
jdduke (slow)
2014/02/21 23:33:02
I'll just compute the constant in kDecelerationRat
| |
15 | |
16 const float kDecelerationRate = std::log(0.78f) / std::log(0.9f); | |
17 | |
18 // Tension lines cross at (kInflexion, 1) | |
19 const float kInflexion = 0.35f; | |
20 | |
21 const float kEpsilon = 1e-5; | |
aelias_OOO_until_Jul13
2014/02/21 02:42:35
1e-5f instead?
jdduke (slow)
2014/02/21 23:33:02
Done.
| |
22 | |
23 bool ApproxEquals(float a, float b) { | |
24 return std::abs(a - b) < kEpsilon; | |
25 } | |
26 | |
27 struct ViscosityConstants { | |
28 ViscosityConstants() | |
29 : viscous_fluid_scale_(8.f), viscous_fluid_normalize_(1.f) { | |
30 viscous_fluid_normalize_ = 1.0f / ApplyViscosity(1.0f); | |
31 } | |
32 | |
33 float ApplyViscosity(float x) { | |
34 x *= viscous_fluid_scale_; | |
35 if (x < 1.0f) { | |
36 x -= (1.0f - std::exp(-x)); | |
37 } else { | |
38 float start = 0.36787944117f; // 1/e == exp(-1) | |
39 x = 1.0f - std::exp(1.0f - x); | |
40 x = start + x * (1.0f - start); | |
41 } | |
42 x *= viscous_fluid_normalize_; | |
43 return x; | |
44 } | |
45 | |
46 private: | |
47 // This controls the viscous fluid effect (how much of it) | |
48 float viscous_fluid_scale_; | |
49 float viscous_fluid_normalize_; | |
50 | |
51 DISALLOW_COPY_AND_ASSIGN(ViscosityConstants); | |
52 }; | |
53 | |
54 struct SplineConstants { | |
55 SplineConstants() { | |
56 const float kStartTension = 0.5f; | |
57 const float kEndTension = 1.0f; | |
58 const float kP1 = kStartTension * kInflexion; | |
59 const float kP2 = 1.0f - kEndTension * (1.0f - kInflexion); | |
60 | |
61 float x_min = 0.0f; | |
62 float y_min = 0.0f; | |
63 for (int i = 0; i < NUM_SAMPLES; i++) { | |
64 const float alpha = static_cast<float>(i) / NUM_SAMPLES; | |
65 | |
66 float x_max = 1.0f; | |
67 float x, tx, coef; | |
68 while (true) { | |
69 x = x_min + (x_max - x_min) / 2.0f; | |
70 coef = 3.0f * x * (1.0f - x); | |
71 tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x; | |
72 if (std::abs(tx - alpha) < 1E-5) | |
aelias_OOO_until_Jul13
2014/02/21 02:42:35
kEpsilon?
jdduke (slow)
2014/02/21 23:33:02
Done.
| |
73 break; | |
74 if (tx > alpha) | |
75 x_max = x; | |
76 else | |
77 x_min = x; | |
78 } | |
79 spline_position_[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x; | |
80 | |
81 float y_max = 1.0f; | |
82 float y, dy; | |
83 while (true) { | |
84 y = y_min + (y_max - y_min) / 2.0f; | |
85 coef = 3.0f * y * (1.0f - y); | |
86 dy = coef * ((1.0f - y) * kStartTension + y) + y * y * y; | |
87 if (std::abs(dy - alpha) < 1E-5) | |
aelias_OOO_until_Jul13
2014/02/21 02:42:35
kEpsilon?
jdduke (slow)
2014/02/21 23:33:02
Done.
| |
88 break; | |
89 if (dy > alpha) | |
90 y_max = y; | |
91 else | |
92 y_min = y; | |
93 } | |
94 spline_time_[i] = coef * ((1.0f - y) * kP1 + y * kP2) + y * y * y; | |
95 } | |
96 spline_position_[NUM_SAMPLES] = spline_time_[NUM_SAMPLES] = 1.0f; | |
97 } | |
98 | |
99 void CalculateCoefficients(float t, | |
100 float* distance_coef, | |
101 float* velocity_coef) { | |
102 *distance_coef = 1.f; | |
103 *velocity_coef = 0.f; | |
104 const int index = static_cast<int>(NUM_SAMPLES * t); | |
105 if (index < NUM_SAMPLES) { | |
106 const float t_inf = static_cast<float>(index) / NUM_SAMPLES; | |
107 const float t_sup = static_cast<float>(index + 1) / NUM_SAMPLES; | |
108 const float d_inf = spline_position_[index]; | |
109 const float d_sup = spline_position_[index + 1]; | |
110 *velocity_coef = (d_sup - d_inf) / (t_sup - t_inf); | |
111 *distance_coef = d_inf + (t - t_inf) * *velocity_coef; | |
112 } | |
113 } | |
114 | |
115 private: | |
116 enum { | |
117 NUM_SAMPLES = 100 | |
118 }; | |
119 | |
120 float spline_position_[NUM_SAMPLES + 1]; | |
121 float spline_time_[NUM_SAMPLES + 1]; | |
122 | |
123 DISALLOW_COPY_AND_ASSIGN(SplineConstants); | |
124 }; | |
125 | |
126 float ComputeDeceleration(float friction) { | |
127 const float kGravityEarth = 9.80665f; | |
128 return kGravityEarth // g (m/s^2) | |
129 * 39.37f // inch/meter | |
130 * 160.f // pixels/inch | |
131 * friction; | |
132 } | |
133 | |
134 template <typename T> | |
135 int Signum(T t) { | |
136 return (T(0) < t) - (t < T(0)); | |
137 } | |
138 | |
139 template <typename T> | |
140 T Clamped(T t, T a, T b) { | |
141 return t < a ? a : (t > b ? b : t); | |
142 } | |
143 | |
144 // Leaky to allow access from the impl thread. | |
145 base::LazyInstance<ViscosityConstants>::Leaky g_viscosity_constants = | |
146 LAZY_INSTANCE_INITIALIZER; | |
147 | |
148 base::LazyInstance<SplineConstants>::Leaky g_spline_constants = | |
149 LAZY_INSTANCE_INITIALIZER; | |
150 | |
151 } // namespace | |
152 | |
153 Scroller::Scroller(bool enable_flywheel) | |
154 : mode_(UNDEFINED), | |
155 start_x_(0), | |
156 start_y_(0), | |
157 final_x_(0), | |
158 final_y_(0), | |
159 min_x_(0), | |
160 max_x_(0), | |
161 min_y_(0), | |
162 max_y_(0), | |
163 curr_x_(0), | |
164 curr_y_(0), | |
165 duration_seconds_reciprocal_(1), | |
166 delta_x_(0), | |
167 delta_x_norm_(1), | |
168 delta_y_(0), | |
169 delta_y_norm_(1), | |
170 finished_(true), | |
171 flywheel_enabled_(enable_flywheel), | |
172 velocity_(0), | |
173 curr_velocity_(0), | |
174 distance_(0), | |
175 fling_friction_(ViewConfiguration::GetScrollFriction()), | |
176 deceleration_(ComputeDeceleration(fling_friction_)), | |
177 tuning_coeff_(ComputeDeceleration(0.84f)) {} | |
178 | |
179 Scroller::~Scroller() {} | |
180 | |
181 void Scroller::StartScroll(float start_x, | |
182 float start_y, | |
183 float dx, | |
184 float dy, | |
185 base::TimeTicks start_time) { | |
186 StartScroll(start_x, start_y, dx, dy, start_time, kDefaultDuration); | |
187 } | |
188 | |
189 void Scroller::StartScroll(float start_x, | |
190 float start_y, | |
191 float dx, | |
192 float dy, | |
193 base::TimeTicks start_time, | |
194 base::TimeDelta duration) { | |
195 mode_ = SCROLL_MODE; | |
196 finished_ = false; | |
197 duration_ = duration; | |
198 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF(); | |
199 start_time_ = start_time; | |
200 start_x_ = start_x; | |
201 start_y_ = start_y; | |
202 final_x_ = start_x + dx; | |
203 final_y_ = start_y + dy; | |
204 RecomputeDeltas(); | |
205 curr_time_ = start_time_; | |
206 } | |
207 | |
208 void Scroller::Fling(float start_x, | |
209 float start_y, | |
210 float velocity_x, | |
211 float velocity_y, | |
212 float min_x, | |
213 float max_x, | |
214 float min_y, | |
215 float max_y, | |
216 base::TimeTicks start_time) { | |
217 // Continue a scroll or fling in progress | |
218 if (flywheel_enabled_ && !finished_) { | |
219 float old_velocity_x = GetCurrVelocityX(); | |
220 float old_velocity_y = GetCurrVelocityY(); | |
221 if (Signum(velocity_x) == Signum(old_velocity_x) && | |
222 Signum(velocity_y) == Signum(old_velocity_y)) { | |
223 velocity_x += old_velocity_x; | |
224 velocity_y += old_velocity_y; | |
225 } | |
226 } | |
227 | |
228 mode_ = FLING_MODE; | |
229 finished_ = false; | |
230 | |
231 float velocity = std::sqrt(velocity_x * velocity_x + velocity_y * velocity_y); | |
232 | |
233 velocity_ = velocity; | |
234 duration_ = GetSplineFlingDuration(velocity); | |
235 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF(); | |
236 start_time_ = start_time; | |
237 curr_time_ = start_time_; | |
238 start_x_ = start_x; | |
239 start_y_ = start_y; | |
240 | |
241 float coeff_x = velocity == 0 ? 1.0f : velocity_x / velocity; | |
242 float coeff_y = velocity == 0 ? 1.0f : velocity_y / velocity; | |
243 | |
244 double total_distance = GetSplineFlingDistance(velocity); | |
245 distance_ = total_distance * Signum(velocity); | |
246 | |
247 min_x_ = min_x; | |
248 max_x_ = max_x; | |
249 min_y_ = min_y; | |
250 max_y_ = max_y; | |
251 | |
252 final_x_ = start_x + total_distance * coeff_x; | |
253 final_x_ = Clamped(final_x_, min_x_, max_x_); | |
254 | |
255 final_y_ = start_y + total_distance * coeff_y; | |
256 final_y_ = Clamped(final_y_, min_y_, max_y_); | |
257 | |
258 RecomputeDeltas(); | |
259 } | |
260 | |
261 bool Scroller::ComputeScrollOffset(base::TimeTicks time) { | |
262 if (finished_) | |
263 return false; | |
264 | |
265 base::TimeDelta time_passed = time - start_time_; | |
266 | |
267 if (time_passed < base::TimeDelta()) { | |
268 time_passed = base::TimeDelta(); | |
269 } | |
270 | |
271 if (time_passed >= duration_) { | |
272 curr_x_ = final_x_; | |
273 curr_y_ = final_y_; | |
274 curr_time_ = start_time_ + duration_; | |
275 finished_ = true; | |
276 return true; | |
277 } | |
278 | |
279 curr_time_ = time; | |
280 | |
281 const float t = time_passed.InSecondsF() * duration_seconds_reciprocal_; | |
282 | |
283 switch (mode_) { | |
284 case UNDEFINED: | |
285 NOTREACHED() << "Invalid scroll mode when computing scroll offset."; | |
286 return false; | |
287 | |
288 case SCROLL_MODE: { | |
289 float x = g_viscosity_constants.Get().ApplyViscosity(t); | |
290 | |
291 curr_x_ = start_x_ + x * delta_x_; | |
292 curr_y_ = start_y_ + x * delta_y_; | |
293 } break; | |
294 | |
295 case FLING_MODE: { | |
296 float distance_coef = 1.f; | |
297 float velocity_coef = 0.f; | |
298 g_spline_constants.Get().CalculateCoefficients( | |
299 t, &distance_coef, &velocity_coef); | |
300 | |
301 curr_velocity_ = velocity_coef * distance_ * duration_seconds_reciprocal_; | |
302 | |
303 curr_x_ = start_x_ + distance_coef * delta_x_; | |
304 curr_x_ = Clamped(curr_x_, min_x_, max_x_); | |
305 | |
306 curr_y_ = start_y_ + distance_coef * delta_y_; | |
307 curr_y_ = Clamped(curr_y_, min_y_, max_y_); | |
308 | |
309 if (ApproxEquals(curr_x_, final_x_) && ApproxEquals(curr_y_, final_y_)) { | |
310 finished_ = true; | |
311 } | |
312 } break; | |
313 } | |
314 | |
315 return true; | |
316 } | |
317 | |
318 void Scroller::ExtendDuration(base::TimeDelta extend) { | |
319 base::TimeDelta passed = GetTimePassed(); | |
320 duration_ = passed + extend; | |
321 duration_seconds_reciprocal_ = 1. / duration_.InSecondsF(); | |
322 finished_ = false; | |
323 } | |
324 | |
325 void Scroller::SetFinalX(float new_x) { | |
326 final_x_ = new_x; | |
327 finished_ = false; | |
328 RecomputeDeltas(); | |
329 } | |
330 | |
331 void Scroller::SetFinalY(float new_y) { | |
332 final_y_ = new_y; | |
333 finished_ = false; | |
334 RecomputeDeltas(); | |
335 } | |
336 | |
337 void Scroller::AbortAnimation() { | |
338 curr_x_ = final_x_; | |
339 curr_y_ = final_y_; | |
340 curr_time_ = start_time_ + duration_; | |
341 finished_ = true; | |
342 } | |
343 | |
344 void Scroller::ForceFinished(bool finished) { finished_ = finished; } | |
345 | |
346 bool Scroller::IsFinished() const { return finished_; } | |
347 | |
348 base::TimeDelta Scroller::GetTimePassed() const { | |
349 return curr_time_ - start_time_; | |
350 } | |
351 | |
352 base::TimeDelta Scroller::GetDuration() const { return duration_; } | |
353 | |
354 float Scroller::GetCurrX() const { return curr_x_; } | |
355 | |
356 float Scroller::GetCurrY() const { return curr_y_; } | |
357 | |
358 float Scroller::GetCurrVelocity() const { | |
359 return mode_ == FLING_MODE | |
360 ? curr_velocity_ | |
361 : velocity_ - deceleration_ * GetTimePassed().InSecondsF() * 0.5f; | |
362 } | |
363 | |
364 float Scroller::GetCurrVelocityX() const { | |
365 return delta_x_norm_ * GetCurrVelocity(); | |
366 } | |
367 | |
368 float Scroller::GetCurrVelocityY() const { | |
369 return delta_y_norm_ * GetCurrVelocity(); | |
370 } | |
371 | |
372 float Scroller::GetStartX() const { return start_x_; } | |
373 | |
374 float Scroller::GetStartY() const { return start_y_; } | |
375 | |
376 float Scroller::GetFinalX() const { return final_x_; } | |
377 | |
378 float Scroller::GetFinalY() const { return final_y_; } | |
379 | |
380 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const { | |
381 return !finished_ && Signum(xvel) == Signum(delta_x_) && | |
382 Signum(yvel) == Signum(delta_y_); | |
383 } | |
384 | |
385 void Scroller::RecomputeDeltas() { | |
386 delta_x_ = final_x_ - start_x_; | |
387 delta_y_ = final_y_ - start_y_; | |
388 | |
389 const float hyp = std::sqrt(delta_x_ * delta_x_ + delta_y_ * delta_y_); | |
390 if (hyp > kEpsilon) { | |
391 delta_x_norm_ = delta_x_ / hyp; | |
392 delta_y_norm_ = delta_y_ / hyp; | |
393 } else { | |
394 delta_x_norm_ = delta_y_norm_ = 1; | |
395 } | |
396 } | |
397 | |
398 double Scroller::GetSplineDeceleration(float velocity) const { | |
399 return std::log(kInflexion * std::abs(velocity) / | |
400 (fling_friction_ * tuning_coeff_)); | |
401 } | |
402 | |
403 base::TimeDelta Scroller::GetSplineFlingDuration(float velocity) const { | |
404 const double l = GetSplineDeceleration(velocity); | |
405 const double decel_minus_one = kDecelerationRate - 1.0; | |
406 const double time_seconds = std::exp(l / decel_minus_one); | |
407 return base::TimeDelta::FromMicroseconds(time_seconds * | |
408 base::Time::kMicrosecondsPerSecond); | |
409 } | |
410 | |
411 double Scroller::GetSplineFlingDistance(float velocity) const { | |
412 const double l = GetSplineDeceleration(velocity); | |
413 const double decel_minus_one = kDecelerationRate - 1.0; | |
414 return fling_friction_ * tuning_coeff_ * | |
415 std::exp(kDecelerationRate / decel_minus_one * l); | |
416 } | |
417 | |
418 } // namespace gfx | |
OLD | NEW |