From 3772cfd806f1cff686dbd20d0b94104680739a42 Mon Sep 17 00:00:00 2001 From: hanbg Date: Thu, 29 Oct 2026 11:46:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor(theme):=20=E6=98=8E=20/=20=E6=9A=97?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E6=94=B6=E8=BF=9B=20AppPalette.isDark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「按 background 亮度 < 0.5 判暗」这条在仓里抄了 9 份:toDesktopTokens 里一份 取反、about_dialog 一份、查询页 / 对象面板 / 六个设计器各一份。about_dialog 的注释还写着「判据跟查询页 / 对象面板一致」——那是靠人记的约定,不是约束。 收成一个 getter,并补用例钉住「跟底色走、不跟 ThemeMode 走」:主题定制能把 「明亮」调成深底,走 Theme.of(brightness) 的实现会当场判错。 --- lib/theme/app_theme.dart | 12 ++++++++++- lib/theme/sql_highlight.dart | 7 +++---- lib/widgets/about_dialog.dart | 2 +- lib/widgets/matview_design_page.dart | 2 +- lib/widgets/object_panel.dart | 2 +- lib/widgets/query_page.dart | 2 +- lib/widgets/routine_design_page.dart | 2 +- lib/widgets/table_designer_page.dart | 2 +- lib/widgets/user_design_page.dart | 2 +- lib/widgets/view_design_page.dart | 2 +- test/theme_palette_test.dart | 31 ++++++++++++++++++++++++++++ 11 files changed, 53 insertions(+), 13 deletions(-) diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 6c29a82..32c8dff 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -99,6 +99,16 @@ class AppPalette { Color get accentSoftBg => Color.alphaBlend(accent.withValues(alpha: 0.20), background); + /// 明 / 暗判定:按**窗口底色**的亮度切,阈值 0.5。 + /// + /// 刻意不走 `Theme.of(context).brightness` —— 主题定制色板下两者会不一致 + /// (用户能把「明亮」调成深底),而派生色、品牌图标提亮档、SQL 高亮配色 + /// 都必须跟着实际看到的那块底色走。 + /// + /// 这条判据原先散在 9 处(`toDesktopTokens` 里一份取反、`about_dialog` + /// 一份、查询页 / 对象面板 / 六个设计器各一份),改阈值要满仓找。 + bool get isDark => background.computeLuminance() < 0.5; + /// 拷贝并覆盖指定字段,用于主题定制时逐项修改。 AppPalette copyWith({ Color? background, @@ -273,7 +283,7 @@ class AppPalette { /// /// 只做颜色映射,**不含显示缩放** —— 渲染组件请用 [desktopTokensFor]。 DesktopTokens toDesktopTokens() { - final light = background.computeLuminance() > 0.5; + final light = !isDark; // ── 按钮(对齐 Windows 桌面按钮)──────────────────────────────────── // 尺寸(24 高 / 最小 73 宽)由 base-ui 的 winForm 预设给(VCL/WinForms diff --git a/lib/theme/sql_highlight.dart b/lib/theme/sql_highlight.dart index 4cd1f4c..8c35c95 100644 --- a/lib/theme/sql_highlight.dart +++ b/lib/theme/sql_highlight.dart @@ -114,9 +114,8 @@ TextSpan? _lastSpan; /// 把一段 SQL / DDL 解析成带高亮的 [TextSpan],供 `Text.rich` / /// `SelectableText.rich` 直接渲染。 /// -/// 字体与明暗都从 [palette] 推导:明暗口径沿用全应用既有的「按 background 亮度」 -/// 判法,不走 `Theme.of` —— 定制色板下两者可能不一致。字号 / 行高各展示位不同, -/// 故仍留参数。 +/// 字体与明暗都从 [palette] 推导:明暗取 [AppPalette.isDark],不走 `Theme.of` +/// —— 定制色板下两者可能不一致。字号 / 行高各展示位不同,故仍留参数。 /// /// 返回的是**根 span**,兜底字体([sqlCodeStyle])挂在它身上,命中的 scope 子 span /// 只带自己的颜色与斜体、字号与字体沿用它——所以调用方直接 `Text.rich(span)` 即可, @@ -128,7 +127,7 @@ TextSpan highlightedSqlSpan( double fontSize = 12, double? height = 1.45, }) { - final isDark = palette.background.computeLuminance() < 0.5; + final isDark = palette.isDark; final base = sqlCodeStyle( color: palette.foreground, fontSize: fontSize, diff --git a/lib/widgets/about_dialog.dart b/lib/widgets/about_dialog.dart index 79b2073..4a4a925 100644 --- a/lib/widgets/about_dialog.dart +++ b/lib/widgets/about_dialog.dart @@ -124,7 +124,7 @@ class DaroAboutDialog extends StatelessWidget { Widget _buildLinks(BuildContext context, DesktopTokens dt, AppPalette t) { // 品牌原色是按亮底挑的,暗色主题下对比度不够(GitHub 的黑几乎看不见), // 取每个平台备好的提亮档 —— 判据跟查询页 / 对象面板一致,跟主题色板走。 - final dark = t.background.computeLuminance() < 0.5; + final dark = t.isDark; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/widgets/matview_design_page.dart b/lib/widgets/matview_design_page.dart index 37b0e6c..1d398ea 100644 --- a/lib/widgets/matview_design_page.dart +++ b/lib/widgets/matview_design_page.dart @@ -1133,7 +1133,7 @@ class _MatViewDesignPageState extends State { required CodeFindController findController, bool showLineNumbers = true, }) { - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return CodeEditor( controller: controller, readOnly: readOnly, diff --git a/lib/widgets/object_panel.dart b/lib/widgets/object_panel.dart index 75061a8..90449a9 100644 --- a/lib/widgets/object_panel.dart +++ b/lib/widgets/object_panel.dart @@ -651,7 +651,7 @@ class _ObjectPanelState extends State { /// 工具条走灰(见 [_buildToolbar] 里的 searchTokens),边框线仍把输入框勾勒 /// 出来。hover / pressed 在该灰底上派生(暗色提亮、亮色加深),交互反馈不变。 DesktopTokens _toolbarTokens(AppPalette t) { - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; final hoverBlend = isDark ? Colors.white.withValues(alpha: 0.08) : Colors.black.withValues(alpha: 0.08); final pressedBlend = diff --git a/lib/widgets/query_page.dart b/lib/widgets/query_page.dart index aa26e26..715d04d 100644 --- a/lib/widgets/query_page.dart +++ b/lib/widgets/query_page.dart @@ -1279,7 +1279,7 @@ class _QueryPageState extends State { // SQL 编辑区:re_editor 自绘编辑器(语法高亮 + 行号 + schema 感知补全) Widget _editor(AppPalette t) { - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return CodeAutocomplete( viewBuilder: (context, notifier, onSelected) => _SqlPromptPanel( notifier: notifier, diff --git a/lib/widgets/routine_design_page.dart b/lib/widgets/routine_design_page.dart index fc9509d..4a09548 100644 --- a/lib/widgets/routine_design_page.dart +++ b/lib/widgets/routine_design_page.dart @@ -799,7 +799,7 @@ class _RoutineDesignPageState extends State { CodeFindBuilder? findBuilder, bool readOnly = false, }) { - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return CodeEditor( controller: controller, focusNode: readOnly ? null : _focusNode, diff --git a/lib/widgets/table_designer_page.dart b/lib/widgets/table_designer_page.dart index aa9f00f..d9e09cc 100644 --- a/lib/widgets/table_designer_page.dart +++ b/lib/widgets/table_designer_page.dart @@ -2474,7 +2474,7 @@ class _TableDesignerPageState extends State { Widget _sqlTab(AppPalette t) { final sql = _previewSql(); if (_sqlCtrl.text != sql) _sqlCtrl.text = sql; - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/lib/widgets/user_design_page.dart b/lib/widgets/user_design_page.dart index 6ba967b..e81c02b 100644 --- a/lib/widgets/user_design_page.dart +++ b/lib/widgets/user_design_page.dart @@ -1029,7 +1029,7 @@ class _UserDesignPageState extends State { _pullFromControllers(); final text = UserSql.buildScriptText(_typeId, _spec); if (_previewCtrl.text != text) _previewCtrl.text = text; - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return CodeEditor( controller: _previewCtrl, readOnly: true, diff --git a/lib/widgets/view_design_page.dart b/lib/widgets/view_design_page.dart index 097a63f..52679d5 100644 --- a/lib/widgets/view_design_page.dart +++ b/lib/widgets/view_design_page.dart @@ -1245,7 +1245,7 @@ class _ViewDesignPageState extends State { bool showLineNumbers = true, CodeFindController? findController, }) { - final isDark = t.background.computeLuminance() < 0.5; + final isDark = t.isDark; return CodeEditor( controller: controller, focusNode: readOnly ? null : _focusNode, diff --git a/test/theme_palette_test.dart b/test/theme_palette_test.dart index 0644e78..312e0c5 100644 --- a/test/theme_palette_test.dart +++ b/test/theme_palette_test.dart @@ -159,4 +159,35 @@ void main() { expect(AppPalette.fromJson(AppTheme.dark.toJson()), AppTheme.dark); }); }); + + group('明 / 暗判定(AppPalette.isDark)', () { + test('内置两套色板各归一边', () { + expect(AppTheme.light.isDark, isFalse); + expect(AppTheme.dark.isDark, isTrue); + }); + + test('判据跟底色走,不跟 ThemeMode 走', () { + // 主题定制允许把「明亮」调成深底(反之亦然)。派生色、品牌图标提亮档、 + // SQL 高亮配色都得跟着实际看到的那块底色走 —— 走 Theme.of(brightness) + // 的实现会在这一条上红。 + final deep = + AppTheme.light.copyWith(background: const Color(0xFF1E1E1E)); + expect(deep.isDark, isTrue); + final pale = + AppTheme.dark.copyWith(background: const Color(0xFFFAFAFA)); + expect(pale.isDark, isFalse); + }); + + test('toDesktopTokens 的按钮面色与 isDark 同向', () { + // 桥接里那句 light = !isDark 必须与判定同源:分两处算,就会出现 + // 「色板判成暗、按钮却按亮底配面色」的错位。 + expect(AppTheme.light.toDesktopTokens().buttonFaceColor, + AppTheme.light.background); + expect(AppTheme.dark.toDesktopTokens().buttonFaceColor, + isNot(AppTheme.dark.background)); + final deep = + AppTheme.light.copyWith(background: const Color(0xFF1E1E1E)); + expect(deep.toDesktopTokens().buttonFaceColor, isNot(deep.background)); + }); + }); }