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