fix: move window buttons in-place on macOS (#30322)
This commit is contained in:
parent
1c29734c91
commit
97929eab5f
9 changed files with 334 additions and 278 deletions
|
@ -168,6 +168,7 @@ using FullScreenTransitionState =
|
|||
- (void)windowDidResize:(NSNotification*)notification {
|
||||
[super windowDidResize:notification];
|
||||
shell_->NotifyWindowResize();
|
||||
shell_->RedrawTrafficLights();
|
||||
}
|
||||
|
||||
- (void)windowWillMove:(NSNotification*)notification {
|
||||
|
|
66
shell/browser/ui/cocoa/window_buttons_proxy.h
Normal file
66
shell/browser/ui/cocoa/window_buttons_proxy.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
|
||||
#define SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
|
||||
@class WindowButtonsProxy;
|
||||
|
||||
// A helper view that floats above the window buttons.
|
||||
@interface ButtonsAreaHoverView : NSView {
|
||||
@private
|
||||
WindowButtonsProxy* proxy_;
|
||||
}
|
||||
- (id)initWithProxy:(WindowButtonsProxy*)proxy;
|
||||
@end
|
||||
|
||||
// Manipulating the window buttons.
|
||||
@interface WindowButtonsProxy : NSObject {
|
||||
@private
|
||||
NSWindow* window_;
|
||||
// The view that contains the window buttons and title.
|
||||
NSView* titlebar_container_;
|
||||
|
||||
// The window buttons.
|
||||
NSButton* left_;
|
||||
NSButton* right_;
|
||||
NSButton* middle_;
|
||||
|
||||
// Current left-top margin of buttons.
|
||||
gfx::Point margin_;
|
||||
// The default left-top margin.
|
||||
gfx::Point default_margin_;
|
||||
|
||||
// Track mouse moves above window buttons.
|
||||
BOOL show_on_hover_;
|
||||
BOOL mouse_inside_;
|
||||
base::scoped_nsobject<NSTrackingArea> tracking_area_;
|
||||
base::scoped_nsobject<ButtonsAreaHoverView> hover_view_;
|
||||
}
|
||||
|
||||
- (id)initWithWindow:(NSWindow*)window;
|
||||
|
||||
- (void)setVisible:(BOOL)visible;
|
||||
- (BOOL)isVisible;
|
||||
|
||||
// Only show window buttons when mouse moves above them.
|
||||
- (void)setShowOnHover:(BOOL)yes;
|
||||
|
||||
// Set left-top margin of the window buttons..
|
||||
- (void)setMargin:(const absl::optional<gfx::Point>&)margin;
|
||||
|
||||
// Return the bounds of all 3 buttons, with margin on all sides.
|
||||
- (NSRect)getButtonsContainerBounds;
|
||||
|
||||
- (void)redraw;
|
||||
- (void)updateTrackingAreas;
|
||||
@end
|
||||
|
||||
#endif // SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
|
200
shell/browser/ui/cocoa/window_buttons_proxy.mm
Normal file
200
shell/browser/ui/cocoa/window_buttons_proxy.mm
Normal file
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/window_buttons_proxy.h"
|
||||
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "base/notreached.h"
|
||||
|
||||
@implementation ButtonsAreaHoverView : NSView
|
||||
|
||||
- (id)initWithProxy:(WindowButtonsProxy*)proxy {
|
||||
if ((self = [super init])) {
|
||||
proxy_ = proxy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Ignore all mouse events.
|
||||
- (NSView*)hitTest:(NSPoint)aPoint {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
[proxy_ updateTrackingAreas];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation WindowButtonsProxy
|
||||
|
||||
- (id)initWithWindow:(NSWindow*)window {
|
||||
window_ = window;
|
||||
show_on_hover_ = NO;
|
||||
mouse_inside_ = NO;
|
||||
|
||||
// Save the sequence of the buttons for later computation.
|
||||
if (base::i18n::IsRTL()) {
|
||||
left_ = [window_ standardWindowButton:NSWindowZoomButton];
|
||||
right_ = [window_ standardWindowButton:NSWindowCloseButton];
|
||||
} else {
|
||||
left_ = [window_ standardWindowButton:NSWindowCloseButton];
|
||||
right_ = [window_ standardWindowButton:NSWindowZoomButton];
|
||||
}
|
||||
middle_ = [window_ standardWindowButton:NSWindowMiniaturizeButton];
|
||||
|
||||
// Safety check just in case Apple changes the view structure in a macOS
|
||||
// upgrade.
|
||||
if (!left_.superview || !left_.superview.superview) {
|
||||
NOTREACHED() << "macOS has changed its window buttons view structure.";
|
||||
titlebar_container_ = nullptr;
|
||||
return self;
|
||||
}
|
||||
titlebar_container_ = left_.superview.superview;
|
||||
|
||||
// Remember the default margin.
|
||||
margin_ = default_margin_ = [self getCurrentMargin];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (hover_view_)
|
||||
[hover_view_ removeFromSuperview];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)setVisible:(BOOL)visible {
|
||||
if (!titlebar_container_)
|
||||
return;
|
||||
[titlebar_container_ setHidden:!visible];
|
||||
}
|
||||
|
||||
- (BOOL)isVisible {
|
||||
if (!titlebar_container_)
|
||||
return YES;
|
||||
return ![titlebar_container_ isHidden];
|
||||
}
|
||||
|
||||
- (void)setShowOnHover:(BOOL)yes {
|
||||
if (!titlebar_container_)
|
||||
return;
|
||||
show_on_hover_ = yes;
|
||||
// Put a transparent view above the window buttons so we can track mouse
|
||||
// events when mouse enter/leave the window buttons.
|
||||
if (show_on_hover_) {
|
||||
hover_view_.reset([[ButtonsAreaHoverView alloc] initWithProxy:self]);
|
||||
[hover_view_ setFrame:[self getButtonsBounds]];
|
||||
[titlebar_container_ addSubview:hover_view_.get()];
|
||||
} else {
|
||||
[hover_view_ removeFromSuperview];
|
||||
hover_view_.reset();
|
||||
}
|
||||
[self updateButtonsVisibility];
|
||||
}
|
||||
|
||||
- (void)setMargin:(const absl::optional<gfx::Point>&)margin {
|
||||
if (margin)
|
||||
margin_ = *margin;
|
||||
else
|
||||
margin_ = default_margin_;
|
||||
[self redraw];
|
||||
}
|
||||
|
||||
- (NSRect)getButtonsContainerBounds {
|
||||
return NSInsetRect([self getButtonsBounds], -margin_.x(), -margin_.y());
|
||||
}
|
||||
|
||||
- (void)redraw {
|
||||
if (!titlebar_container_)
|
||||
return;
|
||||
|
||||
float button_width = NSWidth(left_.frame);
|
||||
float button_height = NSHeight(left_.frame);
|
||||
float padding = NSMinX(middle_.frame) - NSMaxX(left_.frame);
|
||||
float start;
|
||||
if (base::i18n::IsRTL())
|
||||
start =
|
||||
NSWidth(window_.frame) - 3 * button_width - 2 * padding - margin_.x();
|
||||
else
|
||||
start = margin_.x();
|
||||
|
||||
NSRect cbounds = titlebar_container_.frame;
|
||||
cbounds.size.height = button_height + 2 * margin_.y();
|
||||
cbounds.origin.y = NSHeight(window_.frame) - NSHeight(cbounds);
|
||||
[titlebar_container_ setFrame:cbounds];
|
||||
|
||||
[left_ setFrameOrigin:NSMakePoint(start, margin_.y())];
|
||||
start += button_width + padding;
|
||||
[middle_ setFrameOrigin:NSMakePoint(start, margin_.y())];
|
||||
start += button_width + padding;
|
||||
[right_ setFrameOrigin:NSMakePoint(start, margin_.y())];
|
||||
|
||||
if (hover_view_)
|
||||
[hover_view_ setFrame:[self getButtonsBounds]];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
if (tracking_area_)
|
||||
[hover_view_ removeTrackingArea:tracking_area_.get()];
|
||||
tracking_area_.reset([[NSTrackingArea alloc]
|
||||
initWithRect:NSZeroRect
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
|
||||
NSTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil]);
|
||||
[hover_view_ addTrackingArea:tracking_area_.get()];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent*)event {
|
||||
mouse_inside_ = YES;
|
||||
[self updateButtonsVisibility];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent*)event {
|
||||
mouse_inside_ = NO;
|
||||
[self updateButtonsVisibility];
|
||||
}
|
||||
|
||||
- (void)updateButtonsVisibility {
|
||||
NSArray* buttons = @[
|
||||
[window_ standardWindowButton:NSWindowCloseButton],
|
||||
[window_ standardWindowButton:NSWindowMiniaturizeButton],
|
||||
[window_ standardWindowButton:NSWindowZoomButton],
|
||||
];
|
||||
// Show buttons when mouse hovers above them.
|
||||
BOOL hidden = show_on_hover_ && !mouse_inside_;
|
||||
// Always show buttons under fullscreen.
|
||||
if ([window_ styleMask] & NSWindowStyleMaskFullScreen)
|
||||
hidden = NO;
|
||||
for (NSView* button in buttons) {
|
||||
[button setHidden:hidden];
|
||||
[button setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the bounds of all 3 buttons.
|
||||
- (NSRect)getButtonsBounds {
|
||||
return NSMakeRect(NSMinX(left_.frame), NSMinY(left_.frame),
|
||||
NSMaxX(right_.frame) - NSMinX(left_.frame),
|
||||
NSHeight(left_.frame));
|
||||
}
|
||||
|
||||
// Compute margin from position of current buttons.
|
||||
- (gfx::Point)getCurrentMargin {
|
||||
gfx::Point result;
|
||||
if (!titlebar_container_)
|
||||
return result;
|
||||
|
||||
result.set_y((NSHeight(titlebar_container_.frame) - NSHeight(left_.frame)) /
|
||||
2);
|
||||
|
||||
if (base::i18n::IsRTL())
|
||||
result.set_x(NSWidth(window_.frame) - NSMaxX(right_.frame));
|
||||
else
|
||||
result.set_x(NSMinX(left_.frame));
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_
|
||||
#define SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
|
||||
// Custom Quit, Minimize and Full Screen button container for frameless
|
||||
// windows.
|
||||
@interface WindowButtonsView : NSView {
|
||||
@private
|
||||
BOOL mouse_inside_;
|
||||
BOOL show_on_hover_;
|
||||
BOOL is_rtl_;
|
||||
gfx::Point margin_;
|
||||
base::scoped_nsobject<NSTrackingArea> tracking_area_;
|
||||
}
|
||||
|
||||
+ (gfx::Point)defaultMargin;
|
||||
+ (gfx::Point)hiddenInsetMargin;
|
||||
- (id)initWithMargin:(const absl::optional<gfx::Point>&)margin;
|
||||
- (void)setMargin:(const absl::optional<gfx::Point>&)margin;
|
||||
- (void)setShowOnHover:(BOOL)yes;
|
||||
- (void)setNeedsDisplayForButtons;
|
||||
- (gfx::Point)getMargin;
|
||||
@end
|
||||
|
||||
#endif // SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright (c) 2021 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/window_buttons_view.h"
|
||||
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "base/logging.h"
|
||||
#include "ui/gfx/mac/coordinate_conversion.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const CGFloat kButtonPadding = 20.;
|
||||
|
||||
const NSWindowButton kButtonTypes[] = {
|
||||
NSWindowCloseButton,
|
||||
NSWindowMiniaturizeButton,
|
||||
NSWindowZoomButton,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@implementation WindowButtonsView
|
||||
|
||||
+ (gfx::Point)defaultMargin {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return gfx::Point(7, 6);
|
||||
} else {
|
||||
return gfx::Point(7, 3);
|
||||
}
|
||||
}
|
||||
|
||||
+ (gfx::Point)hiddenInsetMargin {
|
||||
// For macOS >= 11, while this value does not match offical macOS apps like
|
||||
// Safari or Notes, it matches titleBarStyle's old implementation before
|
||||
// Electron <= 12.
|
||||
return gfx::Point(12, 11);
|
||||
}
|
||||
|
||||
- (id)initWithMargin:(const absl::optional<gfx::Point>&)margin {
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
[self setMargin:margin];
|
||||
|
||||
mouse_inside_ = false;
|
||||
show_on_hover_ = false;
|
||||
is_rtl_ = base::i18n::IsRTL();
|
||||
|
||||
for (size_t i = 0; i < base::size(kButtonTypes); ++i) {
|
||||
NSButton* button = [NSWindow standardWindowButton:kButtonTypes[i]
|
||||
forStyleMask:NSWindowStyleMaskTitled];
|
||||
[button setTag:i];
|
||||
int left_index = is_rtl_ ? base::size(kButtonTypes) - i - 1 : i;
|
||||
[button setFrameOrigin:NSMakePoint(left_index * kButtonPadding, 0)];
|
||||
[self addSubview:button];
|
||||
}
|
||||
|
||||
NSView* last_button =
|
||||
is_rtl_ ? [[self subviews] firstObject] : [[self subviews] lastObject];
|
||||
[self setFrameSize:NSMakeSize(last_button.frame.origin.x +
|
||||
last_button.frame.size.width,
|
||||
last_button.frame.size.height)];
|
||||
[self setNeedsDisplayForButtons];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setMargin:(const absl::optional<gfx::Point>&)margin {
|
||||
margin_ = margin.value_or([WindowButtonsView defaultMargin]);
|
||||
}
|
||||
|
||||
- (void)setShowOnHover:(BOOL)yes {
|
||||
show_on_hover_ = yes;
|
||||
[self setNeedsDisplayForButtons];
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplayForButtons {
|
||||
for (NSView* subview in self.subviews) {
|
||||
[subview setHidden:(show_on_hover_ && !mouse_inside_)];
|
||||
[subview setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview {
|
||||
[super removeFromSuperview];
|
||||
mouse_inside_ = NO;
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
// Stay in upper left corner.
|
||||
CGFloat y =
|
||||
self.superview.frame.size.height - self.frame.size.height - margin_.y();
|
||||
if (is_rtl_) {
|
||||
CGFloat x =
|
||||
self.superview.frame.size.width - self.frame.size.width - margin_.x();
|
||||
[self setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
|
||||
[self setFrameOrigin:NSMakePoint(x, y)];
|
||||
} else {
|
||||
[self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
|
||||
[self setFrameOrigin:NSMakePoint(margin_.x(), y)];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)_mouseInGroup:(NSButton*)button {
|
||||
return mouse_inside_;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
[super updateTrackingAreas];
|
||||
if (tracking_area_)
|
||||
[self removeTrackingArea:tracking_area_.get()];
|
||||
|
||||
tracking_area_.reset([[NSTrackingArea alloc]
|
||||
initWithRect:NSZeroRect
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
|
||||
NSTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil]);
|
||||
[self addTrackingArea:tracking_area_.get()];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent*)event {
|
||||
[super mouseEntered:event];
|
||||
mouse_inside_ = YES;
|
||||
[self setNeedsDisplayForButtons];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent*)event {
|
||||
[super mouseExited:event];
|
||||
mouse_inside_ = NO;
|
||||
[self setNeedsDisplayForButtons];
|
||||
}
|
||||
|
||||
- (gfx::Point)getMargin {
|
||||
return margin_;
|
||||
}
|
||||
|
||||
@end
|
Loading…
Add table
Add a link
Reference in a new issue