// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/wm/immersive_fullscreen_controller.h" #include #include "ash/shell.h" #include "ash/wm/window_state.h" #include "base/metrics/histogram.h" #include "ui/aura/client/activation_client.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/screen_position_client.h" #include "ui/aura/env.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" #include "ui/gfx/animation/slide_animation.h" #include "ui/gfx/display.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" #include "ui/gfx/screen.h" #include "ui/views/bubble/bubble_delegate.h" #include "ui/views/corewm/window_util.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" using views::View; namespace ash { namespace { // Duration for the reveal show/hide slide animation. The slower duration is // used for the initial slide out to give the user more change to see what // happened. const int kRevealSlowAnimationDurationMs = 400; const int kRevealFastAnimationDurationMs = 200; // The delay in milliseconds between the mouse stopping at the top edge of the // screen and the top-of-window views revealing. const int kMouseRevealDelayMs = 200; // The maximum amount of pixels that the cursor can move for the cursor to be // considered "stopped". This allows the user to reveal the top-of-window views // without holding the cursor completely still. const int kMouseRevealXThresholdPixels = 3; // How many pixels a gesture can start away from |top_container_| when in // closed state and still be considered near it. This is needed to overcome // issues with poor location values near the edge of the display. const int kNearTopContainerDistance = 8; // Used to multiply x value of an update in check to determine if gesture is // vertical. This is used to make sure that gesture is close to vertical instead // of just more vertical then horizontal. const int kSwipeVerticalThresholdMultiplier = 3; // The height in pixels of the region above the top edge of the display which // hosts the immersive fullscreen window in which mouse events are ignored // (cannot reveal or unreveal the top-of-window views). // See ShouldIgnoreMouseEventAtLocation() for more details. const int kHeightOfDeadRegionAboveTopContainer = 10; // The height in pixels of the region below the top edge of the display in which // the mouse can trigger revealing the top-of-window views. The height must be // greater than 1px because the top pixel is used to trigger moving the cursor // between displays if the user has a vertical display layout (primary display // above/below secondary display). const int kMouseRevealBoundsHeight = 3; // Returns the BubbleDelegateView corresponding to |maybe_bubble| if // |maybe_bubble| is a bubble. views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) { if (!maybe_bubble) return NULL; views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble); if (!widget) return NULL; return widget->widget_delegate()->AsBubbleDelegate(); } // Returns true if |maybe_transient| is a transient child of |toplevel|. bool IsWindowTransientChildOf(aura::Window* maybe_transient, aura::Window* toplevel) { if (!maybe_transient || !toplevel) return false; for (aura::Window* window = maybe_transient; window; window = views::corewm::GetTransientParent(window)) { if (window == toplevel) return true; } return false; } // Returns the location of |event| in screen coordinates. gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) { gfx::Point location_in_screen = event.location(); aura::Window* target = static_cast(event.target()); aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(target->GetRootWindow()); screen_position_client->ConvertPointToScreen(target, &location_in_screen); return location_in_screen; } // Returns the bounds of the display nearest to |window| in screen coordinates. gfx::Rect GetDisplayBoundsInScreen(aura::Window* window) { return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds(); } } // namespace //////////////////////////////////////////////////////////////////////////////// // Class which keeps the top-of-window views revealed as long as one of the // bubbles it is observing is visible. The logic to keep the top-of-window // views revealed based on the visibility of bubbles anchored to // children of |ImmersiveFullscreenController::top_container_| is separate from // the logic related to |ImmersiveFullscreenController::focus_revealed_lock_| // so that bubbles which are not activatable and bubbles which do not close // upon deactivation also keep the top-of-window views revealed for the // duration of their visibility. class ImmersiveFullscreenController::BubbleManager : public aura::WindowObserver { public: explicit BubbleManager(ImmersiveFullscreenController* controller); virtual ~BubbleManager(); // Start / stop observing changes to |bubble|'s visibility. void StartObserving(aura::Window* bubble); void StopObserving(aura::Window* bubble); private: // Updates |revealed_lock_| based on whether any of |bubbles_| is visible. void UpdateRevealedLock(); // aura::WindowObserver overrides: virtual void OnWindowVisibilityChanged(aura::Window* window, bool visible) OVERRIDE; virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; ImmersiveFullscreenController* controller_; std::set bubbles_; // Lock which keeps the top-of-window views revealed based on whether any of // |bubbles_| is visible. scoped_ptr revealed_lock_; DISALLOW_COPY_AND_ASSIGN(BubbleManager); }; ImmersiveFullscreenController::BubbleManager::BubbleManager( ImmersiveFullscreenController* controller) : controller_(controller) { } ImmersiveFullscreenController::BubbleManager::~BubbleManager() { for (std::set::const_iterator it = bubbles_.begin(); it != bubbles_.end(); ++it) { (*it)->RemoveObserver(this); } } void ImmersiveFullscreenController::BubbleManager::StartObserving( aura::Window* bubble) { if (bubbles_.insert(bubble).second) { bubble->AddObserver(this); UpdateRevealedLock(); } } void ImmersiveFullscreenController::BubbleManager::StopObserving( aura::Window* bubble) { if (bubbles_.erase(bubble)) { bubble->RemoveObserver(this); UpdateRevealedLock(); } } void ImmersiveFullscreenController::BubbleManager::UpdateRevealedLock() { bool has_visible_bubble = false; for (std::set::const_iterator it = bubbles_.begin(); it != bubbles_.end(); ++it) { if ((*it)->IsVisible()) { has_visible_bubble = true; break; } } bool was_revealed = controller_->IsRevealed(); if (has_visible_bubble) { if (!revealed_lock_.get()) { // Reveal the top-of-window views without animating because it looks // weird for the top-of-window views to animate and the bubble not to // animate along with the top-of-window views. revealed_lock_.reset(controller_->GetRevealedLock( ImmersiveFullscreenController::ANIMATE_REVEAL_NO)); } } else { revealed_lock_.reset(); } if (!was_revealed && revealed_lock_.get()) { // Currently, there is no nice way for bubbles to reposition themselves // whenever the anchor view moves. Tell the bubbles to reposition themselves // explicitly instead. The hidden bubbles are also repositioned because // BubbleDelegateView does not reposition its widget as a result of a // visibility change. for (std::set::const_iterator it = bubbles_.begin(); it != bubbles_.end(); ++it) { AsBubbleDelegate(*it)->OnAnchorBoundsChanged(); } } } void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged( aura::Window*, bool visible) { UpdateRevealedLock(); } void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying( aura::Window* window) { StopObserving(window); } //////////////////////////////////////////////////////////////////////////////// ImmersiveFullscreenController::ImmersiveFullscreenController() : delegate_(NULL), top_container_(NULL), widget_(NULL), native_window_(NULL), observers_enabled_(false), enabled_(false), reveal_state_(CLOSED), revealed_lock_count_(0), mouse_x_when_hit_top_in_screen_(-1), gesture_begun_(false), animation_(new gfx::SlideAnimation(this)), animations_disabled_for_test_(false), weak_ptr_factory_(this) { } ImmersiveFullscreenController::~ImmersiveFullscreenController() { EnableWindowObservers(false); } void ImmersiveFullscreenController::Init(Delegate* delegate, views::Widget* widget, views::View* top_container) { delegate_ = delegate; top_container_ = top_container; widget_ = widget; native_window_ = widget_->GetNativeWindow(); } void ImmersiveFullscreenController::SetEnabled(WindowType window_type, bool enabled) { if (enabled_ == enabled) return; enabled_ = enabled; EnableWindowObservers(enabled_); // Auto hide the shelf in immersive fullscreen instead of hiding it. wm::GetWindowState(native_window_)->set_hide_shelf_when_fullscreen(!enabled); Shell::GetInstance()->UpdateShelfVisibility(); if (enabled_) { // Animate enabling immersive mode by sliding out the top-of-window views. // No animation occurs if a lock is holding the top-of-window views open. // Do a reveal to set the initial state for the animation. (And any // required state in case the animation cannot run because of a lock holding // the top-of-window views open.) MaybeStartReveal(ANIMATE_NO); // Reset the located event and the focus revealed locks so that they do not // affect whether the top-of-window views are hidden. located_event_revealed_lock_.reset(); focus_revealed_lock_.reset(); // Try doing the animation. MaybeEndReveal(ANIMATE_SLOW); if (reveal_state_ == REVEALED) { // Reveal was unsuccessful. Reacquire the revealed locks if appropriate. UpdateLocatedEventRevealedLock(NULL); UpdateFocusRevealedLock(); } else { // Clearing focus is important because it closes focus-related popups like // the touch selection handles. widget_->GetFocusManager()->ClearFocus(); } } else { // Stop cursor-at-top tracking. top_edge_hover_timer_.Stop(); reveal_state_ = CLOSED; delegate_->OnImmersiveFullscreenExited(); } if (enabled_) { UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType", window_type, WINDOW_TYPE_COUNT); } } bool ImmersiveFullscreenController::IsEnabled() const { return enabled_; } bool ImmersiveFullscreenController::IsRevealed() const { return enabled_ && reveal_state_ != CLOSED; } ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock( AnimateReveal animate_reveal) { return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(), animate_reveal); } //////////////////////////////////////////////////////////////////////////////// // Testing interface: void ImmersiveFullscreenController::SetupForTest() { DCHECK(!enabled_); animations_disabled_for_test_ = true; // Move the mouse off of the top-of-window views so that it does not keep the // top-of-window views revealed. std::vector bounds_in_screen( delegate_->GetVisibleBoundsInScreen()); DCHECK(!bounds_in_screen.empty()); int bottommost_in_screen = bounds_in_screen[0].bottom(); for (size_t i = 1; i < bounds_in_screen.size(); ++i) { if (bounds_in_screen[i].bottom() > bottommost_in_screen) bottommost_in_screen = bounds_in_screen[i].bottom(); } gfx::Point cursor_pos(0, bottommost_in_screen + 100); aura::Env::GetInstance()->set_last_mouse_location(cursor_pos); UpdateLocatedEventRevealedLock(NULL); } //////////////////////////////////////////////////////////////////////////////// // ui::EventHandler overrides: void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent* event) { if (!enabled_) return; if (event->type() != ui::ET_MOUSE_MOVED && event->type() != ui::ET_MOUSE_PRESSED && event->type() != ui::ET_MOUSE_RELEASED && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { return; } // Mouse hover can initiate revealing the top-of-window views while |widget_| // is inactive. if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) { top_edge_hover_timer_.Stop(); UpdateLocatedEventRevealedLock(event); } else if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { // Trigger a reveal if the cursor pauses at the top of the screen for a // while. UpdateTopEdgeHoverTimer(event); } } void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent* event) { if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED) return; // Touch should not initiate revealing the top-of-window views while |widget_| // is inactive. if (!widget_->IsActive()) return; UpdateLocatedEventRevealedLock(event); } void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) { if (!enabled_) return; // Touch gestures should not initiate revealing the top-of-window views while // |widget_| is inactive. if (!widget_->IsActive()) return; switch (event->type()) { case ui::ET_GESTURE_SCROLL_BEGIN: if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) { gesture_begun_ = true; // Do not consume the event. Otherwise, we end up consuming all // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views // when the top-of-window views are revealed. } break; case ui::ET_GESTURE_SCROLL_UPDATE: if (gesture_begun_) { if (UpdateRevealedLocksForSwipe(GetSwipeType(event))) event->SetHandled(); gesture_begun_ = false; } break; case ui::ET_GESTURE_SCROLL_END: case ui::ET_SCROLL_FLING_START: gesture_begun_ = false; break; default: break; } } //////////////////////////////////////////////////////////////////////////////// // views::FocusChangeListener overrides: void ImmersiveFullscreenController::OnWillChangeFocus( views::View* focused_before, views::View* focused_now) { } void ImmersiveFullscreenController::OnDidChangeFocus( views::View* focused_before, views::View* focused_now) { UpdateFocusRevealedLock(); } //////////////////////////////////////////////////////////////////////////////// // views::WidgetObserver overrides: void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget* widget) { EnableWindowObservers(false); native_window_ = NULL; // Set |enabled_| to false such that any calls to MaybeStartReveal() and // MaybeEndReveal() have no effect. enabled_ = false; } void ImmersiveFullscreenController::OnWidgetActivationChanged( views::Widget* widget, bool active) { UpdateFocusRevealedLock(); } //////////////////////////////////////////////////////////////////////////////// // gfx::AnimationDelegate overrides: void ImmersiveFullscreenController::AnimationEnded( const gfx::Animation* animation) { if (reveal_state_ == SLIDING_OPEN) { OnSlideOpenAnimationCompleted(); } else if (reveal_state_ == SLIDING_CLOSED) { OnSlideClosedAnimationCompleted(); } } void ImmersiveFullscreenController::AnimationProgressed( const gfx::Animation* animation) { delegate_->SetVisibleFraction(animation->GetCurrentValue()); } //////////////////////////////////////////////////////////////////////////////// // aura::WindowObserver overrides: void ImmersiveFullscreenController::OnAddTransientChild( aura::Window* window, aura::Window* transient) { views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient); if (bubble_delegate && bubble_delegate->GetAnchorView() && top_container_->Contains(bubble_delegate->GetAnchorView())) { // Observe the aura::Window because the BubbleDelegateView may not be // parented to the widget's root view yet so |bubble_delegate->GetWidget()| // may still return NULL. bubble_manager_->StartObserving(transient); } } void ImmersiveFullscreenController::OnRemoveTransientChild( aura::Window* window, aura::Window* transient) { bubble_manager_->StopObserving(transient); } //////////////////////////////////////////////////////////////////////////////// // ash::ImmersiveRevealedLock::Delegate overrides: void ImmersiveFullscreenController::LockRevealedState( AnimateReveal animate_reveal) { ++revealed_lock_count_; Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ? ANIMATE_FAST : ANIMATE_NO; MaybeStartReveal(animate); } void ImmersiveFullscreenController::UnlockRevealedState() { --revealed_lock_count_; DCHECK_GE(revealed_lock_count_, 0); if (revealed_lock_count_ == 0) { // Always animate ending the reveal fast. MaybeEndReveal(ANIMATE_FAST); } } //////////////////////////////////////////////////////////////////////////////// // private: void ImmersiveFullscreenController::EnableWindowObservers(bool enable) { if (observers_enabled_ == enable) return; observers_enabled_ = enable; views::FocusManager* focus_manager = widget_->GetFocusManager(); if (enable) { widget_->AddObserver(this); focus_manager->AddFocusChangeListener(this); Shell::GetInstance()->AddPreTargetHandler(this); native_window_->AddObserver(this); RecreateBubbleManager(); } else { widget_->RemoveObserver(this); focus_manager->RemoveFocusChangeListener(this); Shell::GetInstance()->RemovePreTargetHandler(this); native_window_->RemoveObserver(this); // We have stopped observing whether transient children are added or removed // to |native_window_|. The set of bubbles that BubbleManager is observing // will become stale really quickly. Destroy BubbleManager and recreate it // when we start observing |native_window_| again. bubble_manager_.reset(); animation_->Stop(); } } void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer( ui::MouseEvent* event) { DCHECK(enabled_); DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED); // Check whether |native_window_| is the event target's parent window instead // of checking for activation. This allows the timer to be started when // |widget_| is inactive but prevents starting the timer if the mouse is over // a portion of the top edge obscured by an unrelated widget. if (!top_edge_hover_timer_.IsRunning() && !native_window_->Contains(static_cast(event->target()))) { return; } // Mouse hover should not initiate revealing the top-of-window views while a // window has mouse capture. if (aura::client::GetCaptureWindow(native_window_)) return; gfx::Point location_in_screen = GetEventLocationInScreen(*event); if (ShouldIgnoreMouseEventAtLocation(location_in_screen)) return; // Stop the timer if the cursor left the top edge or is on a different // display. gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen(native_window_); hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight); if (!hit_bounds_in_screen.Contains(location_in_screen)) { top_edge_hover_timer_.Stop(); return; } // The cursor is now at the top of the screen. Consider the cursor "not // moving" even if it moves a little bit because users don't have perfect // pointing precision. (The y position is not tested because // |hit_bounds_in_screen| is short.) if (top_edge_hover_timer_.IsRunning() && abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <= kMouseRevealXThresholdPixels) return; // Start the reveal if the cursor doesn't move for some amount of time. mouse_x_when_hit_top_in_screen_ = location_in_screen.x(); top_edge_hover_timer_.Stop(); // Timer is stopped when |this| is destroyed, hence Unretained() is safe. top_edge_hover_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs), base::Bind( &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock, base::Unretained(this))); } void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock( ui::LocatedEvent* event) { if (!enabled_) return; DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent()); // Neither the mouse nor touch can initiate a reveal when the top-of-window // views are sliding closed or are closed with the following exceptions: // - Hovering at y = 0 which is handled in OnMouseEvent(). // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent(). if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED) return; // For the sake of simplicity, ignore |widget_|'s activation in computing // whether the top-of-window views should stay revealed. Ideally, the // top-of-window views would stay revealed only when the mouse cursor is // hovered above a non-obscured portion of the top-of-window views. The // top-of-window views may be partially obscured when |widget_| is inactive. // Ignore all events while a window has capture. This keeps the top-of-window // views revealed during a drag. if (aura::client::GetCaptureWindow(native_window_)) return; gfx::Point location_in_screen; if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { location_in_screen = GetEventLocationInScreen(*event); } else { aura::client::CursorClient* cursor_client = aura::client::GetCursorClient( native_window_->GetRootWindow()); if (!cursor_client->IsMouseEventsEnabled()) { // If mouse events are disabled, the user's last interaction was probably // via touch. Do no do further processing in this case as there is no easy // way of retrieving the position of the user's last touch. return; } location_in_screen = aura::Env::GetInstance()->last_mouse_location(); } if ((!event || event->IsMouseEvent()) && ShouldIgnoreMouseEventAtLocation(location_in_screen)) { return; } // The visible bounds of |top_container_| should be contained in // |hit_bounds_in_screen|. std::vector hit_bounds_in_screen = delegate_->GetVisibleBoundsInScreen(); bool keep_revealed = false; for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) { // Allow the cursor to move slightly off the top-of-window views before // sliding closed. In the case of ImmersiveModeControllerAsh, this helps // when the user is attempting to click on the bookmark bar and overshoots // slightly. if (event && event->type() == ui::ET_MOUSE_MOVED) { const int kBoundsOffsetY = 8; hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY); } if (hit_bounds_in_screen[i].Contains(location_in_screen)) { keep_revealed = true; break; } } if (keep_revealed) AcquireLocatedEventRevealedLock(); else located_event_revealed_lock_.reset(); } void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() { // CAUTION: Acquiring the lock results in a reentrant call to // AcquireLocatedEventRevealedLock() when // |ImmersiveFullscreenController::animations_disabled_for_test_| is true. if (!located_event_revealed_lock_.get()) located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); } void ImmersiveFullscreenController::UpdateFocusRevealedLock() { if (!enabled_) return; bool hold_lock = false; if (widget_->IsActive()) { views::View* focused_view = widget_->GetFocusManager()->GetFocusedView(); if (top_container_->Contains(focused_view)) hold_lock = true; } else { aura::Window* active_window = aura::client::GetActivationClient( native_window_->GetRootWindow())->GetActiveWindow(); views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(active_window); if (bubble_delegate && bubble_delegate->anchor_widget()) { // BubbleManager will already have locked the top-of-window views if the // bubble is anchored to a child of |top_container_|. Don't acquire // |focus_revealed_lock_| here for the sake of simplicity. // Note: Instead of checking for the existence of the |anchor_view|, // the existence of the |anchor_widget| is performed to avoid the case // where the view is already gone (and the widget is still running). } else { // The currently active window is not |native_window_| and it is not a // bubble with an anchor view. The top-of-window views should be revealed // if: // 1) The active window is a transient child of |native_window_|. // 2) The top-of-window views are already revealed. This restriction // prevents a transient window opened by the web contents while the // top-of-window views are hidden from from initiating a reveal. // The top-of-window views will stay revealed till |native_window_| is // reactivated. if (IsRevealed() && IsWindowTransientChildOf(active_window, native_window_)) { hold_lock = true; } } } if (hold_lock) { if (!focus_revealed_lock_.get()) focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); } else { focus_revealed_lock_.reset(); } } bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe( SwipeType swipe_type) { if (!enabled_ || swipe_type == SWIPE_NONE) return false; // Swipes while |native_window_| is inactive should have been filtered out in // OnGestureEvent(). DCHECK(widget_->IsActive()); if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) { if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) { located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); return true; } } else { if (swipe_type == SWIPE_CLOSE) { // Attempt to end the reveal. If other code is holding onto a lock, the // attempt will be unsuccessful. located_event_revealed_lock_.reset(); focus_revealed_lock_.reset(); if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) { widget_->GetFocusManager()->ClearFocus(); return true; } // Ending the reveal was unsuccessful. Reaquire the locks if appropriate. UpdateLocatedEventRevealedLock(NULL); UpdateFocusRevealedLock(); } } return false; } int ImmersiveFullscreenController::GetAnimationDuration(Animate animate) const { switch (animate) { case ANIMATE_NO: return 0; case ANIMATE_SLOW: return kRevealSlowAnimationDurationMs; case ANIMATE_FAST: return kRevealFastAnimationDurationMs; } NOTREACHED(); return 0; } void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) { if (!enabled_) return; if (animations_disabled_for_test_) animate = ANIMATE_NO; // Callers with ANIMATE_NO expect this function to synchronously reveal the // top-of-window views. if (reveal_state_ == REVEALED || (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) { return; } RevealState previous_reveal_state = reveal_state_; reveal_state_ = SLIDING_OPEN; if (previous_reveal_state == CLOSED) { delegate_->OnImmersiveRevealStarted(); // Do not do any more processing if OnImmersiveRevealStarted() changed // |reveal_state_|. if (reveal_state_ != SLIDING_OPEN) return; } // Slide in the reveal view. if (animate == ANIMATE_NO) { animation_->Reset(1); OnSlideOpenAnimationCompleted(); } else { animation_->SetSlideDuration(GetAnimationDuration(animate)); animation_->Show(); } } void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() { DCHECK_EQ(SLIDING_OPEN, reveal_state_); reveal_state_ = REVEALED; delegate_->SetVisibleFraction(1); // The user may not have moved the mouse since the reveal was initiated. // Update the revealed lock to reflect the mouse's current state. UpdateLocatedEventRevealedLock(NULL); } void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) { if (!enabled_ || revealed_lock_count_ != 0) return; if (animations_disabled_for_test_) animate = ANIMATE_NO; // Callers with ANIMATE_NO expect this function to synchronously close the // top-of-window views. if (reveal_state_ == CLOSED || (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) { return; } reveal_state_ = SLIDING_CLOSED; int duration_ms = GetAnimationDuration(animate); if (duration_ms > 0) { animation_->SetSlideDuration(duration_ms); animation_->Hide(); } else { animation_->Reset(0); OnSlideClosedAnimationCompleted(); } } void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() { DCHECK_EQ(SLIDING_CLOSED, reveal_state_); reveal_state_ = CLOSED; delegate_->OnImmersiveRevealEnded(); } ImmersiveFullscreenController::SwipeType ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent* event) const { if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE) return SWIPE_NONE; // Make sure that it is a clear vertical gesture. if (abs(event->details().scroll_y()) <= kSwipeVerticalThresholdMultiplier * abs(event->details().scroll_x())) return SWIPE_NONE; if (event->details().scroll_y() < 0) return SWIPE_CLOSE; else if (event->details().scroll_y() > 0) return SWIPE_OPEN; return SWIPE_NONE; } bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation( const gfx::Point& location) const { // Ignore mouse events in the region immediately above the top edge of the // display. This is to handle the case of a user with a vertical display // layout (primary display above/below secondary display) and the immersive // fullscreen window on the bottom display. It is really hard to trigger a // reveal in this case because: // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight| // pixels of the bottom display. // - The cursor is warped to the top display if the cursor gets to the top // edge of the bottom display. // Mouse events are ignored in the bottom few pixels of the top display // (Mouse events in this region cannot start or end a reveal). This allows a // user to overshoot the top of the bottom display and still reveal the // top-of-window views. gfx::Rect dead_region = GetDisplayBoundsInScreen(native_window_); dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer); dead_region.set_height(kHeightOfDeadRegionAboveTopContainer); return dead_region.Contains(location); } bool ImmersiveFullscreenController::ShouldHandleGestureEvent( const gfx::Point& location) const { DCHECK(widget_->IsActive()); if (reveal_state_ == REVEALED) { std::vector hit_bounds_in_screen( delegate_->GetVisibleBoundsInScreen()); for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) { if (hit_bounds_in_screen[i].Contains(location)) return true; } return false; } // When the top-of-window views are not fully revealed, handle gestures which // start in the top few pixels of the screen. gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_)); hit_bounds_in_screen.set_height(kNearTopContainerDistance); if (hit_bounds_in_screen.Contains(location)) return true; // There may be a bezel sensor off screen logically above // |hit_bounds_in_screen|. The check for the event not contained by the // closest screen ensures that the event is from a valid bezel (as opposed to // another screen in an extended desktop). gfx::Rect screen_bounds = Shell::GetScreen()->GetDisplayNearestPoint(location).bounds(); return (!screen_bounds.Contains(location) && location.y() < hit_bounds_in_screen.y() && location.x() >= hit_bounds_in_screen.x() && location.x() < hit_bounds_in_screen.right()); } void ImmersiveFullscreenController::RecreateBubbleManager() { bubble_manager_.reset(new BubbleManager(this)); const std::vector transient_children = views::corewm::GetTransientChildren(native_window_); for (size_t i = 0; i < transient_children.size(); ++i) { aura::Window* transient_child = transient_children[i]; views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient_child); if (bubble_delegate && bubble_delegate->GetAnchorView() && top_container_->Contains(bubble_delegate->GetAnchorView())) { bubble_manager_->StartObserving(transient_child); } } } } // namespace ash