quick: emulate Windows behavior more, just like widget

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-09-14 17:07:06 +08:00
parent 1b36587f14
commit 47dcb8f032
6 changed files with 164 additions and 2 deletions

View File

@ -66,6 +66,7 @@ public Q_SLOTS:
void setTitleBarItem(QQuickItem *value);
void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType);
void setHitTestVisible(QQuickItem *item, const bool visible = true);
void setHitTestVisible(const QRect &rect, const bool visible = true);
void showSystemMenu(const QPoint &pos);
void windowStartSystemMove2(const QPoint &pos);

View File

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

View File

@ -113,6 +113,10 @@ Q_SIGNALS:
private:
void initialize();
void updateAll();
void mouseEventHandler(const QMouseEvent *event);
Q_NODISCARD QRect windowIconRect() const;
Q_NODISCARD bool isInTitleBarIconArea(const QPoint &pos) const;
Q_NODISCARD bool windowIconVisible_real() const;
private:
Qt::Alignment m_labelAlignment = {};
@ -128,6 +132,7 @@ private:
bool m_extended = false;
bool m_hideWhenClose = false;
QScopedPointer<QuickChromePalette> m_chromePalette;
bool m_closeTriggered = false;
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -61,6 +61,7 @@ struct QuickHelperData
QPointer<QQuickItem> minimizeButton = nullptr;
QPointer<QQuickItem> maximizeButton = nullptr;
QPointer<QQuickItem> closeButton = nullptr;
QList<QRect> hitTestVisibleRects = {};
};
struct QuickHelper
@ -279,6 +280,26 @@ void FramelessQuickHelperPrivate::setHitTestVisible(QQuickItem *item, const bool
}
}
void FramelessQuickHelperPrivate::setHitTestVisible(const QRect &rect, const bool visible)
{
Q_ASSERT(rect.isValid());
if (!rect.isValid()) {
return;
}
const QMutexLocker locker(&g_quickHelper()->mutex);
QuickHelperData *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 FramelessQuickHelperPrivate::showSystemMenu(const QPoint &pos)
{
#ifdef Q_OS_WINDOWS
@ -620,6 +641,13 @@ bool FramelessQuickHelperPrivate::isInTitleBarDraggableArea(const QPoint &pos) c
}
}
}
if (!data.hitTestVisibleRects.isEmpty()) {
for (auto &&rect : qAsConst(data.hitTestVisibleRects)) {
if (rect.isValid()) {
region -= rect;
}
}
}
return region.contains(pos);
}
@ -856,6 +884,16 @@ void FramelessQuickHelper::setHitTestVisible(QQuickItem *item, const bool visibl
d->setHitTestVisible(item, visible);
}
void FramelessQuickHelper::setHitTestVisible(const QRect &rect, const bool visible)
{
Q_ASSERT(rect.isValid());
if (!rect.isValid()) {
return;
}
Q_D(FramelessQuickHelper);
d->setHitTestVisible(rect, visible);
}
void FramelessQuickHelper::showSystemMenu(const QPoint &pos)
{
Q_D(FramelessQuickHelper);

View File

@ -25,8 +25,11 @@
#include "quickstandardtitlebar_p.h"
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include "quickimageitem.h"
#include "framelessquickhelper.h"
#include "quickstandardsystembutton_p.h"
#include "framelessquickwindow_p.h"
#include <QtCore/qtimer.h>
#include <QtGui/qevent.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickanchors_p.h>
#include <QtQuick/private/qquickanchors_p_p.h>
@ -181,7 +184,6 @@ void QuickStandardTitleBar::setWindowIconSize(const QSizeF &value)
m_windowIcon->setWidth(value.width());
m_windowIcon->setHeight(value.height());
#endif
Q_EMIT windowIconSizeChanged();
}
bool QuickStandardTitleBar::windowIconVisible() const
@ -201,6 +203,7 @@ void QuickStandardTitleBar::setWindowIconVisible(const bool value)
} else {
labelAnchors->setLeft(QQuickItemPrivate::get(this)->left());
}
FramelessQuickHelper::get(this)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
}
QVariant QuickStandardTitleBar::windowIcon() const
@ -354,6 +357,93 @@ void QuickStandardTitleBar::updateWindowIcon()
m_windowIcon->setSource(icon);
}
void QuickStandardTitleBar::mouseEventHandler(const QMouseEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
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 to wait long enough because the time interval between these
// events is really really short, if the delay time is not long enough,
// we still can't trigger the double click event due to we have handled
// the mouse release events here already. But we also can't wait too
// long, otherwise the system menu will show up too late, which is not
// a good user experience. In my experiments, I found that 150ms is
// the minimum value we can use 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, 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;
}
FramelessQuickHelper::get(this)->showSystemMenu([this, button, &scenePos]() -> QPoint {
if (button == Qt::LeftButton) {
return {0, qRound(height())};
}
return scenePos;
}());
});
}
break;
case QEvent::MouseButtonDblClick:
if (QQuickWindow * const w = window()) {
if ((button == Qt::LeftButton) && interestArea) {
m_closeTriggered = true;
w->close();
}
}
break;
default:
break;
}
}
QRect QuickStandardTitleBar::windowIconRect() const
{
#if 0
const QSizeF size = windowIconSize();
const qreal y = ((height() - size.height()) / qreal(2));
return QRectF(QPointF(kDefaultTitleBarContentsMargin, y), size).toRect();
#else
return QRectF(QPointF(m_windowIcon->x(), m_windowIcon->y()), windowIconSize()).toRect();
#endif
}
bool QuickStandardTitleBar::windowIconVisible_real() const
{
if (m_windowIcon.isNull() || !m_windowIcon->isVisible() || !m_windowIcon->source().isValid()) {
return false;
}
return true;
}
bool QuickStandardTitleBar::isInTitleBarIconArea(const QPoint &pos) const
{
if (!windowIconVisible_real()) {
return false;
}
return windowIconRect().contains(pos);
}
void QuickStandardTitleBar::initialize()
{
setSmooth(true);
@ -380,6 +470,9 @@ void QuickStandardTitleBar::initialize()
iconAnchors->setVerticalCenter(thisPriv->verticalCenter());
connect(m_windowIcon.data(), &QuickImageItem::visibleChanged, this, &QuickStandardTitleBar::windowIconVisibleChanged);
connect(m_windowIcon.data(), &QuickImageItem::sourceChanged, this, &QuickStandardTitleBar::windowIconChanged);
// ### TODO: QuickImageItem::sizeChanged()
connect(m_windowIcon.data(), &QuickImageItem::widthChanged, this, &QuickStandardTitleBar::windowIconSizeChanged);
connect(m_windowIcon.data(), &QuickImageItem::heightChanged, this, &QuickStandardTitleBar::windowIconSizeChanged);
m_windowTitleLabel.reset(new QQuickLabel(this));
QFont f = m_windowTitleLabel->font();
@ -428,13 +521,30 @@ void QuickStandardTitleBar::itemChange(const ItemChange change, const ItemChange
m_windowTitleChangeConnection = connect(value.window, &QQuickWindow::windowTitleChanged, this, &QuickStandardTitleBar::updateTitleLabelText);
updateAll();
value.window->installEventFilter(this);
FramelessQuickHelper::get(this)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
}
}
bool QuickStandardTitleBar::eventFilter(QObject *object, QEvent *event)
{
if (event && (event->type() == QEvent::LanguageChange)) {
Q_ASSERT(object);
Q_ASSERT(event);
if (!object || !event) {
return false;
}
if (!object->isWindowType()) {
return QQuickRectangle::eventFilter(object, event);
}
const auto w = qobject_cast<QQuickWindow *>(object);
if (w != window()) {
return QQuickRectangle::eventFilter(object, event);
}
const QEvent::Type type = event->type();
if (type == QEvent::LanguageChange) {
retranslateUi();
} else if ((type >= QEvent::MouseButtonPress) && (type <= QEvent::MouseMove)) {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
mouseEventHandler(mouseEvent);
}
return QQuickRectangle::eventFilter(object, event);
}

View File

@ -278,6 +278,13 @@ void StandardTitleBarPrivate::mouseEventHandler(const QMouseEvent *event)
// 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 to wait long enough because the time interval between these
// events is really really short, if the delay time is not long enough,
// we still can't trigger the double click event due to we have handled
// the mouse release events here already. But we also can't wait too
// long, otherwise the system menu will show up too late, which is not
// a good user experience. In my experiments, I found that 150ms is
// the minimum value we can use 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](){