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.
5 #include "ash/wm/window_animations.h"
12 #include "ash/ash_switches.h"
13 #include "ash/launcher/launcher.h"
14 #include "ash/screen_ash.h"
15 #include "ash/shell.h"
16 #include "ash/wm/window_util.h"
17 #include "ash/wm/workspace_controller.h"
18 #include "ash/wm/workspace/workspace_animations.h"
19 #include "base/command_line.h"
20 #include "base/compiler_specific.h"
21 #include "base/logging.h"
22 #include "base/message_loop.h"
23 #include "base/stl_util.h"
24 #include "base/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"
44 const int kLayerAnimationsForMinimizeDurationMS
= 200;
46 // Durations for the cross-fade animation, in milliseconds.
47 const float kCrossFadeDurationMinMs
= 200.f
;
48 const float kCrossFadeDurationMaxMs
= 400.f
;
50 // Durations for the brightness/grayscale fade animation, in milliseconds.
51 const int kBrightnessGrayscaleFadeDurationMs
= 1000;
53 // Brightness/grayscale values for hide/show window animations.
54 const float kWindowAnimation_HideBrightnessGrayscale
= 1.f
;
55 const float kWindowAnimation_ShowBrightnessGrayscale
= 0.f
;
57 const float kWindowAnimation_HideOpacity
= 0.f
;
58 const float kWindowAnimation_ShowOpacity
= 1.f
;
59 // TODO(sky): if we end up sticking with 0, nuke the code doing the rotation.
60 const float kWindowAnimation_MinimizeRotate
= 0.f
;
62 // Tween type when cross fading a workspace window.
63 const ui::Tween::Type kCrossFadeTweenType
= ui::Tween::EASE_IN_OUT
;
65 // Scales for AshWindow above/below current workspace.
66 const float kLayerScaleAboveSize
= 1.1f
;
67 const float kLayerScaleBelowSize
= .9f
;
69 int64
Round64(float f
) {
70 return static_cast<int64
>(f
+ 0.5f
);
75 gfx::Rect
GetMinimizeRectForWindow(aura::Window
* window
) {
76 Launcher
* launcher
= Launcher::ForWindow(window
);
77 // Launcher is created lazily and can be NULL.
80 gfx::Rect target_bounds
= Launcher::ForWindow(window
)->
81 GetScreenBoundsOfItemIconForWindow(window
);
82 if (target_bounds
.IsEmpty()) {
83 // Assume the launcher is overflowed, zoom off to the bottom right of the
86 Shell::GetScreen()->GetDisplayNearestWindow(window
).work_area();
87 target_bounds
.SetRect(work_area
.right(), work_area
.bottom(), 0, 0);
90 ScreenAsh::ConvertRectFromScreen(window
->parent(), target_bounds
);
94 void AddLayerAnimationsForMinimize(aura::Window
* window
, bool show
) {
95 // Recalculate the transform at restore time since the launcher item may have
96 // moved while the window was minimized.
97 gfx::Rect bounds
= window
->bounds();
98 gfx::Rect target_bounds
= GetMinimizeRectForWindow(window
);
100 float scale_x
= static_cast<float>(target_bounds
.height()) / bounds
.width();
101 float scale_y
= static_cast<float>(target_bounds
.width()) / bounds
.height();
103 scoped_ptr
<ui::InterpolatedTransform
> scale(
104 new ui::InterpolatedScale(gfx::Point3F(1, 1, 1),
105 gfx::Point3F(scale_x
, scale_y
, 1)));
107 scoped_ptr
<ui::InterpolatedTransform
> translation(
108 new ui::InterpolatedTranslation(
110 gfx::Point(target_bounds
.x() - bounds
.x(),
111 target_bounds
.y() - bounds
.y())));
113 scoped_ptr
<ui::InterpolatedTransform
> rotation(
114 new ui::InterpolatedRotation(0, kWindowAnimation_MinimizeRotate
));
116 scoped_ptr
<ui::InterpolatedTransform
> rotation_about_pivot(
117 new ui::InterpolatedTransformAboutPivot(
118 gfx::Point(bounds
.width() * 0.5, bounds
.height() * 0.5),
119 rotation
.release()));
121 scale
->SetChild(translation
.release());
122 rotation_about_pivot
->SetChild(scale
.release());
124 rotation_about_pivot
->SetReversed(show
);
126 base::TimeDelta duration
= base::TimeDelta::FromMilliseconds(
127 kLayerAnimationsForMinimizeDurationMS
);
129 scoped_ptr
<ui::LayerAnimationElement
> transition(
130 ui::LayerAnimationElement::CreateInterpolatedTransformElement(
131 rotation_about_pivot
.release(), duration
));
133 transition
->set_tween_type(
134 show
? ui::Tween::EASE_IN
: ui::Tween::EASE_IN_OUT
);
136 window
->layer()->GetAnimator()->ScheduleAnimation(
137 new ui::LayerAnimationSequence(transition
.release()));
139 // When hiding a window, turn off blending until the animation is 3 / 4 done
140 // to save bandwidth and reduce jank.
142 window
->layer()->GetAnimator()->SchedulePauseForProperties(
143 (duration
* 3) / 4, ui::LayerAnimationElement::OPACITY
, -1);
146 // Fade in and out quickly when the window is small to reduce jank.
147 float opacity
= show
? 1.0f
: 0.0f
;
148 window
->layer()->GetAnimator()->ScheduleAnimation(
149 new ui::LayerAnimationSequence(
150 ui::LayerAnimationElement::CreateOpacityElement(
151 opacity
, duration
/ 4)));
154 void AnimateShowWindow_Minimize(aura::Window
* window
) {
155 window
->layer()->set_delegate(window
);
156 window
->layer()->SetOpacity(kWindowAnimation_HideOpacity
);
157 AddLayerAnimationsForMinimize(window
, true);
159 // Now that the window has been restored, we need to clear its animation style
160 // to default so that normal animation applies.
161 views::corewm::SetWindowVisibilityAnimationType(
162 window
, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT
);
165 void AnimateHideWindow_Minimize(aura::Window
* window
) {
166 window
->layer()->set_delegate(NULL
);
168 // Property sets within this scope will be implicitly animated.
169 ui::ScopedLayerAnimationSettings
settings(window
->layer()->GetAnimator());
170 base::TimeDelta duration
= base::TimeDelta::FromMilliseconds(
171 kLayerAnimationsForMinimizeDurationMS
);
172 settings
.SetTransitionDuration(duration
);
173 settings
.AddObserver(
174 views::corewm::CreateHidingWindowAnimationObserver(window
));
175 window
->layer()->SetVisible(false);
177 AddLayerAnimationsForMinimize(window
, false);
180 void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window
* window
,
182 window
->layer()->set_delegate(window
);
184 float start_value
, end_value
;
186 start_value
= kWindowAnimation_HideBrightnessGrayscale
;
187 end_value
= kWindowAnimation_ShowBrightnessGrayscale
;
189 start_value
= kWindowAnimation_ShowBrightnessGrayscale
;
190 end_value
= kWindowAnimation_HideBrightnessGrayscale
;
193 window
->layer()->SetLayerBrightness(start_value
);
194 window
->layer()->SetLayerGrayscale(start_value
);
196 window
->layer()->SetOpacity(kWindowAnimation_ShowOpacity
);
197 window
->layer()->SetVisible(true);
200 base::TimeDelta duration
=
201 base::TimeDelta::FromMilliseconds(kBrightnessGrayscaleFadeDurationMs
);
203 ui::ScopedLayerAnimationSettings
settings(window
->layer()->GetAnimator());
204 settings
.SetTransitionDuration(duration
);
206 settings
.AddObserver(
207 views::corewm::CreateHidingWindowAnimationObserver(window
));
210 window
->layer()->GetAnimator()->
212 CreateBrightnessGrayscaleAnimationSequence(end_value
, duration
));
214 window
->layer()->SetOpacity(kWindowAnimation_HideOpacity
);
215 window
->layer()->SetVisible(false);
219 void AnimateShowWindow_BrightnessGrayscale(aura::Window
* window
) {
220 AnimateShowHideWindowCommon_BrightnessGrayscale(window
, true);
223 void AnimateHideWindow_BrightnessGrayscale(aura::Window
* window
) {
224 AnimateShowHideWindowCommon_BrightnessGrayscale(window
, false);
227 bool AnimateShowWindow(aura::Window
* window
) {
228 if (!views::corewm::HasWindowVisibilityAnimationTransition(
229 window
, views::corewm::ANIMATE_SHOW
)) {
233 switch (views::corewm::GetWindowVisibilityAnimationType(window
)) {
234 case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE
:
235 AnimateShowWindow_Minimize(window
);
237 case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE
:
238 AnimateShowWindow_BrightnessGrayscale(window
);
246 bool AnimateHideWindow(aura::Window
* window
) {
247 if (!views::corewm::HasWindowVisibilityAnimationTransition(
248 window
, views::corewm::ANIMATE_HIDE
)) {
252 switch (views::corewm::GetWindowVisibilityAnimationType(window
)) {
253 case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE
:
254 AnimateHideWindow_Minimize(window
);
256 case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE
:
257 AnimateHideWindow_BrightnessGrayscale(window
);
265 // Observer for a window cross-fade animation. If either the window closes or
266 // the layer's animation completes or compositing is aborted due to GPU crash,
267 // it deletes the layer and removes itself as an observer.
268 class CrossFadeObserver
: public ui::CompositorObserver
,
269 public aura::WindowObserver
,
270 public ui::ImplicitAnimationObserver
{
272 // Observes |window| for destruction, but does not take ownership.
273 // Takes ownership of |layer| and its child layers.
274 CrossFadeObserver(aura::Window
* window
, ui::Layer
* layer
)
277 window_
->AddObserver(this);
278 layer_
->GetCompositor()->AddObserver(this);
280 virtual ~CrossFadeObserver() {
281 window_
->RemoveObserver(this);
283 layer_
->GetCompositor()->RemoveObserver(this);
284 views::corewm::DeepDeleteLayers(layer_
);
288 // ui::CompositorObserver overrides:
289 virtual void OnCompositingDidCommit(ui::Compositor
* compositor
) OVERRIDE
{
291 virtual void OnCompositingStarted(ui::Compositor
* compositor
,
292 base::TimeTicks start_time
) OVERRIDE
{
294 virtual void OnCompositingEnded(ui::Compositor
* compositor
) OVERRIDE
{
296 virtual void OnCompositingAborted(ui::Compositor
* compositor
) OVERRIDE
{
297 // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
298 layer_
->GetAnimator()->StopAnimating();
300 virtual void OnCompositingLockStateChanged(
301 ui::Compositor
* compositor
) OVERRIDE
{
303 virtual void OnUpdateVSyncParameters(ui::Compositor
* compositor
,
304 base::TimeTicks timebase
,
305 base::TimeDelta interval
) OVERRIDE
{
308 // aura::WindowObserver overrides:
309 virtual void OnWindowDestroying(aura::Window
* window
) OVERRIDE
{
310 // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
311 layer_
->GetAnimator()->StopAnimating();
314 // ui::ImplicitAnimationObserver overrides:
315 virtual void OnImplicitAnimationsCompleted() OVERRIDE
{
320 aura::Window
* window_
; // not owned
321 ui::Layer
* layer_
; // owned
323 DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver
);
326 // Implementation of cross fading. Window is the window being cross faded. It
327 // should be at the target bounds. |old_layer| the previous layer from |window|.
328 // This takes ownership of |old_layer| and deletes when the animation is done.
329 // |pause_duration| is the duration to pause at the current bounds before
330 // animating. Returns the duration of the fade.
331 base::TimeDelta
CrossFadeImpl(aura::Window
* window
,
332 ui::Layer
* old_layer
,
333 ui::Tween::Type tween_type
) {
334 const gfx::Rect
old_bounds(old_layer
->bounds());
335 const gfx::Rect
new_bounds(window
->bounds());
336 const bool old_on_top
= (old_bounds
.width() > new_bounds
.width());
338 // Shorten the animation if there's not much visual movement.
339 const base::TimeDelta duration
= GetCrossFadeDuration(old_bounds
, new_bounds
);
341 // Scale up the old layer while translating to new position.
343 old_layer
->GetAnimator()->StopAnimating();
344 ui::ScopedLayerAnimationSettings
settings(old_layer
->GetAnimator());
346 // Animation observer owns the old layer and deletes itself.
347 settings
.AddObserver(new CrossFadeObserver(window
, old_layer
));
348 settings
.SetTransitionDuration(duration
);
349 settings
.SetTweenType(tween_type
);
350 gfx::Transform out_transform
;
351 float scale_x
= static_cast<float>(new_bounds
.width()) /
352 static_cast<float>(old_bounds
.width());
353 float scale_y
= static_cast<float>(new_bounds
.height()) /
354 static_cast<float>(old_bounds
.height());
355 out_transform
.Translate(new_bounds
.x() - old_bounds
.x(),
356 new_bounds
.y() - old_bounds
.y());
357 out_transform
.Scale(scale_x
, scale_y
);
358 old_layer
->SetTransform(out_transform
);
360 // The old layer is on top, and should fade out. The new layer below will
361 // stay opaque to block the desktop.
362 old_layer
->SetOpacity(kWindowAnimation_HideOpacity
);
364 // In tests |old_layer| is deleted here, as animations have zero duration.
368 // Set the new layer's current transform, such that the user sees a scaled
369 // version of the window with the original bounds at the original position.
370 gfx::Transform in_transform
;
371 const float scale_x
= static_cast<float>(old_bounds
.width()) /
372 static_cast<float>(new_bounds
.width());
373 const float scale_y
= static_cast<float>(old_bounds
.height()) /
374 static_cast<float>(new_bounds
.height());
375 in_transform
.Translate(old_bounds
.x() - new_bounds
.x(),
376 old_bounds
.y() - new_bounds
.y());
377 in_transform
.Scale(scale_x
, scale_y
);
378 window
->layer()->SetTransform(in_transform
);
380 // The new layer is on top and should fade in. The old layer below will
381 // stay opaque and block the desktop.
382 window
->layer()->SetOpacity(kWindowAnimation_HideOpacity
);
385 // Animate the new layer to the identity transform, so the window goes to
386 // its newly set bounds.
387 ui::ScopedLayerAnimationSettings
settings(window
->layer()->GetAnimator());
388 settings
.SetTransitionDuration(duration
);
389 settings
.SetTweenType(tween_type
);
390 window
->layer()->SetTransform(gfx::Transform());
392 // New layer is on top, fade it in.
393 window
->layer()->SetOpacity(kWindowAnimation_ShowOpacity
);
399 void CrossFadeToBounds(aura::Window
* window
, const gfx::Rect
& new_bounds
) {
400 DCHECK(window
->TargetVisibility());
401 const gfx::Rect old_bounds
= window
->bounds();
403 // Create fresh layers for the window and all its children to paint into.
404 // Takes ownership of the old layer and all its children, which will be
405 // cleaned up after the animation completes.
406 ui::Layer
* old_layer
= views::corewm::RecreateWindowLayers(window
, false);
407 ui::Layer
* new_layer
= window
->layer();
409 // Resize the window to the new size, which will force a layout and paint.
410 window
->SetBounds(new_bounds
);
412 // Ensure the higher-resolution layer is on top.
413 bool old_on_top
= (old_bounds
.width() > new_bounds
.width());
415 old_layer
->parent()->StackBelow(new_layer
, old_layer
);
417 old_layer
->parent()->StackAbove(new_layer
, old_layer
);
419 CrossFadeImpl(window
, old_layer
, ui::Tween::EASE_OUT
);
422 void CrossFadeWindowBetweenWorkspaces(aura::Window
* new_workspace
,
423 aura::Window
* window
,
424 ui::Layer
* old_layer
) {
425 ui::Layer
* layer_parent
= new_workspace
->layer()->parent();
426 layer_parent
->Add(old_layer
);
427 const bool restoring
= old_layer
->bounds().width() > window
->bounds().width();
429 layer_parent
->StackAbove(old_layer
, new_workspace
->layer());
431 layer_parent
->StackBelow(old_layer
, new_workspace
->layer());
433 CrossFadeImpl(window
, old_layer
, kCrossFadeTweenType
);
436 base::TimeDelta
GetCrossFadeDuration(const gfx::Rect
& old_bounds
,
437 const gfx::Rect
& new_bounds
) {
438 if (views::corewm::WindowAnimationsDisabled(NULL
))
439 return base::TimeDelta();
441 int old_area
= old_bounds
.width() * old_bounds
.height();
442 int new_area
= new_bounds
.width() * new_bounds
.height();
443 int max_area
= std::max(old_area
, new_area
);
444 // Avoid divide by zero.
446 return base::TimeDelta::FromMilliseconds(internal::kWorkspaceSwitchTimeMS
);
448 int delta_area
= std::abs(old_area
- new_area
);
449 // If the area didn't change, the animation is instantaneous.
451 return base::TimeDelta::FromMilliseconds(internal::kWorkspaceSwitchTimeMS
);
454 static_cast<float>(delta_area
) / static_cast<float>(max_area
);
455 const float kRange
= kCrossFadeDurationMaxMs
- kCrossFadeDurationMinMs
;
456 return base::TimeDelta::FromMilliseconds(
457 Round64(kCrossFadeDurationMinMs
+ (factor
* kRange
)));
460 bool AnimateOnChildWindowVisibilityChanged(aura::Window
* window
, bool visible
) {
461 if (views::corewm::WindowAnimationsDisabled(window
))
464 // Attempt to run CoreWm supplied animation types.
465 if (views::corewm::AnimateOnChildWindowVisibilityChanged(window
, visible
))
468 // Otherwise try to run an Ash-specific animation.
470 return AnimateShowWindow(window
);
471 // Don't start hiding the window again if it's already being hidden.
472 return window
->layer()->GetTargetOpacity() != 0.0f
&&
473 AnimateHideWindow(window
);
476 std::vector
<ui::LayerAnimationSequence
*>
477 CreateBrightnessGrayscaleAnimationSequence(float target_value
,
478 base::TimeDelta duration
) {
479 ui::Tween::Type animation_type
= ui::Tween::EASE_OUT
;
480 if (CommandLine::ForCurrentProcess()->HasSwitch(
481 ash::switches::kAshBootAnimationFunction2
)) {
482 animation_type
= ui::Tween::EASE_OUT_2
;
483 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
484 ash::switches::kAshBootAnimationFunction3
)) {
485 animation_type
= ui::Tween::EASE_OUT_3
;
487 scoped_ptr
<ui::LayerAnimationSequence
> brightness_sequence(
488 new ui::LayerAnimationSequence());
489 scoped_ptr
<ui::LayerAnimationSequence
> grayscale_sequence(
490 new ui::LayerAnimationSequence());
492 scoped_ptr
<ui::LayerAnimationElement
> brightness_element(
493 ui::LayerAnimationElement::CreateBrightnessElement(
494 target_value
, duration
));
495 brightness_element
->set_tween_type(animation_type
);
496 brightness_sequence
->AddElement(brightness_element
.release());
498 scoped_ptr
<ui::LayerAnimationElement
> grayscale_element(
499 ui::LayerAnimationElement::CreateGrayscaleElement(
500 target_value
, duration
));
501 grayscale_element
->set_tween_type(animation_type
);
502 grayscale_sequence
->AddElement(grayscale_element
.release());
504 std::vector
<ui::LayerAnimationSequence
*> animations
;
505 animations
.push_back(brightness_sequence
.release());
506 animations
.push_back(grayscale_sequence
.release());
511 // Returns scale related to the specified AshWindowScaleType.
512 void SetTransformForScaleAnimation(ui::Layer
* layer
,
513 LayerScaleAnimationDirection type
) {
515 type
== LAYER_SCALE_ANIMATION_ABOVE
? kLayerScaleAboveSize
:
516 kLayerScaleBelowSize
;
517 gfx::Transform transform
;
518 transform
.Translate(-layer
->bounds().width() * (scale
- 1.0f
) / 2,
519 -layer
->bounds().height() * (scale
- 1.0f
) / 2);
520 transform
.Scale(scale
, scale
);
521 layer
->SetTransform(transform
);