| Index: ui/gfx/compositor/layer_animator.cc
|
| diff --git a/ui/gfx/compositor/layer_animator.cc b/ui/gfx/compositor/layer_animator.cc
|
| index c9a7bca2d8521798a25ede160f27d84b17e67ce3..e737377c86c600ca0674470eba190fc75b18ee7f 100644
|
| --- a/ui/gfx/compositor/layer_animator.cc
|
| +++ b/ui/gfx/compositor/layer_animator.cc
|
| @@ -4,185 +4,440 @@
|
|
|
| #include "ui/gfx/compositor/layer_animator.h"
|
|
|
| +#include "base/debug/trace_event.h"
|
| #include "base/logging.h"
|
| -#include "base/stl_util.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| #include "ui/base/animation/animation_container.h"
|
| -#include "ui/base/animation/animation.h"
|
| -#include "ui/base/animation/tween.h"
|
| #include "ui/gfx/compositor/compositor.h"
|
| #include "ui/gfx/compositor/layer.h"
|
| -#include "ui/gfx/compositor/layer_animator_delegate.h"
|
| -#include "ui/gfx/transform.h"
|
| -#include "ui/gfx/rect.h"
|
| +#include "ui/gfx/compositor/layer_animation_delegate.h"
|
| +#include "ui/gfx/compositor/layer_animation_sequence.h"
|
| +
|
| +namespace ui {
|
| +
|
| +class LayerAnimator;
|
|
|
| namespace {
|
|
|
| -void SetMatrixElement(SkMatrix44& matrix, int index, SkMScalar value) {
|
| - int row = index / 4;
|
| - int col = index % 4;
|
| - matrix.set(row, col, value);
|
| +static const base::TimeDelta kDefaultTransitionDuration =
|
| + base::TimeDelta::FromMilliseconds(250);
|
| +
|
| +static const base::TimeDelta kTimerInterval =
|
| + base::TimeDelta::FromMilliseconds(10);
|
| +
|
| +} // namespace;
|
| +
|
| +// LayerAnimator public --------------------------------------------------------
|
| +
|
| +LayerAnimator::LayerAnimator(base::TimeDelta transition_duration)
|
| + : delegate_(NULL),
|
| + preemption_strategy_(IMMEDIATELY_SET_NEW_TARGET),
|
| + transition_duration_(transition_duration),
|
| + is_started_(false),
|
| + disable_timer_for_test_(false) {
|
| }
|
|
|
| -SkMScalar GetMatrixElement(const SkMatrix44& matrix, int index) {
|
| - int row = index / 4;
|
| - int col = index % 4;
|
| - return matrix.get(row, col);
|
| +LayerAnimator::~LayerAnimator() {
|
| + ClearAnimations();
|
| }
|
|
|
| -} // anonymous namespace
|
| +// static
|
| +LayerAnimator* LayerAnimator::CreateDefaultAnimator() {
|
| + return new LayerAnimator(base::TimeDelta::FromMilliseconds(0));
|
| +}
|
|
|
| -namespace ui {
|
| +// static
|
| +LayerAnimator* LayerAnimator::CreateImplicitAnimator() {
|
| + return new LayerAnimator(kDefaultTransitionDuration);
|
| +}
|
|
|
| -LayerAnimator::LayerAnimator(Layer* layer)
|
| - : layer_(layer),
|
| - got_initial_tick_(false) {
|
| +void LayerAnimator::SetTransform(const Transform& transform) {
|
| + if (transition_duration_ == base::TimeDelta())
|
| + delegate_->SetTransformFromAnimation(transform);
|
| + else
|
| + StartAnimation(new LayerAnimationSequence(
|
| + LayerAnimationElement::CreateTransformElement(transform,
|
| + transition_duration_)));
|
| }
|
|
|
| -LayerAnimator::~LayerAnimator() {
|
| +void LayerAnimator::SetBounds(const gfx::Rect& bounds) {
|
| + if (transition_duration_ == base::TimeDelta())
|
| + delegate_->SetBoundsFromAnimation(bounds);
|
| + else
|
| + StartAnimation(new LayerAnimationSequence(
|
| + LayerAnimationElement::CreateBoundsElement(bounds,
|
| + transition_duration_)));
|
| +}
|
| +
|
| +void LayerAnimator::SetOpacity(float opacity) {
|
| + if (transition_duration_ == base::TimeDelta())
|
| + delegate_->SetOpacityFromAnimation(opacity);
|
| + else
|
| + StartAnimation(new LayerAnimationSequence(
|
| + LayerAnimationElement::CreateOpacityElement(opacity,
|
| + transition_duration_)));
|
| +}
|
| +
|
| +void LayerAnimator::SetDelegate(LayerAnimationDelegate* delegate) {
|
| + DCHECK(delegate);
|
| + delegate_ = delegate;
|
| }
|
|
|
| -void LayerAnimator::SetAnimation(Animation* animation) {
|
| - animation_.reset(animation);
|
| - if (animation_.get()) {
|
| - static ui::AnimationContainer* container = NULL;
|
| - if (!container) {
|
| - container = new AnimationContainer;
|
| - container->AddRef();
|
| +void LayerAnimator::StartAnimation(LayerAnimationSequence* animation) {
|
| + if (!StartSequenceImmediately(animation)) {
|
| + // Attempt to preempt a running animation.
|
| + switch (preemption_strategy_) {
|
| + case IMMEDIATELY_SET_NEW_TARGET:
|
| + ImmediatelySetNewTarget(animation);
|
| + break;
|
| + case IMMEDIATELY_ANIMATE_TO_NEW_TARGET:
|
| + ImmediatelyAnimateToNewTarget(animation);
|
| + break;
|
| + case ENQUEUE_NEW_ANIMATION:
|
| + EnqueueNewAnimation(animation);
|
| + break;
|
| + case REPLACE_QUEUED_ANIMATIONS:
|
| + ReplaceQueuedAnimations(animation);
|
| + break;
|
| + case BLEND_WITH_CURRENT_ANIMATION: {
|
| + // TODO(vollick) Add support for blended sequences and use them here.
|
| + NOTIMPLEMENTED();
|
| + break;
|
| + }
|
| }
|
| - animation_->set_delegate(this);
|
| - animation_->SetContainer(container);
|
| - got_initial_tick_ = false;
|
| + }
|
| + FinishAnyAnimationWithZeroDuration();
|
| +}
|
| +
|
| +void LayerAnimator::ScheduleAnimation(LayerAnimationSequence* animation) {
|
| + if (is_animating()) {
|
| + animation_queue_.push_back(make_linked_ptr(animation));
|
| + ProcessQueue();
|
| + } else {
|
| + StartSequenceImmediately(animation);
|
| }
|
| }
|
|
|
| -void LayerAnimator::AnimateToPoint(const gfx::Point& target) {
|
| - StopAnimating(LOCATION);
|
| - const gfx::Rect& layer_bounds = layer_->bounds();
|
| - if (target == layer_bounds.origin())
|
| - return; // Already there.
|
| +void LayerAnimator::ScheduleTogether(
|
| + const std::vector<LayerAnimationSequence*>& animations) {
|
| + // Collect all the affected properties.
|
| + LayerAnimationElement::AnimatableProperties animated_properties;
|
| + std::vector<LayerAnimationSequence*>::const_iterator iter;
|
| + for (iter = animations.begin(); iter != animations.end(); ++iter) {
|
| + animated_properties.insert((*iter)->properties().begin(),
|
| + (*iter)->properties().end());
|
| + }
|
| +
|
| + // Scheduling a zero duration pause that affects all the animated properties
|
| + // will prevent any of the sequences from animating until there are no
|
| + // running animations that affect any of these properties.
|
| + ScheduleAnimation(
|
| + new LayerAnimationSequence(
|
| + LayerAnimationElement::CreatePauseElement(animated_properties,
|
| + base::TimeDelta())));
|
|
|
| - Params& element = elements_[LOCATION];
|
| - element.location.target_x = target.x();
|
| - element.location.target_y = target.y();
|
| - element.location.start_x = layer_bounds.origin().x();
|
| - element.location.start_y = layer_bounds.origin().y();
|
| + // These animations (provided they don't animate any common properties) will
|
| + // now animate together if trivially scheduled.
|
| + for (iter = animations.begin(); iter != animations.end(); ++iter) {
|
| + ScheduleAnimation(*iter);
|
| + }
|
| }
|
|
|
| -void LayerAnimator::AnimateTransform(const Transform& transform) {
|
| - StopAnimating(TRANSFORM);
|
| - const Transform& layer_transform = layer_->transform();
|
| - if (transform == layer_transform)
|
| - return; // Already there.
|
| +void LayerAnimator::StopAnimatingProperty(
|
| + LayerAnimationElement::AnimatableProperty property) {
|
| + while (true) {
|
| + RunningAnimation* running = GetRunningAnimation(property);
|
| + if (!running)
|
| + break;
|
| + FinishAnimation(running->sequence);
|
| + }
|
| +}
|
| +
|
| +void LayerAnimator::StopAnimating() {
|
| + while (is_animating())
|
| + FinishAnimation(running_animations_[0].sequence);
|
| +}
|
|
|
| - Params& element = elements_[TRANSFORM];
|
| - for (int i = 0; i < 16; ++i) {
|
| - element.transform.start[i] =
|
| - GetMatrixElement(layer_transform.matrix(), i);
|
| - element.transform.target[i] =
|
| - GetMatrixElement(transform.matrix(), i);
|
| +// LayerAnimator private -------------------------------------------------------
|
| +
|
| +void LayerAnimator::Step(base::TimeTicks now) {
|
| + TRACE_EVENT0("LayerAnimator", "Step");
|
| + last_step_time_ = now;
|
| + std::vector<LayerAnimationSequence*> to_finish;
|
| + for (RunningAnimations::iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + base::TimeDelta delta = now - (*iter).start_time;
|
| + if (delta >= (*iter).sequence->duration() &&
|
| + !(*iter).sequence->is_cyclic()) {
|
| + to_finish.push_back((*iter).sequence);
|
| + } else {
|
| + (*iter).sequence->Progress(delta, delegate());
|
| + }
|
| + }
|
| + for (std::vector<LayerAnimationSequence*>::iterator iter = to_finish.begin();
|
| + iter != to_finish.end(); ++iter) {
|
| + FinishAnimation(*iter);
|
| }
|
| }
|
|
|
| -void LayerAnimator::AnimateOpacity(float target_opacity) {
|
| - StopAnimating(OPACITY);
|
| - if (layer_->opacity() == target_opacity)
|
| +void LayerAnimator::SetStartTime(base::TimeTicks start_time) {
|
| +}
|
| +
|
| +base::TimeDelta LayerAnimator::GetTimerInterval() const {
|
| + return kTimerInterval;
|
| +}
|
| +
|
| +void LayerAnimator::UpdateAnimationState() {
|
| + if (disable_timer_for_test_)
|
| return;
|
|
|
| - Params& element = elements_[OPACITY];
|
| - element.opacity.start = layer_->opacity();
|
| - element.opacity.target = target_opacity;
|
| + static ui::AnimationContainer* container = NULL;
|
| + if (!container) {
|
| + container = new AnimationContainer();
|
| + container->AddRef();
|
| + }
|
| +
|
| + const bool should_start = is_animating();
|
| + if (should_start && !is_started_)
|
| + container->Start(this);
|
| + else if (!should_start && is_started_)
|
| + container->Stop(this);
|
| +
|
| + is_started_ = should_start;
|
| }
|
|
|
| -gfx::Point LayerAnimator::GetTargetPoint() {
|
| - return IsAnimating(LOCATION) ?
|
| - gfx::Point(elements_[LOCATION].location.target_x,
|
| - elements_[LOCATION].location.target_y) :
|
| - layer_->bounds().origin();
|
| +void LayerAnimator::RemoveAnimation(LayerAnimationSequence* sequence) {
|
| + // First remove from running animations
|
| + for (RunningAnimations::iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + if ((*iter).sequence == sequence) {
|
| + running_animations_.erase(iter);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + // Then remove from the queue
|
| + for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
|
| + queue_iter != animation_queue_.end(); ++queue_iter) {
|
| + if ((*queue_iter) == sequence) {
|
| + animation_queue_.erase(queue_iter);
|
| + break;
|
| + }
|
| + }
|
| }
|
|
|
| -float LayerAnimator::GetTargetOpacity() {
|
| - return IsAnimating(OPACITY) ?
|
| - elements_[OPACITY].opacity.target : layer_->opacity();
|
| +void LayerAnimator::FinishAnimation(LayerAnimationSequence* sequence) {
|
| + sequence->Progress(sequence->duration(), delegate());
|
| + RemoveAnimation(sequence);
|
| + ProcessQueue();
|
| + UpdateAnimationState();
|
| }
|
|
|
| -ui::Transform LayerAnimator::GetTargetTransform() {
|
| - if (IsAnimating(TRANSFORM)) {
|
| - Transform transform;
|
| - for (int i = 0; i < 16; ++i) {
|
| - SetMatrixElement(transform.matrix(), i,
|
| - elements_[TRANSFORM].transform.target[i]);
|
| +void LayerAnimator::FinishAnyAnimationWithZeroDuration() {
|
| + // Special case: if we've started a 0 duration animation, just finish it now
|
| + // and get rid of it. Note at each iteration of the loop, we either increment
|
| + // i or remove an element from running_animations_, so
|
| + // running_animations_.size() - i is always decreasing and we are always
|
| + // progressing towards the termination of the loop.
|
| + for (size_t i = 0; i < running_animations_.size();) {
|
| + if (running_animations_[i].sequence->duration() == base::TimeDelta()) {
|
| + running_animations_[i].sequence->Progress(
|
| + running_animations_[i].sequence->duration(), delegate());
|
| + RemoveAnimation(running_animations_[i].sequence);
|
| + } else {
|
| + ++i;
|
| }
|
| - return transform;
|
| }
|
| - return layer_->transform();
|
| + ProcessQueue();
|
| + UpdateAnimationState();
|
| }
|
|
|
| -bool LayerAnimator::IsAnimating(AnimationProperty property) const {
|
| - return elements_.count(property) > 0;
|
| +void LayerAnimator::ClearAnimations() {
|
| + for (RunningAnimations::iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + (*iter).sequence->Abort();
|
| + }
|
| + running_animations_.clear();
|
| + animation_queue_.clear();
|
| + UpdateAnimationState();
|
| }
|
|
|
| -bool LayerAnimator::IsRunning() const {
|
| - return animation_.get() && animation_->is_animating();
|
| +LayerAnimator::RunningAnimation* LayerAnimator::GetRunningAnimation(
|
| + LayerAnimationElement::AnimatableProperty property) {
|
| + for (RunningAnimations::iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + if ((*iter).sequence->properties().find(property) !=
|
| + (*iter).sequence->properties().end())
|
| + return &(*iter);
|
| + }
|
| + return NULL;
|
| }
|
|
|
| -void LayerAnimator::AnimationProgressed(const ui::Animation* animation) {
|
| - got_initial_tick_ = true;
|
| - for (Elements::const_iterator i = elements_.begin(); i != elements_.end();
|
| - ++i) {
|
| - switch (i->first) {
|
| - case LOCATION: {
|
| - const gfx::Rect& current_bounds(layer_->bounds());
|
| - gfx::Rect new_bounds = animation_->CurrentValueBetween(
|
| - gfx::Rect(gfx::Point(i->second.location.start_x,
|
| - i->second.location.start_y),
|
| - current_bounds.size()),
|
| - gfx::Rect(gfx::Point(i->second.location.target_x,
|
| - i->second.location.target_y),
|
| - current_bounds.size()));
|
| - delegate()->SetBoundsFromAnimator(new_bounds);
|
| - break;
|
| - }
|
| +void LayerAnimator::AddToQueueIfNotPresent(LayerAnimationSequence* animation) {
|
| + // If we don't have the animation in the queue yet, add it.
|
| + bool found_sequence = false;
|
| + for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
|
| + queue_iter != animation_queue_.end(); ++queue_iter) {
|
| + if ((*queue_iter) == animation) {
|
| + found_sequence = true;
|
| + break;
|
| + }
|
| + }
|
|
|
| - case TRANSFORM: {
|
| - Transform transform;
|
| - for (int j = 0; j < 16; ++j) {
|
| - SkMScalar value = animation_->CurrentValueBetween(
|
| - i->second.transform.start[j],
|
| - i->second.transform.target[j]);
|
| - SetMatrixElement(transform.matrix(), j, value);
|
| - }
|
| - delegate()->SetTransformFromAnimator(transform);
|
| + if (!found_sequence)
|
| + animation_queue_.push_front(make_linked_ptr(animation));
|
| +}
|
| +
|
| +void LayerAnimator::RemoveAllAnimationsWithACommonProperty(
|
| + LayerAnimationSequence* sequence,
|
| + bool abort) {
|
| + // For all the running animations, if they animate the same property,
|
| + // progress them to the end and remove them. Note: at each iteration i is
|
| + // incremented or an element is removed from the queue, so
|
| + // animation_queue_.size() - i is always decreasing and we are always making
|
| + // progress towards the loop terminating.
|
| + for (size_t i = 0; i < running_animations_.size();) {
|
| + if (running_animations_[i].sequence->HasCommonProperty(
|
| + sequence->properties())) {
|
| + // Finish the animation.
|
| + if (abort)
|
| + running_animations_[i].sequence->Abort();
|
| + else
|
| + running_animations_[i].sequence->Progress(
|
| + running_animations_[i].sequence->duration(), delegate());
|
| + RemoveAnimation(running_animations_[i].sequence);
|
| + } else {
|
| + ++i;
|
| + }
|
| + }
|
| +
|
| + // Same for the queued animations. See comment above about loop termination.
|
| + for (size_t i = 0; i < animation_queue_.size();) {
|
| + if (animation_queue_[i]->HasCommonProperty(sequence->properties())) {
|
| + // Finish the animation.
|
| + if (abort)
|
| + animation_queue_[i]->Abort();
|
| + else
|
| + animation_queue_[i]->Progress(animation_queue_[i]->duration(),
|
| + delegate());
|
| + RemoveAnimation(animation_queue_[i].get());
|
| + } else {
|
| + ++i;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) {
|
| + const bool abort = false;
|
| + RemoveAllAnimationsWithACommonProperty(sequence, abort);
|
| + sequence->Progress(sequence->duration(), delegate());
|
| + RemoveAnimation(sequence);
|
| +}
|
| +
|
| +void LayerAnimator::ImmediatelyAnimateToNewTarget(
|
| + LayerAnimationSequence* sequence) {
|
| + const bool abort = true;
|
| + RemoveAllAnimationsWithACommonProperty(sequence, abort);
|
| + AddToQueueIfNotPresent(sequence);
|
| + StartSequenceImmediately(sequence);
|
| +}
|
| +
|
| +void LayerAnimator::EnqueueNewAnimation(LayerAnimationSequence* sequence) {
|
| + // It is assumed that if there was no conflicting animation, we would
|
| + // not have been called. No need to check for a collision; just
|
| + // add to the queue.
|
| + animation_queue_.push_back(make_linked_ptr(sequence));
|
| + ProcessQueue();
|
| +}
|
| +
|
| +void LayerAnimator::ReplaceQueuedAnimations(LayerAnimationSequence* sequence) {
|
| + // Remove all animations that aren't running. Note: at each iteration i is
|
| + // incremented or an element is removed from the queue, so
|
| + // animation_queue_.size() - i is always decreasing and we are always making
|
| + // progress towards the loop terminating.
|
| + for (size_t i = 0; i < animation_queue_.size();) {
|
| + bool is_running = false;
|
| + for (RunningAnimations::const_iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + if ((*iter).sequence == animation_queue_[i]) {
|
| + is_running = true;
|
| break;
|
| }
|
| + }
|
| + if (!is_running)
|
| + RemoveAnimation(animation_queue_[i].get());
|
| + else
|
| + ++i;
|
| + }
|
| + animation_queue_.push_back(make_linked_ptr(sequence));
|
| + ProcessQueue();
|
| +}
|
| +
|
| +void LayerAnimator::ProcessQueue() {
|
| + bool started_sequence = false;
|
| + do {
|
| + started_sequence = false;
|
| +
|
| + // Build a list of all currently animated properties.
|
| + LayerAnimationElement::AnimatableProperties animated;
|
|
|
| - case OPACITY: {
|
| - delegate()->SetOpacityFromAnimator(animation_->CurrentValueBetween(
|
| - i->second.opacity.start, i->second.opacity.target));
|
| + for (RunningAnimations::const_iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + animated.insert((*iter).sequence->properties().begin(),
|
| + (*iter).sequence->properties().end());
|
| + }
|
| +
|
| + // Try to find an animation that doesn't conflict with an animated
|
| + // property or a property that will be animated before it.
|
| + for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
|
| + queue_iter != animation_queue_.end(); ++queue_iter) {
|
| + if (!(*queue_iter)->HasCommonProperty(animated)) {
|
| + StartSequenceImmediately((*queue_iter).get());
|
| + started_sequence = true;
|
| break;
|
| }
|
|
|
| - default:
|
| - NOTREACHED();
|
| + // Animation couldn't be started. Add its properties to the collection so
|
| + // that we don't start a conflicting animation. For example, if our queue
|
| + // has the elements { {T,B}, {B} } (that is, an element that animates both
|
| + // the transform and the bounds followed by an element that animates the
|
| + // bounds), and we're currently animating the transform, we can't start
|
| + // the first element because it animates the transform, too. We cannot
|
| + // start the second element, either, because the first element animates
|
| + // bounds too, and needs to go first.
|
| + animated.insert((*queue_iter)->properties().begin(),
|
| + (*queue_iter)->properties().end());
|
| }
|
| - }
|
| - layer_->ScheduleDraw();
|
| -}
|
|
|
| -void LayerAnimator::AnimationEnded(const ui::Animation* animation) {
|
| - AnimationProgressed(animation);
|
| - if (layer_->delegate())
|
| - layer_->delegate()->OnLayerAnimationEnded(animation);
|
| + // If we started a sequence, try again. We may be able to start several.
|
| + } while (started_sequence);
|
| }
|
|
|
| -void LayerAnimator::StopAnimating(AnimationProperty property) {
|
| - if (!IsAnimating(property))
|
| - return;
|
| +bool LayerAnimator::StartSequenceImmediately(LayerAnimationSequence* sequence) {
|
| + // Ensure that no one is animating one of the sequence's properties already.
|
| + for (RunningAnimations::const_iterator iter = running_animations_.begin();
|
| + iter != running_animations_.end(); ++iter) {
|
| + if ((*iter).sequence->HasCommonProperty(sequence->properties()))
|
| + return false;
|
| + }
|
|
|
| - elements_.erase(property);
|
| -}
|
| + // All clear, actually start the sequence. Note: base::TimeTicks::Now has
|
| + // a resolution that can be as bad as 15ms. If this causes glitches in the
|
| + // animations, this can be switched to HighResNow() (animation uses Now()
|
| + // internally).
|
| + base::TimeTicks start_time = is_animating()
|
| + ? last_step_time_
|
| + : base::TimeTicks::Now();
|
| +
|
| + running_animations_.push_back(RunningAnimation(sequence, start_time));
|
| +
|
| + // Need to keep a reference to the animation.
|
| + AddToQueueIfNotPresent(sequence);
|
| +
|
| + // Ensure that animations get stepped at their start time.
|
| + Step(start_time);
|
|
|
| -LayerAnimatorDelegate* LayerAnimator::delegate() {
|
| - return static_cast<LayerAnimatorDelegate*>(layer_);
|
| + return true;
|
| }
|
|
|
| } // namespace ui
|
|
|