Skip to main content

ratatui_kit/component/
mod.rs

1use crate::{
2    element::ElementType,
3    hooks::Hooks,
4    layout_style::LayoutStyle,
5    props::{AnyProps, Props},
6    render::{ComponentDrawer, ComponentUpdater},
7};
8use std::{any::Any, task::Context};
9
10mod component_helper;
11pub(crate) use component_helper::{ComponentHelper, ComponentHelperExt};
12
13mod instantiated_component;
14pub use instantiated_component::{Components, InstantiatedComponent};
15use ratatui::layout::{Direction, Layout};
16
17// 组件系统核心 trait,所有自定义 UI 组件都需实现。
18//
19// - 通过关联类型 `Props` 定义属性类型,支持生命周期。
20// - `new` 创建组件实例。
21// - `update` 响应 props/hook 变化,适合副作用、事件注册等。
22// - `draw` 渲染组件内容。
23// - `calc_children_areas` 默认 flex 布局计算子组件区域,可重写自定义布局;返回区域数必须等于子节点数。
24// - `poll_change` 支持异步/响应式副作用。
25// - `render_ref` 低级渲染接口,通常无需重写。
26//
27// # 手动实现 Component 示例
28//
29// ```rust
30// use ratatui_kit::prelude::*;
31// use ratatui::{style::Style, text::Line};
32//
33// pub struct MyCounter;
34//
35// impl Component for MyCounter {
36//     type Props<'a> = NoProps;
37//     fn new(_props: &Self::Props<'_>) -> Self {
38//         Self
39//     }
40//     fn update(
41//         &mut self,
42//         _props: &mut Self::Props<'_>,
43//         hooks: Hooks,
44//         updater: &mut ComponentUpdater,
45//     ) {
46//         // 手写 Component 默认 hooks 无 context;先升级为 context-aware 才能用 use_event_handler。
47//         let mut hooks = hooks.with_context_stack(updater.component_context_stack());
48//         let mut state = hooks.use_state(|| 0);
49//         hooks.use_event_handler(EventScope::Current, EventPriority::Normal, move |event| {
50//             // 事件处理逻辑
51//             EventResult::Ignored
52//         });
53//         // ...
54//     }
55//     fn draw(&mut self, drawer: &mut ComponentDrawer<'_, '_>) {
56//         let area = drawer.area;
57//         let buf = drawer.buffer_mut();
58//         Line::styled(format!("Counter: {}", 42), Style::default()).render(area, buf);
59//     }
60// }
61// ```
62//
63// > 一般用户无需手动实现,推荐使用 `#[component]` 宏自动生成。
64pub trait Component: Any + Unpin {
65    type Props<'a>: Props
66    where
67        Self: 'a;
68
69    fn new(props: &Self::Props<'_>) -> Self;
70
71    fn update(
72        &mut self,
73        _props: &mut Self::Props<'_>,
74        _hooks: Hooks,
75        _updater: &mut ComponentUpdater,
76    ) {
77    }
78
79    fn draw(&mut self, _drawer: &mut ComponentDrawer<'_, '_>) {}
80
81    // 默认使用flex布局计算子组件的area。实现者重写时必须返回与 children 数量相同的区域。
82    fn calc_children_areas(
83        &self,
84        children: &Components,
85        layout_style: &LayoutStyle,
86        drawer: &mut ComponentDrawer<'_, '_>,
87    ) -> Vec<ratatui::prelude::Rect> {
88        let layout = layout_style
89            .get_layout()
90            .constraints(children.get_constraints(layout_style.flex_direction));
91
92        let areas = layout.split(drawer.area);
93
94        let mut children_areas: Vec<ratatui::prelude::Rect> = vec![];
95
96        let rev_direction = match layout_style.flex_direction {
97            Direction::Horizontal => Direction::Vertical,
98            Direction::Vertical => Direction::Horizontal,
99        };
100        for (area, constraint) in areas.iter().zip(children.get_constraints(rev_direction)) {
101            let area = Layout::new(rev_direction, [constraint]).split(*area)[0];
102            children_areas.push(area);
103        }
104
105        children_areas
106    }
107
108    fn poll_change(&mut self, _cx: &mut Context<'_>) -> std::task::Poll<()> {
109        std::task::Poll::Pending
110    }
111}
112
113#[doc(hidden)]
114pub trait AnyComponent: Any + Unpin {
115    fn update(&mut self, props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater);
116
117    fn draw(&mut self, drawer: &mut ComponentDrawer);
118
119    fn calc_children_areas(
120        &self,
121        children: &Components,
122        layout_style: &LayoutStyle,
123        drawer: &mut ComponentDrawer,
124    ) -> Vec<ratatui::prelude::Rect>;
125
126    fn poll_change(&mut self, cx: &mut Context) -> std::task::Poll<()>;
127}
128
129impl<C> ElementType for C
130where
131    C: Component,
132{
133    type Props<'a> = C::Props<'a>;
134}
135
136impl<C> AnyComponent for C
137where
138    C: Any + Component,
139{
140    fn update(&mut self, mut props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater) {
141        Component::update(
142            self,
143            unsafe { props.downcast_mut_unchecked(ComponentHelper::<C>::props_type_id()) },
144            hooks,
145            updater,
146        );
147    }
148
149    fn draw(&mut self, drawer: &mut ComponentDrawer) {
150        Component::draw(self, drawer);
151    }
152
153    fn calc_children_areas(
154        &self,
155        children: &Components,
156        layout_style: &LayoutStyle,
157        drawer: &mut ComponentDrawer,
158    ) -> Vec<ratatui::prelude::Rect> {
159        Component::calc_children_areas(self, children, layout_style, drawer)
160    }
161
162    fn poll_change(&mut self, cx: &mut Context) -> std::task::Poll<()> {
163        Component::poll_change(self, cx)
164    }
165}