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/toplevel_window_event_handler.h" 6 7 #include "ash/shell.h" 8 #include "ash/wm/resize_shadow_controller.h" 9 #include "ash/wm/window_resizer.h" 10 #include "ash/wm/window_state.h" 11 #include "ash/wm/window_state_observer.h" 12 #include "ash/wm/window_util.h" 13 #include "ash/wm/wm_event.h" 14 #include "base/message_loop/message_loop.h" 15 #include "base/run_loop.h" 16 #include "ui/aura/client/cursor_client.h" 17 #include "ui/aura/env.h" 18 #include "ui/aura/window.h" 19 #include "ui/aura/window_delegate.h" 20 #include "ui/aura/window_event_dispatcher.h" 21 #include "ui/aura/window_observer.h" 22 #include "ui/aura/window_tree_host.h" 23 #include "ui/base/cursor/cursor.h" 24 #include "ui/base/hit_test.h" 25 #include "ui/base/ui_base_types.h" 26 #include "ui/events/event.h" 27 #include "ui/events/event_utils.h" 28 #include "ui/events/gestures/gesture_recognizer.h" 29 #include "ui/gfx/geometry/point_conversions.h" 30 #include "ui/gfx/screen.h" 31 32 namespace { 33 const double kMinHorizVelocityForWindowSwipe = 1100; 34 const double kMinVertVelocityForWindowMinimize = 1000; 35 } 36 37 namespace ash { 38 39 namespace { 40 41 // Returns whether |window| can be moved via a two finger drag given 42 // the hittest results of the two fingers. 43 bool CanStartTwoFingerMove(aura::Window* window, 44 int window_component1, 45 int window_component2) { 46 // We allow moving a window via two fingers when the hittest components are 47 // HTCLIENT. This is done so that a window can be dragged via two fingers when 48 // the tab strip is full and hitting the caption area is difficult. We check 49 // the window type and the state type so that we do not steal touches from the 50 // web contents. 51 if (!wm::GetWindowState(window)->IsNormalOrSnapped() || 52 window->type() != ui::wm::WINDOW_TYPE_NORMAL) { 53 return false; 54 } 55 int component1_behavior = 56 WindowResizer::GetBoundsChangeForWindowComponent(window_component1); 57 int component2_behavior = 58 WindowResizer::GetBoundsChangeForWindowComponent(window_component2); 59 return (component1_behavior & WindowResizer::kBoundsChange_Resizes) == 0 && 60 (component2_behavior & WindowResizer::kBoundsChange_Resizes) == 0; 61 } 62 63 // Returns whether |window| can be moved or resized via one finger given 64 // |window_component|. 65 bool CanStartOneFingerDrag(int window_component) { 66 return WindowResizer::GetBoundsChangeForWindowComponent( 67 window_component) != 0; 68 } 69 70 gfx::Point ConvertPointToParent(aura::Window* window, 71 const gfx::Point& point) { 72 gfx::Point result(point); 73 aura::Window::ConvertPointToTarget(window, window->parent(), &result); 74 return result; 75 } 76 77 // Returns the window component containing |event|'s location. 78 int GetWindowComponent(aura::Window* window, const ui::LocatedEvent& event) { 79 return window->delegate()->GetNonClientComponent(event.location()); 80 } 81 82 } // namespace 83 84 // ScopedWindowResizer --------------------------------------------------------- 85 86 // Wraps a WindowResizer and installs an observer on its target window. When 87 // the window is destroyed ResizerWindowDestroyed() is invoked back on the 88 // ToplevelWindowEventHandler to clean up. 89 class ToplevelWindowEventHandler::ScopedWindowResizer 90 : public aura::WindowObserver, 91 public wm::WindowStateObserver { 92 public: 93 ScopedWindowResizer(ToplevelWindowEventHandler* handler, 94 WindowResizer* resizer); 95 virtual ~ScopedWindowResizer(); 96 97 // Returns true if the drag moves the window and does not resize. 98 bool IsMove() const; 99 100 WindowResizer* resizer() { return resizer_.get(); } 101 102 // WindowObserver overrides: 103 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; 104 105 // WindowStateObserver overrides: 106 virtual void OnPreWindowStateTypeChange(wm::WindowState* window_state, 107 wm::WindowStateType type) OVERRIDE; 108 109 private: 110 ToplevelWindowEventHandler* handler_; 111 scoped_ptr<WindowResizer> resizer_; 112 113 DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer); 114 }; 115 116 ToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer( 117 ToplevelWindowEventHandler* handler, 118 WindowResizer* resizer) 119 : handler_(handler), 120 resizer_(resizer) { 121 resizer_->GetTarget()->AddObserver(this); 122 wm::GetWindowState(resizer_->GetTarget())->AddObserver(this); 123 } 124 125 ToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() { 126 resizer_->GetTarget()->RemoveObserver(this); 127 wm::GetWindowState(resizer_->GetTarget())->RemoveObserver(this); 128 } 129 130 bool ToplevelWindowEventHandler::ScopedWindowResizer::IsMove() const { 131 return resizer_->details().bounds_change == 132 WindowResizer::kBoundsChange_Repositions; 133 } 134 135 void 136 ToplevelWindowEventHandler::ScopedWindowResizer::OnPreWindowStateTypeChange( 137 wm::WindowState* window_state, 138 wm::WindowStateType old) { 139 handler_->CompleteDrag(DRAG_COMPLETE); 140 } 141 142 void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying( 143 aura::Window* window) { 144 DCHECK_EQ(resizer_->GetTarget(), window); 145 handler_->ResizerWindowDestroyed(); 146 } 147 148 // ToplevelWindowEventHandler -------------------------------------------------- 149 150 ToplevelWindowEventHandler::ToplevelWindowEventHandler() 151 : first_finger_hittest_(HTNOWHERE), 152 in_move_loop_(false), 153 in_gesture_drag_(false), 154 drag_reverted_(false), 155 destroyed_(NULL) { 156 Shell::GetInstance()->display_controller()->AddObserver(this); 157 } 158 159 ToplevelWindowEventHandler::~ToplevelWindowEventHandler() { 160 Shell::GetInstance()->display_controller()->RemoveObserver(this); 161 if (destroyed_) 162 *destroyed_ = true; 163 } 164 165 void ToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) { 166 if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED && 167 event->key_code() == ui::VKEY_ESCAPE) { 168 CompleteDrag(DRAG_REVERT); 169 } 170 } 171 172 void ToplevelWindowEventHandler::OnMouseEvent( 173 ui::MouseEvent* event) { 174 if (event->handled()) 175 return; 176 if ((event->flags() & 177 (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0) 178 return; 179 180 if (in_gesture_drag_) 181 return; 182 183 aura::Window* target = static_cast<aura::Window*>(event->target()); 184 switch (event->type()) { 185 case ui::ET_MOUSE_PRESSED: 186 HandleMousePressed(target, event); 187 break; 188 case ui::ET_MOUSE_DRAGGED: 189 HandleDrag(target, event); 190 break; 191 case ui::ET_MOUSE_CAPTURE_CHANGED: 192 case ui::ET_MOUSE_RELEASED: 193 HandleMouseReleased(target, event); 194 break; 195 case ui::ET_MOUSE_MOVED: 196 HandleMouseMoved(target, event); 197 break; 198 case ui::ET_MOUSE_EXITED: 199 HandleMouseExited(target, event); 200 break; 201 default: 202 break; 203 } 204 } 205 206 void ToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) { 207 if (event->handled()) 208 return; 209 aura::Window* target = static_cast<aura::Window*>(event->target()); 210 if (!target->delegate()) 211 return; 212 213 if (window_resizer_.get() && !in_gesture_drag_) 214 return; 215 216 if (window_resizer_.get() && 217 window_resizer_->resizer()->GetTarget() != target) { 218 return; 219 } 220 221 if (event->details().touch_points() > 2) { 222 if (CompleteDrag(DRAG_COMPLETE)) 223 event->StopPropagation(); 224 return; 225 } 226 227 switch (event->type()) { 228 case ui::ET_GESTURE_TAP_DOWN: { 229 int component = GetWindowComponent(target, *event); 230 if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) & 231 WindowResizer::kBoundsChange_Resizes)) 232 return; 233 ResizeShadowController* controller = 234 Shell::GetInstance()->resize_shadow_controller(); 235 if (controller) 236 controller->ShowShadow(target, component); 237 return; 238 } 239 case ui::ET_GESTURE_END: { 240 ResizeShadowController* controller = 241 Shell::GetInstance()->resize_shadow_controller(); 242 if (controller) 243 controller->HideShadow(target); 244 245 if (window_resizer_.get() && 246 (event->details().touch_points() == 1 || 247 !CanStartOneFingerDrag(first_finger_hittest_))) { 248 CompleteDrag(DRAG_COMPLETE); 249 event->StopPropagation(); 250 } 251 return; 252 } 253 case ui::ET_GESTURE_BEGIN: { 254 if (event->details().touch_points() == 1) { 255 first_finger_hittest_ = GetWindowComponent(target, *event); 256 } else if (window_resizer_.get()) { 257 if (!window_resizer_->IsMove()) { 258 // The transition from resizing with one finger to resizing with two 259 // fingers causes unintended resizing because the location of 260 // ET_GESTURE_SCROLL_UPDATE jumps from the position of the first 261 // finger to the position in the middle of the two fingers. For this 262 // reason two finger resizing is not supported. 263 CompleteDrag(DRAG_COMPLETE); 264 event->StopPropagation(); 265 } 266 } else { 267 int second_finger_hittest = GetWindowComponent(target, *event); 268 if (CanStartTwoFingerMove( 269 target, first_finger_hittest_, second_finger_hittest)) { 270 gfx::Point location_in_parent = 271 event->details().bounding_box().CenterPoint(); 272 AttemptToStartDrag(target, location_in_parent, HTCAPTION, 273 aura::client::WINDOW_MOVE_SOURCE_TOUCH); 274 event->StopPropagation(); 275 } 276 } 277 return; 278 } 279 case ui::ET_GESTURE_SCROLL_BEGIN: { 280 // The one finger drag is not started in ET_GESTURE_BEGIN to avoid the 281 // window jumping upon initiating a two finger drag. When a one finger 282 // drag is converted to a two finger drag, a jump occurs because the 283 // location of the ET_GESTURE_SCROLL_UPDATE event switches from the single 284 // finger's position to the position in the middle of the two fingers. 285 if (window_resizer_.get()) 286 return; 287 int component = GetWindowComponent(target, *event); 288 if (!CanStartOneFingerDrag(component)) 289 return; 290 gfx::Point location_in_parent( 291 ConvertPointToParent(target, event->location())); 292 AttemptToStartDrag(target, location_in_parent, component, 293 aura::client::WINDOW_MOVE_SOURCE_TOUCH); 294 event->StopPropagation(); 295 return; 296 } 297 default: 298 break; 299 } 300 301 if (!window_resizer_.get()) 302 return; 303 304 switch (event->type()) { 305 case ui::ET_GESTURE_SCROLL_UPDATE: 306 HandleDrag(target, event); 307 event->StopPropagation(); 308 return; 309 case ui::ET_GESTURE_SCROLL_END: 310 // We must complete the drag here instead of as a result of ET_GESTURE_END 311 // because otherwise the drag will be reverted when EndMoveLoop() is 312 // called. 313 // TODO(pkotwicz): Pass drag completion status to 314 // WindowMoveClient::EndMoveLoop(). 315 CompleteDrag(DRAG_COMPLETE); 316 event->StopPropagation(); 317 return; 318 case ui::ET_SCROLL_FLING_START: 319 CompleteDrag(DRAG_COMPLETE); 320 321 // TODO(pkotwicz): Fix tests which inadvertantly start flings and check 322 // window_resizer_->IsMove() instead of the hittest component at |event|'s 323 // location. 324 if (GetWindowComponent(target, *event) != HTCAPTION || 325 !wm::GetWindowState(target)->IsNormalOrSnapped()) { 326 return; 327 } 328 329 if (event->details().velocity_y() > kMinVertVelocityForWindowMinimize) { 330 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MINIMIZED); 331 } else if (event->details().velocity_y() < 332 -kMinVertVelocityForWindowMinimize) { 333 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MAXIMIZED); 334 } else if (event->details().velocity_x() > 335 kMinHorizVelocityForWindowSwipe) { 336 SetWindowStateTypeFromGesture(target, 337 wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED); 338 } else if (event->details().velocity_x() < 339 -kMinHorizVelocityForWindowSwipe) { 340 SetWindowStateTypeFromGesture(target, 341 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED); 342 } 343 event->StopPropagation(); 344 return; 345 case ui::ET_GESTURE_SWIPE: 346 DCHECK_GT(event->details().touch_points(), 0); 347 if (event->details().touch_points() == 1) 348 return; 349 if (!wm::GetWindowState(target)->IsNormalOrSnapped()) 350 return; 351 352 CompleteDrag(DRAG_COMPLETE); 353 354 if (event->details().swipe_down()) { 355 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MINIMIZED); 356 } else if (event->details().swipe_up()) { 357 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MAXIMIZED); 358 } else if (event->details().swipe_right()) { 359 SetWindowStateTypeFromGesture(target, 360 wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED); 361 } else { 362 SetWindowStateTypeFromGesture(target, 363 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED); 364 } 365 event->StopPropagation(); 366 return; 367 default: 368 return; 369 } 370 } 371 372 aura::client::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop( 373 aura::Window* source, 374 const gfx::Vector2d& drag_offset, 375 aura::client::WindowMoveSource move_source) { 376 DCHECK(!in_move_loop_); // Can only handle one nested loop at a time. 377 aura::Window* root_window = source->GetRootWindow(); 378 DCHECK(root_window); 379 // TODO(tdresser): Use gfx::PointF. See crbug.com/337824. 380 gfx::Point drag_location; 381 if (move_source == aura::client::WINDOW_MOVE_SOURCE_TOUCH && 382 aura::Env::GetInstance()->is_touch_down()) { 383 gfx::PointF drag_location_f; 384 bool has_point = ui::GestureRecognizer::Get()-> 385 GetLastTouchPointForTarget(source, &drag_location_f); 386 drag_location = gfx::ToFlooredPoint(drag_location_f); 387 DCHECK(has_point); 388 } else { 389 drag_location = 390 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot(); 391 aura::Window::ConvertPointToTarget( 392 root_window, source->parent(), &drag_location); 393 } 394 // Set the cursor before calling AttemptToStartDrag(), as that will 395 // eventually call LockCursor() and prevent the cursor from changing. 396 aura::client::CursorClient* cursor_client = 397 aura::client::GetCursorClient(root_window); 398 if (cursor_client) 399 cursor_client->SetCursor(ui::kCursorPointer); 400 if (!AttemptToStartDrag(source, drag_location, HTCAPTION, move_source)) 401 return aura::client::MOVE_CANCELED; 402 403 in_move_loop_ = true; 404 bool destroyed = false; 405 destroyed_ = &destroyed; 406 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 407 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 408 base::RunLoop run_loop; 409 quit_closure_ = run_loop.QuitClosure(); 410 run_loop.Run(); 411 if (destroyed) 412 return aura::client::MOVE_CANCELED; 413 destroyed_ = NULL; 414 in_move_loop_ = false; 415 return drag_reverted_ ? aura::client::MOVE_CANCELED : 416 aura::client::MOVE_SUCCESSFUL; 417 } 418 419 void ToplevelWindowEventHandler::EndMoveLoop() { 420 if (in_move_loop_) 421 CompleteDrag(DRAG_REVERT); 422 } 423 424 void ToplevelWindowEventHandler::OnDisplayConfigurationChanging() { 425 CompleteDrag(DRAG_REVERT); 426 } 427 428 bool ToplevelWindowEventHandler::AttemptToStartDrag( 429 aura::Window* window, 430 const gfx::Point& point_in_parent, 431 int window_component, 432 aura::client::WindowMoveSource source) { 433 if (window_resizer_.get()) 434 return false; 435 WindowResizer* resizer = CreateWindowResizer(window, point_in_parent, 436 window_component, source).release(); 437 if (!resizer) 438 return false; 439 440 window_resizer_.reset(new ScopedWindowResizer(this, resizer)); 441 442 pre_drag_window_bounds_ = window->bounds(); 443 in_gesture_drag_ = (source == aura::client::WINDOW_MOVE_SOURCE_TOUCH); 444 return true; 445 } 446 447 bool ToplevelWindowEventHandler::CompleteDrag(DragCompletionStatus status) { 448 if (!window_resizer_) 449 return false; 450 451 scoped_ptr<ScopedWindowResizer> resizer(window_resizer_.release()); 452 switch (status) { 453 case DRAG_COMPLETE: 454 resizer->resizer()->CompleteDrag(); 455 break; 456 case DRAG_REVERT: 457 resizer->resizer()->RevertDrag(); 458 break; 459 case DRAG_RESIZER_WINDOW_DESTROYED: 460 // We explicitly do not invoke RevertDrag() since that may do things to 461 // WindowResizer::GetTarget() which was destroyed. 462 break; 463 } 464 drag_reverted_ = (status != DRAG_COMPLETE); 465 466 first_finger_hittest_ = HTNOWHERE; 467 in_gesture_drag_ = false; 468 if (in_move_loop_) 469 quit_closure_.Run(); 470 return true; 471 } 472 473 void ToplevelWindowEventHandler::HandleMousePressed( 474 aura::Window* target, 475 ui::MouseEvent* event) { 476 if (event->phase() != ui::EP_PRETARGET || !target->delegate()) 477 return; 478 479 // We also update the current window component here because for the 480 // mouse-drag-release-press case, where the mouse is released and 481 // pressed without mouse move event. 482 int component = GetWindowComponent(target, *event); 483 if ((event->flags() & 484 (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK)) == 0 && 485 WindowResizer::GetBoundsChangeForWindowComponent(component)) { 486 gfx::Point location_in_parent( 487 ConvertPointToParent(target, event->location())); 488 AttemptToStartDrag(target, location_in_parent, component, 489 aura::client::WINDOW_MOVE_SOURCE_MOUSE); 490 // Set as handled so that other event handlers do no act upon the event 491 // but still receive it so that they receive both parts of each pressed/ 492 // released pair. 493 event->SetHandled(); 494 } else { 495 CompleteDrag(DRAG_COMPLETE); 496 } 497 } 498 499 void ToplevelWindowEventHandler::HandleMouseReleased( 500 aura::Window* target, 501 ui::MouseEvent* event) { 502 if (event->phase() != ui::EP_PRETARGET) 503 return; 504 505 CompleteDrag(event->type() == ui::ET_MOUSE_RELEASED ? 506 DRAG_COMPLETE : DRAG_REVERT); 507 } 508 509 void ToplevelWindowEventHandler::HandleDrag( 510 aura::Window* target, 511 ui::LocatedEvent* event) { 512 // This function only be triggered to move window 513 // by mouse drag or touch move event. 514 DCHECK(event->type() == ui::ET_MOUSE_DRAGGED || 515 event->type() == ui::ET_TOUCH_MOVED || 516 event->type() == ui::ET_GESTURE_SCROLL_UPDATE); 517 518 // Drag actions are performed pre-target handling to prevent spurious mouse 519 // moves from the move/size operation from being sent to the target. 520 if (event->phase() != ui::EP_PRETARGET) 521 return; 522 523 if (!window_resizer_) 524 return; 525 window_resizer_->resizer()->Drag( 526 ConvertPointToParent(target, event->location()), event->flags()); 527 event->StopPropagation(); 528 } 529 530 void ToplevelWindowEventHandler::HandleMouseMoved( 531 aura::Window* target, 532 ui::LocatedEvent* event) { 533 // Shadow effects are applied after target handling. Note that we don't 534 // respect ER_HANDLED here right now since we have not had a reason to allow 535 // the target to cancel shadow rendering. 536 if (event->phase() != ui::EP_POSTTARGET || !target->delegate()) 537 return; 538 539 // TODO(jamescook): Move the resize cursor update code into here from 540 // CompoundEventFilter? 541 ResizeShadowController* controller = 542 Shell::GetInstance()->resize_shadow_controller(); 543 if (controller) { 544 if (event->flags() & ui::EF_IS_NON_CLIENT) { 545 int component = 546 target->delegate()->GetNonClientComponent(event->location()); 547 controller->ShowShadow(target, component); 548 } else { 549 controller->HideShadow(target); 550 } 551 } 552 } 553 554 void ToplevelWindowEventHandler::HandleMouseExited( 555 aura::Window* target, 556 ui::LocatedEvent* event) { 557 // Shadow effects are applied after target handling. Note that we don't 558 // respect ER_HANDLED here right now since we have not had a reason to allow 559 // the target to cancel shadow rendering. 560 if (event->phase() != ui::EP_POSTTARGET) 561 return; 562 563 ResizeShadowController* controller = 564 Shell::GetInstance()->resize_shadow_controller(); 565 if (controller) 566 controller->HideShadow(target); 567 } 568 569 void ToplevelWindowEventHandler::SetWindowStateTypeFromGesture( 570 aura::Window* window, 571 wm::WindowStateType new_state_type) { 572 wm::WindowState* window_state = ash::wm::GetWindowState(window); 573 // TODO(oshima): Move extra logic (set_unminimize_to_restore_bounds, 574 // SetRestoreBoundsInParent) that modifies the window state 575 // into WindowState. 576 switch (new_state_type) { 577 case wm::WINDOW_STATE_TYPE_MINIMIZED: 578 if (window_state->CanMinimize()) { 579 window_state->Minimize(); 580 window_state->set_unminimize_to_restore_bounds(true); 581 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); 582 } 583 break; 584 case wm::WINDOW_STATE_TYPE_MAXIMIZED: 585 if (window_state->CanMaximize()) { 586 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); 587 window_state->Maximize(); 588 } 589 break; 590 case wm::WINDOW_STATE_TYPE_LEFT_SNAPPED: 591 if (window_state->CanSnap()) { 592 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); 593 const wm::WMEvent event(wm::WM_EVENT_SNAP_LEFT); 594 window_state->OnWMEvent(&event); 595 } 596 break; 597 case wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED: 598 if (window_state->CanSnap()) { 599 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); 600 const wm::WMEvent event(wm::WM_EVENT_SNAP_RIGHT); 601 window_state->OnWMEvent(&event); 602 } 603 break; 604 default: 605 NOTREACHED(); 606 } 607 } 608 609 void ToplevelWindowEventHandler::ResizerWindowDestroyed() { 610 CompleteDrag(DRAG_RESIZER_WINDOW_DESTROYED); 611 } 612 613 } // namespace ash 614