summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorvollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-24 14:44:19 +0000
committervollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-24 14:44:19 +0000
commitb4db93705d370c3e7d7964c114a6ac51bea635f0 (patch)
treed9d6509e212e5c97278225588984f396704545be /ui
parent3c521f7ca5d09cba6153967e61e6327df0204b02 (diff)
downloadchromium_src-b4db93705d370c3e7d7964c114a6ac51bea635f0.zip
chromium_src-b4db93705d370c3e7d7964c114a6ac51bea635f0.tar.gz
chromium_src-b4db93705d370c3e7d7964c114a6ac51bea635f0.tar.bz2
Explicit animation support
High level description: - LayerPropertySetter is now LayerAnimator since it manages implicit/explicit animations and the animation queue. - LayerAnimationElement represents an animation curve. - LayerAnimationSequence owns a collection of elements. - The animator works as follows: o Has a queue of sequences and a collection of running sequences. o It knows the start time of each running sequence. o While there are running sequences, LayerAnimator::Step(base::TimeTicks now) is called periodically, and each of the running sequences are updated. BUG=None TEST=compositor_unittests, base_unittests Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=106768 Review URL: http://codereview.chromium.org/8247009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106915 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/base/animation/tween.cc43
-rw-r--r--ui/base/animation/tween.h12
-rw-r--r--ui/gfx/compositor/compositor.gyp15
-rw-r--r--ui/gfx/compositor/layer.cc24
-rw-r--r--ui/gfx/compositor/layer.h7
-rw-r--r--ui/gfx/compositor/layer_animation_delegate.h32
-rw-r--r--ui/gfx/compositor/layer_animation_element.cc190
-rw-r--r--ui/gfx/compositor/layer_animation_element.h97
-rw-r--r--ui/gfx/compositor/layer_animation_element_unittest.cc131
-rw-r--r--ui/gfx/compositor/layer_animation_manager.cc187
-rw-r--r--ui/gfx/compositor/layer_animation_manager.h129
-rw-r--r--ui/gfx/compositor/layer_animation_sequence.cc101
-rw-r--r--ui/gfx/compositor/layer_animation_sequence.h90
-rw-r--r--ui/gfx/compositor/layer_animation_sequence_unittest.cc158
-rw-r--r--ui/gfx/compositor/layer_animator.cc499
-rw-r--r--ui/gfx/compositor/layer_animator.h229
-rw-r--r--ui/gfx/compositor/layer_animator_unittest.cc648
-rw-r--r--ui/gfx/compositor/test_layer_animation_delegate.cc44
-rw-r--r--ui/gfx/compositor/test_layer_animation_delegate.h40
-rw-r--r--ui/gfx/compositor/test_utils.cc28
-rw-r--r--ui/gfx/compositor/test_utils.h22
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_