summaryrefslogtreecommitdiff
path: root/src/buffer.rs
blob: dcb7b8ba081d1e05ffc11ab36827ee6dc81fc43b (plain)
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<dyn Write>,
	pub cells: Vec<Cell>,
	pub window_size: WindowSize,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
	Key(KeyEvent),
	Mouse(MouseEvent),
	FocusChanged(bool),
	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)),
			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<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()?;
		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();
		}
	}
}