summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/animation.cc97
-rw-r--r--app/animation.h34
-rw-r--r--app/animation_container.cc93
-rw-r--r--app/animation_container.h92
-rw-r--r--app/animation_container_unittest.cc120
-rw-r--r--app/animation_unittest.cc39
-rw-r--r--app/app.gyp2
-rw-r--r--app/app_base.gypi2
-rw-r--r--app/slide_animation.h6
-rw-r--r--app/test_animation_delegate.h47
-rw-r--r--app/throb_animation.cc5
-rw-r--r--app/throb_animation.h7
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();