forked from github_mirror/framelesshelper
quick: emulate Windows behavior more, just like widget
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
parent
1b36587f14
commit
47dcb8f032
|
@ -66,6 +66,7 @@ public Q_SLOTS:
|
||||||
void setTitleBarItem(QQuickItem *value);
|
void setTitleBarItem(QQuickItem *value);
|
||||||
void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType);
|
void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType);
|
||||||
void setHitTestVisible(QQuickItem *item, const bool visible = true);
|
void setHitTestVisible(QQuickItem *item, 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);
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
void attachToWindow();
|
void attachToWindow();
|
||||||
void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType);
|
void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType);
|
||||||
void setHitTestVisible(QQuickItem *item, const bool visible = true);
|
void setHitTestVisible(QQuickItem *item, 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);
|
||||||
|
|
|
@ -113,6 +113,10 @@ Q_SIGNALS:
|
||||||
private:
|
private:
|
||||||
void initialize();
|
void initialize();
|
||||||
void updateAll();
|
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:
|
private:
|
||||||
Qt::Alignment m_labelAlignment = {};
|
Qt::Alignment m_labelAlignment = {};
|
||||||
|
@ -128,6 +132,7 @@ private:
|
||||||
bool m_extended = false;
|
bool m_extended = false;
|
||||||
bool m_hideWhenClose = false;
|
bool m_hideWhenClose = false;
|
||||||
QScopedPointer<QuickChromePalette> m_chromePalette;
|
QScopedPointer<QuickChromePalette> m_chromePalette;
|
||||||
|
bool m_closeTriggered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
FRAMELESSHELPER_END_NAMESPACE
|
FRAMELESSHELPER_END_NAMESPACE
|
||||||
|
|
|
@ -61,6 +61,7 @@ struct QuickHelperData
|
||||||
QPointer<QQuickItem> minimizeButton = nullptr;
|
QPointer<QQuickItem> minimizeButton = nullptr;
|
||||||
QPointer<QQuickItem> maximizeButton = nullptr;
|
QPointer<QQuickItem> maximizeButton = nullptr;
|
||||||
QPointer<QQuickItem> closeButton = nullptr;
|
QPointer<QQuickItem> closeButton = nullptr;
|
||||||
|
QList<QRect> hitTestVisibleRects = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QuickHelper
|
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)
|
void FramelessQuickHelperPrivate::showSystemMenu(const QPoint &pos)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WINDOWS
|
#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);
|
return region.contains(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,6 +884,16 @@ void FramelessQuickHelper::setHitTestVisible(QQuickItem *item, const bool visibl
|
||||||
d->setHitTestVisible(item, visible);
|
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)
|
void FramelessQuickHelper::showSystemMenu(const QPoint &pos)
|
||||||
{
|
{
|
||||||
Q_D(FramelessQuickHelper);
|
Q_D(FramelessQuickHelper);
|
||||||
|
|
|
@ -25,8 +25,11 @@
|
||||||
#include "quickstandardtitlebar_p.h"
|
#include "quickstandardtitlebar_p.h"
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
#include "quickimageitem.h"
|
#include "quickimageitem.h"
|
||||||
|
#include "framelessquickhelper.h"
|
||||||
#include "quickstandardsystembutton_p.h"
|
#include "quickstandardsystembutton_p.h"
|
||||||
#include "framelessquickwindow_p.h"
|
#include "framelessquickwindow_p.h"
|
||||||
|
#include <QtCore/qtimer.h>
|
||||||
|
#include <QtGui/qevent.h>
|
||||||
#include <QtQuick/private/qquickitem_p.h>
|
#include <QtQuick/private/qquickitem_p.h>
|
||||||
#include <QtQuick/private/qquickanchors_p.h>
|
#include <QtQuick/private/qquickanchors_p.h>
|
||||||
#include <QtQuick/private/qquickanchors_p_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->setWidth(value.width());
|
||||||
m_windowIcon->setHeight(value.height());
|
m_windowIcon->setHeight(value.height());
|
||||||
#endif
|
#endif
|
||||||
Q_EMIT windowIconSizeChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuickStandardTitleBar::windowIconVisible() const
|
bool QuickStandardTitleBar::windowIconVisible() const
|
||||||
|
@ -201,6 +203,7 @@ void QuickStandardTitleBar::setWindowIconVisible(const bool value)
|
||||||
} else {
|
} else {
|
||||||
labelAnchors->setLeft(QQuickItemPrivate::get(this)->left());
|
labelAnchors->setLeft(QQuickItemPrivate::get(this)->left());
|
||||||
}
|
}
|
||||||
|
FramelessQuickHelper::get(this)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant QuickStandardTitleBar::windowIcon() const
|
QVariant QuickStandardTitleBar::windowIcon() const
|
||||||
|
@ -354,6 +357,93 @@ void QuickStandardTitleBar::updateWindowIcon()
|
||||||
m_windowIcon->setSource(icon);
|
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()
|
void QuickStandardTitleBar::initialize()
|
||||||
{
|
{
|
||||||
setSmooth(true);
|
setSmooth(true);
|
||||||
|
@ -380,6 +470,9 @@ void QuickStandardTitleBar::initialize()
|
||||||
iconAnchors->setVerticalCenter(thisPriv->verticalCenter());
|
iconAnchors->setVerticalCenter(thisPriv->verticalCenter());
|
||||||
connect(m_windowIcon.data(), &QuickImageItem::visibleChanged, this, &QuickStandardTitleBar::windowIconVisibleChanged);
|
connect(m_windowIcon.data(), &QuickImageItem::visibleChanged, this, &QuickStandardTitleBar::windowIconVisibleChanged);
|
||||||
connect(m_windowIcon.data(), &QuickImageItem::sourceChanged, this, &QuickStandardTitleBar::windowIconChanged);
|
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));
|
m_windowTitleLabel.reset(new QQuickLabel(this));
|
||||||
QFont f = m_windowTitleLabel->font();
|
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);
|
m_windowTitleChangeConnection = connect(value.window, &QQuickWindow::windowTitleChanged, this, &QuickStandardTitleBar::updateTitleLabelText);
|
||||||
updateAll();
|
updateAll();
|
||||||
value.window->installEventFilter(this);
|
value.window->installEventFilter(this);
|
||||||
|
FramelessQuickHelper::get(this)->setHitTestVisible(windowIconRect(), windowIconVisible_real());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuickStandardTitleBar::eventFilter(QObject *object, QEvent *event)
|
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();
|
retranslateUi();
|
||||||
|
} else if ((type >= QEvent::MouseButtonPress) && (type <= QEvent::MouseMove)) {
|
||||||
|
const auto mouseEvent = static_cast<QMouseEvent *>(event);
|
||||||
|
mouseEventHandler(mouseEvent);
|
||||||
}
|
}
|
||||||
return QQuickRectangle::eventFilter(object, event);
|
return QQuickRectangle::eventFilter(object, event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,6 +278,13 @@ void StandardTitleBarPrivate::mouseEventHandler(const QMouseEvent *event)
|
||||||
// so we have to wait for a little bit to make sure the double
|
// so we have to wait for a little bit to make sure the double
|
||||||
// click events are handled first, before we actually handle the
|
// click events are handled first, before we actually handle the
|
||||||
// mouse release events here.
|
// 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
|
// We need a copy of the "scenePos" variable here, otherwise it will
|
||||||
// soon fall out of scope when the lambda function actually runs.
|
// soon fall out of scope when the lambda function actually runs.
|
||||||
QTimer::singleShot(150, this, [this, button, q, scenePos](){
|
QTimer::singleShot(150, this, [this, button, q, scenePos](){
|
||||||
|
|
Loading…
Reference in New Issue