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.
4
5 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
6
7 #include <set>
8
9 #include "base/bind.h"
10 #include "chromeos/ui/base/window_properties.h"
11 #include "chromeos/ui/frame/immersive/immersive_context.h"
12 #include "chromeos/ui/frame/immersive/immersive_focus_watcher.h"
13 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_delegate.h"
14 #include "ui/aura/client/aura_constants.h"
15 #include "ui/aura/client/cursor_client.h"
16 #include "ui/aura/env.h"
17 #include "ui/aura/window.h"
18 #include "ui/aura/window_targeter.h"
19 #include "ui/display/display.h"
20 #include "ui/display/screen.h"
21 #include "ui/events/base_event_utils.h"
22 #include "ui/gfx/animation/animation_delegate_notifier.h"
23 #include "ui/gfx/geometry/point.h"
24 #include "ui/gfx/geometry/rect.h"
25 #include "ui/views/animation/animation_delegate_views.h"
26 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29
30 DEFINE_UI_CLASS_PROPERTY_TYPE(chromeos::ImmersiveFullscreenController*)
31
32 namespace chromeos {
33
34 DEFINE_UI_CLASS_PROPERTY_KEY(ImmersiveFullscreenController*,
35 kImmersiveFullscreenControllerKey,
36 nullptr)
37
38 namespace {
39
40 // A window targeter installed on a Widget's window when it's in immersive mode.
41 // This targeter insets the touch area for direct children of the window it's
42 // installed on (see calls to SetInsets) so that gestures at the top of the
43 // screen will be directed to the Widget window for triggering immersive reveal.
44 // The insets are disabled while the top view is revealed.
45 class ImmersiveWindowTargeter : public aura::WindowTargeter {
46 public:
47 ImmersiveWindowTargeter() = default;
48 ~ImmersiveWindowTargeter() override = default;
49
ShouldUseExtendedBounds(const aura::Window * target) const50 bool ShouldUseExtendedBounds(const aura::Window* target) const override {
51 return target->parent() == window();
52 }
53
54 private:
55 DISALLOW_COPY_AND_ASSIGN(ImmersiveWindowTargeter);
56 };
57
58 // The delay in milliseconds between the mouse stopping at the top edge of the
59 // screen and the top-of-window views revealing.
60 const int kMouseRevealDelayMs = 200;
61
62 // The maximum amount of pixels that the cursor can move for the cursor to be
63 // considered "stopped". This allows the user to reveal the top-of-window views
64 // without holding the cursor completely still.
65 const int kMouseRevealXThresholdPixels = 3;
66
67 // Used to multiply x value of an update in check to determine if gesture is
68 // vertical. This is used to make sure that gesture is close to vertical instead
69 // of just more vertical then horizontal.
70 const int kSwipeVerticalThresholdMultiplier = 3;
71
72 // The height in pixels of the region above the top edge of the display which
73 // hosts the immersive fullscreen window in which mouse events are ignored
74 // (cannot reveal or unreveal the top-of-window views).
75 // See ShouldIgnoreMouseEventAtLocation() for more details.
76 const int kHeightOfDeadRegionAboveTopContainer = 10;
77
78 } // namespace
79
80 // static
81 const int ImmersiveFullscreenController::kImmersiveFullscreenTopEdgeInset = 8;
82
83 // static
84 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3;
85
86 // static
87 bool ImmersiveFullscreenController::value_for_animations_disabled_for_test_ =
88 false;
89
90 ////////////////////////////////////////////////////////////////////////////////
91
ImmersiveFullscreenController()92 ImmersiveFullscreenController::ImmersiveFullscreenController()
93 : animations_disabled_for_test_(value_for_animations_disabled_for_test_) {}
94
~ImmersiveFullscreenController()95 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
96 EnableEventObservers(false);
97 EnableWindowObservers(false);
98 }
99
Init(ImmersiveFullscreenControllerDelegate * delegate,views::Widget * widget,views::View * top_container)100 void ImmersiveFullscreenController::Init(
101 ImmersiveFullscreenControllerDelegate* delegate,
102 views::Widget* widget,
103 views::View* top_container) {
104 // This function may be called more than once (e.g. by
105 // ClientControlledShellSurface).
106 EnableWindowObservers(false);
107
108 delegate_ = delegate;
109 top_container_ = top_container;
110 animation_notifier_ = std::make_unique<
111 gfx::AnimationDelegateNotifier<views::AnimationDelegateViews>>(
112 this, top_container);
113 animation_ = std::make_unique<gfx::SlideAnimation>(animation_notifier_.get());
114 widget_ = widget;
115
116 // A widget can have more than one ImmersiveFullscreenController
117 // (WideFrameView does this), so this key only tracks the first
118 // ImmersiveFullscreenController.
119 if (nullptr == widget->GetNativeWindow()->GetProperty(
120 kImmersiveFullscreenControllerKey)) {
121 widget->GetNativeWindow()->SetProperty(kImmersiveFullscreenControllerKey,
122 this);
123 }
124
125 EnableWindowObservers(true);
126 }
127
IsEnabled() const128 bool ImmersiveFullscreenController::IsEnabled() const {
129 return enabled_;
130 }
131
IsRevealed() const132 bool ImmersiveFullscreenController::IsRevealed() const {
133 return enabled_ && reveal_state_ != CLOSED;
134 }
135
GetRevealedLock(AnimateReveal animate_reveal)136 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
137 AnimateReveal animate_reveal) {
138 return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
139 animate_reveal);
140 }
141
142 ///////////////////////////////////////////////////////////////////////////////
143 // ui::EventObserver overrides:
144
OnEvent(const ui::Event & event)145 void ImmersiveFullscreenController::OnEvent(const ui::Event& event) {
146 if (!event.IsLocatedEvent())
147 return;
148
149 const ui::LocatedEvent* located_event = event.AsLocatedEvent();
150 aura::Window* target = static_cast<aura::Window*>(event.target());
151 if (event.IsMouseEvent()) {
152 HandleMouseEvent(*event.AsMouseEvent(), located_event->root_location(),
153 views::Widget::GetTopLevelWidgetForNativeView(target));
154 } else if (event.IsTouchEvent()) {
155 HandleTouchEvent(*event.AsTouchEvent(), located_event->root_location());
156 }
157 }
158
159 ////////////////////////////////////////////////////////////////////////////////
160 // ui::EventHandler overrides:
161
OnEvent(ui::Event * event)162 void ImmersiveFullscreenController::OnEvent(ui::Event* event) {
163 ui::EventHandler::OnEvent(event);
164 }
165
OnGestureEvent(ui::GestureEvent * event)166 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) {
167 if (!enabled_)
168 return;
169
170 // Touch gestures should not initiate revealing the top-of-window views while
171 // |widget_| is inactive.
172 if (!widget_->IsActive())
173 return;
174
175 switch (event->type()) {
176 case ui::ET_GESTURE_SCROLL_BEGIN:
177 if (ShouldHandleGestureEvent(
178 event->target()->GetScreenLocation(*event))) {
179 gesture_begun_ = true;
180 // Do not consume the event. Otherwise, we end up consuming all
181 // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
182 // when the top-of-window views are revealed.
183 }
184 break;
185 case ui::ET_GESTURE_SCROLL_UPDATE:
186 if (gesture_begun_) {
187 if (UpdateRevealedLocksForSwipe(GetSwipeType(*event)))
188 event->SetHandled();
189 gesture_begun_ = false;
190 }
191 break;
192 case ui::ET_GESTURE_SCROLL_END:
193 case ui::ET_SCROLL_FLING_START:
194 gesture_begun_ = false;
195 break;
196 default:
197 break;
198 }
199 }
200
201 ////////////////////////////////////////////////////////////////////////////////
202 // aura::WindowObserver overrides:
203
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)204 void ImmersiveFullscreenController::OnWindowPropertyChanged(
205 aura::Window* window,
206 const void* key,
207 intptr_t old) {
208 if (key == kImmersiveIsActive)
209 UpdateEnabled();
210 }
211
OnWindowDestroying(aura::Window * window)212 void ImmersiveFullscreenController::OnWindowDestroying(aura::Window* window) {
213 EnableEventObservers(false);
214 EnableWindowObservers(false);
215
216 // Set |enabled_| to false such that any calls to MaybeStartReveal() and
217 // MaybeEndReveal() have no effect.
218 enabled_ = false;
219 widget_ = nullptr;
220 }
221
222 ////////////////////////////////////////////////////////////////////////////////
223 // views::Observer overrides:
224
OnViewBoundsChanged(views::View * observed_view)225 void ImmersiveFullscreenController::OnViewBoundsChanged(
226 views::View* observed_view) {
227 DCHECK_EQ(top_container_, observed_view);
228 widget()->GetNativeWindow()->SetProperty(
229 kImmersiveTopContainerBoundsInScreen,
230 new gfx::Rect(top_container_->GetBoundsInScreen()));
231 }
232
OnViewIsDeleting(views::View * observed_view)233 void ImmersiveFullscreenController::OnViewIsDeleting(
234 views::View* observed_view) {
235 DCHECK_EQ(observed_view, top_container_);
236 top_container_ = nullptr;
237 }
238
239 ////////////////////////////////////////////////////////////////////////////////
240 // gfx::AnimationDelegate overrides:
241
AnimationEnded(const gfx::Animation * animation)242 void ImmersiveFullscreenController::AnimationEnded(
243 const gfx::Animation* animation) {
244 if (reveal_state_ == SLIDING_OPEN) {
245 OnSlideOpenAnimationCompleted();
246 } else if (reveal_state_ == SLIDING_CLOSED) {
247 OnSlideClosedAnimationCompleted();
248 }
249 }
250
AnimationProgressed(const gfx::Animation * animation)251 void ImmersiveFullscreenController::AnimationProgressed(
252 const gfx::Animation* animation) {
253 delegate_->SetVisibleFraction(animation->GetCurrentValue());
254 }
255
256 ////////////////////////////////////////////////////////////////////////////////
257 // ImmersiveRevealedLock::Delegate overrides:
258
LockRevealedState(AnimateReveal animate_reveal)259 void ImmersiveFullscreenController::LockRevealedState(
260 AnimateReveal animate_reveal) {
261 ++revealed_lock_count_;
262 Animate animate =
263 (animate_reveal == ANIMATE_REVEAL_YES) ? ANIMATE_FAST : ANIMATE_NO;
264 MaybeStartReveal(animate);
265 }
266
UnlockRevealedState()267 void ImmersiveFullscreenController::UnlockRevealedState() {
268 --revealed_lock_count_;
269 DCHECK_GE(revealed_lock_count_, 0);
270 if (revealed_lock_count_ == 0) {
271 // Always animate ending the reveal fast.
272 MaybeEndReveal(ANIMATE_FAST);
273 }
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////
277 // public:
278
279 // static
EnableForWidget(views::Widget * widget,bool enabled)280 void ImmersiveFullscreenController::EnableForWidget(views::Widget* widget,
281 bool enabled) {
282 widget->GetNativeWindow()->SetProperty(kImmersiveIsActive, enabled);
283 }
284
285 // static
Get(views::Widget * widget)286 ImmersiveFullscreenController* ImmersiveFullscreenController::Get(
287 views::Widget* widget) {
288 return widget->GetNativeWindow()->GetProperty(
289 kImmersiveFullscreenControllerKey);
290 }
291
292 ////////////////////////////////////////////////////////////////////////////////
293 // private:
294
EnableWindowObservers(bool enable)295 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) {
296 if (enable) {
297 top_container_->AddObserver(this);
298 widget_->GetNativeWindow()->AddObserver(this);
299 } else {
300 if (top_container_) {
301 top_container_->RemoveObserver(this);
302 top_container_ = nullptr;
303 }
304 if (widget_) {
305 widget_->GetNativeWindow()->RemoveObserver(this);
306 widget_ = nullptr;
307 }
308
309 animation_.reset();
310 animation_notifier_.reset();
311 }
312 }
313
EnableEventObservers(bool enable)314 void ImmersiveFullscreenController::EnableEventObservers(bool enable) {
315 if (event_observers_enabled_ == enable)
316 return;
317 event_observers_enabled_ = enable;
318
319 aura::Window* window = widget_->GetNativeWindow();
320 aura::Env* env = aura::Env::GetInstance();
321 if (enable) {
322 immersive_focus_watcher_ = std::make_unique<ImmersiveFocusWatcher>(this);
323 std::set<ui::EventType> types = {
324 ui::ET_MOUSE_MOVED, ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_RELEASED,
325 ui::ET_MOUSEWHEEL, ui::ET_MOUSE_CAPTURE_CHANGED, ui::ET_TOUCH_PRESSED};
326 env->AddEventObserver(this, env, types);
327 window->AddPreTargetHandler(this);
328 } else {
329 window->RemovePreTargetHandler(this);
330 env->RemoveEventObserver(this);
331 immersive_focus_watcher_.reset();
332
333 animation_->Stop();
334 }
335 }
336
HandleMouseEvent(const ui::MouseEvent & event,const gfx::Point & location_in_screen,views::Widget * target)337 void ImmersiveFullscreenController::HandleMouseEvent(
338 const ui::MouseEvent& event,
339 const gfx::Point& location_in_screen,
340 views::Widget* target) {
341 if (!enabled_)
342 return;
343
344 if (event.type() != ui::ET_MOUSE_MOVED &&
345 event.type() != ui::ET_MOUSE_PRESSED &&
346 event.type() != ui::ET_MOUSE_RELEASED &&
347 event.type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
348 return;
349 }
350
351 // Mouse hover can initiate revealing the top-of-window views while |widget_|
352 // is inactive.
353 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
354 top_edge_hover_timer_.Stop();
355 UpdateLocatedEventRevealedLock(&event, location_in_screen);
356 } else if (event.type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
357 // Trigger reveal if the cursor pauses at the top of the screen for a while.
358 UpdateTopEdgeHoverTimer(event, location_in_screen, target);
359 }
360 }
361
HandleTouchEvent(const ui::TouchEvent & event,const gfx::Point & location_in_screen)362 void ImmersiveFullscreenController::HandleTouchEvent(
363 const ui::TouchEvent& event,
364 const gfx::Point& location_in_screen) {
365 if (!enabled_ || event.type() != ui::ET_TOUCH_PRESSED)
366 return;
367
368 // Touch should not initiate revealing the top-of-window views while |widget_|
369 // is inactive.
370 if (!widget_->IsActive())
371 return;
372
373 UpdateLocatedEventRevealedLock(&event, location_in_screen);
374 }
375
UpdateTopEdgeHoverTimer(const ui::MouseEvent & event,const gfx::Point & location_in_screen,views::Widget * target)376 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
377 const ui::MouseEvent& event,
378 const gfx::Point& location_in_screen,
379 views::Widget* target) {
380 DCHECK(enabled_);
381 DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED);
382
383 // Check whether |widget_| is the event target instead of checking for
384 // activation. This allows the timer to be started when |widget_| is inactive
385 // but prevents starting the timer if the mouse is over a portion of the top
386 // edge obscured by an unrelated widget.
387 if (!top_edge_hover_timer_.IsRunning() && !IsTargetForWidget(target))
388 return;
389
390 // Mouse hover should not initiate revealing the top-of-window views while a
391 // window has mouse capture.
392 if (ImmersiveContext::Get()->DoesAnyWindowHaveCapture())
393 return;
394
395 if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
396 return;
397
398 // Stop the timer if the cursor left the top edge or is on a different
399 // display.
400 gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen();
401 hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
402 if (!hit_bounds_in_screen.Contains(location_in_screen)) {
403 top_edge_hover_timer_.Stop();
404 return;
405 }
406
407 // The cursor is now at the top of the screen. Consider the cursor "not
408 // moving" even if it moves a little bit because users don't have perfect
409 // pointing precision. (The y position is not tested because
410 // |hit_bounds_in_screen| is short.)
411 if (top_edge_hover_timer_.IsRunning() &&
412 abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
413 kMouseRevealXThresholdPixels)
414 return;
415
416 // Start the reveal if the cursor doesn't move for some amount of time.
417 mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
418 top_edge_hover_timer_.Stop();
419 // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
420 top_edge_hover_timer_.Start(
421 FROM_HERE, base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs),
422 base::BindOnce(
423 &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock,
424 base::Unretained(this)));
425 }
426
UpdateLocatedEventRevealedLock(const ui::LocatedEvent * event,const gfx::Point & location_in_screen)427 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
428 const ui::LocatedEvent* event,
429 const gfx::Point& location_in_screen) {
430 if (!enabled_)
431 return;
432 DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
433
434 // Neither the mouse nor touch can initiate a reveal when the top-of-window
435 // views are sliding closed or are closed with the following exceptions:
436 // - Hovering at y = 0 which is handled in HandleMouseEvent().
437 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
438 if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED)
439 return;
440
441 // For the sake of simplicity, ignore |widget_|'s activation in computing
442 // whether the top-of-window views should stay revealed. Ideally, the
443 // top-of-window views would stay revealed only when the mouse cursor is
444 // hovered above a non-obscured portion of the top-of-window views. The
445 // top-of-window views may be partially obscured when |widget_| is inactive.
446
447 // Ignore all events while a window has capture. This keeps the top-of-window
448 // views revealed during a drag.
449 if (ImmersiveContext::Get()->DoesAnyWindowHaveCapture())
450 return;
451
452 if ((!event || event->IsMouseEvent()) &&
453 ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
454 return;
455 }
456
457 // The visible bounds of |top_container_| should be contained in
458 // |hit_bounds_in_screen|.
459 std::vector<gfx::Rect> hit_bounds_in_screen =
460 delegate_->GetVisibleBoundsInScreen();
461 bool keep_revealed = false;
462 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
463 // Allow the cursor to move slightly off the top-of-window views before
464 // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
465 // when the user is attempting to click on the bookmark bar and
466 // overshoots slightly.
467 if (event && event->type() == ui::ET_MOUSE_MOVED) {
468 const int kBoundsOffsetY = 8;
469 hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY);
470 }
471
472 if (hit_bounds_in_screen[i].Contains(location_in_screen)) {
473 keep_revealed = true;
474 break;
475 }
476 }
477
478 if (keep_revealed)
479 AcquireLocatedEventRevealedLock();
480 else
481 located_event_revealed_lock_.reset();
482 }
483
UpdateLocatedEventRevealedLock()484 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock() {
485 if (!aura::client::GetCursorClient(
486 widget_->GetNativeWindow()->GetRootWindow())
487 ->IsMouseEventsEnabled()) {
488 // If mouse events are disabled, the user's last interaction was probably
489 // via touch. Do no further processing in this case as there is no easy
490 // way of retrieving the position of the user's last touch.
491 return;
492 }
493 UpdateLocatedEventRevealedLock(
494 nullptr, display::Screen::GetScreen()->GetCursorScreenPoint());
495 }
496
AcquireLocatedEventRevealedLock()497 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
498 // CAUTION: Acquiring the lock results in a reentrant call to
499 // AcquireLocatedEventRevealedLock() when
500 // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
501 if (!located_event_revealed_lock_.get())
502 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
503 }
504
UpdateRevealedLocksForSwipe(SwipeType swipe_type)505 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
506 SwipeType swipe_type) {
507 if (!enabled_ || swipe_type == SWIPE_NONE)
508 return false;
509
510 // Swipes while |widget_| is inactive should have been filtered out in
511 // OnGestureEvent().
512 DCHECK(widget_->IsActive());
513
514 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
515 if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
516 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
517 return true;
518 }
519 } else {
520 if (swipe_type == SWIPE_CLOSE) {
521 // Attempt to end the reveal. If other code is holding onto a lock, the
522 // attempt will be unsuccessful.
523 located_event_revealed_lock_.reset();
524 if (immersive_focus_watcher_)
525 immersive_focus_watcher_->ReleaseLock();
526
527 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
528 widget_->GetFocusManager()->ClearFocus();
529 return true;
530 }
531
532 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
533 UpdateLocatedEventRevealedLock();
534 if (immersive_focus_watcher_)
535 immersive_focus_watcher_->UpdateFocusRevealedLock();
536 }
537 }
538 return false;
539 }
540
GetAnimationDuration(Animate animate) const541 base::TimeDelta ImmersiveFullscreenController::GetAnimationDuration(
542 Animate animate) const {
543 switch (animate) {
544 case ANIMATE_NO:
545 return base::TimeDelta();
546 case ANIMATE_SLOW:
547 return base::TimeDelta::FromMilliseconds(400);
548 case ANIMATE_FAST:
549 return base::TimeDelta::FromMilliseconds(200);
550 }
551 NOTREACHED();
552 return base::TimeDelta();
553 }
554
MaybeStartReveal(Animate animate)555 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) {
556 if (!enabled_)
557 return;
558
559 if (animations_disabled_for_test_)
560 animate = ANIMATE_NO;
561
562 // Callers with ANIMATE_NO expect this function to synchronously reveal the
563 // top-of-window views.
564 if (reveal_state_ == REVEALED ||
565 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
566 return;
567 }
568
569 RevealState previous_reveal_state = reveal_state_;
570 reveal_state_ = SLIDING_OPEN;
571 if (previous_reveal_state == CLOSED) {
572 EnableTouchInsets(false);
573
574 delegate_->OnImmersiveRevealStarted();
575
576 // Do not do any more processing if OnImmersiveRevealStarted() changed
577 // |reveal_state_|.
578 if (reveal_state_ != SLIDING_OPEN)
579 return;
580 }
581 // Slide in the reveal view.
582 if (animate == ANIMATE_NO) {
583 animation_->Reset(1);
584 OnSlideOpenAnimationCompleted();
585 } else {
586 animation_->SetSlideDuration(GetAnimationDuration(animate));
587 animation_->Show();
588 }
589 }
590
OnSlideOpenAnimationCompleted()591 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
592 DCHECK_EQ(SLIDING_OPEN, reveal_state_);
593 reveal_state_ = REVEALED;
594 delegate_->SetVisibleFraction(1);
595
596 // The user may not have moved the mouse since the reveal was initiated.
597 // Update the revealed lock to reflect the mouse's current state.
598 UpdateLocatedEventRevealedLock();
599 }
600
MaybeEndReveal(Animate animate)601 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) {
602 if (!enabled_ || revealed_lock_count_ != 0)
603 return;
604
605 if (animations_disabled_for_test_)
606 animate = ANIMATE_NO;
607
608 // Callers with ANIMATE_NO expect this function to synchronously close the
609 // top-of-window views.
610 if (reveal_state_ == CLOSED ||
611 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
612 return;
613 }
614
615 reveal_state_ = SLIDING_CLOSED;
616 base::TimeDelta duration = GetAnimationDuration(animate);
617 if (duration > base::TimeDelta()) {
618 animation_->SetSlideDuration(duration);
619 animation_->Hide();
620 } else {
621 animation_->Reset(0);
622 OnSlideClosedAnimationCompleted();
623 }
624 }
625
OnSlideClosedAnimationCompleted()626 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
627 DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
628 reveal_state_ = CLOSED;
629
630 EnableTouchInsets(true);
631 delegate_->OnImmersiveRevealEnded();
632 }
633
634 ImmersiveFullscreenController::SwipeType
GetSwipeType(const ui::GestureEvent & event) const635 ImmersiveFullscreenController::GetSwipeType(
636 const ui::GestureEvent& event) const {
637 if (event.type() != ui::ET_GESTURE_SCROLL_UPDATE)
638 return SWIPE_NONE;
639 // Make sure that it is a clear vertical gesture.
640 if (std::abs(event.details().scroll_y()) <=
641 kSwipeVerticalThresholdMultiplier * std::abs(event.details().scroll_x()))
642 return SWIPE_NONE;
643 if (event.details().scroll_y() < 0)
644 return SWIPE_CLOSE;
645 if (event.details().scroll_y() > 0)
646 return SWIPE_OPEN;
647 return SWIPE_NONE;
648 }
649
ShouldIgnoreMouseEventAtLocation(const gfx::Point & location) const650 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
651 const gfx::Point& location) const {
652 // Ignore mouse events in the region immediately above the top edge of the
653 // display. This is to handle the case of a user with a vertical display
654 // layout (primary display above/below secondary display) and the immersive
655 // fullscreen window on the bottom display. It is really hard to trigger a
656 // reveal in this case because:
657 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
658 // pixels of the bottom display.
659 // - The cursor is warped to the top display if the cursor gets to the top
660 // edge of the bottom display.
661 // Mouse events are ignored in the bottom few pixels of the top display
662 // (Mouse events in this region cannot start or end a reveal). This allows a
663 // user to overshoot the top of the bottom display and still reveal the
664 // top-of-window views.
665 gfx::Rect dead_region = GetDisplayBoundsInScreen();
666 dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
667 dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
668 return dead_region.Contains(location);
669 }
670
ShouldHandleGestureEvent(const gfx::Point & location) const671 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
672 const gfx::Point& location) const {
673 DCHECK(widget_->IsActive());
674 if (reveal_state_ == REVEALED) {
675 std::vector<gfx::Rect> hit_bounds_in_screen(
676 delegate_->GetVisibleBoundsInScreen());
677 for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
678 if (hit_bounds_in_screen[i].Contains(location))
679 return true;
680 }
681 return false;
682 }
683
684 // When the top-of-window views are not fully revealed, handle gestures which
685 // start in the top few pixels of the screen.
686 gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen());
687 hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset);
688 if (hit_bounds_in_screen.Contains(location))
689 return true;
690
691 // There may be a bezel sensor off screen logically above
692 // |hit_bounds_in_screen|. The check for the event not contained by the
693 // closest screen ensures that the event is from a valid bezel (as opposed to
694 // another screen in an extended desktop).
695 gfx::Rect screen_bounds =
696 display::Screen::GetScreen()->GetDisplayNearestPoint(location).bounds();
697 return (!screen_bounds.Contains(location) &&
698 location.y() < hit_bounds_in_screen.y() &&
699 location.x() >= hit_bounds_in_screen.x() &&
700 location.x() < hit_bounds_in_screen.right());
701 }
702
GetDisplayBoundsInScreen() const703 gfx::Rect ImmersiveFullscreenController::GetDisplayBoundsInScreen() const {
704 return ImmersiveContext::Get()->GetDisplayBoundsInScreen(widget_);
705 }
706
IsTargetForWidget(views::Widget * target) const707 bool ImmersiveFullscreenController::IsTargetForWidget(
708 views::Widget* target) const {
709 return target == widget_ || target == top_container_->GetWidget();
710 }
711
UpdateEnabled()712 void ImmersiveFullscreenController::UpdateEnabled() {
713 if (!widget_)
714 return;
715
716 const bool enabled =
717 widget_->GetNativeWindow()->GetProperty(kImmersiveIsActive);
718
719 if (enabled_ == enabled) {
720 // Frame layout depends on the window's state and size,
721 // which can happen asynchronously and/or independently,
722 // from the timing when the immersive state change.
723 delegate_->Relayout();
724 return;
725 }
726 enabled_ = enabled;
727
728 EnableEventObservers(enabled_);
729
730 ImmersiveContext::Get()->OnEnteringOrExitingImmersive(this, enabled);
731
732 if (enabled_) {
733 // Animate enabling immersive mode by sliding out the top-of-window views.
734 // No animation occurs if a lock is holding the top-of-window views open.
735
736 normal_targeter_ = widget_->GetNativeWindow()->SetEventTargeter(
737 std::make_unique<ImmersiveWindowTargeter>());
738
739 // Do a reveal to set the initial state for the animation. (And any
740 // required state in case the animation cannot run because of a lock holding
741 // the top-of-window views open.)
742 MaybeStartReveal(ANIMATE_NO);
743
744 // Reset the located event so that it does not affect whether the
745 // top-of-window views are hidden.
746 located_event_revealed_lock_.reset();
747
748 // Try doing the animation.
749 MaybeEndReveal(ANIMATE_SLOW);
750
751 if (reveal_state_ == REVEALED) {
752 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
753 UpdateLocatedEventRevealedLock();
754 if (immersive_focus_watcher_)
755 immersive_focus_watcher_->UpdateFocusRevealedLock();
756 }
757
758 delegate_->OnImmersiveFullscreenEntered();
759 } else {
760 // Stop cursor-at-top tracking.
761 top_edge_hover_timer_.Stop();
762 reveal_state_ = CLOSED;
763
764 widget_->GetNativeWindow()->SetEventTargeter(std::move(normal_targeter_));
765
766 delegate_->OnImmersiveFullscreenExited();
767 }
768 }
769
EnableTouchInsets(bool enable)770 void ImmersiveFullscreenController::EnableTouchInsets(bool enable) {
771 if (!widget_->GetNativeWindow()->targeter())
772 return;
773
774 widget_->GetNativeWindow()->targeter()->SetInsets(
775 {}, gfx::Insets(enable ? kImmersiveFullscreenTopEdgeInset : 0, 0, 0, 0));
776 }
777
778 } // namespace chromeos
779