From 4f84a534a2f49836325a01e7a9e4d41ff7812ad5 Mon Sep 17 00:00:00 2001 From: wjyrich Date: Mon, 13 Apr 2026 17:58:34 +0800 Subject: [PATCH] feat: add PositionFixer component for pixel-perfect positioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a new PositionFixer QML component to handle pixel-perfect positioning of items within the dock panel. This component replaces multiple ad-hoc position fixing implementations in AppItem.qml and ShellSurfaceItemProxy.qml with a unified solution. The fixer calculates the correct physical pixel position considering device pixel ratio and applies rounding or ceiling to prevent sub-pixel rendering issues that cause blurriness. Key changes: 1. Created PositionFixer C++ class with QML integration 2. Added PositionFixer to AppletItemButton.qml for applet positioning 3. Replaced manual position fixing in AppItem.qml with PositionFixer 4. Replaced manual position fixing in ShellSurfaceItemProxy.qml with PositionFixer 5. Added CMakeLists.txt entries for new PositionFixer files The PositionFixer component provides configurable options including useCeil for ceiling operations and useZeroTarget for positioning at origin. It uses a delayed timer to ensure proper timing for position calculations. Log: Improved icon and surface positioning accuracy in dock panel Influence: 1. Test applet icons in dock panel for proper positioning without blurriness 2. Verify application icons in task manager maintain crisp edges 3. Check shell surface items (like system tray popups) render at correct pixel boundaries 4. Test with different display scaling factors (100%, 150%, 200%) 5. Verify drag-and-drop operations still work correctly 6. Test launch animations don't interfere with positioning feat: 添加 PositionFixer 组件实现像素级精确定位 新增 PositionFixer QML 组件,用于处理任务栏面板内项目的像素级精确定位。 该组件取代了 AppItem.qml 和 ShellSurfaceItemProxy.qml 中多个临时位置修 复实现,提供了统一的解决方案。修复器会计算考虑设备像素比的正确物理像素位 置,并应用四舍五入或向上取整操作,防止导致模糊的子像素渲染问题。 主要变更: 1. 创建了带有 QML 集成的 PositionFixer C++ 类 2. 在 AppletItemButton.qml 中添加 PositionFixer 用于小程序定位 3. 在 AppItem.qml 中用 PositionFixer 替换手动位置修复 4. 在 ShellSurfaceItemProxy.qml 中用 PositionFixer 替换手动位置修复 5. 在 CMakeLists.txt 中添加新 PositionFixer 文件的条目 PositionFixer 组件提供可配置选项,包括用于向上取整操作的 useCeil 和用于 原点定位的 useZeroTarget。它使用延迟计时器确保位置计算的正确时机。 Log: 提升任务栏面板中图标和表面定位的准确性 Influence: 1. 测试任务栏面板中的小程序图标定位是否正确且无模糊 2. 验证任务管理器中的应用图标是否保持清晰边缘 3. 检查外壳表面项目(如系统托盘弹出窗口)是否在正确的像素边界渲染 4. 使用不同的显示缩放比例测试(100%、150%、200%) 5. 验证拖放操作是否仍能正常工作 6. 测试启动动画是否不会干扰定位 --- panels/dock/AppletItemButton.qml | 23 ++++ panels/dock/CMakeLists.txt | 2 + panels/dock/positionfixer.cpp | 79 +++++++++++++ panels/dock/positionfixer.h | 44 +++++++ panels/dock/taskmanager/package/AppItem.qml | 121 ++++++++------------ panels/dock/tray/ShellSurfaceItemProxy.qml | 41 ++----- 6 files changed, 204 insertions(+), 106 deletions(-) create mode 100644 panels/dock/positionfixer.cpp create mode 100644 panels/dock/positionfixer.h diff --git a/panels/dock/AppletItemButton.qml b/panels/dock/AppletItemButton.qml index 0b94c3777..1c52fd334 100644 --- a/panels/dock/AppletItemButton.qml +++ b/panels/dock/AppletItemButton.qml @@ -42,4 +42,27 @@ IconButton { Component.onCompleted: { contentItem.smooth = false } + + property var contentGlobalPoint: { + var a = contentItem + if (!a) return Qt.point(0, 0) + var x = 0, y = 0 + while (a && a.parent) { + x += a.x + y += a.y + a = a.parent + } + + return Qt.point(x, y) + } + + PositionFixer { + id: positionFixer + item: control + container: control + } + + onContentGlobalPointChanged: { + positionFixer.fix() + } } diff --git a/panels/dock/CMakeLists.txt b/panels/dock/CMakeLists.txt index 98d20dce7..a2bc3037f 100644 --- a/panels/dock/CMakeLists.txt +++ b/panels/dock/CMakeLists.txt @@ -110,6 +110,8 @@ file( pluginmanagerintegration.cpp dockpositioner.h dockpositioner.cpp + positionfixer.h + positionfixer.cpp ) set_source_files_properties(DockCompositor.qml PROPERTIES diff --git a/panels/dock/positionfixer.cpp b/panels/dock/positionfixer.cpp new file mode 100644 index 000000000..0aff77061 --- /dev/null +++ b/panels/dock/positionfixer.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "positionfixer.h" +#include +#include + +namespace dock { + +PositionFixer::PositionFixer(QQuickItem *parent) + : QQuickItem(parent) +{ + m_timer = new QTimer(this); + m_timer->setInterval(100); + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, &PositionFixer::forceFix); +} + +QQuickItem *PositionFixer::item() const +{ + return m_item; +} + +void PositionFixer::setItem(QQuickItem *newItem) +{ + if (m_item == newItem) + return; + m_item = newItem; + if (m_item && !m_container) { + setContainer(m_item->parentItem()); + } + emit itemChanged(); +} + +QQuickItem *PositionFixer::container() const +{ + return m_container; +} + +void PositionFixer::setContainer(QQuickItem *newContainer) +{ + if (m_container == newContainer) + return; + m_container = newContainer; + emit containerChanged(); +} + +void PositionFixer::fix() +{ + m_timer->start(); +} + +void PositionFixer::forceFix() +{ + if (!m_item || !m_container || !m_container->window()) { + return; + } + + QQuickItem *contentItem = m_container->window()->contentItem(); + if (!contentItem) { + return; + } + + QPointF scenePos = m_container->mapToItem(contentItem, QPointF(0, 0)); + + qreal dpr = m_container->window()->devicePixelRatio(); + qreal physicalX = std::round(scenePos.x() * dpr); + qreal physicalY = std::round(scenePos.y() * dpr); + + QQuickItem *itemParent = m_item->parentItem() ? m_item->parentItem() : m_container; + + QPointF localPosX = itemParent->mapFromItem(contentItem, QPointF(physicalX / dpr, scenePos.y())); + QPointF localPosY = itemParent->mapFromItem(contentItem, QPointF(scenePos.x(), physicalY / dpr)); + m_item->setX(localPosX.x()); + m_item->setY(localPosY.y()); +} + +} diff --git a/panels/dock/positionfixer.h b/panels/dock/positionfixer.h new file mode 100644 index 000000000..380628e39 --- /dev/null +++ b/panels/dock/positionfixer.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace dock { + +class PositionFixer : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *item READ item WRITE setItem NOTIFY itemChanged) + Q_PROPERTY(QQuickItem *container READ container WRITE setContainer NOTIFY containerChanged) + + QML_NAMED_ELEMENT(PositionFixer) + +public: + explicit PositionFixer(QQuickItem *parent = nullptr); + + QQuickItem *item() const; + void setItem(QQuickItem *newItem); + + QQuickItem *container() const; + void setContainer(QQuickItem *newContainer); + + Q_INVOKABLE void fix(); + Q_INVOKABLE void forceFix(); + +signals: + void itemChanged(); + void containerChanged(); + void useZeroTargetChanged(); + +private: + QQuickItem *m_item = nullptr; + QQuickItem *m_container = nullptr; + QTimer *m_timer = nullptr; +}; + +} diff --git a/panels/dock/taskmanager/package/AppItem.qml b/panels/dock/taskmanager/package/AppItem.qml index 94e332aae..58ec082cb 100644 --- a/panels/dock/taskmanager/package/AppItem.qml +++ b/panels/dock/taskmanager/package/AppItem.qml @@ -140,88 +140,63 @@ Item { } target: Panel } - - D.DciIcon { - id: icon - name: root.iconName - height: iconSize - width: iconSize - sourceSize: Qt.size(iconSize, iconSize) + //加一层Item为了实现图标centerIn,不受到icon中fixposition的影响 + Item { + width: root.iconSize + height: root.iconSize anchors.centerIn: parent - retainWhileLoading: true - smooth: false - - function mapToScene(px, py) { - return parent.mapToItem(Window.window.contentItem, Qt.point(px, py)) - } - function mapFromScene(px, py) { - return parent.mapFromItem(Window.window.contentItem, Qt.point(px, py)) - } - - function fixPosition() { - if (root.Drag.active || !parent || launchAnimation.running) { - return + D.DciIcon { + id: icon + name: root.iconName + anchors.fill: parent + sourceSize: Qt.size(iconSize, iconSize) + retainWhileLoading: true + smooth: false + + PositionFixer { + id: positionFixer + item: icon } - anchors.centerIn = undefined - var targetX = (parent.width - width) / 2 - var targetY = (parent.height - height) / 2 - - var scenePos = mapToScene(targetX, targetY) - - var physicalX = Math.round(scenePos.x * Panel.devicePixelRatio) - var physicalY = Math.round(scenePos.y * Panel.devicePixelRatio) - - var localPos = mapFromScene(physicalX / Panel.devicePixelRatio, physicalY / Panel.devicePixelRatio) - - x = localPos.x - y = localPos.y - } - Timer { - id: fixPositionTimer - interval: 100 - repeat: false - running: false - onTriggered: { - icon.fixPosition() - } - } - - Connections { - target: root - function onIconGlobalPointChanged() { - fixPositionTimer.start() - } - } - LaunchAnimation { - id: launchAnimation - launchSpace: { - switch (Panel.position) { - case Dock.Top: - case Dock.Bottom: - return (root.height - icon.height) / 2 - case Dock.Left: - case Dock.Right: - return (root.width - icon.width) / 2 + Connections { + target: root + function onIconGlobalPointChanged() { + if (root.Drag.active || !parent || launchAnimation.running) { + return + } + positionFixer.fix() } } + LaunchAnimation { + id: launchAnimation + launchSpace: { + switch (Panel.position) { + case Dock.Top: + case Dock.Bottom: + return (root.height - icon.height) / 2 + case Dock.Left: + case Dock.Right: + return (root.width - icon.width) / 2 + } + } - direction: { - switch (Panel.position) { - case Dock.Top: - return LaunchAnimation.Direction.Down - case Dock.Bottom: - return LaunchAnimation.Direction.Up - case Dock.Left: - return LaunchAnimation.Direction.Right - case Dock.Right: - return LaunchAnimation.Direction.Left + direction: { + switch (Panel.position) { + case Dock.Top: + return LaunchAnimation.Direction.Down + case Dock.Bottom: + return LaunchAnimation.Direction.Up + case Dock.Left: + return LaunchAnimation.Direction.Right + case Dock.Right: + return LaunchAnimation.Direction.Left + } } + target: icon + loops: 1 + running: false } - target: icon - loops: 1 - running: false } } } diff --git a/panels/dock/tray/ShellSurfaceItemProxy.qml b/panels/dock/tray/ShellSurfaceItemProxy.qml index 5fe978aae..47a278a36 100644 --- a/panels/dock/tray/ShellSurfaceItemProxy.qml +++ b/panels/dock/tray/ShellSurfaceItemProxy.qml @@ -27,7 +27,12 @@ Item { } function fixPosition() { - fixPositionTimer.start() + positionFixer.fix() + } + + PositionFixer { + id: positionFixer + item: impl } ShellSurfaceItem { @@ -56,11 +61,10 @@ Item { onVisibleChanged: function () { if (visible) { - fixPositionTimer.start() + positionFixer.fix() } if (autoClose && !visible) { - // surface is valid but client's shellSurface maybe invalid. Qt.callLater(closeShellSurface) } } @@ -71,37 +75,8 @@ Item { } } - function mapToScene(x, y) { - const point = Qt.point(x, y) - // Must use parent.mapFoo, because the impl's position is relative to the parent Item - const mappedPoint = parent.mapToItem(Window.window.contentItem, point) - return mappedPoint - } - - function mapFromScene(x, y) { - const point = Qt.point(x, y) - // Must use parent.mapFoo, because the impl's position is relative to the parent Item - const mappedPoint = parent.mapFromItem(Window.window.contentItem, point) - return mappedPoint - } - - function fixPosition() { - // See QTBUG: https://bugreports.qt.io/browse/QTBUG-135833 - // TODO: should get the devicePixelRatio from the Window - x = mapFromScene(Math.ceil(mapToScene(0, 0).x * Panel.devicePixelRatio) / Panel.devicePixelRatio, 0).x - y = mapFromScene(0, Math.ceil(mapToScene(0, 0).y * Panel.devicePixelRatio) / Panel.devicePixelRatio).y - } - - Timer { - id: fixPositionTimer - interval: 100 - repeat: false - running: false - onTriggered: { - impl.fixPosition() - } - } } + Component.onCompleted: function () { impl.surfaceDestroyed.connect(root.surfaceDestroyed) }