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/workspace/snap_sizer.h"
6
7#include <cmath>
8
9#include "ash/ash_switches.h"
10#include "ash/screen_ash.h"
11#include "ash/wm/window_resizer.h"
12#include "ash/wm/window_state.h"
13#include "ash/wm/window_util.h"
14#include "base/command_line.h"
15#include "ui/aura/window.h"
16#include "ui/aura/window_delegate.h"
17#include "ui/gfx/screen.h"
18
19namespace ash {
20namespace internal {
21
22namespace {
23
24// A list of ideal window widths in DIP which will be used to populate the
25// |usable_width_| list.
26const int kIdealWidth[] = { 1280, 1024, 768, 640 };
27
28// Windows are initially snapped to the size in |usable_width_| at index 0.
29// The index into |usable_width_| is changed if any of the following happen:
30// . The user stops moving the mouse for |kDelayBeforeIncreaseMS| and then
31//   moves the mouse again.
32// . The mouse moves |kPixelsBeforeAdjust| horizontal pixels.
33// . The mouse is against the edge of the screen and the mouse is moved
34//   |kMovesBeforeAdjust| times.
35const int kDelayBeforeIncreaseMS = 500;
36const int kMovesBeforeAdjust = 25;
37const int kPixelsBeforeAdjust = 100;
38
39// The maximum fraction of the screen width that a snapped window is allowed
40// to take up.
41const int kMaximumScreenPercent = 90;
42
43// The width that a window should be snapped to if resizing is disabled in the
44// SnapSizer for devices with small screen resolutions.
45const int kDefaultWidthSmallScreen = 1024;
46
47// Returns the minimum width that |window| can be snapped to. The returned width
48// may not be in the width list generated by BuildIdealWidthList().
49int GetMinWidth(aura::Window* window) {
50  return window->delegate() ? window->delegate()->GetMinimumSize().width() : 0;
51}
52
53// Returns the maximum width that |window| can be snapped to. The returned width
54// may not be in the width list generated by BuildIdealWidthList().
55// The aura::WindowDelegate's max size is ignored because
56// ash::wm::CanSnapWindow() returns false when a max size is specified.
57int GetMaxWidth(aura::Window* window) {
58  gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window));
59  return std::max(work_area.width() * kMaximumScreenPercent / 100,
60                  GetMinWidth(window));
61}
62
63// Returns the width that |window| should be snapped to if resizing is disabled
64// in the SnapSizer.
65int GetDefaultWidth(aura::Window* window) {
66  gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window));
67
68  int width = 0;
69  if (!CommandLine::ForCurrentProcess()->HasSwitch(
70          switches::kAshMultipleSnapWindowWidths)) {
71    width = work_area.width() / 2;
72  } else {
73    width = std::max(kDefaultWidthSmallScreen, work_area.width() / 2);
74  }
75
76  width = std::min(width, GetMaxWidth(window));
77  return std::max(width, GetMinWidth(window));
78}
79
80// Creates the list of possible width for the current screen configuration:
81// Returns a list with items from |kIdealWidth| which fit on the screen and
82// supplement it with the 'half of screen' size. Furthermore, add an entry for
83// 90% of the screen size if it is smaller than the biggest value in the
84// |kIdealWidth| list (to get a step between the values).
85std::vector<int> BuildIdealWidthList(aura::Window* window) {
86  if (!CommandLine::ForCurrentProcess()->HasSwitch(
87          switches::kAshMultipleSnapWindowWidths)) {
88    return std::vector<int>(1u, GetDefaultWidth(window));
89  }
90
91  int minimum_width = GetMinWidth(window);
92  int maximum_width = GetMaxWidth(window);
93
94  gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window));
95  int half_width = work_area.width() / 2;
96  if (half_width < minimum_width || half_width > maximum_width)
97    half_width = 0;
98
99  std::vector<int> ideal_width_list;
100  for (size_t i = 0; i < arraysize(kIdealWidth); i++) {
101    if (kIdealWidth[i] >= minimum_width && kIdealWidth[i] <= maximum_width) {
102      if (i && !ideal_width_list.size() && maximum_width != kIdealWidth[i])
103        ideal_width_list.push_back(maximum_width);
104      if (half_width > kIdealWidth[i])
105        ideal_width_list.push_back(half_width);
106      if (half_width >= kIdealWidth[i])
107        half_width = 0;
108      ideal_width_list.push_back(kIdealWidth[i]);
109    }
110  }
111  if (half_width)
112    ideal_width_list.push_back(half_width);
113  if (ideal_width_list.empty()) {
114    if (minimum_width > 0)
115      ideal_width_list.push_back(minimum_width);
116    else
117      ideal_width_list.push_back(maximum_width);
118  }
119
120  return ideal_width_list;
121}
122
123// Changes |window|'s bounds to |snap_bounds| while preserving the restore
124// bounds.
125void SnapWindowToBounds(wm::WindowState* window_state,
126                        SnapSizer::Edge edge,
127                        const gfx::Rect& snap_bounds) {
128  if (edge == SnapSizer::LEFT_EDGE) {
129    window_state->SnapLeft(snap_bounds);
130  } else {
131    window_state->SnapRight(snap_bounds);
132  }
133}
134
135}  // namespace
136
137SnapSizer::SnapSizer(wm::WindowState* window_state,
138                     const gfx::Point& start,
139                     Edge edge,
140                     InputType input_type)
141    : window_state_(window_state),
142      edge_(edge),
143      time_last_update_(base::TimeTicks::Now()),
144      size_index_(0),
145      end_of_sequence_(false),
146      resize_disabled_(false),
147      num_moves_since_adjust_(0),
148      last_adjust_x_(start.x()),
149      last_update_x_(start.x()),
150      start_x_(start.x()),
151      input_type_(input_type),
152      usable_width_(BuildIdealWidthList(window_state->window())) {
153  DCHECK(!usable_width_.empty());
154  target_bounds_ = GetTargetBounds();
155}
156
157SnapSizer::~SnapSizer() {
158}
159
160void SnapSizer::SnapWindow(wm::WindowState* window_state,
161                           SnapSizer::Edge edge) {
162  if (!window_state->CanSnap())
163    return;
164  internal::SnapSizer sizer(window_state, gfx::Point(), edge,
165      internal::SnapSizer::OTHER_INPUT);
166  SnapWindowToBounds(window_state, edge,
167                     sizer.GetSnapBounds(window_state->window()->bounds()));
168}
169
170void SnapSizer::SnapWindowToTargetBounds() {
171  SnapWindowToBounds(window_state_, edge_, target_bounds());
172}
173
174void SnapSizer::Update(const gfx::Point& location) {
175  // See description above for details on this behavior.
176  num_moves_since_adjust_++;
177  if ((base::TimeTicks::Now() - time_last_update_).InMilliseconds() >
178      kDelayBeforeIncreaseMS) {
179    ChangeBounds(location.x(),
180                 CalculateIncrement(location.x(), last_update_x_));
181  } else {
182    bool along_edge = AlongEdge(location.x());
183    int pixels_before_adjust = kPixelsBeforeAdjust;
184    if (input_type_ == TOUCH_MAXIMIZE_BUTTON_INPUT) {
185      const gfx::Rect& workspace_bounds =
186          window_state_->window()->parent()->bounds();
187      if (start_x_ > location.x()) {
188        pixels_before_adjust =
189            std::min(pixels_before_adjust, start_x_ / 10);
190      } else {
191        pixels_before_adjust =
192            std::min(pixels_before_adjust,
193                     (workspace_bounds.width() - start_x_) / 10);
194      }
195    }
196    if (std::abs(location.x() - last_adjust_x_) >= pixels_before_adjust ||
197        (along_edge && num_moves_since_adjust_ >= kMovesBeforeAdjust)) {
198      ChangeBounds(location.x(),
199                   CalculateIncrement(location.x(), last_adjust_x_));
200    }
201  }
202  last_update_x_ = location.x();
203  time_last_update_ = base::TimeTicks::Now();
204}
205
206gfx::Rect SnapSizer::GetSnapBounds(const gfx::Rect& bounds) {
207  int current = 0;
208  if (!resize_disabled_) {
209    for (current = usable_width_.size() - 1; current >= 0; current--) {
210      gfx::Rect target = GetTargetBoundsForSize(current);
211      if (target == bounds) {
212        ++current;
213        break;
214      }
215    }
216  }
217  if (current < 0)
218    current = 0;
219  return GetTargetBoundsForSize(current % usable_width_.size());
220}
221
222void SnapSizer::SelectDefaultSizeAndDisableResize() {
223  resize_disabled_ = true;
224  size_index_ = 0;
225  end_of_sequence_ = false;
226  target_bounds_ = GetTargetBounds();
227}
228
229gfx::Rect SnapSizer::GetTargetBoundsForSize(size_t size_index) const {
230  gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
231      window_state_->window()));
232  int y = work_area.y();
233  int max_y = work_area.bottom();
234  int width = 0;
235  if (resize_disabled_) {
236    width = GetDefaultWidth(window_state_->window());
237  } else {
238    DCHECK(size_index < usable_width_.size());
239    width = usable_width_[size_index];
240  }
241
242  if (edge_ == LEFT_EDGE) {
243    int x = work_area.x();
244    int mid_x = x + width;
245    return gfx::Rect(x, y, mid_x - x, max_y - y);
246  }
247  int max_x = work_area.right();
248  int x = max_x - width;
249  return gfx::Rect(x , y, max_x - x, max_y - y);
250}
251
252int SnapSizer::CalculateIncrement(int x, int reference_x) const {
253  if (AlongEdge(x))
254    return 1;
255  if (x == reference_x)
256    return 0;
257  if (edge_ == LEFT_EDGE) {
258    if (x < reference_x)
259      return 1;
260    return -1;
261  }
262  // edge_ == RIGHT_EDGE.
263  if (x > reference_x)
264    return 1;
265  return -1;
266}
267
268void SnapSizer::ChangeBounds(int x, int delta) {
269  end_of_sequence_ =
270      delta > 0 && size_index_ == static_cast<int>(usable_width_.size()) - 1;
271  int index = std::min(static_cast<int>(usable_width_.size()) - 1,
272                       std::max(size_index_ + delta, 0));
273  if (index != size_index_) {
274    size_index_ = index;
275    target_bounds_ = GetTargetBounds();
276  }
277  num_moves_since_adjust_ = 0;
278  last_adjust_x_ = x;
279}
280
281gfx::Rect SnapSizer::GetTargetBounds() const {
282  return GetTargetBoundsForSize(size_index_);
283}
284
285bool SnapSizer::AlongEdge(int x) const {
286  gfx::Rect area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
287      window_state_->window()));
288  return (x <= area.x()) || (x >= area.right() - 1);
289}
290
291}  // namespace internal
292}  // namespace ash
293