scrin 0.1.37

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
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))
}