Commit a017c5fc authored by Peter Kasting's avatar Peter Kasting Committed by Commit Bot

Keep running animations across an AnimationRunner switch.

Without this, removing an animated view from a widget would stall the animation;
this could leave its state incorrect, e.g. if it was later re-added.  This can
affect the omnibox with an upcoming patch I'm writing.

This was introduced by http://crrev.com/657587 ; before that, AnimationRunner
did not exist, and removing a view from a widget would have no effect on
animations.

Bug: none
Change-Id: Iae457306a3a3f68e42772009a24d34dd65d4a144
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1857191
Commit-Queue: Peter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Auto-Submit: Peter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706612}
parent bfb2cd15
...@@ -685,6 +685,7 @@ test("gfx_unittests") { ...@@ -685,6 +685,7 @@ test("gfx_unittests") {
if (!is_ios) { if (!is_ios) {
sources += [ sources += [
"animation/animation_container_unittest.cc", "animation/animation_container_unittest.cc",
"animation/animation_runner_unittest.cc",
"animation/animation_unittest.cc", "animation/animation_unittest.cc",
"animation/multi_animation_unittest.cc", "animation/multi_animation_unittest.cc",
"animation/slide_animation_unittest.cc", "animation/slide_animation_unittest.cc",
......
...@@ -19,16 +19,16 @@ AnimationContainer::~AnimationContainer() { ...@@ -19,16 +19,16 @@ AnimationContainer::~AnimationContainer() {
if (observer_) if (observer_)
observer_->AnimationContainerShuttingDown(this); observer_->AnimationContainerShuttingDown(this);
// The animations own us and stop themselves before being deleted. If // The animations own us and stop themselves before being deleted. If they're
// elements_ is not empty, something is wrong. // still running, something is wrong.
DCHECK(elements_.empty()); DCHECK(!is_running());
} }
void AnimationContainer::Start(AnimationContainerElement* element) { void AnimationContainer::Start(AnimationContainerElement* element) {
DCHECK(elements_.count(element) == 0); // Start should only be invoked if the DCHECK(elements_.count(element) == 0); // Start should only be invoked if the
// element isn't running. // element isn't running.
if (elements_.empty()) { if (!is_running()) {
last_tick_time_ = base::TimeTicks::Now(); last_tick_time_ = base::TimeTicks::Now();
SetMinTimerInterval(element->GetTimerInterval()); SetMinTimerInterval(element->GetTimerInterval());
min_timer_interval_count_ = 1; min_timer_interval_count_ = 1;
...@@ -49,7 +49,7 @@ void AnimationContainer::Stop(AnimationContainerElement* element) { ...@@ -49,7 +49,7 @@ void AnimationContainer::Stop(AnimationContainerElement* element) {
base::TimeDelta interval = element->GetTimerInterval(); base::TimeDelta interval = element->GetTimerInterval();
elements_.erase(element); elements_.erase(element);
if (elements_.empty()) { if (!is_running()) {
runner_->Stop(); runner_->Stop();
min_timer_interval_count_ = 0; min_timer_interval_count_ = 0;
if (observer_) if (observer_)
...@@ -76,6 +76,8 @@ void AnimationContainer::SetAnimationRunner( ...@@ -76,6 +76,8 @@ void AnimationContainer::SetAnimationRunner(
runner_ = has_custom_animation_runner_ runner_ = has_custom_animation_runner_
? std::move(runner) ? std::move(runner)
: AnimationRunner::CreateDefaultAnimationRunner(); : AnimationRunner::CreateDefaultAnimationRunner();
if (is_running())
RestartTimer(base::TimeTicks::Now() - last_tick_time_);
} }
void AnimationContainer::Run(base::TimeTicks current_time) { void AnimationContainer::Run(base::TimeTicks current_time) {
...@@ -107,14 +109,18 @@ void AnimationContainer::SetMinTimerInterval(base::TimeDelta delta) { ...@@ -107,14 +109,18 @@ void AnimationContainer::SetMinTimerInterval(base::TimeDelta delta) {
// that shouldn't be a problem for uses of Animation/AnimationContainer. // that shouldn't be a problem for uses of Animation/AnimationContainer.
runner_->Stop(); runner_->Stop();
min_timer_interval_ = delta; min_timer_interval_ = delta;
RestartTimer(base::TimeDelta());
}
void AnimationContainer::RestartTimer(base::TimeDelta elapsed) {
runner_->Start( runner_->Start(
min_timer_interval_, min_timer_interval_, elapsed,
base::BindRepeating(&AnimationContainer::Run, base::Unretained(this))); base::BindRepeating(&AnimationContainer::Run, base::Unretained(this)));
} }
std::pair<TimeDelta, size_t> AnimationContainer::GetMinIntervalAndCount() std::pair<TimeDelta, size_t> AnimationContainer::GetMinIntervalAndCount()
const { const {
DCHECK(!elements_.empty()); DCHECK(is_running());
// Find the minimum interval and the number of elements sharing that same // Find the minimum interval and the number of elements sharing that same
// interval. It is tempting to create a map of intervals -> counts in order to // interval. It is tempting to create a map of intervals -> counts in order to
......
...@@ -56,6 +56,7 @@ class ANIMATION_EXPORT AnimationContainer ...@@ -56,6 +56,7 @@ class ANIMATION_EXPORT AnimationContainer
bool is_running() const { return !elements_.empty(); } bool is_running() const { return !elements_.empty(); }
void SetAnimationRunner(std::unique_ptr<AnimationRunner> runner); void SetAnimationRunner(std::unique_ptr<AnimationRunner> runner);
AnimationRunner* animation_runner_for_testing() { return runner_.get(); }
bool has_custom_animation_runner() const { bool has_custom_animation_runner() const {
return has_custom_animation_runner_; return has_custom_animation_runner_;
} }
...@@ -80,6 +81,10 @@ class ANIMATION_EXPORT AnimationContainer ...@@ -80,6 +81,10 @@ class ANIMATION_EXPORT AnimationContainer
// Sets min_timer_interval_ and restarts the timer. // Sets min_timer_interval_ and restarts the timer.
void SetMinTimerInterval(base::TimeDelta delta); void SetMinTimerInterval(base::TimeDelta delta);
// Restarts the timer, assuming |elapsed| has already elapsed out of the timer
// interval.
void RestartTimer(base::TimeDelta elapsed);
// Returns the min timer interval of all the timers, and the count of timers // Returns the min timer interval of all the timers, and the count of timers
// at that interval. // at that interval.
std::pair<base::TimeDelta, size_t> GetMinIntervalAndCount() const; std::pair<base::TimeDelta, size_t> GetMinIntervalAndCount() const;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/animation/animation_container_observer.h" #include "ui/gfx/animation/animation_container_observer.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/gfx/animation/linear_animation.h" #include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/animation/test_animation_delegate.h" #include "ui/gfx/animation/test_animation_delegate.h"
...@@ -53,6 +54,8 @@ class TestAnimation : public LinearAnimation { ...@@ -53,6 +54,8 @@ class TestAnimation : public LinearAnimation {
void AnimateToState(double state) override {} void AnimateToState(double state) override {}
using LinearAnimation::duration;
private: private:
DISALLOW_COPY_AND_ASSIGN(TestAnimation); DISALLOW_COPY_AND_ASSIGN(TestAnimation);
}; };
...@@ -143,4 +146,27 @@ TEST_F(AnimationContainerTest, Observer) { ...@@ -143,4 +146,27 @@ TEST_F(AnimationContainerTest, Observer) {
container->set_observer(NULL); container->set_observer(NULL);
} }
// Tests that calling SetAnimationRunner() keeps running animations at their
// current point.
TEST_F(AnimationContainerTest, AnimationsRunAcrossRunnerChange) {
TestAnimationDelegate delegate;
auto container = base::MakeRefCounted<AnimationContainer>();
AnimationContainerTestApi test_api(container.get());
TestAnimation animation(&delegate);
animation.SetContainer(container.get());
animation.Start();
test_api.IncrementTime(animation.duration() / 2);
EXPECT_FALSE(delegate.finished());
container->SetAnimationRunner(nullptr);
AnimationRunner* runner = container->animation_runner_for_testing();
ASSERT_TRUE(runner);
ASSERT_FALSE(runner->step_is_null_for_testing());
EXPECT_FALSE(delegate.finished());
test_api.IncrementTime(animation.duration() / 2);
EXPECT_TRUE(delegate.finished());
}
} // namespace gfx } // namespace gfx
...@@ -18,21 +18,40 @@ class DefaultAnimationRunner : public gfx::AnimationRunner { ...@@ -18,21 +18,40 @@ class DefaultAnimationRunner : public gfx::AnimationRunner {
~DefaultAnimationRunner() override = default; ~DefaultAnimationRunner() override = default;
// gfx::AnimationRunner: // gfx::AnimationRunner:
void Stop() override { timer_.Stop(); } void Stop() override;
protected: protected:
// gfx::AnimationRunner: // gfx::AnimationRunner:
void OnStart(base::TimeDelta min_interval) override { void OnStart(base::TimeDelta min_interval, base::TimeDelta elapsed) override;
timer_.Start(FROM_HERE, min_interval, this,
&DefaultAnimationRunner::OnTimerTick);
}
private: private:
void OnTimerTick() { Step(base::TimeTicks::Now()); } void OnTimerTick();
base::RepeatingTimer timer_; base::OneShotTimer timer_;
base::TimeDelta min_interval_;
}; };
void DefaultAnimationRunner::Stop() {
timer_.Stop();
}
void DefaultAnimationRunner::OnStart(base::TimeDelta min_interval,
base::TimeDelta elapsed) {
min_interval_ = min_interval;
timer_.Start(FROM_HERE, min_interval - elapsed, this,
&DefaultAnimationRunner::OnTimerTick);
}
void DefaultAnimationRunner::OnTimerTick() {
// This is effectively a RepeatingTimer. It's possible to use a true
// RepeatingTimer for this, but since OnStart() may need to use a OneShotTimer
// anyway (when |elapsed| is nonzero), it's just more complicated.
timer_.Start(FROM_HERE, min_interval_, this,
&DefaultAnimationRunner::OnTimerTick);
// Call Step() after timer_.Start() in case Step() calls Stop().
Step(base::TimeTicks::Now());
}
} // namespace } // namespace
namespace gfx { namespace gfx {
...@@ -47,9 +66,10 @@ AnimationRunner::~AnimationRunner() = default; ...@@ -47,9 +66,10 @@ AnimationRunner::~AnimationRunner() = default;
void AnimationRunner::Start( void AnimationRunner::Start(
base::TimeDelta min_interval, base::TimeDelta min_interval,
base::TimeDelta elapsed,
base::RepeatingCallback<void(base::TimeTicks)> step) { base::RepeatingCallback<void(base::TimeTicks)> step) {
step_ = std::move(step); step_ = std::move(step);
OnStart(min_interval); OnStart(min_interval, elapsed);
} }
AnimationRunner::AnimationRunner() = default; AnimationRunner::AnimationRunner() = default;
......
...@@ -27,20 +27,25 @@ class ANIMATION_EXPORT AnimationRunner { ...@@ -27,20 +27,25 @@ class ANIMATION_EXPORT AnimationRunner {
virtual ~AnimationRunner(); virtual ~AnimationRunner();
// Sets the provided |step| callback, then calls OnStart() with the provided // Sets the provided |step| callback, then calls OnStart() with the provided
// |min_interval| to allow the subclass to actually begin animating. // |min_interval| and |elapsed| time to allow the subclass to actually begin
// Subclasses are expected to call Step() periodically to drive the animation. // animating. Subclasses are expected to call Step() periodically to drive the
// animation.
void Start(base::TimeDelta min_interval, void Start(base::TimeDelta min_interval,
base::TimeDelta elapsed,
base::RepeatingCallback<void(base::TimeTicks)> step); base::RepeatingCallback<void(base::TimeTicks)> step);
// Called when subclasses don't need to call Step() anymore. // Called when subclasses don't need to call Step() anymore.
virtual void Stop() = 0; virtual void Stop() = 0;
bool step_is_null_for_testing() const { return step_.is_null(); }
protected: protected:
AnimationRunner(); AnimationRunner();
// Called when subclasses should start calling Step() periodically to // Called when subclasses should start calling Step() periodically to
// drive the animation. // drive the animation.
virtual void OnStart(base::TimeDelta min_interval) = 0; virtual void OnStart(base::TimeDelta min_interval,
base::TimeDelta elapsed) = 0;
// Advances the animation based on |tick|. // Advances the animation based on |tick|.
void Step(base::TimeTicks tick); void Step(base::TimeTicks tick);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/animation/animation_runner.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gfx {
namespace {
using AnimationRunnerTest = testing::Test;
// Verifies that calling Stop() during Step() actually stops the timer.
TEST(AnimationRunnerTest, StopDuringStep) {
base::test::TaskEnvironment task_environment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto runner = AnimationRunner::CreateDefaultAnimationRunner();
constexpr auto kDelay = base::TimeDelta::FromMilliseconds(20);
int call_count = 0;
runner->Start(kDelay, base::TimeDelta(),
base::BindLambdaForTesting([&](base::TimeTicks ticks) {
++call_count;
runner->Stop();
}));
EXPECT_EQ(0, call_count);
task_environment.FastForwardBy(kDelay);
EXPECT_EQ(1, call_count);
task_environment.FastForwardBy(kDelay);
EXPECT_EQ(1, call_count);
}
} // namespace
} // namespace gfx
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
namespace gfx { namespace gfx {
class AnimationTest: public testing::Test { class AnimationTest : public testing::Test {
protected: protected:
AnimationTest() AnimationTest()
: task_environment_( : task_environment_(
......
...@@ -15,31 +15,25 @@ namespace gfx { ...@@ -15,31 +15,25 @@ namespace gfx {
// message loop. // message loop.
class TestAnimationDelegate : public AnimationDelegate { class TestAnimationDelegate : public AnimationDelegate {
public: public:
TestAnimationDelegate() : canceled_(false), finished_(false) { TestAnimationDelegate() = default;
}
virtual void AnimationEnded(const Animation* animation) { virtual void AnimationEnded(const Animation* animation) {
finished_ = true; finished_ = true;
base::RunLoop::QuitCurrentWhenIdleDeprecated(); if (base::RunLoop::IsRunningOnCurrentThread())
base::RunLoop::QuitCurrentWhenIdleDeprecated();
} }
virtual void AnimationCanceled(const Animation* animation) { virtual void AnimationCanceled(const Animation* animation) {
finished_ = true;
canceled_ = true; canceled_ = true;
base::RunLoop::QuitCurrentWhenIdleDeprecated(); AnimationEnded(animation);
} }
bool finished() const { bool finished() const { return finished_; }
return finished_; bool canceled() const { return canceled_; }
}
bool canceled() const {
return canceled_;
}
private: private:
bool canceled_; bool canceled_ = false;
bool finished_; bool finished_ = false;
DISALLOW_COPY_AND_ASSIGN(TestAnimationDelegate); DISALLOW_COPY_AND_ASSIGN(TestAnimationDelegate);
}; };
......
...@@ -59,11 +59,12 @@ void CompositorAnimationRunner::OnCompositingShuttingDown( ...@@ -59,11 +59,12 @@ void CompositorAnimationRunner::OnCompositingShuttingDown(
compositor_ = nullptr; compositor_ = nullptr;
} }
void CompositorAnimationRunner::OnStart(base::TimeDelta min_interval) { void CompositorAnimationRunner::OnStart(base::TimeDelta min_interval,
base::TimeDelta elapsed) {
if (!compositor_) if (!compositor_)
return; return;
last_tick_ = base::TimeTicks::Now(); last_tick_ = base::TimeTicks::Now() - elapsed;
min_interval_ = min_interval; min_interval_ = min_interval;
DCHECK(!compositor_->HasAnimationObserver(this)); DCHECK(!compositor_->HasAnimationObserver(this));
compositor_->AddAnimationObserver(this); compositor_->AddAnimationObserver(this);
......
...@@ -30,7 +30,7 @@ class CompositorAnimationRunner : public gfx::AnimationRunner, ...@@ -30,7 +30,7 @@ class CompositorAnimationRunner : public gfx::AnimationRunner,
protected: protected:
// gfx::AnimationRunner: // gfx::AnimationRunner:
void OnStart(base::TimeDelta min_interval) override; void OnStart(base::TimeDelta min_interval, base::TimeDelta elapsed) override;
private: private:
// This observes Compositor's destruction and helps CompositorAnimationRunner // This observes Compositor's destruction and helps CompositorAnimationRunner
......
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