Skip to content

feat: Implement full-row hover effect in DataTreeView#398

Closed
dengzhongyuan365-dev wants to merge 2 commits intolinuxdeepin:masterfrom
dengzhongyuan365-dev:pwd-dev
Closed

feat: Implement full-row hover effect in DataTreeView#398
dengzhongyuan365-dev wants to merge 2 commits intolinuxdeepin:masterfrom
dengzhongyuan365-dev:pwd-dev

Conversation

@dengzhongyuan365-dev
Copy link
Copy Markdown
Contributor

  • Added a new ItemViewRowHoverProxyStyle to manage hover effects for rows in the DataTreeView.
  • Enhanced the drawRow method to support full-row highlighting on hover, improving user interaction.
  • Introduced mouse tracking and hover row management to update the visual state based on mouse position.
  • Updated mouseMoveEvent and leaveEvent to handle hover state changes effectively.

This feature enhances the visual feedback for users, making the interface more intuitive and responsive.
bug: https://pms.uniontech.com/bug-view-283255.html

Copy link
Copy Markdown

@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.

Sorry @dengzhongyuan365-dev, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dengzhongyuan365-dev

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

- Added a new ItemViewRowHoverProxyStyle to manage hover effects for rows in the DataTreeView.
- Enhanced the drawRow method to support full-row highlighting on hover, improving user interaction.
- Introduced mouse tracking and hover row management to update the visual state based on mouse position.
- Updated mouseMoveEvent and leaveEvent to handle hover state changes effectively.

This feature enhances the visual feedback for users, making the interface more intuitive and responsive.
bug: https://pms.uniontech.com/bug-view-283255.html
- Modified the progress update logic to accept sub-percent values while ensuring monotonic progress.
- Introduced a throttling mechanism to limit UI updates, reducing potential performance issues from frequent progress changes.
- Enhanced reset and timer restart functions to invalidate the UI update timer appropriately.

These changes enhance the responsiveness and accuracy of the progress display, improving user experience during long-running tasks.

bug: TASK: https://pms.uniontech.com/task-view-388835.html
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码主要包含三个方面的修改:ProgressPage 中的进度更新逻辑优化、DataTreeView 的行悬停效果实现以及相关的辅助类 ItemViewRowHoverProxyStyle。以下是针对代码的详细审查意见:

1. 语法逻辑

ProgressPage::setProgress

  • 问题qRound(dPercent) 的结果被赋值给 iPercent,然后与 m_iPerent 比较。变量名 m_iPerent 存在拼写错误,应为 m_iPercent。虽然这不影响编译,但会降低代码可读性。
  • 问题:在 setProgress 中,逻辑是“整数进度变化必刷;否则最多每 200ms 刷一次”。代码中 if (!percentChanged && !timeToUpdate) return; 这段逻辑是正确的,但紧接着的 if (timeToUpdate) m_uiUpdateTimer.restart(); 放在 return 之后,意味着只有当 percentChanged 为真(即进度条整数部分变化)时,才会检查并重启计时器。
    • 潜在Bug:如果进度在 0.1% 到 0.9% 之间快速跳动(未达到整数变化),且间隔超过 200ms,第一次会更新并重启计时器。但如果之后进度一直不变,timeToUpdate 会一直为真,导致每次调用 setProgress(即使进度没变)都会触发 restart() 和后续计算。虽然前面有 if (dPercent <= m_dProgressPercent) 的单调性检查,但如果外部频繁调用且传入相同值,这里仍会有不必要的开销。
    • 建议:将 m_uiUpdateTimer.restart() 的逻辑移动到实际执行更新的代码块中,或者确保只有在真正执行了 UI 更新逻辑后才重启计时器。目前的写法在 percentChanged 为真时,无论 timeToUpdate 是否为真(实际上此时 timeToUpdate 可能还没到 200ms),计时器都会被重置。这可能会导致在整数进度刚变化后,接下来的 200ms 内子百分比变化不会被更新(尽管整数没变,但通常希望整数变化后立即刷新速度/时间,这个逻辑目前是符合预期的,但注释说明不够清晰)。

