diff options
-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 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/views/fullscreen_exit_bubble.h | 1 | ||||
-rw-r--r-- | chrome/browser/views/infobars/infobars.h | 1 | ||||
-rw-r--r-- | chrome/browser/views/tabs/side_tab.cc | 1 | ||||
-rw-r--r-- | chrome/browser/views/tabs/tab_renderer.cc | 2 | ||||
-rw-r--r-- | views/animation/bounds_animator.cc | 235 | ||||
-rw-r--r-- | views/animation/bounds_animator.h | 140 |
19 files changed, 742 insertions, 184 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(); diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc index ef1df93..20307d3 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc @@ -564,7 +564,7 @@ void TabRendererGtk::AnimationEnded(const Animation* animation) { void TabRendererGtk::StartCrashAnimation() { if (!crash_animation_.get()) crash_animation_.reset(new FavIconCrashAnimation(this)); - crash_animation_->Reset(); + crash_animation_->Stop(); crash_animation_->Start(); } diff --git a/chrome/browser/views/fullscreen_exit_bubble.h b/chrome/browser/views/fullscreen_exit_bubble.h index 3767518..c9db5bb 100644 --- a/chrome/browser/views/fullscreen_exit_bubble.h +++ b/chrome/browser/views/fullscreen_exit_bubble.h @@ -7,6 +7,7 @@ #include "app/slide_animation.h" #include "base/scoped_ptr.h" +#include "base/timer.h" #include "chrome/browser/command_updater.h" #include "views/controls/link.h" diff --git a/chrome/browser/views/infobars/infobars.h b/chrome/browser/views/infobars/infobars.h index 5c0f5f6..56cf48f 100644 --- a/chrome/browser/views/infobars/infobars.h +++ b/chrome/browser/views/infobars/infobars.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_VIEWS_INFOBARS_INFOBARS_H_ #include "app/animation.h" +#include "base/task.h" #include "chrome/browser/tab_contents/infobar_delegate.h" #include "views/controls/button/button.h" #include "views/controls/link.h" diff --git a/chrome/browser/views/tabs/side_tab.cc b/chrome/browser/views/tabs/side_tab.cc index c11fead..9c45dd2 100644 --- a/chrome/browser/views/tabs/side_tab.cc +++ b/chrome/browser/views/tabs/side_tab.cc @@ -6,6 +6,7 @@ #include "app/resource_bundle.h" #include "app/theme_provider.h" +#include "base/logging.h" #include "base/utf_string_conversions.h" #include "gfx/canvas.h" #include "gfx/path.h" diff --git a/chrome/browser/views/tabs/tab_renderer.cc b/chrome/browser/views/tabs/tab_renderer.cc index fe1a1db..b84dc20 100644 --- a/chrome/browser/views/tabs/tab_renderer.cc +++ b/chrome/browser/views/tabs/tab_renderer.cc @@ -853,7 +853,7 @@ double TabRenderer::GetThrobValue() { void TabRenderer::StartCrashAnimation() { if (!crash_animation_) crash_animation_ = new FavIconCrashAnimation(this); - crash_animation_->Reset(); + crash_animation_->Stop(); crash_animation_->Start(); } diff --git a/views/animation/bounds_animator.cc b/views/animation/bounds_animator.cc index 4ea34b0..6dbd105 100644 --- a/views/animation/bounds_animator.cc +++ b/views/animation/bounds_animator.cc @@ -5,106 +5,229 @@ #include "views/animation/bounds_animator.h" #include "app/slide_animation.h" -#include "base/compiler_specific.h" +#include "base/scoped_ptr.h" #include "views/view.h" +// Duration in milliseconds for animations. +static const int kAnimationDuration = 200; + namespace views { -BoundsAnimator::BoundsAnimator() - : ALLOW_THIS_IN_INITIALIZER_LIST(animation_(new SlideAnimation(this))), - is_slide_(true) { +BoundsAnimator::BoundsAnimator(View* parent) + : parent_(parent), + observer_(NULL), + container_(new AnimationContainer()) { + container_->set_observer(this); } BoundsAnimator::~BoundsAnimator() { - data_.clear(); - animation_->set_delegate(NULL); - animation_.reset(); + // Reset the delegate so that we don't attempt to notify our observer from + // the destructor. + container_->set_observer(NULL); + + // Delete all the animations, but don't remove any child views. We assume the + // view owns us and is going to be deleted anyway. + for (ViewToDataMap::iterator i = data_.begin(); i != data_.end(); ++i) + delete i->second.animation; } void BoundsAnimator::AnimateViewTo(View* view, const gfx::Rect& target, bool delete_when_done) { + DCHECK_EQ(view->GetParent(), parent_); + + scoped_ptr<Animation> current_animation; + + if (data_.find(view) != data_.end()) { + // Currently animating this view, blow away the current animation and + // we'll create another animation below. + // We delay deleting the view until the end so that we don't prematurely + // send out notification that we're done. + current_animation.reset(ResetAnimationForView(view)); + } else if (target == view->bounds()) { + // View is already at the target location, delete it if necessary. + if (delete_when_done) + delete view; + return; + } + Data& data = data_[view]; data.start_bounds = view->bounds(); data.target_bounds = target; + data.animation = CreateAnimation(); data.delete_when_done = delete_when_done; + + animation_to_view_[data.animation] = view; + + data.animation->Show(); +} + +void BoundsAnimator::SetAnimationForView(View* view, + SlideAnimation* animation) { + scoped_ptr<SlideAnimation> animation_wrapper(animation); + if (data_.find(view) == data_.end()) + return; + + // We delay deleting the animation until the end so that we don't prematurely + // send out notification that we're done. + scoped_ptr<Animation> old_animation(ResetAnimationForView(view)); + + data_[view].animation = animation_wrapper.release(); + animation_to_view_[animation] = view; + + animation->set_delegate(this); + animation->SetContainer(container_.get()); + animation->Show(); +} + +const SlideAnimation* BoundsAnimator::GetAnimationForView(View* view) { + return data_.find(view) == data_.end() ? NULL : data_[view].animation; +} + +void BoundsAnimator::SetAnimationDelegate(View* view, + AnimationDelegate* delegate, + bool delete_when_done) { + DCHECK(IsAnimating(view)); + data_[view].delegate = delegate; + data_[view].delete_delegate_when_done = delete_when_done; +} + +void BoundsAnimator::StopAnimatingView(View* view) { + if (data_.find(view) == data_.end()) + return; + + data_[view].animation->Stop(); } bool BoundsAnimator::IsAnimating(View* view) const { return data_.find(view) != data_.end(); } -void BoundsAnimator::Start() { - // Unset the delegate so that we don't attempt to cleanup if the animation is - // running and we cancel it. - animation_->set_delegate(NULL); +bool BoundsAnimator::IsAnimating() const { + return !data_.empty(); +} - animation_->Stop(); +void BoundsAnimator::Cancel() { + if (data_.empty()) + return; - if (is_slide_) { - // TODO(sky): this is yucky, need a better way to express this. - static_cast<SlideAnimation*>(animation_.get())->Hide(); - static_cast<SlideAnimation*>(animation_.get())->Reset(); - static_cast<SlideAnimation*>(animation_.get())->Show(); - } else { - animation_->Start(); - } + while (!data_.empty()) + data_.begin()->second.animation->Stop(); - animation_->set_delegate(this); + // Invoke AnimationContainerProgressed to force a repaint and notify delegate. + AnimationContainerProgressed(container_.get()); } -void BoundsAnimator::Stop() { - animation_->set_delegate(NULL); - animation_->Stop(); - animation_->set_delegate(this); +SlideAnimation* BoundsAnimator::CreateAnimation() { + SlideAnimation* animation = new SlideAnimation(this); + animation->SetContainer(container_.get()); + animation->SetSlideDuration(kAnimationDuration); + animation->SetTweenType(SlideAnimation::EASE_OUT); + return animation; +} + +void BoundsAnimator::RemoveFromMapsAndDelete(View* view) { + DCHECK(data_.count(view) > 0); + + Data& data = data_[view]; + animation_to_view_.erase(data.animation); + if (data.delete_when_done) + delete view; + data_.erase(view); +} - DeleteViews(); - data_.clear(); +void BoundsAnimator::CleanupData(Data* data) { + if (data->delete_delegate_when_done) { + delete static_cast<OwnedAnimationDelegate*>(data->delegate); + data->delegate = NULL; + } + + delete data->animation; + data->animation = NULL; } -void BoundsAnimator::SetAnimation(Animation* animation, bool is_slide) { - animation_.reset(animation); - is_slide_ = is_slide; +Animation* BoundsAnimator::ResetAnimationForView(View* view) { + if (data_.find(view) == data_.end()) + return NULL; + + Animation* old_animation = data_[view].animation; + animation_to_view_.erase(old_animation); + data_[view].animation = NULL; + // Reset the delegate so that we don't attempt any processing when the + // animation calls us back. + old_animation->set_delegate(NULL); + return old_animation; } void BoundsAnimator::AnimationProgressed(const Animation* animation) { - gfx::Rect repaint_bounds; - - for (ViewToDataMap::const_iterator i = data_.begin(); i != data_.end(); ++i) { - View* view = i->first; - gfx::Rect new_bounds = - animation_->CurrentValueBetween(i->second.start_bounds, - i->second.target_bounds); - if (new_bounds != view->bounds()) { - gfx::Rect total_bounds = new_bounds.Union(view->bounds()); - if (repaint_bounds.IsEmpty()) - repaint_bounds = total_bounds; - else - repaint_bounds = repaint_bounds.Union(total_bounds); - view->SetBounds(new_bounds); - } + DCHECK(animation_to_view_.find(animation) != animation_to_view_.end()); + + View* view = animation_to_view_[animation]; + const Data& data = data_[view]; + gfx::Rect new_bounds = + animation->CurrentValueBetween(data.start_bounds, data.target_bounds); + if (new_bounds != view->bounds()) { + gfx::Rect total_bounds = new_bounds.Union(view->bounds()); + + // Build up the region to repaint in repaint_bounds_. We'll do the repaint + // when all animations complete (in AnimationContainerProgressed). + if (repaint_bounds_.IsEmpty()) + repaint_bounds_ = total_bounds; + else + repaint_bounds_ = repaint_bounds_.Union(total_bounds); + + view->SetBounds(new_bounds); } - if (!data_.empty() && !repaint_bounds.IsEmpty()) - data_.begin()->first->GetParent()->SchedulePaint(repaint_bounds, false); + if (data_[view].delegate) + data_[view].delegate->AnimationProgressed(animation); } void BoundsAnimator::AnimationEnded(const Animation* animation) { - DeleteViews(); + View* view = animation_to_view_[animation]; + AnimationDelegate* delegate = data_[view].delegate; + + // Make a copy of the data as Remove empties out the maps. + Data data = data_[view]; + + RemoveFromMapsAndDelete(view); + + if (delegate) + delegate->AnimationEnded(animation); + + CleanupData(&data); } void BoundsAnimator::AnimationCanceled(const Animation* animation) { + View* view = animation_to_view_[animation]; + AnimationDelegate* delegate = data_[view].delegate; + + // Make a copy of the data as Remove empties out the maps. + Data data = data_[view]; + + RemoveFromMapsAndDelete(view); + + if (delegate) + delegate->AnimationCanceled(animation); + + CleanupData(&data); } -void BoundsAnimator::DeleteViews() { - for (ViewToDataMap::iterator i = data_.begin(); i != data_.end(); ++i) { - if (i->second.delete_when_done) { - View* view = i->first; - view->GetParent()->RemoveChildView(view); - delete view; - } +void BoundsAnimator::AnimationContainerProgressed( + AnimationContainer* container) { + if (!repaint_bounds_.IsEmpty()) { + parent_->SchedulePaint(repaint_bounds_, false); + repaint_bounds_.SetRect(0, 0, 0, 0); + } + + if (observer_ && !IsAnimating()) { + // Notify here rather than from AnimationXXX to avoid deleting the animation + // while the animaion is calling us. + observer_->OnBoundsAnimatorDone(this); } - data_.clear(); +} + +void BoundsAnimator::AnimationContainerEmpty(AnimationContainer* container) { } } // namespace views diff --git a/views/animation/bounds_animator.h b/views/animation/bounds_animator.h index c996a98..ab2fde9 100644 --- a/views/animation/bounds_animator.h +++ b/views/animation/bounds_animator.h @@ -8,77 +8,161 @@ #include <map> #include "app/animation.h" -#include "base/scoped_ptr.h" +#include "app/animation_container.h" #include "gfx/rect.h" +class SlideAnimation; + namespace views { +class BoundsAnimator; class View; +class BoundsAnimatorObserver { + public: + // Invoked when all animations are complete. + virtual void OnBoundsAnimatorDone(BoundsAnimator* animator) = 0; +}; + // Bounds animator is responsible for animating the bounds of a view from the // the views current location and size to a target position and size. To use // BoundsAnimator invoke AnimateViewTo for the set of views you want to -// animate, followed by Start to start the animation. -class BoundsAnimator : public AnimationDelegate { +// animate. +// +// BoundsAnimator internally creates an animation for each view. If you need +// a specific animation invoke SetAnimationForView after invoking AnimateViewTo. +// You can attach an AnimationDelegate to the individual animation for a view +// by way of SetAnimationDelegate. Additionally you can attach an observer to +// the BoundsAnimator that is notified when all animations are complete. +class BoundsAnimator : public AnimationDelegate, + public AnimationContainer::Observer { public: - BoundsAnimator(); + // If |delete_when_done| is set to true in |SetAnimationDelegate| the + // |AnimationDelegate| must subclass this class. + class OwnedAnimationDelegate : public AnimationDelegate { + public: + virtual ~OwnedAnimationDelegate() {} + }; + + explicit BoundsAnimator(View* view); ~BoundsAnimator(); - // Schedules |view| to animate from it's current bounds to |target|. If + // Starts animating |view| from its current bounds to |target|. If // |delete_when_done| is true the view is deleted when the animation - // completes. Invoke Start to start the animation. + // completes. If there is already an animation running for the view it's + // stopped and a new one started. void AnimateViewTo(View* view, const gfx::Rect& target, bool delete_when_done); + // Sets the animation for the specified view. BoundsAnimator takes ownership + // of the specified animation. + void SetAnimationForView(View* view, SlideAnimation* animation); + + // Returns the animation for the specified view. BoundsAnimator owns the + // returned Animation. + const SlideAnimation* GetAnimationForView(View* view); + + // Stops animating the specified view. If the view was scheduled for deletion + // it is deleted. + void StopAnimatingView(View* view); + + // Sets the delegate for the animation created for the specified view. If + // |delete_when_done| is true the |delegate| is deleted when done and + // |delegate| must subclass OwnedAnimationDelegate. + void SetAnimationDelegate(View* view, + AnimationDelegate* delegate, + bool delete_when_done); + // Returns true if BoundsAnimator is animating the bounds of |view|. bool IsAnimating(View* view) const; - // Starts the animation. - void Start(); + // Returns true if BoundsAnimator is animating any view. + bool IsAnimating() const; - // Stops the animation. - void Stop(); + // Cancels all animations, leaving the views at their current location and + // size. Any views marked for deletion are deleted. + void Cancel(); - // Sets the animation to use when animating changes. BoundsAnimator takes - // ownership of |animation|. Set |is_slide| to true if |animation| is a - // SlideAnimation. - void SetAnimation(Animation* animation, bool is_slide); - - // AnimationDelegate overrides. - virtual void AnimationProgressed(const Animation* animation); - virtual void AnimationEnded(const Animation* animation); - virtual void AnimationCanceled(const Animation* animation); + void set_observer(BoundsAnimatorObserver* observer) { + observer_ = observer; + } private: // Tracks data about the view being animated. struct Data { - Data() : delete_when_done(false) {} + Data() + : delete_when_done(false), + delete_delegate_when_done(false), + animation(NULL), + delegate(NULL) {} // Should the view be deleted when done? bool delete_when_done; + // If true the delegate is deleted when done. + bool delete_delegate_when_done; + // The initial bounds. gfx::Rect start_bounds; // Target bounds. gfx::Rect target_bounds; + + // The animation. We own this. + SlideAnimation* animation; + + // Additional delegate for the animation, may be null. + AnimationDelegate* delegate; }; typedef std::map<View*, Data> ViewToDataMap; - // Empties data_, deleting any views that have been marked as needing to be - // deleted. - void DeleteViews(); + typedef std::map<const Animation*, View*> AnimationToViewMap; + + // Creates the animation to use for animating views. + SlideAnimation* CreateAnimation(); + + // Removes references to |view| and its animation as well as deleting |view| + // (if necessary). This does NOT delete the animation or delegate. + void RemoveFromMapsAndDelete(View* view); + + // Does the necessary cleanup for |data|. + void CleanupData(Data* data); + + // Used when changing the animation for a view. This resets the maps for + // the animation used by view and returns the current animation. Ownership + // of the returned animation passes to the caller. + Animation* ResetAnimationForView(View* view); + + // AnimationDelegate overrides. + virtual void AnimationProgressed(const Animation* animation); + virtual void AnimationEnded(const Animation* animation); + virtual void AnimationCanceled(const Animation* animation); + + // AnimationContainer::Observer overrides. + virtual void AnimationContainerProgressed(AnimationContainer* container); + virtual void AnimationContainerEmpty(AnimationContainer* container); + + // Parent of all views being animated. + View* parent_; + + BoundsAnimatorObserver* observer_; + + // All animations we create up with the same container. + scoped_refptr<AnimationContainer> container_; - // Mapes from view being animated to info about the view. + // Maps from view being animated to info about the view. ViewToDataMap data_; - // The animation. - scoped_ptr<Animation> animation_; + // Makes from animation to view. + AnimationToViewMap animation_to_view_; - // Is |animation_| a SlideAnimation? - bool is_slide_; + // As the animations we created update (AnimationProgressed is invoked) this + // is updated. When all the animations have completed for a given tick of + // the timer (AnimationContainerProgressed is invoked) the parent_ is asked + // to repaint these bounds. + gfx::Rect repaint_bounds_; DISALLOW_COPY_AND_ASSIGN(BoundsAnimator); }; |