// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/session_state_animator_impl.h"
#include <memory>
#include <utility>
#include <vector>
#include "ash/shell.h"
#include "ash/utility/layer_copy_animator.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/window_animations.h"
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// Returns the primary root window's container.
aura::Window* GetWallpaper() {
aura::Window* root_window = Shell::GetPrimaryRootWindow();
return Shell::GetContainer(root_window, kShellWindowId_WallpaperContainer);
}
// Fades |window| to |opacity| over |duration|.
void StartOpacityAnimationForWindow(aura::Window* window,
float opacity,
base::TimeDelta duration,
ui::LayerAnimationObserver* observer) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(opacity, duration));
if (observer)
sequence->AddObserver(observer);
animator->StartAnimation(sequence);
}
// Makes |window| fully transparent instantaneously.
void HideWindowImmediately(aura::Window* window,
ui::LayerAnimationObserver* observer) {
window->layer()->SetOpacity(0.0);
if (observer)
observer->OnLayerAnimationEnded(NULL);
}
void HideWindow(aura::Window* window,
base::TimeDelta duration,
bool above,
ui::LayerAnimationObserver* observer) {
ui::Layer* layer = window->layer();
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(duration);
settings.SetTweenType(gfx::Tween::EASE_OUT);
SetTransformForScaleAnimation(
layer, above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);
settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
layer->SetOpacity(0.0f);
// After the animation completes snap the transform back to the identity,
// otherwise any one that asks for screen bounds gets a slightly scaled
// version.
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
settings.SetTransitionDuration(base::TimeDelta());
layer->SetTransform(gfx::Transform());
// A bit of a dirty trick: we need to catch the end of the animation we don't
// control. So we use two facts we know: which animator will be used and the
// target opacity to add "Do nothing" animation sequence.
// Unfortunately, we can not just use empty LayerAnimationSequence, because
// it does not call NotifyEnded().
if (observer) {
ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(0.0,
base::TimeDelta()));
sequence->AddObserver(observer);
layer->GetAnimator()->ScheduleAnimation(sequence);
}
}
// Animates |layer| to identity transform and full opacity over |duration|.
void TransformLayerToBaseState(ui::Layer* layer,
base::TimeDelta duration,
ui::LayerAnimationObserver* observer) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
// Animate to target values.
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(duration);
settings.SetTweenType(gfx::Tween::EASE_OUT);
layer->SetTransform(gfx::Transform());
// TODO(oshima): TweenType can't be changed per property.
settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
layer->SetOpacity(1.0f);
// A bit of a dirty trick: we need to catch the end of the animation we don't
// control. So we use two facts we know: which animator will be used and the
// target opacity to add "Do nothing" animation sequence.
// Unfortunately, we can not just use empty LayerAnimationSequence, because
// it does not call NotifyEnded().
if (observer) {
ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(1.0,
base::TimeDelta()));
sequence->AddObserver(observer);
layer->GetAnimator()->ScheduleAnimation(sequence);
}
}
// Starts grayscale/brightness animation for |window| over |duration|. Target
// value for both grayscale and brightness are specified by |target|.
void StartGrayscaleBrightnessAnimationForWindow(
aura::Window* window,
float target,
base::TimeDelta duration,
gfx::Tween::Type tween_type,
ui::LayerAnimationObserver* observer) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
std::unique_ptr<ui::LayerAnimationSequence> brightness_sequence =
std::make_unique<ui::LayerAnimationSequence>();
std::unique_ptr<ui::LayerAnimationSequence> grayscale_sequence =
std::make_unique<ui::LayerAnimationSequence>();
std::unique_ptr<ui::LayerAnimationElement> brightness_element =
ui::LayerAnimationElement::CreateBrightnessElement(target, duration);
brightness_element->set_tween_type(tween_type);
brightness_sequence->AddElement(std::move(brightness_element));
std::unique_ptr<ui::LayerAnimationElement> grayscale_element =
ui::LayerAnimationElement::CreateGrayscaleElement(target, duration);
grayscale_element->set_tween_type(tween_type);
grayscale_sequence->AddElement(std::move(grayscale_element));
std::vector<ui::LayerAnimationSequence*> animations;
animations.push_back(brightness_sequence.release());
animations.push_back(grayscale_sequence.release());
if (observer)
animations[0]->AddObserver(observer);
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animator->StartTogether(animations);
}
// Animation observer that will drop animated foreground once animation is
// finished. It is used in when undoing shutdown animation.
class CallbackAnimationObserver : public ui::LayerAnimationObserver {
public:
explicit CallbackAnimationObserver(base::OnceClosure callback)
: callback_(std::move(callback)) {}
CallbackAnimationObserver(const CallbackAnimationObserver&) = delete;
CallbackAnimationObserver& operator=(const CallbackAnimationObserver&) =
delete;
~CallbackAnimationObserver() override = default;
private:
// Overridden from ui::LayerAnimationObserver:
void OnLayerAnimationEnded(ui::LayerAnimationSequence* seq) override {
// Drop foreground once animation is over.
std::move(callback_).Run();
delete this;
}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* seq) override {
// Drop foreground once animation is over.
std::move(callback_).Run();
delete this;
}
void OnLayerAnimationScheduled(ui::LayerAnimationSequence* seq) override {}
base::OnceClosure callback_;
};
void GetContainersInRootWindow(int container_mask,
aura::Window* root_window,
aura::Window::Windows* containers) {
if (container_mask & SessionStateAnimator::ROOT_CONTAINER) {
containers->push_back(root_window);
}
if (container_mask & SessionStateAnimator::WALLPAPER) {
containers->push_back(
Shell::GetContainer(root_window, kShellWindowId_WallpaperContainer));
}
if (container_mask & SessionStateAnimator::SHELF) {
containers->push_back(
Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
}
if (container_mask & SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS) {
// `non_lock_screen_containers` may already be removed in some tests.
if (aura::Window* non_lock_screen_containers = Shell::GetContainer(
root_window, kShellWindowId_NonLockScreenContainersContainer);
non_lock_screen_containers) {
for (const int id : SessionStateAnimatorImpl::
ContainersToAnimateInNonLockScreenContainer) {
containers->push_back(Shell::GetContainer(root_window, id));
}
// The active desk container should be animated as well besides the ones
// inside `ContainersToAnimateInNonLockScreenContainer`.
containers->push_back(
desks_util::GetActiveDeskContainerForRoot(root_window));
}
}
if (container_mask & SessionStateAnimator::LOCK_SCREEN_WALLPAPER) {
containers->push_back(Shell::GetContainer(
root_window, kShellWindowId_LockScreenWallpaperContainer));
}
if (container_mask & SessionStateAnimator::LOCK_SCREEN_CONTAINERS) {
containers->push_back(Shell::GetContainer(
root_window, kShellWindowId_LockScreenContainersContainer));
}
if (container_mask & SessionStateAnimator::LOCK_SCREEN_RELATED_CONTAINERS) {
containers->push_back(Shell::GetContainer(
root_window, kShellWindowId_LockScreenRelatedContainersContainer));
}
}
void ShowWindow(aura::Window* window,
base::TimeDelta duration,
bool above,
ui::LayerAnimationObserver* observer) {
if (window->children().empty()) {
window->layer()->SetTransform(gfx::Transform());
window->layer()->SetOpacity(1.f);
return;
}
auto* animator = LayerCopyAnimator::Get(window);
if (!animator || animator->animation_requested())
animator = new LayerCopyAnimator(window);
auto animation_callback = [](base::TimeDelta duration, bool above,
ui::Layer* animating_layer,
ui::LayerAnimationObserver* observer) {
DCHECK(animating_layer->parent());
// Set initial state of animation
SetTransformForScaleAnimation(
animating_layer,
above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);
animating_layer->SetOpacity(0.f);
TransformLayerToBaseState(animating_layer, duration, observer);
};
animator->MaybeStartAnimation(
observer, base::BindOnce(animation_callback, duration, above));
}
} // namespace
// This observer is intended to use in cases when some action has to be taken
// once some animation successfully completes (i.e. it was not aborted).
// Observer will count a number of sequences it is attached to, and a number of
// finished sequences (either Ended or Aborted). Once these two numbers are
// equal, observer will delete itself, calling callback passed to constructor if
// there were no aborted animations.
// This way it can be either used to wait for some animation to be finished in
// multiple layers, to wait once a sequence of animations is finished in one
// layer or the mixture of both.
class SessionStateAnimatorImpl::AnimationSequence
: public SessionStateAnimator::AnimationSequence,
public ui::LayerAnimationObserver {
public:
explicit AnimationSequence(SessionStateAnimatorImpl* animator,
AnimationCallback callback)
: SessionStateAnimator::AnimationSequence(std::move(callback)),
animator_(animator),
sequences_attached_(0),
sequences_completed_(0) {}
AnimationSequence(const AnimationSequence&) = delete;
AnimationSequence& operator=(const AnimationSequence&) = delete;
// SessionStateAnimator::AnimationSequence:
void StartAnimation(int container_mask,
SessionStateAnimator::AnimationType type,
SessionStateAnimator::AnimationSpeed speed) override {
animator_->StartAnimationInSequence(container_mask, type, speed, this);
}
void EndSequence() override {
SessionStateAnimator::AnimationSequence::EndSequence();
// Mark animation completed if there are no pending ones at the end in
// case it is skipped during animation setup because the sequence is not
// marked as ended..
if (sequences_completed_ == sequences_attached_) {
OnAnimationCompleted();
}
}
private:
~AnimationSequence() override = default;
// ui::LayerAnimationObserver:
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {
sequences_completed_++;
if (sequence_ended() && sequences_completed_ == sequences_attached_) {
OnAnimationCompleted();
}
}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {
sequences_completed_++;
if (sequence_ended() && sequences_completed_ == sequences_attached_) {
OnAnimationAborted();
}
}
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {}
void OnAttachedToSequence(ui::LayerAnimationSequence* sequence) override {
LayerAnimationObserver::OnAttachedToSequence(sequence);
sequences_attached_++;
}
raw_ptr<SessionStateAnimatorImpl, LeakedDanglingUntriaged>
animator_; // not owned
// Number of sequences this observer was attached to.
int sequences_attached_;
// Number of sequences either ended or aborted.
int sequences_completed_;
};
SessionStateAnimatorImpl::SessionStateAnimatorImpl() = default;
SessionStateAnimatorImpl::~SessionStateAnimatorImpl() = default;
// Fills |containers| with the containers described by |container_mask|.
void SessionStateAnimatorImpl::GetContainers(
int container_mask,
aura::Window::Windows* containers) {
containers->clear();
for (aura::Window* root_window : Shell::GetAllRootWindows())
GetContainersInRootWindow(container_mask, root_window, containers);
// Some of containers may be null in some tests.
containers->erase(
std::remove(containers->begin(), containers->end(), nullptr),
containers->end());
}
void SessionStateAnimatorImpl::StartAnimation(int container_mask,
AnimationType type,
AnimationSpeed speed) {
aura::Window::Windows containers;
GetContainers(container_mask, &containers);
for (aura::Window::Windows::const_iterator it = containers.begin();
it != containers.end(); ++it) {
RunAnimationForWindow(*it, type, speed, NULL);
}
}
void SessionStateAnimatorImpl::StartAnimationWithCallback(
int container_mask,
AnimationType type,
AnimationSpeed speed,
base::OnceClosure callback) {
aura::Window::Windows containers;
GetContainers(container_mask, &containers);
base::RepeatingClosure animation_done_closure =
base::BarrierClosure(containers.size(), std::move(callback));
for (aura::Window::Windows::const_iterator it = containers.begin();
it != containers.end(); ++it) {
ui::LayerAnimationObserver* observer =
new CallbackAnimationObserver(animation_done_closure);
RunAnimationForWindow(*it, type, speed, observer);
}
}
SessionStateAnimator::AnimationSequence*
SessionStateAnimatorImpl::BeginAnimationSequence(AnimationCallback callback) {
return new AnimationSequence(this, std::move(callback));
}
bool SessionStateAnimatorImpl::IsWallpaperHidden() const {
return !GetWallpaper()->IsVisible();
}
void SessionStateAnimatorImpl::ShowWallpaper() {
ui::ScopedLayerAnimationSettings settings(
GetWallpaper()->layer()->GetAnimator());
settings.SetTransitionDuration(base::TimeDelta());
GetWallpaper()->Show();
}
void SessionStateAnimatorImpl::HideWallpaper() {
ui::ScopedLayerAnimationSettings settings(
GetWallpaper()->layer()->GetAnimator());
settings.SetTransitionDuration(base::TimeDelta());
GetWallpaper()->Hide();
}
void SessionStateAnimatorImpl::StartAnimationInSequence(
int container_mask,
AnimationType type,
AnimationSpeed speed,
AnimationSequence* observer) {
aura::Window::Windows containers;
GetContainers(container_mask, &containers);
for (aura::Window::Windows::const_iterator it = containers.begin();
it != containers.end(); ++it) {
RunAnimationForWindow(*it, type, speed, observer);
}
}
void SessionStateAnimatorImpl::RunAnimationForWindow(
aura::Window* window,
AnimationType type,
AnimationSpeed speed,
ui::LayerAnimationObserver* observer) {
base::TimeDelta duration = GetDuration(speed);
switch (type) {
case ANIMATION_FADE_IN:
StartOpacityAnimationForWindow(window, 1.0, duration, observer);
break;
case ANIMATION_FADE_OUT:
StartOpacityAnimationForWindow(window, 0.0, duration, observer);
break;
case ANIMATION_HIDE_IMMEDIATELY:
DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
HideWindowImmediately(window, observer);
break;
case ANIMATION_LIFT:
HideWindow(window, duration, true, observer);
break;
case ANIMATION_DROP:
ShowWindow(window, duration, true, observer);
break;
case ANIMATION_UNDO_LIFT:
TransformLayerToBaseState(window->layer(), duration, observer);
break;
case ANIMATION_RAISE_TO_SCREEN:
ShowWindow(window, duration, false, observer);
break;
case ANIMATION_GRAYSCALE_BRIGHTNESS:
StartGrayscaleBrightnessAnimationForWindow(window, 1.0, duration,
gfx::Tween::EASE_IN, observer);
break;
case ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
StartGrayscaleBrightnessAnimationForWindow(
window, 0.0, duration, gfx::Tween::EASE_IN_OUT, observer);
break;
case ANIMATION_COPY_LAYER:
if (!window->children().empty())
new LayerCopyAnimator(window);
break;
}
}
} // namespace ash