summaryrefslogtreecommitdiffstats
path: root/cc/animation
diff options
context:
space:
mode:
authorjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 07:09:09 +0000
committerjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 07:09:09 +0000
commit95e4e1a0fda6929b47702e69e4ddfe384b5d014b (patch)
tree1cf6fc6d2d063aac4a14388de969a5214e2e9243 /cc/animation
parenta2f3755a9e22ced917b1dda50c57e4e912594e9d (diff)
downloadchromium_src-95e4e1a0fda6929b47702e69e4ddfe384b5d014b.zip
chromium_src-95e4e1a0fda6929b47702e69e4ddfe384b5d014b.tar.gz
chromium_src-95e4e1a0fda6929b47702e69e4ddfe384b5d014b.tar.bz2
Part 3 of cc/ directory shuffles: animation
Continuation of https://src.chromium.org/viewvc/chrome?view=rev&revision=188681 BUG=190824 TBR=enne@chromium.org, vollick@chromium.org Review URL: https://codereview.chromium.org/12822004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188688 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/animation')
-rw-r--r--cc/animation/animation.cc236
-rw-r--r--cc/animation/animation.h190
-rw-r--r--cc/animation/animation_curve.cc30
-rw-r--r--cc/animation/animation_curve.h56
-rw-r--r--cc/animation/animation_events.h43
-rw-r--r--cc/animation/animation_id_provider.cc19
-rw-r--r--cc/animation/animation_id_provider.h21
-rw-r--r--cc/animation/animation_registrar.cc51
-rw-r--r--cc/animation/animation_registrar.h65
-rw-r--r--cc/animation/animation_unittest.cc205
-rw-r--r--cc/animation/keyframed_animation_curve.cc200
-rw-r--r--cc/animation/keyframed_animation_curve.h129
-rw-r--r--cc/animation/keyframed_animation_curve_unittest.cc243
-rw-r--r--cc/animation/layer_animation_controller.cc628
-rw-r--r--cc/animation/layer_animation_controller.h154
-rw-r--r--cc/animation/layer_animation_controller_unittest.cc901
-rw-r--r--cc/animation/layer_animation_event_observer.h18
-rw-r--r--cc/animation/layer_animation_value_observer.h23
-rw-r--r--cc/animation/scrollbar_animation_controller.h32
-rw-r--r--cc/animation/scrollbar_animation_controller_linear_fade.cc94
-rw-r--r--cc/animation/scrollbar_animation_controller_linear_fade.h50
-rw-r--r--cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc168
22 files changed, 3556 insertions, 0 deletions
diff --git a/cc/animation/animation.cc b/cc/animation/animation.cc
new file mode 100644
index 0000000..d81713b
--- /dev/null
+++ b/cc/animation/animation.cc
@@ -0,0 +1,236 @@
+// Copyright 2012 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 "cc/animation/animation.h"
+
+#include <cmath>
+
+#include "base/debug/trace_event.h"
+#include "base/string_util.h"
+#include "cc/animation/animation_curve.h"
+
+namespace {
+
+// This should match the RunState enum.
+static const char* const s_runStateNames[] = {
+ "WaitingForNextTick",
+ "WaitingForTargetAvailability",
+ "WaitingForStartTime",
+ "WaitingForDeletion",
+ "Starting",
+ "Running",
+ "Paused",
+ "Finished",
+ "Aborted"
+};
+
+COMPILE_ASSERT(static_cast<int>(cc::Animation::RunStateEnumSize) ==
+ arraysize(s_runStateNames),
+ RunState_names_match_enum);
+
+// This should match the TargetProperty enum.
+static const char* const s_targetPropertyNames[] = {
+ "Transform",
+ "Opacity"
+};
+
+COMPILE_ASSERT(static_cast<int>(cc::Animation::TargetPropertyEnumSize) ==
+ arraysize(s_targetPropertyNames),
+ TargetProperty_names_match_enum);
+
+} // namespace
+
+namespace cc {
+
+scoped_ptr<Animation> Animation::Create(
+ scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property) {
+ return make_scoped_ptr(new Animation(curve.Pass(),
+ animation_id,
+ group_id,
+ target_property)); }
+
+Animation::Animation(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property)
+ : curve_(curve.Pass()),
+ id_(animation_id),
+ group_(group_id),
+ target_property_(target_property),
+ run_state_(WaitingForTargetAvailability),
+ iterations_(1),
+ start_time_(0),
+ alternates_direction_(false),
+ time_offset_(0),
+ needs_synchronized_start_time_(false),
+ suspended_(false),
+ pause_time_(0),
+ total_paused_time_(0),
+ is_controlling_instance_(false),
+ is_impl_only_(false) {}
+
+Animation::~Animation() {
+ if (run_state_ == Running || run_state_ == Paused)
+ SetRunState(Aborted, 0);
+}
+
+void Animation::SetRunState(RunState run_state, double monotonic_time) {
+ if (suspended_)
+ return;
+
+ char nameBuffer[256];
+ base::snprintf(nameBuffer,
+ sizeof(nameBuffer),
+ "%s-%d%s",
+ s_targetPropertyNames[target_property_],
+ group_,
+ is_controlling_instance_ ? "(impl)" : "");
+
+ bool is_waiting_to_start = run_state_ == WaitingForNextTick ||
+ run_state_ == WaitingForTargetAvailability ||
+ run_state_ == WaitingForStartTime ||
+ run_state_ == Starting;
+
+ if (is_waiting_to_start && run_state == Running) {
+ TRACE_EVENT_ASYNC_BEGIN1(
+ "cc", "Animation", this, "Name", TRACE_STR_COPY(nameBuffer));
+ }
+
+ bool was_finished = is_finished();
+
+ const char* old_run_state_name = s_runStateNames[run_state_];
+
+ if (run_state == Running && run_state_ == Paused)
+ total_paused_time_ += monotonic_time - pause_time_;
+ else if (run_state == Paused)
+ pause_time_ = monotonic_time;
+ run_state_ = run_state;
+
+ const char* new_run_state_name = s_runStateNames[run_state];
+
+ if (!was_finished && is_finished())
+ TRACE_EVENT_ASYNC_END0("cc", "Animation", this);
+
+ char stateBuffer[256];
+ base::snprintf(stateBuffer,
+ sizeof(stateBuffer),
+ "%s->%s",
+ old_run_state_name,
+ new_run_state_name);
+
+ TRACE_EVENT_INSTANT2("cc",
+ "LayerAnimationController::setRunState",
+ "Name",
+ TRACE_STR_COPY(nameBuffer),
+ "State",
+ TRACE_STR_COPY(stateBuffer));
+}
+
+void Animation::Suspend(double monotonic_time) {
+ SetRunState(Paused, monotonic_time);
+ suspended_ = true;
+}
+
+void Animation::Resume(double monotonic_time) {
+ suspended_ = false;
+ SetRunState(Running, monotonic_time);
+}
+
+bool Animation::IsFinishedAt(double monotonic_time) const {
+ if (is_finished())
+ return true;
+
+ if (needs_synchronized_start_time_)
+ return false;
+
+ return run_state_ == Running &&
+ iterations_ >= 0 &&
+ iterations_ * curve_->Duration() <= (monotonic_time -
+ start_time() -
+ total_paused_time_);
+}
+
+double Animation::TrimTimeToCurrentIteration(double monotonic_time) const {
+ double trimmed = monotonic_time + time_offset_;
+
+ // If we're paused, time is 'stuck' at the pause time.
+ if (run_state_ == Paused)
+ trimmed = pause_time_;
+
+ // Returned time should always be relative to the start time and should
+ // subtract all time spent paused.
+ trimmed -= start_time_ + total_paused_time_;
+
+ // Zero is always the start of the animation.
+ if (trimmed <= 0)
+ return 0;
+
+ // Always return zero if we have no iterations.
+ if (!iterations_)
+ return 0;
+
+ // Don't attempt to trim if we have no duration.
+ if (curve_->Duration() <= 0)
+ return 0;
+
+ // If less than an iteration duration, just return trimmed.
+ if (trimmed < curve_->Duration())
+ return trimmed;
+
+ // If greater than or equal to the total duration, return iteration duration.
+ if (iterations_ >= 0 && trimmed >= curve_->Duration() * iterations_) {
+ if (alternates_direction_ && !(iterations_ % 2))
+ return 0;
+ return curve_->Duration();
+ }
+
+ // We need to know the current iteration if we're alternating.
+ int iteration = static_cast<int>(trimmed / curve_->Duration());
+
+ // Calculate x where trimmed = x + n * curve_->Duration() for some positive
+ // integer n.
+ trimmed = fmod(trimmed, curve_->Duration());
+
+ // If we're alternating and on an odd iteration, reverse the direction.
+ if (alternates_direction_ && iteration % 2 == 1)
+ return curve_->Duration() - trimmed;
+
+ return trimmed;
+}
+
+scoped_ptr<Animation> Animation::Clone(InstanceType instance_type) const {
+ return CloneAndInitialize(instance_type, run_state_, start_time_);
+}
+
+scoped_ptr<Animation> Animation::CloneAndInitialize(InstanceType instance_type,
+ RunState initial_run_state,
+ double start_time) const {
+ scoped_ptr<Animation> to_return(
+ new Animation(curve_->Clone(), id_, group_, target_property_));
+ to_return->run_state_ = initial_run_state;
+ to_return->iterations_ = iterations_;
+ to_return->start_time_ = start_time;
+ to_return->pause_time_ = pause_time_;
+ to_return->total_paused_time_ = total_paused_time_;
+ to_return->time_offset_ = time_offset_;
+ to_return->alternates_direction_ = alternates_direction_;
+ to_return->is_controlling_instance_ = instance_type == ControllingInstance;
+ return to_return.Pass();
+}
+
+void Animation::PushPropertiesTo(Animation* other) const {
+ // Currently, we only push changes due to pausing and resuming animations on
+ // the main thread.
+ if (run_state_ == Animation::Paused ||
+ other->run_state_ == Animation::Paused) {
+ other->run_state_ = run_state_;
+ other->pause_time_ = pause_time_;
+ other->total_paused_time_ = total_paused_time_;
+ }
+}
+
+} // namespace cc
diff --git a/cc/animation/animation.h b/cc/animation/animation.h
new file mode 100644
index 0000000..f1692ad
--- /dev/null
+++ b/cc/animation/animation.h
@@ -0,0 +1,190 @@
+// Copyright 2012 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 CC_ANIMATION_ANIMATION_H_
+#define CC_ANIMATION_ANIMATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class AnimationCurve;
+
+// An Animation, contains all the state required to play an AnimationCurve.
+// Specifically, the affected property, the run state (paused, finished, etc.),
+// loop count, last pause time, and the total time spent paused.
+class CC_EXPORT Animation {
+ public:
+ // Animations begin in one of the 'waiting' states. Animations waiting for the
+ // next tick will start the next time the controller animates. Animations
+ // waiting for target availibility will run as soon as their target property
+ // is free (and all the animations animating with it are also able to run).
+ // Animations waiting for their start time to come have be scheduled to run at
+ // a particular point in time. When this time arrives, the controller will
+ // move the animations into the Starting state, and then into the Running
+ // state. Running animations may toggle between Running and Paused, and may be
+ // stopped by moving into either the Aborted or Finished states. A Finished
+ // animation was allowed to run to completion, but an Aborted animation was
+ // not.
+ enum RunState {
+ WaitingForNextTick = 0,
+ WaitingForTargetAvailability,
+ WaitingForStartTime,
+ WaitingForDeletion,
+ Starting,
+ Running,
+ Paused,
+ Finished,
+ Aborted,
+ // This sentinel must be last.
+ RunStateEnumSize
+ };
+
+ enum TargetProperty {
+ Transform = 0,
+ Opacity,
+ // This sentinel must be last.
+ TargetPropertyEnumSize
+ };
+
+ static scoped_ptr<Animation> Create(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property);
+
+ virtual ~Animation();
+
+ int id() const { return id_; }
+ int group() const { return group_; }
+ TargetProperty target_property() const { return target_property_; }
+
+ RunState run_state() const { return run_state_; }
+ void SetRunState(RunState run_state, double monotonic_time);
+
+ // This is the number of times that the animation will play. If this
+ // value is zero the animation will not play. If it is negative, then
+ // the animation will loop indefinitely.
+ int iterations() const { return iterations_; }
+ void set_iterations(int n) { iterations_ = n; }
+
+ double start_time() const { return start_time_; }
+ void set_start_time(double monotonic_time) { start_time_ = monotonic_time; }
+ bool has_set_start_time() const { return !!start_time_; }
+
+ double time_offset() const { return time_offset_; }
+ void set_time_offset(double monotonic_time) { time_offset_ = monotonic_time; }
+
+ void Suspend(double monotonic_time);
+ void Resume(double monotonic_time);
+
+ // If alternatesDirection is true, on odd numbered iterations we reverse the
+ // curve.
+ bool alternates_direction() const { return alternates_direction_; }
+ void set_alternates_direction(bool alternates) {
+ alternates_direction_ = alternates;
+ }
+
+ bool IsFinishedAt(double monotonic_time) const;
+ bool is_finished() const {
+ return run_state_ == Finished ||
+ run_state_ == Aborted ||
+ run_state_ == WaitingForDeletion;
+ }
+
+ AnimationCurve* curve() { return curve_.get(); }
+ const AnimationCurve* curve() const { return curve_.get(); }
+
+ // If this is true, even if the animation is running, it will not be tickable
+ // until it is given a start time. This is true for animations running on the
+ // main thread.
+ bool needs_synchronized_start_time() const {
+ return needs_synchronized_start_time_;
+ }
+ void set_needs_synchronized_start_time(bool needs_synchronized_start_time) {
+ needs_synchronized_start_time_ = needs_synchronized_start_time;
+ }
+
+ // Takes the given absolute time, and using the start time and the number
+ // of iterations, returns the relative time in the current iteration.
+ double TrimTimeToCurrentIteration(double monotonic_time) const;
+
+ enum InstanceType {
+ ControllingInstance = 0,
+ NonControllingInstance
+ };
+
+ scoped_ptr<Animation> Clone(InstanceType instance_type) const;
+ scoped_ptr<Animation> CloneAndInitialize(InstanceType instance_type,
+ RunState initial_run_state,
+ double start_time) const;
+ bool is_controlling_instance() const { return is_controlling_instance_; }
+
+ void PushPropertiesTo(Animation* other) const;
+
+ void set_is_impl_only(bool is_impl_only) { is_impl_only_ = is_impl_only; }
+ bool is_impl_only() const { return is_impl_only_; }
+
+ private:
+ Animation(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property);
+
+ scoped_ptr<AnimationCurve> curve_;
+
+ // IDs are not necessarily unique.
+ int id_;
+
+ // Animations that must be run together are called 'grouped' and have the same
+ // group id. Grouped animations are guaranteed to start at the same time and
+ // no other animations may animate any of the group's target properties until
+ // all animations in the group have finished animating. Note: an active
+ // animation's group id and target property uniquely identify that animation.
+ int group_;
+
+ TargetProperty target_property_;
+ RunState run_state_;
+ int iterations_;
+ double start_time_;
+ bool alternates_direction_;
+
+ // The time offset effectively pushes the start of the animation back in time.
+ // This is used for resuming paused animations -- an animation is added with a
+ // non-zero time offset, causing the animation to skip ahead to the desired
+ // point in time.
+ double time_offset_;
+
+ bool needs_synchronized_start_time_;
+
+ // When an animation is suspended, it behaves as if it is paused and it also
+ // ignores all run state changes until it is resumed. This is used for testing
+ // purposes.
+ bool suspended_;
+
+ // These are used in trimTimeToCurrentIteration to account for time
+ // spent while paused. This is not included in AnimationState since it
+ // there is absolutely no need for clients of this controller to know
+ // about these values.
+ double pause_time_;
+ double total_paused_time_;
+
+ // Animations lead dual lives. An active animation will be conceptually owned
+ // by two controllers, one on the impl thread and one on the main. In reality,
+ // there will be two separate Animation instances for the same animation. They
+ // will have the same group id and the same target property (these two values
+ // uniquely identify an animation). The instance on the impl thread is the
+ // instance that ultimately controls the values of the animating layer and so
+ // we will refer to it as the 'controlling instance'.
+ bool is_controlling_instance_;
+
+ bool is_impl_only_;
+
+ DISALLOW_COPY_AND_ASSIGN(Animation);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_H_
diff --git a/cc/animation/animation_curve.cc b/cc/animation/animation_curve.cc
new file mode 100644
index 0000000..05cda9b
--- /dev/null
+++ b/cc/animation/animation_curve.cc
@@ -0,0 +1,30 @@
+// Copyright 2012 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 "cc/animation/animation_curve.h"
+
+#include "base/logging.h"
+
+namespace cc {
+
+const FloatAnimationCurve* AnimationCurve::ToFloatAnimationCurve() const {
+ DCHECK(Type() == AnimationCurve::Float);
+ return static_cast<const FloatAnimationCurve*>(this);
+}
+
+AnimationCurve::CurveType FloatAnimationCurve::Type() const {
+ return Float;
+}
+
+const TransformAnimationCurve* AnimationCurve::ToTransformAnimationCurve()
+ const {
+ DCHECK(Type() == AnimationCurve::Transform);
+ return static_cast<const TransformAnimationCurve*>(this);
+}
+
+AnimationCurve::CurveType TransformAnimationCurve::Type() const {
+ return Transform;
+}
+
+} // namespace cc
diff --git a/cc/animation/animation_curve.h b/cc/animation/animation_curve.h
new file mode 100644
index 0000000..13f95b4
--- /dev/null
+++ b/cc/animation/animation_curve.h
@@ -0,0 +1,56 @@
+// Copyright 2012 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 CC_ANIMATION_ANIMATION_CURVE_H_
+#define CC_ANIMATION_ANIMATION_CURVE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class FloatAnimationCurve;
+class TransformAnimationCurve;
+class TransformOperations;
+
+// An animation curve is a function that returns a value given a time.
+// There are currently only two types of curve, float and transform.
+class CC_EXPORT AnimationCurve {
+ public:
+ enum CurveType { Float, Transform };
+
+ virtual ~AnimationCurve() {}
+
+ virtual double Duration() const = 0;
+ virtual CurveType Type() const = 0;
+ virtual scoped_ptr<AnimationCurve> Clone() const = 0;
+
+ const FloatAnimationCurve* ToFloatAnimationCurve() const;
+ const TransformAnimationCurve* ToTransformAnimationCurve() const;
+};
+
+class CC_EXPORT FloatAnimationCurve : public AnimationCurve {
+ public:
+ virtual ~FloatAnimationCurve() {}
+
+ virtual float GetValue(double t) const = 0;
+
+ // Partial Animation implementation.
+ virtual CurveType Type() const OVERRIDE;
+};
+
+class CC_EXPORT TransformAnimationCurve : public AnimationCurve {
+ public:
+ virtual ~TransformAnimationCurve() {}
+
+ virtual gfx::Transform GetValue(double t) const = 0;
+
+ // Partial Animation implementation.
+ virtual CurveType Type() const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_CURVE_H_
diff --git a/cc/animation/animation_events.h b/cc/animation/animation_events.h
new file mode 100644
index 0000000..b279b4b
--- /dev/null
+++ b/cc/animation/animation_events.h
@@ -0,0 +1,43 @@
+// Copyright 2012 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 CC_ANIMATION_ANIMATION_EVENTS_H_
+#define CC_ANIMATION_ANIMATION_EVENTS_H_
+
+#include <vector>
+
+#include "cc/animation/animation.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+struct AnimationEvent {
+ enum Type { Started, Finished, PropertyUpdate };
+
+ AnimationEvent(Type type,
+ int layer_id,
+ int group_id,
+ Animation::TargetProperty target_property,
+ double monotonic_time)
+ : type(type),
+ layer_id(layer_id),
+ group_id(group_id),
+ target_property(target_property),
+ monotonic_time(monotonic_time),
+ opacity(0.f) {}
+
+ Type type;
+ int layer_id;
+ int group_id;
+ Animation::TargetProperty target_property;
+ double monotonic_time;
+ float opacity;
+ gfx::Transform transform;
+};
+
+typedef std::vector<AnimationEvent> AnimationEventsVector;
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_EVENTS_H_
diff --git a/cc/animation/animation_id_provider.cc b/cc/animation/animation_id_provider.cc
new file mode 100644
index 0000000..5c7afb6
--- /dev/null
+++ b/cc/animation/animation_id_provider.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 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 "cc/animation/animation_id_provider.h"
+
+namespace cc {
+
+int AnimationIdProvider::NextAnimationId() {
+ static int next_animation_id = 1;
+ return next_animation_id++;
+}
+
+int AnimationIdProvider::NextGroupId() {
+ static int next_group_id = 1;
+ return next_group_id++;
+}
+
+} // namespace cc
diff --git a/cc/animation/animation_id_provider.h b/cc/animation/animation_id_provider.h
new file mode 100644
index 0000000..da08c92
--- /dev/null
+++ b/cc/animation/animation_id_provider.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 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 CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
+#define CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT AnimationIdProvider {
+ public:
+ // These functions each return monotonically increasing values.
+ static int NextAnimationId();
+ static int NextGroupId();
+};
+
+}
+
+#endif // CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
diff --git a/cc/animation/animation_registrar.cc b/cc/animation/animation_registrar.cc
new file mode 100644
index 0000000..948a582
--- /dev/null
+++ b/cc/animation/animation_registrar.cc
@@ -0,0 +1,51 @@
+// Copyright 2012 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 "cc/animation/animation_registrar.h"
+
+#include "cc/animation/layer_animation_controller.h"
+
+namespace cc {
+
+AnimationRegistrar::AnimationRegistrar() { }
+AnimationRegistrar::~AnimationRegistrar()
+{
+ AnimationControllerMap copy = all_animation_controllers_;
+ for (AnimationControllerMap::iterator iter = copy.begin(); iter != copy.end(); ++iter)
+ (*iter).second->SetAnimationRegistrar(NULL);
+}
+
+scoped_refptr<LayerAnimationController>
+AnimationRegistrar::GetAnimationControllerForId(int id)
+{
+ scoped_refptr<LayerAnimationController> toReturn;
+ if (!ContainsKey(all_animation_controllers_, id)) {
+ toReturn = LayerAnimationController::Create(id);
+ toReturn->SetAnimationRegistrar(this);
+ all_animation_controllers_[id] = toReturn.get();
+ } else
+ toReturn = all_animation_controllers_[id];
+ return toReturn;
+}
+
+void AnimationRegistrar::DidActivateAnimationController(LayerAnimationController* controller) {
+ active_animation_controllers_[controller->id()] = controller;
+}
+
+void AnimationRegistrar::DidDeactivateAnimationController(LayerAnimationController* controller) {
+ if (ContainsKey(active_animation_controllers_, controller->id()))
+ active_animation_controllers_.erase(controller->id());
+}
+
+void AnimationRegistrar::RegisterAnimationController(LayerAnimationController* controller) {
+ all_animation_controllers_[controller->id()] = controller;
+}
+
+void AnimationRegistrar::UnregisterAnimationController(LayerAnimationController* controller) {
+ if (ContainsKey(all_animation_controllers_, controller->id()))
+ all_animation_controllers_.erase(controller->id());
+ DidDeactivateAnimationController(controller);
+}
+
+} // namespace cc
diff --git a/cc/animation/animation_registrar.h b/cc/animation/animation_registrar.h
new file mode 100644
index 0000000..5d1eb44
--- /dev/null
+++ b/cc/animation/animation_registrar.h
@@ -0,0 +1,65 @@
+// Copyright 2012 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 CC_ANIMATION_ANIMATION_REGISTRAR_H_
+#define CC_ANIMATION_ANIMATION_REGISTRAR_H_
+
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class LayerAnimationController;
+
+class CC_EXPORT AnimationRegistrar {
+ public:
+ typedef base::hash_map<int, LayerAnimationController*> AnimationControllerMap;
+
+ static scoped_ptr<AnimationRegistrar> create() {
+ return make_scoped_ptr(new AnimationRegistrar());
+ }
+
+ virtual ~AnimationRegistrar();
+
+ // If an animation has been registered for the given id, return it. Otherwise
+ // creates a new one and returns a scoped_refptr to it.
+ scoped_refptr<LayerAnimationController> GetAnimationControllerForId(int id);
+
+ // Registers the given animation controller as active. An active animation
+ // controller is one that has a running animation that needs to be ticked.
+ void DidActivateAnimationController(LayerAnimationController*);
+
+ // Unregisters the given animation controller. When this happens, the
+ // animation controller will no longer be ticked (since it's not active). It
+ // is not an error to call this function with a deactivated controller.
+ void DidDeactivateAnimationController(LayerAnimationController*);
+
+ // Registers the given controller as alive.
+ void RegisterAnimationController(LayerAnimationController*);
+
+ // Unregisters the given controller as alive.
+ void UnregisterAnimationController(LayerAnimationController*);
+
+ const AnimationControllerMap& active_animation_controllers() const {
+ return active_animation_controllers_;
+ }
+
+ const AnimationControllerMap& all_animation_controllers() const {
+ return all_animation_controllers_;
+ }
+
+ private:
+ AnimationRegistrar();
+
+ AnimationControllerMap active_animation_controllers_;
+ AnimationControllerMap all_animation_controllers_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationRegistrar);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_REGISTRAR_H_
diff --git a/cc/animation/animation_unittest.cc b/cc/animation/animation_unittest.cc
new file mode 100644
index 0000000..337d3ee
--- /dev/null
+++ b/cc/animation/animation_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2012 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 "cc/animation/animation.h"
+
+#include "cc/test/animation_test_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+scoped_ptr<Animation> CreateAnimation(int iterations, double duration) {
+ scoped_ptr<Animation> to_return(Animation::Create(
+ make_scoped_ptr(
+ new FakeFloatAnimationCurve(duration)).PassAs<AnimationCurve>(),
+ 0,
+ 1,
+ Animation::Opacity));
+ to_return->set_iterations(iterations);
+ return to_return.Pass();
+}
+
+scoped_ptr<Animation> CreateAnimation(int iterations) {
+ return CreateAnimation(iterations, 1);
+}
+
+TEST(AnimationTest, TrimTimeZeroIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, TrimTimeOneIteration) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(2.0));
+}
+
+TEST(AnimationTest, TrimTimeInfiniteIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1.5));
+}
+
+TEST(AnimationTest, TrimTimeAlternating) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ anim->set_alternates_direction(true);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(0.75, anim->TrimTimeToCurrentIteration(1.25));
+}
+
+TEST(AnimationTest, TrimTimeStartTime) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->set_start_time(4);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(4.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(4.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(5.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(6.0));
+}
+
+TEST(AnimationTest, TrimTimeTimeOffset) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->set_time_offset(4);
+ anim->set_start_time(4);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, TrimTimePauseResume) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ anim->SetRunState(Animation::Paused, 0.5);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ anim->SetRunState(Animation::Running, 1024.0);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1024.5));
+}
+
+TEST(AnimationTest, TrimTimeSuspendResume) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ anim->Suspend(0.5);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ anim->Resume(1024);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1024.5));
+}
+
+TEST(AnimationTest, TrimTimeZeroDuration) {
+ scoped_ptr<Animation> anim(CreateAnimation(0, 0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, IsFinishedAtZeroIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(-1.0));
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ EXPECT_TRUE(anim->IsFinishedAt(1.0));
+}
+
+TEST(AnimationTest, IsFinishedAtOneIteration) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(-1.0));
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ EXPECT_TRUE(anim->IsFinishedAt(1.0));
+ EXPECT_TRUE(anim->IsFinishedAt(2.0));
+}
+
+TEST(AnimationTest, IsFinishedAtInfiniteIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ EXPECT_FALSE(anim->IsFinishedAt(0.5));
+ EXPECT_FALSE(anim->IsFinishedAt(1.0));
+ EXPECT_FALSE(anim->IsFinishedAt(1.5));
+}
+
+TEST(AnimationTest, IsFinishedAtNotRunning) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Paused, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForNextTick, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForStartTime, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+}
+
+TEST(AnimationTest, IsFinished) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Paused, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForNextTick, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForStartTime, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+}
+
+TEST(AnimationTest, IsFinishedNeedsSynchronizedStartTime) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Paused, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForNextTick, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForStartTime, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+}
+
+TEST(AnimationTest, RunStateChangesIgnoredWhileSuspended) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->Suspend(0);
+ EXPECT_EQ(Animation::Paused, anim->run_state());
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(Animation::Paused, anim->run_state());
+ anim->Resume(0);
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(Animation::Running, anim->run_state());
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/animation/keyframed_animation_curve.cc b/cc/animation/keyframed_animation_curve.cc
new file mode 100644
index 0000000..577737d
--- /dev/null
+++ b/cc/animation/keyframed_animation_curve.cc
@@ -0,0 +1,200 @@
+// Copyright 2012 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 "cc/animation/keyframed_animation_curve.h"
+
+namespace cc {
+
+namespace {
+
+template <class Keyframe>
+void InsertKeyframe(scoped_ptr<Keyframe> keyframe,
+ ScopedPtrVector<Keyframe>& keyframes) {
+ // Usually, the keyframes will be added in order, so this loop would be
+ // unnecessary and we should skip it if possible.
+ if (!keyframes.empty() && keyframe->Time() < keyframes.back()->Time()) {
+ for (size_t i = 0; i < keyframes.size(); ++i) {
+ if (keyframe->Time() < keyframes[i]->Time()) {
+ keyframes.insert(keyframes.begin() + i, keyframe.Pass());
+ return;
+ }
+ }
+ }
+
+ keyframes.push_back(keyframe.Pass());
+}
+
+scoped_ptr<TimingFunction> CloneTimingFunction(
+ const TimingFunction* timing_function) {
+ DCHECK(timing_function);
+ scoped_ptr<AnimationCurve> curve(timing_function->Clone());
+ return scoped_ptr<TimingFunction>(
+ static_cast<TimingFunction*>(curve.release()));
+}
+
+} // namespace
+
+Keyframe::Keyframe(double time, scoped_ptr<TimingFunction> timing_function)
+ : time_(time),
+ timing_function_(timing_function.Pass()) {}
+
+Keyframe::~Keyframe() {}
+
+double Keyframe::Time() const {
+ return time_;
+}
+
+scoped_ptr<FloatKeyframe> FloatKeyframe::Create(
+ double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(
+ new FloatKeyframe(time, value, timing_function.Pass()));
+}
+
+FloatKeyframe::FloatKeyframe(double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function)
+ : Keyframe(time, timing_function.Pass()),
+ value_(value) {}
+
+FloatKeyframe::~FloatKeyframe() {}
+
+float FloatKeyframe::Value() const {
+ return value_;
+}
+
+scoped_ptr<FloatKeyframe> FloatKeyframe::Clone() const {
+ scoped_ptr<TimingFunction> func;
+ if (timing_function())
+ func = CloneTimingFunction(timing_function());
+ return FloatKeyframe::Create(Time(), Value(), func.Pass());
+}
+
+scoped_ptr<TransformKeyframe> TransformKeyframe::Create(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(
+ new TransformKeyframe(time, value, timing_function.Pass()));
+}
+
+TransformKeyframe::TransformKeyframe(double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function)
+ : Keyframe(time, timing_function.Pass()),
+ value_(value) {}
+
+TransformKeyframe::~TransformKeyframe() {}
+
+const TransformOperations& TransformKeyframe::Value() const {
+ return value_;
+}
+
+scoped_ptr<TransformKeyframe> TransformKeyframe::Clone() const {
+ scoped_ptr<TimingFunction> func;
+ if (timing_function())
+ func = CloneTimingFunction(timing_function());
+ return TransformKeyframe::Create(Time(), Value(), func.Pass());
+}
+
+scoped_ptr<KeyframedFloatAnimationCurve> KeyframedFloatAnimationCurve::
+ Create() {
+ return make_scoped_ptr(new KeyframedFloatAnimationCurve);
+}
+
+KeyframedFloatAnimationCurve::KeyframedFloatAnimationCurve() {}
+
+KeyframedFloatAnimationCurve::~KeyframedFloatAnimationCurve() {}
+
+void KeyframedFloatAnimationCurve::AddKeyframe(
+ scoped_ptr<FloatKeyframe> keyframe) {
+ InsertKeyframe(keyframe.Pass(), keyframes_);
+}
+
+double KeyframedFloatAnimationCurve::Duration() const {
+ return keyframes_.back()->Time() - keyframes_.front()->Time();
+}
+
+scoped_ptr<AnimationCurve> KeyframedFloatAnimationCurve::Clone() const {
+ scoped_ptr<KeyframedFloatAnimationCurve> to_return(
+ KeyframedFloatAnimationCurve::Create());
+ for (size_t i = 0; i < keyframes_.size(); ++i)
+ to_return->AddKeyframe(keyframes_[i]->Clone());
+ return to_return.PassAs<AnimationCurve>();
+}
+
+float KeyframedFloatAnimationCurve::GetValue(double t) const {
+ if (t <= keyframes_.front()->Time())
+ return keyframes_.front()->Value();
+
+ if (t >= keyframes_.back()->Time())
+ return keyframes_.back()->Value();
+
+ size_t i = 0;
+ for (; i < keyframes_.size() - 1; ++i) {
+ if (t < keyframes_[i+1]->Time())
+ break;
+ }
+
+ float progress =
+ static_cast<float>((t - keyframes_[i]->Time()) /
+ (keyframes_[i+1]->Time() - keyframes_[i]->Time()));
+
+ if (keyframes_[i]->timing_function())
+ progress = keyframes_[i]->timing_function()->GetValue(progress);
+
+ return keyframes_[i]->Value() +
+ (keyframes_[i+1]->Value() - keyframes_[i]->Value()) * progress;
+}
+
+scoped_ptr<KeyframedTransformAnimationCurve> KeyframedTransformAnimationCurve::
+ Create() {
+ return make_scoped_ptr(new KeyframedTransformAnimationCurve);
+}
+
+KeyframedTransformAnimationCurve::KeyframedTransformAnimationCurve() {}
+
+KeyframedTransformAnimationCurve::~KeyframedTransformAnimationCurve() {}
+
+void KeyframedTransformAnimationCurve::AddKeyframe(
+ scoped_ptr<TransformKeyframe> keyframe) {
+ InsertKeyframe(keyframe.Pass(), keyframes_);
+}
+
+double KeyframedTransformAnimationCurve::Duration() const {
+ return keyframes_.back()->Time() - keyframes_.front()->Time();
+}
+
+scoped_ptr<AnimationCurve> KeyframedTransformAnimationCurve::Clone() const {
+ scoped_ptr<KeyframedTransformAnimationCurve> to_return(
+ KeyframedTransformAnimationCurve::Create());
+ for (size_t i = 0; i < keyframes_.size(); ++i)
+ to_return->AddKeyframe(keyframes_[i]->Clone());
+ return to_return.PassAs<AnimationCurve>();
+}
+
+gfx::Transform KeyframedTransformAnimationCurve::GetValue(double t) const {
+ if (t <= keyframes_.front()->Time())
+ return keyframes_.front()->Value().Apply();
+
+ if (t >= keyframes_.back()->Time())
+ return keyframes_.back()->Value().Apply();
+
+ size_t i = 0;
+ for (; i < keyframes_.size() - 1; ++i) {
+ if (t < keyframes_[i+1]->Time())
+ break;
+ }
+
+ double progress = (t - keyframes_[i]->Time()) /
+ (keyframes_[i+1]->Time() - keyframes_[i]->Time());
+
+ if (keyframes_[i]->timing_function())
+ progress = keyframes_[i]->timing_function()->GetValue(progress);
+
+ return keyframes_[i+1]->Value().Blend(keyframes_[i]->Value(), progress);
+}
+
+} // namespace cc
diff --git a/cc/animation/keyframed_animation_curve.h b/cc/animation/keyframed_animation_curve.h
new file mode 100644
index 0000000..aa53b67
--- /dev/null
+++ b/cc/animation/keyframed_animation_curve.h
@@ -0,0 +1,129 @@
+// Copyright 2012 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 CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
+#define CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
+
+#include "cc/animation/animation_curve.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/timing_function.h"
+#include "cc/transform_operations.h"
+
+namespace cc {
+
+class CC_EXPORT Keyframe {
+ public:
+ double Time() const;
+ const TimingFunction* timing_function() const {
+ return timing_function_.get();
+ }
+
+ protected:
+ Keyframe(double time, scoped_ptr<TimingFunction> timing_function);
+ virtual ~Keyframe();
+
+ private:
+ double time_;
+ scoped_ptr<TimingFunction> timing_function_;
+
+ DISALLOW_COPY_AND_ASSIGN(Keyframe);
+};
+
+class CC_EXPORT FloatKeyframe : public Keyframe {
+ public:
+ static scoped_ptr<FloatKeyframe> Create(
+ double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function);
+ virtual ~FloatKeyframe();
+
+ float Value() const;
+
+ scoped_ptr<FloatKeyframe> Clone() const;
+
+ private:
+ FloatKeyframe(double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function);
+
+ float value_;
+};
+
+class CC_EXPORT TransformKeyframe : public Keyframe {
+ public:
+ static scoped_ptr<TransformKeyframe> Create(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+ virtual ~TransformKeyframe();
+
+ const TransformOperations& Value() const;
+
+ scoped_ptr<TransformKeyframe> Clone() const;
+
+ private:
+ TransformKeyframe(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+
+ TransformOperations value_;
+};
+
+class CC_EXPORT KeyframedFloatAnimationCurve : public FloatAnimationCurve {
+ public:
+ // It is required that the keyframes be sorted by time.
+ static scoped_ptr<KeyframedFloatAnimationCurve> Create();
+
+ virtual ~KeyframedFloatAnimationCurve();
+
+ void AddKeyframe(scoped_ptr<FloatKeyframe> keyframe);
+
+ // AnimationCurve implementation
+ virtual double Duration() const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ // FloatAnimationCurve implementation
+ virtual float GetValue(double t) const OVERRIDE;
+
+ private:
+ KeyframedFloatAnimationCurve();
+
+ // Always sorted in order of increasing time. No two keyframes have the
+ // same time.
+ ScopedPtrVector<FloatKeyframe> keyframes_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyframedFloatAnimationCurve);
+};
+
+class CC_EXPORT KeyframedTransformAnimationCurve : public TransformAnimationCurve {
+ public:
+ // It is required that the keyframes be sorted by time.
+ static scoped_ptr<KeyframedTransformAnimationCurve> Create();
+
+ virtual ~KeyframedTransformAnimationCurve();
+
+ void AddKeyframe(scoped_ptr<TransformKeyframe> keyframe);
+
+ // AnimationCurve implementation
+ virtual double Duration() const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ // TransformAnimationCurve implementation
+ virtual gfx::Transform GetValue(double t) const OVERRIDE;
+
+ private:
+ KeyframedTransformAnimationCurve();
+
+ // Always sorted in order of increasing time. No two keyframes have the
+ // same time.
+ ScopedPtrVector<TransformKeyframe> keyframes_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyframedTransformAnimationCurve);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
diff --git a/cc/animation/keyframed_animation_curve_unittest.cc b/cc/animation/keyframed_animation_curve_unittest.cc
new file mode 100644
index 0000000..446801d
--- /dev/null
+++ b/cc/animation/keyframed_animation_curve_unittest.cc
@@ -0,0 +1,243 @@
+// Copyright 2012 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 "cc/animation/keyframed_animation_curve.h"
+
+#include "cc/transform_operations.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+void expectTranslateX(double translateX, const gfx::Transform& transform)
+{
+ EXPECT_FLOAT_EQ(translateX, transform.matrix().getDouble(0, 3));
+}
+
+// Tests that a float animation with one keyframe works as expected.
+TEST(KeyframedAnimationCurveTest, OneFloatKeyframe)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(2.f));
+}
+
+// Tests that a float animation with two keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, TwoFloatKeyframe)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(2.f));
+}
+
+// Tests that a float animation with three keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, ThreeFloatKeyframe)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 8.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a float animation with multiple keys at a given time works sanely.
+TEST(KeyframedAnimationCurveTest, RepeatedFloatKeyTimes)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 6.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 6.f, scoped_ptr<TimingFunction>()));
+
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(0.5f));
+
+ // There is a discontinuity at 1. Any value between 4 and 6 is valid.
+ float value = curve->GetValue(1.f);
+ EXPECT_TRUE(value >= 4 && value <= 6);
+
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(3.f));
+}
+
+
+// Tests that a transform animation with one keyframe works as expected.
+TEST(KeyframedAnimationCurveTest, OneTransformKeyframe)
+{
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations;
+ operations.AppendTranslate(2.f, 0.f, 0.f);
+ curve->AddKeyframe(
+ TransformKeyframe::Create(0.f, operations, scoped_ptr<TimingFunction>()));
+
+ expectTranslateX(2.f, curve->GetValue(-1.f));
+ expectTranslateX(2.f, curve->GetValue(0.f));
+ expectTranslateX(2.f, curve->GetValue(0.5f));
+ expectTranslateX(2.f, curve->GetValue(1.f));
+ expectTranslateX(2.f, curve->GetValue(2.f));
+}
+
+// Tests that a transform animation with two keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, TwoTransformKeyframe)
+{
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations1;
+ operations1.AppendTranslate(2.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ expectTranslateX(2.f, curve->GetValue(-1.f));
+ expectTranslateX(2.f, curve->GetValue(0.f));
+ expectTranslateX(3.f, curve->GetValue(0.5f));
+ expectTranslateX(4.f, curve->GetValue(1.f));
+ expectTranslateX(4.f, curve->GetValue(2.f));
+}
+
+// Tests that a transform animation with three keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, ThreeTransformKeyframe)
+{
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations1;
+ operations1.AppendTranslate(2.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations3;
+ operations3.AppendTranslate(8.f, 0.f, 0.f);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 2.f, operations3, scoped_ptr<TimingFunction>()));
+ expectTranslateX(2.f, curve->GetValue(-1.f));
+ expectTranslateX(2.f, curve->GetValue(0.f));
+ expectTranslateX(3.f, curve->GetValue(0.5f));
+ expectTranslateX(4.f, curve->GetValue(1.f));
+ expectTranslateX(6.f, curve->GetValue(1.5f));
+ expectTranslateX(8.f, curve->GetValue(2.f));
+ expectTranslateX(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a transform animation with multiple keys at a given time works
+// sanely.
+TEST(KeyframedAnimationCurveTest, RepeatedTransformKeyTimes)
+{
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ // A step function.
+ TransformOperations operations1;
+ operations1.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations3;
+ operations3.AppendTranslate(6.f, 0.f, 0.f);
+ TransformOperations operations4;
+ operations4.AppendTranslate(6.f, 0.f, 0.f);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations3, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 2.f, operations4, scoped_ptr<TimingFunction>()));
+
+ expectTranslateX(4.f, curve->GetValue(-1.f));
+ expectTranslateX(4.f, curve->GetValue(0.f));
+ expectTranslateX(4.f, curve->GetValue(0.5f));
+
+ // There is a discontinuity at 1. Any value between 4 and 6 is valid.
+ gfx::Transform value = curve->GetValue(1.f);
+ EXPECT_TRUE(value.matrix().getDouble(0.f, 3.f) >= 4);
+ EXPECT_TRUE(value.matrix().getDouble(0.f, 3.f) <= 6);
+
+ expectTranslateX(6.f, curve->GetValue(1.5f));
+ expectTranslateX(6.f, curve->GetValue(2.f));
+ expectTranslateX(6.f, curve->GetValue(3.f));
+}
+
+// Tests that the keyframes may be added out of order.
+TEST(KeyframedAnimationCurveTest, UnsortedKeyframes)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 8.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a cubic bezier timing function works as expected.
+TEST(KeyframedAnimationCurveTest, CubicBezierTimingFunction)
+{
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(
+ 0.f,
+ 0,
+ CubicBezierTimingFunction::create(
+ 0.25f, 0.f, 0.75f, 1.f).PassAs<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 1.f, scoped_ptr<TimingFunction>()));
+
+ EXPECT_FLOAT_EQ(0.f, curve->GetValue(0.f));
+ EXPECT_LT(0.f, curve->GetValue(0.25f));
+ EXPECT_GT(0.25f, curve->GetValue(0.25f));
+ EXPECT_NEAR(curve->GetValue(0.5f), 0.5f, 0.00015f);
+ EXPECT_LT(0.75f, curve->GetValue(0.75f));
+ EXPECT_GT(1.f, curve->GetValue(0.75f));
+ EXPECT_FLOAT_EQ(1.f, curve->GetValue(1.f));
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/animation/layer_animation_controller.cc b/cc/animation/layer_animation_controller.cc
new file mode 100644
index 0000000..ef280d6
--- /dev/null
+++ b/cc/animation/layer_animation_controller.cc
@@ -0,0 +1,628 @@
+// Copyright 2012 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 "cc/animation/layer_animation_controller.h"
+
+#include <algorithm>
+
+#include "cc/animation/animation.h"
+#include "cc/animation/animation_registrar.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/layer_animation_value_observer.h"
+#include "cc/base/scoped_ptr_algorithm.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+LayerAnimationController::LayerAnimationController(int id)
+ : force_sync_(false),
+ registrar_(0),
+ id_(id),
+ is_active_(false),
+ last_tick_time_(0) {}
+
+LayerAnimationController::~LayerAnimationController() {
+ if (registrar_)
+ registrar_->UnregisterAnimationController(this);
+}
+
+scoped_refptr<LayerAnimationController> LayerAnimationController::Create(
+ int id) {
+ return make_scoped_refptr(new LayerAnimationController(id));
+}
+
+void LayerAnimationController::PauseAnimation(int animation_id,
+ double time_offset) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->id() == animation_id) {
+ active_animations_[i]->SetRunState(
+ Animation::Paused, time_offset + active_animations_[i]->start_time());
+ }
+ }
+}
+
+struct HasAnimationId {
+ HasAnimationId(int id) : id_(id) {}
+ bool operator()(Animation* animation) const {
+ return animation->id() == id_;
+ }
+
+ private:
+ int id_;
+};
+
+void LayerAnimationController::RemoveAnimation(int animation_id) {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(animations,
+ animations.begin(),
+ animations.end(),
+ HasAnimationId(animation_id)),
+ animations.end());
+ UpdateActivation(NormalActivation);
+}
+
+struct HasAnimationIdAndProperty {
+ HasAnimationIdAndProperty(int id, Animation::TargetProperty target_property)
+ : id_(id), target_property_(target_property) {}
+ bool operator()(Animation* animation) const {
+ return animation->id() == id_ &&
+ animation->target_property() == target_property_;
+ }
+
+ private:
+ int id_;
+ Animation::TargetProperty target_property_;
+};
+
+void LayerAnimationController::RemoveAnimation(
+ int animation_id,
+ Animation::TargetProperty target_property) {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(animations,
+ animations.begin(),
+ animations.end(),
+ HasAnimationIdAndProperty(animation_id,
+ target_property)),
+ animations.end());
+ UpdateActivation(NormalActivation);
+}
+
+// According to render layer backing, these are for testing only.
+void LayerAnimationController::SuspendAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (!active_animations_[i]->is_finished())
+ active_animations_[i]->SetRunState(Animation::Paused, monotonic_time);
+ }
+}
+
+// Looking at GraphicsLayerCA, this appears to be the analog to
+// suspendAnimations, which is for testing.
+void LayerAnimationController::ResumeAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Paused)
+ active_animations_[i]->SetRunState(Animation::Running, monotonic_time);
+ }
+}
+
+// Ensures that the list of active animations on the main thread and the impl
+// thread are kept in sync.
+void LayerAnimationController::PushAnimationUpdatesTo(
+ LayerAnimationController* controller_impl) {
+ if (force_sync_) {
+ ReplaceImplThreadAnimations(controller_impl);
+ force_sync_ = false;
+ } else {
+ PurgeAnimationsMarkedForDeletion();
+ PushNewAnimationsToImplThread(controller_impl);
+
+ // Remove finished impl side animations only after pushing,
+ // and only after the animations are deleted on the main thread
+ // this insures we will never push an animation twice.
+ RemoveAnimationsCompletedOnMainThread(controller_impl);
+
+ PushPropertiesToImplThread(controller_impl);
+ }
+ controller_impl->UpdateActivation(NormalActivation);
+ UpdateActivation(NormalActivation);
+}
+
+void LayerAnimationController::Animate(double monotonic_time) {
+ if (!HasActiveObserver())
+ return;
+
+ StartAnimationsWaitingForNextTick(monotonic_time);
+ StartAnimationsWaitingForStartTime(monotonic_time);
+ StartAnimationsWaitingForTargetAvailability(monotonic_time);
+ ResolveConflicts(monotonic_time);
+ TickAnimations(monotonic_time);
+ last_tick_time_ = monotonic_time;
+}
+
+void LayerAnimationController::AccumulatePropertyUpdates(
+ double monotonic_time,
+ AnimationEventsVector* events) {
+ if (!events)
+ return;
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ Animation* animation = active_animations_[i];
+ if (!animation->is_impl_only())
+ continue;
+
+ if (animation->target_property() == Animation::Opacity) {
+ AnimationEvent event(AnimationEvent::PropertyUpdate,
+ id_,
+ animation->group(),
+ Animation::Opacity,
+ monotonic_time);
+ event.opacity = animation->curve()->ToFloatAnimationCurve()->GetValue(
+ monotonic_time);
+
+ events->push_back(event);
+ }
+ else if (animation->target_property() == Animation::Transform) {
+ AnimationEvent event(AnimationEvent::PropertyUpdate,
+ id_,
+ animation->group(),
+ Animation::Transform,
+ monotonic_time);
+ event.transform =
+ animation->curve()->ToTransformAnimationCurve()->GetValue(
+ monotonic_time);
+ events->push_back(event);
+ }
+ }
+}
+
+void LayerAnimationController::UpdateState(AnimationEventsVector* events) {
+ if (!HasActiveObserver())
+ return;
+
+ PromoteStartedAnimations(last_tick_time_, events);
+ MarkFinishedAnimations(last_tick_time_);
+ MarkAnimationsForDeletion(last_tick_time_, events);
+ StartAnimationsWaitingForTargetAvailability(last_tick_time_);
+ PromoteStartedAnimations(last_tick_time_, events);
+
+ AccumulatePropertyUpdates(last_tick_time_, events);
+
+ UpdateActivation(NormalActivation);
+}
+
+void LayerAnimationController::AddAnimation(scoped_ptr<Animation> animation) {
+ active_animations_.push_back(animation.Pass());
+ UpdateActivation(NormalActivation);
+}
+
+Animation* LayerAnimationController::GetAnimation(
+ int group_id,
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i)
+ if (active_animations_[i]->group() == group_id &&
+ active_animations_[i]->target_property() == target_property)
+ return active_animations_[i];
+ return 0;
+}
+
+Animation* LayerAnimationController::GetAnimation(
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ size_t index = active_animations_.size() - i - 1;
+ if (active_animations_[index]->target_property() == target_property)
+ return active_animations_[index];
+ }
+ return 0;
+}
+
+bool LayerAnimationController::HasActiveAnimation() const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (!active_animations_[i]->is_finished())
+ return true;
+ }
+ return false;
+}
+
+bool LayerAnimationController::IsAnimatingProperty(
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() != Animation::Finished &&
+ active_animations_[i]->run_state() != Animation::Aborted &&
+ active_animations_[i]->target_property() == target_property)
+ return true;
+ }
+ return false;
+}
+
+void LayerAnimationController::OnAnimationStarted(
+ const AnimationEvent& event) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->group() == event.group_id &&
+ active_animations_[i]->target_property() == event.target_property &&
+ active_animations_[i]->needs_synchronized_start_time()) {
+ active_animations_[i]->set_needs_synchronized_start_time(false);
+ active_animations_[i]->set_start_time(event.monotonic_time);
+ return;
+ }
+ }
+}
+
+void LayerAnimationController::SetAnimationRegistrar(
+ AnimationRegistrar* registrar) {
+ if (registrar_ == registrar)
+ return;
+
+ if (registrar_)
+ registrar_->UnregisterAnimationController(this);
+
+ registrar_ = registrar;
+ if (registrar_)
+ registrar_->RegisterAnimationController(this);
+
+ UpdateActivation(ForceActivation);
+}
+
+void LayerAnimationController::AddObserver(
+ LayerAnimationValueObserver* observer) {
+ if (!observers_.HasObserver(observer))
+ observers_.AddObserver(observer);
+}
+
+void LayerAnimationController::RemoveObserver(
+ LayerAnimationValueObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void LayerAnimationController::PushNewAnimationsToImplThread(
+ LayerAnimationController* controller_impl) const {
+ // Any new animations owned by the main thread's controller are cloned and
+ // add to the impl thread's controller.
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ // If the animation is already running on the impl thread, there is no
+ // need to copy it over.
+ if (controller_impl->GetAnimation(active_animations_[i]->group(),
+ active_animations_[i]->target_property()))
+ continue;
+
+ // If the animation is not running on the impl thread, it does not
+ // necessarily mean that it needs to be copied over and started; it may
+ // have already finished. In this case, the impl thread animation will
+ // have already notified that it has started and the main thread animation
+ // will no longer need
+ // a synchronized start time.
+ if (!active_animations_[i]->needs_synchronized_start_time())
+ continue;
+
+ // The new animation should be set to run as soon as possible.
+ Animation::RunState initial_run_state =
+ Animation::WaitingForTargetAvailability;
+ double start_time = 0;
+ scoped_ptr<Animation> to_add(active_animations_[i]->CloneAndInitialize(
+ Animation::ControllingInstance, initial_run_state, start_time));
+ DCHECK(!to_add->needs_synchronized_start_time());
+ controller_impl->AddAnimation(to_add.Pass());
+ }
+}
+
+struct IsCompleted {
+ IsCompleted(const LayerAnimationController& main_thread_controller)
+ : main_thread_controller_(main_thread_controller) {}
+ bool operator()(Animation* animation) const {
+ if (animation->is_impl_only())
+ return false;
+ return !main_thread_controller_.GetAnimation(animation->group(),
+ animation->target_property());
+ }
+
+ private:
+ const LayerAnimationController& main_thread_controller_;
+};
+
+void LayerAnimationController::RemoveAnimationsCompletedOnMainThread(
+ LayerAnimationController* controller_impl) const {
+ // Delete all impl thread animations for which there is no corresponding
+ // main thread animation. Each iteration,
+ // controller->active_animations_.size() is decremented or i is incremented
+ // guaranteeing progress towards loop termination.
+ ScopedPtrVector<Animation>& animations =
+ controller_impl->active_animations_;
+ animations.erase(cc::remove_if(animations,
+ animations.begin(),
+ animations.end(),
+ IsCompleted(*this)),
+ animations.end());
+}
+
+void LayerAnimationController::PushPropertiesToImplThread(
+ LayerAnimationController* controller_impl) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ Animation* current_impl =
+ controller_impl->GetAnimation(
+ active_animations_[i]->group(),
+ active_animations_[i]->target_property());
+ if (current_impl)
+ active_animations_[i]->PushPropertiesTo(current_impl);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForNextTick(
+ double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::WaitingForNextTick)
+ active_animations_[i]->SetRunState(Animation::Starting, monotonic_time);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForStartTime(
+ double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::WaitingForStartTime &&
+ active_animations_[i]->start_time() <= monotonic_time)
+ active_animations_[i]->SetRunState(Animation::Starting, monotonic_time);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForTargetAvailability(
+ double monotonic_time) {
+ // First collect running properties.
+ TargetProperties blocked_properties;
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running ||
+ active_animations_[i]->run_state() == Animation::Finished)
+ blocked_properties.insert(active_animations_[i]->target_property());
+ }
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() ==
+ Animation::WaitingForTargetAvailability) {
+ // Collect all properties for animations with the same group id (they
+ // should all also be in the list of animations).
+ TargetProperties enqueued_properties;
+ enqueued_properties.insert(active_animations_[i]->target_property());
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if (active_animations_[i]->group() == active_animations_[j]->group())
+ enqueued_properties.insert(active_animations_[j]->target_property());
+ }
+
+ // Check to see if intersection of the list of properties affected by
+ // the group and the list of currently blocked properties is null. In
+ // any case, the group's target properties need to be added to the list
+ // of blocked properties.
+ bool null_intersection = true;
+ for (TargetProperties::iterator p_iter = enqueued_properties.begin();
+ p_iter != enqueued_properties.end();
+ ++p_iter) {
+ if (!blocked_properties.insert(*p_iter).second)
+ null_intersection = false;
+ }
+
+ // If the intersection is null, then we are free to start the animations
+ // in the group.
+ if (null_intersection) {
+ active_animations_[i]->SetRunState(
+ Animation::Starting, monotonic_time);
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if (active_animations_[i]->group() ==
+ active_animations_[j]->group()) {
+ active_animations_[j]->SetRunState(
+ Animation::Starting, monotonic_time);
+ }
+ }
+ }
+ }
+ }
+}
+
+void LayerAnimationController::PromoteStartedAnimations(
+ double monotonic_time,
+ AnimationEventsVector* events) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting) {
+ active_animations_[i]->SetRunState(Animation::Running, monotonic_time);
+ if (!active_animations_[i]->has_set_start_time())
+ active_animations_[i]->set_start_time(monotonic_time);
+ if (events) {
+ events->push_back(AnimationEvent(
+ AnimationEvent::Started,
+ id_,
+ active_animations_[i]->group(),
+ active_animations_[i]->target_property(),
+ monotonic_time));
+ }
+ }
+ }
+}
+
+void LayerAnimationController::MarkFinishedAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->IsFinishedAt(monotonic_time))
+ active_animations_[i]->SetRunState(Animation::Finished, monotonic_time);
+ }
+}
+
+void LayerAnimationController::ResolveConflicts(double monotonic_time) {
+ // Find any animations that are animating the same property and resolve the
+ // confict. We could eventually blend, but for now we'll just abort the
+ // previous animation (where 'previous' means: (1) has a prior start time or
+ // (2) has an equal start time, but was added to the queue earlier, i.e.,
+ // has a lower index in active_animations_).
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running) {
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if ((active_animations_[j]->run_state() == Animation::Starting ||
+ active_animations_[j]->run_state() == Animation::Running) &&
+ active_animations_[i]->target_property() ==
+ active_animations_[j]->target_property()) {
+ if (active_animations_[i]->start_time() >
+ active_animations_[j]->start_time()) {
+ active_animations_[j]->SetRunState(Animation::Aborted,
+ monotonic_time);
+ } else {
+ active_animations_[i]->SetRunState(Animation::Aborted,
+ monotonic_time);
+ }
+ }
+ }
+ }
+ }
+}
+
+void LayerAnimationController::MarkAnimationsForDeletion(
+ double monotonic_time, AnimationEventsVector* events) {
+ for (size_t i = 0; i < active_animations_.size(); i++) {
+ int group_id = active_animations_[i]->group();
+ bool all_anims_with_same_id_are_finished = false;
+ // If an animation is finished, and not already marked for deletion,
+ // Find out if all other animations in the same group are also finished.
+ if (active_animations_[i]->is_finished() &&
+ active_animations_[i]->run_state() != Animation::WaitingForDeletion) {
+ all_anims_with_same_id_are_finished = true;
+ for (size_t j = 0; j < active_animations_.size(); ++j) {
+ if (group_id == active_animations_[j]->group() &&
+ !active_animations_[j]->is_finished()) {
+ all_anims_with_same_id_are_finished = false;
+ break;
+ }
+ }
+ }
+ if (all_anims_with_same_id_are_finished) {
+ // We now need to remove all animations with the same group id as
+ // group_id (and send along animation finished notifications, if
+ // necessary).
+ for (size_t j = i; j < active_animations_.size(); j++) {
+ if (group_id == active_animations_[j]->group()) {
+ if (events) {
+ events->push_back(AnimationEvent(
+ AnimationEvent::Finished,
+ id_,
+ active_animations_[j]->group(),
+ active_animations_[j]->target_property(),
+ monotonic_time));
+ }
+ active_animations_[j]->SetRunState(Animation::WaitingForDeletion,
+ monotonic_time);
+ }
+ }
+ }
+ }
+}
+
+static bool IsWaitingForDeletion(Animation* animation) {
+ return animation->run_state() == Animation::WaitingForDeletion;
+}
+
+void LayerAnimationController::PurgeAnimationsMarkedForDeletion() {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(animations,
+ animations.begin(),
+ animations.end(),
+ IsWaitingForDeletion),
+ animations.end());
+}
+
+void LayerAnimationController::ReplaceImplThreadAnimations(
+ LayerAnimationController* controller_impl) const {
+ controller_impl->active_animations_.clear();
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ scoped_ptr<Animation> to_add;
+ if (active_animations_[i]->needs_synchronized_start_time()) {
+ // We haven't received an animation started notification yet, so it
+ // is important that we add it in a 'waiting' and not 'running' state.
+ Animation::RunState initial_run_state =
+ Animation::WaitingForTargetAvailability;
+ double start_time = 0;
+ to_add = active_animations_[i]->CloneAndInitialize(
+ Animation::ControllingInstance,
+ initial_run_state, start_time).Pass();
+ } else {
+ to_add = active_animations_[i]->Clone(
+ Animation::ControllingInstance).Pass();
+ }
+
+ controller_impl->AddAnimation(to_add.Pass());
+ }
+}
+
+void LayerAnimationController::TickAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running ||
+ active_animations_[i]->run_state() == Animation::Paused) {
+ double trimmed =
+ active_animations_[i]->TrimTimeToCurrentIteration(monotonic_time);
+
+ // Animation assumes its initial value until it gets the synchronized
+ // start time from the impl thread and can start ticking.
+ if (active_animations_[i]->needs_synchronized_start_time())
+ trimmed = 0;
+
+ // A just-started animation assumes its initial value.
+ if (active_animations_[i]->run_state() == Animation::Starting &&
+ !active_animations_[i]->has_set_start_time())
+ trimmed = 0;
+
+ switch (active_animations_[i]->target_property()) {
+
+ case Animation::Transform: {
+ const TransformAnimationCurve* transform_animation_curve =
+ active_animations_[i]->curve()->ToTransformAnimationCurve();
+ const gfx::Transform transform =
+ transform_animation_curve->GetValue(trimmed);
+ NotifyObserversTransformAnimated(transform);
+ break;
+ }
+
+ case Animation::Opacity: {
+ const FloatAnimationCurve* float_animation_curve =
+ active_animations_[i]->curve()->ToFloatAnimationCurve();
+ const float opacity = float_animation_curve->GetValue(trimmed);
+ NotifyObserversOpacityAnimated(opacity);
+ break;
+ }
+
+ // Do nothing for sentinel value.
+ case Animation::TargetPropertyEnumSize:
+ NOTREACHED();
+ }
+ }
+ }
+}
+
+void LayerAnimationController::UpdateActivation(UpdateActivationType type) {
+ bool force = type == ForceActivation;
+ if (registrar_) {
+ if (!active_animations_.empty() && (!is_active_ || force))
+ registrar_->DidActivateAnimationController(this);
+ else if (active_animations_.empty() && (is_active_ || force))
+ registrar_->DidDeactivateAnimationController(this);
+ is_active_ = !active_animations_.empty();
+ }
+}
+
+void LayerAnimationController::NotifyObserversOpacityAnimated(float opacity) {
+ FOR_EACH_OBSERVER(LayerAnimationValueObserver,
+ observers_,
+ OnOpacityAnimated(opacity));
+}
+
+void LayerAnimationController::NotifyObserversTransformAnimated(
+ const gfx::Transform& transform) {
+ FOR_EACH_OBSERVER(LayerAnimationValueObserver,
+ observers_,
+ OnTransformAnimated(transform));
+}
+
+bool LayerAnimationController::HasActiveObserver() {
+ if (observers_.might_have_observers()) {
+ ObserverListBase<LayerAnimationValueObserver>::Iterator it(observers_);
+ LayerAnimationValueObserver* obs;
+ while ((obs = it.GetNext()) != NULL)
+ if (obs->IsActive())
+ return true;
+ }
+ return false;
+}
+
+} // namespace cc
diff --git a/cc/animation/layer_animation_controller.h b/cc/animation/layer_animation_controller.h
new file mode 100644
index 0000000..1de1131
--- /dev/null
+++ b/cc/animation/layer_animation_controller.h
@@ -0,0 +1,154 @@
+// Copyright 2012 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 CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/animation/layer_animation_event_observer.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx { class Transform; }
+
+namespace cc {
+
+class Animation;
+class AnimationRegistrar;
+class KeyframeValueList;
+class LayerAnimationValueObserver;
+
+class CC_EXPORT LayerAnimationController
+ : public base::RefCounted<LayerAnimationController>,
+ public LayerAnimationEventObserver {
+ public:
+ static scoped_refptr<LayerAnimationController> Create(int id);
+
+ int id() const { return id_; }
+
+ // These methods are virtual for testing.
+ virtual void AddAnimation(scoped_ptr<Animation> animation);
+ virtual void PauseAnimation(int animation_id, double time_offset);
+ virtual void RemoveAnimation(int animation_id);
+ virtual void RemoveAnimation(int animation_id,
+ Animation::TargetProperty target_property);
+ virtual void SuspendAnimations(double monotonic_time);
+ virtual void ResumeAnimations(double monotonic_time);
+
+ // Ensures that the list of active animations on the main thread and the impl
+ // thread are kept in sync. This function does not take ownership of the impl
+ // thread controller.
+ virtual void PushAnimationUpdatesTo(
+ LayerAnimationController* controller_impl);
+
+ void Animate(double monotonic_time);
+ void AccumulatePropertyUpdates(double monotonic_time,
+ AnimationEventsVector* events);
+ void UpdateState(AnimationEventsVector* events);
+
+ // Returns the active animation in the given group, animating the given
+ // property, if such an animation exists.
+ Animation* GetAnimation(int group_id,
+ Animation::TargetProperty target_property) const;
+
+ // Returns the active animation animating the given property that is either
+ // running, or is next to run, if such an animation exists.
+ Animation* GetAnimation(Animation::TargetProperty target_property) const;
+
+ // Returns true if there are any animations that have neither finished nor
+ // aborted.
+ bool HasActiveAnimation() const;
+
+ // Returns true if there are any animations at all to process.
+ bool has_any_animation() const { return !active_animations_.empty(); }
+
+ // Returns true if there is an animation currently animating the given
+ // property, or if there is an animation scheduled to animate this property in
+ // the future.
+ bool IsAnimatingProperty(Animation::TargetProperty target_property) const;
+
+ // This is called in response to an animation being started on the impl
+ // thread. This function updates the corresponding main thread animation's
+ // start time.
+ virtual void OnAnimationStarted(const AnimationEvent& event) OVERRIDE;
+
+ // If a sync is forced, then the next time animation updates are pushed to the
+ // impl thread, all animations will be transferred.
+ void set_force_sync() { force_sync_ = true; }
+
+ void SetAnimationRegistrar(AnimationRegistrar* registrar);
+ AnimationRegistrar* animation_registrar() { return registrar_; }
+
+ void AddObserver(LayerAnimationValueObserver* observer);
+ void RemoveObserver(LayerAnimationValueObserver* observer);
+
+ protected:
+ friend class base::RefCounted<LayerAnimationController>;
+
+ LayerAnimationController(int id);
+ virtual ~LayerAnimationController();
+
+ private:
+ typedef base::hash_set<int> TargetProperties;
+
+ void PushNewAnimationsToImplThread(
+ LayerAnimationController* controller_impl) const;
+ void RemoveAnimationsCompletedOnMainThread(
+ LayerAnimationController* controller_impl) const;
+ void PushPropertiesToImplThread(
+ LayerAnimationController* controller_impl) const;
+ void ReplaceImplThreadAnimations(
+ LayerAnimationController* controller_impl) const;
+
+ void StartAnimationsWaitingForNextTick(double monotonic_time);
+ void StartAnimationsWaitingForStartTime(double monotonic_time);
+ void StartAnimationsWaitingForTargetAvailability(double monotonic_time);
+ void ResolveConflicts(double monotonic_time);
+ void PromoteStartedAnimations(double monotonic_time,
+ AnimationEventsVector* events);
+ void MarkFinishedAnimations(double monotonic_time);
+ void MarkAnimationsForDeletion(double monotonic_time,
+ AnimationEventsVector* events);
+ void PurgeAnimationsMarkedForDeletion();
+
+ void TickAnimations(double monotonic_time);
+
+ enum UpdateActivationType {
+ NormalActivation,
+ ForceActivation
+ };
+ void UpdateActivation(UpdateActivationType type);
+
+ void NotifyObserversOpacityAnimated(float opacity);
+ void NotifyObserversTransformAnimated(const gfx::Transform& transform);
+
+ bool HasActiveObserver();
+
+ // If this is true, we force a sync to the impl thread.
+ bool force_sync_;
+
+ AnimationRegistrar* registrar_;
+ int id_;
+ ScopedPtrVector<Animation> active_animations_;
+
+ // This is used to ensure that we don't spam the registrar.
+ bool is_active_;
+
+ double last_tick_time_;
+
+ ObserverList<LayerAnimationValueObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerAnimationController);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
diff --git a/cc/animation/layer_animation_controller_unittest.cc b/cc/animation/layer_animation_controller_unittest.cc
new file mode 100644
index 0000000..2e7c29b
--- /dev/null
+++ b/cc/animation/layer_animation_controller_unittest.cc
@@ -0,0 +1,901 @@
+// Copyright 2012 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 "cc/animation/layer_animation_controller.h"
+
+#include "cc/animation/animation.h"
+#include "cc/animation/animation_curve.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/transform_operations.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+void ExpectTranslateX(double translate_x, const gfx::Transform& matrix) {
+ EXPECT_FLOAT_EQ(translate_x, matrix.matrix().getDouble(0, 3)); }
+
+scoped_ptr<Animation> CreateAnimation(scoped_ptr<AnimationCurve> curve,
+ int id,
+ Animation::TargetProperty property) {
+ return Animation::Create(curve.Pass(), 0, id, property);
+}
+
+TEST(LayerAnimationControllerTest, SyncNewAnimation) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(0, Animation::Opacity));
+
+ addOpacityTransitionToController(*controller, 1, 0, 1, false);
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(0, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+}
+
+// If an animation is started on the impl thread before it is ticked on the main
+// thread, we must be sure to respect the synchronized start time.
+TEST(LayerAnimationControllerTest, DoNotClobberStartTimes) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(0, Animation::Opacity));
+
+ addOpacityTransitionToController(*controller, 1, 0, 1, false);
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(0, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+
+ AnimationEventsVector events;
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(&events);
+
+ // Synchronize the start times.
+ EXPECT_EQ(1u, events.size());
+ controller->OnAnimationStarted(events[0]);
+ EXPECT_EQ(controller->GetAnimation(0, Animation::Opacity)->start_time(),
+ controller_impl->GetAnimation(0, Animation::Opacity)->start_time());
+
+ // Start the animation on the main thread. Should not affect the start time.
+ controller->Animate(1.5);
+ controller->UpdateState(NULL);
+ EXPECT_EQ(controller->GetAnimation(0, Animation::Opacity)->start_time(),
+ controller_impl->GetAnimation(0, Animation::Opacity)->start_time());
+}
+
+TEST(LayerAnimationControllerTest, SyncPauseAndResume) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(0, Animation::Opacity));
+
+ addOpacityTransitionToController(*controller, 1, 0, 1, false);
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(0, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // Start the animations on each controller.
+ AnimationEventsVector events;
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(&events);
+ controller->Animate(0.0);
+ controller->UpdateState(NULL);
+ EXPECT_EQ(Animation::Running,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+ EXPECT_EQ(Animation::Running,
+ controller->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // Pause the main-thread animation.
+ controller->SuspendAnimations(1.0);
+ EXPECT_EQ(Animation::Paused,
+ controller->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // The pause run state change should make it to the impl thread controller.
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+ EXPECT_EQ(Animation::Paused,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // Resume the main-thread animation.
+ controller->ResumeAnimations(2.0);
+ EXPECT_EQ(Animation::Running,
+ controller->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // The pause run state change should make it to the impl thread controller.
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+ EXPECT_EQ(Animation::Running,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+}
+
+TEST(LayerAnimationControllerTest, DoNotSyncFinishedAnimation) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(0, Animation::Opacity));
+
+ int animation_id =
+ addOpacityTransitionToController(*controller, 1, 0, 1, false);
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(0, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(0, Animation::Opacity)->run_state());
+
+ // Notify main thread controller that the animation has started.
+ AnimationEvent animation_started_event(
+ AnimationEvent::Started, 0, 0, Animation::Opacity, 0);
+ controller->OnAnimationStarted(animation_started_event);
+
+ // Force animation to complete on impl thread.
+ controller_impl->RemoveAnimation(animation_id);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(animation_id, Animation::Opacity));
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ // Even though the main thread has a 'new' animation, it should not be pushed
+ // because the animation has already completed on the impl thread.
+ EXPECT_FALSE(controller_impl->GetAnimation(animation_id, Animation::Opacity));
+}
+
+// Tests that transitioning opacity from 0 to 1 works as expected.
+
+static const AnimationEvent* GetMostRecentPropertyUpdateEvent(
+ const AnimationEventsVector* events) {
+ const AnimationEvent* event = 0;
+ for (size_t i = 0; i < events->size(); ++i)
+ if ((*events)[i].type == AnimationEvent::PropertyUpdate)
+ event = &(*events)[i];
+
+ return event;
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransition) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ controller->AddAnimation(to_add.Pass());
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // A non-implOnly animation should not generate property updates.
+ const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_FALSE(event);
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ event = GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_FALSE(event);
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransitionOnImpl) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_is_impl_only(true);
+
+ controller_impl->AddAnimation(to_add.Pass());
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(events.get());
+ EXPECT_TRUE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy_impl.opacity());
+ EXPECT_EQ(2, events->size());
+ const AnimationEvent* start_opacity_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(0, start_opacity_event->opacity);
+
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy_impl.opacity());
+ EXPECT_FALSE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(4, events->size());
+ const AnimationEvent* end_opacity_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(1, end_opacity_event->opacity);
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransformOnImpl) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+
+ // Choose different values for x and y to avoid coincidental values in the
+ // observed transforms.
+ const float delta_x = 3;
+ const float delta_y = 4;
+
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+
+ // Create simple Transform animation.
+ TransformOperations operations;
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0, operations, scoped_ptr<cc::TimingFunction>()));
+ operations.AppendTranslate(delta_x, delta_y, 0);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1, operations, scoped_ptr<cc::TimingFunction>()));
+
+ scoped_ptr<Animation> animation(Animation::Create(
+ curve.PassAs<AnimationCurve>(), 1, 0, Animation::Transform));
+ animation->set_is_impl_only(true);
+ controller_impl->AddAnimation(animation.Pass());
+
+ // Run animation.
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(events.get());
+ EXPECT_TRUE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(gfx::Transform(), dummy_impl.transform());
+ EXPECT_EQ(2, events->size());
+ const AnimationEvent* start_transform_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ ASSERT_TRUE(start_transform_event);
+ EXPECT_EQ(gfx::Transform(), start_transform_event->transform);
+
+ gfx::Transform expected_transform;
+ expected_transform.Translate(delta_x, delta_y);
+
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(events.get());
+ EXPECT_EQ(expected_transform, dummy_impl.transform());
+ EXPECT_FALSE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(4, events->size());
+ const AnimationEvent* end_transform_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(expected_transform, end_transform_event->transform);
+}
+
+// Tests animations that are waiting for a synchronized start time do not
+// finish.
+TEST(LayerAnimationControllerTest,
+ AnimationsWaitingForStartTimeDoNotFinishIfTheyWaitLongerToStartThanTheirDuration) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_needs_synchronized_start_time(true);
+
+ // We should pause at the first keyframe indefinitely waiting for that
+ // animation to start.
+ controller->AddAnimation(to_add.Pass());
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ // Send the synchronized start time.
+ controller->OnAnimationStarted(AnimationEvent(
+ AnimationEvent::Started, 0, 1, Animation::Opacity, 2));
+ controller->Animate(5.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests that two queued animations affecting the same property run in sequence.
+TEST(LayerAnimationControllerTest, TrivialQueuing) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests interrupting a transition with another transition.
+TEST(LayerAnimationControllerTest, Interrupt) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForNextTick, 0);
+ controller->AddAnimation(to_add.Pass());
+
+ // Since the animation was in the WaitingForNextTick state, it should start
+ // right in this call to animate.
+ controller->Animate(0.5);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(1.5);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling two animations to run together when only one property is
+// free.
+TEST(LayerAnimationControllerTest, ScheduleTogetherWhenAPropertyIsBlocked) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 1,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 2,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.f, dummy.opacity());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ // Should not have started the float transition yet.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // The float animation should have started at time 1 and should be done.
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling two animations to run together with different lengths and
+// another animation queued to start when the shorter animation finishes (should
+// wait for both to finish).
+TEST(LayerAnimationControllerTest, ScheduleTogetherWithAnAnimWaiting) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(2)).Pass(),
+ 1,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ // Animations with id 1 should both start now.
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // The opacity animation should have finished at time 1, but the group
+ // of animations with id 1 don't finish until time 2 because of the length
+ // of the transform animation.
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ // Should not have started the float transition yet.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+
+ // The second opacity animation should start at time 2 and should be done by
+ // time 3.
+ controller->Animate(3.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future.
+TEST(LayerAnimationControllerTest, ScheduleAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future that's interrupting a
+// running animation.
+TEST(LayerAnimationControllerTest,
+ ScheduledAnimationInterruptsRunningAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 0.5f, 0.f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ // First 2s opacity transition should start immediately.
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future that interrupts a
+// running animation and there is yet another animation queued to start later.
+TEST(LayerAnimationControllerTest,
+ ScheduledAnimationInterruptsRunningAnimationWithAnimInQueue) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(2.0, 0.5f, 0.f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 0.f, 0.75f)).Pass(),
+ 3,
+ Animation::Opacity));
+
+ // First 2s opacity transition should start immediately.
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ controller->Animate(3.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(4.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Test that a looping animation loops and for the correct number of iterations.
+TEST(LayerAnimationControllerTest, TrivialLooping) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_iterations(3);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.25);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.75);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(2.25);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(2.75);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(3.0);
+ controller->UpdateState(events.get());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+
+ // Just be extra sure.
+ controller->Animate(4.0);
+ controller->UpdateState(events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+}
+
+// Test that an infinitely looping animation does indeed go until aborted.
+TEST(LayerAnimationControllerTest, InfiniteLooping) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ const int id = 1;
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+ to_add->set_iterations(-1);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.25);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.75);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+
+ controller->Animate(1073741824.25);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1073741824.75);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Aborted, 0.75);
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+}
+
+// Test that pausing and resuming work as expected.
+TEST(LayerAnimationControllerTest, PauseResume) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ const int id = 1;
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Paused, 0.5);
+
+ controller->Animate(1024);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Running, 1024);
+
+ controller->Animate(1024.25);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(1024.5);
+ controller->UpdateState(events.get());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+}
+
+TEST(LayerAnimationControllerTest, AbortAGroupedAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ const int id = 1;
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ id,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.75f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Aborted, 1);
+ controller->Animate(1.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(!controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+}
+
+TEST(LayerAnimationControllerTest, ForceSyncWhenSynchronizedStartTimeNeeded) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddObserver(&dummy_impl);
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 0,
+ Animation::Opacity));
+ to_add->set_needs_synchronized_start_time(true);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ Animation* active_animation = controller->GetAnimation(0, Animation::Opacity);
+ EXPECT_TRUE(active_animation);
+ EXPECT_TRUE(active_animation->needs_synchronized_start_time());
+
+ controller->set_force_sync();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ active_animation = controller_impl->GetAnimation(0, Animation::Opacity);
+ EXPECT_TRUE(active_animation);
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ active_animation->run_state());
+}
+
+// Tests that skipping a call to updateState works as expected.
+TEST(LayerAnimationControllerTest, SkipUpdateState) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 1,
+ Animation::Transform));
+
+ controller->Animate(0.0);
+ controller->UpdateState(events.get());
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ // Animate but don't updateState.
+ controller->Animate(1.0);
+
+ controller->Animate(2.0);
+ events.reset(new AnimationEventsVector);
+ controller->UpdateState(events.get());
+
+ // Should have one Started event and one Finished event.
+ EXPECT_EQ(2, events->size());
+ EXPECT_NE((*events)[0].type, (*events)[1].type);
+
+ // The float transition should still be at its starting point.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ controller->Animate(3.0);
+ controller->UpdateState(events.get());
+
+ // The float tranisition should now be done.
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/animation/layer_animation_event_observer.h b/cc/animation/layer_animation_event_observer.h
new file mode 100644
index 0000000..b20c590
--- /dev/null
+++ b/cc/animation/layer_animation_event_observer.h
@@ -0,0 +1,18 @@
+// Copyright 2012 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 CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+
+namespace cc {
+
+class CC_EXPORT LayerAnimationEventObserver {
+ public:
+ virtual void OnAnimationStarted(const AnimationEvent& event) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+
diff --git a/cc/animation/layer_animation_value_observer.h b/cc/animation/layer_animation_value_observer.h
new file mode 100644
index 0000000..9441c9e
--- /dev/null
+++ b/cc/animation/layer_animation_value_observer.h
@@ -0,0 +1,23 @@
+// Copyright 2012 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 CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+
+namespace cc {
+
+class CC_EXPORT LayerAnimationValueObserver {
+ public:
+ virtual ~LayerAnimationValueObserver() { }
+
+ virtual void OnOpacityAnimated(float) = 0;
+ virtual void OnTransformAnimated(const gfx::Transform&) = 0;
+
+ virtual bool IsActive() const = 0;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+
diff --git a/cc/animation/scrollbar_animation_controller.h b/cc/animation/scrollbar_animation_controller.h
new file mode 100644
index 0000000..8c7aafe
--- /dev/null
+++ b/cc/animation/scrollbar_animation_controller.h
@@ -0,0 +1,32 @@
+// Copyright 2012 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 CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
+#define CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
+
+#include "base/time.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+// This abstract class represents the compositor-side analogy of ScrollbarAnimator.
+// Individual platforms should subclass it to provide specialized implementation.
+class CC_EXPORT ScrollbarAnimationController {
+public:
+ virtual ~ScrollbarAnimationController() {}
+
+ virtual bool isScrollGestureInProgress() const = 0;
+ virtual bool isAnimating() const = 0;
+ virtual base::TimeDelta delayBeforeStart(base::TimeTicks now) const = 0;
+
+ virtual bool animate(base::TimeTicks) = 0;
+ virtual void didScrollGestureBegin() = 0;
+ virtual void didScrollGestureEnd(base::TimeTicks now) = 0;
+ virtual void didProgrammaticallyUpdateScroll(base::TimeTicks now) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
diff --git a/cc/animation/scrollbar_animation_controller_linear_fade.cc b/cc/animation/scrollbar_animation_controller_linear_fade.cc
new file mode 100644
index 0000000..63d2f6e
--- /dev/null
+++ b/cc/animation/scrollbar_animation_controller_linear_fade.cc
@@ -0,0 +1,94 @@
+// Copyright 2012 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 "cc/animation/scrollbar_animation_controller_linear_fade.h"
+
+#include "base/time.h"
+#include "cc/layer_impl.h"
+
+namespace cc {
+
+scoped_ptr<ScrollbarAnimationControllerLinearFade> ScrollbarAnimationControllerLinearFade::create(LayerImpl* scrollLayer, base::TimeDelta fadeoutDelay, base::TimeDelta fadeoutLength)
+{
+ return make_scoped_ptr(new ScrollbarAnimationControllerLinearFade(scrollLayer, fadeoutDelay, fadeoutLength));
+}
+
+ScrollbarAnimationControllerLinearFade::ScrollbarAnimationControllerLinearFade(LayerImpl* scrollLayer, base::TimeDelta fadeoutDelay, base::TimeDelta fadeoutLength)
+ : ScrollbarAnimationController()
+ , m_scrollLayer(scrollLayer)
+ , m_scrollGestureInProgress(false)
+ , m_fadeoutDelay(fadeoutDelay)
+ , m_fadeoutLength(fadeoutLength)
+{
+}
+
+ScrollbarAnimationControllerLinearFade::~ScrollbarAnimationControllerLinearFade()
+{
+}
+
+bool ScrollbarAnimationControllerLinearFade::isScrollGestureInProgress() const
+{
+ return m_scrollGestureInProgress;
+}
+
+bool ScrollbarAnimationControllerLinearFade::isAnimating() const
+{
+ return !m_lastAwakenTime.is_null();
+}
+
+base::TimeDelta ScrollbarAnimationControllerLinearFade::delayBeforeStart(base::TimeTicks now) const
+{
+ if (now > m_lastAwakenTime + m_fadeoutDelay)
+ return base::TimeDelta();
+ return m_fadeoutDelay - (now - m_lastAwakenTime);
+}
+
+bool ScrollbarAnimationControllerLinearFade::animate(base::TimeTicks now)
+{
+ float opacity = opacityAtTime(now);
+ m_scrollLayer->SetScrollbarOpacity(opacity);
+ if (!opacity)
+ m_lastAwakenTime = base::TimeTicks();
+ return isAnimating() && delayBeforeStart(now) == base::TimeDelta();
+}
+
+void ScrollbarAnimationControllerLinearFade::didScrollGestureBegin()
+{
+ m_scrollLayer->SetScrollbarOpacity(1);
+ m_scrollGestureInProgress = true;
+ m_lastAwakenTime = base::TimeTicks();
+}
+
+void ScrollbarAnimationControllerLinearFade::didScrollGestureEnd(base::TimeTicks now)
+{
+ m_scrollGestureInProgress = false;
+ m_lastAwakenTime = now;
+}
+
+void ScrollbarAnimationControllerLinearFade::didProgrammaticallyUpdateScroll(base::TimeTicks now)
+{
+ // Don't set m_scrollGestureInProgress as this scroll is not from a gesture
+ // and we won't receive ScrollEnd.
+ m_scrollLayer->SetScrollbarOpacity(1);
+ m_lastAwakenTime = now;
+}
+
+float ScrollbarAnimationControllerLinearFade::opacityAtTime(base::TimeTicks now)
+{
+ if (m_scrollGestureInProgress)
+ return 1;
+
+ if (m_lastAwakenTime.is_null())
+ return 0;
+
+ base::TimeDelta delta = now - m_lastAwakenTime;
+
+ if (delta <= m_fadeoutDelay)
+ return 1;
+ if (delta < m_fadeoutDelay + m_fadeoutLength)
+ return (m_fadeoutDelay + m_fadeoutLength - delta).InSecondsF() / m_fadeoutLength.InSecondsF();
+ return 0;
+}
+
+} // namespace cc
diff --git a/cc/animation/scrollbar_animation_controller_linear_fade.h b/cc/animation/scrollbar_animation_controller_linear_fade.h
new file mode 100644
index 0000000..a126306
--- /dev/null
+++ b/cc/animation/scrollbar_animation_controller_linear_fade.h
@@ -0,0 +1,50 @@
+// Copyright 2012 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 CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
+#define CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+class LayerImpl;
+
+class CC_EXPORT ScrollbarAnimationControllerLinearFade : public ScrollbarAnimationController {
+public:
+ static scoped_ptr<ScrollbarAnimationControllerLinearFade> create(LayerImpl* scrollLayer, base::TimeDelta fadeoutDelay, base::TimeDelta fadeoutLength);
+
+ virtual ~ScrollbarAnimationControllerLinearFade();
+
+ // ScrollbarAnimationController overrides.
+ virtual bool isScrollGestureInProgress() const OVERRIDE;
+ virtual bool isAnimating() const OVERRIDE;
+ virtual base::TimeDelta delayBeforeStart(base::TimeTicks now) const OVERRIDE;
+
+ virtual bool animate(base::TimeTicks) OVERRIDE;
+ virtual void didScrollGestureBegin() OVERRIDE;
+ virtual void didScrollGestureEnd(base::TimeTicks now) OVERRIDE;
+ virtual void didProgrammaticallyUpdateScroll(base::TimeTicks now) OVERRIDE;
+
+protected:
+ ScrollbarAnimationControllerLinearFade(LayerImpl* scrollLayer, base::TimeDelta fadeoutDelay, base::TimeDelta fadeoutLength);
+
+private:
+ float opacityAtTime(base::TimeTicks);
+
+ LayerImpl* m_scrollLayer;
+
+ base::TimeTicks m_lastAwakenTime;
+ bool m_scrollGestureInProgress;
+
+ base::TimeDelta m_fadeoutDelay;
+ base::TimeDelta m_fadeoutLength;
+
+ double m_currentTimeForTesting;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
diff --git a/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc b/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc
new file mode 100644
index 0000000..26c32b2
--- /dev/null
+++ b/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2012 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 "cc/animation/scrollbar_animation_controller_linear_fade.h"
+
+#include "cc/scrollbar_layer_impl.h"
+#include "cc/single_thread_proxy.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_web_scrollbar_theme_geometry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class ScrollbarAnimationControllerLinearFadeTest : public testing::Test {
+public:
+ ScrollbarAnimationControllerLinearFadeTest()
+ : m_hostImpl(&m_proxy)
+ {
+ }
+
+protected:
+ virtual void SetUp()
+ {
+ m_scrollLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1);
+ scoped_ptr<ScrollbarGeometryFixedThumb> geometry(ScrollbarGeometryFixedThumb::create(FakeWebScrollbarThemeGeometry::create(false)));
+ m_scrollbarLayer = ScrollbarLayerImpl::Create(m_hostImpl.active_tree(), 2, geometry.Pass());
+
+ m_scrollLayer->SetMaxScrollOffset(gfx::Vector2d(50, 50));
+ m_scrollLayer->SetBounds(gfx::Size(50, 50));
+ m_scrollLayer->SetHorizontalScrollbarLayer(m_scrollbarLayer.get());
+
+ m_scrollbarController = ScrollbarAnimationControllerLinearFade::create(m_scrollLayer.get(), base::TimeDelta::FromSeconds(2), base::TimeDelta::FromSeconds(3));
+ }
+
+ FakeImplProxy m_proxy;
+ FakeLayerTreeHostImpl m_hostImpl;
+ scoped_ptr<ScrollbarAnimationControllerLinearFade> m_scrollbarController;
+ scoped_ptr<LayerImpl> m_scrollLayer;
+ scoped_ptr<ScrollbarLayerImpl> m_scrollbarLayer;
+
+};
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, verifyHiddenInBegin)
+{
+ m_scrollbarController->animate(base::TimeTicks());
+ EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity());
+}
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, verifyAwakenByScrollGesture)
+{
+ base::TimeTicks time;
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->didScrollGestureBegin();
+ EXPECT_TRUE(m_scrollbarController->isScrollGestureInProgress());
+ EXPECT_FALSE(m_scrollbarController->isAnimating());
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(100);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+ m_scrollbarController->didScrollGestureEnd(time);
+
+ EXPECT_FALSE(m_scrollbarController->isScrollGestureInProgress());
+ EXPECT_TRUE(m_scrollbarController->isAnimating());
+ EXPECT_EQ(2, m_scrollbarController->delayBeforeStart(time).InSeconds());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+
+ m_scrollbarController->didScrollGestureBegin();
+ m_scrollbarController->didScrollGestureEnd(time);
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity());
+}
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, verifyAwakenByProgrammaticScroll)
+{
+ base::TimeTicks time;
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->didProgrammaticallyUpdateScroll(time);
+ EXPECT_FALSE(m_scrollbarController->isScrollGestureInProgress());
+ EXPECT_TRUE(m_scrollbarController->isAnimating());
+ EXPECT_EQ(2, m_scrollbarController->delayBeforeStart(time).InSeconds());
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+ m_scrollbarController->didProgrammaticallyUpdateScroll(time);
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->didProgrammaticallyUpdateScroll(time);
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, m_scrollbarLayer->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ m_scrollbarController->animate(time);
+ EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity());
+}
+
+} // namespace
+} // namespace cc