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