use std::ops::{Mul, MulAssign};
use std::convert::TryFrom;
use derive_builder::Builder;
use downcast_rs::{Downcast, impl_downcast};
use crate::{Error::{self, *}, Result};
#[cfg(feature = "text")]
pub use crate::text::{Caption, CaptionBuilder, FontBuilder, Alignment};
#[cfg(feature = "images")]
pub use crate::image::Image;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
impl Mul<f32> for Color {
type Output = Self;
fn mul(self, coeff: f32) -> Self {
Self {
red: (self.red as f32 * coeff) as u8,
green: (self.green as f32 * coeff) as u8,
blue: (self.blue as f32 * coeff) as u8,
alpha: self.alpha
}
}
}
impl MulAssign<f32> for Color {
fn mul_assign(&mut self, coeff: f32) {
self.red = ((self.red as f32) * coeff) as u8;
self.green = ((self.green as f32) * coeff) as u8;
self.blue = ((self.blue as f32) * coeff) as u8;
}
}
impl Color {
pub fn hex(color_string: &str) -> Result<Self> {
if color_string.len() != 7 && color_string.len() != 9 {
return Err(InvalidColorString(color_string.into(), "length must be 7 or 9"));
}
if color_string.chars().next() != Some('#') {
return Err(InvalidColorString(color_string.into(), "first char must be #"));
}
if !color_string.chars().skip(1).all(|c| c.is_ascii_hexdigit()) {
return Err(InvalidColorString(color_string.into(), "all characters but first must be hex"));
}
Ok(Self {
red: u8::from_str_radix(&color_string[1..3], 16).unwrap(),
green: u8::from_str_radix(&color_string[3..5], 16).unwrap(),
blue: u8::from_str_radix(&color_string[5..7], 16).unwrap(),
alpha: if color_string.len() == 9 {
u8::from_str_radix(&color_string[7..9], 16).unwrap()
} else {
255
}
})
}
}
impl From<(u8, u8, u8)> for Color {
fn from(rgb: (u8, u8, u8)) -> Self {
Self { red: rgb.0, green: rgb.1, blue: rgb.2, alpha: 255 }
}
}
impl From<(u8, u8, u8, u8)> for Color {
fn from(rgba: (u8, u8, u8, u8)) -> Self {
Self { red: rgba.0, green: rgba.1, blue: rgba.2, alpha: rgba.3 }
}
}
impl TryFrom<&str> for Color {
type Error = Error;
fn try_from(color_string: &str) -> Result<Self> {
Self::hex(color_string)
}
}
pub trait Shape: Downcast {
fn render(&self) -> Vec<Vec<Option<Color>>>;
fn at(self, x: usize, y: usize) -> PositionedShape
where Self: Sized + 'static {
PositionedShape { x, y, shape: Box::new(self) }
}
}
impl_downcast!(Shape);
pub struct PositionedShape {
pub x: usize,
pub y: usize,
pub shape: Box<dyn Shape + 'static>,
}
impl PositionedShape {
pub fn new<T: Shape + 'static>(x: usize, y: usize, shape: T) -> Self {
Self { x, y, shape: Box::new(shape) }
}
pub fn inner<T: Shape + 'static>(&self) -> Option<&T> {
self.shape.downcast_ref()
}
pub fn inner_mut<T: Shape + 'static>(&mut self) -> Option<&mut T> {
self.shape.downcast_mut()
}
}
#[derive(Debug, Builder)]
pub struct Rectangle {
pub width: usize,
pub height: usize,
#[builder(default = "1")]
pub border_width: usize,
#[builder(setter(into, strip_option), default)]
pub border_color: Option<Color>,
#[builder(setter(into, strip_option), default)]
pub fill_color: Option<Color>,
}
impl Rectangle {
pub fn builder() -> RectangleBuilder {
RectangleBuilder::default()
}
}
impl Shape for Rectangle {
fn render(&self) -> Vec<Vec<Option<Color>>> {
(0..self.height)
.map(|y| {
(0..self.width)
.map(|x| {
if x < self.border_width
|| x >= self.width - self.border_width
|| y < self.border_width
|| y >= self.height - self.border_width
{
self.border_color
} else {
self.fill_color
}
})
.collect()
})
.collect()
}
}