From 76924a335f40a0c3cc13805b4cb9429c336d33ca Mon Sep 17 00:00:00 2001 From: SineStriker <trueful@163.com> Date: 周五, 29 12月 2023 11:34:22 +0800 Subject: [PATCH] Optimize system button area APIs --- src/quick/quickwindowagent.h | 4 + src/widgets/widgetwindowagent_mac.cpp | 30 ++++++ src/core/contexts/abstractwindowcontext.cpp | 5 src/core/contexts/cocoawindowcontext.mm | 134 +++++++++++++++++++++------------ src/quick/quickwindowagent_mac.cpp | 20 ++++ src/core/contexts/abstractwindowcontext_p.h | 10 +- src/core/contexts/cocoawindowcontext_p.h | 2 src/widgets/widgetwindowagent.h | 4 + src/core/windowagentbase.cpp | 2 examples/mainwindow/mainwindow.cpp | 7 + src/core/qwkglobal.h | 8 ++ src/quick/quickwindowagent.cpp | 3 12 files changed, 168 insertions(+), 61 deletions(-) diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index 154aa62..313c771 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -289,6 +289,13 @@ #endif windowAgent->setHitTestVisible(menuBar, true); +#ifdef Q_OS_MAC + windowAgent->setSystemButtonAreaCallback([](const QSize &size) { + static constexpr const int width = 75; + return QRect(QPoint(size.width() - width, 0), QSize(width, size.height())); // + }); +#endif + setMenuWidget(windowBar); // 3. Adds simulated mouse events to the title bar buttons diff --git a/src/core/contexts/abstractwindowcontext.cpp b/src/core/contexts/abstractwindowcontext.cpp index 2fdada5..32efc02 100644 --- a/src/core/contexts/abstractwindowcontext.cpp +++ b/src/core/contexts/abstractwindowcontext.cpp @@ -120,8 +120,9 @@ } #ifdef Q_OS_MAC - void AbstractWindowContext::setSystemButtonArea(const QRect &rect) { - m_systemButtonArea = rect; + void + AbstractWindowContext::setSystemButtonAreaCallback(const ScreenRectCallback &callback) { + m_systemButtonAreaCallback = callback; virtual_hook(SystemButtonAreaChangedHook, nullptr); } #endif diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h index 04651ad..f375b83 100644 --- a/src/core/contexts/abstractwindowcontext_p.h +++ b/src/core/contexts/abstractwindowcontext_p.h @@ -50,8 +50,8 @@ bool setTitleBar(QObject *obj); #ifdef Q_OS_MAC - inline QRect systemButtonArea() const; - void setSystemButtonArea(const QRect &rect); + inline ScreenRectCallback systemButtonAreaCallback() const; + void setSystemButtonAreaCallback(const ScreenRectCallback &callback); #endif bool isInSystemButtons(const QPoint &pos, WindowAgentBase::SystemButton *button) const; @@ -88,7 +88,7 @@ QSet<const QObject *> m_hitTestVisibleItems; #ifdef Q_OS_MAC - QRect m_systemButtonArea; + ScreenRectCallback m_systemButtonAreaCallback; #endif QObject *m_titleBar{}; @@ -126,8 +126,8 @@ } #ifdef Q_OS_MAC - inline QRect AbstractWindowContext::systemButtonArea() const { - return m_systemButtonArea; + inline ScreenRectCallback AbstractWindowContext::systemButtonAreaCallback() const { + return m_systemButtonAreaCallback; } #endif diff --git a/src/core/contexts/cocoawindowcontext.mm b/src/core/contexts/cocoawindowcontext.mm index eed5bfd..cc9bfeb 100644 --- a/src/core/contexts/cocoawindowcontext.mm +++ b/src/core/contexts/cocoawindowcontext.mm @@ -10,6 +10,9 @@ #include "qwkglobal_p.h" #include "systemwindow_p.h" +// https://forgetsou.github.io/2020/11/06/macos%E5%BC%80%E5%8F%91-%E5%85%B3%E9%97%AD-%E6%9C%80%E5%B0%8F%E5%8C%96-%E5%85%A8%E5%B1%8F%E5%B1%85%E4%B8%AD%E5%A4%84%E7%90%86(%E4%BB%BFMac%20QQ)/ +// https://nyrra33.com/2019/03/26/changing-titlebars-height/ + namespace QWK { struct NSWindowProxy; @@ -24,12 +27,16 @@ struct QWK_NSWindowDelegate { public: + enum NSEventType { + WillEnterFullScreen, + DidEnterFullScreen, + WillExitFullScreen, + DidExitFullScreen, + DidResize, + }; + virtual ~QWK_NSWindowDelegate() = default; - virtual void windowWillEnterFullScreen(){}; - virtual void windowDidEnterFullScreen(){}; - virtual void windowWillExitFullScreen(){}; - virtual void windowDidExitFullScreen(){}; - virtual void windowDidResize(){}; + virtual void windowEvent(NSEventType eventType) = 0; }; // @@ -77,35 +84,40 @@ - (void)windowWillEnterFullScreen:(NSNotification *)notification { auto nswindow = reinterpret_cast<NSWindow *>(notification.object); if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) { - reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowWillEnterFullScreen(); + reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent( + QWK_NSWindowDelegate::WillEnterFullScreen); } } - (void)windowDidEnterFullScreen:(NSNotification *)notification { auto nswindow = reinterpret_cast<NSWindow *>(notification.object); if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) { - reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidEnterFullScreen(); + reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent( + QWK_NSWindowDelegate::DidEnterFullScreen); } } - (void)windowWillExitFullScreen:(NSNotification *)notification { auto nswindow = reinterpret_cast<NSWindow *>(notification.object); if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) { - reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowWillExitFullScreen(); + reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent( + QWK_NSWindowDelegate::WillExitFullScreen); } } - (void)windowDidExitFullScreen:(NSNotification *)notification { auto nswindow = reinterpret_cast<NSWindow *>(notification.object); if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) { - reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidExitFullScreen(); + reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent( + QWK_NSWindowDelegate::DidExitFullScreen); } } - (void)windowDidResize:(NSNotification *)notification { auto nswindow = reinterpret_cast<NSWindow *>(notification.object); if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) { - reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidResize(); + reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent( + QWK_NSWindowDelegate::DidResize); } } @@ -134,38 +146,43 @@ } // Delegate - void windowWillEnterFullScreen() override { - } + void windowEvent(NSEventType eventType) override { + switch (eventType) { + case WillExitFullScreen: { + if (!screenRectCallback || !systemButtonVisible) + return; - void windowDidEnterFullScreen() override { - } + // The system buttons will stuck at their default positions when the + // exit-fullscreen animation is running, we need to hide them until the + // animation finishes + for (const auto &button : systemButtons()) { + button.hidden = true; + } + break; + } - void windowWillExitFullScreen() override { - if (systemButtonRect.isEmpty() || !systemButtonVisible) - return; + case DidExitFullScreen: { + if (!screenRectCallback || !systemButtonVisible) + return; - // The system buttons will stuck at their default positions when the exit-fullscreen - // animation is running, we need to hide them until the animation finishes - for (const auto &button : systemButtons()) { - button.hidden = true; + for (const auto &button : systemButtons()) { + button.hidden = false; + } + updateSystemButtonRect(); + break; + } + + case DidResize: { + if (!screenRectCallback || !systemButtonVisible) { + return; + } + updateSystemButtonRect(); + break; + } + + default: + break; } - } - - void windowDidExitFullScreen() override { - if (systemButtonRect.isEmpty() || !systemButtonVisible) - return; - - for (const auto &button : systemButtons()) { - button.hidden = false; - } - updateSystemButtonRect(); - } - - void windowDidResize() override { - if (systemButtonRect.isEmpty() || !systemButtonVisible) { - return; - } - updateSystemButtonRect(); } // System buttons visibility @@ -175,35 +192,42 @@ button.hidden = !visible; } - if (systemButtonRect.isEmpty() || !visible) { + if (!screenRectCallback || !visible) { return; } updateSystemButtonRect(); } // System buttons area - void setSystemButtonRect(const QRect &rect) { - systemButtonRect = rect; + void setScreenRectCallback(const ScreenRectCallback &callback) { + screenRectCallback = callback; - if (rect.isEmpty() || !systemButtonVisible) { + if (!callback || !systemButtonVisible) { return; } updateSystemButtonRect(); } void updateSystemButtonRect() { - // https://forgetsou.github.io/2020/11/06/macos%E5%BC%80%E5%8F%91-%E5%85%B3%E9%97%AD-%E6%9C%80%E5%B0%8F%E5%8C%96-%E5%85%A8%E5%B1%8F%E5%B1%85%E4%B8%AD%E5%A4%84%E7%90%86(%E4%BB%BFMac%20QQ)/ - const auto &buttons = systemButtons(); const auto &leftButton = buttons[0]; const auto &midButton = buttons[1]; const auto &rightButton = buttons[2]; + auto titlebar = rightButton.superview; + int titlebarHeight = titlebar.frame.size.height; + auto spacing = midButton.frame.origin.x - leftButton.frame.origin.x; auto width = midButton.frame.size.width; auto height = midButton.frame.size.height; - QPoint center = systemButtonRect.center(); + auto viewSize = + nswindow.contentView ? nswindow.contentView.frame.size : nswindow.frame.size; + QPoint center = screenRectCallback(QSize(viewSize.width, titlebarHeight)).center(); + + // The origin of the NSWindow coordinate system is in the lower left corner, we + // do the necessary transformations + center.ry() = titlebarHeight - center.y(); // Mid button NSPoint centerOrigin = { @@ -232,6 +256,11 @@ NSButton *minimizeBtn = [nswindow standardWindowButton:NSWindowMiniaturizeButton]; NSButton *zoomBtn = [nswindow standardWindowButton:NSWindowZoomButton]; return {closeBtn, minimizeBtn, zoomBtn}; + } + + inline int titleBarHeight() const { + NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton]; + return closeBtn.superview.frame.size.height; } // Blur effect @@ -293,7 +322,7 @@ nswindow.titlebarAppearsTransparent = (visible ? NO : YES); nswindow.titleVisibility = (visible ? NSWindowTitleVisible : NSWindowTitleHidden); nswindow.hasShadow = YES; - nswindow.showsToolbarButton = NO; + // nswindow.showsToolbarButton = NO; nswindow.movableByWindowBackground = NO; nswindow.movable = NO; // This line causes the window in the wrong position when // become fullscreen. @@ -437,7 +466,7 @@ NSWindow *nswindow = nil; bool systemButtonVisible = true; - QRect systemButtonRect; + ScreenRectCallback screenRectCallback; static inline QWK_NSWindowObserver *windowObserver = nil; @@ -638,7 +667,7 @@ void CocoaWindowContext::virtual_hook(int id, void *data) { switch (id) { case SystemButtonAreaChangedHook: { - ensureWindowProxy(windowId)->setSystemButtonRect(m_systemButtonArea); + ensureWindowProxy(windowId)->setScreenRectCallback(m_systemButtonAreaCallback); return; } @@ -648,6 +677,15 @@ AbstractWindowContext::virtual_hook(id, data); } + QVariant CocoaWindowContext::windowAttribute(const QString &key) const { + if (key == QStringLiteral("title-bar-height")) { + if (!m_windowHandle) + return 0; + return ensureWindowProxy(windowId)->titleBarHeight(); + } + return AbstractWindowContext::windowAttribute(key); + } + void CocoaWindowContext::winIdChanged() { // If the original window id is valid, remove all resources related if (windowId) { diff --git a/src/core/contexts/cocoawindowcontext_p.h b/src/core/contexts/cocoawindowcontext_p.h index 67c97db..ad7e6be 100644 --- a/src/core/contexts/cocoawindowcontext_p.h +++ b/src/core/contexts/cocoawindowcontext_p.h @@ -23,6 +23,8 @@ QString key() const override; void virtual_hook(int id, void *data) override; + QVariant windowAttribute(const QString &key) const override; + protected: void winIdChanged() override; bool windowAttributeChanged(const QString &key, const QVariant &attribute, diff --git a/src/core/qwkglobal.h b/src/core/qwkglobal.h index 1d8be98..fc71534 100644 --- a/src/core/qwkglobal.h +++ b/src/core/qwkglobal.h @@ -1,6 +1,8 @@ #ifndef QWKGLOBAL_H #define QWKGLOBAL_H +#include <functional> + #include <QtCore/QEvent> #include <QtGui/QtEvents> @@ -28,4 +30,10 @@ # define QWINDOWKIT_CONFIG(feature) ((1 / QWINDOWKIT_##feature) == 1) #endif +namespace QWK { + + using ScreenRectCallback = std::function<QRect(const QSize &)>; + +} + #endif // QWKGLOBAL_H diff --git a/src/core/windowagentbase.cpp b/src/core/windowagentbase.cpp index 7743886..be6c6ca 100644 --- a/src/core/windowagentbase.cpp +++ b/src/core/windowagentbase.cpp @@ -102,6 +102,8 @@ \li \c blur-effect: You can specify a string value, "dark" to enable dark mode, "light" to set enable mode, "none" to disable. You can also specify a boolean value, \c true to enable current theme mode, \c false to disable. + \li \c title-bar-height: Returns the system title bar height, the system button display + area will be limited to this height. (Readonly) */ bool WindowAgentBase::setWindowAttribute(const QString &key, const QVariant &attribute) { Q_D(WindowAgentBase); diff --git a/src/quick/quickwindowagent.cpp b/src/quick/quickwindowagent.cpp index 20b48b9..3cecb21 100644 --- a/src/quick/quickwindowagent.cpp +++ b/src/quick/quickwindowagent.cpp @@ -59,6 +59,9 @@ if (!d->context->setTitleBar(item)) { return; } +#ifdef Q_OS_MAC + setSystemButtonArea(nullptr); +#endif Q_EMIT titleBarWidgetChanged(item); } diff --git a/src/quick/quickwindowagent.h b/src/quick/quickwindowagent.h index b3ebc2b..2fd7b3e 100644 --- a/src/quick/quickwindowagent.h +++ b/src/quick/quickwindowagent.h @@ -31,8 +31,12 @@ Q_INVOKABLE void setHitTestVisible(const QQuickItem *item, bool visible = true); #ifdef Q_OS_MAC + // The system button area APIs are experimental, may be changed in the future. Q_INVOKABLE QQuickItem *systemButtonArea() const; Q_INVOKABLE void setSystemButtonArea(QQuickItem *item); + + Q_INVOKABLE ScreenRectCallback systemButtonAreaCallback() const; + Q_INVOKABLE void setSystemButtonAreaCallback(const ScreenRectCallback &callback); #endif Q_SIGNALS: diff --git a/src/quick/quickwindowagent_mac.cpp b/src/quick/quickwindowagent_mac.cpp index 0e19772..87895fb 100644 --- a/src/quick/quickwindowagent_mac.cpp +++ b/src/quick/quickwindowagent_mac.cpp @@ -27,11 +27,14 @@ &SystemButtonAreaItemHandler::updateSystemButtonArea); connect(item, &QQuickItem::heightChanged, this, &SystemButtonAreaItemHandler::updateSystemButtonArea); - updateSystemButtonArea(); + + ctx->setSystemButtonAreaCallback([item](const QSize &) { + return QRectF(item->mapToScene(QPointF(0, 0)), item->size()).toRect(); // + }); } void SystemButtonAreaItemHandler::updateSystemButtonArea() { - ctx->setSystemButtonArea(QRectF(item->mapToScene(QPointF(0, 0)), item->size()).toRect()); + ctx->virtual_hook(AbstractWindowContext::SystemButtonAreaChangedHook, nullptr); } QQuickItem *QuickWindowAgent::systemButtonArea() const { @@ -48,10 +51,21 @@ d->systemButtonAreaItem = item; if (!item) { d->systemButtonAreaItemHandler.reset(); - ctx->setSystemButtonArea({}); + ctx->setSystemButtonAreaCallback({}); return; } d->systemButtonAreaItemHandler = std::make_unique<SystemButtonAreaItemHandler>(item, ctx); } + ScreenRectCallback QuickWindowAgent::systemButtonAreaCallback() const { + Q_D(const QuickWindowAgent); + return d->systemButtonAreaItem ? nullptr : d->context->systemButtonAreaCallback(); + } + + void QuickWindowAgent::setSystemButtonAreaCallback(const ScreenRectCallback &callback) { + Q_D(QuickWindowAgent); + setSystemButtonArea(nullptr); + d->context->setSystemButtonAreaCallback(callback); + } + } diff --git a/src/widgets/widgetwindowagent.h b/src/widgets/widgetwindowagent.h index 85eb3be..d159448 100644 --- a/src/widgets/widgetwindowagent.h +++ b/src/widgets/widgetwindowagent.h @@ -27,8 +27,12 @@ void setSystemButton(SystemButton button, QWidget *w); #ifdef Q_OS_MAC + // The system button area APIs are experimental, may be changed in the future. QWidget *systemButtonArea() const; void setSystemButtonArea(QWidget *widget); + + ScreenRectCallback systemButtonAreaCallback() const; + void setSystemButtonAreaCallback(const ScreenRectCallback &callback); #endif bool isHitTestVisible(const QWidget *w) const; diff --git a/src/widgets/widgetwindowagent_mac.cpp b/src/widgets/widgetwindowagent_mac.cpp index 1d4f320..d3cb1ba 100644 --- a/src/widgets/widgetwindowagent_mac.cpp +++ b/src/widgets/widgetwindowagent_mac.cpp @@ -14,6 +14,9 @@ QObject *parent = nullptr) : QObject(parent), widget(widget), ctx(ctx) { widget->installEventFilter(this); + ctx->setSystemButtonAreaCallback([widget](const QSize &) { + return getWidgetSceneRect(widget); // + }); } ~SystemButtonAreaWidgetEventFilter() override = default; @@ -23,7 +26,7 @@ switch (event->type()) { case QEvent::Move: case QEvent::Resize: { - ctx->setSystemButtonArea(getWidgetSceneRect(widget)); + ctx->virtual_hook(AbstractWindowContext::SystemButtonAreaChangedHook, nullptr); break; } @@ -49,6 +52,8 @@ /*! Sets the widget that acts as the system button area. The system button will be centered in its area, it is recommended to place the widget in a layout and set a fixed size policy. + + The system button will be visible in the system title bar area. */ void WidgetWindowAgent::setSystemButtonArea(QWidget *widget) { Q_D(WidgetWindowAgent); @@ -58,13 +63,32 @@ auto ctx = d->context.get(); d->systemButtonAreaWidget = widget; if (!widget) { + d->context->setSystemButtonAreaCallback({}); d->systemButtonAreaWidgetEventFilter.reset(); - ctx->setSystemButtonArea({}); return; } d->systemButtonAreaWidgetEventFilter = std::make_unique<SystemButtonAreaWidgetEventFilter>(widget, ctx); - ctx->setSystemButtonArea(getWidgetSceneRect(widget)); + } + + /*! + Returns the the system button area callback. + */ + ScreenRectCallback WidgetWindowAgent::systemButtonAreaCallback() const { + Q_D(const WidgetWindowAgent); + return d->systemButtonAreaWidget ? nullptr : d->context->systemButtonAreaCallback(); + } + + /*! + Sets the the system button area callback, the \c size of the callback is the native title + bar size. + + The system button position will be updated when the window resizes. + */ + void WidgetWindowAgent::setSystemButtonAreaCallback(const ScreenRectCallback &callback) { + Q_D(WidgetWindowAgent); + setSystemButtonArea(nullptr); + d->context->setSystemButtonAreaCallback(callback); } } \ No newline at end of file -- Gitblit v1.9.1