macOS: widgets: preserve the native title bar elements

Quick part to be done.
This commit is contained in:
Yuhang Zhao 2023-01-14 18:32:47 +08:00
parent 9e975e02e0
commit 819ffb79fb
14 changed files with 128 additions and 58 deletions

View File

@ -52,7 +52,9 @@ void Dialog::setupUi()
titleBar = new StandardTitleBar(this); titleBar = new StandardTitleBar(this);
titleBar->setWindowIconVisible(true); titleBar->setWindowIconVisible(true);
#ifndef Q_OS_MACOS
titleBar->maximizeButton()->hide(); titleBar->maximizeButton()->hide();
#endif // Q_OS_MACOS
label = new QLabel(tr("Find &what:")); label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit; lineEdit = new QLineEdit;
@ -124,9 +126,11 @@ void Dialog::setupUi()
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->setTitleBarWidget(titleBar); helper->setTitleBarWidget(titleBar);
#ifndef Q_OS_MACOS
helper->setSystemButton(titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(titleBar->minimizeButton(), SystemButtonType::Minimize);
helper->setSystemButton(titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(titleBar->maximizeButton(), SystemButtonType::Maximize);
helper->setSystemButton(titleBar->closeButton(), SystemButtonType::Close); helper->setSystemButton(titleBar->closeButton(), SystemButtonType::Close);
#endif // Q_OS_MACOS
// Special hack to disable the overriding of the mouse cursor, it's totally different // Special hack to disable the overriding of the mouse cursor, it's totally different
// with making the window un-resizable: we still want the window be able to resize // with making the window un-resizable: we still want the window be able to resize
// programatically, but we also want the user not able to resize the window manually. // programatically, but we also want the user not able to resize the window manually.

View File

@ -101,9 +101,11 @@ QMenuBar::item:pressed {
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->setTitleBarWidget(m_titleBar); helper->setTitleBarWidget(m_titleBar);
#ifndef Q_OS_MACOS
helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize);
helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize);
helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close); helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close);
#endif // Q_OS_MACOS
helper->setHitTestVisible(mb); // IMPORTANT! helper->setHitTestVisible(mb); // IMPORTANT!
connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){ connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){
const auto savedGeometry = Settings::get<QRect>({}, kGeometry); const auto savedGeometry = Settings::get<QRect>({}, kGeometry);

View File

@ -77,9 +77,11 @@ void MainWindow::initialize()
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->setTitleBarWidget(m_titleBar); helper->setTitleBarWidget(m_titleBar);
#ifndef Q_OS_MACOS
helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize);
helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize);
helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close); helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close);
#endif // Q_OS_MACOS
connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){ connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){
const auto savedGeometry = Settings::get<QRect>({}, kGeometry); const auto savedGeometry = Settings::get<QRect>({}, kGeometry);
if (savedGeometry.isValid() && !parent()) { if (savedGeometry.isValid() && !parent()) {

View File

@ -128,9 +128,11 @@ void Widget::initialize()
FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this);
helper->setTitleBarWidget(m_titleBar); helper->setTitleBarWidget(m_titleBar);
#ifndef Q_OS_MACOS
helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(m_titleBar->minimizeButton(), SystemButtonType::Minimize);
helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize); helper->setSystemButton(m_titleBar->maximizeButton(), SystemButtonType::Maximize);
helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close); helper->setSystemButton(m_titleBar->closeButton(), SystemButtonType::Close);
#endif // Q_OS_MACOS
connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){ connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){
const QString objName = objectName(); const QString objName = objectName();
const auto savedGeometry = Settings::get<QRect>(objName, kGeometry); const auto savedGeometry = Settings::get<QRect>(objName, kGeometry);

View File

@ -206,6 +206,7 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API)
[[maybe_unused]] inline constexpr const int kDefaultWindowFrameBorderThickness = 1; [[maybe_unused]] inline constexpr const int kDefaultWindowFrameBorderThickness = 1;
[[maybe_unused]] inline constexpr const int kDefaultTitleBarFontPointSize = 11; [[maybe_unused]] inline constexpr const int kDefaultTitleBarFontPointSize = 11;
[[maybe_unused]] inline constexpr const int kDefaultTitleBarContentsMargin = 10; [[maybe_unused]] inline constexpr const int kDefaultTitleBarContentsMargin = 10;
[[maybe_unused]] inline constexpr const int kMacOSChromeButtonAreaWidth = 60;
[[maybe_unused]] inline constexpr const QSize kDefaultWindowIconSize = {16, 16}; [[maybe_unused]] inline constexpr const QSize kDefaultWindowIconSize = {16, 16};
// We have to use "qRound()" here because "std::round()" is not constexpr, yet. // We have to use "qRound()" here because "std::round()" is not constexpr, yet.
[[maybe_unused]] inline constexpr const QSize kDefaultSystemButtonSize = {qRound(qreal(kDefaultTitleBarHeight) * 1.5), kDefaultTitleBarHeight}; [[maybe_unused]] inline constexpr const QSize kDefaultSystemButtonSize = {qRound(qreal(kDefaultTitleBarHeight) * 1.5), kDefaultTitleBarHeight};

