1use crate::backend::Backend;
4use crate::buffer::Buffer;
5use crate::geometry::Rect;
6
7#[derive(Debug, Clone)]
9pub struct TerminalOptions {
10 pub alternate_screen: bool,
12 pub hide_cursor: bool,
14}
15
16impl Default for TerminalOptions {
17 fn default() -> Self {
18 Self {
19 alternate_screen: true,
20 hide_cursor: true,
21 }
22 }
23}
24
25pub struct Terminal<B: Backend> {
43 backend: B,
44 buffers: [Buffer; 2],
45 current: usize,
46 hidden_cursor: bool,
47}
48
49impl<B: Backend> Terminal<B> {
50 pub fn new(backend: B) -> Result<Self, B::Error> {
52 Self::with_options(backend, TerminalOptions::default())
53 }
54
55 pub fn with_options(mut backend: B, options: TerminalOptions) -> Result<Self, B::Error> {
57 let size = backend.size()?;
58
59 if options.alternate_screen {
60 backend.enter_alternate_screen()?;
61 }
62
63 if options.hide_cursor {
64 backend.hide_cursor()?;
65 }
66
67 backend.enable_raw_mode()?;
68 backend.clear()?;
69 backend.flush()?;
70
71 Ok(Self {
72 backend,
73 buffers: [Buffer::empty(size), Buffer::empty(size)],
74 current: 0,
75 hidden_cursor: options.hide_cursor,
76 })
77 }
78
79 pub fn size(&self) -> Result<Rect, B::Error> {
81 self.backend.size()
82 }
83
84 #[must_use]
86 pub fn viewport(&self) -> Rect {
87 self.buffers[self.current].area
88 }
89
90 pub fn clear(&mut self) -> Result<(), B::Error> {
92 self.backend.clear()?;
93 self.buffers[self.current].clear();
94 Ok(())
95 }
96
97 pub fn draw<F>(&mut self, render: F) -> Result<(), B::Error>
108 where
109 F: FnOnce(&mut Frame<'_>),
110 {
111 let size = self.backend.size()?;
113 if size != self.buffers[self.current].area {
114 self.resize(size)?;
115 }
116
117 let next = (self.current + 1) % 2;
118 self.buffers[next].clear();
119
120 let mut frame = Frame {
122 buffer: &mut self.buffers[next],
123 area: size,
124 };
125 render(&mut frame);
126
127 let diff = self.buffers[self.current].diff(&self.buffers[next]);
129 for change in diff {
130 for cell in change.cells {
131 self.backend.draw_cell(change.x, change.y, cell)?;
132 }
133 }
134
135 self.backend.flush()?;
136 self.current = next;
137
138 Ok(())
139 }
140
141 fn resize(&mut self, size: Rect) -> Result<(), B::Error> {
143 self.buffers[0].resize(size);
144 self.buffers[1].resize(size);
145 self.backend.clear()?;
146 Ok(())
147 }
148
149 pub fn show_cursor(&mut self) -> Result<(), B::Error> {
151 self.backend.show_cursor()?;
152 self.hidden_cursor = false;
153 Ok(())
154 }
155
156 pub fn hide_cursor(&mut self) -> Result<(), B::Error> {
158 self.backend.hide_cursor()?;
159 self.hidden_cursor = true;
160 Ok(())
161 }
162
163 pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), B::Error> {
165 self.backend.set_cursor(x, y)
166 }
167
168 pub fn backend_mut(&mut self) -> &mut B {
170 &mut self.backend
171 }
172
173 pub fn flush(&mut self) -> Result<(), B::Error> {
175 self.backend.flush()
176 }
177}
178
179impl<B: Backend> Drop for Terminal<B> {
180 fn drop(&mut self) {
181 let _ = self.backend.disable_raw_mode();
182 let _ = self.backend.leave_alternate_screen();
183 if self.hidden_cursor {
184 let _ = self.backend.show_cursor();
185 }
186 let _ = self.backend.flush();
187 }
188}
189
190pub struct Frame<'a> {
194 buffer: &'a mut Buffer,
195 area: Rect,
196}
197
198impl<'a> Frame<'a> {
199 #[must_use]
201 pub const fn area(&self) -> Rect {
202 self.area
203 }
204
205 pub fn buffer_mut(&mut self) -> &mut Buffer {
207 self.buffer
208 }
209
210 pub fn render_widget<W>(&mut self, widget: W, area: Rect)
212 where
213 W: Widget,
214 {
215 widget.render(area, self.buffer);
216 }
217}
218
219pub trait Widget {
221 fn render(self, area: Rect, buf: &mut Buffer);
223}
224
225impl Widget for &str {
227 fn render(self, area: Rect, buf: &mut Buffer) {
228 if area.height > 0 {
229 buf.set_string(area.x, area.y, self, crate::style::Style::default());
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::backend::TestBackend;
238
239 #[test]
240 fn test_terminal_creation() {
241 let backend = TestBackend::new(80, 24);
242 let terminal = Terminal::new(backend);
243 assert!(terminal.is_ok());
244 }
245
246 #[test]
247 fn test_terminal_draw() {
248 let backend = TestBackend::new(80, 24);
249 let mut terminal = Terminal::new(backend).unwrap();
250
251 let result = terminal.draw(|frame| {
252 let area = frame.area();
253 assert_eq!(area.width, 80);
254 assert_eq!(area.height, 24);
255 });
256
257 assert!(result.is_ok());
258 }
259}