1 // Copyright (c) 2013 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/dock/docked_window_layout_manager.h" 6 7 #include "ash/screen_util.h" 8 #include "ash/shelf/shelf.h" 9 #include "ash/shelf/shelf_constants.h" 10 #include "ash/shelf/shelf_layout_manager.h" 11 #include "ash/shelf/shelf_types.h" 12 #include "ash/shelf/shelf_widget.h" 13 #include "ash/shell.h" 14 #include "ash/shell_window_ids.h" 15 #include "ash/wm/coordinate_conversion.h" 16 #include "ash/wm/window_animations.h" 17 #include "ash/wm/window_properties.h" 18 #include "ash/wm/window_resizer.h" 19 #include "ash/wm/window_state.h" 20 #include "ash/wm/window_util.h" 21 #include "ash/wm/workspace_controller.h" 22 #include "base/auto_reset.h" 23 #include "base/command_line.h" 24 #include "base/metrics/histogram.h" 25 #include "grit/ash_resources.h" 26 #include "third_party/skia/include/core/SkColor.h" 27 #include "third_party/skia/include/core/SkPaint.h" 28 #include "ui/aura/client/focus_client.h" 29 #include "ui/aura/client/window_tree_client.h" 30 #include "ui/aura/window.h" 31 #include "ui/aura/window_delegate.h" 32 #include "ui/aura/window_event_dispatcher.h" 33 #include "ui/base/resource/resource_bundle.h" 34 #include "ui/compositor/scoped_layer_animation_settings.h" 35 #include "ui/gfx/canvas.h" 36 #include "ui/gfx/image/image_skia_operations.h" 37 #include "ui/gfx/rect.h" 38 #include "ui/views/background.h" 39 #include "ui/wm/core/window_util.h" 40 #include "ui/wm/public/activation_client.h" 41 42 namespace ash { 43 44 // Minimum, maximum width of the dock area and a width of the gap 45 // static 46 const int DockedWindowLayoutManager::kMaxDockWidth = 360; 47 // static 48 const int DockedWindowLayoutManager::kMinDockWidth = 200; 49 // static 50 const int DockedWindowLayoutManager::kMinDockGap = 2; 51 // static 52 const int DockedWindowLayoutManager::kIdealWidth = 250; 53 const int kMinimumHeight = 250; 54 const int kSlideDurationMs = 120; 55 const int kFadeDurationMs = 60; 56 const int kMinimizeDurationMs = 720; 57 58 class DockedBackgroundWidget : public views::Widget, 59 public BackgroundAnimatorDelegate { 60 public: 61 explicit DockedBackgroundWidget(aura::Window* container) 62 : alignment_(DOCKED_ALIGNMENT_NONE), 63 background_animator_(this, 0, kShelfBackgroundAlpha), 64 alpha_(0), 65 opaque_background_(ui::LAYER_SOLID_COLOR), 66 visible_background_type_(SHELF_BACKGROUND_DEFAULT), 67 visible_background_change_type_(BACKGROUND_CHANGE_IMMEDIATE) { 68 InitWidget(container); 69 } 70 71 // Sets widget bounds and sizes opaque background layer to fill the widget. 72 void SetBackgroundBounds(const gfx::Rect bounds, DockedAlignment alignment) { 73 SetBounds(bounds); 74 opaque_background_.SetBounds(gfx::Rect(bounds.size())); 75 alignment_ = alignment; 76 } 77 78 // Sets the background type. Starts an animation to transition to 79 // |background_type| if the widget is visible. If the widget is not visible, 80 // the animation is postponed till the widget becomes visible. 81 void SetBackgroundType(ShelfBackgroundType background_type, 82 BackgroundAnimatorChangeType change_type) { 83 visible_background_type_ = background_type; 84 visible_background_change_type_ = change_type; 85 if (IsVisible()) 86 UpdateBackground(); 87 } 88 89 // views::Widget: 90 virtual void OnNativeWidgetVisibilityChanged(bool visible) OVERRIDE { 91 views::Widget::OnNativeWidgetVisibilityChanged(visible); 92 UpdateBackground(); 93 } 94 95 virtual void OnNativeWidgetPaint(gfx::Canvas* canvas) OVERRIDE { 96 const gfx::ImageSkia& shelf_background( 97 alignment_ == DOCKED_ALIGNMENT_LEFT ? 98 shelf_background_left_ : shelf_background_right_); 99 gfx::Rect rect = gfx::Rect(GetWindowBoundsInScreen().size()); 100 SkPaint paint; 101 paint.setAlpha(alpha_); 102 canvas->DrawImageInt(shelf_background, 103 0, 104 0, 105 shelf_background.width(), 106 shelf_background.height(), 107 alignment_ == DOCKED_ALIGNMENT_LEFT 108 ? rect.width() - shelf_background.width() 109 : 0, 110 0, 111 shelf_background.width(), 112 rect.height(), 113 false, 114 paint); 115 canvas->DrawImageInt( 116 shelf_background, 117 alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width() - 1, 118 0, 119 1, 120 shelf_background.height(), 121 alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width(), 122 0, 123 rect.width() - shelf_background.width(), 124 rect.height(), 125 false, 126 paint); 127 } 128 129 // BackgroundAnimatorDelegate: 130 virtual void UpdateBackground(int alpha) OVERRIDE { 131 alpha_ = alpha; 132 SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size())); 133 } 134 135 private: 136 void InitWidget(aura::Window* parent) { 137 views::Widget::InitParams params; 138 params.type = views::Widget::InitParams::TYPE_POPUP; 139 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 140 params.keep_on_top = false; 141 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 142 params.parent = parent; 143 params.accept_events = false; 144 set_focus_on_creation(false); 145 Init(params); 146 SetVisibilityChangedAnimationsEnabled(false); 147 GetNativeWindow()->SetProperty(kStayInSameRootWindowKey, true); 148 opaque_background_.SetColor(SK_ColorBLACK); 149 opaque_background_.SetBounds(gfx::Rect(GetWindowBoundsInScreen().size())); 150 opaque_background_.SetOpacity(0.0f); 151 GetNativeWindow()->layer()->Add(&opaque_background_); 152 Hide(); 153 154 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 155 gfx::ImageSkia shelf_background = 156 *rb.GetImageSkiaNamed(IDR_ASH_SHELF_BACKGROUND); 157 shelf_background_left_ = gfx::ImageSkiaOperations::CreateRotatedImage( 158 shelf_background, SkBitmapOperations::ROTATION_90_CW); 159 shelf_background_right_ = gfx::ImageSkiaOperations::CreateRotatedImage( 160 shelf_background, SkBitmapOperations::ROTATION_270_CW); 161 } 162 163 // Transitions to |visible_background_type_| if the widget is visible and to 164 // SHELF_BACKGROUND_DEFAULT if it is not. 165 void UpdateBackground() { 166 ShelfBackgroundType background_type = IsVisible() ? 167 visible_background_type_ : SHELF_BACKGROUND_DEFAULT; 168 BackgroundAnimatorChangeType change_type = IsVisible() ? 169 visible_background_change_type_ : BACKGROUND_CHANGE_IMMEDIATE; 170 171 float target_opacity = 172 (background_type == SHELF_BACKGROUND_MAXIMIZED) ? 1.0f : 0.0f; 173 scoped_ptr<ui::ScopedLayerAnimationSettings> opaque_background_animation; 174 if (change_type != BACKGROUND_CHANGE_IMMEDIATE) { 175 opaque_background_animation.reset(new ui::ScopedLayerAnimationSettings( 176 opaque_background_.GetAnimator())); 177 opaque_background_animation->SetTransitionDuration( 178 base::TimeDelta::FromMilliseconds(kTimeToSwitchBackgroundMs)); 179 } 180 opaque_background_.SetOpacity(target_opacity); 181 182 // TODO(varkha): use ui::Layer on both opaque_background and normal 183 // background retire background_animator_ at all. It would be simpler. 184 // See also ShelfWidget::SetPaintsBackground. 185 background_animator_.SetPaintsBackground( 186 background_type != SHELF_BACKGROUND_DEFAULT, 187 change_type); 188 SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size())); 189 } 190 191 DockedAlignment alignment_; 192 193 // The animator for the background transitions. 194 BackgroundAnimator background_animator_; 195 196 // The alpha to use for drawing image assets covering the docked background. 197 int alpha_; 198 199 // Solid black background that can be made fully opaque. 200 ui::Layer opaque_background_; 201 202 // Backgrounds created from shelf background by 90 or 270 degree rotation. 203 gfx::ImageSkia shelf_background_left_; 204 gfx::ImageSkia shelf_background_right_; 205 206 // The background type to use when the widget is visible. When not visible, 207 // the widget uses SHELF_BACKGROUND_DEFAULT. 208 ShelfBackgroundType visible_background_type_; 209 210 // Whether the widget should animate to |visible_background_type_|. 211 BackgroundAnimatorChangeType visible_background_change_type_; 212 213 DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget); 214 }; 215 216 namespace { 217 218 // Returns true if a window is a popup or a transient child. 219 bool IsPopupOrTransient(const aura::Window* window) { 220 return (window->type() == ui::wm::WINDOW_TYPE_POPUP || 221 ::wm::GetTransientParent(window)); 222 } 223 224 // Certain windows (minimized, hidden or popups) do not matter to docking. 225 bool IsUsedByLayout(const aura::Window* window) { 226 return (window->IsVisible() && 227 !wm::GetWindowState(window)->IsMinimized() && 228 !IsPopupOrTransient(window)); 229 } 230 231 void UndockWindow(aura::Window* window) { 232 gfx::Rect previous_bounds = window->bounds(); 233 aura::Window* old_parent = window->parent(); 234 aura::client::ParentWindowWithContext(window, window, gfx::Rect()); 235 if (window->parent() != old_parent) 236 wm::ReparentTransientChildrenOfChild(window, old_parent, window->parent()); 237 // Start maximize or fullscreen (affecting packaged apps) animation from 238 // previous window bounds. 239 window->layer()->SetBounds(previous_bounds); 240 } 241 242 // Returns width that is as close as possible to |target_width| while being 243 // consistent with docked min and max restrictions and respects the |window|'s 244 // minimum and maximum size. 245 int GetWindowWidthCloseTo(const aura::Window* window, int target_width) { 246 if (!wm::GetWindowState(window)->CanResize()) { 247 DCHECK_LE(window->bounds().width(), 248 DockedWindowLayoutManager::kMaxDockWidth); 249 return window->bounds().width(); 250 } 251 int width = std::max(DockedWindowLayoutManager::kMinDockWidth, 252 std::min(target_width, 253 DockedWindowLayoutManager::kMaxDockWidth)); 254 if (window->delegate()) { 255 if (window->delegate()->GetMinimumSize().width() != 0) 256 width = std::max(width, window->delegate()->GetMinimumSize().width()); 257 if (window->delegate()->GetMaximumSize().width() != 0) 258 width = std::min(width, window->delegate()->GetMaximumSize().width()); 259 } 260 DCHECK_LE(width, DockedWindowLayoutManager::kMaxDockWidth); 261 return width; 262 } 263 264 // Returns height that is as close as possible to |target_height| while 265 // respecting the |window|'s minimum and maximum size. 266 int GetWindowHeightCloseTo(const aura::Window* window, int target_height) { 267 if (!wm::GetWindowState(window)->CanResize()) 268 return window->bounds().height(); 269 int minimum_height = kMinimumHeight; 270 int maximum_height = 0; 271 const aura::WindowDelegate* delegate(window->delegate()); 272 if (delegate) { 273 if (delegate->GetMinimumSize().height() != 0) { 274 minimum_height = std::max(kMinimumHeight, 275 delegate->GetMinimumSize().height()); 276 } 277 if (delegate->GetMaximumSize().height() != 0) 278 maximum_height = delegate->GetMaximumSize().height(); 279 } 280 if (minimum_height) 281 target_height = std::max(target_height, minimum_height); 282 if (maximum_height) 283 target_height = std::min(target_height, maximum_height); 284 return target_height; 285 } 286 287 // A functor used to sort the windows in order of their minimum height. 288 struct CompareMinimumHeight { 289 bool operator()(WindowWithHeight win1, WindowWithHeight win2) { 290 return GetWindowHeightCloseTo(win1.window(), 0) < 291 GetWindowHeightCloseTo(win2.window(), 0); 292 } 293 }; 294 295 // A functor used to sort the windows in order of their center Y position. 296 // |delta| is a pre-calculated distance from the bottom of one window to the top 297 // of the next. Its value can be positive (gap) or negative (overlap). 298 // Half of |delta| is used as a transition point at which windows could ideally 299 // swap positions. 300 struct CompareWindowPos { 301 CompareWindowPos(aura::Window* dragged_window, 302 aura::Window* docked_container, 303 float delta) 304 : dragged_window_(dragged_window), 305 docked_container_(docked_container), 306 delta_(delta / 2) {} 307 308 bool operator()(WindowWithHeight window_with_height1, 309 WindowWithHeight window_with_height2) { 310 // Use target coordinates since animations may be active when windows are 311 // reordered. 312 aura::Window* win1(window_with_height1.window()); 313 aura::Window* win2(window_with_height2.window()); 314 gfx::Rect win1_bounds = ScreenUtil::ConvertRectToScreen( 315 docked_container_, win1->GetTargetBounds()); 316 gfx::Rect win2_bounds = ScreenUtil::ConvertRectToScreen( 317 docked_container_, win2->GetTargetBounds()); 318 win1_bounds.set_height(window_with_height1.height_); 319 win2_bounds.set_height(window_with_height2.height_); 320 // If one of the windows is the |dragged_window_| attempt to make an 321 // earlier swap between the windows than just based on their centers. 322 // This is possible if the dragged window is at least as tall as the other 323 // window. 324 if (win1 == dragged_window_) 325 return compare_two_windows(win1_bounds, win2_bounds); 326 if (win2 == dragged_window_) 327 return !compare_two_windows(win2_bounds, win1_bounds); 328 // Otherwise just compare the centers. 329 return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y(); 330 } 331 332 // Based on center point tries to deduce where the drag is coming from. 333 // When dragging from below up the transition point is lower. 334 // When dragging from above down the transition point is higher. 335 bool compare_bounds(const gfx::Rect dragged, const gfx::Rect other) { 336 if (dragged.CenterPoint().y() < other.CenterPoint().y()) 337 return dragged.CenterPoint().y() < other.y() - delta_; 338 return dragged.CenterPoint().y() < other.bottom() + delta_; 339 } 340 341 // Performs comparison both ways and selects stable result. 342 bool compare_two_windows(const gfx::Rect bounds1, const gfx::Rect bounds2) { 343 // Try comparing windows in both possible orders and see if the comparison 344 // is stable. 345 bool result1 = compare_bounds(bounds1, bounds2); 346 bool result2 = compare_bounds(bounds2, bounds1); 347 if (result1 != result2) 348 return result1; 349 350 // Otherwise it is not possible to be sure that the windows will not bounce. 351 // In this case just compare the centers. 352 return bounds1.CenterPoint().y() < bounds2.CenterPoint().y(); 353 } 354 355 private: 356 aura::Window* dragged_window_; 357 aura::Window* docked_container_; 358 float delta_; 359 }; 360 361 } // namespace 362 363 //////////////////////////////////////////////////////////////////////////////// 364 // A class that observes shelf for bounds changes. 365 class DockedWindowLayoutManager::ShelfWindowObserver : public WindowObserver { 366 public: 367 explicit ShelfWindowObserver( 368 DockedWindowLayoutManager* docked_layout_manager) 369 : docked_layout_manager_(docked_layout_manager) { 370 DCHECK(docked_layout_manager_->shelf()->shelf_widget()); 371 docked_layout_manager_->shelf()->shelf_widget()->GetNativeView() 372 ->AddObserver(this); 373 } 374 375 virtual ~ShelfWindowObserver() { 376 if (docked_layout_manager_->shelf() && 377 docked_layout_manager_->shelf()->shelf_widget()) 378 docked_layout_manager_->shelf()->shelf_widget()->GetNativeView() 379 ->RemoveObserver(this); 380 } 381 382 // aura::WindowObserver: 383 virtual void OnWindowBoundsChanged(aura::Window* window, 384 const gfx::Rect& old_bounds, 385 const gfx::Rect& new_bounds) OVERRIDE { 386 shelf_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen( 387 window->parent(), new_bounds); 388 docked_layout_manager_->OnShelfBoundsChanged(); 389 } 390 391 const gfx::Rect& shelf_bounds_in_screen() const { 392 return shelf_bounds_in_screen_; 393 } 394 395 private: 396 DockedWindowLayoutManager* docked_layout_manager_; 397 gfx::Rect shelf_bounds_in_screen_; 398 399 DISALLOW_COPY_AND_ASSIGN(ShelfWindowObserver); 400 }; 401 402 //////////////////////////////////////////////////////////////////////////////// 403 // DockedWindowLayoutManager public implementation: 404 DockedWindowLayoutManager::DockedWindowLayoutManager( 405 aura::Window* dock_container, 406 WorkspaceController* workspace_controller) 407 : SnapToPixelLayoutManager(dock_container), 408 dock_container_(dock_container), 409 in_layout_(false), 410 dragged_window_(NULL), 411 is_dragged_window_docked_(false), 412 is_dragged_from_dock_(false), 413 shelf_(NULL), 414 workspace_controller_(workspace_controller), 415 in_fullscreen_(workspace_controller_->GetWindowState() == 416 WORKSPACE_WINDOW_STATE_FULL_SCREEN), 417 docked_width_(0), 418 alignment_(DOCKED_ALIGNMENT_NONE), 419 last_active_window_(NULL), 420 last_action_time_(base::Time::Now()), 421 background_widget_(new DockedBackgroundWidget(dock_container_)) { 422 DCHECK(dock_container); 423 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 424 AddObserver(this); 425 Shell::GetInstance()->AddShellObserver(this); 426 } 427 428 DockedWindowLayoutManager::~DockedWindowLayoutManager() { 429 Shutdown(); 430 } 431 432 void DockedWindowLayoutManager::Shutdown() { 433 if (shelf_ && shelf_->shelf_widget()) { 434 ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( 435 shelf_->shelf_widget()->GetNativeWindow()); 436 shelf_layout_manager->RemoveObserver(this); 437 shelf_observer_.reset(); 438 } 439 shelf_ = NULL; 440 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 441 aura::Window* child = dock_container_->children()[i]; 442 child->RemoveObserver(this); 443 wm::GetWindowState(child)->RemoveObserver(this); 444 } 445 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 446 RemoveObserver(this); 447 Shell::GetInstance()->RemoveShellObserver(this); 448 } 449 450 void DockedWindowLayoutManager::AddObserver( 451 DockedWindowLayoutManagerObserver* observer) { 452 observer_list_.AddObserver(observer); 453 } 454 455 void DockedWindowLayoutManager::RemoveObserver( 456 DockedWindowLayoutManagerObserver* observer) { 457 observer_list_.RemoveObserver(observer); 458 } 459 460 void DockedWindowLayoutManager::StartDragging(aura::Window* window) { 461 DCHECK(!dragged_window_); 462 dragged_window_ = window; 463 DCHECK(!IsPopupOrTransient(window)); 464 // Start observing a window unless it is docked container's child in which 465 // case it is already observed. 466 wm::WindowState* dragged_state = wm::GetWindowState(dragged_window_); 467 if (dragged_window_->parent() != dock_container_) { 468 dragged_window_->AddObserver(this); 469 dragged_state->AddObserver(this); 470 } else if (!IsAnyWindowDocked() && 471 dragged_state->drag_details() && 472 !(dragged_state->drag_details()->bounds_change & 473 WindowResizer::kBoundsChange_Resizes)) { 474 // If there are no other docked windows clear alignment when a docked window 475 // is moved (but not when it is resized or the window could get undocked 476 // when resized away from the edge while docked). 477 alignment_ = DOCKED_ALIGNMENT_NONE; 478 } 479 is_dragged_from_dock_ = window->parent() == dock_container_; 480 DCHECK(!is_dragged_window_docked_); 481 482 // Resize all windows that are flush with the dock edge together if one of 483 // them gets resized. 484 if (dragged_window_->bounds().width() == docked_width_ && 485 (dragged_state->drag_details()->bounds_change & 486 WindowResizer::kBoundsChange_Resizes) && 487 (dragged_state->drag_details()->size_change_direction & 488 WindowResizer::kBoundsChangeDirection_Horizontal)) { 489 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 490 aura::Window* window1(dock_container_->children()[i]); 491 if (IsUsedByLayout(window1) && 492 window1 != dragged_window_ && 493 window1->bounds().width() == docked_width_) { 494 wm::GetWindowState(window1)->set_bounds_changed_by_user(false); 495 } 496 } 497 } 498 } 499 500 void DockedWindowLayoutManager::DockDraggedWindow(aura::Window* window) { 501 DCHECK(!IsPopupOrTransient(window)); 502 OnDraggedWindowDocked(window); 503 Relayout(); 504 } 505 506 void DockedWindowLayoutManager::UndockDraggedWindow() { 507 DCHECK(!IsPopupOrTransient(dragged_window_)); 508 OnDraggedWindowUndocked(); 509 Relayout(); 510 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 511 is_dragged_from_dock_ = false; 512 } 513 514 void DockedWindowLayoutManager::FinishDragging(DockedAction action, 515 DockedActionSource source) { 516 DCHECK(dragged_window_); 517 DCHECK(!IsPopupOrTransient(dragged_window_)); 518 if (is_dragged_window_docked_) 519 OnDraggedWindowUndocked(); 520 DCHECK (!is_dragged_window_docked_); 521 // Stop observing a window unless it is docked container's child in which 522 // case it needs to keep being observed after the drag completes. 523 if (dragged_window_->parent() != dock_container_) { 524 dragged_window_->RemoveObserver(this); 525 wm::GetWindowState(dragged_window_)->RemoveObserver(this); 526 if (last_active_window_ == dragged_window_) 527 last_active_window_ = NULL; 528 } else { 529 // If this is the first window that got docked by a move update alignment. 530 if (alignment_ == DOCKED_ALIGNMENT_NONE) 531 alignment_ = GetEdgeNearestWindow(dragged_window_); 532 // A window is no longer dragged and is a child. 533 // When a window becomes a child at drag start this is 534 // the only opportunity we will have to enforce a window 535 // count limit so do it here. 536 MaybeMinimizeChildrenExcept(dragged_window_); 537 } 538 dragged_window_ = NULL; 539 dragged_bounds_ = gfx::Rect(); 540 Relayout(); 541 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 542 RecordUmaAction(action, source); 543 } 544 545 void DockedWindowLayoutManager::SetShelf(Shelf* shelf) { 546 DCHECK(!shelf_); 547 shelf_ = shelf; 548 if (shelf_->shelf_widget()) { 549 ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( 550 shelf_->shelf_widget()->GetNativeWindow()); 551 shelf_layout_manager->AddObserver(this); 552 shelf_observer_.reset(new ShelfWindowObserver(this)); 553 } 554 } 555 556 DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow( 557 const aura::Window* window) const { 558 const gfx::Rect& bounds(window->GetBoundsInScreen()); 559 560 // Test overlap with an existing docked area first. 561 if (docked_bounds_.Intersects(bounds) && 562 alignment_ != DOCKED_ALIGNMENT_NONE) { 563 // A window is being added to other docked windows (on the same side). 564 return alignment_; 565 } 566 567 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); 568 if (bounds.x() <= container_bounds.x() && 569 bounds.right() > container_bounds.x()) { 570 return DOCKED_ALIGNMENT_LEFT; 571 } else if (bounds.x() < container_bounds.right() && 572 bounds.right() >= container_bounds.right()) { 573 return DOCKED_ALIGNMENT_RIGHT; 574 } 575 return DOCKED_ALIGNMENT_NONE; 576 } 577 578 DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const { 579 // Find a child that is not being dragged and is not a popup. 580 // If such exists the current alignment is returned - even if some of the 581 // children are hidden or minimized (so they can be restored without losing 582 // the docked state). 583 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 584 aura::Window* window(dock_container_->children()[i]); 585 if (window != dragged_window_ && !IsPopupOrTransient(window)) 586 return alignment_; 587 } 588 // No docked windows remain other than possibly the window being dragged. 589 // Return |NONE| to indicate that windows may get docked on either side. 590 return DOCKED_ALIGNMENT_NONE; 591 } 592 593 bool DockedWindowLayoutManager::CanDockWindow( 594 aura::Window* window, 595 DockedAlignment desired_alignment) { 596 // Don't allow interactive docking of windows with transient parents such as 597 // modal browser dialogs. Prevent docking of panels attached to shelf during 598 // the drag. 599 wm::WindowState* window_state = wm::GetWindowState(window); 600 bool should_attach_to_shelf = window_state->drag_details() && 601 window_state->drag_details()->should_attach_to_shelf; 602 if (IsPopupOrTransient(window) || should_attach_to_shelf) 603 return false; 604 // If a window is wide and cannot be resized down to maximum width allowed 605 // then it cannot be docked. 606 // TODO(varkha). Prevent windows from changing size programmatically while 607 // they are docked. The size will take effect only once a window is undocked. 608 // See http://crbug.com/307792. 609 if (window->bounds().width() > kMaxDockWidth && 610 (!window_state->CanResize() || 611 (window->delegate() && 612 window->delegate()->GetMinimumSize().width() != 0 && 613 window->delegate()->GetMinimumSize().width() > kMaxDockWidth))) { 614 return false; 615 } 616 // If a window is tall and cannot be resized down to maximum height allowed 617 // then it cannot be docked. 618 const gfx::Rect work_area = 619 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 620 if (GetWindowHeightCloseTo(window, work_area.height()) > work_area.height()) 621 return false; 622 // Cannot dock on the other size from an existing dock. 623 const DockedAlignment alignment = CalculateAlignment(); 624 if (desired_alignment != DOCKED_ALIGNMENT_NONE && 625 alignment != DOCKED_ALIGNMENT_NONE && 626 alignment != desired_alignment) { 627 return false; 628 } 629 // Do not allow docking on the same side as shelf. 630 ShelfAlignment shelf_alignment = SHELF_ALIGNMENT_BOTTOM; 631 if (shelf_) 632 shelf_alignment = shelf_->alignment(); 633 if ((desired_alignment == DOCKED_ALIGNMENT_LEFT && 634 shelf_alignment == SHELF_ALIGNMENT_LEFT) || 635 (desired_alignment == DOCKED_ALIGNMENT_RIGHT && 636 shelf_alignment == SHELF_ALIGNMENT_RIGHT)) { 637 return false; 638 } 639 return true; 640 } 641 642 void DockedWindowLayoutManager::OnShelfBoundsChanged() { 643 Relayout(); 644 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); 645 } 646 647 //////////////////////////////////////////////////////////////////////////////// 648 // DockedWindowLayoutManager, aura::LayoutManager implementation: 649 void DockedWindowLayoutManager::OnWindowResized() { 650 MaybeMinimizeChildrenExcept(dragged_window_); 651 Relayout(); 652 // When screen resizes update the insets even when dock width or alignment 653 // does not change. 654 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED); 655 } 656 657 void DockedWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) { 658 if (IsPopupOrTransient(child)) 659 return; 660 // Dragged windows are already observed by StartDragging and do not change 661 // docked alignment during the drag. 662 if (child == dragged_window_) 663 return; 664 // If this is the first window getting docked - update alignment. 665 // A window can be added without proper bounds when window is moved to another 666 // display via API or due to display configuration change, so the alignment 667 // is set based on which edge is closer in the new display. 668 if (alignment_ == DOCKED_ALIGNMENT_NONE) 669 alignment_ = GetEdgeNearestWindow(child); 670 MaybeMinimizeChildrenExcept(child); 671 child->AddObserver(this); 672 wm::GetWindowState(child)->AddObserver(this); 673 Relayout(); 674 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 675 } 676 677 void DockedWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) { 678 if (IsPopupOrTransient(child)) 679 return; 680 // Dragged windows are stopped being observed by FinishDragging and do not 681 // change alignment during the drag. They also cannot be set to be the 682 // |last_active_window_|. 683 if (child == dragged_window_) 684 return; 685 // If this is the last window, set alignment and maximize the workspace. 686 if (!IsAnyWindowDocked()) { 687 alignment_ = DOCKED_ALIGNMENT_NONE; 688 UpdateDockedWidth(0); 689 } 690 if (last_active_window_ == child) 691 last_active_window_ = NULL; 692 child->RemoveObserver(this); 693 wm::GetWindowState(child)->RemoveObserver(this); 694 Relayout(); 695 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 696 } 697 698 void DockedWindowLayoutManager::OnChildWindowVisibilityChanged( 699 aura::Window* child, 700 bool visible) { 701 if (IsPopupOrTransient(child)) 702 return; 703 if (visible) 704 wm::GetWindowState(child)->Restore(); 705 Relayout(); 706 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 707 } 708 709 void DockedWindowLayoutManager::SetChildBounds( 710 aura::Window* child, 711 const gfx::Rect& requested_bounds) { 712 // The minimum constraints have to be applied first by the layout manager. 713 gfx::Rect actual_new_bounds(requested_bounds); 714 if (child->delegate()) { 715 const gfx::Size& min_size = child->delegate()->GetMinimumSize(); 716 actual_new_bounds.set_width( 717 std::max(min_size.width(), actual_new_bounds.width())); 718 actual_new_bounds.set_height( 719 std::max(min_size.height(), actual_new_bounds.height())); 720 } 721 SnapToPixelLayoutManager::SetChildBounds(child, actual_new_bounds); 722 if (IsPopupOrTransient(child)) 723 return; 724 // Whenever one of our windows is moved or resized enforce layout. 725 ShelfLayoutManager* shelf_layout = 726 ShelfLayoutManager::ForShelf(dock_container_); 727 if (shelf_layout) 728 shelf_layout->UpdateVisibilityState(); 729 } 730 731 //////////////////////////////////////////////////////////////////////////////// 732 // DockedWindowLayoutManager, ash::ShellObserver implementation: 733 734 void DockedWindowLayoutManager::OnDisplayWorkAreaInsetsChanged() { 735 Relayout(); 736 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); 737 MaybeMinimizeChildrenExcept(dragged_window_); 738 } 739 740 void DockedWindowLayoutManager::OnFullscreenStateChanged( 741 bool is_fullscreen, aura::Window* root_window) { 742 if (dock_container_->GetRootWindow() != root_window) 743 return; 744 // Entering fullscreen mode (including immersive) hides docked windows. 745 in_fullscreen_ = workspace_controller_->GetWindowState() == 746 WORKSPACE_WINDOW_STATE_FULL_SCREEN; 747 { 748 // prevent Relayout from getting called multiple times during this 749 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); 750 // Use a copy of children array because a call to MinimizeDockedWindow or 751 // RestoreDockedWindow can change order. 752 aura::Window::Windows children(dock_container_->children()); 753 for (aura::Window::Windows::const_iterator iter = children.begin(); 754 iter != children.end(); ++iter) { 755 aura::Window* window(*iter); 756 if (IsPopupOrTransient(window)) 757 continue; 758 wm::WindowState* window_state = wm::GetWindowState(window); 759 if (in_fullscreen_) { 760 if (window->IsVisible()) 761 MinimizeDockedWindow(window_state); 762 } else { 763 if (!window_state->IsMinimized()) 764 RestoreDockedWindow(window_state); 765 } 766 } 767 } 768 Relayout(); 769 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 770 } 771 772 void DockedWindowLayoutManager::OnShelfAlignmentChanged( 773 aura::Window* root_window) { 774 if (dock_container_->GetRootWindow() != root_window) 775 return; 776 777 if (!shelf_ || !shelf_->shelf_widget()) 778 return; 779 780 if (alignment_ == DOCKED_ALIGNMENT_NONE) 781 return; 782 783 // Do not allow shelf and dock on the same side. Switch side that 784 // the dock is attached to and move all dock windows to that new side. 785 ShelfAlignment shelf_alignment = shelf_->shelf_widget()->GetAlignment(); 786 if (alignment_ == DOCKED_ALIGNMENT_LEFT && 787 shelf_alignment == SHELF_ALIGNMENT_LEFT) { 788 alignment_ = DOCKED_ALIGNMENT_RIGHT; 789 } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT && 790 shelf_alignment == SHELF_ALIGNMENT_RIGHT) { 791 alignment_ = DOCKED_ALIGNMENT_LEFT; 792 } 793 Relayout(); 794 UpdateDockBounds(DockedWindowLayoutManagerObserver::SHELF_ALIGNMENT_CHANGED); 795 } 796 797 ///////////////////////////////////////////////////////////////////////////// 798 // DockedWindowLayoutManager, ShelfLayoutManagerObserver implementation: 799 void DockedWindowLayoutManager::OnBackgroundUpdated( 800 ShelfBackgroundType background_type, 801 BackgroundAnimatorChangeType change_type) { 802 background_widget_->SetBackgroundType(background_type, change_type); 803 } 804 805 ///////////////////////////////////////////////////////////////////////////// 806 // DockedWindowLayoutManager, WindowStateObserver implementation: 807 808 void DockedWindowLayoutManager::OnPreWindowStateTypeChange( 809 wm::WindowState* window_state, 810 wm::WindowStateType old_type) { 811 aura::Window* window = window_state->window(); 812 if (IsPopupOrTransient(window)) 813 return; 814 // The window property will still be set, but no actual change will occur 815 // until OnFullscreenStateChange is called when exiting fullscreen. 816 if (in_fullscreen_) 817 return; 818 if (window_state->IsMinimized()) { 819 MinimizeDockedWindow(window_state); 820 } else if (window_state->IsMaximizedOrFullscreen() || 821 window_state->IsSnapped()) { 822 if (window != dragged_window_) { 823 UndockWindow(window); 824 RecordUmaAction(DOCKED_ACTION_MAXIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN); 825 } 826 } else if (old_type == wm::WINDOW_STATE_TYPE_MINIMIZED) { 827 RestoreDockedWindow(window_state); 828 } 829 } 830 831 ///////////////////////////////////////////////////////////////////////////// 832 // DockedWindowLayoutManager, WindowObserver implementation: 833 834 void DockedWindowLayoutManager::OnWindowBoundsChanged( 835 aura::Window* window, 836 const gfx::Rect& old_bounds, 837 const gfx::Rect& new_bounds) { 838 // Only relayout if the dragged window would get docked. 839 if (window == dragged_window_ && is_dragged_window_docked_) 840 Relayout(); 841 } 842 843 void DockedWindowLayoutManager::OnWindowVisibilityChanging( 844 aura::Window* window, bool visible) { 845 if (IsPopupOrTransient(window)) 846 return; 847 int animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT; 848 if (visible) { 849 animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; 850 ::wm::SetWindowVisibilityAnimationDuration( 851 window, base::TimeDelta::FromMilliseconds(kFadeDurationMs)); 852 } else if (wm::GetWindowState(window)->IsMinimized()) { 853 animation_type = WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE; 854 } 855 ::wm::SetWindowVisibilityAnimationType(window, animation_type); 856 } 857 858 void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) { 859 if (dragged_window_ == window) { 860 FinishDragging(DOCKED_ACTION_NONE, DOCKED_ACTION_SOURCE_UNKNOWN); 861 DCHECK(!dragged_window_); 862 DCHECK(!is_dragged_window_docked_); 863 } 864 if (window == last_active_window_) 865 last_active_window_ = NULL; 866 RecordUmaAction(DOCKED_ACTION_CLOSE, DOCKED_ACTION_SOURCE_UNKNOWN); 867 } 868 869 870 //////////////////////////////////////////////////////////////////////////////// 871 // DockedWindowLayoutManager, aura::client::ActivationChangeObserver 872 // implementation: 873 874 void DockedWindowLayoutManager::OnWindowActivated(aura::Window* gained_active, 875 aura::Window* lost_active) { 876 if (gained_active && IsPopupOrTransient(gained_active)) 877 return; 878 // Ignore if the window that is not managed by this was activated. 879 aura::Window* ancestor = NULL; 880 for (aura::Window* parent = gained_active; 881 parent; parent = parent->parent()) { 882 if (parent->parent() == dock_container_) { 883 ancestor = parent; 884 break; 885 } 886 } 887 if (ancestor) 888 UpdateStacking(ancestor); 889 } 890 891 //////////////////////////////////////////////////////////////////////////////// 892 // DockedWindowLayoutManager private implementation: 893 894 void DockedWindowLayoutManager::MaybeMinimizeChildrenExcept( 895 aura::Window* child) { 896 // Minimize any windows that don't fit without overlap. 897 const gfx::Rect work_area = 898 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 899 int available_room = work_area.height(); 900 bool gap_needed = !!child; 901 if (child) 902 available_room -= GetWindowHeightCloseTo(child, 0); 903 // Use a copy of children array because a call to Minimize can change order. 904 aura::Window::Windows children(dock_container_->children()); 905 aura::Window::Windows::const_reverse_iterator iter = children.rbegin(); 906 while (iter != children.rend()) { 907 aura::Window* window(*iter++); 908 if (window == child || !IsUsedByLayout(window)) 909 continue; 910 int room_needed = GetWindowHeightCloseTo(window, 0) + 911 (gap_needed ? kMinDockGap : 0); 912 gap_needed = true; 913 if (available_room > room_needed) { 914 available_room -= room_needed; 915 } else { 916 // Slow down minimizing animations. Lock duration so that it is not 917 // overridden by other ScopedLayerAnimationSettings down the stack. 918 ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); 919 settings.SetTransitionDuration( 920 base::TimeDelta::FromMilliseconds(kMinimizeDurationMs)); 921 settings.LockTransitionDuration(); 922 wm::GetWindowState(window)->Minimize(); 923 } 924 } 925 } 926 927 void DockedWindowLayoutManager::MinimizeDockedWindow( 928 wm::WindowState* window_state) { 929 DCHECK(!IsPopupOrTransient(window_state->window())); 930 window_state->window()->Hide(); 931 if (window_state->IsActive()) 932 window_state->Deactivate(); 933 RecordUmaAction(DOCKED_ACTION_MINIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN); 934 } 935 936 void DockedWindowLayoutManager::RestoreDockedWindow( 937 wm::WindowState* window_state) { 938 aura::Window* window = window_state->window(); 939 DCHECK(!IsPopupOrTransient(window)); 940 // Always place restored window at the bottom shuffling the other windows up. 941 // TODO(varkha): add a separate container for docked windows to keep track 942 // of ordering. 943 gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow( 944 dock_container_); 945 const gfx::Rect work_area = display.work_area(); 946 947 // Evict the window if it can no longer be docked because of its height. 948 if (!CanDockWindow(window, DOCKED_ALIGNMENT_NONE)) { 949 UndockWindow(window); 950 RecordUmaAction(DOCKED_ACTION_EVICT, DOCKED_ACTION_SOURCE_UNKNOWN); 951 return; 952 } 953 gfx::Rect bounds(window->bounds()); 954 bounds.set_y(work_area.bottom()); 955 window->SetBounds(bounds); 956 window->Show(); 957 MaybeMinimizeChildrenExcept(window); 958 RecordUmaAction(DOCKED_ACTION_RESTORE, DOCKED_ACTION_SOURCE_UNKNOWN); 959 } 960 961 void DockedWindowLayoutManager::RecordUmaAction(DockedAction action, 962 DockedActionSource source) { 963 if (action == DOCKED_ACTION_NONE) 964 return; 965 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.Action", action, DOCKED_ACTION_COUNT); 966 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.ActionSource", source, 967 DOCKED_ACTION_SOURCE_COUNT); 968 base::Time time_now = base::Time::Now(); 969 base::TimeDelta time_between_use = time_now - last_action_time_; 970 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.Dock.TimeBetweenUse", 971 time_between_use.InSeconds(), 972 1, 973 base::TimeDelta::FromHours(10).InSeconds(), 974 100); 975 last_action_time_ = time_now; 976 int docked_all_count = 0; 977 int docked_visible_count = 0; 978 int docked_panels_count = 0; 979 int large_windows_count = 0; 980 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 981 const aura::Window* window(dock_container_->children()[i]); 982 if (IsPopupOrTransient(window)) 983 continue; 984 docked_all_count++; 985 if (!IsUsedByLayout(window)) 986 continue; 987 docked_visible_count++; 988 if (window->type() == ui::wm::WINDOW_TYPE_PANEL) 989 docked_panels_count++; 990 const wm::WindowState* window_state = wm::GetWindowState(window); 991 if (window_state->HasRestoreBounds()) { 992 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen(); 993 if (restore_bounds.width() > kMaxDockWidth) 994 large_windows_count++; 995 } 996 } 997 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsAll", docked_all_count); 998 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsLarge", large_windows_count); 999 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsPanels", docked_panels_count); 1000 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsVisible", docked_visible_count); 1001 } 1002 1003 void DockedWindowLayoutManager::UpdateDockedWidth(int width) { 1004 if (docked_width_ == width) 1005 return; 1006 docked_width_ = width; 1007 UMA_HISTOGRAM_COUNTS_10000("Ash.Dock.Width", docked_width_); 1008 } 1009 1010 void DockedWindowLayoutManager::OnDraggedWindowDocked(aura::Window* window) { 1011 DCHECK(!is_dragged_window_docked_); 1012 is_dragged_window_docked_ = true; 1013 } 1014 1015 void DockedWindowLayoutManager::OnDraggedWindowUndocked() { 1016 DCHECK (is_dragged_window_docked_); 1017 is_dragged_window_docked_ = false; 1018 } 1019 1020 bool DockedWindowLayoutManager::IsAnyWindowDocked() { 1021 return CalculateAlignment() != DOCKED_ALIGNMENT_NONE; 1022 } 1023 1024 DockedAlignment DockedWindowLayoutManager::GetEdgeNearestWindow( 1025 const aura::Window* window) const { 1026 const gfx::Rect& bounds(window->GetBoundsInScreen()); 1027 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); 1028 return (abs(bounds.x() - container_bounds.x()) < 1029 abs(bounds.right() - container_bounds.right())) ? 1030 DOCKED_ALIGNMENT_LEFT : DOCKED_ALIGNMENT_RIGHT; 1031 } 1032 1033 void DockedWindowLayoutManager::Relayout() { 1034 if (in_layout_) 1035 return; 1036 if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_) 1037 return; 1038 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); 1039 1040 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen(); 1041 aura::Window* active_window = NULL; 1042 std::vector<WindowWithHeight> visible_windows; 1043 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 1044 aura::Window* window(dock_container_->children()[i]); 1045 1046 if (!IsUsedByLayout(window) || window == dragged_window_) 1047 continue; 1048 1049 // If the shelf is currently hidden (full-screen mode), hide window until 1050 // full-screen mode is exited. 1051 if (in_fullscreen_) { 1052 // The call to Hide does not set the minimize property, so the window will 1053 // be restored when the shelf becomes visible again. 1054 window->Hide(); 1055 continue; 1056 } 1057 if (window->HasFocus() || 1058 window->Contains( 1059 aura::client::GetFocusClient(window)->GetFocusedWindow())) { 1060 DCHECK(!active_window); 1061 active_window = window; 1062 } 1063 visible_windows.push_back(WindowWithHeight(window)); 1064 } 1065 // Consider docked dragged_window_ when fanning out other child windows. 1066 if (is_dragged_window_docked_) { 1067 visible_windows.push_back(WindowWithHeight(dragged_window_)); 1068 DCHECK(!active_window); 1069 active_window = dragged_window_; 1070 } 1071 1072 // Position docked windows as well as the window being dragged. 1073 gfx::Rect work_area = 1074 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 1075 if (shelf_observer_) 1076 work_area.Subtract(shelf_observer_->shelf_bounds_in_screen()); 1077 int available_room = CalculateWindowHeightsAndRemainingRoom(work_area, 1078 &visible_windows); 1079 FanOutChildren(work_area, 1080 CalculateIdealWidth(visible_windows), 1081 available_room, 1082 &visible_windows); 1083 1084 // After the first Relayout allow the windows to change their order easier 1085 // since we know they are docked. 1086 is_dragged_from_dock_ = true; 1087 UpdateStacking(active_window); 1088 } 1089 1090 int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom( 1091 const gfx::Rect work_area, 1092 std::vector<WindowWithHeight>* visible_windows) { 1093 int available_room = work_area.height(); 1094 int remaining_windows = visible_windows->size(); 1095 int gap_height = remaining_windows > 1 ? kMinDockGap : 0; 1096 1097 // Sort windows by their minimum heights and calculate target heights. 1098 std::sort(visible_windows->begin(), visible_windows->end(), 1099 CompareMinimumHeight()); 1100 // Distribute the free space among the docked windows. Since the windows are 1101 // sorted (tall windows first) we can now assume that any window which 1102 // required more space than the current window will have already been 1103 // accounted for previously in this loop, so we can safely give that window 1104 // its proportional share of the remaining space. 1105 for (std::vector<WindowWithHeight>::reverse_iterator iter = 1106 visible_windows->rbegin(); 1107 iter != visible_windows->rend(); ++iter) { 1108 iter->height_ = GetWindowHeightCloseTo( 1109 iter->window(), 1110 (available_room + gap_height) / remaining_windows - gap_height); 1111 available_room -= (iter->height_ + gap_height); 1112 remaining_windows--; 1113 } 1114 return available_room + gap_height; 1115 } 1116 1117 int DockedWindowLayoutManager::CalculateIdealWidth( 1118 const std::vector<WindowWithHeight>& visible_windows) { 1119 int smallest_max_width = kMaxDockWidth; 1120 int largest_min_width = kMinDockWidth; 1121 // Ideal width of the docked area is as close to kIdealWidth as possible 1122 // while still respecting the minimum and maximum width restrictions on the 1123 // individual docked windows as well as the width that was possibly set by a 1124 // user (which needs to be preserved when dragging and rearranging windows). 1125 for (std::vector<WindowWithHeight>::const_iterator iter = 1126 visible_windows.begin(); 1127 iter != visible_windows.end(); ++iter) { 1128 const aura::Window* window = iter->window(); 1129 int min_window_width = window->bounds().width(); 1130 int max_window_width = min_window_width; 1131 if (!wm::GetWindowState(window)->bounds_changed_by_user()) { 1132 min_window_width = GetWindowWidthCloseTo(window, kMinDockWidth); 1133 max_window_width = GetWindowWidthCloseTo(window, kMaxDockWidth); 1134 } 1135 largest_min_width = std::max(largest_min_width, min_window_width); 1136 smallest_max_width = std::min(smallest_max_width, max_window_width); 1137 } 1138 int ideal_width = std::max(largest_min_width, 1139 std::min(smallest_max_width, kIdealWidth)); 1140 // Restrict docked area width regardless of window restrictions. 1141 ideal_width = std::max(std::min(ideal_width, kMaxDockWidth), kMinDockWidth); 1142 return ideal_width; 1143 } 1144 1145 void DockedWindowLayoutManager::FanOutChildren( 1146 const gfx::Rect& work_area, 1147 int ideal_docked_width, 1148 int available_room, 1149 std::vector<WindowWithHeight>* visible_windows) { 1150 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen(); 1151 1152 // Calculate initial vertical offset and the gap or overlap between windows. 1153 const int num_windows = visible_windows->size(); 1154 const float delta = static_cast<float>(available_room) / 1155 ((available_room > 0 || num_windows <= 1) ? 1156 num_windows + 1 : num_windows - 1); 1157 float y_pos = work_area.y() + ((delta > 0) ? delta : 0); 1158 1159 // Docked area is shown only if there is at least one non-dragged visible 1160 // docked window. 1161 int new_width = ideal_docked_width; 1162 if (visible_windows->empty() || 1163 (visible_windows->size() == 1 && 1164 (*visible_windows)[0].window() == dragged_window_)) { 1165 new_width = 0; 1166 } 1167 UpdateDockedWidth(new_width); 1168 // Sort windows by their center positions and fan out overlapping 1169 // windows. 1170 std::sort(visible_windows->begin(), visible_windows->end(), 1171 CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : NULL, 1172 dock_container_, 1173 delta)); 1174 for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin(); 1175 iter != visible_windows->end(); ++iter) { 1176 aura::Window* window = iter->window(); 1177 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen( 1178 dock_container_, window->GetTargetBounds()); 1179 // A window is extended or shrunk to be as close as possible to the ideal 1180 // docked area width. Windows that were resized by a user are kept at their 1181 // existing size. 1182 // This also enforces the min / max restrictions on the docked area width. 1183 bounds.set_width(GetWindowWidthCloseTo( 1184 window, 1185 wm::GetWindowState(window)->bounds_changed_by_user() ? 1186 bounds.width() : ideal_docked_width)); 1187 DCHECK_LE(bounds.width(), ideal_docked_width); 1188 1189 DockedAlignment alignment = alignment_; 1190 if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_) 1191 alignment = GetEdgeNearestWindow(window); 1192 1193 // Fan out windows evenly distributing the overlap or remaining free space. 1194 bounds.set_height(iter->height_); 1195 bounds.set_y(std::max(work_area.y(), 1196 std::min(work_area.bottom() - bounds.height(), 1197 static_cast<int>(y_pos + 0.5)))); 1198 y_pos += bounds.height() + delta + kMinDockGap; 1199 1200 // All docked windows other than the one currently dragged remain stuck 1201 // to the screen edge (flush with the edge or centered in the dock area). 1202 switch (alignment) { 1203 case DOCKED_ALIGNMENT_LEFT: 1204 bounds.set_x(dock_bounds.x() + 1205 (ideal_docked_width - bounds.width()) / 2); 1206 break; 1207 case DOCKED_ALIGNMENT_RIGHT: 1208 bounds.set_x(dock_bounds.right() - 1209 (ideal_docked_width + bounds.width()) / 2); 1210 break; 1211 case DOCKED_ALIGNMENT_NONE: 1212 break; 1213 } 1214 if (window == dragged_window_) { 1215 dragged_bounds_ = bounds; 1216 continue; 1217 } 1218 // If the following asserts it is probably because not all the children 1219 // have been removed when dock was closed. 1220 DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE); 1221 bounds = ScreenUtil::ConvertRectFromScreen(dock_container_, bounds); 1222 if (bounds != window->GetTargetBounds()) { 1223 ui::Layer* layer = window->layer(); 1224 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator()); 1225 slide_settings.SetPreemptionStrategy( 1226 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 1227 slide_settings.SetTransitionDuration( 1228 base::TimeDelta::FromMilliseconds(kSlideDurationMs)); 1229 SetChildBoundsDirect(window, bounds); 1230 } 1231 } 1232 } 1233 1234 void DockedWindowLayoutManager::UpdateDockBounds( 1235 DockedWindowLayoutManagerObserver::Reason reason) { 1236 int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0); 1237 const gfx::Rect work_area = 1238 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 1239 gfx::Rect bounds = gfx::Rect( 1240 alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 ? 1241 dock_container_->bounds().right() - dock_inset: 1242 dock_container_->bounds().x(), 1243 dock_container_->bounds().y(), 1244 dock_inset, 1245 work_area.height()); 1246 docked_bounds_ = bounds + 1247 dock_container_->GetBoundsInScreen().OffsetFromOrigin(); 1248 FOR_EACH_OBSERVER( 1249 DockedWindowLayoutManagerObserver, 1250 observer_list_, 1251 OnDockBoundsChanging(bounds, reason)); 1252 // Show or hide background for docked area. 1253 gfx::Rect background_bounds(docked_bounds_); 1254 if (shelf_observer_) 1255 background_bounds.Subtract(shelf_observer_->shelf_bounds_in_screen()); 1256 background_widget_->SetBackgroundBounds(background_bounds, alignment_); 1257 if (docked_width_ > 0) 1258 background_widget_->Show(); 1259 else 1260 background_widget_->Hide(); 1261 } 1262 1263 void DockedWindowLayoutManager::UpdateStacking(aura::Window* active_window) { 1264 if (!active_window) { 1265 if (!last_active_window_) 1266 return; 1267 active_window = last_active_window_; 1268 } 1269 1270 // Windows are stacked like a deck of cards: 1271 // ,------. 1272 // |,------.| 1273 // |,------.| 1274 // | active | 1275 // | window | 1276 // |`------'| 1277 // |`------'| 1278 // `------' 1279 // Use the middle of each window to figure out how to stack the window. 1280 // This allows us to update the stacking when a window is being dragged around 1281 // by the titlebar. 1282 std::map<int, aura::Window*> window_ordering; 1283 for (aura::Window::Windows::const_iterator it = 1284 dock_container_->children().begin(); 1285 it != dock_container_->children().end(); ++it) { 1286 if (!IsUsedByLayout(*it) || 1287 ((*it) == dragged_window_ && !is_dragged_window_docked_)) { 1288 continue; 1289 } 1290 gfx::Rect bounds = (*it)->bounds(); 1291 window_ordering.insert(std::make_pair(bounds.y() + bounds.height() / 2, 1292 *it)); 1293 } 1294 int active_center_y = active_window->bounds().CenterPoint().y(); 1295 1296 aura::Window* previous_window = NULL; 1297 for (std::map<int, aura::Window*>::const_iterator it = 1298 window_ordering.begin(); 1299 it != window_ordering.end() && it->first < active_center_y; ++it) { 1300 if (previous_window) 1301 dock_container_->StackChildAbove(it->second, previous_window); 1302 previous_window = it->second; 1303 } 1304 for (std::map<int, aura::Window*>::const_reverse_iterator it = 1305 window_ordering.rbegin(); 1306 it != window_ordering.rend() && it->first > active_center_y; ++it) { 1307 if (previous_window) 1308 dock_container_->StackChildAbove(it->second, previous_window); 1309 previous_window = it->second; 1310 } 1311 1312 if (previous_window && active_window->parent() == dock_container_) 1313 dock_container_->StackChildAbove(active_window, previous_window); 1314 if (active_window != dragged_window_) 1315 last_active_window_ = active_window; 1316 } 1317 1318 //////////////////////////////////////////////////////////////////////////////// 1319 // keyboard::KeyboardControllerObserver implementation: 1320 1321 void DockedWindowLayoutManager::OnKeyboardBoundsChanging( 1322 const gfx::Rect& keyboard_bounds) { 1323 // This bounds change will have caused a change to the Shelf which does not 1324 // propagate automatically to this class, so manually recalculate bounds. 1325 Relayout(); 1326 UpdateDockBounds(DockedWindowLayoutManagerObserver::KEYBOARD_BOUNDS_CHANGING); 1327 } 1328 1329 } // namespace ash 1330