blob: 426c52e89052655ec1f2b03bebd229f47b620d67 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/fullscreen/immersive_fullscreen_controller.h"
#import "base/mac/mac_util.h"
#include "base/mac/sdk_forward_declarations.h"
#import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller.h"
#import "ui/base/cocoa/tracking_area.h"
namespace {
// The height from the top of the screen that will show the menubar.
const CGFloat kMenubarShowZoneHeight = 4;
// The height from the top of the screen that will hide the menubar.
// The value must be greater than the menubar's height of 22px.
const CGFloat kMenubarHideZoneHeight = 28;
} // namespace
@interface ImmersiveFullscreenController () {
id<FullscreenToolbarContextDelegate> delegate_; // weak
// Used to track the mouse movements to show/hide the menu.
base::scoped_nsobject<CrTrackingArea> trackingArea_;
// The content view for the window.
NSView* contentView_; // weak
// Tracks the currently requested system fullscreen mode, used to show or
// hide the menubar. Its value is as follows:
// + |kFullScreenModeNormal| - when the window is not main or not fullscreen,
// + |kFullScreenModeHideDock| - when the user interacts with the top of the
// screen
// + |kFullScreenModeHideAll| - when the conditions don't meet the first two
// modes.
base::mac::FullScreenMode systemFullscreenMode_;
// True if the menubar should be shown on the screen.
BOOL isMenubarVisible_;
}
// Whether the current screen is expected to have a menubar, regardless of
// current visibility of the menubar.
- (BOOL)doesScreenHaveMenubar;
// Adjusts the AppKit Fullscreen options of the application.
- (void)setSystemFullscreenModeTo:(base::mac::FullScreenMode)mode;
// Sets |isMenubarVisible_|. If the value has changed, update the tracking
// area, the dock, and the menubar
- (void)setMenubarVisibility:(BOOL)visible;
// Methods that update and remove the tracking area.
- (void)updateTrackingArea;
- (void)removeTrackingArea;
@end
@implementation ImmersiveFullscreenController
- (instancetype)initWithDelegate:
(id<FullscreenToolbarContextDelegate>)delegate {
if ((self = [super init])) {
delegate_ = delegate;
systemFullscreenMode_ = base::mac::kFullScreenModeNormal;
contentView_ = [[delegate_ window] contentView];
DCHECK(contentView_);
isMenubarVisible_ = NO;
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
NSWindow* window = [delegate_ window];
[nc addObserver:self
selector:@selector(windowDidBecomeMain:)
name:NSWindowDidBecomeMainNotification
object:window];
[nc addObserver:self
selector:@selector(windowDidResignMain:)
name:NSWindowDidResignMainNotification
object:window];
[self updateTrackingArea];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self removeTrackingArea];
[self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal];
[super dealloc];
}
- (void)updateMenuBarAndDockVisibility {
BOOL isMouseOnScreen = NSMouseInRect(
[NSEvent mouseLocation], [[delegate_ window] screen].frame, false);
if (!isMouseOnScreen || ![delegate_ isInImmersiveFullscreen])
[self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal];
else if ([self shouldShowMenubar])
[self setSystemFullscreenModeTo:base::mac::kFullScreenModeHideDock];
else
[self setSystemFullscreenModeTo:base::mac::kFullScreenModeHideAll];
}
- (BOOL)shouldShowMenubar {
return [self doesScreenHaveMenubar] && isMenubarVisible_;
}
- (void)windowDidBecomeMain:(NSNotification*)notification {
[self updateMenuBarAndDockVisibility];
}
- (void)windowDidResignMain:(NSNotification*)notification {
[self updateMenuBarAndDockVisibility];
}
- (BOOL)doesScreenHaveMenubar {
NSScreen* screen = [[delegate_ window] screen];
NSScreen* primaryScreen = [[NSScreen screens] firstObject];
BOOL isWindowOnPrimaryScreen = [screen isEqual:primaryScreen];
BOOL eachScreenShouldHaveMenuBar = [NSScreen screensHaveSeparateSpaces];
return eachScreenShouldHaveMenuBar ?: isWindowOnPrimaryScreen;
}
- (void)setSystemFullscreenModeTo:(base::mac::FullScreenMode)mode {
if (mode == systemFullscreenMode_)
return;
if (systemFullscreenMode_ == base::mac::kFullScreenModeNormal)
base::mac::RequestFullScreen(mode);
else if (mode == base::mac::kFullScreenModeNormal)
base::mac::ReleaseFullScreen(systemFullscreenMode_);
else
base::mac::SwitchFullScreenModes(systemFullscreenMode_, mode);
systemFullscreenMode_ = mode;
}
- (void)setMenubarVisibility:(BOOL)visible {
if (isMenubarVisible_ == visible)
return;
isMenubarVisible_ = visible;
[self updateTrackingArea];
[self updateMenuBarAndDockVisibility];
}
- (void)updateTrackingArea {
[self removeTrackingArea];
CGFloat trackingHeight =
isMenubarVisible_ ? kMenubarHideZoneHeight : kMenubarShowZoneHeight;
NSRect trackingFrame = [contentView_ bounds];
trackingFrame.origin.y = NSMaxY(trackingFrame) - trackingHeight;
trackingFrame.size.height = trackingHeight;
// If we replace the tracking area with a new one under the cursor, the new
// tracking area might not receive a |-mouseEntered:| or |-mouseExited| call.
// As a result, we should also track the mouse's movements so that the
// so the menubar won't get stuck.
NSTrackingAreaOptions options =
NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
if (isMenubarVisible_)
options |= NSTrackingMouseMoved;
// Create and add a new tracking area for |frame|.
trackingArea_.reset([[CrTrackingArea alloc] initWithRect:trackingFrame
options:options
owner:self
userInfo:nil]);
[contentView_ addTrackingArea:trackingArea_];
}
- (void)removeTrackingArea {
if (trackingArea_) {
[contentView_ removeTrackingArea:trackingArea_];
trackingArea_.reset();
}
}
- (void)mouseEntered:(NSEvent*)event {
[self setMenubarVisibility:YES];
}
- (void)mouseExited:(NSEvent*)event {
[self setMenubarVisibility:NO];
}
- (void)mouseMoved:(NSEvent*)event {
[self setMenubarVisibility:[trackingArea_
mouseInsideTrackingAreaForView:contentView_]];
}
@end