use std::fmt::{self, Display};
#[cfg(feature = "colored")]
use colored::{self, ColoredString, Colorize};
use hashbrown::HashMap;
use crate::{
block::{base::color::ColorDesc, binary::BinaryColor, Block, Color, Description},
board::{Board, LineDirection},
utils::{
pad, pad_with,
rc::{MutRc, ReadRc, ReadRef},
transpose,
},
};
pub trait Renderer<B>
where
B: Block,
{
fn with_board(board: MutRc<Board<B>>) -> Self;
fn render(&self) -> String;
fn render_simple(&self) -> String;
fn concat(rows: impl Iterator<Item = Vec<String>>) -> String {
let rows: Vec<_> = rows.map(|line| line.concat()).collect();
rows.join("\n")
}
}
#[derive(Debug)]
pub struct ShellRenderer<B>
where
B: Block,
{
board: MutRc<Board<B>>,
}
impl<B> Renderer<B> for ShellRenderer<B>
where
B: Block + Display,
B::Color: Display,
{
fn with_board(board: MutRc<Board<B>>) -> Self {
Self { board }
}
fn render(&self) -> String {
let full_width = self.side_width() + self.board().width();
let mut header = self.header_lines();
for row in &mut header {
pad_with(row, "#".to_string(), full_width, false);
}
let header = header.into_iter().map(|row| {
row.into_iter()
.map(|s| ColoredString::from(s.as_str()))
.collect()
});
let side = self.side_lines();
let side = side
.into_iter()
.map(|row| row.into_iter().map(|s| ColoredString::from(s.as_str())));
let grid = self.grid_lines();
let grid = side.zip(grid).map(|(s, g)| s.chain(g).collect());
Self::concat(header.chain(grid).map(|line: Vec<ColoredString>| {
line.iter().map(|symbol| pad(symbol, 2, true)).collect()
}))
}
fn render_simple(&self) -> String {
Self::concat(
self.grid_lines()
.into_iter()
.map(|row| row.iter().map(ToString::to_string).collect()),
)
}
}
impl<B> ShellRenderer<B>
where
B: Block,
{
fn board(&self) -> ReadRef<'_, Board<B>> {
self.board.read()
}
fn side_width(&self) -> usize {
Self::descriptions_width(self.board().descriptions(LineDirection::Row))
}
fn descriptions_width(descriptions: &[ReadRc<Description<B>>]) -> usize {
descriptions
.iter()
.map(|desc| desc.vec.len())
.max()
.unwrap_or(0)
}
}
impl<B> ShellRenderer<B>
where
B: Block + Display,
{
fn desc_to_string(desc: &ReadRc<Description<B>>) -> Vec<String> {
desc.vec.iter().map(ToString::to_string).collect()
}
fn descriptions_to_matrix(descriptions: &[ReadRc<Description<B>>]) -> Vec<Vec<String>> {
let mut rows: Vec<_> = descriptions.iter().map(Self::desc_to_string).collect();
let width = Self::descriptions_width(descriptions);
for row in &mut rows {
pad_with(row, " ".to_string(), width, false);
}
rows
}
fn side_lines(&self) -> Vec<Vec<String>> {
Self::descriptions_to_matrix(self.board().descriptions(LineDirection::Row))
}
fn header_lines(&self) -> Vec<Vec<String>> {
transpose(&Self::descriptions_to_matrix(
self.board().descriptions(LineDirection::Column),
))
.unwrap()
}
}
#[cfg(not(feature = "colored"))]
type ColoredString = String;
impl From<ColorDesc> for ColoredString {
#[cfg(feature = "colored")]
fn from(color_desc: ColorDesc) -> Self {
let color_res: Result<colored::Color, _> = color_desc.name().parse();
if let Ok(color) = color_res {
" ".on_color(color)
} else {
let symbol = color_desc.symbol();
symbol.as_str().into()
}
}
#[cfg(not(feature = "colored"))]
fn from(color_desc: ColorDesc) -> Self {
color_desc.symbol()
}
}
impl<B> ShellRenderer<B>
where
B: Block,
B::Color: Display,
{
fn cell_symbol(&self, cell: &B::Color) -> ColoredString {
let id = cell.as_color_id();
id.and_then(|color_id| self.board().desc_by_id(color_id).map(From::from))
.unwrap_or_else(|| cell.to_string().as_str().into())
}
fn grid_lines(&self) -> Vec<Vec<ColoredString>> {
let mut color_cache = HashMap::new();
self.board()
.iter_rows()
.map(|row| {
row.iter()
.map(|cell| {
color_cache
.entry(cell)
.or_insert_with(|| self.cell_symbol(cell))
.clone()
})
.collect()
})
.collect()
}
}
impl Display for BinaryColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use BinaryColor::{Black, BlackOrWhite, Undefined, White};
let symbol = match self {
White => '.',
Black => '\u{25A0}',
Undefined | BlackOrWhite => '?',
};
write!(f, "{}", symbol)
}
}