finish the implementation of StandardSystemButton

Convenient button widget to emulate the standard system button

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-04-07 10:17:16 +08:00
parent 4eac2b2970
commit 1839c968db
5 changed files with 470 additions and 50 deletions

View File

@ -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

View File

@ -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;

View File

@ -23,3 +23,58 @@
*/
#pragma once
#include "framelesshelperwidgets_global.h"
#include <QtWidgets/qabstractbutton.h>
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<StandardSystemButtonPrivate> d_ptr;
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,13 +23,13 @@
*/
#include "framelesswidgetshelper.h"
#include "standardsystembutton.h"
#include <QtCore/qdebug.h>
#include <QtGui/qpainter.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
#include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qlabel.h>
#include <QtWidgets/qpushbutton.h>
#include <framelesswindowsmanager.h>
#include <utils.h>
@ -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<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, resource)));
if (isZoomed()) {
m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Restore, theme, resource)));
} else {
m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, resource)));
}
m_systemCloseButton->setIcon(qvariant_cast<QIcon>(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()

View File

@ -23,3 +23,397 @@
*/
#include "standardsystembutton.h"
#include <QtCore/qvariant.h>
#include <QtGui/qpainter.h>
#include <framelesswindowsmanager.h>
#include <utils.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
static constexpr const QRect g_buttonRect = {QPoint(0, 0), kDefaultSystemButtonSize};
static constexpr const auto g_buttonIconX = static_cast<int>(qRound(qreal(kDefaultSystemButtonSize.width() - kDefaultSystemButtonIconSize.width()) / 2.0));
static constexpr const auto g_buttonIconY = static_cast<int>(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<QImage>(Utils::getSystemButtonIconResource(m_buttonType, m_buttonTheme, ResourceType::Image)), false);
setImage(qvariant_cast<QImage>(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"