use std::{
fmt::{self, Display, Write},
io::{self, Error, Write as ioWrite},
usize,
};
pub mod colchar;
pub mod utils;
pub mod vec2d;
pub use colchar::{ColChar, Modifier};
pub use utils::Wrapping;
pub use vec2d::Vec2D;
pub struct View {
pub width: usize,
pub height: usize,
pub background_char: ColChar,
pixels: Vec<ColChar>,
terminal_prepared: bool,
}
impl From<&View> for Vec2D {
fn from(value: &View) -> Self {
Vec2D {
x: isize::try_from(value.width).expect("Failed to convert View.width to isize"),
y: isize::try_from(value.height).expect("Failed to convert View.height to isize"),
}
}
}
impl View {
pub fn new(width: usize, height: usize, background_char: ColChar) -> View {
let mut view = View {
width,
height,
background_char,
pixels: Vec::new(),
terminal_prepared: false,
};
view.clear();
let _ = view.prepare_terminal(); view
}
fn prepare_terminal(&mut self) -> Result<(), Error> {
if !self.terminal_prepared {
let rows = termsize::get()
.ok_or(Error::new(
std::io::ErrorKind::NotFound,
"Couldnt get termsize",
))?
.rows;
let rows_us = usize::try_from(rows).expect("u16 couldnt convert to usize");
println!(
"{}",
vec!['\n'; rows_us].iter().cloned().collect::<String>()
);
}
self.terminal_prepared = true;
Ok(())
}
pub fn center(&self) -> Vec2D {
Vec2D::new((self.width / 2) as isize, (self.height / 2) as isize)
}
pub fn clear(&mut self) {
self.pixels = vec![self.background_char; self.width * self.height]
}
pub fn plot(&mut self, pos: Vec2D, c: ColChar, wrapping: Wrapping) {
let mut pos = pos;
let in_bounds_pos = pos.clone() % (Vec2D::from(&*self));
match wrapping {
Wrapping::Wrap => pos = in_bounds_pos,
Wrapping::Ignore => {
if pos.x < 0 || pos.y < 0 || pos != in_bounds_pos {
return;
}
}
Wrapping::Panic => {
if pos.x < 0 || pos.y < 0 || pos != in_bounds_pos {
panic!("{} is not within the view's boundaries", pos);
}
}
}
let ux = pos.x as usize;
let uy = pos.y as usize;
self.pixels[self.width * uy + ux] = c;
}
pub fn blit<T: ViewElement>(&mut self, element: &T, wrapping: Wrapping) {
let active_pixels = element.active_pixels();
for point in active_pixels {
self.plot(point.pos, point.fill_char, wrapping);
}
}
pub fn display_render(&self) -> io::Result<()> {
let mut stdout = io::stdout().lock();
write!(stdout, "{self}")
}
}
impl Display for View {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\x1b[H\x1b[J")?;
for y in 0..self.height {
for pixel in self.pixels[self.width * y..self.width * (y + 1)].iter() {
write!(f, "{pixel}")?;
}
f.write_char('\n')?;
}
f.write_str("\x1b[J")?;
Ok(())
}
}
#[derive(Debug, Copy)]
pub struct Point {
pub pos: Vec2D,
pub fill_char: ColChar,
}
impl Point {
pub fn new(pos: Vec2D, fill_char: ColChar) -> Self {
Self { pos, fill_char }
}
}
impl Clone for Point {
fn clone(&self) -> Self {
Self {
pos: self.pos,
fill_char: self.fill_char,
}
}
}
impl From<(Vec2D, ColChar)> for Point {
fn from(value: (Vec2D, ColChar)) -> Self {
Self {
pos: value.0,
fill_char: value.1,
}
}
}
impl ViewElement for Point {
fn active_pixels(&self) -> Vec<Point> {
vec![*self]
}
}
pub trait ViewElement {
fn active_pixels(&self) -> Vec<Point>;
}