diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index d5f5e75..7c62329 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -84,8 +84,10 @@ QT_END_NAMESPACE #ifndef QT_NATIVE_EVENT_RESULT_TYPE # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # define QT_NATIVE_EVENT_RESULT_TYPE qintptr +# define QT_ENTER_EVENT_TYPE QEnterEvent # else # define QT_NATIVE_EVENT_RESULT_TYPE long +# define QT_ENTER_EVENT_TYPE QEvent # endif #endif diff --git a/include/FramelessHelper/Widgets/framelesswidgetshelper.h b/include/FramelessHelper/Widgets/framelesswidgetshelper.h index 16d9bc0..87ba4b0 100644 --- a/include/FramelessHelper/Widgets/framelesswidgetshelper.h +++ b/include/FramelessHelper/Widgets/framelesswidgetshelper.h @@ -30,7 +30,6 @@ QT_BEGIN_NAMESPACE class QLabel; -class QPushButton; class QVBoxLayout; class QShowEvent; class QPaintEvent; @@ -39,6 +38,8 @@ QT_END_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE +class StandardSystemButton; + class FRAMELESSHELPER_WIDGETS_API FramelessWidgetsHelper : public QObject { Q_OBJECT @@ -93,16 +94,16 @@ private: private Q_SLOTS: void updateContentsMargins(); void updateSystemTitleBarStyleSheet(); - void updateSystemButtonsIcon(); + void updateSystemMaximizeButton(); private: QWidget *q = nullptr; bool m_initialized = false; QWidget *m_systemTitleBarWidget = nullptr; QLabel *m_systemWindowTitleLabel = nullptr; - QPushButton *m_systemMinimizeButton = nullptr; - QPushButton *m_systemMaximizeButton = nullptr; - QPushButton *m_systemCloseButton = nullptr; + StandardSystemButton *m_systemMinimizeButton = nullptr; + StandardSystemButton *m_systemMaximizeButton = nullptr; + StandardSystemButton *m_systemCloseButton = nullptr; QWidget *m_userTitleBarWidget = nullptr; QWidget *m_userContentWidget = nullptr; QVBoxLayout *m_mainLayout = nullptr; diff --git a/include/FramelessHelper/Widgets/standardsystembutton.h b/include/FramelessHelper/Widgets/standardsystembutton.h index f00cb06..91baf76 100644 --- a/include/FramelessHelper/Widgets/standardsystembutton.h +++ b/include/FramelessHelper/Widgets/standardsystembutton.h @@ -23,3 +23,58 @@ */ #pragma once + +#include "framelesshelperwidgets_global.h" +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class StandardSystemButtonPrivate; + +class FRAMELESSHELPER_WIDGETS_API StandardSystemButton : public QAbstractButton +{ + Q_OBJECT + Q_DECLARE_PRIVATE(StandardSystemButton) + Q_DISABLE_COPY_MOVE(StandardSystemButton) + Q_PROPERTY(Global::SystemButtonType buttonType READ buttonType WRITE setButtonType NOTIFY buttonTypeChanged FINAL) + Q_PROPERTY(bool hover READ isHover WRITE setHover NOTIFY hoverChanged FINAL) + Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor NOTIFY hoverColorChanged FINAL) + Q_PROPERTY(QColor pressColor READ pressColor WRITE setPressColor NOTIFY pressColorChanged FINAL) + +public: + explicit StandardSystemButton(QWidget *parent = nullptr); + explicit StandardSystemButton(const Global::SystemButtonType type, QWidget *parent = nullptr); + ~StandardSystemButton() override; + + Q_NODISCARD QSize sizeHint() const override; + + void setIcon(const QIcon &icon); + + Q_NODISCARD Global::SystemButtonType buttonType(); + void setButtonType(const Global::SystemButtonType value); + + Q_NODISCARD bool isHover() const; + void setHover(const bool value); + + Q_NODISCARD QColor hoverColor() const; + void setHoverColor(const QColor &value); + + Q_NODISCARD QColor pressColor() const; + void setPressColor(const QColor &value); + +protected: + void enterEvent(QT_ENTER_EVENT_TYPE *event) override; + void leaveEvent(QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +Q_SIGNALS: + void buttonTypeChanged(); + void hoverChanged(); + void hoverColorChanged(); + void pressColorChanged(); + +private: + QScopedPointer d_ptr; +}; + +FRAMELESSHELPER_END_NAMESPACE diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index 45c9d63..cf2c141 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -23,13 +23,13 @@ */ #include "framelesswidgetshelper.h" +#include "standardsystembutton.h" #include #include #include #include #include #include -#include #include #include @@ -39,21 +39,6 @@ using namespace Global; static constexpr const char QT_MAINWINDOW_CLASS_NAME[] = "QMainWindow"; -static const QString kSystemButtonStyleSheet = FRAMELESSHELPER_STRING_LITERAL(R"( -QPushButton { - border-style: none; - background-color: transparent; -} - -QPushButton:hover { - background-color: #cccccc; -} - -QPushButton:pressed { - background-color: #b3b3b3; -} -)"); - FRAMELESSHELPER_STRING_CONSTANT2(StyleSheetColorTemplate, "color: %1;") FRAMELESSHELPER_STRING_CONSTANT2(StyleSheetBackgroundColorTemplate, "background-color: %1;") @@ -229,12 +214,7 @@ void FramelessWidgetsHelper::changeEventHandler(QEvent *event) const bool standardLayout = (m_settings.options & Option::CreateStandardWindowLayout); if (type == QEvent::WindowStateChange) { if (standardLayout) { - if (isZoomed()) { - m_systemMaximizeButton->setToolTip(tr("Restore")); - } else { - m_systemMaximizeButton->setToolTip(tr("Maximize")); - } - updateSystemButtonsIcon(); + updateSystemMaximizeButton(); } updateContentsMargins(); } @@ -404,7 +384,6 @@ void FramelessWidgetsHelper::initialize() m_params.setWindowFlags, true); } if (m_settings.options & Option::TransparentWindowBackground) { - q->setWindowFlags(q->windowFlags() | Qt::FramelessWindowHint); q->setAttribute(Qt::WA_NoSystemBackground); q->setAttribute(Qt::WA_TranslucentBackground); } @@ -414,7 +393,6 @@ void FramelessWidgetsHelper::initialize() connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ if (m_settings.options & Option::CreateStandardWindowLayout) { updateSystemTitleBarStyleSheet(); - updateSystemButtonsIcon(); q->update(); } QMetaObject::invokeMethod(q, "systemThemeChanged"); @@ -447,22 +425,21 @@ void FramelessWidgetsHelper::createSystemTitleBar() m_systemWindowTitleLabel->setFont(windowTitleFont); m_systemWindowTitleLabel->setText(q->windowTitle()); connect(q, &QWidget::windowTitleChanged, m_systemWindowTitleLabel, &QLabel::setText); - m_systemMinimizeButton = new QPushButton(m_systemTitleBarWidget); + m_systemMinimizeButton = new StandardSystemButton(SystemButtonType::Minimize, m_systemTitleBarWidget); m_systemMinimizeButton->setFixedSize(kDefaultSystemButtonSize); m_systemMinimizeButton->setIconSize(kDefaultSystemButtonIconSize); m_systemMinimizeButton->setToolTip(tr("Minimize")); - connect(m_systemMinimizeButton, &QPushButton::clicked, q, &QWidget::showMinimized); - m_systemMaximizeButton = new QPushButton(m_systemTitleBarWidget); + connect(m_systemMinimizeButton, &StandardSystemButton::clicked, q, &QWidget::showMinimized); + m_systemMaximizeButton = new StandardSystemButton(SystemButtonType::Maximize, m_systemTitleBarWidget); m_systemMaximizeButton->setFixedSize(kDefaultSystemButtonSize); m_systemMaximizeButton->setIconSize(kDefaultSystemButtonIconSize); - m_systemMaximizeButton->setToolTip(tr("Maximize")); - connect(m_systemMaximizeButton, &QPushButton::clicked, this, &FramelessWidgetsHelper::toggleMaximized); - m_systemCloseButton = new QPushButton(m_systemTitleBarWidget); + connect(m_systemMaximizeButton, &StandardSystemButton::clicked, this, &FramelessWidgetsHelper::toggleMaximized); + m_systemCloseButton = new StandardSystemButton(SystemButtonType::Close, m_systemTitleBarWidget); m_systemCloseButton->setFixedSize(kDefaultSystemButtonSize); m_systemCloseButton->setIconSize(kDefaultSystemButtonIconSize); m_systemCloseButton->setToolTip(tr("Close")); - connect(m_systemCloseButton, &QPushButton::clicked, q, &QWidget::close); - updateSystemButtonsIcon(); + connect(m_systemCloseButton, &StandardSystemButton::clicked, q, &QWidget::close); + updateSystemMaximizeButton(); const auto systemTitleBarLayout = new QHBoxLayout(m_systemTitleBarWidget); systemTitleBarLayout->setContentsMargins(0, 0, 0, 0); systemTitleBarLayout->setSpacing(0); @@ -643,26 +620,17 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet() }(); const QColor systemWindowTitleLabelTextColor = (active ? ((dark || colorizedTitleBar) ? kDefaultWhiteColor : kDefaultBlackColor) : kDefaultDarkGrayColor); m_systemWindowTitleLabel->setStyleSheet(kStyleSheetColorTemplate.arg(systemWindowTitleLabelTextColor.name())); - m_systemMinimizeButton->setStyleSheet(kSystemButtonStyleSheet); - m_systemMaximizeButton->setStyleSheet(kSystemButtonStyleSheet); - m_systemCloseButton->setStyleSheet(kSystemButtonStyleSheet); m_systemTitleBarWidget->setStyleSheet(kStyleSheetBackgroundColorTemplate.arg(systemTitleBarWidgetBackgroundColor.name())); } -void FramelessWidgetsHelper::updateSystemButtonsIcon() +void FramelessWidgetsHelper::updateSystemMaximizeButton() { if (!(m_settings.options & Option::CreateStandardWindowLayout)) { return; } - const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); - const ResourceType resource = ResourceType::Icon; - m_systemMinimizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, resource))); - if (isZoomed()) { - m_systemMaximizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Restore, theme, resource))); - } else { - m_systemMaximizeButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, resource))); - } - m_systemCloseButton->setIcon(qvariant_cast(Utils::getSystemButtonIconResource(SystemButtonType::Close, theme, resource))); + const bool zoomed = isZoomed(); + m_systemMaximizeButton->setToolTip(zoomed ? tr("Restore") : tr("Maximize")); + m_systemMaximizeButton->setButtonType(zoomed ? SystemButtonType::Restore : SystemButtonType::Maximize); } void FramelessWidgetsHelper::toggleMaximized() diff --git a/src/widgets/standardsystembutton.cpp b/src/widgets/standardsystembutton.cpp index 8b47ddb..dd7c10a 100644 --- a/src/widgets/standardsystembutton.cpp +++ b/src/widgets/standardsystembutton.cpp @@ -23,3 +23,397 @@ */ #include "standardsystembutton.h" +#include +#include +#include +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +using namespace Global; + +static constexpr const QRect g_buttonRect = {QPoint(0, 0), kDefaultSystemButtonSize}; +static constexpr const auto g_buttonIconX = static_cast(qRound(qreal(kDefaultSystemButtonSize.width() - kDefaultSystemButtonIconSize.width()) / 2.0)); +static constexpr const auto g_buttonIconY = static_cast(qRound(qreal(kDefaultSystemButtonSize.height() - kDefaultSystemButtonIconSize.height()) / 2.0)); + +class StandardSystemButtonPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(StandardSystemButton) + Q_DISABLE_COPY_MOVE(StandardSystemButtonPrivate) + +public: + explicit StandardSystemButtonPrivate(StandardSystemButton *q); + ~StandardSystemButtonPrivate() override; + + void refreshButtonTheme(const bool force); + + Q_NODISCARD SystemButtonType getButtonType() const; + void setButtonType(const SystemButtonType type); + + void setIcon(const QIcon &value, const bool reverse); + void setPixmap(const QPixmap &value, const bool reverse); + void setImage(const QImage &value, const bool reverse); + + Q_NODISCARD QSize getRecommendedButtonSize() const; + + Q_NODISCARD bool isHover() const; + Q_NODISCARD QColor getHoverColor() const; + Q_NODISCARD QColor getPressColor() const; + + void setHover(const bool value); + void setHoverColor(const QColor &value); + void setPressColor(const QColor &value); + + void enterEventHandler(QT_ENTER_EVENT_TYPE *event); + void leaveEventHandler(QEvent *event); + void paintEventHandler(QPaintEvent *event); + +private: + void initialize(); + +private: + StandardSystemButton *q_ptr; + SystemTheme m_buttonTheme = SystemTheme::Unknown; + SystemButtonType m_buttonType = SystemButtonType::Unknown; + QPixmap m_icon = {}; + QPixmap m_reversedIcon = {}; + QColor m_hoverColor = {}; + QColor m_pressColor = {}; + bool m_hovered = false; + bool m_pressed = false; +}; + +StandardSystemButtonPrivate::StandardSystemButtonPrivate(StandardSystemButton *q) : QObject(q) +{ + Q_ASSERT(q); + if (!q) { + return; + } + q_ptr = q; + initialize(); +} + +StandardSystemButtonPrivate::~StandardSystemButtonPrivate() = default; + +void StandardSystemButtonPrivate::refreshButtonTheme(const bool force) +{ + if (m_buttonType == SystemButtonType::Unknown) { + return; + } + const SystemTheme systemTheme = Utils::getSystemTheme(); + if ((m_buttonTheme == systemTheme) && !force) { + return; + } + m_buttonTheme = systemTheme; + const SystemTheme reversedTheme = [this]() -> SystemTheme { + if (m_buttonTheme == SystemTheme::Light) { + return SystemTheme::Dark; + } + return SystemTheme::Light; + }(); + // QPixmap doesn't support SVG images. Please refer to: + // https://doc.qt.io/qt-6/qpixmap.html#reading-and-writing-image-files + setImage(qvariant_cast(Utils::getSystemButtonIconResource(m_buttonType, m_buttonTheme, ResourceType::Image)), false); + setImage(qvariant_cast(Utils::getSystemButtonIconResource(m_buttonType, reversedTheme, ResourceType::Image)), true); +} + +SystemButtonType StandardSystemButtonPrivate::getButtonType() const +{ + return m_buttonType; +} + +void StandardSystemButtonPrivate::setButtonType(const SystemButtonType type) +{ + Q_ASSERT(type != SystemButtonType::Unknown); + if (type == SystemButtonType::Unknown) { + return; + } + if (m_buttonType == type) { + return; + } + m_buttonType = type; + setHoverColor(Utils::calculateSystemButtonBackgroundColor(type, ButtonState::Hovered)); + setPressColor(Utils::calculateSystemButtonBackgroundColor(type, ButtonState::Pressed)); + refreshButtonTheme(true); +} + +void StandardSystemButtonPrivate::setIcon(const QIcon &value, const bool reverse) +{ + Q_ASSERT(!value.isNull()); + if (value.isNull()) { + return; + } + const QPixmap pixmap = value.pixmap(kDefaultSystemButtonIconSize); + if (reverse) { + m_reversedIcon = pixmap; + } else { + m_icon = pixmap; + } + Q_Q(StandardSystemButton); + q->update(); +} + +void StandardSystemButtonPrivate::setPixmap(const QPixmap &value, const bool reverse) +{ + Q_ASSERT(!value.isNull()); + if (value.isNull()) { + return; + } + const QPixmap pixmap = ((value.size() == kDefaultSystemButtonIconSize) ? value : + value.scaled(kDefaultSystemButtonIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + if (reverse) { + m_reversedIcon = pixmap; + } else { + m_icon = pixmap; + } + Q_Q(StandardSystemButton); + q->update(); +} + +void StandardSystemButtonPrivate::setImage(const QImage &value, const bool reverse) +{ + Q_ASSERT(!value.isNull()); + if (value.isNull()) { + return; + } + const QImage image = ((value.size() == kDefaultSystemButtonIconSize) ? value : + value.scaled(kDefaultSystemButtonIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + const QPixmap pixmap = QPixmap::fromImage(image); + if (reverse) { + m_reversedIcon = pixmap; + } else { + m_icon = pixmap; + } + Q_Q(StandardSystemButton); + q->update(); +} + +QSize StandardSystemButtonPrivate::getRecommendedButtonSize() const +{ + return kDefaultSystemButtonSize; +} + +bool StandardSystemButtonPrivate::isHover() const +{ + return m_hovered; +} + +QColor StandardSystemButtonPrivate::getHoverColor() const +{ + return m_hoverColor; +} + +QColor StandardSystemButtonPrivate::getPressColor() const +{ + return m_pressColor; +} + +void StandardSystemButtonPrivate::setHover(const bool value) +{ + if (m_hovered == value) { + return; + } + m_hovered = value; + Q_Q(StandardSystemButton); + q->update(); + Q_EMIT q->hoverChanged(); +} + +void StandardSystemButtonPrivate::setHoverColor(const QColor &value) +{ + Q_ASSERT(value.isValid()); + if (!value.isValid()) { + return; + } + if (m_hoverColor == value) { + return; + } + m_hoverColor = value; + Q_Q(StandardSystemButton); + q->update(); + Q_EMIT q->hoverColorChanged(); +} + +void StandardSystemButtonPrivate::setPressColor(const QColor &value) +{ + Q_ASSERT(value.isValid()); + if (!value.isValid()) { + return; + } + if (m_pressColor == value) { + return; + } + m_pressColor = value; + Q_Q(StandardSystemButton); + q->update(); + Q_EMIT q->pressColorChanged(); +} + +void StandardSystemButtonPrivate::enterEventHandler(QT_ENTER_EVENT_TYPE *event) +{ + Q_ASSERT(event); + if (!event) { + return; + } + setHover(true); +} + +void StandardSystemButtonPrivate::leaveEventHandler(QEvent *event) +{ + Q_ASSERT(event); + if (!event) { + return; + } + setHover(false); +} + +void StandardSystemButtonPrivate::paintEventHandler(QPaintEvent *event) +{ + Q_ASSERT(event); + if (!event) { + return; + } + Q_Q(StandardSystemButton); + QPainter painter(q); + painter.save(); + QColor color = {}; + // The pressed state has higher priority than the hovered state. + if (m_pressed && m_pressColor.isValid()) { + color = m_pressColor; + } else if (m_hovered && m_hoverColor.isValid()) { + color = m_hoverColor; + } + if (color.isValid()) { + painter.fillRect(g_buttonRect, color); + } + if (!m_icon.isNull()) { + painter.drawPixmap(g_buttonIconX, + g_buttonIconY, + ((m_buttonType == SystemButtonType::Close) && m_hovered + && !m_reversedIcon.isNull()) + ? m_reversedIcon + : m_icon); + } + painter.restore(); +} + +void StandardSystemButtonPrivate::initialize() +{ + Q_Q(StandardSystemButton); + q->setFixedSize(kDefaultSystemButtonSize); + q->setIconSize(kDefaultSystemButtonIconSize); + connect(q, &StandardSystemButton::pressed, this, [this, q](){ + if (m_pressed) { + return; + } + m_pressed = true; + q->update(); + }); + connect(q, &StandardSystemButton::released, this, [this, q](){ + if (!m_pressed) { + return; + } + m_pressed = false; + q->update(); + }); + connect(FramelessWindowsManager::instance(), &FramelessWindowsManager::systemThemeChanged, + this, [this](){ refreshButtonTheme(false); }); +} + +StandardSystemButton::StandardSystemButton(QWidget *parent) : QAbstractButton(parent) +{ + d_ptr.reset(new StandardSystemButtonPrivate(this)); +} + +StandardSystemButton::StandardSystemButton(const SystemButtonType type, QWidget *parent) : StandardSystemButton(parent) +{ + setButtonType(type); +} + +StandardSystemButton::~StandardSystemButton() = default; + +QSize StandardSystemButton::sizeHint() const +{ + Q_D(const StandardSystemButton); + return d->getRecommendedButtonSize(); +} + +void StandardSystemButton::setIcon(const QIcon &icon) +{ + QAbstractButton::setIcon(icon); + Q_D(StandardSystemButton); + d->setIcon(icon, false); +} + +SystemButtonType StandardSystemButton::buttonType() +{ + Q_D(const StandardSystemButton); + return d->getButtonType(); +} + +void StandardSystemButton::setButtonType(const Global::SystemButtonType value) +{ + Q_D(StandardSystemButton); + d->setButtonType(value); +} + +bool StandardSystemButton::isHover() const +{ + Q_D(const StandardSystemButton); + return d->isHover(); +} + +void StandardSystemButton::setHover(const bool value) +{ + Q_D(StandardSystemButton); + d->setHover(value); +} + +QColor StandardSystemButton::hoverColor() const +{ + Q_D(const StandardSystemButton); + return d->getHoverColor(); +} + +void StandardSystemButton::setHoverColor(const QColor &value) +{ + Q_D(StandardSystemButton); + d->setHoverColor(value); +} + +QColor StandardSystemButton::pressColor() const +{ + Q_D(const StandardSystemButton); + return d->getPressColor(); +} + +void StandardSystemButton::setPressColor(const QColor &value) +{ + Q_D(StandardSystemButton); + d->setPressColor(value); +} + +void StandardSystemButton::enterEvent(QT_ENTER_EVENT_TYPE *event) +{ + QAbstractButton::enterEvent(event); + Q_D(StandardSystemButton); + d->enterEventHandler(event); +} + +void StandardSystemButton::leaveEvent(QEvent *event) +{ + QAbstractButton::leaveEvent(event); + Q_D(StandardSystemButton); + d->leaveEventHandler(event); +} + +void StandardSystemButton::paintEvent(QPaintEvent *event) +{ + Q_D(StandardSystemButton); + d->paintEventHandler(event); +} + +FRAMELESSHELPER_END_NAMESPACE + +#include "standardsystembutton.moc"