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, WorkspaceController* workspace_controller) 406 : dock_container_(dock_container), 407 in_layout_(false), 408 dragged_window_(NULL), 409 is_dragged_window_docked_(false), 410 is_dragged_from_dock_(false), 411 shelf_(NULL), 412 workspace_controller_(workspace_controller), 413 in_fullscreen_(workspace_controller_->GetWindowState() == 414 WORKSPACE_WINDOW_STATE_FULL_SCREEN), 415 docked_width_(0), 416 alignment_(DOCKED_ALIGNMENT_NONE), 417 last_active_window_(NULL), 418 last_action_time_(base::Time::Now()), 419 background_widget_(new DockedBackgroundWidget(dock_container_)) { 420 DCHECK(dock_container); 421 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 422 AddObserver(this); 423 Shell::GetInstance()->AddShellObserver(this); 424 } 425 426 DockedWindowLayoutManager::~DockedWindowLayoutManager() { 427 Shutdown(); 428 } 429 430 void DockedWindowLayoutManager::Shutdown() { 431 if (shelf_ && shelf_->shelf_widget()) { 432 ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( 433 shelf_->shelf_widget()->GetNativeWindow()); 434 shelf_layout_manager->RemoveObserver(this); 435 shelf_observer_.reset(); 436 } 437 shelf_ = NULL; 438 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 439 aura::Window* child = dock_container_->children()[i]; 440 child->RemoveObserver(this); 441 wm::GetWindowState(child)->RemoveObserver(this); 442 } 443 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())-> 444 RemoveObserver(this); 445 Shell::GetInstance()->RemoveShellObserver(this); 446 } 447 448 void DockedWindowLayoutManager::AddObserver( 449 DockedWindowLayoutManagerObserver* observer) { 450 observer_list_.AddObserver(observer); 451 } 452 453 void DockedWindowLayoutManager::RemoveObserver( 454 DockedWindowLayoutManagerObserver* observer) { 455 observer_list_.RemoveObserver(observer); 456 } 457 458 void DockedWindowLayoutManager::StartDragging(aura::Window* window) { 459 DCHECK(!dragged_window_); 460 dragged_window_ = window; 461 DCHECK(!IsPopupOrTransient(window)); 462 // Start observing a window unless it is docked container's child in which 463 // case it is already observed. 464 wm::WindowState* dragged_state = wm::GetWindowState(dragged_window_); 465 if (dragged_window_->parent() != dock_container_) { 466 dragged_window_->AddObserver(this); 467 dragged_state->AddObserver(this); 468 } else if (!IsAnyWindowDocked() && 469 dragged_state->drag_details() && 470 !(dragged_state->drag_details()->bounds_change & 471 WindowResizer::kBoundsChange_Resizes)) { 472 // If there are no other docked windows clear alignment when a docked window 473 // is moved (but not when it is resized or the window could get undocked 474 // when resized away from the edge while docked). 475 alignment_ = DOCKED_ALIGNMENT_NONE; 476 } 477 is_dragged_from_dock_ = window->parent() == dock_container_; 478 DCHECK(!is_dragged_window_docked_); 479 480 // Resize all windows that are flush with the dock edge together if one of 481 // them gets resized. 482 if (dragged_window_->bounds().width() == docked_width_ && 483 (dragged_state->drag_details()->bounds_change & 484 WindowResizer::kBoundsChange_Resizes) && 485 (dragged_state->drag_details()->size_change_direction & 486 WindowResizer::kBoundsChangeDirection_Horizontal)) { 487 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 488 aura::Window* window1(dock_container_->children()[i]); 489 if (IsUsedByLayout(window1) && 490 window1 != dragged_window_ && 491 window1->bounds().width() == docked_width_) { 492 wm::GetWindowState(window1)->set_bounds_changed_by_user(false); 493 } 494 } 495 } 496 } 497 498 void DockedWindowLayoutManager::DockDraggedWindow(aura::Window* window) { 499 DCHECK(!IsPopupOrTransient(window)); 500 OnDraggedWindowDocked(window); 501 Relayout(); 502 } 503 504 void DockedWindowLayoutManager::UndockDraggedWindow() { 505 DCHECK(!IsPopupOrTransient(dragged_window_)); 506 OnDraggedWindowUndocked(); 507 Relayout(); 508 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 509 is_dragged_from_dock_ = false; 510 } 511 512 void DockedWindowLayoutManager::FinishDragging(DockedAction action, 513 DockedActionSource source) { 514 DCHECK(dragged_window_); 515 DCHECK(!IsPopupOrTransient(dragged_window_)); 516 if (is_dragged_window_docked_) 517 OnDraggedWindowUndocked(); 518 DCHECK (!is_dragged_window_docked_); 519 // Stop observing a window unless it is docked container's child in which 520 // case it needs to keep being observed after the drag completes. 521 if (dragged_window_->parent() != dock_container_) { 522 dragged_window_->RemoveObserver(this); 523 wm::GetWindowState(dragged_window_)->RemoveObserver(this); 524 if (last_active_window_ == dragged_window_) 525 last_active_window_ = NULL; 526 } else { 527 // If this is the first window that got docked by a move update alignment. 528 if (alignment_ == DOCKED_ALIGNMENT_NONE) 529 alignment_ = GetEdgeNearestWindow(dragged_window_); 530 // A window is no longer dragged and is a child. 531 // When a window becomes a child at drag start this is 532 // the only opportunity we will have to enforce a window 533 // count limit so do it here. 534 MaybeMinimizeChildrenExcept(dragged_window_); 535 } 536 dragged_window_ = NULL; 537 dragged_bounds_ = gfx::Rect(); 538 Relayout(); 539 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 540 RecordUmaAction(action, source); 541 } 542 543 void DockedWindowLayoutManager::SetShelf(Shelf* shelf) { 544 DCHECK(!shelf_); 545 shelf_ = shelf; 546 if (shelf_->shelf_widget()) { 547 ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( 548 shelf_->shelf_widget()->GetNativeWindow()); 549 shelf_layout_manager->AddObserver(this); 550 shelf_observer_.reset(new ShelfWindowObserver(this)); 551 } 552 } 553 554 DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow( 555 const aura::Window* window) const { 556 const gfx::Rect& bounds(window->GetBoundsInScreen()); 557 558 // Test overlap with an existing docked area first. 559 if (docked_bounds_.Intersects(bounds) && 560 alignment_ != DOCKED_ALIGNMENT_NONE) { 561 // A window is being added to other docked windows (on the same side). 562 return alignment_; 563 } 564 565 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); 566 if (bounds.x() <= container_bounds.x() && 567 bounds.right() > container_bounds.x()) { 568 return DOCKED_ALIGNMENT_LEFT; 569 } else if (bounds.x() < container_bounds.right() && 570 bounds.right() >= container_bounds.right()) { 571 return DOCKED_ALIGNMENT_RIGHT; 572 } 573 return DOCKED_ALIGNMENT_NONE; 574 } 575 576 DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const { 577 // Find a child that is not being dragged and is not a popup. 578 // If such exists the current alignment is returned - even if some of the 579 // children are hidden or minimized (so they can be restored without losing 580 // the docked state). 581 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 582 aura::Window* window(dock_container_->children()[i]); 583 if (window != dragged_window_ && !IsPopupOrTransient(window)) 584 return alignment_; 585 } 586 // No docked windows remain other than possibly the window being dragged. 587 // Return |NONE| to indicate that windows may get docked on either side. 588 return DOCKED_ALIGNMENT_NONE; 589 } 590 591 bool DockedWindowLayoutManager::CanDockWindow( 592 aura::Window* window, 593 DockedAlignment desired_alignment) { 594 // Don't allow interactive docking of windows with transient parents such as 595 // modal browser dialogs. Prevent docking of panels attached to shelf during 596 // the drag. 597 wm::WindowState* window_state = wm::GetWindowState(window); 598 bool should_attach_to_shelf = window_state->drag_details() && 599 window_state->drag_details()->should_attach_to_shelf; 600 if (IsPopupOrTransient(window) || should_attach_to_shelf) 601 return false; 602 // If a window is wide and cannot be resized down to maximum width allowed 603 // then it cannot be docked. 604 // TODO(varkha). Prevent windows from changing size programmatically while 605 // they are docked. The size will take effect only once a window is undocked. 606 // See http://crbug.com/307792. 607 if (window->bounds().width() > kMaxDockWidth && 608 (!window_state->CanResize() || 609 (window->delegate() && 610 window->delegate()->GetMinimumSize().width() != 0 && 611 window->delegate()->GetMinimumSize().width() > kMaxDockWidth))) { 612 return false; 613 } 614 // If a window is tall and cannot be resized down to maximum height allowed 615 // then it cannot be docked. 616 const gfx::Rect work_area = 617 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 618 if (GetWindowHeightCloseTo(window, work_area.height()) > work_area.height()) 619 return false; 620 // Cannot dock on the other size from an existing dock. 621 const DockedAlignment alignment = CalculateAlignment(); 622 if (desired_alignment != DOCKED_ALIGNMENT_NONE && 623 alignment != DOCKED_ALIGNMENT_NONE && 624 alignment != desired_alignment) { 625 return false; 626 } 627 // Do not allow docking on the same side as shelf. 628 ShelfAlignment shelf_alignment = SHELF_ALIGNMENT_BOTTOM; 629 if (shelf_) 630 shelf_alignment = shelf_->alignment(); 631 if ((desired_alignment == DOCKED_ALIGNMENT_LEFT && 632 shelf_alignment == SHELF_ALIGNMENT_LEFT) || 633 (desired_alignment == DOCKED_ALIGNMENT_RIGHT && 634 shelf_alignment == SHELF_ALIGNMENT_RIGHT)) { 635 return false; 636 } 637 return true; 638 } 639 640 void DockedWindowLayoutManager::OnShelfBoundsChanged() { 641 Relayout(); 642 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); 643 } 644 645 //////////////////////////////////////////////////////////////////////////////// 646 // DockedWindowLayoutManager, aura::LayoutManager implementation: 647 void DockedWindowLayoutManager::OnWindowResized() { 648 MaybeMinimizeChildrenExcept(dragged_window_); 649 Relayout(); 650 // When screen resizes update the insets even when dock width or alignment 651 // does not change. 652 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED); 653 } 654 655 void DockedWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) { 656 if (IsPopupOrTransient(child)) 657 return; 658 // Dragged windows are already observed by StartDragging and do not change 659 // docked alignment during the drag. 660 if (child == dragged_window_) 661 return; 662 // If this is the first window getting docked - update alignment. 663 // A window can be added without proper bounds when window is moved to another 664 // display via API or due to display configuration change, so the alignment 665 // is set based on which edge is closer in the new display. 666 if (alignment_ == DOCKED_ALIGNMENT_NONE) 667 alignment_ = GetEdgeNearestWindow(child); 668 MaybeMinimizeChildrenExcept(child); 669 child->AddObserver(this); 670 wm::GetWindowState(child)->AddObserver(this); 671 Relayout(); 672 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 673 } 674 675 void DockedWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) { 676 if (IsPopupOrTransient(child)) 677 return; 678 // Dragged windows are stopped being observed by FinishDragging and do not 679 // change alignment during the drag. They also cannot be set to be the 680 // |last_active_window_|. 681 if (child == dragged_window_) 682 return; 683 // If this is the last window, set alignment and maximize the workspace. 684 if (!IsAnyWindowDocked()) { 685 alignment_ = DOCKED_ALIGNMENT_NONE; 686 UpdateDockedWidth(0); 687 } 688 if (last_active_window_ == child) 689 last_active_window_ = NULL; 690 child->RemoveObserver(this); 691 wm::GetWindowState(child)->RemoveObserver(this); 692 Relayout(); 693 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 694 } 695 696 void DockedWindowLayoutManager::OnChildWindowVisibilityChanged( 697 aura::Window* child, 698 bool visible) { 699 if (IsPopupOrTransient(child)) 700 return; 701 if (visible) 702 wm::GetWindowState(child)->Restore(); 703 Relayout(); 704 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 705 } 706 707 void DockedWindowLayoutManager::SetChildBounds( 708 aura::Window* child, 709 const gfx::Rect& requested_bounds) { 710 // The minimum constraints have to be applied first by the layout manager. 711 gfx::Rect actual_new_bounds(requested_bounds); 712 if (child->delegate()) { 713 const gfx::Size& min_size = child->delegate()->GetMinimumSize(); 714 actual_new_bounds.set_width( 715 std::max(min_size.width(), actual_new_bounds.width())); 716 actual_new_bounds.set_height( 717 std::max(min_size.height(), actual_new_bounds.height())); 718 } 719 // Whenever one of our windows is moved or resized enforce layout. 720 SetChildBoundsDirect(child, actual_new_bounds); 721 if (IsPopupOrTransient(child)) 722 return; 723 ShelfLayoutManager* shelf_layout = 724 ShelfLayoutManager::ForShelf(dock_container_); 725 if (shelf_layout) 726 shelf_layout->UpdateVisibilityState(); 727 } 728 729 //////////////////////////////////////////////////////////////////////////////// 730 // DockedWindowLayoutManager, ash::ShellObserver implementation: 731 732 void DockedWindowLayoutManager::OnDisplayWorkAreaInsetsChanged() { 733 Relayout(); 734 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); 735 MaybeMinimizeChildrenExcept(dragged_window_); 736 } 737 738 void DockedWindowLayoutManager::OnFullscreenStateChanged( 739 bool is_fullscreen, aura::Window* root_window) { 740 if (dock_container_->GetRootWindow() != root_window) 741 return; 742 // Entering fullscreen mode (including immersive) hides docked windows. 743 in_fullscreen_ = workspace_controller_->GetWindowState() == 744 WORKSPACE_WINDOW_STATE_FULL_SCREEN; 745 { 746 // prevent Relayout from getting called multiple times during this 747 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); 748 // Use a copy of children array because a call to MinimizeDockedWindow or 749 // RestoreDockedWindow can change order. 750 aura::Window::Windows children(dock_container_->children()); 751 for (aura::Window::Windows::const_iterator iter = children.begin(); 752 iter != children.end(); ++iter) { 753 aura::Window* window(*iter); 754 if (IsPopupOrTransient(window)) 755 continue; 756 wm::WindowState* window_state = wm::GetWindowState(window); 757 if (in_fullscreen_) { 758 if (window->IsVisible()) 759 MinimizeDockedWindow(window_state); 760 } else { 761 if (!window_state->IsMinimized()) 762 RestoreDockedWindow(window_state); 763 } 764 } 765 } 766 Relayout(); 767 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); 768 } 769 770 void DockedWindowLayoutManager::OnShelfAlignmentChanged( 771 aura::Window* root_window) { 772 if (dock_container_->GetRootWindow() != root_window) 773 return; 774 775 if (!shelf_ || !shelf_->shelf_widget()) 776 return; 777 778 if (alignment_ == DOCKED_ALIGNMENT_NONE) 779 return; 780 781 // Do not allow shelf and dock on the same side. Switch side that 782 // the dock is attached to and move all dock windows to that new side. 783 ShelfAlignment shelf_alignment = shelf_->shelf_widget()->GetAlignment(); 784 if (alignment_ == DOCKED_ALIGNMENT_LEFT && 785 shelf_alignment == SHELF_ALIGNMENT_LEFT) { 786 alignment_ = DOCKED_ALIGNMENT_RIGHT; 787 } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT && 788 shelf_alignment == SHELF_ALIGNMENT_RIGHT) { 789 alignment_ = DOCKED_ALIGNMENT_LEFT; 790 } 791 Relayout(); 792 UpdateDockBounds(DockedWindowLayoutManagerObserver::SHELF_ALIGNMENT_CHANGED); 793 } 794 795 ///////////////////////////////////////////////////////////////////////////// 796 // DockedWindowLayoutManager, ShelfLayoutManagerObserver implementation: 797 void DockedWindowLayoutManager::OnBackgroundUpdated( 798 ShelfBackgroundType background_type, 799 BackgroundAnimatorChangeType change_type) { 800 background_widget_->SetBackgroundType(background_type, change_type); 801 } 802 803 ///////////////////////////////////////////////////////////////////////////// 804 // DockedWindowLayoutManager, WindowStateObserver implementation: 805 806 void DockedWindowLayoutManager::OnPreWindowStateTypeChange( 807 wm::WindowState* window_state, 808 wm::WindowStateType old_type) { 809 aura::Window* window = window_state->window(); 810 if (IsPopupOrTransient(window)) 811 return; 812 // The window property will still be set, but no actual change will occur 813 // until OnFullscreenStateChange is called when exiting fullscreen. 814 if (in_fullscreen_) 815 return; 816 if (window_state->IsMinimized()) { 817 MinimizeDockedWindow(window_state); 818 } else if (window_state->IsMaximizedOrFullscreen() || 819 window_state->IsSnapped()) { 820 if (window != dragged_window_) { 821 UndockWindow(window); 822 RecordUmaAction(DOCKED_ACTION_MAXIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN); 823 } 824 } else if (old_type == wm::WINDOW_STATE_TYPE_MINIMIZED) { 825 RestoreDockedWindow(window_state); 826 } 827 } 828 829 ///////////////////////////////////////////////////////////////////////////// 830 // DockedWindowLayoutManager, WindowObserver implementation: 831 832 void DockedWindowLayoutManager::OnWindowBoundsChanged( 833 aura::Window* window, 834 const gfx::Rect& old_bounds, 835 const gfx::Rect& new_bounds) { 836 // Only relayout if the dragged window would get docked. 837 if (window == dragged_window_ && is_dragged_window_docked_) 838 Relayout(); 839 } 840 841 void DockedWindowLayoutManager::OnWindowVisibilityChanging( 842 aura::Window* window, bool visible) { 843 if (IsPopupOrTransient(window)) 844 return; 845 int animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT; 846 if (visible) { 847 animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; 848 ::wm::SetWindowVisibilityAnimationDuration( 849 window, base::TimeDelta::FromMilliseconds(kFadeDurationMs)); 850 } else if (wm::GetWindowState(window)->IsMinimized()) { 851 animation_type = WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE; 852 } 853 ::wm::SetWindowVisibilityAnimationType(window, animation_type); 854 } 855 856 void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) { 857 if (dragged_window_ == window) { 858 FinishDragging(DOCKED_ACTION_NONE, DOCKED_ACTION_SOURCE_UNKNOWN); 859 DCHECK(!dragged_window_); 860 DCHECK(!is_dragged_window_docked_); 861 } 862 if (window == last_active_window_) 863 last_active_window_ = NULL; 864 RecordUmaAction(DOCKED_ACTION_CLOSE, DOCKED_ACTION_SOURCE_UNKNOWN); 865 } 866 867 868 //////////////////////////////////////////////////////////////////////////////// 869 // DockedWindowLayoutManager, aura::client::ActivationChangeObserver 870 // implementation: 871 872 void DockedWindowLayoutManager::OnWindowActivated(aura::Window* gained_active, 873 aura::Window* lost_active) { 874 if (gained_active && IsPopupOrTransient(gained_active)) 875 return; 876 // Ignore if the window that is not managed by this was activated. 877 aura::Window* ancestor = NULL; 878 for (aura::Window* parent = gained_active; 879 parent; parent = parent->parent()) { 880 if (parent->parent() == dock_container_) { 881 ancestor = parent; 882 break; 883 } 884 } 885 if (ancestor) 886 UpdateStacking(ancestor); 887 } 888 889 //////////////////////////////////////////////////////////////////////////////// 890 // DockedWindowLayoutManager private implementation: 891 892 void DockedWindowLayoutManager::MaybeMinimizeChildrenExcept( 893 aura::Window* child) { 894 // Minimize any windows that don't fit without overlap. 895 const gfx::Rect work_area = 896 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 897 int available_room = work_area.height(); 898 bool gap_needed = !!child; 899 if (child) 900 available_room -= GetWindowHeightCloseTo(child, 0); 901 // Use a copy of children array because a call to Minimize can change order. 902 aura::Window::Windows children(dock_container_->children()); 903 aura::Window::Windows::const_reverse_iterator iter = children.rbegin(); 904 while (iter != children.rend()) { 905 aura::Window* window(*iter++); 906 if (window == child || !IsUsedByLayout(window)) 907 continue; 908 int room_needed = GetWindowHeightCloseTo(window, 0) + 909 (gap_needed ? kMinDockGap : 0); 910 gap_needed = true; 911 if (available_room > room_needed) { 912 available_room -= room_needed; 913 } else { 914 // Slow down minimizing animations. Lock duration so that it is not 915 // overridden by other ScopedLayerAnimationSettings down the stack. 916 ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); 917 settings.SetTransitionDuration( 918 base::TimeDelta::FromMilliseconds(kMinimizeDurationMs)); 919 settings.LockTransitionDuration(); 920 wm::GetWindowState(window)->Minimize(); 921 } 922 } 923 } 924 925 void DockedWindowLayoutManager::MinimizeDockedWindow( 926 wm::WindowState* window_state) { 927 DCHECK(!IsPopupOrTransient(window_state->window())); 928 window_state->window()->Hide(); 929 if (window_state->IsActive()) 930 window_state->Deactivate(); 931 RecordUmaAction(DOCKED_ACTION_MINIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN); 932 } 933 934 void DockedWindowLayoutManager::RestoreDockedWindow( 935 wm::WindowState* window_state) { 936 aura::Window* window = window_state->window(); 937 DCHECK(!IsPopupOrTransient(window)); 938 // Always place restored window at the bottom shuffling the other windows up. 939 // TODO(varkha): add a separate container for docked windows to keep track 940 // of ordering. 941 gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow( 942 dock_container_); 943 const gfx::Rect work_area = display.work_area(); 944 945 // Evict the window if it can no longer be docked because of its height. 946 if (!CanDockWindow(window, DOCKED_ALIGNMENT_NONE)) { 947 UndockWindow(window); 948 RecordUmaAction(DOCKED_ACTION_EVICT, DOCKED_ACTION_SOURCE_UNKNOWN); 949 return; 950 } 951 gfx::Rect bounds(window->bounds()); 952 bounds.set_y(work_area.bottom()); 953 window->SetBounds(bounds); 954 window->Show(); 955 MaybeMinimizeChildrenExcept(window); 956 RecordUmaAction(DOCKED_ACTION_RESTORE, DOCKED_ACTION_SOURCE_UNKNOWN); 957 } 958 959 void DockedWindowLayoutManager::RecordUmaAction(DockedAction action, 960 DockedActionSource source) { 961 if (action == DOCKED_ACTION_NONE) 962 return; 963 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.Action", action, DOCKED_ACTION_COUNT); 964 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.ActionSource", source, 965 DOCKED_ACTION_SOURCE_COUNT); 966 base::Time time_now = base::Time::Now(); 967 base::TimeDelta time_between_use = time_now - last_action_time_; 968 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.Dock.TimeBetweenUse", 969 time_between_use.InSeconds(), 970 1, 971 base::TimeDelta::FromHours(10).InSeconds(), 972 100); 973 last_action_time_ = time_now; 974 int docked_all_count = 0; 975 int docked_visible_count = 0; 976 int docked_panels_count = 0; 977 int large_windows_count = 0; 978 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 979 const aura::Window* window(dock_container_->children()[i]); 980 if (IsPopupOrTransient(window)) 981 continue; 982 docked_all_count++; 983 if (!IsUsedByLayout(window)) 984 continue; 985 docked_visible_count++; 986 if (window->type() == ui::wm::WINDOW_TYPE_PANEL) 987 docked_panels_count++; 988 const wm::WindowState* window_state = wm::GetWindowState(window); 989 if (window_state->HasRestoreBounds()) { 990 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen(); 991 if (restore_bounds.width() > kMaxDockWidth) 992 large_windows_count++; 993 } 994 } 995 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsAll", docked_all_count); 996 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsLarge", large_windows_count); 997 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsPanels", docked_panels_count); 998 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsVisible", docked_visible_count); 999 } 1000 1001 void DockedWindowLayoutManager::UpdateDockedWidth(int width) { 1002 if (docked_width_ == width) 1003 return; 1004 docked_width_ = width; 1005 UMA_HISTOGRAM_COUNTS_10000("Ash.Dock.Width", docked_width_); 1006 } 1007 1008 void DockedWindowLayoutManager::OnDraggedWindowDocked(aura::Window* window) { 1009 DCHECK(!is_dragged_window_docked_); 1010 is_dragged_window_docked_ = true; 1011 } 1012 1013 void DockedWindowLayoutManager::OnDraggedWindowUndocked() { 1014 DCHECK (is_dragged_window_docked_); 1015 is_dragged_window_docked_ = false; 1016 } 1017 1018 bool DockedWindowLayoutManager::IsAnyWindowDocked() { 1019 return CalculateAlignment() != DOCKED_ALIGNMENT_NONE; 1020 } 1021 1022 DockedAlignment DockedWindowLayoutManager::GetEdgeNearestWindow( 1023 const aura::Window* window) const { 1024 const gfx::Rect& bounds(window->GetBoundsInScreen()); 1025 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); 1026 return (abs(bounds.x() - container_bounds.x()) < 1027 abs(bounds.right() - container_bounds.right())) ? 1028 DOCKED_ALIGNMENT_LEFT : DOCKED_ALIGNMENT_RIGHT; 1029 } 1030 1031 void DockedWindowLayoutManager::Relayout() { 1032 if (in_layout_) 1033 return; 1034 if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_) 1035 return; 1036 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); 1037 1038 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen(); 1039 aura::Window* active_window = NULL; 1040 std::vector<WindowWithHeight> visible_windows; 1041 for (size_t i = 0; i < dock_container_->children().size(); ++i) { 1042 aura::Window* window(dock_container_->children()[i]); 1043 1044 if (!IsUsedByLayout(window) || window == dragged_window_) 1045 continue; 1046 1047 // If the shelf is currently hidden (full-screen mode), hide window until 1048 // full-screen mode is exited. 1049 if (in_fullscreen_) { 1050 // The call to Hide does not set the minimize property, so the window will 1051 // be restored when the shelf becomes visible again. 1052 window->Hide(); 1053 continue; 1054 } 1055 if (window->HasFocus() || 1056 window->Contains( 1057 aura::client::GetFocusClient(window)->GetFocusedWindow())) { 1058 DCHECK(!active_window); 1059 active_window = window; 1060 } 1061 visible_windows.push_back(WindowWithHeight(window)); 1062 } 1063 // Consider docked dragged_window_ when fanning out other child windows. 1064 if (is_dragged_window_docked_) { 1065 visible_windows.push_back(WindowWithHeight(dragged_window_)); 1066 DCHECK(!active_window); 1067 active_window = dragged_window_; 1068 } 1069 1070 // Position docked windows as well as the window being dragged. 1071 gfx::Rect work_area = 1072 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 1073 if (shelf_observer_) 1074 work_area.Subtract(shelf_observer_->shelf_bounds_in_screen()); 1075 int available_room = CalculateWindowHeightsAndRemainingRoom(work_area, 1076 &visible_windows); 1077 FanOutChildren(work_area, 1078 CalculateIdealWidth(visible_windows), 1079 available_room, 1080 &visible_windows); 1081 1082 // After the first Relayout allow the windows to change their order easier 1083 // since we know they are docked. 1084 is_dragged_from_dock_ = true; 1085 UpdateStacking(active_window); 1086 } 1087 1088 int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom( 1089 const gfx::Rect work_area, 1090 std::vector<WindowWithHeight>* visible_windows) { 1091 int available_room = work_area.height(); 1092 int remaining_windows = visible_windows->size(); 1093 int gap_height = remaining_windows > 1 ? kMinDockGap : 0; 1094 1095 // Sort windows by their minimum heights and calculate target heights. 1096 std::sort(visible_windows->begin(), visible_windows->end(), 1097 CompareMinimumHeight()); 1098 // Distribute the free space among the docked windows. Since the windows are 1099 // sorted (tall windows first) we can now assume that any window which 1100 // required more space than the current window will have already been 1101 // accounted for previously in this loop, so we can safely give that window 1102 // its proportional share of the remaining space. 1103 for (std::vector<WindowWithHeight>::reverse_iterator iter = 1104 visible_windows->rbegin(); 1105 iter != visible_windows->rend(); ++iter) { 1106 iter->height_ = GetWindowHeightCloseTo( 1107 iter->window(), 1108 (available_room + gap_height) / remaining_windows - gap_height); 1109 available_room -= (iter->height_ + gap_height); 1110 remaining_windows--; 1111 } 1112 return available_room + gap_height; 1113 } 1114 1115 int DockedWindowLayoutManager::CalculateIdealWidth( 1116 const std::vector<WindowWithHeight>& visible_windows) { 1117 int smallest_max_width = kMaxDockWidth; 1118 int largest_min_width = kMinDockWidth; 1119 // Ideal width of the docked area is as close to kIdealWidth as possible 1120 // while still respecting the minimum and maximum width restrictions on the 1121 // individual docked windows as well as the width that was possibly set by a 1122 // user (which needs to be preserved when dragging and rearranging windows). 1123 for (std::vector<WindowWithHeight>::const_iterator iter = 1124 visible_windows.begin(); 1125 iter != visible_windows.end(); ++iter) { 1126 const aura::Window* window = iter->window(); 1127 int min_window_width = window->bounds().width(); 1128 int max_window_width = min_window_width; 1129 if (!wm::GetWindowState(window)->bounds_changed_by_user()) { 1130 min_window_width = GetWindowWidthCloseTo(window, kMinDockWidth); 1131 max_window_width = GetWindowWidthCloseTo(window, kMaxDockWidth); 1132 } 1133 largest_min_width = std::max(largest_min_width, min_window_width); 1134 smallest_max_width = std::min(smallest_max_width, max_window_width); 1135 } 1136 int ideal_width = std::max(largest_min_width, 1137 std::min(smallest_max_width, kIdealWidth)); 1138 // Restrict docked area width regardless of window restrictions. 1139 ideal_width = std::max(std::min(ideal_width, kMaxDockWidth), kMinDockWidth); 1140 return ideal_width; 1141 } 1142 1143 void DockedWindowLayoutManager::FanOutChildren( 1144 const gfx::Rect& work_area, 1145 int ideal_docked_width, 1146 int available_room, 1147 std::vector<WindowWithHeight>* visible_windows) { 1148 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen(); 1149 1150 // Calculate initial vertical offset and the gap or overlap between windows. 1151 const int num_windows = visible_windows->size(); 1152 const float delta = static_cast<float>(available_room) / 1153 ((available_room > 0 || num_windows <= 1) ? 1154 num_windows + 1 : num_windows - 1); 1155 float y_pos = work_area.y() + ((delta > 0) ? delta : 0); 1156 1157 // Docked area is shown only if there is at least one non-dragged visible 1158 // docked window. 1159 int new_width = ideal_docked_width; 1160 if (visible_windows->empty() || 1161 (visible_windows->size() == 1 && 1162 (*visible_windows)[0].window() == dragged_window_)) { 1163 new_width = 0; 1164 } 1165 UpdateDockedWidth(new_width); 1166 // Sort windows by their center positions and fan out overlapping 1167 // windows. 1168 std::sort(visible_windows->begin(), visible_windows->end(), 1169 CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : NULL, 1170 dock_container_, 1171 delta)); 1172 for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin(); 1173 iter != visible_windows->end(); ++iter) { 1174 aura::Window* window = iter->window(); 1175 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen( 1176 dock_container_, window->GetTargetBounds()); 1177 // A window is extended or shrunk to be as close as possible to the ideal 1178 // docked area width. Windows that were resized by a user are kept at their 1179 // existing size. 1180 // This also enforces the min / max restrictions on the docked area width. 1181 bounds.set_width(GetWindowWidthCloseTo( 1182 window, 1183 wm::GetWindowState(window)->bounds_changed_by_user() ? 1184 bounds.width() : ideal_docked_width)); 1185 DCHECK_LE(bounds.width(), ideal_docked_width); 1186 1187 DockedAlignment alignment = alignment_; 1188 if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_) 1189 alignment = GetEdgeNearestWindow(window); 1190 1191 // Fan out windows evenly distributing the overlap or remaining free space. 1192 bounds.set_height(iter->height_); 1193 bounds.set_y(std::max(work_area.y(), 1194 std::min(work_area.bottom() - bounds.height(), 1195 static_cast<int>(y_pos + 0.5)))); 1196 y_pos += bounds.height() + delta + kMinDockGap; 1197 1198 // All docked windows other than the one currently dragged remain stuck 1199 // to the screen edge (flush with the edge or centered in the dock area). 1200 switch (alignment) { 1201 case DOCKED_ALIGNMENT_LEFT: 1202 bounds.set_x(dock_bounds.x() + 1203 (ideal_docked_width - bounds.width()) / 2); 1204 break; 1205 case DOCKED_ALIGNMENT_RIGHT: 1206 bounds.set_x(dock_bounds.right() - 1207 (ideal_docked_width + bounds.width()) / 2); 1208 break; 1209 case DOCKED_ALIGNMENT_NONE: 1210 break; 1211 } 1212 if (window == dragged_window_) { 1213 dragged_bounds_ = bounds; 1214 continue; 1215 } 1216 // If the following asserts it is probably because not all the children 1217 // have been removed when dock was closed. 1218 DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE); 1219 bounds = ScreenUtil::ConvertRectFromScreen(dock_container_, bounds); 1220 if (bounds != window->GetTargetBounds()) { 1221 ui::Layer* layer = window->layer(); 1222 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator()); 1223 slide_settings.SetPreemptionStrategy( 1224 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 1225 slide_settings.SetTransitionDuration( 1226 base::TimeDelta::FromMilliseconds(kSlideDurationMs)); 1227 SetChildBoundsDirect(window, bounds); 1228 } 1229 } 1230 } 1231 1232 void DockedWindowLayoutManager::UpdateDockBounds( 1233 DockedWindowLayoutManagerObserver::Reason reason) { 1234 int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0); 1235 const gfx::Rect work_area = 1236 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area(); 1237 gfx::Rect bounds = gfx::Rect( 1238 alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 ? 1239 dock_container_->bounds().right() - dock_inset: 1240 dock_container_->bounds().x(), 1241 dock_container_->bounds().y(), 1242 dock_inset, 1243 work_area.height()); 1244 docked_bounds_ = bounds + 1245 dock_container_->GetBoundsInScreen().OffsetFromOrigin(); 1246 FOR_EACH_OBSERVER( 1247 DockedWindowLayoutManagerObserver, 1248 observer_list_, 1249 OnDockBoundsChanging(bounds, reason)); 1250 // Show or hide background for docked area. 1251 gfx::Rect background_bounds(docked_bounds_); 1252 if (shelf_observer_) 1253 background_bounds.Subtract(shelf_observer_->shelf_bounds_in_screen()); 1254 background_widget_->SetBackgroundBounds(background_bounds, alignment_); 1255 if (docked_width_ > 0) 1256 background_widget_->Show(); 1257 else 1258 background_widget_->Hide(); 1259 } 1260 1261 void DockedWindowLayoutManager::UpdateStacking(aura::Window* active_window) { 1262 if (!active_window) { 1263 if (!last_active_window_) 1264 return; 1265 active_window = last_active_window_; 1266 } 1267 1268 // Windows are stacked like a deck of cards: 1269 // ,------. 1270 // |,------.| 1271 // |,------.| 1272 // | active | 1273 // | window | 1274 // |`------'| 1275 // |`------'| 1276 // `------' 1277 // Use the middle of each window to figure out how to stack the window. 1278 // This allows us to update the stacking when a window is being dragged around 1279 // by the titlebar. 1280 std::map<int, aura::Window*> window_ordering; 1281 for (aura::Window::Windows::const_iterator it = 1282 dock_container_->children().begin(); 1283 it != dock_container_->children().end(); ++it) { 1284 if (!IsUsedByLayout(*it) || 1285 ((*it) == dragged_window_ && !is_dragged_window_docked_)) { 1286 continue; 1287 } 1288 gfx::Rect bounds = (*it)->bounds(); 1289 window_ordering.insert(std::make_pair(bounds.y() + bounds.height() / 2, 1290 *it)); 1291 } 1292 int active_center_y = active_window->bounds().CenterPoint().y(); 1293 1294 aura::Window* previous_window = NULL; 1295 for (std::map<int, aura::Window*>::const_iterator it = 1296 window_ordering.begin(); 1297 it != window_ordering.end() && it->first < active_center_y; ++it) { 1298 if (previous_window) 1299 dock_container_->StackChildAbove(it->second, previous_window); 1300 previous_window = it->second; 1301 } 1302 for (std::map<int, aura::Window*>::const_reverse_iterator it = 1303 window_ordering.rbegin(); 1304 it != window_ordering.rend() && it->first > active_center_y; ++it) { 1305 if (previous_window) 1306 dock_container_->StackChildAbove(it->second, previous_window); 1307 previous_window = it->second; 1308 } 1309 1310 if (previous_window && active_window->parent() == dock_container_) 1311 dock_container_->StackChildAbove(active_window, previous_window); 1312 if (active_window != dragged_window_) 1313 last_active_window_ = active_window; 1314 } 1315 1316 //////////////////////////////////////////////////////////////////////////////// 1317 // keyboard::KeyboardControllerObserver implementation: 1318 1319 void DockedWindowLayoutManager::OnKeyboardBoundsChanging( 1320 const gfx::Rect& keyboard_bounds) { 1321 // This bounds change will have caused a change to the Shelf which does not 1322 // propagate automatically to this class, so manually recalculate bounds. 1323 Relayout(); 1324 UpdateDockBounds(DockedWindowLayoutManagerObserver::KEYBOARD_BOUNDS_CHANGING); 1325 } 1326 1327 } // namespace ash 1328