#![warn(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TerminalPixel {
pub color: Color,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompositedCell {
pub top_color: Color,
pub bottom_color: Color,
}
pub struct Canvas {
pub width: usize,
pub height: usize,
pixels: Vec<TerminalPixel>,
composited_cells: Vec<CompositedCell>,
previous_composited_cells: Vec<CompositedCell>,
pub default_color: Color,
max_z_layers: usize,
}
impl Canvas {
const DEFAULT_MAX_Z_LAYERS: usize = 10;
pub fn new(width: usize, height: usize, default_color: Color) -> Self {
let initial_pixel = TerminalPixel {
color: default_color,
};
let initial_composited_cell = CompositedCell {
top_color: default_color,
bottom_color: default_color,
};
let opposite_color = Color {
r: 255 - default_color.r,
g: 255 - default_color.g,
b: 255 - default_color.b,
};
let different_composited_cell = CompositedCell {
top_color: opposite_color,
bottom_color: opposite_color,
};
let total_half_block_pixels = width * height * 2 * Self::DEFAULT_MAX_Z_LAYERS;
let total_terminal_cells = width * height;
Self {
width,
height,
pixels: vec![initial_pixel; total_half_block_pixels],
composited_cells: vec![initial_composited_cell; total_terminal_cells],
previous_composited_cells: vec![different_composited_cell; total_terminal_cells],
default_color,
max_z_layers: Self::DEFAULT_MAX_Z_LAYERS,
}
}
pub fn clear(&mut self) {
let initial_pixel = TerminalPixel {
color: self.default_color,
};
for pixel in self.pixels.iter_mut() {
*pixel = initial_pixel;
}
}
fn get_index(&self, x: usize, y: usize, z: usize) -> Option<usize> {
if x >= self.width { return None; }
if y >= self.height * 2 { return None; } if z >= self.max_z_layers { return None; }
Some(x + (y * self.width) + (z * self.width * self.height * 2))
}
pub fn set_pixel(&mut self, x: usize, y: usize, z: usize, color: Color) {
if let Some(index) = self.get_index(x, y, z) {
let pixel = &mut self.pixels[index];
pixel.color = color;
}
}
pub fn render(&mut self) -> String {
let mut buffer = String::new();
for terminal_cell_y in 0..self.height {
for terminal_cell_x in 0..self.width {
let top_half_pixel_y = terminal_cell_y * 2;
let bottom_half_pixel_y = terminal_cell_y * 2 + 1;
let mut current_top_color = self.default_color;
let mut current_bottom_color = self.default_color;
for z in (0..self.max_z_layers).rev() {
if let Some(index) = self.get_index(terminal_cell_x, top_half_pixel_y, z) {
let pixel = &self.pixels[index];
if pixel.color != self.default_color {
current_top_color = pixel.color;
break;
}
}
}
for z in (0..self.max_z_layers).rev() {
if let Some(index) = self.get_index(terminal_cell_x, bottom_half_pixel_y, z) {
let pixel = &self.pixels[index];
if pixel.color != self.default_color {
current_bottom_color = pixel.color;
break;
}
}
}
let current_composited_cell = CompositedCell {
top_color: current_top_color,
bottom_color: current_bottom_color,
};
let terminal_cell_index = terminal_cell_y * self.width + terminal_cell_x;
if current_composited_cell != self.previous_composited_cells[terminal_cell_index] {
buffer.push_str(&format!("\u{1b}[{};{}H", terminal_cell_y + 1, terminal_cell_x + 1));
buffer.push_str(&format!(
"\u{1b}[48;2;{};{};{}m\u{1b}[38;2;{};{};{}m▄",
current_top_color.r,
current_top_color.g,
current_top_color.b,
current_bottom_color.r,
current_bottom_color.g,
current_bottom_color.b
));
}
self.composited_cells[terminal_cell_index] = current_composited_cell;
}
}
self.previous_composited_cells = self.composited_cells.clone();
buffer
}
}