Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions qt6/src/qml/AlertToolTip.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,44 @@ Control {
property string text
property int timeout: 0
property bool _expired: false
readonly property bool _shown: control.visible && !_expired

// 目标是否已被裁剪出可视区域(例如编辑页向上滚动后输入框已不可见)。
// 此时提示框不应继续显示,否则会出现输入框已滚走、提示框仍悬浮在页面上的问题。
// 做法参照 Qt 内部 Qt::ImInputItemClipRectangle 的实现(qquickitem.cpp):
// 从目标自身开始逐级向上映射矩形,对每一个 clip 为 true 的祖先求交,
// 最后再与窗口矩形求交,结果为空即表示目标已完全不可见。
readonly property bool _targetOutOfView: {
if (!target || !target.visible || target.opacity <= 0)
return true
Comment on lines +23 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The visibility check considers only target.visible and target.opacity, so a target whose ancestor is hidden or fully transparent is still treated as visible. Its nonzero geometry can therefore keep _shown true and leave the tooltip displayed even though the target is not rendered.

Triggers: When a visible target is nested under an ancestor with visible: false or opacity: 0.

Suggested fix: Traverse the ancestor chain and reject the target when an ancestor is not effectively visible or has zero effective opacity.

Suggested change
if (!target || !target.visible || target.opacity <= 0)
return true
let ancestor = target
while (ancestor) {
if (!ancestor.visible || ancestor.opacity <= 0)
return true
ancestor = ancestor.parent
}


let rect = Qt.rect(0, 0, target.width, target.height)
let par = target
while (par.parent) {
const parpar = par.parent
rect = parpar.mapFromItem(par, rect)
Comment on lines +29 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): _targetOutOfView does not establish QML binding dependencies on the positions of the target's ancestors because those positions are accessed only inside mapFromItem(). Scrolling a Flickable typically changes the content item's transform or position without changing the target's own properties, so _targetOutOfView is not reevaluated and _shown remains true while the tooltip stays visible.

Triggers: When the target is inside a Flickable or another scroll container whose content position changes during scrolling.

Suggested fix: Read the relevant ancestor position/scroll properties explicitly in the binding, or update an explicit visibility check from the scroll container's movement signal.

Suggested change
const parpar = par.parent
rect = parpar.mapFromItem(par, rect)
const parpar = par.parent
par.x
par.y
parpar.x
parpar.y
rect = parpar.mapFromItem(par, rect)

if (parpar.clip)
rect = _intersectRect(rect, Qt.rect(0, 0, parpar.width, parpar.height))
if (rect.width <= 0 || rect.height <= 0)
return true
par = parpar
}
// 映射到场景坐标后与窗口矩形求交。循环结束时 par 即为根项,
// 也就是窗口的 contentItem,其尺寸等同窗口尺寸。
rect = par.mapToItem(null, rect)
rect = _intersectRect(rect, Qt.rect(0, 0, par.width, par.height))
return rect.width <= 0 || rect.height <= 0
}

// QML 的 rect 值类型未暴露 intersected(),这里手动求交
function _intersectRect(a, b) {
const x = Math.max(a.x, b.x)
const y = Math.max(a.y, b.y)
const right = Math.min(a.x + a.width, b.x + b.width)
const bottom = Math.min(a.y + a.height, b.y + b.height)
return Qt.rect(x, y, Math.max(0, right - x), Math.max(0, bottom - y))
}

readonly property bool _shown: control.visible && !_expired && !_targetOutOfView

property point __itemGlobalPos: {
let x = 0, y = 0
Expand All @@ -26,8 +63,11 @@ Control {
return Qt.point(x, y)
}
x: __itemGlobalPos.x
y: __itemGlobalPos.y + (target ? target.height : 0) + (_shown ? DS.Style.control.spacing : 0)
Behavior on y {
y: __itemGlobalPos.y + (target ? target.height : 0) + DS.Style.control.spacing + __slideOffset
// 仅显示/隐藏时做滑入滑出动画;滚动或布局变化时 y 立即跟随,
// 避免动画反复重启导致提示框滞后于输入框
property real __slideOffset: _shown ? 0 : -DS.Style.control.spacing
Behavior on __slideOffset {
NumberAnimation { duration: 200 }
}
parent: _shown ? Overlay.overlay : target
Expand Down
Loading