Skip to main content

ratatui_kit/components/
border.rs

1// Border 组件:为内容添加可定制的边框、标题、内边距等。
2//
3// 常用于包裹内容、分组、突出显示等场景。
4//
5// ## 用法示例
6// ```rust
7// element!(Border(
8//     border_style: Style::default().blue(),   // Some(_) 覆盖主题;省略则用主题
9//     top_title: Some(Line::from("标题")),
10//     padding: Padding::new(1, 1, 0, 0),
11// ){
12//     ChildComponent()
13// })
14// ```
15// 样式默认来自主题(`BorderTheme`,从 `Palette` 派生);`border_style` / `style` 为 `Option<Style>`,
16// `None` 用主题、`Some(s)` 以 `theme.patch(s)` 覆盖。
17
18use ratatui::{
19    style::Style,
20    symbols::border,
21    text::Line,
22    widgets::{Block, Padding, Widget},
23};
24use ratatui_kit_macros::{Props, with_layout_style};
25
26use crate::{AnyElement, Component, ComponentTheme, Palette, components::theme::resolve_style};
27
28/// Border 组件的主题 slot。
29#[non_exhaustive]
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct BorderTheme {
32    /// 边框颜色样式。
33    pub border_style: Style,
34    /// 整体区域样式。
35    pub style: Style,
36}
37
38impl ComponentTheme for BorderTheme {
39    fn from_palette(palette: &Palette) -> Self {
40        Self {
41            border_style: Style::new().fg(palette.border),
42            style: Style::new(),
43        }
44    }
45}
46
47impl Default for BorderTheme {
48    fn default() -> Self {
49        Self::from_palette(&Palette::default())
50    }
51}
52
53#[with_layout_style]
54#[derive(Props)]
55// Border 组件属性。
56pub struct BorderProps<'a> {
57    // 内边距。
58    pub padding: Padding,
59    // 边框样式覆盖。`None` 用主题,`Some(s)` 以 `theme.patch(s)` 覆盖。
60    pub border_style: Option<Style>,
61    // 显示哪些边。
62    pub borders: ratatui::widgets::Borders,
63    // 边框字符集。
64    pub border_set: border::Set<'static>,
65    // 整体样式覆盖。`None` 用主题,`Some(s)` 以 `theme.patch(s)` 覆盖。
66    pub style: Option<Style>,
67    // 子元素列表。
68    pub children: Vec<AnyElement<'a>>,
69    // 顶部标题。可直接传 `Line`(经宏 `.into()` + std `From<T> for Option<T>` 自动 `Some`)或 `Option<Line>`。
70    pub top_title: Option<Line<'static>>,
71    // 底部标题。可直接传 `Line`(自动 `Some`)或 `Option<Line>`。
72    pub bottom_title: Option<Line<'static>>,
73}
74
75impl Default for BorderProps<'_> {
76    fn default() -> Self {
77        Self {
78            padding: Padding::default(),
79            border_style: None,
80            borders: ratatui::widgets::Borders::ALL,
81            children: Vec::new(),
82            border_set: border::Set::default(),
83            style: None,
84            top_title: None,
85            bottom_title: None,
86            margin: Default::default(),
87            offset: Default::default(),
88            width: Default::default(),
89            height: Default::default(),
90            gap: Default::default(),
91            flex_direction: Default::default(),
92            justify_content: Default::default(),
93        }
94    }
95}
96
97// Border 组件实现。持有**已解析**的样式(主题 patch 过 props),draw 直接用。
98pub struct Border {
99    pub padding: Padding,
100    pub border_style: Style,
101    pub borders: ratatui::widgets::Borders,
102    pub border_set: border::Set<'static>,
103    pub style: Style,
104    pub top_title: Option<Line<'static>>,
105    pub bottom_title: Option<Line<'static>>,
106}
107
108impl Border {
109    // 从 props 派生非样式字段(样式在 update 中经主题解析后写入)。
110    fn from_props(props: &BorderProps<'_>) -> Self {
111        Self {
112            padding: props.padding,
113            border_style: Style::default(),
114            borders: props.borders,
115            border_set: props.border_set,
116            style: Style::default(),
117            top_title: props.top_title.clone(),
118            bottom_title: props.bottom_title.clone(),
119        }
120    }
121}
122
123impl Component for Border {
124    type Props<'a> = BorderProps<'a>;
125
126    // 根据属性创建 Border 组件实例(样式待 update 解析)
127    fn new(props: &Self::Props<'_>) -> Self {
128        Self::from_props(props)
129    }
130
131    // 根据最新属性和子组件更新自身状态
132    fn update(
133        &mut self,
134        props: &mut Self::Props<'_>,
135        _hooks: crate::Hooks,
136        updater: &mut crate::ComponentUpdater,
137    ) {
138        *self = Self::from_props(props);
139        // 主题解析:theme slot 铺底,props 的 Option<Style> 在上 patch(None → 用主题)。
140        // use_component_theme 返回 owned 值、读后即弃守卫,不与后续 &mut updater 冲突。
141        let theme = updater.use_component_theme::<BorderTheme>();
142        self.border_style = resolve_style(theme.border_style, props.border_style);
143        self.style = resolve_style(theme.style, props.style);
144        // 布局与子节点收尾保持显式(不并入 from_props,后者只构造自身状态)。
145        updater.set_layout_style(props.layout_style());
146        updater.update_children(&mut props.children, None);
147    }
148
149    // 渲染 Border 组件
150    fn draw(&mut self, drawer: &mut crate::ComponentDrawer<'_, '_>) {
151        // 构建 Block,设置样式、边框、内边距等
152        let mut block = Block::new()
153            .style(self.style)
154            .borders(self.borders)
155            .border_set(self.border_set)
156            .border_style(self.border_style)
157            .padding(self.padding);
158
159        // 设置顶部标题(如有)
160        if let Some(top_title) = &self.top_title {
161            block = block.title_top(top_title.clone());
162        }
163
164        // 设置底部标题(如有)
165        if let Some(bottom_title) = &self.bottom_title {
166            block = block.title_bottom(bottom_title.clone());
167        }
168
169        // 计算内容区域
170        let inner_area = block.inner(drawer.area);
171        // 渲染边框
172        block.render(drawer.area, drawer.buffer_mut());
173        // 更新绘制区域为内容区,供子组件使用
174        drawer.area = inner_area;
175    }
176}