209 lines
8.2 KiB
C++
209 lines
8.2 KiB
C++
#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 {
|
||
|
||
// ── 表头图标/尺寸(规范 §4.3:高 36px、标题图标 14px、操作按钮 24×24 含 16px 图标)──
|
||
constexpr int kHeaderHeight = 36; // 表头高度(§4.3)。标准/Tab/分段表头共用,保持一致。
|
||
constexpr int kTitleIcon = 14; // 表头标题图标(§4.3「14px 图标」)
|
||
constexpr int kActionIcon = 16; // 操作按钮内图标(§9「按钮内 16px」)
|
||
constexpr int kActionButton = 24; // 操作按钮命中区 24×24(§4.3 / §12 无障碍 ≥24×24)
|
||
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:%5px; padding:0px; }"
|
||
"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 页签字号
|
||
.arg(radius::kSm); // %5 操作按钮悬停底圆角(§3.2)
|
||
}
|
||
|
||
// 数量徽标(默认隐藏,调用方 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(scaledPx(kActionIcon), scaledPx(kActionIcon)));
|
||
// 命中区固定 24×24(§4.3 / §12 无障碍):16px 图标居中、四周自然留出内距。
|
||
btn->setFixedSize(QSize(scaledPx(kActionButton), scaledPx(kActionButton)));
|
||
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(scaledPx(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(scaledPx(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(scaledPx(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
|