widgets: emulate Windows behavior more

On Windows, when you left click the window icon in the title bar, the system menu will be triggered.
And when you double click it, the window will be closed.

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-09-14 13:30:08 +08:00
parent 8667d1996e
commit 1b36587f14
9 changed files with 198 additions and 9 deletions

View File

@ -60,6 +60,7 @@ public Q_SLOTS:
void setTitleBarWidget(QWidget *widget); void setTitleBarWidget(QWidget *widget);
void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType); void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType);
void setHitTestVisible(QWidget *widget, const bool visible = true); void setHitTestVisible(QWidget *widget, const bool visible = true);
void setHitTestVisible(const QRect &rect, const bool visible = true);
void showSystemMenu(const QPoint &pos); void showSystemMenu(const QPoint &pos);
void windowStartSystemMove2(const QPoint &pos); void windowStartSystemMove2(const QPoint &pos);

View File

@ -52,6 +52,7 @@ public:
void attachToWindow(); void attachToWindow();
void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType); void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType);
void setHitTestVisible(QWidget *widget, const bool visible = true); void setHitTestVisible(QWidget *widget, const bool visible = true);
void setHitTestVisible(const QRect &rect, const bool visible = true);
void showSystemMenu(const QPoint &pos); void showSystemMenu(const QPoint &pos);
void windowStartSystemMove2(const QPoint &pos); void windowStartSystemMove2(const QPoint &pos);
void windowStartSystemResize2(const Qt::Edges edges, const QPoint &pos); void windowStartSystemResize2(const Qt::Edges edges, const QPoint &pos);

View File

