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