Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / ash / wm / immersive_fullscreen_controller.cc
blob538323687091efaca6f609dd17e7526dd97290da
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"
7 #include <set>
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"
33 using views::View;
35 namespace ash {
37 namespace {
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
41 // happened.
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) {
68 if (!maybe_bubble)
69 return NULL;
70 views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble);
71 if (!widget)
72 return NULL;
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)
80 return false;
82 for (aura::Window* window = maybe_transient; window;
83 window = ::wm::GetTransientParent(window)) {
84 if (window == toplevel)
85 return true;
87 return false;
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();
105 } // namespace
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.
109 #if defined(OS_WIN)
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;
114 #else
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;
119 #endif
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 {
133 public:
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);
141 private:
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;
194 break;
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));
207 } else {
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(
225 aura::Window*,
226 bool visible) {
227 UpdateRevealedLock();
230 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
231 aura::Window* window) {
232 StopObserving(window);
235 ////////////////////////////////////////////////////////////////////////////////
237 ImmersiveFullscreenController::ImmersiveFullscreenController()
238 : delegate_(NULL),
239 top_container_(NULL),
240 widget_(NULL),
241 native_window_(NULL),
242 observers_enabled_(false),
243 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;
262 widget_ = widget;
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,
269 bool enabled) {
270 if (enabled_ == enabled)
271 return;
272 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();
285 if (enabled_) {
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 } else {
308 // Stop cursor-at-top tracking.
309 top_edge_hover_timer_.Stop();
310 reveal_state_ = CLOSED;
312 delegate_->OnImmersiveFullscreenExited();
315 if (enabled_) {
316 UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType",
317 window_type,
318 WINDOW_TYPE_COUNT);
322 bool ImmersiveFullscreenController::IsEnabled() const {
323 return enabled_;
326 bool ImmersiveFullscreenController::IsRevealed() const {
327 return enabled_ && reveal_state_ != CLOSED;
330 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
331 AnimateReveal animate_reveal) {
332 return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
333 animate_reveal);
336 ////////////////////////////////////////////////////////////////////////////////
337 // Testing interface:
339 void ImmersiveFullscreenController::SetupForTest() {
340 DCHECK(!enabled_);
341 animations_disabled_for_test_ = true;
343 // Move the mouse off of the top-of-window views so that it does not keep the
344 // top-of-window views revealed.
345 std::vector<gfx::Rect> bounds_in_screen(
346 delegate_->GetVisibleBoundsInScreen());
347 DCHECK(!bounds_in_screen.empty());
348 int bottommost_in_screen = bounds_in_screen[0].bottom();
349 for (size_t i = 1; i < bounds_in_screen.size(); ++i) {
350 if (bounds_in_screen[i].bottom() > bottommost_in_screen)
351 bottommost_in_screen = bounds_in_screen[i].bottom();
353 gfx::Point cursor_pos(0, bottommost_in_screen + 100);
354 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos);
355 UpdateLocatedEventRevealedLock(NULL);
358 ////////////////////////////////////////////////////////////////////////////////
359 // ui::EventHandler overrides:
361 void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent* event) {
362 if (!enabled_)
363 return;
365 if (event->type() != ui::ET_MOUSE_MOVED &&
366 event->type() != ui::ET_MOUSE_PRESSED &&
367 event->type() != ui::ET_MOUSE_RELEASED &&
368 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
369 return;
372 // Mouse hover can initiate revealing the top-of-window views while |widget_|
373 // is inactive.
375 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
376 top_edge_hover_timer_.Stop();
377 UpdateLocatedEventRevealedLock(event);
378 } else if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
379 // Trigger a reveal if the cursor pauses at the top of the screen for a
380 // while.
381 UpdateTopEdgeHoverTimer(event);
385 void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent* event) {
386 if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED)
387 return;
389 // Touch should not initiate revealing the top-of-window views while |widget_|
390 // is inactive.
391 if (!widget_->IsActive())
392 return;
394 UpdateLocatedEventRevealedLock(event);
397 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) {
398 if (!enabled_)
399 return;
401 // Touch gestures should not initiate revealing the top-of-window views while
402 // |widget_| is inactive.
403 if (!widget_->IsActive())
404 return;
406 switch (event->type()) {
407 #if defined(OS_WIN)
408 case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
409 UpdateRevealedLocksForSwipe(GetSwipeType(event));
410 event->SetHandled();
411 break;
412 #endif
413 case ui::ET_GESTURE_SCROLL_BEGIN:
414 if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) {
415 gesture_begun_ = true;
416 // Do not consume the event. Otherwise, we end up consuming all
417 // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
418 // when the top-of-window views are revealed.
420 break;
421 case ui::ET_GESTURE_SCROLL_UPDATE:
422 if (gesture_begun_) {
423 if (UpdateRevealedLocksForSwipe(GetSwipeType(event)))
424 event->SetHandled();
425 gesture_begun_ = false;
427 break;
428 case ui::ET_GESTURE_SCROLL_END:
429 case ui::ET_SCROLL_FLING_START:
430 gesture_begun_ = false;
431 break;
432 default:
433 break;
437 ////////////////////////////////////////////////////////////////////////////////
438 // views::FocusChangeListener overrides:
440 void ImmersiveFullscreenController::OnWillChangeFocus(
441 views::View* focused_before,
442 views::View* focused_now) {
445 void ImmersiveFullscreenController::OnDidChangeFocus(
446 views::View* focused_before,
447 views::View* focused_now) {
448 UpdateFocusRevealedLock();
451 ////////////////////////////////////////////////////////////////////////////////
452 // views::WidgetObserver overrides:
454 void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget* widget) {
455 EnableWindowObservers(false);
456 native_window_ = NULL;
458 // Set |enabled_| to false such that any calls to MaybeStartReveal() and
459 // MaybeEndReveal() have no effect.
460 enabled_ = false;
463 void ImmersiveFullscreenController::OnWidgetActivationChanged(
464 views::Widget* widget,
465 bool active) {
466 UpdateFocusRevealedLock();
469 ////////////////////////////////////////////////////////////////////////////////
470 // gfx::AnimationDelegate overrides:
472 void ImmersiveFullscreenController::AnimationEnded(
473 const gfx::Animation* animation) {
474 if (reveal_state_ == SLIDING_OPEN) {
475 OnSlideOpenAnimationCompleted();
476 } else if (reveal_state_ == SLIDING_CLOSED) {
477 OnSlideClosedAnimationCompleted();
481 void ImmersiveFullscreenController::AnimationProgressed(
482 const gfx::Animation* animation) {
483 delegate_->SetVisibleFraction(animation->GetCurrentValue());
486 ////////////////////////////////////////////////////////////////////////////////
487 // aura::WindowObserver overrides:
489 void ImmersiveFullscreenController::OnTransientChildAdded(
490 aura::Window* window,
491 aura::Window* transient) {
492 views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient);
493 if (bubble_delegate &&
494 bubble_delegate->GetAnchorView() &&
495 top_container_->Contains(bubble_delegate->GetAnchorView())) {
496 // Observe the aura::Window because the BubbleDelegateView may not be
497 // parented to the widget's root view yet so |bubble_delegate->GetWidget()|
498 // may still return NULL.
499 bubble_manager_->StartObserving(transient);
503 void ImmersiveFullscreenController::OnTransientChildRemoved(
504 aura::Window* window,
505 aura::Window* transient) {
506 bubble_manager_->StopObserving(transient);
509 ////////////////////////////////////////////////////////////////////////////////
510 // ash::ImmersiveRevealedLock::Delegate overrides:
512 void ImmersiveFullscreenController::LockRevealedState(
513 AnimateReveal animate_reveal) {
514 ++revealed_lock_count_;
515 Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ?
516 ANIMATE_FAST : ANIMATE_NO;
517 MaybeStartReveal(animate);
520 void ImmersiveFullscreenController::UnlockRevealedState() {
521 --revealed_lock_count_;
522 DCHECK_GE(revealed_lock_count_, 0);
523 if (revealed_lock_count_ == 0) {
524 // Always animate ending the reveal fast.
525 MaybeEndReveal(ANIMATE_FAST);
529 ////////////////////////////////////////////////////////////////////////////////
530 // private:
532 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) {
533 if (observers_enabled_ == enable)
534 return;
535 observers_enabled_ = enable;
537 views::FocusManager* focus_manager = widget_->GetFocusManager();
539 if (enable) {
540 widget_->AddObserver(this);
541 focus_manager->AddFocusChangeListener(this);
542 Shell::GetInstance()->AddPreTargetHandler(this);
543 ::wm::TransientWindowManager::Get(native_window_)->
544 AddObserver(this);
546 RecreateBubbleManager();
547 } else {
548 widget_->RemoveObserver(this);
549 focus_manager->RemoveFocusChangeListener(this);
550 Shell::GetInstance()->RemovePreTargetHandler(this);
551 ::wm::TransientWindowManager::Get(native_window_)->
552 RemoveObserver(this);
554 // We have stopped observing whether transient children are added or removed
555 // to |native_window_|. The set of bubbles that BubbleManager is observing
556 // will become stale really quickly. Destroy BubbleManager and recreate it
557 // when we start observing |native_window_| again.
558 bubble_manager_.reset();
560 animation_->Stop();
564 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
565 ui::MouseEvent* event) {
566 DCHECK(enabled_);
567 DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED);
569 // Check whether |native_window_| is the event target's parent window instead
570 // of checking for activation. This allows the timer to be started when
571 // |widget_| is inactive but prevents starting the timer if the mouse is over
572 // a portion of the top edge obscured by an unrelated widget.
573 if (!top_edge_hover_timer_.IsRunning() &&
574 !native_window_->Contains(static_cast<aura::Window*>(event->target()))) {
575 return;
578 // Mouse hover should not initiate revealing the top-of-window views while a
579 // window has mouse capture.
580 if (aura::client::GetCaptureWindow(native_window_))
581 return;
583 gfx::Point location_in_screen = GetEventLocationInScreen(*event);
584 if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
585 return;
587 // Stop the timer if the cursor left the top edge or is on a different
588 // display.
589 gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen(native_window_);
590 hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
591 if (!hit_bounds_in_screen.Contains(location_in_screen)) {
592 top_edge_hover_timer_.Stop();
593 return;
596 // The cursor is now at the top of the screen. Consider the cursor "not
597 // moving" even if it moves a little bit because users don't have perfect
598 // pointing precision. (The y position is not tested because
599 // |hit_bounds_in_screen| is short.)
600 if (top_edge_hover_timer_.IsRunning() &&
601 abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
602 kMouseRevealXThresholdPixels)
603 return;
605 // Start the reveal if the cursor doesn't move for some amount of time.
606 mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
607 top_edge_hover_timer_.Stop();
608 // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
609 top_edge_hover_timer_.Start(
610 FROM_HERE,
611 base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs),
612 base::Bind(
613 &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock,
614 base::Unretained(this)));
617 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
618 ui::LocatedEvent* event) {
619 if (!enabled_)
620 return;
621 DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
623 // Neither the mouse nor touch can initiate a reveal when the top-of-window
624 // views are sliding closed or are closed with the following exceptions:
625 // - Hovering at y = 0 which is handled in OnMouseEvent().
626 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
627 if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED)
628 return;
630 // For the sake of simplicity, ignore |widget_|'s activation in computing
631 // whether the top-of-window views should stay revealed. Ideally, the
632 // top-of-window views would stay revealed only when the mouse cursor is
633 // hovered above a non-obscured portion of the top-of-window views. The
634 // top-of-window views may be partially obscured when |widget_| is inactive.
636 // Ignore all events while a window has capture. This keeps the top-of-window
637 // views revealed during a drag.
638 if (aura::client::GetCaptureWindow(native_window_))
639 return;
641 gfx::Point location_in_screen;
642 if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
643 location_in_screen = GetEventLocationInScreen(*event);
644 } else {
645 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
646 native_window_->GetRootWindow());
647 if (!cursor_client->IsMouseEventsEnabled()) {
648 // If mouse events are disabled, the user's last interaction was probably
649 // via touch. Do no do further processing in this case as there is no easy
650 // way of retrieving the position of the user's last touch.
651 return;
653 location_in_screen = aura::Env::GetInstance()->last_mouse_location();
656 if ((!event || event->IsMouseEvent()) &&
657 ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
658 return;
661 // The visible bounds of |top_container_| should be contained in
662 // |hit_bounds_in_screen|.
663 std::vector<gfx::Rect> hit_bounds_in_screen =
664 delegate_->GetVisibleBoundsInScreen();
665 bool keep_revealed = false;
666 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
667 // Allow the cursor to move slightly off the top-of-window views before
668 // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
669 // when the user is attempting to click on the bookmark bar and overshoots
670 // slightly.
671 if (event && event->type() == ui::ET_MOUSE_MOVED) {
672 const int kBoundsOffsetY = 8;
673 hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY);
676 if (hit_bounds_in_screen[i].Contains(location_in_screen)) {
677 keep_revealed = true;
678 break;
682 if (keep_revealed)
683 AcquireLocatedEventRevealedLock();
684 else
685 located_event_revealed_lock_.reset();
688 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
689 // CAUTION: Acquiring the lock results in a reentrant call to
690 // AcquireLocatedEventRevealedLock() when
691 // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
692 if (!located_event_revealed_lock_.get())
693 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
696 void ImmersiveFullscreenController::UpdateFocusRevealedLock() {
697 if (!enabled_)
698 return;
700 bool hold_lock = false;
701 if (widget_->IsActive()) {
702 views::View* focused_view = widget_->GetFocusManager()->GetFocusedView();
703 if (top_container_->Contains(focused_view))
704 hold_lock = true;
705 } else {
706 aura::Window* active_window = aura::client::GetActivationClient(
707 native_window_->GetRootWindow())->GetActiveWindow();
708 views::BubbleDelegateView* bubble_delegate =
709 AsBubbleDelegate(active_window);
710 if (bubble_delegate && bubble_delegate->anchor_widget()) {
711 // BubbleManager will already have locked the top-of-window views if the
712 // bubble is anchored to a child of |top_container_|. Don't acquire
713 // |focus_revealed_lock_| here for the sake of simplicity.
714 // Note: Instead of checking for the existence of the |anchor_view|,
715 // the existence of the |anchor_widget| is performed to avoid the case
716 // where the view is already gone (and the widget is still running).
717 } else {
718 // The currently active window is not |native_window_| and it is not a
719 // bubble with an anchor view. The top-of-window views should be revealed
720 // if:
721 // 1) The active window is a transient child of |native_window_|.
722 // 2) The top-of-window views are already revealed. This restriction
723 // prevents a transient window opened by the web contents while the
724 // top-of-window views are hidden from from initiating a reveal.
725 // The top-of-window views will stay revealed till |native_window_| is
726 // reactivated.
727 if (IsRevealed() &&
728 IsWindowTransientChildOf(active_window, native_window_)) {
729 hold_lock = true;
734 if (hold_lock) {
735 if (!focus_revealed_lock_.get())
736 focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
737 } else {
738 focus_revealed_lock_.reset();
742 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
743 SwipeType swipe_type) {
744 if (!enabled_ || swipe_type == SWIPE_NONE)
745 return false;
747 // Swipes while |native_window_| is inactive should have been filtered out in
748 // OnGestureEvent().
749 DCHECK(widget_->IsActive());
751 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
752 if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
753 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
754 return true;
756 } else {
757 if (swipe_type == SWIPE_CLOSE) {
758 // Attempt to end the reveal. If other code is holding onto a lock, the
759 // attempt will be unsuccessful.
760 located_event_revealed_lock_.reset();
761 focus_revealed_lock_.reset();
763 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
764 widget_->GetFocusManager()->ClearFocus();
765 return true;
768 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
769 UpdateLocatedEventRevealedLock(NULL);
770 UpdateFocusRevealedLock();
773 return false;
776 int ImmersiveFullscreenController::GetAnimationDuration(Animate animate) const {
777 switch (animate) {
778 case ANIMATE_NO:
779 return 0;
780 case ANIMATE_SLOW:
781 return kRevealSlowAnimationDurationMs;
782 case ANIMATE_FAST:
783 return kRevealFastAnimationDurationMs;
785 NOTREACHED();
786 return 0;
789 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) {
790 if (!enabled_)
791 return;
793 if (animations_disabled_for_test_)
794 animate = ANIMATE_NO;
796 // Callers with ANIMATE_NO expect this function to synchronously reveal the
797 // top-of-window views.
798 if (reveal_state_ == REVEALED ||
799 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
800 return;
803 RevealState previous_reveal_state = reveal_state_;
804 reveal_state_ = SLIDING_OPEN;
805 if (previous_reveal_state == CLOSED) {
806 delegate_->OnImmersiveRevealStarted();
808 // Do not do any more processing if OnImmersiveRevealStarted() changed
809 // |reveal_state_|.
810 if (reveal_state_ != SLIDING_OPEN)
811 return;
813 // Slide in the reveal view.
814 if (animate == ANIMATE_NO) {
815 animation_->Reset(1);
816 OnSlideOpenAnimationCompleted();
817 } else {
818 animation_->SetSlideDuration(GetAnimationDuration(animate));
819 animation_->Show();
823 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
824 DCHECK_EQ(SLIDING_OPEN, reveal_state_);
825 reveal_state_ = REVEALED;
826 delegate_->SetVisibleFraction(1);
828 // The user may not have moved the mouse since the reveal was initiated.
829 // Update the revealed lock to reflect the mouse's current state.
830 UpdateLocatedEventRevealedLock(NULL);
833 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) {
834 if (!enabled_ || revealed_lock_count_ != 0)
835 return;
837 if (animations_disabled_for_test_)
838 animate = ANIMATE_NO;
840 // Callers with ANIMATE_NO expect this function to synchronously close the
841 // top-of-window views.
842 if (reveal_state_ == CLOSED ||
843 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
844 return;
847 reveal_state_ = SLIDING_CLOSED;
848 int duration_ms = GetAnimationDuration(animate);
849 if (duration_ms > 0) {
850 animation_->SetSlideDuration(duration_ms);
851 animation_->Hide();
852 } else {
853 animation_->Reset(0);
854 OnSlideClosedAnimationCompleted();
858 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
859 DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
860 reveal_state_ = CLOSED;
861 delegate_->OnImmersiveRevealEnded();
864 ImmersiveFullscreenController::SwipeType
865 ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent* event) const {
866 #if defined(OS_WIN)
867 if (event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE)
868 return SWIPE_OPEN;
869 #endif
870 if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE)
871 return SWIPE_NONE;
872 // Make sure that it is a clear vertical gesture.
873 if (std::abs(event->details().scroll_y()) <=
874 kSwipeVerticalThresholdMultiplier * std::abs(event->details().scroll_x()))
875 return SWIPE_NONE;
876 if (event->details().scroll_y() < 0)
877 return SWIPE_CLOSE;
878 else if (event->details().scroll_y() > 0)
879 return SWIPE_OPEN;
880 return SWIPE_NONE;
883 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
884 const gfx::Point& location) const {
885 // Ignore mouse events in the region immediately above the top edge of the
886 // display. This is to handle the case of a user with a vertical display
887 // layout (primary display above/below secondary display) and the immersive
888 // fullscreen window on the bottom display. It is really hard to trigger a
889 // reveal in this case because:
890 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
891 // pixels of the bottom display.
892 // - The cursor is warped to the top display if the cursor gets to the top
893 // edge of the bottom display.
894 // Mouse events are ignored in the bottom few pixels of the top display
895 // (Mouse events in this region cannot start or end a reveal). This allows a
896 // user to overshoot the top of the bottom display and still reveal the
897 // top-of-window views.
898 gfx::Rect dead_region = GetDisplayBoundsInScreen(native_window_);
899 dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
900 dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
901 return dead_region.Contains(location);
904 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
905 const gfx::Point& location) const {
906 DCHECK(widget_->IsActive());
907 if (reveal_state_ == REVEALED) {
908 std::vector<gfx::Rect> hit_bounds_in_screen(
909 delegate_->GetVisibleBoundsInScreen());
910 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
911 if (hit_bounds_in_screen[i].Contains(location))
912 return true;
914 return false;
917 // When the top-of-window views are not fully revealed, handle gestures which
918 // start in the top few pixels of the screen.
919 gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_));
920 hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset);
921 if (hit_bounds_in_screen.Contains(location))
922 return true;
924 // There may be a bezel sensor off screen logically above
925 // |hit_bounds_in_screen|. The check for the event not contained by the
926 // closest screen ensures that the event is from a valid bezel (as opposed to
927 // another screen in an extended desktop).
928 gfx::Rect screen_bounds =
929 Shell::GetScreen()->GetDisplayNearestPoint(location).bounds();
930 return (!screen_bounds.Contains(location) &&
931 location.y() < hit_bounds_in_screen.y() &&
932 location.x() >= hit_bounds_in_screen.x() &&
933 location.x() < hit_bounds_in_screen.right());
936 void ImmersiveFullscreenController::RecreateBubbleManager() {
937 bubble_manager_.reset(new BubbleManager(this));
938 const std::vector<aura::Window*> transient_children =
939 ::wm::GetTransientChildren(native_window_);
940 for (size_t i = 0; i < transient_children.size(); ++i) {
941 aura::Window* transient_child = transient_children[i];
942 views::BubbleDelegateView* bubble_delegate =
943 AsBubbleDelegate(transient_child);
944 if (bubble_delegate &&
945 bubble_delegate->GetAnchorView() &&
946 top_container_->Contains(bubble_delegate->GetAnchorView())) {
947 bubble_manager_->StartObserving(transient_child);
952 } // namespace ash