DataTreeView 绘制与事件

  • 问题:在 drawRow 中,ItemViewRowHoverProxyStyle 的使用方式有些绕。代码中先设置了 setStyle(hoverProxyStyle),然后在 drawRow 中又通过 dynamic_cast 获取它来调用 baseStyle()->drawPrimitive
    • 建议:既然 ItemViewRowHoverProxyStyle 的主要目的是拦截 PE_PanelItemViewRow 并移除 State_MouseOver,那么在 drawRow 中手动调用 proxy->baseStyle()->drawPrimitive 绘制悬停背景显得有些冗余。如果目的是为了绘制圆角悬停效果,目前的实现是合理的(利用 clipPath)。但逻辑上,ProxyStyle 负责拦截,View 负责手动绘制,职责稍显混乱。
  • 问题mouseMoveEvent 中,当 m_isPressed 为 true 时处理拖拽逻辑,最后调用了 DTreeView::mouseMoveEvent(event)。但在 m_isPressed 为 false 时,只调用了 updateHoverRowFromPosition没有调用基类的 DTreeView::mouseMoveEvent(event)
    • 严重问题:这会破坏基类可能需要处理的鼠标移动逻辑(例如 ToolTip 的触发、自动滚动等)。虽然代码最后有一行 DTreeView::mouseMoveEvent(event);,但它位于 if (m_isPressed) 块之外,且在 updateHoverRowFromPosition 之后。仔细看缩进,这行代码是在 if (m_isPressed) { ... } 块结束之后,也就是无论 m_isPressed 是什么值都会执行。修正:缩进显示它是函数体的最后一句,所以逻辑是正确的,都会调用基类方法。但为了清晰起见,建议将其与 if 块对齐或放在 else 分支中明确意图。

ItemViewRowHoverProxyStyle

  • 问题:构造函数中 Lifetime is managed by the creator 的注释与 setParent(QCoreApplication::instance()) 的行为一致,这是好的做法,防止了析构顺序问题。
  • 逻辑:该类通过移除 State_MouseOver 来防止 DStyle 绘制默认的单元格悬停效果,以便 DataTreeView 可以绘制整行悬停。逻辑清晰。

2. 代码质量

  • 命名规范
    • m_iPerent 应修正为 m_iPercent
    • bVis, cg 等变量名过于简短,建议使用更具描述性的名称,如 isLabelVisible, colorGroup
  • 魔法数字
    • 200 (ms) 应定义为常量,例如 const int kUpdateInterval = 200;
    • 颜色值 QColor(255, 255, 255, 12) 最好也定义为命名常量或主题相关的颜色获取方式。
  • 注释
    • ProgressPage 中的注释解释了节流的原因,非常好。
    • DataTreeView 中的注释 // height-2以让高分屏非整数缩放比例下无被选中的蓝色细线 解释了具体的 UI 修复细节,很有价值。
  • 代码重复
    • m_uiUpdateTimer.invalidate()resetProgressrestartTimer 中重复出现。可以考虑提取一个私有函数 resetUiUpdateTimer()

3. 代码性能

  • ProgressPage
    • 引入节流机制(200ms)是非常好的优化,避免了高频进度更新导致的 UI 线程阻塞和重绘开销。
    • qRound 和浮点比较开销很小,可以忽略。
  • DataTreeView
    • updateHoverRowFromPosition 中调用了 indexAt(pos),这在鼠标移动时频繁触发。QTreeView::indexAt 内部涉及视口坐标转换和模型索引查找,有一定开销。但在现代机器上通常不是瓶颈。
    • viewport()->update() 会触发整个视口的重绘。如果列表很长,这可能会比较耗费资源。
    • 优化建议:可以考虑使用 viewport()->update(visualRect(index)) 仅重绘受影响的行(旧行和新行),而不是整个视口。但这需要记录 m_lastHoverRow,在 updateHoverRowFromPosition 中比较新旧行,分别更新两者的区域。目前的 m_hoverRow 只记录了当前行,无法直接获取旧行的区域。考虑到实现复杂度,全屏更新在一般应用场景下是可以接受的。
  • 绘制逻辑
    • drawRow 中使用了 QPainterPathaddRoundedRect,这比简单的 fillRect 稍微消耗大一点,但为了圆角效果是值得的。
    • painter->save()restore() 用于悬停状态的绘制,开销可控。

