Commit dcd64ce4 authored by Rahul Arakeri's avatar Rahul Arakeri Committed by Commit Bot

New Windows root scroller overscroll - Part 4

This CL builds the Bezier Curve for overscroll dynamically based on the
fling velocity. When the user flings, the velocity of the scroller
(when scroll bound is reached) is used to calculate one of the control
points. This makes the "rubberbanding" feel more natural.

Bug: 1058071
Change-Id: I250ed02374769c73097e4c4ad2babcc8b52204c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2483766Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Commit-Queue: Rahul Arakeri <arakeri@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#819501}
parent b0d1a224
......@@ -55,17 +55,27 @@ base::TimeDelta CalculateBounceBackDuration(double bounce_back_distance) {
}
} // namespace
// Scale one of the control points of the Cubic Bezier curve based on the
// initial_velocity (which is expressed in terms of pixels / ms).
gfx::CubicBezier InitialVelocityBasedBezierCurve(const double initial_velocity,
const double x1,
const double y1,
const double x2,
const double y2) {
const double velocity = std::abs(initial_velocity);
double x = x1, y = y1;
if (x1 * velocity < y1) {
y = x1 * velocity;
} else {
x = y1 / velocity;
}
return gfx::CubicBezier(x, y, x2, y2);
}
ElasticOverscrollControllerBezier::ElasticOverscrollControllerBezier(
cc::ScrollElasticityHelper* helper)
: ElasticOverscrollController(helper),
bounce_forwards_curve_(gfx::CubicBezier(kBounceForwardsX1,
kBounceForwardsY1,
kBounceForwardsX2,
kBounceForwardsY2)),
bounce_backwards_curve_(gfx::CubicBezier(kBounceBackwardsX1,
kBounceBackwardsY1,
kBounceBackwardsX2,
kBounceBackwardsY2)) {}
: ElasticOverscrollController(helper) {}
// Returns the maximum amount to be overscrolled.
gfx::Vector2dF ElasticOverscrollControllerBezier::OverscrollBoundary(
......@@ -85,6 +95,8 @@ void ElasticOverscrollControllerBezier::DidEnterMomentumAnimatedState() {
? scroll_velocity().y() / 1000.f
: 0.f);
residual_velocity_ = velocity;
gfx::Vector2dF bounce_forwards_delta(gfx::Vector2dF(
sqrt(std::abs(velocity.x())), sqrt(std::abs(velocity.y()))));
bounce_forwards_delta.Scale(kOverbounceDistanceMultiplier);
......@@ -124,6 +136,7 @@ void ElasticOverscrollControllerBezier::DidEnterMomentumAnimatedState() {
}
double ElasticOverscrollControllerBezier::StretchAmountForForwardBounce(
const gfx::CubicBezier bounce_forwards_curve,
const base::TimeDelta& delta,
const base::TimeDelta& bounce_forwards_duration,
const double velocity,
......@@ -135,7 +148,7 @@ double ElasticOverscrollControllerBezier::StretchAmountForForwardBounce(
if (delta < bounce_forwards_duration) {
double curve_progress =
delta.InMillisecondsF() / bounce_forwards_duration.InMillisecondsF();
double progress = bounce_forwards_curve_.Solve(curve_progress);
double progress = bounce_forwards_curve.Solve(curve_progress);
return initial_stretch * (1 - progress) +
bounce_forwards_distance * progress;
}
......@@ -144,13 +157,14 @@ double ElasticOverscrollControllerBezier::StretchAmountForForwardBounce(
}
double ElasticOverscrollControllerBezier::StretchAmountForBackwardBounce(
const gfx::CubicBezier bounce_backwards_curve,
const base::TimeDelta& delta,
const base::TimeDelta& bounce_backwards_duration,
const double bounce_forwards_distance) const {
if (delta < bounce_backwards_duration) {
double curve_progress =
delta.InMillisecondsF() / bounce_backwards_duration.InMillisecondsF();
double progress = bounce_backwards_curve_.Solve(curve_progress);
double progress = bounce_backwards_curve.Solve(curve_progress);
return bounce_forwards_distance * (1 - progress);
}
return 0.f;
......@@ -163,15 +177,23 @@ gfx::Vector2d ElasticOverscrollControllerBezier::StretchAmountForTimeDelta(
// if the velocity isn't 0, a bounce forwards animation will need to be
// played.
base::TimeDelta time_delta = delta;
const gfx::CubicBezier bounce_forwards_curve_x =
InitialVelocityBasedBezierCurve(residual_velocity_.x(), kBounceForwardsX1,
kBounceForwardsY1, kBounceForwardsX2,
kBounceForwardsY2);
const gfx::CubicBezier bounce_forwards_curve_y =
InitialVelocityBasedBezierCurve(residual_velocity_.y(), kBounceForwardsX1,
kBounceForwardsY1, kBounceForwardsX2,
kBounceForwardsY2);
const gfx::Vector2d forward_animation(gfx::ToRoundedVector2d(gfx::Vector2dF(
StretchAmountForForwardBounce(time_delta, bounce_forwards_duration_x_,
scroll_velocity().x(),
momentum_animation_initial_stretch_.x(),
bounce_forwards_distance_.x()),
StretchAmountForForwardBounce(time_delta, bounce_forwards_duration_y_,
scroll_velocity().y(),
momentum_animation_initial_stretch_.y(),
bounce_forwards_distance_.y()))));
StretchAmountForForwardBounce(
bounce_forwards_curve_x, time_delta, bounce_forwards_duration_x_,
scroll_velocity().x(), momentum_animation_initial_stretch_.x(),
bounce_forwards_distance_.x()),
StretchAmountForForwardBounce(
bounce_forwards_curve_y, time_delta, bounce_forwards_duration_y_,
scroll_velocity().y(), momentum_animation_initial_stretch_.y(),
bounce_forwards_distance_.y()))));
if (!forward_animation.IsZero())
return forward_animation;
......@@ -180,10 +202,20 @@ gfx::Vector2d ElasticOverscrollControllerBezier::StretchAmountForTimeDelta(
time_delta -= bounce_forwards_duration_x_;
time_delta -= bounce_forwards_duration_y_;
const gfx::CubicBezier bounce_backwards_curve_x =
InitialVelocityBasedBezierCurve(residual_velocity_.x(),
kBounceBackwardsX1, kBounceBackwardsY1,
kBounceBackwardsX2, kBounceBackwardsY2);
const gfx::CubicBezier bounce_backwards_curve_y =
InitialVelocityBasedBezierCurve(residual_velocity_.y(),
kBounceBackwardsX1, kBounceBackwardsY1,
kBounceBackwardsX2, kBounceBackwardsY2);
return gfx::ToRoundedVector2d(gfx::Vector2dF(
StretchAmountForBackwardBounce(time_delta, bounce_backwards_duration_x_,
StretchAmountForBackwardBounce(bounce_backwards_curve_x, time_delta,
bounce_backwards_duration_x_,
bounce_forwards_distance_.x()),
StretchAmountForBackwardBounce(time_delta, bounce_backwards_duration_y_,
StretchAmountForBackwardBounce(bounce_backwards_curve_y, time_delta,
bounce_backwards_duration_y_,
bounce_forwards_distance_.y())));
}
......
......@@ -34,12 +34,14 @@ class PLATFORM_EXPORT ElasticOverscrollControllerBezier
const gfx::Vector2dF& delta) const override;
gfx::Vector2dF OverscrollBoundary(const gfx::Size& scroller_bounds) const;
double StretchAmountForForwardBounce(
const gfx::CubicBezier bounce_forwards_curve,
const base::TimeDelta& delta,
const base::TimeDelta& bounce_forwards_duration,
const double velocity,
const double initial_stretch,
const double bounce_forwards_distance) const;
double StretchAmountForBackwardBounce(
const gfx::CubicBezier bounce_backwards_curve,
const base::TimeDelta& delta,
const base::TimeDelta& bounce_backwards_duration,
const double bounce_forwards_distance) const;
......@@ -50,12 +52,15 @@ class PLATFORM_EXPORT ElasticOverscrollControllerBezier
FRIEND_TEST_ALL_PREFIXES(ElasticOverscrollControllerBezierTest,
VerifyForwardAnimationIsNotPlayed);
const gfx::CubicBezier bounce_forwards_curve_;
// This is the velocity (in px/ms) of the fling at the moment the scroller
// reaches its bounds. This is useful to tweak the Cubic Bezier curve for the
// bounce animation that is about to get played.
gfx::Vector2dF residual_velocity_;
base::TimeDelta bounce_forwards_duration_x_;
base::TimeDelta bounce_forwards_duration_y_;
gfx::Vector2dF bounce_forwards_distance_;
const gfx::CubicBezier bounce_backwards_curve_;
base::TimeDelta bounce_backwards_duration_x_;
base::TimeDelta bounce_backwards_duration_y_;
DISALLOW_COPY_AND_ASSIGN(ElasticOverscrollControllerBezier);
......
......@@ -222,11 +222,11 @@ TEST_F(ElasticOverscrollControllerBezierTest, VerifyBackwardAnimationTick) {
controller_.Animate(now + base::TimeDelta::FromMilliseconds(32));
EXPECT_EQ(controller_.state_,
ElasticOverscrollController::kStateMomentumAnimated);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -8);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -14);
// Frame 5. The stretch amount moving closer to 0 proves that we're animating.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(80));
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -4);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -8);
// Frame 15. StretchAmount < abs(1), so snap to 0. state_ is kStateInactive.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(240));
......@@ -242,13 +242,13 @@ TEST_F(ElasticOverscrollControllerBezierTest, VerifyBackwardAnimationTick) {
// Frame 2.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(32));
ASSERT_FLOAT_EQ(helper_.StretchAmount().x(), -6);
ASSERT_FLOAT_EQ(helper_.StretchAmount().x(), -10);
// Frame 5. The stretch amount moving closer to 0 proves that we're animating.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(80));
EXPECT_EQ(controller_.state_,
ElasticOverscrollController::kStateMomentumAnimated);
ASSERT_FLOAT_EQ(helper_.StretchAmount().x(), -2);
ASSERT_FLOAT_EQ(helper_.StretchAmount().x(), -5);
// Frame 15. StretchAmount < abs(1), so snap to 0. state_ is kStateInactive.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(240));
......@@ -269,10 +269,10 @@ TEST_F(ElasticOverscrollControllerBezierTest, VerifyForwardAnimationTick) {
const base::TimeTicks now = base::TimeTicks::Now();
SendGestureScrollEnd(now);
const int TOTAL_FRAMES = 26;
const int TOTAL_FRAMES = 28;
const int stretch_amount_y[TOTAL_FRAMES] = {
-19, -41, -55, -65, -72, -78, -82, -85, -88, -89, -64, -45, -34,
-26, -20, -16, -13, -10, -8, -6, -4, -3, -2, -1, -1, 0};
-19, -41, -55, -65, -72, -78, -82, -85, -88, -89, -78, -64, -53, -44,
-37, -30, -25, -20, -16, -13, -10, -7, -5, -4, -2, -1, -1, 0};
for (int i = 0; i < TOTAL_FRAMES; i++) {
controller_.Animate(now + base::TimeDelta::FromMilliseconds(i * 16));
......@@ -290,8 +290,8 @@ TEST_F(ElasticOverscrollControllerBezierTest, VerifyForwardAnimationTick) {
SendGestureScrollEnd(now);
const int stretch_amount_x[TOTAL_FRAMES] = {
-9, -28, -40, -49, -55, -60, -64, -66, -68, -69, -50, -35, -26,
-20, -16, -12, -10, -8, -6, -5, -3, -2, -2, -1, -1, 0};
-9, -24, -34, -42, -48, -54, -58, -62, -66, -69, -62, -52, -43, -36,
-30, -25, -20, -17, -13, -10, -8, -6, -4, -3, -2, -1, 0, 0};
for (int i = 0; i < TOTAL_FRAMES; i++) {
controller_.Animate(now + base::TimeDelta::FromMilliseconds(i * 16));
......@@ -345,16 +345,16 @@ TEST_F(ElasticOverscrollControllerBezierTest, VerifyScrollDuringBounceBack) {
// Frame 2.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(32));
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -8);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -14);
// Frame 5. The stretch amount moving closer to 0 proves that we're animating.
controller_.Animate(now + base::TimeDelta::FromMilliseconds(80));
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -4);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -8);
// While the animation is still ticking, initiate a scroll.
SendGestureScrollBegin(PhaseState::kNonMomentum);
SendGestureScrollUpdate(PhaseState::kNonMomentum, Vector2dF(0, -50));
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -13);
ASSERT_FLOAT_EQ(helper_.StretchAmount().y(), -17);
}
// Tests that animation doesn't get created when unused_delta is 0.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment