| use std::io; |
| |
| use crate::backend::Backend; |
| use crate::buffer::Buffer; |
| use crate::layout::Rect; |
| use crate::widgets::{StatefulWidget, Widget}; |
| |
| /// Interface to the terminal backed by Termion |
| #[derive(Debug)] |
| pub struct Terminal<B> |
| where |
| B: Backend, |
| { |
| backend: B, |
| /// Holds the results of the current and previous draw calls. The two are compared at the end |
| /// of each draw pass to output the necessary updates to the terminal |
| buffers: [Buffer; 2], |
| /// Index of the current buffer in the previous array |
| current: usize, |
| /// Whether the cursor is currently hidden |
| hidden_cursor: bool, |
| /// Terminal size used for rendering. |
| known_size: Rect, |
| } |
| |
| /// Represents a consistent terminal interface for rendering. |
| pub struct Frame<'a, B: 'a> |
| where |
| B: Backend, |
| { |
| terminal: &'a mut Terminal<B>, |
| |
| /// Where should the cursor be after drawing this frame? |
| /// |
| /// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x, |
| /// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`. |
| cursor_position: Option<(u16, u16)>, |
| } |
| |
| impl<'a, B> Frame<'a, B> |
| where |
| B: Backend, |
| { |
| /// Terminal size, guaranteed not to change when rendering. |
| pub fn size(&self) -> Rect { |
| self.terminal.known_size |
| } |
| |
| /// Render a [`Widget`] to the current buffer using [`Widget::render`]. |
| /// |
| /// # Examples |
| /// |
| /// ```rust,no_run |
| /// # use std::io; |
| /// # use tui::Terminal; |
| /// # use tui::backend::TermionBackend; |
| /// # use tui::layout::Rect; |
| /// # use tui::widgets::Block; |
| /// # let stdout = io::stdout(); |
| /// # let backend = TermionBackend::new(stdout); |
| /// # let mut terminal = Terminal::new(backend).unwrap(); |
| /// let block = Block::default(); |
| /// let area = Rect::new(0, 0, 5, 5); |
| /// let mut frame = terminal.get_frame(); |
| /// frame.render_widget(block, area); |
| /// ``` |
| pub fn render_widget<W>(&mut self, widget: W, area: Rect) |
| where |
| W: Widget, |
| { |
| widget.render(area, self.terminal.current_buffer_mut()); |
| } |
| |
| /// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`]. |
| /// |
| /// The last argument should be an instance of the [`StatefulWidget::State`] associated to the |
| /// given [`StatefulWidget`]. |
| /// |
| /// # Examples |
| /// |
| /// ```rust,no_run |
| /// # use std::io; |
| /// # use tui::Terminal; |
| /// # use tui::backend::TermionBackend; |
| /// # use tui::layout::Rect; |
| /// # use tui::widgets::{List, ListItem, ListState}; |
| /// # let stdout = io::stdout(); |
| /// # let backend = TermionBackend::new(stdout); |
| /// # let mut terminal = Terminal::new(backend).unwrap(); |
| /// let mut state = ListState::default(); |
| /// state.select(Some(1)); |
| /// let items = vec![ |
| /// ListItem::new("Item 1"), |
| /// ListItem::new("Item 2"), |
| /// ]; |
| /// let list = List::new(items); |
| /// let area = Rect::new(0, 0, 5, 5); |
| /// let mut frame = terminal.get_frame(); |
| /// frame.render_stateful_widget(list, area, &mut state); |
| /// ``` |
| pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State) |
| where |
| W: StatefulWidget, |
| { |
| widget.render(area, self.terminal.current_buffer_mut(), state); |
| } |
| |
| /// After drawing this frame, make the cursor visible and put it at the specified (x, y) |
| /// coordinates. If this method is not called, the cursor will be hidden. |
| /// |
| /// Note that this will interfere with calls to `Terminal::hide_cursor()`, |
| /// `Terminal::show_cursor()`, and `Terminal::set_cursor()`. Pick one of the APIs and stick |
| /// with it. |
| pub fn set_cursor(&mut self, x: u16, y: u16) { |
| self.cursor_position = Some((x, y)); |
| } |
| } |
| |
| impl<B> Drop for Terminal<B> |
| where |
| B: Backend, |
| { |
| fn drop(&mut self) { |
| // Attempt to restore the cursor state |
| if self.hidden_cursor { |
| if let Err(err) = self.show_cursor() { |
| eprintln!("Failed to show the cursor: {}", err); |
| } |
| } |
| } |
| } |
| |
| impl<B> Terminal<B> |
| where |
| B: Backend, |
| { |
| /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and |
| /// default colors for the foreground and the background |
| pub fn new(backend: B) -> io::Result<Terminal<B>> { |
| let size = backend.size()?; |
| Ok(Terminal { |
| backend, |
| buffers: [Buffer::empty(size), Buffer::empty(size)], |
| current: 0, |
| hidden_cursor: false, |
| known_size: size, |
| }) |
| } |
| |
| /// Get a Frame object which provides a consistent view into the terminal state for rendering. |
| pub fn get_frame(&mut self) -> Frame<B> { |
| Frame { |
| terminal: self, |
| cursor_position: None, |
| } |
| } |
| |
| pub fn current_buffer_mut(&mut self) -> &mut Buffer { |
| &mut self.buffers[self.current] |
| } |
| |
| pub fn backend(&self) -> &B { |
| &self.backend |
| } |
| |
| pub fn backend_mut(&mut self) -> &mut B { |
| &mut self.backend |
| } |
| |
| /// Obtains a difference between the previous and the current buffer and passes it to the |
| /// current backend for drawing. |
| pub fn flush(&mut self) -> io::Result<()> { |
| let previous_buffer = &self.buffers[1 - self.current]; |
| let current_buffer = &self.buffers[self.current]; |
| let updates = previous_buffer.diff(current_buffer); |
| self.backend.draw(updates.into_iter()) |
| } |
| |
| /// Updates the Terminal so that internal buffers match the requested size. Requested size will |
| /// be saved so the size can remain consistent when rendering. |
| /// This leads to a full clear of the screen. |
| pub fn resize(&mut self, area: Rect) -> io::Result<()> { |
| self.buffers[self.current].resize(area); |
| self.buffers[1 - self.current].reset(); |
| self.buffers[1 - self.current].resize(area); |
| self.known_size = area; |
| self.backend.clear() |
| } |
| |
| /// Queries the backend for size and resizes if it doesn't match the previous size. |
| pub fn autoresize(&mut self) -> io::Result<()> { |
| let size = self.size()?; |
| if self.known_size != size { |
| self.resize(size)?; |
| } |
| Ok(()) |
| } |
| |
| /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state |
| /// and prepares for the next draw call. |
| pub fn draw<F>(&mut self, f: F) -> io::Result<()> |
| where |
| F: FnOnce(&mut Frame<B>), |
| { |
| // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets |
| // and the terminal (if growing), which may OOB. |
| self.autoresize()?; |
| |
| let mut frame = self.get_frame(); |
| f(&mut frame); |
| // We can't change the cursor position right away because we have to flush the frame to |
| // stdout first. But we also can't keep the frame around, since it holds a &mut to |
| // Terminal. Thus, we're taking the important data out of the Frame and dropping it. |
| let cursor_position = frame.cursor_position; |
| |
| // Draw to stdout |
| self.flush()?; |
| |
| match cursor_position { |
| None => self.hide_cursor()?, |
| Some((x, y)) => { |
| self.show_cursor()?; |
| self.set_cursor(x, y)?; |
| } |
| } |
| |
| // Swap buffers |
| self.buffers[1 - self.current].reset(); |
| self.current = 1 - self.current; |
| |
| // Flush |
| self.backend.flush()?; |
| Ok(()) |
| } |
| |
| pub fn hide_cursor(&mut self) -> io::Result<()> { |
| self.backend.hide_cursor()?; |
| self.hidden_cursor = true; |
| Ok(()) |
| } |
| pub fn show_cursor(&mut self) -> io::Result<()> { |
| self.backend.show_cursor()?; |
| self.hidden_cursor = false; |
| Ok(()) |
| } |
| pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> { |
| self.backend.get_cursor() |
| } |
| pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { |
| self.backend.set_cursor(x, y) |
| } |
| pub fn clear(&mut self) -> io::Result<()> { |
| self.backend.clear() |
| } |
| /// Queries the real size of the backend. |
| pub fn size(&self) -> io::Result<Rect> { |
| self.backend.size() |
| } |
| } |