root/ash/wm/window_animations.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Round64
  2. GetCrossFadeDuration
  3. AddLayerAnimationsForMinimize
  4. AnimateShowWindow_Minimize
  5. AnimateHideWindow_Minimize
  6. AnimateShowHideWindowCommon_BrightnessGrayscale
  7. AnimateShowWindow_BrightnessGrayscale
  8. AnimateHideWindow_BrightnessGrayscale
  9. AnimateShowWindow
  10. AnimateHideWindow
  11. layer_owner_
  12. OnCompositingDidCommit
  13. OnCompositingStarted
  14. OnCompositingEnded
  15. OnCompositingAborted
  16. OnCompositingLockStateChanged
  17. OnWindowDestroying
  18. OnWindowRemovingFromRootWindow
  19. OnImplicitAnimationsCompleted
  20. CrossFadeAnimation
  21. AnimateOnChildWindowVisibilityChanged
  22. CreateBrightnessGrayscaleAnimationSequence
  23. SetTransformForScaleAnimation
  24. GetMinimizeAnimationTargetBoundsInScreen

// 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/screen_util.h"
#include "ash/shelf/shelf.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/layer_tree_owner.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/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.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;

// 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);
}

base::TimeDelta GetCrossFadeDuration(aura::Window* window,
                                     const gfx::Rect& old_bounds,
                                     const gfx::Rect& new_bounds) {
  if (::wm::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)));
}

}  // 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 =
      ScreenUtil::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())));

  scale->SetChild(translation.release());
  scale->SetReversed(show);

  base::TimeDelta duration = window->layer()->GetAnimator()->
      GetTransitionDuration();

  scoped_ptr<ui::LayerAnimationElement> transition(
      ui::LayerAnimationElement::CreateInterpolatedTransformElement(
          scale.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);
  }

  // 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)));

  // Reset the transform to identity when the minimize animation is completed.
  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateTransformElement(
              gfx::Transform(),
              base::TimeDelta())));
}

void AnimateShowWindow_Minimize(aura::Window* 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.
  ::wm::SetWindowVisibilityAnimationType(
      window, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}

void AnimateHideWindow_Minimize(aura::Window* window) {
  // Property sets within this scope will be implicitly animated.
  ::wm::ScopedHidingAnimationSettings hiding_settings(window);
  base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
      kLayerAnimationsForMinimizeDurationMS);
  hiding_settings.layer_animation_settings()->SetTransitionDuration(duration);
  window->layer()->SetVisible(false);

  AddLayerAnimationsForMinimize(window, false);
}

void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
                                                     bool show) {
  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);

  if (show) {
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
    window->layer()->GetAnimator()->
        ScheduleTogether(
            CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
  } else {
    ::wm::ScopedHidingAnimationSettings hiding_settings(window);
    window->layer()->GetAnimator()->
        ScheduleTogether(
            CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
    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 (!::wm::HasWindowVisibilityAnimationTransition(
          window, ::wm::ANIMATE_SHOW)) {
    return false;
  }

  switch (::wm::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 (!::wm::HasWindowVisibilityAnimationTransition(
          window, ::wm::ANIMATE_HIDE)) {
    return false;
  }

  switch (::wm::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,
                    scoped_ptr<ui::LayerTreeOwner> layer_owner)
      : window_(window),
        layer_owner_(layer_owner.Pass()) {
    window_->AddObserver(this);
    layer_owner_->root()->GetCompositor()->AddObserver(this);
  }
  virtual ~CrossFadeObserver() {
    window_->RemoveObserver(this);
    window_ = NULL;
    layer_owner_->root()->GetCompositor()->RemoveObserver(this);
  }

  // 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_owner_->root()->GetAnimator()->StopAnimating();
  }
  virtual void OnCompositingLockStateChanged(
      ui::Compositor* compositor) OVERRIDE {
  }

  // aura::WindowObserver overrides:
  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
    // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
    layer_owner_->root()->GetAnimator()->StopAnimating();
  }
  virtual void OnWindowRemovingFromRootWindow(aura::Window* window,
                                              aura::Window* new_root) OVERRIDE {
    layer_owner_->root()->GetAnimator()->StopAnimating();
  }

  // ui::ImplicitAnimationObserver overrides:
  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    delete this;
  }

 private:
  aura::Window* window_;  // not owned
  scoped_ptr<ui::LayerTreeOwner> layer_owner_;

  DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver);
};

base::TimeDelta CrossFadeAnimation(
    aura::Window* window,
    scoped_ptr<ui::LayerTreeOwner> old_layer_owner,
    gfx::Tween::Type tween_type) {
  DCHECK(old_layer_owner->root());
  const gfx::Rect old_bounds(old_layer_owner->root()->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.
  {
    ui::Layer* old_layer = old_layer_owner->root();
    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_owner.Pass()));
    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;
}

bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
  if (::wm::WindowAnimationsDisabled(window))
    return false;

  // Attempt to run CoreWm supplied animation types.
  if (::wm::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) {
  Shelf* shelf = Shelf::ForWindow(window);
  // Shelf is created lazily and can be NULL.
  if (!shelf)
    return gfx::Rect();
  gfx::Rect item_rect = shelf->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) {
    ShelfLayoutManager* layout_manager = ShelfLayoutManager::ForShelf(window);
    if (layout_manager->visibility_state() == SHELF_AUTO_HIDE) {
      gfx::Rect shelf_bounds = shelf->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 shelf 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

/* [<][>][^][v][top][bottom][index][help] */