1pub mod cursor;
23pub mod cursor_slot;
24pub mod diff_backend;
25
26pub use cursor::CursorState;
27pub use cursor_slot::CursorSlot;
28pub use diff_backend::DiffBackend;
29
30use ratatui::Terminal;
31use ratatui::backend::Backend;
32
33use crate::theme::{TerminalCaps, Theme};
34use crate::widget::{FocusTarget, RenderCtx, RetainedTree};
35
36pub fn draw_frame<B: Backend>(
38 term: &mut Terminal<B>,
39 tree: &mut RetainedTree,
40 cursor: &mut CursorState,
41 focus: FocusTarget,
42 theme: &Theme,
43 caps: &TerminalCaps,
44) -> Result<FrameOutcome, B::Error> {
45 let prev_size = term.size()?;
46 term.autoresize()?;
47 let resized = term.size()? != prev_size;
48 if !tree.any_hash_changed() && !resized {
49 return Ok(FrameOutcome::Idle);
50 }
51 let want = {
52 let mut frame = term.get_frame();
53 let mut ctx = RenderCtx::new(&mut frame, theme, caps);
54 ctx.focus = focus;
55 tree.render(&mut ctx)
56 };
57 term.flush()?;
58 cursor.reconcile(want, term)?;
59 term.swap_buffers();
60 term.backend_mut().flush()?;
61 Ok(FrameOutcome::Rendered)
62}
63
64pub fn draw_frame_closure<B, F>(
77 term: &mut Terminal<B>,
78 cursor: &mut CursorState,
79 focus: FocusTarget,
80 theme: &Theme,
81 caps: &TerminalCaps,
82 render_fn: F,
83) -> Result<FrameOutcome, B::Error>
84where
85 B: Backend,
86 F: FnOnce(&mut RenderCtx<'_, '_>),
87{
88 term.autoresize()?;
90
91 let want = {
95 let mut frame = term.get_frame();
96 let mut ctx = RenderCtx::new(&mut frame, theme, caps);
97 ctx.focus = focus;
98 render_fn(&mut ctx);
99 ctx.take_cursor_slot().resolve(None)
100 };
101
102 term.flush()?;
104
105 cursor.reconcile(want, term)?;
107
108 term.swap_buffers();
110 term.backend_mut().flush()?;
111
112 Ok(FrameOutcome::Rendered)
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
118pub enum FrameOutcome {
119 #[default]
122 Idle,
123 Rendered,
126}
127
128#[cfg(test)]
129mod draw_frame_tests {
130 use super::*;
131 use crate::widget::{RetainedTree, Text};
132 use ratatui::layout::Position;
133 use ratatui::{Terminal, backend::TestBackend};
134
135 fn make_term() -> Terminal<TestBackend> {
136 Terminal::new(TestBackend::new(40, 10)).unwrap()
137 }
138
139 #[test]
140 fn first_frame_is_rendered() {
141 let mut term = make_term();
142 let mut tree = RetainedTree::new(Box::new(Text::new("hello")));
143 let mut cursor = CursorState::new();
144 let theme = Theme::dark();
145 let caps = TerminalCaps::default();
146 let outcome = draw_frame(
147 &mut term,
148 &mut tree,
149 &mut cursor,
150 crate::widget::FocusTarget::None,
151 &theme,
152 &caps,
153 )
154 .unwrap();
155 assert_eq!(outcome, FrameOutcome::Rendered);
156 }
157
158 #[test]
159 fn second_frame_with_unchanged_hash_is_idle() {
160 let mut term = make_term();
161 let mut tree = RetainedTree::new(Box::new(Text::new("hello")));
162 let mut cursor = CursorState::new();
163 let theme = Theme::dark();
164 let caps = TerminalCaps::default();
165 let _ = draw_frame(
166 &mut term,
167 &mut tree,
168 &mut cursor,
169 crate::widget::FocusTarget::None,
170 &theme,
171 &caps,
172 )
173 .unwrap();
174 let outcome = draw_frame(
175 &mut term,
176 &mut tree,
177 &mut cursor,
178 crate::widget::FocusTarget::None,
179 &theme,
180 &caps,
181 )
182 .unwrap();
183 assert_eq!(outcome, FrameOutcome::Idle);
184 }
185
186 #[test]
187 fn draw_frame_closure_renders_via_fn() {
188 let mut term = make_term();
189 let mut cursor = CursorState::new();
190 let theme = Theme::dark();
191 let caps = TerminalCaps::default();
192 let mut rendered_with = None;
193 let outcome = draw_frame_closure(
194 &mut term,
195 &mut cursor,
196 crate::widget::FocusTarget::None,
197 &theme,
198 &caps,
199 |ctx| {
200 rendered_with = Some(ctx.area().width);
201 ctx.set_cursor(Position { x: 0, y: 0 });
202 },
203 )
204 .unwrap();
205 assert_eq!(outcome, FrameOutcome::Rendered);
206 assert_eq!(rendered_with, Some(40));
207 }
208
209 #[test]
210 fn draw_frame_closure_keeps_compiling_without_retained_tree() {
211 let mut term = make_term();
213 let mut cursor = CursorState::new();
214 let theme = Theme::dark();
215 let caps = TerminalCaps::default();
216 let outcome = draw_frame_closure(
217 &mut term,
218 &mut cursor,
219 crate::widget::FocusTarget::None,
220 &theme,
221 &caps,
222 |_ctx| {},
223 )
224 .unwrap();
225 assert_eq!(outcome, FrameOutcome::Rendered);
226 }
227}