geopro/src/app/PanelHeader.cpp

205 lines
7.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "PanelHeader.hpp"
#include "Theme.hpp"
#include <QButtonGroup>
#include <QColor>
#include <QHBoxLayout>
#include <QLabel>
#include <QSize>
#include <QStackedWidget>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
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<int>(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<HeaderAction>& 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<PanelTab>& tabs, const QVector<HeaderAction>& 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<QString>& segments,
const QVector<HeaderAction>& 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