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