summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskobes <skobes@chromium.org>2015-12-30 15:03:14 -0800
committerCommit bot <commit-bot@chromium.org>2015-12-30 23:04:25 +0000
commita78fdc918ae09ee12096d99dc75a42b949f6b5de (patch)
treecd13104fc8faca78e1b457d77e2c01b24b1ecdff
parent450f23789b3cc8a7f1b66b9c54da31099a40a005 (diff)
downloadchromium_src-a78fdc918ae09ee12096d99dc75a42b949f6b5de.zip
chromium_src-a78fdc918ae09ee12096d99dc75a42b949f6b5de.tar.gz
chromium_src-a78fdc918ae09ee12096d99dc75a42b949f6b5de.tar.bz2
Tweak the math in ScrollOffsetAnimationCurve::UpdateTarget.
The existing division-by-0 guard was not correct, since MaximumDimension can return negative values. This led to occasional wild jumps when scrolling up and down quickly (because new_velocity had the wrong sign). This patch addresses the sign issue and improves handling of cases where we have a large current velocity and a small delta to the new target. BUG=575 CQ_INCLUDE_TRYBOTS=tryserver.blink:linux_blink_rel Review URL: https://codereview.chromium.org/1553603003 Cr-Commit-Position: refs/heads/master@{#367200}
-rw-r--r--cc/animation/scroll_offset_animation_curve.cc61
-rw-r--r--cc/animation/scroll_offset_animation_curve_unittest.cc65
2 files changed, 64 insertions, 62 deletions
diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc
index 6cd823f..371f4f6 100644
--- a/cc/animation/scroll_offset_animation_curve.cc
+++ b/cc/animation/scroll_offset_animation_curve.cc
@@ -19,6 +19,8 @@ namespace cc {
namespace {
+const double kEpsilon = 0.01f;
+
static float MaximumDimension(const gfx::Vector2dF& delta) {
return std::abs(delta.x()) > std::abs(delta.y()) ? delta.x() : delta.y();
}
@@ -125,6 +127,33 @@ scoped_ptr<AnimationCurve> ScrollOffsetAnimationCurve::Clone() const {
return std::move(curve_clone);
}
+static double VelocityBasedDurationBound(gfx::Vector2dF old_delta,
+ double old_normalized_velocity,
+ double old_duration,
+ gfx::Vector2dF new_delta) {
+ double old_delta_max_dimension = MaximumDimension(old_delta);
+ double new_delta_max_dimension = MaximumDimension(new_delta);
+
+ // If we are already at the target, stop animating.
+ if (std::abs(new_delta_max_dimension) < kEpsilon)
+ return 0;
+
+ // Guard against division by zero.
+ if (std::abs(old_delta_max_dimension) < kEpsilon ||
+ std::abs(old_normalized_velocity) < kEpsilon) {
+ return std::numeric_limits<double>::infinity();
+ }
+
+ // Estimate how long it will take to reach the new target at our present
+ // velocity, with some fudge factor to account for the "ease out".
+ double old_true_velocity =
+ old_normalized_velocity * old_delta_max_dimension / old_duration;
+ double bound = (new_delta_max_dimension / old_true_velocity) * 2.5f;
+
+ // If bound < 0 we are moving in the opposite direction.
+ return bound < 0 ? std::numeric_limits<double>::infinity() : bound;
+}
+
void ScrollOffsetAnimationCurve::UpdateTarget(
double t,
const gfx::ScrollOffset& new_target) {
@@ -135,28 +164,36 @@ void ScrollOffsetAnimationCurve::UpdateTarget(
double old_duration =
(total_animation_duration_ - last_retarget_).InSecondsF();
- double new_duration =
- SegmentDuration(new_delta, duration_behavior_).InSecondsF();
-
- double old_velocity = timing_function_->Velocity(
+ double old_normalized_velocity = timing_function_->Velocity(
(t - last_retarget_.InSecondsF()) / old_duration);
+ // Use the velocity-based duration bound when it is less than the constant
+ // segment duration. This minimizes the "rubber-band" bouncing effect when
+ // old_normalized_velocity is large and new_delta is small.
+ double new_duration =
+ std::min(SegmentDuration(new_delta, duration_behavior_).InSecondsF(),
+ VelocityBasedDurationBound(old_delta, old_normalized_velocity,
+ old_duration, new_delta));
+
+ if (new_duration < kEpsilon) {
+ // We are already at or very close to the new target. Stop animating.
+ target_value_ = new_target;
+ total_animation_duration_ = base::TimeDelta::FromSecondsD(t);
+ return;
+ }
+
// 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 =
- 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);
+ double new_normalized_velocity =
+ old_normalized_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);
+ timing_function_ = EaseOutWithInitialVelocity(new_normalized_velocity);
}
} // namespace cc
diff --git a/cc/animation/scroll_offset_animation_curve_unittest.cc b/cc/animation/scroll_offset_animation_curve_unittest.cc
index 4638ecf..7044b03a 100644
--- a/cc/animation/scroll_offset_animation_curve_unittest.cc
+++ b/cc/animation/scroll_offset_animation_curve_unittest.cc
@@ -128,64 +128,29 @@ TEST(ScrollOffsetAnimationCurveTest, UpdateTarget) {
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()));
- curve->SetInitialValue(initial_value);
- EXPECT_EQ(1.0, curve->Duration().InSecondsF());
- EXPECT_EQ(1800.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.5)).y());
- EXPECT_EQ(3600.0, curve->GetValue(base::TimeDelta::FromSecondsD(1.0)).y());
-
- curve->UpdateTarget(0.5, gfx::ScrollOffset(0.0, 9900.0));
-
- EXPECT_EQ(2.0, curve->Duration().InSecondsF());
- EXPECT_EQ(1800.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.5)).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.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.705)).y());
-}
-
-TEST(ScrollOffsetAnimationCurveTest, UpdateTargetWithLargeVelocity) {
- gfx::ScrollOffset initial_value(0.f, 0.f);
- gfx::ScrollOffset target_value(0.f, 900.f);
- scoped_ptr<ScrollOffsetAnimationCurve> curve(
- ScrollOffsetAnimationCurve::Create(target_value,
- EaseInOutTimingFunction::Create()));
- curve->SetInitialValue(initial_value);
- EXPECT_EQ(0.5, curve->Duration().InSecondsF());
-
- EXPECT_EQ(450.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.25)).y());
-
- // This leads to a new computed velocity larger than 5000.
- curve->UpdateTarget(0.25, gfx::ScrollOffset(0.0, 450.0001));
-
- EXPECT_NEAR(0.25015, curve->Duration().InSecondsF(), 0.0001);
- EXPECT_NEAR(450.0,
- curve->GetValue(base::TimeDelta::FromSecondsD(0.22501)).y(),
- 0.001);
- EXPECT_NEAR(450.0,
- curve->GetValue(base::TimeDelta::FromSecondsD(0.225015)).y(),
- 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(),
ScrollOffsetAnimationCurve::DurationBehavior::CONSTANT));
curve->SetInitialValue(initial_value);
EXPECT_EQ(0.2, curve->Duration().InSecondsF());
+ EXPECT_EQ(1800.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.1)).y());
+ EXPECT_EQ(3600.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.2)).y());
curve->UpdateTarget(0.1, gfx::ScrollOffset(0.0, 9900.0));
+
EXPECT_EQ(0.3, curve->Duration().InSecondsF());
+ EXPECT_EQ(1800.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.1)).y());
+ EXPECT_NEAR(6827.59, curve->GetValue(base::TimeDelta::FromSecondsD(0.2)).y(),
+ 0.01);
+ EXPECT_EQ(9900.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.3)).y());
+
+ curve->UpdateTarget(0.2, gfx::ScrollOffset(0.0, 7200.0));
+
+ // A closer target at high velocity reduces the duration.
+ EXPECT_NEAR(0.22, curve->Duration().InSecondsF(), 0.01);
+ EXPECT_NEAR(6827.59, curve->GetValue(base::TimeDelta::FromSecondsD(0.2)).y(),
+ 0.01);
+ EXPECT_EQ(7200.0, curve->GetValue(base::TimeDelta::FromSecondsD(0.22)).y());
}
} // namespace