summaryrefslogtreecommitdiff
path: root/src/buffer.rs
blob: fa0feaa41c294a8e7e3cb5b91d3733c6788c04da (plain)
use std::io::Write;

use crossterm::{
	cursor::{Hide, MoveTo, Show},
	event::{self, Event},
	execute,
	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,
}

#[derive(Debug)]
pub struct Buffer<'a, W: Write> {
	stdout: &'a mut W,
	pub cells: Vec<Cell>,
	pub window_size: WindowSize,
}

impl<'a, W: Write> Buffer<'a, W> {
	pub fn new(stdout: &'a mut W) -> 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<()> {
		self.cells.sort_unstable_by_key(|cell| cell.z);
		execute!(self.stdout, BeginSynchronizedUpdate, Clear(ClearType::All))?;
		for cell in &self.cells {
			execute!(
				self.stdout,
				MoveTo(cell.x, cell.y),
				PrintStyledContent(StyledContent::new(cell.style, cell.character))
			)?;
		}
		execute!(self.stdout, EndSynchronizedUpdate)?;
		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),
		mut update: impl FnMut(&mut Self),
	) -> std::io::Result<()> {
		Self::initialize(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 {
				while let Ok(event) = receiver.try_recv() {
					event_handler(self, event);
				}
				update(self);
				self.render()?;
			}
		})?;
		self.finish()?;
		Ok(())
	}
}