use std::io::Write; use crossterm::{ cursor::{Hide, MoveTo, Show}, event::{self, KeyEvent}, execute, queue, style::{ContentStyle, PrintStyledContent, StyledContent}, terminal::{ BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, WindowSize, disable_raw_mode, enable_raw_mode, window_size, }, }; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] pub struct Cell { pub character: char, pub style: ContentStyle, pub x: u16, pub y: u16, pub z: u8, } pub struct Buffer { stdout: Box, pub cells: Vec, pub window_size: WindowSize, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Event { Key(KeyEvent), Render, } impl TryFrom for Event { type Error = (); fn try_from(value: crossterm::event::Event) -> Result { match value { crossterm::event::Event::Key(event) => Ok(Event::Key(event)), _ => Err(()), } } } impl Buffer { pub fn new(stdout: Box) -> std::io::Result { Ok(Self { stdout, cells: Vec::new(), window_size: window_size()?, }) } fn initialize(writer: &mut impl Write) -> std::io::Result<()> { enable_raw_mode()?; execute!(writer, Hide, EnterAlternateScreen)?; Ok(()) } fn render(&mut self) -> std::io::Result<()> { puffin::profile_function!(); { puffin::profile_scope!("sort cells"); self.cells.sort_unstable_by_key(|cell| cell.z); } { puffin::profile_scope!("queue"); queue!(self.stdout, BeginSynchronizedUpdate, Clear(ClearType::All))?; for cell in &self.cells { queue!( self.stdout, MoveTo(cell.x, cell.y), PrintStyledContent(StyledContent::new(cell.style, cell.character)) )?; } queue!(self.stdout, EndSynchronizedUpdate)?; } puffin::profile_scope!("flush"); self.stdout.flush()?; Ok(()) } pub fn finish(&mut self) -> std::io::Result<()> { execute!(self.stdout, Show, LeaveAlternateScreen)?; disable_raw_mode()?; Ok(()) } pub fn render_loop( &mut self, mut event_handler: impl FnMut(&mut Self, Event), ) -> std::io::Result<()> { Self::initialize(&mut self.stdout)?; let (sender, receiver) = std::sync::mpsc::channel(); std::thread::scope(|scope| -> std::io::Result<()> { scope.spawn(|| { loop { let Ok(event) = event::read() else { continue; }; let _ = sender.send(event); } }); loop { puffin::profile_scope!("frame"); while let Ok(event) = receiver.try_recv() { if let Ok(event) = event.try_into() { event_handler(self, event); } } event_handler(self, Event::Render); self.render()?; puffin::GlobalProfiler::lock().new_frame(); } })?; self.finish()?; Ok(()) } }