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<dyn Write>,
pub cells: Vec<Cell>,
pub window_size: WindowSize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
Key(KeyEvent),
Render,
}
impl TryFrom<crossterm::event::Event> for Event {
type Error = ();
fn try_from(value: crossterm::event::Event) -> Result<Self, Self::Error> {
match value {
crossterm::event::Event::Key(event) => Ok(Event::Key(event)),
_ => Err(()),
}
}
}
impl Buffer {
pub fn new(stdout: Box<dyn Write>) -> std::io::Result<Self> {
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(())
}
}
|