windui 0.4.0

轻量跨平台桌面 GUI:tiny-skia 渲染 + 平台原生窗口/文字(Windows: Win32+DirectWrite;macOS: Cocoa+CoreText)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
//! 主题系统:集中定义颜色 / 间距 / 字体,避免控件硬编码。
//!
//! 两层模型:`Palette`+`Metrics` 是全局 base;每个控件主题用 `Option` 字段做覆盖层
//! (`None` 回退到 base,`Some` 即覆盖)。整体可与 TOML 互转(serde),为外部主题文件打底。
//!
//! 控件经 `theme::current()` 读取当前主题(thread_local,未设置时为默认主题——
//! 故单元测试无需显式设置)。宿主在每帧布局/绘制前 `set_current`。

use std::cell::RefCell;
use std::rc::Rc;

use serde::{Deserialize, Serialize};

use crate::geometry::Color;

/// 全局基础调色板。
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Palette {
    pub accent: Color,
    pub accent_hover: Color,
    pub accent_active: Color,
    /// 强调色之上的前景(按钮文字等)。
    pub on_accent: Color,
    /// 窗口背景。
    pub bg: Color,
    /// 卡片 / 输入框等表面。
    pub surface: Color,
    /// 次级表面(斑马纹等)。
    pub surface_alt: Color,
    pub text: Color,
    pub text_muted: Color,
    pub text_disabled: Color,
    pub border: Color,
    /// 关闭态轨道(开关 / 滑块)。
    pub track: Color,
    pub placeholder: Color,
    pub divider: Color,
    pub danger: Color,
}

impl Default for Palette {
    fn default() -> Self {
        Self {
            accent: Color::hex(0x4C8BF5),
            accent_hover: Color::hex(0x6BA3FF),
            accent_active: Color::hex(0x3A6FD0),
            on_accent: Color::WHITE,
            bg: Color::hex(0xF3F3F3),
            surface: Color::WHITE,
            surface_alt: Color::hex(0xF6F8FA),
            text: Color::hex(0x2D3436),
            text_muted: Color::hex(0x636E72),
            text_disabled: Color::hex(0xB0B6BD),
            border: Color::hex(0xCFD4DC),
            track: Color::hex(0xCFD4DC),
            placeholder: Color::hex(0xAAB0B8),
            divider: Color::hex(0xE2E6EA),
            danger: Color::hex(0xE5484D),
        }
    }
}

impl Palette {
    /// 暗色调色板预设(与亮色 Default 语义角色一一对应,仅取暗色值)。
    pub fn dark() -> Self {
        Self {
            accent: Color::hex(0x4C8BF5),
            accent_hover: Color::hex(0x6BA3FF),
            accent_active: Color::hex(0x3A6FD0),
            on_accent: Color::WHITE,
            bg: Color::hex(0x111827),
            surface: Color::hex(0x1B2333),
            surface_alt: Color::hex(0x232C3E),
            text: Color::hex(0xE6E8EC),
            text_muted: Color::hex(0x9AA3B2),
            text_disabled: Color::hex(0x5A6172),
            border: Color::hex(0x2C3650),
            track: Color::hex(0x2B3344),
            placeholder: Color::hex(0x6B7280),
            divider: Color::hex(0x242C3C),
            danger: Color::hex(0xE5484D),
        }
    }
}

/// 控件语义意图色。内置三核心 + 开放扩展点 `Custom`——使用者传任意基色即可,
/// 框架派生整组视觉(hover/active 变亮变暗、fg 按亮度自适应),零新增 palette 色槽。
#[derive(Clone, Copy)]
pub enum Intent {
    /// 主操作:accent 家族(控件默认)。
    Primary,
    /// 次要操作:中性灰。
    Neutral,
    /// 危险操作:palette.danger。
    Danger,
    /// 扩展点:任意基色,派生整组视觉。
    Custom(Color),
}

/// `Intent` 解析出的一组语义色。控件各取所需(Button 用全部,CheckBox 取 bg+fg)。
#[derive(Clone, Copy, Debug)]
pub struct IntentColors {
    pub bg: Color,
    pub hover: Color,
    pub active: Color,
    /// 对比自适应前景:在 bg 上始终可读(Button 文字 / CheckBox 对勾共用)。
    pub fg: Color,
}

