From 68271653bec0de7944bcbba73f72923265978fac Mon Sep 17 00:00:00 2001 From: guolin Date: Wed, 23 Sep 2026 08:25:51 +0800 Subject: [PATCH 1/2] feat(dock): draw windowless app tooltips on treeland MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add requestTooltip to the window monitor and route the plain-text tooltip of app items without windows through the treeland dock preview context, so the compositor can animate between tooltip and window preview. The preview context is now created lazily and shared by both requests. 无窗口应用的悬停提示改由合成器绘制,preview context 改为惰性创建并 在预览与 tooltip 之间复用,使两者切换动画连续。 Log: 无窗口应用的 tooltip 交由合成器绘制 PMS: TASK-396711 Influence: wayland(treeland)下无窗口应用的 tooltip 由合成器绘制,与窗口预览切换更连贯;X11 行为不变。 --- .../taskmanager/abstractwindowmonitor.cpp | 10 ++ .../dock/taskmanager/abstractwindowmonitor.h | 4 + panels/dock/taskmanager/package/AppItem.qml | 61 +++++++++- panels/dock/taskmanager/taskmanager.cpp | 10 ++ panels/dock/taskmanager/taskmanager.h | 2 + .../taskmanager/treelandwindowmonitor.cpp | 104 ++++++++++++------ .../dock/taskmanager/treelandwindowmonitor.h | 5 + 7 files changed, 157 insertions(+), 39 deletions(-) diff --git a/panels/dock/taskmanager/abstractwindowmonitor.cpp b/panels/dock/taskmanager/abstractwindowmonitor.cpp index c8b198e7f..df5771d54 100644 --- a/panels/dock/taskmanager/abstractwindowmonitor.cpp +++ b/panels/dock/taskmanager/abstractwindowmonitor.cpp @@ -69,6 +69,16 @@ void AbstractWindowMonitor::requestUpdateWindowIconGeometry(const QModelIndex &i { } +bool AbstractWindowMonitor::requestTooltip(const QString &text, QWindow *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction) +{ + Q_UNUSED(text) + Q_UNUSED(relativePositionItem) + Q_UNUSED(previewXoffset) + Q_UNUSED(previewYoffset) + Q_UNUSED(direction) + return false; +} + void AbstractWindowMonitor::requestWindowsView(const QModelIndexList &indexes) const { qDebug() << indexes; diff --git a/panels/dock/taskmanager/abstractwindowmonitor.h b/panels/dock/taskmanager/abstractwindowmonitor.h index dbc20dec4..0e0db9df6 100644 --- a/panels/dock/taskmanager/abstractwindowmonitor.h +++ b/panels/dock/taskmanager/abstractwindowmonitor.h @@ -45,6 +45,10 @@ class AbstractWindowMonitor : public QAbstractListModel, public AbstractTaskMana virtual void requestPreview(QAbstractItemModel *sourceModel, QWindow *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction) = 0; + // 返回是否已交由合成器绘制,false 时调用方应回退到自身的 tooltip。 + virtual bool + requestTooltip(const QString &text, QWindow *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction); + void requestWindowsView(const QModelIndexList &indexes) const override; virtual void presentWindows(QList windowsId) = 0; diff --git a/panels/dock/taskmanager/package/AppItem.qml b/panels/dock/taskmanager/package/AppItem.qml index 3ec95614d..2975e85cf 100644 --- a/panels/dock/taskmanager/package/AppItem.qml +++ b/panels/dock/taskmanager/package/AppItem.qml @@ -436,6 +436,9 @@ Item { } } + readonly property bool useCompositorTooltip: Qt.platform.pluginName === "wayland" + property bool compositorTooltipActive: false + Timer { id: previewTimer interval: 500 @@ -444,16 +447,34 @@ Item { property int xOffset: 0 property int yOffset: 0 onTriggered: { - if (root.windows.length != 0 || Qt.platform.pluginName === "wayland") { + if (root.windows.length != 0) { // 使用基于 modelIndex 的预览API,确保精确匹配 taskmanager.Applet.requestPreview(root.modelIndex, Panel.rootObject, xOffset, yOffset, Panel.position); } } } + Timer { + id: tooltipTimer + interval: 50 + running: false + repeat: false + property int xOffset: 0 + property int yOffset: 0 + onTriggered: { + var text = root.itemId === "dde-trash" ? root.name + "-" + taskmanager.Applet.getTrashTipText() : root.name + // 合成器协议不可用时回退到原生 tooltip,避免 tooltip 直接消失 + if (taskmanager.Applet.requestTooltip(text, Panel.rootObject, xOffset, yOffset, Panel.position)) { + root.compositorTooltipActive = true + } else { + root.compositorTooltipActive = false + toolTipShowTimer.start() + } + } + } function onEntered() { - if (windows.length === 0) { + if (windows.length === 0 && !useCompositorTooltip) { toolTipShowTimer.start() return } @@ -467,6 +488,16 @@ Item { xOffset = (Panel.position == 1 ? -interval : interval + Panel.dockSize) yOffset = itemPos.y + (root.height / 2) } + + if (windows.length === 0) { + // 无窗口应用:交给合成器绘制 tooltip,以便与窗口预览保持连续的过渡动画 + tooltipTimer.xOffset = xOffset + tooltipTimer.yOffset = yOffset + tooltipTimer.start() + return + } + + compositorTooltipActive = false previewTimer.xOffset = xOffset previewTimer.yOffset = yOffset previewTimer.start() @@ -481,8 +512,17 @@ Item { previewTimer.stop() } + if (tooltipTimer.running) { + tooltipTimer.stop() + } + if (windows.length === 0) { - toolTip.close() + if (useCompositorTooltip) { + compositorTooltipActive = false + taskmanager.Applet.hideItemPreview() + } else { + toolTip.close() + } return } closeItemPreview() @@ -537,6 +577,14 @@ Item { root.Drag.imageSource = result.url; }) } + // 按下时立即取消待显示的 tooltip,避免启动应用后 tooltip 才弹出 + if (tooltipTimer.running) { + tooltipTimer.stop() + } + if (compositorTooltipActive) { + compositorTooltipActive = false + taskmanager.Applet.hideItemPreview() + } toolTip.close() closeItemPreview() } @@ -632,6 +680,13 @@ Item { if (windows.length > 0 && toolTip.toolTipVisible) { toolTip.close() } + // 无窗口应用在悬停期间新开窗口时,从合成器 tooltip 切换到窗口预览 + if (windows.length > 0 && compositorTooltipActive) { + compositorTooltipActive = false + previewTimer.xOffset = tooltipTimer.xOffset + previewTimer.yOffset = tooltipTimer.yOffset + previewTimer.start() + } } onIconGlobalPointChanged: { diff --git a/panels/dock/taskmanager/taskmanager.cpp b/panels/dock/taskmanager/taskmanager.cpp index 02ef7189e..64a284684 100644 --- a/panels/dock/taskmanager/taskmanager.cpp +++ b/panels/dock/taskmanager/taskmanager.cpp @@ -317,6 +317,16 @@ void TaskManager::requestPreview(const QModelIndex &index, QObject *relativePosi m_windowMonitor->requestPreview(m_hoverPreviewModel, qobject_cast(relativePositionItem), previewXoffset, previewYoffset, direction); } +bool TaskManager::requestTooltip(const QString &text, QObject *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction) +{ + if (!m_windowMonitor) { + qCWarning(taskManagerLog) << "TaskManager::requestTooltip: m_windowMonitor is null"; + return false; + } + + return m_windowMonitor->requestTooltip(text, qobject_cast(relativePositionItem), previewXoffset, previewYoffset, direction); +} + void TaskManager::requestWindowsView(const QModelIndexList &indexes) const { dataModel()->requestWindowsView(indexes); diff --git a/panels/dock/taskmanager/taskmanager.h b/panels/dock/taskmanager/taskmanager.h index 2f1ab4444..bdd277071 100644 --- a/panels/dock/taskmanager/taskmanager.h +++ b/panels/dock/taskmanager/taskmanager.h @@ -93,6 +93,8 @@ class TaskManager : public DS_NAMESPACE::DContainment, public AbstractTaskManage Q_INVOKABLE void requestUpdateWindowIconGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) const override; Q_INVOKABLE void requestPreview(const QModelIndex &index, QObject *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction); + Q_INVOKABLE bool + requestTooltip(const QString &text, QObject *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction); Q_INVOKABLE void requestWindowsView(const QModelIndexList &indexes) const override; Q_INVOKABLE QString desktopIdToAppId(const QString& desktopId); diff --git a/panels/dock/taskmanager/treelandwindowmonitor.cpp b/panels/dock/taskmanager/treelandwindowmonitor.cpp index d0ccaabc1..2675a281f 100644 --- a/panels/dock/taskmanager/treelandwindowmonitor.cpp +++ b/panels/dock/taskmanager/treelandwindowmonitor.cpp @@ -9,6 +9,7 @@ #include "taskmanager.h" #include "treelandwindow.h" +#include #include #include #include @@ -61,6 +62,12 @@ void TreeLandDockPreviewContext::showWindowsPreview(QByteArray windowsId, int32_ show(windowsId, previewXoffset, previewYoffset, direction); } +void TreeLandDockPreviewContext::showTooltip(const QString &text, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction) +{ + m_isDockMouseAreaEnter = true; + show_tooltip(text, previewXoffset, previewYoffset, direction); +} + void TreeLandDockPreviewContext::hideWindowsPreview() { m_isDockMouseAreaEnter = false; @@ -126,57 +133,82 @@ void TreeLandWindowMonitor::hideItemPreview() m_dockPreview->hideWindowsPreview(); } +void TreeLandWindowMonitor::ensureDockPreviewContext(QWindow *relativePositionItem) +{ + if (!m_dockPreview.isNull()) + return; + + auto window = relativePositionItem; + if (!window) return; + auto waylandWindow = dynamic_cast(window->handle()); + if (!waylandWindow) return; + + auto context = m_foreignToplevelManager->get_dock_preview_context(waylandWindow->wlSurface()); + m_dockPreview.reset(new TreeLandDockPreviewContext(context)); + connect(window, &QWindow::visibleChanged, m_dockPreview.get(), [this]() { + m_dockPreview.reset(); + }); + connect(m_dockPreview.get(), &TreeLandDockPreviewContext::closed, this, [this]() { + // 当预览关闭时,发出信号清空过滤状态 + emit previewShouldClear(); + }); +} + void TreeLandWindowMonitor::requestPreview(QAbstractItemModel *sourceModel, QWindow *relativePositionItem, int32_t previewXoffset, int32_t previewYoffset, uint32_t direction) { - if (!m_foreignToplevelManager->isActive()) { + if (!m_foreignToplevelManager || !m_foreignToplevelManager->isActive()) { return; } - if (m_dockPreview.isNull()) { - auto window = relativePositionItem; - if (!window) return; - auto waylandWindow = dynamic_cast(window->handle()); - if (!waylandWindow) return; - - auto context = m_foreignToplevelManager->get_dock_preview_context(waylandWindow->wlSurface()); - m_dockPreview.reset(new TreeLandDockPreviewContext(context)); - connect(window, &QWindow::visibleChanged, m_dockPreview.get(), [this]() { - m_dockPreview.reset(); - }); - connect(m_dockPreview.get(), &TreeLandDockPreviewContext::closed, this, [this]() { - // 当预览关闭时,发出信号清空过滤状态 - emit previewShouldClear(); - }); + if (!sourceModel || sourceModel->rowCount() == 0) { + qWarning() << "requestPreview called without any window"; + return; } - m_dockPreview->m_isDockMouseAreaEnter = true; + ensureDockPreviewContext(relativePositionItem); + if (m_dockPreview.isNull()) + return; - if (sourceModel && sourceModel->rowCount() > 0) { - QVarLengthArray array; - for (int i = 0; i < sourceModel->rowCount(); ++i) { - QModelIndex index = sourceModel->index(i, 0); - uint32_t winId = index.data(TaskManager::WinIdRole).toUInt(); - if (winId != 0) { - array.append(winId); - } + QVarLengthArray array; + for (int i = 0; i < sourceModel->rowCount(); ++i) { + QModelIndex index = sourceModel->index(i, 0); + uint32_t winId = index.data(TaskManager::WinIdRole).toUInt(); + if (winId != 0) { + array.append(winId); } + } - if (!array.isEmpty()) { - QByteArray byteArray; - int size = array.size() * sizeof(uint32_t); - byteArray.resize(size); - memcpy(byteArray.data(), array.constData(), size); - m_dockPreview->showWindowsPreview(byteArray, previewXoffset, previewYoffset, direction); - } else { - m_dockPreview->show_tooltip("???", previewXoffset, previewYoffset, direction); - } - } else { - m_dockPreview->show_tooltip("???", previewXoffset, previewYoffset, direction); + if (array.isEmpty()) { + qWarning() << "requestPreview called without any valid window id"; + return; } + + QByteArray byteArray; + int size = array.size() * sizeof(uint32_t); + byteArray.resize(size); + memcpy(byteArray.data(), array.constData(), size); + m_dockPreview->showWindowsPreview(byteArray, previewXoffset, previewYoffset, direction); +} + +bool TreeLandWindowMonitor::requestTooltip(const QString &text, + QWindow *relativePositionItem, + int32_t previewXoffset, + int32_t previewYoffset, + uint32_t direction) +{ + if (!m_foreignToplevelManager || !m_foreignToplevelManager->isActive()) + return false; + + ensureDockPreviewContext(relativePositionItem); + if (m_dockPreview.isNull()) + return false; + + m_dockPreview->showTooltip(text, previewXoffset, previewYoffset, direction); + return true; } void TreeLandWindowMonitor::requestUpdateWindowIconGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate) const diff --git a/panels/dock/taskmanager/treelandwindowmonitor.h b/panels/dock/taskmanager/treelandwindowmonitor.h index ec06b4845..4aa8c9fa5 100644 --- a/panels/dock/taskmanager/treelandwindowmonitor.h +++ b/panels/dock/taskmanager/treelandwindowmonitor.h @@ -40,6 +40,7 @@ class TreeLandDockPreviewContext : public QWaylandClientExtensionTemplate> m_windows; QScopedPointer m_foreignToplevelManager; QScopedPointer m_dockPreview; From 392c6538823a06210af1be6b91a14cad7dfcaac7 Mon Sep 17 00:00:00 2001 From: guolin Date: Tue, 20 Oct 2026 15:49:54 +0800 Subject: [PATCH 2/2] feat(dock): use compositor tooltip for the multitasking view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve the task manager applet of the same dock panel and reuse its dock preview context, so the multitasking view tooltip is drawn by the compositor on wayland and falls back to PanelToolTip otherwise. 多任务视图在 wayland 下复用同面板 taskmanager 的 dock preview context, 由合成器绘制 tooltip;其余情况回退到 PanelToolTip。 Log: 多任务视图 tooltip 改用合成器绘制 PMS: TASK-396711 Influence: wayland 下多任务视图 tooltip 与任务栏 tooltip 一致;找不到 taskmanager 或非 wayland 时行为不变。 --- .../multitaskview/package/multitaskview.qml | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/panels/dock/multitaskview/package/multitaskview.qml b/panels/dock/multitaskview/package/multitaskview.qml index 2e2b524c2..b165b7e78 100644 --- a/panels/dock/multitaskview/package/multitaskview.qml +++ b/panels/dock/multitaskview/package/multitaskview.qml @@ -34,10 +34,58 @@ AppletDockItem { sourceSize: Qt.size(Dock.MAX_DOCK_TASKMANAGER_ICON_SIZE, Dock.MAX_DOCK_TASKMANAGER_ICON_SIZE) } + readonly property bool useCompositorTooltip: Qt.platform.pluginName === "wayland" + property bool compositorTooltipActive: false + function compositorTooltipApplet() { + if (!useCompositorTooltip) + return null + + const applets = DS.appletList("org.deepin.ds.dock.taskmanager") + for (let i = 0; i < applets.length; ++i) { + if (applets[i].parent === Applet.parent) + return applets[i] + } + return null + } + + function hideTooltip() { + if (toolTipShowTimer.running) + toolTipShowTimer.stop() + + if (compositorTooltipActive) { + compositorTooltipActive = false + var applet = compositorTooltipApplet() + if (applet) + applet.hideItemPreview() + } else { + toolTip.close() + } + } + Timer { id: toolTipShowTimer interval: 50 onTriggered: { + var applet = compositorTooltipApplet() + if (applet) { + var itemPos = toggleworkspace.mapToItem(null, 0, 0) + var xOffset, yOffset, interval = 10 + if (!useColumnLayout) { + xOffset = itemPos.x + toggleworkspace.width / 2 + yOffset = (Panel.position == Dock.Bottom ? -interval : interval + Panel.dockSize) + } else { + xOffset = (Panel.position == Dock.Right ? -interval : interval + Panel.dockSize) + yOffset = itemPos.y + toggleworkspace.height / 2 + } + + compositorTooltipActive = false + if (applet.requestTooltip(toolTip.text, Panel.rootObject, xOffset, yOffset, Panel.position)) { + compositorTooltipActive = true + return + } + // 合成器协议不可用时回退到原生 tooltip,避免 tooltip 直接消失 + } + var point = toggleworkspace.mapToItem(null, toggleworkspace.width / 2, toggleworkspace.height / 2) toolTip.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, toolTip.width, toolTip.height) toolTip.open() @@ -51,7 +99,7 @@ AppletDockItem { onTapped: function (eventPoint, buttons) { if (buttons === Qt.LeftButton) { Applet.openWorkspace() - toolTip.close() + hideTooltip() } } } @@ -62,7 +110,7 @@ AppletDockItem { gesturePolicy: TapHandler.WithinBounds onTapped: function (eventPoint, buttons) { Applet.openWorkspace() - toolTip.close() + hideTooltip() } } HoverHandler { @@ -70,11 +118,7 @@ AppletDockItem { if (hovered) { toolTipShowTimer.start() } else { - if (toolTipShowTimer.running) { - toolTipShowTimer.stop() - } - - toolTip.close() + hideTooltip() } } }