use std::io::{self, Write};
use std::mem;
#[derive(Debug)]
pub(crate) struct Screen {
pub(crate) previous: Frame,
pub(crate) next: Frame,
}
impl Screen {
pub(crate) fn new(rows: usize, cols: usize) -> Self {
Screen {
previous: Frame::new(rows, cols),
next: Frame::new(rows, cols),
}
}
pub(crate) fn prepare_next_frame(&mut self, rows: usize, cols: usize) {
mem::swap(&mut self.next, &mut self.previous);
self.next.reset(rows, cols);
}
pub(crate) fn render(&self, writer: &mut impl Write) -> io::Result<()> {
if self.next.dims() != self.previous.dims() {
self.redraw(writer)
} else {
self.redraw_diff(writer)
}
}
pub(crate) fn redraw(&self, writer: &mut impl Write) -> io::Result<()> {
use termion::cursor::Goto;
write!(writer, "{}", termion::clear::All)?;
assert!(
self.next.rows < u16::max_value().into(),
"rows must fit in u16"
);
for row in 0..self.next.rows {
for col in 0..self.next.cols {
write!(writer, "{}", Goto((col as u16) + 1, (row as u16) + 1))?; let current = self.next.get(row, col);
if let Some((prev_row, prev_col)) = self.next.prev_row_col(row, col) {
let prev = self.next.get(prev_row, prev_col);
if prev.color_fg != current.color_fg {
current.write_fg(writer)?;
}
if prev.color_bg != current.color_bg {
current.write_bg(writer)?;
}
} else {
current.write_fg(writer)?;
current.write_bg(writer)?;
}
write!(writer, "{}", current.glyph)?;
}
}
Ok(())
}
pub(crate) fn redraw_diff(&self, writer: &mut impl Write) -> io::Result<()> {
use termion::cursor::Goto;
assert!(
self.next.rows < u16::max_value().into(),
"rows must fit in u16"
);
let mut prev_fg = Color::default();
let mut prev_bg = Color::default();
prev_fg.write_fg(writer)?;
prev_bg.write_bg(writer)?;
for row in 0..self.next.rows {
for col in 0..self.next.cols {
let next = self.next.get(row, col);
let prev = self.previous.get(row, col);
if next == prev {
continue;
}
write!(writer, "{}", Goto((col as u16) + 1, (row as u16) + 1))?;
if next.color_fg != prev_fg {
next.write_fg(writer)?;
prev_fg = next.color_fg
}
if next.color_bg != prev_bg {
next.write_bg(writer)?;
prev_bg = next.color_bg
}
write!(writer, "{}", next.glyph)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Frame {
rows: usize,
cols: usize,
buffer: Vec<Char>,
}
impl Frame {
fn new(rows: usize, cols: usize) -> Frame {
Frame {
rows,
cols,
buffer: vec![Default::default(); rows * cols],
}
}
fn reset(&mut self, rows: usize, cols: usize) {
self.buffer.clear();
self.rows = rows;
self.cols = cols;
for _ in 0..(rows * cols) {
self.buffer.push(Default::default());
}
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn columns(&self) -> usize {
self.cols
}
fn dims(&self) -> (usize, usize) {
(self.rows, self.cols)
}
pub fn set(&mut self, row: usize, col: usize, ch: Char) {
self.check_dims(row, col);
self.buffer[row * self.cols + col] = ch;
}
pub fn get(&self, row: usize, col: usize) -> Char {
self.check_dims(row, col);
self.buffer[row * self.cols + col]
}
fn prev_row_col(&self, row: usize, col: usize) -> Option<(usize, usize)> {
if row == 0 && col == 0 {
None
} else {
match col {
0 => Some((row - 1, self.cols - 1)),
n => Some((row, n - 1)),
}
}
}
fn check_dims(&self, row: usize, col: usize) {
if row >= self.rows {
panic!(
"Row {} is out of bounds (number of rows: {})",
row, self.rows
);
}
if col >= self.cols {
panic!(
"Column {} is out of bounds (number of columns: {})",
col, self.cols
);
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Char {
pub glyph: char,
pub color_fg: Color,
pub color_bg: Color,
}
impl Char {
pub fn new(glyph: char) -> Char {
Char {
glyph,
color_fg: Color::default(),
color_bg: Color::default(),
}
}
pub fn write_fg(&self, writer: &mut impl Write) -> io::Result<()> {
self.color_fg.write_fg(writer)
}
pub fn write_bg(&self, writer: &mut impl Write) -> io::Result<()> {
self.color_bg.write_bg(writer)
}
}
impl Default for Char {
fn default() -> Self {
Char {
glyph: ' ',
color_fg: Color::default(),
color_bg: Color::default(),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Color {
Default,
Black,
Blue,
Cyan,
LightBlack,
LightBlue,
LightCyan,
LightGreen,
LightMagenta,
LightRed,
LightWhite,
LightYellow,
Magenta,
Red,
Rgb(u8, u8, u8),
White,
Yellow,
}
impl Color {
fn write_fg(&self, writer: &mut impl Write) -> io::Result<()> {
use termion::color;
match self {
Color::Default => write!(writer, "{}", color::Fg(color::Reset)),
Color::Black => write!(writer, "{}", color::Fg(color::Black)),
Color::Blue => write!(writer, "{}", color::Fg(color::Blue)),
Color::Cyan => write!(writer, "{}", color::Fg(color::Cyan)),
Color::LightBlack => write!(writer, "{}", color::Fg(color::LightBlack)),
Color::LightBlue => write!(writer, "{}", color::Fg(color::LightBlue)),
Color::LightCyan => write!(writer, "{}", color::Fg(color::LightCyan)),
Color::LightGreen => write!(writer, "{}", color::Fg(color::LightGreen)),
Color::LightMagenta => write!(writer, "{}", color::Fg(color::LightMagenta)),
Color::LightRed => write!(writer, "{}", color::Fg(color::LightRed)),
Color::LightWhite => write!(writer, "{}", color::Fg(color::LightWhite)),
Color::LightYellow => write!(writer, "{}", color::Fg(color::LightYellow)),
Color::Magenta => write!(writer, "{}", color::Fg(color::Magenta)),
Color::Red => write!(writer, "{}", color::Fg(color::Red)),
Color::Rgb(r, g, b) => write!(writer, "{}", color::Fg(color::Rgb(*r, *g, *b))),
Color::White => write!(writer, "{}", color::Fg(color::White)),
Color::Yellow => write!(writer, "{}", color::Fg(color::Yellow)),
}
}
fn write_bg(&self, writer: &mut impl Write) -> io::Result<()> {
use termion::color;
match self {
Color::Default => write!(writer, "{}", color::Bg(color::Reset)),
Color::Black => write!(writer, "{}", color::Bg(color::Black)),
Color::Blue => write!(writer, "{}", color::Bg(color::Blue)),
Color::Cyan => write!(writer, "{}", color::Bg(color::Cyan)),
Color::LightBlack => write!(writer, "{}", color::Bg(color::LightBlack)),
Color::LightBlue => write!(writer, "{}", color::Bg(color::LightBlue)),
Color::LightCyan => write!(writer, "{}", color::Bg(color::LightCyan)),
Color::LightGreen => write!(writer, "{}", color::Bg(color::LightGreen)),
Color::LightMagenta => write!(writer, "{}", color::Bg(color::LightMagenta)),
Color::LightRed => write!(writer, "{}", color::Bg(color::LightRed)),
Color::LightWhite => write!(writer, "{}", color::Bg(color::LightWhite)),
Color::LightYellow => write!(writer, "{}", color::Bg(color::LightYellow)),
Color::Magenta => write!(writer, "{}", color::Bg(color::Magenta)),
Color::Red => write!(writer, "{}", color::Bg(color::Red)),
Color::Rgb(r, g, b) => write!(writer, "{}", color::Bg(color::Rgb(*r, *g, *b))),
Color::White => write!(writer, "{}", color::Bg(color::White)),
Color::Yellow => write!(writer, "{}", color::Bg(color::Yellow)),
}
}
}
impl Default for Color {
fn default() -> Self {
Color::Default
}
}
#[macro_export]
macro_rules! char {
() => {
$crate::Char::default()
};
($glyph:expr) => {
$crate::Char::new($glyph)
};
($glyph:expr, $fg:expr) => {
$crate::Char {
glyph: $glyph,
color_fg: $fg,
color_bg: Color::default(),
}
};
($glyph:expr, $fg:expr, $bg:expr) => {
$crate::Char {
glyph: $glyph,
color_fg: $fg,
color_bg: $bg,
}
};
}