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(),
9//     top_title: Some(Line::from("标题")),
10//     padding: Padding::new(1, 1, 0, 0),
11// ){
12//     ChildComponent()
13// })
14// ```
15// 支持自定义边框样式、边框字符集、上下标题、内边距等属性。
16
17use ratatui::{
18    symbols::border,
19    text::Line,
20    widgets::{Block, Padding, Widget},
21};
22use ratatui_kit_macros::{Props, with_layout_style};
23
24use crate::{AnyElement, Component};
25
26#[with_layout_style]
27#[derive(Props)]
28// Border 组件属性。
29pub struct BorderProps<'a> {
30    // 内边距。
31    pub padding: Padding,
32    // 边框样式。
33    pub border_style: ratatui::style::Style,
34    // 显示哪些边。
35    pub borders: ratatui::widgets::Borders,
36    // 边框字符集。
37    pub border_set: border::Set<'static>,
38    // 整体样式。
39    pub style: ratatui::style::Style,
40    // 子元素列表。
41    pub children: Vec<AnyElement<'a>>,
42    // 顶部标题。可直接传 `Line`(经宏 `.into()` + std `From<T> for Option<T>` 自动 `Some`)或 `Option<Line>`。
43    pub top_title: Option<Line<'static>>,
44    // 底部标题。可直接传 `Line`(自动 `Some`)或 `Option<Line>`。
45    pub bottom_title: Option<Line<'static>>,
46}
47
48impl Default for BorderProps<'_> {
49    fn default() -> Self {
50        Self {
51            padding: Padding::default(),
52            border_style: ratatui::style::Style::default(),
53            borders: ratatui::widgets::Borders::ALL,
54            children: Vec::new(),
55            border_set: border::Set::default(),
56            style: ratatui::style::Style::default(),
57            top_title: None,
58            bottom_title: None,
59            margin: Default::default(),
60            offset: Default::default(),
61            width: Default::default(),
62            height: Default::default(),
63            gap: Default::default(),
64            flex_direction: Default::default(),
65            justify_content: Default::default(),
66        }
67    }
68}
69
70// Border 组件实现。
71pub struct Border {
72    pub padding: Padding,
73    pub border_style: ratatui::style::Style,
74    pub borders: ratatui::widgets::Borders,
75    pub border_set: border::Set<'static>,
76    pub style: ratatui::style::Style,
77    pub top_title: Option<Line<'static>>,
78    pub bottom_title: Option<Line<'static>>,
79}
80
81impl Border {
82    // 从 props 派生自身状态的单一构造源(字段镜像列表只写一处,避免 new/update 漂移)。
83    fn from_props(props: &BorderProps<'_>) -> Self {
84        Self {
85            padding: props.padding,
86            border_style: props.border_style,
87            borders: props.borders,
88            border_set: props.border_set,
89            style: props.style,
90            top_title: props.top_title.clone(),
91            bottom_title: props.bottom_title.clone(),
92        }
93    }
94}
95
96impl Component for Border {
97    type Props<'a> = BorderProps<'a>;
98
99    // 根据属性创建 Border 组件实例
100    fn new(props: &Self::Props<'_>) -> Self {
101        Self::from_props(props)
102    }
103
104    // 根据最新属性和子组件更新自身状态
105    fn update(
106        &mut self,
107        props: &mut Self::Props<'_>,
108        _hooks: crate::Hooks,
109        updater: &mut crate::ComponentUpdater,
110    ) {
111        *self = Self::from_props(props);
112        // 布局与子节点收尾保持显式(不并入 from_props,后者只构造自身状态)。
113        updater.set_layout_style(props.layout_style());
114        updater.update_children(&mut props.children, None);
115    }
116
117    // 渲染 Border 组件
118    fn draw(&mut self, drawer: &mut crate::ComponentDrawer<'_, '_>) {
119        // 构建 Block,设置样式、边框、内边距等
120        let mut block = Block::new()
121            .style(self.style)
122            .borders(self.borders)
123            .border_set(self.border_set)
124            .border_style(self.border_style)
125            .padding(self.padding);
126
127        // 设置顶部标题(如有)
128        if let Some(top_title) = &self.top_title {
129            block = block.title_top(top_title.clone());
130        }
131
132        // 设置底部标题(如有)
133        if let Some(bottom_title) = &self.bottom_title {
134            block = block.title_bottom(bottom_title.clone());
135        }
136
137        // 计算内容区域
138        let inner_area = block.inner(drawer.area);
139        // 渲染边框
140        block.render(drawer.area, drawer.buffer_mut());
141        // 更新绘制区域为内容区,供子组件使用
142        drawer.area = inner_area;
143    }
144}