Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(365)

Unified Diff: ui/gfx/android/scroller.cc

Issue 172933004: [Android] Port Scroller.java to C++ and use for fling animations (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/gfx/android/scroller.cc
diff --git a/ui/gfx/android/scroller.cc b/ui/gfx/android/scroller.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c125be2a64011fc1fda5a807f03c27496e1c4eed
--- /dev/null
+++ b/ui/gfx/android/scroller.cc
@@ -0,0 +1,418 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+
+#include "base/lazy_instance.h"
+#include "ui/gfx/android/scroller.h"
+#include "ui/gfx/android/view_configuration.h"
+
+namespace gfx {
+namespace {
+
+const base::TimeDelta kDefaultDuration = base::TimeDelta::FromMilliseconds(250);
aelias_OOO_until_Jul13 2014/02/21 02:42:35 Hmm, I'm concerned these will end up as static ini
jdduke (slow) 2014/02/21 23:33:02 I'll just compute the constant in kDecelerationRat
+
+const float kDecelerationRate = std::log(0.78f) / std::log(0.9f);
+
+// Tension lines cross at (kInflexion, 1)
+const float kInflexion = 0.35f;
+
+const float kEpsilon = 1e-5;
aelias_OOO_until_Jul13 2014/02/21 02:42:35 1e-5f instead?
jdduke (slow) 2014/02/21 23:33:02 Done.
+
+bool ApproxEquals(float a, float b) {
+ return std::abs(a - b) < kEpsilon;
+}
+
+struct ViscosityConstants {
+ ViscosityConstants()
+ : viscous_fluid_scale_(8.f), viscous_fluid_normalize_(1.f) {
+ viscous_fluid_normalize_ = 1.0f / ApplyViscosity(1.0f);
+ }
+
+ float ApplyViscosity(float x) {
+ x *= viscous_fluid_scale_;
+ if (x < 1.0f) {
+ x -= (1.0f - std::exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - std::exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= viscous_fluid_normalize_;
+ return x;
+ }
+
+ private:
+ // This controls the viscous fluid effect (how much of it)
+ float viscous_fluid_scale_;
+ float viscous_fluid_normalize_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViscosityConstants);
+};
+
+struct SplineConstants {
+ SplineConstants() {
+ const float kStartTension = 0.5f;
+ const float kEndTension = 1.0f;
+ const float kP1 = kStartTension * kInflexion;
+ const float kP2 = 1.0f - kEndTension * (1.0f - kInflexion);
+
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NUM_SAMPLES; i++) {
+ const float alpha = static_cast<float>(i) / NUM_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
+ if (std::abs(tx - alpha) < 1E-5)
aelias_OOO_until_Jul13 2014/02/21 02:42:35 kEpsilon?
jdduke (slow) 2014/02/21 23:33:02 Done.
+ break;
+ if (tx > alpha)
+ x_max = x;
+ else
+ x_min = x;
+ }
+ spline_position_[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * kStartTension + y) + y * y * y;
+ if (std::abs(dy - alpha) < 1E-5)
aelias_OOO_until_Jul13 2014/02/21 02:42:35 kEpsilon?
jdduke (slow) 2014/02/21 23:33:02 Done.
+ break;
+ if (dy > alpha)
+ y_max = y;
+ else
+ y_min = y;
+ }
+ spline_time_[i] = coef * ((1.0f - y) * kP1 + y * kP2) + y * y * y;
+ }
+ spline_position_[NUM_SAMPLES] = spline_time_[NUM_SAMPLES] = 1.0f;
+ }
+
+ void CalculateCoefficients(float t,
+ float* distance_coef,
+ float* velocity_coef) {
+ *distance_coef = 1.f;
+ *velocity_coef = 0.f;
+ const int index = static_cast<int>(NUM_SAMPLES * t);
+ if (index < NUM_SAMPLES) {
+ const float t_inf = static_cast<float>(index) / NUM_SAMPLES;
+ const float t_sup = static_cast<float>(index + 1) / NUM_SAMPLES;
+ const float d_inf = spline_position_[index];
+ const float d_sup = spline_position_[index + 1];
+ *velocity_coef = (d_sup - d_inf) / (t_sup - t_inf);
+ *distance_coef = d_inf + (t - t_inf) * *velocity_coef;
+ }
+ }
+
+ private:
+ enum {
+ NUM_SAMPLES = 100
+ };
+
+ float spline_position_[NUM_SAMPLES + 1];
+ float spline_time_[NUM_SAMPLES + 1];
+
+ DISALLOW_COPY_AND_ASSIGN(SplineConstants);
+};
+
+float ComputeDeceleration(float friction) {
+ const float kGravityEarth = 9.80665f;
+ return kGravityEarth // g (m/s^2)
+ * 39.37f // inch/meter
+ * 160.f // pixels/inch
+ * friction;
+}
+
+template <typename T>
+int Signum(T t) {
+ return (T(0) < t) - (t < T(0));
+}
+
+template <typename T>
+T Clamped(T t, T a, T b) {
+ return t < a ? a : (t > b ? b : t);
+}
+
+// Leaky to allow access from the impl thread.
+base::LazyInstance<ViscosityConstants>::Leaky g_viscosity_constants =
+ LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<SplineConstants>::Leaky g_spline_constants =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+Scroller::Scroller(bool enable_flywheel)
+ : mode_(UNDEFINED),
+ start_x_(0),
+ start_y_(0),
+ final_x_(0),
+ final_y_(0),
+ min_x_(0),
+ max_x_(0),
+ min_y_(0),
+ max_y_(0),
+ curr_x_(0),
+ curr_y_(0),
+ duration_seconds_reciprocal_(1),
+ delta_x_(0),
+ delta_x_norm_(1),
+ delta_y_(0),
+ delta_y_norm_(1),
+ finished_(true),
+ flywheel_enabled_(enable_flywheel),
+ velocity_(0),
+ curr_velocity_(0),
+ distance_(0),
+ fling_friction_(ViewConfiguration::GetScrollFriction()),
+ deceleration_(ComputeDeceleration(fling_friction_)),
+ tuning_coeff_(ComputeDeceleration(0.84f)) {}
+
+Scroller::~Scroller() {}
+
+void Scroller::StartScroll(float start_x,
+ float start_y,
+ float dx,
+ float dy,
+ base::TimeTicks start_time) {
+ StartScroll(start_x, start_y, dx, dy, start_time, kDefaultDuration);
+}
+
+void Scroller::StartScroll(float start_x,
+ float start_y,
+ float dx,
+ float dy,
+ base::TimeTicks start_time,
+ base::TimeDelta duration) {
+ mode_ = SCROLL_MODE;
+ finished_ = false;
+ duration_ = duration;
+ duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
+ start_time_ = start_time;
+ start_x_ = start_x;
+ start_y_ = start_y;
+ final_x_ = start_x + dx;
+ final_y_ = start_y + dy;
+ RecomputeDeltas();
+ curr_time_ = start_time_;
+}
+
+void Scroller::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) {
+ // Continue a scroll or fling in progress
+ if (flywheel_enabled_ && !finished_) {
+ float old_velocity_x = GetCurrVelocityX();
+ float old_velocity_y = GetCurrVelocityY();
+ if (Signum(velocity_x) == Signum(old_velocity_x) &&
+ Signum(velocity_y) == Signum(old_velocity_y)) {
+ velocity_x += old_velocity_x;
+ velocity_y += old_velocity_y;
+ }
+ }
+
+ mode_ = FLING_MODE;
+ finished_ = false;
+
+ float velocity = std::sqrt(velocity_x * velocity_x + velocity_y * velocity_y);
+
+ velocity_ = velocity;
+ duration_ = GetSplineFlingDuration(velocity);
+ duration_seconds_reciprocal_ = 1.0 / duration_.InSecondsF();
+ start_time_ = start_time;
+ curr_time_ = start_time_;
+ start_x_ = start_x;
+ start_y_ = start_y;
+
+ float coeff_x = velocity == 0 ? 1.0f : velocity_x / velocity;
+ float coeff_y = velocity == 0 ? 1.0f : velocity_y / velocity;
+
+ double total_distance = GetSplineFlingDistance(velocity);
+ distance_ = total_distance * Signum(velocity);
+
+ min_x_ = min_x;
+ max_x_ = max_x;
+ min_y_ = min_y;
+ max_y_ = max_y;
+
+ final_x_ = start_x + total_distance * coeff_x;
+ final_x_ = Clamped(final_x_, min_x_, max_x_);
+
+ final_y_ = start_y + total_distance * coeff_y;
+ final_y_ = Clamped(final_y_, min_y_, max_y_);
+
+ RecomputeDeltas();
+}
+
+bool Scroller::ComputeScrollOffset(base::TimeTicks time) {
+ if (finished_)
+ return false;
+
+ base::TimeDelta time_passed = time - start_time_;
+
+ if (time_passed < base::TimeDelta()) {
+ time_passed = base::TimeDelta();
+ }
+
+ if (time_passed >= duration_) {
+ curr_x_ = final_x_;
+ curr_y_ = final_y_;
+ curr_time_ = start_time_ + duration_;
+ finished_ = true;
+ return true;
+ }
+
+ curr_time_ = time;
+
+ const float t = time_passed.InSecondsF() * duration_seconds_reciprocal_;
+
+ switch (mode_) {
+ case UNDEFINED:
+ NOTREACHED() << "Invalid scroll mode when computing scroll offset.";
+ return false;
+
+ case SCROLL_MODE: {
+ float x = g_viscosity_constants.Get().ApplyViscosity(t);
+
+ curr_x_ = start_x_ + x * delta_x_;
+ curr_y_ = start_y_ + x * delta_y_;
+ } break;
+
+ case FLING_MODE: {
+ float distance_coef = 1.f;
+ float velocity_coef = 0.f;
+ g_spline_constants.Get().CalculateCoefficients(
+ t, &distance_coef, &velocity_coef);
+
+ curr_velocity_ = velocity_coef * distance_ * duration_seconds_reciprocal_;
+
+ curr_x_ = start_x_ + distance_coef * delta_x_;
+ curr_x_ = Clamped(curr_x_, min_x_, max_x_);
+
+ curr_y_ = start_y_ + distance_coef * delta_y_;
+ curr_y_ = Clamped(curr_y_, min_y_, max_y_);
+
+ if (ApproxEquals(curr_x_, final_x_) && ApproxEquals(curr_y_, final_y_)) {
+ finished_ = true;
+ }
+ } break;
+ }
+
+ return true;
+}
+
+void Scroller::ExtendDuration(base::TimeDelta extend) {
+ base::TimeDelta passed = GetTimePassed();
+ duration_ = passed + extend;
+ duration_seconds_reciprocal_ = 1. / duration_.InSecondsF();
+ finished_ = false;
+}
+
+void Scroller::SetFinalX(float new_x) {
+ final_x_ = new_x;
+ finished_ = false;
+ RecomputeDeltas();
+}
+
+void Scroller::SetFinalY(float new_y) {
+ final_y_ = new_y;
+ finished_ = false;
+ RecomputeDeltas();
+}
+
+void Scroller::AbortAnimation() {
+ curr_x_ = final_x_;
+ curr_y_ = final_y_;
+ curr_time_ = start_time_ + duration_;
+ finished_ = true;
+}
+
+void Scroller::ForceFinished(bool finished) { finished_ = finished; }
+
+bool Scroller::IsFinished() const { return finished_; }
+
+base::TimeDelta Scroller::GetTimePassed() const {
+ return curr_time_ - start_time_;
+}
+
+base::TimeDelta Scroller::GetDuration() const { return duration_; }
+
+float Scroller::GetCurrX() const { return curr_x_; }
+
+float Scroller::GetCurrY() const { return curr_y_; }
+
+float Scroller::GetCurrVelocity() const {
+ return mode_ == FLING_MODE
+ ? curr_velocity_
+ : velocity_ - deceleration_ * GetTimePassed().InSecondsF() * 0.5f;
+}
+
+float Scroller::GetCurrVelocityX() const {
+ return delta_x_norm_ * GetCurrVelocity();
+}
+
+float Scroller::GetCurrVelocityY() const {
+ return delta_y_norm_ * GetCurrVelocity();
+}
+
+float Scroller::GetStartX() const { return start_x_; }
+
+float Scroller::GetStartY() const { return start_y_; }
+
+float Scroller::GetFinalX() const { return final_x_; }
+
+float Scroller::GetFinalY() const { return final_y_; }
+
+bool Scroller::IsScrollingInDirection(float xvel, float yvel) const {
+ return !finished_ && Signum(xvel) == Signum(delta_x_) &&
+ Signum(yvel) == Signum(delta_y_);
+}
+
+void Scroller::RecomputeDeltas() {
+ delta_x_ = final_x_ - start_x_;
+ delta_y_ = final_y_ - start_y_;
+
+ const float hyp = std::sqrt(delta_x_ * delta_x_ + delta_y_ * delta_y_);
+ if (hyp > kEpsilon) {
+ delta_x_norm_ = delta_x_ / hyp;
+ delta_y_norm_ = delta_y_ / hyp;
+ } else {
+ delta_x_norm_ = delta_y_norm_ = 1;
+ }
+}
+
+double Scroller::GetSplineDeceleration(float velocity) const {
+ return std::log(kInflexion * std::abs(velocity) /
+ (fling_friction_ * tuning_coeff_));
+}
+
+base::TimeDelta Scroller::GetSplineFlingDuration(float velocity) const {
+ const double l = GetSplineDeceleration(velocity);
+ const double decel_minus_one = kDecelerationRate - 1.0;
+ const double time_seconds = std::exp(l / decel_minus_one);
+ return base::TimeDelta::FromMicroseconds(time_seconds *
+ base::Time::kMicrosecondsPerSecond);
+}
+
+double Scroller::GetSplineFlingDistance(float velocity) const {
+ const double l = GetSplineDeceleration(velocity);
+ const double decel_minus_one = kDecelerationRate - 1.0;
+ return fling_friction_ * tuning_coeff_ *
+ std::exp(kDecelerationRate / decel_minus_one * l);
+}
+
+} // namespace gfx

Powered by Google App Engine
This is Rietveld 408576698