use crate::spec_ai_tui::buffer::{Buffer, Cell};
use crate::spec_ai_tui::geometry::{Rect, Size};
use crate::spec_ai_tui::style::Color;
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::EnableBracketedPaste,
execute, queue,
style::{Attribute, Print, ResetColor, SetAttribute, SetBackgroundColor, SetForegroundColor},
terminal::{self, enable_raw_mode, Clear, ClearType, EnterAlternateScreen},
};
use std::io::{self, Stdout, Write};
use super::RawModeGuard;
pub struct Terminal {
stdout: Stdout,
size: Size,
prev_buffer: Option<Buffer>,
}
impl Terminal {
pub fn new() -> io::Result<Self> {
let stdout = io::stdout();
let (width, height) = terminal::size()?;
Ok(Self {
stdout,
size: Size::new(width, height),
prev_buffer: None,
})
}
pub fn enter_raw_mode(&mut self) -> io::Result<RawModeGuard> {
enable_raw_mode()?;
execute!(
self.stdout,
EnterAlternateScreen,
Hide,
EnableBracketedPaste
)?;
Ok(RawModeGuard::new())
}
pub fn size(&self) -> Size {
self.size
}
pub fn refresh_size(&mut self) -> io::Result<()> {
let (width, height) = terminal::size()?;
self.size = Size::new(width, height);
Ok(())
}
pub fn full_rect(&self) -> Rect {
Rect::sized(self.size.width, self.size.height)
}
pub fn clear(&mut self) -> io::Result<()> {
execute!(self.stdout, Clear(ClearType::All))
}
pub fn move_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
execute!(self.stdout, MoveTo(x, y))
}
pub fn show_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, Show)
}
pub fn hide_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, Hide)
}
pub fn set_cursor_visible(&mut self, visible: bool) -> io::Result<()> {
if visible {
self.show_cursor()
} else {
self.hide_cursor()
}
}
pub fn draw_cell(&mut self, x: u16, y: u16, cell: &Cell) -> io::Result<()> {
queue!(self.stdout, MoveTo(x, y))?;
queue!(self.stdout, SetForegroundColor(cell.fg.into()))?;
queue!(self.stdout, SetBackgroundColor(cell.bg.into()))?;
if !cell.modifier.is_empty() {
for attr in cell.modifier.attributes() {
queue!(self.stdout, SetAttribute(attr))?;
}
}
queue!(self.stdout, Print(&cell.symbol))?;
if !cell.modifier.is_empty() {
queue!(self.stdout, SetAttribute(Attribute::Reset))?;
}
Ok(())
}
pub fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
pub fn draw(&mut self, buffer: &Buffer) -> io::Result<()> {
let needs_full_draw = self.prev_buffer.is_none();
if needs_full_draw {
self.draw_full(buffer)?;
} else {
let prev = self.prev_buffer.as_ref().unwrap();
let changes: Vec<_> = buffer
.diff(prev)
.map(|(x, y, cell)| (x, y, cell.clone()))
.collect();
for (x, y, cell) in changes {
self.draw_cell(x, y, &cell)?;
}
}
self.prev_buffer = Some(buffer.clone());
self.flush()
}
pub fn draw_full(&mut self, buffer: &Buffer) -> io::Result<()> {
queue!(self.stdout, ResetColor)?;
let mut last_style = (Color::Reset, Color::Reset);
for (x, y, cell) in buffer.iter() {
queue!(self.stdout, MoveTo(x, y))?;
let current_style = (cell.fg, cell.bg);
if current_style != last_style {
queue!(self.stdout, SetForegroundColor(cell.fg.into()))?;
queue!(self.stdout, SetBackgroundColor(cell.bg.into()))?;
last_style = current_style;
}
if !cell.modifier.is_empty() {
for attr in cell.modifier.attributes() {
queue!(self.stdout, SetAttribute(attr))?;
}
}
queue!(self.stdout, Print(&cell.symbol))?;
if !cell.modifier.is_empty() {
queue!(self.stdout, SetAttribute(Attribute::Reset))?;
last_style = (Color::Reset, Color::Reset); }
}
self.prev_buffer = Some(buffer.clone());
self.flush()
}
pub fn invalidate(&mut self) {
self.prev_buffer = None;
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_terminal_creation() {
}
}