| // 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 <set> |
| |
| #include "ash/ash_constants.h" |
| #include "ash/shell.h" |
| #include "ash/wm/resize_handle_window_targeter.h" |
| #include "ash/wm/window_state.h" |
| #include "base/metrics/histogram.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/window.h" |
| #include "ui/aura/window_event_dispatcher.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/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/transient_window_manager.h" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_client.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; |
| |
| // 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; |
| |
| // 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 = ::wm::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<aura::Window*>(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 |
| |
| // 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. |
| #if defined(OS_WIN) |
| // Windows 8 reserves some pixels at the top of the screen for the hand icon |
| // that allows you to drag a metro app off the screen, so a few additional |
| // pixels of space must be reserved for the mouse reveal. |
| const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 9; |
| #else |
| // 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 ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3; |
| #endif |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // 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<aura::Window*> bubbles_; |
| |
| // Lock which keeps the top-of-window views revealed based on whether any of |
| // |bubbles_| is visible. |
| scoped_ptr<ImmersiveRevealedLock> revealed_lock_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BubbleManager); |
| }; |
| |
| ImmersiveFullscreenController::BubbleManager::BubbleManager( |
| ImmersiveFullscreenController* controller) |
| : controller_(controller) { |
| } |
| |
| ImmersiveFullscreenController::BubbleManager::~BubbleManager() { |
| for (std::set<aura::Window*>::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<aura::Window*>::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<aura::Window*>::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(); |
| native_window_->SetEventTargeter(scoped_ptr<ui::EventTargeter>( |
| new ResizeHandleWindowTargeter(native_window_, this))); |
| } |
| |
| void ImmersiveFullscreenController::SetEnabled(WindowType window_type, |
| bool enabled) { |
| if (enabled_ == enabled) |
| return; |
| enabled_ = enabled; |
| |
| EnableWindowObservers(enabled_); |
| |
| ash::wm::WindowState* window_state = wm::GetWindowState(native_window_); |
| // Auto hide the shelf in immersive fullscreen instead of hiding it. |
| window_state->set_hide_shelf_when_fullscreen(!enabled); |
| |
| // Update the window's immersive mode state for the window manager. |
| window_state->set_in_immersive_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<gfx::Rect> 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()) { |
| #if defined(OS_WIN) |
| case ui::ET_GESTURE_WIN8_EDGE_SWIPE: |
| UpdateRevealedLocksForSwipe(GetSwipeType(event)); |
| event->SetHandled(); |
| break; |
| #endif |
| 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::OnTransientChildAdded( |
| 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::OnTransientChildRemoved( |
| 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); |
| ::wm::TransientWindowManager::Get(native_window_)-> |
| AddObserver(this); |
| |
| RecreateBubbleManager(); |
| } else { |
| widget_->RemoveObserver(this); |
| focus_manager->RemoveFocusChangeListener(this); |
| Shell::GetInstance()->RemovePreTargetHandler(this); |
| ::wm::TransientWindowManager::Get(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<aura::Window*>(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<gfx::Rect> 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 defined(OS_WIN) |
| if (event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE) |
| return SWIPE_OPEN; |
| #endif |
| if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE) |
| return SWIPE_NONE; |
| // Make sure that it is a clear vertical gesture. |
| if (std::abs(event->details().scroll_y()) <= |
| kSwipeVerticalThresholdMultiplier * std::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<gfx::Rect> 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(kImmersiveFullscreenTopEdgeInset); |
| 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<aura::Window*> transient_children = |
| ::wm::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 |