Skip to content

Fix: Optimize tooltip positioning logic.#571

Open
JWWTSL wants to merge 1 commit intolinuxdeepin:masterfrom
JWWTSL:origin/solve-bug
Open

Fix: Optimize tooltip positioning logic.#571
JWWTSL wants to merge 1 commit intolinuxdeepin:masterfrom
JWWTSL:origin/solve-bug

Conversation

@JWWTSL
Copy link
Contributor

@JWWTSL JWWTSL commented Feb 10, 2026

Log: The Popup is rendered on the window's Overlay layer and is not part of the content visual tree. As a result, the tooltip does not follow its anchor item during scrolling or window resizing, causing positional drift. To resolve this, AlertToolTip has been changed from a ToolTip (which uses Popup) to a regular Item, making it part of the content visual tree. This ensures it naturally scrolls with its parent, respects container clipping, and maintains correct positioning at all times.

PMS: bug-341973

Summary by Sourcery

Replace the alert tooltip popup with an in-tree item to keep it correctly positioned with its target and respect container scrolling and clipping.

Bug Fixes:

  • Fix alert tooltip drift by making it part of the content visual tree instead of a window overlay popup.

Enhancements:

  • Add explicit sizing, z-ordering, and optional auto-hide timeout behavior to the alert tooltip component in both QtQuick 2 and Qt 6 implementations.

@deepin-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: JWWTSL

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 10, 2026

Reviewer's Guide

Replaces AlertToolTip’s ToolTip/Popup-based implementation with a regular Item-based layout in both Qt5 and Qt6 QML, so the tooltip remains within the visual tree, scrolls/clips with its target, and gains explicit sizing and timeout behavior.

Sequence diagram for AlertToolTip visibility and timeout behavior

sequenceDiagram
    actor User
    participant TargetItem
    participant AlertToolTip as AlertToolTip_Item
    participant Timer as AlertToolTip_Timer
    participant ContentView

    User->>TargetItem: Hover or focus causes validation error
    TargetItem-->>ContentView: Request to show AlertToolTip_Item
    ContentView->>AlertToolTip: set target, text, timeout
    ContentView->>AlertToolTip: visible = true
    AlertToolTip->>AlertToolTip: compute y = target.height + spacing
    AlertToolTip->>AlertToolTip: compute implicitWidth, implicitHeight
    AlertToolTip->>Timer: interval = timeout
    AlertToolTip->>Timer: running = timeout > 0 && visible

    loop While ContentView scrolls
        ContentView->>TargetItem: update position within visual tree
        ContentView->>AlertToolTip: layout reflow
        AlertToolTip->>AlertToolTip: y remains relative to target
    end

    Timer-->>AlertToolTip: onTriggered()
    AlertToolTip->>AlertToolTip: visible = false
Loading

Class diagram for updated AlertToolTip Item-based structure (Qt5)

classDiagram
    class AlertToolTip_Qt5 {
        <<Item>>
        Item target
        string text
        font font
        int timeout
        real __naturalWidth
        real x
        real y
        int z
        real implicitWidth
        real implicitHeight
        real width
        real height
    }

    class AlertToolTip_Timer_Qt5 {
        <<Timer>>
        int interval
        bool running
        onTriggered()
    }

    class AlertToolTip_Background_Qt5 {
        <<Item>>
        anchors.fill parent
    }

    class AlertToolTip_Text_Qt5 {
        <<Text>>
        D.Palette textColor
        font font
        string text
        color color
        wrapMode wrapMode
        horizontalAlignment horizontalAlignment
        verticalAlignment verticalAlignment
    }

    class AlertToolTip_Connector_Qt5 {
        <<BoxShadow>>
        D.Palette dropShadowColor
        D.Palette backgroundColor
        real y
        real width
        real height
        real shadowBlur
        real radius
        real offsetX
        real offsetY
        real spread
    }

    AlertToolTip_Qt5 "1" o-- "1" AlertToolTip_Timer_Qt5 : contains
    AlertToolTip_Qt5 "1" o-- "1" AlertToolTip_Background_Qt5 : contains
    AlertToolTip_Qt5 "1" o-- "1" AlertToolTip_Text_Qt5 : contains
    AlertToolTip_Qt5 "1" o-- "1" AlertToolTip_Connector_Qt5 : contains
Loading

Class diagram for updated AlertToolTip Item-based structure (Qt6)