impl Intent {
    /// 解析为一组语义色。`Primary` 用 palette 精调的 accent 家族;其余 intent 由基色派生。
    pub fn colors(self, p: &Palette) -> IntentColors {
        const L: f32 = 0.10; // hover 变亮系数
        const D: f32 = 0.10; // active 变暗系数
        match self {
            Intent::Primary => IntentColors {
                bg: p.accent,
                hover: p.accent_hover,
                active: p.accent_active,
                fg: p.on_accent,
            },
            Intent::Neutral => IntentColors {
                bg: p.border,
                hover: p.border.darken(D),
                active: p.border.darken(D * 2.0),
                fg: p.text,
            },
            Intent::Danger => IntentColors {
                bg: p.danger,
                hover: p.danger.lighten(L),
                active: p.danger.darken(D),
                fg: p.danger.pick_fg(p.text, p.on_accent),
            },
            Intent::Custom(c) => IntentColors {
                bg: c,
                hover: c.lighten(L),
                active: c.darken(D),
                fg: c.pick_fg(p.text, p.on_accent),
            },
        }
    }
}

/// 全局基础度量(间距 / 圆角 / 字号)。
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Metrics {
    pub corner_sm: f32,
    pub corner_md: f32,
    pub corner_lg: f32,
    pub border_width: f32,
    /// 基础间距单位。
    pub spacing: i32,
    /// 文本控件内边距。
    pub text_pad: i32,
    pub font_sm: f32,
    pub font_md: f32,
    pub font_lg: f32,
}

impl Default for Metrics {
    fn default() -> Self {
        Self {
            corner_sm: 4.0,
            corner_md: 6.0,
            corner_lg: 10.0,
            border_width: 1.5,
            spacing: 8,
            text_pad: 10,
            font_sm: 13.0,
            font_md: 14.0,
            font_lg: 16.0,
        }
    }
}

/// 按钮覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ButtonTheme {
    pub bg: Option<Color>,
    pub hover: Option<Color>,
    pub active: Option<Color>,
    /// 禁用态背景(默认回退 palette.track 灰)。
    pub disabled: Option<Color>,
    pub fg: Option<Color>,
    pub corner: Option<f32>,
}

impl ButtonTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.accent)
    }
    pub fn hover(&self, p: &Palette) -> Color {
        self.hover.unwrap_or(p.accent_hover)
    }
    pub fn active(&self, p: &Palette) -> Color {
        self.active.unwrap_or(p.accent_active)
    }
    pub fn disabled(&self, p: &Palette) -> Color {
        self.disabled.unwrap_or(p.track)
    }
    pub fn fg(&self, p: &Palette) -> Color {
        self.fg.unwrap_or(p.on_accent)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_md)
    }
}

/// 文本输入覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct InputTheme {
    pub bg: Option<Color>,
    pub border: Option<Color>,
    pub border_focus: Option<Color>,
    pub text: Option<Color>,
    pub placeholder: Option<Color>,
    /// 选区高亮(含 alpha)。
    pub selection: Option<Color>,
    pub cursor: Option<Color>,
    pub corner: Option<f32>,
}

impl InputTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.surface)
    }
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn border_focus(&self, p: &Palette) -> Color {
        self.border_focus.unwrap_or(p.accent)
    }
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn placeholder(&self, p: &Palette) -> Color {
        self.placeholder.unwrap_or(p.placeholder)
    }
    pub fn selection(&self, p: &Palette) -> Color {
        self.selection
            .unwrap_or(Color::rgba(p.accent.r, p.accent.g, p.accent.b, 0x55))
    }
    pub fn cursor(&self, p: &Palette) -> Color {
        self.cursor.unwrap_or(p.text)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_md)
    }
}

/// 勾选/开关/单选/滑块共享覆盖层(强调色 + 关闭态轨道)。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ToggleTheme {
    pub accent: Option<Color>,
    pub track: Option<Color>,
    /// 旋钮/勾的前景。
    pub knob: Option<Color>,
}

impl ToggleTheme {
    pub fn accent(&self, p: &Palette) -> Color {
        self.accent.unwrap_or(p.accent)
    }
    pub fn track(&self, p: &Palette) -> Color {
        self.track.unwrap_or(p.track)
    }
    pub fn knob(&self, p: &Palette) -> Color {
        self.knob.unwrap_or(p.surface)
    }
}

