MD Downloads: prevent search text from overlapping with the cancel search (X)
[chromium-blink-merge.git] / ui / events / android / scroller.cc
blob17c457b3f740cb7e96a51b4ad1337fde69cc29a6
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.
5 #include "ui/events/android/scroller.h"
7 #include <cmath>
9 #include "base/lazy_instance.h"
11 namespace ui {
12 namespace {
14 // Default scroll duration from android.widget.Scroller.
15 const int kDefaultDurationMs = 250;
17 // Default friction constant in android.view.ViewConfiguration.
18 const float kDefaultFriction = 0.015f;
20 // == std::log(0.78f) / std::log(0.9f)
21 const float kDecelerationRate = 2.3582018f;
23 // Tension lines cross at (kInflexion, 1).
24 const float kInflexion = 0.35f;
26 const float kEpsilon = 1e-5f;
28 // Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
29 // pixels or closer from the end.
30 const float kThresholdForFlingEnd = 0.1;
32 bool ApproxEquals(float a, float b) {
33 return std::abs(a - b) < kEpsilon;
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);
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);
51 x *= viscous_fluid_normalize_;
52 return x;
55 private:
56 // This controls the intensity of the viscous fluid effect.
57 float viscous_fluid_scale_;
58 float viscous_fluid_normalize_;
60 DISALLOW_COPY_AND_ASSIGN(ViscosityConstants);
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);
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;
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;
88 spline_position_[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
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;
103 spline_time_[i] = coef * ((1.0f - y) * kP1 + y * kP2) + y * y * y;
105 spline_position_[NUM_SAMPLES] = spline_time_[NUM_SAMPLES] = 1.0f;
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;
124 private:
125 enum { NUM_SAMPLES = 100 };
127 float spline_position_[NUM_SAMPLES + 1];
128 float spline_time_[NUM_SAMPLES + 1];
130 DISALLOW_COPY_AND_ASSIGN(SplineConstants);
133 float ComputeDeceleration(float friction) {
134 const float kGravityEarth = 9.80665f;
135 return kGravityEarth // g (m/s^2)
136 * 39.37f // inch/meter
137 * 160.f // pixels/inch
138 * friction;
141 template <typename T>
142 int Signum(T t) {
143 return (T(0) < t) - (t < T(0));
146 template <typename T>
147 T Clamped(T t, T a, T b) {
148 return t < a ? a : (t > b ? b : t);
151 // Leaky to allow access from the impl thread.
152 base::LazyInstance<ViscosityConstants>::Leaky g_viscosity_constants =
153 LAZY_INSTANCE_INITIALIZER;
155 base::LazyInstance<SplineConstants>::Leaky g_spline_constants =
156 LAZY_INSTANCE_INITIALIZER;
158 } // namespace
160 Scroller::Config::Config()
161 : fling_friction(kDefaultFriction), flywheel_enabled(false) {
164 Scroller::Scroller(const Config& config)
165 : mode_(UNDEFINED),
166 start_x_(0),
167 start_y_(0),
168 final_x_(0),
169 final_y_(0),
170 min_x_(0),
171 max_x_(0),
172 min_y_(0),
173 max_y_(0),
174 curr_x_(0),
175 curr_y_(0),
176 duration_seconds_reciprocal_(1),
177 delta_x_(0),
178 delta_x_norm_(1),
179 delta_y_(0),
180 delta_y_norm_(1),
181 finished_(true),
182 flywheel_enabled_(config.flywheel_enabled),
183 velocity_(0),
184 curr_velocity_(0),
185 distance_(0),
186 fling_friction_(config.fling_friction),
187 deceleration_(ComputeDeceleration(fling_friction_)),
188 tuning_coeff_(ComputeDeceleration(0.84f)) {
191 Scroller::~Scroller() {
194 bool Scroller::ComputeScrollOffset(base::TimeTicks time,
195 gfx::Vector2dF* offset,
196 gfx::Vector2dF* velocity) {
197 DCHECK(offset);
198 DCHECK(velocity);
199 if (!ComputeScrollOffsetInternal(time)) {
200 *offset = gfx::Vector2dF(GetFinalX(), GetFinalY());
201 *velocity = gfx::Vector2dF();
202 return false;
205 *offset = gfx::Vector2dF(GetCurrX(), GetCurrY());
206 *velocity = gfx::Vector2dF(GetCurrVelocityX(), GetCurrVelocityY());
207 return true;
210 void Scroller::StartScroll(float start_x,
211 float start_y,
212 float dx,
213 float dy,
214 base::TimeTicks start_time) {
215 StartScroll(start_x,
216 start_y,
219 start_time,
220 base::TimeDelta::FromMilliseconds(kDefaultDurationMs));
223 void Scroller::StartScroll(float start_x,
224 float start_y,
225 float dx,
226 float dy,
227 base::TimeTicks start_time,
228 base::TimeDelta duration) {
229 DCHECK_GT(duration.ToInternalValue(), 0);
230 mode_ = SCROLL_MODE;
231 finished_ = false;
232 duration_ = duration;
233 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
234 start_time_ = start_time;
235 curr_x_ = start_x_ = start_x;
236 curr_y_ = start_y_ = start_y;
237 final_x_ = start_x + dx;
238 final_y_ = start_y + dy;
239 RecomputeDeltas();
240 curr_time_ = start_time_;
243 void Scroller::Fling(float start_x,
244 float start_y,
245 float velocity_x,
246 float velocity_y,
247 float min_x,
248 float max_x,
249 float min_y,
250 float max_y,
251 base::TimeTicks start_time) {
252 DCHECK(velocity_x || velocity_y);
254 // Continue a scroll or fling in progress.
255 if (flywheel_enabled_ && !finished_) {
256 float old_velocity_x = GetCurrVelocityX();
257 float old_velocity_y = GetCurrVelocityY();
258 if (Signum(velocity_x) == Signum(old_velocity_x) &&
259 Signum(velocity_y) == Signum(old_velocity_y)) {
260 velocity_x += old_velocity_x;
261 velocity_y += old_velocity_y;
265 mode_ = FLING_MODE;
266 finished_ = false;
268 float velocity = std::sqrt(velocity_x * velocity_x + velocity_y * velocity_y);
270 velocity_ = velocity;
271 duration_ = GetSplineFlingDuration(velocity);
272 DCHECK_GT(duration_.ToInternalValue(), 0);
273 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
274 start_time_ = start_time;
275 curr_time_ = start_time_;
276 curr_x_ = start_x_ = start_x;
277 curr_y_ = start_y_ = start_y;
279 float coeff_x = velocity == 0 ? 1.0f : velocity_x / velocity;
280 float coeff_y = velocity == 0 ? 1.0f : velocity_y / velocity;
282 double total_distance = GetSplineFlingDistance(velocity);
283 distance_ = total_distance * Signum(velocity);
285 min_x_ = min_x;
286 max_x_ = max_x;
287 min_y_ = min_y;
288 max_y_ = max_y;
290 final_x_ = start_x + total_distance * coeff_x;
291 final_x_ = Clamped(final_x_, min_x_, max_x_);
293 final_y_ = start_y + total_distance * coeff_y;
294 final_y_ = Clamped(final_y_, min_y_, max_y_);
296 RecomputeDeltas();
299 void Scroller::ExtendDuration(base::TimeDelta extend) {
300 base::TimeDelta passed = GetTimePassed();
301 duration_ = passed + extend;
302 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
303 finished_ = false;
306 void Scroller::SetFinalX(float new_x) {
307 final_x_ = new_x;
308 finished_ = false;
309 RecomputeDeltas();
312 void Scroller::SetFinalY(float new_y) {
313 final_y_ = new_y;
314 finished_ = false;
315 RecomputeDeltas();
318 void Scroller::AbortAnimation() {
319 curr_x_ = final_x_;
320 curr_y_ = final_y_;
321 curr_velocity_ = 0;
322 curr_time_ = start_time_ + duration_;
323 finished_ = true;
326 void Scroller::ForceFinished(bool finished) {
327 finished_ = finished;
330 bool Scroller::IsFinished() const {
331 return finished_;
334 base::TimeDelta Scroller::GetTimePassed() const {
335 return curr_time_ - start_time_;
338 base::TimeDelta Scroller::GetDuration() const {
339 return duration_;
342 float Scroller::GetCurrX() const {
343 return curr_x_;
346 float Scroller::GetCurrY() const {
347 return curr_y_;
350 float Scroller::GetCurrVelocity() const {
351 if (finished_)
352 return 0;
353 if (mode_ == FLING_MODE)
354 return curr_velocity_;
355 return velocity_ - deceleration_ * GetTimePassed().InSecondsF() * 0.5f;
358 float Scroller::GetCurrVelocityX() const {
359 return delta_x_norm_ * GetCurrVelocity();
362 float Scroller::GetCurrVelocityY() const {
363 return delta_y_norm_ * GetCurrVelocity();
366 float Scroller::GetStartX() const {
367 return start_x_;
370 float Scroller::GetStartY() const {
371 return start_y_;
374 float Scroller::GetFinalX() const {
375 return final_x_;
378 float Scroller::GetFinalY() const {
379 return final_y_;
382 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const {
383 return !finished_ && Signum(xvel) == Signum(delta_x_) &&
384 Signum(yvel) == Signum(delta_y_);
387 bool Scroller::ComputeScrollOffsetInternal(base::TimeTicks time) {
388 if (finished_)
389 return false;
391 if (time <= start_time_)
392 return true;
394 if (time == curr_time_)
395 return true;
397 base::TimeDelta time_passed = time - start_time_;
398 if (time_passed >= duration_) {
399 AbortAnimation();
400 return false;
403 curr_time_ = time;
405 const float u = time_passed.InSecondsF() * duration_seconds_reciprocal_;
406 switch (mode_) {
407 case UNDEFINED:
408 NOTREACHED() << "|StartScroll()| or |Fling()| must be called prior to "
409 "scroll offset computation.";
410 return false;
412 case SCROLL_MODE: {
413 float x = g_viscosity_constants.Get().ApplyViscosity(u);
415 curr_x_ = start_x_ + x * delta_x_;
416 curr_y_ = start_y_ + x * delta_y_;
417 } break;
419 case FLING_MODE: {
420 float distance_coef = 1.f;
421 float velocity_coef = 0.f;
422 g_spline_constants.Get().CalculateCoefficients(
423 u, &distance_coef, &velocity_coef);
425 curr_velocity_ = velocity_coef * distance_ * duration_seconds_reciprocal_;
427 curr_x_ = start_x_ + distance_coef * delta_x_;
428 curr_x_ = Clamped(curr_x_, min_x_, max_x_);
430 curr_y_ = start_y_ + distance_coef * delta_y_;
431 curr_y_ = Clamped(curr_y_, min_y_, max_y_);
433 float diff_x = std::abs(curr_x_ - final_x_);
434 float diff_y = std::abs(curr_y_ - final_y_);
435 if (diff_x < kThresholdForFlingEnd && diff_y < kThresholdForFlingEnd)
436 AbortAnimation();
437 } break;
440 return !finished_;
443 void Scroller::RecomputeDeltas() {
444 delta_x_ = final_x_ - start_x_;
445 delta_y_ = final_y_ - start_y_;
447 const float hyp = std::sqrt(delta_x_ * delta_x_ + delta_y_ * delta_y_);
448 if (hyp > kEpsilon) {
449 delta_x_norm_ = delta_x_ / hyp;
450 delta_y_norm_ = delta_y_ / hyp;
451 } else {
452 delta_x_norm_ = delta_y_norm_ = 1;
456 double Scroller::GetSplineDeceleration(float velocity) const {
457 return std::log(kInflexion * std::abs(velocity) /
458 (fling_friction_ * tuning_coeff_));
461 base::TimeDelta Scroller::GetSplineFlingDuration(float velocity) const {
462 const double l = GetSplineDeceleration(velocity);
463 const double decel_minus_one = kDecelerationRate - 1.0;
464 const double time_seconds = std::exp(l / decel_minus_one);
465 return base::TimeDelta::FromMicroseconds(time_seconds *
466 base::Time::kMicrosecondsPerSecond);
469 double Scroller::GetSplineFlingDistance(float velocity) const {
470 const double l = GetSplineDeceleration(velocity);
471 const double decel_minus_one = kDecelerationRate - 1.0;
472 return fling_friction_ * tuning_coeff_ *
473 std::exp(kDecelerationRate / decel_minus_one * l);
476 } // namespace ui