OLD | NEW |
| (Empty) |
1 // Copyright (c) 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 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/command_line.h" | |
10 #import "base/mac/mac_util.h" | |
11 #include "base/mac/sdk_forward_declarations.h" | |
12 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | |
13 #include "chrome/common/chrome_switches.h" | |
14 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h
" | |
15 #import "ui/base/cocoa/nsview_additions.h" | |
16 | |
17 NSString* const kWillEnterFullscreenNotification = | |
18 @"WillEnterFullscreenNotification"; | |
19 NSString* const kWillLeaveFullscreenNotification = | |
20 @"WillLeaveFullscreenNotification"; | |
21 | |
22 namespace { | |
23 | |
24 // The activation zone for the main menu is 4 pixels high; if we make it any | |
25 // smaller, then the menu can be made to appear without the bar sliding down. | |
26 const CGFloat kDropdownActivationZoneHeight = 4; | |
27 const NSTimeInterval kDropdownAnimationDuration = 0.12; | |
28 const NSTimeInterval kMouseExitCheckDelay = 0.1; | |
29 // This show delay attempts to match the delay for the main menu. | |
30 const NSTimeInterval kDropdownShowDelay = 0.3; | |
31 const NSTimeInterval kDropdownHideDelay = 0.2; | |
32 | |
33 // The duration the toolbar is revealed for tab strip changes. | |
34 const NSTimeInterval kDropdownForTabStripChangesDuration = 0.75; | |
35 | |
36 // The event kind value for a undocumented menubar show/hide Carbon event. | |
37 const CGFloat kMenuBarRevealEventKind = 2004; | |
38 | |
39 // The amount by which the floating bar is offset downwards (to avoid the menu) | |
40 // in presentation mode. (We can't use |-[NSMenu menuBarHeight]| since it | |
41 // returns 0 when the menu bar is hidden.) | |
42 const CGFloat kFloatingBarVerticalOffset = 22; | |
43 | |
44 OSStatus MenuBarRevealHandler(EventHandlerCallRef handler, | |
45 EventRef event, | |
46 void* context) { | |
47 PresentationModeController* self = | |
48 static_cast<PresentationModeController*>(context); | |
49 | |
50 // If Chrome has multiple fullscreen windows in their own space, the Handler | |
51 // becomes flaky and might start receiving kMenuBarRevealEventKind events | |
52 // from another space. Since the menubar in the another space is in either a | |
53 // shown or hidden state, it will give us a reveal fraction of 0.0 or 1.0. | |
54 // As such, we should ignore the kMenuBarRevealEventKind event if it gives | |
55 // us a fraction of 0.0 or 1.0, and rely on kEventMenuBarShown and | |
56 // kEventMenuBarHidden to set these values. | |
57 if ([self isMainWindow] && ![self isFullscreenTransitionInProgress]) { | |
58 if (GetEventKind(event) == kMenuBarRevealEventKind) { | |
59 CGFloat revealFraction = 0; | |
60 GetEventParameter(event, FOUR_CHAR_CODE('rvlf'), typeCGFloat, NULL, | |
61 sizeof(CGFloat), NULL, &revealFraction); | |
62 if (revealFraction > 0.0 && revealFraction < 1.0) | |
63 [self setMenuBarRevealProgress:revealFraction]; | |
64 } else if (GetEventKind(event) == kEventMenuBarShown) { | |
65 [self setMenuBarRevealProgress:1.0]; | |
66 } else { | |
67 [self setMenuBarRevealProgress:0.0]; | |
68 } | |
69 } | |
70 | |
71 return CallNextEventHandler(handler, event); | |
72 } | |
73 | |
74 } // end namespace | |
75 | |
76 // Helper class to manage animations for the dropdown bar. Calls | |
77 // [PresentationModeController changeToolbarFraction] once per | |
78 // animation step. | |
79 @interface DropdownAnimation : NSAnimation { | |
80 @private | |
81 PresentationModeController* controller_; | |
82 CGFloat startFraction_; | |
83 CGFloat endFraction_; | |
84 } | |
85 | |
86 @property(readonly, nonatomic) CGFloat startFraction; | |
87 @property(readonly, nonatomic) CGFloat endFraction; | |
88 | |
89 // Designated initializer. Asks |controller| for the current shown fraction, so | |
90 // if the bar is already partially shown or partially hidden, the animation | |
91 // duration may be less than |fullDuration|. | |
92 - (id)initWithFraction:(CGFloat)fromFraction | |
93 fullDuration:(CGFloat)fullDuration | |
94 animationCurve:(NSAnimationCurve)animationCurve | |
95 controller:(PresentationModeController*)controller; | |
96 | |
97 @end | |
98 | |
99 @implementation DropdownAnimation | |
100 | |
101 @synthesize startFraction = startFraction_; | |
102 @synthesize endFraction = endFraction_; | |
103 | |
104 - (id)initWithFraction:(CGFloat)toFraction | |
105 fullDuration:(CGFloat)fullDuration | |
106 animationCurve:(NSAnimationCurve)animationCurve | |
107 controller:(PresentationModeController*)controller { | |
108 // Calculate the effective duration, based on the current shown fraction. | |
109 DCHECK(controller); | |
110 CGFloat fromFraction = controller.toolbarFraction; | |
111 CGFloat effectiveDuration = fabs(fullDuration * (fromFraction - toFraction)); | |
112 | |
113 if ((self = [super gtm_initWithDuration:effectiveDuration | |
114 eventMask:NSLeftMouseDownMask | |
115 animationCurve:animationCurve])) { | |
116 startFraction_ = fromFraction; | |
117 endFraction_ = toFraction; | |
118 controller_ = controller; | |
119 } | |
120 return self; | |
121 } | |
122 | |
123 // Called once per animation step. Overridden to change the floating bar's | |
124 // position based on the animation's progress. | |
125 - (void)setCurrentProgress:(NSAnimationProgress)progress { | |
126 CGFloat fraction = | |
127 startFraction_ + (progress * (endFraction_ - startFraction_)); | |
128 [controller_ changeToolbarFraction:fraction]; | |
129 } | |
130 | |
131 @end | |
132 | |
133 | |
134 @interface PresentationModeController (PrivateMethods) | |
135 | |
136 // Updates the visibility of the menu bar and the dock. | |
137 - (void)updateMenuBarAndDockVisibility; | |
138 | |
139 // Whether the current screen is expected to have a menu bar, regardless of | |
140 // current visibility of the menu bar. | |
141 - (BOOL)doesScreenHaveMenuBar; | |
142 | |
143 // Returns YES if the window is on the primary screen. | |
144 - (BOOL)isWindowOnPrimaryScreen; | |
145 | |
146 // Returns |kFullScreenModeHideAll| when the overlay is hidden and | |
147 // |kFullScreenModeHideDock| when the overlay is shown. | |
148 - (base::mac::FullScreenMode)desiredSystemFullscreenMode; | |
149 | |
150 // Change the overlay to the given fraction, with or without animation. Only | |
151 // guaranteed to work properly with |fraction == 0| or |fraction == 1|. This | |
152 // performs the show/hide (animation) immediately. It does not touch the timers. | |
153 - (void)changeOverlayToFraction:(CGFloat)fraction | |
154 withAnimation:(BOOL)animate; | |
155 | |
156 // Schedule the floating bar to be shown/hidden because of mouse position. | |
157 - (void)scheduleShowForMouse; | |
158 - (void)scheduleHideForMouse; | |
159 | |
160 // Set up the tracking area used to activate the sliding bar or keep it active | |
161 // using with the rectangle in |trackingAreaBounds_|, or remove the tracking | |
162 // area if one was previously set up. | |
163 - (void)setupTrackingArea; | |
164 - (void)removeTrackingAreaIfNecessary; | |
165 | |
166 // Returns YES if the mouse is currently in any current tracking rectangle, NO | |
167 // otherwise. | |
168 - (BOOL)mouseInsideTrackingRect; | |
169 | |
170 // The tracking area can "falsely" report exits when the menu slides down over | |
171 // it. In that case, we have to monitor for a "real" mouse exit on a timer. | |
172 // |-setupMouseExitCheck| schedules a check; |-cancelMouseExitCheck| cancels any | |
173 // scheduled check. | |
174 - (void)setupMouseExitCheck; | |
175 - (void)cancelMouseExitCheck; | |
176 | |
177 // Called (after a delay) by |-setupMouseExitCheck|, to check whether the mouse | |
178 // has exited or not; if it hasn't, it will schedule another check. | |
179 - (void)checkForMouseExit; | |
180 | |
181 // Start timers for showing/hiding the floating bar. | |
182 - (void)startShowTimer; | |
183 - (void)startHideTimer; | |
184 - (void)cancelShowTimer; | |
185 - (void)cancelHideTimer; | |
186 - (void)cancelAllTimers; | |
187 | |
188 // Methods called when the show/hide timers fire. Do not call directly. | |
189 - (void)showTimerFire:(NSTimer*)timer; | |
190 - (void)hideTimerFire:(NSTimer*)timer; | |
191 | |
192 // Stops any running animations, removes tracking areas, etc. | |
193 - (void)cleanup; | |
194 | |
195 // Shows and hides the UI associated with this window being active (having main | |
196 // status). This includes hiding the menu bar. These functions are called when | |
197 // the window gains or loses main status as well as in |-cleanup|. | |
198 - (void)showActiveWindowUI; | |
199 - (void)hideActiveWindowUI; | |
200 | |
201 // Whether the menu bar should be shown in immersive fullscreen for the screen | |
202 // that contains the window. | |
203 - (BOOL)shouldShowMenubarInImmersiveFullscreen; | |
204 | |
205 @end | |
206 | |
207 @implementation PresentationModeController | |
208 | |
209 @synthesize inPresentationMode = inPresentationMode_; | |
210 @synthesize slidingStyle = slidingStyle_; | |
211 @synthesize toolbarFraction = toolbarFraction_; | |
212 | |
213 - (id)initWithBrowserController:(BrowserWindowController*)controller | |
214 style:(fullscreen_mac::SlidingStyle)style { | |
215 if ((self = [super init])) { | |
216 browserController_ = controller; | |
217 systemFullscreenMode_ = base::mac::kFullScreenModeNormal; | |
218 slidingStyle_ = style; | |
219 } | |
220 | |
221 // Let the world know what we're up to. | |
222 [[NSNotificationCenter defaultCenter] | |
223 postNotificationName:kWillEnterFullscreenNotification | |
224 object:nil]; | |
225 | |
226 // Install the Carbon event handler for the menubar show, hide and | |
227 // undocumented reveal event. | |
228 EventTypeSpec eventSpecs[3]; | |
229 | |
230 eventSpecs[0].eventClass = kEventClassMenu; | |
231 eventSpecs[0].eventKind = kMenuBarRevealEventKind; | |
232 | |
233 eventSpecs[1].eventClass = kEventClassMenu; | |
234 eventSpecs[1].eventKind = kEventMenuBarShown; | |
235 | |
236 eventSpecs[2].eventClass = kEventClassMenu; | |
237 eventSpecs[2].eventKind = kEventMenuBarHidden; | |
238 | |
239 InstallApplicationEventHandler(NewEventHandlerUPP(&MenuBarRevealHandler), 3, | |
240 eventSpecs, self, &menuBarTrackingHandler_); | |
241 | |
242 return self; | |
243 } | |
244 | |
245 - (void)dealloc { | |
246 RemoveEventHandler(menuBarTrackingHandler_); | |
247 DCHECK(!inPresentationMode_); | |
248 DCHECK(!trackingArea_); | |
249 [super dealloc]; | |
250 } | |
251 | |
252 - (void)enterPresentationModeForContentView:(NSView*)contentView | |
253 showDropdown:(BOOL)showDropdown { | |
254 DCHECK(!inPresentationMode_); | |
255 enteringPresentationMode_ = YES; | |
256 inPresentationMode_ = YES; | |
257 contentView_ = contentView; | |
258 [self changeToolbarFraction:(showDropdown ? 1 : 0)]; | |
259 [self updateMenuBarAndDockVisibility]; | |
260 | |
261 // Register for notifications. Self is removed as an observer in |-cleanup|. | |
262 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; | |
263 NSWindow* window = [browserController_ window]; | |
264 | |
265 [nc addObserver:self | |
266 selector:@selector(windowDidBecomeMain:) | |
267 name:NSWindowDidBecomeMainNotification | |
268 object:window]; | |
269 | |
270 [nc addObserver:self | |
271 selector:@selector(windowDidResignMain:) | |
272 name:NSWindowDidResignMainNotification | |
273 object:window]; | |
274 | |
275 enteringPresentationMode_ = NO; | |
276 } | |
277 | |
278 - (void)exitPresentationMode { | |
279 [[NSNotificationCenter defaultCenter] | |
280 postNotificationName:kWillLeaveFullscreenNotification | |
281 object:nil]; | |
282 DCHECK(inPresentationMode_); | |
283 inPresentationMode_ = NO; | |
284 | |
285 [self cleanup]; | |
286 } | |
287 | |
288 - (void)windowDidChangeScreen:(NSNotification*)notification { | |
289 [browserController_ resizeFullscreenWindow]; | |
290 } | |
291 | |
292 - (void)windowDidMove:(NSNotification*)notification { | |
293 [browserController_ resizeFullscreenWindow]; | |
294 } | |
295 | |
296 - (void)windowDidBecomeMain:(NSNotification*)notification { | |
297 [self showActiveWindowUI]; | |
298 } | |
299 | |
300 - (void)windowDidResignMain:(NSNotification*)notification { | |
301 [self hideActiveWindowUI]; | |
302 } | |
303 | |
304 // On OSX 10.8+, the menu bar shows on the secondary screen in fullscreen. | |
305 - (CGFloat)floatingBarVerticalOffset { | |
306 return kFloatingBarVerticalOffset; | |
307 } | |
308 | |
309 - (void)overlayFrameChanged:(NSRect)frame { | |
310 if (!inPresentationMode_) | |
311 return; | |
312 | |
313 // Make sure |trackingAreaBounds_| always reflects either the tracking area or | |
314 // the desired tracking area. | |
315 trackingAreaBounds_ = frame; | |
316 // The tracking area should always be at least the height of activation zone. | |
317 NSRect contentBounds = [contentView_ bounds]; | |
318 trackingAreaBounds_.origin.y = | |
319 std::min(trackingAreaBounds_.origin.y, | |
320 NSMaxY(contentBounds) - kDropdownActivationZoneHeight); | |
321 trackingAreaBounds_.size.height = | |
322 NSMaxY(contentBounds) - trackingAreaBounds_.origin.y + 1; | |
323 | |
324 // If an animation is currently running, do not set up a tracking area now. | |
325 // Instead, leave it to be created it in |-animationDidEnd:|. | |
326 if (currentAnimation_) | |
327 return; | |
328 | |
329 // If this is part of the initial setup, lock bar visibility if the mouse is | |
330 // within the tracking area bounds. | |
331 if (enteringPresentationMode_ && [self mouseInsideTrackingRect]) | |
332 [browserController_ lockBarVisibilityForOwner:self | |
333 withAnimation:NO | |
334 delay:NO]; | |
335 [self setupTrackingArea]; | |
336 } | |
337 | |
338 - (void)ensureOverlayShownWithAnimation:(BOOL)animate delay:(BOOL)delay { | |
339 if (!inPresentationMode_) | |
340 return; | |
341 | |
342 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) | |
343 return; | |
344 | |
345 if (self.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) | |
346 return; | |
347 | |
348 if (animate) { | |
349 if (delay) { | |
350 [self startShowTimer]; | |
351 } else { | |
352 [self cancelAllTimers]; | |
353 [self changeOverlayToFraction:1 withAnimation:YES]; | |
354 } | |
355 } else { | |
356 DCHECK(!delay); | |
357 [self cancelAllTimers]; | |
358 [self changeOverlayToFraction:1 withAnimation:NO]; | |
359 } | |
360 } | |
361 | |
362 - (void)ensureOverlayHiddenWithAnimation:(BOOL)animate delay:(BOOL)delay { | |
363 if (!inPresentationMode_) | |
364 return; | |
365 | |
366 if (self.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) | |
367 return; | |
368 | |
369 if (animate) { | |
370 if (delay) { | |
371 [self startHideTimer]; | |
372 } else { | |
373 [self cancelAllTimers]; | |
374 [self changeOverlayToFraction:0 withAnimation:YES]; | |
375 } | |
376 } else { | |
377 DCHECK(!delay); | |
378 [self cancelAllTimers]; | |
379 [self changeOverlayToFraction:0 withAnimation:NO]; | |
380 } | |
381 } | |
382 | |
383 - (void)cancelAnimationAndTimers { | |
384 [self cancelAllTimers]; | |
385 [currentAnimation_ stopAnimation]; | |
386 currentAnimation_.reset(); | |
387 } | |
388 | |
389 - (void)revealToolbarForTabStripChanges { | |
390 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | |
391 switches::kEnableFullscreenToolbarReveal)) { | |
392 return; | |
393 } | |
394 | |
395 revealToolbarForTabStripChanges_ = YES; | |
396 [self ensureOverlayShownWithAnimation:YES delay:NO]; | |
397 } | |
398 | |
399 - (void)setSystemFullscreenModeTo:(base::mac::FullScreenMode)mode { | |
400 if (mode == systemFullscreenMode_) | |
401 return; | |
402 if (systemFullscreenMode_ == base::mac::kFullScreenModeNormal) | |
403 base::mac::RequestFullScreen(mode); | |
404 else if (mode == base::mac::kFullScreenModeNormal) | |
405 base::mac::ReleaseFullScreen(systemFullscreenMode_); | |
406 else | |
407 base::mac::SwitchFullScreenModes(systemFullscreenMode_, mode); | |
408 systemFullscreenMode_ = mode; | |
409 } | |
410 | |
411 - (void)changeToolbarFraction:(CGFloat)fraction { | |
412 toolbarFraction_ = fraction; | |
413 [browserController_ layoutSubviews]; | |
414 | |
415 // In AppKit fullscreen, moving the mouse to the top of the screen toggles | |
416 // menu visibility. Replicate the same effect for immersive fullscreen. | |
417 if ([browserController_ isInImmersiveFullscreen]) | |
418 [self updateMenuBarAndDockVisibility]; | |
419 } | |
420 | |
421 // This method works, but is fragile. | |
422 // | |
423 // It gets used during view layout, which sometimes needs to be done at the | |
424 // beginning of an animation. As such, this method needs to reflect the | |
425 // menubarOffset expected at the end of the animation. This information is not | |
426 // readily available. (The layout logic needs a refactor). | |
427 // | |
428 // For AppKit Fullscreen, the menubar always starts hidden, and | |
429 // menubarFraction_ always starts at 0, so the logic happens to work. For | |
430 // Immersive Fullscreen, this class controls the visibility of the menu bar, so | |
431 // the logic is correct and not fragile. | |
432 - (CGFloat)menubarOffset { | |
433 if ([browserController_ isInAppKitFullscreen]) | |
434 return -std::floor(menubarFraction_ * [self floatingBarVerticalOffset]); | |
435 | |
436 return [self shouldShowMenubarInImmersiveFullscreen] | |
437 ? -[self floatingBarVerticalOffset] | |
438 : 0; | |
439 } | |
440 | |
441 - (BOOL)isFullscreenTransitionInProgress { | |
442 return [browserController_ isFullscreenTransitionInProgress]; | |
443 } | |
444 | |
445 - (BOOL)isMainWindow { | |
446 return [browserController_ window].isMainWindow; | |
447 } | |
448 | |
449 // Used to activate the floating bar in presentation mode. | |
450 - (void)mouseEntered:(NSEvent*)event { | |
451 DCHECK(inPresentationMode_); | |
452 | |
453 // Having gotten a mouse entered, we no longer need to do exit checks. | |
454 [self cancelMouseExitCheck]; | |
455 | |
456 NSTrackingArea* trackingArea = [event trackingArea]; | |
457 if (trackingArea == trackingArea_) { | |
458 // The tracking area shouldn't be active during animation. | |
459 DCHECK(!currentAnimation_); | |
460 | |
461 // Don't show anything if the style is set to OMNIBOX_TABS_NONE. | |
462 if (self.slidingStyle != fullscreen_mac::OMNIBOX_TABS_NONE) | |
463 [self scheduleShowForMouse]; | |
464 } | |
465 } | |
466 | |
467 // Used to deactivate the floating bar in presentation mode. | |
468 - (void)mouseExited:(NSEvent*)event { | |
469 DCHECK(inPresentationMode_); | |
470 | |
471 NSTrackingArea* trackingArea = [event trackingArea]; | |
472 if (trackingArea == trackingArea_) { | |
473 // The tracking area shouldn't be active during animation. | |
474 DCHECK(!currentAnimation_); | |
475 | |
476 // We can get a false mouse exit when the menu slides down, so if the mouse | |
477 // is still actually over the tracking area, we ignore the mouse exit, but | |
478 // we set up to check the mouse position again after a delay. | |
479 if ([self mouseInsideTrackingRect]) { | |
480 [self setupMouseExitCheck]; | |
481 return; | |
482 } | |
483 | |
484 if (self.slidingStyle != fullscreen_mac::OMNIBOX_TABS_NONE) | |
485 [self scheduleHideForMouse]; | |
486 } | |
487 } | |
488 | |
489 - (void)animationDidStop:(NSAnimation*)animation { | |
490 // Reset the |currentAnimation_| pointer now that the animation is over. | |
491 currentAnimation_.reset(); | |
492 | |
493 // Invariant says that the tracking area is not installed while animations are | |
494 // in progress. Ensure this is true. | |
495 DCHECK(!trackingArea_); | |
496 [self removeTrackingAreaIfNecessary]; // For paranoia. | |
497 | |
498 // Don't automatically set up a new tracking area. When explicitly stopped, | |
499 // either another animation is going to start immediately or the state will be | |
500 // changed immediately. | |
501 if (revealToolbarForTabStripChanges_) { | |
502 if (toolbarFraction_ > 0.0) { | |
503 // Set the timer to hide the toolbar. | |
504 [hideTimer_ invalidate]; | |
505 hideTimer_.reset([[NSTimer | |
506 scheduledTimerWithTimeInterval:kDropdownForTabStripChangesDuration | |
507 target:self | |
508 selector:@selector(hideTimerFire:) | |
509 userInfo:nil | |
510 repeats:NO] retain]); | |
511 } else { | |
512 revealToolbarForTabStripChanges_ = NO; | |
513 } | |
514 } | |
515 } | |
516 | |
517 - (void)animationDidEnd:(NSAnimation*)animation { | |
518 [self animationDidStop:animation]; | |
519 | |
520 // |trackingAreaBounds_| contains the correct tracking area bounds, including | |
521 // |any updates that may have come while the animation was running. Install a | |
522 // new tracking area with these bounds. | |
523 [self setupTrackingArea]; | |
524 | |
525 // TODO(viettrungluu): Better would be to check during the animation; doing it | |
526 // here means that the timing is slightly off. | |
527 if (![self mouseInsideTrackingRect]) | |
528 [self scheduleHideForMouse]; | |
529 } | |
530 | |
531 - (void)setMenuBarRevealProgress:(CGFloat)progress { | |
532 menubarFraction_ = progress; | |
533 | |
534 // If an animation is not running, then -layoutSubviews will not be called | |
535 // for each tick of the menu bar reveal. Do that manually. | |
536 // TODO(erikchen): The animation is janky. layoutSubviews need a refactor so | |
537 // that it calls setFrameOffset: instead of setFrame: if the frame's size has | |
538 // not changed. | |
539 if (!currentAnimation_.get()) | |
540 [browserController_ layoutSubviews]; | |
541 } | |
542 | |
543 @end | |
544 | |
545 | |
546 @implementation PresentationModeController (PrivateMethods) | |
547 | |
548 - (void)updateMenuBarAndDockVisibility { | |
549 if (![self isMainWindow] || ![browserController_ isInImmersiveFullscreen]) { | |
550 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal]; | |
551 return; | |
552 } | |
553 | |
554 // The screen does not have a menu bar, so there's no need to hide it. | |
555 if (![self doesScreenHaveMenuBar]) { | |
556 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeHideDock]; | |
557 return; | |
558 } | |
559 | |
560 [self setSystemFullscreenModeTo:[self desiredSystemFullscreenMode]]; | |
561 } | |
562 | |
563 - (BOOL)doesScreenHaveMenuBar { | |
564 if (![[NSScreen class] | |
565 respondsToSelector:@selector(screensHaveSeparateSpaces)]) | |
566 return [self isWindowOnPrimaryScreen]; | |
567 | |
568 BOOL eachScreenShouldHaveMenuBar = [NSScreen screensHaveSeparateSpaces]; | |
569 return eachScreenShouldHaveMenuBar ?: [self isWindowOnPrimaryScreen]; | |
570 } | |
571 | |
572 - (BOOL)isWindowOnPrimaryScreen { | |
573 NSScreen* screen = [[browserController_ window] screen]; | |
574 NSScreen* primaryScreen = [[NSScreen screens] firstObject]; | |
575 return (screen == primaryScreen); | |
576 } | |
577 | |
578 - (base::mac::FullScreenMode)desiredSystemFullscreenMode { | |
579 if ([self shouldShowMenubarInImmersiveFullscreen]) | |
580 return base::mac::kFullScreenModeHideDock; | |
581 return base::mac::kFullScreenModeHideAll; | |
582 } | |
583 | |
584 - (void)changeOverlayToFraction:(CGFloat)fraction | |
585 withAnimation:(BOOL)animate { | |
586 // The non-animated case is really simple, so do it and return. | |
587 if (!animate) { | |
588 [currentAnimation_ stopAnimation]; | |
589 [self changeToolbarFraction:fraction]; | |
590 return; | |
591 } | |
592 | |
593 // If we're already animating to the given fraction, then there's nothing more | |
594 // to do. | |
595 if (currentAnimation_ && [currentAnimation_ endFraction] == fraction) | |
596 return; | |
597 | |
598 // In all other cases, we want to cancel any running animation (which may be | |
599 // to show or to hide). | |
600 [currentAnimation_ stopAnimation]; | |
601 | |
602 // Create the animation and set it up. | |
603 currentAnimation_.reset( | |
604 [[DropdownAnimation alloc] initWithFraction:fraction | |
605 fullDuration:kDropdownAnimationDuration | |
606 animationCurve:NSAnimationEaseOut | |
607 controller:self]); | |
608 DCHECK(currentAnimation_); | |
609 [currentAnimation_ setAnimationBlockingMode:NSAnimationNonblocking]; | |
610 [currentAnimation_ setDelegate:self]; | |
611 | |
612 // If there is an existing tracking area, remove it. We do not track mouse | |
613 // movements during animations (see class comment in the header file). | |
614 [self removeTrackingAreaIfNecessary]; | |
615 | |
616 [currentAnimation_ startAnimation]; | |
617 } | |
618 | |
619 - (void)scheduleShowForMouse { | |
620 [browserController_ lockBarVisibilityForOwner:self | |
621 withAnimation:YES | |
622 delay:YES]; | |
623 } | |
624 | |
625 - (void)scheduleHideForMouse { | |
626 [browserController_ releaseBarVisibilityForOwner:self | |
627 withAnimation:YES | |
628 delay:YES]; | |
629 } | |
630 | |
631 - (void)setupTrackingArea { | |
632 if (trackingArea_) { | |
633 // If the tracking rectangle is already |trackingAreaBounds_|, quit early. | |
634 NSRect oldRect = [trackingArea_ rect]; | |
635 if (NSEqualRects(trackingAreaBounds_, oldRect)) | |
636 return; | |
637 | |
638 // Otherwise, remove it. | |
639 [self removeTrackingAreaIfNecessary]; | |
640 } | |
641 | |
642 // Create and add a new tracking area for |frame|. | |
643 trackingArea_.reset( | |
644 [[NSTrackingArea alloc] initWithRect:trackingAreaBounds_ | |
645 options:NSTrackingMouseEnteredAndExited | | |
646 NSTrackingActiveInKeyWindow | |
647 owner:self | |
648 userInfo:nil]); | |
649 DCHECK(contentView_); | |
650 [contentView_ addTrackingArea:trackingArea_]; | |
651 } | |
652 | |
653 - (void)removeTrackingAreaIfNecessary { | |
654 if (trackingArea_) { | |
655 DCHECK(contentView_); // |contentView_| better be valid. | |
656 [contentView_ removeTrackingArea:trackingArea_]; | |
657 trackingArea_.reset(); | |
658 } | |
659 } | |
660 | |
661 - (BOOL)mouseInsideTrackingRect { | |
662 NSWindow* window = [browserController_ window]; | |
663 NSPoint mouseLoc = [window mouseLocationOutsideOfEventStream]; | |
664 NSPoint mousePos = [contentView_ convertPoint:mouseLoc fromView:nil]; | |
665 return NSMouseInRect(mousePos, trackingAreaBounds_, [contentView_ isFlipped]); | |
666 } | |
667 | |
668 - (void)setupMouseExitCheck { | |
669 [self performSelector:@selector(checkForMouseExit) | |
670 withObject:nil | |
671 afterDelay:kMouseExitCheckDelay]; | |
672 } | |
673 | |
674 - (void)cancelMouseExitCheck { | |
675 [NSObject cancelPreviousPerformRequestsWithTarget:self | |
676 selector:@selector(checkForMouseExit) object:nil]; | |
677 } | |
678 | |
679 - (void)checkForMouseExit { | |
680 if ([self mouseInsideTrackingRect]) | |
681 [self setupMouseExitCheck]; | |
682 else | |
683 [self scheduleHideForMouse]; | |
684 } | |
685 | |
686 - (void)startShowTimer { | |
687 // If there's already a show timer going, just keep it. | |
688 if (showTimer_) { | |
689 DCHECK([showTimer_ isValid]); | |
690 DCHECK(!hideTimer_); | |
691 return; | |
692 } | |
693 | |
694 // Cancel the hide timer (if necessary) and set up the new show timer. | |
695 [self cancelHideTimer]; | |
696 showTimer_.reset( | |
697 [[NSTimer scheduledTimerWithTimeInterval:kDropdownShowDelay | |
698 target:self | |
699 selector:@selector(showTimerFire:) | |
700 userInfo:nil | |
701 repeats:NO] retain]); | |
702 DCHECK([showTimer_ isValid]); // This also checks that |showTimer_ != nil|. | |
703 } | |
704 | |
705 - (void)startHideTimer { | |
706 // If there's already a hide timer going, just keep it. | |
707 if (hideTimer_) { | |
708 DCHECK([hideTimer_ isValid]); | |
709 DCHECK(!showTimer_); | |
710 return; | |
711 } | |
712 | |
713 // Cancel the show timer (if necessary) and set up the new hide timer. | |
714 [self cancelShowTimer]; | |
715 hideTimer_.reset( | |
716 [[NSTimer scheduledTimerWithTimeInterval:kDropdownHideDelay | |
717 target:self | |
718 selector:@selector(hideTimerFire:) | |
719 userInfo:nil | |
720 repeats:NO] retain]); | |
721 DCHECK([hideTimer_ isValid]); // This also checks that |hideTimer_ != nil|. | |
722 } | |
723 | |
724 - (void)cancelShowTimer { | |
725 [showTimer_ invalidate]; | |
726 showTimer_.reset(); | |
727 } | |
728 | |
729 - (void)cancelHideTimer { | |
730 [hideTimer_ invalidate]; | |
731 hideTimer_.reset(); | |
732 } | |
733 | |
734 - (void)cancelAllTimers { | |
735 [self cancelShowTimer]; | |
736 [self cancelHideTimer]; | |
737 } | |
738 | |
739 - (void)showTimerFire:(NSTimer*)timer { | |
740 DCHECK_EQ(showTimer_, timer); // This better be our show timer. | |
741 [showTimer_ invalidate]; // Make sure it doesn't repeat. | |
742 showTimer_.reset(); // And get rid of it. | |
743 [self changeOverlayToFraction:1 withAnimation:YES]; | |
744 } | |
745 | |
746 - (void)hideTimerFire:(NSTimer*)timer { | |
747 DCHECK_EQ(hideTimer_, timer); // This better be our hide timer. | |
748 [hideTimer_ invalidate]; // Make sure it doesn't repeat. | |
749 hideTimer_.reset(); // And get rid of it. | |
750 [self changeOverlayToFraction:0 withAnimation:YES]; | |
751 } | |
752 | |
753 - (void)cleanup { | |
754 [self cancelMouseExitCheck]; | |
755 [self cancelAnimationAndTimers]; | |
756 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
757 | |
758 [self removeTrackingAreaIfNecessary]; | |
759 contentView_ = nil; | |
760 | |
761 // This isn't tracked when not in presentation mode. | |
762 [browserController_ releaseBarVisibilityForOwner:self | |
763 withAnimation:NO | |
764 delay:NO]; | |
765 | |
766 // Call the main status resignation code to perform the associated cleanup, | |
767 // since we will no longer be receiving actual status resignation | |
768 // notifications. | |
769 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal]; | |
770 | |
771 // No more calls back up to the BWC. | |
772 browserController_ = nil; | |
773 } | |
774 | |
775 - (void)showActiveWindowUI { | |
776 [self updateMenuBarAndDockVisibility]; | |
777 | |
778 // TODO(rohitrao): Insert the Exit Fullscreen button. http://crbug.com/35956 | |
779 } | |
780 | |
781 - (void)hideActiveWindowUI { | |
782 [self updateMenuBarAndDockVisibility]; | |
783 | |
784 // TODO(rohitrao): Remove the Exit Fullscreen button. http://crbug.com/35956 | |
785 } | |
786 | |
787 - (BOOL)shouldShowMenubarInImmersiveFullscreen { | |
788 return [self doesScreenHaveMenuBar] && toolbarFraction_ > 0.99; | |
789 } | |
790 | |
791 @end | |
OLD | NEW |