summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskobes@chromium.org <skobes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-18 22:37:26 +0000
committerskobes@chromium.org <skobes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-18 22:37:26 +0000
commit7a7d5bec832fd997b6b2eef3ec9c0906b74ef16d (patch)
tree618f497d0ca76b781df27da3ae6de9188b00bcdf
parent472de00aa7609c352c95d64abb125145296fc73a (diff)
downloadchromium_src-7a7d5bec832fd997b6b2eef3ec9c0906b74ef16d.zip
chromium_src-7a7d5bec832fd997b6b2eef3ec9c0906b74ef16d.tar.gz
chromium_src-7a7d5bec832fd997b6b2eef3ec9c0906b74ef16d.tar.bz2
Scroll offset animation curve retargeting.
Adds support for updating the target of an in-progress input-triggered impl-side scroll offset animation, keeping the velocity function continuous. BUG=575 Review URL: https://codereview.chromium.org/393713002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284228 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--cc/animation/scroll_offset_animation_curve.cc78
-rw-r--r--cc/animation/scroll_offset_animation_curve.h7
-rw-r--r--cc/animation/scroll_offset_animation_curve_unittest.cc25
-rw-r--r--cc/animation/timing_function.cc4
-rw-r--r--cc/animation/timing_function.h2
-rw-r--r--cc/trees/layer_tree_host_impl.cc24
-rw-r--r--cc/trees/layer_tree_host_impl_unittest.cc15
-rw-r--r--ui/gfx/geometry/cubic_bezier.cc41
-rw-r--r--ui/gfx/geometry/cubic_bezier.h3
-rw-r--r--ui/gfx/geometry/cubic_bezier_unittest.cc28
10 files changed, 190 insertions, 37 deletions
diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc
index a7283b8..8211a95 100644
--- a/cc/animation/scroll_offset_animation_curve.cc
+++ b/cc/animation/scroll_offset_animation_curve.cc
@@ -15,6 +15,35 @@ const double kDurationDivisor = 60.0;
namespace cc {
+namespace {
+
+static float MaximumDimension(gfx::Vector2dF delta) {
+ return std::max(std::abs(delta.x()), std::abs(delta.y()));
+}
+
+static base::TimeDelta DurationFromDelta(gfx::Vector2dF delta) {
+ // The duration of a scroll animation depends on the size of the scroll.
+ // The exact relationship between the size and the duration isn't specified
+ // by the CSSOM View smooth scroll spec and is instead left up to user agents
+ // to decide. The calculation performed here will very likely be further
+ // tweaked before the smooth scroll API ships.
+ return base::TimeDelta::FromMicroseconds(
+ (std::sqrt(MaximumDimension(delta)) / kDurationDivisor) *
+ base::Time::kMicrosecondsPerSecond);
+}
+
+static scoped_ptr<TimingFunction> EaseOutWithInitialVelocity(double velocity) {
+ // Based on EaseInOutTimingFunction::Create with first control point rotated.
+ const double r2 = 0.42 * 0.42;
+ const double v2 = velocity * velocity;
+ const double x1 = std::sqrt(r2 / (v2 + 1));
+ const double y1 = std::sqrt(r2 * v2 / (v2 + 1));
+ return CubicBezierTimingFunction::Create(x1, y1, 0.58, 1)
+ .PassAs<TimingFunction>();
+}
+
+} // namespace
+
scoped_ptr<ScrollOffsetAnimationCurve> ScrollOffsetAnimationCurve::Create(
const gfx::Vector2dF& target_value,
scoped_ptr<TimingFunction> timing_function) {
@@ -33,22 +62,12 @@ ScrollOffsetAnimationCurve::~ScrollOffsetAnimationCurve() {}
void ScrollOffsetAnimationCurve::SetInitialValue(
const gfx::Vector2dF& initial_value) {
initial_value_ = initial_value;
-
- // The duration of a scroll animation depends on the size of the scroll.
- // The exact relationship between the size and the duration isn't specified
- // by the CSSOM View smooth scroll spec and is instead left up to user agents
- // to decide. The calculation performed here will very likely be further
- // tweaked before the smooth scroll API ships.
- float delta_x = std::abs(target_value_.x() - initial_value_.x());
- float delta_y = std::abs(target_value_.y() - initial_value_.y());
- float max_delta = std::max(delta_x, delta_y);
- duration_ = base::TimeDelta::FromMicroseconds(
- (std::sqrt(max_delta) / kDurationDivisor) *
- base::Time::kMicrosecondsPerSecond);
+ total_animation_duration_ = DurationFromDelta(target_value_ - initial_value_);
}
gfx::Vector2dF ScrollOffsetAnimationCurve::GetValue(double t) const {
- double duration = duration_.InSecondsF();
+ double duration = (total_animation_duration_ - last_retarget_).InSecondsF();
+ t -= last_retarget_.InSecondsF();
if (t <= 0)
return initial_value_;
@@ -64,7 +83,7 @@ gfx::Vector2dF ScrollOffsetAnimationCurve::GetValue(double t) const {
}
double ScrollOffsetAnimationCurve::Duration() const {
- return duration_.InSecondsF();
+ return total_animation_duration_.InSecondsF();
}
AnimationCurve::CurveType ScrollOffsetAnimationCurve::Type() const {
@@ -77,8 +96,37 @@ scoped_ptr<AnimationCurve> ScrollOffsetAnimationCurve::Clone() const {
scoped_ptr<ScrollOffsetAnimationCurve> curve_clone =
Create(target_value_, timing_function.Pass());
curve_clone->initial_value_ = initial_value_;
- curve_clone->duration_ = duration_;
+ curve_clone->total_animation_duration_ = total_animation_duration_;
+ curve_clone->last_retarget_ = last_retarget_;
return curve_clone.PassAs<AnimationCurve>();
}
+void ScrollOffsetAnimationCurve::UpdateTarget(
+ double t,
+ const gfx::Vector2dF& new_target) {
+ gfx::Vector2dF current_position = GetValue(t);
+ gfx::Vector2dF old_delta = target_value_ - initial_value_;
+ gfx::Vector2dF new_delta = new_target - current_position;
+
+ double old_duration =
+ (total_animation_duration_ - last_retarget_).InSecondsF();
+ double new_duration = DurationFromDelta(new_delta).InSecondsF();
+
+ double old_velocity = timing_function_->Velocity(
+ (t - last_retarget_.InSecondsF()) / old_duration);
+
+ // TimingFunction::Velocity gives the slope of the curve from 0 to 1.
+ // To match the "true" velocity in px/sec we must adjust this slope for
+ // differences in duration and scroll delta between old and new curves.
+ double new_velocity =
+ old_velocity * (new_duration / old_duration) *
+ (MaximumDimension(old_delta) / MaximumDimension(new_delta));
+
+ initial_value_ = current_position;
+ target_value_ = new_target;
+ total_animation_duration_ = base::TimeDelta::FromSecondsD(t + new_duration);
+ last_retarget_ = base::TimeDelta::FromSecondsD(t);
+ timing_function_ = EaseOutWithInitialVelocity(new_velocity);
+}
+
} // namespace cc
diff --git a/cc/animation/scroll_offset_animation_curve.h b/cc/animation/scroll_offset_animation_curve.h
index 543ae37..3d71e13 100644
--- a/cc/animation/scroll_offset_animation_curve.h
+++ b/cc/animation/scroll_offset_animation_curve.h
@@ -24,6 +24,8 @@ class CC_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve {
void SetInitialValue(const gfx::Vector2dF& initial_value);
gfx::Vector2dF GetValue(double t) const;
+ gfx::Vector2dF target_value() const { return target_value_; }
+ void UpdateTarget(double t, const gfx::Vector2dF& new_target);
// AnimationCurve implementation
virtual double Duration() const OVERRIDE;
@@ -36,7 +38,10 @@ class CC_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve {
gfx::Vector2dF initial_value_;
gfx::Vector2dF target_value_;
- base::TimeDelta duration_;
+ base::TimeDelta total_animation_duration_;
+
+ // Time from animation start to most recent UpdateTarget.
+ base::TimeDelta last_retarget_;
scoped_ptr<TimingFunction> timing_function_;
diff --git a/cc/animation/scroll_offset_animation_curve_unittest.cc b/cc/animation/scroll_offset_animation_curve_unittest.cc
index 63445d9..1b8fdb5 100644
--- a/cc/animation/scroll_offset_animation_curve_unittest.cc
+++ b/cc/animation/scroll_offset_animation_curve_unittest.cc
@@ -119,5 +119,30 @@ TEST(ScrollOffsetAnimationCurveTest, Clone) {
EXPECT_NEAR(37.4168f, value.y(), 0.00015f);
}
+TEST(ScrollOffsetAnimationCurveTest, UpdateTarget) {
+ gfx::Vector2dF initial_value(0.f, 0.f);
+ gfx::Vector2dF target_value(0.f, 3600.f);
+ scoped_ptr<ScrollOffsetAnimationCurve> curve(
+ ScrollOffsetAnimationCurve::Create(
+ target_value, EaseInOutTimingFunction::Create().Pass()));
+ curve->SetInitialValue(initial_value);
+ EXPECT_EQ(1.0, curve->Duration());
+ EXPECT_EQ(1800.0, curve->GetValue(0.5).y());
+ EXPECT_EQ(3600.0, curve->GetValue(1.0).y());
+
+ curve->UpdateTarget(0.5, gfx::Vector2dF(0.0, 9900.0));
+
+ EXPECT_EQ(2.0, curve->Duration());
+ EXPECT_EQ(1800.0, curve->GetValue(0.5).y());
+ EXPECT_NEAR(5566.49, curve->GetValue(1.0).y(), 0.01);
+ EXPECT_EQ(9900.0, curve->GetValue(2.0).y());
+
+ curve->UpdateTarget(1.0, gfx::Vector2dF(0.0, 7200.0));
+
+ EXPECT_NEAR(1.674, curve->Duration(), 0.01);
+ EXPECT_NEAR(5566.49, curve->GetValue(1.0).y(), 0.01);
+ EXPECT_EQ(7200.0, curve->GetValue(1.674).y());
+}
+
} // namespace
} // namespace cc
diff --git a/cc/animation/timing_function.cc b/cc/animation/timing_function.cc
index bf11c20..71319e1 100644
--- a/cc/animation/timing_function.cc
+++ b/cc/animation/timing_function.cc
@@ -45,6 +45,10 @@ void CubicBezierTimingFunction::Range(float* min, float* max) const {
*max = static_cast<float>(max_d);
}
+float CubicBezierTimingFunction::Velocity(double x) const {
+ return static_cast<float>(bezier_.Slope(x));
+}
+
// These numbers come from
// http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag.
scoped_ptr<TimingFunction> EaseTimingFunction::Create() {
diff --git a/cc/animation/timing_function.h b/cc/animation/timing_function.h
index 647701f..cc97939 100644
--- a/cc/animation/timing_function.h
+++ b/cc/animation/timing_function.h
@@ -22,6 +22,7 @@ class CC_EXPORT TimingFunction : public FloatAnimationCurve {
// The smallest and largest values returned by GetValue for inputs in
// [0, 1].
virtual void Range(float* min, float* max) const = 0;
+ virtual float Velocity(double time) const = 0;
protected:
TimingFunction();
@@ -41,6 +42,7 @@ class CC_EXPORT CubicBezierTimingFunction : public TimingFunction {
virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
virtual void Range(float* min, float* max) const OVERRIDE;
+ virtual float Velocity(double time) const OVERRIDE;
protected:
CubicBezierTimingFunction(double x1, double y1, double x2, double y2);
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index a2c8111..f137cb4 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2302,9 +2302,25 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
InputHandler::ScrollStatus LayerTreeHostImpl::ScrollAnimated(
const gfx::Point& viewport_point,
const gfx::Vector2dF& scroll_delta) {
- if (CurrentlyScrollingLayer()) {
- // TODO(skobes): Update the target of the existing animation.
- return ScrollIgnored;
+ if (LayerImpl* layer_impl = CurrentlyScrollingLayer()) {
+ Animation* animation =
+ layer_impl->layer_animation_controller()->GetAnimation(
+ Animation::ScrollOffset);
+ if (!animation)
+ return ScrollIgnored;
+
+ ScrollOffsetAnimationCurve* curve =
+ animation->curve()->ToScrollOffsetAnimationCurve();
+
+ gfx::Vector2dF new_target = curve->target_value() + scroll_delta;
+ new_target.SetToMax(gfx::Vector2dF());
+ new_target.SetToMin(layer_impl->MaxScrollOffset());
+
+ curve->UpdateTarget(
+ animation->TrimTimeToCurrentIteration(CurrentFrameTimeTicks()),
+ new_target);
+
+ return ScrollStarted;
}
// ScrollAnimated is only used for wheel scrolls. We use the same bubbling
// behavior as ScrollBy to determine which layer to animate, but we do not
@@ -2341,7 +2357,7 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollAnimated(
curve->SetInitialValue(current_offset);
scoped_ptr<Animation> animation =
- Animation::Create(curve->Clone().Pass(),
+ Animation::Create(curve.PassAs<AnimationCurve>(),
AnimationIdProvider::NextAnimationId(),
AnimationIdProvider::NextGroupId(),
Animation::ScrollOffset);
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 05fc478..d05a493 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -6670,7 +6670,7 @@ TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) {
}
TEST_F(LayerTreeHostImplTest, ScrollAnimated) {
- SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ SetupScrollAndContentsLayers(gfx::Size(100, 150));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
@@ -6693,10 +6693,21 @@ TEST_F(LayerTreeHostImplTest, ScrollAnimated) {
float y = scrolling_layer->TotalScrollOffset().y();
EXPECT_TRUE(y > 1 && y < 49);
+ // Update target.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)));
+
host_impl_->Animate(start_time + base::TimeDelta::FromMilliseconds(200));
host_impl_->UpdateAnimationState(true);
- EXPECT_EQ(gfx::Vector2dF(0, 50), scrolling_layer->TotalScrollOffset());
+ y = scrolling_layer->TotalScrollOffset().y();
+ EXPECT_TRUE(y > 50 && y < 100);
+ EXPECT_EQ(scrolling_layer, host_impl_->CurrentlyScrollingLayer());
+
+ host_impl_->Animate(start_time + base::TimeDelta::FromMilliseconds(250));
+ host_impl_->UpdateAnimationState(true);
+
+ EXPECT_EQ(gfx::Vector2dF(0, 100), scrolling_layer->TotalScrollOffset());
EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
}
diff --git a/ui/gfx/geometry/cubic_bezier.cc b/ui/gfx/geometry/cubic_bezier.cc
index 3d7e8fe..f9f786e 100644
--- a/ui/gfx/geometry/cubic_bezier.cc
+++ b/ui/gfx/geometry/cubic_bezier.cc
@@ -16,19 +16,26 @@ namespace {
static const double kBezierEpsilon = 1e-7;
static const int MAX_STEPS = 30;
-static double eval_bezier(double x1, double x2, double t) {
- const double x1_times_3 = 3.0 * x1;
- const double x2_times_3 = 3.0 * x2;
- const double h3 = x1_times_3;
- const double h1 = x1_times_3 - x2_times_3 + 1.0;
- const double h2 = x2_times_3 - 6.0 * x1;
+static double eval_bezier(double p1, double p2, double t) {
+ const double p1_times_3 = 3.0 * p1;
+ const double p2_times_3 = 3.0 * p2;
+ const double h3 = p1_times_3;
+ const double h1 = p1_times_3 - p2_times_3 + 1.0;
+ const double h2 = p2_times_3 - 6.0 * p1;
return t * (t * (t * h1 + h2) + h3);
}
+static double eval_bezier_derivative(double p1, double p2, double t) {
+ const double h1 = 9.0 * p1 - 9.0 * p2 + 3.0;
+ const double h2 = 6.0 * p2 - 12.0 * p1;
+ const double h3 = 3.0 * p1;
+ return t * (t * h1 + h2) + h3;
+}
+
+// Finds t such that eval_bezier(x1, x2, t) = x.
+// There is a unique solution if x1 and x2 lie within (0, 1).
static double bezier_interp(double x1,
- double y1,
double x2,
- double y2,
double x) {
DCHECK_GE(1.0, x1);
DCHECK_LE(0.0, x1);
@@ -39,10 +46,6 @@ static double bezier_interp(double x1,
x2 = std::min(std::max(x2, 0.0), 1.0);
x = std::min(std::max(x, 0.0), 1.0);
- // Step 1. Find the t corresponding to the given x. I.e., we want t such that
- // eval_bezier(x1, x2, t) = x. There is a unique solution if x1 and x2 lie
- // within (0, 1).
- //
// We're just going to do bisection for now (for simplicity), but we could
// easily do some newton steps if this turns out to be a bottleneck.
double t = 0.0;
@@ -58,8 +61,7 @@ static double bezier_interp(double x1,
// because we exceeded MAX_STEPS. Do a DCHECK here to confirm.
DCHECK_GT(kBezierEpsilon, std::abs(eval_bezier(x1, x2, t) - x));
- // Step 2. Return the interpolated y values at the t we computed above.
- return eval_bezier(y1, y2, t);
+ return t;
}
} // namespace
@@ -75,7 +77,14 @@ CubicBezier::~CubicBezier() {
}
double CubicBezier::Solve(double x) const {
- return bezier_interp(x1_, y1_, x2_, y2_, x);
+ return eval_bezier(y1_, y2_, bezier_interp(x1_, x2_, x));
+}
+
+double CubicBezier::Slope(double x) const {
+ double t = bezier_interp(x1_, x2_, x);
+ double dx_dt = eval_bezier_derivative(x1_, x2_, t);
+ double dy_dt = eval_bezier_derivative(y1_, y2_, t);
+ return dy_dt / dx_dt;
}
void CubicBezier::Range(double* min, double* max) const {
@@ -85,6 +94,8 @@ void CubicBezier::Range(double* min, double* max) const {
return;
// Represent the function's derivative in the form at^2 + bt + c.
+ // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
+ // but does not actually give the slope of the curve.)
double a = 3 * (y1_ - y2_) + 1;
double b = 2 * (y2_ - 2 * y1_);
double c = y1_;
diff --git a/ui/gfx/geometry/cubic_bezier.h b/ui/gfx/geometry/cubic_bezier.h
index 645dfb7..66d5e8a 100644
--- a/ui/gfx/geometry/cubic_bezier.h
+++ b/ui/gfx/geometry/cubic_bezier.h
@@ -18,6 +18,9 @@ class GFX_EXPORT CubicBezier {
// Returns an approximation of y at the given x.
double Solve(double x) const;
+ // Returns an approximation of dy/dx at the given x.
+ double Slope(double x) const;
+
// Sets |min| and |max| to the bezier's minimum and maximium y values in the
// interval [0, 1].
void Range(double* min, double* max) const;
diff --git a/ui/gfx/geometry/cubic_bezier_unittest.cc b/ui/gfx/geometry/cubic_bezier_unittest.cc
index 4fd60e8..168817c 100644
--- a/ui/gfx/geometry/cubic_bezier_unittest.cc
+++ b/ui/gfx/geometry/cubic_bezier_unittest.cc
@@ -136,5 +136,33 @@ TEST(CubicBezierTest, Range) {
EXPECT_EQ(1.f, max);
}
+TEST(CubicBezierTest, Slope) {
+ CubicBezier function(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Slope(0), 0, epsilon);
+ EXPECT_NEAR(function.Slope(0.05), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(0.1), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.15), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.2), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.25), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.3), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.35), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.4), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.45), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.5), 1.33333, epsilon);
+ EXPECT_NEAR(function.Slope(0.55), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.6), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.65), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.7), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.75), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.8), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.85), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.9), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.95), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(1), 0, epsilon);
+}
+
} // namespace
} // namespace gfx