/// 下拉覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct DropdownTheme {
    pub bg: Option<Color>,
    pub border: Option<Color>,
    pub border_focus: Option<Color>,
    pub text: Option<Color>,
    pub chevron: Option<Color>,
    pub corner: Option<f32>,
}

impl DropdownTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.surface)
    }
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn border_focus(&self, p: &Palette) -> Color {
        self.border_focus.unwrap_or(p.accent)
    }
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn chevron(&self, p: &Palette) -> Color {
        self.chevron.unwrap_or(p.text_muted)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_md)
    }
}

/// 浮层菜单覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct MenuTheme {
    pub bg: Option<Color>,
    pub border: Option<Color>,
    pub text: Option<Color>,
    pub text_disabled: Option<Color>,
    pub hover: Option<Color>,
    pub accent: Option<Color>,
}

impl MenuTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.surface)
    }
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn text_disabled(&self, p: &Palette) -> Color {
        self.text_disabled.unwrap_or(p.text_disabled)
    }
    pub fn hover(&self, p: &Palette) -> Color {
        self.hover
            .unwrap_or(Color::rgba(p.accent.r, p.accent.g, p.accent.b, 0x22))
    }
    pub fn accent(&self, p: &Palette) -> Color {
        self.accent.unwrap_or(p.accent)
    }
}

/// 标签页覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct TabTheme {
    pub accent: Option<Color>,
    pub inactive: Option<Color>,
    pub hover: Option<Color>,
}

impl TabTheme {
    pub fn accent(&self, p: &Palette) -> Color {
        self.accent.unwrap_or(p.accent)
    }
    pub fn inactive(&self, p: &Palette) -> Color {
        self.inactive.unwrap_or(p.text_muted)
    }
    pub fn hover(&self, p: &Palette) -> Color {
        self.hover.unwrap_or(p.text)
    }
}

/// 进度条覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ProgressTheme {
    pub track: Option<Color>,
    pub fill: Option<Color>,
}

impl ProgressTheme {
    pub fn track(&self, p: &Palette) -> Color {
        self.track.unwrap_or(p.track)
    }
    pub fn fill(&self, p: &Palette) -> Color {
        self.fill.unwrap_or(p.accent)
    }
}

/// 数字步进覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct StepperTheme {
    pub bg: Option<Color>,
    pub border: Option<Color>,
    pub text: Option<Color>,
    /// +/- 按钮区前景。
    pub button: Option<Color>,
    /// +/- 按钮悬停底色。
    pub button_hover: Option<Color>,
}

impl StepperTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.surface)
    }
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn button(&self, p: &Palette) -> Color {
        self.button.unwrap_or(p.accent)
    }
    pub fn button_hover(&self, p: &Palette) -> Color {
        self.button_hover
            .unwrap_or(Color::rgba(p.accent.r, p.accent.g, p.accent.b, 0x18))
    }
}

/// 列表覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ListTheme {
    pub text: Option<Color>,
    pub selected_bg: Option<Color>,
    pub selected_text: Option<Color>,
    pub hover_bg: Option<Color>,
}

impl ListTheme {
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn selected_bg(&self, p: &Palette) -> Color {
        self.selected_bg
            .unwrap_or(Color::rgba(p.accent.r, p.accent.g, p.accent.b, 0x22))
    }
    pub fn selected_text(&self, p: &Palette) -> Color {
        self.selected_text.unwrap_or(p.accent)
    }
    pub fn hover_bg(&self, p: &Palette) -> Color {
        self.hover_bg.unwrap_or(p.surface_alt)
    }
}

/// 链接覆盖层(链接色三态,回退到 accent 家族)。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct LinkTheme {
    pub color: Option<Color>,
    pub hover: Option<Color>,
    pub pressed: Option<Color>,
}

impl LinkTheme {
    pub fn color(&self, p: &Palette) -> Color {
        self.color.unwrap_or(p.accent)
    }
    pub fn hover(&self, p: &Palette) -> Color {
        self.hover.unwrap_or(p.accent_hover)
    }
    pub fn pressed(&self, p: &Palette) -> Color {
        self.pressed.unwrap_or(p.accent_active)
    }
}

