From 26404cee2f17068da8c4f0c5b9432dfb3c221969 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 10 Jun 2026 07:43:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(ela):=20P2=20=E6=9A=97=E8=89=B2=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E6=A1=A5=20=E2=80=94=20ElaTheme=20=E6=98=8E/=E6=9A=97?= =?UTF-8?q?=20=E2=86=92=20=E5=85=A8=E5=B1=80=20QSS+=E8=B0=83=E8=89=B2?= =?UTF-8?q?=E6=9D=BF=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Theme.cpp: kDarkMap 浅→暗 hex 映射, 复用 kStyleSheet 结构生成暗色 QSS(含 ads--*); buildPalette(dark) 暗色调色板; applyThemeMode(app,dark); applyTheme=浅色快捷入口 - main.cpp(ela 分支): 连 ElaTheme::themeModeChanged → applyThemeMode 同步非 Ela 面; 初始对齐(解 review C2); Ctrl+Shift+T 切换(正式按钮待 P3 TopBar Ela 化) - VTK 视口背景暗色联动记入 P4(渲染器在 buildWorkbench 内, 需另接) --- src/app/Theme.cpp | 152 ++++++++++++++++++++++++++++++++-------------- src/app/Theme.hpp | 6 +- src/app/main.cpp | 17 ++++++ 3 files changed, 130 insertions(+), 45 deletions(-) diff --git a/src/app/Theme.cpp b/src/app/Theme.cpp index ede5b4d..599c329 100644 --- a/src/app/Theme.cpp +++ b/src/app/Theme.cpp @@ -369,67 +369,131 @@ ads--CDockWidgetTab[activeTab="true"] QLabel { } )QSS"; -// 浅色专业调色板:让标准控件在无 QSS 覆盖处也保持一致底色/选中色。 -QPalette buildPalette() +// ── 暗色映射(P2 主题桥):把浅色 QSS 里每个浅色令牌就近替换为暗色等价, +// 复用同一 kStyleSheet 结构(选择器/度量不变),仅换色。ads--* 规则同在 kStyleSheet 内, +// 故停靠区也随之暗化。品牌蓝在暗底略提亮以保对比度;danger 同理。 +struct HexPair { + const char* light; + const char* dark; +}; +constexpr HexPair kDarkMap[] = { + {"#1F2A3D", "#E3E5E8"}, // 主文字 → 亮 + {"#F4F6FA", "#1E1F22"}, // 外壳底 + {"#FFFFFF", "#2B2D30"}, // 面板白 + {"#EDF1F7", "#323539"}, // 抬升/表头 + {"#EAEEF4", "#3A3D42"}, // 细分隔/边框线 + {"#EEF3FB", "#3A3D42"}, // 悬停底 + {"#DCE9F8", "#2E3F54"}, // 选中行底 + {"#1B3D67", "#CFE0F5"}, // 选中行文字 + {"#3A475C", "#C4C9D0"}, // 表头文字 + {"#5A6B85", "#A9B0BC"}, // 次要文字 + {"#D5DBE5", "#3A3D42"}, // 边框 + {"#C2CCDA", "#4A4E55"}, // 强边框/滚动条柄 + {"#C7D2E0", "#4A4E55"}, // 输入边框 + {"#2D6CB5", "#4A90E2"}, // 强调(品牌蓝,暗底提亮) + {"#2862A6", "#5C9CE8"}, // 强调悬停 + {"#234F87", "#3A7BC8"}, // 强调按下 + {"#9AA6B6", "#6E747C"}, // 禁用文字 + {"#F0F2F6", "#26282B"}, // 禁用底 + {"#8A93A3", "#7A8088"}, // 禁用文字2 + {"#DCE0E7", "#3A3D42"}, // 禁用边框 + {"#A7B4C7", "#5A5F66"}, // 滚动条柄悬停 + {"#E1E6EE", "#3A3D42"}, // 菜单分隔 + {"#DCE6F4", "#34373C"}, // 菜单栏选中 + {"#E6EBF3", "#3A3D42"}, // 进度条底 + {"#EAF1FB", "#2E3F54"}, // 工具按钮选中底 + {"#C0392B", "#E0685C"}, // 危险(暗底提亮) + {"#E6EAF1", "#3A3D42"}, // 面板表头分隔(与 PanelHeader 一致) +}; + +// 浅/暗专业调色板:让标准控件在无 QSS 覆盖处也保持一致底色/选中色。 +QPalette buildPalette(bool dark) { QPalette p; - const QColor shell("#F4F6FA"); - const QColor panel("#FFFFFF"); - const QColor text("#1F2A3D"); - const QColor mutedText("#5A6B85"); - const QColor accent("#2D6CB5"); - - p.setColor(QPalette::Window, shell); - p.setColor(QPalette::WindowText, text); - p.setColor(QPalette::Base, panel); - p.setColor(QPalette::AlternateBase, QColor("#F0F3F8")); - p.setColor(QPalette::Text, text); - p.setColor(QPalette::Button, QColor("#EDF1F7")); - p.setColor(QPalette::ButtonText, text); - p.setColor(QPalette::ToolTipBase, QColor("#1F2A3D")); - p.setColor(QPalette::ToolTipText, shell); - p.setColor(QPalette::Highlight, accent); - p.setColor(QPalette::HighlightedText, panel); - p.setColor(QPalette::PlaceholderText, mutedText); - p.setColor(QPalette::Link, accent); - - // 关键:把 Fusion 用于绘制 3D 凹凸(斜角/凹槽/分隔条阴影)的明暗角色统一压成相近浅灰, - // 立体效果即塌成平面。ADS 分隔条用 palette(dark),这样也变成一条扁平浅灰细线(无 3D)。 - p.setColor(QPalette::Light, QColor("#FFFFFF")); - p.setColor(QPalette::Midlight, QColor("#EEF1F5")); - p.setColor(QPalette::Mid, QColor("#E1E6EE")); - p.setColor(QPalette::Dark, QColor("#D7DEE8")); - p.setColor(QPalette::Shadow, QColor("#D7DEE8")); - - // 禁用态:统一灰化,避免 Fusion 默认禁用色偏暗看不清。 - p.setColor(QPalette::Disabled, QPalette::Text, QColor("#9AA6B6")); - p.setColor(QPalette::Disabled, QPalette::WindowText, QColor("#9AA6B6")); - p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor("#9AA6B6")); + if (!dark) { + const QColor shell("#F4F6FA"), panel("#FFFFFF"), text("#1F2A3D"), + mutedText("#5A6B85"), accent("#2D6CB5"); + p.setColor(QPalette::Window, shell); + p.setColor(QPalette::WindowText, text); + p.setColor(QPalette::Base, panel); + p.setColor(QPalette::AlternateBase, QColor("#F0F3F8")); + p.setColor(QPalette::Text, text); + p.setColor(QPalette::Button, QColor("#EDF1F7")); + p.setColor(QPalette::ButtonText, text); + p.setColor(QPalette::ToolTipBase, QColor("#1F2A3D")); + p.setColor(QPalette::ToolTipText, shell); + p.setColor(QPalette::Highlight, accent); + p.setColor(QPalette::HighlightedText, panel); + p.setColor(QPalette::PlaceholderText, mutedText); + p.setColor(QPalette::Link, accent); + // 把 Fusion 的明暗 3D 角色压成相近浅灰,立体效果塌成平面。 + p.setColor(QPalette::Light, QColor("#FFFFFF")); + p.setColor(QPalette::Midlight, QColor("#EEF1F5")); + p.setColor(QPalette::Mid, QColor("#E1E6EE")); + p.setColor(QPalette::Dark, QColor("#D7DEE8")); + p.setColor(QPalette::Shadow, QColor("#D7DEE8")); + p.setColor(QPalette::Disabled, QPalette::Text, QColor("#9AA6B6")); + p.setColor(QPalette::Disabled, QPalette::WindowText, QColor("#9AA6B6")); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor("#9AA6B6")); + } else { + const QColor shell("#1E1F22"), panel("#2B2D30"), text("#E3E5E8"), + mutedText("#A9B0BC"), accent("#4A90E2"); + p.setColor(QPalette::Window, shell); + p.setColor(QPalette::WindowText, text); + p.setColor(QPalette::Base, panel); + p.setColor(QPalette::AlternateBase, QColor("#303236")); + p.setColor(QPalette::Text, text); + p.setColor(QPalette::Button, QColor("#323539")); + p.setColor(QPalette::ButtonText, text); + p.setColor(QPalette::ToolTipBase, QColor("#E3E5E8")); + p.setColor(QPalette::ToolTipText, QColor("#1E1F22")); + p.setColor(QPalette::Highlight, accent); + p.setColor(QPalette::HighlightedText, QColor("#0E1013")); + p.setColor(QPalette::PlaceholderText, mutedText); + p.setColor(QPalette::Link, accent); + p.setColor(QPalette::Light, QColor("#34373C")); + p.setColor(QPalette::Midlight, QColor("#303236")); + p.setColor(QPalette::Mid, QColor("#3A3D42")); + p.setColor(QPalette::Dark, QColor("#3A3D42")); + p.setColor(QPalette::Shadow, QColor("#3A3D42")); + p.setColor(QPalette::Disabled, QPalette::Text, QColor("#6E747C")); + p.setColor(QPalette::Disabled, QPalette::WindowText, QColor("#6E747C")); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor("#6E747C")); + } return p; } +// 当前模式的全局 QSS:暗色 = 在浅色 kStyleSheet 上逐一替换色令牌。 +QString styleSheetForMode(bool dark) +{ + QString s = QString::fromUtf8(kStyleSheet); + if (dark) { + for (const auto& hp : kDarkMap) + s.replace(QLatin1String(hp.light), QLatin1String(hp.dark)); + } + return s; +} + } // namespace -void applyTheme(QApplication& app) +void applyThemeMode(QApplication& app, bool dark) { // Fusion:跨平台一致且对 QSS 友好(Windows 原生风对部分控件会忽略样式表)。 app.setStyle(QStyleFactory::create(QStringLiteral("Fusion"))); - // 基础字体:中文界面用 微软雅黑 UI 渲染最清爽;缺失时 Qt 自动回退。 - // 基准字号取排版令牌 type::kBody(13px)——统一为 px,与 QSS 同单位 - // (旧值 10pt≈13.3px,观感几乎不变);9pt 偏小显拥挤。抗锯齿优先,观感更精致。 + // 基础字体:微软雅黑 UI;基准字号取令牌 type::kBody(13px),与 QSS 同单位。 QFont base(QStringLiteral("Microsoft YaHei UI")); base.setPixelSize(type::kBody); base.setStyleStrategy(QFont::PreferAntialias); app.setFont(base); - app.setPalette(buildPalette()); + app.setPalette(buildPalette(dark)); + app.setStyleSheet(styleSheetForMode(dark)); +} - // 注意:不要给 ADS 停靠标题(ads--CDockWidgetTab QLabel)追加任何样式—— - // 这些子窗口标题栏在 main.cpp 里被 setVisible(false) 刻意隐藏(表头由各面板 - // 自绘的 PanelHeader 承担)。改写其字号/内边距会变更度量并触发 ADS 重新 - // 评估标题栏可见性,把隐藏的标题又显示出来。字号统一只作用于可见控件。 - app.setStyleSheet(QString::fromUtf8(kStyleSheet)); +void applyTheme(QApplication& app) +{ + applyThemeMode(app, false); } } // namespace geopro::app diff --git a/src/app/Theme.hpp b/src/app/Theme.hpp index 71a51d0..385b005 100644 --- a/src/app/Theme.hpp +++ b/src/app/Theme.hpp @@ -80,7 +80,11 @@ inline constexpr const char* kWarningFill = "#FBEAD2"; // 警告底纹(配 kW } // namespace semantic -// 应用浅色专业主题(Fusion + 调色板 + 全局样式表)。幂等,启动调用一次即可。 +// 应用专业主题(Fusion + 调色板 + 全局样式表)。dark=true 走暗色(P2 主题桥用)。 +// 暗色复用同一 QSS 结构,仅按 kDarkMap 换色;幂等,可随主题切换重复调用。 +void applyThemeMode(QApplication& app, bool dark); + +// 浅色主题快捷入口(= applyThemeMode(app,false))。经典壳启动调用一次。 void applyTheme(QApplication& app); } // namespace geopro::app diff --git a/src/app/main.cpp b/src/app/main.cpp index 51a9377..3fb41bd 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -36,7 +36,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -59,6 +61,8 @@ #include #include +#include +#include #include #include "model/ColorScale.hpp" @@ -1030,6 +1034,19 @@ int main(int argc, char* argv[]) // 注意:不能用 setCentralCustomWidget——它把控件插到页栈容器“之上”,空页栈仍占底部, // 导致状态栏不贴底边(见 ElaCentralStackedWidget::setCustomWidget 的 insertWidget(0,...))。 ela->addPageNode(kTitle, inner); + + // ── P2 主题桥:ElaTheme 明/暗切换 → 同步全局 QSS+调色板(覆盖所有标准控件与 ADS)。 + // Ela 自家控件由 ElaTheme 自动换肤;这里补齐非 Ela 面。也确定了主题最终态(解 review C2)。 + QObject::connect(eTheme, &ElaTheme::themeModeChanged, ela, [&app](ElaThemeType::ThemeMode m) { + geopro::app::applyThemeMode(app, m == ElaThemeType::Dark); + }); + geopro::app::applyThemeMode(app, eTheme->getThemeMode() == ElaThemeType::Dark); // 初始对齐 + // 主题切换快捷键 Ctrl+Shift+T(正式切换入口待 P3 TopBar Ela 化后加按钮)。 + auto* themeSc = new QShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+T")), ela); + QObject::connect(themeSc, &QShortcut::activated, ela, [] { + eTheme->setThemeMode(eTheme->getThemeMode() == ElaThemeType::Light ? ElaThemeType::Dark + : ElaThemeType::Light); + }); topLevel = ela; } else { auto* window = new QMainWindow;