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