Home | History | Annotate | Download | only in android
      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 bool ApproxEquals(float a, float b) {
     29   return std::abs(a - b) < kEpsilon;
     30 }
     31 
     32 struct 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 
     59 struct 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 
    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>
    140 int Signum(T t) {
    141   return (T(0) < t) - (t < T(0));
    142 }
    143 
    144 template <typename T>
    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 
    158 Scroller::Config::Config()
    159     : fling_friction(kDefaultFriction),
    160       flywheel_enabled(false) {}
    161 
    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 
    188 Scroller::~Scroller() {}
    189 
    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 
    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 
    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 
    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 
    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 
    343 void Scroller::SetFinalX(float new_x) {
    344   final_x_ = new_x;
    345   finished_ = false;
    346   RecomputeDeltas();
    347 }
    348 
    349 void Scroller::SetFinalY(float new_y) {
    350   final_y_ = new_y;
    351   finished_ = false;
    352   RecomputeDeltas();
    353 }
    354 
    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 
    363 void Scroller::ForceFinished(bool finished) { finished_ = finished; }
    364 
    365 bool Scroller::IsFinished() const { return finished_; }
    366 
    367 base::TimeDelta Scroller::GetTimePassed() const {
    368   return curr_time_ - start_time_;
    369 }
    370 
    371 base::TimeDelta Scroller::GetDuration() const { return duration_; }
    372 
    373 float Scroller::GetCurrX() const { return curr_x_; }
    374 
    375 float Scroller::GetCurrY() const { return curr_y_; }
    376 
    377 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 
    385 float Scroller::GetCurrVelocityX() const {
    386   return delta_x_norm_ * GetCurrVelocity();
    387 }
    388 
    389 float Scroller::GetCurrVelocityY() const {
    390   return delta_y_norm_ * GetCurrVelocity();
    391 }
    392 
    393 float Scroller::GetStartX() const { return start_x_; }
    394 
    395 float Scroller::GetStartY() const { return start_y_; }
    396 
    397 float Scroller::GetFinalX() const { return final_x_; }
    398 
    399 float Scroller::GetFinalY() const { return final_y_; }
    400 
    401 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const {
    402   return !finished_ && Signum(xvel) == Signum(delta_x_) &&
    403          Signum(yvel) == Signum(delta_y_);
    404 }
    405 
    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 
    419 double Scroller::GetSplineDeceleration(float velocity) const {
    420   return std::log(kInflexion * std::abs(velocity) /
    421                   (fling_friction_ * tuning_coeff_));
    422 }
    423 
    424 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 
    432 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