View File

@ -25,9 +25,11 @@
#pragma once #pragma once
#include "framelesshelperwidgets_global.h" #include "framelesshelperwidgets_global.h"
#include <QtGui/qfont.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QPaintEvent; class QPaintEvent;
class QMouseEvent;
QT_END_NAMESPACE QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -43,6 +45,13 @@ class FRAMELESSHELPER_WIDGETS_API StandardTitleBarPrivate : public QObject
Q_DISABLE_COPY_MOVE(StandardTitleBarPrivate) Q_DISABLE_COPY_MOVE(StandardTitleBarPrivate)
public: public:
struct FontMetrics
{
int width = 0;
int height = 0;
int baseline = 0;
};
explicit StandardTitleBarPrivate(StandardTitleBar *q); explicit StandardTitleBarPrivate(StandardTitleBar *q);
~StandardTitleBarPrivate() override; ~StandardTitleBarPrivate() override;
@ -80,6 +89,9 @@ public:
Q_NODISCARD bool windowIconVisible_real() const; Q_NODISCARD bool windowIconVisible_real() const;
Q_NODISCARD bool isInTitleBarIconArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarIconArea(const QPoint &pos) const;
Q_NODISCARD QFont defaultFont() const;
Q_NODISCARD FontMetrics titleLabelSize() const;
public Q_SLOTS: public Q_SLOTS:
void updateMaximizeButton(); void updateMaximizeButton();
void updateTitleBarColor(); void updateTitleBarColor();
@ -94,9 +106,11 @@ private:
private: private:
StandardTitleBar *q_ptr = nullptr; StandardTitleBar *q_ptr = nullptr;
#ifndef Q_OS_MACOS
StandardSystemButton *m_minimizeButton = nullptr; StandardSystemButton *m_minimizeButton = nullptr;
StandardSystemButton *m_maximizeButton = nullptr; StandardSystemButton *m_maximizeButton = nullptr;
StandardSystemButton *m_closeButton = nullptr; StandardSystemButton *m_closeButton = nullptr;
#endif // Q_OS_MACOS
QPointer<QWidget> m_window = nullptr; QPointer<QWidget> m_window = nullptr;
bool m_extended = false; bool m_extended = false;
Qt::Alignment m_labelAlignment = {}; Qt::Alignment m_labelAlignment = {};

View File

