window_positioning_utils.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // Copyright 2016 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_positioning_utils.h"
  5. #include <algorithm>
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/display/display_util.h"
  8. #include "ash/public/cpp/window_properties.h"
  9. #include "ash/root_window_controller.h"
  10. #include "ash/screen_util.h"
  11. #include "ash/shell.h"
  12. #include "ash/wm/system_modal_container_layout_manager.h"
  13. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  14. #include "ash/wm/window_properties.h"
  15. #include "ash/wm/window_state.h"
  16. #include "ash/wm/window_util.h"
  17. #include "ash/wm/wm_event.h"
  18. #include "base/cxx17_backports.h"
  19. #include "base/notreached.h"
  20. #include "base/numerics/ranges.h"
  21. #include "chromeos/ui/wm/features.h"
  22. #include "ui/aura/client/focus_client.h"
  23. #include "ui/aura/window.h"
  24. #include "ui/aura/window_delegate.h"
  25. #include "ui/aura/window_tracker.h"
  26. #include "ui/display/display.h"
  27. #include "ui/display/screen.h"
  28. #include "ui/display/types/display_constants.h"
  29. #include "ui/gfx/geometry/rect.h"
  30. #include "ui/gfx/geometry/size.h"
  31. #include "ui/wm/core/window_util.h"
  32. namespace ash {
  33. namespace {
  34. int GetSnappedWindowAxisLength(float snap_ratio,
  35. int work_area_axis_length,
  36. int min_axis_length,
  37. bool is_primary_snap) {
  38. DCHECK_GT(snap_ratio, 0);
  39. DCHECK_LE(snap_ratio, 1.f);
  40. min_axis_length = std::min(min_axis_length, work_area_axis_length);
  41. // The primary snap size is proportional to |snap_ratio|.
  42. if (is_primary_snap) {
  43. return base::clamp(static_cast<int>(snap_ratio * work_area_axis_length),
  44. min_axis_length, work_area_axis_length);
  45. }
  46. // The secondary snap size is proportional to the |snap_ratio|, but
  47. // we want to make sure there is no gap between the primary and secondary
  48. // windows when their |snap_ratio|'s sum up to 1. Thus to avoid a gap from
  49. // integer rounding up issue, we compute the empty-space size and subtracted
  50. // it from |work_area_axis_length|. An example test is
  51. // `WindowPositioningUtilsTest.SnapBoundsWithOddNumberedScreenWidth`.
  52. const int empty_space_axis_length =
  53. static_cast<int>((1 - snap_ratio) * work_area_axis_length);
  54. return base::clamp(work_area_axis_length - empty_space_axis_length,
  55. min_axis_length, work_area_axis_length);
  56. }
  57. // Return true if the window or one of its ancestor returns true from
  58. // IsLockedToRoot().
  59. bool IsWindowOrAncestorLockedToRoot(const aura::Window* window) {
  60. return window && (window->GetProperty(kLockedToRootKey) ||
  61. IsWindowOrAncestorLockedToRoot(window->parent()));
  62. }
  63. } // namespace
  64. void AdjustBoundsSmallerThan(const gfx::Size& max_size, gfx::Rect* bounds) {
  65. bounds->set_width(std::min(bounds->width(), max_size.width()));
  66. bounds->set_height(std::min(bounds->height(), max_size.height()));
  67. }
  68. void AdjustBoundsToEnsureWindowVisibility(const gfx::Rect& visible_area,
  69. int min_width,
  70. int min_height,
  71. gfx::Rect* bounds) {
  72. AdjustBoundsSmallerThan(visible_area.size(), bounds);
  73. min_width = std::min(min_width, visible_area.width());
  74. min_height = std::min(min_height, visible_area.height());
  75. if (bounds->right() < visible_area.x() + min_width) {
  76. bounds->set_x(visible_area.x() + std::min(bounds->width(), min_width) -
  77. bounds->width());
  78. } else if (bounds->x() > visible_area.right() - min_width) {
  79. bounds->set_x(visible_area.right() - std::min(bounds->width(), min_width));
  80. }
  81. if (bounds->bottom() < visible_area.y() + min_height) {
  82. bounds->set_y(visible_area.y() + std::min(bounds->height(), min_height) -
  83. bounds->height());
  84. } else if (bounds->y() > visible_area.bottom() - min_height) {
  85. bounds->set_y(visible_area.bottom() -
  86. std::min(bounds->height(), min_height));
  87. }
  88. if (bounds->y() < visible_area.y())
  89. bounds->set_y(visible_area.y());
  90. }
  91. void AdjustBoundsToEnsureMinimumWindowVisibility(const gfx::Rect& visible_area,
  92. gfx::Rect* bounds) {
  93. AdjustBoundsToEnsureWindowVisibility(visible_area, kMinimumOnScreenArea,
  94. kMinimumOnScreenArea, bounds);
  95. }
  96. gfx::Rect GetSnappedWindowBoundsInParent(aura::Window* window,
  97. SnapViewType type,
  98. float snap_ratio) {
  99. return GetSnappedWindowBounds(
  100. screen_util::GetDisplayWorkAreaBoundsInParent(window),
  101. display::Screen::GetScreen()->GetDisplayNearestWindow(window), window,
  102. type, snap_ratio);
  103. }
  104. gfx::Rect GetDefaultSnappedWindowBoundsInParent(aura::Window* window,
  105. SnapViewType type) {
  106. return GetSnappedWindowBoundsInParent(window, type, kDefaultPositionRatio);
  107. }
  108. gfx::Rect GetSnappedWindowBounds(const gfx::Rect& work_area,
  109. const display::Display display,
  110. aura::Window* window,
  111. SnapViewType type,
  112. float snap_ratio) {
  113. chromeos::OrientationType orientation = GetSnapDisplayOrientation(display);
  114. enum class SnapPosition { kLeft, kRight, kBottom, kTop, kInvalid };
  115. SnapPosition position = SnapPosition::kInvalid;
  116. const bool is_primary_snap = type == SnapViewType::kPrimary;
  117. bool is_horizontal = true;
  118. // Find the actual position of window should be snapped to based on
  119. // |orientation| and |type|
  120. switch (orientation) {
  121. case chromeos::OrientationType::kLandscapePrimary:
  122. position = is_primary_snap ? SnapPosition::kLeft : SnapPosition::kRight;
  123. break;
  124. case chromeos::OrientationType::kLandscapeSecondary:
  125. position = is_primary_snap ? SnapPosition::kRight : SnapPosition::kLeft;
  126. break;
  127. case chromeos::OrientationType::kPortraitPrimary:
  128. position = is_primary_snap ? SnapPosition::kTop : SnapPosition::kBottom;
  129. is_horizontal = false;
  130. break;
  131. case chromeos::OrientationType::kPortraitSecondary:
  132. position = is_primary_snap ? SnapPosition::kBottom : SnapPosition::kTop;
  133. is_horizontal = false;
  134. break;
  135. default:
  136. position = SnapPosition::kInvalid;
  137. NOTREACHED();
  138. break;
  139. }
  140. // Compute size of the side of the window bound that should be proportional
  141. // |WindowState::snap_ratio_| to that of the work area, i.e. width for
  142. // horizontal layout and height for vertical layout.
  143. gfx::Rect snap_bounds = gfx::Rect(work_area);
  144. const int work_area_axis_length =
  145. is_horizontal ? work_area.width() : work_area.height();
  146. int min_size = 0;
  147. if (window->delegate()) {
  148. const gfx::Size minimum_size = window->delegate()->GetMinimumSize();
  149. min_size = is_horizontal ? minimum_size.width() : minimum_size.height();
  150. }
  151. int axis_length = GetSnappedWindowAxisLength(
  152. snap_ratio, work_area_axis_length, min_size, is_primary_snap);
  153. const gfx::Size* preferred_size =
  154. window->GetProperty(kUnresizableSnappedSizeKey);
  155. if (preferred_size && !WindowState::Get(window)->CanResize()) {
  156. DCHECK(preferred_size->width() == 0 || preferred_size->height() == 0);
  157. if (is_horizontal && preferred_size->width() > 0)
  158. axis_length = preferred_size->width();
  159. if (!is_horizontal && preferred_size->height() > 0)
  160. axis_length = preferred_size->height();
  161. }
  162. // Set the size of such side and the window position based on a given snap
  163. // position.
  164. switch (position) {
  165. case SnapPosition::kLeft:
  166. snap_bounds.set_width(axis_length);
  167. break;
  168. case SnapPosition::kRight:
  169. snap_bounds.set_width(axis_length);
  170. // Snap to the right.
  171. snap_bounds.set_x(work_area.right() - axis_length);
  172. break;
  173. case SnapPosition::kTop:
  174. snap_bounds.set_height(axis_length);
  175. break;
  176. case SnapPosition::kBottom:
  177. snap_bounds.set_height(axis_length);
  178. // Snap to the bottom.
  179. snap_bounds.set_y(work_area.bottom() - axis_length);
  180. break;
  181. case SnapPosition::kInvalid:
  182. NOTREACHED();
  183. break;
  184. }
  185. return snap_bounds;
  186. }
  187. chromeos::OrientationType GetSnapDisplayOrientation(
  188. const display::Display& display) {
  189. // This function is used by `GetSnappedWindowBounds()` for clamshell mode
  190. // only. Tablet mode uses a different function
  191. // `SplitViewController::GetSnappedWindowBoundsInScreen()`1.
  192. auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
  193. DCHECK(!tablet_mode_controller || !tablet_mode_controller->InTabletMode());
  194. const display::Display::Rotation& rotation =
  195. Shell::Get()
  196. ->display_manager()
  197. ->GetDisplayInfo(display.id())
  198. .GetActiveRotation();
  199. return RotationToOrientation(chromeos::GetDisplayNaturalOrientation(display),
  200. rotation);
  201. }
  202. void CenterWindow(aura::Window* window) {
  203. WMEvent event(WM_EVENT_CENTER);
  204. WindowState::Get(window)->OnWMEvent(&event);
  205. }
  206. void SetBoundsInScreen(aura::Window* window,
  207. const gfx::Rect& bounds_in_screen,
  208. const display::Display& display) {
  209. // Don't move a window to other root window if:
  210. // a) the window is a transient window. It moves when its
  211. // transient parent moves.
  212. // b) if the window or its ancestor has IsLockedToRoot(). It's intentionally
  213. // kept in the same root window even if the bounds is outside of the
  214. // display.
  215. if (!::wm::GetTransientParent(window) &&
  216. !IsWindowOrAncestorLockedToRoot(window)) {
  217. RootWindowController* dst_root_window_controller =
  218. Shell::GetRootWindowControllerWithDisplayId(display.id());
  219. DCHECK(dst_root_window_controller);
  220. aura::Window* dst_root = dst_root_window_controller->GetRootWindow();
  221. DCHECK(dst_root);
  222. aura::Window* dst_container = nullptr;
  223. if (dst_root != window->GetRootWindow()) {
  224. int container_id = window->parent()->GetId();
  225. // All containers that use screen coordinates must have valid window ids.
  226. DCHECK_GE(container_id, 0);
  227. // Don't move modal background.
  228. if (!SystemModalContainerLayoutManager::IsModalBackground(window))
  229. dst_container = dst_root->GetChildById(container_id);
  230. }
  231. if (dst_container && window->parent() != dst_container) {
  232. aura::Window* focused = window_util::GetFocusedWindow();
  233. aura::Window* active = window_util::GetActiveWindow();
  234. aura::WindowTracker tracker;
  235. if (focused)
  236. tracker.Add(focused);
  237. if (active && focused != active)
  238. tracker.Add(active);
  239. // Client controlled window will have its own logic on client side
  240. // to adjust bounds.
  241. // TODO(oshima): Use WM_EVENT_SET_BOUNDS with target display id.
  242. auto* window_state = WindowState::Get(window);
  243. if (!window_state || !window_state->allow_set_bounds_direct()) {
  244. gfx::Point origin = bounds_in_screen.origin();
  245. const gfx::Point display_origin = display.bounds().origin();
  246. origin.Offset(-display_origin.x(), -display_origin.y());
  247. gfx::Rect new_bounds = gfx::Rect(origin, bounds_in_screen.size());
  248. // Set new bounds now so that the container's layout manager can adjust
  249. // the bounds if necessary.
  250. if (window_state)
  251. window_state->set_is_moving_to_another_display(true);
  252. window->SetBounds(new_bounds);
  253. }
  254. dst_container->AddChild(window);
  255. if (window_state)
  256. window_state->set_is_moving_to_another_display(false);
  257. // Restore focused/active window.
  258. if (focused && tracker.Contains(focused)) {
  259. aura::client::GetFocusClient(focused)->FocusWindow(focused);
  260. Shell::SetRootWindowForNewWindows(focused->GetRootWindow());
  261. } else if (active && tracker.Contains(active)) {
  262. wm::ActivateWindow(active);
  263. }
  264. // TODO(oshima): We should not have to update the bounds again
  265. // below in theory, but we currently do need as there is a code
  266. // that assumes that the bounds will never be overridden by the
  267. // layout mananger. We should have more explicit control how
  268. // constraints are applied by the layout manager.
  269. }
  270. }
  271. gfx::Point origin(bounds_in_screen.origin());
  272. const gfx::Point display_origin = display::Screen::GetScreen()
  273. ->GetDisplayNearestWindow(window)
  274. .bounds()
  275. .origin();
  276. origin.Offset(-display_origin.x(), -display_origin.y());
  277. window->SetBounds(gfx::Rect(origin, bounds_in_screen.size()));
  278. }
  279. } // namespace ash