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