diff options
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(); |