blob: 2cf25dc5339e01742db010fe6149ff72e718917c [file] [log] [blame]
// Copyright (c) 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 "ui/compositor/layer_animator.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "ui/base/animation/animation_container.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_delegate.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
namespace ui {
class LayerAnimator;
namespace {
static const base::TimeDelta kDefaultTransitionDuration =
base::TimeDelta::FromMilliseconds(120);
static const base::TimeDelta kTimerInterval =
base::TimeDelta::FromMilliseconds(10);
// Returns the AnimationContainer we're added to.
ui::AnimationContainer* GetAnimationContainer() {
static ui::AnimationContainer* container = NULL;
if (!container) {
container = new AnimationContainer();
container->AddRef();
}
return container;
}
} // namespace
// static
bool LayerAnimator::disable_animations_for_test_ = false;
// static
bool LayerAnimator::slow_animation_mode_ = false;
// static
int LayerAnimator::slow_animation_scale_factor_ = 4;
// LayerAnimator public --------------------------------------------------------
LayerAnimator::LayerAnimator(base::TimeDelta transition_duration)
: delegate_(NULL),
preemption_strategy_(IMMEDIATELY_SET_NEW_TARGET),
transition_duration_(transition_duration),
tween_type_(Tween::LINEAR),
is_started_(false),
disable_timer_for_test_(false) {
}
LayerAnimator::~LayerAnimator() {
for (size_t i = 0; i < running_animations_.size(); ++i)
running_animations_[i].sequence->OnAnimatorDestroyed();
ClearAnimationsInternal();
delegate_ = NULL;
}
// static
LayerAnimator* LayerAnimator::CreateDefaultAnimator() {
return new LayerAnimator(base::TimeDelta::FromMilliseconds(0));
}
// static
LayerAnimator* LayerAnimator::CreateImplicitAnimator() {
return new LayerAnimator(kDefaultTransitionDuration);
}
void LayerAnimator::SetTransform(const Transform& transform) {
base::TimeDelta duration = GetTransitionDuration();
scoped_ptr<LayerAnimationElement> element(
LayerAnimationElement::CreateTransformElement(transform, duration));
element->set_tween_type(tween_type_);
StartAnimation(new LayerAnimationSequence(element.release()));
}
Transform LayerAnimator::GetTargetTransform() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.transform;
}
void LayerAnimator::SetBounds(const gfx::Rect& bounds) {
base::TimeDelta duration = GetTransitionDuration();
scoped_ptr<LayerAnimationElement> element(
LayerAnimationElement::CreateBoundsElement(bounds, duration));
element->set_tween_type(tween_type_);
StartAnimation(new LayerAnimationSequence(element.release()));
}
gfx::Rect LayerAnimator::GetTargetBounds() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.bounds;
}
void LayerAnimator::SetOpacity(float opacity) {
base::TimeDelta duration = GetTransitionDuration();
scoped_ptr<LayerAnimationElement> element(
LayerAnimationElement::CreateOpacityElement(opacity, duration));
element->set_tween_type(tween_type_);
StartAnimation(new LayerAnimationSequence(element.release()));
}
float LayerAnimator::GetTargetOpacity() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.opacity;
}
void LayerAnimator::SetVisibility(bool visibility) {
base::TimeDelta duration = GetTransitionDuration();
// Tween type doesn't matter for visibility.
StartAnimation(new LayerAnimationSequence(
LayerAnimationElement::CreateVisibilityElement(
visibility, duration)));
}
bool LayerAnimator::GetTargetVisibility() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.visibility;
}
void LayerAnimator::SetBrightness(float brightness) {
base::TimeDelta duration = GetTransitionDuration();
scoped_ptr<LayerAnimationElement> element(
LayerAnimationElement::CreateBrightnessElement(brightness, duration));
element->set_tween_type(tween_type_);
StartAnimation(new LayerAnimationSequence(element.release()));
}
float LayerAnimator::GetTargetBrightness() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.brightness;
}
void LayerAnimator::SetGrayscale(float grayscale) {
base::TimeDelta duration = GetTransitionDuration();
scoped_ptr<LayerAnimationElement> element(
LayerAnimationElement::CreateGrayscaleElement(grayscale, duration));
element->set_tween_type(tween_type_);
StartAnimation(new LayerAnimationSequence(element.release()));
}
float LayerAnimator::GetTargetGrayscale() const {
LayerAnimationElement::TargetValue target(delegate());
GetTargetValue(&target);
return target.grayscale;
}
void LayerAnimator::SetDelegate(LayerAnimationDelegate* delegate) {
delegate_ = delegate;
}
void LayerAnimator::StartAnimation(LayerAnimationSequence* animation) {
scoped_refptr<LayerAnimator> retain(this);
OnScheduled(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;
}
}
}
FinishAnyAnimationWithZeroDuration();
UpdateAnimationState();
}
void LayerAnimator::ScheduleAnimation(LayerAnimationSequence* animation) {
scoped_refptr<LayerAnimator> retain(this);
OnScheduled(animation);
if (is_animating()) {
animation_queue_.push_back(make_linked_ptr(animation));
ProcessQueue();
} else {
StartSequenceImmediately(animation);
}
UpdateAnimationState();
}
void LayerAnimator::ScheduleTogether(
const std::vector<LayerAnimationSequence*>& animations) {
scoped_refptr<LayerAnimator> retain(this);
// 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())));
// 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);
}
UpdateAnimationState();
}
void LayerAnimator::SchedulePauseForProperties(
base::TimeDelta duration,
LayerAnimationElement::AnimatableProperty property,
...) {
ui::LayerAnimationElement::AnimatableProperties properties_to_pause;
va_list marker;
va_start(marker, property);
for (int p = static_cast<int>(property); p != -1; p = va_arg(marker, int)) {
properties_to_pause.insert(
static_cast<LayerAnimationElement::AnimatableProperty>(p));
}
va_end(marker);
ScheduleAnimation(new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreatePauseElement(
properties_to_pause, duration)));
}
bool LayerAnimator::IsAnimatingProperty(
LayerAnimationElement::AnimatableProperty property) const {
for (AnimationQueue::const_iterator queue_iter = animation_queue_.begin();
queue_iter != animation_queue_.end(); ++queue_iter) {
if ((*queue_iter)->properties().find(property) !=
(*queue_iter)->properties().end()) {
return true;
}
}
return false;
}
void LayerAnimator::StopAnimatingProperty(
LayerAnimationElement::AnimatableProperty property) {
scoped_refptr<LayerAnimator> retain(this);
while (true) {
RunningAnimation* running = GetRunningAnimation(property);
if (!running)
break;
FinishAnimation(running->sequence);
}
}
void LayerAnimator::StopAnimating() {
scoped_refptr<LayerAnimator> retain(this);
while (is_animating())
FinishAnimation(running_animations_[0].sequence);
}
void LayerAnimator::AddObserver(LayerAnimationObserver* observer) {
if (!observers_.HasObserver(observer))
observers_.AddObserver(observer);
}
void LayerAnimator::RemoveObserver(LayerAnimationObserver* observer) {
observers_.RemoveObserver(observer);
// Remove the observer from all sequences as well.
for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
queue_iter != animation_queue_.end(); ++queue_iter) {
(*queue_iter)->RemoveObserver(observer);
}
}
// LayerAnimator protected -----------------------------------------------------
void LayerAnimator::ProgressAnimation(LayerAnimationSequence* sequence,
base::TimeDelta delta) {
if (!delegate())
return;
sequence->Progress(delta, delegate());
}
bool LayerAnimator::HasAnimation(LayerAnimationSequence* sequence) const {
for (AnimationQueue::const_iterator queue_iter = animation_queue_.begin();
queue_iter != animation_queue_.end(); ++queue_iter) {
if ((*queue_iter).get() == sequence)
return true;
}
return false;
}
// LayerAnimator private -------------------------------------------------------
void LayerAnimator::Step(base::TimeTicks now) {
TRACE_EVENT0("ui", "LayerAnimator::Step");
scoped_refptr<LayerAnimator> retain(this);
last_step_time_ = now;
// We need to make a copy of the running animations because progressing them
// and finishing them may indirectly affect the collection of running
// animations.
RunningAnimations running_animations_copy = running_animations_;
for (size_t i = 0; i < running_animations_copy.size(); ++i) {
if (!HasAnimation(running_animations_copy[i].sequence))
continue;
base::TimeDelta delta = now - running_animations_copy[i].start_time;
if (delta >= running_animations_copy[i].sequence->duration() &&
!running_animations_copy[i].sequence->is_cyclic()) {
FinishAnimation(running_animations_copy[i].sequence);
} else
ProgressAnimation(running_animations_copy[i].sequence, delta);
}
}
void LayerAnimator::SetStartTime(base::TimeTicks start_time) {
// Do nothing.
}
base::TimeDelta LayerAnimator::GetTimerInterval() const {
return kTimerInterval;
}
void LayerAnimator::UpdateAnimationState() {
if (disable_timer_for_test_)
return;
const bool should_start = is_animating();
if (should_start && !is_started_)
GetAnimationContainer()->Start(this);
else if (!should_start && is_started_)
GetAnimationContainer()->Stop(this);
is_started_ = should_start;
}
LayerAnimationSequence* LayerAnimator::RemoveAnimation(
LayerAnimationSequence* sequence) {
linked_ptr<LayerAnimationSequence> to_return;
// 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) {
to_return = *queue_iter;
animation_queue_.erase(queue_iter);
break;
}
}
return to_return.release();
}
void LayerAnimator::FinishAnimation(LayerAnimationSequence* sequence) {
scoped_refptr<LayerAnimator> retain(this);
scoped_ptr<LayerAnimationSequence> removed(RemoveAnimation(sequence));
if (delegate())
sequence->Progress(sequence->duration(), delegate());
ProcessQueue();
UpdateAnimationState();
}
void LayerAnimator::FinishAnyAnimationWithZeroDuration() {
scoped_refptr<LayerAnimator> retain(this);
// Special case: if we've started a 0 duration animation, just finish it now
// and get rid of it. We need to make a copy because Progress may indirectly
// cause new animations to start running.
RunningAnimations running_animations_copy = running_animations_;
for (size_t i = 0; i < running_animations_copy.size(); ++i) {
if (!HasAnimation(running_animations_copy[i].sequence))
continue;
if (running_animations_copy[i].sequence->duration() == base::TimeDelta()) {
ProgressAnimation(running_animations_copy[i].sequence,
running_animations_copy[i].sequence->duration());
scoped_ptr<LayerAnimationSequence> removed(
RemoveAnimation(running_animations_copy[i].sequence));
}
}
ProcessQueue();
UpdateAnimationState();
}
void LayerAnimator::ClearAnimations() {
scoped_refptr<LayerAnimator> retain(this);
ClearAnimationsInternal();
}
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::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;
}
}
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, Aborting or Progressing
// animations may affect the collection of running animations, so we need to
// operate on a copy.
RunningAnimations running_animations_copy = running_animations_;
for (size_t i = 0; i < running_animations_copy.size(); ++i) {
if (!HasAnimation(running_animations_copy[i].sequence))
continue;
if (running_animations_copy[i].sequence->HasCommonProperty(
sequence->properties())) {
scoped_ptr<LayerAnimationSequence> removed(
RemoveAnimation(running_animations_copy[i].sequence));
if (abort)
running_animations_copy[i].sequence->Abort();
else
ProgressAnimation(running_animations_copy[i].sequence,
running_animations_copy[i].sequence->duration());
}
}
// Same for the queued animations that haven't been started. Again, we'll
// need to operate on a copy.
std::vector<LayerAnimationSequence*> sequences;
for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
queue_iter != animation_queue_.end(); ++queue_iter)
sequences.push_back((*queue_iter).get());
for (size_t i = 0; i < sequences.size(); ++i) {
if (!HasAnimation(sequences[i]))
continue;
if (sequences[i]->HasCommonProperty(sequence->properties())) {
scoped_ptr<LayerAnimationSequence> removed(
RemoveAnimation(sequences[i]));
if (abort)
sequences[i]->Abort();
else
ProgressAnimation(sequences[i], sequences[i]->duration());
}
}
}
void LayerAnimator::ImmediatelySetNewTarget(LayerAnimationSequence* sequence) {
// Ensure that sequence is disposed of when this function completes.
scoped_ptr<LayerAnimationSequence> to_dispose(sequence);
const bool abort = false;
RemoveAllAnimationsWithACommonProperty(sequence, abort);
LayerAnimationSequence* removed = RemoveAnimation(sequence);
DCHECK(removed == NULL || removed == sequence);
ProgressAnimation(sequence, sequence->duration());
}
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)
delete 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;
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. Note: starting
// the animation may indirectly cause more animations to be started, so we
// need to operate on a copy.
std::vector<LayerAnimationSequence*> sequences;
for (AnimationQueue::iterator queue_iter = animation_queue_.begin();
queue_iter != animation_queue_.end(); ++queue_iter)
sequences.push_back((*queue_iter).get());
for (size_t i = 0; i < sequences.size(); ++i) {
if (!sequences[i]->HasCommonProperty(animated)) {
StartSequenceImmediately(sequences[i]);
started_sequence = true;
break;
}
// 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(sequences[i]->properties().begin(),
sequences[i]->properties().end());
}
// If we started a sequence, try again. We may be able to start several.
} while (started_sequence);
}
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;
}
// 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).
// All LayerAnimators share the same AnimationContainer. Use the
// last_tick_time() from there to ensure animations started during the same
// event complete at the same time.
base::TimeTicks start_time;
if (is_animating())
start_time = last_step_time_;
else if (GetAnimationContainer()->is_running())
start_time = GetAnimationContainer()->last_tick_time();
else
start_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);
return true;
}
void LayerAnimator::GetTargetValue(
LayerAnimationElement::TargetValue* target) const {
for (AnimationQueue::const_iterator iter = animation_queue_.begin();
iter != animation_queue_.end(); ++iter) {
(*iter)->GetTargetValue(target);
}
}
void LayerAnimator::OnScheduled(LayerAnimationSequence* sequence) {
if (observers_.might_have_observers()) {
ObserverListBase<LayerAnimationObserver>::Iterator it(observers_);
LayerAnimationObserver* obs;
while ((obs = it.GetNext()) != NULL) {
sequence->AddObserver(obs);
}
}
sequence->OnScheduled();
}
base::TimeDelta LayerAnimator::GetTransitionDuration() const {
return transition_duration_;
}
void LayerAnimator::ClearAnimationsInternal() {
// Abort should never affect the set of running animations, but just in case
// clients are badly behaved, we will use a copy of the running animations.
RunningAnimations running_animations_copy = running_animations_;
for (size_t i = 0; i < running_animations_copy.size(); ++i) {
if (!HasAnimation(running_animations_copy[i].sequence))
continue;
scoped_ptr<LayerAnimationSequence> removed(
RemoveAnimation(running_animations_copy[i].sequence));
if (removed.get())
removed->Abort();
}
// This *should* have cleared the list of running animations.
DCHECK(running_animations_.empty());
running_animations_.clear();
animation_queue_.clear();
UpdateAnimationState();
}
} // namespace ui