1use log::error;
3use std::{borrow::Borrow, io};
4
5use tui::{backend::Backend, buffer::Buffer, layout::Rect};
6
7pub trait ToplevelComponent {
12 type Props;
13
14 fn render(&mut self, props: impl Borrow<Self::Props>, area: Rect, buf: &mut Buffer);
15}
16
17#[derive(Debug)]
18pub struct Terminal<B>
19where
20 B: Backend,
21{
22 pub backend: B,
23 buffers: [Buffer; 2],
24 current: usize,
25 hidden_cursor: bool,
26 known_size: Rect,
27}
28
29impl<B> Drop for Terminal<B>
30where
31 B: Backend,
32{
33 fn drop(&mut self) {
34 if self.hidden_cursor {
36 if let Err(err) = self.show_cursor() {
37 error!("Failed to show the cursor: {}", err);
38 }
39 }
40 }
41}
42
43impl<B> Terminal<B>
44where
45 B: Backend,
46{
47 pub fn new(backend: B) -> io::Result<Terminal<B>> {
48 let size = backend.size()?;
49 Ok(Terminal {
50 backend,
51 buffers: [Buffer::empty(size), Buffer::empty(size)],
52 current: 0,
53 hidden_cursor: false,
54 known_size: size,
55 })
56 }
57
58 pub fn current_buffer_mut(&mut self) -> &mut Buffer {
59 &mut self.buffers[self.current]
60 }
61
62 pub fn reconcile_and_flush(&mut self) -> io::Result<()> {
63 let previous_buffer = &self.buffers[1 - self.current];
64 let current_buffer = &self.buffers[self.current];
65 let updates = previous_buffer.diff(current_buffer);
66 self.backend.draw(updates.into_iter())
67 }
68
69 pub fn resize(&mut self, area: Rect) -> io::Result<()> {
70 self.buffers[self.current].resize(area);
71 self.buffers[1 - self.current].reset();
72 self.buffers[1 - self.current].resize(area);
73 self.known_size = area;
74 self.backend.clear()
75 }
76
77 pub fn autoresize(&mut self) -> io::Result<()> {
78 let size = self.size()?;
79 if self.known_size != size {
80 self.resize(size)?;
81 }
82 Ok(())
83 }
84
85 pub fn pre_render(&mut self) -> io::Result<Rect> {
87 self.autoresize()?;
90 Ok(self.known_size)
91 }
92
93 pub fn post_render(&mut self) -> io::Result<()> {
94 self.reconcile_and_flush()?;
95
96 self.buffers[1 - self.current].reset();
97 self.current = 1 - self.current;
98
99 self.backend.flush()?;
100 Ok(())
101 }
102
103 pub fn render<C>(&mut self, component: &mut C, props: impl Borrow<C::Props>) -> io::Result<()>
104 where
105 C: ToplevelComponent,
106 {
107 self.pre_render()?;
108 component.render(props, self.known_size, self.current_buffer_mut());
109 self.post_render()
110 }
111
112 pub fn hide_cursor(&mut self) -> io::Result<()> {
113 self.backend.hide_cursor()?;
114 self.hidden_cursor = true;
115 Ok(())
116 }
117 pub fn show_cursor(&mut self) -> io::Result<()> {
118 self.backend.show_cursor()?;
119 self.hidden_cursor = false;
120 Ok(())
121 }
122 pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
123 self.backend.get_cursor()
124 }
125 pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
126 self.backend.set_cursor(x, y)
127 }
128 pub fn clear(&mut self) -> io::Result<()> {
129 self.backend.clear()
130 }
131 pub fn size(&self) -> io::Result<Rect> {
132 self.backend.size()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use tui::backend::TestBackend;
140
141 #[derive(Default, Clone)]
142 struct ComplexProps {
143 x: usize,
144 y: String,
145 }
146
147 #[derive(Default)]
148 struct StatefulComponent {
149 x: usize,
150 }
151
152 #[derive(Default)]
153 struct StatelessComponent;
154
155 impl ToplevelComponent for StatefulComponent {
156 type Props = usize;
157
158 fn render(&mut self, props: impl Borrow<Self::Props>, _area: Rect, _buf: &mut Buffer) {
159 self.x += *props.borrow();
160 }
161 }
162
163 impl ToplevelComponent for StatelessComponent {
164 type Props = ComplexProps;
165 fn render(&mut self, _props: impl Borrow<Self::Props>, _area: Rect, _buf: &mut Buffer) {
166 }
168 }
169
170 #[test]
171 fn it_does_render_with_simple_and_complex_props() {
172 let mut term = Terminal::new(TestBackend::new(20, 20)).unwrap();
173 let mut c = StatefulComponent::default();
174
175 term.render(&mut c, 3usize).ok();
176 assert_eq!(c.x, 3);
177
178 let mut c = StatelessComponent::default();
179 term.render(&mut c, ComplexProps::default()).ok();
180 }
181}