From add2f25aba10d8d39ba96567ba8b719eed3187c7 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 21 May 2022 22:33:37 +0200 Subject: [PATCH] Use Frame for rendering instead of Buffer --- examples/hello_world.rs | 16 ++++++------ src/buffer.rs | 24 ++---------------- src/frame.rs | 41 +++++++++++++++++++++++++++++++ src/lib.rs | 3 ++- src/terminal.rs | 54 ++++++++++++++++++++++++----------------- 5 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 src/frame.rs diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 44c10fa..f8414a2 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,32 +1,32 @@ use std::io; use crossterm::style::{ContentStyle, Stylize}; -use toss::buffer::Pos; +use toss::frame::Pos; use toss::terminal::Terminal; fn main() -> io::Result<()> { // Automatically enters alternate screen and enables raw mode - let mut term = Terminal::new(Box::new(io::stdout()))?; + let mut term = Terminal::new()?; // Must be called before rendering, otherwise the terminal has out-of-date // size information and will present garbage. term.autoresize()?; - // Render things to the buffer - let b = term.buffer(); - b.write( + // Render stuff onto the next frame + let f = term.frame(); + f.write( Pos::new(0, 0), "Hello world!", ContentStyle::default().green(), ); - b.write( + f.write( Pos::new(0, 1), "Press any key to exit", ContentStyle::default().on_dark_blue(), ); - b.set_cursor(Some(Pos::new(16, 0))); + f.show_cursor(Pos::new(16, 0)); - // Show the buffer's contents on screen + // Show the next frame on the screen term.present()?; // Wait for input before exiting diff --git a/src/buffer.rs b/src/buffer.rs index c666cda..266b0ab 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -2,7 +2,7 @@ use crossterm::style::ContentStyle; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Size { pub width: u16, pub height: u16, @@ -48,22 +48,13 @@ impl Cell { } } +#[derive(Debug, Default)] pub struct Buffer { size: Size, data: Vec, - cursor: Option, } impl Buffer { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { - size: Size::ZERO, - data: vec![], - cursor: None, - } - } - fn index(&self, x: u16, y: u16) -> usize { assert!(x < self.size.width); assert!(y < self.size.height); @@ -86,7 +77,6 @@ impl Buffer { pub fn resize(&mut self, size: Size) { if size == self.size() { self.data.fill_with(Cell::empty); - self.cursor = None; } else { let width: usize = size.width.into(); let height: usize = size.height.into(); @@ -95,7 +85,6 @@ impl Buffer { self.size = size; self.data.clear(); self.data.resize_with(len, Cell::empty); - self.cursor = None; } } @@ -104,7 +93,6 @@ impl Buffer { /// `buf.reset()` is equivalent to `buf.resize(buf.size())`. pub fn reset(&mut self) { self.data.fill_with(Cell::empty); - self.cursor = None; } pub fn write(&mut self, mut pos: Pos, content: &str, style: ContentStyle) { @@ -133,14 +121,6 @@ impl Buffer { } } - pub fn cursor(&self) -> Option { - self.cursor - } - - pub fn set_cursor(&mut self, pos: Option) { - self.cursor = pos; - } - pub fn cells(&self) -> Cells<'_> { Cells { buffer: self, diff --git a/src/frame.rs b/src/frame.rs new file mode 100644 index 0000000..ae54a2e --- /dev/null +++ b/src/frame.rs @@ -0,0 +1,41 @@ +use crossterm::style::ContentStyle; + +use crate::buffer::Buffer; +pub use crate::buffer::{Pos, Size}; + +#[derive(Debug, Default)] +pub struct Frame { + pub(crate) buffer: Buffer, + cursor: Option, +} + +impl Frame { + pub fn size(&self) -> Size { + self.buffer.size() + } + + pub fn reset(&mut self) { + self.buffer.reset(); + self.cursor = None; + } + + pub fn cursor(&self) -> Option { + self.cursor + } + + pub fn set_cursor(&mut self, pos: Option) { + self.cursor = pos; + } + + pub fn show_cursor(&mut self, pos: Pos) { + self.set_cursor(Some(pos)); + } + + pub fn hide_cursor(&mut self) { + self.set_cursor(None); + } + + pub fn write(&mut self, pos: Pos, content: &str, style: ContentStyle) { + self.buffer.write(pos, content, style); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7c6e2ac..56465e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ -pub mod buffer; +mod buffer; +pub mod frame; pub mod terminal; diff --git a/src/terminal.rs b/src/terminal.rs index 7653855..cfb7510 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -7,13 +7,15 @@ use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternate use crossterm::{ExecutableCommand, QueueableCommand}; use crate::buffer::{Buffer, Size}; +use crate::frame::Frame; pub struct Terminal { + /// Render target. out: Box, - /// Currently visible on screen. - prev_buffer: Buffer, - /// Buffer to render to. - curr_buffer: Buffer, + /// The frame being currently rendered. + frame: Frame, + /// Buffer from the previous frame. + prev_frame_buffer: Buffer, /// When the screen is updated next, it must be cleared and redrawn fully /// instead of performing an incremental update. full_redraw: bool, @@ -27,11 +29,15 @@ impl Drop for Terminal { } impl Terminal { - pub fn new(out: Box) -> io::Result { + pub fn new() -> io::Result { + Self::with_target(Box::new(io::stdout())) + } + + pub fn with_target(out: Box) -> io::Result { let mut result = Self { out, - prev_buffer: Buffer::new(), - curr_buffer: Buffer::new(), + frame: Frame::default(), + prev_frame_buffer: Buffer::default(), full_redraw: true, }; crossterm::terminal::enable_raw_mode()?; @@ -39,28 +45,32 @@ impl Terminal { Ok(result) } - pub fn buffer(&mut self) -> &mut Buffer { - &mut self.curr_buffer - } - + /// Resize the frame and other internal buffers if the terminal size has + /// changed. pub fn autoresize(&mut self) -> io::Result<()> { let (width, height) = crossterm::terminal::size()?; let size = Size { width, height }; - if size != self.curr_buffer.size() { - self.prev_buffer.resize(size); - self.curr_buffer.resize(size); + if size != self.frame.size() { + self.frame.buffer.resize(size); + self.prev_frame_buffer.resize(size); self.full_redraw = true; } Ok(()) } - /// Display the contents of the buffer on the screen and prepare rendering - /// the next frame. + pub fn frame(&mut self) -> &mut Frame { + &mut self.frame + } + + /// Display the current frame on the screen and prepare the next frame. + /// + /// After calling this function, the frame returned by [`Self::frame`] will + /// be empty again and have no cursor position. pub fn present(&mut self) -> io::Result<()> { if self.full_redraw { io::stdout().queue(Clear(ClearType::All))?; - self.prev_buffer.reset(); + self.prev_frame_buffer.reset(); // Because the screen is now empty self.full_redraw = false; } @@ -68,15 +78,15 @@ impl Terminal { self.update_cursor()?; self.out.flush()?; - mem::swap(&mut self.prev_buffer, &mut self.curr_buffer); - self.curr_buffer.reset(); + mem::swap(&mut self.prev_frame_buffer, &mut self.frame.buffer); + self.frame.reset(); Ok(()) } fn draw_differences(&mut self) -> io::Result<()> { // TODO Only draw the differences - for (x, y, cell) in self.curr_buffer.cells() { + for (x, y, cell) in self.frame.buffer.cells() { let content = StyledContent::new(cell.style, &cell.content as &str); self.out .queue(MoveTo(x, y))? @@ -86,8 +96,8 @@ impl Terminal { } fn update_cursor(&mut self) -> io::Result<()> { - if let Some(pos) = self.curr_buffer.cursor() { - let size = self.curr_buffer.size(); + if let Some(pos) = self.frame.cursor() { + let size = self.frame.size(); let x_in_bounds = 0 <= pos.x && pos.x < size.width as i32; let y_in_bounds = 0 <= pos.y && pos.y < size.height as i32; if x_in_bounds && y_in_bounds {