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