classDiagram
    class AlertToolTip_Qt6 {
        <<Item>>
        Item target
        string text
        font font
        int timeout
        real __naturalWidth
        real x
        real y
        int z
        real implicitWidth
        real implicitHeight
        real width
        real height
    }

    class AlertToolTip_Timer_Qt6 {
        <<Timer>>
        int interval
        bool running
        onTriggered()
    }

    class AlertToolTip_FloatingPanel_Qt6 {
        <<FloatingPanel>>
        anchors.fill parent
        real radius
        real implicitWidth
        real implicitHeight
        D.Palette backgroundColor
        D.Palette insideBorderColor
        D.Palette outsideBorderColor
    }

    class AlertToolTip_Text_Qt6 {
        <<Text>>
        D.Palette textColor
        font font
        string text
        color color
        wrapMode wrapMode
        horizontalAlignment horizontalAlignment
        verticalAlignment verticalAlignment
    }

    class AlertToolTip_Connector_Qt6 {
        <<BoxShadow>>
        D.Palette dropShadowColor
        D.Palette backgroundColor
        D.Palette borderColor
        real y
        real width
        real height
        real shadowBlur
        real radius
        real offsetX
        real offsetY
        real spread
    }

    AlertToolTip_Qt6 "1" o-- "1" AlertToolTip_Timer_Qt6 : contains
    AlertToolTip_Qt6 "1" o-- "1" AlertToolTip_FloatingPanel_Qt6 : contains
    AlertToolTip_Qt6 "1" o-- "1" AlertToolTip_Text_Qt6 : contains
    AlertToolTip_Qt6 "1" o-- "1" AlertToolTip_Connector_Qt6 : contains
Loading

File-Level Changes

Change Details Files
Replace ToolTip (Popup) with an Item-based tooltip that stays in the content visual tree and tracks its target correctly.
  • Change AlertToolTip root from ToolTip to Item in both Qt5 and Qt6 variants and position it relative to the target using y = target.height + spacing and a fixed x origin.
  • Introduce explicit sizing logic using a computed natural width based on style width and text implicit size, clamped to the target width when available, and compute implicit height from text height plus vertical padding.
  • Set a fixed z-order (constant in Qt5, D.DTK.TopOrder in Qt6) so the tooltip appears above content while still remaining inside the visual tree.
src/qml/AlertToolTip.qml
qt6/src/qml/AlertToolTip.qml
Inline the tooltip’s background and text layout instead of using ToolTip’s background/contentItem properties.
  • Wrap the visual content in a child Item (Qt5) or FloatingPanel (Qt6) anchored to fill the parent, replacing the previous background property usage.
  • Configure the inner text element with id contentText, full anchoring with padding margins from DS.Style.alertToolTip, and expose its font via a property alias on the control.
  • Recalculate BoxShadow connector positioning to account for removal of ToolTip padding/margins, simplifying the y offset expression.
src/qml/AlertToolTip.qml
qt6/src/qml/AlertToolTip.qml
Add behavior and API for auto-hide timeouts while removing ToolTip-specific transitions and popup behaviors.
  • Add text, font alias, and timeout properties on the root Item to mirror the old API while supporting the new implementation.
  • Add a Timer driven by the timeout property that hides the tooltip by toggling control.visible when the interval elapses.
  • Remove enter/exit Transition animations, closePolicy, margins, and Popup-related padding properties that no longer apply to the Item-based implementation.
src/qml/AlertToolTip.qml
qt6/src/qml/AlertToolTip.qml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • With the switch from ToolTip(Popup) to Item, the previous enter/exit transitions were dropped; consider reintroducing a simple slide/fade-in/out via Behaviors or explicit animations on y/opacity to preserve the prior interaction feel.
  • The new Item-based tooltip now relies on visible instead of open()/close() semantics from ToolTip; if existing callers expect the old API, consider providing small helper functions or a wrapper to keep the usage pattern consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- With the switch from ToolTip(Popup) to Item, the previous enter/exit transitions were dropped; consider reintroducing a simple slide/fade-in/out via Behaviors or explicit animations on `y`/`opacity` to preserve the prior interaction feel.
