From 220705c2b06962462e471b28ad85c76c0f9c3080 Mon Sep 17 00:00:00 2001 From: "ben@chromium.org" Date: Tue, 5 May 2009 04:52:11 +0000 Subject: Move *Animation to app/ http://crbug.com/11387 Review URL: http://codereview.chromium.org/109001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15275 0039d316-1c4b-4281-b951-d872f2087c98 --- app/animation.cc | 124 +++++++++++++++++++++++++++++++++++++++++++++ app/animation.h | 122 ++++++++++++++++++++++++++++++++++++++++++++ app/animation_unittest.cc | 102 +++++++++++++++++++++++++++++++++++++ app/app.vcproj | 24 +++++++++ app/slide_animation.cc | 125 ++++++++++++++++++++++++++++++++++++++++++++++ app/slide_animation.h | 102 +++++++++++++++++++++++++++++++++++++ app/throb_animation.cc | 68 +++++++++++++++++++++++++ app/throb_animation.h | 59 ++++++++++++++++++++++ 8 files changed, 726 insertions(+) create mode 100644 app/animation.cc create mode 100644 app/animation.h create mode 100644 app/animation_unittest.cc create mode 100644 app/slide_animation.cc create mode 100644 app/slide_animation.h create mode 100644 app/throb_animation.cc create mode 100644 app/throb_animation.h (limited to 'app') diff --git a/app/animation.cc b/app/animation.cc new file mode 100644 index 0000000..6a4e7b7 --- /dev/null +++ b/app/animation.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2006-2008 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" + +using base::TimeDelta; + +Animation::Animation(int frame_rate, + AnimationDelegate* delegate) + : animating_(false), + frame_rate_(frame_rate), + timer_interval_(CalculateInterval(frame_rate)), + duration_(0), + iteration_count_(0), + current_iteration_(0), + state_(0.0), + delegate_(delegate) { +} + +Animation::Animation(int duration, + int frame_rate, + AnimationDelegate* delegate) + : animating_(false), + frame_rate_(frame_rate), + timer_interval_(CalculateInterval(frame_rate)), + duration_(0), + iteration_count_(0), + current_iteration_(0), + state_(0.0), + delegate_(delegate) { + + SetDuration(duration); +} + +Animation::~Animation() { +} + +void Animation::Reset() { + current_iteration_ = 0; +} + +double Animation::GetCurrentValue() const { + // Default is linear relationship, subclass to adapt. + return state_; +} + +void Animation::Start() { + if (!animating_) { + timer_.Start(TimeDelta::FromMilliseconds(timer_interval_), this, + &Animation::Run); + + animating_ = true; + if (delegate_) + delegate_->AnimationStarted(this); + } +} + +void Animation::Stop() { + if (animating_) { + timer_.Stop(); + + animating_ = false; + if (delegate_) { + if (state_ >= 1.0) + delegate_->AnimationEnded(this); + else + delegate_->AnimationCanceled(this); + } + } +} + +void Animation::End() { + if (animating_) { + timer_.Stop(); + + animating_ = false; + AnimateToState(1.0); + if (delegate_) + delegate_->AnimationEnded(this); + } +} + +bool Animation::IsAnimating() { + return animating_; +} + +void Animation::SetDuration(int duration) { + duration_ = duration; + if (duration_ < timer_interval_) + duration_ = timer_interval_; + iteration_count_ = duration_ / timer_interval_; + + // Changing the number of iterations forces us to reset the + // animation to the first iteration. + current_iteration_ = 0; +} + +void Animation::Step() { + state_ = static_cast(++current_iteration_) / iteration_count_; + + if (state_ >= 1.0) + state_ = 1.0; + + AnimateToState(state_); + if (delegate_) + delegate_->AnimationProgressed(this); + + if (state_ == 1.0) + Stop(); +} + +void Animation::Run() { + Step(); +} + +int Animation::CalculateInterval(int frame_rate) { + int timer_interval = 1000 / frame_rate; + if (timer_interval < 10) + timer_interval = 10; + return timer_interval; +} diff --git a/app/animation.h b/app/animation.h new file mode 100644 index 0000000..58a2c67 --- /dev/null +++ b/app/animation.h @@ -0,0 +1,122 @@ +// Copyright (c) 2006-2008 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 + +#ifndef APP_ANIMATION_H_ +#define APP_ANIMATION_H_ + +#include "base/timer.h" + +class Animation; + +// AnimationDelegate +// +// Implement this interface when you want to receive notifications about the +// state of an animation. +// +class AnimationDelegate { + public: + // Called when an animation has started. + virtual void AnimationStarted(const Animation* animation) { + } + + // Called when an animation has completed. + virtual void AnimationEnded(const Animation* animation) { + } + + // Called when an animation has progressed. + virtual void AnimationProgressed(const Animation* animation) { + } + + // Called when an animation has been canceled. + virtual void AnimationCanceled(const Animation* animation) { + } +}; + +// Animation +// +// This class provides a basic implementation of an object that uses a timer +// to increment its state over the specified time and frame-rate. To +// actually do something useful with this you need to subclass it and override +// AnimateToState and optionally GetCurrentValue to update your state. +// +// The Animation notifies a delegate when events of interest occur. +// +// The practice is to instantiate a subclass and call Init and any other +// initialization specific to the subclass, and then call |Start|. The +// animation uses the current thread's message loop. +// +class Animation { + public: + // Initializes everything except the duration. + // + // Caller must make sure to call SetDuration() if they use this + // constructor; it is preferable to use the full one, but sometimes + // duration can change between calls to Start() and we need to + // expose this interface. + Animation(int frame_rate, AnimationDelegate* delegate); + + // Initializes all fields. + 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; + + // Gets the value for the current state, according to the animation + // curve in use. This class provides only for a linear relationship, + // however subclasses can override this to provide others. + virtual double GetCurrentValue() const; + + // Start the animation. + void Start(); + + // Stop the animation. + void Stop(); + + // Skip to the end of the current animation. + void End(); + + // Return whether this animation is animating. + bool IsAnimating(); + + // Changes the length of the animation. This resets the current + // state of the animation to the beginning. + void SetDuration(int duration); + + protected: + // Overriddable, called by Run. + virtual void Step(); + + // Calculates the timer interval from the constructor list. + int CalculateInterval(int frame_rate); + + // Whether or not we are currently animating. + bool animating_; + + int frame_rate_; + int timer_interval_; + int duration_; + + // For determining state. + int iteration_count_; + int current_iteration_; + double state_; + + AnimationDelegate* delegate_; + + base::RepeatingTimer timer_; + + private: + // Called when the animation's timer expires, calls Step. + void Run(); + + DISALLOW_COPY_AND_ASSIGN(Animation); +}; + +#endif // APP_ANIMATION_H_ diff --git a/app/animation_unittest.cc b/app/animation_unittest.cc new file mode 100644 index 0000000..a7450e3 --- /dev/null +++ b/app/animation_unittest.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2006-2008 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 "testing/gtest/include/gtest/gtest.h" + +using namespace std; + +class AnimationTest: public testing::Test { + private: + MessageLoopForUI message_loop_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// RunAnimation + +class RunAnimation : public Animation { + public: + RunAnimation(int frame_rate, AnimationDelegate* delegate) + : Animation(frame_rate, delegate) { + } + + virtual void AnimateToState(double state) { + EXPECT_LE(0.0, state); + EXPECT_GE(1.0, state); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// CancelAnimation + +class CancelAnimation : public Animation { + public: + CancelAnimation(int duration, int frame_rate, AnimationDelegate* delegate) + : Animation(duration, frame_rate, delegate) { + } + + virtual void AnimateToState(double state) { + if (state >= 0.5) + Stop(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// 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); + a1.SetDuration(2000); + a1.Start(); + MessageLoop::current()->Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_FALSE(ad.canceled()); +} + +TEST_F(AnimationTest, CancelCase) { + TestAnimationDelegate ad; + CancelAnimation a2(2000, 150, &ad); + a2.Start(); + MessageLoop::current()->Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_TRUE(ad.canceled()); +} diff --git a/app/app.vcproj b/app/app.vcproj index 22c1e1f..02d76cd 100644 --- a/app/app.vcproj +++ b/app/app.vcproj @@ -122,6 +122,14 @@ + + + + @@ -133,6 +141,22 @@ RelativePath=".\resource_bundle_win.cc" > + + + + + + + + diff --git a/app/slide_animation.cc b/app/slide_animation.cc new file mode 100644 index 0000000..ca468d2 --- /dev/null +++ b/app/slide_animation.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2006-2008 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/slide_animation.h" + +#include + +// How many frames per second to target. +static const int kDefaultFramerateHz = 50; + +// How long animations should take by default. +static const int kDefaultDurationMs = 120; + +SlideAnimation::SlideAnimation(AnimationDelegate* target) + : Animation(kDefaultFramerateHz, target), + target_(target), + tween_type_(EASE_OUT), + showing_(false), + value_start_(0), + value_end_(0), + value_current_(0), + slide_duration_(kDefaultDurationMs) { +} + +SlideAnimation::~SlideAnimation() { +} + +void SlideAnimation::Reset() { + Reset(0); +} + +void SlideAnimation::Reset(double value) { + Stop(); + showing_ = static_cast(value == 1); + value_current_ = value; +} + +void SlideAnimation::Show() { + // If we're already showing (or fully shown), we have nothing to do. + if (showing_) + return; + + showing_ = true; + value_start_ = value_current_; + value_end_ = 1.0; + + // Make sure we actually have something to do. + if (slide_duration_ == 0) { + AnimateToState(1.0); // Skip to the end of the animation. + return; + } else if (value_current_ == value_end_) { + return; + } + + // This will also reset the currently-occuring animation. + SetDuration(static_cast(slide_duration_ * (1 - value_current_))); + Start(); +} + +void SlideAnimation::Hide() { + // If we're already hiding (or hidden), we have nothing to do. + if (!showing_) + return; + + showing_ = false; + value_start_ = value_current_; + value_end_ = 0.0; + + // Make sure we actually have something to do. + if (slide_duration_ == 0) { + AnimateToState(0.0); // Skip to the end of the animation. + return; + } else if (value_current_ == value_end_) { + return; + } + + // This will also reset the currently-occuring animation. + SetDuration(static_cast(slide_duration_ * value_current_)); + Start(); +} + +void SlideAnimation::AnimateToState(double state) { + if (state > 1.0) + state = 1.0; + + // Make our animation ease-out. + switch (tween_type_) { + case EASE_IN: + state = pow(state, 2); + break; + case EASE_IN_OUT: + if (state < 0.5) + state = pow(state * 2, 2) / 2.0; + else + state = 1.0 - (pow((state - 1.0) * 2, 2) / 2.0); + break; + case FAST_IN_OUT: + state = (pow(state - 0.5, 3) + 0.125) / 0.25; + break; + case NONE: + // state remains linear. + break; + case EASE_OUT_SNAP: + state = 0.95 * (1.0 - pow(1.0 - state, 2)); + break; + case EASE_OUT: + default: + state = 1.0 - pow(1.0 - state, 2); + break; + } + + value_current_ = value_start_ + (value_end_ - value_start_) * state; + + // Implement snapping. + if (tween_type_ == EASE_OUT_SNAP && fabs(value_current_ - value_end_) <= 0.06) + value_current_ = value_end_; + + // Correct for any overshoot (while state may be capped at 1.0, let's not + // take any rounding error chances. + if ((value_end_ >= value_start_ && value_current_ > value_end_) || + (value_end_ < value_start_ && value_current_ < value_end_)) { + value_current_ = value_end_; + } +} diff --git a/app/slide_animation.h b/app/slide_animation.h new file mode 100644 index 0000000..4bbcd2e --- /dev/null +++ b/app/slide_animation.h @@ -0,0 +1,102 @@ +// Copyright (c) 2006-2008 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_SLIDE_ANIMATION_H_ +#define APP_SLIDE_ANIMATION_H_ + +#include "app/animation.h" + +// Slide Animation +// +// Used for reversible animations and as a general helper class. Typical usage: +// +// #include "app/slide_animation.h" +// +// class MyClass : public AnimationDelegate { +// public: +// MyClass() { +// animation_.reset(new SlideAnimation(this)); +// animation_->SetSlideDuration(500); +// } +// void OnMouseOver() { +// animation_->Show(); +// } +// void OnMouseOut() { +// animation_->Hide(); +// } +// void AnimationProgressed(const Animation* animation) { +// if (animation == animation_.get()) { +// Layout(); +// SchedulePaint(); +// } else if (animation == other_animation_.get()) { +// ... +// } +// } +// void Layout() { +// if (animation_->IsAnimating()) { +// hover_image_.SetOpacity(animation_->GetCurrentValue()); +// } +// } +// private: +// scoped_ptr animation_; +// } +class SlideAnimation : public Animation { + public: + explicit SlideAnimation(AnimationDelegate* target); + virtual ~SlideAnimation(); + + enum TweenType { + NONE, // Default linear. + EASE_OUT, // Fast in, slow out. + EASE_IN, // Slow in, fast out. + EASE_IN_OUT, // Slow in and out, fast in the middle. + FAST_IN_OUT, // Fast in and out, slow in the middle. + EASE_OUT_SNAP, // Fast in, slow out, snap to final value. + }; + + // Set the animation back to the 0 state. + virtual void Reset(); + virtual void Reset(double value); + + // Begin a showing animation or reverse a hiding animation in progress. + virtual void Show(); + + // Begin a hiding animation or reverse a showing animation in progress. + virtual void Hide(); + + // Sets the time a slide will take. Note that this isn't actually + // the amount of time an animation will take as the current value of + // the slide is considered. + virtual void SetSlideDuration(int duration) { slide_duration_ = duration; } + int GetSlideDuration() const { return slide_duration_; } + void SetTweenType(TweenType tween_type) { tween_type_ = tween_type; } + + double GetCurrentValue() const { return value_current_; } + bool IsShowing() const { return showing_; } + + private: + // Overridden from Animation. + void AnimateToState(double state); + + AnimationDelegate* target_; + + TweenType tween_type_; + + // Used to determine which way the animation is going. + bool showing_; + + // Animation values. These are a layer on top of Animation::state_ to + // provide the reversability. + double value_start_; + double value_end_; + double value_current_; + + // How long a hover in/out animation will last for. This defaults to + // kHoverFadeDurationMS, but can be overridden with SetDuration. + int slide_duration_; + + DISALLOW_COPY_AND_ASSIGN(SlideAnimation); +}; + +#endif // APP_SLIDE_ANIMATION_H_ diff --git a/app/throb_animation.cc b/app/throb_animation.cc new file mode 100644 index 0000000..28b5763 --- /dev/null +++ b/app/throb_animation.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2006-2008 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/throb_animation.h" + +static const int kDefaultThrobDurationMS = 400; + +ThrobAnimation::ThrobAnimation(AnimationDelegate* target) + : SlideAnimation(target), + slide_duration_(GetSlideDuration()), + throb_duration_(kDefaultThrobDurationMS), + cycles_remaining_(0), + throbbing_(false) { +} + +void ThrobAnimation::StartThrobbing(int cycles_til_stop) { + cycles_remaining_ = cycles_til_stop; + throbbing_ = true; + SlideAnimation::SetSlideDuration(throb_duration_); + if (IsAnimating()) + return; // We're already running, we'll cycle when current loop finishes. + + if (IsShowing()) + SlideAnimation::Hide(); + else + SlideAnimation::Show(); + cycles_remaining_ = cycles_til_stop; +} + +void ThrobAnimation::Reset() { + ResetForSlide(); + SlideAnimation::Reset(); +} + +void ThrobAnimation::Show() { + ResetForSlide(); + SlideAnimation::Show(); +} + +void ThrobAnimation::Hide() { + ResetForSlide(); + SlideAnimation::Hide(); +} + +void ThrobAnimation::Step() { + Animation::Step(); + 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. + cycles_remaining_--; + if (IsShowing()) { + // We want to stop hidden, hence this doesn't check cycles_remaining_. + SlideAnimation::Hide(); + } else if (cycles_remaining_ > 0) { + SlideAnimation::Show(); + } else { + // We're done throbbing. + throbbing_ = false; + } + } +} + +void ThrobAnimation::ResetForSlide() { + SlideAnimation::SetSlideDuration(slide_duration_); + cycles_remaining_ = 0; + throbbing_ = false; +} diff --git a/app/throb_animation.h b/app/throb_animation.h new file mode 100644 index 0000000..637e8e0 --- /dev/null +++ b/app/throb_animation.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006-2008 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_THROB_ANIMATION_H_ +#define APP_THROB_ANIMATION_H_ + +#include "app/slide_animation.h" + +// A subclass of SlideAnimation that can continually slide. All of the Animation +// methods behave like that of SlideAnimation: transition to the next state. +// The StartThrobbing method causes the ThrobAnimation to cycle between hidden +// and shown for a set number of cycles. +// +// A ThrobAnimation has two durations: the duration used when behavior like +// a SlideAnimation, and the duration used when throbbing. +class ThrobAnimation : public SlideAnimation { + public: + explicit ThrobAnimation(AnimationDelegate* target); + virtual ~ThrobAnimation() {} + + // Starts throbbing. cycles_til_stop gives the number of cycles to do before + // stopping. + void StartThrobbing(int cycles_til_stop); + + // Sets the duration of the slide animation when throbbing. + void SetThrobDuration(int duration) { throb_duration_ = duration; } + + // Overridden to reset to the slide duration. + virtual void Reset(); + 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; } + + private: + // Resets state such that we behave like SlideAnimation. + void ResetForSlide(); + + // Duration of the slide animation. + int slide_duration_; + + // Duration of the slide animation when throbbing. + int throb_duration_; + + // If throbbing, this is the number of cycles left. + int cycles_remaining_; + + // Are we throbbing? + bool throbbing_; + + DISALLOW_COPY_AND_ASSIGN(ThrobAnimation); +}; + +#endif // APP_THROB_ANIMATION_H_ -- cgit v1.1