ratatui_kit/render/
tree.rs1use 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 pub(crate) fn update_once(&mut self, terminal: &mut dyn UpdaterTerminal) {
49 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 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 Either::Left(((), _)) => continue,
88 Either::Right((Some(event), _)) => {
90 if CrossTerminal::received_ctrl_c(event.clone()) {
92 break;
93 }
94 self.system_context.input.dispatch(event);
95 continue;
98 }
99 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}