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