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