summaryrefslogtreecommitdiff
path: root/src/buffer.rs
blob: 6d983db7061b96949f7f272d0b0cf8be9e154453 (plain)
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(())
	}
}