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