From 92681dc9d913c164cb9c0945033c3586f6d406b9 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sun, 10 Oct 2021 21:08:42 +0800 Subject: [PATCH] copy WindowButtonsProxy from electron --- examples/mainwindow/mainwindow.cpp | 1 - examples/minimal/flwindow.cpp | 13 -- examples/minimal/flwindow.h | 4 - src/CMakeLists.txt | 6 +- src/core/utilities.h | 1 + src/core/utilities_macos.mm | 63 ++++++-- src/core/window_buttons_proxy.h | 57 ++++++++ src/core/window_buttons_proxy.mm | 225 +++++++++++++++++++++++++++++ 8 files changed, 342 insertions(+), 28 deletions(-) create mode 100644 src/core/window_buttons_proxy.h create mode 100644 src/core/window_buttons_proxy.mm diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index 47a0e7e..bae30e9 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -107,7 +107,6 @@ void MainWindow::showEvent(QShowEvent *event) titleBarWidget->maximizeButton->hide(); titleBarWidget->closeButton->hide(); Utilities::showMacWindowButton(windowHandle()); - Utilities::setStandardWindowButtonsOffset(win, QPoint(0, -5)); #endif // Q_OS_MAC inited = true; } diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index c5d6a95..a32beb3 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -37,7 +37,6 @@ void FLWindow::initFramelessWindow() m_maximizeButton->hide(); m_closeButton->hide(); Utilities::showMacWindowButton(windowHandle()); - Utilities::setStandardWindowButtonsOffset(windowHandle(), QPoint(10, 10)); #endif } @@ -65,18 +64,6 @@ bool FLWindow::nativeEvent(const QByteArray &eventType, void *message, long *res } #endif // Q_OS_WIN -#ifdef Q_OS_MAC -void FLWindow::resizeEvent(QResizeEvent *event) -{ - auto win = windowHandle(); - if (win) { - Utilities::setStandardWindowButtonsOffset(win, QPoint(10, 10)); - } - - QWidget::resizeEvent(event); -} -#endif // Q_OS_MAC - void FLWindow::setupUi() { resize(800, 600); diff --git a/examples/minimal/flwindow.h b/examples/minimal/flwindow.h index b599d71..2fbb5ed 100644 --- a/examples/minimal/flwindow.h +++ b/examples/minimal/flwindow.h @@ -18,10 +18,6 @@ protected: bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; #endif // Q_OS_WIN -#ifdef Q_OS_MAC - void resizeEvent(QResizeEvent *event) override; -#endif // Q_OS_MAC - private: void initFramelessWindow(); void setupUi(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a66547e..80533c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,7 +24,11 @@ if(WIN32) ) else() if(APPLE) - list(APPEND SOURCES core/utilities_macos.mm) + list(APPEND SOURCES + core/utilities_macos.mm + core/window_buttons_proxy.h + core/window_buttons_proxy.mm + ) else() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) list(APPEND SOURCES core/utilities_linux.cpp) diff --git a/src/core/utilities.h b/src/core/utilities.h index d131665..ef72684 100644 --- a/src/core/utilities.h +++ b/src/core/utilities.h @@ -81,6 +81,7 @@ FRAMELESSHELPER_API bool isMacMainWindow(QWindow *w); FRAMELESSHELPER_API bool makeMacKeyWindow(QWindow *w); FRAMELESSHELPER_API bool makeMacMainWindow(QWindow *w); FRAMELESSHELPER_API bool setStandardWindowButtonsOffset(QWindow *w, const QPoint &offset); +FRAMELESSHELPER_API bool setTrafficLightPosition(QWindow *w, const QPoint &pos); #endif // Q_OS_MAC } diff --git a/src/core/utilities_macos.mm b/src/core/utilities_macos.mm index 12e5ea3..ed9e37d 100644 --- a/src/core/utilities_macos.mm +++ b/src/core/utilities_macos.mm @@ -32,6 +32,8 @@ #include #include +#include "window_buttons_proxy.h" + FRAMELESSHELPER_BEGIN_NAMESPACE namespace Utilities { @@ -241,8 +243,8 @@ static void overrideNSWindowMethods(NSWindow* window) cls, @selector(canBecomeMainWindow), (IMP) __canBecomeMainWindow); gOrigSendEvent = (sendEventType) replaceMethod( cls, @selector(sendEvent:), (IMP) __sendEvent); - gOrigIsFlipped = (isFlippedType) replaceMethod( - cls, @selector (isFlipped), (IMP) __isFlipped); + //gOrigIsFlipped = (isFlippedType) replaceMethod( + // cls, @selector (isFlipped), (IMP) __isFlipped); gNSWindowOverrode = true; } @@ -271,15 +273,51 @@ static void restoreNSWindowMethods(NSWindow* window) restoreMethod(cls, @selector(sendEvent:), (IMP) gOrigSendEvent); gOrigSendEvent = nullptr; - restoreMethod(cls, @selector(isFlipped), (IMP) gOrigIsFlipped); - gOrigIsFlipped = nullptr; + //restoreMethod(cls, @selector(isFlipped), (IMP) gOrigIsFlipped); + //gOrigIsFlipped = nullptr; gNSWindowOverrode = false; } } -static QHash gQWindowToNSWindow; +class NSWindowData +{ +private: + NSWindow* m_window; + WindowButtonsProxy *m_buttonProxy; + bool m_windowButtonVisibility; + QPoint m_trafficLightPosition; + +public: + NSWindowData(NSWindow *window) + : m_windowButtonVisibility(false) + , m_buttonProxy(nullptr) + , m_window(window) + { + overrideNSWindowMethods(window); + m_buttonProxy = [[WindowButtonsProxy alloc] initWithWindow:window]; + } + + ~NSWindowData() + { + restoreNSWindowMethods(m_window); + [m_buttonProxy release]; + } + + NSWindow* window() { return m_window; } + + QPoint trafficLightPosition() { return m_trafficLightPosition; } + bool setTrafficLightPosition(const QPoint &pos) { + m_trafficLightPosition = pos; + if (m_buttonProxy) { + [m_buttonProxy setMargin:m_trafficLightPosition]; + } + return true; + } +}; + +static QHash gQWindowToNSWindow; static NSWindow* getNSWindow(QWindow* w) { @@ -303,8 +341,8 @@ bool setMacWindowHook(QWindow* w) if (nswindow == nullptr) return false; - gQWindowToNSWindow.insert(w, nswindow); - overrideNSWindowMethods(nswindow); + NSWindowData *obj = new NSWindowData(nswindow); + gQWindowToNSWindow.insert(w, obj); return true; } @@ -313,9 +351,10 @@ bool unsetMacWindowHook(QWindow* w) { if (!gQWindowToNSWindow.contains(w)) return false; - NSWindow* obj = gQWindowToNSWindow[w]; + + NSWindowData* obj = gQWindowToNSWindow[w]; gQWindowToNSWindow.remove(w); - restoreNSWindowMethods(obj); + delete obj; return true; } @@ -487,6 +526,12 @@ bool setStandardWindowButtonsOffset(QWindow *w, const QPoint &offset) return true; } +bool setTrafficLightPosition(QWindow *w, const QPoint &pos) +{ + NSWindowData* obj = gQWindowToNSWindow[w]; + return obj->setTrafficLightPosition(pos); +} + } // namespace Utilities FRAMELESSHELPER_END_NAMESPACE diff --git a/src/core/window_buttons_proxy.h b/src/core/window_buttons_proxy.h new file mode 100644 index 0000000..17a1991 --- /dev/null +++ b/src/core/window_buttons_proxy.h @@ -0,0 +1,57 @@ +// 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_ + +#include +#import +#include + +@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_; + + // Current left-top margin of buttons. + QPoint margin_; + // The default left-top margin. + QPoint default_margin_; + + // Track mouse moves above window buttons. + BOOL show_on_hover_; + BOOL mouse_inside_; + NSTrackingArea* tracking_area_; + 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 QPoint&)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_ diff --git a/src/core/window_buttons_proxy.mm b/src/core/window_buttons_proxy.mm new file mode 100644 index 0000000..59ef313 --- /dev/null +++ b/src/core/window_buttons_proxy.mm @@ -0,0 +1,225 @@ +// 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 "window_buttons_proxy.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; + + // 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 { + NSView* titleBarContainer = [self titleBarContainer]; + if (!titleBarContainer) + return; + [titleBarContainer setHidden:!visible]; +} + +- (BOOL)isVisible { + NSView* titleBarContainer = [self titleBarContainer]; + if (!titleBarContainer) + return YES; + return ![titleBarContainer isHidden]; +} + +- (void)setShowOnHover:(BOOL)yes { + NSView* titleBarContainer = [self titleBarContainer]; + if (!titleBarContainer) + 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_) { + auto old_ptr = hover_view_; + hover_view_ = [[ButtonsAreaHoverView alloc] initWithProxy:self]; + [old_ptr release]; + [hover_view_ setFrame:[self getButtonsBounds]]; + [titleBarContainer addSubview:hover_view_]; + } else { + [hover_view_ removeFromSuperview]; + [hover_view_ release]; + hover_view_ = nullptr; + } + [self updateButtonsVisibility]; +} + +- (void)setMargin:(const QPoint&)margin { + if (!margin.isNull()) + margin_ = margin; + else + margin_ = default_margin_; + [self redraw]; +} + +- (NSRect)getButtonsContainerBounds { + return NSInsetRect([self getButtonsBounds], -margin_.x(), -margin_.y()); +} + +- (void)redraw { + NSView* titleBarContainer = [self titleBarContainer]; + if (!titleBarContainer) + return; + + NSView* left = [self leftButton]; + NSView* middle = [self middleButton]; + NSView* right = [self rightButton]; + + float button_width = NSWidth(left.frame); + float button_height = NSHeight(left.frame); + float padding = NSMinX(middle.frame) - NSMaxX(left.frame); + float start; + if (false /*base::i18n::IsRTL()*/) + start = + NSWidth(window_.frame) - 3 * button_width - 2 * padding - margin_.x(); + else + start = margin_.x(); + + NSRect cbounds = titleBarContainer.frame; + cbounds.size.height = button_height + 2 * margin_.y(); + cbounds.origin.y = NSHeight(window_.frame) - NSHeight(cbounds); + [titleBarContainer 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_]; + auto old_ptr = tracking_area_; + tracking_area_ = [[NSTrackingArea alloc] + initWithRect:NSZeroRect + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | + NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [old_ptr release]; + [hover_view_ addTrackingArea:tracking_area_]; +} + +- (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 { + NSView* left = [self leftButton]; + NSView* right = [self rightButton]; + return NSMakeRect(NSMinX(left.frame), NSMinY(left.frame), + NSMaxX(right.frame) - NSMinX(left.frame), + NSHeight(left.frame)); +} + +// Compute margin from position of current buttons. +- (QPoint)getCurrentMargin { + QPoint result; + NSView* titleBarContainer = [self titleBarContainer]; + if (!titleBarContainer) + return result; + + NSView* left = [self leftButton]; + NSView* right = [self rightButton]; + + result.setX((NSHeight(titleBarContainer.frame) - NSHeight(left.frame)) / 2); + + if (false /*base::i18n::IsRTL()*/) + result.setX(NSWidth(window_.frame) - NSMaxX(right.frame)); + else + result.setX(NSMinX(left.frame)); + return result; +} + +// Receive the titlebar container, which might be nil if the window does not +// have the NSWindowStyleMaskTitled style. +- (NSView*)titleBarContainer { + NSView* left = [self leftButton]; + if (!left.superview) + return nil; + return left.superview.superview; +} + +// Receive the window buttons, note that the buttons might be removed and +// re-added on the fly so we should not cache them. +- (NSButton*)leftButton { + if (false /*base::i18n::IsRTL()*/) + return [window_ standardWindowButton:NSWindowZoomButton]; + else + return [window_ standardWindowButton:NSWindowCloseButton]; +} + +- (NSButton*)middleButton { + return [window_ standardWindowButton:NSWindowMiniaturizeButton]; +} + +- (NSButton*)rightButton { + if (false /*base::i18n::IsRTL()*/) + return [window_ standardWindowButton:NSWindowCloseButton]; + else + return [window_ standardWindowButton:NSWindowZoomButton]; +} + +@end