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); ...@@ -74,6 +74,9 @@ const SkColor kCardBackgroundColorFullscreen = SkColorSetRGB(0xFA, 0xFA, 0xFC);
// Duration in milliseconds for page transition. // Duration in milliseconds for page transition.
const int kPageTransitionDurationInMs = 250; const int kPageTransitionDurationInMs = 250;
// Dampening value for PaginationModel's SlideAnimation.
const int kPageTransitionDurationDampening = 3;
// Duration in milliseconds for over scroll page transition. // Duration in milliseconds for over scroll page transition.
const int kOverscrollPageTransitionDurationMs = 50; const int kOverscrollPageTransitionDurationMs = 50;
......
...@@ -66,6 +66,7 @@ APP_LIST_EXPORT extern const SkColor kCardBackgroundColor; ...@@ -66,6 +66,7 @@ APP_LIST_EXPORT extern const SkColor kCardBackgroundColor;
APP_LIST_EXPORT extern const SkColor kCardBackgroundColorFullscreen; APP_LIST_EXPORT extern const SkColor kCardBackgroundColorFullscreen;
APP_LIST_EXPORT extern const int kPageTransitionDurationInMs; 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 kOverscrollPageTransitionDurationMs;
APP_LIST_EXPORT extern const int kFolderTransitionInDurationMs; APP_LIST_EXPORT extern const int kFolderTransitionInDurationMs;
APP_LIST_EXPORT extern const int kFolderTransitionOutDurationMs; APP_LIST_EXPORT extern const int kFolderTransitionOutDurationMs;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/pagination_model_observer.h" #include "ui/app_list/pagination_model_observer.h"
#include "ui/gfx/animation/slide_animation.h" #include "ui/gfx/animation/slide_animation.h"
...@@ -17,8 +18,7 @@ PaginationModel::PaginationModel() ...@@ -17,8 +18,7 @@ PaginationModel::PaginationModel()
transition_(-1, 0), transition_(-1, 0),
pending_selected_page_(-1), pending_selected_page_(-1),
transition_duration_ms_(0), transition_duration_ms_(0),
overscroll_transition_duration_ms_(0), overscroll_transition_duration_ms_(0) {}
last_overscroll_target_page_(0) {}
PaginationModel::~PaginationModel() {} PaginationModel::~PaginationModel() {}
...@@ -44,22 +44,6 @@ void PaginationModel::SelectPage(int page, bool animate) { ...@@ -44,22 +44,6 @@ void PaginationModel::SelectPage(int page, bool animate) {
if (page == selected_page_) if (page == selected_page_)
return; 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. // Creates an animation if there is not one.
StartTransitionAnimation(Transition(page, 0)); StartTransitionAnimation(Transition(page, 0));
return; return;
...@@ -240,6 +224,7 @@ void PaginationModel::StartTransitionAnimation(const Transition& transition) { ...@@ -240,6 +224,7 @@ void PaginationModel::StartTransitionAnimation(const Transition& transition) {
SetTransition(transition); SetTransition(transition);
transition_animation_.reset(new gfx::SlideAnimation(this)); transition_animation_.reset(new gfx::SlideAnimation(this));
transition_animation_->SetDampeningValue(kPageTransitionDurationDampening);
transition_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); transition_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
transition_animation_->Reset(transition_.progress); transition_animation_->Reset(transition_.progress);
......
...@@ -139,7 +139,6 @@ class APP_LIST_EXPORT PaginationModel : public gfx::AnimationDelegate { ...@@ -139,7 +139,6 @@ class APP_LIST_EXPORT PaginationModel : public gfx::AnimationDelegate {
int transition_duration_ms_; // Transition duration in millisecond. int transition_duration_ms_; // Transition duration in millisecond.
int overscroll_transition_duration_ms_; int overscroll_transition_duration_ms_;
int last_overscroll_target_page_;
base::TimeTicks last_overscroll_animation_start_time_; base::TimeTicks last_overscroll_animation_start_time_;
base::ObserverList<PaginationModelObserver> observers_; base::ObserverList<PaginationModelObserver> observers_;
......
...@@ -19,10 +19,10 @@ SlideAnimation::SlideAnimation(AnimationDelegate* target) ...@@ -19,10 +19,10 @@ SlideAnimation::SlideAnimation(AnimationDelegate* target)
value_start_(0), value_start_(0),
value_end_(0), value_end_(0),
value_current_(0), value_current_(0),
slide_duration_(kDefaultDurationMs) {} slide_duration_(kDefaultDurationMs),
dampening_value_(1.0) {}
SlideAnimation::~SlideAnimation() { SlideAnimation::~SlideAnimation() {}
}
void SlideAnimation::Reset() { void SlideAnimation::Reset() {
Reset(0); Reset(0);
...@@ -47,13 +47,12 @@ void SlideAnimation::Show() { ...@@ -47,13 +47,12 @@ void SlideAnimation::Show() {
if (slide_duration_ == 0) { if (slide_duration_ == 0) {
AnimateToState(1.0); // Skip to the end of the animation. AnimateToState(1.0); // Skip to the end of the animation.
return; return;
} else if (value_current_ == value_end_) { } else if (value_current_ == value_end_) {
return; return;
} }
// This will also reset the currently-occurring animation. // This will also reset the currently-occurring animation.
SetDuration(base::TimeDelta::FromMilliseconds( SetDuration(GetDuration());
static_cast<int>(slide_duration_ * (1 - value_current_))));
Start(); Start();
} }
...@@ -77,8 +76,7 @@ void SlideAnimation::Hide() { ...@@ -77,8 +76,7 @@ void SlideAnimation::Hide() {
} }
// This will also reset the currently-occurring animation. // This will also reset the currently-occurring animation.
SetDuration(base::TimeDelta::FromMilliseconds( SetDuration(GetDuration());
static_cast<int>(slide_duration_ * value_current_)));
Start(); Start();
} }
...@@ -86,13 +84,27 @@ void SlideAnimation::SetSlideDuration(int duration) { ...@@ -86,13 +84,27 @@ void SlideAnimation::SetSlideDuration(int duration) {
slide_duration_ = duration; slide_duration_ = duration;
} }
void SlideAnimation::SetDampeningValue(double dampening_value) {
dampening_value_ = dampening_value;
}
double SlideAnimation::GetCurrentValue() const { double SlideAnimation::GetCurrentValue() const {
return value_current_; 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) { void SlideAnimation::AnimateToState(double state) {
if (state > 1.0) if (state > 1.0)
state = 1.0; state = 1.0;
else if (state < 0)
state = 0;
state = Tween::CalculateValue(tween_type_, state); state = Tween::CalculateValue(tween_type_, state);
......
...@@ -67,6 +67,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation { ...@@ -67,6 +67,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
int GetSlideDuration() const { return slide_duration_; } int GetSlideDuration() const { return slide_duration_; }
void SetTweenType(Tween::Type tween_type) { tween_type_ = tween_type; } 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; double GetCurrentValue() const override;
// TODO(bruthig): Fix IsShowing() and IsClosing() to be consistent. e.g. // TODO(bruthig): Fix IsShowing() and IsClosing() to be consistent. e.g.
// IsShowing() will currently return true after the 'show' animation has been // IsShowing() will currently return true after the 'show' animation has been
...@@ -78,6 +82,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation { ...@@ -78,6 +82,10 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
class TestApi; class TestApi;
private: private:
// Gets the duration based on the dampening factor and whether the animation
// is showing or hiding.
base::TimeDelta GetDuration();
// Overridden from Animation. // Overridden from Animation.
void AnimateToState(double state) override; void AnimateToState(double state) override;
...@@ -98,6 +106,9 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation { ...@@ -98,6 +106,9 @@ class ANIMATION_EXPORT SlideAnimation : public LinearAnimation {
// kHoverFadeDurationMS, but can be overridden with SetDuration. // kHoverFadeDurationMS, but can be overridden with SetDuration.
int slide_duration_; int slide_duration_;
// Dampens the reduction in duration for animations which start partway.
double dampening_value_;
DISALLOW_COPY_AND_ASSIGN(SlideAnimation); DISALLOW_COPY_AND_ASSIGN(SlideAnimation);
}; };
......
...@@ -18,10 +18,23 @@ namespace gfx { ...@@ -18,10 +18,23 @@ namespace gfx {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// SlideAnimationTest // SlideAnimationTest
class SlideAnimationTest : public testing::Test { 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: protected:
SlideAnimationTest() SlideAnimationTest()
: scoped_task_environment_( : 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: private:
base::test::ScopedTaskEnvironment scoped_task_environment_; base::test::ScopedTaskEnvironment scoped_task_environment_;
...@@ -29,50 +42,45 @@ class SlideAnimationTest : public testing::Test { ...@@ -29,50 +42,45 @@ class SlideAnimationTest : public testing::Test {
// Tests animation construction. // Tests animation construction.
TEST_F(SlideAnimationTest, InitialState) { TEST_F(SlideAnimationTest, InitialState) {
SlideAnimation animation(nullptr);
// By default, slide animations are 60 Hz, so the timer interval should be // By default, slide animations are 60 Hz, so the timer interval should be
// 1/60th of a second. // 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. // Duration defaults to 120 ms.
EXPECT_EQ(120, animation.GetSlideDuration()); EXPECT_EQ(120, slide_animation_->GetSlideDuration());
// Slide is neither showing nor closing. // Slide is neither showing nor closing.
EXPECT_FALSE(animation.IsShowing()); EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_FALSE(animation.IsClosing()); EXPECT_FALSE(slide_animation_->IsClosing());
// Starts at 0. // Starts at 0.
EXPECT_EQ(0.0, animation.GetCurrentValue()); EXPECT_EQ(0.0, slide_animation_->GetCurrentValue());
} }
TEST_F(SlideAnimationTest, Basics) { TEST_F(SlideAnimationTest, Basics) {
SlideAnimation animation(nullptr);
AnimationTestApi test_api(&animation);
// Use linear tweening to make the math easier below. // Use linear tweening to make the math easier below.
animation.SetTweenType(Tween::LINEAR); slide_animation_->SetTweenType(Tween::LINEAR);
// Duration can be set after construction. // Duration can be set after construction.
animation.SetSlideDuration(100); slide_animation_->SetSlideDuration(100);
EXPECT_EQ(100, animation.GetSlideDuration()); EXPECT_EQ(100, slide_animation_->GetSlideDuration());
// Show toggles the appropriate state. // Show toggles the appropriate state.
animation.Show(); slide_animation_->Show();
EXPECT_TRUE(animation.IsShowing()); EXPECT_TRUE(slide_animation_->IsShowing());
EXPECT_FALSE(animation.IsClosing()); EXPECT_FALSE(slide_animation_->IsClosing());
// Simulate running the animation. // Simulate running the animation.
test_api.SetStartTime(base::TimeTicks()); RunAnimationFor(base::TimeDelta::FromMilliseconds(50));
test_api.Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(50)); EXPECT_EQ(0.5, slide_animation_->GetCurrentValue());
EXPECT_EQ(0.5, animation.GetCurrentValue());
// We can start hiding mid-way through the animation. // We can start hiding mid-way through the animation.
animation.Hide(); slide_animation_->Hide();
EXPECT_FALSE(animation.IsShowing()); EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_TRUE(animation.IsClosing()); EXPECT_TRUE(slide_animation_->IsClosing());
// Reset stops the animation. // Reset stops the animation.
animation.Reset(); slide_animation_->Reset();
EXPECT_EQ(0.0, animation.GetCurrentValue()); EXPECT_EQ(0.0, slide_animation_->GetCurrentValue());
EXPECT_FALSE(animation.IsShowing()); EXPECT_FALSE(slide_animation_->IsShowing());
EXPECT_FALSE(animation.IsClosing()); EXPECT_FALSE(slide_animation_->IsClosing());
} }
// Tests that delegate is not notified when animation is running and is deleted. // Tests that delegate is not notified when animation is running and is deleted.
...@@ -92,4 +100,128 @@ TEST_F(SlideAnimationTest, DontNotifyOnDelete) { ...@@ -92,4 +100,128 @@ TEST_F(SlideAnimationTest, DontNotifyOnDelete) {
EXPECT_FALSE(delegate.canceled()); 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 } // 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