OLD | NEW |
| (Empty) |
1 // Copyright 2012 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 "chrome/browser/ui/views/immersive_mode_controller.h" | |
6 | |
7 #include "chrome/browser/ui/views/frame/browser_view.h" | |
8 #include "chrome/browser/ui/views/frame/top_container_view.h" | |
9 #include "chrome/browser/ui/views/tabs/tab_strip.h" | |
10 #include "chrome/common/chrome_switches.h" | |
11 #include "ui/compositor/layer_animation_observer.h" | |
12 #include "ui/compositor/scoped_layer_animation_settings.h" | |
13 #include "ui/gfx/transform.h" | |
14 #include "ui/views/view.h" | |
15 #include "ui/views/widget/widget.h" | |
16 #include "ui/views/window/non_client_view.h" | |
17 | |
18 #if defined(USE_ASH) | |
19 #include "ash/ash_switches.h" | |
20 #include "ash/shell.h" | |
21 #include "ash/wm/window_properties.h" | |
22 #include "base/command_line.h" | |
23 #endif | |
24 | |
25 #if defined(USE_AURA) | |
26 #include "ui/aura/client/aura_constants.h" | |
27 #include "ui/aura/window.h" | |
28 #include "ui/aura/window_observer.h" | |
29 #endif | |
30 | |
31 using views::View; | |
32 | |
33 namespace { | |
34 | |
35 // Time after which the edge trigger fires and top-chrome is revealed. This is | |
36 // after the mouse stops moving. | |
37 const int kTopEdgeRevealDelayMs = 200; | |
38 | |
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; | |
44 | |
45 } // namespace | |
46 | |
47 //////////////////////////////////////////////////////////////////////////////// | |
48 | |
49 #if defined(USE_AURA) | |
50 // Observer to watch for window restore. views::Widget does not provide a hook | |
51 // to observe for window restore, so do this at the Aura level. | |
52 class ImmersiveModeController::WindowObserver : public aura::WindowObserver { | |
53 public: | |
54 explicit WindowObserver(ImmersiveModeController* controller) | |
55 : controller_(controller) { | |
56 controller_->native_window_->AddObserver(this); | |
57 } | |
58 | |
59 virtual ~WindowObserver() { | |
60 controller_->native_window_->RemoveObserver(this); | |
61 } | |
62 | |
63 // aura::WindowObserver overrides: | |
64 virtual void OnWindowPropertyChanged(aura::Window* window, | |
65 const void* key, | |
66 intptr_t old) OVERRIDE { | |
67 using aura::client::kShowStateKey; | |
68 if (key == kShowStateKey) { | |
69 // Disable immersive mode when leaving the fullscreen state. | |
70 if (window->GetProperty(kShowStateKey) != ui::SHOW_STATE_FULLSCREEN) | |
71 controller_->SetEnabled(false); | |
72 return; | |
73 } | |
74 #if defined(USE_ASH) | |
75 using ash::internal::kImmersiveModeKey; | |
76 if (key == kImmersiveModeKey) { | |
77 // Another component has toggled immersive mode. | |
78 controller_->SetEnabled(window->GetProperty(kImmersiveModeKey)); | |
79 return; | |
80 } | |
81 #endif | |
82 } | |
83 | |
84 private: | |
85 ImmersiveModeController* controller_; // Not owned. | |
86 | |
87 DISALLOW_COPY_AND_ASSIGN(WindowObserver); | |
88 }; | |
89 #endif // defined(USE_AURA) | |
90 | |
91 //////////////////////////////////////////////////////////////////////////////// | |
92 | |
93 class ImmersiveModeController::AnimationObserver | |
94 : public ui::ImplicitAnimationObserver { | |
95 public: | |
96 enum AnimationType { | |
97 SLIDE_OPEN, | |
98 SLIDE_CLOSED, | |
99 }; | |
100 | |
101 AnimationObserver(ImmersiveModeController* controller, AnimationType type) | |
102 : controller_(controller), animation_type_(type) {} | |
103 virtual ~AnimationObserver() {} | |
104 | |
105 // ui::ImplicitAnimationObserver overrides: | |
106 virtual void OnImplicitAnimationsCompleted() OVERRIDE { | |
107 if (animation_type_ == SLIDE_OPEN) | |
108 controller_->OnSlideOpenAnimationCompleted(); | |
109 else if (animation_type_ == SLIDE_CLOSED) | |
110 controller_->OnSlideClosedAnimationCompleted(); | |
111 else | |
112 NOTREACHED(); | |
113 } | |
114 | |
115 private: | |
116 ImmersiveModeController* controller_; | |
117 AnimationType animation_type_; | |
118 | |
119 DISALLOW_COPY_AND_ASSIGN(AnimationObserver); | |
120 }; | |
121 | |
122 //////////////////////////////////////////////////////////////////////////////// | |
123 | |
124 ImmersiveModeController::ImmersiveModeController() | |
125 : browser_view_(NULL), | |
126 enabled_(false), | |
127 reveal_state_(CLOSED), | |
128 reveal_locked_(false), | |
129 hide_tab_indicators_(false), | |
130 reveal_hovered_(false), | |
131 native_window_(NULL) { | |
132 } | |
133 | |
134 ImmersiveModeController::~ImmersiveModeController() { | |
135 // The browser view is being destroyed so there's no need to update its | |
136 // layout or layers, even if the top views are revealed. But the window | |
137 // observers still need to be removed. | |
138 EnableWindowObservers(false); | |
139 } | |
140 | |
141 void ImmersiveModeController::Init(BrowserView* browser_view) { | |
142 browser_view_ = browser_view; | |
143 // Browser view is detached from its widget during destruction. Cache the | |
144 // window pointer so |this| can stop observing during destruction. | |
145 native_window_ = browser_view_->GetNativeWindow(); | |
146 DCHECK(native_window_); | |
147 EnableWindowObservers(true); | |
148 | |
149 slide_open_observer_.reset( | |
150 new AnimationObserver(this, AnimationObserver::SLIDE_OPEN)); | |
151 slide_closed_observer_.reset( | |
152 new AnimationObserver(this, AnimationObserver::SLIDE_CLOSED)); | |
153 | |
154 #if defined(USE_ASH) | |
155 // Optionally allow the tab indicators to be hidden. | |
156 hide_tab_indicators_ = CommandLine::ForCurrentProcess()-> | |
157 HasSwitch(ash::switches::kAshImmersiveHideTabIndicators); | |
158 #endif | |
159 } | |
160 | |
161 // static | |
162 bool ImmersiveModeController::UseImmersiveFullscreen() { | |
163 #if defined(OS_CHROMEOS) | |
164 // Kiosk mode needs the whole screen. | |
165 CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
166 return !command_line->HasSwitch(switches::kKioskMode) && | |
167 !command_line->HasSwitch(ash::switches::kAshDisableImmersiveMode); | |
168 #endif | |
169 return false; | |
170 } | |
171 | |
172 void ImmersiveModeController::SetEnabled(bool enabled) { | |
173 DCHECK(browser_view_) << "Must initialize before enabling"; | |
174 if (enabled_ == enabled) | |
175 return; | |
176 enabled_ = enabled; | |
177 | |
178 TopContainerView* top_container = browser_view_->top_container(); | |
179 if (enabled_) { | |
180 // Slide closed the reveal view on enable by slamming it to revealed then | |
181 // triggering an end-reveal animation. | |
182 reveal_state_ = REVEALED; | |
183 top_container->SetPaintToLayer(true); | |
184 top_container->SetFillsBoundsOpaquely(true); | |
185 // Layout updates at the end of the slide closed. | |
186 EndReveal(ANIMATE_SLOW); | |
187 } else { | |
188 // Stop cursor-at-top tracking. | |
189 top_timer_.Stop(); | |
190 // Snap immediately to the closed state. | |
191 reveal_state_ = CLOSED; | |
192 top_container->SetFillsBoundsOpaquely(false); | |
193 top_container->SetPaintToLayer(false); | |
194 browser_view_->GetWidget()->non_client_view()->frame_view()-> | |
195 ResetWindowControls(); | |
196 browser_view_->tabstrip()->SetImmersiveStyle(false); | |
197 // Don't need explicit layout because we're inside a fullscreen transition | |
198 // and it blocks layout calls. | |
199 } | |
200 | |
201 #if defined(USE_ASH) | |
202 // This causes a no-op call to SetEnabled() since enabled_ is already set. | |
203 native_window_->SetProperty(ash::internal::kImmersiveModeKey, enabled_); | |
204 // Ash on Windows may not have a shell. | |
205 if (ash::Shell::HasInstance()) { | |
206 // Shelf auto-hides in immersive mode. | |
207 ash::Shell::GetInstance()->UpdateShelfVisibility(); | |
208 } | |
209 #endif | |
210 } | |
211 | |
212 void ImmersiveModeController::MaybeStackViewAtTop() { | |
213 #if defined(USE_AURA) | |
214 if (enabled_ && reveal_state_ != CLOSED) { | |
215 ui::Layer* reveal_layer = browser_view_->top_container()->layer(); | |
216 if (reveal_layer) | |
217 reveal_layer->parent()->StackAtTop(reveal_layer); | |
218 } | |
219 #endif | |
220 } | |
221 | |
222 void ImmersiveModeController::MaybeStartReveal() { | |
223 if (enabled_ && reveal_state_ != REVEALED) | |
224 StartReveal(ANIMATE_FAST); | |
225 } | |
226 | |
227 void ImmersiveModeController::CancelReveal() { | |
228 EndReveal(ANIMATE_NO); | |
229 } | |
230 | |
231 void ImmersiveModeController::RevealAndLock(bool reveal) { | |
232 if (!enabled_) | |
233 return; | |
234 reveal_locked_ = reveal; | |
235 if (reveal_locked_ && reveal_state_ != REVEALED) | |
236 StartReveal(ANIMATE_FAST); | |
237 else if (!reveal_locked_ && reveal_state_ != CLOSED) | |
238 EndReveal(ANIMATE_FAST); | |
239 } | |
240 | |
241 void ImmersiveModeController::OnRevealViewLostFocus() { | |
242 // Stop the reveal if the mouse is outside the reveal view. | |
243 if (!reveal_locked_ && !reveal_hovered_) | |
244 EndReveal(ANIMATE_FAST); | |
245 } | |
246 | |
247 //////////////////////////////////////////////////////////////////////////////// | |
248 | |
249 // ui::EventHandler overrides: | |
250 void ImmersiveModeController::OnMouseEvent(ui::MouseEvent* event) { | |
251 if (!enabled_ || event->type() != ui::ET_MOUSE_MOVED) | |
252 return; | |
253 if (event->location().y() == 0) { | |
254 // Start a reveal if the mouse touches the top of the screen and then stops | |
255 // moving for a little while. This mirrors the Ash launcher behavior. | |
256 top_timer_.Stop(); | |
257 // Timer is stopped when |this| is destroyed, hence Unretained() is safe. | |
258 top_timer_.Start(FROM_HERE, | |
259 base::TimeDelta::FromMilliseconds(kTopEdgeRevealDelayMs), | |
260 base::Bind(&ImmersiveModeController::StartReveal, | |
261 base::Unretained(this), | |
262 ANIMATE_FAST)); | |
263 } else { | |
264 // Cursor left the top edge. | |
265 top_timer_.Stop(); | |
266 } | |
267 | |
268 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) { | |
269 // Look for the mouse leaving the bottom edge of the revealed view. | |
270 int bottom_edge = browser_view_->top_container()->bounds().bottom(); | |
271 if (event->location().y() > bottom_edge) { | |
272 reveal_hovered_ = false; | |
273 OnRevealViewLostMouse(); | |
274 } else { | |
275 reveal_hovered_ = true; | |
276 } | |
277 } | |
278 | |
279 // Pass along event for further handling. | |
280 } | |
281 | |
282 //////////////////////////////////////////////////////////////////////////////// | |
283 // Testing interface: | |
284 | |
285 void ImmersiveModeController::SetHideTabIndicatorsForTest(bool hide) { | |
286 hide_tab_indicators_ = hide; | |
287 } | |
288 | |
289 void ImmersiveModeController::StartRevealForTest() { | |
290 StartReveal(ANIMATE_NO); | |
291 } | |
292 | |
293 void ImmersiveModeController::OnRevealViewLostMouseForTest() { | |
294 OnRevealViewLostMouse(); | |
295 } | |
296 | |
297 //////////////////////////////////////////////////////////////////////////////// | |
298 // private: | |
299 | |
300 void ImmersiveModeController::EnableWindowObservers(bool enable) { | |
301 if (!native_window_) { | |
302 NOTREACHED() << "ImmersiveModeController not initialized"; | |
303 return; | |
304 } | |
305 #if defined(USE_AURA) | |
306 // TODO(jamescook): Porting immersive mode to non-Aura views will require | |
307 // a method to monitor incoming mouse move events without handling them. | |
308 // Currently views uses GetEventHandlerForPoint() to route events directly | |
309 // to either a tab or the caption area, bypassing pre-target handlers and | |
310 // intermediate views. | |
311 if (enable) | |
312 native_window_->AddPreTargetHandler(this); | |
313 else | |
314 native_window_->RemovePreTargetHandler(this); | |
315 | |
316 // The window observer adds and removes itself from the native window. | |
317 // TODO(jamescook): Porting to non-Aura will also require a method to monitor | |
318 // for window restore, which is not provided by views Widget. | |
319 window_observer_.reset(enable ? new WindowObserver(this) : NULL); | |
320 #endif // defined(USE_AURA) | |
321 } | |
322 | |
323 void ImmersiveModeController::StartReveal(Animate animate) { | |
324 DCHECK_NE(ANIMATE_SLOW, animate); | |
325 if (reveal_state_ == CLOSED) { | |
326 reveal_state_ = SLIDING_OPEN; | |
327 // Turn on layer painting so we can smoothly animate. | |
328 TopContainerView* top_container = browser_view_->top_container(); | |
329 top_container->SetPaintToLayer(true); | |
330 top_container->SetFillsBoundsOpaquely(true); | |
331 | |
332 // Ensure window caption buttons are updated and the view bounds are | |
333 // computed at normal (non-immersive-style) size. | |
334 LayoutBrowserView(false); | |
335 | |
336 // Slide in the reveal view. | |
337 if (animate != ANIMATE_NO) | |
338 AnimateSlideOpen(); // Show is always fast. | |
339 } else if (reveal_state_ == SLIDING_CLOSED) { | |
340 reveal_state_ = SLIDING_OPEN; | |
341 // Reverse the animation. | |
342 AnimateSlideOpen(); | |
343 } | |
344 } | |
345 | |
346 void ImmersiveModeController::LayoutBrowserView(bool immersive_style) { | |
347 // Update the window caption buttons. | |
348 browser_view_->GetWidget()->non_client_view()->frame_view()-> | |
349 ResetWindowControls(); | |
350 browser_view_->tabstrip()->SetImmersiveStyle(immersive_style); | |
351 browser_view_->Layout(); | |
352 } | |
353 | |
354 void ImmersiveModeController::AnimateSlideOpen() { | |
355 ui::Layer* layer = browser_view_->top_container()->layer(); | |
356 // Stop any slide closed animation in progress. | |
357 layer->GetAnimator()->AbortAllAnimations(); | |
358 | |
359 gfx::Transform transform; | |
360 transform.Translate(0, -layer->bounds().height()); | |
361 layer->SetTransform(transform); | |
362 | |
363 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); | |
364 settings.AddObserver(slide_open_observer_.get()); | |
365 settings.SetTweenType(ui::Tween::EASE_OUT); | |
366 settings.SetTransitionDuration( | |
367 base::TimeDelta::FromMilliseconds(kRevealFastAnimationDurationMs)); | |
368 layer->SetTransform(gfx::Transform()); | |
369 } | |
370 | |
371 void ImmersiveModeController::OnSlideOpenAnimationCompleted() { | |
372 if (reveal_state_ == SLIDING_OPEN) | |
373 reveal_state_ = REVEALED; | |
374 } | |
375 | |
376 void ImmersiveModeController::OnRevealViewLostMouse() { | |
377 // Stop the reveal if the top view's children don't have focus. | |
378 views::View* focused = browser_view_->GetFocusManager()->GetFocusedView(); | |
379 if (!reveal_locked_ && | |
380 !browser_view_->top_container()->Contains(focused)) | |
381 EndReveal(ANIMATE_FAST); | |
382 } | |
383 | |
384 void ImmersiveModeController::EndReveal(Animate animate) { | |
385 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) { | |
386 reveal_state_ = SLIDING_CLOSED; | |
387 if (animate == ANIMATE_FAST) | |
388 AnimateSlideClosed(kRevealFastAnimationDurationMs); | |
389 else if (animate == ANIMATE_SLOW) | |
390 AnimateSlideClosed(kRevealSlowAnimationDurationMs); | |
391 else | |
392 OnSlideClosedAnimationCompleted(); | |
393 } | |
394 } | |
395 | |
396 void ImmersiveModeController::AnimateSlideClosed(int duration_ms) { | |
397 // Stop any slide open animation in progress, but don't skip to the end. This | |
398 // avoids a visual "pop" when starting a hide in the middle of a show. | |
399 ui::Layer* layer = browser_view_->top_container()->layer(); | |
400 layer->GetAnimator()->AbortAllAnimations(); | |
401 | |
402 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); | |
403 settings.SetTweenType(ui::Tween::EASE_OUT); | |
404 settings.SetTransitionDuration( | |
405 base::TimeDelta::FromMilliseconds(duration_ms)); | |
406 settings.AddObserver(slide_closed_observer_.get()); | |
407 gfx::Transform transform; | |
408 transform.Translate(0, -layer->bounds().height()); | |
409 layer->SetTransform(transform); | |
410 } | |
411 | |
412 void ImmersiveModeController::OnSlideClosedAnimationCompleted() { | |
413 if (reveal_state_ == SLIDING_CLOSED) { | |
414 reveal_state_ = CLOSED; | |
415 TopContainerView* top_container = browser_view_->top_container(); | |
416 // Layer isn't needed after animation completes. | |
417 top_container->SetFillsBoundsOpaquely(false); | |
418 top_container->SetPaintToLayer(false); | |
419 // Update tabstrip for closed state. | |
420 LayoutBrowserView(true); | |
421 } | |
422 } | |
OLD | NEW |