blob: 96ecddc2a808f17f752d75422e44f35ceb5b47b9 [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 "ash/wm/window_animations.h"
#include <math.h>
#include <algorithm>
#include <vector>
#include "ash/ash_switches.h"
#include "ash/launcher/launcher.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace_controller.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_property.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/screen.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
DECLARE_WINDOW_PROPERTY_TYPE(int)
DECLARE_WINDOW_PROPERTY_TYPE(ash::WindowVisibilityAnimationType)
DECLARE_WINDOW_PROPERTY_TYPE(ash::WindowVisibilityAnimationTransition)
DECLARE_WINDOW_PROPERTY_TYPE(float)
using aura::Window;
using base::TimeDelta;
using ui::Layer;
namespace ash {
namespace internal {
namespace {
const float kWindowAnimation_Vertical_TranslateY = 15.f;
}
DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationType,
kWindowVisibilityAnimationTypeKey,
WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationDurationKey, 0);
DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationTransition,
kWindowVisibilityAnimationTransitionKey,
ANIMATE_BOTH);
DEFINE_WINDOW_PROPERTY_KEY(float,
kWindowVisibilityAnimationVerticalPositionKey,
kWindowAnimation_Vertical_TranslateY);
namespace {
const int kDefaultAnimationDurationForMenuMS = 150;
const int kLayerAnimationsForMinimizeDurationMS = 200;
// Durations for the cross-fade animation, in milliseconds.
const float kCrossFadeDurationMinMs = 100.f;
const float kCrossFadeDurationMaxMs = 400.f;
// Durations for the brightness/grayscale fade animation, in milliseconds.
const int kBrightnessGrayscaleFadeDurationMs = 1000;
// Brightness/grayscale values for hide/show window animations.
const float kWindowAnimation_HideBrightnessGrayscale = 1.f;
const float kWindowAnimation_ShowBrightnessGrayscale = 0.f;
const float kWindowAnimation_HideOpacity = 0.f;
const float kWindowAnimation_ShowOpacity = 1.f;
const float kWindowAnimation_TranslateFactor = 0.025f;
const float kWindowAnimation_ScaleFactor = .95f;
// TODO(sky): if we end up sticking with 0, nuke the code doing the rotation.
const float kWindowAnimation_MinimizeRotate = 0.f;
// Amount windows are scaled during workspace animations.
const float kWorkspaceScale = .95f;
int64 Round64(float f) {
return static_cast<int64>(f + 0.5f);
}
base::TimeDelta GetWindowVisibilityAnimationDuration(aura::Window* window) {
int duration =
window->GetProperty(kWindowVisibilityAnimationDurationKey);
if (duration == 0 && window->type() == aura::client::WINDOW_TYPE_MENU) {
return base::TimeDelta::FromMilliseconds(
kDefaultAnimationDurationForMenuMS);
}
return TimeDelta::FromInternalValue(duration);
}
bool HasWindowVisibilityAnimationTransition(
aura::Window* window,
WindowVisibilityAnimationTransition transition) {
WindowVisibilityAnimationTransition prop = window->GetProperty(
kWindowVisibilityAnimationTransitionKey);
return (prop & transition) != 0;
}
// Gets/sets the WindowVisibilityAnimationType associated with a window.
WindowVisibilityAnimationType GetWindowVisibilityAnimationType(
aura::Window* window) {
WindowVisibilityAnimationType type =
window->GetProperty(kWindowVisibilityAnimationTypeKey);
if (type == WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT) {
return (window->type() == aura::client::WINDOW_TYPE_MENU ||
window->type() == aura::client::WINDOW_TYPE_TOOLTIP) ?
WINDOW_VISIBILITY_ANIMATION_TYPE_FADE :
WINDOW_VISIBILITY_ANIMATION_TYPE_DROP;
}
return type;
}
// Observes a hide animation.
// A window can be hidden for a variety of reasons. Sometimes, Hide() will be
// called and life is simple. Sometimes, the window is actually bound to a
// views::Widget and that Widget is closed, and life is a little more
// complicated. When a Widget is closed the aura::Window* is actually not
// destroyed immediately - it is actually just immediately hidden and then
// destroyed when the stack unwinds. To handle this case, we start the hide
// animation immediately when the window is hidden, then when the window is
// subsequently destroyed this object acquires ownership of the window's layer,
// so that it can continue animating it until the animation completes.
// Regardless of whether or not the window is destroyed, this object deletes
// itself when the animation completes.
class HidingWindowAnimationObserver : public ui::ImplicitAnimationObserver,
public aura::WindowObserver {
public:
explicit HidingWindowAnimationObserver(aura::Window* window)
: window_(window) {
window_->AddObserver(this);
}
virtual ~HidingWindowAnimationObserver() {
STLDeleteElements(&layers_);
}
private:
// Overridden from ui::ImplicitAnimationObserver:
virtual void OnImplicitAnimationsCompleted() OVERRIDE {
// Window may have been destroyed by this point.
if (window_)
window_->RemoveObserver(this);
delete this;
}
// Overridden from aura::WindowObserver:
virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
DCHECK_EQ(window, window_);
DCHECK(layers_.empty());
AcquireAllLayers(window_);
// If the Widget has views with layers, then it is necessary to take
// ownership of those layers too.
views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window_);
const views::Widget* const_widget = widget;
if (widget && const_widget->GetRootView() && widget->GetContentsView())
AcquireAllViewLayers(widget->GetContentsView());
window_->RemoveObserver(this);
window_ = NULL;
}
void AcquireAllLayers(aura::Window* window) {
ui::Layer* layer = window->AcquireLayer();
DCHECK(layer);
layers_.push_back(layer);
for (aura::Window::Windows::const_iterator it = window->children().begin();
it != window->children().end();
++it)
AcquireAllLayers(*it);
}
void AcquireAllViewLayers(views::View* view) {
for (int i = 0; i < view->child_count(); ++i)
AcquireAllViewLayers(view->child_at(i));
if (view->layer()) {
ui::Layer* layer = view->RecreateLayer();
if (layer) {
layer->SuppressPaint();
layers_.push_back(layer);
}
}
}
aura::Window* window_;
std::vector<ui::Layer*> layers_;
DISALLOW_COPY_AND_ASSIGN(HidingWindowAnimationObserver);
};
// ImplicitAnimationObserver used when switching workspaces. Resets the layer
// visibility to 'false' when done. This doesn't need the complexity of
// HidingWindowAnimationObserver as the window isn't closing, and if it does a
// HidingWindowAnimationObserver will be created.
class WorkspaceHidingWindowAnimationObserver
: public ui::ImplicitAnimationObserver {
public:
explicit WorkspaceHidingWindowAnimationObserver(aura::Window* window)
: layer_(window->layer()) {
}
virtual ~WorkspaceHidingWindowAnimationObserver() {
}
private:
// Overridden from ui::ImplicitAnimationObserver:
virtual void OnImplicitAnimationsCompleted() OVERRIDE {
// Restore the correct visibility value (overridden for the duration of the
// animation in AnimateHideWindow()).
layer_->SetVisible(false);
delete this;
}
ui::Layer* layer_;
DISALLOW_COPY_AND_ASSIGN(WorkspaceHidingWindowAnimationObserver);
};
// Shows a window using an animation, animating its opacity from 0.f to 1.f,
// its visibility to true, and its transform from |start_transform| to
// |end_transform|.
void AnimateShowWindowCommon(aura::Window* window,
const ui::Transform& start_transform,
const ui::Transform& end_transform) {
window->layer()->set_delegate(window);
window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
window->layer()->SetTransform(start_transform);
{
// Property sets within this scope will be implicitly animated.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
base::TimeDelta duration = GetWindowVisibilityAnimationDuration(window);
if (duration.ToInternalValue() > 0)
settings.SetTransitionDuration(duration);
window->layer()->SetVisible(true);
window->layer()->SetTransform(end_transform);
window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
}
}
// Hides a window using an animation, animating its opacity from 1.f to 0.f,
// its visibility to false, and its transform to |end_transform|.
void AnimateHideWindowCommon(aura::Window* window,
const ui::Transform& end_transform) {
window->layer()->set_delegate(NULL);
// Property sets within this scope will be implicitly animated.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.AddObserver(new HidingWindowAnimationObserver(window));
base::TimeDelta duration = GetWindowVisibilityAnimationDuration(window);
if (duration.ToInternalValue() > 0)
settings.SetTransitionDuration(duration);
window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
window->layer()->SetTransform(end_transform);
window->layer()->SetVisible(false);
}
// Show/Hide windows using a shrink animation.
void AnimateShowWindow_Drop(aura::Window* window) {
ui::Transform transform;
transform.ConcatScale(kWindowAnimation_ScaleFactor,
kWindowAnimation_ScaleFactor);
gfx::Rect bounds = window->bounds();
transform.ConcatTranslate(
kWindowAnimation_TranslateFactor * bounds.width(),
kWindowAnimation_TranslateFactor * bounds.height());
AnimateShowWindowCommon(window, transform, ui::Transform());
}
void AnimateHideWindow_Drop(aura::Window* window) {
ui::Transform transform;
transform.ConcatScale(kWindowAnimation_ScaleFactor,
kWindowAnimation_ScaleFactor);
gfx::Rect bounds = window->bounds();
transform.ConcatTranslate(
kWindowAnimation_TranslateFactor * bounds.width(),
kWindowAnimation_TranslateFactor * bounds.height());
AnimateHideWindowCommon(window, transform);
}
// Show/Hide windows using a vertical Glenimation.
void AnimateShowWindow_Vertical(aura::Window* window) {
ui::Transform transform;
transform.ConcatTranslate(0, window->GetProperty(
kWindowVisibilityAnimationVerticalPositionKey));
AnimateShowWindowCommon(window, transform, ui::Transform());
}
void AnimateHideWindow_Vertical(aura::Window* window) {
ui::Transform transform;
transform.ConcatTranslate(0, window->GetProperty(
kWindowVisibilityAnimationVerticalPositionKey));
AnimateHideWindowCommon(window, transform);
}
// Show/Hide windows using a fade.
void AnimateShowWindow_Fade(aura::Window* window) {
AnimateShowWindowCommon(window, ui::Transform(), ui::Transform());
}
void AnimateHideWindow_Fade(aura::Window* window) {
AnimateHideWindowCommon(window, ui::Transform());
}
// Builds the transform used when switching workspaces for the specified
// window.
ui::Transform BuildWorkspaceSwitchTransform(aura::Window* window, float scale) {
// Animations for transitioning workspaces scale all windows. To give the
// effect of scaling from the center of the screen the windows are translated.
gfx::Rect bounds = window->bounds();
gfx::Rect parent_bounds(window->parent()->bounds());
float mid_x = static_cast<float>(parent_bounds.width()) / 2.0f;
float initial_x =
(static_cast<float>(bounds.x()) - mid_x) * scale + mid_x;
float mid_y = static_cast<float>(parent_bounds.height()) / 2.0f;
float initial_y =
(static_cast<float>(bounds.y()) - mid_y) * scale + mid_y;
ui::Transform transform;
transform.ConcatTranslate(
initial_x - static_cast<float>(bounds.x()),
initial_y - static_cast<float>(bounds.y()));
transform.ConcatScale(scale, scale);
return transform;
}
void AnimateShowWindow_Workspace(aura::Window* window) {
ui::Transform transform(
BuildWorkspaceSwitchTransform(window, kWorkspaceScale));
// When we call SetOpacity here, if a hide sequence is already running,
// the default animation preemption strategy fast forwards the hide sequence
// to completion and notifies the WorkspaceHidingWindowAnimationObserver to
// set the layer to be invisible. We should call SetVisible after SetOpacity
// to ensure our layer is visible again.
window->layer()->SetOpacity(0.0f);
window->layer()->SetTransform(transform);
window->layer()->SetVisible(true);
{
// Property sets within this scope will be implicitly animated.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
window->layer()->SetTransform(ui::Transform());
// Opacity animates only during the first half of the animation.
settings.SetTransitionDuration(settings.GetTransitionDuration() / 2);
window->layer()->SetOpacity(1.0f);
}
}
void AnimateHideWindow_Workspace(aura::Window* window) {
ui::Transform transform(
BuildWorkspaceSwitchTransform(window, kWorkspaceScale));
window->layer()->SetOpacity(1.0f);
window->layer()->SetTransform(ui::Transform());
// Opacity animates from 1 to 0 only over the second half of the animation. To
// get this functionality two animations are schedule for opacity, the first
// from 1 to 1 (which effectively does nothing) the second from 1 to 0.
// Because we're scheduling two animations of the same property we need to
// change the preemption strategy.
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
{
// Property sets within this scope will be implicitly animated.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
// Add an observer that sets visibility of the layer to false once animation
// completes.
settings.AddObserver(new WorkspaceHidingWindowAnimationObserver(window));
window->layer()->SetTransform(transform);
settings.SetTransitionDuration(settings.GetTransitionDuration() / 2);
window->layer()->SetOpacity(1.0f);
window->layer()->SetOpacity(0.0f);
}
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
}
gfx::Rect GetMinimizeRectForWindow(aura::Window* window) {
gfx::Rect target_bounds = Shell::GetInstance()->launcher()->
GetScreenBoundsOfItemIconForWindow(window);
if (target_bounds.IsEmpty()) {
// Assume the launcher is overflowed, zoom off to the bottom right of the
// work area.
gfx::Rect work_area =
gfx::Screen::GetDisplayNearestWindow(window).work_area();
target_bounds.SetRect(work_area.right(), work_area.bottom(), 0, 0);
}
target_bounds =
ScreenAsh::ConvertRectFromScreen(window->parent(), target_bounds);
return target_bounds;
}
void AddLayerAnimationsForMinimize(aura::Window* window, bool show) {
// Recalculate the transform at restore time since the launcher item may have
// moved while the window was minimized.
gfx::Rect bounds = window->bounds();
gfx::Rect target_bounds = GetMinimizeRectForWindow(window);
float scale_x = static_cast<float>(target_bounds.height()) / bounds.width();
float scale_y = static_cast<float>(target_bounds.width()) / bounds.height();
scoped_ptr<ui::InterpolatedTransform> scale(
new ui::InterpolatedScale(gfx::Point3f(1, 1, 1),
gfx::Point3f(scale_x, scale_y, 1)));
scoped_ptr<ui::InterpolatedTransform> translation(
new ui::InterpolatedTranslation(
gfx::Point(),
gfx::Point(target_bounds.x() - bounds.x(),
target_bounds.y() - bounds.y())));
scoped_ptr<ui::InterpolatedTransform> rotation(
new ui::InterpolatedRotation(0, kWindowAnimation_MinimizeRotate));
scoped_ptr<ui::InterpolatedTransform> rotation_about_pivot(
new ui::InterpolatedTransformAboutPivot(
gfx::Point(bounds.width() * 0.5, bounds.height() * 0.5),
rotation.release()));
scale->SetChild(translation.release());
rotation_about_pivot->SetChild(scale.release());
rotation_about_pivot->SetReversed(show);
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
kLayerAnimationsForMinimizeDurationMS);
scoped_ptr<ui::LayerAnimationElement> transition(
ui::LayerAnimationElement::CreateInterpolatedTransformElement(
rotation_about_pivot.release(), duration));
transition->set_tween_type(
show ? ui::Tween::EASE_IN : ui::Tween::EASE_IN_OUT);
window->layer()->GetAnimator()->ScheduleAnimation(
new ui::LayerAnimationSequence(transition.release()));
// When hiding a window, turn off blending until the animation is 3 / 4 done
// to save bandwidth and reduce jank
if (!show) {
window->layer()->GetAnimator()->SchedulePauseForProperties(
(duration * 3 ) / 4, ui::LayerAnimationElement::OPACITY, -1);
}
// Fade in and out quickly when the window is small to reduce jank
float opacity = show ? 1.0f : 0.0f;
window->layer()->GetAnimator()->ScheduleAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(
opacity, duration / 4)));
}
void AnimateShowWindow_Minimize(aura::Window* window) {
window->layer()->set_delegate(window);
window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
AddLayerAnimationsForMinimize(window, true);
// Now that the window has been restored, we need to clear its animation style
// to default so that normal animation applies.
SetWindowVisibilityAnimationType(
window, WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}
void AnimateHideWindow_Minimize(aura::Window* window) {
window->layer()->set_delegate(NULL);
// Property sets within this scope will be implicitly animated.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
kLayerAnimationsForMinimizeDurationMS);
settings.SetTransitionDuration(duration);
settings.AddObserver(new HidingWindowAnimationObserver(window));
window->layer()->SetVisible(false);
AddLayerAnimationsForMinimize(window, false);
}
void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
bool show) {
window->layer()->set_delegate(window);
float start_value, end_value;
if (show) {
start_value = kWindowAnimation_HideBrightnessGrayscale;
end_value = kWindowAnimation_ShowBrightnessGrayscale;
} else {
start_value = kWindowAnimation_ShowBrightnessGrayscale;
end_value = kWindowAnimation_HideBrightnessGrayscale;
}
window->layer()->SetLayerBrightness(start_value);
window->layer()->SetLayerGrayscale(start_value);
if (show) {
window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
window->layer()->SetVisible(true);
}
int animation_duration = kBrightnessGrayscaleFadeDurationMs;
ui::Tween::Type animation_type = ui::Tween::EASE_OUT;
if (CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshBootAnimationFunction2)) {
animation_type = ui::Tween::EASE_OUT_2;
} else if (CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshBootAnimationFunction3)) {
animation_type = ui::Tween::EASE_OUT_3;
}
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(animation_duration));
if (!show)
settings.AddObserver(new HidingWindowAnimationObserver(window));
scoped_ptr<ui::LayerAnimationSequence> brightness_sequence(
new ui::LayerAnimationSequence());
scoped_ptr<ui::LayerAnimationSequence> grayscale_sequence(
new ui::LayerAnimationSequence());
scoped_ptr<ui::LayerAnimationElement> brightness_element(
ui::LayerAnimationElement::CreateBrightnessElement(
end_value,
base::TimeDelta::FromMilliseconds(animation_duration)));
brightness_element->set_tween_type(animation_type);
brightness_sequence->AddElement(brightness_element.release());
scoped_ptr<ui::LayerAnimationElement> grayscale_element(
ui::LayerAnimationElement::CreateGrayscaleElement(
end_value,
base::TimeDelta::FromMilliseconds(animation_duration)));
grayscale_element->set_tween_type(animation_type);
grayscale_sequence->AddElement(grayscale_element.release());
std::vector<ui::LayerAnimationSequence*> animations;
animations.push_back(brightness_sequence.release());
animations.push_back(grayscale_sequence.release());
window->layer()->GetAnimator()->ScheduleTogether(animations);
if (!show) {
window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
window->layer()->SetVisible(false);
}
}
void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) {
AnimateShowHideWindowCommon_BrightnessGrayscale(window, true);
}
void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) {
AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
}
bool AnimateShowWindow(aura::Window* window) {
if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW))
return false;
switch (GetWindowVisibilityAnimationType(window)) {
case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
AnimateShowWindow_Drop(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
AnimateShowWindow_Vertical(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
AnimateShowWindow_Fade(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_WORKSPACE_SHOW:
AnimateShowWindow_Workspace(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
AnimateShowWindow_Minimize(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
AnimateShowWindow_BrightnessGrayscale(window);
return true;
default:
NOTREACHED();
return false;
}
}
bool AnimateHideWindow(aura::Window* window) {
if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE))
return false;
switch (GetWindowVisibilityAnimationType(window)) {
case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
AnimateHideWindow_Drop(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
AnimateHideWindow_Vertical(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
AnimateHideWindow_Fade(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_WORKSPACE_HIDE:
AnimateHideWindow_Workspace(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
AnimateHideWindow_Minimize(window);
return true;
case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
AnimateHideWindow_BrightnessGrayscale(window);
return true;
default:
NOTREACHED();
return false;
}
}
// Observer for a window cross-fade animation. If either the window closes or
// the layer's animation completes or compositing is aborted due to GPU crash,
// it deletes the layer and removes itself as an observer.
class CrossFadeObserver : public ui::CompositorObserver,
public aura::WindowObserver,
public ui::ImplicitAnimationObserver {
public:
// Observes |window| for destruction, but does not take ownership.
// Takes ownership of |layer| and its child layers.
CrossFadeObserver(Window* window, Layer* layer)
: window_(window),
layer_(layer) {
window_->AddObserver(this);
layer_->GetCompositor()->AddObserver(this);
}
virtual ~CrossFadeObserver() {
window_->RemoveObserver(this);
window_ = NULL;
layer_->GetCompositor()->RemoveObserver(this);
wm::DeepDeleteLayers(layer_);
layer_ = NULL;
}
// ui::CompositorObserver overrides:
virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {
}
virtual void OnCompositingWillStart(ui::Compositor* compositor) OVERRIDE {
}
virtual void OnCompositingStarted(ui::Compositor* compositor) OVERRIDE {
}
virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE {
}
virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {
// Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
layer_->GetAnimator()->StopAnimating();
}
// aura::WindowObserver overrides:
virtual void OnWindowDestroying(Window* window) OVERRIDE {
// Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
layer_->GetAnimator()->StopAnimating();
}
// ui::ImplicitAnimationObserver overrides:
virtual void OnImplicitAnimationsCompleted() OVERRIDE {
delete this;
}
private:
Window* window_; // not owned
Layer* layer_; // owned
DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver);
};
} // namespace
} // namespace internal
namespace {
// Scales for workspaces above/below current workspace.
const float kWorkspaceScaleAbove = 1.1f;
const float kWorkspaceScaleBelow = .9f;
// Amount of time to pause before animating anything. Only used during initial
// animation (when logging in).
const int kWorkspaceInitialPauseTimeMS = 750;
// TODO: leaving in for now since Nicholas wants to play with this, remove if we
// leave it at 0.
const int kPauseTimeMS = 0;
} // namespace
// Amount of time for animating a workspace in or out.
const int kWorkspaceSwitchTimeMS = 200 + kPauseTimeMS;
namespace {
// Brightness for the non-active workspace.
// TODO(sky): ideally this would be -.33f, but it slows down animations by a
// factor of 2. Figure out why.
const float kWorkspaceBrightness = 0.0f;
enum WorkspaceScaleType {
WORKSPACE_SCALE_ABOVE,
WORKSPACE_SCALE_BELOW,
};
// Used to identify what should animate in AnimateWorkspaceIn/Out.
enum WorkspaceAnimateTypes {
WORKSPACE_ANIMATE_OPACITY = 1 << 0,
WORKSPACE_ANIMATE_BRIGHTNESS = 1 << 1,
};
void ApplyWorkspaceScale(ui::Layer* layer, WorkspaceScaleType type) {
const float scale = type == WORKSPACE_SCALE_ABOVE ? kWorkspaceScaleAbove :
kWorkspaceScaleBelow;
ui::Transform transform;
transform.ConcatScale(scale, scale);
transform.ConcatTranslate(
-layer->bounds().width() * (scale - 1.0f) / 2,
-layer->bounds().height() * (scale - 1.0f) / 2);
layer->SetTransform(transform);
}
// Implementation of cross fading. Window is the window being cross faded. It
// should be at the target bounds. |old_layer| the previous layer from |window|.
// This takes ownership of |old_layer| and deletes when the animation is done.
// |pause_duration| is the duration to pause at the current bounds before
// animating. Returns the duration of the fade.
TimeDelta CrossFadeImpl(aura::Window* window,
ui::Layer* old_layer,
ui::Tween::Type tween_type,
base::TimeDelta pause_duration) {
const gfx::Rect old_bounds(old_layer->bounds());
const gfx::Rect new_bounds(window->bounds());
const bool old_on_top = (old_bounds.width() > new_bounds.width());
// Shorten the animation if there's not much visual movement.
const TimeDelta duration = GetCrossFadeDuration(old_bounds, new_bounds);
// Scale up the old layer while translating to new position.
{
old_layer->GetAnimator()->StopAnimating();
ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
old_layer->GetAnimator()->SchedulePauseForProperties(
pause_duration, ui::LayerAnimationElement::TRANSFORM,
ui::LayerAnimationElement::OPACITY, -1);
// Animation observer owns the old layer and deletes itself.
settings.AddObserver(new internal::CrossFadeObserver(window, old_layer));
settings.SetTransitionDuration(duration);
settings.SetTweenType(tween_type);
ui::Transform out_transform;
float scale_x = static_cast<float>(new_bounds.width()) /
static_cast<float>(old_bounds.width());
float scale_y = static_cast<float>(new_bounds.height()) /
static_cast<float>(old_bounds.height());
out_transform.ConcatScale(scale_x, scale_y);
out_transform.ConcatTranslate(new_bounds.x() - old_bounds.x(),
new_bounds.y() - old_bounds.y());
old_layer->SetTransform(out_transform);
if (old_on_top) {
// The old layer is on top, and should fade out. The new layer below will
// stay opaque to block the desktop.
old_layer->SetOpacity(0.f);
}
// In tests |old_layer| is deleted here, as animations have zero duration.
old_layer = NULL;
}
// Set the new layer's current transform, such that the user sees a scaled
// version of the window with the original bounds at the original position.
ui::Transform in_transform;
const float scale_x = static_cast<float>(old_bounds.width()) /
static_cast<float>(new_bounds.width());
const float scale_y = static_cast<float>(old_bounds.height()) /
static_cast<float>(new_bounds.height());
in_transform.ConcatScale(scale_x, scale_y);
in_transform.ConcatTranslate(old_bounds.x() - new_bounds.x(),
old_bounds.y() - new_bounds.y());
window->layer()->SetTransform(in_transform);
if (!old_on_top) {
// The new layer is on top and should fade in. The old layer below will
// stay opaque and block the desktop.
window->layer()->SetOpacity(0.f);
}
{
// Animate the new layer to the identity transform, so the window goes to
// its newly set bounds.
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
window->layer()->GetAnimator()->SchedulePauseForProperties(
pause_duration, ui::LayerAnimationElement::TRANSFORM,
ui::LayerAnimationElement::OPACITY, -1);
settings.SetTransitionDuration(duration);
settings.SetTweenType(tween_type);
window->layer()->SetTransform(ui::Transform());
if (!old_on_top) {
// New layer is on top, fade it in.
window->layer()->SetOpacity(1.f);
}
}
return duration;
}
// Returns a TimeDelta from |time_ms|. If animations are disabled this returns
// a TimeDelta of 0 (so the animation completes immediately).
base::TimeDelta AdjustAnimationTime(int time_ms) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshWindowAnimationsDisabled))
time_ms = 0;
return base::TimeDelta::FromMilliseconds(time_ms);
}
// Returns a TimeDelta adjusted for animations. If animations are disabled this
// returns 0. If animations are not disabled and |time_delta| is non-empty, it
// is returned. Otherwise |delta_time_ms| is returned.
base::TimeDelta AdjustAnimationTimeDelta(base::TimeDelta time_delta,
int delta_time_ms) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshWindowAnimationsDisabled))
return base::TimeDelta();
return time_delta == base::TimeDelta() ?
base::TimeDelta::FromMilliseconds(delta_time_ms) : time_delta;
}
void AnimateWorkspaceInImpl(aura::Window* window,
WorkspaceAnimationDirection direction,
uint32 animate_types,
int pause_time_ms,
ui::Tween::Type tween_type,
base::TimeDelta duration) {
window->layer()->SetOpacity(
(animate_types & WORKSPACE_ANIMATE_OPACITY) ? 0.0f : 1.0f);
window->layer()->SetLayerBrightness(
(animate_types & WORKSPACE_ANIMATE_BRIGHTNESS) ?
kWorkspaceBrightness : 0.0f);
window->Show();
ApplyWorkspaceScale(window->layer(),
direction == WORKSPACE_ANIMATE_UP ?
WORKSPACE_SCALE_BELOW : WORKSPACE_SCALE_ABOVE);
window->layer()->GetAnimator()->StopAnimating();
{
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
if (pause_time_ms > 0) {
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
window->layer()->GetAnimator()->SchedulePauseForProperties(
AdjustAnimationTime(pause_time_ms),
ui::LayerAnimationElement::TRANSFORM,
ui::LayerAnimationElement::OPACITY,
ui::LayerAnimationElement::BRIGHTNESS,
ui::LayerAnimationElement::VISIBILITY,
-1);
}
settings.SetTweenType(tween_type);
settings.SetTransitionDuration(duration);
window->layer()->SetTransform(ui::Transform());
window->layer()->SetOpacity(1.0f);
window->layer()->SetLayerBrightness(0.0f);
}
}
void AnimateWorkspaceOutImpl(aura::Window* window,
WorkspaceAnimationDirection direction,
uint32 animate_types,
int pause_time_ms,
ui::Tween::Type tween_type,
TimeDelta duration) {
window->Show();
window->layer()->SetTransform(ui::Transform());
window->layer()->SetLayerBrightness(0.0f);
window->layer()->SetOpacity(1.0f);
window->layer()->GetAnimator()->StopAnimating();
{
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
if (pause_time_ms > 0) {
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
window->layer()->GetAnimator()->SchedulePauseForProperties(
AdjustAnimationTime(pause_time_ms),
ui::LayerAnimationElement::TRANSFORM,
ui::LayerAnimationElement::OPACITY,
ui::LayerAnimationElement::BRIGHTNESS,
ui::LayerAnimationElement::VISIBILITY,
-1);
}
settings.SetTransitionDuration(duration);
settings.SetTweenType(tween_type);
ApplyWorkspaceScale(window->layer(),
direction == WORKSPACE_ANIMATE_UP ?
WORKSPACE_SCALE_ABOVE : WORKSPACE_SCALE_BELOW);
// NOTE: Hide() must be before SetOpacity(), else
// VisibilityController::UpdateLayerVisibility doesn't pass the false to the
// layer so that the layer and window end up out of sync and confused.
window->Hide();
if (animate_types & WORKSPACE_ANIMATE_OPACITY)
window->layer()->SetOpacity(0.0f);
if (animate_types & WORKSPACE_ANIMATE_BRIGHTNESS)
window->layer()->SetLayerBrightness(kWorkspaceBrightness);
// 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());
window->layer()->SetTransform(ui::Transform());
}
}
ui::Tween::Type TweenTypeForWorskpaceOut(WorkspaceType type) {
return WORKSPACE_DESKTOP ? ui::Tween::LINEAR : ui::Tween::EASE_OUT;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// External interface
void SetWindowVisibilityAnimationType(aura::Window* window,
WindowVisibilityAnimationType type) {
window->SetProperty(internal::kWindowVisibilityAnimationTypeKey, type);
}
WindowVisibilityAnimationType GetWindowVisibilityAnimationType(
aura::Window* window) {
return window->GetProperty(internal::kWindowVisibilityAnimationTypeKey);
}
void SetWindowVisibilityAnimationTransition(
aura::Window* window,
WindowVisibilityAnimationTransition transition) {
window->SetProperty(internal::kWindowVisibilityAnimationTransitionKey,
transition);
}
void SetWindowVisibilityAnimationDuration(aura::Window* window,
const TimeDelta& duration) {
window->SetProperty(internal::kWindowVisibilityAnimationDurationKey,
static_cast<int>(duration.ToInternalValue()));
}
void SetWindowVisibilityAnimationVerticalPosition(aura::Window* window,
float position) {
window->SetProperty(internal::kWindowVisibilityAnimationVerticalPositionKey,
position);
}
ui::ImplicitAnimationObserver* CreateHidingWindowAnimationObserver(
aura::Window* window) {
return new internal::HidingWindowAnimationObserver(window);
}
void CrossFadeToBounds(aura::Window* window, const gfx::Rect& new_bounds) {
DCHECK(window->TargetVisibility());
const gfx::Rect old_bounds = window->bounds();
// Create fresh layers for the window and all its children to paint into.
// Takes ownership of the old layer and all its children, which will be
// cleaned up after the animation completes.
ui::Layer* old_layer = wm::RecreateWindowLayers(window, false);
ui::Layer* new_layer = window->layer();
// Resize the window to the new size, which will force a layout and paint.
window->SetBounds(new_bounds);
// Ensure the higher-resolution layer is on top.
bool old_on_top = (old_bounds.width() > new_bounds.width());
if (old_on_top)
old_layer->parent()->StackBelow(new_layer, old_layer);
else
old_layer->parent()->StackAbove(new_layer, old_layer);
CrossFadeImpl(window, old_layer, ui::Tween::EASE_OUT, base::TimeDelta());
}
void CrossFadeWindowBetweenWorkspaces(aura::Window* old_workspace,
aura::Window* new_workspace,
aura::Window* window,
ui::Layer* old_layer) {
ui::Layer* layer_parent = new_workspace->layer()->parent();
layer_parent->Add(old_layer);
const bool restoring = old_layer->bounds().width() > window->bounds().width();
ui::Tween::Type tween_type, workspace_tween_type;
if (restoring) {
layer_parent->StackAbove(old_layer, new_workspace->layer());
tween_type = ui::Tween::EASE_OUT;
workspace_tween_type = ui::Tween::EASE_OUT;
} else {
layer_parent->StackBelow(old_layer, new_workspace->layer());
tween_type = ui::Tween::EASE_IN_2;
workspace_tween_type = ui::Tween::LINEAR;
}
const TimeDelta duration =
CrossFadeImpl(window, old_layer, tween_type,
AdjustAnimationTime(restoring ? 0 : kPauseTimeMS));
if (restoring) {
typedef aura::Window::Windows Windows;
if (old_workspace)
AnimateWorkspaceOutImpl(old_workspace, WORKSPACE_ANIMATE_UP,
WORKSPACE_ANIMATE_BRIGHTNESS,
0, workspace_tween_type, duration);
// Ideally we would use AnimateWorkspaceIn() for |new_workspace|, but that
// results in |window| animating with the workspace scale. We don't want
// that, so we explicitly animate each of the children to give the effect of
// the workspace scaling.
new_workspace->Show();
new_workspace->SetTransform(ui::Transform());
new_workspace->layer()->SetOpacity(1.0f);
new_workspace->layer()->SetLayerBrightness(0.0f);
const Windows& children(new_workspace->children());
for (Windows::const_iterator i = children.begin(); i != children.end();
++i) {
aura::Window* child = *i;
if (child == window)
continue;
child->SetTransform(ash::internal::BuildWorkspaceSwitchTransform(
child, kWorkspaceScaleBelow));
child->layer()->SetLayerBrightness(kWorkspaceBrightness);
ui::ScopedLayerAnimationSettings settings(child->layer()->GetAnimator());
settings.SetTweenType(ui::Tween::EASE_OUT);
settings.SetTransitionDuration(duration);
child->SetTransform(ui::Transform());
child->layer()->SetLayerBrightness(0.0f);
}
} else {
if (old_workspace) {
AnimateWorkspaceOutImpl(old_workspace, WORKSPACE_ANIMATE_DOWN,
WORKSPACE_ANIMATE_BRIGHTNESS,
0, workspace_tween_type, duration);
}
new_workspace->Show();
new_workspace->layer()->SetOpacity(1.f);
new_workspace->layer()->SetTransform(ui::Transform());
new_workspace->layer()->SetLayerBrightness(0.0f);
}
}
void AnimateBetweenWorkspaces(aura::Window* old_window,
WorkspaceType old_type,
bool animate_old,
aura::Window* new_window,
WorkspaceType new_type,
bool is_restoring_maximized_window) {
uint32 common_animate_types = 0;
if (animate_old) {
// When switching to the desktop the old window lifts off.
uint32 animate_types = 0;
if (!(new_type == WORKSPACE_MAXIMIZED && old_type == WORKSPACE_MAXIMIZED))
animate_types |= WORKSPACE_ANIMATE_BRIGHTNESS;
if ((new_type == WORKSPACE_DESKTOP || old_type == WORKSPACE_MAXIMIZED) &&
!is_restoring_maximized_window)
animate_types |= WORKSPACE_ANIMATE_OPACITY;
AnimateWorkspaceOutImpl(
old_window,
new_type == WORKSPACE_DESKTOP ? WORKSPACE_ANIMATE_UP :
WORKSPACE_ANIMATE_DOWN,
animate_types,
0,
TweenTypeForWorskpaceOut(old_type),
AdjustAnimationTime(kWorkspaceSwitchTimeMS));
}
// Switching from the desktop to a maximized animates down.
uint32 animate_types = common_animate_types;
if (!(new_type == WORKSPACE_MAXIMIZED && old_type == WORKSPACE_MAXIMIZED) &&
!is_restoring_maximized_window)
animate_types |= WORKSPACE_ANIMATE_BRIGHTNESS;
if (old_type == WORKSPACE_DESKTOP)
animate_types |= WORKSPACE_ANIMATE_OPACITY;
AnimateWorkspaceInImpl(
new_window,
old_type == WORKSPACE_DESKTOP ?
WORKSPACE_ANIMATE_DOWN : WORKSPACE_ANIMATE_UP,
animate_types,
0,
ui::Tween::EASE_OUT,
AdjustAnimationTime(kWorkspaceSwitchTimeMS));
}
void AnimateWorkspaceIn(aura::Window* window,
WorkspaceAnimationDirection direction,
bool initial_animate,
base::TimeDelta delta) {
AnimateWorkspaceInImpl(
window, direction,
WORKSPACE_ANIMATE_BRIGHTNESS |
(initial_animate ? WORKSPACE_ANIMATE_OPACITY : 0),
initial_animate ? kWorkspaceInitialPauseTimeMS : 0,
ui::Tween::EASE_OUT,
AdjustAnimationTimeDelta(delta, kWorkspaceSwitchTimeMS));
}
void AnimateWorkspaceOut(aura::Window* window,
WorkspaceAnimationDirection direction,
WorkspaceType type,
bool initial_animate,
base::TimeDelta delta) {
AnimateWorkspaceOutImpl(window, direction, WORKSPACE_ANIMATE_BRIGHTNESS,
initial_animate ? kWorkspaceInitialPauseTimeMS : 0,
TweenTypeForWorskpaceOut(type),
AdjustAnimationTimeDelta(delta,
kWorkspaceSwitchTimeMS));
}
base::TimeDelta GetSystemBackgroundDestroyDuration() {
return base::TimeDelta::FromMilliseconds(
std::max(static_cast<int>(internal::kCrossFadeDurationMaxMs),
kWorkspaceSwitchTimeMS));
}
TimeDelta GetCrossFadeDuration(const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kAshWindowAnimationsDisabled))
return base::TimeDelta();
const int min_time_ms = internal::WorkspaceController::IsWorkspace2Enabled() ?
kWorkspaceSwitchTimeMS : 0;
int old_area = old_bounds.width() * old_bounds.height();
int new_area = new_bounds.width() * new_bounds.height();
int max_area = std::max(old_area, new_area);
// Avoid divide by zero.
if (max_area == 0)
return TimeDelta::FromMilliseconds(min_time_ms);
int delta_area = std::abs(old_area - new_area);
// If the area didn't change, the animation is instantaneous.
if (delta_area == 0)
return TimeDelta::FromMilliseconds(min_time_ms);
float factor =
static_cast<float>(delta_area) / static_cast<float>(max_area);
const float kRange = internal::kCrossFadeDurationMaxMs -
internal::kCrossFadeDurationMinMs;
return TimeDelta::FromMilliseconds(
internal::Round64(internal::kCrossFadeDurationMinMs + (factor * kRange)));
}
namespace internal {
bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
if (window->GetProperty(aura::client::kAnimationsDisabledKey) ||
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshWindowAnimationsDisabled)) {
return false;
}
if (visible) {
return AnimateShowWindow(window);
} else {
// Don't start hiding the window again if it's already being hidden.
return window->layer()->GetTargetOpacity() != 0.0f &&
AnimateHideWindow(window);
}
}
} // namespace internal
} // namespace ash