window_positioner.cc 13 KB

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