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
26#[doc(hidden)]
27pub struct Tree<'a> {
28    root_component: InstantiatedComponent,
29    props: AnyProps<'a>,
30    system_context: SystemContext,
31}
32
33impl<'a> Tree<'a> {
34    pub(crate) fn new(mut props: AnyProps<'a>, helper: Box<dyn ComponentHelperExt>) -> Self {
35        Tree {
36            root_component: InstantiatedComponent::new(
37                ElementKey::user("_root_tree_"),
38                props.borrow(),
39                helper,
40            ),
41            props,
42            system_context: SystemContext::new(),
43        }
44    }
45
46    // 只跑一次 update(自顶向下运行组件、协调子树)。终端以对象安全的
47    // `&mut dyn UpdaterTerminal` 传入,故渲染 harness 可用 no-op 终端驱动。
48    pub(crate) fn update_once(&mut self, terminal: &mut dyn UpdaterTerminal) {
49        // 每帧重建输入注册表(清空上一帧层/handler、铸造 root 层)。
50        // 必须在 ContextStack::root 借走 &mut system_context 之前完成,二者借用不重叠。
51        self.system_context.input.begin_frame();
52        let mut component_context_stack = ContextStack::root(&mut self.system_context);
53        self.root_component
54            .update(terminal, &mut component_context_stack, self.props.borrow());
55    }
56
57    // 只跑一次 draw(把树绘到给定 drawer)。供渲染 harness 直接画到 TestBackend Buffer。
58    pub(crate) fn draw_root(&mut self, drawer: &mut ComponentDrawer) {
59        self.root_component.draw(drawer);
60    }
61
62    fn render(&mut self, terminal: &mut Terminal) -> io::Result<()> {
63        self.update_once(terminal);
64
65        terminal.draw(|frame| {
66            let area = frame.area();
67            let mut drawer = ComponentDrawer::new(frame, area);
68            self.draw_root(&mut drawer);
69        })?;
70
71        Ok(())
72    }
73
74    async fn render_loop(&mut self, terminal: &mut Terminal) -> io::Result<()> {
75        loop {
76            self.render(terminal)?;
77            if self.system_context.should_exit() {
78                break;
79            }
80            match select(
81                self.root_component.wait().boxed_local(),
82                terminal.next_event().boxed_local(),
83            )
84            .await
85            {
86                // 组件树/状态变更:仅回到循环顶重渲染。
87                Either::Left(((), _)) => continue,
88                // 取到一个 raw 事件。
89                Either::Right((Some(event), _)) => {
90                    // ctrl_c 先于 dispatch 判定,任何层的 Consumed 都吞不掉它。
91                    if CrossTerminal::received_ctrl_c(event.clone()) {
92                        break;
93                    }
94                    self.system_context.input.dispatch(event);
95                    // dispatch 后无条件 continue:纯副作用/退出型 handler 不写 State 不唤醒,
96                    // 仍需回到循环顶 render + 复查 should_exit,否则会在 select 永久阻塞。
97                    continue;
98                }
99                // 事件流结束。
100                Either::Right((None, _)) => break,
101            }
102        }
103        Ok(())
104    }
105}
106
107pub(crate) async fn render_loop<E: ElementRepr>(
108    mut element: E,
109    mut terminal: Terminal,
110) -> io::Result<()> {
111    let helper = element.helper();
112    let mut tree = Tree::new(element.props_mut(), helper);
113    let _restore_guard = RestoreGuard;
114
115    tree.render_loop(&mut terminal).await
116}