diff --git a/src/source/page/progresspage.cpp b/src/source/page/progresspage.cpp index 1e646e4b..8087e239 100644 --- a/src/source/page/progresspage.cpp +++ b/src/source/page/progresspage.cpp @@ -100,24 +100,35 @@ void ProgressPage::setProgress(double dPercent) dPercent = 0; } - // 原先用「整数百分比是否变大」过滤更新,会导致子百分比进度(如 1.1%、1.2%)全部被丢弃, - // 从而永远不调用 calSpeedAndRemainingTime,速度与剩余时间一直停在「计算中」。 - // 这里改为:进度必须单调递增(浮点),进度条仍可按四舍五入刷新。 + // Accept sub-percent progress, but keep it monotonic. if (dPercent <= m_dProgressPercent) { qDebug() << "Progress not increased, current:" << m_dProgressPercent << "new:" << dPercent; return; } - m_dProgressPercent = dPercent; const int iPercent = qMin(100, qRound(dPercent)); - if (iPercent != m_iPerent) { + const bool percentChanged = (iPercent != m_iPerent); + if (percentChanged) { m_iPerent = iPercent; m_pProgressBar->setValue(m_iPerent); // 进度条刷新值 m_pProgressBar->update(); } - qInfo() << "Updating progress:" << dPercent << "% (bar" << m_iPerent << "%)"; + // 节流:进度可能非常密集(子百分比频繁变化),每次都刷新速度/剩余时间会拖慢任务并造成显示抖动。 + // 规则:整数进度变化必刷;否则最多每 200ms 刷一次速度/剩余时间。 + if (!m_uiUpdateTimer.isValid()) { + m_uiUpdateTimer.start(); + } + const bool timeToUpdate = (m_uiUpdateTimer.elapsed() >= 200); + if (!percentChanged && !timeToUpdate) { + return; + } + if (timeToUpdate) { + m_uiUpdateTimer.restart(); + } + + qDebug() << "Updating progress:" << dPercent << "% (bar" << m_iPerent << "%)"; // 刷新界面显示 double dSpeed = 0.0; qint64 qRemainingTime = 0; @@ -171,6 +182,9 @@ void ProgressPage::resetProgress() m_iPerent = 0; m_dProgressPercent = -1.0; m_qConsumeTime = 0; + if (m_uiUpdateTimer.isValid()) { + m_uiUpdateTimer.invalidate(); + } } void ProgressPage::restartTimer() @@ -178,6 +192,9 @@ void ProgressPage::restartTimer() qDebug() << "restartTimer"; m_qConsumeTime = 0; m_timer.restart(); + if (m_uiUpdateTimer.isValid()) { + m_uiUpdateTimer.invalidate(); + } } void ProgressPage::setPushButtonCheckable(bool a, bool b) diff --git a/src/source/page/progresspage.h b/src/source/page/progresspage.h index 3fd65e27..5fb62cb1 100644 --- a/src/source/page/progresspage.h +++ b/src/source/page/progresspage.h @@ -149,6 +149,7 @@ private Q_SLOTS: double m_dProgressPercent = -1.0; QElapsedTimer m_timer; //计时器 qint64 m_qConsumeTime = 0; //消耗时间 + QElapsedTimer m_uiUpdateTimer; // UI 刷新节流计时器(避免解压高频进度拖慢) QString m_strArchiveName; }; diff --git a/src/source/tree/datatreeview.cpp b/src/source/tree/datatreeview.cpp index 086bb285..39df338d 100644 --- a/src/source/tree/datatreeview.cpp +++ b/src/source/tree/datatreeview.cpp @@ -6,12 +6,15 @@ #include "datatreeview.h" #include "treeheaderview.h" #include "datamodel.h" +#include "itemviewrowhoverproxystyle.h" #include "uitools.h" #include #include #include +#include +#include #include #include #include @@ -201,6 +204,12 @@ void DataTreeView::initUI() setContextMenuPolicy(Qt::CustomContextMenu); // 设置自定义右键菜单 setAcceptDrops(true); + setMouseTracking(true); + // Parent the proxy style to qApp to avoid exit-time destruction ordering issues. + auto *hoverProxyStyle = new ItemViewRowHoverProxyStyle(QApplication::style(), this); + hoverProxyStyle->setParent(QCoreApplication::instance()); + setStyle(hoverProxyStyle); + m_selectionModel = selectionModel(); } @@ -278,23 +287,21 @@ void DataTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option auto margin = style->pixelMetric(DStyle::PM_ContentsMargins, &options); //根据实际情况设置颜色,奇数行为灰色 auto palette = options.palette; - QBrush background; + QBrush zebraBrush; bool bVis = m_pHeaderView->getpreLbl()->isVisible(); if (bVis ? (index.row() & 1) : !(index.row() & 1)) { if(DGuiApplicationHelper::DarkType == DGuiApplicationHelper::instance()->themeType()) { - background = QColor(255, 255, 255, 12); + zebraBrush = QColor(255, 255, 255, 12); } else { - background = palette.color(cg, DPalette::AlternateBase); + zebraBrush = palette.color(cg, DPalette::AlternateBase); } } else { - background = palette.color(cg, DPalette::Base); - } - if (options.state & DStyle::State_Enabled) { - if (m_selectionModel->isSelected(index)) { - background = palette.color(cg, DPalette::Highlight); - } + zebraBrush = palette.color(cg, DPalette::Base); } + const bool selected = (options.state & DStyle::State_Enabled) && m_selectionModel->isSelected(index); + const bool hoverRow = (options.state & DStyle::State_Enabled) && index.row() == m_hoverRow && !selected; + // 绘制整行背景,高度-2以让高分屏非整数缩放比例下无被选中的蓝色细线,防止原来通过delegate绘制单元格交替颜色背景出现的高分屏非整数缩放比例下qrect精度问题导致的横向单元格间出现白色边框 QPainterPath path; QRect rowRect { options.rect.x() - header()->offset(), @@ -305,7 +312,30 @@ void DataTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option rowRect.setWidth(rowRect.width() - margin); path.addRoundedRect(rowRect, radius, radius); - painter->fillPath(path, background); + painter->fillPath(path, zebraBrush); + + if (hoverRow) { + QStyleOptionViewItem hoverOpt = options; + hoverOpt.state &= ~QStyle::State_Selected; + hoverOpt.state |= QStyle::State_MouseOver; + hoverOpt.rect = rowRect; + + // Use the original base style to paint the row hover background, + // since our proxy style clears State_MouseOver to prevent per-cell hover. + painter->save(); + // Keep rounded corners: style hover may be rectangular, so clip it. + painter->setClipPath(path); + if (auto *proxy = dynamic_cast(this->style())) { + proxy->baseStyle()->drawPrimitive(QStyle::PE_PanelItemViewRow, &hoverOpt, painter, this); + } else { + style->drawPrimitive(QStyle::PE_PanelItemViewRow, &hoverOpt, painter, this); + } + painter->restore(); + } + + if (selected) { + painter->fillPath(path, palette.color(cg, DPalette::Highlight)); + } QTreeView::drawRow(painter, options, index); // draw focus @@ -475,9 +505,23 @@ void DataTreeView::mouseReleaseEvent(QMouseEvent *event) DTreeView::mouseReleaseEvent(event); } +void DataTreeView::updateHoverRowFromPosition(const QPoint &pos) +{ + const QModelIndex idx = indexAt(pos); + const int newRow = idx.isValid() ? idx.row() : -1; + if (newRow != m_hoverRow) { + m_hoverRow = newRow; + viewport()->update(); + } +} + void DataTreeView::mouseMoveEvent(QMouseEvent *event) { // qDebug() << "Mouse move at position:" << event->pos(); + if (!m_isPressed) { + updateHoverRowFromPosition(event->pos()); + } + if (m_isPressed) { //最小距离为防误触和双向滑动时,只触发横向或者纵向的 int touchmindistance = 2; @@ -505,6 +549,17 @@ void DataTreeView::mouseMoveEvent(QMouseEvent *event) m_lastTouchBeginPos = event->pos(); return; } + + DTreeView::mouseMoveEvent(event); +} + +void DataTreeView::leaveEvent(QEvent *event) +{ + if (m_hoverRow >= 0) { + m_hoverRow = -1; + viewport()->update(); + } + DTreeView::leaveEvent(event); } void DataTreeView::keyPressEvent(QKeyEvent *event) diff --git a/src/source/tree/datatreeview.h b/src/source/tree/datatreeview.h index 3fa29cfe..0aace620 100644 --- a/src/source/tree/datatreeview.h +++ b/src/source/tree/datatreeview.h @@ -12,8 +12,11 @@ #include #include +#include #include +#include + DWIDGET_USE_NAMESPACE class DataModel; @@ -78,6 +81,7 @@ class DataTreeView : public DTreeView void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; /** * @brief keyPressEvent 键盘事件 delete、enter、Alt+M * @param event @@ -107,6 +111,7 @@ class DataTreeView : public DTreeView * @brief resizeColumnWidth 重置列宽度 */ void resizeColumnWidth(); + void updateHoverRowFromPosition(const QPoint &pos); Q_SIGNALS: /** @@ -155,6 +160,7 @@ protected Q_SLOTS: private: Qt::FocusReason m_reson; QItemSelectionModel *m_selectionModel = nullptr; + mutable int m_hoverRow = -1; }; diff --git a/src/source/tree/itemviewrowhoverproxystyle.cpp b/src/source/tree/itemviewrowhoverproxystyle.cpp new file mode 100644 index 00000000..44670e23 --- /dev/null +++ b/src/source/tree/itemviewrowhoverproxystyle.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2022 ~ 2026 Uniontech Software Technology Co.,Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "itemviewrowhoverproxystyle.h" +#include "datatreeview.h" + +#include + +ItemViewRowHoverProxyStyle::ItemViewRowHoverProxyStyle(QStyle *baseStyle, DataTreeView *treeView) + : QProxyStyle(baseStyle) + , m_treeView(treeView) +{ + // Lifetime is managed by the creator (we typically parent to qApp). +} + +void ItemViewRowHoverProxyStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (element == QStyle::PE_PanelItemViewRow && option && m_treeView && widget == m_treeView) { + if (const auto *vopt = qstyleoption_cast(option)) { + if (vopt->state.testFlag(QStyle::State_MouseOver)) { + QStyleOptionViewItem copy(*vopt); + copy.state &= ~QStyle::State_MouseOver; + QProxyStyle::drawPrimitive(element, ©, painter, widget); + return; + } + } + } + QProxyStyle::drawPrimitive(element, option, painter, widget); +} diff --git a/src/source/tree/itemviewrowhoverproxystyle.h b/src/source/tree/itemviewrowhoverproxystyle.h new file mode 100644 index 00000000..52037cd2 --- /dev/null +++ b/src/source/tree/itemviewrowhoverproxystyle.h @@ -0,0 +1,32 @@ +// Copyright (C) 2022 ~ 2026 Uniontech Software Technology Co.,Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ITEMVIEWROWHOVERPROXYSTYLE_H +#define ITEMVIEWROWHOVERPROXYSTYLE_H + +#include +#include + +class DataTreeView; + +/** + * Clears State_MouseOver before PE_PanelItemViewRow so DStyle does not paint + * per-cell hover; DataTreeView draws full-row hover instead (mirrors how + * selection is not double-drawn per cell). + */ +class ItemViewRowHoverProxyStyle : public QProxyStyle +{ +public: + explicit ItemViewRowHoverProxyStyle(QStyle *baseStyle, DataTreeView *treeView); + +protected: + void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const override; + +private: + QPointer m_treeView; +}; + +#endif diff --git a/src/source/tree/uncompressview.cpp b/src/source/tree/uncompressview.cpp index d373b3bd..502d3d1f 100644 --- a/src/source/tree/uncompressview.cpp +++ b/src/source/tree/uncompressview.cpp @@ -97,6 +97,8 @@ void UnCompressView::mousePressEvent(QMouseEvent *event) void UnCompressView:: mouseMoveEvent(QMouseEvent *event) { // qDebug() << "Mouse move event at position:" << event->pos(); + updateHoverRowFromPosition(event->pos()); + if (m_isPressed) { // qDebug() << "Is pressed"; //最小距离为防误触和双向滑动时,只触发横向或者纵向的