@ -28,6 +28,7 @@
#include "standardsystembutton.h" #include "standardsystembutton.h"
#include <QtCore/qobject.h> #include <QtCore/qobject.h>
#include <QtCore/qpointer.h> #include <QtCore/qpointer.h>
#include <optional>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QEnterEvent; class QEnterEvent;
@ -65,6 +66,7 @@ public:
Q_NODISCARD QColor getActiveForegroundColor() const; Q_NODISCARD QColor getActiveForegroundColor() const;
Q_NODISCARD QColor getInactiveForegroundColor() const; Q_NODISCARD QColor getInactiveForegroundColor() const;
Q_NODISCARD bool isActive() const; Q_NODISCARD bool isActive() const;
Q_NODISCARD int iconSize2() const;
void setHovered(const bool value); void setHovered(const bool value);
void setPressed(const bool value); void setPressed(const bool value);
@ -74,6 +76,7 @@ public:
void setActiveForegroundColor(const QColor &value); void setActiveForegroundColor(const QColor &value);
void setInactiveForegroundColor(const QColor &value); void setInactiveForegroundColor(const QColor &value);
void setActive(const bool value); void setActive(const bool value);
void setIconSize2(const int value);
void enterEventHandler(QT_ENTER_EVENT_TYPE *event); void enterEventHandler(QT_ENTER_EVENT_TYPE *event);
void leaveEventHandler(QEvent *event); void leaveEventHandler(QEvent *event);
@ -94,6 +97,7 @@ private:
bool m_hovered = false; bool m_hovered = false;
bool m_pressed = false; bool m_pressed = false;
bool m_active = false; bool m_active = false;
std::optional<int> m_iconSize2 = std::nullopt;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -77,6 +77,12 @@ public:
Q_NODISCARD QFont titleFont() const; Q_NODISCARD QFont titleFont() const;
void setTitleFont(const QFont &value); void setTitleFont(const QFont &value);
void mouseEventHandler(const QMouseEvent *event);
Q_NODISCARD QRect windowIconRect() const;
Q_NODISCARD bool windowIconVisible_real() const;
Q_NODISCARD bool isInTitleBarIconArea(const QPoint &pos) const;
public Q_SLOTS: public Q_SLOTS:
void updateMaximizeButton(); void updateMaximizeButton();
void updateTitleBarColor(); void updateTitleBarColor();
@ -100,9 +106,10 @@ private:
bool m_hideWhenClose = false; bool m_hideWhenClose = false;
QScopedPointer<ChromePalette> m_chromePalette; QScopedPointer<ChromePalette> m_chromePalette;
bool m_titleLabelVisible = true; bool m_titleLabelVisible = true;
QSize m_windowIconSize = {}; std::optional<QSize> m_windowIconSize = std::nullopt;
bool m_windowIconVisible = false; bool m_windowIconVisible = false;
std::optional<QFont> m_titleFont = std::nullopt; std::optional<QFont> m_titleFont = std::nullopt;
bool m_closeTriggered = false;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -49,6 +49,7 @@ class FRAMELESSHELPER_WIDGETS_API StandardSystemButton : public QAbstractButton
Q_PROPERTY(QColor activeForegroundColor READ activeForegroundColor WRITE setActiveForegroundColor NOTIFY activeForegroundColorChanged FINAL) Q_PROPERTY(QColor activeForegroundColor READ activeForegroundColor WRITE setActiveForegroundColor NOTIFY activeForegroundColorChanged FINAL)
Q_PROPERTY(QColor inactiveForegroundColor READ inactiveForegroundColor WRITE setInactiveForegroundColor NOTIFY inactiveForegroundColorChanged FINAL) Q_PROPERTY(QColor inactiveForegroundColor READ inactiveForegroundColor WRITE setInactiveForegroundColor NOTIFY inactiveForegroundColorChanged FINAL)
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL) Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL)
Q_PROPERTY(int iconSize2 READ iconSize2 WRITE setIconSize2 NOTIFY iconSize2Changed FINAL)
public: public:
explicit StandardSystemButton(QWidget *parent = nullptr); explicit StandardSystemButton(QWidget *parent = nullptr);
@ -66,6 +67,7 @@ public:
Q_NODISCARD QColor activeForegroundColor() const; Q_NODISCARD QColor activeForegroundColor() const;
Q_NODISCARD QColor inactiveForegroundColor() const; Q_NODISCARD QColor inactiveForegroundColor() const;
Q_NODISCARD bool isActive() const; Q_NODISCARD bool isActive() const;
Q_NODISCARD int iconSize2() const;
public Q_SLOTS: public Q_SLOTS:
void setButtonType(const Global::SystemButtonType value); void setButtonType(const Global::SystemButtonType value);
@ -78,6 +80,7 @@ public Q_SLOTS:
void setActiveForegroundColor(const QColor &value); void setActiveForegroundColor(const QColor &value);
void setInactiveForegroundColor(const QColor &value); void setInactiveForegroundColor(const QColor &value);
void setActive(const bool value); void setActive(const bool value);
void setIconSize2(const int value);
protected: protected:
void enterEvent(QT_ENTER_EVENT_TYPE *event) override; void enterEvent(QT_ENTER_EVENT_TYPE *event) override;
@ -95,6 +98,7 @@ Q_SIGNALS:
void activeForegroundColorChanged(); void activeForegroundColorChanged();
void inactiveForegroundColorChanged(); void inactiveForegroundColorChanged();
void activeChanged(); void activeChanged();
void iconSize2Changed();
private: private:
QScopedPointer<StandardSystemButtonPrivate> d_ptr; QScopedPointer<StandardSystemButtonPrivate> d_ptr;

View File

@ -86,6 +86,8 @@ public:
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
Q_SIGNALS: Q_SIGNALS:
void extendedChanged(); void extendedChanged();

View File