@ -41,9 +41,11 @@ class FRAMELESSHELPER_WIDGETS_API StandardTitleBar : public QWidget
Q_DECLARE_PRIVATE(StandardTitleBar) Q_DECLARE_PRIVATE(StandardTitleBar)
Q_DISABLE_COPY_MOVE(StandardTitleBar) Q_DISABLE_COPY_MOVE(StandardTitleBar)
Q_PROPERTY(Qt::Alignment titleLabelAlignment READ titleLabelAlignment WRITE setTitleLabelAlignment NOTIFY titleLabelAlignmentChanged FINAL) Q_PROPERTY(Qt::Alignment titleLabelAlignment READ titleLabelAlignment WRITE setTitleLabelAlignment NOTIFY titleLabelAlignmentChanged FINAL)
#ifndef Q_OS_MACOS
Q_PROPERTY(StandardSystemButton* minimizeButton READ minimizeButton CONSTANT FINAL) Q_PROPERTY(StandardSystemButton* minimizeButton READ minimizeButton CONSTANT FINAL)
Q_PROPERTY(StandardSystemButton* maximizeButton READ maximizeButton CONSTANT FINAL) Q_PROPERTY(StandardSystemButton* maximizeButton READ maximizeButton CONSTANT FINAL)
Q_PROPERTY(StandardSystemButton* closeButton READ closeButton CONSTANT FINAL) Q_PROPERTY(StandardSystemButton* closeButton READ closeButton CONSTANT FINAL)
#endif // Q_OS_MACOS
Q_PROPERTY(bool extended READ isExtended WRITE setExtended NOTIFY extendedChanged FINAL) Q_PROPERTY(bool extended READ isExtended WRITE setExtended NOTIFY extendedChanged FINAL)
Q_PROPERTY(bool hideWhenClose READ isHideWhenClose WRITE setHideWhenClose NOTIFY hideWhenCloseChanged FINAL) Q_PROPERTY(bool hideWhenClose READ isHideWhenClose WRITE setHideWhenClose NOTIFY hideWhenCloseChanged FINAL)
Q_PROPERTY(ChromePalette* chromePalette READ chromePalette CONSTANT FINAL) Q_PROPERTY(ChromePalette* chromePalette READ chromePalette CONSTANT FINAL)
@ -59,9 +61,11 @@ public:
Q_NODISCARD Qt::Alignment titleLabelAlignment() const; Q_NODISCARD Qt::Alignment titleLabelAlignment() const;
void setTitleLabelAlignment(const Qt::Alignment value); void setTitleLabelAlignment(const Qt::Alignment value);
#ifndef Q_OS_MACOS
Q_NODISCARD StandardSystemButton *minimizeButton() const; Q_NODISCARD StandardSystemButton *minimizeButton() const;
Q_NODISCARD StandardSystemButton *maximizeButton() const; Q_NODISCARD StandardSystemButton *maximizeButton() const;
Q_NODISCARD StandardSystemButton *closeButton() const; Q_NODISCARD StandardSystemButton *closeButton() const;
#endif // Q_OS_MACOS
Q_NODISCARD bool isExtended() const; Q_NODISCARD bool isExtended() const;
void setExtended(const bool value); void setExtended(const bool value);

View File

@ -88,15 +88,12 @@ void FramelessHelperQt::addWindow(const SystemParameters &params)
data.eventFilter = new FramelessHelperQt(window); data.eventFilter = new FramelessHelperQt(window);
g_qtHelper()->data.insert(windowId, data); g_qtHelper()->data.insert(windowId, data);
g_qtHelper()->mutex.unlock(); g_qtHelper()->mutex.unlock();
const auto shouldApplyFramelessFlag = [&params]() -> bool { const auto shouldApplyFramelessFlag = []() -> bool {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
const auto widget = params.getWidgetHandle(); return false;
return (widget && widget->isWidgetType());
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
Q_UNUSED(params);
return !Utils::isCustomDecorationSupported(); return !Utils::isCustomDecorationSupported();
#else // Windows #else // Windows
Q_UNUSED(params);
return true; return true;
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
}(); }();

View File

@ -197,10 +197,6 @@ void initialize()
Utils::fixupDialogsDpiScaling(); Utils::fixupDialogsDpiScaling();
#endif #endif
// This attribute is known to be __NOT__ compatible with QGLWidget.
// Please consider migrating to the recommended QOpenGLWidget instead.
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// Enable high DPI scaling by default, but only for Qt5 applications, // Enable high DPI scaling by default, but only for Qt5 applications,
// because this has become the default setting since Qt6 and it can't // because this has become the default setting since Qt6 and it can't

View File

