• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ApproxEquals(float a,float b)28 bool ApproxEquals(float a, float b) {
29   return std::abs(a - b) < kEpsilon;
30 }
31 
32 struct ViscosityConstants {
ViscosityConstantsgfx::__anone76902cd0111::ViscosityConstants33   ViscosityConstants()
34       : viscous_fluid_scale_(8.f), viscous_fluid_normalize_(1.f) {
35     viscous_fluid_normalize_ = 1.0f / ApplyViscosity(1.0f);
36   }
37 
ApplyViscositygfx::__anone76902cd0111::ViscosityConstants38   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 
59 struct SplineConstants {
SplineConstantsgfx::__anone76902cd0111::SplineConstants60   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 
CalculateCoefficientsgfx::__anone76902cd0111::SplineConstants104   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 
ComputeDeceleration(float friction)131 float 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 
139 template <typename T>
Signum(T t)140 int Signum(T t) {
141   return (T(0) < t) - (t < T(0));
142 }
143 
144 template <typename T>
Clamped(T t,T a,T b)145 T 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.
150 base::LazyInstance<ViscosityConstants>::Leaky g_viscosity_constants =
151     LAZY_INSTANCE_INITIALIZER;
152 
153 base::LazyInstance<SplineConstants>::Leaky g_spline_constants =
154     LAZY_INSTANCE_INITIALIZER;
155 
156 }  // namespace
157 
Config()158 Scroller::Config::Config()
159     : fling_friction(kDefaultFriction),
160       flywheel_enabled(false) {}
161 
Scroller(const Config & config)162 Scroller::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 
~Scroller()188 Scroller::~Scroller() {}
189 
StartScroll(float start_x,float start_y,float dx,float dy,base::TimeTicks start_time)190 void 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 
StartScroll(float start_x,float start_y,float dx,float dy,base::TimeTicks start_time,base::TimeDelta duration)203 void 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 
Fling(float start_x,float start_y,float velocity_x,float velocity_y,float min_x,float max_x,float min_y,float max_y,base::TimeTicks start_time)222 void 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 
ComputeScrollOffset(base::TimeTicks time)275 bool 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 
ExtendDuration(base::TimeDelta extend)336 void 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 
SetFinalX(float new_x)343 void Scroller::SetFinalX(float new_x) {
344   final_x_ = new_x;
345   finished_ = false;
346   RecomputeDeltas();
347 }
348 
SetFinalY(float new_y)349 void Scroller::SetFinalY(float new_y) {
350   final_y_ = new_y;
351   finished_ = false;
352   RecomputeDeltas();
353 }
354 
AbortAnimation()355 void 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 
ForceFinished(bool finished)363 void Scroller::ForceFinished(bool finished) { finished_ = finished; }
364 
IsFinished() const365 bool Scroller::IsFinished() const { return finished_; }
366 
GetTimePassed() const367 base::TimeDelta Scroller::GetTimePassed() const {
368   return curr_time_ - start_time_;
369 }
370 
GetDuration() const371 base::TimeDelta Scroller::GetDuration() const { return duration_; }
372 
GetCurrX() const373 float Scroller::GetCurrX() const { return curr_x_; }
374 
GetCurrY() const375 float Scroller::GetCurrY() const { return curr_y_; }
376 
GetCurrVelocity() const377 float 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 
GetCurrVelocityX() const385 float Scroller::GetCurrVelocityX() const {
386   return delta_x_norm_ * GetCurrVelocity();
387 }
388 
GetCurrVelocityY() const389 float Scroller::GetCurrVelocityY() const {
390   return delta_y_norm_ * GetCurrVelocity();
391 }
392 
GetStartX() const393 float Scroller::GetStartX() const { return start_x_; }
394 
GetStartY() const395 float Scroller::GetStartY() const { return start_y_; }
396 
GetFinalX() const397 float Scroller::GetFinalX() const { return final_x_; }
398 
GetFinalY() const399 float Scroller::GetFinalY() const { return final_y_; }
400 
IsScrollingInDirection(float xvel,float yvel) const401 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const {
402   return !finished_ && Signum(xvel) == Signum(delta_x_) &&
403          Signum(yvel) == Signum(delta_y_);
404 }
405 
RecomputeDeltas()406 void 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 
GetSplineDeceleration(float velocity) const419 double Scroller::GetSplineDeceleration(float velocity) const {
420   return std::log(kInflexion * std::abs(velocity) /
421                   (fling_friction_ * tuning_coeff_));
422 }
423 
GetSplineFlingDuration(float velocity) const424 base::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 
GetSplineFlingDistance(float velocity) const432 double 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