use crate::{
geometry::PointU16,
utils::{BoxError, Utils},
};
use crossterm::{QueueableCommand, terminal::SetTitle};
use ratatui::{Frame, Terminal as BaseRatatuiTerminal, TerminalOptions, Viewport, backend::CrosstermBackend};
use std::{
fmt::Display,
io::{Error as IoError, Write},
};
type RatatuiTerminal = BaseRatatuiTerminal<CrosstermBackend<Vec<u8>>>;
pub struct Terminal {
ratatui_terminal: RatatuiTerminal,
bytes: Vec<u8>,
}
impl Terminal {
pub fn new(size: PointU16) -> Result<Self, IoError> {
let ratatui_terminal = Self::ratatui_terminal(size)?;
let bytes = Vec::new();
let terminal = Self {
ratatui_terminal,
bytes,
};
terminal.ok()
}
fn ratatui_terminal(size: PointU16) -> Result<RatatuiTerminal, IoError> {
let backend = CrosstermBackend::new(Vec::new());
let rect = size.ratatui_rect();
let viewport = Viewport::Fixed(rect);
let options = TerminalOptions { viewport };
let mut ratatui_terminal = RatatuiTerminal::with_options(backend, options)?;
ratatui_terminal.resize(rect)?;
ratatui_terminal.ok()
}
pub fn size(&self) -> Result<PointU16, IoError> {
self.ratatui_terminal.size()?.convert::<PointU16>().ok()
}
pub fn resize(&mut self, num_cols: u16, num_rows: u16) -> Result<(), IoError> {
let size = num_cols.pair(num_rows).convert::<PointU16>();
self.ratatui_terminal.resize(size.ratatui_rect())?;
().ok()
}
pub fn set_title<T: Display>(&mut self, title: T) -> Result<&mut Self, IoError> {
let set_title = SetTitle(title);
self.bytes.queue(set_title)?.flush()?;
self.ok()
}
pub fn draw<E: Into<BoxError>, F: FnOnce(&mut Frame) -> Result<(), E>>(
&mut self,
draw_fn: F,
) -> Result<&mut Self, IoError> {
self.ratatui_terminal.clear()?;
self.ratatui_terminal.try_draw(|frame| draw_fn(frame).io_result())?;
self.ratatui_terminal
.backend_mut()
.writer_mut()
.split_off(0)
.push_all_to(&mut self.bytes);
self.ok()
}
pub fn take_bytes(&mut self) -> Vec<u8> {
self.bytes.mem_take()
}
}