@ -292,6 +292,7 @@ public Q_SLOTS:
oldSetTitlebarAppearsTransparent = reinterpret_cast<setTitlebarAppearsTransparentPtr>(method_setImplementation(method, reinterpret_cast<IMP>(setTitlebarAppearsTransparent))); oldSetTitlebarAppearsTransparent = reinterpret_cast<setTitlebarAppearsTransparentPtr>(method_setImplementation(method, reinterpret_cast<IMP>(setTitlebarAppearsTransparent)));
Q_ASSERT(oldSetTitlebarAppearsTransparent); Q_ASSERT(oldSetTitlebarAppearsTransparent);
#if 0
method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow)); method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow));
Q_ASSERT(method); Q_ASSERT(method);
oldCanBecomeKeyWindow = reinterpret_cast<canBecomeKeyWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeKeyWindow))); oldCanBecomeKeyWindow = reinterpret_cast<canBecomeKeyWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeKeyWindow)));
@ -301,6 +302,7 @@ public Q_SLOTS:
Q_ASSERT(method); Q_ASSERT(method);
oldCanBecomeMainWindow = reinterpret_cast<canBecomeMainWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeMainWindow))); oldCanBecomeMainWindow = reinterpret_cast<canBecomeMainWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeMainWindow)));
Q_ASSERT(oldCanBecomeMainWindow); Q_ASSERT(oldCanBecomeMainWindow);
#endif
method = class_getInstanceMethod(windowClass, @selector(sendEvent:)); method = class_getInstanceMethod(windowClass, @selector(sendEvent:));
Q_ASSERT(method); Q_ASSERT(method);
@ -320,6 +322,7 @@ public Q_SLOTS:
method_setImplementation(method, reinterpret_cast<IMP>(oldSetTitlebarAppearsTransparent)); method_setImplementation(method, reinterpret_cast<IMP>(oldSetTitlebarAppearsTransparent));
oldSetTitlebarAppearsTransparent = nil; oldSetTitlebarAppearsTransparent = nil;
#if 0
method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow)); method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow));
Q_ASSERT(method); Q_ASSERT(method);
method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeKeyWindow)); method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeKeyWindow));
@ -329,6 +332,7 @@ public Q_SLOTS:
Q_ASSERT(method); Q_ASSERT(method);
method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeMainWindow)); method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeMainWindow));
oldCanBecomeMainWindow = nil; oldCanBecomeMainWindow = nil;
#endif
method = class_getInstanceMethod(windowClass, @selector(sendEvent:)); method = class_getInstanceMethod(windowClass, @selector(sendEvent:));
Q_ASSERT(method); Q_ASSERT(method);
@ -487,6 +491,7 @@ private:
oldSendEvent(obj, sel, event); oldSendEvent(obj, sel, event);
} }
#if 0
const auto nswindow = reinterpret_cast<NSWindow *>(obj); const auto nswindow = reinterpret_cast<NSWindow *>(obj);
if (!instances.contains(nswindow)) { if (!instances.contains(nswindow)) {
return; return;
@ -498,12 +503,13 @@ private:
QCoreApplication::processEvents(); QCoreApplication::processEvents();
proxy->lastMouseDownEvent = nil; proxy->lastMouseDownEvent = nil;
} }
#endif
} }
private: private:
QWindow *qwindow = nil; QWindow *qwindow = nil;
NSWindow *nswindow = nil; NSWindow *nswindow = nil;
NSEvent *lastMouseDownEvent = nil; //NSEvent *lastMouseDownEvent = nil;
NSView *blurEffect = nil; NSView *blurEffect = nil;
NSWindowStyleMask oldStyleMask = 0; NSWindowStyleMask oldStyleMask = 0;

View File

