From 18d084047f2bd6585aa0065349cfc40f777a2961 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 10 Jun 2026 15:17:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E8=AF=AD=E4=B9=89=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD=EF=BC=88=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E8=A1=A8+token/fillTokens=20API=EF=BC=8C=E8=A7=84?= =?UTF-8?q?=E8=8C=83=C2=A71=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/Theme.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++++ src/app/Theme.hpp | 12 +++++++ 2 files changed, 91 insertions(+) diff --git a/src/app/Theme.cpp b/src/app/Theme.cpp index e05e7eb..3a8b0e1 100644 --- a/src/app/Theme.cpp +++ b/src/app/Theme.cpp @@ -28,6 +28,63 @@ public: } }; +// ── 语义令牌表(全 UI 唯一颜色来源)。改色只改这一处。 ────────────────── +// 取值来源:规范 §1.5 语义映射 + 附录 A 速查 + §1.3 画布专用色。 +// 画布(canvas/*)与 bg/canvas 两模式同值——规范 §0.5「视图区永远深色」。 +struct Token { const char* name; const char* light; const char* dark; }; +const Token kTokens[] = { + // 背景 + {"bg/app", "#F7F8FA", "#0E1116"}, + {"bg/panel", "#FFFFFF", "#161A20"}, + {"bg/panel-subtle", "#FCFCFD", "#161B22"}, + {"bg/header", "#FFFFFF", "#12161C"}, + {"bg/hover", "#EFF1F4", "#1B2129"}, + {"bg/selected", "#EFF5FF", "#16243F"}, + {"bg/canvas", "#0B1320", "#0B1320"}, + // 边框 + {"border/default", "#E3E6EB", "#262C35"}, + {"border/strong", "#CDD2DA", "#333B45"}, + {"border/focus", "#3B73EC", "#5E8DF5"}, + // 文字 + {"text/primary", "#272C35", "#E6E9EF"}, + {"text/secondary", "#5A626F", "#A4ADBB"}, + {"text/tertiary", "#7C8493", "#7A8494"}, + {"text/disabled", "#A8AFBC", "#5A626F"}, + {"text/link", "#3B73EC", "#5E8DF5"}, + {"text/on-primary", "#FFFFFF", "#FFFFFF"}, + // 强调 + {"accent/primary", "#3B73EC", "#5E8DF5"}, + {"accent/primary-hover", "#2B5FD9", "#93B4FA"}, + {"accent/primary-pressed","#2450B8", "#3B73EC"}, + // 其他 + {"divider", "#E3E6EB", "#22272F"}, + {"scrollbar/thumb", "#CDD2DA", "#3A424D"}, + {"scrollbar/thumb-hover", "#A8AFBC", "#4A535F"}, + // 状态色(主色 + 浅底)规范 §1.4 + {"status/danger", "#E5484D", "#FF6166"}, + {"status/danger-bg", "#FDECEC", "#3A1D1F"}, + {"status/warning", "#E08A1E", "#F5A623"}, + {"status/warning-bg", "#FBF0DD", "#3A2C12"}, + {"status/success", "#2E9E5B", "#46C07A"}, + {"status/success-bg", "#E7F6ED", "#16301F"}, + {"status/info", "#3B73EC", "#5E8DF5"}, + {"status/info-bg", "#EFF5FF", "#16243F"}, + {"status/neutral", "#7C8493", "#8A93A3"}, + // 画布专用(两模式同值)规范 §1.3 + {"canvas/bg", "#0B1320", "#0B1320"}, + {"canvas/bg-soft", "#111B2D", "#111B2D"}, + {"canvas/grid", "#1E2A3D", "#1E2A3D"}, + {"canvas/text", "#E6ECF5", "#E6ECF5"}, + {"canvas/text-dim", "#8A97AC", "#8A97AC"}, +}; + +QString tokenHex(const char* name, bool dark) +{ + for (const auto& t : kTokens) + if (qstrcmp(t.name, name) == 0) return QString::fromLatin1(dark ? t.dark : t.light); + return QStringLiteral("#FF00FF"); // 漏配的令牌显眼品红,便于一眼发现 +} + // 全局样式表(浅色专业)。只描述外观,不含任何交互逻辑。 // 注意:刻意不重写 QCheckBox::indicator —— Fusion 一旦检测到 indicator 子控件被改写, // 就需要自带勾选 image,否则勾选态会变成空白方块。这里交给 Fusion 原生绘制, @@ -625,4 +682,26 @@ void applyThemedStyleSheet(QWidget* w, const QString& designQss) [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)); } + +QString fillTokens(const QString& tmpl) +{ + const bool dark = isDarkTheme(); + QString s = tmpl; + for (const auto& t : kTokens) + s.replace(QStringLiteral("{{%1}}").arg(QLatin1String(t.name)), + QString::fromLatin1(dark ? t.dark : t.light)); + return s; +} + +void applyTokenizedStyleSheet(QWidget* w, const QString& tmpl) +{ + if (!w) return; + w->setStyleSheet(fillTokens(tmpl)); + QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, w, + [w, tmpl]() { w->setStyleSheet(fillTokens(tmpl)); }); +} + } // namespace geopro::app diff --git a/src/app/Theme.hpp b/src/app/Theme.hpp index 50e98bb..9d79c0f 100644 --- a/src/app/Theme.hpp +++ b/src/app/Theme.hpp @@ -9,6 +9,7 @@ // 文字 #1F2A3D 次要文字 #5A6B85 边框 #D5DBE5 强边框 #C2CCDA // 危险 #C0392B +#include #include #include @@ -136,4 +137,15 @@ void applyThemedStyleSheet(QWidget* w, const QString& designQss); // 如 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" 以便一眼发现漏配) +QColor tokenColor(const char* name); // 同上,QColor 形式 + +// 把 QSS 模板里的 {{token}} 占位替换为当前明暗的 hex 后返回。 +QString fillTokens(const QString& tmpl); + +// 应用一段 {{token}} 模板 QSS 到 widget,并随主题切换自动重填。 +void applyTokenizedStyleSheet(QWidget* w, const QString& tmpl); + } // namespace geopro::app