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

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

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

Powered by Google App Engine
This is Rietveld 408576698