/// 分段控制器覆盖层(连体多段单选)。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct SegmentTheme {
    /// 容器底色。
    pub bg: Option<Color>,
    pub border: Option<Color>,
    /// 聚焦时边框(回退 accent)。
    pub border_focus: Option<Color>,
    /// 选中段底色(含 alpha)。
    pub selected_bg: Option<Color>,
    /// 选中段文字色。
    pub selected_text: Option<Color>,
    /// 未选中段文字色。
    pub text: Option<Color>,
    /// 悬停段浅底(含 alpha)。
    pub hover_bg: Option<Color>,
    /// 段间分隔线。
    pub divider: Option<Color>,
    pub corner: Option<f32>,
}

impl SegmentTheme {
    pub fn bg(&self, p: &Palette) -> Color {
        self.bg.unwrap_or(p.surface)
    }
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn border_focus(&self, p: &Palette) -> Color {
        self.border_focus.unwrap_or(p.accent)
    }
    pub fn selected_bg(&self, p: &Palette) -> Color {
        // 选中段为实心强调色(参考设计:高亮色 + 文字反色),无半透明渐变感。
        self.selected_bg.unwrap_or(p.accent)
    }
    pub fn selected_text(&self, p: &Palette) -> Color {
        // 选中段文字反色(强调色底上的对比前景)。
        self.selected_text.unwrap_or(p.on_accent)
    }
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text_muted)
    }
    pub fn hover_bg(&self, p: &Palette) -> Color {
        self.hover_bg
            .unwrap_or(Color::rgba(p.accent.r, p.accent.g, p.accent.b, 0x12))
    }
    pub fn divider(&self, p: &Palette) -> Color {
        self.divider.unwrap_or(p.divider)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_md)
    }
}

/// 导航覆盖层(NavRow 钻入行 + CollapsibleHeader 折叠头共用)。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct NavTheme {
    pub text: Option<Color>,
    /// 悬停/按下底色。
    pub hover_bg: Option<Color>,
    /// 右侧箭头色。
    pub chevron: Option<Color>,
}

impl NavTheme {
    pub fn text(&self, p: &Palette) -> Color {
        self.text.unwrap_or(p.text)
    }
    pub fn hover_bg(&self, p: &Palette) -> Color {
        self.hover_bg.unwrap_or(p.surface_alt)
    }
    pub fn chevron(&self, p: &Palette) -> Color {
        self.chevron.unwrap_or(p.text_muted)
    }
}

/// 动画时长覆盖层(毫秒)。控件按语义档位取时长,回退 120/200/300ms。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct AnimTheme {
    /// 快档(hover/press 等轻量反馈)。
    pub fast: Option<u32>,
    /// 常规档(开关滑块、展开等)。
    pub normal: Option<u32>,
    /// 慢档(大块过渡)。
    pub slow: Option<u32>,
}

impl AnimTheme {
    pub fn fast(&self) -> u32 {
        self.fast.unwrap_or(120)
    }
    pub fn normal(&self) -> u32 {
        self.normal.unwrap_or(200)
    }
    pub fn slow(&self) -> u32 {
        self.slow.unwrap_or(300)
    }
}

/// 手风琴覆盖层(Accordion 卡片外框 + 面板头背景)。chevron/text/hover 复用 [`NavTheme`]。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct AccordionTheme {
    /// 卡片外框边框色。
    pub border: Option<Color>,
    /// 卡片圆角。
    pub corner: Option<f32>,
    /// 面板头背景(区别于内容区,营造卡片分层)。
    pub header_bg: Option<Color>,
    /// 面板间分隔线色。
    pub divider: Option<Color>,
}

impl AccordionTheme {
    pub fn border(&self, p: &Palette) -> Color {
        self.border.unwrap_or(p.border)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_md)
    }
    pub fn header_bg(&self, p: &Palette) -> Color {
        self.header_bg.unwrap_or(p.surface_alt)
    }
    pub fn divider(&self, p: &Palette) -> Color {
        self.divider.unwrap_or(p.divider)
    }
}

/// 悬停提示浮层覆盖层(深底浅字)。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct TooltipTheme {
    pub bg: Option<Color>,
    pub text: Option<Color>,
    pub corner: Option<f32>,
}

impl TooltipTheme {
    pub fn bg(&self, _p: &Palette) -> Color {
        self.bg.unwrap_or(Color::hex(0x303033))
    }
    pub fn text(&self, _p: &Palette) -> Color {
        self.text.unwrap_or(Color::WHITE)
    }
    pub fn corner(&self, m: &Metrics) -> f32 {
        self.corner.unwrap_or(m.corner_sm)
    }
}

