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