immersive_mode_controller_ash.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
1// Copyright 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 "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h" 6 7#include "ash/ash_switches.h" 8#include "ash/shell.h" 9#include "ash/wm/window_properties.h" 10#include "base/command_line.h" 11#include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 12#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 13#include "chrome/browser/ui/views/frame/browser_view.h" 14#include "chrome/browser/ui/views/frame/top_container_view.h" 15#include "chrome/browser/ui/views/tabs/tab_strip.h" 16#include "chrome/common/chrome_notification_types.h" 17#include "content/public/browser/notification_service.h" 18#include "ui/aura/client/activation_client.h" 19#include "ui/aura/client/aura_constants.h" 20#include "ui/aura/client/capture_client.h" 21#include "ui/aura/env.h" 22#include "ui/aura/window.h" 23#include "ui/aura/window_observer.h" 24#include "ui/base/gestures/gesture_configuration.h" 25#include "ui/compositor/layer_animation_observer.h" 26#include "ui/compositor/scoped_layer_animation_settings.h" 27#include "ui/gfx/screen.h" 28#include "ui/gfx/transform.h" 29#include "ui/views/view.h" 30#include "ui/views/widget/widget.h" 31#include "ui/views/window/non_client_view.h" 32 33using ui::GestureConfiguration; 34using views::View; 35 36namespace { 37 38// The slide open/closed animation looks better if it starts and ends just a 39// few pixels before the view goes completely off the screen, which reduces 40// the visual "pop" as the 2-pixel tall immersive-style tabs become visible. 41const int kAnimationOffsetY = 3; 42 43// Duration for the reveal show/hide slide animation. The slower duration is 44// used for the initial slide out to give the user more change to see what 45// happened. 46const int kRevealSlowAnimationDurationMs = 400; 47const int kRevealFastAnimationDurationMs = 200; 48 49// How many pixels a gesture can start away from the TopContainerView when in 50// closed state and still be considered near it. This is needed to overcome 51// issues with poor location values near the edge of the display. 52const int kNearTopContainerDistance = 5; 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. 57const int kSwipeVerticalThresholdMultiplier = 3; 58 59// If |hovered| is true, moves the mouse above |view|. Moves it outside of 60// |view| otherwise. 61// Should not be called outside of tests. 62void MoveMouse(views::View* view, bool hovered) { 63 gfx::Point cursor_pos; 64 if (!hovered) { 65 int bottom_edge = view->bounds().bottom(); 66 cursor_pos = gfx::Point(0, bottom_edge + 100); 67 } 68 views::View::ConvertPointToScreen(view, &cursor_pos); 69 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos); 70} 71 72// Returns true if the currently active window is a transient child of 73// |toplevel|. 74bool IsActiveWindowTransientChildOf(aura::Window* toplevel) { 75 if (!toplevel) 76 return false; 77 78 aura::Window* active_window = aura::client::GetActivationClient( 79 toplevel->GetRootWindow())->GetActiveWindow(); 80 81 if (!active_window) 82 return false; 83 84 for (aura::Window* window = active_window; window; 85 window = window->transient_parent()) { 86 if (window == toplevel) 87 return true; 88 } 89 return false; 90} 91 92//////////////////////////////////////////////////////////////////////////////// 93 94class RevealedLockAsh : public ImmersiveRevealedLock { 95 public: 96 RevealedLockAsh(const base::WeakPtr<ImmersiveModeControllerAsh>& controller, 97 ImmersiveModeController::AnimateReveal animate_reveal) 98 : controller_(controller) { 99 DCHECK(controller_); 100 controller_->LockRevealedState(animate_reveal); 101 } 102 103 virtual ~RevealedLockAsh() { 104 if (controller_) 105 controller_->UnlockRevealedState(); 106 } 107 108 private: 109 base::WeakPtr<ImmersiveModeControllerAsh> controller_; 110 111 DISALLOW_COPY_AND_ASSIGN(RevealedLockAsh); 112}; 113 114} // namespace 115 116//////////////////////////////////////////////////////////////////////////////// 117 118// Manages widgets which should move in sync with the top-of-window views. 119class ImmersiveModeControllerAsh::AnchoredWidgetManager 120 : public views::WidgetObserver { 121 public: 122 explicit AnchoredWidgetManager(ImmersiveModeControllerAsh* controller); 123 virtual ~AnchoredWidgetManager(); 124 125 // Anchors |widget| such that it stays |y_offset| below the top-of-window 126 // views. |widget| will be repositioned whenever the top-of-window views are 127 // animated (top-of-window views revealing / unrevealing) or the top-of-window 128 // bounds change (eg the bookmark bar is shown). 129 // If the top-of-window views are revealed (or become revealed), |widget| will 130 // keep the top-of-window views revealed till |widget| is hidden or 131 // RemoveAnchoredWidget() is called. 132 void AddAnchoredWidget(views::Widget* widget, int y_offset); 133 134 // Stops managing |widget|'s y position. 135 // Closes the top-of-window views if no locks or other anchored widgets are 136 // keeping the top-of-window views revealed. 137 void RemoveAnchoredWidget(views::Widget* widget); 138 139 // Repositions the anchored widgets for the current top container bounds if 140 // immersive mode is enabled. 141 void MaybeRepositionAnchoredWidgets(); 142 143 // Called when immersive mode has been enabled. 144 void OnImmersiveModeEnabled(); 145 146 const std::set<views::Widget*>& visible_anchored_widgets() const { 147 return visible_; 148 } 149 150 private: 151 // Updates |revealed_lock_| based on the visible anchored widgets. 152 void UpdateRevealedLock(); 153 154 // Updates the y position of |widget| given |y_offset| and the top 155 // container's target bounds. 156 void UpdateWidgetBounds(views::Widget* widget, int y_offset); 157 158 // views::WidgetObserver overrides: 159 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; 160 virtual void OnWidgetVisibilityChanged(views::Widget* widget, 161 bool visible) OVERRIDE; 162 163 ImmersiveModeControllerAsh* controller_; 164 165 // Mapping of anchored widgets to the y offset below the top-of-window views 166 // that they should be positioned at. 167 std::map<views::Widget*, int> widgets_; 168 169 // The subset of |widgets_| which are visible. 170 std::set<views::Widget*> visible_; 171 172 // Lock which keeps the top-of-window views revealed based on the visible 173 // anchored widgets. 174 scoped_ptr<ImmersiveRevealedLock> revealed_lock_; 175 176 DISALLOW_COPY_AND_ASSIGN(AnchoredWidgetManager); 177}; 178 179ImmersiveModeControllerAsh::AnchoredWidgetManager::AnchoredWidgetManager( 180 ImmersiveModeControllerAsh* controller) 181 : controller_(controller) { 182} 183 184ImmersiveModeControllerAsh::AnchoredWidgetManager::~AnchoredWidgetManager() { 185 for (std::map<views::Widget*, int>::iterator it = widgets_.begin(); 186 it != widgets_.end(); ++it) { 187 RemoveAnchoredWidget(it->first); 188 } 189} 190 191void ImmersiveModeControllerAsh::AnchoredWidgetManager::AddAnchoredWidget( 192 views::Widget* widget, 193 int y_offset) { 194 DCHECK(widget); 195 bool already_added = widgets_.count(widget) > 0; 196 widgets_[widget] = y_offset; 197 198 if (already_added) 199 return; 200 201 widget->AddObserver(this); 202 203 if (widget->IsVisible()) 204 visible_.insert(widget); 205 206 UpdateRevealedLock(); 207 UpdateWidgetBounds(widget, y_offset); 208} 209 210void ImmersiveModeControllerAsh::AnchoredWidgetManager::RemoveAnchoredWidget( 211 views::Widget* widget) { 212 if (!widgets_.count(widget)) 213 return; 214 215 widget->RemoveObserver(this); 216 widgets_.erase(widget); 217 visible_.erase(widget); 218 219 UpdateRevealedLock(); 220} 221 222void ImmersiveModeControllerAsh::AnchoredWidgetManager:: 223 MaybeRepositionAnchoredWidgets() { 224 for (std::map<views::Widget*, int>::iterator it = widgets_.begin(); 225 it != widgets_.end(); ++it) { 226 UpdateWidgetBounds(it->first, it->second); 227 } 228 229 UpdateRevealedLock(); 230} 231 232void ImmersiveModeControllerAsh::AnchoredWidgetManager:: 233 OnImmersiveModeEnabled() { 234 UpdateRevealedLock(); 235 // The top container bounds may have changed while immersive mode was 236 // disabled. 237 MaybeRepositionAnchoredWidgets(); 238} 239 240void ImmersiveModeControllerAsh::AnchoredWidgetManager::UpdateRevealedLock() { 241 if (visible_.empty()) { 242 revealed_lock_.reset(); 243 } else if (controller_->IsRevealed()) { 244 // It is hard to determine the required initial transforms and the required 245 // durations of the animations of |visible_| such that they appear to be 246 // anchored to the top-of-window views while the top-of-window views are 247 // animating. Skip to the end of the reveal animation instead. 248 // We do not query the controller's reveal state because we may be called 249 // as a result of LayoutBrowserRootView() in MaybeStartReveal() when 250 // |reveal_state_| is SLIDING_OPEN but no animation is running yet. 251 ui::Layer* top_container_layer = 252 controller_->browser_view_->top_container()->layer(); 253 if (top_container_layer && 254 top_container_layer->GetAnimator()->is_animating()) { 255 controller_->MaybeRevealWithoutAnimation(); 256 } 257 258 if (!revealed_lock_.get()) { 259 revealed_lock_.reset(controller_->GetRevealedLock( 260 ImmersiveModeController::ANIMATE_REVEAL_YES)); 261 } 262 } 263} 264 265void ImmersiveModeControllerAsh::AnchoredWidgetManager::UpdateWidgetBounds( 266 views::Widget* widget, 267 int y_offset) { 268 if (!controller_->IsEnabled() || !widget->IsVisible()) 269 return; 270 271 gfx::Rect top_container_target_bounds = 272 controller_->browser_view_->top_container()->GetTargetBoundsInScreen(); 273 gfx::Rect bounds(widget->GetWindowBoundsInScreen()); 274 bounds.set_y( 275 top_container_target_bounds.bottom() + y_offset); 276 widget->SetBounds(bounds); 277} 278 279void ImmersiveModeControllerAsh::AnchoredWidgetManager::OnWidgetDestroying( 280 views::Widget* widget) { 281 RemoveAnchoredWidget(widget); 282} 283 284void ImmersiveModeControllerAsh::AnchoredWidgetManager:: 285 OnWidgetVisibilityChanged( 286 views::Widget* widget, 287 bool visible) { 288 if (visible) 289 visible_.insert(widget); 290 else 291 visible_.erase(widget); 292 293 UpdateRevealedLock(); 294 295 std::map<views::Widget*, int>::iterator it = widgets_.find(widget); 296 DCHECK(it != widgets_.end()); 297 UpdateWidgetBounds(it->first, it->second); 298} 299 300//////////////////////////////////////////////////////////////////////////////// 301 302// Observer to watch for window restore. views::Widget does not provide a hook 303// to observe for window restore, so do this at the Aura level. 304class ImmersiveModeControllerAsh::WindowObserver : public aura::WindowObserver { 305 public: 306 explicit WindowObserver(ImmersiveModeControllerAsh* controller) 307 : controller_(controller) { 308 controller_->native_window_->AddObserver(this); 309 } 310 311 virtual ~WindowObserver() { 312 controller_->native_window_->RemoveObserver(this); 313 } 314 315 // aura::WindowObserver overrides: 316 virtual void OnWindowPropertyChanged(aura::Window* window, 317 const void* key, 318 intptr_t old) OVERRIDE { 319 using aura::client::kShowStateKey; 320 if (key == kShowStateKey) { 321 // Disable immersive mode when leaving the fullscreen state. 322 ui::WindowShowState show_state = static_cast<ui::WindowShowState>( 323 window->GetProperty(kShowStateKey)); 324 if (controller_->IsEnabled() && 325 show_state != ui::SHOW_STATE_FULLSCREEN && 326 show_state != ui::SHOW_STATE_MINIMIZED) { 327 controller_->browser_view_->FullScreenStateChanged(); 328 } 329 return; 330 } 331 } 332 333 private: 334 ImmersiveModeControllerAsh* controller_; // Not owned. 335 336 DISALLOW_COPY_AND_ASSIGN(WindowObserver); 337}; 338 339//////////////////////////////////////////////////////////////////////////////// 340 341ImmersiveModeControllerAsh::ImmersiveModeControllerAsh() 342 : browser_view_(NULL), 343 observers_enabled_(false), 344 enabled_(false), 345 reveal_state_(CLOSED), 346 revealed_lock_count_(0), 347 tab_indicator_visibility_(TAB_INDICATORS_HIDE), 348 mouse_x_when_hit_top_(-1), 349 native_window_(NULL), 350 weak_ptr_factory_(this), 351 gesture_begun_(false) { 352} 353 354ImmersiveModeControllerAsh::~ImmersiveModeControllerAsh() { 355 // The browser view is being destroyed so there's no need to update its 356 // layout or layers, even if the top views are revealed. But the window 357 // observers still need to be removed. 358 EnableWindowObservers(false); 359} 360 361void ImmersiveModeControllerAsh::LockRevealedState( 362 AnimateReveal animate_reveal) { 363 ++revealed_lock_count_; 364 Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ? 365 ANIMATE_FAST : ANIMATE_NO; 366 MaybeStartReveal(animate); 367} 368 369void ImmersiveModeControllerAsh::UnlockRevealedState() { 370 --revealed_lock_count_; 371 DCHECK_GE(revealed_lock_count_, 0); 372 if (revealed_lock_count_ == 0) { 373 // Always animate ending the reveal fast. 374 MaybeEndReveal(ANIMATE_FAST); 375 } 376} 377 378void ImmersiveModeControllerAsh::MaybeRevealWithoutAnimation() { 379 MaybeStartReveal(ANIMATE_NO); 380} 381 382void ImmersiveModeControllerAsh::Init(BrowserView* browser_view) { 383 browser_view_ = browser_view; 384 // Browser view is detached from its widget during destruction. Cache the 385 // window pointer so |this| can stop observing during destruction. 386 native_window_ = browser_view_->GetNativeWindow(); 387 DCHECK(native_window_); 388 389 // Optionally allow the tab indicators to be hidden. 390 if (CommandLine::ForCurrentProcess()-> 391 HasSwitch(ash::switches::kAshImmersiveHideTabIndicators)) { 392 tab_indicator_visibility_ = TAB_INDICATORS_FORCE_HIDE; 393 } 394 395 anchored_widget_manager_.reset(new AnchoredWidgetManager(this)); 396} 397 398void ImmersiveModeControllerAsh::SetEnabled(bool enabled) { 399 DCHECK(browser_view_) << "Must initialize before enabling"; 400 if (enabled_ == enabled) 401 return; 402 enabled_ = enabled; 403 404 // Delay the initialization of the window observers till the first call to 405 // SetEnabled(true) because FullscreenController is not yet initialized when 406 // Init() is called. 407 EnableWindowObservers(true); 408 409 UpdateUseMinimalChrome(LAYOUT_NO); 410 411 if (enabled_) { 412 // Animate enabling immersive mode by sliding out the top-of-window views. 413 // No animation occurs if a lock is holding the top-of-window views open. 414 415 // Do a reveal to set the initial state for the animation. (And any 416 // required state in case the animation cannot run because of a lock holding 417 // the top-of-window views open.) This call has the side effect of relaying 418 // out |browser_view_|'s root view. 419 MaybeStartReveal(ANIMATE_NO); 420 421 // Reset the mouse and the focus revealed locks so that they do not affect 422 // whether the top-of-window views are hidden. 423 mouse_revealed_lock_.reset(); 424 focus_revealed_lock_.reset(); 425 426 // Try doing the animation. 427 MaybeEndReveal(ANIMATE_SLOW); 428 429 if (reveal_state_ == REVEALED) { 430 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate. 431 UpdateMouseRevealedLock(true, ui::ET_UNKNOWN); 432 UpdateFocusRevealedLock(); 433 } 434 anchored_widget_manager_->OnImmersiveModeEnabled(); 435 } else { 436 // Stop cursor-at-top tracking. 437 top_edge_hover_timer_.Stop(); 438 // Snap immediately to the closed state. 439 reveal_state_ = CLOSED; 440 EnablePaintToLayer(false); 441 browser_view_->tabstrip()->SetImmersiveStyle(false); 442 443 // Relayout the root view because disabling immersive fullscreen may have 444 // changed the result of NonClientFrameView::GetBoundsForClientView(). 445 LayoutBrowserRootView(); 446 } 447} 448 449bool ImmersiveModeControllerAsh::IsEnabled() const { 450 return enabled_; 451} 452 453bool ImmersiveModeControllerAsh::ShouldHideTabIndicators() const { 454 return tab_indicator_visibility_ != TAB_INDICATORS_SHOW; 455} 456 457bool ImmersiveModeControllerAsh::ShouldHideTopViews() const { 458 return enabled_ && reveal_state_ == CLOSED; 459} 460 461bool ImmersiveModeControllerAsh::IsRevealed() const { 462 return enabled_ && reveal_state_ != CLOSED; 463} 464 465void ImmersiveModeControllerAsh::MaybeStackViewAtTop() { 466 if (enabled_ && reveal_state_ != CLOSED) { 467 ui::Layer* reveal_layer = browser_view_->top_container()->layer(); 468 if (reveal_layer) 469 reveal_layer->parent()->StackAtTop(reveal_layer); 470 } 471} 472 473ImmersiveRevealedLock* ImmersiveModeControllerAsh::GetRevealedLock( 474 AnimateReveal animate_reveal) { 475 return new RevealedLockAsh(weak_ptr_factory_.GetWeakPtr(), animate_reveal); 476} 477 478void ImmersiveModeControllerAsh::AnchorWidgetToTopContainer( 479 views::Widget* widget, 480 int y_offset) { 481 anchored_widget_manager_->AddAnchoredWidget(widget, y_offset); 482} 483 484void ImmersiveModeControllerAsh::UnanchorWidgetFromTopContainer( 485 views::Widget* widget) { 486 anchored_widget_manager_->RemoveAnchoredWidget(widget); 487} 488 489void ImmersiveModeControllerAsh::OnTopContainerBoundsChanged() { 490 anchored_widget_manager_->MaybeRepositionAnchoredWidgets(); 491} 492 493//////////////////////////////////////////////////////////////////////////////// 494// Observers: 495 496void ImmersiveModeControllerAsh::Observe( 497 int type, 498 const content::NotificationSource& source, 499 const content::NotificationDetails& details) { 500 DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type); 501 if (enabled_) 502 UpdateUseMinimalChrome(LAYOUT_YES); 503} 504 505void ImmersiveModeControllerAsh::OnMouseEvent(ui::MouseEvent* event) { 506 if (!enabled_) 507 return; 508 509 if (event->flags() & ui::EF_IS_SYNTHESIZED) 510 return; 511 512 // Handle ET_MOUSE_PRESSED and ET_MOUSE_RELEASED so that we get the updated 513 // mouse position ASAP once a nested message loop finishes running. 514 if (event->type() != ui::ET_MOUSE_MOVED && 515 event->type() != ui::ET_MOUSE_PRESSED && 516 event->type() != ui::ET_MOUSE_RELEASED) { 517 return; 518 } 519 520 // Mouse hover should not initiate revealing the top-of-window views while 521 // |native_window_| is inactive. 522 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) 523 return; 524 525 // Mouse hover might trigger a reveal if the cursor pauses at the top of the 526 // screen for a while. 527 UpdateTopEdgeHoverTimer(event); 528 529 UpdateMouseRevealedLock(false, event->type()); 530 // Pass along event for further handling. 531} 532 533void ImmersiveModeControllerAsh::OnGestureEvent(ui::GestureEvent* event) { 534 if (!enabled_) 535 return; 536 537 // Touch gestures should not initiate revealing the top-of-window views while 538 // |native_window_| is inactive. 539 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) 540 return; 541 542 switch (event->type()) { 543 case ui::ET_GESTURE_SCROLL_BEGIN: 544 if (IsNearTopContainer(event->location())) { 545 gesture_begun_ = true; 546 event->SetHandled(); 547 } 548 break; 549 case ui::ET_GESTURE_SCROLL_UPDATE: 550 if (gesture_begun_) { 551 SwipeType swipe_type = GetSwipeType(event); 552 if ((reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) && 553 swipe_type == SWIPE_OPEN) { 554 browser_view_->SetFocusToLocationBar(false); 555 event->SetHandled(); 556 } else if ((reveal_state_ == SLIDING_OPEN || 557 reveal_state_ == REVEALED) && 558 swipe_type == SWIPE_CLOSE) { 559 views::FocusManager* focus_manager = browser_view_->GetFocusManager(); 560 DCHECK(focus_manager); 561 focus_manager->ClearFocus(); 562 event->SetHandled(); 563 } 564 gesture_begun_ = false; 565 } 566 break; 567 case ui::ET_GESTURE_SCROLL_END: 568 case ui::ET_SCROLL_FLING_START: 569 gesture_begun_ = false; 570 break; 571 default: 572 break; 573 } 574} 575 576void ImmersiveModeControllerAsh::OnWillChangeFocus(views::View* focused_before, 577 views::View* focused_now) { 578} 579 580void ImmersiveModeControllerAsh::OnDidChangeFocus(views::View* focused_before, 581 views::View* focused_now) { 582 UpdateMouseRevealedLock(true, ui::ET_UNKNOWN); 583 UpdateFocusRevealedLock(); 584} 585 586void ImmersiveModeControllerAsh::OnWidgetDestroying(views::Widget* widget) { 587 EnableWindowObservers(false); 588 native_window_ = NULL; 589 590 // Set |enabled_| to false such that any calls to MaybeStartReveal() and 591 // MaybeEndReveal() have no effect. 592 enabled_ = false; 593} 594 595void ImmersiveModeControllerAsh::OnWidgetActivationChanged( 596 views::Widget* widget, 597 bool active) { 598 // Mouse hover should not initiate revealing the top-of-window views while 599 // |native_window_| is inactive. 600 top_edge_hover_timer_.Stop(); 601 602 UpdateMouseRevealedLock(true, ui::ET_UNKNOWN); 603 UpdateFocusRevealedLock(); 604} 605 606//////////////////////////////////////////////////////////////////////////////// 607// Animation observer: 608 609void ImmersiveModeControllerAsh::OnImplicitAnimationsCompleted() { 610 if (reveal_state_ == SLIDING_OPEN) 611 OnSlideOpenAnimationCompleted(); 612 else if (reveal_state_ == SLIDING_CLOSED) 613 OnSlideClosedAnimationCompleted(); 614} 615 616//////////////////////////////////////////////////////////////////////////////// 617// Testing interface: 618 619void ImmersiveModeControllerAsh::SetForceHideTabIndicatorsForTest(bool force) { 620 if (force) 621 tab_indicator_visibility_ = TAB_INDICATORS_FORCE_HIDE; 622 else if (tab_indicator_visibility_ == TAB_INDICATORS_FORCE_HIDE) 623 tab_indicator_visibility_ = TAB_INDICATORS_HIDE; 624 UpdateUseMinimalChrome(LAYOUT_YES); 625} 626 627void ImmersiveModeControllerAsh::StartRevealForTest(bool hovered) { 628 MaybeStartReveal(ANIMATE_NO); 629 MoveMouse(browser_view_->top_container(), hovered); 630 UpdateMouseRevealedLock(false, ui::ET_UNKNOWN); 631} 632 633void ImmersiveModeControllerAsh::SetMouseHoveredForTest(bool hovered) { 634 MoveMouse(browser_view_->top_container(), hovered); 635 UpdateMouseRevealedLock(false, ui::ET_UNKNOWN); 636} 637 638//////////////////////////////////////////////////////////////////////////////// 639// private: 640 641void ImmersiveModeControllerAsh::EnableWindowObservers(bool enable) { 642 if (observers_enabled_ == enable) 643 return; 644 observers_enabled_ = enable; 645 646 if (!native_window_) { 647 NOTREACHED() << "ImmersiveModeControllerAsh not initialized"; 648 return; 649 } 650 651 views::Widget* widget = 652 views::Widget::GetWidgetForNativeWindow(native_window_); 653 views::FocusManager* focus_manager = widget->GetFocusManager(); 654 if (enable) { 655 widget->AddObserver(this); 656 focus_manager->AddFocusChangeListener(this); 657 } else { 658 widget->RemoveObserver(this); 659 focus_manager->RemoveFocusChangeListener(this); 660 } 661 662 if (enable) 663 native_window_->AddPreTargetHandler(this); 664 else 665 native_window_->RemovePreTargetHandler(this); 666 667 // The window observer adds and removes itself from the native window. 668 window_observer_.reset(enable ? new WindowObserver(this) : NULL); 669 670 if (enable) { 671 registrar_.Add( 672 this, 673 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 674 content::Source<FullscreenController>( 675 browser_view_->browser()->fullscreen_controller())); 676 } else { 677 registrar_.Remove( 678 this, 679 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 680 content::Source<FullscreenController>( 681 browser_view_->browser()->fullscreen_controller())); 682 } 683 684 if (!enable) 685 StopObservingImplicitAnimations(); 686} 687 688void ImmersiveModeControllerAsh::UpdateTopEdgeHoverTimer( 689 ui::MouseEvent* event) { 690 DCHECK(enabled_); 691 // If the top-of-window views are already revealed or the cursor left the 692 // top edge we don't need to trigger based on a timer anymore. 693 if (reveal_state_ == SLIDING_OPEN || 694 reveal_state_ == REVEALED || 695 event->root_location().y() != 0) { 696 top_edge_hover_timer_.Stop(); 697 return; 698 } 699 // The cursor is now at the top of the screen. Consider the cursor "not 700 // moving" even if it moves a little bit in x, because users don't have 701 // perfect pointing precision. 702 int mouse_x = event->root_location().x(); 703 if (top_edge_hover_timer_.IsRunning() && 704 abs(mouse_x - mouse_x_when_hit_top_) <= 705 GestureConfiguration::immersive_mode_reveal_x_threshold_pixels()) 706 return; 707 708 // Start the reveal if the cursor doesn't move for some amount of time. 709 mouse_x_when_hit_top_ = mouse_x; 710 top_edge_hover_timer_.Stop(); 711 // Timer is stopped when |this| is destroyed, hence Unretained() is safe. 712 top_edge_hover_timer_.Start( 713 FROM_HERE, 714 base::TimeDelta::FromMilliseconds( 715 GestureConfiguration::immersive_mode_reveal_delay_ms()), 716 base::Bind(&ImmersiveModeControllerAsh::AcquireMouseRevealedLock, 717 base::Unretained(this))); 718} 719 720void ImmersiveModeControllerAsh::UpdateMouseRevealedLock( 721 bool maybe_drag, 722 ui::EventType event_type) { 723 if (!enabled_) 724 return; 725 726 // Hover cannot initiate a reveal when the top-of-window views are sliding 727 // closed or are closed. (With the exception of hovering at y = 0 which is 728 // handled in OnMouseEvent() ). 729 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) 730 return; 731 732 // Mouse hover should not keep the top-of-window views revealed if 733 // |native_window_| is not active. 734 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) { 735 mouse_revealed_lock_.reset(); 736 return; 737 } 738 739 // If a window has capture, we may be in the middle of a drag. Delay updating 740 // the revealed lock till we get more specifics via OnMouseEvent(). 741 if (maybe_drag && aura::client::GetCaptureWindow(native_window_)) 742 return; 743 744 views::View* top_container = browser_view_->top_container(); 745 gfx::Point cursor_pos = gfx::Screen::GetScreenFor( 746 native_window_)->GetCursorScreenPoint(); 747 // Transform to the parent of |top_container|. This avoids problems with 748 // coordinate conversion while |top_container|'s layer has an animating 749 // transform and also works properly if |top_container| is not at 0, 0. 750 views::View::ConvertPointFromScreen(top_container->parent(), &cursor_pos); 751 // Allow the cursor to move slightly below the top container's bottom edge 752 // before sliding closed. This helps when the user is attempting to click on 753 // the bookmark bar and overshoots slightly. 754 gfx::Rect hover_bounds = top_container->bounds(); 755 if (event_type == ui::ET_MOUSE_MOVED) { 756 const int kBoundsOffsetY = 8; 757 hover_bounds.Inset(0, -kBoundsOffsetY); 758 } 759 if (hover_bounds.Contains(cursor_pos)) 760 AcquireMouseRevealedLock(); 761 else 762 mouse_revealed_lock_.reset(); 763} 764 765void ImmersiveModeControllerAsh::AcquireMouseRevealedLock() { 766 if (!mouse_revealed_lock_.get()) 767 mouse_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 768} 769 770void ImmersiveModeControllerAsh::UpdateFocusRevealedLock() { 771 if (!enabled_) 772 return; 773 774 bool hold_lock = false; 775 views::Widget* widget = 776 views::Widget::GetWidgetForNativeWindow(native_window_); 777 if (widget->IsActive()) { 778 views::View* focused_view = widget->GetFocusManager()->GetFocusedView(); 779 if (browser_view_->top_container()->Contains(focused_view)) 780 hold_lock = true; 781 } else { 782 // If the currently active window is not |native_window_|, the top-of-window 783 // views should be revealed if: 784 // 1) The newly active window is a transient child of |native_window_|. 785 // 2) The top-of-window views are already revealed. This restriction 786 // prevents a transient window opened by the web contents while the 787 // top-of-window views are hidden from from initiating a reveal. 788 // The top-of-window views will stay revealed till |native_window_| is 789 // reactivated. 790 if (IsRevealed() && IsActiveWindowTransientChildOf(native_window_)) 791 hold_lock = true; 792 } 793 794 if (hold_lock) { 795 if (!focus_revealed_lock_.get()) 796 focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 797 } else { 798 focus_revealed_lock_.reset(); 799 } 800} 801 802void ImmersiveModeControllerAsh::UpdateUseMinimalChrome(Layout layout) { 803 bool in_tab_fullscreen = browser_view_->browser()->fullscreen_controller()-> 804 IsFullscreenForTabOrPending(); 805 bool use_minimal_chrome = !in_tab_fullscreen && enabled_; 806 native_window_->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey, 807 use_minimal_chrome); 808 809 TabIndicatorVisibility previous_tab_indicator_visibility = 810 tab_indicator_visibility_; 811 if (tab_indicator_visibility_ != TAB_INDICATORS_FORCE_HIDE) { 812 tab_indicator_visibility_ = use_minimal_chrome ? 813 TAB_INDICATORS_SHOW : TAB_INDICATORS_HIDE; 814 } 815 816 // Ash on Windows may not have a shell. 817 if (ash::Shell::HasInstance()) { 818 // When using minimal chrome, the shelf is auto-hidden. The auto-hidden 819 // shelf displays a 3px 'light bar' when it is closed. 820 ash::Shell::GetInstance()->UpdateShelfVisibility(); 821 } 822 823 if (tab_indicator_visibility_ != previous_tab_indicator_visibility) { 824 // If the top-of-window views are revealed or animating, the change will 825 // take effect with the layout once the top-of-window views are closed. 826 if (layout == LAYOUT_YES && reveal_state_ == CLOSED) 827 LayoutBrowserRootView(); 828 } 829} 830 831int ImmersiveModeControllerAsh::GetAnimationDuration(Animate animate) const { 832 switch (animate) { 833 case ANIMATE_NO: 834 return 0; 835 case ANIMATE_SLOW: 836 return kRevealSlowAnimationDurationMs; 837 case ANIMATE_FAST: 838 return kRevealFastAnimationDurationMs; 839 } 840 NOTREACHED(); 841 return 0; 842} 843 844void ImmersiveModeControllerAsh::MaybeStartReveal(Animate animate) { 845 if (!enabled_) 846 return; 847 848 // Callers with ANIMATE_NO expect this function to synchronously reveal the 849 // top-of-window views. In particular, this property is used to terminate the 850 // reveal animation if an equivalent animation for the anchored widgets 851 // cannot be created. 852 if (reveal_state_ == REVEALED || 853 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) { 854 return; 855 } 856 857 RevealState previous_reveal_state = reveal_state_; 858 reveal_state_ = SLIDING_OPEN; 859 if (previous_reveal_state == CLOSED) { 860 // Turn on layer painting so we can smoothly animate. 861 EnablePaintToLayer(true); 862 863 // Ensure window caption buttons are updated and the view bounds are 864 // computed at normal (non-immersive-style) size. 865 browser_view_->tabstrip()->SetImmersiveStyle(false); 866 LayoutBrowserRootView(); 867 868 // Do not do any more processing if LayoutBrowserView() changed 869 // |reveal_state_|. 870 if (reveal_state_ != SLIDING_OPEN) 871 return; 872 873 if (animate != ANIMATE_NO) { 874 // Now that we have a layer, move it to the initial offscreen position. 875 ui::Layer* layer = browser_view_->top_container()->layer(); 876 gfx::Transform transform; 877 transform.Translate(0, -layer->bounds().height() + kAnimationOffsetY); 878 layer->SetTransform(transform); 879 880 typedef std::set<views::Widget*> WidgetSet; 881 const WidgetSet& visible_widgets = 882 anchored_widget_manager_->visible_anchored_widgets(); 883 for (WidgetSet::const_iterator it = visible_widgets.begin(); 884 it != visible_widgets.end(); ++it) { 885 (*it)->GetNativeWindow()->SetTransform(transform); 886 } 887 } 888 } 889 // Slide in the reveal view. 890 DoAnimation(gfx::Transform(), GetAnimationDuration(animate)); 891} 892 893void ImmersiveModeControllerAsh::EnablePaintToLayer(bool enable) { 894 browser_view_->top_container()->SetPaintToLayer(enable); 895 896 // Views software compositing is not fully layer aware. If the bookmark bar 897 // is detached while the top container layer slides on or off the screen, 898 // the pixels that become exposed are the remnants of the last software 899 // composite of the BrowserView, not the freshly-exposed bookmark bar. 900 // Force the bookmark bar to paint to a layer so the views composite 901 // properly. The infobar container does not need this treatment because 902 // BrowserView::PaintChildren() always draws it last when it is visible. 903 BookmarkBarView* bookmark_bar = browser_view_->bookmark_bar(); 904 if (!bookmark_bar) 905 return; 906 if (enable && bookmark_bar->IsDetached()) 907 bookmark_bar->SetPaintToLayer(true); 908 else 909 bookmark_bar->SetPaintToLayer(false); 910} 911 912void ImmersiveModeControllerAsh::LayoutBrowserRootView() { 913 // Update the window caption buttons. 914 browser_view_->GetWidget()->non_client_view()->frame_view()-> 915 ResetWindowControls(); 916 browser_view_->frame()->GetRootView()->Layout(); 917} 918 919void ImmersiveModeControllerAsh::OnSlideOpenAnimationCompleted() { 920 DCHECK_EQ(SLIDING_OPEN, reveal_state_); 921 reveal_state_ = REVEALED; 922 923 // The user may not have moved the mouse since the reveal was initiated. 924 // Update the revealed lock to reflect the mouse's current state. 925 UpdateMouseRevealedLock(true, ui::ET_UNKNOWN); 926} 927 928void ImmersiveModeControllerAsh::MaybeEndReveal(Animate animate) { 929 if (!enabled_ || revealed_lock_count_ != 0) 930 return; 931 932 // Callers with ANIMATE_NO expect this function to synchronously close the 933 // top-of-window views. 934 if (reveal_state_ == CLOSED || 935 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) { 936 return; 937 } 938 939 // Visible anchored widgets keep the top-of-window views revealed. 940 DCHECK(anchored_widget_manager_->visible_anchored_widgets().empty()); 941 942 reveal_state_ = SLIDING_CLOSED; 943 int duration_ms = GetAnimationDuration(animate); 944 if (duration_ms > 0) { 945 // The bookmark bar may have become detached during the reveal so ensure 946 // layers are available. This is a no-op for the top container. 947 EnablePaintToLayer(true); 948 949 ui::Layer* top_container_layer = browser_view_->top_container()->layer(); 950 gfx::Transform target_transform; 951 target_transform.Translate(0, 952 -top_container_layer->bounds().height() + kAnimationOffsetY); 953 954 DoAnimation(target_transform, duration_ms); 955 } else { 956 OnSlideClosedAnimationCompleted(); 957 } 958} 959 960void ImmersiveModeControllerAsh::OnSlideClosedAnimationCompleted() { 961 DCHECK_EQ(SLIDING_CLOSED, reveal_state_); 962 reveal_state_ = CLOSED; 963 // Layers aren't needed after animation completes. 964 EnablePaintToLayer(false); 965 // Update tabstrip for closed state. 966 browser_view_->tabstrip()->SetImmersiveStyle(true); 967 LayoutBrowserRootView(); 968} 969 970void ImmersiveModeControllerAsh::DoAnimation( 971 const gfx::Transform& target_transform, 972 int duration_ms) { 973 StopObservingImplicitAnimations(); 974 DoLayerAnimation(browser_view_->top_container()->layer(), target_transform, 975 duration_ms, this); 976 977 typedef std::set<views::Widget*> WidgetSet; 978 const WidgetSet& visible_widgets = 979 anchored_widget_manager_->visible_anchored_widgets(); 980 for (WidgetSet::const_iterator it = visible_widgets.begin(); 981 it != visible_widgets.end(); ++it) { 982 // The anchored widget's bounds are set to the target bounds right when the 983 // animation starts. The transform is used to animate the widget's position. 984 // Using the target bounds allows us to "stay anchored" if other code 985 // changes the widget bounds in the middle of the animation. (This is the 986 // case if the fullscreen exit bubble type is changed during the immersive 987 // reveal animation). 988 DoLayerAnimation((*it)->GetNativeWindow()->layer(), gfx::Transform(), 989 duration_ms, NULL); 990 } 991} 992 993void ImmersiveModeControllerAsh::DoLayerAnimation( 994 ui::Layer* layer, 995 const gfx::Transform& target_transform, 996 int duration_ms, 997 ui::ImplicitAnimationObserver* observer) { 998 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); 999 settings.SetTweenType(ui::Tween::EASE_OUT); 1000 settings.SetTransitionDuration( 1001 base::TimeDelta::FromMilliseconds(duration_ms)); 1002 settings.SetPreemptionStrategy( 1003 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 1004 if (observer) 1005 settings.AddObserver(observer); 1006 layer->SetTransform(target_transform); 1007} 1008 1009 1010ImmersiveModeControllerAsh::SwipeType ImmersiveModeControllerAsh::GetSwipeType( 1011 ui::GestureEvent* event) const { 1012 if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE) 1013 return SWIPE_NONE; 1014 // Make sure that it is a clear vertical gesture. 1015 if (abs(event->details().scroll_y()) <= 1016 kSwipeVerticalThresholdMultiplier * abs(event->details().scroll_x())) 1017 return SWIPE_NONE; 1018 if (event->details().scroll_y() < 0) 1019 return SWIPE_CLOSE; 1020 else if (event->details().scroll_y() > 0) 1021 return SWIPE_OPEN; 1022 return SWIPE_NONE; 1023} 1024 1025bool ImmersiveModeControllerAsh::IsNearTopContainer(gfx::Point location) const { 1026 gfx::Rect near_bounds = 1027 browser_view_->top_container()->GetTargetBoundsInScreen(); 1028 if (reveal_state_ == CLOSED) 1029 near_bounds.Inset(gfx::Insets(0, 0, -kNearTopContainerDistance, 0)); 1030 return near_bounds.Contains(location); 1031} 1032