@ -79,12 +79,6 @@ const FramelessDialogPrivate *FramelessDialogPrivate::get(const FramelessDialog
void FramelessDialogPrivate::initialize() void FramelessDialogPrivate::initialize()
{ {
Q_Q(FramelessDialog); Q_Q(FramelessDialog);
// Without this flag, Qt will always create an invisible native parent window
// for any native widgets which will intercept some win32 messages and confuse
// our own native event filter, so to prevent some weired bugs from happening,
// just disable this feature.
q->setAttribute(Qt::WA_DontCreateNativeAncestors);
q->setAttribute(Qt::WA_NativeWindow);
FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar(); FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar();
m_sharedHelper = new WidgetsSharedHelper(this); m_sharedHelper = new WidgetsSharedHelper(this);
m_sharedHelper->setup(q); m_sharedHelper->setup(q);

View File

@ -79,12 +79,6 @@ const FramelessMainWindowPrivate *FramelessMainWindowPrivate::get(const Frameles
void FramelessMainWindowPrivate::initialize() void FramelessMainWindowPrivate::initialize()
{ {
Q_Q(FramelessMainWindow); Q_Q(FramelessMainWindow);
// Without this flag, Qt will always create an invisible native parent window
// for any native widgets which will intercept some win32 messages and confuse
// our own native event filter, so to prevent some weired bugs from happening,
// just disable this feature.
q->setAttribute(Qt::WA_DontCreateNativeAncestors);
q->setAttribute(Qt::WA_NativeWindow);
FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar(); FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar();
m_sharedHelper = new WidgetsSharedHelper(this); m_sharedHelper = new WidgetsSharedHelper(this);
m_sharedHelper->setup(q); m_sharedHelper->setup(q);

View File

@ -79,12 +79,6 @@ const FramelessWidgetPrivate *FramelessWidgetPrivate::get(const FramelessWidget
void FramelessWidgetPrivate::initialize() void FramelessWidgetPrivate::initialize()
{ {
Q_Q(FramelessWidget); Q_Q(FramelessWidget);
// Without this flag, Qt will always create an invisible native parent window
// for any native widgets which will intercept some win32 messages and confuse
// our own native event filter, so to prevent some weired bugs from happening,
// just disable this feature.
q->setAttribute(Qt::WA_DontCreateNativeAncestors);
q->setAttribute(Qt::WA_NativeWindow);
FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar(); FramelessWidgetsHelper::get(q)->extendsContentIntoTitleBar();
m_sharedHelper = new WidgetsSharedHelper(this); m_sharedHelper = new WidgetsSharedHelper(this);
m_sharedHelper->setup(q); m_sharedHelper->setup(q);

View File

@ -30,6 +30,7 @@
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qevent.h> #include <QtGui/qevent.h>
#include <QtGui/qfontmetrics.h>
#include <QtWidgets/qboxlayout.h> #include <QtWidgets/qboxlayout.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -132,6 +133,32 @@ ChromePalette *StandardTitleBarPrivate::chromePalette() const
return m_chromePalette; return m_chromePalette;
} }
QFont StandardTitleBarPrivate::defaultFont() const
{
Q_Q(const StandardTitleBar);
QFont font = q->font();
font.setPointSize(kDefaultTitleBarFontPointSize);
return font;
}
StandardTitleBarPrivate::FontMetrics StandardTitleBarPrivate::titleLabelSize() const
{
if (!m_window) {
return {};
}
const QString text = m_window->windowTitle();
if (text.isEmpty()) {
return {};
}
const QFont font = m_titleFont.value_or(defaultFont());
const QFontMetrics fontMetrics(font);
return {
fontMetrics.horizontalAdvance(text),
fontMetrics.height(),
fontMetrics.ascent()
};
}
void StandardTitleBarPrivate::paintTitleBar(QPaintEvent *event) void StandardTitleBarPrivate::paintTitleBar(QPaintEvent *event)
{ {
Q_ASSERT(event); Q_ASSERT(event);
@ -154,41 +181,35 @@ void StandardTitleBarPrivate::paintTitleBar(QPaintEvent *event)
painter.setRenderHints(QPainter::Antialiasing | painter.setRenderHints(QPainter::Antialiasing |
QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
painter.fillRect(QRect(QPoint(0, 0), q->size()), backgroundColor); painter.fillRect(QRect(QPoint(0, 0), q->size()), backgroundColor);
int titleLabelLeftOffset = 0;
if (m_windowIconVisible) { if (m_windowIconVisible) {
const QIcon icon = m_window->windowIcon(); const QIcon icon = m_window->windowIcon();
if (!icon.isNull()) { if (!icon.isNull()) {
const QRect rect = windowIconRect(); icon.paint(&painter, windowIconRect());
titleLabelLeftOffset = (rect.left() + rect.width());
icon.paint(&painter, rect);
} }
} }
if (m_titleLabelVisible) { if (m_titleLabelVisible) {
const QString text = m_window->windowTitle(); const QString text = m_window->windowTitle();
if (!text.isEmpty()) { if (!text.isEmpty()) {
painter.setPen(foregroundColor); painter.setPen(foregroundColor);
painter.setFont(m_titleFont.value_or([q]() -> QFont { painter.setFont(m_titleFont.value_or(defaultFont()));
QFont f = q->font(); const auto pos = [this, q]() -> QPoint {
f.setPointSize(kDefaultTitleBarFontPointSize); const FontMetrics labelSize = titleLabelSize();
return f; const int titleBarWidth = q->width();
}())); int x = 0;
const auto rect = [this, q, titleLabelLeftOffset]() -> QRect {
const int w = q->width();
int leftMargin = 0;
int rightMargin = 0;
if (m_labelAlignment & Qt::AlignLeft) { if (m_labelAlignment & Qt::AlignLeft) {
leftMargin = (kDefaultTitleBarContentsMargin + titleLabelLeftOffset); x = (windowIconRect().right() + kDefaultTitleBarContentsMargin);
} else if (m_labelAlignment & Qt::AlignRight) {
x = (titleBarWidth - kDefaultTitleBarContentsMargin - labelSize.width);
#ifndef Q_OS_MACOS
x -= m_minimizeButton->x();
#endif // Q_OS_MACOS
} else {
x = std::round(qreal(titleBarWidth - labelSize.width) / qreal(2));
} }
if (m_labelAlignment & Qt::AlignRight) { const int y = std::round((qreal(q->height() - labelSize.height) / qreal(2)) + qreal(labelSize.baseline));
rightMargin = (w - m_minimizeButton->x() + kDefaultTitleBarContentsMargin); return {x, y};
}
const int x = leftMargin;
const int y = 0;
const int width = (w - leftMargin - rightMargin);
const int height = q->height();
return {QPoint(x, y), QSize(width, height)};
}(); }();
painter.drawText(rect, int(m_labelAlignment), text); painter.drawText(pos, text);
} }
} }
painter.restore(); painter.restore();
@ -242,6 +263,10 @@ void StandardTitleBarPrivate::setWindowIconVisible(const bool value)
return; return;
} }
m_windowIconVisible = value; m_windowIconVisible = value;
Q_Q(StandardTitleBar);
q->update();
Q_EMIT q->windowIconVisibleChanged();
#ifndef Q_OS_MACOS
// Ideally we should use FramelessWidgetsHelper::get(this) everywhere, but sadly when // Ideally we should use FramelessWidgetsHelper::get(this) everywhere, but sadly when
// we call it here, it may be too early that FramelessWidgetsHelper has not attached // we call it here, it may be too early that FramelessWidgetsHelper has not attached
// to the top level widget yet, and thus it will trigger an assert error (the assert // to the top level widget yet, and thus it will trigger an assert error (the assert
@ -250,9 +275,7 @@ void StandardTitleBarPrivate::setWindowIconVisible(const bool value)
// NOTE: In your own code, you should always use FramelessWidgetsHelper::get(this) // NOTE: In your own code, you should always use FramelessWidgetsHelper::get(this)
// if possible. // if possible.
FramelessWidgetsHelper::get(m_window)->setHitTestVisible(windowIconRect(), windowIconVisible_real()); FramelessWidgetsHelper::get(m_window)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
Q_Q(StandardTitleBar); #endif // Q_OS_MACOS
q->update();
Q_EMIT q->windowIconVisibleChanged();
} }
QFont StandardTitleBarPrivate::titleFont() const QFont StandardTitleBarPrivate::titleFont() const
@ -273,17 +296,21 @@ void StandardTitleBarPrivate::setTitleFont(const QFont &value)
bool StandardTitleBarPrivate::mouseEventHandler(QMouseEvent *event) bool StandardTitleBarPrivate::mouseEventHandler(QMouseEvent *event)
{ {
#ifdef Q_OS_MACOS
Q_UNUSED(event);
return false;
#else // !Q_OS_MACOS
Q_ASSERT(event); Q_ASSERT(event);
if (!event) { if (!event) {
return false; return false;
} }
Q_Q(const StandardTitleBar); Q_Q(const StandardTitleBar);
const Qt::MouseButton button = event->button(); const Qt::MouseButton button = event->button();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint(); const QPoint scenePos = event->scenePosition().toPoint();
#else # else // (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->windowPos().toPoint(); const QPoint scenePos = event->windowPos().toPoint();
#endif # endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const bool interestArea = isInTitleBarIconArea(scenePos); const bool interestArea = isInTitleBarIconArea(scenePos);
switch (event->type()) { switch (event->type()) {
case QEvent::MouseButtonRelease: case QEvent::MouseButtonRelease:
@ -336,14 +363,31 @@ bool StandardTitleBarPrivate::mouseEventHandler(QMouseEvent *event)
break; break;
} }
return false; return false;
#endif // Q_OS_MACOS
} }
QRect StandardTitleBarPrivate::windowIconRect() const QRect StandardTitleBarPrivate::windowIconRect() const
{ {
Q_Q(const StandardTitleBar); Q_Q(const StandardTitleBar);
const QSize size = windowIconSize(); const QSize size = windowIconSize();
#ifdef Q_OS_MACOS
const auto x = [this, q, &size]() -> int {
if (m_labelAlignment & Qt::AlignLeft) {
return (kMacOSChromeButtonAreaWidth + kDefaultTitleBarContentsMargin);
}
const int titleBarWidth = q->width();
const int labelWidth = titleLabelSize().width;
if (m_labelAlignment & Qt::AlignRight) {
return (titleBarWidth - labelWidth - kDefaultTitleBarContentsMargin - size.width());
}
const int centeredX = std::round(qreal(titleBarWidth - labelWidth) / qreal(2));
return (centeredX - kDefaultTitleBarContentsMargin - size.width());
}();
#else // !Q_OS_MACOS
const int x = kDefaultTitleBarContentsMargin;
#endif // Q_OS_MACOS
const int y = std::round(qreal(q->height() - size.height()) / qreal(2)); const int y = std::round(qreal(q->height() - size.height()) / qreal(2));
return {QPoint(kDefaultTitleBarContentsMargin, y), size}; return {QPoint(x, y), size};
} }
bool StandardTitleBarPrivate::windowIconVisible_real() const bool StandardTitleBarPrivate::windowIconVisible_real() const
@ -361,9 +405,11 @@ bool StandardTitleBarPrivate::isInTitleBarIconArea(const QPoint &pos) const
void StandardTitleBarPrivate::updateMaximizeButton() void StandardTitleBarPrivate::updateMaximizeButton()
{ {
#ifndef Q_OS_MACOS
const bool max = m_window->isMaximized(); const bool max = m_window->isMaximized();
m_maximizeButton->setButtonType(max ? SystemButtonType::Restore : SystemButtonType::Maximize); m_maximizeButton->setButtonType(max ? SystemButtonType::Restore : SystemButtonType::Maximize);
m_maximizeButton->setToolTip(max ? tr("Restore") : tr("Maximize")); m_maximizeButton->setToolTip(max ? tr("Restore") : tr("Maximize"));
#endif // Q_OS_MACOS
} }
void StandardTitleBarPrivate::updateTitleBarColor() void StandardTitleBarPrivate::updateTitleBarColor()
@ -374,6 +420,7 @@ void StandardTitleBarPrivate::updateTitleBarColor()
void StandardTitleBarPrivate::updateChromeButtonColor() void StandardTitleBarPrivate::updateChromeButtonColor()
{ {
#ifndef Q_OS_MACOS
const bool active = m_window->isActiveWindow(); const bool active = m_window->isActiveWindow();
const QColor activeForeground = m_chromePalette->titleBarActiveForegroundColor(); const QColor activeForeground = m_chromePalette->titleBarActiveForegroundColor();
const QColor inactiveForeground = m_chromePalette->titleBarInactiveForegroundColor(); const QColor inactiveForeground = m_chromePalette->titleBarInactiveForegroundColor();
@ -398,13 +445,16 @@ void StandardTitleBarPrivate::updateChromeButtonColor()
m_closeButton->setHoverColor(m_chromePalette->closeButtonHoverColor()); m_closeButton->setHoverColor(m_chromePalette->closeButtonHoverColor());
m_closeButton->setPressColor(m_chromePalette->closeButtonPressColor()); m_closeButton->setPressColor(m_chromePalette->closeButtonPressColor());
m_closeButton->setActive(active); m_closeButton->setActive(active);
#endif // Q_OS_MACOS
} }
void StandardTitleBarPrivate::retranslateUi() void StandardTitleBarPrivate::retranslateUi()
{ {
#ifndef Q_OS_MACOS
m_minimizeButton->setToolTip(tr("Minimize")); m_minimizeButton->setToolTip(tr("Minimize"));
m_maximizeButton->setToolTip(m_window->isMaximized() ? tr("Restore") : tr("Maximize")); m_maximizeButton->setToolTip(m_window->isMaximized() ? tr("Restore") : tr("Maximize"));
m_closeButton->setToolTip(tr("Close")); m_closeButton->setToolTip(tr("Close"));
#endif // Q_OS_MACOS
} }
bool StandardTitleBarPrivate::eventFilter(QObject *object, QEvent *event) bool StandardTitleBarPrivate::eventFilter(QObject *object, QEvent *event)
@ -457,6 +507,13 @@ void StandardTitleBarPrivate::initialize()
Q_UNUSED(title); Q_UNUSED(title);
q->update(); q->update();
}); });
#ifdef Q_OS_MACOS
const auto titleBarLayout = new QHBoxLayout(q);
titleBarLayout->setSpacing(0);
titleBarLayout->setContentsMargins(0, 0, 0, 0);
q->setLayout(titleBarLayout);
setTitleLabelAlignment(Qt::AlignCenter);
#else // !Q_OS_MACOS
m_minimizeButton = new StandardSystemButton(SystemButtonType::Minimize, q); m_minimizeButton = new StandardSystemButton(SystemButtonType::Minimize, q);
connect(m_minimizeButton, &StandardSystemButton::clicked, m_window, &QWidget::showMinimized); connect(m_minimizeButton, &StandardSystemButton::clicked, m_window, &QWidget::showMinimized);
m_maximizeButton = new StandardSystemButton(SystemButtonType::Maximize, q); m_maximizeButton = new StandardSystemButton(SystemButtonType::Maximize, q);
@ -497,6 +554,7 @@ void StandardTitleBarPrivate::initialize()
titleBarLayout->addLayout(systemButtonsOuterLayout); titleBarLayout->addLayout(systemButtonsOuterLayout);
q->setLayout(titleBarLayout); q->setLayout(titleBarLayout);
setTitleLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter); setTitleLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
#endif // Q_OS_MACOS
retranslateUi(); retranslateUi();
updateTitleBarColor(); updateTitleBarColor();
updateChromeButtonColor(); updateChromeButtonColor();
@ -522,6 +580,7 @@ void StandardTitleBar::setTitleLabelAlignment(const Qt::Alignment value)
d->setTitleLabelAlignment(value); d->setTitleLabelAlignment(value);
} }
#ifndef Q_OS_MACOS
StandardSystemButton *StandardTitleBar::minimizeButton() const StandardSystemButton *StandardTitleBar::minimizeButton() const
{ {
Q_D(const StandardTitleBar); Q_D(const StandardTitleBar);
@ -539,6 +598,7 @@ StandardSystemButton *StandardTitleBar::closeButton() const
Q_D(const StandardTitleBar); Q_D(const StandardTitleBar);
return d->m_closeButton; return d->m_closeButton;
} }
#endif // Q_OS_MACOS
bool StandardTitleBar::isExtended() const bool StandardTitleBar::isExtended() const
{ {