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