From 1d7c300a017babf419ec71afa65f5141adfa4558 Mon Sep 17 00:00:00 2001 From: Ye ShanShan Date: Wed, 4 Jun 2025 15:28:43 +0800 Subject: [PATCH] feat: enhance touch drag support for QML windows in qt6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Added touch drag support for QQuickWindow with proper grabber state tracking 2. Implemented multi-screen aware touch drag handling with position validation 3. Added new Utility::updateMousePointForWindowMove overload for custom positions 4. Improved window boundary checks using scene coordinates 5. Added debug logging for window drag operations The changes enable proper touch-based window dragging for QML-based applications while maintaining existing mouse drag functionality. The implementation tracks touch grabber states to determine if QML items have handled the touch events, and only initiates window dragging when no QML items have claimed the touch points. The multi-screen support ensures correct behavior across different display configurations. feat: 增强QML窗口的触摸拖拽支持 1. 为QQuickWindow添加触摸拖拽支持,并正确跟踪grabber状态 2. 实现多屏幕感知的触摸拖拽处理,包含位置验证 3. 新增Utility::updateMousePointForWindowMove重载方法支持自定义位置 4. 使用场景坐标改进窗口边界检查 5. 添加窗口拖拽操作的调试日志 这些变更使得基于QML的应用程序能够正确支持触摸拖拽窗口,同时保留现有的鼠 标拖拽功能。实现通过跟踪触摸grabber状态来判断QML组件是否处理了触摸事件, 仅当没有QML组件处理时才启动窗口拖拽。多屏幕支持确保在不同显示配置下的正 确行为。 pms: BUG-309157 --- xcb/dnotitlebarwindowhelper.cpp | 167 +++++++++++++++++++++++++++++++- xcb/dnotitlebarwindowhelper.h | 12 +++ xcb/utility.h | 2 + xcb/utility_x11.cpp | 9 +- 4 files changed, 187 insertions(+), 3 deletions(-) diff --git a/xcb/dnotitlebarwindowhelper.cpp b/xcb/dnotitlebarwindowhelper.cpp index c9715602..8252c76b 100644 --- a/xcb/dnotitlebarwindowhelper.cpp +++ b/xcb/dnotitlebarwindowhelper.cpp @@ -13,6 +13,7 @@ #include "dplatformintegration.h" #include +#include #include #include #include @@ -40,6 +41,9 @@ DNoTitlebarWindowHelper::DNoTitlebarWindowHelper(QWindow *window, quint32 window if (window->flags().testFlag(Qt::FramelessWindowHint)) window->setFlags(window->flags() & ~Qt::FramelessWindowHint); + // 初始化窗口类型判断结果 + m_isQuickWindow = window->inherits("QQuickWindow"); + mapped[window] = this; m_nativeSettingsValid = DPlatformIntegration::buildNativeSettings(this, windowID); Q_ASSERT(m_nativeSettingsValid); @@ -524,15 +528,35 @@ bool DNoTitlebarWindowHelper::windowEvent(QEvent *event) VtableHook::resetVtable(w); return w->event(event); } + DNoTitlebarWindowHelper *self = mapped.value(w); // get touch begin position static bool isTouchDown = false; static QPointF touchBeginPosition; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + static QHash touchPointGrabbers; // 新增:记录触摸点的grabber状态 + const auto isQuickWindow = self->m_isQuickWindow; +#endif + if (event->type() == QEvent::TouchBegin) { isTouchDown = true; + // ========== 新增:记录处理前的grabber状态 ========== +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (isQuickWindow && event->isPointerEvent()) { + QPointerEvent *pe = static_cast(event); + touchPointGrabbers.clear(); + for (const auto &point : pe->points()) { + QObject *grabber = pe->exclusiveGrabber(point); + touchPointGrabbers[point.id()] = grabber; + } + } +#endif } if (event->type() == QEvent::TouchEnd || event->type() == QEvent::MouseButtonRelease) { isTouchDown = false; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + touchPointGrabbers.clear(); // 清理grabber记录 +#endif } if (isTouchDown && event->type() == QEvent::MouseButtonPress) { touchBeginPosition = static_cast(event)->globalPos(); @@ -546,7 +570,6 @@ bool DNoTitlebarWindowHelper::windowEvent(QEvent *event) } } - DNoTitlebarWindowHelper *self = mapped.value(w); quint32 winId = self->m_windowID; bool is_mouse_move = event->type() == QEvent::MouseMove && static_cast(event)->buttons() == Qt::LeftButton; @@ -570,10 +593,15 @@ bool DNoTitlebarWindowHelper::windowEvent(QEvent *event) g_pressPoint[this] = dynamic_cast(event)->globalPos(); } + // ========== 修改:鼠标事件处理保持原有逻辑 ========== if (is_mouse_move && !event->isAccepted() && g_pressPoint.contains(this)) { QMouseEvent *me = static_cast(event); QRect windowRect = QRect(QPoint(0, 0), w->size()); - if (!windowRect.contains(me->windowPos().toPoint())) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (!windowRect.contains(me->scenePosition().toPoint())) { +#else + if (!windowRect.contains(me->localPos().toPoint())) { +#endif return ret; } @@ -583,6 +611,7 @@ bool DNoTitlebarWindowHelper::windowEvent(QEvent *event) } if (!self->m_windowMoving && self->isEnableSystemMove(winId)) { + qDebug() << "Starting mouse drag for window ID:" << winId; self->m_windowMoving = true; event->accept(); @@ -594,6 +623,39 @@ bool DNoTitlebarWindowHelper::windowEvent(QEvent *event) } } + // ========== 新增:QML窗口触摸事件的特殊处理 ========== +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (isQuickWindow && event->isPointerEvent()) { + QPointerEvent *pe = static_cast(event); + + // 检查是否是触摸事件 + bool isTouchEvent = (event->type() == QEvent::TouchBegin || + event->type() == QEvent::TouchUpdate || + event->type() == QEvent::TouchEnd); + + if (isTouchEvent) { + // 检查是否有新的grabber产生(说明QML Item处理了触摸事件) + bool wasHandledByQML = false; + for (const auto &point : pe->points()) { + QObject *grabberBefore = touchPointGrabbers.value(point.id()); + QObject *grabberAfter = pe->exclusiveGrabber(point); + + if (grabberBefore != grabberAfter && grabberAfter != nullptr) { + wasHandledByQML = true; + break; + } + } + + // 如果没有QML Item处理触摸事件,尝试窗口拖拽 + if (!wasHandledByQML && self->isEnableSystemMove(winId)) { + if (self->handleTouchDragForQML(pe)) { + return ret; + } + } + } + } +#endif + return ret; } @@ -733,4 +795,105 @@ void DNoTitlebarWindowHelper::updateMoveWindow(quint32 winId) Utility::updateMousePointForWindowMove(winId); } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +bool DNoTitlebarWindowHelper::handleTouchDragForQML(QPointerEvent *touchEvent) +{ + QWindow *w = this->m_window; + static QHash touchStartPositions; + static QHash touchIsMoving; + static QHash lastTouchPositions; // 新增:记录最后的触摸位置用于更新 + + const auto points = touchEvent->points(); + if (points.isEmpty()) { + return false; + } + + // 只处理第一个触摸点 + const auto &point = points.first(); + int touchId = point.id(); + + // ========== 新增:多屏幕坐标处理 ========== + QScreen *windowScreen = w->screen(); + QPointF globalPos = point.globalPosition(); + QPointF localPos = point.position(); + + switch (touchEvent->type()) { + case QEvent::TouchBegin: { + touchStartPositions[touchId] = globalPos; + lastTouchPositions[touchId] = globalPos; + touchIsMoving[touchId] = false; + return false; // 不消费事件,让QML先处理 + } + + case QEvent::TouchUpdate: { + if (!touchStartPositions.contains(touchId)) { + return false; + } + + QPointF startPos = touchStartPositions[touchId]; + QPointF currentPos = globalPos; + QPointF delta = currentPos - startPos; + qreal distance = delta.manhattanLength(); + qreal threshold = QGuiApplication::styleHints()->startDragDistance(); + + // 更新最后触摸位置 + lastTouchPositions[touchId] = currentPos; + + // 检查移动距离是否达到拖拽阈值 + if (!touchIsMoving[touchId] && distance >= threshold) { + + // ========== 修改:多屏幕环境下的窗口边界检查 ========== + QRect windowRect = QRect(QPoint(0, 0), w->size()); + QPoint localTouchPos = localPos.toPoint(); + bool inBounds = windowRect.contains(localTouchPos); + + // 使用本地坐标检查(更可靠) + if (!inBounds) { + return false; + } + + // 开始窗口移动 + if (isEnableSystemMove(m_windowID)) { + qDebug() << "Starting touch drag for QML window ID:" << m_windowID + << "on screen:" << (windowScreen ? windowScreen->name() : "unknown"); + m_windowMoving = true; + touchIsMoving[touchId] = true; + + startMoveWindow(m_windowID); + touchEvent->accept(); + return true; + } + } else if (touchIsMoving[touchId] && m_windowMoving) { + // ========== 修改:使用带坐标参数的updateMousePointForWindowMove ========== + Utility::updateMousePointForWindowMove(m_windowID, currentPos.toPoint()); + touchEvent->accept(); + return true; + } + break; + } + + case QEvent::TouchEnd: { + if (touchIsMoving.value(touchId, false)) { + m_windowMoving = false; + // ========== 修改:使用最后的触摸位置结束窗口移动 ========== + QPointF lastPos = lastTouchPositions.value(touchId, globalPos); + Utility::updateMousePointForWindowMove(m_windowID, lastPos.toPoint(), true); + qDebug() << "Finished touch drag for QML window ID:" << m_windowID; + } + + // 清理状态 + touchStartPositions.remove(touchId); + touchIsMoving.remove(touchId); + lastTouchPositions.remove(touchId); + break; + } + + default: + break; + } + + return false; +} +#endif + DPP_END_NAMESPACE diff --git a/xcb/dnotitlebarwindowhelper.h b/xcb/dnotitlebarwindowhelper.h index 9c943067..6b4bfa30 100644 --- a/xcb/dnotitlebarwindowhelper.h +++ b/xcb/dnotitlebarwindowhelper.h @@ -15,6 +15,9 @@ QT_BEGIN_NAMESPACE class QWindow; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +class QPointerEvent; +#endif QT_END_NAMESPACE DPP_BEGIN_NAMESPACE @@ -104,6 +107,14 @@ private slots: bool isEnableSystemMove(quint32 winId); bool updateWindowBlurAreasForWM(); void updateWindowShape(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + // 新增:QML窗口触摸拖拽处理函数 (仅Qt6) + bool handleTouchDragForQML(QPointerEvent *touchEvent); +#else + // Qt5版本的触摸拖拽处理函数 + bool handleTouchDragForQML(QTouchEvent *touchEvent); +#endif void onWindowSizeChanged(); @@ -114,6 +125,7 @@ private slots: quint32 m_windowID; bool m_windowMoving = false; bool m_nativeSettingsValid = false; + bool m_isQuickWindow = false; // 新增:缓存窗口类型判断结果 QVector m_blurAreaList; QList m_blurPathList; diff --git a/xcb/utility.h b/xcb/utility.h index cd44baf0..6c93d717 100644 --- a/xcb/utility.h +++ b/xcb/utility.h @@ -54,6 +54,8 @@ class Utility // 导致窗口无法移动。此处跟deepin-wm配合,使用其它方式通知窗管鼠标位置更新了 // TODO: kwin适配udpate之后没有结束move状态,新增一个finished参数,当传入true时通知kwin结束 static void updateMousePointForWindowMove(quint32 WId, bool finished = false); + // 新增:支持多屏幕的版本,接受自定义全局坐标 + static void updateMousePointForWindowMove(quint32 WId, const QPoint &globalPos, bool finished = false); static void showWindowSystemMenu(quint32 WId, QPoint globalPos = QPoint()); static void setFrameExtents(WId wid, const QMargins &margins); diff --git a/xcb/utility_x11.cpp b/xcb/utility_x11.cpp index f29ed705..06386bf0 100644 --- a/xcb/utility_x11.cpp +++ b/xcb/utility_x11.cpp @@ -168,8 +168,15 @@ void Utility::cancelWindowMoveResize(quint32 WId) void Utility::updateMousePointForWindowMove(quint32 WId, bool finished/* = false*/) { - xcb_client_message_event_t xev; + // 获取主屏幕的光标位置并调用重载版本 const QPoint &globalPos = qApp->primaryScreen()->handle()->cursor()->pos(); + updateMousePointForWindowMove(WId, globalPos, finished); +} + +// 新增:支持多屏幕的版本,接受自定义全局坐标 +void Utility::updateMousePointForWindowMove(quint32 WId, const QPoint &globalPos, bool finished/* = false*/) +{ + xcb_client_message_event_t xev; xev.response_type = XCB_CLIENT_MESSAGE; xev.type = internAtom("_DEEPIN_MOVE_UPDATE");