use std::{io::Write, time::Duration}; use crossterm::{ cursor::{Hide, MoveTo, Show}, event::{ self, DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture, KeyEvent, MouseEvent, }, 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), Mouse(MouseEvent), FocusChanged(bool), 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)), crossterm::event::Event::Mouse(event) => Ok(Event::Mouse(event)), crossterm::event::Event::FocusGained => Ok(Event::FocusChanged(true)), crossterm::event::Event::FocusLost => Ok(Event::FocusChanged(false)), _ => 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()?; queue!( writer, Hide, EnterAlternateScreen, EnableMouseCapture, EnableFocusChange )?; 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, DisableMouseCapture, DisableFocusChange, 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)?; loop { puffin::profile_scope!("frame"); while matches!(event::poll(Duration::ZERO), Ok(true)) { let Ok(event) = event::read() else { continue }; let Ok(event) = event.try_into() else { continue; }; event_handler(self, event); } event_handler(self, Event::Render); self.render()?; puffin::GlobalProfiler::lock().new_frame(); } } }