use crate::core::buffer::Buffer;
use crate::core::rect::Rect;
use crossterm::{
cursor,
event::{self, Event},
execute, terminal,
};
use std::io::{self, Stdout, Write};
#[derive(Debug)]
pub struct Terminal {
stdout: Stdout,
width: u16,
height: u16,
front_buffer: Buffer,
back_buffer: Buffer,
hidden_cursor: bool,
}
impl Terminal {
pub fn init() -> io::Result<Self> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide,)?;
let (w, h) = terminal::size()?;
let front = Buffer::new(w as usize, h as usize);
let back = Buffer::new(w as usize, h as usize);
Ok(Self {
stdout,
width: w,
height: h,
front_buffer: front,
back_buffer: back,
hidden_cursor: true,
})
}
pub fn restore(&mut self) -> io::Result<()> {
execute!(self.stdout, cursor::Show, terminal::LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
Ok(())
}
pub fn size(&self) -> Rect {
Rect::new(0, 0, self.width, self.height)
}
pub fn width(&self) -> usize {
self.width as usize
}
pub fn height(&self) -> usize {
self.height as usize
}
pub fn back_buffer(&mut self) -> &mut Buffer {
&mut self.back_buffer
}
pub fn front_buffer(&self) -> &Buffer {
&self.front_buffer
}
pub fn draw(&mut self) -> io::Result<()> {
let mut output = String::with_capacity(self.width as usize * self.height as usize * 8);
let mut last_fg = crate::core::color::Color::BLACK;
let mut last_bg: Option<crate::core::color::Color> = None;
let mut last_bold = false;
let mut last_italic = false;
let mut last_underlined = false;
for y in 0..self.height as usize {
let mut line_dirty = false;
for x in 0..self.width as usize {
let back_cell = self.back_buffer.get(x, y).unwrap();
let front_cell = self.front_buffer.get(x, y);
if let Some(fc) = front_cell {
if fc == back_cell {
continue;
}
}
if !line_dirty {
output.push_str(&format!("\x1b[{};1H", y + 1));
line_dirty = true;
}
if back_cell.fg != last_fg {
output.push_str(&back_cell.fg.to_ansi_fg());
last_fg = back_cell.fg;
}
if back_cell.bg != last_bg {
match back_cell.bg {
Some(c) => output.push_str(&c.to_ansi_bg()),
None => output.push_str("\x1b[49m"),
}
last_bg = back_cell.bg;
}
if back_cell.bold != last_bold {
if back_cell.bold {
output.push_str("\x1b[1m");
} else {
output.push_str("\x1b[22m");
}
last_bold = back_cell.bold;
}
if back_cell.italic != last_italic {
if back_cell.italic {
output.push_str("\x1b[3m");
} else {
output.push_str("\x1b[23m");
}
last_italic = back_cell.italic;
}
if back_cell.underlined != last_underlined {
if back_cell.underlined {
output.push_str("\x1b[4m");
} else {
output.push_str("\x1b[24m");
}
last_underlined = back_cell.underlined;
}
output.push(back_cell.ch);
}
}
output.push_str("\x1b[0m");
write!(self.stdout, "{}", output)?;
self.stdout.flush()?;
self.front_buffer = self.back_buffer.clone();
Ok(())
}
pub fn clear(&mut self) {
self.back_buffer = Buffer::new(self.width as usize, self.height as usize);
}
pub fn clear_area(&mut self, area: Rect) {
self.back_buffer.clear(area);
}
pub fn hide_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, cursor::Hide)?;
self.hidden_cursor = true;
Ok(())
}
pub fn show_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, cursor::Show)?;
self.hidden_cursor = false;
Ok(())
}
pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
execute!(self.stdout, cursor::MoveTo(x, y))?;
Ok(())
}
pub fn poll_event(&self) -> io::Result<Option<Event>> {
if event::poll(std::time::Duration::from_millis(0))? {
Ok(Some(event::read()?))
} else {
Ok(None)
}
}
pub fn wait_event(&self) -> io::Result<Event> {
event::read()
}
pub fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
pub fn size_changed(&mut self) -> io::Result<bool> {
let (w, h) = terminal::size()?;
if w != self.width || h != self.height {
self.width = w;
self.height = h;
self.front_buffer.resize(w as usize, h as usize);
self.back_buffer.resize(w as usize, h as usize);
Ok(true)
} else {
Ok(false)
}
}
pub fn raw_output(&self) -> &Stdout {
&self.stdout
}
}
impl Drop for Terminal {
fn drop(&mut self) {
let _ = self.restore();
}
}
pub fn terminal_size() -> (u16, u16) {
terminal::size().unwrap_or((80, 24))
}