// 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 #include #include #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 "ash/wm/workspace/workspace_animations.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; } // namespace 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 = 200.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; // Tween type when cross fading a workspace window. const ui::Tween::Type kCrossFadeTweenType = ui::Tween::EASE_IN_OUT; int64 Round64(float f) { return static_cast(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 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 gfx::Transform& start_transform, const gfx::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 gfx::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) { gfx::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, gfx::Transform()); } void AnimateHideWindow_Drop(aura::Window* window) { gfx::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) { gfx::Transform transform; transform.ConcatTranslate(0, window->GetProperty( kWindowVisibilityAnimationVerticalPositionKey)); AnimateShowWindowCommon(window, transform, gfx::Transform()); } void AnimateHideWindow_Vertical(aura::Window* window) { gfx::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, gfx::Transform(), gfx::Transform()); } void AnimateHideWindow_Fade(aura::Window* window) { AnimateHideWindowCommon(window, gfx::Transform()); } // Builds the transform used when switching workspaces for the specified // window. gfx::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(parent_bounds.width()) / 2.0f; float initial_x = (static_cast(bounds.x()) - mid_x) * scale + mid_x; float mid_y = static_cast(parent_bounds.height()) / 2.0f; float initial_y = (static_cast(bounds.y()) - mid_y) * scale + mid_y; gfx::Transform transform; transform.ConcatTranslate( initial_x - static_cast(bounds.x()), initial_y - static_cast(bounds.y())); transform.ConcatScale(scale, scale); return transform; } void AnimateShowWindow_Workspace(aura::Window* window) { gfx::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(gfx::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) { gfx::Transform transform( BuildWorkspaceSwitchTransform(window, kWorkspaceScale)); window->layer()->SetOpacity(1.0f); window->layer()->SetTransform(gfx::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 = Shell::GetScreen()->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(target_bounds.height()) / bounds.width(); float scale_y = static_cast(target_bounds.width()) / bounds.height(); scoped_ptr scale( new ui::InterpolatedScale(gfx::Point3f(1, 1, 1), gfx::Point3f(scale_x, scale_y, 1))); scoped_ptr translation( new ui::InterpolatedTranslation( gfx::Point(), gfx::Point(target_bounds.x() - bounds.x(), target_bounds.y() - bounds.y()))); scoped_ptr rotation( new ui::InterpolatedRotation(0, kWindowAnimation_MinimizeRotate)); scoped_ptr 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 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 brightness_sequence( new ui::LayerAnimationSequence()); scoped_ptr grayscale_sequence( new ui::LayerAnimationSequence()); scoped_ptr 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 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 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 { // 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) { 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()); // Animation observer owns the old layer and deletes itself. settings.AddObserver(new internal::CrossFadeObserver(window, old_layer)); settings.SetTransitionDuration(duration); settings.SetTweenType(tween_type); gfx::Transform out_transform; float scale_x = static_cast(new_bounds.width()) / static_cast(old_bounds.width()); float scale_y = static_cast(new_bounds.height()) / static_cast(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. gfx::Transform in_transform; const float scale_x = static_cast(old_bounds.width()) / static_cast(new_bounds.width()); const float scale_y = static_cast(old_bounds.height()) / static_cast(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.SetTransitionDuration(duration); settings.SetTweenType(tween_type); window->layer()->SetTransform(gfx::Transform()); if (!old_on_top) { // New layer is on top, fade it in. window->layer()->SetOpacity(1.f); } } return duration; } } // 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(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); } void CrossFadeWindowBetweenWorkspaces(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(); if (restoring) layer_parent->StackAbove(old_layer, new_workspace->layer()); else layer_parent->StackBelow(old_layer, new_workspace->layer()); CrossFadeImpl(window, old_layer, internal::kCrossFadeTweenType); } 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() ? internal::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(delta_area) / static_cast(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