pub use crate::dynamic_printer::*;
pub use crate::errors::*;
pub use crate::printing_position::*;
use std::cmp::Ordering;
use std::fmt;
use std::{io, io::Write};
#[derive(Default, Debug)]
pub struct Printer {
pub(crate) previous_grid: String,
origin_position: Option<(usize, usize)>,
grid_height: Option<usize>,
grid_width: Option<usize>,
previous_terminal_dimensions: Option<(usize, usize)>,
printing_position: PrintingPosition,
pub(crate) printing_position_changed_since_last_print: bool,
}
impl Printer {
pub fn new() -> Self {
Self {
..Default::default()
}
}
pub fn new_with_printing_position(printing_position: PrintingPosition) -> Self {
Self {
printing_position,
..Default::default()
}
}
pub fn replace_printing_position(&mut self, printing_position: PrintingPosition) {
self.printing_position = printing_position;
self.printing_position_changed_since_last_print = true;
}
pub fn replace_x_printing_position(
&mut self,
new_x_printing_position: XPrintingPosition,
) -> Result<(), PrintingError> {
self.printing_position.x_printing_position = new_x_printing_position;
self.printing_position_changed_since_last_print = true;
Ok(())
}
pub fn replace_y_printing_position(
&mut self,
new_y_printing_position: YPrintingPosition,
) -> Result<(), PrintingError> {
self.printing_position.y_printing_position = new_y_printing_position;
self.printing_position_changed_since_last_print = true;
Ok(())
}
pub fn get_current_printing_position(&self) -> &PrintingPosition {
&self.printing_position
}
pub fn create_grid_from_single_character(character: char, width: usize, height: usize) -> String {
let pixel_row = character.to_string().repeat(width) + "\n";
let mut frame = pixel_row.repeat(height);
frame.pop();
frame
}
pub fn create_grid_from_full_character_list<T>(
characters: &Vec<T>,
width: usize,
height: usize,
) -> Result<String, PrintingError>
where
T: fmt::Display,
{
let grid_size = width * height;
match characters.len().cmp(&grid_size) {
Ordering::Less => Err(PrintingError::TooLittleCharacters(LengthErrorData::new(
characters.len(),
grid_size,
))),
Ordering::Greater => Err(PrintingError::TooManyCharacters(LengthErrorData::new(
characters.len(),
grid_size,
))),
Ordering::Equal => Ok(create_grid_from_characters(characters, width)),
}
}
pub fn print_over_previous_grid(grid: String, height: usize) {
print!("\x1b[{};A", height - 1);
print!("\r{grid}");
let _ = io::stdout().flush();
}
pub(crate) fn get_grid_dimensions(&self) -> Result<(usize, usize), PrintingError> {
let (Some(width), Some(height)) = (self.grid_width, self.grid_height) else {
return Err(PrintingError::GridDimensionsNotDefined);
};
Ok((width, height))
}
pub(crate) fn get_origin_position(&self) -> Result<(usize, usize), PrintingError> {
self.origin_position.ok_or(PrintingError::OriginNotDefined)
}
pub(crate) fn get_terminal_dimensions_from_previous_print(
&self,
) -> Result<(usize, usize), PrintingError> {
self
.previous_terminal_dimensions
.ok_or(PrintingError::TerminalDimensionsNotDefined)
}
pub fn get_rectangular_dimensions(
rectangle_shape: &str,
) -> Result<(usize, usize), PrintingError> {
if rectangle_shape.is_empty() {
return Err(PrintingError::NonRectangularGrid);
}
let rows: Vec<&str> = rectangle_shape.split('\n').collect();
let model_width = rows[0].chars().count();
let rows_have_same_lengths = rows.iter().all(|row| row.chars().count() == model_width);
if rows_have_same_lengths {
Ok((model_width, rows.len()))
} else {
Err(PrintingError::NonRectangularGrid)
}
}
pub fn is_rectangular(rectangle_shape: &str) -> bool {
Self::get_rectangular_dimensions(rectangle_shape).is_ok()
}
pub fn get_terminal_dimensions() -> Result<(usize, usize), PrintingError> {
match termion::terminal_size() {
Ok(terminal_dimensions) => Ok((
terminal_dimensions.0 as usize,
terminal_dimensions.1 as usize,
)),
Err(io_error) => Err(PrintingError::FailedToGetTerminalDimensions(
io_error.to_string(),
)),
}
}
pub fn reset(&mut self) {
*self = Printer::default()
}
pub fn reset_and_retain_printing_position(&mut self) {
*self = Printer {
printing_position: std::mem::take(&mut self.printing_position),
..Default::default()
};
}
pub fn reset_with_position(&mut self, printing_position: PrintingPosition) {
*self = Printer {
printing_position,
..Default::default()
}
}
pub fn pad_rows_for_rectangle(grid: &mut String) {
let Some(largest_row) = grid.split('\n').max_by_key(|row| row.chars().count()) else {
return;
};
let largest_row_size = largest_row.chars().count();
let padded_grid: String = grid
.lines()
.map(|row| {
let padding = " ".repeat(largest_row_size - row.chars().count());
format!("{row}{padding}")
})
.collect::<Vec<String>>()
.join("\n");
*grid = padded_grid;
}
pub(crate) fn update_origin(&mut self, new_origin: (usize, usize)) {
if let Ok(current_origin) = self.get_origin_position() {
if current_origin.0 != new_origin.0 || current_origin.1 != new_origin.1 {
self.printing_position_changed_since_last_print = true;
}
}
self.origin_position = Some(new_origin);
}
pub(crate) fn update_dimensions(&mut self, new_dimensions: (usize, usize)) {
if let Ok(current_dimensions) = self.get_grid_dimensions() {
if current_dimensions.0 != new_dimensions.0 || current_dimensions.1 != new_dimensions.1 {
self.printing_position_changed_since_last_print = true;
}
}
self.grid_width = Some(new_dimensions.0);
self.grid_height = Some(new_dimensions.1);
}
pub(crate) fn update_terminal_dimensions_from_previous_print(
&mut self,
new_terminal_dimensions: (usize, usize),
) {
if let Ok(current_dimensions) = self.get_terminal_dimensions_from_previous_print() {
if current_dimensions.0 != new_terminal_dimensions.0
|| current_dimensions.1 != new_terminal_dimensions.1
{
self.printing_position_changed_since_last_print = true;
}
}
self.previous_terminal_dimensions = Some(new_terminal_dimensions);
}
}
fn create_grid_from_characters<T: fmt::Display>(characters: &[T], width: usize) -> String {
characters
.chunks(width)
.map(|row| {
row.iter().fold(String::new(), |mut row, character| {
row.push_str(format!("{character}").as_str());
row
})
})
.collect::<Vec<String>>()
.join("\n")
}