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