@ -63,6 +63,7 @@ struct WidgetsHelperData
QPointer<QWidget> minimizeButton = nullptr; QPointer<QWidget> minimizeButton = nullptr;
QPointer<QWidget> maximizeButton = nullptr; QPointer<QWidget> maximizeButton = nullptr;
QPointer<QWidget> closeButton = nullptr; QPointer<QWidget> closeButton = nullptr;
QList<QRect> hitTestVisibleRects = {};
}; };
struct WidgetsHelper struct WidgetsHelper
@ -310,6 +311,26 @@ void FramelessWidgetsHelperPrivate::setHitTestVisible(QWidget *widget, const boo
} }
} }
void FramelessWidgetsHelperPrivate::setHitTestVisible(const QRect &rect, const bool visible)
{
Q_ASSERT(rect.isValid());
if (!rect.isValid()) {
return;
}
const QMutexLocker locker(&g_widgetsHelper()->mutex);
WidgetsHelperData *data = getWindowDataMutable();
if (!data) {
return;
}
const bool exists = data->hitTestVisibleRects.contains(rect);
if (visible && !exists) {
data->hitTestVisibleRects.append(rect);
}
if (!visible && exists) {
data->hitTestVisibleRects.removeAll(rect);
}
}
void FramelessWidgetsHelperPrivate::attachToWindow() void FramelessWidgetsHelperPrivate::attachToWindow()
{ {
QWidget * const window = getWindow(); QWidget * const window = getWindow();
@ -535,6 +556,13 @@ bool FramelessWidgetsHelperPrivate::isInTitleBarDraggableArea(const QPoint &pos)
} }
} }
} }
if (!data.hitTestVisibleRects.isEmpty()) {
for (auto &&rect : qAsConst(data.hitTestVisibleRects)) {
if (rect.isValid()) {
region -= rect;
}
}
}
return region.contains(pos); return region.contains(pos);
} }
@ -824,6 +852,16 @@ void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget, const bool visib
d->setHitTestVisible(widget, visible); d->setHitTestVisible(widget, visible);
} }
void FramelessWidgetsHelper::setHitTestVisible(const QRect &rect, const bool visible)
{
Q_ASSERT(rect.isValid());
if (!rect.isValid()) {
return;
}
Q_D(FramelessWidgetsHelper);
d->setHitTestVisible(rect, visible);
}
void FramelessWidgetsHelper::showSystemMenu(const QPoint &pos) void FramelessWidgetsHelper::showSystemMenu(const QPoint &pos)
{ {
Q_D(FramelessWidgetsHelper); Q_D(FramelessWidgetsHelper);

View File

@ -154,6 +154,11 @@ bool StandardSystemButtonPrivate::isActive() const
return m_active; return m_active;
} }
int StandardSystemButtonPrivate::iconSize2() const
{
return m_iconSize2.value_or(FramelessManagerPrivate::getIconFont().pointSize());
}
void StandardSystemButtonPrivate::setHovered(const bool value) void StandardSystemButtonPrivate::setHovered(const bool value)
{ {
if (m_hovered == value) { if (m_hovered == value) {
@ -287,6 +292,21 @@ void StandardSystemButtonPrivate::setActive(const bool value)
Q_EMIT q->activeChanged(); Q_EMIT q->activeChanged();
} }
void StandardSystemButtonPrivate::setIconSize2(const int value)
{
Q_ASSERT(value > 0);
if (value <= 0) {
return;
}
if (iconSize2() == value) {
return;
}
m_iconSize2 = value;
Q_Q(StandardSystemButton);
q->update();
Q_EMIT q->iconSize2Changed();
}
void StandardSystemButtonPrivate::enterEventHandler(QT_ENTER_EVENT_TYPE *event) void StandardSystemButtonPrivate::enterEventHandler(QT_ENTER_EVENT_TYPE *event)
{ {
Q_ASSERT(event); Q_ASSERT(event);
@ -343,7 +363,13 @@ void StandardSystemButtonPrivate::paintEventHandler(QPaintEvent *event)
} }
return kDefaultBlackColor; return kDefaultBlackColor;
}()); }());
painter.setFont(FramelessManagerPrivate::getIconFont()); painter.setFont([this]() -> QFont {
QFont font = FramelessManagerPrivate::getIconFont();
if (m_iconSize2.has_value()) {
font.setPointSize(m_iconSize2.value());
}
return font;
}());
painter.drawText(buttonRect, Qt::AlignCenter, m_code); painter.drawText(buttonRect, Qt::AlignCenter, m_code);
} }
painter.restore(); painter.restore();
@ -360,11 +386,13 @@ void StandardSystemButtonPrivate::initialize()
connect(q, &StandardSystemButton::released, this, [this](){ setPressed(false); }); connect(q, &StandardSystemButton::released, this, [this](){ setPressed(false); });
} }
StandardSystemButton::StandardSystemButton(QWidget *parent) : QAbstractButton(parent), d_ptr(new StandardSystemButtonPrivate(this)) StandardSystemButton::StandardSystemButton(QWidget *parent)
: QAbstractButton(parent), d_ptr(new StandardSystemButtonPrivate(this))
{ {
} }
StandardSystemButton::StandardSystemButton(const SystemButtonType type, QWidget *parent) : StandardSystemButton(parent) StandardSystemButton::StandardSystemButton(const SystemButtonType type, QWidget *parent)
: StandardSystemButton(parent)
{ {
setButtonType(type); setButtonType(type);
} }
@ -467,6 +495,12 @@ bool StandardSystemButton::isActive() const
return d->isActive(); return d->isActive();
} }
int StandardSystemButton::iconSize2() const
{
Q_D(const StandardSystemButton);
return d->iconSize2();
}
void StandardSystemButton::setPressColor(const QColor &value) void StandardSystemButton::setPressColor(const QColor &value)
{ {
Q_D(StandardSystemButton); Q_D(StandardSystemButton);
@ -497,6 +531,12 @@ void StandardSystemButton::setActive(const bool value)
d->setActive(value); d->setActive(value);
} }
void StandardSystemButton::setIconSize2(const int value)
{
Q_D(StandardSystemButton);
d->setIconSize2(value);
}
void StandardSystemButton::enterEvent(QT_ENTER_EVENT_TYPE *event) void StandardSystemButton::enterEvent(QT_ENTER_EVENT_TYPE *event)
{ {
QAbstractButton::enterEvent(event); QAbstractButton::enterEvent(event);

View File

@ -25,8 +25,11 @@
#include "standardtitlebar.h" #include "standardtitlebar.h"
#include "standardtitlebar_p.h" #include "standardtitlebar_p.h"
#include "standardsystembutton.h" #include "standardsystembutton.h"
#include "framelesswidgetshelper.h"
#include <QtCore/qcoreevent.h> #include <QtCore/qcoreevent.h>
#include <QtCore/qtimer.h>
#include <QtGui/qpainter.h> #include <QtGui/qpainter.h>
#include <QtGui/qevent.h>
#include <QtWidgets/qboxlayout.h> #include <QtWidgets/qboxlayout.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -144,9 +147,7 @@ void StandardTitleBarPrivate::paintTitleBar(QPaintEvent *event)
if (m_windowIconVisible) { if (m_windowIconVisible) {
const QIcon icon = m_window->windowIcon(); const QIcon icon = m_window->windowIcon();
if (!icon.isNull()) { if (!icon.isNull()) {
const QSize size = (m_windowIconSize.isEmpty() ? kDefaultWindowIconSize : m_windowIconSize); const QRect rect = windowIconRect();
const int y = qRound(qreal(q->height() - size.height()) / qreal(2));
const QRect rect = {QPoint(kDefaultTitleBarContentsMargin, y), size};
titleLabelLeftOffset = (rect.left() + rect.width()); titleLabelLeftOffset = (rect.left() + rect.width());
icon.paint(&painter, rect); icon.paint(&painter, rect);
} }
@ -200,7 +201,7 @@ void StandardTitleBarPrivate::setTitleLabelVisible(const bool value)
QSize StandardTitleBarPrivate::windowIconSize() const QSize StandardTitleBarPrivate::windowIconSize() const
{ {
return m_windowIconSize; return m_windowIconSize.value_or(kDefaultWindowIconSize);
} }
void StandardTitleBarPrivate::setWindowIconSize(const QSize &value) void StandardTitleBarPrivate::setWindowIconSize(const QSize &value)
@ -209,7 +210,7 @@ void StandardTitleBarPrivate::setWindowIconSize(const QSize &value)
if (value.isEmpty()) { if (value.isEmpty()) {
return; return;
} }
if (m_windowIconSize == value) { if (windowIconSize() == value) {
return; return;
} }
m_windowIconSize = value; m_windowIconSize = value;
@ -229,6 +230,7 @@ void StandardTitleBarPrivate::setWindowIconVisible(const bool value)
return; return;
} }
m_windowIconVisible = value; m_windowIconVisible = value;
FramelessWidgetsHelper::get(m_window)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
Q_Q(StandardTitleBar); Q_Q(StandardTitleBar);
q->update(); q->update();
Q_EMIT q->windowIconVisibleChanged(); Q_EMIT q->windowIconVisibleChanged();
@ -250,6 +252,82 @@ void StandardTitleBarPrivate::setTitleFont(const QFont &value)
Q_EMIT q->titleFontChanged(); Q_EMIT q->titleFontChanged();
} }
void StandardTitleBarPrivate::mouseEventHandler(const QMouseEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
if (!m_window) {
return;
}
Q_Q(const StandardTitleBar);
const Qt::MouseButton button = event->button();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint();
#else
const QPoint scenePos = event->windowPos().toPoint();
#endif
const bool interestArea = isInTitleBarIconArea(scenePos);
switch (event->type()) {
case QEvent::MouseButtonRelease:
if (interestArea) {
// Sadly the mouse release events are always triggered before the
// mouse double click events, and if we intercept the mouse release
// events here, we'll never get the double click events afterwards,
// so we have to wait for a little bit to make sure the double
// click events are handled first, before we actually handle the
// mouse release events here.
// We need a copy of the "scenePos" variable here, otherwise it will
// soon fall out of scope when the lambda function actually runs.
QTimer::singleShot(150, this, [this, button, q, scenePos](){
// The close event is already triggered, don't try to show the
// system menu anymore, otherwise it will prevent our window
// from closing.
if (m_closeTriggered) {
return;
}
FramelessWidgetsHelper::get(m_window)->showSystemMenu([button, q, &scenePos]() -> QPoint {
if (button == Qt::LeftButton) {
return {0, q->height()};
}
return scenePos;
}());
});
}
break;
case QEvent::MouseButtonDblClick:
if ((button == Qt::LeftButton) && interestArea) {
m_closeTriggered = true;
m_window->close();
}
break;
default:
break;
}
}
QRect StandardTitleBarPrivate::windowIconRect() const
{
Q_Q(const StandardTitleBar);
const QSize size = windowIconSize();
const int y = qRound(qreal(q->height() - size.height()) / qreal(2));
return {QPoint(kDefaultTitleBarContentsMargin, y), size};
}
bool StandardTitleBarPrivate::windowIconVisible_real() const
{
return (m_windowIconVisible && !m_window->windowIcon().isNull());
}
bool StandardTitleBarPrivate::isInTitleBarIconArea(const QPoint &pos) const
{
if (!windowIconVisible_real()) {
return false;
}
return windowIconRect().contains(pos);
}
void StandardTitleBarPrivate::updateMaximizeButton() void StandardTitleBarPrivate::updateMaximizeButton()
{ {
const bool max = m_window->isMaximized(); const bool max = m_window->isMaximized();
@ -515,4 +593,18 @@ void StandardTitleBar::paintEvent(QPaintEvent *event)
d->paintTitleBar(event); d->paintTitleBar(event);
} }
void StandardTitleBar::mouseReleaseEvent(QMouseEvent *event)
{
QWidget::mouseReleaseEvent(event);
Q_D(StandardTitleBar);
d->mouseEventHandler(event);
}
void StandardTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
QWidget::mouseDoubleClickEvent(event);
Q_D(StandardTitleBar);
d->mouseEventHandler(event);
}
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE