diff options
21 files changed, 2510 insertions, 216 deletions
diff --git a/ui/base/animation/tween.cc b/ui/base/animation/tween.cc index 515ae57..a16bb18 100644 --- a/ui/base/animation/tween.cc +++ b/ui/base/animation/tween.cc @@ -11,7 +11,10 @@ #endif #include "base/logging.h" -#include "ui/gfx/rect.h" + +#if !defined(OS_MACOSX) +#include "ui/gfx/interpolated_transform.h" +#endif namespace ui { @@ -83,4 +86,42 @@ gfx::Rect Tween::ValueBetween(double value, target_bounds.height())); } +#if !defined(OS_MACOSX) +// static +Transform Tween::ValueBetween(double value, + const Transform& start_transform, + const Transform& end_transform) { + Transform to_return; + gfx::Point start_translation, end_translation; + float start_rotation, end_rotation; + gfx::Point3f start_scale, end_scale; + if (InterpolatedTransform::FactorTRS(start_transform, + &start_translation, + &start_rotation, + &start_scale) && + InterpolatedTransform::FactorTRS(end_transform, + &end_translation, + &end_rotation, + &end_scale)) { + to_return.SetScale(ValueBetween(value, start_scale.x(), end_scale.x()), + ValueBetween(value, start_scale.y(), end_scale.y())); + to_return.ConcatRotate(ValueBetween(value, start_rotation, end_rotation)); + to_return.ConcatTranslate( + ValueBetween(value, start_translation.x(), end_translation.x()), + ValueBetween(value, start_translation.y(), end_translation.y())); + } else { + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + to_return.matrix().set(row, col, + ValueBetween(value, + start_transform.matrix().get(row, col), + end_transform.matrix().get(row, col))); + } + } + } + + return to_return; +} +#endif + } // namespace ui diff --git a/ui/base/animation/tween.h b/ui/base/animation/tween.h index 6bde5dd..02c08c5 100644 --- a/ui/base/animation/tween.h +++ b/ui/base/animation/tween.h @@ -8,10 +8,11 @@ #include "base/basictypes.h" #include "ui/base/ui_export.h" +#include "ui/gfx/rect.h" -namespace gfx { -class Rect; -} +#if !defined(OS_MACOSX) +#include "ui/gfx/transform.h" +#endif namespace ui { @@ -36,6 +37,11 @@ class UI_EXPORT Tween { static gfx::Rect ValueBetween(double value, const gfx::Rect& start_bounds, const gfx::Rect& target_bounds); +#if !defined(OS_MACOSX) + static Transform ValueBetween(double value, + const Transform& start_transform, + const Transform& target_transform); +#endif private: Tween(); diff --git a/ui/gfx/compositor/compositor.gyp b/ui/gfx/compositor/compositor.gyp index 0b88f32..02368cf 100644 --- a/ui/gfx/compositor/compositor.gyp +++ b/ui/gfx/compositor/compositor.gyp @@ -47,9 +47,15 @@ 'compositor_win.cc', 'layer.cc', 'layer.h', + 'layer_animation_delegate.h', + 'layer_animation_element.cc', + 'layer_animation_element.h', + 'layer_animation_manager.cc', + 'layer_animation_manager.h', + 'layer_animation_sequence.cc', + 'layer_animation_sequence.h', 'layer_animator.cc', 'layer_animator.h', - 'layer_animator_delegate.h', ], 'conditions': [ ['os_posix == 1 and OS != "mac"', { @@ -131,6 +137,9 @@ 'compositor_test_support', ], 'sources': [ + 'layer_animation_element_unittest.cc', + 'layer_animation_sequence_unittest.cc', + 'layer_animator_unittest.cc', 'layer_unittest.cc', 'run_all_unittests.cc', 'test_compositor.cc', @@ -138,10 +147,14 @@ 'test_compositor_host.h', 'test_compositor_host_linux.cc', 'test_compositor_host_win.cc', + 'test_layer_animation_delegate.cc', + 'test_layer_animation_delegate.h', 'test_suite.cc', 'test_suite.h', 'test_texture.cc', 'test_texture.h', + 'test_utils.cc', + 'test_utils.h', '<(SHARED_INTERMEDIATE_DIR)/ui/gfx/gfx_resources.rc', '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_resources.rc', ], diff --git a/ui/gfx/compositor/layer.cc b/ui/gfx/compositor/layer.cc index 1a42754..91c3199 100644 --- a/ui/gfx/compositor/layer.cc +++ b/ui/gfx/compositor/layer.cc @@ -12,7 +12,7 @@ #include "third_party/WebKit/Source/WebKit/chromium/public/WebFloatRect.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" #include "ui/base/animation/animation.h" -#include "ui/gfx/compositor/layer_animator.h" +#include "ui/gfx/compositor/layer_animation_manager.h" #include "ui/gfx/canvas_skia.h" #include "ui/gfx/interpolated_transform.h" #include "ui/gfx/point3.h" @@ -131,7 +131,7 @@ bool Layer::Contains(const Layer* other) const { void Layer::SetAnimation(Animation* animation) { if (animation) { if (!animator_.get()) - animator_.reset(new LayerAnimator(this)); + animator_.reset(new LayerAnimationManager(this)); animation->Start(); animator_->SetAnimation(animation); } else { @@ -140,7 +140,7 @@ void Layer::SetAnimation(Animation* animation) { } void Layer::SetTransform(const ui::Transform& transform) { - StopAnimatingIfNecessary(LayerAnimator::TRANSFORM); + StopAnimatingIfNecessary(LayerAnimationManager::TRANSFORM); if (animator_.get() && animator_->IsRunning()) { animator_->AnimateTransform(transform); return; @@ -149,7 +149,7 @@ void Layer::SetTransform(const ui::Transform& transform) { } void Layer::SetBounds(const gfx::Rect& bounds) { - StopAnimatingIfNecessary(LayerAnimator::LOCATION); + StopAnimatingIfNecessary(LayerAnimationManager::LOCATION); if (animator_.get() && animator_->IsRunning() && bounds.size() == bounds_.size()) { animator_->AnimateToPoint(bounds.origin()); @@ -165,7 +165,7 @@ gfx::Rect Layer::GetTargetBounds() const { } void Layer::SetOpacity(float opacity) { - StopAnimatingIfNecessary(LayerAnimator::OPACITY); + StopAnimatingIfNecessary(LayerAnimationManager::OPACITY); if (animator_.get() && animator_->IsRunning()) { animator_->AnimateOpacity(opacity); return; @@ -515,23 +515,23 @@ bool Layer::GetTransformRelativeTo(const Layer* ancestor, } void Layer::StopAnimatingIfNecessary( - LayerAnimator::AnimationProperty property) { + LayerAnimationManager::AnimationProperty property) { if (!animator_.get() || !animator_->IsRunning() || !animator_->got_initial_tick()) { return; } - if (property != LayerAnimator::LOCATION && - animator_->IsAnimating(LayerAnimator::LOCATION)) { + if (property != LayerAnimationManager::LOCATION && + animator_->IsAnimating(LayerAnimationManager::LOCATION)) { SetBoundsImmediately( gfx::Rect(animator_->GetTargetPoint(), bounds_.size())); } - if (property != LayerAnimator::OPACITY && - animator_->IsAnimating(LayerAnimator::OPACITY)) { + if (property != LayerAnimationManager::OPACITY && + animator_->IsAnimating(LayerAnimationManager::OPACITY)) { SetOpacityImmediately(animator_->GetTargetOpacity()); } - if (property != LayerAnimator::TRANSFORM && - animator_->IsAnimating(LayerAnimator::TRANSFORM)) { + if (property != LayerAnimationManager::TRANSFORM && + animator_->IsAnimating(LayerAnimationManager::TRANSFORM)) { SetTransformImmediately(animator_->GetTargetTransform()); } animator_.reset(); diff --git a/ui/gfx/compositor/layer.h b/ui/gfx/compositor/layer.h index 91dc05f..d94d4e6 100644 --- a/ui/gfx/compositor/layer.h +++ b/ui/gfx/compositor/layer.h @@ -18,7 +18,7 @@ #include "ui/gfx/rect.h" #include "ui/gfx/transform.h" #include "ui/gfx/compositor/compositor.h" -#include "ui/gfx/compositor/layer_animator.h" +#include "ui/gfx/compositor/layer_animation_manager.h" #include "ui/gfx/compositor/layer_animator_delegate.h" #include "ui/gfx/compositor/layer_delegate.h" @@ -243,7 +243,8 @@ class COMPOSITOR_EXPORT Layer : // If the animation is running and has progressed, it is stopped and all // properties that are animated (except |property|) are immediately set to // their target value. - void StopAnimatingIfNecessary(LayerAnimator::AnimationProperty property); + void StopAnimatingIfNecessary( + LayerAnimationManager::AnimationProperty property); // Following are invoked from the animation or if no animation exists to // update the values immediately. @@ -292,7 +293,7 @@ class COMPOSITOR_EXPORT Layer : LayerDelegate* delegate_; - scoped_ptr<LayerAnimator> animator_; + scoped_ptr<LayerAnimationManager> animator_; #if defined(USE_WEBKIT_COMPOSITOR) WebKit::WebContentLayer web_layer_; diff --git a/ui/gfx/compositor/layer_animation_delegate.h b/ui/gfx/compositor/layer_animation_delegate.h new file mode 100644 index 0000000..c6f4fa0 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_delegate.h @@ -0,0 +1,32 @@ +// 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_GFX_COMPOSITOR_LAYER_ANIMATION_DELEGATE_H_ +#define UI_GFX_COMPOSITOR_LAYER_ANIMATION_DELEGATE_H_ +#pragma once + +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/compositor/compositor_export.h" + +namespace ui { + +// LayerAnimation interacts with the Layer using this interface. +class COMPOSITOR_EXPORT LayerAnimationDelegate { + public: + virtual void SetBoundsFromAnimation(const gfx::Rect& bounds) = 0; + virtual void SetTransformFromAnimation(const Transform& transform) = 0; + virtual void SetOpacityFromAnimation(float opacity) = 0; + virtual void ScheduleDrawForAnimation() = 0; + virtual const gfx::Rect& GetBoundsForAnimation() const = 0; + virtual const Transform& GetTransformForAnimation() const = 0; + virtual float GetOpacityForAnimation() const = 0; + + protected: + virtual ~LayerAnimationDelegate() {} +}; + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_LAYER_ANIMATION_DELEGATE_H_ diff --git a/ui/gfx/compositor/layer_animation_element.cc b/ui/gfx/compositor/layer_animation_element.cc new file mode 100644 index 0000000..fb52bbb --- /dev/null +++ b/ui/gfx/compositor/layer_animation_element.cc @@ -0,0 +1,190 @@ +// 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/gfx/compositor/layer_animation_element.h" + +#include "base/compiler_specific.h" +#include "ui/base/animation/tween.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" + +namespace ui { + +namespace { + +// Pause ----------------------------------------------------------------------- +class Pause : public LayerAnimationElement { + public: + Pause(const AnimatableProperties& properties, base::TimeDelta duration) + : LayerAnimationElement(properties, duration) { + } + virtual ~Pause() {} + + private: + virtual void OnStart(LayerAnimationDelegate* delegate) OVERRIDE {} + virtual void OnProgress(double t, + LayerAnimationDelegate* delegate) OVERRIDE {} + virtual void OnAbort() OVERRIDE {} + + DISALLOW_COPY_AND_ASSIGN(Pause); +}; + +// TransformTransition --------------------------------------------------------- + +class TransformTransition : public LayerAnimationElement { + public: + TransformTransition(const Transform& target, base::TimeDelta duration) + : LayerAnimationElement(GetProperties(), duration), + target_(target) { + } + virtual ~TransformTransition() {} + + protected: + virtual void OnStart(LayerAnimationDelegate* delegate) OVERRIDE { + start_ = delegate->GetTransformForAnimation(); + } + + virtual void OnProgress(double t, LayerAnimationDelegate* delegate) OVERRIDE { + delegate->SetTransformFromAnimation( + Tween::ValueBetween(t, start_, target_)); + } + + virtual void OnAbort() OVERRIDE {} + + private: + static const AnimatableProperties& GetProperties() { + static AnimatableProperties properties; + if (properties.size() == 0) + properties.insert(LayerAnimationElement::TRANSFORM); + return properties; + } + + Transform start_; + const Transform target_; + + DISALLOW_COPY_AND_ASSIGN(TransformTransition); +}; + +// BoundsTransition ------------------------------------------------------------ + +class BoundsTransition : public LayerAnimationElement { + public: + BoundsTransition(const gfx::Rect& target, base::TimeDelta duration) + : LayerAnimationElement(GetProperties(), duration), + target_(target) { + } + virtual ~BoundsTransition() {} + + protected: + virtual void OnStart(LayerAnimationDelegate* delegate) OVERRIDE { + start_ = delegate->GetBoundsForAnimation(); + } + + virtual void OnProgress(double t, LayerAnimationDelegate* delegate) OVERRIDE { + delegate->SetBoundsFromAnimation(Tween::ValueBetween(t, start_, target_)); + } + + virtual void OnAbort() OVERRIDE {} + + private: + static const AnimatableProperties& GetProperties() { + static AnimatableProperties properties; + if (properties.size() == 0) + properties.insert(LayerAnimationElement::BOUNDS); + return properties; + } + + gfx::Rect start_; + const gfx::Rect target_; + + DISALLOW_COPY_AND_ASSIGN(BoundsTransition); +}; + +class OpacityTransition : public LayerAnimationElement { + public: + OpacityTransition(float target, base::TimeDelta duration) + : LayerAnimationElement(GetProperties(), duration), + target_(target) { + } + virtual ~OpacityTransition() {} + + protected: + virtual void OnStart(LayerAnimationDelegate* delegate) OVERRIDE { + start_ = delegate->GetOpacityForAnimation(); + } + + virtual void OnProgress(double t, LayerAnimationDelegate* delegate) OVERRIDE { + delegate->SetOpacityFromAnimation(Tween::ValueBetween(t, start_, target_)); + } + + virtual void OnAbort() OVERRIDE {} + + private: + static const AnimatableProperties& GetProperties() { + static AnimatableProperties properties; + if (properties.size() == 0) + properties.insert(LayerAnimationElement::OPACITY); + return properties; + } + + float start_; + const float target_; + + DISALLOW_COPY_AND_ASSIGN(OpacityTransition); +}; + +} // namespace + +// LayerAnimationElement ------------------------------------------------------- + +LayerAnimationElement::LayerAnimationElement( + const AnimatableProperties& properties, + base::TimeDelta duration) + : first_frame_(true), + properties_(properties), + duration_(duration) { +} + +LayerAnimationElement::~LayerAnimationElement() { +} + +void LayerAnimationElement::Progress(double t, + LayerAnimationDelegate* delegate) { + if (first_frame_) + OnStart(delegate); + OnProgress(t, delegate); + first_frame_ = t == 1.0; +} + +void LayerAnimationElement::Abort() { + first_frame_ = true; + OnAbort(); +} + +// static +LayerAnimationElement* LayerAnimationElement::CreateTransformElement( + const Transform& transform, base::TimeDelta duration) { + return new TransformTransition(transform, duration); +} + +// static +LayerAnimationElement* LayerAnimationElement::CreateBoundsElement( + const gfx::Rect& bounds, base::TimeDelta duration) { + return new BoundsTransition(bounds, duration); +} + +// static +LayerAnimationElement* LayerAnimationElement::CreateOpacityElement( + float opacity, base::TimeDelta duration) { + return new OpacityTransition(opacity, duration); +} + +// static +LayerAnimationElement* LayerAnimationElement::CreatePauseElement( + const AnimatableProperties& properties, base::TimeDelta duration) { + return new Pause(properties, duration); +} + +} // namespace ui diff --git a/ui/gfx/compositor/layer_animation_element.h b/ui/gfx/compositor/layer_animation_element.h new file mode 100644 index 0000000..12697b0 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_element.h @@ -0,0 +1,97 @@ +// 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_GFX_COMPOSITOR_LAYER_ANIMATION_ELEMENT_H_ +#define UI_GFX_COMPOSITOR_LAYER_ANIMATION_ELEMENT_H_ +#pragma once + +#include <set> + +#include "base/time.h" +#include "ui/gfx/compositor/compositor_export.h" + +namespace gfx { +class Rect; +} // gfx + +namespace ui { + +class LayerAnimationDelegate; +class Transform; + +// LayerAnimationElements represent one segment of an animation between two +// keyframes. They know how to update a LayerAnimationDelegate given a value +// between 0 and 1 (0 for initial, and 1 for final). +class COMPOSITOR_EXPORT LayerAnimationElement { + public: + enum AnimatableProperty { + TRANSFORM = 0, + BOUNDS, + OPACITY + }; + + typedef std::set<AnimatableProperty> AnimatableProperties; + + LayerAnimationElement(const AnimatableProperties& properties, + base::TimeDelta duration); + virtual ~LayerAnimationElement(); + + // Creates an element that transitions to the given transform. The caller owns + // the return value. + static LayerAnimationElement* CreateTransformElement( + const Transform& transform, + base::TimeDelta duration); + + // Creates an element that transitions to the given bounds. The caller owns + // the return value. + static LayerAnimationElement* CreateBoundsElement( + const gfx::Rect& bounds, + base::TimeDelta duration); + + // Creates an element that transitions to the given opacity. The caller owns + // the return value. + static LayerAnimationElement* CreateOpacityElement( + float opacity, + base::TimeDelta duration); + + // Creates an element that pauses the given properties. The caller owns the + // return value. + static LayerAnimationElement* CreatePauseElement( + const AnimatableProperties& properties, + base::TimeDelta duration); + + // Updates the delegate to the appropriate value for |t|, which is in the + // range [0, 1] (0 for initial, and 1 for final). If the animation is not + // aborted, it is guaranteed that Progress will eventually be called with + // t = 1.0. + void Progress(double t, LayerAnimationDelegate* delegate); + + // Called if the animation is not allowed to complete. This may be called + // before OnStarted or Progress. + void Abort(); + + // The properties that the element modifies. + const AnimatableProperties& properties() const { return properties_; } + + // The duration of the animation + base::TimeDelta duration() const { return duration_; } + + protected: + // Called once each time the animation element is run before any call to + // OnProgress. + virtual void OnStart(LayerAnimationDelegate* delegate) = 0; + virtual void OnProgress(double t, LayerAnimationDelegate* delegate) = 0; + virtual void OnAbort() = 0; + + private: + bool first_frame_; + const AnimatableProperties properties_; + const base::TimeDelta duration_; + + DISALLOW_COPY_AND_ASSIGN(LayerAnimationElement); +}; + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_LAYER_ANIMATION_ELEMENT_H_ diff --git a/ui/gfx/compositor/layer_animation_element_unittest.cc b/ui/gfx/compositor/layer_animation_element_unittest.cc new file mode 100644 index 0000000..efbc1ca --- /dev/null +++ b/ui/gfx/compositor/layer_animation_element_unittest.cc @@ -0,0 +1,131 @@ +// 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/gfx/compositor/layer_animation_element.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/compositor/test_utils.h" +#include "ui/gfx/compositor/test_layer_animation_delegate.h" + +namespace ui { + +namespace { + +// Check that the transformation element progresses the delegate as expected and +// that the element can be reused after it completes. +TEST(LayerAnimationElementTest, TransformElement) { + TestLayerAnimationDelegate delegate; + Transform start_transform, target_transform, middle_transform; + start_transform.SetRotate(-90); + target_transform.SetRotate(90); + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + scoped_ptr<LayerAnimationElement> element( + LayerAnimationElement::CreateTransformElement(target_transform, delta)); + + for (int i = 0; i < 2; ++i) { + delegate.SetTransformFromAnimation(start_transform); + element->Progress(0.0, &delegate); + CheckApproximatelyEqual(start_transform, + delegate.GetTransformForAnimation()); + element->Progress(0.5, &delegate); + CheckApproximatelyEqual(middle_transform, + delegate.GetTransformForAnimation()); + element->Progress(1.0, &delegate); + CheckApproximatelyEqual(target_transform, + delegate.GetTransformForAnimation()); + } + + EXPECT_EQ(delta, element->duration()); +} + +// Check that the bounds element progresses the delegate as expected and +// that the element can be reused after it completes. +TEST(LayerAnimationElementTest, BoundsElement) { + TestLayerAnimationDelegate delegate; + gfx::Rect start, target, middle; + start = target = middle = gfx::Rect(0, 0, 50, 50); + start.set_x(-90); + target.set_x(90); + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + scoped_ptr<LayerAnimationElement> element( + LayerAnimationElement::CreateBoundsElement(target, delta)); + + for (int i = 0; i < 2; ++i) { + delegate.SetBoundsFromAnimation(start); + element->Progress(0.0, &delegate); + CheckApproximatelyEqual(start, delegate.GetBoundsForAnimation()); + element->Progress(0.5, &delegate); + CheckApproximatelyEqual(middle, delegate.GetBoundsForAnimation()); + element->Progress(1.0, &delegate); + CheckApproximatelyEqual(target, delegate.GetBoundsForAnimation()); + } + + EXPECT_EQ(delta, element->duration()); +} + +// Check that the opacity element progresses the delegate as expected and +// that the element can be reused after it completes. +TEST(LayerAnimationElementTest, OpacityElement) { + TestLayerAnimationDelegate delegate; + float start = 0.0; + float middle = 0.5; + float target = 1.0; + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + scoped_ptr<LayerAnimationElement> element( + LayerAnimationElement::CreateOpacityElement(target, delta)); + + for (int i = 0; i < 2; ++i) { + delegate.SetOpacityFromAnimation(start); + element->Progress(0.0, &delegate); + EXPECT_FLOAT_EQ(start, delegate.GetOpacityForAnimation()); + element->Progress(0.5, &delegate); + EXPECT_FLOAT_EQ(middle, delegate.GetOpacityForAnimation()); + element->Progress(1.0, &delegate); + EXPECT_FLOAT_EQ(target, delegate.GetOpacityForAnimation()); + } + + EXPECT_EQ(delta, element->duration()); +} + +// Check that the pause element progresses the delegate as expected and +// that the element can be reused after it completes. +TEST(LayerAnimationElementTest, PauseElement) { + LayerAnimationElement::AnimatableProperties properties; + properties.insert(LayerAnimationElement::TRANSFORM); + properties.insert(LayerAnimationElement::BOUNDS); + properties.insert(LayerAnimationElement::OPACITY); + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + scoped_ptr<LayerAnimationElement> element( + LayerAnimationElement::CreatePauseElement(properties, delta)); + + TestLayerAnimationDelegate delegate; + TestLayerAnimationDelegate copy = delegate; + + element->Progress(1.0, &delegate); + + // Nothing should have changed. + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), + copy.GetBoundsForAnimation()); + CheckApproximatelyEqual(delegate.GetTransformForAnimation(), + copy.GetTransformForAnimation()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), + copy.GetOpacityForAnimation()); + + // Pause should last for |delta|. + EXPECT_EQ(delta, element->duration()); +} + +} // namespace + +} // namespace ui diff --git a/ui/gfx/compositor/layer_animation_manager.cc b/ui/gfx/compositor/layer_animation_manager.cc new file mode 100644 index 0000000..0301ba4 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_manager.cc @@ -0,0 +1,187 @@ +// 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/gfx/compositor/layer_animation_manager.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "ui/base/animation/animation_container.h" +#include "ui/base/animation/animation.h" +#include "ui/base/animation/tween.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/gfx/compositor/layer_animator_delegate.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/rect.h" + +namespace { + +void SetMatrixElement(SkMatrix44& matrix, int index, SkMScalar value) { + int row = index / 4; + int col = index % 4; + matrix.set(row, col, value); +} + +SkMScalar GetMatrixElement(const SkMatrix44& matrix, int index) { + int row = index / 4; + int col = index % 4; + return matrix.get(row, col); +} + +} // anonymous namespace + +namespace ui { + +LayerAnimationManager::LayerAnimationManager(Layer* layer) + : layer_(layer), + got_initial_tick_(false) { +} + +LayerAnimationManager::~LayerAnimationManager() { +} + +void LayerAnimationManager::SetAnimation(Animation* animation) { + animation_.reset(animation); + if (animation_.get()) { + static ui::AnimationContainer* container = NULL; + if (!container) { + container = new AnimationContainer; + container->AddRef(); + } + animation_->set_delegate(this); + animation_->SetContainer(container); + got_initial_tick_ = false; + } +} + +void LayerAnimationManager::AnimateToPoint(const gfx::Point& target) { + StopAnimating(LOCATION); + const gfx::Rect& layer_bounds = layer_->bounds(); + if (target == layer_bounds.origin()) + return; // Already there. + + Params& element = elements_[LOCATION]; + element.location.target_x = target.x(); + element.location.target_y = target.y(); + element.location.start_x = layer_bounds.origin().x(); + element.location.start_y = layer_bounds.origin().y(); +} + +void LayerAnimationManager::AnimateTransform(const Transform& transform) { + StopAnimating(TRANSFORM); + const Transform& layer_transform = layer_->transform(); + if (transform == layer_transform) + return; // Already there. + + Params& element = elements_[TRANSFORM]; + for (int i = 0; i < 16; ++i) { + element.transform.start[i] = + GetMatrixElement(layer_transform.matrix(), i); + element.transform.target[i] = + GetMatrixElement(transform.matrix(), i); + } +} + +void LayerAnimationManager::AnimateOpacity(float target_opacity) { + StopAnimating(OPACITY); + if (layer_->opacity() == target_opacity) + return; + + Params& element = elements_[OPACITY]; + element.opacity.start = layer_->opacity(); + element.opacity.target = target_opacity; +} + +gfx::Point LayerAnimationManager::GetTargetPoint() { + return IsAnimating(LOCATION) ? + gfx::Point(elements_[LOCATION].location.target_x, + elements_[LOCATION].location.target_y) : + layer_->bounds().origin(); +} + +float LayerAnimationManager::GetTargetOpacity() { + return IsAnimating(OPACITY) ? + elements_[OPACITY].opacity.target : layer_->opacity(); +} + +ui::Transform LayerAnimationManager::GetTargetTransform() { + if (IsAnimating(TRANSFORM)) { + Transform transform; + for (int i = 0; i < 16; ++i) { + SetMatrixElement(transform.matrix(), i, + elements_[TRANSFORM].transform.target[i]); + } + return transform; + } + return layer_->transform(); +} + +bool LayerAnimationManager::IsAnimating(AnimationProperty property) const { + return elements_.count(property) > 0; +} + +bool LayerAnimationManager::IsRunning() const { + return animation_.get() && animation_->is_animating(); +} + +void LayerAnimationManager::AnimationProgressed( + const ui::Animation* animation) { + got_initial_tick_ = true; + for (Elements::const_iterator i = elements_.begin(); i != elements_.end(); + ++i) { + switch (i->first) { + case LOCATION: { + const gfx::Rect& current_bounds(layer_->bounds()); + gfx::Rect new_bounds = animation_->CurrentValueBetween( + gfx::Rect(gfx::Point(i->second.location.start_x, + i->second.location.start_y), + current_bounds.size()), + gfx::Rect(gfx::Point(i->second.location.target_x, + i->second.location.target_y), + current_bounds.size())); + delegate()->SetBoundsFromAnimator(new_bounds); + break; + } + + case TRANSFORM: { + Transform transform; + for (int j = 0; j < 16; ++j) { + SkMScalar value = animation_->CurrentValueBetween( + i->second.transform.start[j], + i->second.transform.target[j]); + SetMatrixElement(transform.matrix(), j, value); + } + delegate()->SetTransformFromAnimator(transform); + break; + } + + case OPACITY: { + delegate()->SetOpacityFromAnimator(animation_->CurrentValueBetween( + i->second.opacity.start, i->second.opacity.target)); + break; + } + + default: + NOTREACHED(); + } + } + layer_->ScheduleDraw(); +} + +void LayerAnimationManager::AnimationEnded(const ui::Animation* animation) { + AnimationProgressed(animation); +} + +void LayerAnimationManager::StopAnimating(AnimationProperty property) { + if (!IsAnimating(property)) + return; + + elements_.erase(property); +} + +LayerAnimatorDelegate* LayerAnimationManager::delegate() { + return static_cast<LayerAnimatorDelegate*>(layer_); +} + +} // namespace ui diff --git a/ui/gfx/compositor/layer_animation_manager.h b/ui/gfx/compositor/layer_animation_manager.h new file mode 100644 index 0000000..b5b6540 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_manager.h @@ -0,0 +1,129 @@ +// 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_GFX_COMPOSITOR_LAYER_ANIMATION_MANAGER_H_ +#define UI_GFX_COMPOSITOR_LAYER_ANIMATION_MANAGER_H_ +#pragma once + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "third_party/skia/include/utils/SkMatrix44.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/gfx/compositor/compositor_export.h" + +namespace gfx { +class Point; +} + +namespace ui { + +class Animation; +class Layer; +class LayerAnimatorDelegate; +class Transform; + +// LayerAnimationManager manages animating various properties of a Layer. +class COMPOSITOR_EXPORT LayerAnimationManager : public ui::AnimationDelegate { + public: + // Types of properties that can be animated. + enum AnimationProperty { + LOCATION, + OPACITY, + TRANSFORM, + }; + + explicit LayerAnimationManager(Layer* layer); + virtual ~LayerAnimationManager(); + + // Sets the animation to use. LayerAnimationManager takes ownership of the + // animation. + void SetAnimation(Animation* animation); + + ui::Layer* layer() { return layer_; } + + // Animates the layer to the specified point. The point is relative to the + // parent layer. + void AnimateToPoint(const gfx::Point& target); + + // Animates the transform from the current transform to |transform|. + void AnimateTransform(const Transform& transform); + + // Animates the opacity from the current opacity to |target_opacity|. + void AnimateOpacity(float target_opacity); + + // Returns the target value for the specified type. If the specified property + // is not animating, the current value is returned. + gfx::Point GetTargetPoint(); + float GetTargetOpacity(); + ui::Transform GetTargetTransform(); + + // Returns true if animating |property|. + bool IsAnimating(AnimationProperty property) const; + + // Returns true if the animation is running. + bool IsRunning() const; + + // Returns true if the animation has progressed at least once since + // SetAnimation() was invoked. + bool got_initial_tick() const { return got_initial_tick_; } + + // AnimationDelegate: + virtual void AnimationProgressed(const Animation* animation) OVERRIDE; + virtual void AnimationEnded(const Animation* animation) OVERRIDE; + + private: + // Parameters used when animating the location. + struct LocationParams { + int start_x; + int start_y; + int target_x; + int target_y; + }; + + // Parameters used when animating the transform. + struct TransformParams { + SkMScalar start[16]; + SkMScalar target[16]; + }; + + // Parameters used when animating the opacity. + struct OpacityParams { + float start; + float target; + }; + + union Params { + LocationParams location; + OpacityParams opacity; + TransformParams transform; + }; + + typedef std::map<AnimationProperty, Params> Elements; + + // Stops animating the specified property. This does not set the property + // being animated to its final value. + void StopAnimating(AnimationProperty property); + + LayerAnimatorDelegate* delegate(); + + // The layer. + Layer* layer_; + + // Properties being animated. + Elements elements_; + + scoped_ptr<ui::Animation> animation_; + + bool got_initial_tick_; + + DISALLOW_COPY_AND_ASSIGN(LayerAnimationManager); +}; + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_LAYER_ANIMATION_MANAGER_H_ diff --git a/ui/gfx/compositor/layer_animation_sequence.cc b/ui/gfx/compositor/layer_animation_sequence.cc new file mode 100644 index 0000000..f0deca6 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_sequence.cc @@ -0,0 +1,101 @@ +// 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/gfx/compositor/layer_animation_sequence.h" + +#include <algorithm> +#include <iterator> + +#include "base/debug/trace_event.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/compositor/layer_animation_element.h" + +namespace ui { + +LayerAnimationSequence::LayerAnimationSequence() + : is_cyclic_(false), + last_element_(0) { +} + +LayerAnimationSequence::LayerAnimationSequence(LayerAnimationElement* element) + : is_cyclic_(false), + last_element_(0) { + AddElement(element); +} + +LayerAnimationSequence::~LayerAnimationSequence() { +} + +void LayerAnimationSequence::Progress(base::TimeDelta elapsed, + LayerAnimationDelegate* delegate) { + TRACE_EVENT0("LayerAnimationSequence", "Progress"); + if (elements_.size() == 0 || duration_ == base::TimeDelta()) + return; + + if (is_cyclic_) { + // If delta = elapsed - last_start_ is huge, we can skip ahead by complete + // loops to save time. + base::TimeDelta delta = elapsed - last_start_; + int64 k = delta.ToInternalValue() / duration_.ToInternalValue() - 1; + if (k > 0) { + last_start_ += base::TimeDelta::FromInternalValue( + k * duration_.ToInternalValue()); + } + } + + size_t current_index = last_element_ % elements_.size(); + while ((is_cyclic_ || last_element_ < elements_.size()) && + (last_start_ + elements_[current_index]->duration() < elapsed)) { + // Let the element we're passing finish. + elements_[current_index]->Progress(1.0, delegate); + last_start_ += elements_[current_index]->duration(); + ++last_element_; + current_index = last_element_ % elements_.size(); + } + + if (is_cyclic_ || last_element_ < elements_.size()) { + double t = 1.0; + if (elements_[current_index]->duration() > base::TimeDelta()) { + t = (elapsed - last_start_).InMillisecondsF() / + elements_[current_index]->duration().InMillisecondsF(); + } + elements_[current_index]->Progress(t, delegate); + } + + if (!is_cyclic_ && elapsed == duration_) { + last_element_ = 0; + last_start_ = base::TimeDelta::FromMilliseconds(0); + } +} + +void LayerAnimationSequence::Abort() { + size_t current_index = last_element_ % elements_.size(); + while (current_index < elements_.size()) { + elements_[current_index]->Abort(); + ++current_index; + } + last_element_ = 0; + last_start_ = base::TimeDelta::FromMilliseconds(0); +} + +void LayerAnimationSequence::AddElement(LayerAnimationElement* element) { + // Update duration and properties. + duration_ += element->duration(); + properties_.insert(element->properties().begin(), + element->properties().end()); + elements_.push_back(make_linked_ptr(element)); +} + +bool LayerAnimationSequence::HasCommonProperty( + const LayerAnimationElement::AnimatableProperties& other) const { + LayerAnimationElement::AnimatableProperties intersection; + std::insert_iterator<LayerAnimationElement::AnimatableProperties> ii( + intersection, intersection.begin()); + std::set_intersection(properties_.begin(), properties_.end(), + other.begin(), other.end(), + ii); + return intersection.size() > 0; +} + +} // namespace ui diff --git a/ui/gfx/compositor/layer_animation_sequence.h b/ui/gfx/compositor/layer_animation_sequence.h new file mode 100644 index 0000000..87673d7 --- /dev/null +++ b/ui/gfx/compositor/layer_animation_sequence.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_GFX_COMPOSITOR_LAYER_ANIMATION_SEQUENCE_H_ +#define UI_GFX_COMPOSITOR_LAYER_ANIMATION_SEQUENCE_H_ +#pragma once + +#include <vector> + +#include "base/memory/linked_ptr.h" +#include "base/time.h" +#include "ui/gfx/compositor/compositor_export.h" +#include "ui/gfx/compositor/layer_animation_element.h" + +namespace ui { + +class LayerAnimationDelegate; + +// Contains a collection of layer animation elements to be played one after +// another. Although it has a similar interface to LayerAnimationElement, it is +// not a LayerAnimationElement (i.e., it is not permitted to have a sequence in +// a sequence). Sequences own their elements, and sequences are themselves owned +// by a LayerAnimator. +// +// TODO(vollick) Create a 'blended' sequence for transitioning between +// sequences. +class COMPOSITOR_EXPORT LayerAnimationSequence { + public: + LayerAnimationSequence(); + // Takes ownership of the given element and adds it to the sequence. + explicit LayerAnimationSequence(LayerAnimationElement* element); + virtual ~LayerAnimationSequence(); + + // Updates the delegate to the appropriate value for |elapsed|, which is in + // the range [0, Duration()]. If the animation is not aborted, it is + // guaranteed that Animate will be called with elapsed = Duration(). + void Progress(base::TimeDelta elapsed, LayerAnimationDelegate* delegate); + + // Aborts the given animation. + void Abort(); + + // All properties modified by the sequence. + const LayerAnimationElement::AnimatableProperties& properties() const { + return properties_; + } + + // The total, finite duration of one cycle of the sequence. + base::TimeDelta duration() const { + return duration_; + } + + // Adds an element to the sequence. The sequences takes ownership of this + // element. + void AddElement(LayerAnimationElement* element); + + // Sequences can be looped indefinitely. + void set_is_cyclic(bool is_cyclic) { is_cyclic_ = is_cyclic; } + bool is_cyclic() const { return is_cyclic_; } + + // Returns true if this sequence has at least one element affecting a + // property in |other|. + bool HasCommonProperty( + const LayerAnimationElement::AnimatableProperties& other) const; + + private: + typedef std::vector<linked_ptr<LayerAnimationElement> > Elements; + + // The sum of the durations of all the elements in the sequence. + base::TimeDelta duration_; + + // The union of all the properties modified by all elements in the sequence. + LayerAnimationElement::AnimatableProperties properties_; + + // The elements in the sequence. + Elements elements_; + + // True if the sequence should be looped forever. + bool is_cyclic_; + + // These are used when animating to efficiently find the next element. + size_t last_element_; + base::TimeDelta last_start_; + + DISALLOW_COPY_AND_ASSIGN(LayerAnimationSequence); +}; + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_LAYER_ANIMATION_SEQUENCE_H_ diff --git a/ui/gfx/compositor/layer_animation_sequence_unittest.cc b/ui/gfx/compositor/layer_animation_sequence_unittest.cc new file mode 100644 index 0000000..3b283a3f --- /dev/null +++ b/ui/gfx/compositor/layer_animation_sequence_unittest.cc @@ -0,0 +1,158 @@ +// 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/gfx/compositor/layer_animation_sequence.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/compositor/layer_animation_element.h" +#include "ui/gfx/compositor/test_utils.h" +#include "ui/gfx/compositor/test_layer_animation_delegate.h" + +namespace ui { + +namespace { + +// Check that the sequence behaves sanely when it contains no elements. +TEST(LayerAnimationSequenceTest, NoElement) { + LayerAnimationSequence sequence; + EXPECT_EQ(sequence.duration(), base::TimeDelta()); + EXPECT_TRUE(sequence.properties().size() == 0); + LayerAnimationElement::AnimatableProperties properties; + EXPECT_FALSE(sequence.HasCommonProperty(properties)); +} + +// Check that the sequences progresses the delegate as expected when it contains +// a single element. +TEST(LayerAnimationSequenceTest, SingleElement) { + LayerAnimationSequence sequence; + TestLayerAnimationDelegate delegate; + float start = 0.0; + float middle = 0.5; + float target = 1.0; + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + sequence.AddElement( + LayerAnimationElement::CreateOpacityElement(target, delta)); + + for (int i = 0; i < 2; ++i) { + delegate.SetOpacityFromAnimation(start); + sequence.Progress(base::TimeDelta::FromMilliseconds(0), &delegate); + EXPECT_FLOAT_EQ(start, delegate.GetOpacityForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(500), &delegate); + EXPECT_FLOAT_EQ(middle, delegate.GetOpacityForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(1000), &delegate); + EXPECT_FLOAT_EQ(target, delegate.GetOpacityForAnimation()); + } + + EXPECT_TRUE(sequence.properties().size() == 1); + EXPECT_TRUE(sequence.properties().find(LayerAnimationElement::OPACITY) != + sequence.properties().end()); + EXPECT_EQ(delta, sequence.duration()); +} + +// Check that the sequences progresses the delegate as expected when it contains +// multiple elements. Note, see the layer animator tests for cyclic sequences. +TEST(LayerAnimationSequenceTest, MultipleElement) { + LayerAnimationSequence sequence; + TestLayerAnimationDelegate delegate; + float start_opacity = 0.0; + float middle_opacity = 0.5; + float target_opacity = 1.0; + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + sequence.AddElement( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta)); + + // Pause bounds for a second. + LayerAnimationElement::AnimatableProperties properties; + properties.insert(LayerAnimationElement::BOUNDS); + + sequence.AddElement( + LayerAnimationElement::CreatePauseElement(properties, delta)); + + Transform start_transform, target_transform, middle_transform; + start_transform.SetRotate(-90); + target_transform.SetRotate(90); + + sequence.AddElement( + LayerAnimationElement::CreateTransformElement(target_transform, delta)); + + for (int i = 0; i < 2; ++i) { + delegate.SetOpacityFromAnimation(start_opacity); + delegate.SetTransformFromAnimation(start_transform); + + sequence.Progress(base::TimeDelta::FromMilliseconds(0), &delegate); + EXPECT_FLOAT_EQ(start_opacity, delegate.GetOpacityForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(500), &delegate); + EXPECT_FLOAT_EQ(middle_opacity, delegate.GetOpacityForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(1000), &delegate); + EXPECT_FLOAT_EQ(target_opacity, delegate.GetOpacityForAnimation()); + TestLayerAnimationDelegate copy = delegate; + + // In the middle of the pause -- nothing should have changed. + sequence.Progress(base::TimeDelta::FromMilliseconds(1500), &delegate); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), + copy.GetBoundsForAnimation()); + CheckApproximatelyEqual(delegate.GetTransformForAnimation(), + copy.GetTransformForAnimation()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), + copy.GetOpacityForAnimation()); + + + sequence.Progress(base::TimeDelta::FromMilliseconds(2000), &delegate); + CheckApproximatelyEqual(start_transform, + delegate.GetTransformForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(2500), &delegate); + CheckApproximatelyEqual(middle_transform, + delegate.GetTransformForAnimation()); + sequence.Progress(base::TimeDelta::FromMilliseconds(3000), &delegate); + CheckApproximatelyEqual(target_transform, + delegate.GetTransformForAnimation()); + } + + EXPECT_TRUE(sequence.properties().size() == 3); + EXPECT_TRUE(sequence.properties().find(LayerAnimationElement::OPACITY) != + sequence.properties().end()); + EXPECT_TRUE(sequence.properties().find(LayerAnimationElement::TRANSFORM) != + sequence.properties().end()); + EXPECT_TRUE(sequence.properties().find(LayerAnimationElement::BOUNDS) != + sequence.properties().end()); + EXPECT_EQ(delta + delta + delta, sequence.duration()); +} + +// Check that a sequence can still be aborted if it has cycled many times. +TEST(LayerAnimationSequenceTest, AbortingCyclicSequence) { + LayerAnimationSequence sequence; + TestLayerAnimationDelegate delegate; + float start_opacity = 0.0; + float target_opacity = 1.0; + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + sequence.AddElement( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta)); + + sequence.AddElement( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta)); + + sequence.set_is_cyclic(true); + + delegate.SetOpacityFromAnimation(start_opacity); + + sequence.Progress(base::TimeDelta::FromMilliseconds(101000), &delegate); + EXPECT_FLOAT_EQ(target_opacity, delegate.GetOpacityForAnimation()); + sequence.Abort(); + + // Should be able to reuse the sequence after aborting. + delegate.SetOpacityFromAnimation(start_opacity); + sequence.Progress(base::TimeDelta::FromMilliseconds(100000), &delegate); + EXPECT_FLOAT_EQ(start_opacity, delegate.GetOpacityForAnimation()); +} + +} // namespace + +} // namespace ui diff --git a/ui/gfx/compositor/layer_animator.cc b/ui/gfx/compositor/layer_animator.cc index c9a7bca..e737377 100644 --- a/ui/gfx/compositor/layer_animator.cc +++ b/ui/gfx/compositor/layer_animator.cc @@ -4,185 +4,440 @@ #include "ui/gfx/compositor/layer_animator.h" +#include "base/debug/trace_event.h" #include "base/logging.h" -#include "base/stl_util.h" +#include "base/memory/scoped_ptr.h" #include "ui/base/animation/animation_container.h" -#include "ui/base/animation/animation.h" -#include "ui/base/animation/tween.h" #include "ui/gfx/compositor/compositor.h" #include "ui/gfx/compositor/layer.h" -#include "ui/gfx/compositor/layer_animator_delegate.h" -#include "ui/gfx/transform.h" -#include "ui/gfx/rect.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/compositor/layer_animation_sequence.h" + +namespace ui { + +class LayerAnimator; namespace { -void SetMatrixElement(SkMatrix44& matrix, int index, SkMScalar value) { - int row = index / 4; - int col = index % 4; - matrix.set(row, col, value); +static const base::TimeDelta kDefaultTransitionDuration = + base::TimeDelta::FromMilliseconds(250); + +static const base::TimeDelta kTimerInterval = + base::TimeDelta::FromMilliseconds(10); + +} // namespace; + +// LayerAnimator public -------------------------------------------------------- + +LayerAnimator::LayerAnimator(base::TimeDelta transition_duration) + : delegate_(NULL), + preemption_strategy_(IMMEDIATELY_SET_NEW_TARGET), + transition_duration_(transition_duration), + is_started_(false), + disable_timer_for_test_(false) { } -SkMScalar GetMatrixElement(const SkMatrix44& matrix, int index) { - int row = index / 4; - int col = index % 4; - return matrix.get(row, col); +LayerAnimator::~LayerAnimator() { + ClearAnimations(); } -} // anonymous namespace +// static +LayerAnimator* LayerAnimator::CreateDefaultAnimator() { + return new LayerAnimator(base::TimeDelta::FromMilliseconds(0)); +} -namespace ui { +// static +LayerAnimator* LayerAnimator::CreateImplicitAnimator() { + return new LayerAnimator(kDefaultTransitionDuration); +} -LayerAnimator::LayerAnimator(Layer* layer) - : layer_(layer), - got_initial_tick_(false) { +void LayerAnimator::SetTransform(const Transform& transform) { + if (transition_duration_ == base::TimeDelta()) + delegate_->SetTransformFromAnimation(transform); + else + StartAnimation(new LayerAnimationSequence( + LayerAnimationElement::CreateTransformElement(transform, + transition_duration_))); } -LayerAnimator::~LayerAnimator() { +void LayerAnimator::SetBounds(const gfx::Rect& bounds) { + if (transition_duration_ == base::TimeDelta()) + delegate_->SetBoundsFromAnimation(bounds); + else + StartAnimation(new LayerAnimationSequence( + LayerAnimationElement::CreateBoundsElement(bounds, + transition_duration_))); +} + +void LayerAnimator::SetOpacity(float opacity) { + if (transition_duration_ == base::TimeDelta()) + delegate_->SetOpacityFromAnimation(opacity); + else + StartAnimation(new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(opacity, + transition_duration_))); +} + +void LayerAnimator::SetDelegate(LayerAnimationDelegate* delegate) { + DCHECK(delegate); + delegate_ = delegate; } -void LayerAnimator::SetAnimation(Animation* animation) { - animation_.reset(animation); - if (animation_.get()) { - static ui::AnimationContainer* container = NULL; - if (!container) { - container = new AnimationContainer; - container->AddRef(); +void LayerAnimator::StartAnimation(LayerAnimationSequence* animation) { + if (!StartSequenceImmediately(animation)) { + // Attempt to preempt a running animation. + switch (preemption_strategy_) { + case IMMEDIATELY_SET_NEW_TARGET: + ImmediatelySetNewTarget(animation); + break; + case IMMEDIATELY_ANIMATE_TO_NEW_TARGET: + ImmediatelyAnimateToNewTarget(animation); + break; + case ENQUEUE_NEW_ANIMATION: + EnqueueNewAnimation(animation); + break; + case REPLACE_QUEUED_ANIMATIONS: + ReplaceQueuedAnimations(animation); + break; + case BLEND_WITH_CURRENT_ANIMATION: { + // TODO(vollick) Add support for blended sequences and use them here. + NOTIMPLEMENTED(); + break; + } } - animation_->set_delegate(this); - animation_->SetContainer(container); - got_initial_tick_ = false; + } + FinishAnyAnimationWithZeroDuration(); +} + +void LayerAnimator::ScheduleAnimation(LayerAnimationSequence* animation) { + if (is_animating()) { + animation_queue_.push_back(make_linked_ptr(animation)); + ProcessQueue(); + } else { + StartSequenceImmediately(animation); } } -void LayerAnimator::AnimateToPoint(const gfx::Point& target) { - StopAnimating(LOCATION); - const gfx::Rect& layer_bounds = layer_->bounds(); - if (target == layer_bounds.origin()) - return; // Already there. +void LayerAnimator::ScheduleTogether( + const std::vector<LayerAnimationSequence*>& animations) { + // Collect all the affected properties. + LayerAnimationElement::AnimatableProperties animated_properties; + std::vector<LayerAnimationSequence*>::const_iterator iter; + for (iter = animations.begin(); iter != animations.end(); ++iter) { + animated_properties.insert((*iter)->properties().begin(), + (*iter)->properties().end()); + } + + // Scheduling a zero duration pause that affects all the animated properties + // will prevent any of the sequences from animating until there are no + // running animations that affect any of these properties. + ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreatePauseElement(animated_properties, + base::TimeDelta()))); - Params& element = elements_[LOCATION]; - element.location.target_x = target.x(); - element.location.target_y = target.y(); - element.location.start_x = layer_bounds.origin().x(); - element.location.start_y = layer_bounds.origin().y(); + // These animations (provided they don't animate any common properties) will + // now animate together if trivially scheduled. + for (iter = animations.begin(); iter != animations.end(); ++iter) { + ScheduleAnimation(*iter); + } } -void LayerAnimator::AnimateTransform(const Transform& transform) { - StopAnimating(TRANSFORM); - const Transform& layer_transform = layer_->transform(); - if (transform == layer_transform) - return; // Already there. +void LayerAnimator::StopAnimatingProperty( + LayerAnimationElement::AnimatableProperty property) { + while (true) { + RunningAnimation* running = GetRunningAnimation(property); + if (!running) + break; + FinishAnimation(running->sequence); + } +} + +void LayerAnimator::StopAnimating() { + while (is_animating()) + FinishAnimation(running_animations_[0].sequence); +} - Params& element = elements_[TRANSFORM]; - for (int i = 0; i < 16; ++i) { - element.transform.start[i] = - GetMatrixElement(layer_transform.matrix(), i); - element.transform.target[i] = - GetMatrixElement(transform.matrix(), i); +// LayerAnimator private ------------------------------------------------------- + +void LayerAnimator::Step(base::TimeTicks now) { + TRACE_EVENT0("LayerAnimator", "Step"); + last_step_time_ = now; + std::vector<LayerAnimationSequence*> to_finish; + for (RunningAnimations::iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + base::TimeDelta delta = now - (*iter).start_time; + if (delta >= (*iter).sequence->duration() && + !(*iter).sequence->is_cyclic()) { + to_finish.push_back((*iter).sequence); + } else { + (*iter).sequence->Progress(delta, delegate()); + } + } + for (std::vector<LayerAnimationSequence*>::iterator iter = to_finish.begin(); + iter != to_finish.end(); ++iter) { + FinishAnimation(*iter); } } -void LayerAnimator::AnimateOpacity(float target_opacity) { - StopAnimating(OPACITY); - if (layer_->opacity() == target_opacity) +void LayerAnimator::SetStartTime(base::TimeTicks start_time) { +} + +base::TimeDelta LayerAnimator::GetTimerInterval() const { + return kTimerInterval; +} + +void LayerAnimator::UpdateAnimationState() { + if (disable_timer_for_test_) return; - Params& element = elements_[OPACITY]; - element.opacity.start = layer_->opacity(); - element.opacity.target = target_opacity; + static ui::AnimationContainer* container = NULL; + if (!container) { + container = new AnimationContainer(); + container->AddRef(); + } + + const bool should_start = is_animating(); + if (should_start && !is_started_) + container->Start(this); + else if (!should_start && is_started_) + container->Stop(this); + + is_started_ = should_start; } -gfx::Point LayerAnimator::GetTargetPoint() { - return IsAnimating(LOCATION) ? - gfx::Point(elements_[LOCATION].location.target_x, - elements_[LOCATION].location.target_y) : - layer_->bounds().origin(); +void LayerAnimator::RemoveAnimation(LayerAnimationSequence* sequence) { + // First remove from running animations + for (RunningAnimations::iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + if ((*iter).sequence == sequence) { + running_animations_.erase(iter); + break; + } + } + + // Then remove from the queue + for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); + queue_iter != animation_queue_.end(); ++queue_iter) { + if ((*queue_iter) == sequence) { + animation_queue_.erase(queue_iter); + break; + } + } } -float LayerAnimator::GetTargetOpacity() { - return IsAnimating(OPACITY) ? - elements_[OPACITY].opacity.target : layer_->opacity(); +void LayerAnimator::FinishAnimation(LayerAnimationSequence* sequence) { + sequence->Progress(sequence->duration(), delegate()); + RemoveAnimation(sequence); + ProcessQueue(); + UpdateAnimationState(); } -ui::Transform LayerAnimator::GetTargetTransform() { - if (IsAnimating(TRANSFORM)) { - Transform transform; - for (int i = 0; i < 16; ++i) { - SetMatrixElement(transform.matrix(), i, - elements_[TRANSFORM].transform.target[i]); +void LayerAnimator::FinishAnyAnimationWithZeroDuration() { + // Special case: if we've started a 0 duration animation, just finish it now + // and get rid of it. Note at each iteration of the loop, we either increment + // i or remove an element from running_animations_, so + // running_animations_.size() - i is always decreasing and we are always + // progressing towards the termination of the loop. + for (size_t i = 0; i < running_animations_.size();) { + if (running_animations_[i].sequence->duration() == base::TimeDelta()) { + running_animations_[i].sequence->Progress( + running_animations_[i].sequence->duration(), delegate()); + RemoveAnimation(running_animations_[i].sequence); + } else { + ++i; } - return transform; } - return layer_->transform(); + ProcessQueue(); + UpdateAnimationState(); } -bool LayerAnimator::IsAnimating(AnimationProperty property) const { - return elements_.count(property) > 0; +void LayerAnimator::ClearAnimations() { + for (RunningAnimations::iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + (*iter).sequence->Abort(); + } + running_animations_.clear(); + animation_queue_.clear(); + UpdateAnimationState(); } -bool LayerAnimator::IsRunning() const { - return animation_.get() && animation_->is_animating(); +LayerAnimator::RunningAnimation* LayerAnimator::GetRunningAnimation( + LayerAnimationElement::AnimatableProperty property) { + for (RunningAnimations::iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + if ((*iter).sequence->properties().find(property) != + (*iter).sequence->properties().end()) + return &(*iter); + } + return NULL; } -void LayerAnimator::AnimationProgressed(const ui::Animation* animation) { - got_initial_tick_ = true; - for (Elements::const_iterator i = elements_.begin(); i != elements_.end(); - ++i) { - switch (i->first) { - case LOCATION: { - const gfx::Rect& current_bounds(layer_->bounds()); - gfx::Rect new_bounds = animation_->CurrentValueBetween( - gfx::Rect(gfx::Point(i->second.location.start_x, - i->second.location.start_y), - current_bounds.size()), - gfx::Rect(gfx::Point(i->second.location.target_x, - i->second.location.target_y), - current_bounds.size())); - delegate()->SetBoundsFromAnimator(new_bounds); - break; - } +void LayerAnimator::AddToQueueIfNotPresent(LayerAnimationSequence* animation) { + // If we don't have the animation in the queue yet, add it. + bool found_sequence = false; + for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); + queue_iter != animation_queue_.end(); ++queue_iter) { + if ((*queue_iter) == animation) { + found_sequence = true; + break; + } + } - case TRANSFORM: { - Transform transform; - for (int j = 0; j < 16; ++j) { - SkMScalar value = animation_->CurrentValueBetween( - i->second.transform.start[j], - i->second.transform.target[j]); - SetMatrixElement(transform.matrix(), j, value); - } - delegate()->SetTransformFromAnimator(transform); + if (!found_sequence) + animation_queue_.push_front(make_linked_ptr(animation)); +} + +void LayerAnimator::RemoveAllAnimationsWithACommonProperty( + LayerAnimationSequence* sequence, + bool abort) { + // For all the running animations, if they animate the same property, + // progress them to the end and remove them. Note: at each iteration i is + // incremented or an element is removed from the queue, so + // animation_queue_.size() - i is always decreasing and we are always making + // progress towards the loop terminating. + for (size_t i = 0; i < running_animations_.size();) { + if (running_animations_[i].sequence->HasCommonProperty( + sequence->properties())) { + // Finish the animation. + if (abort) + running_animations_[i].sequence->Abort(); + else + running_animations_[i].sequence->Progress( + running_animations_[i].sequence->duration(), delegate()); + RemoveAnimation(running_animations_[i].sequence); + } else { + ++i; + } + } + + // Same for the queued animations. See comment above about loop termination. + for (size_t i = 0; i < animation_queue_.size();) { + if (animation_queue_[i]->HasCommonProperty(sequence->properties())) { + // Finish the animation. + if (abort) + animation_queue_[i]->Abort(); + else + animation_queue_[i]->Progress(animation_queue_[i]->duration(), + delegate()); + RemoveAnimation(animation_queue_[i].get()); + } else { + ++i; + } + } +} + +void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) { + const bool abort = false; + RemoveAllAnimationsWithACommonProperty(sequence, abort); + sequence->Progress(sequence->duration(), delegate()); + RemoveAnimation(sequence); +} + +void LayerAnimator::ImmediatelyAnimateToNewTarget( + LayerAnimationSequence* sequence) { + const bool abort = true; + RemoveAllAnimationsWithACommonProperty(sequence, abort); + AddToQueueIfNotPresent(sequence); + StartSequenceImmediately(sequence); +} + +void LayerAnimator::EnqueueNewAnimation(LayerAnimationSequence* sequence) { + // It is assumed that if there was no conflicting animation, we would + // not have been called. No need to check for a collision; just + // add to the queue. + animation_queue_.push_back(make_linked_ptr(sequence)); + ProcessQueue(); +} + +void LayerAnimator::ReplaceQueuedAnimations(LayerAnimationSequence* sequence) { + // Remove all animations that aren't running. Note: at each iteration i is + // incremented or an element is removed from the queue, so + // animation_queue_.size() - i is always decreasing and we are always making + // progress towards the loop terminating. + for (size_t i = 0; i < animation_queue_.size();) { + bool is_running = false; + for (RunningAnimations::const_iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + if ((*iter).sequence == animation_queue_[i]) { + is_running = true; break; } + } + if (!is_running) + RemoveAnimation(animation_queue_[i].get()); + else + ++i; + } + animation_queue_.push_back(make_linked_ptr(sequence)); + ProcessQueue(); +} + +void LayerAnimator::ProcessQueue() { + bool started_sequence = false; + do { + started_sequence = false; + + // Build a list of all currently animated properties. + LayerAnimationElement::AnimatableProperties animated; - case OPACITY: { - delegate()->SetOpacityFromAnimator(animation_->CurrentValueBetween( - i->second.opacity.start, i->second.opacity.target)); + for (RunningAnimations::const_iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + animated.insert((*iter).sequence->properties().begin(), + (*iter).sequence->properties().end()); + } + + // Try to find an animation that doesn't conflict with an animated + // property or a property that will be animated before it. + for (AnimationQueue::iterator queue_iter = animation_queue_.begin(); + queue_iter != animation_queue_.end(); ++queue_iter) { + if (!(*queue_iter)->HasCommonProperty(animated)) { + StartSequenceImmediately((*queue_iter).get()); + started_sequence = true; break; } - default: - NOTREACHED(); + // Animation couldn't be started. Add its properties to the collection so + // that we don't start a conflicting animation. For example, if our queue + // has the elements { {T,B}, {B} } (that is, an element that animates both + // the transform and the bounds followed by an element that animates the + // bounds), and we're currently animating the transform, we can't start + // the first element because it animates the transform, too. We cannot + // start the second element, either, because the first element animates + // bounds too, and needs to go first. + animated.insert((*queue_iter)->properties().begin(), + (*queue_iter)->properties().end()); } - } - layer_->ScheduleDraw(); -} -void LayerAnimator::AnimationEnded(const ui::Animation* animation) { - AnimationProgressed(animation); - if (layer_->delegate()) - layer_->delegate()->OnLayerAnimationEnded(animation); + // If we started a sequence, try again. We may be able to start several. + } while (started_sequence); } -void LayerAnimator::StopAnimating(AnimationProperty property) { - if (!IsAnimating(property)) - return; +bool LayerAnimator::StartSequenceImmediately(LayerAnimationSequence* sequence) { + // Ensure that no one is animating one of the sequence's properties already. + for (RunningAnimations::const_iterator iter = running_animations_.begin(); + iter != running_animations_.end(); ++iter) { + if ((*iter).sequence->HasCommonProperty(sequence->properties())) + return false; + } - elements_.erase(property); -} + // All clear, actually start the sequence. Note: base::TimeTicks::Now has + // a resolution that can be as bad as 15ms. If this causes glitches in the + // animations, this can be switched to HighResNow() (animation uses Now() + // internally). + base::TimeTicks start_time = is_animating() + ? last_step_time_ + : base::TimeTicks::Now(); + + running_animations_.push_back(RunningAnimation(sequence, start_time)); + + // Need to keep a reference to the animation. + AddToQueueIfNotPresent(sequence); + + // Ensure that animations get stepped at their start time. + Step(start_time); -LayerAnimatorDelegate* LayerAnimator::delegate() { - return static_cast<LayerAnimatorDelegate*>(layer_); + return true; } } // namespace ui diff --git a/ui/gfx/compositor/layer_animator.h b/ui/gfx/compositor/layer_animator.h index 52b8870..a51079c 100644 --- a/ui/gfx/compositor/layer_animator.h +++ b/ui/gfx/compositor/layer_animator.h @@ -6,119 +6,200 @@ #define UI_GFX_COMPOSITOR_LAYER_ANIMATOR_H_ #pragma once -#include <map> +#include <deque> +#include <vector> -#include "base/basictypes.h" -#include "base/compiler_specific.h" +#include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" -#include "third_party/skia/include/core/SkScalar.h" -#include "third_party/skia/include/utils/SkMatrix44.h" -#include "ui/base/animation/animation_delegate.h" +#include "base/time.h" +#include "ui/base/animation/animation_container_element.h" #include "ui/gfx/compositor/compositor_export.h" +#include "ui/gfx/compositor/layer_animation_element.h" namespace gfx { -class Point; +class Rect; } namespace ui { - class Animation; class Layer; -class LayerAnimatorDelegate; +class LayerAnimationSequence; class Transform; -// LayerAnimator manages animating various properties of a Layer. -class COMPOSITOR_EXPORT LayerAnimator : public ui::AnimationDelegate { +// When a property of layer needs to be changed it is set by way of +// LayerAnimator. This enables LayerAnimator to animate property +// changes. +class COMPOSITOR_EXPORT LayerAnimator : public AnimationContainerElement { public: - // Types of properties that can be animated. - enum AnimationProperty { - LOCATION, - OPACITY, - TRANSFORM, + enum PreemptionStrategy { + IMMEDIATELY_SET_NEW_TARGET, + IMMEDIATELY_ANIMATE_TO_NEW_TARGET, + ENQUEUE_NEW_ANIMATION, + REPLACE_QUEUED_ANIMATIONS, + BLEND_WITH_CURRENT_ANIMATION }; - explicit LayerAnimator(Layer* layer); + explicit LayerAnimator(base::TimeDelta transition_duration); virtual ~LayerAnimator(); - // Sets the animation to use. LayerAnimator takes ownership of the animation. - void SetAnimation(Animation* animation); + // No implicit animations when properties are set. + static LayerAnimator* CreateDefaultAnimator(); + + // Implicitly animates when properties are set. + static LayerAnimator* CreateImplicitAnimator(); + + // Sets the transform on the delegate. May cause an implicit animation. + virtual void SetTransform(const Transform& transform); + + // Sets the bounds on the delegate. May cause an implicit animation. + virtual void SetBounds(const gfx::Rect& bounds); + + // Sets the opacity on the delegate. May cause an implicit animation. + virtual void SetOpacity(float opacity); + + // Sets the layer animation delegate the animator is associated with. The + // animator does not own the delegate. + void SetDelegate(LayerAnimationDelegate* delegate); - ui::Layer* layer() { return layer_; } + // Sets the animation preemption strategy. This determines the behaviour if + // a property is set during an animation. The default is + // IMMEDIATELY_SET_NEW_TARGET (see ImmediatelySetNewTarget below). + void set_preemption_strategy(PreemptionStrategy strategy) { + preemption_strategy_ = strategy; + } - // Animates the layer to the specified point. The point is relative to the - // parent layer. - void AnimateToPoint(const gfx::Point& target); + // Start an animation sequence. If an animation for the same property is in + // progress, it needs to be interrupted with the new animation. The animator + // takes ownership of this animation sequence. + void StartAnimation(LayerAnimationSequence* animation); - // Animates the transform from the current transform to |transform|. - void AnimateTransform(const Transform& transform); + // Schedule an animation to be run when possible. The animator takes ownership + // of this animation sequence. + void ScheduleAnimation(LayerAnimationSequence* animation); - // Animates the opacity from the current opacity to |target_opacity|. - void AnimateOpacity(float target_opacity); + // Schedules the animations to be run together. Obviously will no work if + // they animate any common properties. The animator takes ownership of the + // animation sequences. + void ScheduleTogether(const std::vector<LayerAnimationSequence*>& animations); - // Returns the target value for the specified type. If the specified property - // is not animating, the current value is returned. - gfx::Point GetTargetPoint(); - float GetTargetOpacity(); - ui::Transform GetTargetTransform(); + // Returns true if there is an animation in the queue (animations remain in + // the queue until they complete). + bool is_animating() const { return !animation_queue_.empty(); } - // Returns true if animating |property|. - bool IsAnimating(AnimationProperty property) const; + // Stops animating the given property. No effect if there is no running + // animation for the given property. Skips to the final state of the + // animation. + void StopAnimatingProperty( + LayerAnimationElement::AnimatableProperty property); - // Returns true if the animation is running. - bool IsRunning() const; + // Stops all animation and clears any queued animations. + void StopAnimating(); - // Returns true if the animation has progressed at least once since - // SetAnimation() was invoked. - bool got_initial_tick() const { return got_initial_tick_; } + // For testing purposes only. + void set_disable_timer_for_test(bool enabled) { + disable_timer_for_test_ = enabled; + } + base::TimeTicks get_last_step_time_for_test() { return last_step_time_; } - // AnimationDelegate: - virtual void AnimationProgressed(const Animation* animation) OVERRIDE; - virtual void AnimationEnded(const Animation* animation) OVERRIDE; + protected: + LayerAnimationDelegate* delegate() { return delegate_; } private: - // Parameters used when animating the location. - struct LocationParams { - int start_x; - int start_y; - int target_x; - int target_y; + // We need to keep track of the start time of every running animation. + struct RunningAnimation { + RunningAnimation(LayerAnimationSequence* sequence, + base::TimeTicks start_time) + : sequence(sequence), + start_time(start_time) { + } + LayerAnimationSequence* sequence; + base::TimeTicks start_time; }; - // Parameters used when animating the transform. - struct TransformParams { - SkMScalar start[16]; - SkMScalar target[16]; - }; + typedef std::vector<RunningAnimation> RunningAnimations; + typedef std::deque<linked_ptr<LayerAnimationSequence> > AnimationQueue; - // Parameters used when animating the opacity. - struct OpacityParams { - float start; - float target; - }; + // Implementation of AnimationContainerElement + virtual void SetStartTime(base::TimeTicks start_time) OVERRIDE; + virtual void Step(base::TimeTicks time_now) OVERRIDE; + virtual base::TimeDelta GetTimerInterval() const OVERRIDE; - union Params { - LocationParams location; - OpacityParams opacity; - TransformParams transform; - }; + // Starts or stops stepping depending on whether thare are running animations. + void UpdateAnimationState(); + + // Removes the sequences from both the running animations and the queue. + void RemoveAnimation(LayerAnimationSequence* sequence); + + // Progresses to the end of the sequence before removing it. + void FinishAnimation(LayerAnimationSequence* sequence); + + // Finishes any running animation with zero duration. + void FinishAnyAnimationWithZeroDuration(); + + // Clears the running animations and the queue. No sequences are progressed. + void ClearAnimations(); + + // Returns the running animation animating the given property, if any. + RunningAnimation* GetRunningAnimation( + LayerAnimationElement::AnimatableProperty property); + + // Checks if the sequence has already been added to the queue and adds it + // to the front if note. + void AddToQueueIfNotPresent(LayerAnimationSequence* sequence); + + // Any running or queued animation that affects a property in common with + // |sequence| is either finished or aborted depending on |abort|. + void RemoveAllAnimationsWithACommonProperty(LayerAnimationSequence* sequence, + bool abort); + + // Preempts a running animation by progressing both the running animation and + // the given sequence to the end. + void ImmediatelySetNewTarget(LayerAnimationSequence* sequence); + + // Preempts by aborting the running animation, and starts the given animation. + void ImmediatelyAnimateToNewTarget(LayerAnimationSequence* sequence); + + // Preempts by adding the new animation to the queue. + void EnqueueNewAnimation(LayerAnimationSequence* sequence); + + // Preempts by wiping out any unstarted animation in the queue and then + // enqueuing this animation. + void ReplaceQueuedAnimations(LayerAnimationSequence* sequence); + + // If there's an animation in the queue that doesn't animate the same property + // as a running animation, or an animation schedule to run before it, start it + // up. Repeat until there are no such animations. + void ProcessQueue(); + + // Attempts to add the sequence to the list of running animations. Returns + // false if there is an animation running that already animates one of the + // properties affected by |sequence|. + bool StartSequenceImmediately(LayerAnimationSequence* sequence); + + // This is the queue of animations to run. + AnimationQueue animation_queue_; - typedef std::map<AnimationProperty, Params> Elements; + // The target of all layer animations. + LayerAnimationDelegate* delegate_; - // Stops animating the specified property. This does not set the property - // being animated to its final value. - void StopAnimating(AnimationProperty property); + // The currently running animations. + RunningAnimations running_animations_; - LayerAnimatorDelegate* delegate(); + // Determines how animations are replaced. + PreemptionStrategy preemption_strategy_; - // The layer. - Layer* layer_; + // The default length of animations. + base::TimeDelta transition_duration_; - // Properties being animated. - Elements elements_; + // Used for coordinating the starting of animations. + base::TimeTicks last_step_time_; - scoped_ptr<ui::Animation> animation_; + // True if we are being stepped by our container. + bool is_started_; - bool got_initial_tick_; + // This prevents the animator from automatically stepping through animations + // and allows for manual stepping. + bool disable_timer_for_test_; DISALLOW_COPY_AND_ASSIGN(LayerAnimator); }; diff --git a/ui/gfx/compositor/layer_animator_unittest.cc b/ui/gfx/compositor/layer_animator_unittest.cc new file mode 100644 index 0000000..6eccb52 --- /dev/null +++ b/ui/gfx/compositor/layer_animator_unittest.cc @@ -0,0 +1,648 @@ +// 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/gfx/compositor/layer_animator.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" +#include "ui/gfx/compositor/layer_animation_element.h" +#include "ui/gfx/compositor/layer_animation_sequence.h" +#include "ui/gfx/compositor/test_utils.h" +#include "ui/gfx/compositor/test_layer_animation_delegate.h" + +namespace ui { + +namespace { + +// Checks that setting a property on an implicit animator causes an animation to +// happen. +TEST(LayerAnimatorTest, ImplicitAnimation) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateImplicitAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + base::TimeTicks now = base::TimeTicks::Now(); + animator->SetOpacity(0.5); + EXPECT_TRUE(animator->is_animating()); + element->Step(now + base::TimeDelta::FromSeconds(1)); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), 0.5); +} + +// Checks that if the animator is a default animator, that implicit animations +// are not started. +TEST(LayerAnimatorTest, NoImplicitAnimation) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + base::TimeTicks now = base::TimeTicks::Now(); + animator->SetOpacity(0.5); + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), 0.5); +} + +// Checks that StopAnimatingProperty stops animation for that property, and also +// skips the stopped animation to the end. +TEST(LayerAnimatorTest, StopAnimatingProperty) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateImplicitAnimator()); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + base::TimeTicks now = base::TimeTicks::Now(); + double target_opacity(0.5); + gfx::Rect target_bounds(0, 0, 50, 50); + animator->SetOpacity(target_opacity); + animator->SetBounds(target_bounds); + animator->StopAnimatingProperty(LayerAnimationElement::OPACITY); + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), 0.5); + animator->StopAnimatingProperty(LayerAnimationElement::BOUNDS); + EXPECT_FALSE(animator->is_animating()); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), target_bounds); +} + +// Checks that multiple running animation for separate properties can be stopped +// simultaneously and that all animations are advanced to their target values. +TEST(LayerAnimatorTest, StopAnimating) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateImplicitAnimator()); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + base::TimeTicks now = base::TimeTicks::Now(); + double target_opacity(0.5); + gfx::Rect target_bounds(0, 0, 50, 50); + animator->SetOpacity(target_opacity); + animator->SetBounds(target_bounds); + EXPECT_TRUE(animator->is_animating()); + animator->StopAnimating(); + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), 0.5); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), target_bounds); +} + +// Schedule an animation that can run immediately. This is the trivial case and +// should result in the animation being started immediately. +TEST(LayerAnimatorTest, ScheduleAnimationThatCanRunImmediately) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); +} + +// Schedule two animations on separate properties. Both animations should +// start immediately and should progress in lock step. +TEST(LayerAnimatorTest, ScheduleTwoAnimationsThatCanRunImmediately) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + gfx::Rect start_bounds, target_bounds, middle_bounds; + start_bounds = target_bounds = middle_bounds = gfx::Rect(0, 0, 50, 50); + start_bounds.set_x(-90); + target_bounds.set_x(90); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + delegate.SetBoundsFromAnimation(start_bounds); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateBoundsElement(target_bounds, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), middle_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), target_bounds); +} + +// Schedule two animations on the same property. In this case, the two +// animations should run one after another. +TEST(LayerAnimatorTest, ScheduleTwoAnimationsOnSameProperty) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); +} + +// Schedule [{o}, {o,b}, {b}] and ensure that {b} doesn't run right away. That +// is, ensure that all animations targetting a particular property are run in +// order. +TEST(LayerAnimatorTest, ScheduleBlockedAnimation) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + gfx::Rect start_bounds, target_bounds, middle_bounds; + start_bounds = target_bounds = middle_bounds = gfx::Rect(0, 0, 50, 50); + start_bounds.set_x(-90); + target_bounds.set_x(90); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + delegate.SetBoundsFromAnimation(start_bounds); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + scoped_ptr<LayerAnimationSequence> bounds_and_opacity( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + bounds_and_opacity->AddElement( + LayerAnimationElement::CreateBoundsElement(target_bounds, delta)); + + animator->ScheduleAnimation(bounds_and_opacity.release()); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateBoundsElement(start_bounds, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(3000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), target_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(4000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); +} + +// Schedule {o} and then schedule {o} and {b} together. In this case, since +// ScheduleTogether is being used, the bounds animation should not start until +// the second opacity animation starts. +TEST(LayerAnimatorTest, ScheduleTogether) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double target_opacity(1.0); + + gfx::Rect start_bounds, target_bounds, middle_bounds; + start_bounds = target_bounds = gfx::Rect(0, 0, 50, 50); + start_bounds.set_x(-90); + target_bounds.set_x(90); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + delegate.SetBoundsFromAnimation(start_bounds); + + animator->ScheduleAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + std::vector<LayerAnimationSequence*> sequences; + sequences.push_back(new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + sequences.push_back(new LayerAnimationSequence( + LayerAnimationElement::CreateBoundsElement(target_bounds, delta))); + + animator->ScheduleTogether(sequences); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), start_bounds); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + CheckApproximatelyEqual(delegate.GetBoundsForAnimation(), target_bounds); +} + +// Start animation (that can run immediately). This is the trivial case (see +// the trival case for ScheduleAnimation). +TEST(LayerAnimatorTest, StartAnimationThatCanRunImmediately) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); +} + +// Preempt by immediately setting new target. +TEST(LayerAnimatorTest, PreemptBySettingNewTarget) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->set_preemption_strategy(LayerAnimator::IMMEDIATELY_SET_NEW_TARGET); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); +} + +// Preempt by animating to new target. +TEST(LayerAnimatorTest, PreemptByImmediatelyAnimatingToNewTarget) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->set_preemption_strategy( + LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), + 0.5 * (start_opacity + middle_opacity)); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1500)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); +} + +// Preempt by enqueuing the new animation. +TEST(LayerAnimatorTest, PreemptEnqueueNewAnimation) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->set_preemption_strategy(LayerAnimator::ENQUEUE_NEW_ANIMATION); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + EXPECT_TRUE(animator->is_animating()); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); +} + +// Start an animation when there are sequences waiting in the queue. In this +// case, all pending and running animations should be finished, and the new +// animation started. +TEST(LayerAnimatorTest, PreemptyByReplacingQueuedAnimations) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double middle_opacity(0.5); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + animator->set_preemption_strategy(LayerAnimator::REPLACE_QUEUED_ANIMATIONS); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(500)); + + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(middle_opacity, delta))); + + // Queue should now have two animations. Starting a third should replace the + // second. + animator->StartAnimation( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta))); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1500)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), middle_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_FALSE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); +} + +// Test that cyclic sequences continue to animate. +TEST(LayerAnimatorTest, CyclicSequences) { + scoped_ptr<LayerAnimator> animator(LayerAnimator::CreateDefaultAnimator()); + AnimationContainerElement* element = animator.get(); + animator->set_disable_timer_for_test(true); + TestLayerAnimationDelegate delegate; + animator->SetDelegate(&delegate); + + double start_opacity(0.0); + double target_opacity(1.0); + + base::TimeDelta delta = base::TimeDelta::FromSeconds(1); + + delegate.SetOpacityFromAnimation(start_opacity); + + scoped_ptr<LayerAnimationSequence> sequence( + new LayerAnimationSequence( + LayerAnimationElement::CreateOpacityElement(target_opacity, delta))); + + sequence->AddElement( + LayerAnimationElement::CreateOpacityElement(start_opacity, delta)); + + sequence->set_is_cyclic(true); + + animator->StartAnimation(sequence.release()); + + base::TimeTicks start_time = animator->get_last_step_time_for_test(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(2000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(3000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + // Skip ahead by a lot. + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000000000000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), start_opacity); + + // Skip ahead by a lot. + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000000001000)); + + EXPECT_TRUE(animator->is_animating()); + EXPECT_FLOAT_EQ(delegate.GetOpacityForAnimation(), target_opacity); + + animator->StopAnimatingProperty(LayerAnimationElement::OPACITY); + + EXPECT_FALSE(animator->is_animating()); +} + +} // namespace + +} // namespace ui diff --git a/ui/gfx/compositor/test_layer_animation_delegate.cc b/ui/gfx/compositor/test_layer_animation_delegate.cc new file mode 100644 index 0000000..5d87ae0 --- /dev/null +++ b/ui/gfx/compositor/test_layer_animation_delegate.cc @@ -0,0 +1,44 @@ +// 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/gfx/compositor/test_layer_animation_delegate.h" + +namespace ui { + +TestLayerAnimationDelegate::TestLayerAnimationDelegate() : opacity_(1.0f) { +} + +TestLayerAnimationDelegate::~TestLayerAnimationDelegate() { +} + +void TestLayerAnimationDelegate::SetBoundsFromAnimation( + const gfx::Rect& bounds) { + bounds_ = bounds; +} + +void TestLayerAnimationDelegate::SetTransformFromAnimation( + const Transform& transform) { + transform_ = transform; +} + +void TestLayerAnimationDelegate::SetOpacityFromAnimation(float opacity) { + opacity_ = opacity; +} + +void TestLayerAnimationDelegate::ScheduleDrawForAnimation() { +} + +const gfx::Rect& TestLayerAnimationDelegate::GetBoundsForAnimation() const { + return bounds_; +} + +const Transform& TestLayerAnimationDelegate::GetTransformForAnimation() const { + return transform_; +} + +float TestLayerAnimationDelegate::GetOpacityForAnimation() const { + return opacity_; +} + +} // namespace ui diff --git a/ui/gfx/compositor/test_layer_animation_delegate.h b/ui/gfx/compositor/test_layer_animation_delegate.h new file mode 100644 index 0000000..046e25c --- /dev/null +++ b/ui/gfx/compositor/test_layer_animation_delegate.h @@ -0,0 +1,40 @@ +// 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_GFX_COMPOSITOR_TEST_LAYER_ANIMATION_DELEGATE_H_ +#define UI_GFX_COMPOSITOR_TEST_LAYER_ANIMATION_DELEGATE_H_ +#pragma once + +#include "base/compiler_specific.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/compositor/layer_animation_delegate.h" + +namespace ui { + +class TestLayerAnimationDelegate : public LayerAnimationDelegate { + public: + TestLayerAnimationDelegate(); + virtual ~TestLayerAnimationDelegate(); + + // Implementation of LayerAnimationDelegate + virtual void SetBoundsFromAnimation(const gfx::Rect& bounds) OVERRIDE; + virtual void SetTransformFromAnimation(const Transform& transform) OVERRIDE; + virtual void SetOpacityFromAnimation(float opacity) OVERRIDE; + virtual void ScheduleDrawForAnimation() OVERRIDE; + virtual const gfx::Rect& GetBoundsForAnimation() const OVERRIDE; + virtual const Transform& GetTransformForAnimation() const OVERRIDE; + virtual float GetOpacityForAnimation() const OVERRIDE; + + private: + gfx::Rect bounds_; + Transform transform_; + float opacity_; + + // Allow copy and assign. +}; + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_TEST_LAYER_ANIMATION_DELEGATE_H_ diff --git a/ui/gfx/compositor/test_utils.cc b/ui/gfx/compositor/test_utils.cc new file mode 100644 index 0000000..867c354 --- /dev/null +++ b/ui/gfx/compositor/test_utils.cc @@ -0,0 +1,28 @@ +// 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/gfx/compositor/test_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" + +namespace ui { + +void CheckApproximatelyEqual(const Transform& lhs, const Transform& rhs) { + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + EXPECT_FLOAT_EQ(lhs.matrix().get(i, j), rhs.matrix().get(i, j)); + } + } +} + +void CheckApproximatelyEqual(const gfx::Rect& lhs, const gfx::Rect& rhs) { + EXPECT_FLOAT_EQ(lhs.x(), rhs.x()); + EXPECT_FLOAT_EQ(lhs.y(), rhs.y()); + EXPECT_FLOAT_EQ(lhs.width(), rhs.width()); + EXPECT_FLOAT_EQ(lhs.height(), rhs.height()); +} + +} // namespace ui diff --git a/ui/gfx/compositor/test_utils.h b/ui/gfx/compositor/test_utils.h new file mode 100644 index 0000000..9c4411c --- /dev/null +++ b/ui/gfx/compositor/test_utils.h @@ -0,0 +1,22 @@ +// 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_GFX_COMPOSITOR_TEST_UTILS_H_ +#define UI_GFX_COMPOSITOR_TEST_UTILS_H_ +#pragma once + +namespace gfx { +class Rect; +} + +namespace ui { + +class Transform; + +void CheckApproximatelyEqual(const Transform& lhs, const Transform& rhs); +void CheckApproximatelyEqual(const gfx::Rect& lhs, const gfx::Rect& rhs); + +} // namespace ui + +#endif // UI_GFX_COMPOSITOR_TEST_UTILS_H_ |