1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/wm/immersive_fullscreen_controller.h"
9 #include "ash/ash_constants.h"
10 #include "ash/shell.h"
11 #include "ash/wm/resize_handle_window_targeter.h"
12 #include "ash/wm/window_state.h"
13 #include "base/metrics/histogram.h"
14 #include "ui/aura/client/aura_constants.h"
15 #include "ui/aura/client/capture_client.h"
16 #include "ui/aura/client/cursor_client.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/env.h"
19 #include "ui/aura/window.h"
20 #include "ui/aura/window_event_dispatcher.h"
21 #include "ui/gfx/animation/slide_animation.h"
22 #include "ui/gfx/display.h"
23 #include "ui/gfx/geometry/point.h"
24 #include "ui/gfx/geometry/rect.h"
25 #include "ui/gfx/screen.h"
26 #include "ui/views/bubble/bubble_delegate.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29 #include "ui/wm/core/transient_window_manager.h"
30 #include "ui/wm/core/window_util.h"
31 #include "ui/wm/public/activation_client.h"
39 // Duration for the reveal show/hide slide animation. The slower duration is
40 // used for the initial slide out to give the user more change to see what
42 const int kRevealSlowAnimationDurationMs
= 400;
43 const int kRevealFastAnimationDurationMs
= 200;
45 // The delay in milliseconds between the mouse stopping at the top edge of the
46 // screen and the top-of-window views revealing.
47 const int kMouseRevealDelayMs
= 200;
49 // The maximum amount of pixels that the cursor can move for the cursor to be
50 // considered "stopped". This allows the user to reveal the top-of-window views
51 // without holding the cursor completely still.
52 const int kMouseRevealXThresholdPixels
= 3;
54 // Used to multiply x value of an update in check to determine if gesture is
55 // vertical. This is used to make sure that gesture is close to vertical instead
56 // of just more vertical then horizontal.
57 const int kSwipeVerticalThresholdMultiplier
= 3;
59 // The height in pixels of the region above the top edge of the display which
60 // hosts the immersive fullscreen window in which mouse events are ignored
61 // (cannot reveal or unreveal the top-of-window views).
62 // See ShouldIgnoreMouseEventAtLocation() for more details.
63 const int kHeightOfDeadRegionAboveTopContainer
= 10;
65 // Returns the BubbleDelegateView corresponding to |maybe_bubble| if
66 // |maybe_bubble| is a bubble.
67 views::BubbleDelegateView
* AsBubbleDelegate(aura::Window
* maybe_bubble
) {
70 views::Widget
* widget
= views::Widget::GetWidgetForNativeView(maybe_bubble
);
73 return widget
->widget_delegate()->AsBubbleDelegate();
76 // Returns true if |maybe_transient| is a transient child of |toplevel|.
77 bool IsWindowTransientChildOf(aura::Window
* maybe_transient
,
78 aura::Window
* toplevel
) {
79 if (!maybe_transient
|| !toplevel
)
82 for (aura::Window
* window
= maybe_transient
; window
;
83 window
= ::wm::GetTransientParent(window
)) {
84 if (window
== toplevel
)
90 // Returns the location of |event| in screen coordinates.
91 gfx::Point
GetEventLocationInScreen(const ui::LocatedEvent
& event
) {
92 gfx::Point location_in_screen
= event
.location();
93 aura::Window
* target
= static_cast<aura::Window
*>(event
.target());
94 aura::client::ScreenPositionClient
* screen_position_client
=
95 aura::client::GetScreenPositionClient(target
->GetRootWindow());
96 screen_position_client
->ConvertPointToScreen(target
, &location_in_screen
);
97 return location_in_screen
;
100 // Returns the bounds of the display nearest to |window| in screen coordinates.
101 gfx::Rect
GetDisplayBoundsInScreen(aura::Window
* window
) {
102 return Shell::GetScreen()->GetDisplayNearestWindow(window
).bounds();
107 // The height in pixels of the region below the top edge of the display in which
108 // the mouse can trigger revealing the top-of-window views.
110 // Windows 8 reserves some pixels at the top of the screen for the hand icon
111 // that allows you to drag a metro app off the screen, so a few additional
112 // pixels of space must be reserved for the mouse reveal.
113 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight
= 9;
115 // The height must be greater than 1px because the top pixel is used to trigger
116 // moving the cursor between displays if the user has a vertical display layout
117 // (primary display above/below secondary display).
118 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight
= 3;
121 ////////////////////////////////////////////////////////////////////////////////
123 // Class which keeps the top-of-window views revealed as long as one of the
124 // bubbles it is observing is visible. The logic to keep the top-of-window
125 // views revealed based on the visibility of bubbles anchored to
126 // children of |ImmersiveFullscreenController::top_container_| is separate from
127 // the logic related to |ImmersiveFullscreenController::focus_revealed_lock_|
128 // so that bubbles which are not activatable and bubbles which do not close
129 // upon deactivation also keep the top-of-window views revealed for the
130 // duration of their visibility.
131 class ImmersiveFullscreenController::BubbleManager
132 : public aura::WindowObserver
{
134 explicit BubbleManager(ImmersiveFullscreenController
* controller
);
135 ~BubbleManager() override
;
137 // Start / stop observing changes to |bubble|'s visibility.
138 void StartObserving(aura::Window
* bubble
);
139 void StopObserving(aura::Window
* bubble
);
142 // Updates |revealed_lock_| based on whether any of |bubbles_| is visible.
143 void UpdateRevealedLock();
145 // aura::WindowObserver overrides:
146 void OnWindowVisibilityChanged(aura::Window
* window
, bool visible
) override
;
147 void OnWindowDestroying(aura::Window
* window
) override
;
149 ImmersiveFullscreenController
* controller_
;
151 std::set
<aura::Window
*> bubbles_
;
153 // Lock which keeps the top-of-window views revealed based on whether any of
154 // |bubbles_| is visible.
155 scoped_ptr
<ImmersiveRevealedLock
> revealed_lock_
;
157 DISALLOW_COPY_AND_ASSIGN(BubbleManager
);
160 ImmersiveFullscreenController::BubbleManager::BubbleManager(
161 ImmersiveFullscreenController
* controller
)
162 : controller_(controller
) {
165 ImmersiveFullscreenController::BubbleManager::~BubbleManager() {
166 for (std::set
<aura::Window
*>::const_iterator it
= bubbles_
.begin();
167 it
!= bubbles_
.end(); ++it
) {
168 (*it
)->RemoveObserver(this);
172 void ImmersiveFullscreenController::BubbleManager::StartObserving(
173 aura::Window
* bubble
) {
174 if (bubbles_
.insert(bubble
).second
) {
175 bubble
->AddObserver(this);
176 UpdateRevealedLock();
180 void ImmersiveFullscreenController::BubbleManager::StopObserving(
181 aura::Window
* bubble
) {
182 if (bubbles_
.erase(bubble
)) {
183 bubble
->RemoveObserver(this);
184 UpdateRevealedLock();
188 void ImmersiveFullscreenController::BubbleManager::UpdateRevealedLock() {
189 bool has_visible_bubble
= false;
190 for (std::set
<aura::Window
*>::const_iterator it
= bubbles_
.begin();
191 it
!= bubbles_
.end(); ++it
) {
192 if ((*it
)->IsVisible()) {
193 has_visible_bubble
= true;
198 bool was_revealed
= controller_
->IsRevealed();
199 if (has_visible_bubble
) {
200 if (!revealed_lock_
.get()) {
201 // Reveal the top-of-window views without animating because it looks
202 // weird for the top-of-window views to animate and the bubble not to
203 // animate along with the top-of-window views.
204 revealed_lock_
.reset(controller_
->GetRevealedLock(
205 ImmersiveFullscreenController::ANIMATE_REVEAL_NO
));
208 revealed_lock_
.reset();
211 if (!was_revealed
&& revealed_lock_
.get()) {
212 // Currently, there is no nice way for bubbles to reposition themselves
213 // whenever the anchor view moves. Tell the bubbles to reposition themselves
214 // explicitly instead. The hidden bubbles are also repositioned because
215 // BubbleDelegateView does not reposition its widget as a result of a
216 // visibility change.
217 for (std::set
<aura::Window
*>::const_iterator it
= bubbles_
.begin();
218 it
!= bubbles_
.end(); ++it
) {
219 AsBubbleDelegate(*it
)->OnAnchorBoundsChanged();
224 void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged(
227 UpdateRevealedLock();
230 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
231 aura::Window
* window
) {
232 StopObserving(window
);
235 ////////////////////////////////////////////////////////////////////////////////
237 ImmersiveFullscreenController::ImmersiveFullscreenController()
239 top_container_(NULL
),
241 native_window_(NULL
),
242 observers_enabled_(false),
244 reveal_state_(CLOSED
),
245 revealed_lock_count_(0),
246 mouse_x_when_hit_top_in_screen_(-1),
247 gesture_begun_(false),
248 animation_(new gfx::SlideAnimation(this)),
249 animations_disabled_for_test_(false),
250 weak_ptr_factory_(this) {
253 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
254 EnableWindowObservers(false);
257 void ImmersiveFullscreenController::Init(Delegate
* delegate
,
258 views::Widget
* widget
,
259 views::View
* top_container
) {
260 delegate_
= delegate
;
261 top_container_
= top_container
;
263 native_window_
= widget_
->GetNativeWindow();
264 native_window_
->SetEventTargeter(scoped_ptr
<ui::EventTargeter
>(
265 new ResizeHandleWindowTargeter(native_window_
, this)));
268 void ImmersiveFullscreenController::SetEnabled(WindowType window_type
,
270 if (enabled_
== enabled
)
274 EnableWindowObservers(enabled_
);
276 ash::wm::WindowState
* window_state
= wm::GetWindowState(native_window_
);
277 // Auto hide the shelf in immersive fullscreen instead of hiding it.
278 window_state
->set_hide_shelf_when_fullscreen(!enabled
);
280 // Update the window's immersive mode state for the window manager.
281 window_state
->set_in_immersive_fullscreen(enabled
);
283 Shell::GetInstance()->UpdateShelfVisibility();
286 // Animate enabling immersive mode by sliding out the top-of-window views.
287 // No animation occurs if a lock is holding the top-of-window views open.
289 // Do a reveal to set the initial state for the animation. (And any
290 // required state in case the animation cannot run because of a lock holding
291 // the top-of-window views open.)
292 MaybeStartReveal(ANIMATE_NO
);
294 // Reset the located event and the focus revealed locks so that they do not
295 // affect whether the top-of-window views are hidden.
296 located_event_revealed_lock_
.reset();
297 focus_revealed_lock_
.reset();
299 // Try doing the animation.
300 MaybeEndReveal(ANIMATE_SLOW
);
302 if (reveal_state_
== REVEALED
) {
303 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
304 UpdateLocatedEventRevealedLock(NULL
);
305 UpdateFocusRevealedLock();
307 // Clearing focus is important because it closes focus-related popups like
308 // the touch selection handles.
309 widget_
->GetFocusManager()->ClearFocus();
312 // Stop cursor-at-top tracking.
313 top_edge_hover_timer_
.Stop();
314 reveal_state_
= CLOSED
;
316 delegate_
->OnImmersiveFullscreenExited();
320 UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType",
326 bool ImmersiveFullscreenController::IsEnabled() const {
330 bool ImmersiveFullscreenController::IsRevealed() const {
331 return enabled_
&& reveal_state_
!= CLOSED
;
334 ImmersiveRevealedLock
* ImmersiveFullscreenController::GetRevealedLock(
335 AnimateReveal animate_reveal
) {
336 return new ImmersiveRevealedLock(weak_ptr_factory_
.GetWeakPtr(),
340 ////////////////////////////////////////////////////////////////////////////////
341 // Testing interface:
343 void ImmersiveFullscreenController::SetupForTest() {
345 animations_disabled_for_test_
= true;
347 // Move the mouse off of the top-of-window views so that it does not keep the
348 // top-of-window views revealed.
349 std::vector
<gfx::Rect
> bounds_in_screen(
350 delegate_
->GetVisibleBoundsInScreen());
351 DCHECK(!bounds_in_screen
.empty());
352 int bottommost_in_screen
= bounds_in_screen
[0].bottom();
353 for (size_t i
= 1; i
< bounds_in_screen
.size(); ++i
) {
354 if (bounds_in_screen
[i
].bottom() > bottommost_in_screen
)
355 bottommost_in_screen
= bounds_in_screen
[i
].bottom();
357 gfx::Point
cursor_pos(0, bottommost_in_screen
+ 100);
358 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos
);
359 UpdateLocatedEventRevealedLock(NULL
);
362 ////////////////////////////////////////////////////////////////////////////////
363 // ui::EventHandler overrides:
365 void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent
* event
) {
369 if (event
->type() != ui::ET_MOUSE_MOVED
&&
370 event
->type() != ui::ET_MOUSE_PRESSED
&&
371 event
->type() != ui::ET_MOUSE_RELEASED
&&
372 event
->type() != ui::ET_MOUSE_CAPTURE_CHANGED
) {
376 // Mouse hover can initiate revealing the top-of-window views while |widget_|
379 if (reveal_state_
== SLIDING_OPEN
|| reveal_state_
== REVEALED
) {
380 top_edge_hover_timer_
.Stop();
381 UpdateLocatedEventRevealedLock(event
);
382 } else if (event
->type() != ui::ET_MOUSE_CAPTURE_CHANGED
) {
383 // Trigger a reveal if the cursor pauses at the top of the screen for a
385 UpdateTopEdgeHoverTimer(event
);
389 void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent
* event
) {
390 if (!enabled_
|| event
->type() != ui::ET_TOUCH_PRESSED
)
393 // Touch should not initiate revealing the top-of-window views while |widget_|
395 if (!widget_
->IsActive())
398 UpdateLocatedEventRevealedLock(event
);
401 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent
* event
) {
405 // Touch gestures should not initiate revealing the top-of-window views while
406 // |widget_| is inactive.
407 if (!widget_
->IsActive())
410 switch (event
->type()) {
412 case ui::ET_GESTURE_WIN8_EDGE_SWIPE
:
413 UpdateRevealedLocksForSwipe(GetSwipeType(event
));
417 case ui::ET_GESTURE_SCROLL_BEGIN
:
418 if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event
))) {
419 gesture_begun_
= true;
420 // Do not consume the event. Otherwise, we end up consuming all
421 // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
422 // when the top-of-window views are revealed.
425 case ui::ET_GESTURE_SCROLL_UPDATE
:
426 if (gesture_begun_
) {
427 if (UpdateRevealedLocksForSwipe(GetSwipeType(event
)))
429 gesture_begun_
= false;
432 case ui::ET_GESTURE_SCROLL_END
:
433 case ui::ET_SCROLL_FLING_START
:
434 gesture_begun_
= false;
441 ////////////////////////////////////////////////////////////////////////////////
442 // views::FocusChangeListener overrides:
444 void ImmersiveFullscreenController::OnWillChangeFocus(
445 views::View
* focused_before
,
446 views::View
* focused_now
) {
449 void ImmersiveFullscreenController::OnDidChangeFocus(
450 views::View
* focused_before
,
451 views::View
* focused_now
) {
452 UpdateFocusRevealedLock();
455 ////////////////////////////////////////////////////////////////////////////////
456 // views::WidgetObserver overrides:
458 void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget
* widget
) {
459 EnableWindowObservers(false);
460 native_window_
= NULL
;
462 // Set |enabled_| to false such that any calls to MaybeStartReveal() and
463 // MaybeEndReveal() have no effect.
467 void ImmersiveFullscreenController::OnWidgetActivationChanged(
468 views::Widget
* widget
,
470 UpdateFocusRevealedLock();
473 ////////////////////////////////////////////////////////////////////////////////
474 // gfx::AnimationDelegate overrides:
476 void ImmersiveFullscreenController::AnimationEnded(
477 const gfx::Animation
* animation
) {
478 if (reveal_state_
== SLIDING_OPEN
) {
479 OnSlideOpenAnimationCompleted();
480 } else if (reveal_state_
== SLIDING_CLOSED
) {
481 OnSlideClosedAnimationCompleted();
485 void ImmersiveFullscreenController::AnimationProgressed(
486 const gfx::Animation
* animation
) {
487 delegate_
->SetVisibleFraction(animation
->GetCurrentValue());
490 ////////////////////////////////////////////////////////////////////////////////
491 // aura::WindowObserver overrides:
493 void ImmersiveFullscreenController::OnTransientChildAdded(
494 aura::Window
* window
,
495 aura::Window
* transient
) {
496 views::BubbleDelegateView
* bubble_delegate
= AsBubbleDelegate(transient
);
497 if (bubble_delegate
&&
498 bubble_delegate
->GetAnchorView() &&
499 top_container_
->Contains(bubble_delegate
->GetAnchorView())) {
500 // Observe the aura::Window because the BubbleDelegateView may not be
501 // parented to the widget's root view yet so |bubble_delegate->GetWidget()|
502 // may still return NULL.
503 bubble_manager_
->StartObserving(transient
);
507 void ImmersiveFullscreenController::OnTransientChildRemoved(
508 aura::Window
* window
,
509 aura::Window
* transient
) {
510 bubble_manager_
->StopObserving(transient
);
513 ////////////////////////////////////////////////////////////////////////////////
514 // ash::ImmersiveRevealedLock::Delegate overrides:
516 void ImmersiveFullscreenController::LockRevealedState(
517 AnimateReveal animate_reveal
) {
518 ++revealed_lock_count_
;
519 Animate animate
= (animate_reveal
== ANIMATE_REVEAL_YES
) ?
520 ANIMATE_FAST
: ANIMATE_NO
;
521 MaybeStartReveal(animate
);
524 void ImmersiveFullscreenController::UnlockRevealedState() {
525 --revealed_lock_count_
;
526 DCHECK_GE(revealed_lock_count_
, 0);
527 if (revealed_lock_count_
== 0) {
528 // Always animate ending the reveal fast.
529 MaybeEndReveal(ANIMATE_FAST
);
533 ////////////////////////////////////////////////////////////////////////////////
536 void ImmersiveFullscreenController::EnableWindowObservers(bool enable
) {
537 if (observers_enabled_
== enable
)
539 observers_enabled_
= enable
;
541 views::FocusManager
* focus_manager
= widget_
->GetFocusManager();
544 widget_
->AddObserver(this);
545 focus_manager
->AddFocusChangeListener(this);
546 Shell::GetInstance()->AddPreTargetHandler(this);
547 ::wm::TransientWindowManager::Get(native_window_
)->
550 RecreateBubbleManager();
552 widget_
->RemoveObserver(this);
553 focus_manager
->RemoveFocusChangeListener(this);
554 Shell::GetInstance()->RemovePreTargetHandler(this);
555 ::wm::TransientWindowManager::Get(native_window_
)->
556 RemoveObserver(this);
558 // We have stopped observing whether transient children are added or removed
559 // to |native_window_|. The set of bubbles that BubbleManager is observing
560 // will become stale really quickly. Destroy BubbleManager and recreate it
561 // when we start observing |native_window_| again.
562 bubble_manager_
.reset();
568 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
569 ui::MouseEvent
* event
) {
571 DCHECK(reveal_state_
== SLIDING_CLOSED
|| reveal_state_
== CLOSED
);
573 // Check whether |native_window_| is the event target's parent window instead
574 // of checking for activation. This allows the timer to be started when
575 // |widget_| is inactive but prevents starting the timer if the mouse is over
576 // a portion of the top edge obscured by an unrelated widget.
577 if (!top_edge_hover_timer_
.IsRunning() &&
578 !native_window_
->Contains(static_cast<aura::Window
*>(event
->target()))) {
582 // Mouse hover should not initiate revealing the top-of-window views while a
583 // window has mouse capture.
584 if (aura::client::GetCaptureWindow(native_window_
))
587 gfx::Point location_in_screen
= GetEventLocationInScreen(*event
);
588 if (ShouldIgnoreMouseEventAtLocation(location_in_screen
))
591 // Stop the timer if the cursor left the top edge or is on a different
593 gfx::Rect hit_bounds_in_screen
= GetDisplayBoundsInScreen(native_window_
);
594 hit_bounds_in_screen
.set_height(kMouseRevealBoundsHeight
);
595 if (!hit_bounds_in_screen
.Contains(location_in_screen
)) {
596 top_edge_hover_timer_
.Stop();
600 // The cursor is now at the top of the screen. Consider the cursor "not
601 // moving" even if it moves a little bit because users don't have perfect
602 // pointing precision. (The y position is not tested because
603 // |hit_bounds_in_screen| is short.)
604 if (top_edge_hover_timer_
.IsRunning() &&
605 abs(location_in_screen
.x() - mouse_x_when_hit_top_in_screen_
) <=
606 kMouseRevealXThresholdPixels
)
609 // Start the reveal if the cursor doesn't move for some amount of time.
610 mouse_x_when_hit_top_in_screen_
= location_in_screen
.x();
611 top_edge_hover_timer_
.Stop();
612 // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
613 top_edge_hover_timer_
.Start(
615 base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs
),
617 &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock
,
618 base::Unretained(this)));
621 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
622 ui::LocatedEvent
* event
) {
625 DCHECK(!event
|| event
->IsMouseEvent() || event
->IsTouchEvent());
627 // Neither the mouse nor touch can initiate a reveal when the top-of-window
628 // views are sliding closed or are closed with the following exceptions:
629 // - Hovering at y = 0 which is handled in OnMouseEvent().
630 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
631 if (reveal_state_
== CLOSED
|| reveal_state_
== SLIDING_CLOSED
)
634 // For the sake of simplicity, ignore |widget_|'s activation in computing
635 // whether the top-of-window views should stay revealed. Ideally, the
636 // top-of-window views would stay revealed only when the mouse cursor is
637 // hovered above a non-obscured portion of the top-of-window views. The
638 // top-of-window views may be partially obscured when |widget_| is inactive.
640 // Ignore all events while a window has capture. This keeps the top-of-window
641 // views revealed during a drag.
642 if (aura::client::GetCaptureWindow(native_window_
))
645 gfx::Point location_in_screen
;
646 if (event
&& event
->type() != ui::ET_MOUSE_CAPTURE_CHANGED
) {
647 location_in_screen
= GetEventLocationInScreen(*event
);
649 aura::client::CursorClient
* cursor_client
= aura::client::GetCursorClient(
650 native_window_
->GetRootWindow());
651 if (!cursor_client
->IsMouseEventsEnabled()) {
652 // If mouse events are disabled, the user's last interaction was probably
653 // via touch. Do no do further processing in this case as there is no easy
654 // way of retrieving the position of the user's last touch.
657 location_in_screen
= aura::Env::GetInstance()->last_mouse_location();
660 if ((!event
|| event
->IsMouseEvent()) &&
661 ShouldIgnoreMouseEventAtLocation(location_in_screen
)) {
665 // The visible bounds of |top_container_| should be contained in
666 // |hit_bounds_in_screen|.
667 std::vector
<gfx::Rect
> hit_bounds_in_screen
=
668 delegate_
->GetVisibleBoundsInScreen();
669 bool keep_revealed
= false;
670 for (size_t i
= 0; i
< hit_bounds_in_screen
.size(); ++i
) {
671 // Allow the cursor to move slightly off the top-of-window views before
672 // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
673 // when the user is attempting to click on the bookmark bar and overshoots
675 if (event
&& event
->type() == ui::ET_MOUSE_MOVED
) {
676 const int kBoundsOffsetY
= 8;
677 hit_bounds_in_screen
[i
].Inset(0, 0, 0, -kBoundsOffsetY
);
680 if (hit_bounds_in_screen
[i
].Contains(location_in_screen
)) {
681 keep_revealed
= true;
687 AcquireLocatedEventRevealedLock();
689 located_event_revealed_lock_
.reset();
692 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
693 // CAUTION: Acquiring the lock results in a reentrant call to
694 // AcquireLocatedEventRevealedLock() when
695 // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
696 if (!located_event_revealed_lock_
.get())
697 located_event_revealed_lock_
.reset(GetRevealedLock(ANIMATE_REVEAL_YES
));
700 void ImmersiveFullscreenController::UpdateFocusRevealedLock() {
704 bool hold_lock
= false;
705 if (widget_
->IsActive()) {
706 views::View
* focused_view
= widget_
->GetFocusManager()->GetFocusedView();
707 if (top_container_
->Contains(focused_view
))
710 aura::Window
* active_window
= aura::client::GetActivationClient(
711 native_window_
->GetRootWindow())->GetActiveWindow();
712 views::BubbleDelegateView
* bubble_delegate
=
713 AsBubbleDelegate(active_window
);
714 if (bubble_delegate
&& bubble_delegate
->anchor_widget()) {
715 // BubbleManager will already have locked the top-of-window views if the
716 // bubble is anchored to a child of |top_container_|. Don't acquire
717 // |focus_revealed_lock_| here for the sake of simplicity.
718 // Note: Instead of checking for the existence of the |anchor_view|,
719 // the existence of the |anchor_widget| is performed to avoid the case
720 // where the view is already gone (and the widget is still running).
722 // The currently active window is not |native_window_| and it is not a
723 // bubble with an anchor view. The top-of-window views should be revealed
725 // 1) The active window is a transient child of |native_window_|.
726 // 2) The top-of-window views are already revealed. This restriction
727 // prevents a transient window opened by the web contents while the
728 // top-of-window views are hidden from from initiating a reveal.
729 // The top-of-window views will stay revealed till |native_window_| is
732 IsWindowTransientChildOf(active_window
, native_window_
)) {
739 if (!focus_revealed_lock_
.get())
740 focus_revealed_lock_
.reset(GetRevealedLock(ANIMATE_REVEAL_YES
));
742 focus_revealed_lock_
.reset();
746 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
747 SwipeType swipe_type
) {
748 if (!enabled_
|| swipe_type
== SWIPE_NONE
)
751 // Swipes while |native_window_| is inactive should have been filtered out in
753 DCHECK(widget_
->IsActive());
755 if (reveal_state_
== SLIDING_CLOSED
|| reveal_state_
== CLOSED
) {
756 if (swipe_type
== SWIPE_OPEN
&& !located_event_revealed_lock_
.get()) {
757 located_event_revealed_lock_
.reset(GetRevealedLock(ANIMATE_REVEAL_YES
));
761 if (swipe_type
== SWIPE_CLOSE
) {
762 // Attempt to end the reveal. If other code is holding onto a lock, the
763 // attempt will be unsuccessful.
764 located_event_revealed_lock_
.reset();
765 focus_revealed_lock_
.reset();
767 if (reveal_state_
== SLIDING_CLOSED
|| reveal_state_
== CLOSED
) {
768 widget_
->GetFocusManager()->ClearFocus();
772 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
773 UpdateLocatedEventRevealedLock(NULL
);
774 UpdateFocusRevealedLock();
780 int ImmersiveFullscreenController::GetAnimationDuration(Animate animate
) const {
785 return kRevealSlowAnimationDurationMs
;
787 return kRevealFastAnimationDurationMs
;
793 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate
) {
797 if (animations_disabled_for_test_
)
798 animate
= ANIMATE_NO
;
800 // Callers with ANIMATE_NO expect this function to synchronously reveal the
801 // top-of-window views.
802 if (reveal_state_
== REVEALED
||
803 (reveal_state_
== SLIDING_OPEN
&& animate
!= ANIMATE_NO
)) {
807 RevealState previous_reveal_state
= reveal_state_
;
808 reveal_state_
= SLIDING_OPEN
;
809 if (previous_reveal_state
== CLOSED
) {
810 delegate_
->OnImmersiveRevealStarted();
812 // Do not do any more processing if OnImmersiveRevealStarted() changed
814 if (reveal_state_
!= SLIDING_OPEN
)
817 // Slide in the reveal view.
818 if (animate
== ANIMATE_NO
) {
819 animation_
->Reset(1);
820 OnSlideOpenAnimationCompleted();
822 animation_
->SetSlideDuration(GetAnimationDuration(animate
));
827 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
828 DCHECK_EQ(SLIDING_OPEN
, reveal_state_
);
829 reveal_state_
= REVEALED
;
830 delegate_
->SetVisibleFraction(1);
832 // The user may not have moved the mouse since the reveal was initiated.
833 // Update the revealed lock to reflect the mouse's current state.
834 UpdateLocatedEventRevealedLock(NULL
);
837 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate
) {
838 if (!enabled_
|| revealed_lock_count_
!= 0)
841 if (animations_disabled_for_test_
)
842 animate
= ANIMATE_NO
;
844 // Callers with ANIMATE_NO expect this function to synchronously close the
845 // top-of-window views.
846 if (reveal_state_
== CLOSED
||
847 (reveal_state_
== SLIDING_CLOSED
&& animate
!= ANIMATE_NO
)) {
851 reveal_state_
= SLIDING_CLOSED
;
852 int duration_ms
= GetAnimationDuration(animate
);
853 if (duration_ms
> 0) {
854 animation_
->SetSlideDuration(duration_ms
);
857 animation_
->Reset(0);
858 OnSlideClosedAnimationCompleted();
862 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
863 DCHECK_EQ(SLIDING_CLOSED
, reveal_state_
);
864 reveal_state_
= CLOSED
;
865 delegate_
->OnImmersiveRevealEnded();
868 ImmersiveFullscreenController::SwipeType
869 ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent
* event
) const {
871 if (event
->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE
)
874 if (event
->type() != ui::ET_GESTURE_SCROLL_UPDATE
)
876 // Make sure that it is a clear vertical gesture.
877 if (std::abs(event
->details().scroll_y()) <=
878 kSwipeVerticalThresholdMultiplier
* std::abs(event
->details().scroll_x()))
880 if (event
->details().scroll_y() < 0)
882 else if (event
->details().scroll_y() > 0)
887 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
888 const gfx::Point
& location
) const {
889 // Ignore mouse events in the region immediately above the top edge of the
890 // display. This is to handle the case of a user with a vertical display
891 // layout (primary display above/below secondary display) and the immersive
892 // fullscreen window on the bottom display. It is really hard to trigger a
893 // reveal in this case because:
894 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
895 // pixels of the bottom display.
896 // - The cursor is warped to the top display if the cursor gets to the top
897 // edge of the bottom display.
898 // Mouse events are ignored in the bottom few pixels of the top display
899 // (Mouse events in this region cannot start or end a reveal). This allows a
900 // user to overshoot the top of the bottom display and still reveal the
901 // top-of-window views.
902 gfx::Rect dead_region
= GetDisplayBoundsInScreen(native_window_
);
903 dead_region
.set_y(dead_region
.y() - kHeightOfDeadRegionAboveTopContainer
);
904 dead_region
.set_height(kHeightOfDeadRegionAboveTopContainer
);
905 return dead_region
.Contains(location
);
908 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
909 const gfx::Point
& location
) const {
910 DCHECK(widget_
->IsActive());
911 if (reveal_state_
== REVEALED
) {
912 std::vector
<gfx::Rect
> hit_bounds_in_screen(
913 delegate_
->GetVisibleBoundsInScreen());
914 for (size_t i
= 0; i
< hit_bounds_in_screen
.size(); ++i
) {
915 if (hit_bounds_in_screen
[i
].Contains(location
))
921 // When the top-of-window views are not fully revealed, handle gestures which
922 // start in the top few pixels of the screen.
923 gfx::Rect
hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_
));
924 hit_bounds_in_screen
.set_height(kImmersiveFullscreenTopEdgeInset
);
925 if (hit_bounds_in_screen
.Contains(location
))
928 // There may be a bezel sensor off screen logically above
929 // |hit_bounds_in_screen|. The check for the event not contained by the
930 // closest screen ensures that the event is from a valid bezel (as opposed to
931 // another screen in an extended desktop).
932 gfx::Rect screen_bounds
=
933 Shell::GetScreen()->GetDisplayNearestPoint(location
).bounds();
934 return (!screen_bounds
.Contains(location
) &&
935 location
.y() < hit_bounds_in_screen
.y() &&
936 location
.x() >= hit_bounds_in_screen
.x() &&
937 location
.x() < hit_bounds_in_screen
.right());
940 void ImmersiveFullscreenController::RecreateBubbleManager() {
941 bubble_manager_
.reset(new BubbleManager(this));
942 const std::vector
<aura::Window
*> transient_children
=
943 ::wm::GetTransientChildren(native_window_
);
944 for (size_t i
= 0; i
< transient_children
.size(); ++i
) {
945 aura::Window
* transient_child
= transient_children
[i
];
946 views::BubbleDelegateView
* bubble_delegate
=
947 AsBubbleDelegate(transient_child
);
948 if (bubble_delegate
&&
949 bubble_delegate
->GetAnchorView() &&
950 top_container_
->Contains(bubble_delegate
->GetAnchorView())) {
951 bubble_manager_
->StartObserving(transient_child
);