diff --git a/src/app/SettingsDialog.cpp b/src/app/SettingsDialog.cpp index 8ca3537..d10f106 100644 --- a/src/app/SettingsDialog.cpp +++ b/src/app/SettingsDialog.cpp @@ -74,8 +74,8 @@ QWidget* buildAppearancePage() { rlay->setContentsMargins(96 + 12, 0, 0, 0); // 与控件列对齐 rlay->setSpacing(10); auto* hint = new QLabel(QStringLiteral("界面字号将在重启后生效"), restartRow); - geopro::app::applyThemedStyleSheet( - hint, QStringLiteral("color:#5A6B85; font-size:%1px;") + geopro::app::applyTokenizedStyleSheet( + hint, QStringLiteral("color:{{text/secondary}}; font-size:%1px;") .arg(geopro::app::scaledPx(geopro::app::type::kCaption))); auto* restartBtn = new QPushButton(QStringLiteral("立即重启"), restartRow); rlay->addWidget(hint); diff --git a/src/app/Theme.cpp b/src/app/Theme.cpp index 0a15e91..61b8ec3 100644 --- a/src/app/Theme.cpp +++ b/src/app/Theme.cpp @@ -449,60 +449,6 @@ ads--CDockWidgetTab[activeTab="true"] QLabel { } )QSS"; -// ── 主题桥配色:工作台标准控件的 QSS/调色板颜色直接取自 ElaTheme, -// 保证里外(ElaWindow 外壳 ↔ 内部工作台)在明/暗两模下完全一致——这是关键, -// 否则外壳一种灰、工作台另一种灰,明暗都显得割裂。 -// 做法:把浅色设计稿里每个色令牌按语义角色替换为 ElaTheme 当前模式的真实颜色。 -// 浅→暗 颜色映射(全 UI 唯一颜色来源)。明色 = 令牌本身(QSS 直接写的就是明色); -// 暗色 = 此表对应值。覆盖全部 QSS 用色,缺一即在暗色下露浅色。改色只改这一处。 -struct DarkPair { - const char* light; - const char* dark; -}; -const DarkPair kDarkMap[] = { - // 背景(外壳→面板→抬升) - {"#F4F6FA", "#1E1F22"}, {"#FFFFFF", "#2B2D30"}, {"#EDF1F7", "#34373C"}, - {"#F0F2F6", "#2B2D30"}, {"#EAEEF4", "#3A3D42"}, {"#EAEEF5", "#34373C"}, - {"#EEF2FB", "#34373C"}, {"#E6EBF3", "#34373C"}, {"#EEF3FB", "#34373C"}, - {"#EAF1FB", "#2F4257"}, {"#DCE6F4", "#2A3A4F"}, {"#DCE9F8", "#33527A"}, - {"#FBEAD2", "#46371F"}, - // 文字 - {"#1F2A3D", "#E6E8EB"}, {"#5A6B85", "#A0A8B4"}, {"#3A475C", "#C4CCD8"}, - {"#8A93A3", "#828B98"}, {"#9AA6B6", "#6E7681"}, {"#1B3D67", "#E8F1FB"}, - // 边框 - {"#D5DBE5", "#3A3D42"}, {"#C2CCDA", "#484C52"}, {"#C7D2E0", "#484C52"}, - {"#E1E6EE", "#34373C"}, {"#E6EAF1", "#34373C"}, {"#EEF1F5", "#34373C"}, - {"#DCE0E7", "#3A3D42"}, {"#A7B4C7", "#4A4E54"}, - // 强调(品牌蓝) - {"#2D6CB5", "#5E9BD6"}, {"#2862A6", "#6FA8DD"}, {"#234F87", "#4E89C4"}, - // 语义 - {"#C0392B", "#E06A5E"}, {"#B45309", "#E0964A"}, {"#15803D", "#5BBF7A"}, -}; - -QString darkOf(const QString& lightHex) -{ - for (const auto& p : kDarkMap) - if (lightHex.compare(QLatin1String(p.light), Qt::CaseInsensitive) == 0) - return QString::fromLatin1(p.dark); - return lightHex; -} - -// 设计令牌(浅色 hex) → 当前明暗的真实色。 -QColor roleColor(bool dark, const char* lightHex) -{ - return dark ? QColor(darkOf(QString::fromLatin1(lightHex))) : QColor(QLatin1String(lightHex)); -} - -// 把一段浅色设计稿 QSS 按当前明暗着色:明色原样;暗色把每个浅色令牌替换为暗色。 -QString themedQss(const QString& designQss, bool dark) -{ - if (!dark) return designQss; - QString s = designQss; - for (const auto& p : kDarkMap) - s.replace(QString::fromLatin1(p.light), QString::fromLatin1(p.dark), Qt::CaseInsensitive); - return s; -} - // 当前模式的全局 QSS。 QString styleSheetForMode(bool /*dark*/) { @@ -662,11 +608,6 @@ bool isDarkTheme() return ThemeManager::instance().isDark(); } -QString themed(const QString& designQss) -{ - return themedQss(designQss, isDarkTheme()); -} - void vtkBackground(double& r, double& g, double& b) { // 规范 §0.5/§11:数据画布永远深色,不随明暗切换。取 canvas/bg。 @@ -676,14 +617,6 @@ void vtkBackground(double& r, double& g, double& b) b = c.blueF(); } -void applyThemedStyleSheet(QWidget* w, const QString& designQss) -{ - if (!w) return; - w->setStyleSheet(themedQss(designQss, isDarkTheme())); - QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, w, - [w, designQss]() { w->setStyleSheet(themedQss(designQss, isDarkTheme())); }); -} - QString token(const char* name) { return tokenHex(name, isDarkTheme()); } QColor tokenColor(const char* name) { return QColor(token(name)); } diff --git a/src/app/Theme.hpp b/src/app/Theme.hpp index 9d79c0f..5148b72 100644 --- a/src/app/Theme.hpp +++ b/src/app/Theme.hpp @@ -19,7 +19,7 @@ class QWidget; namespace geopro::app { // 主题管理器(纯 Qt,替代 ElaTheme):持有当前明暗 + 是否跟随系统;切换发 changed() 信号, -// 全 UI(全局 QSS 由 main 重应用、内联 chrome 由 applyThemedStyleSheet)据此热切重着色。 +// 全 UI(全局 QSS 由 main 重应用、内联 chrome 由 applyTokenizedStyleSheet)据此热切重着色。 class ThemeManager : public QObject { Q_OBJECT public: @@ -103,7 +103,7 @@ inline constexpr const char* kWarningFill = "#FBEAD2"; // 警告底纹(配 kW } // namespace semantic // 应用专业主题(Fusion + 调色板 + 全局样式表)。dark=true 走暗色(P2 主题桥用)。 -// 暗色复用同一 QSS 结构,仅按 kDarkMap 换色;幂等,可随主题切换重复调用。 +// 暗色复用同一 QSS 结构,颜色全由 kTokens 双值(fillTokens/tokenHex)驱动;幂等,可随主题切换重复调用。 void applyThemeMode(QApplication& app, bool dark); // 浅色主题快捷入口(= applyThemeMode(app,false))。经典壳启动调用一次。 @@ -128,15 +128,6 @@ bool isDarkTheme(); // VTK 渲染器背景色(随当前主题,取 ElaTheme 窗口底色)。写入 r/g/b(0–1)。 void vtkBackground(double& r, double& g, double& b); -// 把一段「浅色设计稿 QSS」按当前 ElaTheme 配色着色应用到 widget,并随明/暗切换自动重着色。 -// 用于 TopBar/PanelHeader/浮层 等带内联 setStyleSheet 的自定义 chrome——让它们也跟随主题 -// (设计稿里用浅色令牌 #1F2A3D/#FFFFFF/#2D6CB5… 书写即可,与全局 QSS 同一套角色映射)。 -void applyThemedStyleSheet(QWidget* w, const QString& designQss); - -// 把一段「浅色设计稿 QSS」按当前 ElaTheme 配色着色后返回(供需要拼接/手动 setStyleSheet 的场景, -// 如 ADS dockManager 在其自带样式后追加规则)。不自动随主题切换,调用方需自行在切换时重取。 -QString themed(const QString& designQss); - // ── 语义令牌(单一事实来源,取值见 Theme.cpp kTokens;规范 §1.5 + 附录 A + §1.3)── // 组件只引语义 token,禁止散落硬编码 hex。token 名形如 "bg/panel"、"accent/primary"。 QString token(const char* name); // 当前明暗下的 hex(未知名返回品红 "#FF00FF" 以便一眼发现漏配) diff --git a/src/app/login/LoginWindow.cpp b/src/app/login/LoginWindow.cpp index abb81de..f042b27 100644 --- a/src/app/login/LoginWindow.cpp +++ b/src/app/login/LoginWindow.cpp @@ -89,17 +89,17 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent) // 字号引用 Theme 排版令牌:品牌名=display(24)、副标题/字段标签=caption(12)。 // 登录窗整体随 ElaTheme 着色(与 Ela 化的输入/按钮一致,避免暗系统下浅窗+暗控件割裂)。 // 品牌带文字用 white 关键字(不入角色映射→恒为白),保证落在蓝色横幅上始终可读。 - geopro::app::applyThemedStyleSheet( + geopro::app::applyTokenizedStyleSheet( this, QStringLiteral( - "QDialog { background: #F4F6FA; }" + "QDialog { background: {{bg/app}}; }" "#headerBand {" " background: qlineargradient(x1:0, y1:0, x2:1, y2:1," - " stop:0 #2D6CB5, stop:1 #234F87); }" - "#brandTitle { color: white; font-size: %1px; font-weight: %2; }" + " stop:0 {{accent/primary}}, stop:1 {{accent/primary-pressed}}); }" + "#brandTitle { color: {{text/on-primary}}; font-size: %1px; font-weight: %2; }" "#brandSubtitle { color: rgba(255,255,255,0.82); font-size: %3px; }" - "#fieldLabel { color: #5A6B85; font-size: %4px; font-weight: %5; }" + "#fieldLabel { color: {{text/secondary}}; font-size: %4px; font-weight: %5; }" // 输入框已 Ela 化(ElaLineEdit 自绘 Fluent + 自动明暗),不再写 QLineEdit QSS。 - "#captchaImg { border: 1px solid #C7D2E0; border-radius: 8px; background: #EEF2FB; }") + "#captchaImg { border: 1px solid {{border/strong}}; border-radius: 8px; background: {{bg/hover}}; }") .arg(scaledPx(type::kDisplay)) .arg(type::kWeightBold) .arg(scaledPx(type::kCaption)) @@ -178,11 +178,11 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent) refreshBtn_ = new QPushButton(QStringLiteral("看不清?换一张"), body); refreshBtn_->setFlat(true); refreshBtn_->setCursor(Qt::PointingHandCursor); - geopro::app::applyThemedStyleSheet( + geopro::app::applyTokenizedStyleSheet( refreshBtn_, QStringLiteral( - "QPushButton { color: #2D6CB5; border: none; background: transparent; padding: 2px 0; }" - "QPushButton:hover { color: #234F87; text-decoration: underline; }")); + "QPushButton { color: {{accent/primary}}; border: none; background: transparent; padding: 2px 0; }" + "QPushButton:hover { color: {{accent/primary-pressed}}; text-decoration: underline; }")); refreshRow->addWidget(refreshBtn_); form->addLayout(refreshRow); @@ -193,8 +193,8 @@ LoginWindow::LoginWindow(geopro::net::AuthService& auth, QWidget* parent) // 错误提示:固定占位高度,避免出现时整体布局跳动。 errorLabel_ = new QLabel(body); - geopro::app::applyThemedStyleSheet( - errorLabel_, QStringLiteral("color: #C0392B; font-size: %1px;").arg(scaledPx(type::kCaption))); + geopro::app::applyTokenizedStyleSheet( + errorLabel_, QStringLiteral("color: {{status/danger}}; font-size: %1px;").arg(scaledPx(type::kCaption))); errorLabel_->setWordWrap(true); errorLabel_->setMinimumHeight(18); form->addWidget(errorLabel_);