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