Commit 71197762 authored by Alex Newcomer's avatar Alex Newcomer Committed by Commit Bot

cros: Pagination grid animations changes

Pagination grid animations should be constant
regardless of whether they are partially completed.

I created a new flavor of SlideAnimation for this, which
ensures that the duration is dampened if the animation
is partway complete.

Also I added a new unit test for this new flavor of SlideAnimation.

Bug: 771262
Change-Id: Ibb40799cb6d15b04e1e2f9f36ac224c3d28daf13
Reviewed-on: https://chromium-review.googlesource.com/699431
Commit-Queue: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509448}
parent 2cf7729c
......@@ -74,6 +74,9 @@ const SkColor kCardBackgroundColorFullscreen = SkColorSetRGB(0xFA, 0xFA, 0xFC);
// Duration in milliseconds for page transition.
const int kPageTransitionDurationInMs = 250;
// Dampening value for PaginationModel's SlideAnimation.
const int kPageTransitionDurationDampening = 3;
// Duration in milliseconds for over scroll page transition.
const int kOverscrollPageTransitionDurationMs = 50;
......
......@@ -66,6 +66,7 @@ APP_LIST_EXPORT extern const SkColor kCardBackgroundColor;
APP_LIST_EXPORT extern const SkColor kCardBackgroundColorFullscreen;
APP_LIST_EXPORT extern const int kPageTransitionDurationInMs;
APP_LIST_EXPORT extern const int kPageTransitionDurationDampening;
APP_LIST_EXPORT extern const int kOverscrollPageTransitionDurationMs;
APP_LIST_EXPORT extern const int kFolderTransitionInDurationMs;
APP_LIST_EXPORT extern const int kFolderTransitionOutDurationMs;
......
......@@ -6,6 +6,7 @@
#include <algorithm>
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/pagination_model_observer.h"
#include "ui/gfx/animation/slide_animation.h"
......@@ -17,8 +18,7 @@ PaginationModel::PaginationModel()
transition_(-1, 0),
pending_selected_page_(-1),
transition_duration_ms_(0),
overscroll_transition_duration_ms_(0),
last_overscroll_target_page_(0) {}
overscroll_transition_duration_ms_(0) {}
PaginationModel::~PaginationModel() {}
......@@ -44,22 +44,6 @@ void PaginationModel::SelectPage(int page, bool animate) {
if (page == selected_page_)
return;
// Suppress over scroll animation if the same one happens too fast.
if (!is_valid_page(page)) {
const base::TimeTicks now = base::TimeTicks::Now();
if (page == last_overscroll_target_page_) {
const int kMinOverScrollTimeGapInMs = 500;
const base::TimeDelta time_elapsed =
now - last_overscroll_animation_start_time_;
if (time_elapsed.InMilliseconds() < kMinOverScrollTimeGapInMs)
return;
}
last_overscroll_target_page_ = page;
last_overscroll_animation_start_time_ = now;
}
// Creates an animation if there is not one.
StartTransitionAnimation(Transition(page, 0));
return;
......@@ -240,6 +224,7 @@ void PaginationModel::StartTransitionAnimation(const Transition& transition) {
SetTransition(transition);
transition_animation_.reset(new gfx::SlideAnimation(this));
transition_animation_->SetDampeningValue(kPageTransitionDurationDampening);
transition_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
transition_animation_->Reset(transition_.progress);
......
......@@ -139,7 +139,6 @@ class APP_LIST_EXPORT PaginationModel : public gfx::AnimationDelegate {
int transition_duration_ms_; // Transition duration in millisecond.
int overscroll_transition_duration_ms_;
int last_overscroll_target_page_;
base::TimeTicks last_overscroll_animation_start_time_;
base::ObserverList<PaginationModelObserver> observers_;
......
......@@ -19,10 +19,10 @@ SlideAnimation::SlideAnimation(AnimationDelegate* target)
value_start_(0),
value_end_(0),
value_current_(0),
slide_duration_(kDefaultDurationMs) {}
slide_duration_(kDefaultDurationMs),
dampening_value_(1.0) {}
SlideAnimation::~SlideAnimation() {
}
SlideAnimation::~SlideAnimation() {}
void SlideAnimation::Reset() {
Reset(0);
......@@ -47,13 +47,12 @@ void SlideAnimation::Show() {
if (slide_duration_ == 0) {
AnimateToState(1.0); // Skip to the end of the animation.
return;
} else if (value_current_ == value_end_) {
} else if (value_current_ == value_end_) {
return;
}
// This will also reset the currently-occurring animation.
SetDuration(base::TimeDelta::FromMilliseconds(
static_cast<int>(slide_duration_ * (1 - value_current_))));
SetDuration(GetDuration());
Start();
}
......@@ -77,8 +76,7 @@ void SlideAnimation::Hide() {
}
// This will also reset the currently-occurring animation.
SetDuration(base::TimeDelta::FromMilliseconds(
static_cast<int>(slide_duration_ * value_current_)));
SetDuration(GetDuration());
Start();
}
......@@ -86,13 +84,27 @@ void SlideAnimation::SetSlideDuration(int duration) {
slide_duration_ = duration;
}
void SlideAnimation::SetDampeningValue(double dampening_value) {
dampening_value_ = dampening_value;
}
double SlideAnimation::GetCurrentValue() const {
return value_current_;
}
base::TimeDelta SlideAnimation::GetDuration() {
const double current_progress =
showing_ ? value_current_ : 1.0 - value_current_;
return base::TimeDelta::FromMillisecondsD(
slide_duration_ * (1 - pow(current_progress, dampening_value_)));
}
void SlideAnimation::AnimateToState(double state) {
if (state > 1.0)
state = 1.0;
else if (state < 0)
state = 0;
state = Tween::CalculateValue(tween_type_, state);
......
......@@ -67,6 +67,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
int GetSlideDuration() const { return slide_duration_; }
void SetTweenType(Tween::Type tween_type) { tween_type_ = tween_type; }
// Dampens the reduction in duration for an animation which starts partway.
// The default value of 1 has no effect.
void SetDampeningValue(double dampening_value);
double GetCurrentValue() const override;
// TODO(bruthig): Fix IsShowing() and IsClosing() to be consistent. e.g.
// IsShowing() will currently return true after the 'show' animation has been
......@@ -78,6 +82,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
class TestApi;
private:
// Gets the duration based on the dampening factor and whether the animation
// is showing or hiding.
base::TimeDelta GetDuration();
// Overridden from Animation.
void AnimateToState(double state) override;
......@@ -98,6 +106,9 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
// kHoverFadeDurationMS, but can be overridden with SetDuration.
int slide_duration_;
// Dampens the reduction in duration for animations which start partway.
double dampening_value_;
DISALLOW_COPY_AND_ASSIGN(SlideAnimation);
};
......
......@@ -18,10 +18,23 @@ namespace gfx {
////////////////////////////////////////////////////////////////////////////////
// SlideAnimationTest
class SlideAnimationTest : public testing::Test {
public:
void RunAnimationFor(base::TimeDelta duration) {
base::TimeTicks now = base::TimeTicks::Now();
animation_api_->SetStartTime(now);
animation_api_->Step(now + duration);
}
std::unique_ptr<AnimationTestApi> animation_api_;
std::unique_ptr<SlideAnimation> slide_animation_;
protected:
SlideAnimationTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
base::test::ScopedTaskEnvironment::MainThreadType::UI) {
slide_animation_.reset(new SlideAnimation(nullptr));
animation_api_.reset(new AnimationTestApi(slide_animation_.get()));
}
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
......@@ -29,50 +42,45 @@ class SlideAnimationTest : public testing::Test {
// Tests animation construction.
TEST_F(SlideAnimationTest, InitialState) {
SlideAnimation animation(nullptr);
// By default, slide animations are 60 Hz, so the timer interval should be
// 1/60th of a second.
EXPECT_EQ(1000 / 60, animation.timer_interval().InMilliseconds());
EXPECT_EQ(1000 / 60, slide_animation_->timer_interval().InMilliseconds());
// Duration defaults to 120 ms.
EXPECT_EQ(120, animation.GetSlideDuration());
EXPECT_EQ(120, slide_animation_->GetSlideDuration());
// Slide is neither showing nor closing.
EXPECT_FALSE(animation.IsShowing());
EXPECT_FALSE(animation.IsClosing());
EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_FALSE(slide_animation_->IsClosing());
// Starts at 0.
EXPECT_EQ(0.0, animation.GetCurrentValue());
EXPECT_EQ(0.0, slide_animation_->GetCurrentValue());
}
TEST_F(SlideAnimationTest, Basics) {
SlideAnimation animation(nullptr);
AnimationTestApi test_api(&animation);
// Use linear tweening to make the math easier below.
animation.SetTweenType(Tween::LINEAR);
slide_animation_->SetTweenType(Tween::LINEAR);
// Duration can be set after construction.
animation.SetSlideDuration(100);
EXPECT_EQ(100, animation.GetSlideDuration());
slide_animation_->SetSlideDuration(100);
EXPECT_EQ(100, slide_animation_->GetSlideDuration());
// Show toggles the appropriate state.
animation.Show();
EXPECT_TRUE(animation.IsShowing());
EXPECT_FALSE(animation.IsClosing());
slide_animation_->Show();
EXPECT_TRUE(slide_animation_->IsShowing());
EXPECT_FALSE(slide_animation_->IsClosing());
// Simulate running the animation.
test_api.SetStartTime(base::TimeTicks());
test_api.Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(50));
EXPECT_EQ(0.5, animation.GetCurrentValue());
RunAnimationFor(base::TimeDelta::FromMilliseconds(50));
EXPECT_EQ(0.5, slide_animation_->GetCurrentValue());
// We can start hiding mid-way through the animation.
animation.Hide();
EXPECT_FALSE(animation.IsShowing());
EXPECT_TRUE(animation.IsClosing());
slide_animation_->Hide();
EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_TRUE(slide_animation_->IsClosing());
// Reset stops the animation.
animation.Reset();
EXPECT_EQ(0.0, animation.GetCurrentValue());
EXPECT_FALSE(animation.IsShowing());
EXPECT_FALSE(animation.IsClosing());
slide_animation_->Reset();
EXPECT_EQ(0.0, slide_animation_->GetCurrentValue());
EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_FALSE(slide_animation_->IsClosing());
}
// Tests that delegate is not notified when animation is running and is deleted.
......@@ -92,4 +100,128 @@ TEST_F(SlideAnimationTest, DontNotifyOnDelete) {
EXPECT_FALSE(delegate.canceled());
}
// Tests that animations which are started partway and have a dampening factor
// of 1 progress linearly.
TEST_F(SlideAnimationTest,
AnimationWithPartialProgressAndDefaultDampeningFactor) {
slide_animation_->SetTweenType(Tween::LINEAR);
const double duration = 100;
slide_animation_->SetSlideDuration(duration);
slide_animation_->Show();
EXPECT_EQ(slide_animation_->GetCurrentValue(), 0.0);
// Advance the animation to halfway done.
RunAnimationFor(base::TimeDelta::FromMilliseconds(50));
EXPECT_EQ(0.5, slide_animation_->GetCurrentValue());
// Reverse the animation and run it for half of the remaining duration.
slide_animation_->Hide();
RunAnimationFor(base::TimeDelta::FromMilliseconds(25));
EXPECT_EQ(0.25, slide_animation_->GetCurrentValue());
// Reverse the animation again and run it for half of the remaining duration.
slide_animation_->Show();
RunAnimationFor(base::TimeDelta::FromMillisecondsD(37.5));
EXPECT_EQ(0.625, slide_animation_->GetCurrentValue());
}
// Tests that animations which are started partway and have a dampening factor
// of >1 progress sub-leanearly.
TEST_F(SlideAnimationTest,
AnimationWithPartialProgressAndNonDefaultDampeningFactor) {
slide_animation_->SetTweenType(Tween::LINEAR);
const double duration = 100;
slide_animation_->SetDampeningValue(2.0);
slide_animation_->SetSlideDuration(duration);
slide_animation_->Show();
// Advance the animation to halfway done.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration / 2));
EXPECT_EQ(0.5, slide_animation_->GetCurrentValue());
// Reverse the animation and run it for the same duration, it should be
// sub-linear with dampening.
slide_animation_->Hide();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration / 2));
EXPECT_GT(slide_animation_->GetCurrentValue(), 0);
}
// Tests that a mostly complete dampened animation takes a sub-linear
// amount of time to complete.
TEST_F(SlideAnimationTest, DampenedAnimationMostlyComplete) {
slide_animation_->SetTweenType(Tween::LINEAR);
const double duration = 100;
slide_animation_->SetDampeningValue(2.0);
slide_animation_->SetSlideDuration(duration);
slide_animation_->Show();
// Advance the animation to 1/10th of the way done.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.1));
EXPECT_EQ(0.1, slide_animation_->GetCurrentValue());
// Reverse the animation and run it for 1/10th of the duration, it should not
// be complete.
slide_animation_->Hide();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.1));
EXPECT_GT(slide_animation_->GetCurrentValue(), 0);
// Finish the animation and set up the test for a mostly complete show
// animation.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration));
EXPECT_EQ(0, slide_animation_->GetCurrentValue());
slide_animation_->Show();
// Advance the animation to 9/10th of the way done.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.9));
EXPECT_EQ(0.9, slide_animation_->GetCurrentValue());
// Hide and then Show the animation to force the duration to be recalculated,
// then show for 1/10th of the duration and test that the animation is not
// complete.
slide_animation_->Hide();
slide_animation_->Show();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.1));
EXPECT_LT(slide_animation_->GetCurrentValue(), 1);
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.4));
EXPECT_EQ(1, slide_animation_->GetCurrentValue());
}
// Tests that a mostly incomplete dampened animation takes a sub-linear amount
// of time to complete.
TEST_F(SlideAnimationTest, DampenedAnimationMostlyIncomplete) {
slide_animation_->SetTweenType(Tween::LINEAR);
const double duration = 100;
slide_animation_->SetDampeningValue(2.0);
slide_animation_->SetSlideDuration(duration);
slide_animation_->Show();
// Advance the animation to 1/10th of the way done.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.1));
EXPECT_EQ(0.1, slide_animation_->GetCurrentValue());
// Hide and then Show the animation to force the duration to be recalculated,
// then show for 9/10th of the duration and test that the animation is not
// complete.
slide_animation_->Hide();
slide_animation_->Show();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.9));
EXPECT_LT(slide_animation_->GetCurrentValue(), 1);
// Finish the animation and set up the test for a mostly incomplete hide
// animation.
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration));
EXPECT_EQ(1, slide_animation_->GetCurrentValue());
slide_animation_->Hide();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.1));
EXPECT_EQ(0.9, slide_animation_->GetCurrentValue());
// Show and then hide the animation to recompute the duration, then run the
// animation for 9/10ths of the duration and test that the animation is not
// complete.
slide_animation_->Show();
slide_animation_->Hide();
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration * 0.9));
EXPECT_GT(slide_animation_->GetCurrentValue(), 0);
RunAnimationFor(base::TimeDelta::FromMilliseconds(duration));
EXPECT_EQ(0, slide_animation_->GetCurrentValue());
}
} // namespace gfx
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