#include "PanelHeader.hpp" #include "Theme.hpp" #include #include #include #include #include #include #include #include #include namespace geopro::app { namespace { // ── 专业图标/字号尺寸(统一放大)── constexpr int kHeaderHeight = 42; constexpr int kTitleIcon = 20; // 表头标题图标 constexpr int kActionIcon = 19; // 表头操作按钮图标 constexpr int kTabIcon = 19; // Tab 图标 // 表头统一样式(标准表头 + Tab 表头共用)。字号/字重引用 Theme 排版令牌: // 面板标题=title(15)、徽标=caption(12)、Tab 文本=body(13),加粗统一 semibold。 // #panelBadge 为中性计数徽标;#panelBadgeWarn 为“需注意”变体(语义 warning 色), // 供异常计数等承载“待复查”含义的徽标使用(调用方改 objectName 即切换)。 // 表头底/标题/徽标 + 页签样式。页签选中态 = 强调色文字 + 2px 强调色下划线, // 与视图/详情工具条完全一致(全 UI 切换控件统一这一套)。操作按钮(ElaIconButton)自绘 Fluent。 QString headerQss() { return QStringLiteral( "#panelHeader { background:{{bg/panel}}; border-bottom:1px solid {{divider}}; }" "#panelTitle { color:{{text/primary}}; font-size:%1px; font-weight:%3; }" "#panelBadge { background:{{bg/hover}}; color:{{text/secondary}}; border-radius:9px;" " padding:1px 7px; font-size:%2px; font-weight:%3; }" "#panelBadgeWarn { background:{{status/warning-bg}}; color:{{status/warning}}; border-radius:9px;" " padding:1px 7px; font-size:%2px; font-weight:%3; }" "QToolButton#panelAction { border:none; border-radius:7px; padding:5px; }" "QToolButton#panelAction:hover { background:{{bg/hover}}; }" "QToolButton#tabBtn { border:none; border-bottom:2px solid transparent; color:{{text/secondary}};" " padding:8px 6px; font-size:%4px; }" "QToolButton#tabBtn:hover { color:{{text/primary}}; }" "QToolButton#tabBtn:checked { color:{{accent/primary}}; font-weight:%3;" " border-bottom:2px solid {{accent/primary}}; }") .arg(scaledPx(type::kTitle)) // %1 标题字号 .arg(scaledPx(type::kCaption)) // %2 徽标字号 .arg(type::kWeightSemibold) // %3 字重(多处) .arg(scaledPx(type::kBody)); // %4 页签字号 } // 数量徽标(默认隐藏,调用方 setText+setVisible 显示)。 QLabel* makeBadge(QWidget* parent) { auto* badge = new QLabel(parent); badge->setObjectName(QStringLiteral("panelBadge")); badge->setAlignment(Qt::AlignCenter); badge->setMinimumWidth(16); badge->setVisible(false); return badge; } // 表头操作按钮(QToolButton + 项目 glyph 图标,随主题着色;悬停底由 #panelAction QSS 给)。 QWidget* makeActionButton(QWidget* parent, const HeaderAction& a) { auto* btn = new QToolButton(parent); btn->setObjectName(QStringLiteral("panelAction")); btn->setProperty("glyphId", static_cast(a.first)); // 供调用方按图标定位并连接真实功能 setThemedGlyph(btn, a.first, kActionIcon); btn->setIconSize(QSize(kActionIcon, kActionIcon)); btn->setCursor(Qt::PointingHandCursor); btn->setToolTip(a.second + QStringLiteral("(占位)")); btn->setAutoRaise(true); return btn; } } // namespace QWidget* buildPanelHeader(Glyph icon, const QString& title, const QVector& actions) { auto* header = new QWidget(); header->setObjectName(QStringLiteral("panelHeader")); header->setFixedHeight(kHeaderHeight); geopro::app::applyTokenizedStyleSheet(header, headerQss()); auto* lay = new QHBoxLayout(header); lay->setContentsMargins(12, 0, 8, 0); lay->setSpacing(geopro::app::space::kSm); auto* iconLbl = new QLabel(header); setThemedGlyph(iconLbl, icon, kTitleIcon); // 随主题着色(暗色下也清晰) lay->addWidget(iconLbl); auto* titleLbl = new QLabel(title, header); titleLbl->setObjectName(QStringLiteral("panelTitle")); lay->addWidget(titleLbl); lay->addWidget(makeBadge(header)); // 默认隐藏,调用方可经 findChild 更新 lay->addStretch(); for (const auto& a : actions) lay->addWidget(makeActionButton(header, a)); return header; } TabbedPanel buildTabbedPanel(const QVector& tabs, const QVector& actions) { auto* box = new QWidget(); auto* v = new QVBoxLayout(box); v->setContentsMargins(0, 0, 0, 0); v->setSpacing(0); auto* header = new QWidget(box); header->setObjectName(QStringLiteral("panelHeader")); header->setFixedHeight(kHeaderHeight); geopro::app::applyTokenizedStyleSheet(header, headerQss()); auto* hlay = new QHBoxLayout(header); hlay->setContentsMargins(10, 0, 8, 0); hlay->setSpacing(2); auto* stack = new QStackedWidget(box); auto* group = new QButtonGroup(box); group->setExclusive(true); TabbedPanel result; result.container = box; result.tabGroup = group; for (int i = 0; i < tabs.size(); ++i) { const PanelTab& t = tabs[i]; auto* btn = new QToolButton(header); // 页签与工具条统一: QToolButton + 强调色下划线 QSS btn->setObjectName(QStringLiteral("tabBtn")); btn->setText(t.title); setThemedGlyph(btn, t.icon, kTabIcon, kGlyphTextGapPad); // 随主题着色 + 图标→文字6px(§6.7) btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); btn->setCheckable(true); btn->setCursor(Qt::PointingHandCursor); group->addButton(btn, i); hlay->addWidget(btn); QLabel* badge = nullptr; if (t.hasBadge) { badge = makeBadge(header); hlay->addWidget(badge); } result.badges.append(badge); stack->addWidget(t.content); hlay->addSpacing(10); } hlay->addStretch(); for (const auto& a : actions) hlay->addWidget(makeActionButton(header, a)); QObject::connect(group, &QButtonGroup::idClicked, stack, &QStackedWidget::setCurrentIndex); if (auto* first = group->button(0)) first->setChecked(true); stack->setCurrentIndex(0); v->addWidget(header); v->addWidget(stack, 1); return result; } SegmentedHeader buildSegmentedHeader(const QVector& segments, const QVector& actions) { auto* header = new QWidget(); header->setObjectName(QStringLiteral("panelHeader")); header->setFixedHeight(kHeaderHeight); geopro::app::applyTokenizedStyleSheet(header, headerQss()); auto* hlay = new QHBoxLayout(header); hlay->setContentsMargins(10, 0, 8, 0); hlay->setSpacing(2); auto* group = new QButtonGroup(header); group->setExclusive(true); SegmentedHeader result; result.header = header; for (int i = 0; i < segments.size(); ++i) { auto* btn = new QToolButton(header); // 与异常/属性页签统一: tabBtn 样式 + 强调色下划线 btn->setObjectName(QStringLiteral("tabBtn")); btn->setText(segments[i]); btn->setCheckable(true); btn->setCursor(Qt::PointingHandCursor); group->addButton(btn, i); hlay->addWidget(btn); hlay->addSpacing(10); result.buttons.append(btn); } hlay->addStretch(); for (const auto& a : actions) hlay->addWidget(makeActionButton(header, a)); if (!result.buttons.isEmpty()) result.buttons[0]->setChecked(true); return result; } } // namespace geopro::app