4. 代码安全

  • 空指针检查
    • ItemViewRowHoverProxyStyle::drawPrimitive 中检查了 optionm_treeView,使用了 qstyleoption_cast,这很安全。
    • DataTreeView::drawRowm_selectionModelinitUI 中初始化,假设它不为空。如果 m_selectionModel 可能为空(例如在析构阶段),应增加检查。
    • DataTreeView::drawRowstyle() 返回的指针在 initUI 中被设置为 hoverProxyStyle,但 drawRow 中又有一个 else 分支调用 style->drawPrimitive,说明作者考虑到了 style 可能被重置的情况,这是防御性编程的好习惯。
  • 对象生命周期
    • ItemViewRowHoverProxyStyle 的父对象设置为 QCoreApplication::instance(),确保了它在应用退出时最后析构,避免了访问已析构的 DataTreeView(通过 QPointer 保护)。这是处理跨对象生命周期依赖的安全做法。
  • 数据一致性
    • m_hoverRowmutable int。在 const 成员函数中修改它可能是为了在 drawRow 等可能被 const 调用的上下文中更新状态(虽然 drawRow 不是 const)。如果 m_hoverRow 仅用于 UI 状态,使用 mutable 是合理的。

总结与改进建议

  1. 修正拼写:将 m_iPerent 改为 m_iPercent
  2. 优化重绘区域:在 updateHoverRowFromPosition 中,记录上一行索引,仅对受影响的旧行和新行调用 update(visualRect(...)),而不是更新整个视口。
  3. 逻辑清晰化:在 ProgressPage::setProgress 中,明确计时器重置的条件。建议将 m_uiUpdateTimer.restart() 放在 if (timeToUpdate) 块内,或者确保逻辑流清晰:只有真正发生了 UI 更新(无论是进度条变值,还是时间到了刷新速度)才重置计时器。
  4. 常量定义:将 200ms 等魔法数字定义为类常量。
  5. 代码结构DataTreeView 中的悬停绘制逻辑(手动调用 ProxyStyle 的 baseStyle)虽然实现了效果,但耦合度较高。如果未来样式需求变更,可能需要同时修改 ProxyStyle 和 View 的绘制代码。

修改后的 ProgressPage::setProgress 逻辑建议:

// ...
    const int iPercent = qMin(100, qRound(dPercent));
    const bool percentChanged = (iPercent != m_iPercent); // 修正变量名
    if (percentChanged) {
        m_iPercent = iPercent;
        m_pProgressBar->setValue(m_iPercent);
        m_pProgressBar->update();
    }

    if (!m_uiUpdateTimer.isValid()) {
        m_uiUpdateTimer.start();
    }
    
    // 逻辑:如果整数进度没变,且时间未到,则直接返回
    if (!percentChanged && m_uiUpdateTimer.elapsed() < 200) {
        return;
    }

    // 只要走到了这里,说明需要更新 UI(要么进度变了,要么时间到了)
    // 重置计时器,以便下次计算间隔
    m_uiUpdateTimer.restart();

    // 后续更新速度和剩余时间的逻辑...

这样逻辑更清晰:只有决定要执行更新操作时,才重置计时器。原来的代码在 percentChanged 为真时无条件重置计时器,虽然结果一样(因为要更新 UI),但语义上不如“执行更新时重置”清晰。

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.

2 participants