- The new `Item`-based tooltip now relies on `visible` instead of `open()/close()` semantics from `ToolTip`; if existing callers expect the old API, consider providing small helper functions or a wrapper to keep the usage pattern consistent.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@mhduiy mhduiy requested a review from 18202781743 February 10, 2026 07:55
ToolTip {
// Use Item instead of ToolTip(Popup) so it stays in the visual tree,
// scrolls with content and gets clipped properly.
Item {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你这是自己写了个ToolTip呀,之后测试要是提了个ToolTip跟随移动的话怎么整,

ToolTip {
// Use Item instead of ToolTip(Popup) so it stays in the visual tree,
// scrolls with content and gets clipped properly.
Item {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是dtk5的qml,我们目前可以不处理,

ToolTip {
// Use Item instead of ToolTip(Popup) so it stays in the visual tree,
// scrolls with content and gets clipped properly.
Item {
Copy link
Contributor

@18202781743 18202781743 Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对于这个AlertToolTip可以重写一个,如果我们实在不能使用Popup达到这个效果的话,但那个动画我们需要保留,
建议用Control去模拟,这样的话,对应的contentItem和background以及一些边距都能跟之前的保持一致,这种enter和exit动画看能不能用hovered状态去实现,

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Feb 27, 2026

TAG Bot

New tag: 6.7.34
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #576

Log: The Popup is rendered on the window's Overlay layer and is not part of the content visual tree. As a result, the tooltip does not follow its anchor item during scrolling or window resizing, causing positional drift. To resolve this, AlertToolTip has been changed from a ToolTip (which uses Popup) to a regular Item, making it part of the content visual tree. This ensures it naturally scrolls with its parent, respects container clipping, and maintains correct positioning at all times.

PMS: bug-341973
@JWWTSL JWWTSL force-pushed the origin/solve-bug branch from 85e00ca to 901cd4b Compare March 3, 2026 06:40
@deepin-ci-robot
Copy link
Contributor

deepin pr auto review

Git Diff 代码审查报告:AlertToolTip.qml

代码变更概述

这段代码将 AlertToolTip 从继承自 ToolTip 改为继承自 Control,主要目的是使提示框能够保持在视觉树中,随内容滚动并正确裁剪。同时添加了进入/退出动画、超时自动关闭等功能。

语法逻辑分析

优点

  1. ToolTip 改为 Control 的继承结构合理,解决了原实现中提示框不随内容滚动的问题
  2. 使用 _displayY 属性和 Behavior 实现平滑动画,逻辑清晰
  3. 添加了 timeout 属性和 Timer 实现超时自动关闭功能

问题与建议

  1. 命名规范_displayY 使用下划线前缀表示私有属性,但 QML 中通常使用双下划线表示真正私有的属性,建议改为 __displayY
  2. 函数命名_updateDisplayY() 函数名与属性命名不一致,建议改为 __updateDisplayY()
  3. 属性访问:在 Connections 中使用 control.target 访问 target 属性,但 targetAlertToolTip 的属性,这种写法可能导致混淆

代码质量评估

优点

  1. 添加了详细的注释说明设计意图
  2. 使用 readonly 修饰符标记不应被修改的属性
  3. BoxShadow (连接线) 移入 background 内部,使组件结构更清晰

问题与建议

  1. 组件嵌套background 中嵌套了 FloatingPanelBoxShadow,增加了不必要的嵌套层级
  2. 代码重复FloatingPanel 中重复定义了 implicitWidthimplicitHeight,这些属性已在父级 Item 中定义
  3. 魔法数字y: -height * 0.75 使用了魔法数字,建议定义为常量属性

代码性能考虑

优点

  1. 使用 Behavior 实现动画比原来的 Transition 更高效,因为只针对特定属性
  2. Timer 只在需要时运行(control.timeout > 0 && control.visible)

问题与建议

  1. 频繁计算__naturalWidthimplicitWidth 的计算会在每次属性变化时触发,可能导致不必要的重绘
  2. 连接过多Connections 对象监听 targetheightChanged 信号,如果 target 高度频繁变化,可能导致性能问题

代码安全审查

优点

  1. 添加了 timeout 属性,防止提示框无限期显示
  2. 使用 readonly 修饰符保护不应被修改的属性

问题与建议

  1. 空引用风险:多处使用 target 属性前未进行空值检查,如 target.heighttarget.width
  2. 类型安全target 属性定义为 Item 类型,但未验证它是否具有必要的属性和方法

改进建议

Control {
    id: control
    property Item target
    property string text
    property alias font: contentText.font
    property int timeout: 0
    
    // 使用双下划线表示私有属性
    readonly property real __naturalWidth: Math.max(DS.Style.alertToolTip.width,
                                                    contentText.implicitWidth + DS.Style.alertToolTip.horizontalPadding * 2)
    readonly property real __connectorPositionRatio: 0.75 // 将魔法数字定义为常量
    
    x: 0
    y: __displayY
    z: D.DTK.TopOrder
    
    topPadding: DS.Style.alertToolTip.verticalPadding
    bottomPadding: DS.Style.alertToolTip.verticalPadding
    leftPadding: DS.Style.alertToolTip.horizontalPadding
    rightPadding: DS.Style.alertToolTip.horizontalPadding
    
    // 添加空值检查
    implicitWidth: target ? Math.min(__naturalWidth, target.width) : __naturalWidth
    implicitHeight: Math.max(DS.Style.alertToolTip.height,
                             contentText.implicitHeight + DS.Style.alertToolTip.verticalPadding * 2)
    width: implicitWidth
    height: implicitHeight
    
    // 使用双下划线表示私有属性和函数
    property real __displayY: target ? target.height : 0
    function __updateDisplayY() {
        if (target) {
            if (visible)
                __displayY = target.height + DS.Style.control.spacing
            else
                __displayY = target.height
        }
    }
    
    onVisibleChanged: __updateDisplayY()
    onTargetChanged: __updateDisplayY()
    
    // 添加空值检查
    Connections {
        target: control.target
        function onHeightChanged() { 
            if (control.target) 
                control.__updateDisplayY() 
        }
    }
    
    Component.onCompleted: __updateDisplayY()
    
    Behavior on __displayY {
        NumberAnimation { duration: 200 }
    }
    
    Timer {
        interval: control.timeout
        running: control.timeout > 0 && control.visible
        onTriggered: control.visible = false
    }
    
    background: Item {
        implicitWidth: DS.Style.alertToolTip.width
        implicitHeight: DS.Style.alertToolTip.height
        
        FloatingPanel {
            anchors.fill: parent
            radius: DS.Style.alertToolTip.radius
            backgroundColor: DS.Style.alertToolTip.background
            insideBorderColor: DS.Style.alertToolTip.insideBorder
            outsideBorderColor: DS.Style.alertToolTip.outsideBorder
        }
        
        BoxShadow {
            id: connector
            property D.Palette dropShadowColor: DS.Style.alertToolTip.connecterdropShadow
            property D.Palette backgroundColor: DS.Style.alertToolTip.connecterBackground
            property D.Palette borderColor: DS.Style.control.border
            // 使用定义的常量
            y: -height * __connectorPositionRatio
            width: DS.Style.alertToolTip.connectorWidth
            height: DS.Style.alertToolTip.connectorHeight
            shadowBlur: 4
            shadowOffsetY: 2
            shadowColor: D.ColorSelector.dropShadowColor
            cornerRadius: DS.Style.control.radius
            
            Rectangle {
                anchors.fill: parent
                color: connector.D.ColorSelector.backgroundColor
                border.color: connector.D.ColorSelector.borderColor
                border.width: 1
            }
        }
    }
    
    contentItem: Text {
        id: contentText
        property D.Palette textColor: DS.Style.alertToolTip.text
        horizontalAlignment: Text.AlignLeft
        verticalAlignment: Text.AlignVCenter
        text: control.text
        color: D.ColorSelector.textColor
        wrapMode: Text.Wrap
    }
}

总结

这段代码整体质量较高,实现了从 ToolTipControl 的重构,解决了原有实现中的问题。主要改进点包括:

  1. 使用双下划线表示私有属性和函数
  2. 添加空值检查防止运行时错误
  3. 将魔法数字定义为常量
  4. 减少不必要的属性重复定义

这些改进将提高代码的可读性、可维护性和健壮性。

height: implicitHeight

// Animated y so tip stays attached to target; enter = from below target to final, exit = back.
property real _displayY: target ? target.height : 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个_displayY用绑定的方式吧,应该就不需要_updateDisplayY()这个函数去更新了,

outsideBorderColor: DS.Style.alertToolTip.outsideBorder
}

BoxShadow {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个连接线,在逻辑上不属于background吧,还是保持原状,

radius: DS.Style.alertToolTip.radius
readonly property real __naturalWidth: Math.max(DS.Style.alertToolTip.width,
contentText.implicitWidth + DS.Style.alertToolTip.horizontalPadding * 2)
implicitWidth: target ? Math.min(__naturalWidth, target.width) : __naturalWidth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这宽度高度计算,能用之前的方式么?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants