From 8f31f043df0657243b30421a195d9124dcdeff1d Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 10 Jun 2026 16:44:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(panels):=20=E5=BC=82=E5=B8=B8=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=8D=A1=E7=89=87=E5=8C=96(=E8=89=B2=E6=9D=A1+?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=A0=87=E7=AD=BE+=E6=98=BE=E9=9A=90?= =?UTF-8?q?=E7=9C=BC=E7=9D=9B,=E7=9C=9F=E5=AE=9E=E6=95=B0=E6=8D=AE)?= =?UTF-8?q?=EF=BC=88=E8=A7=84=E8=8C=83=C2=A76.3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/main.cpp | 1 + src/app/panels/AnomalyListPanel.cpp | 159 +++++++++++++++++++++++++--- src/app/panels/AnomalyListPanel.hpp | 8 ++ 3 files changed, 151 insertions(+), 17 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index 9d69a3a..19dfe7a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -563,6 +563,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 右上 dock:异常列表 / 对象属性 合并为带 Tab 表头的面板(对齐原型上半)。 auto* anomalyList = new QListWidget(); applyListSelection(anomalyList); + geopro::app::applyAnomalyCardDelegate(anomalyList); auto* objAttrLabel = new QLabel(QStringLiteral("(选中对象后显示其属性)")); objAttrLabel->setWordWrap(true); objAttrLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); diff --git a/src/app/panels/AnomalyListPanel.cpp b/src/app/panels/AnomalyListPanel.cpp index 6f5fe2d..f2eb677 100644 --- a/src/app/panels/AnomalyListPanel.cpp +++ b/src/app/panels/AnomalyListPanel.cpp @@ -3,22 +3,26 @@ #include #include +#include #include -#include +#include #include #include -#include +#include +#include +#include +#include +#include #include +#include +#include "Theme.hpp" #include "model/ColorScale.hpp" namespace geopro::app { namespace { -// 颜色块图标边长(像素)。 -constexpr int kSwatch = 12; - // 由 localPts 算「位置(质心x)·深(质心y)·尺寸(包络对角)」摘要文本。 // 异常坐标在剖面距离/深度空间(x=距离米, y=深度米)。 QString summarize(const geopro::core::Anomaly& a) @@ -43,15 +47,128 @@ QString summarize(const geopro::core::Anomaly& a) .arg(span, 0, 'f', 0); } -// lineColor 字符串("#RRGGBB"/"rgba(...)") → 颜色块 QPixmap。 -QPixmap swatch(const std::string& colorStr) +// lineColor 字符串 → QColor(兼容 "#RRGGBB" 与 "rgba(...)")。 +QColor barColor(const QString& s) { - const auto c = geopro::core::parseColor(colorStr, geopro::core::AlphaScale::Bit255); - QPixmap pm(kSwatch, kSwatch); - pm.fill(QColor(c.r, c.g, c.b)); - return pm; + const auto c = geopro::core::parseColor(s.toStdString(), geopro::core::AlphaScale::Bit255); + return QColor(c.r, c.g, c.b); } +// 右侧眼睛命中区(卡片右端,竖直居中)。 +QRect anomalyEyeRect(const QRect& itemRect) +{ + const QRect r = itemRect.adjusted(4, 2, -4, -2); + const int sz = 22; + return QRect(r.right() - sz - 8, r.center().y() - sz / 2, sz, sz); +} + +class AnomalyCardDelegate : public QStyledItemDelegate { +public: + using QStyledItemDelegate::QStyledItemDelegate; + + QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override + { + return QSize(0, 58); + } + + bool editorEvent(QEvent* e, QAbstractItemModel* model, const QStyleOptionViewItem& opt, + const QModelIndex& idx) override + { + if (e->type() == QEvent::MouseButtonRelease) { + auto* me = static_cast(e); + if (anomalyEyeRect(opt.rect).contains(me->position().toPoint())) { + const auto cur = static_cast(idx.data(Qt::CheckStateRole).toInt()); + model->setData(idx, cur == Qt::Checked ? Qt::Unchecked : Qt::Checked, + Qt::CheckStateRole); + return true; // 吃掉点击:只切显隐,不改选中 + } + } + return QStyledItemDelegate::editorEvent(e, model, opt, idx); + } + + void paint(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& idx) const override + { + p->save(); + p->setRenderHint(QPainter::Antialiasing, true); + + const QRect r = opt.rect.adjusted(4, 3, -4, -3); + const bool selected = opt.state & QStyle::State_Selected; + const bool hover = opt.state & QStyle::State_MouseOver; + + // 卡底(hover/选中高亮) + if (selected || hover) { + QPainterPath path; path.addRoundedRect(r, 6, 6); + p->fillPath(path, geopro::app::tokenColor(selected ? "bg/selected" : "bg/hover")); + } + // 左 3px 状态色竖条(取异常自身 lineColor) + p->fillRect(QRect(r.left(), r.top() + 4, 3, r.height() - 8), + barColor(idx.data(kAnomalyColorRole).toString())); + + const QString name = idx.data(Qt::DisplayRole).toString(); + const QString type = idx.data(kAnomalyTypeRole).toString(); + const QString summary = idx.data(kAnomalySummaryRole).toString(); + + const int left = r.left() + 14; + const int right = anomalyEyeRect(opt.rect).left() - 8; // 给眼睛留位 + const int rowW = right - left; + + // 第一行:名称(加粗) + QFont nf = opt.font; nf.setPixelSize(geopro::app::scaledPx(13)); nf.setWeight(QFont::DemiBold); + p->setFont(nf); + p->setPen(geopro::app::tokenColor("text/primary")); + const QRect nameR(left, r.top() + 8, rowW, 20); + p->drawText(nameR, Qt::AlignLeft | Qt::AlignVCenter, + p->fontMetrics().elidedText(name, Qt::ElideRight, rowW)); + + // 第二行:类型胶囊 + 摘要 + int x = left; + const int cy = r.top() + 38; + if (!type.isEmpty()) { + QFont pf = opt.font; pf.setPixelSize(geopro::app::scaledPx(11)); + p->setFont(pf); + const QFontMetrics fm(pf); + const int tw = fm.horizontalAdvance(type); + const int ph = fm.height() + 2; + const QRect pill(x, cy - ph / 2, tw + 12, ph); + QPainterPath pp; pp.addRoundedRect(pill, ph / 2.0, ph / 2.0); + p->fillPath(pp, geopro::app::tokenColor("bg/hover")); + p->setPen(geopro::app::tokenColor("text/secondary")); + p->drawText(pill, Qt::AlignCenter, type); + x = pill.right() + 8; + } + if (!summary.isEmpty()) { + QFont sf = opt.font; sf.setPixelSize(geopro::app::scaledPx(11)); + p->setFont(sf); + p->setPen(geopro::app::tokenColor("text/secondary")); + const QRect sumR(x, cy - 10, right - x, 20); + p->drawText(sumR, Qt::AlignLeft | Qt::AlignVCenter, + p->fontMetrics().elidedText(summary, Qt::ElideRight, sumR.width())); + } + + // 右侧眼睛(显隐):可见=次要色睁眼;隐藏=禁用色 + 斜杠 + const bool visible = + static_cast(idx.data(Qt::CheckStateRole).toInt()) == Qt::Checked; + const QColor eyeCol = geopro::app::tokenColor(visible ? "text/secondary" : "text/disabled"); + const QRectF eb = anomalyEyeRect(opt.rect); + const QPointF c = eb.center(); + const double w = eb.width() * 0.42, h = eb.height() * 0.24; + p->setPen(QPen(eyeCol, 1.4)); + p->setBrush(Qt::NoBrush); + QPainterPath eye; + eye.moveTo(c.x() - w, c.y()); + eye.quadTo(c.x(), c.y() - h * 2.0, c.x() + w, c.y()); + eye.quadTo(c.x(), c.y() + h * 2.0, c.x() - w, c.y()); + p->drawPath(eye); + p->setBrush(eyeCol); + p->drawEllipse(c, h * 0.95, h * 0.95); + p->setBrush(Qt::NoBrush); + if (!visible) + p->drawLine(QPointF(c.x() - w, c.y() + h * 1.6), QPointF(c.x() + w, c.y() - h * 1.6)); + + p->restore(); + } +}; + } // namespace void populateAnomalyList(QListWidget* list, const std::vector& anomalies) @@ -61,16 +178,24 @@ void populateAnomalyList(QListWidget* list, const std::vectorsetData(kAnomalyIndexRole, static_cast(i)); + item->setData(kAnomalyColorRole, QString::fromStdString(a.lineColor)); + item->setData(kAnomalyTypeRole, QString::fromStdString(a.typeName)); + item->setData(kAnomalySummaryRole, summarize(a)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Checked); // 默认显示 } } +void applyAnomalyCardDelegate(QListWidget* list) +{ + if (!list) return; + list->setItemDelegate(new AnomalyCardDelegate(list)); + list->setMouseTracking(true); + list->setSpacing(0); + QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, list, + [list]() { list->viewport()->update(); }); +} + } // namespace geopro::app diff --git a/src/app/panels/AnomalyListPanel.hpp b/src/app/panels/AnomalyListPanel.hpp index e5566ed..ce6e6b2 100644 --- a/src/app/panels/AnomalyListPanel.hpp +++ b/src/app/panels/AnomalyListPanel.hpp @@ -10,6 +10,14 @@ namespace geopro::app { // 异常索引存于条目的 Qt::UserRole(= 在原异常 vector 中的下标,用于显隐映射)。 constexpr int kAnomalyIndexRole = 0x0100; // Qt::UserRole +// 卡片委托读取的结构化角色(避免把数据塞进显示文本)。 +constexpr int kAnomalyColorRole = 0x0101; // lineColor 字符串 +constexpr int kAnomalyTypeRole = 0x0102; // typeName +constexpr int kAnomalySummaryRole = 0x0103; // 位置·深·尺寸 摘要 + +// 给异常列表套用卡片委托(左色条+名称+类型标签+摘要+右侧显隐眼睛,规范§6.3)。 +void applyAnomalyCardDelegate(QListWidget* list); + // 用异常填充 QListWidget(对齐原型右上「异常列表」):每条目 = 颜色块图标 + 名称 + // 派生「位置 Xm · 深 Ym · 尺寸 Zm」(由 location.coordinate 质心/包络算)。 // 条目可勾选:勾=显示(默认全勾);勾选状态变化由调用方连接驱动该异常 actor 显隐。