use crate::{
drawable::Pixel,
geometry::{Point, Size},
pixelcolor::{BinaryColor, Gray8, GrayColor, PixelColor, Rgb888, RgbColor},
DrawTarget,
};
use core::{
cmp::PartialEq,
fmt::{self, Write},
iter,
};
const SIZE: usize = 64;
#[derive(Copy, Clone)]
pub struct MockDisplay<C>([Option<C>; SIZE * SIZE])
where
C: PixelColor;
impl<C> MockDisplay<C>
where
C: PixelColor,
{
pub fn new() -> Self {
Self::default()
}
pub fn width(&self) -> usize {
SIZE
}
pub fn height(&self) -> usize {
SIZE
}
pub fn get_pixel(&self, p: Point) -> Option<C> {
let Point { x, y } = p;
self.0[x as usize + y as usize * SIZE]
}
pub fn set_pixel(&mut self, p: Point, color: Option<C>) {
let Point { x, y } = p;
self.0[x as usize + y as usize * SIZE] = color;
}
}
impl<C> MockDisplay<C>
where
C: PixelColor + ColorMapping<C>,
{
pub fn from_pattern(pattern: &[&str]) -> MockDisplay<C> {
let pattern_width = pattern.first().map_or(0, |row| row.len());
let pattern_height = pattern.len();
assert!(pattern_width <= SIZE);
assert!(pattern_height <= SIZE);
for row in pattern {
assert_eq!(row.len(), pattern_width);
}
let pattern_colors = pattern
.iter()
.flat_map(|row| {
row.chars()
.map(|c| match c {
' ' => None,
_ => Some(C::char_to_color(c)),
})
.chain(iter::repeat(None))
.take(SIZE)
})
.chain(iter::repeat(None))
.take(SIZE * SIZE);
let mut display = MockDisplay::new();
for (i, color) in pattern_colors.enumerate() {
display.0[i] = color;
}
display
}
}
impl<C> Default for MockDisplay<C>
where
C: PixelColor,
{
fn default() -> Self {
Self([None; SIZE * SIZE])
}
}
impl<C> fmt::Debug for MockDisplay<C>
where
C: PixelColor + ColorMapping<C>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let empty_rows = self
.0
.rchunks(SIZE)
.take_while(|row| row.iter().all(Option::is_none))
.count();
writeln!(f, "MockDisplay[")?;
for row in self.0.chunks(SIZE).take(SIZE - empty_rows) {
for color in row {
f.write_char(color.map_or(' ', C::color_to_char))?;
}
writeln!(f)?;
}
if empty_rows > 0 {
writeln!(f, "({} empty rows skipped)", empty_rows)?;
}
writeln!(f, "]")?;
Ok(())
}
}
impl<C> PartialEq for MockDisplay<C>
where
C: PixelColor,
{
fn eq(&self, other: &MockDisplay<C>) -> bool {
self.0.iter().eq(other.0.iter())
}
}
impl<C> DrawTarget<C> for MockDisplay<C>
where
C: PixelColor,
{
type Error = core::convert::Infallible;
fn draw_pixel(&mut self, pixel: Pixel<C>) -> Result<(), Self::Error> {
let Pixel(Point { x, y }, color) = pixel;
if !(0..SIZE).contains(&(x as usize)) || !(0..SIZE).contains(&(y as usize)) {
return Ok(());
}
let i = x + y * SIZE as i32;
self.0[i as usize] = Some(color);
Ok(())
}
fn size(&self) -> Size {
Size::new(self.width() as u32, self.height() as u32)
}
}
pub trait ColorMapping<C> {
fn char_to_color(c: char) -> C;
fn color_to_char(color: C) -> char;
}
impl ColorMapping<BinaryColor> for BinaryColor {
fn char_to_color(c: char) -> Self {
match c {
'.' => BinaryColor::Off,
'#' => BinaryColor::On,
_ => panic!("Invalid char in pattern: '{}'", c),
}
}
fn color_to_char(color: BinaryColor) -> char {
match color {
BinaryColor::Off => '.',
BinaryColor::On => '#',
}
}
}
impl ColorMapping<Gray8> for Gray8 {
fn char_to_color(c: char) -> Self {
let digit = match c {
'0'..='9' | 'A'..='F' => c.to_digit(16).unwrap(),
_ => panic!("Invalid char in pattern: '{}'", c),
};
Gray8::new(digit as u8 * 0x11)
}
fn color_to_char(color: Gray8) -> char {
let luma = color.luma();
let lower = luma & 0xF;
let upper = luma >> 4;
if lower != upper {
'?'
} else {
core::char::from_digit(lower as u32, 16)
.unwrap()
.to_ascii_uppercase()
}
}
}
impl ColorMapping<Rgb888> for Rgb888 {
fn char_to_color(c: char) -> Self {
match c {
'K' => Rgb888::BLACK,
'R' => Rgb888::RED,
'G' => Rgb888::GREEN,
'B' => Rgb888::BLUE,
'Y' => Rgb888::YELLOW,
'M' => Rgb888::MAGENTA,
'C' => Rgb888::CYAN,
'W' => Rgb888::WHITE,
_ => panic!("Invalid char in pattern: '{}'", c),
}
}
fn color_to_char(color: Rgb888) -> char {
match color {
Rgb888::BLACK => 'K',
Rgb888::RED => 'R',
Rgb888::GREEN => 'G',
Rgb888::BLUE => 'B',
Rgb888::YELLOW => 'Y',
Rgb888::MAGENTA => 'M',
Rgb888::CYAN => 'C',
Rgb888::WHITE => 'W',
_ => '?',
}
}
}