use crate::cell::Cell;
use crate::surface::Surface;
use std::io::{self, Write};
struct SGR {
bold: bool,
italic: bool,
underline: bool,
strikethrough: bool,
#[allow(dead_code)]
reversed: bool,
fg: Option<crate::cell::Color>,
bg: Option<crate::cell::Color>,
}
impl SGR {
fn new() -> Self {
Self {
bold: false,
italic: false,
underline: false,
strikethrough: false,
reversed: false,
fg: None,
bg: None,
}
}
fn reset() -> Self {
Self {
bold: false,
italic: false,
underline: false,
strikethrough: false,
reversed: false,
fg: None,
bg: None,
}
}
fn to_sgr(&self) -> String {
use crate::cell::Color;
let mut codes = Vec::new();
codes.push(0);
if self.bold {
codes.push(1);
}
if self.italic {
codes.push(3);
}
if self.underline {
codes.push(4);
}
if self.strikethrough {
codes.push(9);
}
if let Some(fg) = &self.fg {
match fg {
Color::Default => codes.extend_from_slice(&[39]),
Color::Black => codes.push(30),
Color::Red => codes.push(31),
Color::Green => codes.push(32),
Color::Yellow => codes.push(33),
Color::Blue => codes.push(34),
Color::Magenta => codes.push(35),
Color::Cyan => codes.push(36),
Color::White => codes.push(37),
Color::Indexed(n) => codes.extend_from_slice(&[38, 5, *n as u8]),
Color::Rgb(r, g, b) => {
codes.extend_from_slice(&[38, 2, *r as u8, *g as u8, *b as u8])
}
}
}
if let Some(bg) = &self.bg {
match bg {
Color::Default => codes.extend_from_slice(&[49]),
Color::Black => codes.push(40),
Color::Red => codes.push(41),
Color::Green => codes.push(42),
Color::Yellow => codes.push(43),
Color::Blue => codes.push(44),
Color::Magenta => codes.push(45),
Color::Cyan => codes.push(46),
Color::White => codes.push(47),
Color::Indexed(n) => codes.extend_from_slice(&[48, 5, *n as u8]),
Color::Rgb(r, g, b) => {
codes.extend_from_slice(&[48, 2, *r as u8, *g as u8, *b as u8])
}
}
}
codes
.iter()
.map(|c| format!("{}", c))
.collect::<Vec<_>>()
.join(";")
}
}
pub struct Renderer {
current_sgr: SGR,
}
impl Renderer {
pub fn new() -> Self {
Self {
current_sgr: SGR::new(),
}
}
pub fn reset(&mut self) {
self.current_sgr = SGR::new();
}
fn write_str(&self, s: &str) {
print!("{}", s);
}
pub fn begin_sync(&self) {
print!("\x1b[?2026h");
}
pub fn end_sync(&self) -> io::Result<()> {
print!("\x1b[?2026l");
io::stdout().flush()
}
fn move_cursor(&self, row: u16, col: u16) {
print!("\x1b[{};{}H", row + 1, col + 1);
}
fn apply_sgr(&mut self, cell: &Cell) -> Option<String> {
let new_sgr = SGR {
bold: cell.attrs.bold,
italic: cell.attrs.italic,
underline: cell.attrs.underline,
strikethrough: cell.attrs.strikethrough,
reversed: cell.attrs.reversed,
fg: Some(cell.fg),
bg: Some(cell.bg),
};
if new_sgr.to_sgr() == self.current_sgr.to_sgr() {
return None; }
self.current_sgr = new_sgr;
Some(format!("\x1b[{}m", self.current_sgr.to_sgr()))
}
fn clear_to_eol(&self) {
print!("\x1b[K");
}
pub fn clear_screen(&self) {
print!("\x1b[2J");
}
pub fn render_full(&mut self, surface: &Surface, use_sync: bool) -> io::Result<()> {
if use_sync {
self.begin_sync();
}
for row in 0..surface.height() {
for col in 0..surface.width() {
if let Some(cell) = surface.get(row, col) {
self.render_cell(row, col, cell);
}
}
}
self.move_cursor(0, 0);
if use_sync {
self.end_sync()?;
}
Ok(())
}
pub fn render_dirty(
&mut self,
surface: &Surface,
first_dirty: u16,
last_dirty: u16,
) -> io::Result<()> {
for row in first_dirty..=last_dirty {
for col in 0..surface.width() {
if surface.is_dirty(row, col) {
if let Some(cell) = surface.get(row, col) {
self.render_cell(row, col, cell);
}
}
}
}
Ok(())
}
pub fn render_cell(&mut self, row: u16, col: u16, cell: &Cell) {
self.move_cursor(row, col);
if let Some(sgr) = self.apply_sgr(cell) {
self.write_str(&sgr);
}
self.write_str(&cell.char.to_string());
}
#[allow(dead_code)]
fn render_cell_at(&mut self, row: u16, col: u16, cell: &Cell) {
self.move_cursor(row, col);
if let Some(sgr) = self.apply_sgr(cell) {
self.write_str(&sgr);
}
self.write_str(&cell.char.to_string());
self.clear_to_eol();
}
pub fn render_changed_lines(
&mut self,
surface: &Surface,
first_dirty: u16,
last_dirty: u16,
) -> io::Result<()> {
for row in first_dirty..=last_dirty {
self.move_cursor(row, 0);
let mut any_dirty = false;
for col in 0..surface.width() {
if surface.is_dirty(row, col) {
any_dirty = true;
break;
}
}
if !any_dirty {
continue;
}
self.current_sgr = SGR::reset();
for col in 0..surface.width() {
if let Some(cell) = surface.get(row, col) {
if let Some(sgr) = self.apply_sgr(cell) {
self.write_str(&sgr);
}
self.write_str(&cell.char.to_string());
}
}
self.clear_to_eol();
}
if let Some(_cell) = surface.get(first_dirty, 0) {
self.move_cursor(first_dirty, 0);
}
Ok(())
}
}
impl Default for Renderer {
fn default() -> Self {
Self::new()
}
}
pub trait RenderToSurface {
fn to_ansi(&self) -> String;
}
impl RenderToSurface for Cell {
fn to_ansi(&self) -> String {
let sgr = SGR {
bold: self.attrs.bold,
italic: self.attrs.italic,
underline: self.attrs.underline,
strikethrough: self.attrs.strikethrough,
reversed: self.attrs.reversed,
fg: Some(self.fg),
bg: Some(self.bg),
};
format!("\x1b[{}m{}\x1b[0m", sgr.to_sgr(), self.char)
}
}