diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 23:20:52 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 23:20:52 +0000 |
commit | 059e7a559572484fb677480ef6b95322cde3b34f (patch) | |
tree | d21b6b7393c4d27d1fb2ee172c2a0ac7330138ae /app | |
parent | e3f4cc62db9c967b911b00399674e392c71d0a7d (diff) | |
download | chromium_src-059e7a559572484fb677480ef6b95322cde3b34f.zip chromium_src-059e7a559572484fb677480ef6b95322cde3b34f.tar.gz chromium_src-059e7a559572484fb677480ef6b95322cde3b34f.tar.bz2 |
Adds AnimationContainer, which can be used to group a set of
animations to have the same timer. By default each animation has one
animationcontainer, but I'm going to change the tab renderer to share
the animationcontainer so that the pulse effects happen in unison.
Also updated the BoundsAnimator so that I can use it by the TabStrip.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/1575011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43407 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'app')
-rw-r--r-- | app/animation.cc | 97 | ||||
-rw-r--r-- | app/animation.h | 34 | ||||
-rw-r--r-- | app/animation_container.cc | 93 | ||||
-rw-r--r-- | app/animation_container.h | 92 | ||||
-rw-r--r-- | app/animation_container_unittest.cc | 120 | ||||
-rw-r--r-- | app/animation_unittest.cc | 39 | ||||
-rw-r--r-- | app/app.gyp | 2 | ||||
-rw-r--r-- | app/app_base.gypi | 2 | ||||
-rw-r--r-- | app/slide_animation.h | 6 | ||||
-rw-r--r-- | app/test_animation_delegate.h | 47 | ||||
-rw-r--r-- | app/throb_animation.cc | 5 | ||||
-rw-r--r-- | app/throb_animation.h | 7 |
12 files changed, 446 insertions, 98 deletions
diff --git a/app/animation.cc b/app/animation.cc index f2d95f4..5e9d10c 100644 --- a/app/animation.cc +++ b/app/animation.cc @@ -1,10 +1,10 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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 "app/animation.h" -#include "base/message_loop.h" +#include "app/animation_container.h" #include "gfx/rect.h" #if defined(OS_WIN) @@ -37,10 +37,8 @@ Animation::Animation(int duration, } Animation::~Animation() { -} - -void Animation::Reset() { - start_time_ = Time::Now(); + if (animating_) + container_->Stop(this); } double Animation::GetCurrentValue() const { @@ -69,10 +67,13 @@ gfx::Rect Animation::CurrentValueBetween(const gfx::Rect& start_bounds, void Animation::Start() { if (!animating_) { - start_time_ = Time::Now(); - timer_.Start(timer_interval_, this, &Animation::Run); + if (!container_.get()) + container_ = new AnimationContainer(); animating_ = true; + + container_->Start(this); + if (delegate_) delegate_->AnimationStarted(this); } @@ -80,9 +81,11 @@ void Animation::Start() { void Animation::Stop() { if (animating_) { - timer_.Stop(); - animating_ = false; + + // Notify the container first as the delegate may delete us. + container_->Stop(this); + if (delegate_) { if (state_ >= 1.0) delegate_->AnimationEnded(this); @@ -94,9 +97,11 @@ void Animation::Stop() { void Animation::End() { if (animating_) { - timer_.Stop(); - animating_ = false; + + // Notify the container first as the delegate may delete us. + container_->Stop(this); + AnimateToState(1.0); if (delegate_) delegate_->AnimationEnded(this); @@ -111,34 +116,8 @@ void Animation::SetDuration(int duration) { duration_ = TimeDelta::FromMilliseconds(duration); if (duration_ < timer_interval_) duration_ = timer_interval_; - start_time_ = Time::Now(); -} - -void Animation::Step() { - TimeDelta elapsed_time = Time::Now() - start_time_; - state_ = static_cast<double>(elapsed_time.InMicroseconds()) / - static_cast<double>(duration_.InMicroseconds()); - - if (state_ >= 1.0) - state_ = 1.0; - - AnimateToState(state_); - if (delegate_) - delegate_->AnimationProgressed(this); - - if (state_ == 1.0) - Stop(); -} - -void Animation::Run() { - Step(); -} - -TimeDelta Animation::CalculateInterval(int frame_rate) { - int timer_interval = 1000000 / frame_rate; - if (timer_interval < 10000) - timer_interval = 10000; - return TimeDelta::FromMicroseconds(timer_interval); + if (animating_) + start_time_ = container_->last_tick_time(); } // static @@ -160,3 +139,41 @@ bool Animation::ShouldRenderRichAnimation() { return true; } +void Animation::SetContainer(AnimationContainer* container) { + if (container == container_.get()) + return; + + if (animating_) + container_->Stop(this); + + if (container) + container_ = container; + else + container_ = new AnimationContainer(); + + if (animating_) + container_->Start(this); +} + +void Animation::Step(base::TimeTicks time_now) { + TimeDelta elapsed_time = time_now - start_time_; + state_ = static_cast<double>(elapsed_time.InMicroseconds()) / + static_cast<double>(duration_.InMicroseconds()); + if (state_ >= 1.0) + state_ = 1.0; + + AnimateToState(state_); + + if (delegate_) + delegate_->AnimationProgressed(this); + + if (state_ == 1.0) + Stop(); +} + +TimeDelta Animation::CalculateInterval(int frame_rate) { + int timer_interval = 1000000 / frame_rate; + if (timer_interval < 10000) + timer_interval = 10000; + return TimeDelta::FromMicroseconds(timer_interval); +} diff --git a/app/animation.h b/app/animation.h index bfa7fd0..c483e1d 100644 --- a/app/animation.h +++ b/app/animation.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. // Inspired by NSAnimation @@ -6,10 +6,11 @@ #ifndef APP_ANIMATION_H_ #define APP_ANIMATION_H_ +#include "base/ref_counted.h" #include "base/time.h" -#include "base/timer.h" class Animation; +class AnimationContainer; namespace gfx { class Rect; @@ -69,9 +70,6 @@ class Animation { Animation(int duration, int frame_rate, AnimationDelegate* delegate); virtual ~Animation(); - // Reset state so that the animation can be started again. - virtual void Reset(); - // Called when the animation progresses. Subclasses override this to // efficiently update their state. virtual void AnimateToState(double state) = 0; @@ -112,13 +110,27 @@ class Animation { // Sets the delegate. void set_delegate(AnimationDelegate* delegate) { delegate_ = delegate; } + // Sets the container used to manage the timer. A value of NULL results in + // creating a new AnimationContainer. + void SetContainer(AnimationContainer* container); + + base::TimeDelta timer_interval() const { return timer_interval_; } + protected: - // Overriddable, called by Run. - virtual void Step(); + // Invoked by the AnimationContainer when the animation is running to advance + // the animation. Use |time_now| rather than Time::Now to avoid multiple + // animations running at the same time diverging. + virtual void Step(base::TimeTicks time_now); // Calculates the timer interval from the constructor list. base::TimeDelta CalculateInterval(int frame_rate); + private: + friend class AnimationContainer; + + // Invoked from AnimationContainer when started. + void set_start_time(base::TimeTicks start_time) { start_time_ = start_time; } + // Whether or not we are currently animating. bool animating_; @@ -129,15 +141,11 @@ class Animation { // Current state, on a scale from 0.0 to 1.0. double state_; - base::Time start_time_; + base::TimeTicks start_time_; AnimationDelegate* delegate_; - base::RepeatingTimer<Animation> timer_; - - private: - // Called when the animation's timer expires, calls Step. - void Run(); + scoped_refptr<AnimationContainer> container_; DISALLOW_COPY_AND_ASSIGN(Animation); }; diff --git a/app/animation_container.cc b/app/animation_container.cc new file mode 100644 index 0000000..b0cc86a --- /dev/null +++ b/app/animation_container.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2010 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 "app/animation_container.h" + +#include "app/animation.h" + +using base::TimeDelta; +using base::TimeTicks; + +AnimationContainer::AnimationContainer() + : last_tick_time_(TimeTicks::Now()), + observer_(NULL) { +} + +AnimationContainer::~AnimationContainer() { + // The animations own us and stop themselves before being deleted. If + // animations_ is not empty, something is wrong. + DCHECK(animations_.empty()); +} + +void AnimationContainer::Start(Animation* animation) { + DCHECK(animations_.count(animation) == 0); // Start should only be invoked + // if the animation isn't running. + + if (animations_.empty()) { + last_tick_time_ = TimeTicks::Now(); + SetMinTimerInterval(animation->timer_interval()); + } else if (animation->timer_interval() < min_timer_interval_) { + SetMinTimerInterval(animation->timer_interval()); + } + + animation->set_start_time(last_tick_time_); + animations_.insert(animation); +} + +void AnimationContainer::Stop(Animation* animation) { + DCHECK(animations_.count(animation) > 0); // The animation must be running. + + animations_.erase(animation); + + if (animations_.empty()) { + timer_.Stop(); + if (observer_) + observer_->AnimationContainerEmpty(this); + } else { + TimeDelta min_timer_interval = GetMinInterval(); + if (min_timer_interval > min_timer_interval_) + SetMinTimerInterval(min_timer_interval); + } +} + +void AnimationContainer::Run() { + TimeTicks current_time = TimeTicks::Now(); + + last_tick_time_ = current_time; + + // Make a copy of the animations to iterate over so that if any animations + // are removed as part of invoking Step there aren't any problems. + Animations animations = animations_; + + for (Animations::const_iterator i = animations.begin(); + i != animations.end(); ++i) { + // Make sure the animation is still valid. + if (animations_.find(*i) != animations_.end()) + (*i)->Step(current_time); + } + + if (observer_) + observer_->AnimationContainerProgressed(this); +} + +void AnimationContainer::SetMinTimerInterval(base::TimeDelta delta) { + // This doesn't take into account how far along current animation is, but that + // shouldn't be a problem for uses of Animation/AnimationContainer. + timer_.Stop(); + min_timer_interval_ = delta; + timer_.Start(min_timer_interval_, this, &AnimationContainer::Run); +} + +TimeDelta AnimationContainer::GetMinInterval() { + DCHECK(!animations_.empty()); + + TimeDelta min; + Animations::const_iterator i = animations_.begin(); + min = (*i)->timer_interval(); + for (++i; i != animations_.end(); ++i) { + if ((*i)->timer_interval() < min) + min = (*i)->timer_interval(); + } + return min; +} diff --git a/app/animation_container.h b/app/animation_container.h new file mode 100644 index 0000000..fcb3fad --- /dev/null +++ b/app/animation_container.h @@ -0,0 +1,92 @@ +// Copyright (c) 2010 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. + +#ifndef APP_ANIMATION_CONTAINER_H_ +#define APP_ANIMATION_CONTAINER_H_ + +#include <set> + +#include "base/ref_counted.h" +#include "base/time.h" +#include "base/timer.h" + +class Animation; + +// AnimationContainer is used by Animation to manage the underlying timer. +// Internally each Animation creates a single AnimationContainer. You can +// group a set of Animations into the same AnimationContainer by way of +// Animation::SetContainer. Grouping a set of Animations into the same +// AnimationContainer ensures they all update and start at the same time. +// +// AnimationContainer is ref counted. Each Animation contained within the +// AnimationContainer own it. +class AnimationContainer : public base::RefCounted<AnimationContainer> { + public: + // The observer is notified after every update of the animations managed by + // the container. + class Observer { + public: + // Invoked on every tick of the timer managed by the container and after + // all the animations have updated. + virtual void AnimationContainerProgressed( + AnimationContainer* container) = 0; + + // Invoked when no more animations are being managed by this container. + virtual void AnimationContainerEmpty(AnimationContainer* container) = 0; + }; + + AnimationContainer(); + + void set_observer(Observer* observer) { observer_ = observer; } + + // The time the last animation ran at. + base::TimeTicks last_tick_time() const { return last_tick_time_; } + + // Are there any timers running? + bool is_running() const { return !animations_.empty(); } + + private: + friend class Animation; + friend class base::RefCounted<AnimationContainer>; + + typedef std::set<Animation*> Animations; + + ~AnimationContainer(); + + // Invoked by Animation when it needs to start. Starts the timer if necessary. + void Start(Animation* animation); + + // Invoked by Animation when it needs to stop. If there are no more animations + // running the timer stops. + void Stop(Animation* animation); + + // Timer callback method. + void Run(); + + // Sets min_timer_interval_ and restarts the timer. + void SetMinTimerInterval(base::TimeDelta delta); + + // Returns the min timer interval of all the timers. + base::TimeDelta GetMinInterval(); + + // Represents one of two possible values: + // . If only a single animation has been started and the timer hasn't yet + // fired this is the time the animation was added. + // . The time the last animation ran at (::Run was invoked). + base::TimeTicks last_tick_time_; + + // Set of animations being managed. + Animations animations_; + + // Minimum interval the timers run at. + base::TimeDelta min_timer_interval_; + + base::RepeatingTimer<AnimationContainer> timer_; + + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(AnimationContainer); +}; + +#endif // APP_ANIMATION_CONTAINER_H_ diff --git a/app/animation_container_unittest.cc b/app/animation_container_unittest.cc new file mode 100644 index 0000000..4046ae3 --- /dev/null +++ b/app/animation_container_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2010 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 "app/animation.h" +#include "app/animation_container.h" +#include "app/test_animation_delegate.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::AtLeast; + +namespace { + +class MockObserver : public AnimationContainer::Observer { + public: + MockObserver() {} + + MOCK_METHOD1(AnimationContainerProgressed, void(AnimationContainer*)); + MOCK_METHOD1(AnimationContainerEmpty, void(AnimationContainer*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockObserver); +}; + +class TestAnimation : public Animation { + public: + TestAnimation(AnimationDelegate* delegate) + : Animation(20, 20, delegate) { + } + + virtual void AnimateToState(double state) { + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestAnimation); +}; + +} // namespace + +class AnimationContainerTest: public testing::Test { + private: + MessageLoopForUI message_loop_; +}; + +// Makes sure the animation ups the ref count of the container and releases it +// appropriately. +TEST_F(AnimationContainerTest, Ownership) { + TestAnimationDelegate delegate; + scoped_refptr<AnimationContainer> container(new AnimationContainer()); + scoped_ptr<Animation> animation(new TestAnimation(&delegate)); + animation->SetContainer(container.get()); + // Setting the container should up the ref count. + EXPECT_FALSE(container->HasOneRef()); + + animation.reset(); + + // Releasing the animation should decrement the ref count. + EXPECT_TRUE(container->HasOneRef()); +} + +// Makes sure multiple animations are managed correctly. +TEST_F(AnimationContainerTest, Multi) { + TestAnimationDelegate delegate1; + TestAnimationDelegate delegate2; + + scoped_refptr<AnimationContainer> container(new AnimationContainer()); + TestAnimation animation1(&delegate1); + TestAnimation animation2(&delegate2); + animation1.SetContainer(container.get()); + animation2.SetContainer(container.get()); + + // Start both animations. + animation1.Start(); + EXPECT_TRUE(container->is_running()); + animation2.Start(); + EXPECT_TRUE(container->is_running()); + + // Run the message loop the delegate quits the message loop when notified. + MessageLoop::current()->Run(); + + // Both timers should have finished. + EXPECT_TRUE(delegate1.finished()); + EXPECT_TRUE(delegate2.finished()); + + // And the container should no longer be runnings. + EXPECT_FALSE(container->is_running()); +} + +// Makes sure observer is notified appropriately. +TEST_F(AnimationContainerTest, Observer) { + MockObserver observer; + TestAnimationDelegate delegate1; + + scoped_refptr<AnimationContainer> container(new AnimationContainer()); + container->set_observer(&observer); + TestAnimation animation1(&delegate1); + animation1.SetContainer(container.get()); + + // We expect to get these two calls: the animation progressed, and then when + // the animation completed the container went empty. + EXPECT_CALL(observer, AnimationContainerProgressed(container.get())).Times( + AtLeast(1)); + EXPECT_CALL(observer, AnimationContainerEmpty(container.get())).Times(1); + + // Start the animation. + animation1.Start(); + EXPECT_TRUE(container->is_running()); + + // Run the message loop the delegate quits the message loop when notified. + MessageLoop::current()->Run(); + + // The timer should have finished. + EXPECT_TRUE(delegate1.finished()); + + // And the container should no longer be runnings. + EXPECT_FALSE(container->is_running()); + + container->set_observer(NULL); +} diff --git a/app/animation_unittest.cc b/app/animation_unittest.cc index bc1ebe5..30f9b52 100644 --- a/app/animation_unittest.cc +++ b/app/animation_unittest.cc @@ -1,9 +1,9 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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 "app/animation.h" -#include "base/message_loop.h" +#include "app/test_animation_delegate.h" #if defined(OS_WIN) #include "base/win_util.h" #endif @@ -47,40 +47,6 @@ class CancelAnimation : public Animation { /////////////////////////////////////////////////////////////////////////////// // LinearCase -class TestAnimationDelegate : public AnimationDelegate { - public: - TestAnimationDelegate() : - canceled_(false), - finished_(false) { - } - - virtual void AnimationStarted(const Animation* animation) { - } - - virtual void AnimationEnded(const Animation* animation) { - finished_ = true; - MessageLoop::current()->Quit(); - } - - virtual void AnimationCanceled(const Animation* animation) { - finished_ = true; - canceled_ = true; - MessageLoop::current()->Quit(); - } - - bool finished() { - return finished_; - } - - bool canceled() { - return canceled_; - } - - private: - bool canceled_; - bool finished_; -}; - TEST_F(AnimationTest, RunCase) { TestAnimationDelegate ad; RunAnimation a1(150, &ad); @@ -120,4 +86,3 @@ TEST_F(AnimationTest, ShouldRenderRichAnimation) { EXPECT_TRUE(Animation::ShouldRenderRichAnimation()); #endif } - diff --git a/app/app.gyp b/app/app.gyp index d8fef69..f87f721 100644 --- a/app/app.gyp +++ b/app/app.gyp @@ -28,6 +28,7 @@ 'app_resources', '../net/net.gyp:net_test_support', '../skia/skia.gyp:skia', + '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', '../third_party/icu/icu.gyp:icui18n', '../third_party/icu/icu.gyp:icuuc', @@ -37,6 +38,7 @@ '../third_party/zlib/zlib.gyp:zlib', ], 'sources': [ + 'animation_container_unittest.cc', 'animation_unittest.cc', 'clipboard/clipboard_unittest.cc', 'l10n_util_mac_unittest.mm', diff --git a/app/app_base.gypi b/app/app_base.gypi index 5358a0b..07d5f3c 100644 --- a/app/app_base.gypi +++ b/app/app_base.gypi @@ -95,6 +95,8 @@ # Files that are not required for Win64 Native Client loader 'active_window_watcher_x.cc', 'active_window_watcher_x.h', + 'animation_container.cc', + 'animation_container.h', 'animation.cc', 'animation.h', 'bidi_line_iterator.cc', diff --git a/app/slide_animation.h b/app/slide_animation.h index 15357fd..3cf32d9 100644 --- a/app/slide_animation.h +++ b/app/slide_animation.h @@ -43,9 +43,6 @@ // } class SlideAnimation : public Animation { public: - explicit SlideAnimation(AnimationDelegate* target); - virtual ~SlideAnimation(); - enum TweenType { NONE, // Linear. EASE_OUT, // Fast in, slow out (default). @@ -55,6 +52,9 @@ class SlideAnimation : public Animation { EASE_OUT_SNAP, // Fast in, slow out, snap to final value. }; + explicit SlideAnimation(AnimationDelegate* target); + virtual ~SlideAnimation(); + // Set the animation back to the 0 state. virtual void Reset(); virtual void Reset(double value); diff --git a/app/test_animation_delegate.h b/app/test_animation_delegate.h new file mode 100644 index 0000000..76f0f3e --- /dev/null +++ b/app/test_animation_delegate.h @@ -0,0 +1,47 @@ +// Copyright (c) 2010 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. + +#ifndef APP_TEST_ANIMATION_DELEGATE_H_ +#define APP_TEST_ANIMATION_DELEGATE_H_ + +#include "app/animation.h" +#include "base/message_loop.h" + +// Trivial AnimationDelegate implementation. AnimationEnded/Canceled quit the +// message loop. +class TestAnimationDelegate : public AnimationDelegate { + public: + TestAnimationDelegate() : canceled_(false), finished_(false) { + } + + virtual void AnimationStarted(const Animation* animation) { + } + + virtual void AnimationEnded(const Animation* animation) { + finished_ = true; + MessageLoop::current()->Quit(); + } + + virtual void AnimationCanceled(const Animation* animation) { + finished_ = true; + canceled_ = true; + MessageLoop::current()->Quit(); + } + + bool finished() const { + return finished_; + } + + bool canceled() const { + return canceled_; + } + + private: + bool canceled_; + bool finished_; + + DISALLOW_COPY_AND_ASSIGN(TestAnimationDelegate); +}; + +#endif // APP_TEST_ANIMATION_DELEGATE_H_ diff --git a/app/throb_animation.cc b/app/throb_animation.cc index 28b5763..e8e5730 100644 --- a/app/throb_animation.cc +++ b/app/throb_animation.cc @@ -43,8 +43,9 @@ void ThrobAnimation::Hide() { SlideAnimation::Hide(); } -void ThrobAnimation::Step() { - Animation::Step(); +void ThrobAnimation::Step(base::TimeTicks time_now) { + Animation::Step(time_now); + if (!IsAnimating() && throbbing_) { // Were throbbing a finished a cycle. Start the next cycle unless we're at // the end of the cycles, in which case we stop. diff --git a/app/throb_animation.h b/app/throb_animation.h index 43e4197..2405724 100644 --- a/app/throb_animation.h +++ b/app/throb_animation.h @@ -31,9 +31,6 @@ class ThrobAnimation : public SlideAnimation { virtual void Show(); virtual void Hide(); - // Overriden to continually throb (assuming we're throbbing). - virtual void Step(); - // Overridden to maintain the slide duration. virtual void SetSlideDuration(int duration) { slide_duration_ = duration; } @@ -41,6 +38,10 @@ class ThrobAnimation : public SlideAnimation { void set_cycles_remaining(int value) { cycles_remaining_ = value; } int cycles_remaining() const { return cycles_remaining_; } + protected: + // Overriden to continually throb (assuming we're throbbing). + virtual void Step(base::TimeTicks time_now); + private: // Resets state such that we behave like SlideAnimation. void ResetForSlide(); |