Chromium Code Reviews| Index: ui/gfx/compositor/layer_animator.cc |
| diff --git a/ui/gfx/compositor/layer_animator.cc b/ui/gfx/compositor/layer_animator.cc |
| index 5f2c63cebe3e75700e6aa514066550aa6456bc99..683bf173ecf9f6a1f1a5c5b2ad64462ebec2b19d 100644 |
| --- a/ui/gfx/compositor/layer_animator.cc |
| +++ b/ui/gfx/compositor/layer_animator.cc |
| @@ -4,183 +4,443 @@ |
| #include "ui/gfx/compositor/layer_animator.h" |
| +#include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| -#include "base/stl_util.h" |
| -#include "ui/base/animation/animation_container.h" |
| +#include "base/memory/scoped_ptr.h" |
| #include "ui/base/animation/animation.h" |
| -#include "ui/base/animation/tween.h" |
| #include "ui/gfx/compositor/compositor.h" |
| #include "ui/gfx/compositor/layer.h" |
| -#include "ui/gfx/compositor/layer_animator_delegate.h" |
| -#include "ui/gfx/transform.h" |
| -#include "ui/gfx/rect.h" |
| +#include "ui/gfx/compositor/layer_animation_sequence.h" |
| + |
| +namespace ui { |
| + |
| +class LayerAnimator; |
| namespace { |
| -void SetMatrixElement(SkMatrix44& matrix, int index, SkMScalar value) { |
| - int row = index / 4; |
| - int col = index % 4; |
| - matrix.set(row, col, value); |
| +static const int kDefaultTransitionDurationMs = 250; |
| +static const int kDefaultFramerateHz = 10; |
| + |
| +class LayerAnimation : public Animation { |
| + public: |
| + // The layer animation does not own the animator. |
| + explicit LayerAnimation(LayerAnimator* animator) |
| + : Animation(base::TimeDelta::FromMilliseconds(kDefaultFramerateHz)), |
| + animator_(animator) { |
| + } |
| + virtual ~LayerAnimation() {} |
| + |
| + private: |
| + // Implementation of Animation |
| + double GetCurrentValue() const { return 0.0; } |
|
sky
2011/10/19 00:06:33
virtual/OVERRIDE
|
| + virtual void Step(base::TimeTicks time_now) { animator_->Step(time_now); } |
|
sky
2011/10/19 00:06:33
OVERRIDE
|
| + |
| + LayerAnimator* animator_; |
| +}; |
|
sky
2011/10/19 00:06:33
DISALLOW_COPY_AND_ASSIGN
|
| + |
| +} // namespace; |
| + |
| +/* static */ |
|
sky
2011/10/19 00:06:33
/* static */ -> // static
|
| +LayerAnimator* LayerAnimator::CreateDefaultAnimator() { |
| + return new LayerAnimator(base::TimeDelta::FromMilliseconds(0)); |
| } |
| -SkMScalar GetMatrixElement(const SkMatrix44& matrix, int index) { |
| - int row = index / 4; |
| - int col = index % 4; |
| - return matrix.get(row, col); |
| +/* static */ |
| +LayerAnimator* LayerAnimator::CreateImplicitAnimator() { |
| + return new LayerAnimator( |
| + base::TimeDelta::FromMilliseconds(kDefaultTransitionDurationMs)); |
| } |
| -} // anonymous namespace |
| +LayerAnimator::LayerAnimator(base::TimeDelta transition_duration) |
| + : delegate_(NULL), |
| + preemption_strategy_(IMMEDIATELY_SET_NEW_TARGET), |
| + transition_duration_(transition_duration), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(animation_(new LayerAnimation(this))) { |
| +} |
| -namespace ui { |
| +LayerAnimator::~LayerAnimator() { |
| + ClearAnimations(); |
| +} |
| -LayerAnimator::LayerAnimator(Layer* layer) |
| - : layer_(layer), |
| - got_initial_tick_(false) { |
| +void LayerAnimator::SetTransform(const Transform& transform) { |
| + StartAnimation(new LayerAnimationSequence( |
| + LayerAnimationElement::CreateTransformElement(transform, |
| + transition_duration_))); |
| } |
| -LayerAnimator::~LayerAnimator() { |
| +void LayerAnimator::SetBounds(const gfx::Rect& bounds) { |
| + StartAnimation(new LayerAnimationSequence( |
| + LayerAnimationElement::CreateBoundsElement(bounds, |
| + transition_duration_))); |
| } |
| -void LayerAnimator::SetAnimation(Animation* animation) { |
| - animation_.reset(animation); |
| - if (animation_.get()) { |
| - static ui::AnimationContainer* container = NULL; |
| - if (!container) { |
| - container = new AnimationContainer; |
| - container->AddRef(); |
| +void LayerAnimator::SetOpacity(float opacity) { |
| + StartAnimation(new LayerAnimationSequence( |
| + LayerAnimationElement::CreateOpacityElement(opacity, |
| + transition_duration_))); |
| +} |
| + |
| +void LayerAnimator::SetDelegate(LayerAnimationDelegate* delegate) { |
| + delegate_ = delegate; |
| + if (!delegate_) { |
|
sky
2011/10/19 15:43:25
remove {} Why does no delegate imply you need to c
|
| + ClearAnimations(); |
| + } |
| +} |
| + |
| +void LayerAnimator::StartAnimation(LayerAnimationSequence* animation) { |
| + if (!StartSequenceImmediately(animation)) { |
| + // Attempt to preempt a running animation. |
| + switch (preemption_strategy_) { |
| + case IMMEDIATELY_SET_NEW_TARGET: |
| + ImmediatelySetNewTarget(animation); |
| + break; |
| + case IMMEDIATELY_ANIMATE_TO_NEW_TARGET: |
| + ImmediatelyAnimateToNewTarget(animation); |
| + break; |
| + case ENQUEUE_NEW_ANIMATION: |
| + EnqueueNewAnimation(animation); |
| + break; |
| + case REPLACE_QUEUED_ANIMATIONS: |
| + ReplaceQueuedAnimations(animation); |
| + break; |
| + case BLEND_WITH_CURRENT_ANIMATION: { |
| + // TODO(vollick) Add support for blended sequences and use them here. |
| + NOTIMPLEMENTED(); |
| + break; |
| + } |
| } |
| - animation_->set_delegate(this); |
| - animation_->SetContainer(container); |
|
sky
2011/10/19 00:06:33
Make all animations share the same container like
|
| - got_initial_tick_ = false; |
| } |
| + FinishAnyAnimationWithZeroDuration(); |
| } |
| -void LayerAnimator::AnimateToPoint(const gfx::Point& target) { |
| - StopAnimating(LOCATION); |
| - const gfx::Rect& layer_bounds = layer_->bounds(); |
| - if (target == layer_bounds.origin()) |
| - return; // Already there. |
| +void LayerAnimator::ScheduleAnimation(LayerAnimationSequence* animation) { |
| + animation_queue_.push_back(animation); |
| + ProcessQueue(); |
| +} |
| - Params& element = elements_[LOCATION]; |
| - element.location.target_x = target.x(); |
| - element.location.target_y = target.y(); |
| - element.location.start_x = layer_bounds.origin().x(); |
| - element.location.start_y = layer_bounds.origin().y(); |
| +void LayerAnimator::ScheduleTogether( |
| + const std::vector<LayerAnimationSequence*>& animations) { |
|
sky
2011/10/19 15:43:25
nit: spacing
|
| + // Collect all the affected properties. |
| + LayerAnimationElement::AnimatableProperties animated_properties; |
| + std::vector<LayerAnimationSequence*>::const_iterator iter = |
|
sky
2011/10/19 15:43:25
move iterator into for loop.
|
| + animations.begin(); |
| + while (iter != animations.end()) { |
| + LayerAnimationElement::AnimatableProperties::const_iterator prop_iter = |
|
sky
2011/10/19 15:43:25
Can this be animated_properties.insert((*iter)->pr
|
| + (*iter)->properties().begin(); |
| + while (prop_iter != (*iter)->properties().end()) { |
| + animated_properties.insert(*prop_iter); |
| + ++prop_iter; |
| + } |
| + ++iter; |
| + } |
| + |
| + // Scheduling a zero duration pause that affects all the animated properties |
| + // will prevent any of the sequences from animating until all the properties |
| + // are free. |
|
sky
2011/10/19 15:43:25
free? Do you mean existing animations done?
|
| + ScheduleAnimation( |
| + new LayerAnimationSequence( |
| + LayerAnimationElement::CreatePauseElement(animated_properties, |
| + base::TimeDelta()))); |
| + |
| + // These animations (provided they don't animate any common properties) will |
| + // now animate together if trivially scheduled. |
| + iter = animations.begin(); |
| + while (iter != animations.end()) { |
| + ScheduleAnimation(*iter); |
| + ++iter; |
| + } |
| } |
| -void LayerAnimator::AnimateTransform(const Transform& transform) { |
| - StopAnimating(TRANSFORM); |
| - const Transform& layer_transform = layer_->transform(); |
| - if (transform == layer_transform) |
| - return; // Already there. |
| +bool LayerAnimator::IsAnimating() const { |
| + return animation_queue_.size() > 0; |
|
sky
2011/10/19 15:43:25
!empty(). You can inline this if you want.
|
| +} |
| + |
| +void LayerAnimator::StopAnimatingProperty( |
| + LayerAnimationElement::AnimatableProperty property) { |
| + while (true) { |
| + RunningAnimation* running = GetRunningAnimation(property); |
| + if (!running) |
| + break; |
| + FinishAnimation(running->sequence); |
| + } |
| +} |
| + |
| +void LayerAnimator::StopAnimating() { |
| + while (IsAnimating()) |
| + FinishAnimation(running_animations_[0].sequence); |
| +} |
| - Params& element = elements_[TRANSFORM]; |
| - for (int i = 0; i < 16; ++i) { |
| - element.transform.start[i] = |
| - GetMatrixElement(layer_transform.matrix(), i); |
| - element.transform.target[i] = |
| - GetMatrixElement(transform.matrix(), i); |
| +void LayerAnimator::Step(base::TimeTicks now) { |
| + TRACE_EVENT0("LayerAnimator", "Step"); |
| + last_step_time_ = now; |
| + RunningAnimations running_animations_copy = running_animations_; |
|
sky
2011/10/19 15:43:25
Document why you need to make a copy.
|
| + RunningAnimations::iterator iter = running_animations_copy.begin(); |
|
sky
2011/10/19 15:43:25
move iterator into for lop.
|
| + while (iter != running_animations_copy.end()) { |
| + base::TimeDelta delta = now - (*iter).start_time; |
| + if (delta >= (*iter).sequence->duration() && |
| + !(*iter).sequence->is_cyclic()) { |
| + FinishAnimation((*iter).sequence); |
| + } else { |
| + (*iter).sequence->Progress(delta, delegate()); |
| + } |
| + ++iter; |
| } |
| } |
| -void LayerAnimator::AnimateOpacity(float target_opacity) { |
| - StopAnimating(OPACITY); |
| - if (layer_->opacity() == target_opacity) |
| +void LayerAnimator::SetAnimationForTest(Animation* animation) { |
| + animation_.reset(animation); |
| +} |
| + |
| +void LayerAnimator::UpdateAnimationState() { |
| + if (!animation_.get()) |
| return; |
| - Params& element = elements_[OPACITY]; |
| - element.opacity.start = layer_->opacity(); |
| - element.opacity.target = target_opacity; |
| + if (IsAnimating()) |
| + animation_->Start(); |
| + else |
| + animation_->Stop(); |
| } |
| -gfx::Point LayerAnimator::GetTargetPoint() { |
| - return IsAnimating(LOCATION) ? |
| - gfx::Point(elements_[LOCATION].location.target_x, |
| - elements_[LOCATION].location.target_y) : |
| - layer_->bounds().origin(); |
| +void LayerAnimator::RemoveAnimation(LayerAnimationSequence* sequence) { |
| + // First remove from running animations |
| + RunningAnimations::iterator iter = running_animations_.begin(); |
|
sky
2011/10/19 15:43:25
move into for loop.
|
| + while (iter != running_animations_.end()) { |
| + if ((*iter).sequence == sequence) { |
| + running_animations_.erase(iter); |
| + break; |
| + } |
| + ++iter; |
| + } |
| + |
| + // Then remove from the queue |
| + AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
| + while (queue_iter != animation_queue_.end()) { |
| + if ((*queue_iter) == sequence) { |
| + animation_queue_.erase(queue_iter); |
| + break; |
| + } |
| + ++queue_iter; |
| + } |
| } |
| -float LayerAnimator::GetTargetOpacity() { |
| - return IsAnimating(OPACITY) ? |
| - elements_[OPACITY].opacity.target : layer_->opacity(); |
| +void LayerAnimator::FinishAnimation(LayerAnimationSequence* sequence) { |
| + sequence->Progress(sequence->duration(), delegate()); |
| + RemoveAnimation(sequence); |
| + ProcessQueue(); |
| + UpdateAnimationState(); |
| } |
| -ui::Transform LayerAnimator::GetTargetTransform() { |
| - if (IsAnimating(TRANSFORM)) { |
| - Transform transform; |
| - for (int i = 0; i < 16; ++i) { |
| - SetMatrixElement(transform.matrix(), i, |
| - elements_[TRANSFORM].transform.target[i]); |
| +void LayerAnimator::FinishAnyAnimationWithZeroDuration() { |
| + // Special case: if we've started a 0 duration animation, just finish it now |
| + // and get rid of it. |
| + RunningAnimations copy = running_animations_; |
| + RunningAnimations::iterator iter = copy.begin(); |
| + while (iter != copy.end()) { |
| + if ((*iter).sequence->duration() == base::TimeDelta()) { |
| + (*iter).sequence->Progress((*iter).sequence->duration(), delegate()); |
| + RemoveAnimation((*iter).sequence); |
| } |
| - return transform; |
| + ++iter; |
| } |
| - return layer_->transform(); |
| + ProcessQueue(); |
| + UpdateAnimationState(); |
| } |
| -bool LayerAnimator::IsAnimating(AnimationProperty property) const { |
| - return elements_.count(property) > 0; |
| +void LayerAnimator::ClearAnimations() { |
| + RunningAnimations::iterator iter = running_animations_.begin(); |
| + while (iter != running_animations_.end()) { |
| + (*iter).sequence->Abort(); |
| + ++iter; |
| + } |
| + running_animations_.clear(); |
| + animation_queue_.reset(); |
| + UpdateAnimationState(); |
| } |
| -bool LayerAnimator::IsRunning() const { |
| - return animation_.get() && animation_->is_animating(); |
| +LayerAnimator::RunningAnimation* LayerAnimator::GetRunningAnimation( |
| + LayerAnimationElement::AnimatableProperty property) { |
| + RunningAnimations::iterator iter = running_animations_.begin(); |
|
sky
2011/10/19 15:43:25
move into for loop.
|
| + while (iter != running_animations_.end()) { |
| + if ((*iter).sequence->properties().find(property) != |
| + (*iter).sequence->properties().end()) |
| + return &(*iter); |
| + ++iter; |
| + } |
| + return NULL; |
| } |
| -void LayerAnimator::AnimationProgressed(const ui::Animation* animation) { |
| - got_initial_tick_ = true; |
| - for (Elements::const_iterator i = elements_.begin(); i != elements_.end(); |
| - ++i) { |
| - switch (i->first) { |
| - case LOCATION: { |
| - const gfx::Rect& current_bounds(layer_->bounds()); |
| - gfx::Rect new_bounds = animation_->CurrentValueBetween( |
| - gfx::Rect(gfx::Point(i->second.location.start_x, |
| - i->second.location.start_y), |
| - current_bounds.size()), |
| - gfx::Rect(gfx::Point(i->second.location.target_x, |
| - i->second.location.target_y), |
| - current_bounds.size())); |
| - delegate()->SetBoundsFromAnimator(new_bounds); |
| +void LayerAnimator::AddToQueueIfNotPresent(LayerAnimationSequence* animation) { |
| + // If we don't have the animation in the queue yet, add it. |
| + bool found_sequence = false; |
| + AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
| + while (queue_iter != animation_queue_.end()) { |
| + if ((*queue_iter) == animation) { |
| + found_sequence = true; |
| + break; |
| + } |
| + ++queue_iter; |
| + } |
| + |
| + if (!found_sequence) |
| + animation_queue_.insert(animation_queue_.begin(), animation); |
| +} |
| + |
| +void LayerAnimator::RemoveAllAnimationsWithACommonProperty( |
| + LayerAnimationSequence* sequence, |
| + bool abort) { |
| + // For all the running animations, if they animate the same property, |
| + // progress them to the end and remove them. |
| + RunningAnimations copy = running_animations_; |
| + RunningAnimations::const_iterator iter = copy.begin(); |
| + while (iter != copy.end()) { |
| + if ((*iter).sequence->HasCommonProperty(sequence->properties())) { |
| + // Finish the animation. |
| + if (abort) |
| + (*iter).sequence->Abort(); |
| + else |
| + (*iter).sequence->Progress((*iter).sequence->duration(), delegate()); |
| + RemoveAnimation((*iter).sequence); |
| + } |
| + ++iter; |
| + } |
| + // Same for the queued animations. |
| + AnimationQueue::size_type i = 0; |
| + while (i < animation_queue_.size()) { |
| + if (animation_queue_[i]->HasCommonProperty(sequence->properties())) { |
| + // Finish the animation. |
| + if (abort) |
| + animation_queue_[i]->Abort(); |
| + else |
| + animation_queue_[i]->Progress(animation_queue_[i]->duration(), |
| + delegate()); |
| + RemoveAnimation(animation_queue_[i]); |
| + } else { |
| + ++i; |
| + } |
| + } |
| +} |
| + |
| +void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) { |
| + const bool abort = false; |
| + RemoveAllAnimationsWithACommonProperty(sequence, abort); |
| + sequence->Progress(sequence->duration(), delegate()); |
| + RemoveAnimation(sequence); |
| +} |
| + |
| +void LayerAnimator::ImmediatelyAnimateToNewTarget( |
| + LayerAnimationSequence* sequence) { |
| + const bool abort = true; |
| + RemoveAllAnimationsWithACommonProperty(sequence, abort); |
| + AddToQueueIfNotPresent(sequence); |
| + StartSequenceImmediately(sequence); |
| +} |
| + |
| +void LayerAnimator::EnqueueNewAnimation(LayerAnimationSequence* sequence) { |
| + // It is assumed that if there was no conflicting animation, we would |
| + // not have been called. No need to check for a collision; just |
| + // add to the queue. |
| + animation_queue_.push_back(sequence); |
| + ProcessQueue(); |
| +} |
| + |
| +void LayerAnimator::ReplaceQueuedAnimations(LayerAnimationSequence* sequence) { |
| + // Remove all animations that aren't running. |
| + AnimationQueue::size_type i = 0; |
| + while (i < animation_queue_.size()) { |
| + bool is_running = false; |
| + RunningAnimations::const_iterator iter = |
| + running_animations_.begin(); |
| + while (iter != running_animations_.end()) { |
| + if ((*iter).sequence == animation_queue_[i]) { |
| + is_running = true; |
| break; |
| } |
| + ++iter; |
| + } |
| + if (!is_running) |
| + RemoveAnimation(animation_queue_[i]); |
| + else |
| + ++i; |
| + } |
| + animation_queue_.push_back(sequence); |
| + ProcessQueue(); |
| +} |
| - case TRANSFORM: { |
| - Transform transform; |
| - for (int j = 0; j < 16; ++j) { |
| - SkMScalar value = animation_->CurrentValueBetween( |
| - i->second.transform.start[j], |
| - i->second.transform.target[j]); |
| - SetMatrixElement(transform.matrix(), j, value); |
| - } |
| - delegate()->SetTransformFromAnimator(transform); |
| - break; |
| +void LayerAnimator::ProcessQueue() { |
| + bool started_sequence = false; |
| + |
| + do { |
| + started_sequence = false; |
| + |
| + // Build a list of all currently animated properties. |
| + LayerAnimationElement::AnimatableProperties animated; |
| + RunningAnimations::const_iterator iter = running_animations_.begin(); |
| + while (iter != running_animations_.end()) { |
| + LayerAnimationElement::AnimatableProperties::const_iterator prop_iter = |
| + (*iter).sequence->properties().begin(); |
| + while (prop_iter != (*iter).sequence->properties().end()) { |
| + animated.insert(*prop_iter); |
| + ++prop_iter; |
| } |
| + ++iter; |
| + } |
| - case OPACITY: { |
| - delegate()->SetOpacityFromAnimator(animation_->CurrentValueBetween( |
| - i->second.opacity.start, i->second.opacity.target)); |
| + // Try to find an animation that doesn't conflict with an animated |
| + // property or a property that will be animated before it. |
| + AnimationQueue::iterator queue_iter = animation_queue_.begin(); |
| + while (queue_iter != animation_queue_.end()) { |
| + if (!(*queue_iter)->HasCommonProperty(animated)) { |
| + StartSequenceImmediately(*queue_iter); |
| + started_sequence = true; |
| break; |
| } |
| - |
| - default: |
| - NOTREACHED(); |
| + // Animation couldn't be started. Add its properties to the collection so |
| + // that we don't start a conflicting animation. For example, if our queue |
| + // has the elements { {T,B}, {B} } (that is, an element that animates both |
| + // the transform and the bounds followed by an element that animates the |
| + // bounds), and we're currently animating the transform, we can't start |
| + // the first element because it animates the transform, too. We cannot |
| + // start the second element, either, because the first element animates |
| + // bounds too, and needs to go first. |
| + LayerAnimationElement::AnimatableProperties::const_iterator prop_iter = |
| + (*queue_iter)->properties().begin(); |
| + while (prop_iter != (*queue_iter)->properties().end()) { |
| + animated.insert(*prop_iter); |
| + ++prop_iter; |
| + } |
| + ++queue_iter; |
| } |
| - } |
| - layer_->ScheduleDraw(); |
| -} |
| -void LayerAnimator::AnimationEnded(const ui::Animation* animation) { |
| - AnimationProgressed(animation); |
| + // If we started a sequence, try again. We may be able to start several. |
| + } while (started_sequence); |
| } |
| -void LayerAnimator::StopAnimating(AnimationProperty property) { |
| - if (!IsAnimating(property)) |
| - return; |
| +bool LayerAnimator::StartSequenceImmediately(LayerAnimationSequence* sequence) { |
| + // Ensure that no one is animating one of the sequence's properties already. |
| + RunningAnimations::const_iterator iter = running_animations_.begin(); |
| + while (iter != running_animations_.end()) { |
| + if ((*iter).sequence->HasCommonProperty(sequence->properties())) |
| + return false; |
| + ++iter; |
| + } |
| - elements_.erase(property); |
| -} |
| + // All clear, actually start the sequence. Note: base::TimeTicks::Now has |
| + // a resolution that can be as bad as 15ms. If this causes glitches in the |
| + // animations, this can be switched to HighResNow() (animation uses Now() |
| + // internally). |
| + base::TimeTicks start_time = IsAnimating() |
| + ? last_step_time_ |
| + : base::TimeTicks::Now(); |
|
sky
2011/10/19 15:43:25
For the animating case this effectively starts the
|
| + |
| + running_animations_.push_back(RunningAnimation(sequence, start_time)); |
| + |
| + // Need to keep a reference to the animation. |
| + AddToQueueIfNotPresent(sequence); |
| + |
| + // Ensure that animations get stepped at their start time. |
| + Step(start_time); |
| -LayerAnimatorDelegate* LayerAnimator::delegate() { |
| - return static_cast<LayerAnimatorDelegate*>(layer_); |
| + return true; |
| } |
| } // namespace ui |