diff options
author | skobes@chromium.org <skobes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-18 22:37:26 +0000 |
---|---|---|
committer | skobes@chromium.org <skobes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-18 22:37:26 +0000 |
commit | 7a7d5bec832fd997b6b2eef3ec9c0906b74ef16d (patch) | |
tree | 618f497d0ca76b781df27da3ae6de9188b00bcdf /cc | |
parent | 472de00aa7609c352c95d64abb125145296fc73a (diff) | |
download | chromium_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
Diffstat (limited to 'cc')
-rw-r--r-- | cc/animation/scroll_offset_animation_curve.cc | 78 | ||||
-rw-r--r-- | cc/animation/scroll_offset_animation_curve.h | 7 | ||||
-rw-r--r-- | cc/animation/scroll_offset_animation_curve_unittest.cc | 25 | ||||
-rw-r--r-- | cc/animation/timing_function.cc | 4 | ||||
-rw-r--r-- | cc/animation/timing_function.h | 2 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_impl.cc | 24 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_impl_unittest.cc | 15 |
7 files changed, 133 insertions, 22 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()); } |