summaryrefslogtreecommitdiff
path: root/chromium/ash/wm/window_animations.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ash/wm/window_animations.cc')
-rw-r--r--chromium/ash/wm/window_animations.cc551
1 files changed, 551 insertions, 0 deletions
diff --git a/chromium/ash/wm/window_animations.cc b/chromium/ash/wm/window_animations.cc
new file mode 100644
index 00000000000..894e11b0186
--- /dev/null
+++ b/chromium/ash/wm/window_animations.cc
@@ -0,0 +1,551 @@
+// 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/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<int64>(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<float>(target_bounds.width()) / bounds.width();
+ float scale_y = static_cast<float>(target_bounds.height()) / 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 = window->layer()->GetAnimator()->
+ GetTransitionDuration();
+
+ scoped_ptr<ui::LayerAnimationElement> 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<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.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<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.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<float>(delta_area) / static_cast<float>(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<ui::LayerAnimationSequence*>
+CreateBrightnessGrayscaleAnimationSequence(float target_value,
+ base::TimeDelta duration) {
+ gfx::Tween::Type animation_type = gfx::Tween::EASE_OUT;
+ 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(
+ target_value, duration));
+ brightness_element->set_tween_type(animation_type);
+ brightness_sequence->AddElement(brightness_element.release());
+
+ scoped_ptr<ui::LayerAnimationElement> grayscale_element(
+ ui::LayerAnimationElement::CreateGrayscaleElement(
+ target_value, 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());
+
+ 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