window_resizer.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. // Copyright (c) 2012 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_resizer.h"
  5. #include "ash/public/cpp/presentation_time_recorder.h"
  6. #include "ash/wm/window_positioning_utils.h"
  7. #include "ash/wm/window_state.h"
  8. #include "ash/wm/window_util.h"
  9. #include "base/bind.h"
  10. #include "base/metrics/histogram_macros.h"
  11. #include "base/numerics/safe_conversions.h"
  12. #include "base/time/time.h"
  13. #include "chromeos/ui/frame/frame_header.h"
  14. #include "ui/aura/client/aura_constants.h"
  15. #include "ui/aura/window.h"
  16. #include "ui/aura/window_delegate.h"
  17. #include "ui/aura/window_tree_host.h"
  18. #include "ui/base/hit_test.h"
  19. #include "ui/base/ui_base_types.h"
  20. #include "ui/compositor/compositor.h"
  21. #include "ui/compositor/layer.h"
  22. #include "ui/display/display.h"
  23. #include "ui/display/screen.h"
  24. #include "ui/gfx/geometry/rect.h"
  25. #include "ui/gfx/geometry/resize_utils.h"
  26. #include "ui/gfx/presentation_feedback.h"
  27. #include "ui/views/widget/widget.h"
  28. #include "ui/wm/core/coordinate_conversion.h"
  29. namespace ash {
  30. namespace {
  31. using ::chromeos::FrameHeader;
  32. // Returns true for resize components along the right edge, where a drag in
  33. // positive x will make the window larger.
  34. bool IsRightEdge(int window_component) {
  35. return window_component == HTTOPRIGHT || window_component == HTRIGHT ||
  36. window_component == HTBOTTOMRIGHT || window_component == HTGROWBOX;
  37. }
  38. bool IsBottomEdge(int window_component) {
  39. return window_component == HTBOTTOMLEFT || window_component == HTBOTTOM ||
  40. window_component == HTBOTTOMRIGHT || window_component == HTGROWBOX;
  41. }
  42. // Convert |window_component| to the ResizeEdge used in
  43. // gfx::SizeRectToAspectRatio().
  44. gfx::ResizeEdge GetWindowResizeEdge(int window_component) {
  45. switch (window_component) {
  46. case HTBOTTOM:
  47. return gfx::ResizeEdge::kBottom;
  48. case HTTOP:
  49. return gfx::ResizeEdge::kTop;
  50. case HTLEFT:
  51. return gfx::ResizeEdge::kLeft;
  52. case HTRIGHT:
  53. return gfx::ResizeEdge::kRight;
  54. case HTTOPLEFT:
  55. return gfx::ResizeEdge::kTopLeft;
  56. case HTTOPRIGHT:
  57. return gfx::ResizeEdge::kTopRight;
  58. case HTBOTTOMLEFT:
  59. return gfx::ResizeEdge::kBottomLeft;
  60. case HTBOTTOMRIGHT:
  61. return gfx::ResizeEdge::kBottomRight;
  62. default:
  63. NOTREACHED();
  64. return gfx::ResizeEdge::kBottomRight;
  65. }
  66. }
  67. } // namespace
  68. // static
  69. const int WindowResizer::kBoundsChange_None = 0;
  70. // static
  71. const int WindowResizer::kBoundsChange_Repositions = 1;
  72. // static
  73. const int WindowResizer::kBoundsChange_Resizes = 2;
  74. // static
  75. const int WindowResizer::kBoundsChangeDirection_None = 0;
  76. // static
  77. const int WindowResizer::kBoundsChangeDirection_Horizontal = 1;
  78. // static
  79. const int WindowResizer::kBoundsChangeDirection_Vertical = 2;
  80. WindowResizer::WindowResizer(WindowState* window_state)
  81. : window_state_(window_state) {
  82. DCHECK(window_state_->drag_details());
  83. }
  84. WindowResizer::~WindowResizer() = default;
  85. // static
  86. int WindowResizer::GetBoundsChangeForWindowComponent(int component) {
  87. int bounds_change = WindowResizer::kBoundsChange_None;
  88. switch (component) {
  89. case HTTOPLEFT:
  90. case HTTOP:
  91. case HTTOPRIGHT:
  92. case HTLEFT:
  93. case HTBOTTOMLEFT:
  94. bounds_change |= WindowResizer::kBoundsChange_Repositions |
  95. WindowResizer::kBoundsChange_Resizes;
  96. break;
  97. case HTCAPTION:
  98. bounds_change |= WindowResizer::kBoundsChange_Repositions;
  99. break;
  100. case HTRIGHT:
  101. case HTBOTTOMRIGHT:
  102. case HTBOTTOM:
  103. case HTGROWBOX:
  104. bounds_change |= WindowResizer::kBoundsChange_Resizes;
  105. break;
  106. default:
  107. break;
  108. }
  109. return bounds_change;
  110. }
  111. // static
  112. int WindowResizer::GetPositionChangeDirectionForWindowComponent(
  113. int window_component) {
  114. int pos_change_direction = WindowResizer::kBoundsChangeDirection_None;
  115. switch (window_component) {
  116. case HTTOPLEFT:
  117. case HTBOTTOMRIGHT:
  118. case HTGROWBOX:
  119. case HTCAPTION:
  120. pos_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal |
  121. WindowResizer::kBoundsChangeDirection_Vertical;
  122. break;
  123. case HTTOP:
  124. case HTTOPRIGHT:
  125. case HTBOTTOM:
  126. pos_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
  127. break;
  128. case HTBOTTOMLEFT:
  129. case HTRIGHT:
  130. case HTLEFT:
  131. pos_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
  132. break;
  133. default:
  134. break;
  135. }
  136. return pos_change_direction;
  137. }
  138. aura::Window* WindowResizer::GetTarget() const {
  139. return window_state_ ? window_state_->window() : nullptr;
  140. }
  141. gfx::Rect WindowResizer::CalculateBoundsForDrag(
  142. const gfx::PointF& passed_location) {
  143. if (!details().is_resizable)
  144. return details().initial_bounds_in_parent;
  145. gfx::PointF location = passed_location;
  146. int delta_x = location.x() - details().initial_location_in_parent.x();
  147. int delta_y = location.y() - details().initial_location_in_parent.y();
  148. AdjustDeltaForTouchResize(&delta_x, &delta_y);
  149. // The minimize size constraint may limit how much we change the window
  150. // position. For example, dragging the left edge to the right should stop
  151. // repositioning the window when the minimize size is reached.
  152. gfx::Size size = GetSizeForDrag(&delta_x, &delta_y);
  153. gfx::Point origin = GetOriginForDrag(delta_x, delta_y, passed_location);
  154. gfx::Rect new_bounds(origin, size);
  155. gfx::SizeF* aspect_ratio_size =
  156. GetTarget()->GetProperty(aura::client::kAspectRatio);
  157. if (details().bounds_change & kBoundsChange_Resizes && aspect_ratio_size &&
  158. !aspect_ratio_size->IsEmpty()) {
  159. float aspect_ratio =
  160. aspect_ratio_size->width() / aspect_ratio_size->height();
  161. CalculateBoundsWithAspectRatio(aspect_ratio, &new_bounds);
  162. return new_bounds;
  163. }
  164. // Sizing has to keep the result on the screen. Note that this correction
  165. // has to come first since it might have an impact on the origin as well as
  166. // on the size.
  167. if (details().bounds_change & kBoundsChange_Resizes) {
  168. gfx::Rect work_area = display::Screen::GetScreen()
  169. ->GetDisplayNearestWindow(GetTarget())
  170. .work_area();
  171. ::wm::ConvertRectFromScreen(GetTarget()->parent(), &work_area);
  172. if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
  173. if (IsRightEdge(details().window_component) &&
  174. new_bounds.right() < work_area.x() + kMinimumOnScreenArea) {
  175. int delta = work_area.x() + kMinimumOnScreenArea - new_bounds.right();
  176. new_bounds.set_width(new_bounds.width() + delta);
  177. } else if (new_bounds.x() > work_area.right() - kMinimumOnScreenArea) {
  178. int width =
  179. new_bounds.right() - work_area.right() + kMinimumOnScreenArea;
  180. new_bounds.set_x(work_area.right() - kMinimumOnScreenArea);
  181. new_bounds.set_width(width);
  182. }
  183. }
  184. if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
  185. if (!IsBottomEdge(details().window_component) &&
  186. new_bounds.y() > work_area.bottom() - kMinimumOnScreenArea) {
  187. int height =
  188. new_bounds.bottom() - work_area.bottom() + kMinimumOnScreenArea;
  189. new_bounds.set_y(work_area.bottom() - kMinimumOnScreenArea);
  190. new_bounds.set_height(height);
  191. } else if (details().window_component == HTBOTTOM ||
  192. details().window_component == HTBOTTOMRIGHT ||
  193. details().window_component == HTBOTTOMLEFT) {
  194. // Update bottom edge to stay in the work area when we are resizing
  195. // by dragging the bottom edge or corners.
  196. if (new_bounds.bottom() > work_area.bottom())
  197. new_bounds.Inset(gfx::Insets::TLBR(
  198. 0, 0, new_bounds.bottom() - work_area.bottom(), 0));
  199. }
  200. }
  201. if (details().bounds_change & kBoundsChange_Repositions &&
  202. new_bounds.y() < 0) {
  203. int delta = new_bounds.y();
  204. new_bounds.set_y(0);
  205. new_bounds.set_height(new_bounds.height() + delta);
  206. }
  207. }
  208. if (details().bounds_change & kBoundsChange_Repositions) {
  209. // Make sure that |new_bounds| doesn't leave any of the displays. Note that
  210. // the |work_area| above isn't good for this check since it is the work area
  211. // for the current display but the window can move to a different one.
  212. aura::Window* parent = GetTarget()->parent();
  213. gfx::PointF passed_location_in_screen(passed_location);
  214. ::wm::ConvertPointToScreen(parent, &passed_location_in_screen);
  215. gfx::Rect near_passed_location(
  216. gfx::ToRoundedPoint(passed_location_in_screen), gfx::Size());
  217. // Use a pointer location (matching the logic in DragWindowResizer) to
  218. // calculate the target display after the drag.
  219. const display::Display& display =
  220. display::Screen::GetScreen()->GetDisplayMatching(near_passed_location);
  221. gfx::Rect screen_work_area = display.work_area();
  222. screen_work_area.Inset(gfx::Insets::VH(0, kMinimumOnScreenArea));
  223. gfx::Rect new_bounds_in_screen(new_bounds);
  224. ::wm::ConvertRectToScreen(parent, &new_bounds_in_screen);
  225. if (!screen_work_area.Intersects(new_bounds_in_screen)) {
  226. // Make sure that the x origin does not leave the current display.
  227. new_bounds_in_screen.set_x(std::max(
  228. screen_work_area.x() - new_bounds.width(),
  229. std::min(screen_work_area.right(), new_bounds_in_screen.x())));
  230. new_bounds = new_bounds_in_screen;
  231. ::wm::ConvertRectFromScreen(parent, &new_bounds);
  232. }
  233. }
  234. return new_bounds;
  235. }
  236. void WindowResizer::SetBoundsDuringResize(const gfx::Rect& bounds) {
  237. aura::Window* window = GetTarget();
  238. DCHECK(window);
  239. auto ptr = weak_ptr_factory_.GetWeakPtr();
  240. const gfx::Size original_size = window->bounds().size();
  241. // Prepare to record presentation time (e.g. tracking Configure).
  242. if (recorder_)
  243. recorder_->PrepareToRecord();
  244. window->SetBounds(bounds);
  245. // Resizer can be destroyed when a window is attached during tab dragging.
  246. // crbug.com/970911.
  247. if (!ptr)
  248. return;
  249. // Using `window->bounds()` instead of `bounds` to check size change because
  250. // whether "window->SetBounds()" could reject a bounds change. And when that
  251. // happens, there might be no new frames presented on screen.
  252. if (window->bounds().size() == original_size)
  253. return;
  254. if (recorder_)
  255. recorder_->RequestNext();
  256. }
  257. void WindowResizer::SetPresentationTimeRecorder(
  258. std::unique_ptr<PresentationTimeRecorder> recorder) {
  259. recorder_ = std::move(recorder);
  260. }
  261. void WindowResizer::AdjustDeltaForTouchResize(int* delta_x, int* delta_y) {
  262. if (details().source != ::wm::WINDOW_MOVE_SOURCE_TOUCH ||
  263. !(details().bounds_change & kBoundsChange_Resizes))
  264. return;
  265. if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
  266. if (IsRightEdge(details().window_component)) {
  267. *delta_x += details().initial_location_in_parent.x() -
  268. details().initial_bounds_in_parent.right();
  269. } else {
  270. *delta_x += details().initial_location_in_parent.x() -
  271. details().initial_bounds_in_parent.x();
  272. }
  273. }
  274. if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
  275. if (IsBottomEdge(details().window_component)) {
  276. *delta_y += details().initial_location_in_parent.y() -
  277. details().initial_bounds_in_parent.bottom();
  278. } else {
  279. *delta_y += details().initial_location_in_parent.y() -
  280. details().initial_bounds_in_parent.y();
  281. }
  282. }
  283. }
  284. gfx::Point WindowResizer::GetOriginForDrag(int delta_x,
  285. int delta_y,
  286. const gfx::PointF& event_location) {
  287. gfx::Point origin = details().initial_bounds_in_parent.origin();
  288. if (!(details().bounds_change & kBoundsChange_Repositions))
  289. return origin;
  290. int pos_change_direction =
  291. GetPositionChangeDirectionForWindowComponent(details().window_component);
  292. if (pos_change_direction & kBoundsChangeDirection_Horizontal)
  293. origin.Offset(delta_x, 0);
  294. if (pos_change_direction & kBoundsChangeDirection_Vertical)
  295. origin.Offset(0, delta_y);
  296. // If the window gets respoitioned and changes to it's restored bounds,
  297. // modify the origin so that the cursor remains within the dragged window.
  298. // The ratio of the new origin to the new location should match the ratio
  299. // from the initial origin to the initial location.
  300. const gfx::Rect restore_bounds = details().restore_bounds_in_parent;
  301. if (restore_bounds.IsEmpty())
  302. return origin;
  303. // The ratios that should match is the (drag location x - bounds origin x) /
  304. // bounds width.
  305. const float ratio =
  306. (details().initial_location_in_parent.x() -
  307. static_cast<float>(details().initial_bounds_in_parent.x())) /
  308. details().initial_bounds_in_parent.width();
  309. int new_origin_x =
  310. base::ClampRound(event_location.x() - ratio * restore_bounds.width());
  311. origin.set_x(new_origin_x);
  312. // Windows may not have a widget in tests.
  313. auto* widget = views::Widget::GetWidgetForNativeWindow(GetTarget());
  314. if (!widget)
  315. return origin;
  316. // |widget| may have a custom frame, |header| will be null in this case.
  317. auto* header = FrameHeader::Get(widget);
  318. if (!header)
  319. return origin;
  320. // Compute the available bounds based on the header local bounds. These bounds
  321. // are from the previous layout and do not match |restore_bounds| yet.
  322. gfx::Rect header_bounds = header->view()->GetLocalBounds();
  323. header_bounds.set_height(header->GetHeaderHeight());
  324. gfx::Rect available_bounds = header_bounds;
  325. auto* back_button = header->GetBackButton();
  326. auto* caption_button_container = header->caption_button_container();
  327. if (back_button)
  328. available_bounds.Subtract(back_button->bounds());
  329. if (caption_button_container)
  330. available_bounds.Subtract(caption_button_container->bounds());
  331. // Calculate the new expected available header left and right bounds. The new
  332. // header will still are |new_origin_x| with width |restore_bounds.width()|.
  333. // The available region subtracts the control buttons.
  334. const int header_left =
  335. new_origin_x + (available_bounds.x() - header_bounds.x());
  336. const int header_right = new_origin_x + restore_bounds.width() -
  337. (header_bounds.right() - available_bounds.right());
  338. // If |event_location| x falls outside |available_bounds|, shift
  339. // |new_origin_x| so that the new window bounds will not land on the any of
  340. // the header buttons.
  341. int shift = 0;
  342. if (event_location.x() > header_right)
  343. shift = event_location.x() - header_right;
  344. else if (event_location.x() < header_left)
  345. shift = event_location.x() - header_left;
  346. new_origin_x += shift;
  347. origin.set_x(new_origin_x);
  348. return origin;
  349. }
  350. gfx::Size WindowResizer::GetSizeForDrag(int* delta_x, int* delta_y) {
  351. gfx::Size size = details().initial_bounds_in_parent.size();
  352. if (details().bounds_change & kBoundsChange_Resizes) {
  353. gfx::Size min_size = GetTarget()->delegate()
  354. ? GetTarget()->delegate()->GetMinimumSize()
  355. : gfx::Size();
  356. size.SetSize(GetWidthForDrag(min_size.width(), delta_x),
  357. GetHeightForDrag(min_size.height(), delta_y));
  358. } else if (!details().restore_bounds_in_parent.IsEmpty()) {
  359. size = details().restore_bounds_in_parent.size();
  360. }
  361. return size;
  362. }
  363. int WindowResizer::GetWidthForDrag(int min_width, int* delta_x) {
  364. int width = details().initial_bounds_in_parent.width();
  365. if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
  366. // Along the right edge, positive delta_x increases the window size.
  367. int x_multiplier = IsRightEdge(details().window_component) ? 1 : -1;
  368. width += x_multiplier * (*delta_x);
  369. // Ensure we don't shrink past the minimum width and clamp delta_x
  370. // for the window origin computation.
  371. if (width < min_width) {
  372. width = min_width;
  373. *delta_x = -x_multiplier *
  374. (details().initial_bounds_in_parent.width() - min_width);
  375. }
  376. // And don't let the window go bigger than the display.
  377. int max_width = display::Screen::GetScreen()
  378. ->GetDisplayNearestWindow(GetTarget())
  379. .bounds()
  380. .width();
  381. gfx::Size max_size = GetTarget()->delegate()
  382. ? GetTarget()->delegate()->GetMaximumSize()
  383. : gfx::Size();
  384. if (max_size.width() != 0)
  385. max_width = std::min(max_width, max_size.width());
  386. if (width > max_width) {
  387. width = max_width;
  388. *delta_x = -x_multiplier *
  389. (details().initial_bounds_in_parent.width() - max_width);
  390. }
  391. }
  392. return width;
  393. }
  394. int WindowResizer::GetHeightForDrag(int min_height, int* delta_y) {
  395. int height = details().initial_bounds_in_parent.height();
  396. if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
  397. // Along the bottom edge, positive delta_y increases the window size.
  398. int y_multiplier = IsBottomEdge(details().window_component) ? 1 : -1;
  399. height += y_multiplier * (*delta_y);
  400. // Ensure we don't shrink past the minimum height and clamp delta_y
  401. // for the window origin computation.
  402. if (height < min_height) {
  403. height = min_height;
  404. *delta_y = -y_multiplier *
  405. (details().initial_bounds_in_parent.height() - min_height);
  406. }
  407. // And don't let the window go bigger than the display.
  408. int max_height = display::Screen::GetScreen()
  409. ->GetDisplayNearestWindow(GetTarget())
  410. .bounds()
  411. .height();
  412. gfx::Size max_size = GetTarget()->delegate()
  413. ? GetTarget()->delegate()->GetMaximumSize()
  414. : gfx::Size();
  415. if (max_size.height() != 0)
  416. max_height = std::min(max_height, max_size.height());
  417. if (height > max_height) {
  418. height = max_height;
  419. *delta_y = -y_multiplier *
  420. (details().initial_bounds_in_parent.height() - max_height);
  421. }
  422. }
  423. return height;
  424. }
  425. void WindowResizer::CalculateBoundsWithAspectRatio(float aspect_ratio,
  426. gfx::Rect* new_bounds) {
  427. gfx::Size min_size = GetTarget()->delegate()
  428. ? GetTarget()->delegate()->GetMinimumSize()
  429. : gfx::Size();
  430. gfx::Size max_size = GetTarget()->delegate()
  431. ? GetTarget()->delegate()->GetMaximumSize()
  432. : gfx::Size();
  433. DCHECK(!min_size.IsEmpty());
  434. DCHECK(!max_size.IsEmpty());
  435. gfx::SizeRectToAspectRatio(GetWindowResizeEdge(details().window_component),
  436. aspect_ratio, min_size, max_size, new_bounds);
  437. }
  438. } // namespace ash