1 // Copyright 2013 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/immersive_fullscreen_controller.h" 6 7 #include <set> 8 9 #include "ash/ash_constants.h" 10 #include "ash/shell.h" 11 #include "ash/wm/resize_handle_window_targeter.h" 12 #include "ash/wm/window_state.h" 13 #include "base/metrics/histogram.h" 14 #include "ui/aura/client/aura_constants.h" 15 #include "ui/aura/client/capture_client.h" 16 #include "ui/aura/client/cursor_client.h" 17 #include "ui/aura/client/screen_position_client.h" 18 #include "ui/aura/env.h" 19 #include "ui/aura/window.h" 20 #include "ui/aura/window_event_dispatcher.h" 21 #include "ui/gfx/animation/slide_animation.h" 22 #include "ui/gfx/display.h" 23 #include "ui/gfx/point.h" 24 #include "ui/gfx/rect.h" 25 #include "ui/gfx/screen.h" 26 #include "ui/views/bubble/bubble_delegate.h" 27 #include "ui/views/view.h" 28 #include "ui/views/widget/widget.h" 29 #include "ui/wm/core/transient_window_manager.h" 30 #include "ui/wm/core/window_util.h" 31 #include "ui/wm/public/activation_client.h" 32 33 using views::View; 34 35 namespace ash { 36 37 namespace { 38 39 // Duration for the reveal show/hide slide animation. The slower duration is 40 // used for the initial slide out to give the user more change to see what 41 // happened. 42 const int kRevealSlowAnimationDurationMs = 400; 43 const int kRevealFastAnimationDurationMs = 200; 44 45 // The delay in milliseconds between the mouse stopping at the top edge of the 46 // screen and the top-of-window views revealing. 47 const int kMouseRevealDelayMs = 200; 48 49 // The maximum amount of pixels that the cursor can move for the cursor to be 50 // considered "stopped". This allows the user to reveal the top-of-window views 51 // without holding the cursor completely still. 52 const int kMouseRevealXThresholdPixels = 3; 53 54 // Used to multiply x value of an update in check to determine if gesture is 55 // vertical. This is used to make sure that gesture is close to vertical instead 56 // of just more vertical then horizontal. 57 const int kSwipeVerticalThresholdMultiplier = 3; 58 59 // The height in pixels of the region above the top edge of the display which 60 // hosts the immersive fullscreen window in which mouse events are ignored 61 // (cannot reveal or unreveal the top-of-window views). 62 // See ShouldIgnoreMouseEventAtLocation() for more details. 63 const int kHeightOfDeadRegionAboveTopContainer = 10; 64 65 // Returns the BubbleDelegateView corresponding to |maybe_bubble| if 66 // |maybe_bubble| is a bubble. 67 views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) { 68 if (!maybe_bubble) 69 return NULL; 70 views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble); 71 if (!widget) 72 return NULL; 73 return widget->widget_delegate()->AsBubbleDelegate(); 74 } 75 76 // Returns true if |maybe_transient| is a transient child of |toplevel|. 77 bool IsWindowTransientChildOf(aura::Window* maybe_transient, 78 aura::Window* toplevel) { 79 if (!maybe_transient || !toplevel) 80 return false; 81 82 for (aura::Window* window = maybe_transient; window; 83 window = ::wm::GetTransientParent(window)) { 84 if (window == toplevel) 85 return true; 86 } 87 return false; 88 } 89 90 // Returns the location of |event| in screen coordinates. 91 gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) { 92 gfx::Point location_in_screen = event.location(); 93 aura::Window* target = static_cast<aura::Window*>(event.target()); 94 aura::client::ScreenPositionClient* screen_position_client = 95 aura::client::GetScreenPositionClient(target->GetRootWindow()); 96 screen_position_client->ConvertPointToScreen(target, &location_in_screen); 97 return location_in_screen; 98 } 99 100 // Returns the bounds of the display nearest to |window| in screen coordinates. 101 gfx::Rect GetDisplayBoundsInScreen(aura::Window* window) { 102 return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds(); 103 } 104 105 } // namespace 106 107 // The height in pixels of the region below the top edge of the display in which 108 // the mouse can trigger revealing the top-of-window views. 109 #if defined(OS_WIN) 110 // Windows 8 reserves some pixels at the top of the screen for the hand icon 111 // that allows you to drag a metro app off the screen, so a few additional 112 // pixels of space must be reserved for the mouse reveal. 113 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 9; 114 #else 115 // The height must be greater than 1px because the top pixel is used to trigger 116 // moving the cursor between displays if the user has a vertical display layout 117 // (primary display above/below secondary display). 118 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3; 119 #endif 120 121 //////////////////////////////////////////////////////////////////////////////// 122 123 // Class which keeps the top-of-window views revealed as long as one of the 124 // bubbles it is observing is visible. The logic to keep the top-of-window 125 // views revealed based on the visibility of bubbles anchored to 126 // children of |ImmersiveFullscreenController::top_container_| is separate from 127 // the logic related to |ImmersiveFullscreenController::focus_revealed_lock_| 128 // so that bubbles which are not activatable and bubbles which do not close 129 // upon deactivation also keep the top-of-window views revealed for the 130 // duration of their visibility. 131 class ImmersiveFullscreenController::BubbleManager 132 : public aura::WindowObserver { 133 public: 134 explicit BubbleManager(ImmersiveFullscreenController* controller); 135 virtual ~BubbleManager(); 136 137 // Start / stop observing changes to |bubble|'s visibility. 138 void StartObserving(aura::Window* bubble); 139 void StopObserving(aura::Window* bubble); 140 141 private: 142 // Updates |revealed_lock_| based on whether any of |bubbles_| is visible. 143 void UpdateRevealedLock(); 144 145 // aura::WindowObserver overrides: 146 virtual void OnWindowVisibilityChanged(aura::Window* window, 147 bool visible) OVERRIDE; 148 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; 149 150 ImmersiveFullscreenController* controller_; 151 152 std::set<aura::Window*> bubbles_; 153 154 // Lock which keeps the top-of-window views revealed based on whether any of 155 // |bubbles_| is visible. 156 scoped_ptr<ImmersiveRevealedLock> revealed_lock_; 157 158 DISALLOW_COPY_AND_ASSIGN(BubbleManager); 159 }; 160 161 ImmersiveFullscreenController::BubbleManager::BubbleManager( 162 ImmersiveFullscreenController* controller) 163 : controller_(controller) { 164 } 165 166 ImmersiveFullscreenController::BubbleManager::~BubbleManager() { 167 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 168 it != bubbles_.end(); ++it) { 169 (*it)->RemoveObserver(this); 170 } 171 } 172 173 void ImmersiveFullscreenController::BubbleManager::StartObserving( 174 aura::Window* bubble) { 175 if (bubbles_.insert(bubble).second) { 176 bubble->AddObserver(this); 177 UpdateRevealedLock(); 178 } 179 } 180 181 void ImmersiveFullscreenController::BubbleManager::StopObserving( 182 aura::Window* bubble) { 183 if (bubbles_.erase(bubble)) { 184 bubble->RemoveObserver(this); 185 UpdateRevealedLock(); 186 } 187 } 188 189 void ImmersiveFullscreenController::BubbleManager::UpdateRevealedLock() { 190 bool has_visible_bubble = false; 191 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 192 it != bubbles_.end(); ++it) { 193 if ((*it)->IsVisible()) { 194 has_visible_bubble = true; 195 break; 196 } 197 } 198 199 bool was_revealed = controller_->IsRevealed(); 200 if (has_visible_bubble) { 201 if (!revealed_lock_.get()) { 202 // Reveal the top-of-window views without animating because it looks 203 // weird for the top-of-window views to animate and the bubble not to 204 // animate along with the top-of-window views. 205 revealed_lock_.reset(controller_->GetRevealedLock( 206 ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); 207 } 208 } else { 209 revealed_lock_.reset(); 210 } 211 212 if (!was_revealed && revealed_lock_.get()) { 213 // Currently, there is no nice way for bubbles to reposition themselves 214 // whenever the anchor view moves. Tell the bubbles to reposition themselves 215 // explicitly instead. The hidden bubbles are also repositioned because 216 // BubbleDelegateView does not reposition its widget as a result of a 217 // visibility change. 218 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 219 it != bubbles_.end(); ++it) { 220 AsBubbleDelegate(*it)->OnAnchorBoundsChanged(); 221 } 222 } 223 } 224 225 void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged( 226 aura::Window*, 227 bool visible) { 228 UpdateRevealedLock(); 229 } 230 231 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying( 232 aura::Window* window) { 233 StopObserving(window); 234 } 235 236 //////////////////////////////////////////////////////////////////////////////// 237 238 ImmersiveFullscreenController::ImmersiveFullscreenController() 239 : delegate_(NULL), 240 top_container_(NULL), 241 widget_(NULL), 242 native_window_(NULL), 243 observers_enabled_(false), 244 enabled_(false), 245 reveal_state_(CLOSED), 246 revealed_lock_count_(0), 247 mouse_x_when_hit_top_in_screen_(-1), 248 gesture_begun_(false), 249 animation_(new gfx::SlideAnimation(this)), 250 animations_disabled_for_test_(false), 251 weak_ptr_factory_(this) { 252 } 253 254 ImmersiveFullscreenController::~ImmersiveFullscreenController() { 255 EnableWindowObservers(false); 256 } 257 258 void ImmersiveFullscreenController::Init(Delegate* delegate, 259 views::Widget* widget, 260 views::View* top_container) { 261 delegate_ = delegate; 262 top_container_ = top_container; 263 widget_ = widget; 264 native_window_ = widget_->GetNativeWindow(); 265 native_window_->SetEventTargeter(scoped_ptr<ui::EventTargeter>( 266 new ResizeHandleWindowTargeter(native_window_, this))); 267 } 268 269 void ImmersiveFullscreenController::SetEnabled(WindowType window_type, 270 bool enabled) { 271 if (enabled_ == enabled) 272 return; 273 enabled_ = enabled; 274 275 EnableWindowObservers(enabled_); 276 277 ash::wm::WindowState* window_state = wm::GetWindowState(native_window_); 278 // Auto hide the shelf in immersive fullscreen instead of hiding it. 279 window_state->set_hide_shelf_when_fullscreen(!enabled); 280 281 // Update the window's immersive mode state for the window manager. 282 window_state->set_in_immersive_fullscreen(enabled); 283 284 Shell::GetInstance()->UpdateShelfVisibility(); 285 286 if (enabled_) { 287 // Animate enabling immersive mode by sliding out the top-of-window views. 288 // No animation occurs if a lock is holding the top-of-window views open. 289 290 // Do a reveal to set the initial state for the animation. (And any 291 // required state in case the animation cannot run because of a lock holding 292 // the top-of-window views open.) 293 MaybeStartReveal(ANIMATE_NO); 294 295 // Reset the located event and the focus revealed locks so that they do not 296 // affect whether the top-of-window views are hidden. 297 located_event_revealed_lock_.reset(); 298 focus_revealed_lock_.reset(); 299 300 // Try doing the animation. 301 MaybeEndReveal(ANIMATE_SLOW); 302 303 if (reveal_state_ == REVEALED) { 304 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate. 305 UpdateLocatedEventRevealedLock(NULL); 306 UpdateFocusRevealedLock(); 307 } else { 308 // Clearing focus is important because it closes focus-related popups like 309 // the touch selection handles. 310 widget_->GetFocusManager()->ClearFocus(); 311 } 312 } else { 313 // Stop cursor-at-top tracking. 314 top_edge_hover_timer_.Stop(); 315 reveal_state_ = CLOSED; 316 317 delegate_->OnImmersiveFullscreenExited(); 318 } 319 320 if (enabled_) { 321 UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType", 322 window_type, 323 WINDOW_TYPE_COUNT); 324 } 325 } 326 327 bool ImmersiveFullscreenController::IsEnabled() const { 328 return enabled_; 329 } 330 331 bool ImmersiveFullscreenController::IsRevealed() const { 332 return enabled_ && reveal_state_ != CLOSED; 333 } 334 335 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock( 336 AnimateReveal animate_reveal) { 337 return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(), 338 animate_reveal); 339 } 340 341 //////////////////////////////////////////////////////////////////////////////// 342 // Testing interface: 343 344 void ImmersiveFullscreenController::SetupForTest() { 345 DCHECK(!enabled_); 346 animations_disabled_for_test_ = true; 347 348 // Move the mouse off of the top-of-window views so that it does not keep the 349 // top-of-window views revealed. 350 std::vector<gfx::Rect> bounds_in_screen( 351 delegate_->GetVisibleBoundsInScreen()); 352 DCHECK(!bounds_in_screen.empty()); 353 int bottommost_in_screen = bounds_in_screen[0].bottom(); 354 for (size_t i = 1; i < bounds_in_screen.size(); ++i) { 355 if (bounds_in_screen[i].bottom() > bottommost_in_screen) 356 bottommost_in_screen = bounds_in_screen[i].bottom(); 357 } 358 gfx::Point cursor_pos(0, bottommost_in_screen + 100); 359 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos); 360 UpdateLocatedEventRevealedLock(NULL); 361 } 362 363 //////////////////////////////////////////////////////////////////////////////// 364 // ui::EventHandler overrides: 365 366 void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent* event) { 367 if (!enabled_) 368 return; 369 370 if (event->type() != ui::ET_MOUSE_MOVED && 371 event->type() != ui::ET_MOUSE_PRESSED && 372 event->type() != ui::ET_MOUSE_RELEASED && 373 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { 374 return; 375 } 376 377 // Mouse hover can initiate revealing the top-of-window views while |widget_| 378 // is inactive. 379 380 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) { 381 top_edge_hover_timer_.Stop(); 382 UpdateLocatedEventRevealedLock(event); 383 } else if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { 384 // Trigger a reveal if the cursor pauses at the top of the screen for a 385 // while. 386 UpdateTopEdgeHoverTimer(event); 387 } 388 } 389 390 void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent* event) { 391 if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED) 392 return; 393 394 // Touch should not initiate revealing the top-of-window views while |widget_| 395 // is inactive. 396 if (!widget_->IsActive()) 397 return; 398 399 UpdateLocatedEventRevealedLock(event); 400 } 401 402 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) { 403 if (!enabled_) 404 return; 405 406 // Touch gestures should not initiate revealing the top-of-window views while 407 // |widget_| is inactive. 408 if (!widget_->IsActive()) 409 return; 410 411 switch (event->type()) { 412 #if defined(OS_WIN) 413 case ui::ET_GESTURE_WIN8_EDGE_SWIPE: 414 UpdateRevealedLocksForSwipe(GetSwipeType(event)); 415 event->SetHandled(); 416 break; 417 #endif 418 case ui::ET_GESTURE_SCROLL_BEGIN: 419 if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) { 420 gesture_begun_ = true; 421 // Do not consume the event. Otherwise, we end up consuming all 422 // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views 423 // when the top-of-window views are revealed. 424 } 425 break; 426 case ui::ET_GESTURE_SCROLL_UPDATE: 427 if (gesture_begun_) { 428 if (UpdateRevealedLocksForSwipe(GetSwipeType(event))) 429 event->SetHandled(); 430 gesture_begun_ = false; 431 } 432 break; 433 case ui::ET_GESTURE_SCROLL_END: 434 case ui::ET_SCROLL_FLING_START: 435 gesture_begun_ = false; 436 break; 437 default: 438 break; 439 } 440 } 441 442 //////////////////////////////////////////////////////////////////////////////// 443 // views::FocusChangeListener overrides: 444 445 void ImmersiveFullscreenController::OnWillChangeFocus( 446 views::View* focused_before, 447 views::View* focused_now) { 448 } 449 450 void ImmersiveFullscreenController::OnDidChangeFocus( 451 views::View* focused_before, 452 views::View* focused_now) { 453 UpdateFocusRevealedLock(); 454 } 455 456 //////////////////////////////////////////////////////////////////////////////// 457 // views::WidgetObserver overrides: 458 459 void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget* widget) { 460 EnableWindowObservers(false); 461 native_window_ = NULL; 462 463 // Set |enabled_| to false such that any calls to MaybeStartReveal() and 464 // MaybeEndReveal() have no effect. 465 enabled_ = false; 466 } 467 468 void ImmersiveFullscreenController::OnWidgetActivationChanged( 469 views::Widget* widget, 470 bool active) { 471 UpdateFocusRevealedLock(); 472 } 473 474 //////////////////////////////////////////////////////////////////////////////// 475 // gfx::AnimationDelegate overrides: 476 477 void ImmersiveFullscreenController::AnimationEnded( 478 const gfx::Animation* animation) { 479 if (reveal_state_ == SLIDING_OPEN) { 480 OnSlideOpenAnimationCompleted(); 481 } else if (reveal_state_ == SLIDING_CLOSED) { 482 OnSlideClosedAnimationCompleted(); 483 } 484 } 485 486 void ImmersiveFullscreenController::AnimationProgressed( 487 const gfx::Animation* animation) { 488 delegate_->SetVisibleFraction(animation->GetCurrentValue()); 489 } 490 491 //////////////////////////////////////////////////////////////////////////////// 492 // aura::WindowObserver overrides: 493 494 void ImmersiveFullscreenController::OnTransientChildAdded( 495 aura::Window* window, 496 aura::Window* transient) { 497 views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient); 498 if (bubble_delegate && 499 bubble_delegate->GetAnchorView() && 500 top_container_->Contains(bubble_delegate->GetAnchorView())) { 501 // Observe the aura::Window because the BubbleDelegateView may not be 502 // parented to the widget's root view yet so |bubble_delegate->GetWidget()| 503 // may still return NULL. 504 bubble_manager_->StartObserving(transient); 505 } 506 } 507 508 void ImmersiveFullscreenController::OnTransientChildRemoved( 509 aura::Window* window, 510 aura::Window* transient) { 511 bubble_manager_->StopObserving(transient); 512 } 513 514 //////////////////////////////////////////////////////////////////////////////// 515 // ash::ImmersiveRevealedLock::Delegate overrides: 516 517 void ImmersiveFullscreenController::LockRevealedState( 518 AnimateReveal animate_reveal) { 519 ++revealed_lock_count_; 520 Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ? 521 ANIMATE_FAST : ANIMATE_NO; 522 MaybeStartReveal(animate); 523 } 524 525 void ImmersiveFullscreenController::UnlockRevealedState() { 526 --revealed_lock_count_; 527 DCHECK_GE(revealed_lock_count_, 0); 528 if (revealed_lock_count_ == 0) { 529 // Always animate ending the reveal fast. 530 MaybeEndReveal(ANIMATE_FAST); 531 } 532 } 533 534 //////////////////////////////////////////////////////////////////////////////// 535 // private: 536 537 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) { 538 if (observers_enabled_ == enable) 539 return; 540 observers_enabled_ = enable; 541 542 views::FocusManager* focus_manager = widget_->GetFocusManager(); 543 544 if (enable) { 545 widget_->AddObserver(this); 546 focus_manager->AddFocusChangeListener(this); 547 Shell::GetInstance()->AddPreTargetHandler(this); 548 ::wm::TransientWindowManager::Get(native_window_)-> 549 AddObserver(this); 550 551 RecreateBubbleManager(); 552 } else { 553 widget_->RemoveObserver(this); 554 focus_manager->RemoveFocusChangeListener(this); 555 Shell::GetInstance()->RemovePreTargetHandler(this); 556 ::wm::TransientWindowManager::Get(native_window_)-> 557 RemoveObserver(this); 558 559 // We have stopped observing whether transient children are added or removed 560 // to |native_window_|. The set of bubbles that BubbleManager is observing 561 // will become stale really quickly. Destroy BubbleManager and recreate it 562 // when we start observing |native_window_| again. 563 bubble_manager_.reset(); 564 565 animation_->Stop(); 566 } 567 } 568 569 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer( 570 ui::MouseEvent* event) { 571 DCHECK(enabled_); 572 DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED); 573 574 // Check whether |native_window_| is the event target's parent window instead 575 // of checking for activation. This allows the timer to be started when 576 // |widget_| is inactive but prevents starting the timer if the mouse is over 577 // a portion of the top edge obscured by an unrelated widget. 578 if (!top_edge_hover_timer_.IsRunning() && 579 !native_window_->Contains(static_cast<aura::Window*>(event->target()))) { 580 return; 581 } 582 583 // Mouse hover should not initiate revealing the top-of-window views while a 584 // window has mouse capture. 585 if (aura::client::GetCaptureWindow(native_window_)) 586 return; 587 588 gfx::Point location_in_screen = GetEventLocationInScreen(*event); 589 if (ShouldIgnoreMouseEventAtLocation(location_in_screen)) 590 return; 591 592 // Stop the timer if the cursor left the top edge or is on a different 593 // display. 594 gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen(native_window_); 595 hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight); 596 if (!hit_bounds_in_screen.Contains(location_in_screen)) { 597 top_edge_hover_timer_.Stop(); 598 return; 599 } 600 601 // The cursor is now at the top of the screen. Consider the cursor "not 602 // moving" even if it moves a little bit because users don't have perfect 603 // pointing precision. (The y position is not tested because 604 // |hit_bounds_in_screen| is short.) 605 if (top_edge_hover_timer_.IsRunning() && 606 abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <= 607 kMouseRevealXThresholdPixels) 608 return; 609 610 // Start the reveal if the cursor doesn't move for some amount of time. 611 mouse_x_when_hit_top_in_screen_ = location_in_screen.x(); 612 top_edge_hover_timer_.Stop(); 613 // Timer is stopped when |this| is destroyed, hence Unretained() is safe. 614 top_edge_hover_timer_.Start( 615 FROM_HERE, 616 base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs), 617 base::Bind( 618 &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock, 619 base::Unretained(this))); 620 } 621 622 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock( 623 ui::LocatedEvent* event) { 624 if (!enabled_) 625 return; 626 DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent()); 627 628 // Neither the mouse nor touch can initiate a reveal when the top-of-window 629 // views are sliding closed or are closed with the following exceptions: 630 // - Hovering at y = 0 which is handled in OnMouseEvent(). 631 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent(). 632 if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED) 633 return; 634 635 // For the sake of simplicity, ignore |widget_|'s activation in computing 636 // whether the top-of-window views should stay revealed. Ideally, the 637 // top-of-window views would stay revealed only when the mouse cursor is 638 // hovered above a non-obscured portion of the top-of-window views. The 639 // top-of-window views may be partially obscured when |widget_| is inactive. 640 641 // Ignore all events while a window has capture. This keeps the top-of-window 642 // views revealed during a drag. 643 if (aura::client::GetCaptureWindow(native_window_)) 644 return; 645 646 gfx::Point location_in_screen; 647 if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { 648 location_in_screen = GetEventLocationInScreen(*event); 649 } else { 650 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient( 651 native_window_->GetRootWindow()); 652 if (!cursor_client->IsMouseEventsEnabled()) { 653 // If mouse events are disabled, the user's last interaction was probably 654 // via touch. Do no do further processing in this case as there is no easy 655 // way of retrieving the position of the user's last touch. 656 return; 657 } 658 location_in_screen = aura::Env::GetInstance()->last_mouse_location(); 659 } 660 661 if ((!event || event->IsMouseEvent()) && 662 ShouldIgnoreMouseEventAtLocation(location_in_screen)) { 663 return; 664 } 665 666 // The visible bounds of |top_container_| should be contained in 667 // |hit_bounds_in_screen|. 668 std::vector<gfx::Rect> hit_bounds_in_screen = 669 delegate_->GetVisibleBoundsInScreen(); 670 bool keep_revealed = false; 671 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) { 672 // Allow the cursor to move slightly off the top-of-window views before 673 // sliding closed. In the case of ImmersiveModeControllerAsh, this helps 674 // when the user is attempting to click on the bookmark bar and overshoots 675 // slightly. 676 if (event && event->type() == ui::ET_MOUSE_MOVED) { 677 const int kBoundsOffsetY = 8; 678 hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY); 679 } 680 681 if (hit_bounds_in_screen[i].Contains(location_in_screen)) { 682 keep_revealed = true; 683 break; 684 } 685 } 686 687 if (keep_revealed) 688 AcquireLocatedEventRevealedLock(); 689 else 690 located_event_revealed_lock_.reset(); 691 } 692 693 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() { 694 // CAUTION: Acquiring the lock results in a reentrant call to 695 // AcquireLocatedEventRevealedLock() when 696 // |ImmersiveFullscreenController::animations_disabled_for_test_| is true. 697 if (!located_event_revealed_lock_.get()) 698 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 699 } 700 701 void ImmersiveFullscreenController::UpdateFocusRevealedLock() { 702 if (!enabled_) 703 return; 704 705 bool hold_lock = false; 706 if (widget_->IsActive()) { 707 views::View* focused_view = widget_->GetFocusManager()->GetFocusedView(); 708 if (top_container_->Contains(focused_view)) 709 hold_lock = true; 710 } else { 711 aura::Window* active_window = aura::client::GetActivationClient( 712 native_window_->GetRootWindow())->GetActiveWindow(); 713 views::BubbleDelegateView* bubble_delegate = 714 AsBubbleDelegate(active_window); 715 if (bubble_delegate && bubble_delegate->anchor_widget()) { 716 // BubbleManager will already have locked the top-of-window views if the 717 // bubble is anchored to a child of |top_container_|. Don't acquire 718 // |focus_revealed_lock_| here for the sake of simplicity. 719 // Note: Instead of checking for the existence of the |anchor_view|, 720 // the existence of the |anchor_widget| is performed to avoid the case 721 // where the view is already gone (and the widget is still running). 722 } else { 723 // The currently active window is not |native_window_| and it is not a 724 // bubble with an anchor view. The top-of-window views should be revealed 725 // if: 726 // 1) The active window is a transient child of |native_window_|. 727 // 2) The top-of-window views are already revealed. This restriction 728 // prevents a transient window opened by the web contents while the 729 // top-of-window views are hidden from from initiating a reveal. 730 // The top-of-window views will stay revealed till |native_window_| is 731 // reactivated. 732 if (IsRevealed() && 733 IsWindowTransientChildOf(active_window, native_window_)) { 734 hold_lock = true; 735 } 736 } 737 } 738 739 if (hold_lock) { 740 if (!focus_revealed_lock_.get()) 741 focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 742 } else { 743 focus_revealed_lock_.reset(); 744 } 745 } 746 747 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe( 748 SwipeType swipe_type) { 749 if (!enabled_ || swipe_type == SWIPE_NONE) 750 return false; 751 752 // Swipes while |native_window_| is inactive should have been filtered out in 753 // OnGestureEvent(). 754 DCHECK(widget_->IsActive()); 755 756 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) { 757 if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) { 758 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 759 return true; 760 } 761 } else { 762 if (swipe_type == SWIPE_CLOSE) { 763 // Attempt to end the reveal. If other code is holding onto a lock, the 764 // attempt will be unsuccessful. 765 located_event_revealed_lock_.reset(); 766 focus_revealed_lock_.reset(); 767 768 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) { 769 widget_->GetFocusManager()->ClearFocus(); 770 return true; 771 } 772 773 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate. 774 UpdateLocatedEventRevealedLock(NULL); 775 UpdateFocusRevealedLock(); 776 } 777 } 778 return false; 779 } 780 781 int ImmersiveFullscreenController::GetAnimationDuration(Animate animate) const { 782 switch (animate) { 783 case ANIMATE_NO: 784 return 0; 785 case ANIMATE_SLOW: 786 return kRevealSlowAnimationDurationMs; 787 case ANIMATE_FAST: 788 return kRevealFastAnimationDurationMs; 789 } 790 NOTREACHED(); 791 return 0; 792 } 793 794 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) { 795 if (!enabled_) 796 return; 797 798 if (animations_disabled_for_test_) 799 animate = ANIMATE_NO; 800 801 // Callers with ANIMATE_NO expect this function to synchronously reveal the 802 // top-of-window views. 803 if (reveal_state_ == REVEALED || 804 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) { 805 return; 806 } 807 808 RevealState previous_reveal_state = reveal_state_; 809 reveal_state_ = SLIDING_OPEN; 810 if (previous_reveal_state == CLOSED) { 811 delegate_->OnImmersiveRevealStarted(); 812 813 // Do not do any more processing if OnImmersiveRevealStarted() changed 814 // |reveal_state_|. 815 if (reveal_state_ != SLIDING_OPEN) 816 return; 817 } 818 // Slide in the reveal view. 819 if (animate == ANIMATE_NO) { 820 animation_->Reset(1); 821 OnSlideOpenAnimationCompleted(); 822 } else { 823 animation_->SetSlideDuration(GetAnimationDuration(animate)); 824 animation_->Show(); 825 } 826 } 827 828 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() { 829 DCHECK_EQ(SLIDING_OPEN, reveal_state_); 830 reveal_state_ = REVEALED; 831 delegate_->SetVisibleFraction(1); 832 833 // The user may not have moved the mouse since the reveal was initiated. 834 // Update the revealed lock to reflect the mouse's current state. 835 UpdateLocatedEventRevealedLock(NULL); 836 } 837 838 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) { 839 if (!enabled_ || revealed_lock_count_ != 0) 840 return; 841 842 if (animations_disabled_for_test_) 843 animate = ANIMATE_NO; 844 845 // Callers with ANIMATE_NO expect this function to synchronously close the 846 // top-of-window views. 847 if (reveal_state_ == CLOSED || 848 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) { 849 return; 850 } 851 852 reveal_state_ = SLIDING_CLOSED; 853 int duration_ms = GetAnimationDuration(animate); 854 if (duration_ms > 0) { 855 animation_->SetSlideDuration(duration_ms); 856 animation_->Hide(); 857 } else { 858 animation_->Reset(0); 859 OnSlideClosedAnimationCompleted(); 860 } 861 } 862 863 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() { 864 DCHECK_EQ(SLIDING_CLOSED, reveal_state_); 865 reveal_state_ = CLOSED; 866 delegate_->OnImmersiveRevealEnded(); 867 } 868 869 ImmersiveFullscreenController::SwipeType 870 ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent* event) const { 871 #if defined(OS_WIN) 872 if (event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE) 873 return SWIPE_OPEN; 874 #endif 875 if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE) 876 return SWIPE_NONE; 877 // Make sure that it is a clear vertical gesture. 878 if (std::abs(event->details().scroll_y()) <= 879 kSwipeVerticalThresholdMultiplier * std::abs(event->details().scroll_x())) 880 return SWIPE_NONE; 881 if (event->details().scroll_y() < 0) 882 return SWIPE_CLOSE; 883 else if (event->details().scroll_y() > 0) 884 return SWIPE_OPEN; 885 return SWIPE_NONE; 886 } 887 888 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation( 889 const gfx::Point& location) const { 890 // Ignore mouse events in the region immediately above the top edge of the 891 // display. This is to handle the case of a user with a vertical display 892 // layout (primary display above/below secondary display) and the immersive 893 // fullscreen window on the bottom display. It is really hard to trigger a 894 // reveal in this case because: 895 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight| 896 // pixels of the bottom display. 897 // - The cursor is warped to the top display if the cursor gets to the top 898 // edge of the bottom display. 899 // Mouse events are ignored in the bottom few pixels of the top display 900 // (Mouse events in this region cannot start or end a reveal). This allows a 901 // user to overshoot the top of the bottom display and still reveal the 902 // top-of-window views. 903 gfx::Rect dead_region = GetDisplayBoundsInScreen(native_window_); 904 dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer); 905 dead_region.set_height(kHeightOfDeadRegionAboveTopContainer); 906 return dead_region.Contains(location); 907 } 908 909 bool ImmersiveFullscreenController::ShouldHandleGestureEvent( 910 const gfx::Point& location) const { 911 DCHECK(widget_->IsActive()); 912 if (reveal_state_ == REVEALED) { 913 std::vector<gfx::Rect> hit_bounds_in_screen( 914 delegate_->GetVisibleBoundsInScreen()); 915 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) { 916 if (hit_bounds_in_screen[i].Contains(location)) 917 return true; 918 } 919 return false; 920 } 921 922 // When the top-of-window views are not fully revealed, handle gestures which 923 // start in the top few pixels of the screen. 924 gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_)); 925 hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset); 926 if (hit_bounds_in_screen.Contains(location)) 927 return true; 928 929 // There may be a bezel sensor off screen logically above 930 // |hit_bounds_in_screen|. The check for the event not contained by the 931 // closest screen ensures that the event is from a valid bezel (as opposed to 932 // another screen in an extended desktop). 933 gfx::Rect screen_bounds = 934 Shell::GetScreen()->GetDisplayNearestPoint(location).bounds(); 935 return (!screen_bounds.Contains(location) && 936 location.y() < hit_bounds_in_screen.y() && 937 location.x() >= hit_bounds_in_screen.x() && 938 location.x() < hit_bounds_in_screen.right()); 939 } 940 941 void ImmersiveFullscreenController::RecreateBubbleManager() { 942 bubble_manager_.reset(new BubbleManager(this)); 943 const std::vector<aura::Window*> transient_children = 944 ::wm::GetTransientChildren(native_window_); 945 for (size_t i = 0; i < transient_children.size(); ++i) { 946 aura::Window* transient_child = transient_children[i]; 947 views::BubbleDelegateView* bubble_delegate = 948 AsBubbleDelegate(transient_child); 949 if (bubble_delegate && 950 bubble_delegate->GetAnchorView() && 951 top_container_->Contains(bubble_delegate->GetAnchorView())) { 952 bubble_manager_->StartObserving(transient_child); 953 } 954 } 955 } 956 957 } // namespace ash 958