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

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

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