diff --git a/core/framelesswindowsmanager.cpp b/core/framelesswindowsmanager.cpp index 88a94c8..ec9c96c 100644 --- a/core/framelesswindowsmanager.cpp +++ b/core/framelesswindowsmanager.cpp @@ -25,6 +25,7 @@ #include "framelesswindowsmanager.h" #include "framelesswindowsmanager_p.h" #include +#include #include "framelesshelper_qt.h" #include "utils.h" #ifdef Q_OS_WINDOWS diff --git a/core/framelesswindowsmanager_p.h b/core/framelesswindowsmanager_p.h index b44812e..630bd6c 100644 --- a/core/framelesswindowsmanager_p.h +++ b/core/framelesswindowsmanager_p.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include FRAMELESSHELPER_BEGIN_NAMESPACE diff --git a/core/utils.cpp b/core/utils.cpp index 8aedd04..7269a0f 100644 --- a/core/utils.cpp +++ b/core/utils.cpp @@ -23,6 +23,7 @@ */ #include "utils.h" +#include // The "Q_INIT_RESOURCE()" macro can't be used within a namespace, // so we wrap it into a separate function outside of the namespace and diff --git a/core/utils.h b/core/utils.h index ffa0976..1e11dbb 100644 --- a/core/utils.h +++ b/core/utils.h @@ -25,7 +25,7 @@ #pragma once #include "framelesshelpercore_global.h" -#include +#include FRAMELESSHELPER_BEGIN_NAMESPACE diff --git a/core/utils_win.cpp b/core/utils_win.cpp index 2e6eab3..d17a81c 100644 --- a/core/utils_win.cpp +++ b/core/utils_win.cpp @@ -751,7 +751,7 @@ QColor Utils::getFrameBorderColor(const bool active) if (isFrameBorderColorized()) { return getDwmColorizationColor(); } else { - return (dark ? QColor(QStringLiteral("#4d4d4d")) : QColor(Qt::black)); + return QColor(QStringLiteral("#4d4d4d")); } } else { return (dark ? QColor(QStringLiteral("#575959")) : QColor(QStringLiteral("#a6a6a6"))); diff --git a/examples/widget/widget.h b/examples/widget/widget.h index a88e049..af89853 100644 --- a/examples/widget/widget.h +++ b/examples/widget/widget.h @@ -26,6 +26,10 @@ #include +QT_BEGIN_NAMESPACE +class QLabel; +QT_END_NAMESPACE + class Widget : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessWidget) { Q_OBJECT diff --git a/widgets/CMakeLists.txt b/widgets/CMakeLists.txt index 7810bd5..4d69ea2 100644 --- a/widgets/CMakeLists.txt +++ b/widgets/CMakeLists.txt @@ -2,6 +2,8 @@ set(SUB_PROJ_NAME FramelessHelperWidgets) set(SOURCES framelesshelperwidgets_global.h + framelesswidgetshelper.h + framelesswidgetshelper.cpp framelesswidget.h framelesswidget.cpp ) diff --git a/widgets/framelesswidget.cpp b/widgets/framelesswidget.cpp index 57bce7f..3bc8cb3 100644 --- a/widgets/framelesswidget.cpp +++ b/widgets/framelesswidget.cpp @@ -23,501 +23,81 @@ */ #include "framelesswidget.h" -#include -#include -#include -#include -#include -#include -#include +#include "framelesswidgetshelper.h" FRAMELESSHELPER_BEGIN_NAMESPACE -static const QString kSystemButtonStyleSheet = QStringLiteral(R"( -QPushButton { - border-style: none; - background-color: transparent; -} - -QPushButton:hover { - background-color: #cccccc; -} - -QPushButton:pressed { - background-color: #b3b3b3; -} -)"); - -class FramelessWidgetPrivate : public QObject +FramelessWidget::FramelessWidget(QWidget *parent) : QWidget(parent) { - Q_OBJECT - Q_DECLARE_PUBLIC(FramelessWidget) - Q_DISABLE_COPY_MOVE(FramelessWidgetPrivate) - -public: - explicit FramelessWidgetPrivate(FramelessWidget *q, QObject *parent = nullptr); - ~FramelessWidgetPrivate() override; - - Q_NODISCARD bool isNormal() const; - Q_NODISCARD bool isZoomed() const; - - void setTitleBarWidget(QWidget *widget); - Q_NODISCARD QWidget *titleBarWidget() const; - - void setContentWidget(QWidget *widget); - Q_NODISCARD QWidget *contentWidget() const; - - void setHitTestVisible(QWidget *widget, const bool visible); - -private: - void setupFramelessHelperOnce(); - void createSystemTitleBar(); - void createUserContentContainer(); - void setupInitialUi(); - Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; - Q_NODISCARD bool shouldDrawFrameBorder() const; - -private Q_SLOTS: - void updateContentsMargins(); - void updateSystemTitleBarStyleSheet(); - void updateSystemButtonsIcon(); - -private: - FramelessWidget *q_ptr = nullptr; - bool m_framelessHelperInited = false; - QWidget *m_systemTitleBarWidget = nullptr; - QLabel *m_systemWindowTitleLabel = nullptr; - QPushButton *m_systemMinimizeButton = nullptr; - QPushButton *m_systemMaximizeButton = nullptr; - QPushButton *m_systemCloseButton = nullptr; - QWidget *m_userTitleBarWidget = nullptr; - QWidget *m_userContentWidget = nullptr; - QVBoxLayout *m_mainLayout = nullptr; - QWidgetList m_hitTestVisibleWidgets = {}; - QWidget *m_userContentContainerWidget = nullptr; - QVBoxLayout *m_userContentContainerLayout = nullptr; -}; - -FramelessWidgetPrivate::FramelessWidgetPrivate(FramelessWidget *q, QObject *parent) : QObject(parent) -{ - Q_ASSERT(q); - if (q) { - q_ptr = q; - } -} - -FramelessWidgetPrivate::~FramelessWidgetPrivate() = default; - -bool FramelessWidgetPrivate::isNormal() const -{ - Q_Q(const FramelessWidget); - return (q->windowState() == Qt::WindowNoState); -} - -bool FramelessWidgetPrivate::isZoomed() const -{ - Q_Q(const FramelessWidget); - return (q->isMaximized() || q->isFullScreen()); -} - -void FramelessWidgetPrivate::setTitleBarWidget(QWidget *widget) -{ - Q_ASSERT(widget); - if (!widget) { - return; - } - if (m_userTitleBarWidget == widget) { - return; - } - if (m_systemTitleBarWidget) { - m_systemTitleBarWidget->hide(); - } - if (m_userTitleBarWidget) { - m_mainLayout->removeWidget(m_userTitleBarWidget); - m_userTitleBarWidget = nullptr; - } - m_userTitleBarWidget = widget; - m_mainLayout->insertWidget(0, m_userTitleBarWidget); - Q_Q(FramelessWidget); - Q_EMIT q->titleBarWidgetChanged(); -} - -QWidget *FramelessWidgetPrivate::titleBarWidget() const -{ - return m_userTitleBarWidget; -} - -void FramelessWidgetPrivate::setContentWidget(QWidget *widget) -{ - Q_ASSERT(widget); - if (!widget) { - return; - } - if (m_userContentWidget == widget) { - return; - } - if (m_userContentWidget) { - m_userContentContainerLayout->removeWidget(m_userContentWidget); - m_userContentWidget = nullptr; - } - m_userContentWidget = widget; - m_userContentContainerLayout->addWidget(m_userContentWidget); - Q_Q(FramelessWidget); - Q_EMIT q->contentWidgetChanged(); -} - -QWidget *FramelessWidgetPrivate::contentWidget() const -{ - return m_userContentWidget; -} - -void FramelessWidgetPrivate::setHitTestVisible(QWidget *widget, const bool visible) -{ - Q_ASSERT(widget); - if (!widget) { - return; - } - const bool exists = m_hitTestVisibleWidgets.contains(widget); - if (visible && !exists) { - m_hitTestVisibleWidgets.append(widget); - } - if (!visible && exists) { - m_hitTestVisibleWidgets.removeAll(widget); - } -} - -void FramelessWidgetPrivate::setupFramelessHelperOnce() -{ - if (m_framelessHelperInited) { - return; - } - m_framelessHelperInited = true; - Q_Q(FramelessWidget); - FramelessWindowsManager::addWindow(q->windowHandle()); - const FramelessWindowsManager * const manager = FramelessWindowsManager::instance(); - connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this, q](){ - updateSystemTitleBarStyleSheet(); - updateSystemButtonsIcon(); - Q_EMIT q->systemThemeChanged(); - }); - connect(manager, &FramelessWindowsManager::systemMenuRequested, q, &FramelessWidget::systemMenuRequested); -} - -void FramelessWidgetPrivate::createSystemTitleBar() -{ - if (m_systemTitleBarWidget) { - return; - } - static constexpr const QSize systemButtonSize = {int(qRound(qreal(kDefaultTitleBarHeight) * 1.5)), kDefaultTitleBarHeight}; - static constexpr const QSize systemButtonIconSize = {16, 16}; - Q_Q(FramelessWidget); - m_systemTitleBarWidget = new QWidget(q); - m_systemTitleBarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - m_systemTitleBarWidget->setFixedHeight(kDefaultTitleBarHeight); - m_systemWindowTitleLabel = new QLabel(m_systemTitleBarWidget); - m_systemWindowTitleLabel->setFrameShape(QFrame::NoFrame); - QFont windowTitleFont = q->font(); - windowTitleFont.setPointSize(11); - m_systemWindowTitleLabel->setFont(windowTitleFont); - m_systemWindowTitleLabel->setText(q->windowTitle()); - connect(q, &FramelessWidget::windowTitleChanged, m_systemWindowTitleLabel, &QLabel::setText); - m_systemMinimizeButton = new QPushButton(m_systemTitleBarWidget); - m_systemMinimizeButton->setFixedSize(systemButtonSize); - m_systemMinimizeButton->setIconSize(systemButtonIconSize); - m_systemMinimizeButton->setToolTip(tr("Minimize")); - connect(m_systemMinimizeButton, &QPushButton::clicked, q, &FramelessWidget::showMinimized); - m_systemMaximizeButton = new QPushButton(m_systemTitleBarWidget); - m_systemMaximizeButton->setFixedSize(systemButtonSize); - m_systemMaximizeButton->setIconSize(systemButtonIconSize); - m_systemMaximizeButton->setToolTip(tr("Maximize")); - connect(m_systemMaximizeButton, &QPushButton::clicked, this, [this, q](){ - if (isZoomed()) { - q->showNormal(); - } else { - q->showMaximized(); - } - updateSystemButtonsIcon(); - }); - m_systemCloseButton = new QPushButton(m_systemTitleBarWidget); - m_systemCloseButton->setFixedSize(systemButtonSize); - m_systemCloseButton->setIconSize(systemButtonIconSize); - m_systemCloseButton->setToolTip(tr("Close")); - connect(m_systemCloseButton, &QPushButton::clicked, q, &FramelessWidget::close); - updateSystemButtonsIcon(); - const auto systemTitleBarLayout = new QHBoxLayout(m_systemTitleBarWidget); - systemTitleBarLayout->setContentsMargins(0, 0, 0, 0); - systemTitleBarLayout->setSpacing(0); - systemTitleBarLayout->addSpacerItem(new QSpacerItem(10, 10)); - systemTitleBarLayout->addWidget(m_systemWindowTitleLabel); - systemTitleBarLayout->addStretch(); - systemTitleBarLayout->addWidget(m_systemMinimizeButton); - systemTitleBarLayout->addWidget(m_systemMaximizeButton); - systemTitleBarLayout->addWidget(m_systemCloseButton); - m_systemTitleBarWidget->setLayout(systemTitleBarLayout); -} - -void FramelessWidgetPrivate::createUserContentContainer() -{ - Q_Q(FramelessWidget); - m_userContentContainerWidget = new QWidget(q); - m_userContentContainerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_userContentContainerLayout = new QVBoxLayout(m_userContentContainerWidget); - m_userContentContainerLayout->setContentsMargins(0, 0, 0, 0); - m_userContentContainerLayout->setSpacing(0); - m_userContentContainerWidget->setLayout(m_userContentContainerLayout); -} - -void FramelessWidgetPrivate::setupInitialUi() -{ - createSystemTitleBar(); - createUserContentContainer(); - Q_Q(FramelessWidget); - 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(); -} - -bool FramelessWidgetPrivate::isInTitleBarDraggableArea(const QPoint &pos) const -{ - const QRegion draggableRegion = [this]() -> QRegion { - if (m_userTitleBarWidget) { - QRegion region = {QRect(QPoint(0, 0), m_userTitleBarWidget->size())}; - if (!m_hitTestVisibleWidgets.isEmpty()) { - for (auto &&widget : qAsConst(m_hitTestVisibleWidgets)) { - region -= widget->geometry(); - } - } - return region; - } else { - QRegion region = {QRect(QPoint(0, 0), m_systemTitleBarWidget->size())}; - region -= m_systemMinimizeButton->geometry(); - region -= m_systemMaximizeButton->geometry(); - region -= m_systemCloseButton->geometry(); - return region; - } - }(); - return draggableRegion.contains(pos); -} - -bool FramelessWidgetPrivate::shouldDrawFrameBorder() const -{ -#ifdef Q_OS_WINDOWS - return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater() && isNormal()); -#else - return false; -#endif -} - -void FramelessWidgetPrivate::updateContentsMargins() -{ -#ifdef Q_OS_WINDOWS - Q_Q(FramelessWidget); - q->setContentsMargins(0, (shouldDrawFrameBorder() ? 1 : 0), 0, 0); -#endif -} - -void FramelessWidgetPrivate::updateSystemTitleBarStyleSheet() -{ - Q_Q(FramelessWidget); - const bool active = q->isActiveWindow(); - const bool dark = Utils::shouldAppsUseDarkMode(); - const bool colorizedTitleBar = Utils::isTitleBarColorized(); - const QColor systemTitleBarWidgetBackgroundColor = [active, colorizedTitleBar, dark]() -> QColor { - if (active) { - if (colorizedTitleBar) { - return Utils::getDwmColorizationColor(); - } else { - if (dark) { - return QColor(Qt::black); - } else { - return QColor(Qt::white); - } - } - } else { - if (dark) { - return kDefaultSystemDarkColor; - } else { - return QColor(Qt::white); - } - } - }(); - const QColor systemWindowTitleLabelTextColor = (active ? ((dark || colorizedTitleBar) ? Qt::white : Qt::black) : Qt::darkGray); - m_systemWindowTitleLabel->setStyleSheet(QStringLiteral("color: %1;").arg(systemWindowTitleLabelTextColor.name())); - m_systemMinimizeButton->setStyleSheet(kSystemButtonStyleSheet); - m_systemMaximizeButton->setStyleSheet(kSystemButtonStyleSheet); - m_systemCloseButton->setStyleSheet(kSystemButtonStyleSheet); - m_systemTitleBarWidget->setStyleSheet(QStringLiteral("background-color: %1;").arg(systemTitleBarWidgetBackgroundColor.name())); - q->update(); -} - -void FramelessWidgetPrivate::updateSystemButtonsIcon() -{ - const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); - m_systemMinimizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Minimize, theme)); - if (isZoomed()) { - m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Restore, theme)); - } else { - m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Maximize, theme)); - } - m_systemCloseButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Close, theme)); -} - -FramelessWidget::FramelessWidget(QWidget *parent) : QWidget(parent), d_ptr(new FramelessWidgetPrivate(this, this)) -{ - setAttribute(Qt::WA_DontCreateNativeAncestors); - createWinId(); - Q_D(FramelessWidget); - d->setupInitialUi(); + m_helper.reset(new FramelessWidgetsHelper(this, this)); + m_helper->initialize(); } FramelessWidget::~FramelessWidget() = default; bool FramelessWidget::isNormal() const { - Q_D(const FramelessWidget); - return d->isNormal(); + return m_helper->isNormal(); } bool FramelessWidget::isZoomed() const { - Q_D(const FramelessWidget); - return d->isZoomed(); + return m_helper->isZoomed(); } void FramelessWidget::setTitleBarWidget(QWidget *widget) { - Q_D(FramelessWidget); - d->setTitleBarWidget(widget); + m_helper->setTitleBarWidget(widget); } QWidget *FramelessWidget::titleBarWidget() const { - Q_D(const FramelessWidget); - return d->titleBarWidget(); + return m_helper->titleBarWidget(); } void FramelessWidget::setContentWidget(QWidget *widget) { - Q_ASSERT(widget); - if (!widget) { - return; - } - if (m_userContentWidget == widget) { - return; - } - if (m_userContentWidget) { - m_userContentContainerLayout->removeWidget(m_userContentWidget); - m_userContentWidget = nullptr; - } - m_userContentWidget = widget; - m_userContentContainerLayout->addWidget(m_userContentWidget); - Q_EMIT contentWidgetChanged(); + m_helper->setContentWidget(widget); } QWidget *FramelessWidget::contentWidget() const { - return m_userContentWidget; + return m_helper->contentWidget(); } void FramelessWidget::setHitTestVisible(QWidget *widget, const bool visible) { - Q_ASSERT(widget); - if (!widget) { - return; - } - const bool exists = m_hitTestVisibleWidgets.contains(widget); - if (visible && !exists) { - m_hitTestVisibleWidgets.append(widget); - } - if (!visible && exists) { - m_hitTestVisibleWidgets.removeAll(widget); - } + m_helper->setHitTestVisible(widget, visible); } void FramelessWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); - Q_D(FramelessWidget); - d->setupFramelessHelperOnce(); + m_helper->showEventHandler(event); } void FramelessWidget::changeEvent(QEvent *event) { QWidget::changeEvent(event); - bool shouldUpdate = false; - if (event->type() == QEvent::WindowStateChange) { - if (isZoomed()) { - m_systemMaximizeButton->setToolTip(tr("Restore")); - } else { - m_systemMaximizeButton->setToolTip(tr("Maximize")); - } - updateContentsMargins(); - updateSystemButtonsIcon(); - shouldUpdate = true; - } else if (event->type() == QEvent::ActivationChange) { - shouldUpdate = true; - } - if (shouldUpdate) { - updateSystemTitleBarStyleSheet(); - } + m_helper->changeEventHandler(event); } void FramelessWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); - Q_D(FramelessWidget); - if (d->shouldDrawFrameBorder()) { - QPainter painter(this); - painter.save(); - QPen pen = {}; - pen.setColor(Utils::getFrameBorderColor(isActiveWindow())); - pen.setWidth(1); - painter.setPen(pen); - painter.drawLine(0, 0, width(), 0); - painter.restore(); - } + m_helper->paintEventHandler(event); } void FramelessWidget::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); - const Qt::MouseButton button = event->button(); - if ((button != Qt::LeftButton) && (button != Qt::RightButton)) { - return; - } - Q_D(FramelessWidget); - if (d->isInTitleBarDraggableArea(event->pos())) { - if (button == Qt::LeftButton) { - Utils::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(); - Utils::showSystemMenu(winId(), pos); -#endif - } - } + m_helper->mousePressEventHandler(event); } void FramelessWidget::mouseDoubleClickEvent(QMouseEvent *event) { QWidget::mouseDoubleClickEvent(event); - if (event->button() != Qt::LeftButton) { - return; - } - Q_D(FramelessWidget); - if (d->isInTitleBarDraggableArea(event->pos())) { - d->m_systemMaximizeButton->click(); - } + m_helper->mouseDoubleClickEventHandler(event); } FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelesswidget.h b/widgets/framelesswidget.h index 1105cfd..0403adf 100644 --- a/widgets/framelesswidget.h +++ b/widgets/framelesswidget.h @@ -29,12 +29,11 @@ FRAMELESSHELPER_BEGIN_NAMESPACE -class FramelessWidgetPrivate; +class FramelessWidgetsHelper; class FRAMELESSHELPER_WIDGETS_API FramelessWidget : public QWidget { Q_OBJECT - Q_DECLARE_PRIVATE(FramelessWidget) Q_DISABLE_COPY_MOVE(FramelessWidget) Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL) Q_PROPERTY(QWidget* contentWidget READ contentWidget WRITE setContentWidget NOTIFY contentWidgetChanged FINAL) @@ -68,7 +67,7 @@ Q_SIGNALS: void systemMenuRequested(const QPointF &); private: - QScopedPointer d_ptr; + QScopedPointer m_helper; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelesswidgetshelper.cpp b/widgets/framelesswidgetshelper.cpp new file mode 100644 index 0000000..08116ca --- /dev/null +++ b/widgets/framelesswidgetshelper.cpp @@ -0,0 +1,395 @@ +/* + * 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 "framelesswidgetshelper.h" +#include +#include +#include +#include +#include +#include +#include +#include "framelesswidget.h" + +FRAMELESSHELPER_BEGIN_NAMESPACE + +static const QString kSystemButtonStyleSheet = QStringLiteral(R"( +QPushButton { + border-style: none; + background-color: transparent; +} + +QPushButton:hover { + background-color: #cccccc; +} + +QPushButton:pressed { + background-color: #b3b3b3; +} +)"); + +FramelessWidgetsHelper::FramelessWidgetsHelper(QWidget *q, QObject *parent) : QObject(parent) +{ + Q_ASSERT(q); + if (q) { + this->q = q; + } +} + +FramelessWidgetsHelper::~FramelessWidgetsHelper() = default; + +void FramelessWidgetsHelper::initialize() +{ + q->setAttribute(Qt::WA_DontCreateNativeAncestors); + q->createWinId(); + setupInitialUi(); +} + +bool FramelessWidgetsHelper::isNormal() const +{ + return (q->windowState() == Qt::WindowNoState); +} + +bool FramelessWidgetsHelper::isZoomed() const +{ + return (q->isMaximized() || q->isFullScreen()); +} + +void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget) +{ + Q_ASSERT(widget); + if (!widget) { + return; + } + if (m_userTitleBarWidget == widget) { + return; + } + if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) { + m_systemTitleBarWidget->hide(); + } + if (m_userTitleBarWidget) { + m_mainLayout->removeWidget(m_userTitleBarWidget); + m_userTitleBarWidget = nullptr; + } + m_userTitleBarWidget = widget; + m_mainLayout->insertWidget(0, m_userTitleBarWidget); + QMetaObject::invokeMethod(q, "titleBarWidgetChanged"); +} + +QWidget *FramelessWidgetsHelper::titleBarWidget() const +{ + return m_userTitleBarWidget; +} + +void FramelessWidgetsHelper::setContentWidget(QWidget *widget) +{ + Q_ASSERT(widget); + if (!widget) { + return; + } + if (m_userContentWidget == widget) { + return; + } + if (m_userContentWidget) { + m_userContentContainerLayout->removeWidget(m_userContentWidget); + m_userContentWidget = nullptr; + } + m_userContentWidget = widget; + m_userContentContainerLayout->addWidget(m_userContentWidget); + QMetaObject::invokeMethod(q, "contentWidgetChanged"); +} + +QWidget *FramelessWidgetsHelper::contentWidget() const +{ + return m_userContentWidget; +} + +void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget, const bool visible) +{ + Q_ASSERT(widget); + if (!widget) { + return; + } + const bool exists = m_hitTestVisibleWidgets.contains(widget); + if (visible && !exists) { + m_hitTestVisibleWidgets.append(widget); + } + if (!visible && exists) { + m_hitTestVisibleWidgets.removeAll(widget); + } +} + +void FramelessWidgetsHelper::showEventHandler(QShowEvent *event) +{ + Q_UNUSED(event); + setupFramelessHelperOnce(); +} + +void FramelessWidgetsHelper::changeEventHandler(QEvent *event) +{ + bool shouldUpdate = false; + if (event->type() == QEvent::WindowStateChange) { + if (isZoomed()) { + m_systemMaximizeButton->setToolTip(tr("Restore")); + } else { + m_systemMaximizeButton->setToolTip(tr("Maximize")); + } + updateContentsMargins(); + updateSystemButtonsIcon(); + shouldUpdate = true; + } else if (event->type() == QEvent::ActivationChange) { + shouldUpdate = true; + } + if (shouldUpdate) { + updateSystemTitleBarStyleSheet(); + } +} + +void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event) +{ + Q_UNUSED(event); + if (!shouldDrawFrameBorder()) { + return; + } + QPainter painter(q); + painter.save(); + QPen pen = {}; + pen.setColor(Utils::getFrameBorderColor(q->isActiveWindow())); + pen.setWidth(1); + painter.setPen(pen); + painter.drawLine(0, 0, q->width(), 0); + painter.restore(); +} + +void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event) +{ + const Qt::MouseButton button = event->button(); + if ((button != Qt::LeftButton) && (button != Qt::RightButton)) { + return; + } + if (isInTitleBarDraggableArea(event->pos())) { + if (button == Qt::LeftButton) { + Utils::startSystemMove(q->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 * q->devicePixelRatioF(); + Utils::showSystemMenu(q->winId(), pos); +#endif + } + } +} + +void FramelessWidgetsHelper::mouseDoubleClickEventHandler(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + return; + } + if (isInTitleBarDraggableArea(event->pos())) { + m_systemMaximizeButton->click(); + } +} + +void FramelessWidgetsHelper::setupFramelessHelperOnce() +{ + if (m_framelessHelperInited) { + return; + } + m_framelessHelperInited = true; + FramelessWindowsManager::addWindow(q->windowHandle()); + const FramelessWindowsManager * const manager = FramelessWindowsManager::instance(); + connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ + updateSystemTitleBarStyleSheet(); + updateSystemButtonsIcon(); + QMetaObject::invokeMethod(q, "systemThemeChanged"); + }); + connect(manager, &FramelessWindowsManager::systemMenuRequested, this, [this](const QPointF &pos){ + QMetaObject::invokeMethod(q, "systemMenuRequested", Q_ARG(QPointF, pos)); + }); +} + +void FramelessWidgetsHelper::createSystemTitleBar() +{ + if (m_systemTitleBarWidget) { + return; + } + static constexpr const QSize systemButtonSize = {int(qRound(qreal(kDefaultTitleBarHeight) * 1.5)), kDefaultTitleBarHeight}; + static constexpr const QSize systemButtonIconSize = {16, 16}; + m_systemTitleBarWidget = new QWidget(q); + m_systemTitleBarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_systemTitleBarWidget->setFixedHeight(kDefaultTitleBarHeight); + m_systemWindowTitleLabel = new QLabel(m_systemTitleBarWidget); + m_systemWindowTitleLabel->setFrameShape(QFrame::NoFrame); + QFont windowTitleFont = q->font(); + windowTitleFont.setPointSize(11); + m_systemWindowTitleLabel->setFont(windowTitleFont); + m_systemWindowTitleLabel->setText(q->windowTitle()); + connect(q, &FramelessWidget::windowTitleChanged, m_systemWindowTitleLabel, &QLabel::setText); + m_systemMinimizeButton = new QPushButton(m_systemTitleBarWidget); + m_systemMinimizeButton->setFixedSize(systemButtonSize); + m_systemMinimizeButton->setIconSize(systemButtonIconSize); + m_systemMinimizeButton->setToolTip(tr("Minimize")); + connect(m_systemMinimizeButton, &QPushButton::clicked, q, &FramelessWidget::showMinimized); + m_systemMaximizeButton = new QPushButton(m_systemTitleBarWidget); + m_systemMaximizeButton->setFixedSize(systemButtonSize); + m_systemMaximizeButton->setIconSize(systemButtonIconSize); + m_systemMaximizeButton->setToolTip(tr("Maximize")); + connect(m_systemMaximizeButton, &QPushButton::clicked, this, [this](){ + if (isZoomed()) { + q->showNormal(); + } else { + q->showMaximized(); + } + updateSystemButtonsIcon(); + }); + m_systemCloseButton = new QPushButton(m_systemTitleBarWidget); + m_systemCloseButton->setFixedSize(systemButtonSize); + m_systemCloseButton->setIconSize(systemButtonIconSize); + m_systemCloseButton->setToolTip(tr("Close")); + connect(m_systemCloseButton, &QPushButton::clicked, q, &FramelessWidget::close); + updateSystemButtonsIcon(); + const auto systemTitleBarLayout = new QHBoxLayout(m_systemTitleBarWidget); + systemTitleBarLayout->setContentsMargins(0, 0, 0, 0); + systemTitleBarLayout->setSpacing(0); + systemTitleBarLayout->addSpacerItem(new QSpacerItem(10, 10)); + systemTitleBarLayout->addWidget(m_systemWindowTitleLabel); + systemTitleBarLayout->addStretch(); + systemTitleBarLayout->addWidget(m_systemMinimizeButton); + systemTitleBarLayout->addWidget(m_systemMaximizeButton); + systemTitleBarLayout->addWidget(m_systemCloseButton); + m_systemTitleBarWidget->setLayout(systemTitleBarLayout); +} + +void FramelessWidgetsHelper::createUserContentContainer() +{ + m_userContentContainerWidget = new QWidget(q); + m_userContentContainerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_userContentContainerLayout = new QVBoxLayout(m_userContentContainerWidget); + m_userContentContainerLayout->setContentsMargins(0, 0, 0, 0); + m_userContentContainerLayout->setSpacing(0); + m_userContentContainerWidget->setLayout(m_userContentContainerLayout); +} + +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); + updateSystemTitleBarStyleSheet(); + updateContentsMargins(); +} + +bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const +{ + const QRegion draggableRegion = [this]() -> QRegion { + if (m_userTitleBarWidget) { + QRegion region = {QRect(QPoint(0, 0), m_userTitleBarWidget->size())}; + if (!m_hitTestVisibleWidgets.isEmpty()) { + for (auto &&widget : qAsConst(m_hitTestVisibleWidgets)) { + region -= widget->geometry(); + } + } + return region; + } else { + QRegion region = {QRect(QPoint(0, 0), m_systemTitleBarWidget->size())}; + region -= m_systemMinimizeButton->geometry(); + region -= m_systemMaximizeButton->geometry(); + region -= m_systemCloseButton->geometry(); + return region; + } + }(); + return draggableRegion.contains(pos); +} + +bool FramelessWidgetsHelper::shouldDrawFrameBorder() const +{ +#ifdef Q_OS_WINDOWS + return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater() && isNormal()); +#else + return false; +#endif +} + +void FramelessWidgetsHelper::updateContentsMargins() +{ +#ifdef Q_OS_WINDOWS + q->setContentsMargins(0, (shouldDrawFrameBorder() ? 1 : 0), 0, 0); +#endif +} + +void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet() +{ + const bool active = q->isActiveWindow(); + const bool dark = Utils::shouldAppsUseDarkMode(); + const bool colorizedTitleBar = Utils::isTitleBarColorized(); + const QColor systemTitleBarWidgetBackgroundColor = [active, colorizedTitleBar, dark]() -> QColor { + if (active) { + if (colorizedTitleBar) { + return Utils::getDwmColorizationColor(); + } else { + if (dark) { + return QColor(Qt::black); + } else { + return QColor(Qt::white); + } + } + } else { + if (dark) { + return kDefaultSystemDarkColor; + } else { + return QColor(Qt::white); + } + } + }(); + const QColor systemWindowTitleLabelTextColor = (active ? ((dark || colorizedTitleBar) ? Qt::white : Qt::black) : Qt::darkGray); + m_systemWindowTitleLabel->setStyleSheet(QStringLiteral("color: %1;").arg(systemWindowTitleLabelTextColor.name())); + m_systemMinimizeButton->setStyleSheet(kSystemButtonStyleSheet); + m_systemMaximizeButton->setStyleSheet(kSystemButtonStyleSheet); + m_systemCloseButton->setStyleSheet(kSystemButtonStyleSheet); + m_systemTitleBarWidget->setStyleSheet(QStringLiteral("background-color: %1;").arg(systemTitleBarWidgetBackgroundColor.name())); + q->update(); +} + +void FramelessWidgetsHelper::updateSystemButtonsIcon() +{ + const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); + m_systemMinimizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Minimize, theme)); + if (isZoomed()) { + m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Restore, theme)); + } else { + m_systemMaximizeButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Maximize, theme)); + } + m_systemCloseButton->setIcon(Utils::getSystemButtonIcon(SystemButtonType::Close, theme)); +} + +FRAMELESSHELPER_END_NAMESPACE diff --git a/widgets/framelesswidgetshelper.h b/widgets/framelesswidgetshelper.h new file mode 100644 index 0000000..9b27e86 --- /dev/null +++ b/widgets/framelesswidgetshelper.h @@ -0,0 +1,99 @@ +/* + * 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 +#include + +QT_BEGIN_NAMESPACE +class QLabel; +class QPushButton; +class QVBoxLayout; +class QShowEvent; +class QPaintEvent; +class QMouseEvent; +QT_END_NAMESPACE + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class FRAMELESSHELPER_WIDGETS_API FramelessWidgetsHelper : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(FramelessWidgetsHelper) + +public: + explicit FramelessWidgetsHelper(QWidget *q, QObject *parent = nullptr); + ~FramelessWidgetsHelper() override; + + Q_INVOKABLE void initialize(); + + Q_NODISCARD Q_INVOKABLE bool isNormal() const; + Q_NODISCARD Q_INVOKABLE bool isZoomed() const; + + Q_INVOKABLE void setTitleBarWidget(QWidget *widget); + Q_NODISCARD Q_INVOKABLE QWidget *titleBarWidget() const; + + Q_INVOKABLE void setContentWidget(QWidget *widget); + Q_NODISCARD Q_INVOKABLE QWidget *contentWidget() const; + + Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible); + + void showEventHandler(QShowEvent *event); + void changeEventHandler(QEvent *event); + void paintEventHandler(QPaintEvent *event); + void mousePressEventHandler(QMouseEvent *event); + void mouseDoubleClickEventHandler(QMouseEvent *event); + +private: + void setupFramelessHelperOnce(); + void createSystemTitleBar(); + void createUserContentContainer(); + void setupInitialUi(); + Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; + Q_NODISCARD bool shouldDrawFrameBorder() const; + +private Q_SLOTS: + void updateContentsMargins(); + void updateSystemTitleBarStyleSheet(); + void updateSystemButtonsIcon(); + +private: + QWidget *q = nullptr; + bool m_framelessHelperInited = false; + QWidget *m_systemTitleBarWidget = nullptr; + QLabel *m_systemWindowTitleLabel = nullptr; + QPushButton *m_systemMinimizeButton = nullptr; + QPushButton *m_systemMaximizeButton = nullptr; + QPushButton *m_systemCloseButton = nullptr; + QWidget *m_userTitleBarWidget = nullptr; + QWidget *m_userContentWidget = nullptr; + QVBoxLayout *m_mainLayout = nullptr; + QWidgetList m_hitTestVisibleWidgets = {}; + QWidget *m_userContentContainerWidget = nullptr; + QVBoxLayout *m_userContentContainerLayout = nullptr; +}; + +FRAMELESSHELPER_END_NAMESPACE