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/multi_window_resize_controller.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/window_animations.h" 12 #include "ash/wm/workspace/workspace_event_handler.h" 13 #include "ash/wm/workspace/workspace_window_resizer.h" 14 #include "grit/ash_resources.h" 15 #include "ui/aura/client/screen_position_client.h" 16 #include "ui/aura/root_window.h" 17 #include "ui/aura/window.h" 18 #include "ui/aura/window_delegate.h" 19 #include "ui/base/hit_test.h" 20 #include "ui/base/resource/resource_bundle.h" 21 #include "ui/gfx/canvas.h" 22 #include "ui/gfx/image/image.h" 23 #include "ui/gfx/screen.h" 24 #include "ui/views/corewm/compound_event_filter.h" 25 #include "ui/views/view.h" 26 #include "ui/views/widget/widget.h" 27 #include "ui/views/widget/widget_delegate.h" 28 29 using aura::Window; 30 31 namespace ash { 32 namespace internal { 33 34 namespace { 35 36 // Delay before showing. 37 const int kShowDelayMS = 400; 38 39 // Delay before hiding. 40 const int kHideDelayMS = 500; 41 42 // Padding from the bottom/right edge the resize widget is shown at. 43 const int kResizeWidgetPadding = 15; 44 45 bool ContainsX(Window* window, int x) { 46 return window->bounds().x() <= x && window->bounds().right() >= x; 47 } 48 49 bool ContainsY(Window* window, int y) { 50 return window->bounds().y() <= y && window->bounds().bottom() >= y; 51 } 52 53 bool Intersects(int x1, int max_1, int x2, int max_2) { 54 return x2 <= max_1 && max_2 > x1; 55 } 56 57 } // namespace 58 59 // View contained in the widget. Passes along mouse events to the 60 // MultiWindowResizeController so that it can start/stop the resize loop. 61 class MultiWindowResizeController::ResizeView : public views::View { 62 public: 63 explicit ResizeView(MultiWindowResizeController* controller, 64 Direction direction) 65 : controller_(controller), 66 direction_(direction), 67 image_(NULL) { 68 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 69 int image_id = 70 direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H : 71 IDR_AURA_MULTI_WINDOW_RESIZE_V; 72 image_ = rb.GetImageNamed(image_id).ToImageSkia(); 73 } 74 75 // views::View overrides: 76 virtual gfx::Size GetPreferredSize() OVERRIDE { 77 return gfx::Size(image_->width(), image_->height()); 78 } 79 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 80 canvas->DrawImageInt(*image_, 0, 0); 81 } 82 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { 83 gfx::Point location(event.location()); 84 views::View::ConvertPointToScreen(this, &location); 85 controller_->StartResize(location); 86 return true; 87 } 88 virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE { 89 gfx::Point location(event.location()); 90 views::View::ConvertPointToScreen(this, &location); 91 controller_->Resize(location, event.flags()); 92 return true; 93 } 94 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE { 95 controller_->CompleteResize(event.flags()); 96 } 97 virtual void OnMouseCaptureLost() OVERRIDE { 98 controller_->CancelResize(); 99 } 100 virtual gfx::NativeCursor GetCursor( 101 const ui::MouseEvent& event) OVERRIDE { 102 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM; 103 return views::corewm::CompoundEventFilter::CursorForWindowComponent( 104 component); 105 } 106 107 private: 108 MultiWindowResizeController* controller_; 109 const Direction direction_; 110 const gfx::ImageSkia* image_; 111 112 DISALLOW_COPY_AND_ASSIGN(ResizeView); 113 }; 114 115 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards 116 // Contains() to MultiWindowResizeController. 117 class MultiWindowResizeController::ResizeMouseWatcherHost : 118 public views::MouseWatcherHost { 119 public: 120 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {} 121 122 // MouseWatcherHost overrides: 123 virtual bool Contains(const gfx::Point& point_in_screen, 124 MouseEventType type) OVERRIDE { 125 return host_->IsOverWindows(point_in_screen); 126 } 127 128 private: 129 MultiWindowResizeController* host_; 130 131 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost); 132 }; 133 134 MultiWindowResizeController::ResizeWindows::ResizeWindows() 135 : window1(NULL), 136 window2(NULL), 137 direction(TOP_BOTTOM){ 138 } 139 140 MultiWindowResizeController::ResizeWindows::~ResizeWindows() { 141 } 142 143 bool MultiWindowResizeController::ResizeWindows::Equals( 144 const ResizeWindows& other) const { 145 return window1 == other.window1 && 146 window2 == other.window2 && 147 direction == other.direction; 148 } 149 150 MultiWindowResizeController::MultiWindowResizeController() { 151 } 152 153 MultiWindowResizeController::~MultiWindowResizeController() { 154 window_resizer_.reset(); 155 Hide(); 156 } 157 158 void MultiWindowResizeController::Show(Window* window, 159 int component, 160 const gfx::Point& point_in_window) { 161 // When the resize widget is showing we ignore Show() requests. Instead we 162 // only care about mouse movements from MouseWatcher. This is necessary as 163 // WorkspaceEventHandler only sees mouse movements over the windows, not all 164 // windows or over the desktop. 165 if (resize_widget_) 166 return; 167 168 ResizeWindows windows(DetermineWindows(window, component, point_in_window)); 169 if (IsShowing()) { 170 if (windows_.Equals(windows)) 171 return; // Over the same windows. 172 DelayedHide(); 173 } 174 175 if (!windows.is_valid()) 176 return; 177 Hide(); 178 windows_ = windows; 179 windows_.window1->AddObserver(this); 180 windows_.window2->AddObserver(this); 181 show_location_in_parent_ = point_in_window; 182 Window::ConvertPointToTarget( 183 window, window->parent(), &show_location_in_parent_); 184 if (show_timer_.IsRunning()) 185 return; 186 show_timer_.Start( 187 FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS), 188 this, &MultiWindowResizeController::ShowIfValidMouseLocation); 189 } 190 191 void MultiWindowResizeController::Hide() { 192 hide_timer_.Stop(); 193 if (window_resizer_) 194 return; // Ignore hides while actively resizing. 195 196 if (windows_.window1) { 197 windows_.window1->RemoveObserver(this); 198 windows_.window1 = NULL; 199 } 200 if (windows_.window2) { 201 windows_.window2->RemoveObserver(this); 202 windows_.window2 = NULL; 203 } 204 205 show_timer_.Stop(); 206 207 if (!resize_widget_) 208 return; 209 210 for (size_t i = 0; i < windows_.other_windows.size(); ++i) 211 windows_.other_windows[i]->RemoveObserver(this); 212 mouse_watcher_.reset(); 213 resize_widget_.reset(); 214 windows_ = ResizeWindows(); 215 } 216 217 void MultiWindowResizeController::MouseMovedOutOfHost() { 218 Hide(); 219 } 220 221 void MultiWindowResizeController::OnWindowDestroying( 222 aura::Window* window) { 223 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing. 224 window_resizer_.reset(); 225 Hide(); 226 } 227 228 MultiWindowResizeController::ResizeWindows 229 MultiWindowResizeController::DetermineWindowsFromScreenPoint( 230 aura::Window* window) const { 231 gfx::Point mouse_location( 232 gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint()); 233 wm::ConvertPointFromScreen(window, &mouse_location); 234 const int component = 235 window->delegate()->GetNonClientComponent(mouse_location); 236 return DetermineWindows(window, component, mouse_location); 237 } 238 239 MultiWindowResizeController::ResizeWindows 240 MultiWindowResizeController::DetermineWindows( 241 Window* window, 242 int window_component, 243 const gfx::Point& point) const { 244 ResizeWindows result; 245 gfx::Point point_in_parent(point); 246 Window::ConvertPointToTarget(window, window->parent(), &point_in_parent); 247 switch (window_component) { 248 case HTRIGHT: 249 result.direction = LEFT_RIGHT; 250 result.window1 = window; 251 result.window2 = FindWindowByEdge( 252 window, HTLEFT, window->bounds().right(), point_in_parent.y()); 253 break; 254 case HTLEFT: 255 result.direction = LEFT_RIGHT; 256 result.window1 = FindWindowByEdge( 257 window, HTRIGHT, window->bounds().x(), point_in_parent.y()); 258 result.window2 = window; 259 break; 260 case HTTOP: 261 result.direction = TOP_BOTTOM; 262 result.window1 = FindWindowByEdge( 263 window, HTBOTTOM, point_in_parent.x(), window->bounds().y()); 264 result.window2 = window; 265 break; 266 case HTBOTTOM: 267 result.direction = TOP_BOTTOM; 268 result.window1 = window; 269 result.window2 = FindWindowByEdge( 270 window, HTTOP, point_in_parent.x(), window->bounds().bottom()); 271 break; 272 default: 273 break; 274 } 275 return result; 276 } 277 278 Window* MultiWindowResizeController::FindWindowByEdge( 279 Window* window_to_ignore, 280 int edge_want, 281 int x, 282 int y) const { 283 Window* parent = window_to_ignore->parent(); 284 const Window::Windows& windows(parent->children()); 285 for (Window::Windows::const_reverse_iterator i = windows.rbegin(); 286 i != windows.rend(); ++i) { 287 Window* window = *i; 288 if (window == window_to_ignore || !window->IsVisible()) 289 continue; 290 switch (edge_want) { 291 case HTLEFT: 292 if (ContainsY(window, y) && window->bounds().x() == x) 293 return window; 294 break; 295 case HTRIGHT: 296 if (ContainsY(window, y) && window->bounds().right() == x) 297 return window; 298 break; 299 case HTTOP: 300 if (ContainsX(window, x) && window->bounds().y() == y) 301 return window; 302 break; 303 case HTBOTTOM: 304 if (ContainsX(window, x) && window->bounds().bottom() == y) 305 return window; 306 break; 307 default: 308 NOTREACHED(); 309 } 310 // Window doesn't contain the edge, but if window contains |point| 311 // it's obscuring any other window that could be at the location. 312 if (window->bounds().Contains(x, y)) 313 return NULL; 314 } 315 return NULL; 316 } 317 318 aura::Window* MultiWindowResizeController::FindWindowTouching( 319 aura::Window* window, 320 Direction direction) const { 321 int right = window->bounds().right(); 322 int bottom = window->bounds().bottom(); 323 Window* parent = window->parent(); 324 const Window::Windows& windows(parent->children()); 325 for (Window::Windows::const_reverse_iterator i = windows.rbegin(); 326 i != windows.rend(); ++i) { 327 Window* other = *i; 328 if (other == window || !other->IsVisible()) 329 continue; 330 switch (direction) { 331 case TOP_BOTTOM: 332 if (other->bounds().y() == bottom && 333 Intersects(other->bounds().x(), other->bounds().right(), 334 window->bounds().x(), window->bounds().right())) { 335 return other; 336 } 337 break; 338 case LEFT_RIGHT: 339 if (other->bounds().x() == right && 340 Intersects(other->bounds().y(), other->bounds().bottom(), 341 window->bounds().y(), window->bounds().bottom())) { 342 return other; 343 } 344 break; 345 default: 346 NOTREACHED(); 347 } 348 } 349 return NULL; 350 } 351 352 void MultiWindowResizeController::FindWindowsTouching( 353 aura::Window* start, 354 Direction direction, 355 std::vector<aura::Window*>* others) const { 356 while (start) { 357 start = FindWindowTouching(start, direction); 358 if (start) 359 others->push_back(start); 360 } 361 } 362 363 void MultiWindowResizeController::DelayedHide() { 364 if (hide_timer_.IsRunning()) 365 return; 366 367 hide_timer_.Start(FROM_HERE, 368 base::TimeDelta::FromMilliseconds(kHideDelayMS), 369 this, &MultiWindowResizeController::Hide); 370 } 371 372 void MultiWindowResizeController::ShowIfValidMouseLocation() { 373 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) || 374 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) { 375 ShowNow(); 376 } else { 377 Hide(); 378 } 379 } 380 381 void MultiWindowResizeController::ShowNow() { 382 DCHECK(!resize_widget_.get()); 383 DCHECK(windows_.is_valid()); 384 show_timer_.Stop(); 385 resize_widget_.reset(new views::Widget); 386 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 387 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 388 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 389 params.parent = Shell::GetContainer( 390 Shell::GetActiveRootWindow(), 391 internal::kShellWindowId_AlwaysOnTopContainer); 392 params.can_activate = false; 393 ResizeView* view = new ResizeView(this, windows_.direction); 394 resize_widget_->set_focus_on_creation(false); 395 resize_widget_->Init(params); 396 views::corewm::SetWindowVisibilityAnimationType( 397 resize_widget_->GetNativeWindow(), 398 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); 399 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController"); 400 resize_widget_->SetContentsView(view); 401 show_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen( 402 windows_.window1->parent(), 403 CalculateResizeWidgetBounds(show_location_in_parent_)); 404 resize_widget_->SetBounds(show_bounds_in_screen_); 405 resize_widget_->Show(); 406 mouse_watcher_.reset(new views::MouseWatcher( 407 new ResizeMouseWatcherHost(this), 408 this)); 409 mouse_watcher_->set_notify_on_exit_time( 410 base::TimeDelta::FromMilliseconds(kHideDelayMS)); 411 mouse_watcher_->Start(); 412 } 413 414 bool MultiWindowResizeController::IsShowing() const { 415 return resize_widget_.get() || show_timer_.IsRunning(); 416 } 417 418 void MultiWindowResizeController::StartResize( 419 const gfx::Point& location_in_screen) { 420 DCHECK(!window_resizer_.get()); 421 DCHECK(windows_.is_valid()); 422 hide_timer_.Stop(); 423 gfx::Point location_in_parent(location_in_screen); 424 aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())-> 425 ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent); 426 std::vector<aura::Window*> windows; 427 windows.push_back(windows_.window2); 428 DCHECK(windows_.other_windows.empty()); 429 FindWindowsTouching(windows_.window2, windows_.direction, 430 &windows_.other_windows); 431 for (size_t i = 0; i < windows_.other_windows.size(); ++i) { 432 windows_.other_windows[i]->AddObserver(this); 433 windows.push_back(windows_.other_windows[i]); 434 } 435 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM; 436 window_resizer_.reset(WorkspaceWindowResizer::Create( 437 windows_.window1, 438 location_in_parent, 439 component, 440 aura::client::WINDOW_MOVE_SOURCE_MOUSE, 441 windows)); 442 } 443 444 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen, 445 int event_flags) { 446 gfx::Point location_in_parent(location_in_screen); 447 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())-> 448 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent); 449 window_resizer_->Drag(location_in_parent, event_flags); 450 gfx::Rect bounds = ScreenAsh::ConvertRectToScreen( 451 windows_.window1->parent(), 452 CalculateResizeWidgetBounds(location_in_parent)); 453 454 if (windows_.direction == LEFT_RIGHT) 455 bounds.set_y(show_bounds_in_screen_.y()); 456 else 457 bounds.set_x(show_bounds_in_screen_.x()); 458 resize_widget_->SetBounds(bounds); 459 } 460 461 void MultiWindowResizeController::CompleteResize(int event_flags) { 462 window_resizer_->CompleteDrag(event_flags); 463 window_resizer_.reset(); 464 465 // Mouse may still be over resizer, if not hide. 466 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint(); 467 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) { 468 Hide(); 469 } else { 470 // If the mouse is over the resizer we need to remove observers on any of 471 // the |other_windows|. If we start another resize we'll recalculate the 472 // |other_windows| and invoke AddObserver() as necessary. 473 for (size_t i = 0; i < windows_.other_windows.size(); ++i) 474 windows_.other_windows[i]->RemoveObserver(this); 475 windows_.other_windows.clear(); 476 } 477 } 478 479 void MultiWindowResizeController::CancelResize() { 480 if (!window_resizer_) 481 return; // Happens if window was destroyed and we nuked the WindowResizer. 482 window_resizer_->RevertDrag(); 483 window_resizer_.reset(); 484 Hide(); 485 } 486 487 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds( 488 const gfx::Point& location_in_parent) const { 489 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize(); 490 int x = 0, y = 0; 491 if (windows_.direction == LEFT_RIGHT) { 492 x = windows_.window1->bounds().right() - pref.width() / 2; 493 y = location_in_parent.y() + kResizeWidgetPadding; 494 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() && 495 y + pref.height() / 2 > windows_.window2->bounds().bottom()) { 496 y = location_in_parent.y() - kResizeWidgetPadding - pref.height(); 497 } 498 } else { 499 x = location_in_parent.x() + kResizeWidgetPadding; 500 if (x + pref.height() / 2 > windows_.window1->bounds().right() && 501 x + pref.height() / 2 > windows_.window2->bounds().right()) { 502 x = location_in_parent.x() - kResizeWidgetPadding - pref.width(); 503 } 504 y = windows_.window1->bounds().bottom() - pref.height() / 2; 505 } 506 return gfx::Rect(x, y, pref.width(), pref.height()); 507 } 508 509 bool MultiWindowResizeController::IsOverWindows( 510 const gfx::Point& location_in_screen) const { 511 if (window_resizer_) 512 return true; // Ignore hides while actively resizing. 513 514 if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen)) 515 return true; 516 517 int hit1, hit2; 518 if (windows_.direction == TOP_BOTTOM) { 519 hit1 = HTBOTTOM; 520 hit2 = HTTOP; 521 } else { 522 hit1 = HTRIGHT; 523 hit2 = HTLEFT; 524 } 525 526 return IsOverWindow(windows_.window1, location_in_screen, hit1) || 527 IsOverWindow(windows_.window2, location_in_screen, hit2); 528 } 529 530 bool MultiWindowResizeController::IsOverWindow( 531 aura::Window* window, 532 const gfx::Point& location_in_screen, 533 int component) const { 534 if (!window->delegate()) 535 return false; 536 537 gfx::Point window_loc(location_in_screen); 538 aura::Window::ConvertPointToTarget( 539 window->GetRootWindow(), window, &window_loc); 540 return window->HitTest(window_loc) && 541 window->delegate()->GetNonClientComponent(window_loc) == component; 542 } 543 544 } // namespace internal 545 } // namespace ash 546