Skip to main content

ratatui_kit/components/theme/
mod.rs

1// theme 组件模块:主题系统协议(always-on,零新依赖)。
2//
3// - [`Palette`]:唯一颜色真源。
4// - [`ComponentTheme`]:每组件 `FooTheme` 实现它,从 `Palette` 派生本组件样式 slot。
5// - [`UseTheme`]:`use_palette` / `use_component_theme` 两个读取 Hook(被动读取,不注册 waker;
6//   运行时换肤靠把 `Palette` 放进 `Atom`/`use_state` 驱动 `PaletteProvider`)。
7// - [`PaletteProvider`] / [`ThemeOverride`]:注入全局 palette / 组件级 override 的组件。
8//
9// 解析链(每组件一致):显式 `FooTheme` override context → `FooTheme::from_palette(&palette)`
10// → `FooTheme::default()`。因 `use_palette` 缺省回退 `Palette::default()`,且约定
11// `FooTheme::default() == from_palette(&Palette::default())`,后两级在实现上收敛为一次 `from_palette`。
12
13mod palette;
14pub use palette::*;
15
16use std::marker::PhantomData;
17
18use ratatui::style::Style;
19use ratatui_kit_macros::Props;
20
21use crate::{AnyElement, Component, ComponentUpdater, Context, Hooks, UseContext};
22
23/// 组件主题 trait:每个内置组件的 `FooTheme` 实现它,从 [`Palette`] 派生本组件样式 slot。
24///
25/// 约定:`Self::default()` 必须等价于 `Self::from_palette(&Palette::default())`,
26/// 这样「无 Provider 兜底」与「palette 派生」在解析链上收敛为同一实现。
27pub trait ComponentTheme: Clone + Default + 'static {
28    /// 从共享调色板派生本组件主题。颜色一律取自 `palette`;高亮符号、`DIM`/`BOLD`
29    /// 等非颜色决定由各组件在此承接。
30    fn from_palette(palette: &Palette) -> Self;
31}
32
33/// 合成组件主题 slot 与 per-call 样式覆盖。
34///
35/// `None` 表示完全使用主题;`Some(s)` 表示 `theme.patch(s)`,其中 `Style::reset()`
36/// 可清空主题落下的字段。
37pub(crate) fn resolve_style(theme: Style, override_style: Option<Style>) -> Style {
38    theme.patch(override_style.unwrap_or_default())
39}
40
41mod private {
42    pub trait Sealed {}
43    impl Sealed for crate::Hooks<'_, '_> {}
44}
45
46/// 读取主题的 Hook 扩展(函数组件开箱即用;手写 `Component` 需先
47/// `hooks.with_context_stack(updater.component_context_stack())`,或改用
48/// [`ComponentUpdater::use_palette`] / [`ComponentUpdater::use_component_theme`])。
49pub trait UseTheme: private::Sealed {
50    /// 读取当前 [`Palette`](owned 值);无 [`PaletteProvider`] 时回退 [`Palette::default`]。
51    fn use_palette(&self) -> Palette;
52    /// 按解析链读取某组件主题:显式 override context → `from_palette` → `default`。
53    fn use_component_theme<T: ComponentTheme>(&self) -> T;
54}
55
56impl UseTheme for Hooks<'_, '_> {
57    fn use_palette(&self) -> Palette {
58        // try_use_context 非 panic;拿到守卫立即拷贝出 owned 值,不残留借用。
59        self.try_use_context::<Palette>()
60            .map(|p| *p)
61            .unwrap_or_default()
62    }
63
64    fn use_component_theme<T: ComponentTheme>(&self) -> T {
65        match self.try_use_context::<T>() {
66            Some(t) => t.clone(),
67            None => T::from_palette(&self.use_palette()),
68        }
69    }
70}
71
72impl ComponentUpdater<'_, '_> {
73    /// 手写 `Component` 在 `update` 中读取当前 [`Palette`](owned 值,读后即弃守卫)。
74    pub fn use_palette(&self) -> Palette {
75        self.get_context::<Palette>()
76            .map(|p| *p)
77            .unwrap_or_default()
78    }
79
80    /// 手写 `Component` 在 `update` 中按解析链读取某组件主题(owned 值,读后即弃守卫)。
81    ///
82    /// 借用纪律:返回 owned 值后守卫即 drop,可安全继续 `update_children`。
83    pub fn use_component_theme<T: ComponentTheme>(&self) -> T {
84        match self.get_context::<T>() {
85            Some(t) => t.clone(),
86            None => T::from_palette(&self.use_palette()),
87        }
88    }
89}
90
91/// 全局主题注入:向子树提供一个 [`Palette`],子树内组件据此派生各自主题。
92///
93/// 透明布局节点,不占独立布局盒。运行时换肤:把 `palette` 由 `Atom<Palette>` / `use_state`
94/// 驱动,写入即唤醒整树重渲。
95#[derive(Default, Props)]
96pub struct PaletteProviderProps<'a> {
97    /// 子元素列表。
98    pub children: Vec<AnyElement<'a>>,
99    /// 注入的调色板。
100    pub palette: Palette,
101}
102
103/// 见 [`PaletteProviderProps`]。
104pub struct PaletteProvider;
105
106impl Component for PaletteProvider {
107    type Props<'a> = PaletteProviderProps<'a>;
108
109    fn new(_props: &Self::Props<'_>) -> Self {
110        Self
111    }
112
113    fn update(
114        &mut self,
115        props: &mut Self::Props<'_>,
116        _hooks: Hooks,
117        updater: &mut ComponentUpdater,
118    ) {
119        updater.set_transparent_layout(true);
120        let mut ctx = Context::owned(props.palette);
121        updater.update_children(props.children.iter_mut(), Some(ctx.borrow()));
122    }
123}
124
125/// 组件级主题覆盖:向子树注入某个 `FooTheme` override,只影响该类型组件(解析链第一级)。
126///
127/// 手写泛型组件不会从 `theme:` prop 推断类型,调用时需显式写出主题类型:
128/// `element!(ThemeOverride::<BorderTheme>(theme: my_border_theme) { ... })`。透明布局节点。
129#[derive(Props)]
130pub struct ThemeOverrideProps<'a, T>
131where
132    T: ComponentTheme,
133{
134    /// 子元素列表。
135    pub children: Vec<AnyElement<'a>>,
136    /// 注入的组件主题 override。
137    pub theme: T,
138}
139
140impl<T> Default for ThemeOverrideProps<'_, T>
141where
142    T: ComponentTheme,
143{
144    fn default() -> Self {
145        Self {
146            children: Vec::new(),
147            theme: T::default(),
148        }
149    }
150}
151
152/// 见 [`ThemeOverrideProps`]。
153// 用 `fn() -> T` 而非裸 `T`:不让标记字段把 `T` 的 Unpin/auto-trait/drop 约束传染给组件。
154pub struct ThemeOverride<T>(PhantomData<fn() -> T>);
155
156impl<T> Component for ThemeOverride<T>
157where
158    T: ComponentTheme,
159{
160    type Props<'a> = ThemeOverrideProps<'a, T>;
161
162    fn new(_props: &Self::Props<'_>) -> Self {
163        Self(PhantomData)
164    }
165
166    fn update(
167        &mut self,
168        props: &mut Self::Props<'_>,
169        _hooks: Hooks,
170        updater: &mut ComponentUpdater,
171    ) {
172        updater.set_transparent_layout(true);
173        let mut ctx = Context::owned(props.theme.clone());
174        updater.update_children(props.children.iter_mut(), Some(ctx.borrow()));
175    }
176}