diff options
author | skobes <skobes@chromium.org> | 2015-10-27 08:05:50 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-27 15:06:35 +0000 |
commit | 46aad144b73e893f8b08a18e689ff444c301a39f (patch) | |
tree | 184dce4489fb98965dd532a8d125124463821da3 | |
parent | 0d0154221feddb459b8749f49ecac82e74922579 (diff) | |
download | chromium_src-46aad144b73e893f8b08a18e689ff444c301a39f.zip chromium_src-46aad144b73e893f8b08a18e689ff444c301a39f.tar.gz chromium_src-46aad144b73e893f8b08a18e689ff444c301a39f.tar.bz2 |
Make compositor input-driven smooth scrolling smoother and snappier.
The animation time is now constant instead of proportional to the delta, which
felt sluggish.
The velocity matching in UpdateTarget now handles direction changes correctly,
and scales the first control point on the y axis instead of rotating it, which
gives a smoother velocity transition.
BUG=575
CQ_INCLUDE_TRYBOTS=tryserver.blink:linux_blink_rel
Review URL: https://codereview.chromium.org/1420943004
Cr-Commit-Position: refs/heads/master@{#356291}
-rw-r--r-- | cc/animation/animation_host.cc | 5 | ||||
-rw-r--r-- | cc/animation/scroll_offset_animation_curve.cc | 80 | ||||
-rw-r--r-- | cc/animation/scroll_offset_animation_curve.h | 8 | ||||
-rw-r--r-- | cc/animation/scroll_offset_animation_curve_unittest.cc | 22 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_impl.cc | 5 |
5 files changed, 76 insertions, 44 deletions
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc index 5d8352f..a5bf5ea 100644 --- a/cc/animation/animation_host.cc +++ b/cc/animation/animation_host.cc @@ -45,8 +45,9 @@ class AnimationHost::ScrollOffsetAnimations : public AnimationDelegate { const gfx::ScrollOffset& target_offset, const gfx::ScrollOffset& current_offset) { scoped_ptr<ScrollOffsetAnimationCurve> curve = - ScrollOffsetAnimationCurve::Create(target_offset, - EaseInOutTimingFunction::Create()); + ScrollOffsetAnimationCurve::Create( + target_offset, EaseInOutTimingFunction::Create(), + ScrollOffsetAnimationCurve::DurationBehavior::CONSTANT); curve->SetInitialValue(current_offset); scoped_ptr<Animation> animation = Animation::Create( diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc index 17dff26..4a78c78 100644 --- a/cc/animation/scroll_offset_animation_curve.cc +++ b/cc/animation/scroll_offset_animation_curve.cc @@ -12,6 +12,7 @@ #include "cc/base/time_util.h" #include "ui/gfx/animation/tween.h" +const double kConstantDuration = 12.0; const double kDurationDivisor = 60.0; namespace cc { @@ -19,58 +20,64 @@ namespace cc { namespace { static float MaximumDimension(const gfx::Vector2dF& delta) { - return std::max(std::abs(delta.x()), std::abs(delta.y())); + return std::abs(delta.x()) > std::abs(delta.y()) ? delta.x() : delta.y(); } -static base::TimeDelta DurationFromDelta(const 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 base::TimeDelta SegmentDuration( + const gfx::Vector2dF& delta, + ScrollOffsetAnimationCurve::DurationBehavior behavior) { + if (behavior == ScrollOffsetAnimationCurve::DurationBehavior::DELTA_BASED) { + // The duration of a JS 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(std::abs(MaximumDimension(delta))) / kDurationDivisor) * + base::Time::kMicrosecondsPerSecond); + } else { + // Input-driven scroll animations use a constant duration. + return base::TimeDelta::FromMicroseconds( + (kConstantDuration / kDurationDivisor) * + base::Time::kMicrosecondsPerSecond); + } } static scoped_ptr<TimingFunction> EaseOutWithInitialVelocity(double velocity) { - // Based on EaseInOutTimingFunction::Create with first control point rotated. - if (std::abs(velocity) < 1000.0) { - 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); - } + // Clamp velocity to a sane value. + velocity = std::min(std::max(velocity, -1000.0), 1000.0); - // For large |velocity|, x1 approaches 0 and y1 approaches 0.42. To avoid the - // risk of floating point arithmetic involving infinity and NaN, use those - // values directly rather than computing them above. - return CubicBezierTimingFunction::Create(0, 0.42, 0.58, 1); + // Based on EaseInOutTimingFunction::Create with first control point scaled. + const double x1 = 0.42; + const double y1 = velocity * x1; + return CubicBezierTimingFunction::Create(x1, y1, 0.58, 1); } } // namespace scoped_ptr<ScrollOffsetAnimationCurve> ScrollOffsetAnimationCurve::Create( const gfx::ScrollOffset& target_value, - scoped_ptr<TimingFunction> timing_function) { - return make_scoped_ptr( - new ScrollOffsetAnimationCurve(target_value, timing_function.Pass())); + scoped_ptr<TimingFunction> timing_function, + DurationBehavior duration_behavior) { + return make_scoped_ptr(new ScrollOffsetAnimationCurve( + target_value, timing_function.Pass(), duration_behavior)); } ScrollOffsetAnimationCurve::ScrollOffsetAnimationCurve( const gfx::ScrollOffset& target_value, - scoped_ptr<TimingFunction> timing_function) - : target_value_(target_value), timing_function_(timing_function.Pass()) { -} + scoped_ptr<TimingFunction> timing_function, + DurationBehavior duration_behavior) + : target_value_(target_value), + timing_function_(timing_function.Pass()), + duration_behavior_(duration_behavior) {} ScrollOffsetAnimationCurve::~ScrollOffsetAnimationCurve() {} void ScrollOffsetAnimationCurve::SetInitialValue( const gfx::ScrollOffset& initial_value) { initial_value_ = initial_value; - total_animation_duration_ = DurationFromDelta( - target_value_.DeltaFrom(initial_value_)); + total_animation_duration_ = SegmentDuration( + target_value_.DeltaFrom(initial_value_), duration_behavior_); } gfx::ScrollOffset ScrollOffsetAnimationCurve::GetValue( @@ -104,7 +111,7 @@ scoped_ptr<AnimationCurve> ScrollOffsetAnimationCurve::Clone() const { scoped_ptr<TimingFunction> timing_function( static_cast<TimingFunction*>(timing_function_->Clone().release())); scoped_ptr<ScrollOffsetAnimationCurve> curve_clone = - Create(target_value_, timing_function.Pass()); + Create(target_value_, timing_function.Pass(), duration_behavior_); curve_clone->initial_value_ = initial_value_; curve_clone->total_animation_duration_ = total_animation_duration_; curve_clone->last_retarget_ = last_retarget_; @@ -121,7 +128,8 @@ void ScrollOffsetAnimationCurve::UpdateTarget( double old_duration = (total_animation_duration_ - last_retarget_).InSecondsF(); - double new_duration = DurationFromDelta(new_delta).InSecondsF(); + double new_duration = + SegmentDuration(new_delta, duration_behavior_).InSecondsF(); double old_velocity = timing_function_->Velocity( (t - last_retarget_.InSecondsF()) / old_duration); @@ -129,9 +137,13 @@ void ScrollOffsetAnimationCurve::UpdateTarget( // 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. + const double kEpsilon = 0.01f; + double new_delta_max_dimension = MaximumDimension(new_delta); double new_velocity = - old_velocity * (new_duration / old_duration) * - (MaximumDimension(old_delta) / MaximumDimension(new_delta)); + new_delta_max_dimension < kEpsilon // Guard against division by 0. + ? old_velocity + : old_velocity * (new_duration / old_duration) * + (MaximumDimension(old_delta) / new_delta_max_dimension); initial_value_ = current_position; target_value_ = new_target; diff --git a/cc/animation/scroll_offset_animation_curve.h b/cc/animation/scroll_offset_animation_curve.h index c4ae70b..4c43a20 100644 --- a/cc/animation/scroll_offset_animation_curve.h +++ b/cc/animation/scroll_offset_animation_curve.h @@ -17,9 +17,11 @@ class TimingFunction; class CC_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve { public: + enum class DurationBehavior { DELTA_BASED, CONSTANT }; static scoped_ptr<ScrollOffsetAnimationCurve> Create( const gfx::ScrollOffset& target_value, - scoped_ptr<TimingFunction> timing_function); + scoped_ptr<TimingFunction> timing_function, + DurationBehavior = DurationBehavior::DELTA_BASED); ~ScrollOffsetAnimationCurve() override; @@ -35,7 +37,8 @@ class CC_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve { private: ScrollOffsetAnimationCurve(const gfx::ScrollOffset& target_value, - scoped_ptr <TimingFunction> timing_function); + scoped_ptr<TimingFunction> timing_function, + DurationBehavior); gfx::ScrollOffset initial_value_; gfx::ScrollOffset target_value_; @@ -45,6 +48,7 @@ class CC_EXPORT ScrollOffsetAnimationCurve : public AnimationCurve { base::TimeDelta last_retarget_; scoped_ptr<TimingFunction> timing_function_; + DurationBehavior duration_behavior_; DISALLOW_COPY_AND_ASSIGN(ScrollOffsetAnimationCurve); }; diff --git a/cc/animation/scroll_offset_animation_curve_unittest.cc b/cc/animation/scroll_offset_animation_curve_unittest.cc index 5887513..fd34db0 100644 --- a/cc/animation/scroll_offset_animation_curve_unittest.cc +++ b/cc/animation/scroll_offset_animation_curve_unittest.cc @@ -142,16 +142,16 @@ TEST(ScrollOffsetAnimationCurveTest, UpdateTarget) { EXPECT_EQ(2.0, curve->Duration().InSecondsF()); EXPECT_EQ(1800.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.5)).y()); - EXPECT_NEAR(5566.49, curve->GetValue(base::TimeDelta::FromSecondsD(1.0)).y(), + EXPECT_NEAR(5410.05, curve->GetValue(base::TimeDelta::FromSecondsD(1.0)).y(), 0.01); EXPECT_EQ(9900.0, curve->GetValue(base::TimeDelta::FromSecondsD(2.0)).y()); curve->UpdateTarget(1.0, gfx::ScrollOffset(0.0, 7200.0)); - EXPECT_NEAR(1.674, curve->Duration().InSecondsF(), 0.01); - EXPECT_NEAR(5566.49, curve->GetValue(base::TimeDelta::FromSecondsD(1.0)).y(), + EXPECT_NEAR(1.705, curve->Duration().InSecondsF(), 0.01); + EXPECT_NEAR(5410.05, curve->GetValue(base::TimeDelta::FromSecondsD(1.0)).y(), 0.01); - EXPECT_EQ(7200.0, curve->GetValue(base::TimeDelta::FromSecondsD(1.674)).y()); + EXPECT_EQ(7200.0, curve->GetValue(base::TimeDelta::FromSecondsD(1.705)).y()); } TEST(ScrollOffsetAnimationCurveTest, UpdateTargetWithLargeVelocity) { @@ -177,5 +177,19 @@ TEST(ScrollOffsetAnimationCurveTest, UpdateTargetWithLargeVelocity) { 0.001); } +TEST(ScrollOffsetAnimationCurveTest, UpdateTargetConstantDuration) { + gfx::ScrollOffset initial_value(0.f, 0.f); + gfx::ScrollOffset target_value(0.f, 3600.f); + scoped_ptr<ScrollOffsetAnimationCurve> curve( + ScrollOffsetAnimationCurve::Create( + target_value, EaseInOutTimingFunction::Create().Pass(), + ScrollOffsetAnimationCurve::DurationBehavior::CONSTANT)); + curve->SetInitialValue(initial_value); + EXPECT_EQ(0.2, curve->Duration().InSecondsF()); + + curve->UpdateTarget(0.1, gfx::ScrollOffset(0.0, 9900.0)); + EXPECT_EQ(0.3, curve->Duration().InSecondsF()); +} + } // namespace } // namespace cc diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index 235817d..9f1efdc 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc @@ -3453,8 +3453,9 @@ void LayerTreeHostImpl::ScrollAnimationCreate( layer_impl->id(), target_offset, current_offset); scoped_ptr<ScrollOffsetAnimationCurve> curve = - ScrollOffsetAnimationCurve::Create(target_offset, - EaseInOutTimingFunction::Create()); + ScrollOffsetAnimationCurve::Create( + target_offset, EaseInOutTimingFunction::Create(), + ScrollOffsetAnimationCurve::DurationBehavior::CONSTANT); curve->SetInitialValue(current_offset); scoped_ptr<Animation> animation = Animation::Create( |