use crate::printer::*;
use std::{io, io::Write};
mod tests;
pub trait DynamicPrinter {
fn dynamic_print(&mut self, new_grid: String) -> Result<(), PrintingError>;
fn clear_grid(&mut self) -> Result<(), PrintingError>;
}
impl DynamicPrinter for Printer {
fn dynamic_print(&mut self, new_grid: String) -> Result<(), PrintingError> {
let terminal_dimensions = Printer::get_terminal_dimensions()?;
let new_grid_dimensions = Self::get_rectangular_dimensions(&new_grid)?;
if new_grid_dimensions.0 > terminal_dimensions.0
|| new_grid_dimensions.1 > terminal_dimensions.1
{
return Err(PrintingError::GridLargerThanTerminal);
}
if let Ok((old_grid_width, old_grid_height)) = self.get_grid_dimensions() {
if old_grid_width != new_grid_dimensions.0 || old_grid_height != new_grid_dimensions.1 {
self.printing_position_changed_since_last_print = true;
}
}
if let Ok((old_terminal_width, old_terminal_height)) =
self.get_terminal_dimensions_from_previous_print()
{
if old_terminal_width != terminal_dimensions.0 || old_terminal_height != terminal_dimensions.1
{
self.printing_position_changed_since_last_print = true;
}
}
if !self.previous_grid.is_empty() && !self.printing_position_changed_since_last_print {
let new_origin = self.get_new_origin(new_grid_dimensions, terminal_dimensions);
self.update_origin(new_origin);
let printable_difference = self.get_printable_difference(&new_grid)?;
print!("{}", printable_difference);
} else if self.printing_position_changed_since_last_print {
self.replace_currently_printed_grid(
&new_grid,
Some(new_grid_dimensions),
terminal_dimensions,
)?;
} else {
let new_origin = self.get_new_origin(new_grid_dimensions, terminal_dimensions);
self.update_origin(new_origin);
print_grid_freestanding(&new_grid, new_origin)?;
}
let _ = io::stdout().flush();
self.previous_grid = new_grid;
self.update_dimensions(new_grid_dimensions);
self.update_terminal_dimensions_from_previous_print(terminal_dimensions);
self.printing_position_changed_since_last_print = false;
Ok(())
}
fn clear_grid(&mut self) -> Result<(), PrintingError> {
let (grid_width, grid_height) = self.get_grid_dimensions()?;
Self::clear_space_on_terminal((grid_width, grid_height), self.get_origin_position()?)?;
self.previous_grid = Self::create_grid_from_single_character(' ', grid_width, grid_height);
Ok(())
}
}
trait DynamicPrinterMethods {
fn get_printable_difference(&self, grid: &str) -> Result<String, PrintingError>;
fn move_to_origin(&self) -> Result<(), PrintingError>;
fn get_new_origin(
&self,
new_grid_dimensions: (usize, usize),
terminal_dimensions: (usize, usize),
) -> (usize, usize);
fn replace_currently_printed_grid(
&mut self,
new_grid: &str,
new_grid_dimensions: Option<(usize, usize)>,
terminal_dimensions: (usize, usize),
) -> Result<(), PrintingError>;
fn clear_space_on_terminal(
clearing_dimensions: (usize, usize),
top_left_position: (usize, usize),
) -> Result<(), PrintingError>;
}
impl DynamicPrinterMethods for Printer {
fn get_printable_difference(&self, grid: &str) -> Result<String, PrintingError> {
let old_grid = self.previous_grid.replace('\n', "");
let new_grid = grid.replace('\n', "");
let grid_size = new_grid.chars().count();
let (origin_x, origin_y) = self.get_origin_position()?;
let (grid_width, _) = self.get_grid_dimensions()?;
let mut last_appended_pixel_index = 1000000;
let mut latest_pixel_index = 1000000;
let mut printable_difference = String::new();
old_grid.chars().zip(new_grid.chars()).enumerate().for_each(
|(pixel_index, (old_pixel, new_pixel))| {
if new_pixel == old_pixel {
return;
}
if pixel_index != 0
&& (last_appended_pixel_index == pixel_index - 1 || latest_pixel_index == pixel_index - 1)
&& (pixel_index % grid_width != 0 || pixel_index == grid_size - 1)
{
printable_difference.push(new_pixel);
last_appended_pixel_index = pixel_index;
} else {
let mut index_as_coords = pixel_index.index_as_coordinates(&grid_width);
index_as_coords.0 += origin_x;
index_as_coords.1 += origin_y;
latest_pixel_index = pixel_index;
printable_difference.push_str(&format!(
"\x1B[{};{}H{}",
index_as_coords.1, index_as_coords.0, new_pixel
));
}
},
);
Ok(printable_difference)
}
fn move_to_origin(&self) -> Result<(), PrintingError> {
let (x, y) = self.get_origin_position()?;
print!("\x1B[{};{}H", y, x);
Ok(())
}
fn get_new_origin(
&self,
(grid_width, grid_height): (usize, usize),
(terminal_width, terminal_height): (usize, usize),
) -> (usize, usize) {
let printing_position = self.get_current_printing_position();
let x: usize = match printing_position.x_printing_position {
XPrintingPosition::Left => 1,
XPrintingPosition::Middle => calculate_grid_center_placement(grid_width, terminal_width),
XPrintingPosition::Right => {
calculate_grid_positive_border_placement(grid_width, terminal_width)
}
XPrintingPosition::Custom(cursor_x_position) => {
calculate_custom_grid_position(grid_width, terminal_width, cursor_x_position)
}
};
let y: usize = match printing_position.y_printing_position {
YPrintingPosition::Top => 1,
YPrintingPosition::Middle => calculate_grid_center_placement(grid_height, terminal_height),
YPrintingPosition::Bottom => {
calculate_grid_positive_border_placement(grid_height, terminal_height)
}
YPrintingPosition::Custom(cursor_y_position) => {
calculate_custom_grid_position(grid_height, terminal_height, cursor_y_position)
}
};
(x, y)
}
fn replace_currently_printed_grid(
&mut self,
new_grid: &str,
new_grid_dimensions: Option<(usize, usize)>,
terminal_dimensions: (usize, usize),
) -> Result<(), PrintingError> {
let (new_grid_width, new_grid_height) = if let Some(new_grid_dimensions) = new_grid_dimensions {
new_grid_dimensions
} else {
Self::get_rectangular_dimensions(new_grid)?
};
let _ = self.clear_grid();
let new_origin = self.get_new_origin((new_grid_width, new_grid_height), terminal_dimensions);
self.update_dimensions((new_grid_width, new_grid_height));
self.update_origin(new_origin);
print_grid_freestanding(new_grid, new_origin)?;
Ok(())
}
fn clear_space_on_terminal(
clearing_dimensions: (usize, usize),
top_left_position: (usize, usize),
) -> Result<(), PrintingError> {
let empty_grid =
Self::create_grid_from_single_character(' ', clearing_dimensions.0, clearing_dimensions.1);
print_grid_freestanding(&empty_grid, top_left_position)
}
}
fn print_grid_freestanding(
grid: &str,
printing_position: (usize, usize),
) -> Result<(), PrintingError> {
Printer::get_rectangular_dimensions(grid)?;
let mut grid_with_cursor_movements = String::new();
let cursor_movement = format!("\x1B[1B\x1B[{}G", printing_position.0);
for grid_row in grid.split('\n') {
grid_with_cursor_movements.push_str(grid_row);
grid_with_cursor_movements.push_str(&cursor_movement);
}
print!("\x1B[{};{}H", printing_position.1, printing_position.0);
print!("{}", grid_with_cursor_movements);
Ok(())
}
fn calculate_grid_center_placement(grid_length: usize, terminal_length: usize) -> usize {
((terminal_length as f32 / 2.0).floor() - (grid_length as f32 / 2.0).floor()) as usize
}
fn calculate_grid_positive_border_placement(grid_length: usize, terminal_length: usize) -> usize {
((terminal_length as isize - grid_length as isize).max(0) + 1) as usize
}
fn calculate_custom_grid_position(
grid_length: usize,
terminal_length: usize,
grid_placement: usize,
) -> usize {
let grid_placement = grid_placement.max(1);
grid_placement
- ((grid_placement + grid_length) as isize - terminal_length as isize).max(0) as usize
}
trait VecMethods<T> {
fn get_mut_top(&mut self) -> Option<&mut T>;
}
impl<T> VecMethods<T> for std::vec::Vec<T> {
fn get_mut_top(&mut self) -> Option<&mut T> {
let item_count = self.len();
if !self.is_empty() {
self.get_mut(item_count - 1)
} else {
None
}
}
}
trait UsizeMethods {
fn index_as_coordinates(&self, grid_width: &Self) -> (usize, usize);
}
impl UsizeMethods for usize {
fn index_as_coordinates(&self, grid_width: &Self) -> (usize, usize) {
(self % grid_width, self / grid_width)
}
}