1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/wm/window_animations.h"
6
7#include <math.h>
8
9#include <algorithm>
10#include <vector>
11
12#include "ash/screen_util.h"
13#include "ash/shelf/shelf.h"
14#include "ash/shelf/shelf_layout_manager.h"
15#include "ash/shelf/shelf_widget.h"
16#include "ash/shell.h"
17#include "ash/wm/window_util.h"
18#include "ash/wm/workspace_controller.h"
19#include "base/command_line.h"
20#include "base/compiler_specific.h"
21#include "base/logging.h"
22#include "base/message_loop/message_loop.h"
23#include "base/stl_util.h"
24#include "base/time/time.h"
25#include "ui/aura/client/aura_constants.h"
26#include "ui/aura/window.h"
27#include "ui/aura/window_observer.h"
28#include "ui/aura/window_property.h"
29#include "ui/compositor/compositor_observer.h"
30#include "ui/compositor/layer.h"
31#include "ui/compositor/layer_animation_observer.h"
32#include "ui/compositor/layer_animation_sequence.h"
33#include "ui/compositor/layer_animator.h"
34#include "ui/compositor/layer_tree_owner.h"
35#include "ui/compositor/scoped_layer_animation_settings.h"
36#include "ui/gfx/interpolated_transform.h"
37#include "ui/gfx/screen.h"
38#include "ui/gfx/vector3d_f.h"
39#include "ui/views/view.h"
40#include "ui/views/widget/widget.h"
41#include "ui/wm/core/window_util.h"
42
43namespace ash {
44namespace {
45const int kLayerAnimationsForMinimizeDurationMS = 200;
46
47// Durations for the cross-fade animation, in milliseconds.
48const float kCrossFadeDurationMinMs = 200.f;
49const float kCrossFadeDurationMaxMs = 400.f;
50
51// Durations for the brightness/grayscale fade animation, in milliseconds.
52const int kBrightnessGrayscaleFadeDurationMs = 1000;
53
54// Brightness/grayscale values for hide/show window animations.
55const float kWindowAnimation_HideBrightnessGrayscale = 1.f;
56const float kWindowAnimation_ShowBrightnessGrayscale = 0.f;
57
58const float kWindowAnimation_HideOpacity = 0.f;
59const float kWindowAnimation_ShowOpacity = 1.f;
60
61// Scales for AshWindow above/below current workspace.
62const float kLayerScaleAboveSize = 1.1f;
63const float kLayerScaleBelowSize = .9f;
64
65int64 Round64(float f) {
66  return static_cast<int64>(f + 0.5f);
67}
68
69base::TimeDelta GetCrossFadeDuration(aura::Window* window,
70                                     const gfx::Rect& old_bounds,
71                                     const gfx::Rect& new_bounds) {
72  if (::wm::WindowAnimationsDisabled(window))
73    return base::TimeDelta();
74
75  int old_area = old_bounds.width() * old_bounds.height();
76  int new_area = new_bounds.width() * new_bounds.height();
77  int max_area = std::max(old_area, new_area);
78  // Avoid divide by zero.
79  if (max_area == 0)
80    return base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS);
81
82  int delta_area = std::abs(old_area - new_area);
83  // If the area didn't change, the animation is instantaneous.
84  if (delta_area == 0)
85    return base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS);
86
87  float factor =
88      static_cast<float>(delta_area) / static_cast<float>(max_area);
89  const float kRange = kCrossFadeDurationMaxMs - kCrossFadeDurationMinMs;
90  return base::TimeDelta::FromMilliseconds(
91      Round64(kCrossFadeDurationMinMs + (factor * kRange)));
92}
93
94}  // namespace
95
96const int kCrossFadeDurationMS = 200;
97
98void AddLayerAnimationsForMinimize(aura::Window* window, bool show) {
99  // Recalculate the transform at restore time since the launcher item may have
100  // moved while the window was minimized.
101  gfx::Rect bounds = window->bounds();
102  gfx::Rect target_bounds = GetMinimizeAnimationTargetBoundsInScreen(window);
103  target_bounds =
104      ScreenUtil::ConvertRectFromScreen(window->parent(), target_bounds);
105
106  float scale_x = static_cast<float>(target_bounds.width()) / bounds.width();
107  float scale_y = static_cast<float>(target_bounds.height()) / bounds.height();
108
109  scoped_ptr<ui::InterpolatedTransform> scale(
110      new ui::InterpolatedScale(gfx::Point3F(1, 1, 1),
111                                gfx::Point3F(scale_x, scale_y, 1)));
112
113  scoped_ptr<ui::InterpolatedTransform> translation(
114      new ui::InterpolatedTranslation(
115          gfx::Point(),
116          gfx::Point(target_bounds.x() - bounds.x(),
117                     target_bounds.y() - bounds.y())));
118
119  scale->SetChild(translation.release());
120  scale->SetReversed(show);
121
122  base::TimeDelta duration = window->layer()->GetAnimator()->
123      GetTransitionDuration();
124
125  scoped_ptr<ui::LayerAnimationElement> transition(
126      ui::LayerAnimationElement::CreateInterpolatedTransformElement(
127          scale.release(), duration));
128
129  transition->set_tween_type(
130      show ? gfx::Tween::EASE_IN : gfx::Tween::EASE_IN_OUT);
131
132  window->layer()->GetAnimator()->ScheduleAnimation(
133      new ui::LayerAnimationSequence(transition.release()));
134
135  // When hiding a window, turn off blending until the animation is 3 / 4 done
136  // to save bandwidth and reduce jank.
137  if (!show) {
138    window->layer()->GetAnimator()->SchedulePauseForProperties(
139        (duration * 3) / 4, ui::LayerAnimationElement::OPACITY);
140  }
141
142  // Fade in and out quickly when the window is small to reduce jank.
143  float opacity = show ? 1.0f : 0.0f;
144  window->layer()->GetAnimator()->ScheduleAnimation(
145      new ui::LayerAnimationSequence(
146          ui::LayerAnimationElement::CreateOpacityElement(
147              opacity, duration / 4)));
148
149  // Reset the transform to identity when the minimize animation is completed.
150  window->layer()->GetAnimator()->ScheduleAnimation(
151      new ui::LayerAnimationSequence(
152          ui::LayerAnimationElement::CreateTransformElement(
153              gfx::Transform(),
154              base::TimeDelta())));
155}
156
157void AnimateShowWindow_Minimize(aura::Window* window) {
158  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
159  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
160  base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
161      kLayerAnimationsForMinimizeDurationMS);
162  settings.SetTransitionDuration(duration);
163  AddLayerAnimationsForMinimize(window, true);
164
165  // Now that the window has been restored, we need to clear its animation style
166  // to default so that normal animation applies.
167  ::wm::SetWindowVisibilityAnimationType(
168      window, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
169}
170
171void AnimateHideWindow_Minimize(aura::Window* window) {
172  // Property sets within this scope will be implicitly animated.
173  ::wm::ScopedHidingAnimationSettings hiding_settings(window);
174  base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
175      kLayerAnimationsForMinimizeDurationMS);
176  hiding_settings.layer_animation_settings()->SetTransitionDuration(duration);
177  window->layer()->SetVisible(false);
178
179  AddLayerAnimationsForMinimize(window, false);
180}
181
182void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
183                                                     bool show) {
184  float start_value, end_value;
185  if (show) {
186    start_value = kWindowAnimation_HideBrightnessGrayscale;
187    end_value = kWindowAnimation_ShowBrightnessGrayscale;
188  } else {
189    start_value = kWindowAnimation_ShowBrightnessGrayscale;
190    end_value = kWindowAnimation_HideBrightnessGrayscale;
191  }
192
193  window->layer()->SetLayerBrightness(start_value);
194  window->layer()->SetLayerGrayscale(start_value);
195  if (show) {
196    window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
197    window->layer()->SetVisible(true);
198  }
199
200  base::TimeDelta duration =
201      base::TimeDelta::FromMilliseconds(kBrightnessGrayscaleFadeDurationMs);
202
203  if (show) {
204    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
205    window->layer()->GetAnimator()->
206        ScheduleTogether(
207            CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
208  } else {
209    ::wm::ScopedHidingAnimationSettings hiding_settings(window);
210    window->layer()->GetAnimator()->
211        ScheduleTogether(
212            CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
213    window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
214    window->layer()->SetVisible(false);
215  }
216}
217
218void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) {
219  AnimateShowHideWindowCommon_BrightnessGrayscale(window, true);
220}
221
222void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) {
223  AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
224}
225
226bool AnimateShowWindow(aura::Window* window) {
227  if (!::wm::HasWindowVisibilityAnimationTransition(
228          window, ::wm::ANIMATE_SHOW)) {
229    return false;
230  }
231
232  switch (::wm::GetWindowVisibilityAnimationType(window)) {
233    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
234      AnimateShowWindow_Minimize(window);
235      return true;
236    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
237      AnimateShowWindow_BrightnessGrayscale(window);
238      return true;
239    default:
240      NOTREACHED();
241      return false;
242  }
243}
244
245bool AnimateHideWindow(aura::Window* window) {
246  if (!::wm::HasWindowVisibilityAnimationTransition(
247          window, ::wm::ANIMATE_HIDE)) {
248    return false;
249  }
250
251  switch (::wm::GetWindowVisibilityAnimationType(window)) {
252    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
253      AnimateHideWindow_Minimize(window);
254      return true;
255    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
256      AnimateHideWindow_BrightnessGrayscale(window);
257      return true;
258    default:
259      NOTREACHED();
260      return false;
261  }
262}
263
264// Observer for a window cross-fade animation. If either the window closes or
265// the layer's animation completes or compositing is aborted due to GPU crash,
266// it deletes the layer and removes itself as an observer.
267class CrossFadeObserver : public ui::CompositorObserver,
268                          public aura::WindowObserver,
269                          public ui::ImplicitAnimationObserver {
270 public:
271  // Observes |window| for destruction, but does not take ownership.
272  // Takes ownership of |layer| and its child layers.
273  CrossFadeObserver(aura::Window* window,
274                    scoped_ptr<ui::LayerTreeOwner> layer_owner)
275      : window_(window),
276        layer_owner_(layer_owner.Pass()) {
277    window_->AddObserver(this);
278    layer_owner_->root()->GetCompositor()->AddObserver(this);
279  }
280  virtual ~CrossFadeObserver() {
281    window_->RemoveObserver(this);
282    window_ = NULL;
283    layer_owner_->root()->GetCompositor()->RemoveObserver(this);
284  }
285
286  // ui::CompositorObserver overrides:
287  virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {
288  }
289  virtual void OnCompositingStarted(ui::Compositor* compositor,
290                                    base::TimeTicks start_time) OVERRIDE {
291  }
292  virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE {
293  }
294  virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {
295    // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
296    layer_owner_->root()->GetAnimator()->StopAnimating();
297  }
298  virtual void OnCompositingLockStateChanged(
299      ui::Compositor* compositor) OVERRIDE {
300  }
301
302  // aura::WindowObserver overrides:
303  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
304    // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
305    layer_owner_->root()->GetAnimator()->StopAnimating();
306  }
307  virtual void OnWindowRemovingFromRootWindow(aura::Window* window,
308                                              aura::Window* new_root) OVERRIDE {
309    layer_owner_->root()->GetAnimator()->StopAnimating();
310  }
311
312  // ui::ImplicitAnimationObserver overrides:
313  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
314    delete this;
315  }
316
317 private:
318  aura::Window* window_;  // not owned
319  scoped_ptr<ui::LayerTreeOwner> layer_owner_;
320
321  DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver);
322};
323
324base::TimeDelta CrossFadeAnimation(
325    aura::Window* window,
326    scoped_ptr<ui::LayerTreeOwner> old_layer_owner,
327    gfx::Tween::Type tween_type) {
328  DCHECK(old_layer_owner->root());
329  const gfx::Rect old_bounds(old_layer_owner->root()->bounds());
330  const gfx::Rect new_bounds(window->bounds());
331  const bool old_on_top = (old_bounds.width() > new_bounds.width());
332
333  // Shorten the animation if there's not much visual movement.
334  const base::TimeDelta duration = GetCrossFadeDuration(window,
335                                                        old_bounds, new_bounds);
336
337  // Scale up the old layer while translating to new position.
338  {
339    ui::Layer* old_layer = old_layer_owner->root();
340    old_layer->GetAnimator()->StopAnimating();
341    ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
342
343    // Animation observer owns the old layer and deletes itself.
344    settings.AddObserver(new CrossFadeObserver(window, old_layer_owner.Pass()));
345    settings.SetTransitionDuration(duration);
346    settings.SetTweenType(tween_type);
347    gfx::Transform out_transform;
348    float scale_x = static_cast<float>(new_bounds.width()) /
349        static_cast<float>(old_bounds.width());
350    float scale_y = static_cast<float>(new_bounds.height()) /
351        static_cast<float>(old_bounds.height());
352    out_transform.Translate(new_bounds.x() - old_bounds.x(),
353                            new_bounds.y() - old_bounds.y());
354    out_transform.Scale(scale_x, scale_y);
355    old_layer->SetTransform(out_transform);
356    if (old_on_top) {
357      // The old layer is on top, and should fade out.  The new layer below will
358      // stay opaque to block the desktop.
359      old_layer->SetOpacity(kWindowAnimation_HideOpacity);
360    }
361    // In tests |old_layer| is deleted here, as animations have zero duration.
362    old_layer = NULL;
363  }
364
365  // Set the new layer's current transform, such that the user sees a scaled
366  // version of the window with the original bounds at the original position.
367  gfx::Transform in_transform;
368  const float scale_x = static_cast<float>(old_bounds.width()) /
369      static_cast<float>(new_bounds.width());
370  const float scale_y = static_cast<float>(old_bounds.height()) /
371      static_cast<float>(new_bounds.height());
372  in_transform.Translate(old_bounds.x() - new_bounds.x(),
373                               old_bounds.y() - new_bounds.y());
374  in_transform.Scale(scale_x, scale_y);
375  window->layer()->SetTransform(in_transform);
376  if (!old_on_top) {
377    // The new layer is on top and should fade in.  The old layer below will
378    // stay opaque and block the desktop.
379    window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
380  }
381  {
382    // Animate the new layer to the identity transform, so the window goes to
383    // its newly set bounds.
384    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
385    settings.SetTransitionDuration(duration);
386    settings.SetTweenType(tween_type);
387    window->layer()->SetTransform(gfx::Transform());
388    if (!old_on_top) {
389      // New layer is on top, fade it in.
390      window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
391    }
392  }
393  return duration;
394}
395
396bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
397  if (::wm::WindowAnimationsDisabled(window))
398    return false;
399
400  // Attempt to run CoreWm supplied animation types.
401  if (::wm::AnimateOnChildWindowVisibilityChanged(window, visible))
402    return true;
403
404  // Otherwise try to run an Ash-specific animation.
405  if (visible)
406    return AnimateShowWindow(window);
407  // Don't start hiding the window again if it's already being hidden.
408  return window->layer()->GetTargetOpacity() != 0.0f &&
409      AnimateHideWindow(window);
410}
411
412std::vector<ui::LayerAnimationSequence*>
413CreateBrightnessGrayscaleAnimationSequence(float target_value,
414                                           base::TimeDelta duration) {
415  gfx::Tween::Type animation_type = gfx::Tween::EASE_OUT;
416  scoped_ptr<ui::LayerAnimationSequence> brightness_sequence(
417      new ui::LayerAnimationSequence());
418  scoped_ptr<ui::LayerAnimationSequence> grayscale_sequence(
419      new ui::LayerAnimationSequence());
420
421  scoped_ptr<ui::LayerAnimationElement> brightness_element(
422      ui::LayerAnimationElement::CreateBrightnessElement(
423          target_value, duration));
424  brightness_element->set_tween_type(animation_type);
425  brightness_sequence->AddElement(brightness_element.release());
426
427  scoped_ptr<ui::LayerAnimationElement> grayscale_element(
428      ui::LayerAnimationElement::CreateGrayscaleElement(
429          target_value, duration));
430  grayscale_element->set_tween_type(animation_type);
431  grayscale_sequence->AddElement(grayscale_element.release());
432
433  std::vector<ui::LayerAnimationSequence*> animations;
434  animations.push_back(brightness_sequence.release());
435  animations.push_back(grayscale_sequence.release());
436
437  return animations;
438}
439
440// Returns scale related to the specified AshWindowScaleType.
441void SetTransformForScaleAnimation(ui::Layer* layer,
442                                   LayerScaleAnimationDirection type) {
443  const float scale =
444      type == LAYER_SCALE_ANIMATION_ABOVE ? kLayerScaleAboveSize :
445          kLayerScaleBelowSize;
446  gfx::Transform transform;
447  transform.Translate(-layer->bounds().width() * (scale - 1.0f) / 2,
448                      -layer->bounds().height() * (scale - 1.0f) / 2);
449  transform.Scale(scale, scale);
450  layer->SetTransform(transform);
451}
452
453gfx::Rect GetMinimizeAnimationTargetBoundsInScreen(aura::Window* window) {
454  Shelf* shelf = Shelf::ForWindow(window);
455  // Shelf is created lazily and can be NULL.
456  if (!shelf)
457    return gfx::Rect();
458  gfx::Rect item_rect = shelf->GetScreenBoundsOfItemIconForWindow(window);
459
460  // The launcher item is visible and has an icon.
461  if (!item_rect.IsEmpty())
462    return item_rect;
463
464  // If both the icon width and height are 0, then there is no icon in the
465  // launcher for |window|. If the launcher is auto hidden, one of the height or
466  // width will be 0 but the position in the launcher and the major dimension
467  // are still reported correctly and the window can be animated to the launcher
468  // item's light bar.
469  ShelfLayoutManager* layout_manager = ShelfLayoutManager::ForShelf(window);
470  if (item_rect.width() != 0 || item_rect.height() != 0) {
471    if (layout_manager->visibility_state() == SHELF_AUTO_HIDE) {
472      gfx::Rect shelf_bounds = shelf->shelf_widget()->GetWindowBoundsInScreen();
473      switch (layout_manager->GetAlignment()) {
474        case SHELF_ALIGNMENT_BOTTOM:
475          item_rect.set_y(shelf_bounds.y());
476          break;
477        case SHELF_ALIGNMENT_LEFT:
478          item_rect.set_x(shelf_bounds.right());
479          break;
480        case SHELF_ALIGNMENT_RIGHT:
481          item_rect.set_x(shelf_bounds.x());
482          break;
483        case SHELF_ALIGNMENT_TOP:
484          item_rect.set_y(shelf_bounds.bottom());
485          break;
486      }
487      return item_rect;
488    }
489  }
490
491  // Coming here, there is no visible icon of that shelf item and we zoom back
492  // to the location of the application launcher (which is fixed as first item
493  // of the shelf).
494  gfx::Rect work_area =
495      Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
496  int ltr_adjusted_x = base::i18n::IsRTL() ? work_area.right() : work_area.x();
497  switch (layout_manager->GetAlignment()) {
498    case SHELF_ALIGNMENT_BOTTOM:
499      return gfx::Rect(ltr_adjusted_x, work_area.bottom(), 0, 0);
500    case SHELF_ALIGNMENT_TOP:
501      return gfx::Rect(ltr_adjusted_x, work_area.y(), 0, 0);
502    case SHELF_ALIGNMENT_LEFT:
503      return gfx::Rect(work_area.x(), work_area.y(), 0, 0);
504    case SHELF_ALIGNMENT_RIGHT:
505      return gfx::Rect(work_area.right(), work_area.y(), 0, 0);
506  }
507  NOTREACHED();
508  return gfx::Rect();
509}
510
511}  // namespace ash
512