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 25 namespace ash { 26 27 namespace { 28 29 // Returns true for resize components along the right edge, where a drag in 30 // positive x will make the window larger. 31 bool 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 41 const int WindowResizer::kBoundsChange_None = 0; 42 // static 43 const int WindowResizer::kBoundsChange_Repositions = 1; 44 // static 45 const int WindowResizer::kBoundsChange_Resizes = 2; 46 47 // static 48 const int WindowResizer::kBoundsChangeDirection_None = 0; 49 // static 50 const int WindowResizer::kBoundsChangeDirection_Horizontal = 1; 51 // static 52 const int WindowResizer::kBoundsChangeDirection_Vertical = 2; 53 54 WindowResizer::WindowResizer() { 55 } 56 57 WindowResizer::WindowResizer(wm::WindowState* window_state) 58 : window_state_(window_state) { 59 DCHECK(window_state_->drag_details()); 60 } 61 62 WindowResizer::~WindowResizer() { 63 } 64 65 // static 66 int 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 93 int 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 121 gfx::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 241 bool WindowResizer::IsBottomEdge(int window_component) { 242 return window_component == HTBOTTOMLEFT || 243 window_component == HTBOTTOM || 244 window_component == HTBOTTOMRIGHT || 245 window_component == HTGROWBOX; 246 } 247 248 void 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 273 gfx::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 286 gfx::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 298 int 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 328 int 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