1 // Copyright 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/window_positioner.h"
6
7 #include "ash/screen_util.h"
8 #include "ash/shell.h"
9 #include "ash/shell_delegate.h"
10 #include "ash/wm/mru_window_tracker.h"
11 #include "ash/wm/window_positioning_utils.h"
12 #include "ash/wm/window_state.h"
13 #include "ash/wm/window_util.h"
14 #include "ui/compositor/layer.h"
15 #include "ui/compositor/scoped_layer_animation_settings.h"
16 #include "ui/display/display.h"
17 #include "ui/display/screen.h"
18 #include "ui/gfx/geometry/insets.h"
19 #include "ui/wm/core/window_animations.h"
20 #include "ui/wm/core/window_util.h"
21
22 namespace ash {
23 namespace {
24
25 // The time in milliseconds which should be used to visually move a window
26 // through an automatic "intelligent" window management option.
27 const int kWindowAutoMoveDurationMS = 125;
28
29 // If set to true all window repositioning actions will be ignored. Set through
30 // WindowPositioner::SetIgnoreActivations().
31 static bool disable_auto_positioning = false;
32
33 // Check if any management should be performed (with a given |window|).
UseAutoWindowManager(const aura::Window * window)34 bool UseAutoWindowManager(const aura::Window* window) {
35 if (disable_auto_positioning)
36 return false;
37 const WindowState* window_state = WindowState::Get(window);
38 return !window_state->is_dragged() &&
39 window_state->GetWindowPositionManaged();
40 }
41
42 // Check if a given |window| can be managed. This includes that its
43 // state is not minimized/maximized/fullscreen/the user has changed
44 // its size by hand already. It furthermore checks for the
45 // WindowIsManaged status.
WindowPositionCanBeManaged(const aura::Window * window)46 bool WindowPositionCanBeManaged(const aura::Window* window) {
47 if (disable_auto_positioning)
48 return false;
49 const WindowState* window_state = WindowState::Get(window);
50 return window_state->GetWindowPositionManaged() &&
51 !window_state->IsMinimized() && !window_state->IsMaximized() &&
52 !window_state->IsFullscreen() && !window_state->IsPinned() &&
53 !window_state->bounds_changed_by_user();
54 }
55
56 // Move the given |bounds| on the available |work_area| in the direction
57 // indicated by |move_right|. If |move_right| is true, the rectangle gets moved
58 // to the right edge, otherwise to the left one.
MoveRectToOneSide(const gfx::Rect & work_area,bool move_right,gfx::Rect * bounds)59 bool MoveRectToOneSide(const gfx::Rect& work_area,
60 bool move_right,
61 gfx::Rect* bounds) {
62 if (move_right) {
63 if (work_area.right() > bounds->right()) {
64 bounds->set_x(work_area.right() - bounds->width());
65 return true;
66 }
67 } else {
68 if (work_area.x() < bounds->x()) {
69 bounds->set_x(work_area.x());
70 return true;
71 }
72 }
73 return false;
74 }
75
76 // Move a |window| to new |bounds|. Animate if desired by user.
77 // Moves the transient children of the |window| as well by the same |offset| as
78 // the parent |window|.
SetBoundsAndOffsetTransientChildren(aura::Window * window,const gfx::Rect & bounds,const gfx::Rect & work_area,const gfx::Vector2d & offset)79 void SetBoundsAndOffsetTransientChildren(aura::Window* window,
80 const gfx::Rect& bounds,
81 const gfx::Rect& work_area,
82 const gfx::Vector2d& offset) {
83 aura::Window::Windows transient_children = ::wm::GetTransientChildren(window);
84 for (auto* transient_child : transient_children) {
85 gfx::Rect child_bounds = transient_child->bounds();
86 gfx::Rect new_child_bounds = child_bounds + offset;
87 if ((child_bounds.x() <= work_area.x() &&
88 new_child_bounds.x() <= work_area.x()) ||
89 (child_bounds.right() >= work_area.right() &&
90 new_child_bounds.right() >= work_area.right())) {
91 continue;
92 }
93 if (new_child_bounds.right() > work_area.right())
94 new_child_bounds.set_x(work_area.right() - bounds.width());
95 else if (new_child_bounds.x() < work_area.x())
96 new_child_bounds.set_x(work_area.x());
97 SetBoundsAndOffsetTransientChildren(transient_child, new_child_bounds,
98 work_area, offset);
99 }
100
101 if (::wm::WindowAnimationsDisabled(window)) {
102 window->SetBounds(bounds);
103 return;
104 }
105
106 ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
107 settings.SetTransitionDuration(
108 base::TimeDelta::FromMilliseconds(kWindowAutoMoveDurationMS));
109 window->SetBounds(bounds);
110 }
111
112 // Move a |window| to new |bounds|. Animate if desired by user.
113 // Note: The function will do nothing if the bounds did not change.
SetBoundsAnimated(aura::Window * window,const gfx::Rect & bounds,const gfx::Rect & work_area)114 void SetBoundsAnimated(aura::Window* window,
115 const gfx::Rect& bounds,
116 const gfx::Rect& work_area) {
117 gfx::Rect old_bounds = window->GetTargetBounds();
118 if (bounds == old_bounds)
119 return;
120 gfx::Vector2d offset(bounds.origin() - old_bounds.origin());
121 SetBoundsAndOffsetTransientChildren(window, bounds, work_area, offset);
122 }
123
124 // Move |window| into the center of the screen - or restore it to the previous
125 // position.
AutoPlaceSingleWindow(aura::Window * window,bool animated)126 void AutoPlaceSingleWindow(aura::Window* window, bool animated) {
127 gfx::Rect work_area = screen_util::GetDisplayWorkAreaBoundsInParent(window);
128 gfx::Rect bounds = window->bounds();
129 const base::Optional<gfx::Rect> user_defined_area =
130 WindowState::Get(window)->pre_auto_manage_window_bounds();
131 if (user_defined_area) {
132 bounds = *user_defined_area;
133 AdjustBoundsToEnsureMinimumWindowVisibility(work_area, &bounds);
134 } else {
135 // Center the window (only in x).
136 bounds.set_x(work_area.x() + (work_area.width() - bounds.width()) / 2);
137 }
138
139 if (animated)
140 SetBoundsAnimated(window, bounds, work_area);
141 else
142 window->SetBounds(bounds);
143 }
144
145 // Get the first open (non minimized) window which is on the screen defined.
GetReferenceWindow(const aura::Window * root_window,const aura::Window * exclude,bool * single_window)146 aura::Window* GetReferenceWindow(const aura::Window* root_window,
147 const aura::Window* exclude,
148 bool* single_window) {
149 if (single_window)
150 *single_window = true;
151 // Get the active window.
152 aura::Window* active = window_util::GetActiveWindow();
153 if (active && active->GetRootWindow() != root_window)
154 active = NULL;
155
156 // Get a list of all windows.
157 const aura::Window::Windows windows =
158 Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
159 kActiveDesk);
160
161 if (windows.empty())
162 return nullptr;
163
164 int index = 0;
165 // Find the index of the current active window.
166 if (active)
167 index = std::find(windows.begin(), windows.end(), active) - windows.begin();
168
169 // Scan the cycle list backwards to see which is the second topmost window
170 // (and so on). Note that we might cycle a few indices twice if there is no
171 // suitable window. However - since the list is fairly small this should be
172 // very fast anyways.
173 aura::Window* found = nullptr;
174 for (int i = index + windows.size(); i >= 0; i--) {
175 aura::Window* window = windows[i % windows.size()];
176 while (::wm::GetTransientParent(window))
177 window = ::wm::GetTransientParent(window);
178 if (window != exclude &&
179 window->type() == aura::client::WINDOW_TYPE_NORMAL &&
180 window->GetRootWindow() == root_window && window->TargetVisibility() &&
181 WindowState::Get(window)->GetWindowPositionManaged()) {
182 if (found && found != window) {
183 // no need to check !single_window because the function must have
184 // been already returned in the "if (!single_window)" below.
185 *single_window = false;
186 return found;
187 }
188 found = window;
189 // If there is no need to check single window, return now.
190 if (!single_window)
191 return found;
192 }
193 }
194 return found;
195 }
196
197 } // namespace
198
199 // static
GetBoundsAndShowStateForNewWindow(bool is_saved_bounds,ui::WindowShowState show_state_in,gfx::Rect * bounds_in_out,ui::WindowShowState * show_state_out)200 void WindowPositioner::GetBoundsAndShowStateForNewWindow(
201 bool is_saved_bounds,
202 ui::WindowShowState show_state_in,
203 gfx::Rect* bounds_in_out,
204 ui::WindowShowState* show_state_out) {
205 aura::Window* root_window = Shell::GetRootWindowForNewWindows();
206 aura::Window* top_window = GetReferenceWindow(root_window, nullptr, nullptr);
207
208 // If there is no valid window we take and adjust the passed coordinates.
209 if (!top_window) {
210 gfx::Rect work_area = display::Screen::GetScreen()
211 ->GetDisplayNearestWindow(root_window)
212 .work_area();
213 bounds_in_out->AdjustToFit(work_area);
214 return;
215 }
216
217 WindowState* top_window_state = WindowState::Get(top_window);
218 bool maximized = top_window_state->IsMaximized();
219 // We ignore the saved show state, but look instead for the top level
220 // window's show state.
221 if (show_state_in == ui::SHOW_STATE_DEFAULT) {
222 *show_state_out =
223 maximized ? ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_DEFAULT;
224 }
225
226 if (maximized || top_window_state->IsFullscreen()) {
227 bool has_restore_bounds = top_window_state->HasRestoreBounds();
228 if (has_restore_bounds) {
229 // For a maximized/fullscreen window ignore the real bounds of
230 // the top level window and use its restore bounds
231 // instead. Offset the bounds to prevent the windows from
232 // overlapping exactly when restored.
233 *bounds_in_out = top_window_state->GetRestoreBoundsInScreen() +
234 gfx::Vector2d(kWindowOffset, kWindowOffset);
235 }
236 if (is_saved_bounds || has_restore_bounds) {
237 gfx::Rect work_area = display::Screen::GetScreen()
238 ->GetDisplayNearestWindow(root_window)
239 .work_area();
240 bounds_in_out->AdjustToFit(work_area);
241 // Use adjusted saved bounds or restore bounds, if there is one.
242 return;
243 }
244 }
245
246 // Use the size of the other window. The window's bound will be rearranged
247 // in ash::WorkspaceLayoutManager using this location.
248 *bounds_in_out = top_window->GetBoundsInScreen();
249 }
250
251 // static
RearrangeVisibleWindowOnHideOrRemove(const aura::Window * removed_window)252 void WindowPositioner::RearrangeVisibleWindowOnHideOrRemove(
253 const aura::Window* removed_window) {
254 if (!UseAutoWindowManager(removed_window))
255 return;
256 // Find a single open browser window.
257 bool single_window;
258 aura::Window* other_shown_window = GetReferenceWindow(
259 removed_window->GetRootWindow(), removed_window, &single_window);
260 if (!other_shown_window || !single_window ||
261 !WindowPositionCanBeManaged(other_shown_window))
262 return;
263 AutoPlaceSingleWindow(other_shown_window, true);
264 }
265
266 // static
DisableAutoPositioning(bool ignore)267 bool WindowPositioner::DisableAutoPositioning(bool ignore) {
268 bool old_state = disable_auto_positioning;
269 disable_auto_positioning = ignore;
270 return old_state;
271 }
272
273 // static
RearrangeVisibleWindowOnShow(aura::Window * added_window)274 void WindowPositioner::RearrangeVisibleWindowOnShow(
275 aura::Window* added_window) {
276 WindowState* added_window_state = WindowState::Get(added_window);
277 if (!added_window->TargetVisibility() ||
278 !UseAutoWindowManager(added_window) ||
279 added_window_state->bounds_changed_by_user()) {
280 return;
281 }
282
283 // Find a single open managed window.
284 bool single_window;
285 aura::Window* other_shown_window = GetReferenceWindow(
286 added_window->GetRootWindow(), added_window, &single_window);
287
288 if (!other_shown_window) {
289 // It could be that this window is the first window joining the workspace.
290 if (!WindowPositionCanBeManaged(added_window) || other_shown_window)
291 return;
292 // Since we might be going from 0 to 1 window, we have to arrange the new
293 // window to a good default.
294 AutoPlaceSingleWindow(added_window, false);
295 return;
296 }
297
298 gfx::Rect other_bounds = other_shown_window->bounds();
299 gfx::Rect work_area =
300 screen_util::GetDisplayWorkAreaBoundsInParent(added_window);
301 bool move_other_right =
302 other_bounds.CenterPoint().x() > work_area.x() + work_area.width() / 2;
303
304 // Push the other window to the size only if there are two windows left.
305 if (single_window) {
306 // When going from one to two windows both windows loose their
307 // "positioned by user" flags.
308 added_window_state->set_bounds_changed_by_user(false);
309 WindowState* other_window_state = WindowState::Get(other_shown_window);
310 other_window_state->set_bounds_changed_by_user(false);
311
312 if (WindowPositionCanBeManaged(other_shown_window)) {
313 // Don't override pre auto managed bounds as the current bounds
314 // may not be original.
315 if (!other_window_state->pre_auto_manage_window_bounds())
316 other_window_state->SetPreAutoManageWindowBounds(other_bounds);
317
318 // Push away the other window after remembering its current position.
319 if (MoveRectToOneSide(work_area, move_other_right, &other_bounds))
320 SetBoundsAnimated(other_shown_window, other_bounds, work_area);
321 }
322 }
323
324 // Remember the current location of the window if it's new and push
325 // it also to the opposite location if needed. Since it is just
326 // being shown, we do not need to animate it.
327 gfx::Rect added_bounds = added_window->bounds();
328 if (!added_window_state->pre_auto_manage_window_bounds())
329 added_window_state->SetPreAutoManageWindowBounds(added_bounds);
330 if (MoveRectToOneSide(work_area, !move_other_right, &added_bounds))
331 added_window->SetBounds(added_bounds);
332 }
333
334 WindowPositioner::WindowPositioner() = default;
335
336 WindowPositioner::~WindowPositioner() = default;
337
338 } // namespace ash
339