diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index b5271e4d43..e3c5525b84 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -127,6 +127,7 @@ class NativeWindowMac : public NativeWindow, NORMAL, HIDDEN, HIDDEN_INSET, + CUSTOM_BUTTONS_ON_HOVER, }; TitleBarStyle title_bar_style() const { return title_bar_style_; } diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index e5192c8b62..e4efaa477b 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -46,6 +46,107 @@ bool ScopedDisableResize::disable_resize_ = false; } // namespace +// This view encapsuates the Quit, Minimize and Full Screen buttons. It is being +// used for frameless windows. +@interface SemaphoreView : NSView { + @private + BOOL mouse_inside_; +} +@end + +@implementation SemaphoreView + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + mouse_inside_ = NO; + + // create buttons + NSButton* closeButton = [NSWindow standardWindowButton:NSWindowCloseButton + forStyleMask:NSTitledWindowMask]; + NSButton* minitButton = [NSWindow standardWindowButton:NSWindowMiniaturizeButton + forStyleMask:NSTitledWindowMask]; + NSButton* fullScreenButton = [NSWindow standardWindowButton:NSWindowZoomButton + forStyleMask:NSTitledWindowMask]; + + // size view for buttons + const int top = 3; + const int bottom = 3; + const int left = 7; + const int between = 6; + const int right = 6; + + auto buttonsSize = NSMakeRect(0, + 0, + left + closeButton.frame.size.width + between + minitButton.frame.size.width + between + fullScreenButton.frame.size.width + right, + top + closeButton.frame.size.height + bottom); + [self setFrame:buttonsSize]; + + //set their location + [closeButton setFrame:NSMakeRect(left, + buttonsSize.size.height - closeButton.frame.size.height - top, + closeButton.frame.size.width, + closeButton.frame.size.height)]; + [minitButton setFrame:NSMakeRect( + left + closeButton.frame.size.width + between, + buttonsSize.size.height - minitButton.frame.size.height - top, + minitButton.frame.size.width, + minitButton.frame.size.height)]; + [fullScreenButton setFrame:NSMakeRect( + left + closeButton.frame.size.width + between + minitButton.frame.size.width + between, + buttonsSize.size.height - fullScreenButton.frame.size.height - top, + fullScreenButton.frame.size.width, + fullScreenButton.frame.size.height)]; + + //add buttons to the window + [self addSubview:closeButton]; + [self addSubview:minitButton]; + [self addSubview:fullScreenButton]; + + // stay in upper left corner + [self setAutoresizingMask: NSViewMaxXMargin | NSViewMinYMargin]; + + // refresh for initial conditions + [self setNeedsDisplayForButtons]; + } + + return self; +} + +- (BOOL)_mouseInGroup:(NSButton*)button { + return mouse_inside_; +} + +- (void)updateTrackingAreas { + auto trackingArea = [[[NSTrackingArea alloc] initWithRect:NSZeroRect + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect + owner:self + userInfo:nil] autorelease]; + [self addTrackingArea:trackingArea]; +} + +- (void)mouseEntered:(NSEvent*)event { + [super mouseEntered:event]; + mouse_inside_ = YES; + [self setNeedsDisplayForButtons]; +} + +- (void)mouseExited:(NSEvent*)event { + [super mouseExited:event]; + mouse_inside_ = NO; + [self setNeedsDisplayForButtons]; +} + +- (void)setNeedsDisplayForButtons { + for (NSView* subview in self.subviews) { + [subview setHidden:!mouse_inside_]; + [subview setNeedsDisplay:YES]; + } +} + +@end + // This view always takes the size of its superview. It is intended to be used // as a NSWindow's contentView. It is needed because NSWindow's implementation // explicitly resizes the contentView at inopportune times. @@ -299,7 +400,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (BOOL)windowShouldClose:(id)window { // When user tries to close the window by clicking the close button, we do // not close the window immediately, instead we try to close the web page - // fisrt, and when the web page is closed the window will also be closed. + // first, and when the web page is closed the window will also be closed. shell_->RequestToClosePage(); return NO; } @@ -596,6 +697,22 @@ enum { [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; } +// Custom window button methods + +- (void)performClose:(id)sender { + if (shell_->title_bar_style() == atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER) + [[self delegate] windowShouldClose:self]; + else + [super performClose:sender]; +} + +- (void)performMiniaturize:(id)sender { + if (shell_->title_bar_style() == atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER) + [self miniaturize:self]; + else + [super performMiniaturize:sender]; +} + @end @interface ControlRegionView : NSView @@ -661,9 +778,11 @@ struct Converter { return false; if (title_bar_style == "hidden") { *out = atom::NativeWindowMac::HIDDEN; - } else if (title_bar_style == "hidden-inset" || // Deprecate this after 2.0 + } else if (title_bar_style == "hidden-inset" || // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings title_bar_style == "hiddenInset") { *out = atom::NativeWindowMac::HIDDEN_INSET; + } else if (title_bar_style == "customButtonsOnHover") { + *out = atom::NativeWindowMac::CUSTOM_BUTTONS_ON_HOVER; } else { return false; } @@ -726,6 +845,11 @@ NativeWindowMac::NativeWindowMac( } NSUInteger styleMask = NSTitledWindowMask; + if (title_bar_style_ == CUSTOM_BUTTONS_ON_HOVER && + base::mac::IsAtLeastOS10_10() && + (!useStandardWindow || transparent() || !has_frame())) { + styleMask = NSFullSizeContentViewWindowMask; + } if (minimizable) { styleMask |= NSMiniaturizableWindowMask; } @@ -780,6 +904,9 @@ NativeWindowMac::NativeWindowMac( if (transparent() || !has_frame()) { if (base::mac::IsAtLeastOS10_10()) { // Don't show title bar. + if (title_bar_style_ == CUSTOM_BUTTONS_ON_HOVER) { + [window_ setTitlebarAppearsTransparent:YES]; + } [window_ setTitleVisibility:NSWindowTitleHidden]; } // Remove non-transparent corners, see http://git.io/vfonD. @@ -1563,21 +1690,30 @@ void NativeWindowMac::InstallView() { // The fullscreen button should always be hidden for frameless window. [[window_ standardWindowButton:NSWindowFullScreenButton] setHidden:YES]; - if (title_bar_style_ != NORMAL) { - if (base::mac::IsOS10_9()) { - ShowWindowButton(NSWindowZoomButton); - ShowWindowButton(NSWindowMiniaturizeButton); - ShowWindowButton(NSWindowCloseButton); + if (title_bar_style_ == CUSTOM_BUTTONS_ON_HOVER) { + NSView* buttons = + [[[SemaphoreView alloc] initWithFrame:NSZeroRect] autorelease]; + buttons.frame = CGRectMake(0, + [content_view_ bounds].size.height - buttons.frame.size.height, + buttons.frame.size.width, + buttons.frame.size.height); + [content_view_ addSubview:buttons]; + } else { + if (title_bar_style_ != NORMAL) { + if (base::mac::IsOS10_9()) { + ShowWindowButton(NSWindowZoomButton); + ShowWindowButton(NSWindowMiniaturizeButton); + ShowWindowButton(NSWindowCloseButton); + } + return; } - return; + // Hide the window buttons. + [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; + [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; + [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; } - // Hide the window buttons. - [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; - [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; - [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; - // Some third-party macOS utilities check the zoom button's enabled state to // determine whether to show custom UI on hover, so we disable it here to // prevent them from doing so in a frameless app window. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index cb709c7a0c..69407ec2a7 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -191,14 +191,21 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. Default is `false`. * `type` String (optional) - The type of window, default is normal window. See more about this below. - * `titleBarStyle` String (optional) - The style of window title bar. Default is `default`. Possible values are: + * `titleBarStyle` String (optional) - The style of window title bar. + Default is `default`. Possible values are: * `default` - Results in the standard gray opaque Mac title bar. * `hidden` - Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls ("traffic lights") in the top left. - * `hidden-inset` - Results in a hidden title bar with an alternative look + * `hidden-inset` - Deprecated, use `hiddenInset` instead. + * `hiddenInset` - Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. + * `customButtonsOnHover` Boolean (optional) - Draw custom close, minimize, + and full screen buttons on macOS frameless windows. These buttons will not + display unless hovered over in the top left of the window. These custom + buttons prevent issues with mouse events that occur with the standard + window toolbar buttons. **Note:** This option is currently experimental. * `thickFrame` Boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on Windows, which adds standard window frame. Setting it to `false` will remove window shadow and window animations. Default is `true`. diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index 4f091ec6c3..1ae0293522 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -26,7 +26,7 @@ a chromeless window. Instead of setting `frame` to `false` which disables both the titlebar and window controls, you may want to have the title bar hidden and your content extend to the full window size, yet still preserve the window controls ("traffic lights") for standard window actions. -You can do so by specifying the new `titleBarStyle` option: +You can do so by specifying the `titleBarStyle` option: #### `hidden` @@ -38,13 +38,26 @@ let win = new BrowserWindow({titleBarStyle: 'hidden'}) win.show() ``` -#### `hidden-inset` +#### `hiddenInset` Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. ```javascript const {BrowserWindow} = require('electron') -let win = new BrowserWindow({titleBarStyle: 'hidden-inset'}) +let win = new BrowserWindow({titleBarStyle: 'hiddenInset'}) +win.show() +``` + +#### `customButtonsOnHover` + +Uses custom drawn close, miniaturize, and fullscreen buttons that display +when hovering in the top left of the window. These custom buttons prevent issues +with mouse events that occur with the standard window toolbar buttons. This +option is only applicable for frameless windows. + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({titleBarStyle: 'customButtonsOnHover', frame: false}) win.show() ``` diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 8f1b8732f8..914c22086a 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -129,7 +129,7 @@ let window app.once('ready', () => { window = new BrowserWindow({ frame: false, - titleBarStyle: 'hidden-inset', + titleBarStyle: 'hiddenInset', width: 200, height: 200, backgroundColor: '#000'