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.
5 #include "ash/wm/dock/docked_window_layout_manager.h"
7 #include "ash/ash_switches.h"
8 #include "ash/launcher/launcher.h"
9 #include "ash/screen_ash.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_state.h"
19 #include "ash/wm/window_util.h"
20 #include "ash/wm/workspace_controller.h"
21 #include "base/auto_reset.h"
22 #include "base/command_line.h"
23 #include "third_party/skia/include/core/SkColor.h"
24 #include "ui/aura/client/activation_client.h"
25 #include "ui/aura/client/focus_client.h"
26 #include "ui/aura/client/window_tree_client.h"
27 #include "ui/aura/root_window.h"
28 #include "ui/aura/window.h"
29 #include "ui/aura/window_delegate.h"
30 #include "ui/compositor/scoped_layer_animation_settings.h"
31 #include "ui/gfx/rect.h"
36 // Minimum, maximum width of the dock area and a width of the gap
38 const int DockedWindowLayoutManager::kMaxDockWidth = 360;
40 const int DockedWindowLayoutManager::kMinDockWidth = 200;
42 const int DockedWindowLayoutManager::kMinDockGap = 2;
44 const int DockedWindowLayoutManager::kIdealWidth = 250;
45 const int kMinimumHeight = 250;
46 const int kSlideDurationMs = 120;
47 const int kFadeDurationMs = 720;
51 const SkColor kDockBackgroundColor = SkColorSetARGB(0xff, 0x10, 0x10, 0x10);
52 const float kDockBackgroundOpacity = 0.5f;
54 class DockedBackgroundWidget : public views::Widget {
56 explicit DockedBackgroundWidget(aura::Window* container) {
57 InitWidget(container);
61 void InitWidget(aura::Window* parent) {
62 views::Widget::InitParams params;
63 params.type = views::Widget::InitParams::TYPE_POPUP;
64 params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
65 params.can_activate = false;
66 params.keep_on_top = false;
67 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
68 params.parent = parent;
69 params.accept_events = false;
70 set_focus_on_creation(false);
72 GetNativeWindow()->SetProperty(internal::kStayInSameRootWindowKey, true);
73 DCHECK_EQ(GetNativeView()->GetRootWindow(), parent->GetRootWindow());
74 views::View* content_view = new views::View;
75 content_view->set_background(
76 views::Background::CreateSolidBackground(kDockBackgroundColor));
77 SetContentsView(content_view);
81 DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget);
84 DockedWindowLayoutManager* GetDockLayoutManager(aura::Window* window,
85 const gfx::Point& location) {
86 gfx::Rect near_location(location, gfx::Size());
87 aura::Window* dock = Shell::GetContainer(
88 wm::GetRootWindowMatching(near_location),
89 kShellWindowId_DockedContainer);
90 return static_cast<internal::DockedWindowLayoutManager*>(
91 dock->layout_manager());
94 // Returns true if a window is a popup or a transient child.
95 bool IsPopupOrTransient(aura::Window* window) {
96 return (window->type() == aura::client::WINDOW_TYPE_POPUP ||
97 window->transient_parent());
100 // Certain windows (minimized, hidden or popups) do not matter to docking.
101 bool IsUsedByLayout(aura::Window* window) {
102 return (window->IsVisible() &&
103 !wm::GetWindowState(window)->IsMinimized() &&
104 !IsPopupOrTransient(window));
107 void UndockWindow(aura::Window* window) {
108 gfx::Rect previous_bounds = window->bounds();
109 aura::Window* previous_parent = window->parent();
110 aura::client::ParentWindowWithContext(window, window, gfx::Rect());
111 if (window->parent() != previous_parent)
112 wm::ReparentTransientChildrenOfChild(window->parent(), window);
113 // Start maximize or fullscreen (affecting packaged apps) animation from
114 // previous window bounds.
115 window->layer()->SetBounds(previous_bounds);
118 // Returns width that is as close as possible to |target_width| while being
119 // consistent with docked min and max restrictions and respects the |window|'s
120 // minimum and maximum size.
121 int GetWindowWidthCloseTo(const aura::Window* window, int target_width) {
122 if (!wm::GetWindowState(window)->CanResize()) {
123 DCHECK_LE(window->bounds().width(),
124 DockedWindowLayoutManager::kMaxDockWidth);
125 return window->bounds().width();
127 int width = std::max(DockedWindowLayoutManager::kMinDockWidth,
128 std::min(target_width,
129 DockedWindowLayoutManager::kMaxDockWidth));
130 if (window->delegate()) {
131 if (window->delegate()->GetMinimumSize().width() != 0)
132 width = std::max(width, window->delegate()->GetMinimumSize().width());
133 if (window->delegate()->GetMaximumSize().width() != 0)
134 width = std::min(width, window->delegate()->GetMaximumSize().width());
136 DCHECK_LE(width, DockedWindowLayoutManager::kMaxDockWidth);
140 // Returns height that is as close as possible to |target_height| while
141 // respecting the |window|'s minimum and maximum size.
142 int GetWindowHeightCloseTo(const aura::Window* window, int target_height) {
143 if (!wm::GetWindowState(window)->CanResize())
144 return window->bounds().height();
145 int minimum_height = kMinimumHeight;
146 int maximum_height = 0;
147 const aura::WindowDelegate* delegate(window->delegate());
149 if (delegate->GetMinimumSize().height() != 0) {
150 minimum_height = std::max(kMinimumHeight,
151 delegate->GetMinimumSize().height());
153 if (delegate->GetMaximumSize().height() != 0)
154 maximum_height = delegate->GetMaximumSize().height();
157 target_height = std::max(target_height, minimum_height);
159 target_height = std::min(target_height, maximum_height);
160 return target_height;
163 // A functor used to sort the windows in order of their minimum height.
164 struct CompareMinimumHeight {
165 bool operator()(WindowWithHeight win1, WindowWithHeight win2) {
166 return GetWindowHeightCloseTo(win1.window(), 0) <
167 GetWindowHeightCloseTo(win2.window(), 0);
171 // A functor used to sort the windows in order of their center Y position.
172 // |delta| is a pre-calculated distance from the bottom of one window to the top
173 // of the next. Its value can be positive (gap) or negative (overlap).
174 // Half of |delta| is used as a transition point at which windows could ideally
176 struct CompareWindowPos {
177 CompareWindowPos(aura::Window* dragged_window, float delta)
178 : dragged_window_(dragged_window),
181 bool operator()(WindowWithHeight window_with_height1,
182 WindowWithHeight window_with_height2) {
183 // Use target coordinates since animations may be active when windows are
185 aura::Window* win1(window_with_height1.window());
186 aura::Window* win2(window_with_height2.window());
187 gfx::Rect win1_bounds = ScreenAsh::ConvertRectToScreen(
188 win1->parent(), win1->GetTargetBounds());
189 gfx::Rect win2_bounds = ScreenAsh::ConvertRectToScreen(
190 win2->parent(), win2->GetTargetBounds());
191 win1_bounds.set_height(window_with_height1.height_);
192 win2_bounds.set_height(window_with_height2.height_);
193 // If one of the windows is the |dragged_window_| attempt to make an
194 // earlier swap between the windows than just based on their centers.
195 // This is possible if the dragged window is at least as tall as the other
197 if (win1 == dragged_window_)
198 return compare_two_windows(win1_bounds, win2_bounds);
199 if (win2 == dragged_window_)
200 return !compare_two_windows(win2_bounds, win1_bounds);
201 // Otherwise just compare the centers.
202 return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y();
205 // Based on center point tries to deduce where the drag is coming from.
206 // When dragging from below up the transition point is lower.
207 // When dragging from above down the transition point is higher.
208 bool compare_bounds(const gfx::Rect dragged, const gfx::Rect other) {
209 if (dragged.CenterPoint().y() < other.CenterPoint().y())
210 return dragged.CenterPoint().y() < other.y() - delta_;
211 return dragged.CenterPoint().y() < other.bottom() + delta_;
214 // Performs comparison both ways and selects stable result.
215 bool compare_two_windows(const gfx::Rect bounds1, const gfx::Rect bounds2) {
216 // Try comparing windows in both possible orders and see if the comparison
218 bool result1 = compare_bounds(bounds1, bounds2);
219 bool result2 = compare_bounds(bounds2, bounds1);
220 if (result1 != result2)
223 // Otherwise it is not possible to be sure that the windows will not bounce.
224 // In this case just compare the centers.
225 return bounds1.CenterPoint().y() < bounds2.CenterPoint().y();
229 aura::Window* dragged_window_;
235 ////////////////////////////////////////////////////////////////////////////////
236 // DockLayoutManager public implementation:
237 DockedWindowLayoutManager::DockedWindowLayoutManager(
238 aura::Window* dock_container, WorkspaceController* workspace_controller)
239 : dock_container_(dock_container),
241 dragged_window_(NULL),
242 is_dragged_window_docked_(false),
243 is_dragged_from_dock_(false),
245 workspace_controller_(workspace_controller),
246 in_fullscreen_(workspace_controller_->GetWindowState() ==
247 WORKSPACE_WINDOW_STATE_FULL_SCREEN),
249 alignment_(DOCKED_ALIGNMENT_NONE),
250 last_active_window_(NULL),
251 background_widget_(new DockedBackgroundWidget(dock_container_)) {
252 DCHECK(dock_container);
253 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
255 Shell::GetInstance()->AddShellObserver(this);
258 DockedWindowLayoutManager::~DockedWindowLayoutManager() {
262 void DockedWindowLayoutManager::Shutdown() {
264 for (size_t i = 0; i < dock_container_->children().size(); ++i) {
265 aura::Window* child = dock_container_->children()[i];
266 child->RemoveObserver(this);
267 wm::GetWindowState(child)->RemoveObserver(this);
269 aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
270 RemoveObserver(this);
271 Shell::GetInstance()->RemoveShellObserver(this);
274 void DockedWindowLayoutManager::AddObserver(
275 DockedWindowLayoutManagerObserver* observer) {
276 observer_list_.AddObserver(observer);
279 void DockedWindowLayoutManager::RemoveObserver(
280 DockedWindowLayoutManagerObserver* observer) {
281 observer_list_.RemoveObserver(observer);
284 void DockedWindowLayoutManager::StartDragging(aura::Window* window) {
285 DCHECK(!dragged_window_);
286 dragged_window_ = window;
287 DCHECK(!IsPopupOrTransient(window));
288 // Start observing a window unless it is docked container's child in which
289 // case it is already observed.
290 if (dragged_window_->parent() != dock_container_) {
291 dragged_window_->AddObserver(this);
292 wm::GetWindowState(dragged_window_)->AddObserver(this);
294 is_dragged_from_dock_ = window->parent() == dock_container_;
295 DCHECK(!is_dragged_window_docked_);
298 void DockedWindowLayoutManager::DockDraggedWindow(aura::Window* window) {
299 DCHECK(!IsPopupOrTransient(window));
300 OnDraggedWindowDocked(window);
304 void DockedWindowLayoutManager::UndockDraggedWindow() {
305 DCHECK(!IsPopupOrTransient(dragged_window_));
306 OnDraggedWindowUndocked();
308 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
309 is_dragged_from_dock_ = false;
312 void DockedWindowLayoutManager::FinishDragging() {
313 DCHECK(dragged_window_);
314 DCHECK(!IsPopupOrTransient(dragged_window_));
315 if (is_dragged_window_docked_)
316 OnDraggedWindowUndocked();
317 DCHECK (!is_dragged_window_docked_);
318 // Stop observing a window unless it is docked container's child in which
319 // case it needs to keep being observed after the drag completes.
320 if (dragged_window_->parent() != dock_container_) {
321 dragged_window_->RemoveObserver(this);
322 wm::GetWindowState(dragged_window_)->RemoveObserver(this);
323 if (last_active_window_ == dragged_window_)
324 last_active_window_ = NULL;
326 // A window is no longer dragged and is a child.
327 // When a window becomes a child at drag start this is
328 // the only opportunity we will have to enforce a window
329 // count limit so do it here.
330 MaybeMinimizeChildrenExcept(dragged_window_);
332 dragged_window_ = NULL;
333 dragged_bounds_ = gfx::Rect();
335 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
338 void DockedWindowLayoutManager::SetLauncher(ash::Launcher* launcher) {
340 launcher_ = launcher;
343 DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow(
344 const aura::Window* window) const {
345 const gfx::Rect& bounds(window->GetBoundsInScreen());
347 // Test overlap with an existing docked area first.
348 if (docked_bounds_.Intersects(bounds) &&
349 alignment_ != DOCKED_ALIGNMENT_NONE) {
350 // A window is being added to other docked windows (on the same side).
354 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen();
355 if (bounds.x() <= container_bounds.x() &&
356 bounds.right() > container_bounds.x()) {
357 return DOCKED_ALIGNMENT_LEFT;
358 } else if (bounds.x() < container_bounds.right() &&
359 bounds.right() >= container_bounds.right()) {
360 return DOCKED_ALIGNMENT_RIGHT;
362 return DOCKED_ALIGNMENT_NONE;
365 DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const {
366 // Find a child that is not being dragged and is not a popup.
367 // If such exists the current alignment is returned - even if some of the
368 // children are hidden or minimized (so they can be restored without losing
369 // the docked state).
370 for (size_t i = 0; i < dock_container_->children().size(); ++i) {
371 aura::Window* window(dock_container_->children()[i]);
372 if (window != dragged_window_ && !IsPopupOrTransient(window))
375 // No docked windows remain other than possibly the window being dragged.
376 // Return |NONE| to indicate that windows may get docked on either side.
377 return DOCKED_ALIGNMENT_NONE;
380 bool DockedWindowLayoutManager::CanDockWindow(aura::Window* window,
382 if (!CommandLine::ForCurrentProcess()->HasSwitch(
383 switches::kAshEnableDockedWindows)) {
386 // Don't allow interactive docking of windows with transient parents such as
387 // modal browser dialogs.
388 if (IsPopupOrTransient(window))
390 // If a window is wide and cannot be resized down to maximum width allowed
391 // then it cannot be docked.
392 // TODO(varkha). Prevent windows from changing size programmatically while
393 // they are docked. The size will take effect only once a window is undocked.
394 // See http://crbug.com/307792.
395 if (window->bounds().width() > kMaxDockWidth &&
396 (!wm::GetWindowState(window)->CanResize() ||
397 (window->delegate() &&
398 window->delegate()->GetMinimumSize().width() != 0 &&
399 window->delegate()->GetMinimumSize().width() > kMaxDockWidth))) {
402 // If a window is tall and cannot be resized down to maximum height allowed
403 // then it cannot be docked.
404 const gfx::Rect work_area =
405 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
406 if (GetWindowHeightCloseTo(window, work_area.height() - 2 * kMinDockGap) >
407 work_area.height() - 2 * kMinDockGap) {
410 // Cannot dock on the other size from an existing dock.
411 const DockedAlignment alignment = CalculateAlignment();
412 if ((edge == SNAP_LEFT && alignment == DOCKED_ALIGNMENT_RIGHT) ||
413 (edge == SNAP_RIGHT && alignment == DOCKED_ALIGNMENT_LEFT)) {
416 // Do not allow docking on the same side as launcher shelf.
417 ShelfAlignment shelf_alignment = SHELF_ALIGNMENT_BOTTOM;
419 shelf_alignment = launcher_->alignment();
420 if ((edge == SNAP_LEFT && shelf_alignment == SHELF_ALIGNMENT_LEFT) ||
421 (edge == SNAP_RIGHT && shelf_alignment == SHELF_ALIGNMENT_RIGHT)) {
427 ////////////////////////////////////////////////////////////////////////////////
428 // DockLayoutManager, aura::LayoutManager implementation:
429 void DockedWindowLayoutManager::OnWindowResized() {
430 MaybeMinimizeChildrenExcept(dragged_window_);
432 // When screen resizes update the insets even when dock width or alignment
434 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED);
437 void DockedWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
438 if (IsPopupOrTransient(child))
440 // Dragged windows are already observed by StartDragging and do not change
441 // docked alignment during the drag.
442 if (child == dragged_window_)
444 // If this is the first window getting docked - update alignment.
445 if (alignment_ == DOCKED_ALIGNMENT_NONE) {
446 alignment_ = GetAlignmentOfWindow(child);
447 DCHECK(alignment_ != DOCKED_ALIGNMENT_NONE);
449 MaybeMinimizeChildrenExcept(child);
450 child->AddObserver(this);
451 wm::GetWindowState(child)->AddObserver(this);
453 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
456 void DockedWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
457 if (IsPopupOrTransient(child))
459 // Dragged windows are stopped being observed by FinishDragging and do not
460 // change alignment during the drag. They also cannot be set to be the
461 // |last_active_window_|.
462 if (child == dragged_window_)
464 // If this is the last window, set alignment and maximize the workspace.
465 if (!IsAnyWindowDocked()) {
466 alignment_ = DOCKED_ALIGNMENT_NONE;
469 if (last_active_window_ == child)
470 last_active_window_ = NULL;
471 child->RemoveObserver(this);
472 wm::GetWindowState(child)->RemoveObserver(this);
474 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
477 void DockedWindowLayoutManager::OnChildWindowVisibilityChanged(
480 if (IsPopupOrTransient(child))
483 wm::GetWindowState(child)->Restore();
485 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
488 void DockedWindowLayoutManager::SetChildBounds(
490 const gfx::Rect& requested_bounds) {
491 // Whenever one of our windows is moved or resized enforce layout.
492 SetChildBoundsDirect(child, requested_bounds);
495 ////////////////////////////////////////////////////////////////////////////////
496 // DockLayoutManager, ash::ShellObserver implementation:
498 void DockedWindowLayoutManager::OnDisplayWorkAreaInsetsChanged() {
500 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED);
501 MaybeMinimizeChildrenExcept(dragged_window_);
504 void DockedWindowLayoutManager::OnFullscreenStateChanged(
505 bool is_fullscreen, aura::Window* root_window) {
506 if (dock_container_->GetRootWindow() != root_window)
508 // Entering fullscreen mode (including immersive) hides docked windows.
509 in_fullscreen_ = workspace_controller_->GetWindowState() ==
510 WORKSPACE_WINDOW_STATE_FULL_SCREEN;
512 // prevent Relayout from getting called multiple times during this
513 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
514 // Use a copy of children array because a call to MinimizeDockedWindow or
515 // RestoreDockedWindow can change order.
516 aura::Window::Windows children(dock_container_->children());
517 for (aura::Window::Windows::const_iterator iter = children.begin();
518 iter != children.end(); ++iter) {
519 aura::Window* window(*iter);
520 if (IsPopupOrTransient(window))
522 wm::WindowState* window_state = wm::GetWindowState(window);
523 if (in_fullscreen_) {
524 if (window->IsVisible())
525 MinimizeDockedWindow(window_state);
527 if (!window_state->IsMinimized())
528 RestoreDockedWindow(window_state);
533 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
536 void DockedWindowLayoutManager::OnShelfAlignmentChanged(
537 aura::Window* root_window) {
538 if (dock_container_->GetRootWindow() != root_window)
541 if (!launcher_ || !launcher_->shelf_widget())
544 if (alignment_ == DOCKED_ALIGNMENT_NONE)
547 // Do not allow launcher and dock on the same side. Switch side that
548 // the dock is attached to and move all dock windows to that new side.
549 ShelfAlignment shelf_alignment = launcher_->shelf_widget()->GetAlignment();
550 if (alignment_ == DOCKED_ALIGNMENT_LEFT &&
551 shelf_alignment == SHELF_ALIGNMENT_LEFT) {
552 alignment_ = DOCKED_ALIGNMENT_RIGHT;
553 } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT &&
554 shelf_alignment == SHELF_ALIGNMENT_RIGHT) {
555 alignment_ = DOCKED_ALIGNMENT_LEFT;
558 UpdateDockBounds(DockedWindowLayoutManagerObserver::SHELF_ALIGNMENT_CHANGED);
561 /////////////////////////////////////////////////////////////////////////////
562 // DockLayoutManager, WindowStateObserver implementation:
564 void DockedWindowLayoutManager::OnWindowShowTypeChanged(
565 wm::WindowState* window_state,
566 wm::WindowShowType old_type) {
567 aura::Window* window = window_state->window();
568 if (IsPopupOrTransient(window))
570 // The window property will still be set, but no actual change will occur
571 // until OnFullscreenStateChange is called when exiting fullscreen.
574 if (window_state->IsMinimized()) {
575 MinimizeDockedWindow(window_state);
576 } else if (window_state->IsMaximizedOrFullscreen()) {
577 // Reparenting changes the source bounds for the animation if a window is
578 // visible so hide it here and show later when it is already in the desktop.
579 UndockWindow(window);
580 } else if (old_type == wm::SHOW_TYPE_MINIMIZED) {
581 RestoreDockedWindow(window_state);
585 /////////////////////////////////////////////////////////////////////////////
586 // DockLayoutManager, WindowObserver implementation:
588 void DockedWindowLayoutManager::OnWindowBoundsChanged(
589 aura::Window* window,
590 const gfx::Rect& old_bounds,
591 const gfx::Rect& new_bounds) {
592 // Only relayout if the dragged window would get docked.
593 if (window == dragged_window_ && is_dragged_window_docked_)
597 void DockedWindowLayoutManager::OnWindowVisibilityChanging(
598 aura::Window* window, bool visible) {
599 if (IsPopupOrTransient(window))
601 int animation_type = WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE;
603 animation_type = views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT;
604 views::corewm::SetWindowVisibilityAnimationDuration(
605 window, base::TimeDelta::FromMilliseconds(kFadeDurationMs));
607 views::corewm::SetWindowVisibilityAnimationType(window, animation_type);
610 void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) {
611 if (dragged_window_ == window) {
613 DCHECK(!dragged_window_);
614 DCHECK (!is_dragged_window_docked_);
616 if (window == last_active_window_)
617 last_active_window_ = NULL;
621 ////////////////////////////////////////////////////////////////////////////////
622 // DockLayoutManager, aura::client::ActivationChangeObserver implementation:
624 void DockedWindowLayoutManager::OnWindowActivated(aura::Window* gained_active,
625 aura::Window* lost_active) {
626 if (gained_active && IsPopupOrTransient(gained_active))
628 // Ignore if the window that is not managed by this was activated.
629 aura::Window* ancestor = NULL;
630 for (aura::Window* parent = gained_active;
631 parent; parent = parent->parent()) {
632 if (parent->parent() == dock_container_) {
638 UpdateStacking(ancestor);
641 ////////////////////////////////////////////////////////////////////////////////
642 // DockLayoutManager private implementation:
644 void DockedWindowLayoutManager::MaybeMinimizeChildrenExcept(
645 aura::Window* child) {
646 // Minimize any windows that don't fit without overlap.
647 const gfx::Rect work_area =
648 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
649 int available_room = work_area.height() - kMinDockGap;
651 available_room -= (GetWindowHeightCloseTo(child, 0) + kMinDockGap);
652 // Use a copy of children array because a call to Minimize can change order.
653 aura::Window::Windows children(dock_container_->children());
654 aura::Window::Windows::const_reverse_iterator iter = children.rbegin();
655 while (iter != children.rend()) {
656 aura::Window* window(*iter++);
657 if (window == child || !IsUsedByLayout(window))
659 int room_needed = GetWindowHeightCloseTo(window, 0) + kMinDockGap;
660 if (available_room > room_needed)
661 available_room -= room_needed;
663 wm::GetWindowState(window)->Minimize();
667 void DockedWindowLayoutManager::MinimizeDockedWindow(
668 wm::WindowState* window_state) {
669 DCHECK(!IsPopupOrTransient(window_state->window()));
670 window_state->window()->Hide();
671 if (window_state->IsActive())
672 window_state->Deactivate();
675 void DockedWindowLayoutManager::RestoreDockedWindow(
676 wm::WindowState* window_state) {
677 aura::Window* window = window_state->window();
678 DCHECK(!IsPopupOrTransient(window));
679 // Always place restored window at the top shuffling the other windows down.
680 // TODO(varkha): add a separate container for docked windows to keep track
682 gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
684 const gfx::Rect work_area = display.work_area();
686 // Evict the window if it can no longer be docked because of its height.
687 if (!CanDockWindow(window, SNAP_NONE)) {
688 UndockWindow(window);
691 gfx::Rect bounds(window->bounds());
692 bounds.set_y(work_area.y() - bounds.height());
693 window->SetBounds(bounds);
695 MaybeMinimizeChildrenExcept(window);
698 void DockedWindowLayoutManager::OnDraggedWindowDocked(aura::Window* window) {
699 DCHECK(!is_dragged_window_docked_);
700 is_dragged_window_docked_ = true;
702 // If there are no other docked windows update alignment.
703 if (!IsAnyWindowDocked())
704 alignment_ = DOCKED_ALIGNMENT_NONE;
707 void DockedWindowLayoutManager::OnDraggedWindowUndocked() {
708 // If this is the first window getting docked - update alignment.
709 if (!IsAnyWindowDocked())
710 alignment_ = GetAlignmentOfWindow(dragged_window_);
712 DCHECK (is_dragged_window_docked_);
713 is_dragged_window_docked_ = false;
716 bool DockedWindowLayoutManager::IsAnyWindowDocked() {
717 return CalculateAlignment() != DOCKED_ALIGNMENT_NONE;
720 void DockedWindowLayoutManager::Relayout() {
723 if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_)
725 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
727 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
728 aura::Window* active_window = NULL;
729 std::vector<WindowWithHeight> visible_windows;
730 for (size_t i = 0; i < dock_container_->children().size(); ++i) {
731 aura::Window* window(dock_container_->children()[i]);
733 if (!IsUsedByLayout(window) || window == dragged_window_)
736 // If the shelf is currently hidden (full-screen mode), hide window until
737 // full-screen mode is exited.
738 if (in_fullscreen_) {
739 // The call to Hide does not set the minimize property, so the window will
740 // be restored when the shelf becomes visible again.
744 if (window->HasFocus() ||
746 aura::client::GetFocusClient(window)->GetFocusedWindow())) {
747 DCHECK(!active_window);
748 active_window = window;
750 visible_windows.push_back(WindowWithHeight(window));
752 // Consider docked dragged_window_ when fanning out other child windows.
753 if (is_dragged_window_docked_) {
754 visible_windows.push_back(WindowWithHeight(dragged_window_));
755 DCHECK(!active_window);
756 active_window = dragged_window_;
759 // Position docked windows as well as the window being dragged.
760 const gfx::Rect work_area =
761 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
762 int available_room = CalculateWindowHeightsAndRemainingRoom(work_area,
764 FanOutChildren(work_area,
765 CalculateIdealWidth(visible_windows),
769 // After the first Relayout allow the windows to change their order easier
770 // since we know they are docked.
771 is_dragged_from_dock_ = true;
772 UpdateStacking(active_window);
775 int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom(
776 const gfx::Rect work_area,
777 std::vector<WindowWithHeight>* visible_windows) {
778 int available_room = work_area.height() - kMinDockGap;
779 int remaining_windows = visible_windows->size();
781 // Sort windows by their minimum heights and calculate target heights.
782 std::sort(visible_windows->begin(), visible_windows->end(),
783 CompareMinimumHeight());
784 // Distribute the free space among the docked windows. Since the windows are
785 // sorted (tall windows first) we can now assume that any window which
786 // required more space than the current window will have already been
787 // accounted for previously in this loop, so we can safely give that window
788 // its proportional share of the remaining space.
789 for (std::vector<WindowWithHeight>::reverse_iterator iter =
790 visible_windows->rbegin();
791 iter != visible_windows->rend(); ++iter) {
792 iter->height_ = GetWindowHeightCloseTo(
793 iter->window(), available_room / remaining_windows - kMinDockGap);
794 available_room -= (iter->height_ + kMinDockGap);
797 return available_room;
800 int DockedWindowLayoutManager::CalculateIdealWidth(
801 const std::vector<WindowWithHeight>& visible_windows) {
802 // Calculate ideal width for the docked area adjusting the dragged window
803 // or other windows as necessary.
804 int ideal_docked_width = 0;
805 for (std::vector<WindowWithHeight>::const_iterator iter =
806 visible_windows.begin();
807 iter != visible_windows.end(); ++iter) {
808 const aura::Window* window = iter->window();
810 // Adjust the dragged window to the dock. If that is not possible then
811 // other docked windows area adjusted to the one that is being dragged.
812 int adjusted_docked_width = window->bounds().width();
813 if (window == dragged_window_) {
814 // Adjust the dragged window width to the current dock size or ideal when
815 // there are no other docked windows.
816 adjusted_docked_width = GetWindowWidthCloseTo(
817 window, (docked_width_ > 0) ? docked_width_ : kIdealWidth);
818 } else if (!is_dragged_from_dock_ && is_dragged_window_docked_) {
819 // When a docked window is dragged-in other docked windows' widths are
820 // adjusted to the new width if necessary.
821 // When there is no dragged docked window the docked windows retain their
823 // When a dragged window is simply being reordered in the docked area the
824 // other windows are not resized (but the dragged window can be).
825 adjusted_docked_width = GetWindowWidthCloseTo(window, 0);
827 ideal_docked_width = std::max(ideal_docked_width, adjusted_docked_width);
829 // Restrict docked area width regardless of window restrictions.
830 ideal_docked_width = std::max(std::min(ideal_docked_width, kMaxDockWidth),
833 return ideal_docked_width;
836 void DockedWindowLayoutManager::FanOutChildren(
837 const gfx::Rect& work_area,
838 int ideal_docked_width,
840 std::vector<WindowWithHeight>* visible_windows) {
841 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
843 // Calculate initial vertical offset and the gap or overlap between windows.
844 const int num_windows = visible_windows->size();
845 const float delta = kMinDockGap + (float)available_room /
846 ((available_room > 0 || num_windows <= 1) ?
847 num_windows + 1 : num_windows - 1);
848 float y_pos = work_area.y() + ((delta > 0) ? delta : kMinDockGap);
850 // Docked area is shown only if there is at least one non-dragged visible
852 docked_width_ = ideal_docked_width;
853 if (visible_windows->empty() ||
854 (visible_windows->size() == 1 &&
855 (*visible_windows)[0].window() == dragged_window_)) {
859 // Sort windows by their center positions and fan out overlapping
861 std::sort(visible_windows->begin(), visible_windows->end(),
862 CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : NULL,
864 for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin();
865 iter != visible_windows->end(); ++iter) {
866 aura::Window* window = iter->window();
867 gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
868 window->parent(), window->GetTargetBounds());
869 // A window is extended or shrunk to be as close as possible to the docked
870 // area width. Windows other than the dragged window are kept at their
871 // existing size when the dragged window is just being reordered.
872 // This also enforces the min / max restrictions on the docked area width.
873 bounds.set_width(GetWindowWidthCloseTo(
875 (!is_dragged_from_dock_ || window == dragged_window_) ?
876 ideal_docked_width : bounds.width()));
877 DCHECK_LE(bounds.width(), ideal_docked_width);
879 DockedAlignment alignment = alignment_;
880 if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_) {
881 alignment = GetAlignmentOfWindow(window);
882 if (alignment == DOCKED_ALIGNMENT_NONE)
883 bounds.set_size(gfx::Size());
886 // Fan out windows evenly distributing the overlap or remaining free space.
887 bounds.set_height(iter->height_);
888 bounds.set_y(std::max(work_area.y(),
889 std::min(work_area.bottom() - bounds.height(),
890 static_cast<int>(y_pos + 0.5))));
891 y_pos += bounds.height() + delta;
893 // All docked windows other than the one currently dragged remain stuck
894 // to the screen edge (flush with the edge or centered in the dock area).
896 case DOCKED_ALIGNMENT_LEFT:
897 bounds.set_x(dock_bounds.x() +
898 (ideal_docked_width - bounds.width()) / 2);
900 case DOCKED_ALIGNMENT_RIGHT:
901 bounds.set_x(dock_bounds.right() -
902 (ideal_docked_width + bounds.width()) / 2);
904 case DOCKED_ALIGNMENT_NONE:
907 if (window == dragged_window_) {
908 dragged_bounds_ = bounds;
911 // If the following asserts it is probably because not all the children
912 // have been removed when dock was closed.
913 DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE);
914 bounds = ScreenAsh::ConvertRectFromScreen(dock_container_, bounds);
915 if (bounds != window->GetTargetBounds()) {
916 ui::Layer* layer = window->layer();
917 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
918 slide_settings.SetPreemptionStrategy(
919 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
920 slide_settings.SetTransitionDuration(
921 base::TimeDelta::FromMilliseconds(kSlideDurationMs));
922 SetChildBoundsDirect(window, bounds);
927 void DockedWindowLayoutManager::UpdateDockBounds(
928 DockedWindowLayoutManagerObserver::Reason reason) {
929 int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0);
930 const gfx::Rect work_area =
931 Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
932 gfx::Rect bounds = gfx::Rect(
933 alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 ?
934 dock_container_->bounds().right() - dock_inset:
935 dock_container_->bounds().x(),
936 dock_container_->bounds().y(),
939 docked_bounds_ = bounds +
940 dock_container_->GetBoundsInScreen().OffsetFromOrigin();
942 DockedWindowLayoutManagerObserver,
944 OnDockBoundsChanging(bounds, reason));
945 // Show or hide background for docked area.
946 background_widget_->SetBounds(docked_bounds_);
947 if (docked_width_ > 0) {
948 background_widget_->Show();
949 background_widget_->GetNativeWindow()->layer()->SetOpacity(
950 kDockBackgroundOpacity);
952 background_widget_->Hide();
956 void DockedWindowLayoutManager::UpdateStacking(aura::Window* active_window) {
957 if (!active_window) {
958 if (!last_active_window_)
960 active_window = last_active_window_;
963 // Windows are stacked like a deck of cards:
972 // Use the middle of each window to figure out how to stack the window.
973 // This allows us to update the stacking when a window is being dragged around
975 std::map<int, aura::Window*> window_ordering;
976 for (aura::Window::Windows::const_iterator it =
977 dock_container_->children().begin();
978 it != dock_container_->children().end(); ++it) {
979 if (!IsUsedByLayout(*it) ||
980 ((*it) == dragged_window_ && !is_dragged_window_docked_)) {
983 gfx::Rect bounds = (*it)->bounds();
984 window_ordering.insert(std::make_pair(bounds.y() + bounds.height() / 2,
987 int active_center_y = active_window->bounds().CenterPoint().y();
989 aura::Window* previous_window = NULL;
990 for (std::map<int, aura::Window*>::const_iterator it =
991 window_ordering.begin();
992 it != window_ordering.end() && it->first < active_center_y; ++it) {
994 dock_container_->StackChildAbove(it->second, previous_window);
995 previous_window = it->second;
997 for (std::map<int, aura::Window*>::const_reverse_iterator it =
998 window_ordering.rbegin();
999 it != window_ordering.rend() && it->first > active_center_y; ++it) {
1000 if (previous_window)
1001 dock_container_->StackChildAbove(it->second, previous_window);
1002 previous_window = it->second;
1005 if (previous_window && active_window->parent() == dock_container_)
1006 dock_container_->StackChildAbove(active_window, previous_window);
1007 if (active_window != dragged_window_)
1008 last_active_window_ = active_window;
1011 ////////////////////////////////////////////////////////////////////////////////
1012 // keyboard::KeyboardControllerObserver implementation:
1014 void DockedWindowLayoutManager::OnKeyboardBoundsChanging(
1015 const gfx::Rect& keyboard_bounds) {
1016 // This bounds change will have caused a change to the Shelf which does not
1017 // propagate automatically to this class, so manually recalculate bounds.
1019 UpdateDockBounds(DockedWindowLayoutManagerObserver::KEYBOARD_BOUNDS_CHANGING);
1022 } // namespace internal