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 43 namespace ash { 44 namespace { 45 const int kLayerAnimationsForMinimizeDurationMS = 200; 46 47 // Durations for the cross-fade animation, in milliseconds. 48 const float kCrossFadeDurationMinMs = 200.f; 49 const float kCrossFadeDurationMaxMs = 400.f; 50 51 // Durations for the brightness/grayscale fade animation, in milliseconds. 52 const int kBrightnessGrayscaleFadeDurationMs = 1000; 53 54 // Brightness/grayscale values for hide/show window animations. 55 const float kWindowAnimation_HideBrightnessGrayscale = 1.f; 56 const float kWindowAnimation_ShowBrightnessGrayscale = 0.f; 57 58 const float kWindowAnimation_HideOpacity = 0.f; 59 const float kWindowAnimation_ShowOpacity = 1.f; 60 61 // Scales for AshWindow above/below current workspace. 62 const float kLayerScaleAboveSize = 1.1f; 63 const float kLayerScaleBelowSize = .9f; 64 65 int64 Round64(float f) { 66 return static_cast<int64>(f + 0.5f); 67 } 68 69 base::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 96 const int kCrossFadeDurationMS = 200; 97 98 void 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 157 void 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 171 void 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 182 void 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 218 void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) { 219 AnimateShowHideWindowCommon_BrightnessGrayscale(window, true); 220 } 221 222 void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) { 223 AnimateShowHideWindowCommon_BrightnessGrayscale(window, false); 224 } 225 226 bool 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 245 bool 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. 267 class 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 324 base::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 396 bool 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 412 std::vector<ui::LayerAnimationSequence*> 413 CreateBrightnessGrayscaleAnimationSequence(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. 441 void 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 453 gfx::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