use super::Rect;
use std::{collections::HashMap, hash::Hash, io::Write};
use crossterm::{
cursor, queue,
style::{self, ContentStyle},
};
use itertools::Itertools;
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Pixel<T: Default + Clone + Copy + PartialEq> {
pub text: Option<char>,
pub style: T,
}
impl<T: Default + Clone + Copy + PartialEq> Default for Pixel<T> {
fn default() -> Self {
Self {
text: None,
style: T::default(),
}
}
}
pub struct Screen<T: Default + Clone + Copy + PartialEq + Eq + Hash> {
pub width: u16,
pub height: u16,
pixels: Vec<Pixel<T>>,
changes: HashMap<usize, Pixel<T>>,
pub styles: HashMap<T, ContentStyle>,
}
impl<T: Default + Clone + Copy + PartialEq + Eq + Hash> Default for Screen<T> {
fn default() -> Self {
Self {
width: 0,
height: 0,
pixels: Vec::new(),
changes: HashMap::new(),
styles: HashMap::new(),
}
}
}
impl<T: Default + Hash + Clone + Copy + PartialEq + Eq> Screen<T> {
pub fn set_styles(&mut self, styles: HashMap<T, ContentStyle>) {
self.styles = styles;
}
pub fn resize(&mut self, width: u16, height: u16) {
self.width = width;
self.height = height;
self.pixels = (0..width * height).map(|_| Pixel::default()).collect();
}
pub fn draw_changes<W: Write>(&mut self, stdout: &mut W) -> Result<(), crossterm::ErrorKind> {
let mut changes = self.changes.clone();
changes.retain(|k, v| {
if let Some(pixel) = self.pixels.get(*k) {
if *pixel == *v {
false
} else {
true
}
} else {
false
}
});
self.changes = changes;
let changes = self.changes.keys().sorted().collect_vec();
let mut last = None;
let mut i = 0;
while i < changes.len() {
if self.pixels.get(*changes[i]).is_none() {
i += 1;
continue;
}
let start_i = i;
if last.is_none() || last.unwrap() + 1 != *changes[i] {
let (x, y) = self.coord_to_xy(*changes[i]);
queue!(stdout, cursor::MoveTo(x, y))?;
}
let style = self.changes.get(changes[i]).unwrap().style;
while i + 1 < changes.len() {
if changes[i] + 1 != *changes[i + 1]
|| self.pixels.get(*changes[i + 1]).is_none()
|| self.changes.get(changes[i + 1]).unwrap().style != style
{
break;
}
i += 1;
}
for k in &changes[start_i..i + 1] {
self.pixels[**k] = *self.changes.get(*k).unwrap();
}
let range = *changes[start_i]..(*changes[i] + 1);
let text = self.pixels[range]
.iter()
.map(|pixel| pixel.text.unwrap_or(' '))
.collect::<String>();
let style = match self.styles.get(&style) {
Some(s) => *s,
None => ContentStyle::default(),
};
queue!(stdout, style::PrintStyledContent(style.apply(text)))?;
last = Some(*changes[i]);
i += 1;
}
stdout.flush()?;
Ok(())
}
fn coord_to_xy(&self, coord: usize) -> (u16, u16) {
let y = (coord as f32 / self.width as f32).floor() as usize;
let x = coord - (y * self.width as usize);
(x as u16, y as u16)
}
fn xy_to_coord(&self, x: u16, y: u16) -> usize {
(y * self.width + x) as usize
}
pub fn rect(&mut self, rect: Rect, text: char, style: T) {
let text: String = (0..rect.width).map(|_| text).collect();
for y in 0..rect.height {
self.string(rect.x, rect.y + y, text.clone(), style);
}
}
pub fn string(&mut self, x: u16, y: u16, text: String, style: T) {
let coord = self.xy_to_coord(x, y);
for (i, c) in text.chars().enumerate() {
if i + coord >= self.pixels.len() {
break;
}
self.pixel(
coord + i,
Pixel {
text: Some(c),
style,
},
);
}
}
pub fn pixels(&mut self, x: u16, y: u16, pixels: Vec<Pixel<T>>) {
let coord = self.xy_to_coord(x, y);
for (i, p) in pixels.iter().enumerate() {
if i + coord >= self.pixels.len() {
break;
}
self.pixel(coord + i, *p);
}
}
pub fn pixel(&mut self, coord: usize, pixel: Pixel<T>) {
if self.pixels[coord] != pixel {
self.changes.insert(coord, pixel);
} else {
self.changes.remove(&coord);
}
}
}