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 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 base::TimeDelta time_passed = time - start_time_; |
| 280 |
| 281 if (time_passed < base::TimeDelta()) { |
| 282 time_passed = base::TimeDelta(); |
| 283 } |
| 284 |
| 285 if (time_passed >= duration_) { |
| 286 curr_x_ = final_x_; |
| 287 curr_y_ = final_y_; |
| 288 curr_time_ = start_time_ + duration_; |
| 289 finished_ = true; |
| 290 return true; |
| 291 } |
| 292 |
| 293 curr_time_ = time; |
| 294 |
| 295 const float t = time_passed.InSecondsF() * duration_seconds_reciprocal_; |
| 296 |
| 297 switch (mode_) { |
| 298 case UNDEFINED: |
| 299 NOTREACHED() << "|StartScroll()| or |Fling()| must be called prior to " |
| 300 "scroll offset computation."; |
| 301 return false; |
| 302 |
| 303 case SCROLL_MODE: { |
| 304 float x = g_viscosity_constants.Get().ApplyViscosity(t); |
| 305 |
| 306 curr_x_ = start_x_ + x * delta_x_; |
| 307 curr_y_ = start_y_ + x * delta_y_; |
| 308 } break; |
| 309 |
| 310 case FLING_MODE: { |
| 311 float distance_coef = 1.f; |
| 312 float velocity_coef = 0.f; |
| 313 g_spline_constants.Get().CalculateCoefficients( |
| 314 t, &distance_coef, &velocity_coef); |
| 315 |
| 316 curr_velocity_ = velocity_coef * distance_ * duration_seconds_reciprocal_; |
| 317 |
| 318 curr_x_ = start_x_ + distance_coef * delta_x_; |
| 319 curr_x_ = Clamped(curr_x_, min_x_, max_x_); |
| 320 |
| 321 curr_y_ = start_y_ + distance_coef * delta_y_; |
| 322 curr_y_ = Clamped(curr_y_, min_y_, max_y_); |
| 323 |
| 324 if (ApproxEquals(curr_x_, final_x_) && ApproxEquals(curr_y_, final_y_)) { |
| 325 finished_ = true; |
| 326 } |
| 327 } break; |
| 328 } |
| 329 |
| 330 return true; |
| 331 } |
| 332 |
| 333 void Scroller::ExtendDuration(base::TimeDelta extend) { |
| 334 base::TimeDelta passed = GetTimePassed(); |
| 335 duration_ = passed + extend; |
| 336 duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF(); |
| 337 finished_ = false; |
| 338 } |
| 339 |
| 340 void Scroller::SetFinalX(float new_x) { |
| 341 final_x_ = new_x; |
| 342 finished_ = false; |
| 343 RecomputeDeltas(); |
| 344 } |
| 345 |
| 346 void Scroller::SetFinalY(float new_y) { |
| 347 final_y_ = new_y; |
| 348 finished_ = false; |
| 349 RecomputeDeltas(); |
| 350 } |
| 351 |
| 352 void Scroller::AbortAnimation() { |
| 353 curr_x_ = final_x_; |
| 354 curr_y_ = final_y_; |
| 355 curr_velocity_ = 0; |
| 356 curr_time_ = start_time_ + duration_; |
| 357 finished_ = true; |
| 358 } |
| 359 |
| 360 void Scroller::ForceFinished(bool finished) { finished_ = finished; } |
| 361 |
| 362 bool Scroller::IsFinished() const { return finished_; } |
| 363 |
| 364 base::TimeDelta Scroller::GetTimePassed() const { |
| 365 return curr_time_ - start_time_; |
| 366 } |
| 367 |
| 368 base::TimeDelta Scroller::GetDuration() const { return duration_; } |
| 369 |
| 370 float Scroller::GetCurrX() const { return curr_x_; } |
| 371 |
| 372 float Scroller::GetCurrY() const { return curr_y_; } |
| 373 |
| 374 float Scroller::GetCurrVelocity() const { |
| 375 if (finished_) |
| 376 return 0; |
| 377 if (mode_ == FLING_MODE) |
| 378 return curr_velocity_; |
| 379 return velocity_ - deceleration_ * GetTimePassed().InSecondsF() * 0.5f; |
| 380 } |
| 381 |
| 382 float Scroller::GetCurrVelocityX() const { |
| 383 return delta_x_norm_ * GetCurrVelocity(); |
| 384 } |
| 385 |
| 386 float Scroller::GetCurrVelocityY() const { |
| 387 return delta_y_norm_ * GetCurrVelocity(); |
| 388 } |
| 389 |
| 390 float Scroller::GetStartX() const { return start_x_; } |
| 391 |
| 392 float Scroller::GetStartY() const { return start_y_; } |
| 393 |
| 394 float Scroller::GetFinalX() const { return final_x_; } |
| 395 |
| 396 float Scroller::GetFinalY() const { return final_y_; } |
| 397 |
| 398 bool Scroller::IsScrollingInDirection(float xvel, float yvel) const { |
| 399 return !finished_ && Signum(xvel) == Signum(delta_x_) && |
| 400 Signum(yvel) == Signum(delta_y_); |
| 401 } |
| 402 |
| 403 void Scroller::RecomputeDeltas() { |
| 404 delta_x_ = final_x_ - start_x_; |
| 405 delta_y_ = final_y_ - start_y_; |
| 406 |
| 407 const float hyp = std::sqrt(delta_x_ * delta_x_ + delta_y_ * delta_y_); |
| 408 if (hyp > kEpsilon) { |
| 409 delta_x_norm_ = delta_x_ / hyp; |
| 410 delta_y_norm_ = delta_y_ / hyp; |
| 411 } else { |
| 412 delta_x_norm_ = delta_y_norm_ = 1; |
| 413 } |
| 414 } |
| 415 |
| 416 double Scroller::GetSplineDeceleration(float velocity) const { |
| 417 return std::log(kInflexion * std::abs(velocity) / |
| 418 (fling_friction_ * tuning_coeff_)); |
| 419 } |
| 420 |
| 421 base::TimeDelta Scroller::GetSplineFlingDuration(float velocity) const { |
| 422 const double l = GetSplineDeceleration(velocity); |
| 423 const double decel_minus_one = kDecelerationRate - 1.0; |
| 424 const double time_seconds = std::exp(l / decel_minus_one); |
| 425 return base::TimeDelta::FromMicroseconds(time_seconds * |
| 426 base::Time::kMicrosecondsPerSecond); |
| 427 } |
| 428 |
| 429 double Scroller::GetSplineFlingDistance(float velocity) const { |
| 430 const double l = GetSplineDeceleration(velocity); |
| 431 const double decel_minus_one = kDecelerationRate - 1.0; |
| 432 return fling_friction_ * tuning_coeff_ * |
| 433 std::exp(kDecelerationRate / decel_minus_one * l); |
| 434 } |
| 435 |
| 436 } // namespace gfx |
OLD | NEW |