Skip to main content

ratatui_kit/render/
tree.rs

1use futures::{
2    FutureExt,
3    future::{Either, select},
4};
5use std::io::{self};
6
7use crate::{
8    ElementKey,
9    component::{ComponentHelperExt, InstantiatedComponent},
10    context::{ContextStack, SystemContext},
11    element::ElementRepr,
12    props::AnyProps,
13    terminal::{CrossTerminal, Terminal, TerminalImpl, UpdaterTerminal},
14};
15
16use super::ComponentDrawer;
17
18struct RestoreGuard;
19
20impl Drop for RestoreGuard {
21    fn drop(&mut self) {
22        ratatui::restore();
23    }
24}
25
26fn should_quit_on_ctrl_c(system_context: &SystemContext, event: &crossterm::event::Event) -> bool {
27    system_context.auto_quit_on_ctrl_c() && CrossTerminal::received_ctrl_c(event.clone())
28}
29
30#[doc(hidden)]
31pub struct Tree<'a> {
32    root_component: InstantiatedComponent,
33    props: AnyProps<'a>,
34    system_context: SystemContext,
35}
36
37impl<'a> Tree<'a> {
38    pub(crate) fn new(mut props: AnyProps<'a>, helper: Box<dyn ComponentHelperExt>) -> Self {
39        Tree {
40            root_component: InstantiatedComponent::new(
41                ElementKey::user("_root_tree_"),
42                props.borrow(),
43                helper,
44            ),
45            props,
46            system_context: SystemContext::new(),
47        }
48    }
49
50    // 只跑一次 update(自顶向下运行组件、协调子树)。终端以对象安全的
51    // `&mut dyn UpdaterTerminal` 传入,故渲染 harness 可用 no-op 终端驱动。
52    pub(crate) fn update_once(&mut self, terminal: &mut dyn UpdaterTerminal) {
53        // 每帧重建输入注册表(清空上一帧层/handler、铸造 root 层)。
54        // 必须在 ContextStack::root 借走 &mut system_context 之前完成,二者借用不重叠。
55        self.system_context.input.begin_frame();
56        let mut component_context_stack = ContextStack::root(&mut self.system_context);
57        self.root_component
58            .update(terminal, &mut component_context_stack, self.props.borrow());
59    }
60
61    // 只跑一次 draw(把树绘到给定 drawer)。供渲染 harness 直接画到 TestBackend Buffer。
62    pub(crate) fn draw_root(&mut self, drawer: &mut ComponentDrawer) {
63        self.root_component.draw(drawer);
64    }
65
66    fn render(&mut self, terminal: &mut Terminal) -> io::Result<()> {
67        self.update_once(terminal);
68
69        terminal.draw(|frame| {
70            let area = frame.area();
71            let mut drawer = ComponentDrawer::new(frame, area);
72            self.draw_root(&mut drawer);
73        })?;
74
75        Ok(())
76    }
77
78    async fn render_loop(&mut self, terminal: &mut Terminal) -> io::Result<()> {
79        loop {
80            self.render(terminal)?;
81            if self.system_context.should_exit() {
82                break;
83            }
84            match select(
85                self.root_component.wait().boxed_local(),
86                terminal.next_event().boxed_local(),
87            )
88            .await
89            {
90                // 组件树/状态变更:仅回到循环顶重渲染。
91                Either::Left(((), _)) => continue,
92                // 取到一个 raw 事件。
93                Either::Right((Some(event), _)) => {
94                    if should_quit_on_ctrl_c(&self.system_context, &event) {
95                        break;
96                    }
97                    self.system_context.input.dispatch(event);
98                    // dispatch 后无条件 continue:纯副作用/退出型 handler 不写 State 不唤醒,
99                    // 仍需回到循环顶 render + 复查 should_exit,否则会在 select 永久阻塞。
100                    continue;
101                }
102                // 事件流结束。
103                Either::Right((None, _)) => break,
104            }
105        }
106        Ok(())
107    }
108}
109
110pub(crate) async fn render_loop<E: ElementRepr>(
111    mut element: E,
112    mut terminal: Terminal,
113) -> io::Result<()> {
114    let helper = element.helper();
115    let mut tree = Tree::new(element.props_mut(), helper);
116    let _restore_guard = RestoreGuard;
117
118    tree.render_loop(&mut terminal).await
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
125
126    fn ctrl_c_event() -> Event {
127        Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL))
128    }
129
130    #[test]
131    fn ctrl_c_quits_by_default() {
132        assert!(should_quit_on_ctrl_c(
133            &SystemContext::new(),
134            &ctrl_c_event()
135        ));
136    }
137
138    #[test]
139    fn ctrl_c_is_dispatched_when_auto_quit_is_disabled() {
140        let mut system_context = SystemContext::new();
141        system_context.set_auto_quit_on_ctrl_c(false);
142
143        assert!(!should_quit_on_ctrl_c(&system_context, &ctrl_c_event()));
144    }
145
146    #[test]
147    fn regular_key_does_not_quit() {
148        let event = Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
149
150        assert!(!should_quit_on_ctrl_c(&SystemContext::new(), &event));
151    }
152}