diff options
author | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 07:09:09 +0000 |
---|---|---|
committer | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 07:09:09 +0000 |
commit | 95e4e1a0fda6929b47702e69e4ddfe384b5d014b (patch) | |
tree | 1cf6fc6d2d063aac4a14388de969a5214e2e9243 /cc/animation | |
parent | a2f3755a9e22ced917b1dda50c57e4e912594e9d (diff) | |
download | chromium_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')
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 |