Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(31)

Side by Side Diff: ash/common/wm/dock/docked_window_layout_manager.cc

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

Powered by Google App Engine
This is Rietveld 408576698