// 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/launcher/launcher.h" #include "ash/screen_ash.h" #include "ash/shelf/shelf_layout_manager.h" #include "ash/shelf/shelf_widget.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/message_loop.h" #include "base/stl_util.h" #include "base/time/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/gfx/vector3d_f.h" #include "ui/views/corewm/window_util.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" namespace ash { namespace { 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; // TODO(sky): if we end up sticking with 0, nuke the code doing the rotation. const float kWindowAnimation_MinimizeRotate = 0.f; // Scales for AshWindow above/below current workspace. const float kLayerScaleAboveSize = 1.1f; const float kLayerScaleBelowSize = .9f; int64 Round64(float f) { return static_cast(f + 0.5f); } } // namespace const int kCrossFadeDurationMS = 200; 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 = GetMinimizeAnimationTargetBoundsInScreen(window); target_bounds = ScreenAsh::ConvertRectFromScreen(window->parent(), target_bounds); float scale_x = static_cast(target_bounds.width()) / bounds.width(); float scale_y = static_cast(target_bounds.height()) / 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 = window->layer()->GetAnimator()-> GetTransitionDuration(); scoped_ptr transition( ui::LayerAnimationElement::CreateInterpolatedTransformElement( rotation_about_pivot.release(), duration)); transition->set_tween_type( show ? gfx::Tween::EASE_IN : gfx::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); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); base::TimeDelta duration = base::TimeDelta::FromMilliseconds( kLayerAnimationsForMinimizeDurationMS); settings.SetTransitionDuration(duration); AddLayerAnimationsForMinimize(window, true); // Now that the window has been restored, we need to clear its animation style // to default so that normal animation applies. views::corewm::SetWindowVisibilityAnimationType( window, views::corewm::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( views::corewm::CreateHidingWindowAnimationObserver(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); } base::TimeDelta duration = base::TimeDelta::FromMilliseconds(kBrightnessGrayscaleFadeDurationMs); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); settings.SetTransitionDuration(duration); if (!show) { settings.AddObserver( views::corewm::CreateHidingWindowAnimationObserver(window)); } window->layer()->GetAnimator()-> ScheduleTogether( CreateBrightnessGrayscaleAnimationSequence(end_value, duration)); 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 (!views::corewm::HasWindowVisibilityAnimationTransition( window, views::corewm::ANIMATE_SHOW)) { return false; } switch (views::corewm::GetWindowVisibilityAnimationType(window)) { 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 (!views::corewm::HasWindowVisibilityAnimationTransition( window, views::corewm::ANIMATE_HIDE)) { return false; } switch (views::corewm::GetWindowVisibilityAnimationType(window)) { 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(aura::Window* window, ui::Layer* layer) : window_(window), layer_(layer) { window_->AddObserver(this); layer_->GetCompositor()->AddObserver(this); } virtual ~CrossFadeObserver() { window_->RemoveObserver(this); window_ = NULL; layer_->GetCompositor()->RemoveObserver(this); views::corewm::DeepDeleteLayers(layer_); layer_ = NULL; } // ui::CompositorObserver overrides: virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE { } virtual void OnCompositingStarted(ui::Compositor* compositor, base::TimeTicks start_time) 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(); } virtual void OnCompositingLockStateChanged( ui::Compositor* compositor) OVERRIDE { } virtual void OnUpdateVSyncParameters(ui::Compositor* compositor, base::TimeTicks timebase, base::TimeDelta interval) OVERRIDE { } // aura::WindowObserver overrides: virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { // Triggers OnImplicitAnimationsCompleted() to be called and deletes us. layer_->GetAnimator()->StopAnimating(); } virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE { layer_->GetAnimator()->StopAnimating(); } // ui::ImplicitAnimationObserver overrides: virtual void OnImplicitAnimationsCompleted() OVERRIDE { delete this; } private: aura::Window* window_; // not owned ui::Layer* layer_; // owned DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver); }; // 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. base::TimeDelta CrossFadeImpl(aura::Window* window, ui::Layer* old_layer, gfx::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 base::TimeDelta duration = GetCrossFadeDuration(window, 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 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.Translate(new_bounds.x() - old_bounds.x(), new_bounds.y() - old_bounds.y()); out_transform.Scale(scale_x, scale_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(kWindowAnimation_HideOpacity); } // 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.Translate(old_bounds.x() - new_bounds.x(), old_bounds.y() - new_bounds.y()); in_transform.Scale(scale_x, scale_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(kWindowAnimation_HideOpacity); } { // 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(kWindowAnimation_ShowOpacity); } } return duration; } void CrossFadeToBounds(aura::Window* window, const gfx::Rect& new_bounds) { // Some test results in invoking CrossFadeToBounds when window is not visible. // No animation is necessary in that case, thus just change the bounds and // quit. if (!window->TargetVisibility()) { window->SetBounds(new_bounds); return; } 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. // Specify |set_bounds| to true here to keep the old bounds in the child // windows of |window|. ui::Layer* old_layer = views::corewm::RecreateWindowLayers(window, true); 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, gfx::Tween::EASE_OUT); } base::TimeDelta GetCrossFadeDuration(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) { if (views::corewm::WindowAnimationsDisabled(window)) return base::TimeDelta(); 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 base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS); int delta_area = std::abs(old_area - new_area); // If the area didn't change, the animation is instantaneous. if (delta_area == 0) return base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS); float factor = static_cast(delta_area) / static_cast(max_area); const float kRange = kCrossFadeDurationMaxMs - kCrossFadeDurationMinMs; return base::TimeDelta::FromMilliseconds( Round64(kCrossFadeDurationMinMs + (factor * kRange))); } bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) { if (views::corewm::WindowAnimationsDisabled(window)) return false; // Attempt to run CoreWm supplied animation types. if (views::corewm::AnimateOnChildWindowVisibilityChanged(window, visible)) return true; // Otherwise try to run an Ash-specific animation. if (visible) return AnimateShowWindow(window); // Don't start hiding the window again if it's already being hidden. return window->layer()->GetTargetOpacity() != 0.0f && AnimateHideWindow(window); } std::vector CreateBrightnessGrayscaleAnimationSequence(float target_value, base::TimeDelta duration) { gfx::Tween::Type animation_type = gfx::Tween::EASE_OUT; scoped_ptr brightness_sequence( new ui::LayerAnimationSequence()); scoped_ptr grayscale_sequence( new ui::LayerAnimationSequence()); scoped_ptr brightness_element( ui::LayerAnimationElement::CreateBrightnessElement( target_value, duration)); brightness_element->set_tween_type(animation_type); brightness_sequence->AddElement(brightness_element.release()); scoped_ptr grayscale_element( ui::LayerAnimationElement::CreateGrayscaleElement( target_value, 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()); return animations; } // Returns scale related to the specified AshWindowScaleType. void SetTransformForScaleAnimation(ui::Layer* layer, LayerScaleAnimationDirection type) { const float scale = type == LAYER_SCALE_ANIMATION_ABOVE ? kLayerScaleAboveSize : kLayerScaleBelowSize; gfx::Transform transform; transform.Translate(-layer->bounds().width() * (scale - 1.0f) / 2, -layer->bounds().height() * (scale - 1.0f) / 2); transform.Scale(scale, scale); layer->SetTransform(transform); } gfx::Rect GetMinimizeAnimationTargetBoundsInScreen(aura::Window* window) { Launcher* launcher = Launcher::ForWindow(window); // Shelf is created lazily and can be NULL. if (!launcher) return gfx::Rect(); gfx::Rect item_rect = launcher->GetScreenBoundsOfItemIconForWindow(window); // The launcher item is visible and has an icon. if (!item_rect.IsEmpty()) return item_rect; // If both the icon width and height are 0, then there is no icon in the // launcher for |window| or the icon is hidden in the overflow menu. If the // launcher is auto hidden, one of the height or width will be 0 but the // position in the launcher and the major dimension are still reported // correctly and the window can be animated to the launcher item's light // bar. if (item_rect.width() != 0 || item_rect.height() != 0) { internal::ShelfLayoutManager* layout_manager = internal::ShelfLayoutManager::ForLauncher(window); if (layout_manager->visibility_state() == SHELF_AUTO_HIDE) { gfx::Rect shelf_bounds = launcher->shelf_widget()->GetWindowBoundsInScreen(); switch (layout_manager->GetAlignment()) { case SHELF_ALIGNMENT_BOTTOM: item_rect.set_y(shelf_bounds.y()); break; case SHELF_ALIGNMENT_LEFT: item_rect.set_x(shelf_bounds.right()); break; case SHELF_ALIGNMENT_RIGHT: item_rect.set_x(shelf_bounds.x()); break; case SHELF_ALIGNMENT_TOP: item_rect.set_y(shelf_bounds.bottom()); break; } return item_rect; } } // 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(); return gfx::Rect(work_area.right(), work_area.bottom(), 0, 0); } } // namespace ash