tui_react/
terminal.rs

1//! Derived from TUI-rs, license: MIT, Copyright (c) 2016 Florian Dehau
2use log::error;
3use std::{borrow::Borrow, io};
4
5use tui::{backend::Backend, buffer::Buffer, layout::Rect};
6
7/// A component meant to be rendered by `Terminal::render(...)`.
8/// All other components don't have to implement this trait, and instead
9/// provide a render method by convention, tuned towards their needs using whichever
10/// generic types or lifetimes they need.
11pub 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        // Attempt to restore the cursor state
35        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    /// Get ready for rendering and return the maximum display size as `Rect`
86    pub fn pre_render(&mut self) -> io::Result<Rect> {
87        // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
88        // and the terminal (if growing), which may OOB.
89        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            // does not matter - we want to see it compiles essentially
167        }
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}