From f700b07e5ca70c533df66cd32a4e010e0d048b43 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Thu, 17 Mar 2022 16:48:57 +0800 Subject: [PATCH] wip Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- core/framelesshelper_qt.cpp | 21 +-- core/framelesshelper_qt.h | 4 +- core/framelesshelper_win.cpp | 10 +- core/framelesswindowsmanager.cpp | 98 ++++++-------- core/framelesswindowsmanager.h | 10 +- core/framelesswindowsmanager_p.h | 17 +-- core/utils.cpp | 24 +++- core/utils.h | 11 +- examples/CMakeLists.txt | 4 +- examples/mainwindow/CMakeLists.txt | 1 + examples/mainwindow/MainWindow.ui | 4 - examples/mainwindow/TitleBar.ui | 17 +-- examples/mainwindow/mainwindow.cpp | 172 +++++-------------------- examples/mainwindow/mainwindow.h | 30 ++--- examples/quick/CMakeLists.txt | 2 + examples/quick/main.cpp | 24 +--- examples/quick/qml/CloseButton.qml | 2 +- examples/quick/qml/MainWindow.qml | 2 +- examples/quick/qml/MaximizeButton.qml | 4 +- examples/quick/qml/MinimizeButton.qml | 2 +- examples/widget/widget.cpp | 7 +- quick/CMakeLists.txt | 2 + quick/framelesshelperimageprovider.cpp | 112 ++++++++++++++++ quick/framelesshelperimageprovider.h | 44 +++++++ quick/framelessquickhelper.cpp | 30 ++++- quick/framelessquickhelper.h | 3 + widgets/CMakeLists.txt | 2 + widgets/framelessmainwindow.cpp | 93 +++++++++++++ widgets/framelessmainwindow.h | 68 ++++++++++ widgets/framelesswidgetshelper.cpp | 67 +++++++--- widgets/framelesswidgetshelper.h | 1 + 31 files changed, 565 insertions(+), 323 deletions(-) create mode 100644 quick/framelesshelperimageprovider.cpp create mode 100644 quick/framelesshelperimageprovider.h create mode 100644 widgets/framelessmainwindow.cpp create mode 100644 widgets/framelessmainwindow.h diff --git a/core/framelesshelper_qt.cpp b/core/framelesshelper_qt.cpp index 0f2596f..c6d6982 100644 --- a/core/framelesshelper_qt.cpp +++ b/core/framelesshelper_qt.cpp @@ -33,7 +33,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE struct QtHelper { QMutex mutex = {}; - QWindowList acceptableWindows = {}; + QHash qtFramelessHelpers = {}; explicit QtHelper() = default; ~QtHelper() = default; @@ -55,14 +55,16 @@ void FramelessHelperQt::addWindow(QWindow *window) return; } g_qtHelper()->mutex.lock(); - if (g_qtHelper()->acceptableWindows.contains(window)) { + if (g_qtHelper()->qtFramelessHelpers.contains(window)) { g_qtHelper()->mutex.unlock(); return; } - g_qtHelper()->acceptableWindows.append(window); + // Give it a parent so that it can be deleted even if we forget to do so. + const auto qtFramelessHelper = new FramelessHelperQt(window); + g_qtHelper()->qtFramelessHelpers.insert(window, qtFramelessHelper); g_qtHelper()->mutex.unlock(); window->setFlags(window->flags() | Qt::FramelessWindowHint); - window->installEventFilter(this); + window->installEventFilter(qtFramelessHelper); } void FramelessHelperQt::removeWindow(QWindow *window) @@ -72,13 +74,16 @@ void FramelessHelperQt::removeWindow(QWindow *window) return; } g_qtHelper()->mutex.lock(); - if (!g_qtHelper()->acceptableWindows.contains(window)) { + if (!g_qtHelper()->qtFramelessHelpers.contains(window)) { g_qtHelper()->mutex.unlock(); return; } - g_qtHelper()->acceptableWindows.removeAll(window); + FramelessHelperQt *qtFramelessHelper = g_qtHelper()->qtFramelessHelpers.value(window); + g_qtHelper()->qtFramelessHelpers.remove(window); g_qtHelper()->mutex.unlock(); - window->removeEventFilter(this); + window->removeEventFilter(qtFramelessHelper); + delete qtFramelessHelper; + qtFramelessHelper = nullptr; window->setFlags(window->flags() & ~Qt::FramelessWindowHint); } @@ -100,7 +105,7 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event) } const auto window = qobject_cast(object); g_qtHelper()->mutex.lock(); - if (!g_qtHelper()->acceptableWindows.contains(window)) { + if (!g_qtHelper()->qtFramelessHelpers.contains(window)) { g_qtHelper()->mutex.unlock(); return false; } diff --git a/core/framelesshelper_qt.h b/core/framelesshelper_qt.h index f35f290..a38618a 100644 --- a/core/framelesshelper_qt.h +++ b/core/framelesshelper_qt.h @@ -42,8 +42,8 @@ public: explicit FramelessHelperQt(QObject *parent = nullptr); ~FramelessHelperQt() override; - void addWindow(QWindow *window); - void removeWindow(QWindow *window); + static void addWindow(QWindow *window); + static void removeWindow(QWindow *window); protected: Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override; diff --git a/core/framelesshelper_win.cpp b/core/framelesshelper_win.cpp index 59ff229..02e21b4 100644 --- a/core/framelesshelper_win.cpp +++ b/core/framelesshelper_win.cpp @@ -39,7 +39,7 @@ struct Win32Helper { QMutex mutex = {}; QScopedPointer nativeEventFilter; - QWindowList acceptableWindows = {}; + QWindowList framelessWindows = {}; QHash windowMapping = {}; QHash qtWindowProcs = {}; @@ -187,11 +187,11 @@ void FramelessHelperWin::addWindow(QWindow *window) return; } g_win32Helper()->mutex.lock(); - if (g_win32Helper()->acceptableWindows.contains(window)) { + if (g_win32Helper()->framelessWindows.contains(window)) { g_win32Helper()->mutex.unlock(); return; } - g_win32Helper()->acceptableWindows.append(window); + g_win32Helper()->framelessWindows.append(window); const WId winId = window->winId(); g_win32Helper()->windowMapping.insert(winId, window); if (g_win32Helper()->nativeEventFilter.isNull()) { @@ -216,11 +216,11 @@ void FramelessHelperWin::removeWindow(QWindow *window) return; } g_win32Helper()->mutex.lock(); - if (!g_win32Helper()->acceptableWindows.contains(window)) { + if (!g_win32Helper()->framelessWindows.contains(window)) { g_win32Helper()->mutex.unlock(); return; } - g_win32Helper()->acceptableWindows.removeAll(window); + g_win32Helper()->framelessWindows.removeAll(window); const WId winId = window->winId(); g_win32Helper()->windowMapping.remove(winId); g_win32Helper()->mutex.unlock(); diff --git a/core/framelesswindowsmanager.cpp b/core/framelesswindowsmanager.cpp index ec9c96c..04402ef 100644 --- a/core/framelesswindowsmanager.cpp +++ b/core/framelesswindowsmanager.cpp @@ -40,32 +40,19 @@ static const bool g_usePureQtImplementation = (qEnvironmentVariableIntValue("FRA static constexpr const bool g_usePureQtImplementation = true; #endif -Q_GLOBAL_STATIC(FramelessManagerPrivate, g_managerPrivate) +Q_GLOBAL_STATIC(FramelessWindowsManager, g_manager) -FramelessManagerPrivate::FramelessManagerPrivate() = default; - -FramelessManagerPrivate::~FramelessManagerPrivate() +FramelessWindowsManagerPrivate::FramelessWindowsManagerPrivate(FramelessWindowsManager *q) { - QMutexLocker locker(&mutex); - if (!qtFramelessHelpers.isEmpty()) { - auto it = qtFramelessHelpers.constBegin(); - while (it != qtFramelessHelpers.constEnd()) { - const auto helper = it.value(); - if (helper) { - delete helper; - } - ++it; - } - qtFramelessHelpers.clear(); + Q_ASSERT(q); + if (q) { + q_ptr = q; } } -FramelessManagerPrivate *FramelessManagerPrivate::instance() -{ - return g_managerPrivate(); -} +FramelessWindowsManagerPrivate::~FramelessWindowsManagerPrivate() = default; -QUuid FramelessManagerPrivate::findIdByWindow(QWindow *value) const +QUuid FramelessWindowsManagerPrivate::findIdByWindow(QWindow *value) const { Q_ASSERT(value); if (!value) { @@ -81,7 +68,7 @@ QUuid FramelessManagerPrivate::findIdByWindow(QWindow *value) const return windowMapping.value(value); } -QUuid FramelessManagerPrivate::findIdByWinId(const WId value) const +QUuid FramelessWindowsManagerPrivate::findIdByWinId(const WId value) const { Q_ASSERT(value); if (!value) { @@ -97,7 +84,7 @@ QUuid FramelessManagerPrivate::findIdByWinId(const WId value) const return winIdMapping.value(value); } -QWindow *FramelessManagerPrivate::findWindowById(const QUuid &value) const +QWindow *FramelessWindowsManagerPrivate::findWindowById(const QUuid &value) const { Q_ASSERT(!value.isNull()); if (value.isNull()) { @@ -117,7 +104,7 @@ QWindow *FramelessManagerPrivate::findWindowById(const QUuid &value) const return nullptr; } -WId FramelessManagerPrivate::findWinIdById(const QUuid &value) const +WId FramelessWindowsManagerPrivate::findWinIdById(const QUuid &value) const { Q_ASSERT(!value.isNull()); if (value.isNull()) { @@ -127,15 +114,16 @@ WId FramelessManagerPrivate::findWinIdById(const QUuid &value) const return (window ? window->winId() : 0); } -Q_GLOBAL_STATIC(FramelessWindowsManager, g_managerPublic) - -FramelessWindowsManager::FramelessWindowsManager(QObject *parent) : QObject(parent) {} +FramelessWindowsManager::FramelessWindowsManager(QObject *parent) : QObject(parent) +{ + d_ptr.reset(new FramelessWindowsManagerPrivate(this)); +} FramelessWindowsManager::~FramelessWindowsManager() = default; FramelessWindowsManager *FramelessWindowsManager::instance() { - return g_managerPublic(); + return g_manager(); } void FramelessWindowsManager::addWindow(QWindow *window) @@ -144,20 +132,15 @@ void FramelessWindowsManager::addWindow(QWindow *window) if (!window) { return; } - g_managerPrivate()->mutex.lock(); - if (g_managerPrivate()->windowMapping.contains(window)) { - g_managerPrivate()->mutex.unlock(); + Q_D(FramelessWindowsManager); + d->mutex.lock(); + if (d->windowMapping.contains(window)) { + d->mutex.unlock(); return; } const QUuid uuid = QUuid::createUuid(); - g_managerPrivate()->windowMapping.insert(window, uuid); - g_managerPrivate()->winIdMapping.insert(window->winId(), uuid); - FramelessHelperQt *qtFramelessHelper = nullptr; - if (g_usePureQtImplementation) { - // Give it a parent so that it can be deleted even if we forget to do. - qtFramelessHelper = new FramelessHelperQt(window); - g_managerPrivate()->qtFramelessHelpers.insert(uuid, qtFramelessHelper); - } + d->windowMapping.insert(window, uuid); + d->winIdMapping.insert(window->winId(), uuid); #ifdef Q_OS_WINDOWS if (!g_usePureQtImplementation) { // Work-around Win32 multi-monitor artifacts. @@ -173,15 +156,12 @@ void FramelessWindowsManager::addWindow(QWindow *window) // observed disappeared indeed, amazingly. window->resize(window->size()); }); - g_managerPrivate()->win32WorkaroundConnections.insert(uuid, workaroundConnection); + d->win32WorkaroundConnections.insert(uuid, workaroundConnection); } #endif - g_managerPrivate()->mutex.unlock(); + d->mutex.unlock(); if (g_usePureQtImplementation) { - Q_ASSERT(qtFramelessHelper); - if (qtFramelessHelper) { - qtFramelessHelper->addWindow(window); - } + FramelessHelperQt::addWindow(window); } #ifdef Q_OS_WINDOWS if (!g_usePureQtImplementation) { @@ -196,32 +176,30 @@ void FramelessWindowsManager::removeWindow(QWindow *window) if (!window) { return; } - QMutexLocker locker(&g_managerPrivate()->mutex); - if (!g_managerPrivate()->windowMapping.contains(window)) { + Q_D(FramelessWindowsManager); + QMutexLocker locker(&d->mutex); + if (!d->windowMapping.contains(window)) { return; } - const QUuid uuid = g_managerPrivate()->windowMapping.value(window); + const QUuid uuid = d->windowMapping.value(window); Q_ASSERT(!uuid.isNull()); if (uuid.isNull()) { return; } - if (g_managerPrivate()->qtFramelessHelpers.contains(uuid)) { - const auto helper = g_managerPrivate()->qtFramelessHelpers.value(uuid); - Q_ASSERT(helper); - if (helper) { - helper->removeWindow(window); - delete helper; - } - g_managerPrivate()->qtFramelessHelpers.remove(uuid); + if (g_usePureQtImplementation) { + FramelessHelperQt::removeWindow(window); } #ifdef Q_OS_WINDOWS - if (g_managerPrivate()->win32WorkaroundConnections.contains(uuid)) { - disconnect(g_managerPrivate()->win32WorkaroundConnections.value(uuid)); - g_managerPrivate()->win32WorkaroundConnections.remove(uuid); + if (!g_usePureQtImplementation) { + FramelessHelperWin::removeWindow(window); + } + if (d->win32WorkaroundConnections.contains(uuid)) { + disconnect(d->win32WorkaroundConnections.value(uuid)); + d->win32WorkaroundConnections.remove(uuid); } #endif - g_managerPrivate()->windowMapping.remove(window); - g_managerPrivate()->winIdMapping.remove(window->winId()); + d->windowMapping.remove(window); + d->winIdMapping.remove(window->winId()); } FRAMELESSHELPER_END_NAMESPACE diff --git a/core/framelesswindowsmanager.h b/core/framelesswindowsmanager.h index 4d0307b..3ef391e 100644 --- a/core/framelesswindowsmanager.h +++ b/core/framelesswindowsmanager.h @@ -33,9 +33,12 @@ QT_END_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE +class FramelessWindowsManagerPrivate; + class FRAMELESSHELPER_CORE_API FramelessWindowsManager : public QObject { Q_OBJECT + Q_DECLARE_PRIVATE(FramelessWindowsManager) Q_DISABLE_COPY_MOVE(FramelessWindowsManager) public: @@ -44,12 +47,15 @@ public: Q_NODISCARD static FramelessWindowsManager *instance(); - static void addWindow(QWindow *window); - static void removeWindow(QWindow *window); + void addWindow(QWindow *window); + void removeWindow(QWindow *window); Q_SIGNALS: void systemThemeChanged(); void systemMenuRequested(const QPointF &); + +private: + QScopedPointer d_ptr; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/core/framelesswindowsmanager_p.h b/core/framelesswindowsmanager_p.h index 630bd6c..b243c62 100644 --- a/core/framelesswindowsmanager_p.h +++ b/core/framelesswindowsmanager_p.h @@ -32,19 +32,16 @@ FRAMELESSHELPER_BEGIN_NAMESPACE -class FramelessHelperQt; +class FramelessWindowsManager; -class FRAMELESSHELPER_CORE_API FramelessManagerPrivate +class FRAMELESSHELPER_CORE_API FramelessWindowsManagerPrivate { - Q_DISABLE_COPY_MOVE(FramelessManagerPrivate) - - friend class FramelessWindowsManager; + Q_DECLARE_PUBLIC(FramelessWindowsManager) + Q_DISABLE_COPY_MOVE(FramelessWindowsManagerPrivate) public: - explicit FramelessManagerPrivate(); - ~FramelessManagerPrivate(); - - [[nodiscard]] static FramelessManagerPrivate *instance(); + explicit FramelessWindowsManagerPrivate(FramelessWindowsManager *q); + ~FramelessWindowsManagerPrivate(); [[nodiscard]] QUuid findIdByWindow(QWindow *value) const; [[nodiscard]] QUuid findIdByWinId(const WId value) const; @@ -53,10 +50,10 @@ public: [[nodiscard]] WId findWinIdById(const QUuid &value) const; private: + FramelessWindowsManager *q_ptr = nullptr; mutable QMutex mutex = {}; QHash windowMapping = {}; QHash winIdMapping = {}; - QHash qtFramelessHelpers = {}; #ifdef Q_OS_WINDOWS QHash win32WorkaroundConnections = {}; #endif diff --git a/core/utils.cpp b/core/utils.cpp index 7269a0f..f7c3dfc 100644 --- a/core/utils.cpp +++ b/core/utils.cpp @@ -23,6 +23,7 @@ */ #include "utils.h" +#include #include // The "Q_INIT_RESOURCE()" macro can't be used within a namespace, @@ -36,7 +37,7 @@ static inline void initResource() FRAMELESSHELPER_BEGIN_NAMESPACE -static const QString kResourcePrefix = QStringLiteral(":/org.wangwenx190.FramelessHelper/images"); +static const QString kImageResourcePrefix = QStringLiteral(":/org.wangwenx190.FramelessHelper/images"); Qt::CursorShape Utils::calculateCursorShape(const QWindow *window, const QPointF &pos) { @@ -103,11 +104,12 @@ bool Utils::isWindowFixedSize(const QWindow *window) return (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)); } -QIcon Utils::getSystemButtonIcon(const SystemButtonType type, const SystemTheme theme) +QVariant Utils::getSystemButtonIconResource + (const SystemButtonType button, const SystemTheme theme, const ResourceType type) { - const QString resourcePath = [type, theme]() -> QString { - const QString szType = [type]() -> QString { - switch (type) { + const QString resourceUri = [button, theme]() -> QString { + const QString szButton = [button]() -> QString { + switch (button) { case SystemButtonType::WindowIcon: break; case SystemButtonType::Minimize: @@ -134,10 +136,18 @@ QIcon Utils::getSystemButtonIcon(const SystemButtonType type, const SystemTheme } return {}; }(); - return QStringLiteral("%1/%2/chrome-%3.svg").arg(kResourcePrefix, szTheme, szType); + return QStringLiteral("%1/%2/chrome-%3.svg").arg(kImageResourcePrefix, szTheme, szButton); }(); initResource(); - return QIcon(resourcePath); + switch (type) { + case ResourceType::Image: + return QImage(resourceUri); + case ResourceType::Pixmap: + return QPixmap(resourceUri); + case ResourceType::Icon: + return QIcon(resourceUri); + } + return {}; } FRAMELESSHELPER_END_NAMESPACE diff --git a/core/utils.h b/core/utils.h index 1e11dbb..24a5a81 100644 --- a/core/utils.h +++ b/core/utils.h @@ -48,6 +48,14 @@ enum class SystemButtonType : int }; Q_ENUM_NS(SystemButtonType) +enum class ResourceType : int +{ + Image = 0, + Pixmap = 1, + Icon = 2 +}; +Q_ENUM_NS(ResourceType) + #ifdef Q_OS_WINDOWS enum class DwmColorizationArea : int { @@ -67,7 +75,8 @@ namespace Utils FRAMELESSHELPER_CORE_API void startSystemMove(QWindow *window); FRAMELESSHELPER_CORE_API void startSystemResize(QWindow *window, const Qt::Edges edges); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowFixedSize(const QWindow *window); -[[nodiscard]] FRAMELESSHELPER_CORE_API QIcon getSystemButtonIcon(const SystemButtonType type, const SystemTheme theme); +[[nodiscard]] FRAMELESSHELPER_CORE_API QVariant getSystemButtonIconResource + (const SystemButtonType button, const SystemTheme theme, const ResourceType type); #ifdef Q_OS_WINDOWS [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater(); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 062774f..10247f8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,8 +1,8 @@ if(TARGET Qt${QT_VERSION_MAJOR}::Widgets) add_subdirectory(widget) - #add_subdirectory(mainwindow) + add_subdirectory(mainwindow) endif() if(TARGET Qt${QT_VERSION_MAJOR}::Quick) - #add_subdirectory(quick) + add_subdirectory(quick) endif() diff --git a/examples/mainwindow/CMakeLists.txt b/examples/mainwindow/CMakeLists.txt index 9ed048f..26de8b3 100644 --- a/examples/mainwindow/CMakeLists.txt +++ b/examples/mainwindow/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(MainWindow WIN32 ${SOURCES}) target_link_libraries(MainWindow PRIVATE Qt${QT_VERSION_MAJOR}::Widgets + FramelessHelperCore FramelessHelperWidgets ) diff --git a/examples/mainwindow/MainWindow.ui b/examples/mainwindow/MainWindow.ui index e2aa7a7..31b21df 100644 --- a/examples/mainwindow/MainWindow.ui +++ b/examples/mainwindow/MainWindow.ui @@ -13,10 +13,6 @@ Hello, World! - - - ../example.ico../example.ico - diff --git a/examples/mainwindow/TitleBar.ui b/examples/mainwindow/TitleBar.ui index 49484ea..d2aa2b1 100644 --- a/examples/mainwindow/TitleBar.ui +++ b/examples/mainwindow/TitleBar.ui @@ -179,10 +179,6 @@ Minimize - - - :/images/dark/chrome-minimize.svg:/images/dark/chrome-minimize.svg - 16 @@ -220,11 +216,6 @@ Maximize - - - :/images/dark/chrome-maximize.svg - :/images/dark/chrome-restore.svg:/images/dark/chrome-maximize.svg - 16 @@ -265,10 +256,6 @@ Close - - - :/images/dark/chrome-close.svg:/images/dark/chrome-close.svg - 16 @@ -280,8 +267,6 @@ - - - + diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index e0ce948..8169bc2 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -23,171 +23,61 @@ */ #include "mainwindow.h" -#include -#include -#include -#include +#include "ui_MainWindow.h" +#include "ui_TitleBar.h" -FRAMELESSHELPER_USE_NAMESPACE - -MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) +MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : FramelessMainWindow(parent, flags) { - setAttribute(Qt::WA_DontCreateNativeAncestors); - createWinId(); setupUi(); } MainWindow::~MainWindow() { - if (titleBarWidget) { - delete titleBarWidget; - titleBarWidget = nullptr; + if (titleBar) { + delete titleBar; + titleBar = nullptr; } - if (appMainWindow) { - delete appMainWindow; - appMainWindow = nullptr; - } -} - -void MainWindow::showEvent(QShowEvent *event) -{ - QMainWindow::showEvent(event); - initFramelessHelperOnce(); -} - -void MainWindow::changeEvent(QEvent *event) -{ - QMainWindow::changeEvent(event); - bool shouldUpdate = false; - if (event->type() == QEvent::WindowStateChange) { -#ifdef Q_OS_WINDOWS - if (Utilities::isWindowFrameBorderVisible()) { - if (isMaximized() || isFullScreen()) { - setContentsMargins(0, 0, 0, 0); - } else if (!isMinimized()) { - resetContentsMargins(); - } - } -#endif - shouldUpdate = true; - Q_EMIT windowStateChanged(); - } else if (event->type() == QEvent::ActivationChange) { - shouldUpdate = true; - } - if (shouldUpdate) { - update(); - } -} - -void MainWindow::initFramelessHelperOnce() -{ - if (m_inited) { - return; - } - m_inited = true; - FramelessWindowsManager::addWindow(windowHandle()); -} - -void MainWindow::resetContentsMargins() -{ -#ifdef Q_OS_WINDOWS - if (Utilities::isWindowFrameBorderVisible()) { - setContentsMargins(0, 1, 0, 0); - } -#endif -} - -void MainWindow::paintEvent(QPaintEvent *event) -{ - QMainWindow::paintEvent(event); -#ifdef Q_OS_WINDOWS - if ((windowState() == Qt::WindowNoState) && Utilities::isWindowFrameBorderVisible() && !Utilities::isWin11OrGreater()) { - QPainter painter(this); - painter.save(); - QPen pen = {}; - pen.setColor(Utilities::getFrameBorderColor(isActiveWindow())); - pen.setWidth(1); - painter.setPen(pen); - painter.drawLine(0, 0, width(), 0); - painter.restore(); - } -#endif -} - -void MainWindow::mousePressEvent(QMouseEvent *event) -{ - QMainWindow::mousePressEvent(event); - const Qt::MouseButton button = event->button(); - if ((button != Qt::LeftButton) && (button != Qt::RightButton)) { - return; - } - if (isInTitleBarDraggableArea(event->pos())) { - if (button == Qt::LeftButton) { - Utilities::startSystemMove(windowHandle()); - } else { -#ifdef Q_OS_WINDOWS -# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - const QPointF globalPos = event->globalPosition(); -# else - const QPointF globalPos = event->globalPos(); -# endif - const QPointF pos = globalPos * devicePixelRatioF(); - Utilities::showSystemMenu(winId(), pos); -#endif - } - } -} - -void MainWindow::mouseDoubleClickEvent(QMouseEvent *event) -{ - QMainWindow::mouseDoubleClickEvent(event); - if (event->button() != Qt::LeftButton) { - return; - } - if (isInTitleBarDraggableArea(event->pos())) { - titleBarWidget->maximizeButton->click(); + if (mainWindow) { + delete mainWindow; + mainWindow = nullptr; } } void MainWindow::setupUi() { - appMainWindow = new Ui::MainWindow; - appMainWindow->setupUi(this); + mainWindow = new Ui::MainWindow; + mainWindow->setupUi(this); - const auto widget = new QWidget(this); - titleBarWidget = new Ui::TitleBar; - titleBarWidget->setupUi(widget); + const auto titleBarWidget = new QWidget(this); + titleBar = new Ui::TitleBar; + titleBar->setupUi(titleBarWidget); QMenuBar *mb = menuBar(); - titleBarWidget->horizontalLayout->insertWidget(1, mb); + titleBar->horizontalLayout->insertWidget(1, mb); - setMenuWidget(widget); + setMenuWidget(titleBarWidget); - resetContentsMargins(); + setTitleBarWidget(titleBarWidget); - connect(this, &MainWindow::windowIconChanged, titleBarWidget->iconButton, &QPushButton::setIcon); - connect(this, &MainWindow::windowTitleChanged, titleBarWidget->titleLabel, &QLabel::setText); - connect(titleBarWidget->closeButton, &QPushButton::clicked, this, &MainWindow::close); - connect(titleBarWidget->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized); - connect(titleBarWidget->maximizeButton, &QPushButton::clicked, this, [this](){ - if (isMaximized() || isFullScreen()) { + setHitTestVisible(titleBar->iconButton, true); + setHitTestVisible(titleBar->minimizeButton, true); + setHitTestVisible(titleBar->maximizeButton, true); + setHitTestVisible(titleBar->closeButton, true); + + connect(titleBar->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized); + connect(titleBar->maximizeButton, &QPushButton::clicked, this, [this](){ + if (isZoomed()) { showNormal(); } else { showMaximized(); } }); + connect(titleBar->closeButton, &QPushButton::clicked, this, &MainWindow::close); + connect(this, &MainWindow::windowIconChanged, titleBar->iconButton, &QPushButton::setIcon); + connect(this, &MainWindow::windowTitleChanged, titleBar->titleLabel, &QLabel::setText); connect(this, &MainWindow::windowStateChanged, this, [this](){ - const bool check = (isMaximized() || isFullScreen()); - titleBarWidget->maximizeButton->setChecked(check); - titleBarWidget->maximizeButton->setToolTip(check ? tr("Restore") : tr("Maximize")); + const bool zoomed = isZoomed(); + titleBar->maximizeButton->setChecked(zoomed); + titleBar->maximizeButton->setToolTip(zoomed ? tr("Restore") : tr("Maximize")); }); } - -bool MainWindow::isInTitleBarDraggableArea(const QPoint &pos) const -{ - QRegion draggableArea = {0, 0, menuWidget()->width(), menuWidget()->height()}; - draggableArea -= titleBarWidget->minimizeButton->geometry(); - draggableArea -= titleBarWidget->maximizeButton->geometry(); - draggableArea -= titleBarWidget->closeButton->geometry(); - return draggableArea.contains(pos); -} diff --git a/examples/mainwindow/mainwindow.h b/examples/mainwindow/mainwindow.h index bba6c2b..86c75f5 100644 --- a/examples/mainwindow/mainwindow.h +++ b/examples/mainwindow/mainwindow.h @@ -24,38 +24,30 @@ #pragma once -#include -#include "ui_MainWindow.h" -#include "ui_TitleBar.h" +#include -class MainWindow : public QMainWindow +namespace Ui +{ +class TitleBar; +class MainWindow; +} + +class MainWindow : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessMainWindow) { Q_OBJECT + Q_DISABLE_COPY_MOVE(MainWindow) public: explicit MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); ~MainWindow() override; -protected: - void showEvent(QShowEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void changeEvent(QEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; - private: void setupUi(); - void initFramelessHelperOnce(); - bool isInTitleBarDraggableArea(const QPoint &pos) const; - -private Q_SLOTS: - void resetContentsMargins(); Q_SIGNALS: void windowStateChanged(); private: - bool m_inited = false; - Ui::TitleBar *titleBarWidget = nullptr; - Ui::MainWindow *appMainWindow = nullptr; + Ui::TitleBar *titleBar = nullptr; + Ui::MainWindow *mainWindow = nullptr; }; diff --git a/examples/quick/CMakeLists.txt b/examples/quick/CMakeLists.txt index cea6128..a97afd7 100644 --- a/examples/quick/CMakeLists.txt +++ b/examples/quick/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(Quick WIN32 ${SOURCES}) target_link_libraries(Quick PRIVATE Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::QuickControls2 + FramelessHelperCore FramelessHelperQuick ) @@ -26,4 +27,5 @@ target_compile_definitions(Quick PRIVATE QT_USE_QSTRINGBUILDER QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060400 + $<$,$>:QT_QML_DEBUG> ) diff --git a/examples/quick/main.cpp b/examples/quick/main.cpp index 41a1efd..9172047 100644 --- a/examples/quick/main.cpp +++ b/examples/quick/main.cpp @@ -30,8 +30,6 @@ FRAMELESSHELPER_USE_NAMESPACE -static constexpr const char FRAMELESSHELPER_QUICK_URI[] = "org.wangwenx190.FramelessHelper"; - int main(int argc, char *argv[]) { #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) @@ -54,9 +52,6 @@ int main(int argc, char *argv[]) } #endif - QScopedPointer framelessHelper(new FramelessQuickHelper); - QScopedPointer framelessUtils(new FramelessQuickUtils); - QQmlApplicationEngine engine; #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -65,28 +60,23 @@ int main(int argc, char *argv[]) QQuickStyle::setStyle(QStringLiteral("Default")); #endif - qmlRegisterSingletonInstance(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessHelper", framelessHelper.data()); - qmlRegisterSingletonInstance(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessUtils", framelessUtils.data()); + FramelessQuickHelper::registerTypes(&engine); - const QUrl mainQmlUrl(QStringLiteral("qrc:///qml/MainWindow.qml")); + const QUrl homepageUrl(QStringLiteral("qrc:///qml/MainWindow.qml")); const QMetaObject::Connection connection = QObject::connect( - &engine, - &QQmlApplicationEngine::objectCreated, - &application, - [&mainQmlUrl, &connection](QObject *object, const QUrl &url) { - if (url != mainQmlUrl) { + &engine, &QQmlApplicationEngine::objectCreated, &application, + [&homepageUrl, &connection](QObject *object, const QUrl &url) { + if (url != homepageUrl) { return; } if (object) { QObject::disconnect(connection); - } else { QCoreApplication::exit(-1); } - }, - Qt::QueuedConnection); + }, Qt::QueuedConnection); - engine.load(mainQmlUrl); + engine.load(homepageUrl); return QCoreApplication::exec(); } diff --git a/examples/quick/qml/CloseButton.qml b/examples/quick/qml/CloseButton.qml index 5353409..9618cc7 100644 --- a/examples/quick/qml/CloseButton.qml +++ b/examples/quick/qml/CloseButton.qml @@ -45,7 +45,7 @@ Button { Image { anchors.centerIn: parent source: FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible - ? "qrc:/images/light/chrome-close.svg" : "qrc:/images/dark/chrome-close.svg" + ? "image://framelesshelper/dark/close" : "image://framelesshelper/light/close" } } diff --git a/examples/quick/qml/MainWindow.qml b/examples/quick/qml/MainWindow.qml index fcb97db..6901ea5 100644 --- a/examples/quick/qml/MainWindow.qml +++ b/examples/quick/qml/MainWindow.qml @@ -32,7 +32,7 @@ Window { visible: true width: 800 height: 600 - title: qsTr("Hello, World!") + title: qsTr("Hello, World! - Qt Quick") color: FramelessUtils.darkModeEnabled ? "#202020" : "#f0f0f0" Timer { diff --git a/examples/quick/qml/MaximizeButton.qml b/examples/quick/qml/MaximizeButton.qml index c259df0..5598b96 100644 --- a/examples/quick/qml/MaximizeButton.qml +++ b/examples/quick/qml/MaximizeButton.qml @@ -48,9 +48,9 @@ Button { anchors.centerIn: parent source: button.maximized ? (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible - ? "qrc:/images/light/chrome-restore.svg" : "qrc:/images/dark/chrome-restore.svg") : + ? "image://framelesshelper/dark/restore" : "image://framelesshelper/light/restore") : (FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible - ? "qrc:/images/light/chrome-maximize.svg" : "qrc:/images/dark/chrome-maximize.svg") + ? "image://framelesshelper/dark/maximize" : "image://framelesshelper/light/maximize") } } diff --git a/examples/quick/qml/MinimizeButton.qml b/examples/quick/qml/MinimizeButton.qml index 6801030..bb8c83f 100644 --- a/examples/quick/qml/MinimizeButton.qml +++ b/examples/quick/qml/MinimizeButton.qml @@ -45,7 +45,7 @@ Button { Image { anchors.centerIn: parent source: FramelessUtils.darkModeEnabled || FramelessUtils.titleBarColorVisible - ? "qrc:/images/light/chrome-minimize.svg" : "qrc:/images/dark/chrome-minimize.svg" + ? "image://framelesshelper/dark/minimize" : "image://framelesshelper/light/minimize" } } diff --git a/examples/widget/widget.cpp b/examples/widget/widget.cpp index 9d37564..d935866 100644 --- a/examples/widget/widget.cpp +++ b/examples/widget/widget.cpp @@ -42,14 +42,15 @@ Widget::~Widget() = default; void Widget::timerEvent(QTimerEvent *event) { FramelessWidget::timerEvent(event); - if (m_clockLabel) { - m_clockLabel->setText(QTime::currentTime().toString(QStringLiteral("hh:mm:ss"))); + if (!m_clockLabel) { + return; } + m_clockLabel->setText(QTime::currentTime().toString(QStringLiteral("hh:mm:ss"))); } void Widget::setupUi() { - setWindowTitle(tr("Hello, World!")); + setWindowTitle(tr("Hello, World! - Qt Widgets")); resize(800, 600); m_clockLabel = new QLabel(this); m_clockLabel->setFrameShape(QFrame::NoFrame); diff --git a/quick/CMakeLists.txt b/quick/CMakeLists.txt index 250b710..9d9df18 100644 --- a/quick/CMakeLists.txt +++ b/quick/CMakeLists.txt @@ -6,6 +6,8 @@ set(SOURCES framelessquickhelper.cpp framelessquickutils.h framelessquickutils.cpp + framelesshelperimageprovider.h + framelesshelperimageprovider.cpp ) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) diff --git a/quick/framelesshelperimageprovider.cpp b/quick/framelesshelperimageprovider.cpp new file mode 100644 index 0000000..4dca882 --- /dev/null +++ b/quick/framelesshelperimageprovider.cpp @@ -0,0 +1,112 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "framelesshelperimageprovider.h" +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +static constexpr const QSize kDefaultSystemButtonIconSize = {16, 16}; + +[[nodiscard]] static inline SystemTheme strToTheme(const QString &str) +{ + Q_ASSERT(!str.isEmpty()); + if (str.isEmpty()) { + return SystemTheme::Light; + } + if (str.compare(QStringLiteral("light"), Qt::CaseInsensitive) == 0) { + return SystemTheme::Light; + } + if (str.compare(QStringLiteral("dark"), Qt::CaseInsensitive) == 0) { + return SystemTheme::Dark; + } + if (str.compare(QStringLiteral("hc-light"), Qt::CaseInsensitive) == 0) { + return SystemTheme::HighContrastLight; + } + if (str.compare(QStringLiteral("hc-dark"), Qt::CaseInsensitive) == 0) { + return SystemTheme::HighContrastDark; + } + return SystemTheme::Light; +} + +[[nodiscard]] static inline SystemButtonType strToButton(const QString &str) +{ + Q_ASSERT(!str.isEmpty()); + if (str.isEmpty()) { + return SystemButtonType::WindowIcon; + } + if (str.compare(QStringLiteral("minimize"), Qt::CaseInsensitive) == 0) { + return SystemButtonType::Minimize; + } + if (str.compare(QStringLiteral("maximize"), Qt::CaseInsensitive) == 0) { + return SystemButtonType::Maximize; + } + if (str.compare(QStringLiteral("restore"), Qt::CaseInsensitive) == 0) { + return SystemButtonType::Restore; + } + if (str.compare(QStringLiteral("close"), Qt::CaseInsensitive) == 0) { + return SystemButtonType::Close; + } + return SystemButtonType::WindowIcon; +} + +FramelessHelperImageProvider::FramelessHelperImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {} + +FramelessHelperImageProvider::~FramelessHelperImageProvider() = default; + +QPixmap FramelessHelperImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + Q_ASSERT(!id.isEmpty()); + if (id.isEmpty()) { + return {}; + } + const QStringList params = id.split(u'/', Qt::SkipEmptyParts, Qt::CaseInsensitive); + Q_ASSERT(!params.isEmpty()); + if (params.isEmpty()) { + return {}; + } + Q_ASSERT(params.count() >= 2); + if (params.count() < 2) { + return {}; + } + const SystemTheme theme = strToTheme(params.at(0)); + const SystemButtonType button = strToButton(params.at(1)); + const QVariant pixmapVar = Utils::getSystemButtonIconResource(button, theme, ResourceType::Pixmap); + if (!pixmapVar.isValid()) { + return {}; + } + if (static_cast(pixmapVar.typeId()) != QMetaType::QPixmap) { + return {}; + } + if (size) { + *size = kDefaultSystemButtonIconSize; + } + const auto pixmap = qvariant_cast(pixmapVar); + if (!requestedSize.isEmpty() && (pixmap.size() != requestedSize)) { + return pixmap.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + return pixmap; +} + +FRAMELESSHELPER_END_NAMESPACE diff --git a/quick/framelesshelperimageprovider.h b/quick/framelesshelperimageprovider.h new file mode 100644 index 0000000..4a9d140 --- /dev/null +++ b/quick/framelesshelperimageprovider.h @@ -0,0 +1,44 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "framelesshelperquick_global.h" +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class FRAMELESSHELPER_QUICK_API FramelessHelperImageProvider : public QQuickImageProvider +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(FramelessHelperImageProvider) + +public: + explicit FramelessHelperImageProvider(); + ~FramelessHelperImageProvider() override; + + Q_NODISCARD QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize) override; +}; + +FRAMELESSHELPER_END_NAMESPACE diff --git a/quick/framelessquickhelper.cpp b/quick/framelessquickhelper.cpp index 381c6db..273ccbd 100644 --- a/quick/framelessquickhelper.cpp +++ b/quick/framelessquickhelper.cpp @@ -23,21 +23,47 @@ */ #include "framelessquickhelper.h" +#include +#include "framelesshelperimageprovider.h" +#include "framelessquickutils.h" #include FRAMELESSHELPER_BEGIN_NAMESPACE +static constexpr const char FRAMELESSHELPER_QUICK_URI[] = "org.wangwenx190.FramelessHelper"; + FramelessQuickHelper::FramelessQuickHelper(QObject *parent) : QObject(parent) {} FramelessQuickHelper::~FramelessQuickHelper() = default; +void FramelessQuickHelper::registerTypes(QQmlEngine *engine) +{ + Q_ASSERT(engine); + if (!engine) { + return; + } + engine->addImageProvider(QStringLiteral("framelesshelper"), new FramelessHelperImageProvider); + qmlRegisterSingletonType(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessHelper", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * { + Q_UNUSED(engine); + Q_UNUSED(scriptEngine); + const auto framelessHelper = new FramelessQuickHelper; + return framelessHelper; + }); + qmlRegisterSingletonType(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessUtils", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * { + Q_UNUSED(engine); + Q_UNUSED(scriptEngine); + const auto framelessUtils = new FramelessQuickUtils; + return framelessUtils; + }); +} + void FramelessQuickHelper::addWindow(QWindow *window) { Q_ASSERT(window); if (!window) { return; } - FramelessWindowsManager::addWindow(window); + FramelessWindowsManager::instance()->addWindow(window); } void FramelessQuickHelper::removeWindow(QWindow *window) @@ -46,7 +72,7 @@ void FramelessQuickHelper::removeWindow(QWindow *window) if (!window) { return; } - FramelessWindowsManager::removeWindow(window); + FramelessWindowsManager::instance()->removeWindow(window); } FRAMELESSHELPER_END_NAMESPACE diff --git a/quick/framelessquickhelper.h b/quick/framelessquickhelper.h index 30a91e9..e0db127 100644 --- a/quick/framelessquickhelper.h +++ b/quick/framelessquickhelper.h @@ -29,6 +29,7 @@ QT_BEGIN_NAMESPACE class QWindow; +class QQmlEngine; QT_END_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE @@ -48,6 +49,8 @@ public: explicit FramelessQuickHelper(QObject *parent = nullptr); ~FramelessQuickHelper() override; + Q_INVOKABLE static void registerTypes(QQmlEngine *engine); + Q_INVOKABLE static void addWindow(QWindow *window); Q_INVOKABLE static void removeWindow(QWindow *window); }; diff --git a/widgets/CMakeLists.txt b/widgets/CMakeLists.txt index 4d69ea2..9d5fd72 100644 --- a/widgets/CMakeLists.txt +++ b/widgets/CMakeLists.txt @@ -6,6 +6,8 @@ set(SOURCES framelesswidgetshelper.cpp framelesswidget.h framelesswidget.cpp + framelessmainwindow.h + framelessmainwindow.cpp ) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) diff --git a/widgets/framelessmainwindow.cpp b/widgets/framelessmainwindow.cpp new file mode 100644 index 0000000..39532db --- /dev/null +++ b/widgets/framelessmainwindow.cpp @@ -0,0 +1,93 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "framelessmainwindow.h" +#include "framelesswidgetshelper.h" + +FRAMELESSHELPER_BEGIN_NAMESPACE + +FramelessMainWindow::FramelessMainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) +{ + m_helper.reset(new FramelessWidgetsHelper(this, this)); + m_helper->initialize(); +} + +FramelessMainWindow::~FramelessMainWindow() = default; + +bool FramelessMainWindow::isNormal() const +{ + return m_helper->isNormal(); +} + +bool FramelessMainWindow::isZoomed() const +{ + return m_helper->isZoomed(); +} + +void FramelessMainWindow::setTitleBarWidget(QWidget *widget) +{ + m_helper->setTitleBarWidget(widget); +} + +QWidget *FramelessMainWindow::titleBarWidget() const +{ + return m_helper->titleBarWidget(); +} + +void FramelessMainWindow::setHitTestVisible(QWidget *widget, const bool visible) +{ + m_helper->setHitTestVisible(widget, visible); +} + +void FramelessMainWindow::showEvent(QShowEvent *event) +{ + QMainWindow::showEvent(event); + m_helper->showEventHandler(event); +} + +void FramelessMainWindow::changeEvent(QEvent *event) +{ + QMainWindow::changeEvent(event); + m_helper->changeEventHandler(event); +} + +void FramelessMainWindow::paintEvent(QPaintEvent *event) +{ + QMainWindow::paintEvent(event); + m_helper->paintEventHandler(event); +} + +void FramelessMainWindow::mousePressEvent(QMouseEvent *event) +{ + QMainWindow::mousePressEvent(event); + m_helper->mousePressEventHandler(event); +} + +void FramelessMainWindow::mouseDoubleClickEvent(QMouseEvent *event) +{ + QMainWindow::mouseDoubleClickEvent(event); + m_helper->mouseDoubleClickEventHandler(event); +} + +FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelessmainwindow.h b/widgets/framelessmainwindow.h new file mode 100644 index 0000000..d97b439 --- /dev/null +++ b/widgets/framelessmainwindow.h @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "framelesshelperwidgets_global.h" +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class FramelessWidgetsHelper; + +class FRAMELESSHELPER_WIDGETS_API FramelessMainWindow : public QMainWindow +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(FramelessMainWindow) + Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL) + +public: + explicit FramelessMainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + ~FramelessMainWindow() override; + + Q_NODISCARD bool isNormal() const; + Q_NODISCARD bool isZoomed() const; + + void setTitleBarWidget(QWidget *widget); + Q_NODISCARD QWidget *titleBarWidget() const; + + Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible); + +protected: + void showEvent(QShowEvent *event) override; + void changeEvent(QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + +Q_SIGNALS: + void titleBarWidgetChanged(); + void systemThemeChanged(); + void systemMenuRequested(const QPointF &); + +private: + QScopedPointer m_helper; +}; + +FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelesswidgetshelper.cpp b/widgets/framelesswidgetshelper.cpp index 08116ca..15f569b 100644 --- a/widgets/framelesswidgetshelper.cpp +++ b/widgets/framelesswidgetshelper.cpp @@ -34,6 +34,8 @@ FRAMELESSHELPER_BEGIN_NAMESPACE +static constexpr const char QT_MAINWINDOW_CLASS_NAME[] = "QMainWindow"; + static const QString kSystemButtonStyleSheet = QStringLiteral(R"( QPushButton { border-style: none; @@ -88,12 +90,16 @@ void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget) if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) { m_systemTitleBarWidget->hide(); } - if (m_userTitleBarWidget) { - m_mainLayout->removeWidget(m_userTitleBarWidget); - m_userTitleBarWidget = nullptr; + if (isMainWindow()) { + m_userTitleBarWidget = widget; + } else { + if (m_userTitleBarWidget) { + m_mainLayout->removeWidget(m_userTitleBarWidget); + m_userTitleBarWidget = nullptr; + } + m_userTitleBarWidget = widget; + m_mainLayout->insertWidget(0, m_userTitleBarWidget); } - m_userTitleBarWidget = widget; - m_mainLayout->insertWidget(0, m_userTitleBarWidget); QMetaObject::invokeMethod(q, "titleBarWidgetChanged"); } @@ -108,6 +114,9 @@ void FramelessWidgetsHelper::setContentWidget(QWidget *widget) if (!widget) { return; } + if (isMainWindow()) { + return; + } if (m_userContentWidget == widget) { return; } @@ -122,6 +131,9 @@ void FramelessWidgetsHelper::setContentWidget(QWidget *widget) QWidget *FramelessWidgetsHelper::contentWidget() const { + if (isMainWindow()) { + return nullptr; + } return m_userContentWidget; } @@ -221,8 +233,8 @@ void FramelessWidgetsHelper::setupFramelessHelperOnce() return; } m_framelessHelperInited = true; - FramelessWindowsManager::addWindow(q->windowHandle()); - const FramelessWindowsManager * const manager = FramelessWindowsManager::instance(); + FramelessWindowsManager *manager = FramelessWindowsManager::instance(); + manager->addWindow(q->windowHandle()); connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ updateSystemTitleBarStyleSheet(); updateSystemButtonsIcon(); @@ -287,6 +299,9 @@ void FramelessWidgetsHelper::createSystemTitleBar() void FramelessWidgetsHelper::createUserContentContainer() { + if (isMainWindow()) { + return; + } m_userContentContainerWidget = new QWidget(q); m_userContentContainerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_userContentContainerLayout = new QVBoxLayout(m_userContentContainerWidget); @@ -298,13 +313,16 @@ void FramelessWidgetsHelper::createUserContentContainer() void FramelessWidgetsHelper::setupInitialUi() { createSystemTitleBar(); - createUserContentContainer(); - m_mainLayout = new QVBoxLayout(q); - m_mainLayout->setContentsMargins(0, 0, 0, 0); - m_mainLayout->setSpacing(0); - m_mainLayout->addWidget(m_systemTitleBarWidget); - m_mainLayout->addWidget(m_userContentContainerWidget); - q->setLayout(m_mainLayout); + if (isMainWindow()) { + } else { + createUserContentContainer(); + m_mainLayout = new QVBoxLayout(q); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + m_mainLayout->setSpacing(0); + m_mainLayout->addWidget(m_systemTitleBarWidget); + m_mainLayout->addWidget(m_userContentContainerWidget); + q->setLayout(m_mainLayout); + } updateSystemTitleBarStyleSheet(); updateContentsMargins(); } @@ -316,7 +334,10 @@ bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const QRegion region = {QRect(QPoint(0, 0), m_userTitleBarWidget->size())}; if (!m_hitTestVisibleWidgets.isEmpty()) { for (auto &&widget : qAsConst(m_hitTestVisibleWidgets)) { - region -= widget->geometry(); + Q_ASSERT(widget); + if (widget) { + region -= widget->geometry(); + } } } return region; @@ -340,6 +361,14 @@ bool FramelessWidgetsHelper::shouldDrawFrameBorder() const #endif } +bool FramelessWidgetsHelper::isMainWindow() const +{ + if (!q) { + return false; + } + return q->inherits(QT_MAINWINDOW_CLASS_NAME); +} + void FramelessWidgetsHelper::updateContentsMargins() { #ifdef Q_OS_WINDOWS @@ -383,13 +412,13 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet() void FramelessWidgetsHelper::updateSystemButtonsIcon() { const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); - m_systemMinimizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Minimize, theme)); + m_systemMinimizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, ResourceType::Icon))); if (isZoomed()) { - m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Restore, theme)); + m_systemMaximizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Restore, theme, ResourceType::Icon))); } else { - m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Maximize, theme)); + m_systemMaximizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, ResourceType::Icon))); } - m_systemCloseButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Close, theme)); + m_systemCloseButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Close, theme, ResourceType::Icon))); } FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelesswidgetshelper.h b/widgets/framelesswidgetshelper.h index 9b27e86..245376e 100644 --- a/widgets/framelesswidgetshelper.h +++ b/widgets/framelesswidgetshelper.h @@ -74,6 +74,7 @@ private: void setupInitialUi(); Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool shouldDrawFrameBorder() const; + Q_NODISCARD bool isMainWindow() const; private Q_SLOTS: void updateContentsMargins();