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
5#include "ash/wm/window_resizer.h"
6
7#include "ash/screen_util.h"
8#include "ash/shell.h"
9#include "ash/shell_window_ids.h"
10#include "ash/wm/coordinate_conversion.h"
11#include "ash/wm/dock/docked_window_layout_manager.h"
12#include "ash/wm/window_state.h"
13#include "ash/wm/window_util.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_event_dispatcher.h"
18#include "ui/base/hit_test.h"
19#include "ui/base/ui_base_types.h"
20#include "ui/compositor/scoped_layer_animation_settings.h"
21#include "ui/gfx/display.h"
22#include "ui/gfx/screen.h"
23#include "ui/wm/core/coordinate_conversion.h"
24
25namespace ash {
26
27namespace {
28
29// Returns true for resize components along the right edge, where a drag in
30// positive x will make the window larger.
31bool IsRightEdge(int window_component) {
32  return window_component == HTTOPRIGHT ||
33      window_component == HTRIGHT ||
34      window_component == HTBOTTOMRIGHT ||
35      window_component == HTGROWBOX;
36}
37
38}  // namespace
39
40// static
41const int WindowResizer::kBoundsChange_None = 0;
42// static
43const int WindowResizer::kBoundsChange_Repositions = 1;
44// static
45const int WindowResizer::kBoundsChange_Resizes = 2;
46
47// static
48const int WindowResizer::kBoundsChangeDirection_None = 0;
49// static
50const int WindowResizer::kBoundsChangeDirection_Horizontal = 1;
51// static
52const int WindowResizer::kBoundsChangeDirection_Vertical = 2;
53
54WindowResizer::WindowResizer() {
55}
56
57WindowResizer::WindowResizer(wm::WindowState* window_state)
58    : window_state_(window_state) {
59  DCHECK(window_state_->drag_details());
60}
61
62WindowResizer::~WindowResizer() {
63}
64
65// static
66int WindowResizer::GetBoundsChangeForWindowComponent(int component) {
67  int bounds_change = WindowResizer::kBoundsChange_None;
68  switch (component) {
69    case HTTOPLEFT:
70    case HTTOP:
71    case HTTOPRIGHT:
72    case HTLEFT:
73    case HTBOTTOMLEFT:
74      bounds_change |= WindowResizer::kBoundsChange_Repositions |
75                      WindowResizer::kBoundsChange_Resizes;
76      break;
77    case HTCAPTION:
78      bounds_change |= WindowResizer::kBoundsChange_Repositions;
79      break;
80    case HTRIGHT:
81    case HTBOTTOMRIGHT:
82    case HTBOTTOM:
83    case HTGROWBOX:
84      bounds_change |= WindowResizer::kBoundsChange_Resizes;
85      break;
86    default:
87      break;
88  }
89  return bounds_change;
90}
91
92//static
93int WindowResizer::GetPositionChangeDirectionForWindowComponent(
94    int window_component) {
95  int pos_change_direction = WindowResizer::kBoundsChangeDirection_None;
96  switch (window_component) {
97    case HTTOPLEFT:
98    case HTBOTTOMRIGHT:
99    case HTGROWBOX:
100    case HTCAPTION:
101      pos_change_direction |=
102          WindowResizer::kBoundsChangeDirection_Horizontal |
103          WindowResizer::kBoundsChangeDirection_Vertical;
104      break;
105    case HTTOP:
106    case HTTOPRIGHT:
107    case HTBOTTOM:
108      pos_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
109      break;
110    case HTBOTTOMLEFT:
111    case HTRIGHT:
112    case HTLEFT:
113      pos_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
114      break;
115    default:
116      break;
117  }
118  return pos_change_direction;
119}
120
121gfx::Rect WindowResizer::CalculateBoundsForDrag(
122    const gfx::Point& passed_location) {
123  if (!details().is_resizable)
124    return details().initial_bounds_in_parent;
125
126  gfx::Point location = passed_location;
127  int delta_x = location.x() - details().initial_location_in_parent.x();
128  int delta_y = location.y() - details().initial_location_in_parent.y();
129
130  AdjustDeltaForTouchResize(&delta_x, &delta_y);
131
132  // The minimize size constraint may limit how much we change the window
133  // position.  For example, dragging the left edge to the right should stop
134  // repositioning the window when the minimize size is reached.
135  gfx::Size size = GetSizeForDrag(&delta_x, &delta_y);
136  gfx::Point origin = GetOriginForDrag(delta_x, delta_y);
137  gfx::Rect new_bounds(origin, size);
138
139  // Sizing has to keep the result on the screen. Note that this correction
140  // has to come first since it might have an impact on the origin as well as
141  // on the size.
142  if (details().bounds_change & kBoundsChange_Resizes) {
143    gfx::Rect work_area =
144        Shell::GetScreen()->GetDisplayNearestWindow(GetTarget()).work_area();
145    aura::Window* dock_container = Shell::GetContainer(
146        GetTarget()->GetRootWindow(), kShellWindowId_DockedContainer);
147    DockedWindowLayoutManager* dock_layout =
148        static_cast<DockedWindowLayoutManager*>(
149            dock_container->layout_manager());
150
151    work_area.Union(dock_layout->docked_bounds());
152    work_area = ScreenUtil::ConvertRectFromScreen(GetTarget()->parent(),
153                                                 work_area);
154    if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
155      if (IsRightEdge(details().window_component) &&
156          new_bounds.right() < work_area.x() + kMinimumOnScreenArea) {
157        int delta = work_area.x() + kMinimumOnScreenArea - new_bounds.right();
158        new_bounds.set_width(new_bounds.width() + delta);
159      } else if (new_bounds.x() > work_area.right() - kMinimumOnScreenArea) {
160        int width = new_bounds.right() - work_area.right() +
161                    kMinimumOnScreenArea;
162        new_bounds.set_x(work_area.right() - kMinimumOnScreenArea);
163        new_bounds.set_width(width);
164      }
165    }
166    if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
167      if (!IsBottomEdge(details().window_component) &&
168          new_bounds.y() > work_area.bottom() - kMinimumOnScreenArea) {
169        int height = new_bounds.bottom() - work_area.bottom() +
170                     kMinimumOnScreenArea;
171        new_bounds.set_y(work_area.bottom() - kMinimumOnScreenArea);
172        new_bounds.set_height(height);
173      } else if (details().window_component == HTBOTTOM ||
174                 details().window_component == HTBOTTOMRIGHT ||
175                 details().window_component == HTBOTTOMLEFT) {
176        // Update bottom edge to stay in the work area when we are resizing
177        // by dragging the bottom edge or corners.
178        if (new_bounds.bottom() > work_area.bottom())
179          new_bounds.Inset(0, 0, 0,
180                           new_bounds.bottom() - work_area.bottom());
181      }
182    }
183    if (details().bounds_change & kBoundsChange_Repositions &&
184        new_bounds.y() < 0) {
185      int delta = new_bounds.y();
186      new_bounds.set_y(0);
187      new_bounds.set_height(new_bounds.height() + delta);
188    }
189  }
190
191  if (details().bounds_change & kBoundsChange_Repositions) {
192    // When we might want to reposition a window which is also restored to its
193    // previous size, to keep the cursor within the dragged window.
194    if (!details().restore_bounds.IsEmpty()) {
195      // However - it is not desirable to change the origin if the window would
196      // be still hit by the cursor.
197      if (details().initial_location_in_parent.x() >
198          details().initial_bounds_in_parent.x() +
199          details().restore_bounds.width())
200        new_bounds.set_x(location.x() - details().restore_bounds.width() / 2);
201    }
202
203    // Make sure that |new_bounds| doesn't leave any of the displays.  Note that
204    // the |work_area| above isn't good for this check since it is the work area
205    // for the current display but the window can move to a different one.
206    aura::Window* parent = GetTarget()->parent();
207    gfx::Point passed_location_in_screen(passed_location);
208    ::wm::ConvertPointToScreen(parent, &passed_location_in_screen);
209    gfx::Rect near_passed_location(passed_location_in_screen, gfx::Size());
210    // Use a pointer location (matching the logic in DragWindowResizer) to
211    // calculate the target display after the drag.
212    const gfx::Display& display =
213        Shell::GetScreen()->GetDisplayMatching(near_passed_location);
214    aura::Window* dock_container =
215        Shell::GetContainer(wm::GetRootWindowMatching(near_passed_location),
216                            kShellWindowId_DockedContainer);
217    DockedWindowLayoutManager* dock_layout =
218        static_cast<DockedWindowLayoutManager*>(
219            dock_container->layout_manager());
220
221    gfx::Rect screen_work_area = display.work_area();
222    screen_work_area.Union(dock_layout->docked_bounds());
223    screen_work_area.Inset(kMinimumOnScreenArea, 0);
224    gfx::Rect new_bounds_in_screen =
225        ScreenUtil::ConvertRectToScreen(parent, new_bounds);
226    if (!screen_work_area.Intersects(new_bounds_in_screen)) {
227      // Make sure that the x origin does not leave the current display.
228      new_bounds_in_screen.set_x(
229          std::max(screen_work_area.x() - new_bounds.width(),
230                   std::min(screen_work_area.right(),
231                            new_bounds_in_screen.x())));
232      new_bounds =
233          ScreenUtil::ConvertRectFromScreen(parent, new_bounds_in_screen);
234    }
235  }
236
237  return new_bounds;
238}
239
240// static
241bool WindowResizer::IsBottomEdge(int window_component) {
242  return window_component == HTBOTTOMLEFT ||
243      window_component == HTBOTTOM ||
244      window_component == HTBOTTOMRIGHT ||
245      window_component == HTGROWBOX;
246}
247
248void WindowResizer::AdjustDeltaForTouchResize(int* delta_x, int* delta_y) {
249  if (details().source != aura::client::WINDOW_MOVE_SOURCE_TOUCH ||
250      !(details().bounds_change & kBoundsChange_Resizes))
251    return;
252
253  if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
254    if (IsRightEdge(details().window_component)) {
255      *delta_x += details().initial_location_in_parent.x() -
256          details().initial_bounds_in_parent.right();
257    } else {
258      *delta_x += details().initial_location_in_parent.x() -
259          details().initial_bounds_in_parent.x();
260    }
261  }
262  if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
263    if (IsBottomEdge(details().window_component)) {
264      *delta_y += details().initial_location_in_parent.y() -
265          details().initial_bounds_in_parent.bottom();
266    } else {
267      *delta_y += details().initial_location_in_parent.y() -
268          details().initial_bounds_in_parent.y();
269    }
270  }
271}
272
273gfx::Point WindowResizer::GetOriginForDrag(int delta_x, int delta_y) {
274  gfx::Point origin = details().initial_bounds_in_parent.origin();
275  if (details().bounds_change & kBoundsChange_Repositions) {
276    int pos_change_direction = GetPositionChangeDirectionForWindowComponent(
277        details().window_component);
278    if (pos_change_direction & kBoundsChangeDirection_Horizontal)
279      origin.Offset(delta_x, 0);
280    if (pos_change_direction & kBoundsChangeDirection_Vertical)
281      origin.Offset(0, delta_y);
282  }
283  return origin;
284}
285
286gfx::Size WindowResizer::GetSizeForDrag(int* delta_x, int* delta_y) {
287  gfx::Size size = details().initial_bounds_in_parent.size();
288  if (details().bounds_change & kBoundsChange_Resizes) {
289    gfx::Size min_size = GetTarget()->delegate()->GetMinimumSize();
290    size.SetSize(GetWidthForDrag(min_size.width(), delta_x),
291                 GetHeightForDrag(min_size.height(), delta_y));
292  } else if (!details().restore_bounds.IsEmpty()) {
293    size = details().restore_bounds.size();
294  }
295  return size;
296}
297
298int WindowResizer::GetWidthForDrag(int min_width, int* delta_x) {
299  int width = details().initial_bounds_in_parent.width();
300  if (details().size_change_direction & kBoundsChangeDirection_Horizontal) {
301    // Along the right edge, positive delta_x increases the window size.
302    int x_multiplier = IsRightEdge(details().window_component) ? 1 : -1;
303    width += x_multiplier * (*delta_x);
304
305    // Ensure we don't shrink past the minimum width and clamp delta_x
306    // for the window origin computation.
307    if (width < min_width) {
308      width = min_width;
309      *delta_x = -x_multiplier * (details().initial_bounds_in_parent.width() -
310                                  min_width);
311    }
312
313    // And don't let the window go bigger than the display.
314    int max_width = Shell::GetScreen()->GetDisplayNearestWindow(
315        GetTarget()).bounds().width();
316    gfx::Size max_size = GetTarget()->delegate()->GetMaximumSize();
317    if (max_size.width() != 0)
318      max_width = std::min(max_width, max_size.width());
319    if (width > max_width) {
320      width = max_width;
321      *delta_x = -x_multiplier * (details().initial_bounds_in_parent.width() -
322                                  max_width);
323    }
324  }
325  return width;
326}
327
328int WindowResizer::GetHeightForDrag(int min_height, int* delta_y) {
329  int height = details().initial_bounds_in_parent.height();
330  if (details().size_change_direction & kBoundsChangeDirection_Vertical) {
331    // Along the bottom edge, positive delta_y increases the window size.
332    int y_multiplier = IsBottomEdge(details().window_component) ? 1 : -1;
333    height += y_multiplier * (*delta_y);
334
335    // Ensure we don't shrink past the minimum height and clamp delta_y
336    // for the window origin computation.
337    if (height < min_height) {
338      height = min_height;
339      *delta_y = -y_multiplier * (details().initial_bounds_in_parent.height() -
340                                  min_height);
341    }
342
343    // And don't let the window go bigger than the display.
344    int max_height = Shell::GetScreen()->GetDisplayNearestWindow(
345        GetTarget()).bounds().height();
346    gfx::Size max_size = GetTarget()->delegate()->GetMaximumSize();
347    if (max_size.height() != 0)
348      max_height = std::min(max_height, max_size.height());
349    if (height > max_height) {
350      height = max_height;
351      *delta_y = -y_multiplier * (details().initial_bounds_in_parent.height() -
352                                  max_height);
353    }
354  }
355  return height;
356}
357
358}  // namespace ash
359