From f0b0885da9a4cc822b00ee4b13e044ae6fd37e72 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Fri, 11 Sep 2026 17:46:49 +0800 Subject: [PATCH] feat: add split screen menu to title bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add splitScreenEnabled property to TitleBar and WindowButtonGroup components 2. Implement showSplitMenu and hideSplitMenu methods in DQuickWindowAttached 3. Add platform function bindings for _d_supportSplitMenu, _d_showSplitMenu, and _d_hideSplitMenu 4. Add hover timer (700ms) and hold timer (300ms) to trigger split menu on maximize button 5. Track splitMenuWindowId in DQuickWindowPrivate to manage menu state 6. Handle menu lifecycle on hover, press, release, cancel, visibility and enabled changes 7. Map button geometry to global coordinates for menu positioning Log: Added split screen menu triggered by hovering or long-pressing the maximize button Influence: 1. Test hovering over maximize button for 700ms to show split screen menu 2. Test long-pressing maximize button for 300ms to trigger split menu 3. Verify normal click behavior on maximize button still works when not long-pressed 4. Test menu hides on mouse leave and when splitScreenEnabled is set to false 5. Verify split menu only appears when platform supports _d_supportSplitMenu 6. Test split menu positioning aligns with the maximize button location 7. Verify menu interaction works correctly in embed mode and fullscreen scenarios 8. Test behavior when window is not visible or button is disabled feat: 为标题栏添加分屏菜单 1. 在 TitleBar 和 WindowButtonGroup 组件中添加 splitScreenEnabled 属性 2. 在 DQuickWindowAttached 中实现 showSplitMenu 和 hideSplitMenu 方法 3. 添加 _d_supportSplitMenu、_d_showSplitMenu 和 _d_hideSplitMenu 平台函 数绑定 4. 为最大化按钮添加悬停定时器(700ms)和长按定时器(300ms)以触发分屏 菜单 5. 在 DQuickWindowPrivate 中跟踪 splitMenuWindowId 以管理菜单状态 6. 在悬停、按下、释放、取消、可见性和启用状态变化时处理菜单生命周期 7. 将按钮几何位置映射到全局坐标以定位菜单 Log: 新增通过悬停或长按最大化按钮触发的分屏菜单 Influence: 1. 测试悬停最大化按钮 700ms 后显示分屏菜单 2. 测试长按最大化按钮 300ms 触发分屏菜单 3. 验证非长按时最大化按钮的正常点击行为仍然有效 4. 测试鼠标离开和 splitScreenEnabled 设为 false 时菜单隐藏 5. 验证仅在平台支持 _d_supportSplitMenu 时才显示分屏菜单 6. 测试分屏菜单位置是否与最大化按钮位置对齐 7. 验证嵌入模式和全屏场景下菜单交互正常 8. 测试窗口不可见或按钮禁用时的行为 PMS: BUG-370181 --- qt6/src/qml/TitleBar.qml | 2 + qt6/src/qml/WindowButtonGroup.qml | 79 ++++++++++++++++++++++++++++++- src/dquickwindow.cpp | 41 ++++++++++++++++ src/dquickwindow.h | 3 ++ src/private/dquickwindow_p.h | 1 + 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/qt6/src/qml/TitleBar.qml b/qt6/src/qml/TitleBar.qml index 48902a9d5..68bbf07b4 100644 --- a/qt6/src/qml/TitleBar.qml +++ b/qt6/src/qml/TitleBar.qml @@ -28,6 +28,7 @@ Item { // visibility access property bool fullScreenButtonVisible: true + property bool splitScreenEnabled: true signal toggleWindowState() property alias windowButtonGroup: windowButtonsLoader.sourceComponent @@ -179,6 +180,7 @@ Item { embedMode: control.embedMode textColor: control.textColor fullScreenButtonVisible: control.fullScreenButtonVisible + splitScreenEnabled: control.splitScreenEnabled Component.onCompleted: { control.toggleWindowState.connect(maxOrWinded) } diff --git a/qt6/src/qml/WindowButtonGroup.qml b/qt6/src/qml/WindowButtonGroup.qml index a86a9de13..8d1cdd346 100644 --- a/qt6/src/qml/WindowButtonGroup.qml +++ b/qt6/src/qml/WindowButtonGroup.qml @@ -14,6 +14,7 @@ RowLayout { property D.Palette textColor: DS.Style.button.text property bool fullScreenButtonVisible: true + property bool splitScreenEnabled: true property bool embedMode: false signal maxOrWinded() @@ -76,6 +77,7 @@ RowLayout { WindowButton { id: maxOrWindedBtn; objectName: "maxOrWindedBtn" + hoverEnabled: true property bool hasWindowFlag/*: (Window.window.flags & Qt.WindowMaximizeButtonHint)*/ Component.onCompleted: hasWindowFlag = (Window.window.flags & Qt.WindowMaximizeButtonHint) @@ -86,7 +88,82 @@ RowLayout { property bool isMaximized: Window.window.visibility === Window.Maximized icon.name: isMaximized ? "window_restore" : "window_maximize" textColor: control.textColor - onClicked: maxOrWinded() + property bool __longPressed: false + property bool __menuRequested: false + + function showSplitMenu() { + if (!control.splitScreenEnabled || !visible || !enabled) + return false + if (!__menuRequested) + __menuRequested = control.__dwindow.showSplitMenu(maxOrWindedBtn) + return __menuRequested + } + + function hideSplitMenu(delay) { + hoverTimer.stop() + holdTimer.stop() + control.__dwindow.hideSplitMenu(delay) + __menuRequested = false + } + + onHoveredChanged: { + if (hovered && !pressed) + hoverTimer.restart() + else if (!hovered) { + hoverTimer.stop() + if (pressed) + holdTimer.stop() + else + hideSplitMenu(true) + } + } + onPressed: { + __longPressed = false + hoverTimer.stop() + holdTimer.restart() + } + onReleased: { + holdTimer.stop() + if (!__longPressed) + hideSplitMenu(false) + else + __menuRequested = false + } + onCanceled: { + hideSplitMenu(true) + __longPressed = false + } + onClicked: { + if (!__longPressed) + control.maxOrWinded() + __longPressed = false + } + onVisibleChanged: { + if (!visible) { + hoverTimer.stop() + holdTimer.stop() + __menuRequested = false + } + } + onEnabledChanged: if (!enabled) hideSplitMenu(false) + + Timer { + id: hoverTimer + interval: 700 + onTriggered: maxOrWindedBtn.showSplitMenu() + } + Timer { + id: holdTimer + interval: 300 + onTriggered: maxOrWindedBtn.__longPressed = maxOrWindedBtn.showSplitMenu() + } + Connections { + target: control + function onSplitScreenEnabledChanged() { + if (!control.splitScreenEnabled) + maxOrWindedBtn.hideSplitMenu(false) + } + } visible: (hasWindowFlag && !__forceHind && __canResize && __sizeResizable) enabled: (__canMaximize && __canResize) diff --git a/src/dquickwindow.cpp b/src/dquickwindow.cpp index 4988bc1dd..8b5b863bc 100644 --- a/src/dquickwindow.cpp +++ b/src/dquickwindow.cpp @@ -15,6 +15,8 @@ #include #include +#include +#include DQUICK_BEGIN_NAMESPACE @@ -1010,6 +1012,45 @@ void DQuickWindowAttached::popupSystemWindowMenu() DWindowManagerHelper::popupSystemWindowMenu(window()); } +// Platform split-menu functions take a Qt window id and a global logical rectangle. +bool DQuickWindowAttached::showSplitMenu(QQuickItem *button) +{ + D_D(DQuickWindowAttached); + auto *w = window(); + if (!w || !w->isVisible() || !w->handle() || !button || button->window() != w + || !button->isVisible() || !button->isEnabled()) + return false; + + const auto support = reinterpret_cast( + QGuiApplication::platformFunction("_d_supportSplitMenu")); + const auto show = reinterpret_cast( + QGuiApplication::platformFunction("_d_showSplitMenu")); + const auto hide = QGuiApplication::platformFunction("_d_hideSplitMenu"); + const WId wid = w->winId(); + if (!support || !show || !hide || !support(wid)) + return false; + + const QRect rect = button->mapRectToScene(button->boundingRect()).toAlignedRect(); + if (!rect.isValid()) + return false; + show(wid, QRect(w->mapToGlobal(rect.topLeft()), rect.size())); + d->splitMenuWindowId = wid; + return true; +} + +void DQuickWindowAttached::hideSplitMenu(bool delay) +{ + D_D(DQuickWindowAttached); + if (!d->splitMenuWindowId) + return; + const auto hide = reinterpret_cast( + QGuiApplication::platformFunction("_d_hideSplitMenu")); + if (hide) + hide(d->splitMenuWindowId, delay); + if (!delay) + d->splitMenuWindowId = 0; +} + bool DQuickWindowAttached::setWindowBlurAreaByWM(const QVector &area) { D_D(DQuickWindowAttached); diff --git a/src/dquickwindow.h b/src/dquickwindow.h index db67525b4..bf74fad1f 100644 --- a/src/dquickwindow.h +++ b/src/dquickwindow.h @@ -148,6 +148,9 @@ public Q_SLOTS: void popupSystemWindowMenu(); + bool showSplitMenu(QQuickItem *button); + void hideSplitMenu(bool delay = true); + bool setWindowBlurAreaByWM(const QVector &area); bool setWindowBlurAreaByWM(const QList &area); diff --git a/src/private/dquickwindow_p.h b/src/private/dquickwindow_p.h index 801279e78..07efd715e 100644 --- a/src/private/dquickwindow_p.h +++ b/src/private/dquickwindow_p.h @@ -56,6 +56,7 @@ class DQuickWindowAttachedPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate #endif QPointer window = nullptr; + WId splitMenuWindowId = 0; DPlatformHandle *handle = nullptr; BoolOptional explicitEnable {Invalid}; BoolOptional explicitTranslucentBackground {Invalid};