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