/// 完整主题:base(palette/metrics)+ 各控件覆盖层。
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Theme {
    pub palette: Palette,
    pub metrics: Metrics,
    pub button: ButtonTheme,
    pub input: InputTheme,
    pub toggle: ToggleTheme,
    pub dropdown: DropdownTheme,
    pub menu: MenuTheme,
    pub tab: TabTheme,
    pub progress: ProgressTheme,
    pub stepper: StepperTheme,
    pub list: ListTheme,
    pub link: LinkTheme,
    pub segment: SegmentTheme,
    pub nav: NavTheme,
    pub accordion: AccordionTheme,
    pub anim: AnimTheme,
    pub tooltip: TooltipTheme,
}

impl Theme {
    /// 暗色主题(暗色 palette + 默认 metrics/控件覆盖层)。
    pub fn dark() -> Self {
        Self {
            palette: Palette::dark(),
            ..Theme::default()
        }
    }
    /// 从 TOML 字符串解析(缺省字段回退到默认,支持部分覆盖)。
    pub fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(s)
    }
    /// 序列化为 TOML 字符串。
    pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
        toml::to_string_pretty(self)
    }
}

thread_local! {
    static CURRENT: RefCell<Rc<Theme>> = RefCell::new(Rc::new(Theme::default()));
}

/// 当前线程的活动主题(未设置时为默认主题)。
pub fn current() -> Rc<Theme> {
    CURRENT.with(|c| c.borrow().clone())
}

/// 设置当前线程的活动主题(宿主在布局/绘制前调用)。
pub fn set_current(theme: Rc<Theme>) {
    CURRENT.with(|c| *c.borrow_mut() = theme);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn dark_theme_has_dark_bg_and_light_text() {
        let d = Theme::dark();
        let lum = |c: Color| c.r as u32 + c.g as u32 + c.b as u32;
        assert!(lum(d.palette.bg) < 300, "暗色背景亮度应低");
        assert!(lum(d.palette.text) > 500, "暗色文字应亮");
        // metrics 沿用默认。
        assert_eq!(d.metrics.corner_md, Metrics::default().corner_md);
    }

    #[test]
    fn toml_roundtrip_preserves_palette() {
        let mut t = Theme::default();
        t.palette.accent = Color::hex(0xFF8800);
        let s = t.to_toml().expect("序列化");
        let back = Theme::from_toml(&s).expect("反序列化");
        assert_eq!(back.palette.accent, Color::hex(0xFF8800));
        assert_eq!(back.metrics.corner_md, t.metrics.corner_md);
    }

    #[test]
    fn partial_toml_falls_back_to_defaults() {
        // 仅覆盖强调色,其余回退默认。
        let t = Theme::from_toml("[palette]\naccent = \"#112233\"\n").expect("部分 TOML");
        assert_eq!(t.palette.accent, Color::hex(0x112233));
        assert_eq!(
            t.palette.text,
            Palette::default().text,
            "未指定字段回退默认"
        );
        assert!(t.button.bg.is_none(), "控件覆盖默认 None");
    }

    #[test]
    fn override_layer_resolves_or_falls_back() {
        let p = Palette::default();
        let mut bt = ButtonTheme::default();
        assert_eq!(bt.bg(&p), p.accent, "无覆盖回退 palette.accent");
        bt.bg = Some(Color::hex(0x010203));
        assert_eq!(bt.bg(&p), Color::hex(0x010203), "有覆盖取覆盖值");
    }

    #[test]
    fn intent_colors_maps_palette_and_derives_fg() {
        let p = Palette::default();
        assert_eq!(Intent::Primary.colors(&p).bg, p.accent);
        assert_eq!(Intent::Primary.colors(&p).fg, p.on_accent);
        assert_eq!(Intent::Neutral.colors(&p).bg, p.border);
        assert_eq!(Intent::Danger.colors(&p).bg, p.danger);
        let custom = Color::hex(0x2E9E5B);
        assert_eq!(
            Intent::Custom(custom).colors(&p).bg,
            custom,
            "Custom 基色直用"
        );
        // Custom 浅基色 → fg 取深色(text) 保证对比。
        assert_eq!(Intent::Custom(Color::hex(0xFFF0A0)).colors(&p).fg, p.text);
    }
}