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