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

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

Issue 1921353002: Moves handful of files to ash/wm/common (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@shell_ids
Patch Set: merge to trunk Created 4 years, 8 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/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/wm_globals.h"
13 #include "ash/wm/common/wm_root_window_controller.h"
14 #include "ash/wm/common/wm_shell_window_ids.h"
15 #include "ash/wm/common/wm_window.h"
16 #include "ash/wm/window_resizer.h"
17 #include "ash/wm/window_state.h"
18 #include "base/auto_reset.h"
19 #include "base/metrics/histogram.h"
20 #include "grit/ash_resources.h"
21 #include "third_party/skia/include/core/SkColor.h"
22 #include "third_party/skia/include/core/SkPaint.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/compositor/paint_recorder.h"
25 #include "ui/compositor/scoped_layer_animation_settings.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/gfx/display.h"
28 #include "ui/gfx/image/image_skia_operations.h"
29 #include "ui/views/background.h"
30
31 namespace ash {
32
33 // Minimum, maximum width of the dock area and a width of the gap
34 // static
35 const int DockedWindowLayoutManager::kMaxDockWidth = 360;
36 // static
37 const int DockedWindowLayoutManager::kMinDockWidth = 200;
38 // static
39 const int DockedWindowLayoutManager::kMinDockGap = 2;
40 // static
41 const int DockedWindowLayoutManager::kIdealWidth = 250;
42 const int kMinimumHeight = 250;
43 const int kSlideDurationMs = 120;
44 const int kFadeDurationMs = 60;
45 const int kMinimizeDurationMs = 720;
46
47 class DockedBackgroundWidget : public views::Widget,
48 public BackgroundAnimatorDelegate,
49 public wm::WmShelfObserver {
50 public:
51 explicit DockedBackgroundWidget(DockedWindowLayoutManager* manager)
52 : manager_(manager),
53 alignment_(DOCKED_ALIGNMENT_NONE),
54 background_animator_(this, 0, wm::kShelfBackgroundAlpha),
55 alpha_(0),
56 opaque_background_(ui::LAYER_SOLID_COLOR),
57 visible_background_type_(manager_->shelf()->GetBackgroundType()),
58 visible_background_change_type_(BACKGROUND_CHANGE_IMMEDIATE) {
59 manager_->shelf()->AddObserver(this);
60 InitWidget(manager_->dock_container());
61 }
62
63 ~DockedBackgroundWidget() override {
64 manager_->shelf()->RemoveObserver(this);
65 }
66
67 // Sets widget bounds and sizes opaque background layer to fill the widget.
68 void SetBackgroundBounds(const gfx::Rect& bounds, DockedAlignment alignment) {
69 SetBounds(bounds);
70 opaque_background_.SetBounds(gfx::Rect(bounds.size()));
71 alignment_ = alignment;
72 }
73
74 private:
75 // views::Widget:
76 void OnNativeWidgetVisibilityChanged(bool visible) override {
77 views::Widget::OnNativeWidgetVisibilityChanged(visible);
78 UpdateBackground();
79 }
80
81 void OnNativeWidgetPaint(const ui::PaintContext& context) override {
82 gfx::Rect local_window_bounds(GetWindowBoundsInScreen().size());
83 ui::PaintRecorder recorder(context, local_window_bounds.size());
84 const gfx::ImageSkia& shelf_background(
85 alignment_ == DOCKED_ALIGNMENT_LEFT ?
86 shelf_background_left_ : shelf_background_right_);
87 SkPaint paint;
88 paint.setAlpha(alpha_);
89 recorder.canvas()->DrawImageInt(
90 shelf_background, 0, 0, shelf_background.width(),
91 shelf_background.height(),
92 alignment_ == DOCKED_ALIGNMENT_LEFT
93 ? local_window_bounds.width() - shelf_background.width()
94 : 0,
95 0, shelf_background.width(), local_window_bounds.height(), false,
96 paint);
97 recorder.canvas()->DrawImageInt(
98 shelf_background,
99 alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width() - 1,
100 0, 1, shelf_background.height(),
101 alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width(), 0,
102 local_window_bounds.width() - shelf_background.width(),
103 local_window_bounds.height(), false, paint);
104 }
105
106 // BackgroundAnimatorDelegate:
107 void UpdateBackground(int alpha) override {
108 alpha_ = alpha;
109 SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size()));
110 }
111
112 // ShelfLayoutManagerObserver:
113 void OnBackgroundUpdated(wm::ShelfBackgroundType background_type,
114 BackgroundAnimatorChangeType change_type) override {
115 // Sets the background type. Starts an animation to transition to
116 // |background_type| if the widget is visible. If the widget is not visible,
117 // the animation is postponed till the widget becomes visible.
118 visible_background_type_ = background_type;
119 visible_background_change_type_ = change_type;
120 if (IsVisible())
121 UpdateBackground();
122 }
123
124 void InitWidget(wm::WmWindow* parent) {
125 views::Widget::InitParams params;
126 params.type = views::Widget::InitParams::TYPE_POPUP;
127 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
128 params.keep_on_top = false;
129 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
130 params.accept_events = false;
131 set_focus_on_creation(false);
132 parent->GetRootWindowController()->ConfigureWidgetInitParamsForContainer(
133 this, parent->GetShellWindowId(), &params);
134 Init(params);
135 SetVisibilityChangedAnimationsEnabled(false);
136 wm::WmWindow* wm_window = wm::WmWindow::Get(this);
137 wm_window->SetLockedToRoot(true);
138 opaque_background_.SetColor(SK_ColorBLACK);
139 opaque_background_.SetBounds(gfx::Rect(GetWindowBoundsInScreen().size()));
140 opaque_background_.SetOpacity(0.0f);
141 wm_window->GetLayer()->Add(&opaque_background_);
142
143 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
144 gfx::ImageSkia shelf_background =
145 *rb.GetImageSkiaNamed(IDR_ASH_SHELF_BACKGROUND);
146 shelf_background_left_ = gfx::ImageSkiaOperations::CreateRotatedImage(
147 shelf_background, SkBitmapOperations::ROTATION_90_CW);
148 shelf_background_right_ = gfx::ImageSkiaOperations::CreateRotatedImage(
149 shelf_background, SkBitmapOperations::ROTATION_270_CW);
150
151 // This background should be explicitly stacked below any windows already in
152 // the dock, otherwise the z-order is set by the order in which windows were
153 // added to the container, and UpdateStacking only manages user windows, not
154 // the background widget.
155 parent->StackChildAtBottom(wm_window);
156 }
157
158 // Transitions to |visible_background_type_| if the widget is visible and to
159 // SHELF_BACKGROUND_DEFAULT if it is not.
160 void UpdateBackground() {
161 wm::ShelfBackgroundType background_type =
162 IsVisible() ? visible_background_type_ : wm::SHELF_BACKGROUND_DEFAULT;
163 BackgroundAnimatorChangeType change_type = IsVisible() ?
164 visible_background_change_type_ : BACKGROUND_CHANGE_IMMEDIATE;
165
166 float target_opacity =
167 (background_type == wm::SHELF_BACKGROUND_MAXIMIZED) ? 1.0f : 0.0f;
168 std::unique_ptr<ui::ScopedLayerAnimationSettings>
169 opaque_background_animation;
170 if (change_type != BACKGROUND_CHANGE_IMMEDIATE) {
171 opaque_background_animation.reset(new ui::ScopedLayerAnimationSettings(
172 opaque_background_.GetAnimator()));
173 opaque_background_animation->SetTransitionDuration(
174 base::TimeDelta::FromMilliseconds(wm::kTimeToSwitchBackgroundMs));
175 }
176 opaque_background_.SetOpacity(target_opacity);
177
178 // TODO(varkha): use ui::Layer on both opaque_background and normal
179 // background retire background_animator_ at all. It would be simpler.
180 // See also ShelfWidget::SetPaintsBackground.
181 background_animator_.SetPaintsBackground(
182 background_type != wm::SHELF_BACKGROUND_DEFAULT, change_type);
183 SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size()));
184 }
185
186 DockedWindowLayoutManager* manager_;
187
188 DockedAlignment alignment_;
189
190 // The animator for the background transitions.
191 BackgroundAnimator background_animator_;
192
193 // The alpha to use for drawing image assets covering the docked background.
194 int alpha_;
195
196 // Solid black background that can be made fully opaque.
197 ui::Layer opaque_background_;
198
199 // Backgrounds created from shelf background by 90 or 270 degree rotation.
200 gfx::ImageSkia shelf_background_left_;
201 gfx::ImageSkia shelf_background_right_;
202
203 // The background type to use when the widget is visible. When not visible,
204 // the widget uses SHELF_BACKGROUND_DEFAULT.
205 wm::ShelfBackgroundType visible_background_type_;
206
207 // Whether the widget should animate to |visible_background_type_|.
208 BackgroundAnimatorChangeType visible_background_change_type_;
209
210 DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget);
211 };
212
213 namespace {
214
215 // Returns true if a window is a popup or a transient child.
216 bool IsPopupOrTransient(const wm::WmWindow* window) {
217 return (window->GetType() == ui::wm::WINDOW_TYPE_POPUP ||
218 window->GetTransientParent());
219 }
220
221 // Certain windows (minimized, hidden or popups) are not docked and are ignored
222 // by layout logic even when they are children of a docked container.
223 bool IsWindowDocked(const wm::WmWindow* window) {
224 return (window->IsVisible() && !window->GetWindowState()->IsMinimized() &&
225 !IsPopupOrTransient(window));
226 }
227
228 void UndockWindow(wm::WmWindow* window) {
229 gfx::Rect previous_bounds = window->GetBounds();
230 wm::WmWindow* old_parent = window->GetParent();
231 window->SetParentUsingContext(window, gfx::Rect());
232 if (window->GetParent() != old_parent) {
233 wm::ReparentTransientChildrenOfChild(window, old_parent,
234 window->GetParent());
235 }
236 // Start maximize or fullscreen (affecting packaged apps) animation from
237 // previous window bounds.
238 window->GetLayer()->SetBounds(previous_bounds);
239 }
240
241 // Returns width that is as close as possible to |target_width| while being
242 // consistent with docked min and max restrictions and respects the |window|'s
243 // minimum and maximum size.
244 int GetWindowWidthCloseTo(const wm::WmWindow* window, int target_width) {
245 if (!window->GetWindowState()->CanResize()) {
246 DCHECK_LE(window->GetBounds().width(),
247 DockedWindowLayoutManager::kMaxDockWidth);
248 return window->GetBounds().width();
249 }
250 int width = std::max(DockedWindowLayoutManager::kMinDockWidth,
251 std::min(target_width,
252 DockedWindowLayoutManager::kMaxDockWidth));
253 width = std::max(width, window->GetMinimumSize().width());
254 if (window->GetMaximumSize().width() != 0)
255 width = std::min(width, window->GetMaximumSize().width());
256 DCHECK_LE(width, DockedWindowLayoutManager::kMaxDockWidth);
257 return width;
258 }
259
260 // Returns height that is as close as possible to |target_height| while
261 // respecting the |window|'s minimum and maximum size.
262 int GetWindowHeightCloseTo(const wm::WmWindow* window, int target_height) {
263 if (!window->GetWindowState()->CanResize())
264 return window->GetBounds().height();
265 int minimum_height =
266 std::max(kMinimumHeight, window->GetMinimumSize().height());
267 int maximum_height = window->GetMaximumSize().height();
268 if (minimum_height)
269 target_height = std::max(target_height, minimum_height);
270 if (maximum_height)
271 target_height = std::min(target_height, maximum_height);
272 return target_height;
273 }
274
275 } // namespace
276
277 struct DockedWindowLayoutManager::WindowWithHeight {
278 explicit WindowWithHeight(wm::WmWindow* window)
279 : window(window), height(window->GetBounds().height()) {}
280 wm::WmWindow* window;
281 int height;
282 };
283
284 // A functor used to sort the windows in order of their minimum height.
285 struct DockedWindowLayoutManager::CompareMinimumHeight {
286 bool operator()(const WindowWithHeight& win1, const WindowWithHeight& win2) {
287 return GetWindowHeightCloseTo(win1.window, 0) <
288 GetWindowHeightCloseTo(win2.window, 0);
289 }
290 };
291
292 // A functor used to sort the windows in order of their center Y position.
293 // |delta| is a pre-calculated distance from the bottom of one window to the top
294 // of the next. Its value can be positive (gap) or negative (overlap).
295 // Half of |delta| is used as a transition point at which windows could ideally
296 // swap positions.
297 struct DockedWindowLayoutManager::CompareWindowPos {
298 CompareWindowPos(wm::WmWindow* dragged_window,
299 wm::WmWindow* docked_container,
300 float delta)
301 : dragged_window_(dragged_window),
302 docked_container_(docked_container),
303 delta_(delta / 2) {}
304
305 bool operator()(const WindowWithHeight& window_with_height1,
306 const WindowWithHeight& window_with_height2) {
307 // Use target coordinates since animations may be active when windows are
308 // reordered.
309 wm::WmWindow* win1(window_with_height1.window);
310 wm::WmWindow* win2(window_with_height2.window);
311 gfx::Rect win1_bounds =
312 docked_container_->ConvertRectToScreen(win1->GetTargetBounds());
313 gfx::Rect win2_bounds =
314 docked_container_->ConvertRectToScreen(win2->GetTargetBounds());
315 win1_bounds.set_height(window_with_height1.height);
316 win2_bounds.set_height(window_with_height2.height);
317 // If one of the windows is the |dragged_window_| attempt to make an
318 // earlier swap between the windows than just based on their centers.
319 // This is possible if the dragged window is at least as tall as the other
320 // window.
321 if (win1 == dragged_window_)
322 return compare_two_windows(win1_bounds, win2_bounds);
323 if (win2 == dragged_window_)
324 return !compare_two_windows(win2_bounds, win1_bounds);
325 // Otherwise just compare the centers.
326 return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y();
327 }
328
329 // Based on center point tries to deduce where the drag is coming from.
330 // When dragging from below up the transition point is lower.
331 // When dragging from above down the transition point is higher.
332 bool compare_bounds(const gfx::Rect& dragged, const gfx::Rect& other) {
333 if (dragged.CenterPoint().y() < other.CenterPoint().y())
334 return dragged.CenterPoint().y() < other.y() - delta_;
335 return dragged.CenterPoint().y() < other.bottom() + delta_;
336 }
337
338 // Performs comparison both ways and selects stable result.
339 bool compare_two_windows(const gfx::Rect& bounds1, const gfx::Rect& bounds2) {
340 // Try comparing windows in both possible orders and see if the comparison
341 // is stable.
342 bool result1 = compare_bounds(bounds1, bounds2);
343 bool result2 = compare_bounds(bounds2, bounds1);
344 if (result1 != result2)
345 return result1;
346
347 // Otherwise it is not possible to be sure that the windows will not bounce.
348 // In this case just compare the centers.
349 return bounds1.CenterPoint().y() < bounds2.CenterPoint().y();
350 }
351
352 private:
353 wm::WmWindow* dragged_window_;
354 wm::WmWindow* docked_container_;
355 float delta_;
356 };
357
358 ////////////////////////////////////////////////////////////////////////////////
359 // A class that observes shelf for bounds changes.
360 class DockedWindowLayoutManager::ShelfWindowObserver
361 : public wm::WmWindowObserver {
362 public:
363 explicit ShelfWindowObserver(
364 DockedWindowLayoutManager* docked_layout_manager)
365 : docked_layout_manager_(docked_layout_manager) {
366 DCHECK(docked_layout_manager_->shelf()->GetWindow());
367 docked_layout_manager_->shelf()->GetWindow()->AddObserver(this);
368 }
369
370 ~ShelfWindowObserver() override {
371 if (docked_layout_manager_->shelf() &&
372 docked_layout_manager_->shelf()->GetWindow()) {
373 docked_layout_manager_->shelf()->GetWindow()->RemoveObserver(this);
374 }
375 }
376
377 // wm::WmWindowObserver:
378 void OnWindowBoundsChanged(wm::WmWindow* window,
379 const gfx::Rect& old_bounds,
380 const gfx::Rect& new_bounds) override {
381 shelf_bounds_in_screen_ =
382 window->GetParent()->ConvertRectToScreen(new_bounds);
383 docked_layout_manager_->OnShelfBoundsChanged();
384 }
385
386 const gfx::Rect& shelf_bounds_in_screen() const {
387 return shelf_bounds_in_screen_;
388 }
389
390 private:
391 DockedWindowLayoutManager* docked_layout_manager_;
392 gfx::Rect shelf_bounds_in_screen_;
393
394 DISALLOW_COPY_AND_ASSIGN(ShelfWindowObserver);
395 };
396
397 ////////////////////////////////////////////////////////////////////////////////
398 // DockedWindowLayoutManager public implementation:
399 DockedWindowLayoutManager::DockedWindowLayoutManager(
400 wm::WmWindow* dock_container)
401 : dock_container_(dock_container),
402 root_window_controller_(dock_container->GetRootWindowController()),
403 in_layout_(false),
404 dragged_window_(nullptr),
405 is_dragged_window_docked_(false),
406 is_dragged_from_dock_(false),
407 shelf_(nullptr),
408 in_fullscreen_(root_window_controller_->GetWorkspaceWindowState() ==
409 wm::WORKSPACE_WINDOW_STATE_FULL_SCREEN),
410 docked_width_(0),
411 alignment_(DOCKED_ALIGNMENT_NONE),
412 preferred_alignment_(DOCKED_ALIGNMENT_NONE),
413 event_source_(DOCKED_ACTION_SOURCE_UNKNOWN),
414 last_active_window_(nullptr),
415 last_action_time_(base::Time::Now()),
416 background_widget_(nullptr) {
417 DCHECK(dock_container);
418 dock_container->GetGlobals()->AddActivationObserver(this);
419 root_window_controller_->AddObserver(this);
420 }
421
422 DockedWindowLayoutManager::~DockedWindowLayoutManager() {
423 Shutdown();
424 }
425
426 // static
427 DockedWindowLayoutManager* DockedWindowLayoutManager::Get(
428 wm::WmWindow* window) {
429 if (!window)
430 return nullptr;
431
432 wm::WmWindow* root = window->GetRootWindow();
433 return static_cast<DockedWindowLayoutManager*>(
434 root->GetChildByShellWindowId(kShellWindowId_DockedContainer)
435 ->GetLayoutManager());
436 }
437
438 void DockedWindowLayoutManager::Shutdown() {
439 background_widget_.reset();
440 shelf_observer_.reset();
441 shelf_ = nullptr;
442 for (wm::WmWindow* child : dock_container_->GetChildren()) {
443 child->RemoveObserver(this);
444 child->GetWindowState()->RemoveObserver(this);
445 }
446 dock_container_->GetGlobals()->RemoveActivationObserver(this);
447 root_window_controller_->RemoveObserver(this);
448 }
449
450 void DockedWindowLayoutManager::AddObserver(
451 DockedWindowLayoutManagerObserver* observer) {
452 observer_list_.AddObserver(observer);
453 }
454
455 void DockedWindowLayoutManager::RemoveObserver(
456 DockedWindowLayoutManagerObserver* observer) {
457 observer_list_.RemoveObserver(observer);
458 }
459
460 void DockedWindowLayoutManager::StartDragging(wm::WmWindow* window) {
461 DCHECK(!dragged_window_);
462 dragged_window_ = window;
463 DCHECK(!IsPopupOrTransient(window));
464 // Start observing a window unless it is docked container's child in which
465 // case it is already observed.
466 wm::WindowState* dragged_state = dragged_window_->GetWindowState();
467 if (dragged_window_->GetParent() != dock_container_) {
468 dragged_window_->AddObserver(this);
469 dragged_state->AddObserver(this);
470 } else if (!IsAnyWindowDocked() &&
471 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 = window_state->drag_details() &&
598 window_state->drag_details()->should_attach_to_shelf;
599 if (IsPopupOrTransient(window) || should_attach_to_shelf)
600 return false;
601 // If a window is wide and cannot be resized down to maximum width allowed
602 // then it cannot be docked.
603 // TODO(varkha). Prevent windows from changing size programmatically while
604 // they are docked. The size will take effect only once a window is undocked.
605 // See http://crbug.com/307792.
606 if (window->GetBounds().width() > kMaxDockWidth &&
607 (!window_state->CanResize() ||
608 (window->GetMinimumSize().width() != 0 &&
609 window->GetMinimumSize().width() > kMaxDockWidth))) {
610 return false;
611 }
612 // If a window is tall and cannot be resized down to maximum height allowed
613 // then it cannot be docked.
614 const gfx::Rect work_area =
615 dock_container_->GetDisplayNearestWindow().work_area();
616 if (GetWindowHeightCloseTo(window, work_area.height()) > work_area.height())
617 return false;
618 // Cannot dock on the other size from an existing dock.
619 const DockedAlignment alignment = CalculateAlignmentExcept(window);
620 if (desired_alignment != DOCKED_ALIGNMENT_NONE &&
621 alignment != DOCKED_ALIGNMENT_NONE &&
622 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 ||
650 alignment_ == alignment ||
651 !IsDockedAlignmentValid(alignment)) {
652 return;
653 }
654 alignment_ = alignment;
655
656 Relayout();
657 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
658 }
659
660 void DockedWindowLayoutManager::OnShelfBoundsChanged() {
661 Relayout();
662 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED);
663 }
664
665 ////////////////////////////////////////////////////////////////////////////////
666 // DockedWindowLayoutManager, aura::LayoutManager implementation:
667 void DockedWindowLayoutManager::OnWindowResized() {
668 MaybeMinimizeChildrenExcept(dragged_window_);
669 Relayout();
670 // When screen resizes update the insets even when dock width or alignment
671 // does not change.
672 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED);
673 }
674
675 void DockedWindowLayoutManager::OnWindowAddedToLayout(wm::WmWindow* child) {
676 if (IsPopupOrTransient(child))
677 return;
678 // Dragged windows are already observed by StartDragging and do not change
679 // docked alignment during the drag.
680 if (child == dragged_window_)
681 return;
682 // If this is the first window getting docked - update alignment.
683 // A window can be added without proper bounds when window is moved to another
684 // display via API or due to display configuration change, so the alignment
685 // is set based on which edge is closer in the new display.
686 if (alignment_ == DOCKED_ALIGNMENT_NONE) {
687 alignment_ = preferred_alignment_ != DOCKED_ALIGNMENT_NONE ?
688 preferred_alignment_ : 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 = GetWindowHeightCloseTo(window, 0) +
918 (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(),
980 1,
981 base::TimeDelta::FromHours(10).InSeconds(),
982 100);
983 last_action_time_ = time_now;
984 int docked_all_count = 0;
985 int docked_visible_count = 0;
986 int docked_panels_count = 0;
987 int large_windows_count = 0;
988 for (wm::WmWindow* window : dock_container_->GetChildren()) {
989 if (IsPopupOrTransient(window))
990 continue;
991 docked_all_count++;
992 if (!IsWindowDocked(window))
993 continue;
994 docked_visible_count++;
995 if (window->GetType() == ui::wm::WINDOW_TYPE_PANEL)
996 docked_panels_count++;
997 const wm::WindowState* window_state = window->GetWindowState();
998 if (window_state->HasRestoreBounds()) {
999 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen();
1000 if (restore_bounds.width() > kMaxDockWidth)
1001 large_windows_count++;
1002 }
1003 }
1004 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsAll", docked_all_count);
1005 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsLarge", large_windows_count);
1006 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsPanels", docked_panels_count);
1007 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsVisible", docked_visible_count);
1008 }
1009
1010 void DockedWindowLayoutManager::UpdateDockedWidth(int width) {
1011 if (docked_width_ == width)
1012 return;
1013 docked_width_ = width;
1014 UMA_HISTOGRAM_COUNTS_10000("Ash.Dock.Width", docked_width_);
1015 }
1016
1017 void DockedWindowLayoutManager::OnDraggedWindowDocked(wm::WmWindow* window) {
1018 DCHECK(!is_dragged_window_docked_);
1019 is_dragged_window_docked_ = true;
1020 }
1021
1022 void DockedWindowLayoutManager::OnDraggedWindowUndocked() {
1023 DCHECK (is_dragged_window_docked_);
1024 is_dragged_window_docked_ = false;
1025 }
1026
1027 bool DockedWindowLayoutManager::IsAnyWindowDocked() {
1028 return CalculateAlignment() != DOCKED_ALIGNMENT_NONE;
1029 }
1030
1031 DockedAlignment DockedWindowLayoutManager::GetEdgeNearestWindow(
1032 const wm::WmWindow* window) const {
1033 const gfx::Rect bounds(window->GetBoundsInScreen());
1034 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen();
1035 // Give one pixel preference for docking on the right side to a window that
1036 // has odd width and is centered in a screen that has even width (or vice
1037 // versa). This only matters to the tests but could be a source of flakiness.
1038 return (abs(bounds.x() - container_bounds.x()) + 1 <
1039 abs(bounds.right() - container_bounds.right()))
1040 ? DOCKED_ALIGNMENT_LEFT
1041 : DOCKED_ALIGNMENT_RIGHT;
1042 }
1043
1044 void DockedWindowLayoutManager::Relayout() {
1045 if (in_layout_)
1046 return;
1047 if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_)
1048 return;
1049 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
1050
1051 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
1052 wm::WmWindow* active_window = nullptr;
1053 std::vector<WindowWithHeight> visible_windows;
1054 for (wm::WmWindow* window : dock_container_->GetChildren()) {
1055 if (!IsWindowDocked(window) || window == dragged_window_)
1056 continue;
1057
1058 // If the shelf is currently hidden (full-screen mode), hide window until
1059 // full-screen mode is exited.
1060 if (in_fullscreen_) {
1061 // The call to Hide does not set the minimize property, so the window will
1062 // be restored when the shelf becomes visible again.
1063 window->Hide();
1064 continue;
1065 }
1066 if (window->IsFocused() ||
1067 window->Contains(window->GetGlobals()->GetFocusedWindow())) {
1068 DCHECK(!active_window);
1069 active_window = window;
1070 }
1071 visible_windows.push_back(WindowWithHeight(window));
1072 }
1073 // Consider docked dragged_window_ when fanning out other child windows.
1074 if (is_dragged_window_docked_) {
1075 visible_windows.push_back(WindowWithHeight(dragged_window_));
1076 DCHECK(!active_window);
1077 active_window = dragged_window_;
1078 }
1079
1080 // Position docked windows as well as the window being dragged.
1081 gfx::Rect work_area = dock_container_->GetDisplayNearestWindow().work_area();
1082 if (shelf_observer_)
1083 work_area.Subtract(shelf_observer_->shelf_bounds_in_screen());
1084 int available_room = CalculateWindowHeightsAndRemainingRoom(work_area,
1085 &visible_windows);
1086 FanOutChildren(work_area,
1087 CalculateIdealWidth(visible_windows),
1088 available_room,
1089 &visible_windows);
1090
1091 // After the first Relayout allow the windows to change their order easier
1092 // since we know they are docked.
1093 is_dragged_from_dock_ = true;
1094 UpdateStacking(active_window);
1095 }
1096
1097 int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom(
1098 const gfx::Rect& work_area,
1099 std::vector<WindowWithHeight>* visible_windows) {
1100 int available_room = work_area.height();
1101 int remaining_windows = visible_windows->size();
1102 int gap_height = remaining_windows > 1 ? kMinDockGap : 0;
1103
1104 // Sort windows by their minimum heights and calculate target heights.
1105 std::sort(visible_windows->begin(), visible_windows->end(),
1106 CompareMinimumHeight());
1107 // Distribute the free space among the docked windows. Since the windows are
1108 // sorted (tall windows first) we can now assume that any window which
1109 // required more space than the current window will have already been
1110 // accounted for previously in this loop, so we can safely give that window
1111 // its proportional share of the remaining space.
1112 for (std::vector<WindowWithHeight>::reverse_iterator iter =
1113 visible_windows->rbegin();
1114 iter != visible_windows->rend(); ++iter) {
1115 iter->height = GetWindowHeightCloseTo(
1116 iter->window,
1117 (available_room + gap_height) / remaining_windows - gap_height);
1118 available_room -= (iter->height + gap_height);
1119 remaining_windows--;
1120 }
1121 return available_room + gap_height;
1122 }
1123
1124 int DockedWindowLayoutManager::CalculateIdealWidth(
1125 const std::vector<WindowWithHeight>& visible_windows) {
1126 int smallest_max_width = kMaxDockWidth;
1127 int largest_min_width = kMinDockWidth;
1128 // Ideal width of the docked area is as close to kIdealWidth as possible
1129 // while still respecting the minimum and maximum width restrictions on the
1130 // individual docked windows as well as the width that was possibly set by a
1131 // user (which needs to be preserved when dragging and rearranging windows).
1132 for (std::vector<WindowWithHeight>::const_iterator iter =
1133 visible_windows.begin();
1134 iter != visible_windows.end(); ++iter) {
1135 const wm::WmWindow* window = iter->window;
1136 int min_window_width = window->GetBounds().width();
1137 int max_window_width = min_window_width;
1138 if (!window->GetWindowState()->bounds_changed_by_user()) {
1139 min_window_width = GetWindowWidthCloseTo(window, kMinDockWidth);
1140 max_window_width = GetWindowWidthCloseTo(window, kMaxDockWidth);
1141 }
1142 largest_min_width = std::max(largest_min_width, min_window_width);
1143 smallest_max_width = std::min(smallest_max_width, max_window_width);
1144 }
1145 int ideal_width = std::max(largest_min_width,
1146 std::min(smallest_max_width, kIdealWidth));
1147 // Restrict docked area width regardless of window restrictions.
1148 ideal_width = std::max(std::min(ideal_width, kMaxDockWidth), kMinDockWidth);
1149 return ideal_width;
1150 }
1151
1152 void DockedWindowLayoutManager::FanOutChildren(
1153 const gfx::Rect& work_area,
1154 int ideal_docked_width,
1155 int available_room,
1156 std::vector<WindowWithHeight>* visible_windows) {
1157 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
1158
1159 // Calculate initial vertical offset and the gap or overlap between windows.
1160 const int num_windows = visible_windows->size();
1161 const float delta = static_cast<float>(available_room) /
1162 ((available_room > 0 || num_windows <= 1) ?
1163 num_windows + 1 : num_windows - 1);
1164 float y_pos = work_area.y() + ((delta > 0) ? delta : 0);
1165
1166 // Docked area is shown only if there is at least one non-dragged visible
1167 // docked window.
1168 int new_width = ideal_docked_width;
1169 if (visible_windows->empty() ||
1170 (visible_windows->size() == 1 &&
1171 (*visible_windows)[0].window == dragged_window_)) {
1172 new_width = 0;
1173 }
1174 UpdateDockedWidth(new_width);
1175 // Sort windows by their center positions and fan out overlapping
1176 // windows.
1177 std::sort(visible_windows->begin(), visible_windows->end(),
1178 CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : nullptr,
1179 dock_container_, delta));
1180 for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin();
1181 iter != visible_windows->end(); ++iter) {
1182 wm::WmWindow* window = iter->window;
1183 gfx::Rect bounds =
1184 dock_container_->ConvertRectToScreen(window->GetTargetBounds());
1185 // A window is extended or shrunk to be as close as possible to the ideal
1186 // docked area width. Windows that were resized by a user are kept at their
1187 // existing size.
1188 // This also enforces the min / max restrictions on the docked area width.
1189 bounds.set_width(GetWindowWidthCloseTo(
1190 window, window->GetWindowState()->bounds_changed_by_user()
1191 ? bounds.width()
1192 : ideal_docked_width));
1193 DCHECK_LE(bounds.width(), ideal_docked_width);
1194
1195 DockedAlignment alignment = alignment_;
1196 if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_)
1197 alignment = GetEdgeNearestWindow(window);
1198
1199 // Fan out windows evenly distributing the overlap or remaining free space.
1200 bounds.set_height(iter->height);
1201 bounds.set_y(std::max(work_area.y(),
1202 std::min(work_area.bottom() - bounds.height(),
1203 static_cast<int>(y_pos + 0.5))));
1204 y_pos += bounds.height() + delta + kMinDockGap;
1205
1206 // All docked windows other than the one currently dragged remain stuck
1207 // to the screen edge (flush with the edge or centered in the dock area).
1208 switch (alignment) {
1209 case DOCKED_ALIGNMENT_LEFT:
1210 bounds.set_x(dock_bounds.x() +
1211 (ideal_docked_width - bounds.width()) / 2);
1212 break;
1213 case DOCKED_ALIGNMENT_RIGHT:
1214 bounds.set_x(dock_bounds.right() -
1215 (ideal_docked_width + bounds.width()) / 2);
1216 break;
1217 case DOCKED_ALIGNMENT_NONE:
1218 break;
1219 }
1220 if (window == dragged_window_) {
1221 dragged_bounds_ = bounds;
1222 continue;
1223 }
1224 // If the following asserts it is probably because not all the children
1225 // have been removed when dock was closed.
1226 DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE);
1227 bounds = dock_container_->ConvertRectFromScreen(bounds);
1228 if (bounds != window->GetTargetBounds()) {
1229 ui::Layer* layer = window->GetLayer();
1230 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
1231 slide_settings.SetPreemptionStrategy(
1232 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
1233 slide_settings.SetTransitionDuration(
1234 base::TimeDelta::FromMilliseconds(kSlideDurationMs));
1235 window->SetBoundsDirect(bounds);
1236 }
1237 }
1238 }
1239
1240 void DockedWindowLayoutManager::UpdateDockBounds(
1241 DockedWindowLayoutManagerObserver::Reason reason) {
1242 int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0);
1243 const gfx::Rect work_area =
1244 dock_container_->GetDisplayNearestWindow().work_area();
1245 gfx::Rect bounds = gfx::Rect(
1246 alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0
1247 ? dock_container_->GetBounds().right() - dock_inset
1248 : dock_container_->GetBounds().x(),
1249 dock_container_->GetBounds().y(), dock_inset, work_area.height());
1250 docked_bounds_ = bounds +
1251 dock_container_->GetBoundsInScreen().OffsetFromOrigin();
1252 FOR_EACH_OBSERVER(
1253 DockedWindowLayoutManagerObserver,
1254 observer_list_,
1255 OnDockBoundsChanging(bounds, reason));
1256 // Show or hide background for docked area.
1257 gfx::Rect background_bounds(docked_bounds_);
1258 if (shelf_observer_)
1259 background_bounds.Subtract(shelf_observer_->shelf_bounds_in_screen());
1260 if (docked_width_ > 0) {
1261 if (!background_widget_)
1262 background_widget_.reset(new DockedBackgroundWidget(this));
1263 background_widget_->SetBackgroundBounds(background_bounds, alignment_);
1264 background_widget_->Show();
1265 } else if (background_widget_) {
1266 background_widget_->Hide();
1267 }
1268 }
1269
1270 void DockedWindowLayoutManager::UpdateStacking(wm::WmWindow* active_window) {
1271 if (!active_window) {
1272 if (!last_active_window_)
1273 return;
1274 active_window = last_active_window_;
1275 }
1276
1277 // Windows are stacked like a deck of cards:
1278 // ,------.
1279 // |,------.|
1280 // |,------.|
1281 // | active |
1282 // | window |
1283 // |`------'|
1284 // |`------'|
1285 // `------'
1286 // Use the middle of each window to figure out how to stack the window.
1287 // This allows us to update the stacking when a window is being dragged around
1288 // by the titlebar.
1289 std::map<int, wm::WmWindow*> window_ordering;
1290 for (wm::WmWindow* child : dock_container_->GetChildren()) {
1291 if (!IsWindowDocked(child) ||
1292 (child == dragged_window_ && !is_dragged_window_docked_)) {
1293 continue;
1294 }
1295 gfx::Rect bounds = child->GetBounds();
1296 window_ordering.insert(
1297 std::make_pair(bounds.y() + bounds.height() / 2, child));
1298 }
1299 int active_center_y = active_window->GetBounds().CenterPoint().y();
1300
1301 wm::WmWindow* previous_window = nullptr;
1302 for (std::map<int, wm::WmWindow*>::const_iterator it =
1303 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, wm::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 } // namespace ash
OLDNEW
« no previous file with comments | « ash/wm/dock/docked_window_layout_manager.h ('k') | ash/wm/dock/docked_window_layout_manager_observer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698