diff --git a/examples/barchart.rs b/examples/barchart.rs index 3a0c94d0..26bd58b5 100644 --- a/examples/barchart.rs +++ b/examples/barchart.rs @@ -77,13 +77,12 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(size); + .split(f.size()); BarChart::default() .block(Block::default().title("Data1").borders(Borders::ALL)) .data(&app.data) diff --git a/examples/block.rs b/examples/block.rs index 3d88ccd2..30fcc0ab 100644 --- a/examples/block.rs +++ b/examples/block.rs @@ -31,18 +31,17 @@ fn main() -> Result<(), failure::Error> { let events = Events::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { // Wrapping block for a group // Just draw the block and the group on the same area and build the group // with at least a margin of 1 + let size = f.size(); Block::default().borders(Borders::ALL).render(&mut f, size); let chunks = Layout::default() .direction(Direction::Vertical) .margin(4) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(size); + .split(f.size()); { let chunks = Layout::default() .direction(Direction::Horizontal) diff --git a/examples/canvas.rs b/examples/canvas.rs index 0f5345f7..aff0224f 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -90,13 +90,11 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(size); + .split(f.size()); Canvas::default() .block(Block::default().borders(Borders::ALL).title("World")) .paint(|ctx| { diff --git a/examples/chart.rs b/examples/chart.rs index f63b1573..6f3d4dea 100644 --- a/examples/chart.rs +++ b/examples/chart.rs @@ -71,9 +71,8 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { + let size = f.size(); Chart::default() .block( Block::default() diff --git a/examples/crossterm.rs b/examples/crossterm.rs index e62f6d25..c073c1b4 100644 --- a/examples/crossterm.rs +++ b/examples/crossterm.rs @@ -13,9 +13,8 @@ fn main() -> Result<(), failure::Error> { terminal.hide_cursor()?; loop { - let size = terminal.size()?; - terminal.draw(|mut f| { + let size = f.size(); let text = [ Text::raw("It "), Text::styled("works", Style::default().fg(Color::Yellow)), diff --git a/examples/custom_widget.rs b/examples/custom_widget.rs index 8ea1acbe..f2366ecb 100644 --- a/examples/custom_widget.rs +++ b/examples/custom_widget.rs @@ -54,9 +54,8 @@ fn main() -> Result<(), failure::Error> { let events = Events::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { + let size = f.size(); Label::default().text("Test").render(&mut f, size); })?; diff --git a/examples/demo.rs b/examples/demo.rs index 14a24ec3..f7f78037 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -168,13 +168,11 @@ fn main() -> Result<(), failure::Error> { }; loop { - let size = terminal.size()?; - // Draw UI terminal.draw(|mut f| { let chunks = Layout::default() .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) - .split(size); + .split(f.size()); Tabs::default() .block(Block::default().borders(Borders::ALL).title("Tabs")) .titles(&app.tabs.titles) diff --git a/examples/gauge.rs b/examples/gauge.rs index 4e7d3187..7fc5d209 100644 --- a/examples/gauge.rs +++ b/examples/gauge.rs @@ -70,8 +70,6 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Vertical) @@ -85,7 +83,7 @@ fn main() -> Result<(), failure::Error> { ] .as_ref(), ) - .split(size); + .split(f.size()); Gauge::default() .block(Block::default().title("Gauge1").borders(Borders::ALL)) diff --git a/examples/layout.rs b/examples/layout.rs index 899c80e1..c8c1ea5f 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -34,8 +34,6 @@ fn main() -> Result<(), failure::Error> { let events = Events::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Vertical) @@ -47,7 +45,7 @@ fn main() -> Result<(), failure::Error> { ] .as_ref(), ) - .split(size); + .split(f.size()); Block::default() .title("Block") diff --git a/examples/list.rs b/examples/list.rs index 0fabccfc..b504139a 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -94,13 +94,11 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(size); + .split(f.size()); let style = Style::default().fg(Color::Black).bg(Color::White); SelectableList::default() diff --git a/examples/paragraph.rs b/examples/paragraph.rs index a3d1a4eb..34ec3625 100644 --- a/examples/paragraph.rs +++ b/examples/paragraph.rs @@ -31,13 +31,13 @@ fn main() -> Result<(), failure::Error> { let events = Events::new(); loop { - let size = terminal.size()?; - - let mut long_line: String = std::iter::repeat('X').take(size.width.into()).collect(); - long_line.insert_str(0, "Very long line: "); - long_line.push('\n'); - terminal.draw(|mut f| { + let size = f.size(); + + let mut long_line: String = std::iter::repeat('X').take(size.width.into()).collect(); + long_line.insert_str(0, "Very long line: "); + long_line.push('\n'); + Block::default() .style(Style::default().bg(Color::White)) .render(&mut f, size); diff --git a/examples/rustbox.rs b/examples/rustbox.rs index 8564d585..57a1fdae 100644 --- a/examples/rustbox.rs +++ b/examples/rustbox.rs @@ -30,12 +30,12 @@ fn main() -> Result<(), failure::Error> { } fn draw(t: &mut Terminal) -> Result<(), std::io::Error> { - let size = t.size()?; let text = [ Text::raw("It "), Text::styled("works", Style::default().fg(Color::Yellow)), ]; t.draw(|mut f| { + let size = f.size(); Paragraph::new(text.iter()) .block( Block::default() diff --git a/examples/sparkline.rs b/examples/sparkline.rs index 19504442..31681ba0 100644 --- a/examples/sparkline.rs +++ b/examples/sparkline.rs @@ -70,8 +70,6 @@ fn main() -> Result<(), failure::Error> { let mut app = App::new(); loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Vertical) @@ -85,7 +83,7 @@ fn main() -> Result<(), failure::Error> { ] .as_ref(), ) - .split(size); + .split(f.size()); Sparkline::default() .block( Block::default() diff --git a/examples/table.rs b/examples/table.rs index e5bef95a..9fcd7f4f 100644 --- a/examples/table.rs +++ b/examples/table.rs @@ -56,8 +56,6 @@ fn main() -> Result<(), failure::Error> { // Input loop { - let size = terminal.size()?; - terminal.draw(|mut f| { let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold); let normal_style = Style::default().fg(Color::White); @@ -73,7 +71,7 @@ fn main() -> Result<(), failure::Error> { let rects = Layout::default() .constraints([Constraint::Percentage(100)].as_ref()) .margin(5) - .split(size); + .split(f.size()); Table::new(header.into_iter(), rows) .block(Block::default().borders(Borders::ALL).title("Table")) .widths(&[10, 10, 10]) diff --git a/examples/tabs.rs b/examples/tabs.rs index d5aeebab..78a281a9 100644 --- a/examples/tabs.rs +++ b/examples/tabs.rs @@ -42,9 +42,8 @@ fn main() -> Result<(), failure::Error> { // Main loop loop { - let size = terminal.size()?; - terminal.draw(|mut f| { + let size = f.size(); let chunks = Layout::default() .direction(Direction::Vertical) .margin(5) diff --git a/examples/user_input.rs b/examples/user_input.rs index 8a1df510..3a7ed44e 100644 --- a/examples/user_input.rs +++ b/examples/user_input.rs @@ -65,16 +65,13 @@ fn main() -> Result<(), failure::Error> { let mut app = App::default(); loop { - // Handle resize - let size = terminal.size()?; - // Draw UI terminal.draw(|mut f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref()) - .split(size); + .split(f.size()); Paragraph::new([Text::raw(&app.input)].iter()) .style(Style::default().fg(Color::Yellow)) .block(Block::default().borders(Borders::ALL).title("Input")) diff --git a/src/terminal.rs b/src/terminal.rs index 85629703..9a79e9fb 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -19,9 +19,11 @@ where current: usize, /// Whether the cursor is currently hidden hidden_cursor: bool, - prev_size: Option, + /// Terminal size used for rendering. + known_size: Rect, } +/// Represents a consistent terminal interface for rendering. pub struct Frame<'a, B: 'a> where B: Backend, @@ -33,6 +35,11 @@ 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 + } + /// Calls the draw method of a given widget on the current buffer pub fn render(&mut self, widget: &mut W, area: Rect) where @@ -69,10 +76,11 @@ where buffers: [Buffer::empty(size), Buffer::empty(size)], current: 0, hidden_cursor: false, - prev_size: None, + 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 { Frame { terminal: self } } @@ -111,27 +119,36 @@ where self.backend.draw(content) } - /// Updates the interface so that internal buffers matches the current size of the terminal. - /// This leads to a full redraw of the screen. + /// 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.prev_size = Some(area); + self.known_size = area; self.backend.clear() } - /// Flushes the current internal state and prepares the interface for the next draw call + /// 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(&mut self, f: F) -> io::Result<()> where F: FnOnce(Frame), { // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets // and the terminal (if growing), which may OOB. - let size = self.size()?; - if self.prev_size != Some(size) { - self.resize(size)?; - } + self.autoresize()?; + f(self.get_frame()); // Draw to stdout @@ -159,6 +176,7 @@ where pub fn clear(&mut self) -> io::Result<()> { self.backend.clear() } + /// Queries the real size of the backend. pub fn size(&self) -> io::Result { self.backend.size() }