diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-07 17:33:39 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-07 17:33:39 +0000 |
commit | f676780687464428e340d008a0d1ca13d9944628 (patch) | |
tree | 489e8bd8188e31a7f6c53f15e8d1d9c6ba36d023 /ui | |
parent | 3b65bfd55c56cf8a6db025087d937494f49dc15f (diff) | |
download | chromium_src-f676780687464428e340d008a0d1ca13d9944628.zip chromium_src-f676780687464428e340d008a0d1ca13d9944628.tar.gz chromium_src-f676780687464428e340d008a0d1ca13d9944628.tar.bz2 |
Move animation code to new ui/base/animation directory.
BUG=none
TEST=none
TBR=brettw
Review URL: http://codereview.chromium.org/6154001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@70743 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
26 files changed, 2023 insertions, 0 deletions
diff --git a/ui/base/animation/animation.cc b/ui/base/animation/animation.cc new file mode 100644 index 0000000..1824774 --- /dev/null +++ b/ui/base/animation/animation.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2011 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 "ui/base/animation/animation.h" + +#include "gfx/rect.h" +#include "ui/base/animation/animation_container.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/base/animation/tween.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace ui { + +Animation::Animation(base::TimeDelta timer_interval) + : timer_interval_(timer_interval), + is_animating_(false), + delegate_(NULL) { +} + +Animation::~Animation() { + // Don't send out notification from the destructor. Chances are the delegate + // owns us and is being deleted as well. + if (is_animating_) + container_->Stop(this); +} + +void Animation::Start() { + if (is_animating_) + return; + + if (!container_.get()) + container_ = new AnimationContainer(); + + is_animating_ = true; + + container_->Start(this); + + AnimationStarted(); +} + +void Animation::Stop() { + if (!is_animating_) + return; + + is_animating_ = false; + + // Notify the container first as the delegate may delete us. + container_->Stop(this); + + AnimationStopped(); + + if (delegate_) { + if (ShouldSendCanceledFromStop()) + delegate_->AnimationCanceled(this); + else + delegate_->AnimationEnded(this); + } +} + +double Animation::CurrentValueBetween(double start, double target) const { + return Tween::ValueBetween(GetCurrentValue(), start, target); +} + +int Animation::CurrentValueBetween(int start, int target) const { + return Tween::ValueBetween(GetCurrentValue(), start, target); +} + +gfx::Rect Animation::CurrentValueBetween(const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds) const { + return Tween::ValueBetween(GetCurrentValue(), start_bounds, target_bounds); +} + +void Animation::SetContainer(AnimationContainer* container) { + if (container == container_.get()) + return; + + if (is_animating_) + container_->Stop(this); + + if (container) + container_ = container; + else + container_ = new AnimationContainer(); + + if (is_animating_) + container_->Start(this); +} + +// static +bool Animation::ShouldRenderRichAnimation() { +#if defined(OS_WIN) + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + BOOL result; + // Get "Turn off all unnecessary animations" value. + if (::SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &result, 0)) { + // There seems to be a typo in the MSDN document (as of May 2009): + // http://msdn.microsoft.com/en-us/library/ms724947(VS.85).aspx + // The document states that the result is TRUE when animations are + // _disabled_, but in fact, it is TRUE when they are _enabled_. + return !!result; + } + } + return !::GetSystemMetrics(SM_REMOTESESSION); +#endif + return true; +} + +bool Animation::ShouldSendCanceledFromStop() { + return false; +} + +void Animation::SetStartTime(base::TimeTicks start_time) { + start_time_ = start_time; +} + +base::TimeDelta Animation::GetTimerInterval() const { + return timer_interval_; +} + +} // namespace ui diff --git a/ui/base/animation/animation.h b/ui/base/animation/animation.h new file mode 100644 index 0000000..bb4be5a --- /dev/null +++ b/ui/base/animation/animation.h @@ -0,0 +1,109 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_ANIMATION_H_ +#define UI_BASE_ANIMATION_ANIMATION_H_ +#pragma once + +#include "base/ref_counted.h" +#include "base/time.h" +#include "ui/base/animation/animation_container_element.h" + +namespace gfx { +class Rect; +} + +namespace ui { + +class AnimationContainer; +class AnimationDelegate; + +// Base class used in implementing animations. You only need use this class if +// you're implementing a new animation type, otherwise you'll likely want one of +// LinearAnimation, SlideAnimation, ThrobAnimation or MultiAnimation. +// +// To subclass override Step, which is invoked as the animation progresses and +// GetCurrentValue() to return the value appropriate to the animation. +class Animation : public AnimationContainerElement { + public: + explicit Animation(base::TimeDelta timer_interval); + virtual ~Animation(); + + // Starts the animation. Does nothing if the animation is already running. + void Start(); + + // Stops the animation. Does nothing if the animation is not running. + void Stop(); + + // Gets the value for the current state, according to the animation + // curve in use. + virtual double GetCurrentValue() const = 0; + + // Convenience for returning a value between |start| and |target| based on + // the current value. This is (target - start) * GetCurrentValue() + start. + double CurrentValueBetween(double start, double target) const; + int CurrentValueBetween(int start, int target) const; + gfx::Rect CurrentValueBetween(const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds) const; + + // 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); + + bool is_animating() const { return is_animating_; } + + base::TimeDelta timer_interval() const { return timer_interval_; } + + // Returns true if rich animations should be rendered. + // Looks at session type (e.g. remote desktop) and accessibility settings + // to give guidance for heavy animations such as "start download" arrow. + static bool ShouldRenderRichAnimation(); + + protected: + // Invoked from Start to allow subclasses to prepare for the animation. + virtual void AnimationStarted() {} + + // Invoked from Stop after we're removed from the container but before the + // delegate has been invoked. + virtual void AnimationStopped() {} + + // Invoked from stop to determine if cancel should be invoked. If this returns + // true the delegate is notified the animation was canceled, otherwise the + // delegate is notified the animation stopped. + virtual bool ShouldSendCanceledFromStop(); + + AnimationContainer* container() { return container_.get(); } + base::TimeTicks start_time() const { return start_time_; } + AnimationDelegate* delegate() { return delegate_; } + + // AnimationContainer::Element overrides + virtual void SetStartTime(base::TimeTicks start_time); + virtual void Step(base::TimeTicks time_now) = 0; + virtual base::TimeDelta GetTimerInterval() const; + + private: + // Interval for the animation. + const base::TimeDelta timer_interval_; + + // If true we're running. + bool is_animating_; + + // Our delegate; may be null. + AnimationDelegate* delegate_; + + // Container we're in. If non-null we're animating. + scoped_refptr<AnimationContainer> container_; + + // Time we started at. + base::TimeTicks start_time_; + + DISALLOW_COPY_AND_ASSIGN(Animation); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_ANIMATION_H_ diff --git a/ui/base/animation/animation_container.cc b/ui/base/animation/animation_container.cc new file mode 100644 index 0000000..1e316cd --- /dev/null +++ b/ui/base/animation/animation_container.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2011 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 "ui/base/animation/animation_container.h" + +#include "ui/base/animation/animation_container_element.h" +#include "ui/base/animation/animation_container_observer.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace ui { + +AnimationContainer::AnimationContainer() + : last_tick_time_(TimeTicks::Now()), + observer_(NULL) { +} + +AnimationContainer::~AnimationContainer() { + // The animations own us and stop themselves before being deleted. If + // elements_ is not empty, something is wrong. + DCHECK(elements_.empty()); +} + +void AnimationContainer::Start(AnimationContainerElement* element) { + DCHECK(elements_.count(element) == 0); // Start should only be invoked if the + // element isn't running. + + if (elements_.empty()) { + last_tick_time_ = TimeTicks::Now(); + SetMinTimerInterval(element->GetTimerInterval()); + } else if (element->GetTimerInterval() < min_timer_interval_) { + SetMinTimerInterval(element->GetTimerInterval()); + } + + element->SetStartTime(last_tick_time_); + elements_.insert(element); +} + +void AnimationContainer::Stop(AnimationContainerElement* element) { + DCHECK(elements_.count(element) > 0); // The element must be running. + + elements_.erase(element); + + if (elements_.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() { + // We notify the observer after updating all the elements. If all the elements + // are deleted as a result of updating then our ref count would go to zero and + // we would be deleted before we notify our observer. We add a reference to + // ourself here to make sure we're still valid after running all the elements. + scoped_refptr<AnimationContainer> this_ref(this); + + TimeTicks current_time = TimeTicks::Now(); + + last_tick_time_ = current_time; + + // Make a copy of the elements to iterate over so that if any elements are + // removed as part of invoking Step there aren't any problems. + Elements elements = elements_; + + for (Elements::const_iterator i = elements.begin(); + i != elements.end(); ++i) { + // Make sure the element is still valid. + if (elements_.find(*i) != elements_.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 the current element 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(!elements_.empty()); + + TimeDelta min; + Elements::const_iterator i = elements_.begin(); + min = (*i)->GetTimerInterval(); + for (++i; i != elements_.end(); ++i) { + if ((*i)->GetTimerInterval() < min) + min = (*i)->GetTimerInterval(); + } + return min; +} + +} // namespace ui diff --git a/ui/base/animation/animation_container.h b/ui/base/animation/animation_container.h new file mode 100644 index 0000000..337b421 --- /dev/null +++ b/ui/base/animation/animation_container.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_ANIMATION_CONTAINER_H_ +#define UI_BASE_ANIMATION_ANIMATION_CONTAINER_H_ +#pragma once + +#include <set> + +#include "base/ref_counted.h" +#include "base/time.h" +#include "base/timer.h" + +namespace ui { + +class AnimationContainerElement; +class AnimationContainerObserver; + +// 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: + AnimationContainer(); + + // Invoked by Animation when it needs to start. Starts the timer if necessary. + // NOTE: This is invoked by Animation for you, you shouldn't invoke this + // directly. + void Start(AnimationContainerElement* animation); + + // Invoked by Animation when it needs to stop. If there are no more animations + // running the timer stops. + // NOTE: This is invoked by Animation for you, you shouldn't invoke this + // directly. + void Stop(AnimationContainerElement* animation); + + void set_observer(AnimationContainerObserver* 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 !elements_.empty(); } + + private: + friend class base::RefCounted<AnimationContainer>; + + typedef std::set<AnimationContainerElement*> Elements; + + ~AnimationContainer(); + + // 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 elements (animations) being managed. + Elements elements_; + + // Minimum interval the timers run at. + base::TimeDelta min_timer_interval_; + + base::RepeatingTimer<AnimationContainer> timer_; + + AnimationContainerObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(AnimationContainer); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_ANIMATION_CONTAINER_H_ diff --git a/ui/base/animation/animation_container_element.h b/ui/base/animation/animation_container_element.h new file mode 100644 index 0000000..8ac63a5 --- /dev/null +++ b/ui/base/animation/animation_container_element.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ +#define UI_BASE_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ +#pragma once + +#include "base/time.h" + +namespace ui { + +// Interface for the elements the AnimationContainer contains. This is +// implemented by Animation. +class AnimationContainerElement { + public: + // Sets the start of the animation. This is invoked from + // AnimationContainer::Start. + virtual void SetStartTime(base::TimeTicks start_time) = 0; + + // Invoked when the animation is to progress. + virtual void Step(base::TimeTicks time_now) = 0; + + // Returns the time interval of the animation. If an Element needs to change + // this it should first invoke Stop, then Start. + virtual base::TimeDelta GetTimerInterval() const = 0; + + protected: + virtual ~AnimationContainerElement() {} +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_ANIMATION_CONTAINER_ELEMENT_H_ diff --git a/ui/base/animation/animation_container_observer.h b/ui/base/animation/animation_container_observer.h new file mode 100644 index 0000000..b01a476 --- /dev/null +++ b/ui/base/animation/animation_container_observer.h @@ -0,0 +1,31 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ +#define UI_BASE_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ +#pragma once + +namespace ui { + +class AnimationContainer; + +// The observer is notified after every update of the animations managed by +// the container. +class AnimationContainerObserver { + 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; + + protected: + virtual ~AnimationContainerObserver() {} +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_ANIMATION_CONTAINER_OBSERVER_H_ diff --git a/ui/base/animation/animation_container_unittest.cc b/ui/base/animation/animation_container_unittest.cc new file mode 100644 index 0000000..6345591 --- /dev/null +++ b/ui/base/animation/animation_container_unittest.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2011 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 "base/scoped_ptr.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/animation/animation_container.h" +#include "ui/base/animation/animation_container_observer.h" +#include "ui/base/animation/linear_animation.h" +#include "ui/base/animation/test_animation_delegate.h" + +using testing::AtLeast; + +namespace ui { + +namespace { + +class MockObserver : public AnimationContainerObserver { + public: + MockObserver() {} + + MOCK_METHOD1(AnimationContainerProgressed, void(AnimationContainer*)); + MOCK_METHOD1(AnimationContainerEmpty, void(AnimationContainer*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockObserver); +}; + +class TestAnimation : public LinearAnimation { + public: + explicit TestAnimation(AnimationDelegate* delegate) + : LinearAnimation(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 running. + EXPECT_FALSE(container->is_running()); + + container->set_observer(NULL); +} + +} // namespace ui diff --git a/ui/base/animation/animation_delegate.h b/ui/base/animation/animation_delegate.h new file mode 100644 index 0000000..958aa89 --- /dev/null +++ b/ui/base/animation/animation_delegate.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_ANIMATION_DELEGATE_H_ +#define UI_BASE_ANIMATION_ANIMATION_DELEGATE_H_ +#pragma once + +namespace ui { + +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 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) {} + + protected: + virtual ~AnimationDelegate() {} +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_ANIMATION_DELEGATE_H_ diff --git a/ui/base/animation/animation_unittest.cc b/ui/base/animation/animation_unittest.cc new file mode 100644 index 0000000..7d87600 --- /dev/null +++ b/ui/base/animation/animation_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/base/animation/linear_animation.h" +#include "ui/base/animation/test_animation_delegate.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace ui { + +class AnimationTest: public testing::Test { + private: + MessageLoopForUI message_loop_; +}; + +namespace { + +/////////////////////////////////////////////////////////////////////////////// +// RunAnimation + +class RunAnimation : public LinearAnimation { + public: + RunAnimation(int frame_rate, AnimationDelegate* delegate) + : LinearAnimation(frame_rate, delegate) { + } + + virtual void AnimateToState(double state) { + EXPECT_LE(0.0, state); + EXPECT_GE(1.0, state); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// CancelAnimation + +class CancelAnimation : public LinearAnimation { + public: + CancelAnimation(int duration, int frame_rate, AnimationDelegate* delegate) + : LinearAnimation(duration, frame_rate, delegate) { + } + + virtual void AnimateToState(double state) { + if (state >= 0.5) + Stop(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// EndAnimation + +class EndAnimation : public LinearAnimation { + public: + EndAnimation(int duration, int frame_rate, AnimationDelegate* delegate) + : LinearAnimation(duration, frame_rate, delegate) { + } + + virtual void AnimateToState(double state) { + if (state >= 0.5) + End(); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// DeletingAnimationDelegate + +// AnimationDelegate implementation that deletes the animation in ended. +class DeletingAnimationDelegate : public AnimationDelegate { + public: + virtual void AnimationEnded(const Animation* animation) { + delete animation; + MessageLoop::current()->Quit(); + } +}; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// LinearCase + +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()); +} + +// Lets an animation run, invoking End part way through and make sure we get the +// right delegate methods invoked. +TEST_F(AnimationTest, EndCase) { + TestAnimationDelegate ad; + EndAnimation a2(2000, 150, &ad); + a2.Start(); + MessageLoop::current()->Run(); + + EXPECT_TRUE(ad.finished()); + EXPECT_FALSE(ad.canceled()); +} + +// Runs an animation with a delegate that deletes the animation in end. +TEST_F(AnimationTest, DeleteFromEnd) { + DeletingAnimationDelegate delegate; + RunAnimation* animation = new RunAnimation(150, &delegate); + animation->Start(); + MessageLoop::current()->Run(); + // delegate should have deleted animation. +} + +TEST_F(AnimationTest, ShouldRenderRichAnimation) { +#if defined(OS_WIN) + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + BOOL result; + ASSERT_NE( + 0, ::SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &result, 0)); + // ShouldRenderRichAnimation() should check the SPI_GETCLIENTAREAANIMATION + // value on Vista. + EXPECT_EQ(!!result, Animation::ShouldRenderRichAnimation()); + } else { + // On XP, the function should check the SM_REMOTESESSION value. + EXPECT_EQ(!::GetSystemMetrics(SM_REMOTESESSION), + Animation::ShouldRenderRichAnimation()); + } +#else + EXPECT_TRUE(Animation::ShouldRenderRichAnimation()); +#endif +} + +} // namespace ui diff --git a/ui/base/animation/linear_animation.cc b/ui/base/animation/linear_animation.cc new file mode 100644 index 0000000..60054d9 --- /dev/null +++ b/ui/base/animation/linear_animation.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 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 "ui/base/animation/linear_animation.h" + +#include <math.h> + +#include "ui/base/animation/animation_container.h" +#include "ui/base/animation/animation_delegate.h" + +using base::Time; +using base::TimeDelta; + +namespace ui { + +static TimeDelta CalculateInterval(int frame_rate) { + int timer_interval = 1000000 / frame_rate; + if (timer_interval < 10000) + timer_interval = 10000; + return TimeDelta::FromMicroseconds(timer_interval); +} + +LinearAnimation::LinearAnimation(int frame_rate, + AnimationDelegate* delegate) + : Animation(CalculateInterval(frame_rate)), + state_(0.0), + in_end_(false) { + set_delegate(delegate); +} + +LinearAnimation::LinearAnimation(int duration, + int frame_rate, + AnimationDelegate* delegate) + : Animation(CalculateInterval(frame_rate)), + duration_(TimeDelta::FromMilliseconds(duration)), + state_(0.0), + in_end_(false) { + set_delegate(delegate); + SetDuration(duration); +} + +double LinearAnimation::GetCurrentValue() const { + // Default is linear relationship, subclass to adapt. + return state_; +} + +void LinearAnimation::End() { + if (!is_animating()) + return; + + // NOTE: We don't use AutoReset here as Stop may end up deleting us (by way + // of the delegate). + in_end_ = true; + Stop(); +} + +void LinearAnimation::SetDuration(int duration) { + duration_ = TimeDelta::FromMilliseconds(duration); + if (duration_ < timer_interval()) + duration_ = timer_interval(); + if (is_animating()) + SetStartTime(container()->last_tick_time()); +} + +void LinearAnimation::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(); +} + +void LinearAnimation::AnimationStopped() { + if (!in_end_) + return; + + in_end_ = false; + // Set state_ to ensure we send ended to delegate and not canceled. + state_ = 1; + AnimateToState(1.0); +} + +bool LinearAnimation::ShouldSendCanceledFromStop() { + return state_ != 1; +} + +} // namespace ui diff --git a/ui/base/animation/linear_animation.h b/ui/base/animation/linear_animation.h new file mode 100644 index 0000000..38205ea --- /dev/null +++ b/ui/base/animation/linear_animation.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_LINEAR_ANIMATION_H_ +#define UI_BASE_ANIMATION_LINEAR_ANIMATION_H_ +#pragma once + +#include "base/time.h" +#include "ui/base/animation/animation.h" + +namespace ui { + +class AnimationDelegate; + +// Linear time bounded animation. As the animation progresses AnimateToState is +// invoked. +class LinearAnimation : public 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. + LinearAnimation(int frame_rate, AnimationDelegate* delegate); + + // Initializes all fields. + LinearAnimation(int duration, int frame_rate, AnimationDelegate* delegate); + + // 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; + + // Skip to the end of the current animation. + void End(); + + // Changes the length of the animation. This resets the current + // state of the animation to the beginning. + void SetDuration(int duration); + + protected: + // Called when the animation progresses. Subclasses override this to + // efficiently update their state. + virtual void AnimateToState(double state) = 0; + + // 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); + + // Overriden to advance to the end (if End was invoked). + virtual void AnimationStopped(); + + // Overriden to return true if state is not 1. + virtual bool ShouldSendCanceledFromStop(); + + private: + base::TimeDelta duration_; + + // Current state, on a scale from 0.0 to 1.0. + double state_; + + // If true, we're in end. This is used to determine if the animation should + // be advanced to the end from AnimationStopped. + bool in_end_; + + DISALLOW_COPY_AND_ASSIGN(LinearAnimation); +}; + +} // namespace ui + +#endif // APP_LINEAR_ANIMATION_H_ diff --git a/ui/base/animation/multi_animation.cc b/ui/base/animation/multi_animation.cc new file mode 100644 index 0000000..5a0c1e1 --- /dev/null +++ b/ui/base/animation/multi_animation.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2011 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 "ui/base/animation/multi_animation.h" + +#include "base/logging.h" +#include "ui/base/animation/animation_delegate.h" + +namespace ui { + +// Default interval, in ms. +static const int kDefaultInterval = 20; + +static int TotalTime(const MultiAnimation::Parts& parts) { + int time_ms = 0; + for (size_t i = 0; i < parts.size(); ++i) { + DCHECK(parts[i].end_time_ms - parts[i].start_time_ms >= parts[i].time_ms); + time_ms += parts[i].time_ms; + } + return time_ms; +} + +MultiAnimation::MultiAnimation(const Parts& parts) + : Animation(base::TimeDelta::FromMilliseconds(kDefaultInterval)), + parts_(parts), + cycle_time_ms_(TotalTime(parts)), + current_value_(0), + current_part_index_(0), + continuous_(true) { + DCHECK(!parts_.empty()); +} + +MultiAnimation::~MultiAnimation() {} + +double MultiAnimation::GetCurrentValue() const { + return current_value_; +} + +void MultiAnimation::Step(base::TimeTicks time_now) { + double last_value = current_value_; + size_t last_index = current_part_index_; + + int delta = static_cast<int>((time_now - start_time()).InMilliseconds()); + if (delta >= cycle_time_ms_ && !continuous_) { + current_part_index_ = parts_.size() - 1; + current_value_ = Tween::CalculateValue(parts_[current_part_index_].type, 1); + Stop(); + return; + } + delta %= cycle_time_ms_; + const Part& part = GetPart(&delta, ¤t_part_index_); + double percent = static_cast<double>(delta + part.start_time_ms) / + static_cast<double>(part.end_time_ms); + DCHECK(percent <= 1); + current_value_ = Tween::CalculateValue(part.type, percent); + + if ((current_value_ != last_value || current_part_index_ != last_index) && + delegate()) { + delegate()->AnimationProgressed(this); + } +} + +void MultiAnimation::SetStartTime(base::TimeTicks start_time) { + Animation::SetStartTime(start_time); + current_value_ = 0; + current_part_index_ = 0; +} + +const MultiAnimation::Part& MultiAnimation::GetPart(int* time_ms, + size_t* part_index) { + DCHECK(*time_ms < cycle_time_ms_); + + for (size_t i = 0; i < parts_.size(); ++i) { + if (*time_ms < parts_[i].time_ms) { + *part_index = i; + return parts_[i]; + } + + *time_ms -= parts_[i].time_ms; + } + NOTREACHED(); + *time_ms = 0; + *part_index = 0; + return parts_[0]; +} + +} // namespace ui diff --git a/ui/base/animation/multi_animation.h b/ui/base/animation/multi_animation.h new file mode 100644 index 0000000..fbacde3 --- /dev/null +++ b/ui/base/animation/multi_animation.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_MULTI_ANIMATION_H_ +#define UI_BASE_ANIMATION_MULTI_ANIMATION_H_ +#pragma once + +#include <vector> + +#include "ui/base/animation/animation.h" +#include "ui/base/animation/tween.h" + +namespace ui { + +// MultiAnimation is an animation that consists of a number of sub animations. +// To create a MultiAnimation pass in the parts, invoke Start() and the delegate +// is notified as the animation progresses. By default MultiAnimation runs until +// Stop is invoked, see |set_continuous()| for details. +class MultiAnimation : public Animation { + public: + // Defines part of the animation. Each part consists of the following: + // + // time_ms: the time of the part. + // start_time_ms: the amount of time to offset this part by when calculating + // the percented completed. + // end_time_ms: the end time used to calculate the percentange completed. + // + // In most cases |start_time_ms| = 0 and |end_time_ms| = |time_ms|. But you + // can adjust the start/end for different effects. For example, to run a part + // for 200ms with a % between .25 and .75 use the following three values: 200, + // 100, 400. + struct Part { + Part() : time_ms(0), start_time_ms(0), end_time_ms(0), type(Tween::ZERO) {} + Part(int time_ms, Tween::Type type) + : time_ms(time_ms), + start_time_ms(0), + end_time_ms(time_ms), + type(type) {} + + int time_ms; + int start_time_ms; + int end_time_ms; + Tween::Type type; + }; + + typedef std::vector<Part> Parts; + + explicit MultiAnimation(const Parts& parts); + virtual ~MultiAnimation(); + + // Sets whether the animation continues after it reaches the end. If true, the + // animation runs until explicitly stopped. The default is true. + void set_continuous(bool continuous) { continuous_ = continuous; } + + // Returns the current value. The current value for a MultiAnimation is + // determined from the tween type of the current part. + virtual double GetCurrentValue() const; + + // Returns the index of the current part. + size_t current_part_index() const { return current_part_index_; } + + protected: + // Animation overrides. + virtual void Step(base::TimeTicks time_now); + virtual void SetStartTime(base::TimeTicks start_time); + + private: + // Returns the part containing the specified time. |time_ms| is reset to be + // relative to the part containing the time and |part_index| the index of the + // part. + const Part& GetPart(int* time_ms, size_t* part_index); + + // The parts that make up the animation. + const Parts parts_; + + // Total time of all the parts. + const int cycle_time_ms_; + + // Current value for the animation. + double current_value_; + + // Index of the current part. + size_t current_part_index_; + + // See description above setter. + bool continuous_; + + DISALLOW_COPY_AND_ASSIGN(MultiAnimation); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_MULTI_ANIMATION_H_ diff --git a/ui/base/animation/multi_animation_unittest.cc b/ui/base/animation/multi_animation_unittest.cc new file mode 100644 index 0000000..dfc015d --- /dev/null +++ b/ui/base/animation/multi_animation_unittest.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" +#include "ui/base/animation/animation_container_element.h" +#include "ui/base/animation/multi_animation.h" + +namespace ui { + +typedef testing::Test MultiAnimationTest; + +TEST_F(MultiAnimationTest, Basic) { + // Create a MultiAnimation with two parts. + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(100, Tween::LINEAR)); + parts.push_back(MultiAnimation::Part(100, Tween::EASE_OUT)); + + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast<AnimationContainerElement*>(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 50, which is half way through the first part. + as_element->Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(50)); + EXPECT_EQ(.5, animation.GetCurrentValue()); + + // Step to 120, which is 20% through the second part. + as_element->Step(base::TimeTicks() + + base::TimeDelta::FromMilliseconds(120)); + EXPECT_EQ(Tween::CalculateValue(Tween::EASE_OUT, .2), + animation.GetCurrentValue()); + + // Step to 320, which is 20% through the second part. + as_element->Step(base::TimeTicks() + + base::TimeDelta::FromMilliseconds(320)); + EXPECT_EQ(Tween::CalculateValue(Tween::EASE_OUT, .2), + animation.GetCurrentValue()); +} + +TEST_F(MultiAnimationTest, DifferingStartAndEnd) { + // Create a MultiAnimation with two parts. + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(200, Tween::LINEAR)); + parts[0].start_time_ms = 100; + parts[0].end_time_ms = 400; + + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast<AnimationContainerElement*>(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 0. Because the start_time is 100, this should be 100ms into the + // animation + as_element->Step(base::TimeTicks()); + EXPECT_EQ(.25, animation.GetCurrentValue()); + + // Step to 100, which is effectively 200ms into the animation. + as_element->Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(100)); + EXPECT_EQ(.5, animation.GetCurrentValue()); +} + +// Makes sure multi-animation stops if cycles is false. +TEST_F(MultiAnimationTest, DontCycle) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(200, Tween::LINEAR)); + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast<AnimationContainerElement*>(&animation); + as_element->SetStartTime(base::TimeTicks()); + animation.set_continuous(false); + + // Step to 300, which is greater than the cycle time. + as_element->Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(300)); + EXPECT_EQ(1.0, animation.GetCurrentValue()); + EXPECT_FALSE(animation.is_animating()); +} + +// Makes sure multi-animation cycles correctly. +TEST_F(MultiAnimationTest, Cycle) { + MultiAnimation::Parts parts; + parts.push_back(MultiAnimation::Part(200, Tween::LINEAR)); + MultiAnimation animation(parts); + AnimationContainerElement* as_element = + static_cast<AnimationContainerElement*>(&animation); + as_element->SetStartTime(base::TimeTicks()); + + // Step to 300, which is greater than the cycle time. + as_element->Step(base::TimeTicks() + base::TimeDelta::FromMilliseconds(300)); + EXPECT_EQ(.5, animation.GetCurrentValue()); +} + +} // namespace ui diff --git a/ui/base/animation/slide_animation.cc b/ui/base/animation/slide_animation.cc new file mode 100644 index 0000000..ec08334 --- /dev/null +++ b/ui/base/animation/slide_animation.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011 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 "ui/base/animation/slide_animation.h" + +#include <math.h> + +namespace ui { + +// 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) + : LinearAnimation(kDefaultFramerateHz, target), + target_(target), + tween_type_(Tween::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<bool>(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<int>(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<int>(slide_duration_ * value_current_)); + Start(); +} + +void SlideAnimation::SetSlideDuration(int duration) { + slide_duration_ = duration; +} + +double SlideAnimation::GetCurrentValue() const { + return value_current_; +} + +void SlideAnimation::AnimateToState(double state) { + if (state > 1.0) + state = 1.0; + + state = Tween::CalculateValue(tween_type_, state); + + value_current_ = value_start_ + (value_end_ - value_start_) * state; + + // Implement snapping. + if (tween_type_ == Tween::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_; + } +} + +} // namespace ui diff --git a/ui/base/animation/slide_animation.h b/ui/base/animation/slide_animation.h new file mode 100644 index 0000000..8f5e67c --- /dev/null +++ b/ui/base/animation/slide_animation.h @@ -0,0 +1,100 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_SLIDE_ANIMATION_H_ +#define UI_BASE_ANIMATION_SLIDE_ANIMATION_H_ +#pragma once + +#include "ui/base/animation/linear_animation.h" +#include "ui/base/animation/tween.h" + +namespace ui { + +// Slide Animation +// +// Used for reversible animations and as a general helper class. Typical usage: +// +// #include "ui/base/animation/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_->is_animating()) { +// hover_image_.SetOpacity(animation_->GetCurrentValue()); +// } +// } +// private: +// scoped_ptr<SlideAnimation> animation_; +// } +class SlideAnimation : public LinearAnimation { + public: + explicit SlideAnimation(AnimationDelegate* target); + virtual ~SlideAnimation(); + + // 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); + int GetSlideDuration() const { return slide_duration_; } + void SetTweenType(Tween::Type tween_type) { tween_type_ = tween_type; } + + virtual double GetCurrentValue() const; + bool IsShowing() const { return showing_; } + bool IsClosing() const { return !showing_ && value_end_ < value_current_; } + + private: + // Overridden from Animation. + virtual void AnimateToState(double state); + + AnimationDelegate* target_; + + Tween::Type 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); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_SLIDE_ANIMATION_H_ diff --git a/ui/base/animation/slide_animation_unittest.cc b/ui/base/animation/slide_animation_unittest.cc new file mode 100644 index 0000000..dcce5c0 --- /dev/null +++ b/ui/base/animation/slide_animation_unittest.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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 "base/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/animation/slide_animation.h" +#include "ui/base/animation/test_animation_delegate.h" + +namespace ui { + +class SlideAnimationTest: public testing::Test { + private: + MessageLoopForUI message_loop_; +}; + +// Tests that delegate is not notified when animation is running and is deleted. +// (Such a scenario would cause problems for BoundsAnimator). +TEST_F(SlideAnimationTest, DontNotifyOnDelete) { + TestAnimationDelegate delegate; + scoped_ptr<SlideAnimation> animation(new SlideAnimation(&delegate)); + + // Start the animation. + animation->Show(); + + // Delete the animation. + animation.reset(); + + // Make sure the delegate wasn't notified. + EXPECT_FALSE(delegate.finished()); + EXPECT_FALSE(delegate.canceled()); +} + +} // namespace ui diff --git a/ui/base/animation/test_animation_delegate.h b/ui/base/animation/test_animation_delegate.h new file mode 100644 index 0000000..e6c4047 --- /dev/null +++ b/ui/base/animation/test_animation_delegate.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_TEST_ANIMATION_DELEGATE_H_ +#define UI_BASE_ANIMATION_TEST_ANIMATION_DELEGATE_H_ +#pragma once + +#include "base/message_loop.h" +#include "ui/base/animation/animation_delegate.h" + +namespace ui { + +// Trivial AnimationDelegate implementation. AnimationEnded/Canceled quit the +// message loop. +class TestAnimationDelegate : public AnimationDelegate { + public: + TestAnimationDelegate() : canceled_(false), finished_(false) { + } + + 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); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_TEST_ANIMATION_DELEGATE_H_ diff --git a/ui/base/animation/throb_animation.cc b/ui/base/animation/throb_animation.cc new file mode 100644 index 0000000..95e9a65 --- /dev/null +++ b/ui/base/animation/throb_animation.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2011 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 "ui/base/animation/throb_animation.h" + +#include <limits> + +namespace ui { + +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_til_stop = cycles_til_stop >= 0 ? cycles_til_stop : + std::numeric_limits<int>::max(); + cycles_remaining_ = cycles_til_stop; + throbbing_ = true; + SlideAnimation::SetSlideDuration(throb_duration_); + if (is_animating()) + 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::SetSlideDuration(int duration) { + slide_duration_ = duration; +} + +void ThrobAnimation::Step(base::TimeTicks time_now) { + LinearAnimation::Step(time_now); + + if (!is_animating() && 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; +} + +} // namespace ui diff --git a/ui/base/animation/throb_animation.h b/ui/base/animation/throb_animation.h new file mode 100644 index 0000000..21dc03b --- /dev/null +++ b/ui/base/animation/throb_animation.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_THROB_ANIMATION_H_ +#define UI_BASE_ANIMATION_THROB_ANIMATION_H_ +#pragma once + +#include "ui/base/animation/slide_animation.h" + +namespace ui { + +// 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. A negative value means "throb indefinitely". + 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(); + + // Overridden to maintain the slide duration. + virtual void SetSlideDuration(int duration); + + // The number of cycles remaining until the animation stops. + 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(); + + // 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); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_THROB_ANIMATION_H_ diff --git a/ui/base/animation/tween.cc b/ui/base/animation/tween.cc new file mode 100644 index 0000000..131e686 --- /dev/null +++ b/ui/base/animation/tween.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2011 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 "ui/base/animation/tween.h" + +#include <math.h> + +#if defined(OS_WIN) +#include <float.h> +#endif + +#include "base/logging.h" +#include "gfx/rect.h" + +namespace ui { + +// static +double Tween::CalculateValue(Tween::Type type, double state) { + DCHECK_GE(state, 0); + DCHECK_LE(state, 1); + + switch (type) { + case EASE_IN: + return pow(state, 2); + + case EASE_IN_OUT: + if (state < 0.5) + return pow(state * 2, 2) / 2.0; + return 1.0 - (pow((state - 1.0) * 2, 2) / 2.0); + + case FAST_IN_OUT: + return (pow(state - 0.5, 3) + 0.125) / 0.25; + + case LINEAR: + return state; + + case EASE_OUT_SNAP: + state = 0.95 * (1.0 - pow(1.0 - state, 2)); + break; + + case EASE_OUT: + return 1.0 - pow(1.0 - state, 2); + + case ZERO: + return 0; + } + + NOTREACHED(); + return state; +} + +// static +double Tween::ValueBetween(double value, double start, double target) { + return start + (target - start) * value; +} + +// static +int Tween::ValueBetween(double value, int start, int target) { + if (start == target) + return start; + double delta = static_cast<double>(target - start); + if (delta < 0) + delta--; + else + delta++; +#if defined(OS_WIN) + return start + static_cast<int>(value * _nextafter(delta, 0)); +#else + return start + static_cast<int>(value * nextafter(delta, 0)); +#endif +} + +// static +gfx::Rect Tween::ValueBetween(double value, + const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds) { + return gfx::Rect(ValueBetween(value, start_bounds.x(), target_bounds.x()), + ValueBetween(value, start_bounds.y(), target_bounds.y()), + ValueBetween(value, start_bounds.width(), + target_bounds.width()), + ValueBetween(value, start_bounds.height(), + target_bounds.height())); +} + +} // namespace ui diff --git a/ui/base/animation/tween.h b/ui/base/animation/tween.h new file mode 100644 index 0000000..873281f --- /dev/null +++ b/ui/base/animation/tween.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011 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 UI_BASE_ANIMATION_TWEEN_H_ +#define UI_BASE_ANIMATION_TWEEN_H_ +#pragma once + +#include "base/basictypes.h" + +namespace gfx { +class Rect; +} + +namespace ui { + +class Tween { + public: + enum Type { + LINEAR, // Linear. + EASE_OUT, // Fast in, slow out (default). + 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. + ZERO, // Returns a value of 0 always. + }; + + // Returns the value based on the tween type. |state| is from 0-1. + static double CalculateValue(Type type, double state); + + // Conveniences for getting a value between a start and end point. + static double ValueBetween(double value, double start, double target); + static int ValueBetween(double value, int start, int target); + static gfx::Rect ValueBetween(double value, + const gfx::Rect& start_bounds, + const gfx::Rect& target_bounds); + + private: + Tween(); + ~Tween(); + + DISALLOW_COPY_AND_ASSIGN(Tween); +}; + +} // namespace ui + +#endif // UI_BASE_ANIMATION_TWEEN_H_ diff --git a/ui/base/run_all_unittests.cc b/ui/base/run_all_unittests.cc new file mode 100644 index 0000000..3be8d08 --- /dev/null +++ b/ui/base/run_all_unittests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2011 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 "ui/base/test_suite.h" + +int main(int argc, char** argv) { + return UiBaseTestSuite(argc, argv).Run(); +} diff --git a/ui/base/test_suite.h b/ui/base/test_suite.h new file mode 100644 index 0000000..c548d6a --- /dev/null +++ b/ui/base/test_suite.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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 UI_BASE_TEST_SUITE_H_ +#define UI_BASE_TEST_SUITE_H_ +#pragma once + +#include "build/build_config.h" + +#include "base/path_service.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#endif + +class UiBaseTestSuite : public base::TestSuite { + public: + UiBaseTestSuite(int argc, char** argv) : TestSuite(argc, argv) { + } + + protected: + + virtual void Initialize() { + base::mac::ScopedNSAutoreleasePool autorelease_pool; + + TestSuite::Initialize(); + } + + virtual void Shutdown() { + TestSuite::Shutdown(); + } +}; + +#endif // UI_BASE_TEST_SUITE_H_ diff --git a/ui/base/ui_base.gypi b/ui/base/ui_base.gypi new file mode 100644 index 0000000..f441c6f --- /dev/null +++ b/ui/base/ui_base.gypi @@ -0,0 +1,100 @@ +# Copyright (c) 2011 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'target_defaults': { + 'sources/': [ + ['exclude', '/(cocoa|gtk|win)/'], + ['exclude', '_(cocoa|gtk|linux|mac|posix|skia|win|x)\\.(cc|mm?)$'], + ['exclude', '/(gtk|win|x11)_[^/]*\\.cc$'], + ], + 'conditions': [ + ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', {'sources/': [ + ['include', '/gtk/'], + ['include', '_(gtk|linux|posix|skia|x)\\.cc$'], + ['include', '/(gtk|x11)_[^/]*\\.cc$'], + ]}], + ['OS=="mac"', {'sources/': [ + ['include', '/cocoa/'], + ['include', '_(cocoa|mac|posix)\\.(cc|mm?)$'], + ]}, { # else: OS != "mac" + 'sources/': [ + ['exclude', '\\.mm?$'], + ], + }], + ['OS=="win"', {'sources/': [ + ['include', '_(win)\\.cc$'], + ['include', '/win/'], + ['include', '/win_[^/]*\\.cc$'], + ]}], + ['touchui==0', {'sources/': [ + ['exclude', 'event_x.cc$'], + ['exclude', 'native_menu_x.cc$'], + ['exclude', 'native_menu_x.h$'], + ['exclude', 'touchui/'], + ['exclude', '_(touch)\\.cc$'], + ]}], + ], + }, + 'targets': [ + { + 'target_name': 'ui_base', + 'type': '<(library)', + 'dependencies': [ + '../base/base.gyp:base', + '../gfx/gfx.gyp:gfx', + '../skia/skia.gyp:skia', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + ], + 'sources': [ + 'animation/animation.cc', + 'animation/animation.h', + 'animation/animation_container.cc', + 'animation/animation_container.h', + 'animation/animation_container_element.h', + 'animation/animation_container_observer.h', + 'animation/animation_delegate.h', + 'animation/linear_animation.cc', + 'animation/linear_animation.h', + 'animation/multi_animation.cc', + 'animation/multi_animation.h', + 'animation/slide_animation.cc', + 'animation/slide_animation.h', + 'animation/throb_animation.cc', + 'animation/throb_animation.h', + 'animation/tween.cc', + 'animation/tween.h', + ], + }, + { + 'target_name': 'ui_base_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + 'ui_base', + ], + 'sources': [ + 'animation/animation_container_unittest.cc', + 'animation/animation_unittest.cc', + 'animation/multi_animation_unittest.cc', + 'animation/slide_animation_unittest.cc', + 'run_all_unittests.cc', + 'test_suite.h', + ], + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/ui/ui.gyp b/ui/ui.gyp new file mode 100644 index 0000000..e289b0b --- /dev/null +++ b/ui/ui.gyp @@ -0,0 +1,52 @@ +# Copyright (c) 2011 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'target_defaults': { + 'sources/': [ + ['exclude', '/(cocoa|gtk|win)/'], + ['exclude', '_(cocoa|gtk|linux|mac|posix|skia|win|x)\\.(cc|mm?)$'], + ['exclude', '/(gtk|win|x11)_[^/]*\\.cc$'], + ], + 'conditions': [ + ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', {'sources/': [ + ['include', '/gtk/'], + ['include', '_(gtk|linux|posix|skia|x)\\.cc$'], + ['include', '/(gtk|x11)_[^/]*\\.cc$'], + ]}], + ['OS=="mac"', {'sources/': [ + ['include', '/cocoa/'], + ['include', '_(cocoa|mac|posix)\\.(cc|mm?)$'], + ]}, { # else: OS != "mac" + 'sources/': [ + ['exclude', '\\.mm?$'], + ], + }], + ['OS=="win"', {'sources/': [ + ['include', '_(win)\\.cc$'], + ['include', '/win/'], + ['include', '/win_[^/]*\\.cc$'], + ]}], + ['touchui==0', {'sources/': [ + ['exclude', 'event_x.cc$'], + ['exclude', 'native_menu_x.cc$'], + ['exclude', 'native_menu_x.h$'], + ['exclude', 'touchui/'], + ['exclude', '_(touch)\\.cc$'], + ]}], + ], + }, + 'includes': [ + 'base/ui_base.gypi', + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: |