use std::{error, fmt};
use crate::{
linspace, BasisGradient, BlendMode, CatmullRomGradient, Color, Gradient, GradientBase,
Interpolation, LinearGradient,
};
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum CustomGradientError {
InvalidHtmlColor(Vec<String>),
WrongDomainCount,
WrongDomain,
}
impl fmt::Display for CustomGradientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InvalidHtmlColor(ref colors) => {
write!(
f,
"invalid html colors: {}",
colors
.iter()
.map(|x| format!("'{}'", x))
.collect::<Vec<String>>()
.join(", ")
)
}
Self::WrongDomainCount => f.write_str("wrong domain count"),
Self::WrongDomain => f.write_str("wrong domain"),
}
}
}
impl error::Error for CustomGradientError {}
#[derive(Debug, Clone)]
pub struct CustomGradient {
colors: Vec<Color>,
pos: Vec<f64>,
mode: BlendMode,
interpolation: Interpolation,
invalid_html_colors: Vec<String>,
}
impl CustomGradient {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
colors: Vec::new(),
pos: Vec::new(),
mode: BlendMode::Rgb,
interpolation: Interpolation::Linear,
invalid_html_colors: Vec::new(),
}
}
pub fn colors<'a>(&'a mut self, colors: &[Color]) -> &'a mut Self {
for c in colors {
self.colors.push(c.clone());
}
self
}
pub fn html_colors<'a>(&'a mut self, html_colors: &[&str]) -> &'a mut Self {
for s in html_colors {
if let Ok(c) = csscolorparser::parse(s) {
self.colors.push(c);
} else {
self.invalid_html_colors.push(s.to_string());
}
}
self
}
pub fn domain<'a>(&'a mut self, pos: &[f64]) -> &'a mut Self {
self.pos = pos.to_vec();
self
}
#[allow(clippy::needless_lifetimes)]
pub fn mode<'a>(&'a mut self, mode: BlendMode) -> &'a mut Self {
self.mode = mode;
self
}
#[allow(clippy::needless_lifetimes)]
pub fn interpolation<'a>(&'a mut self, mode: Interpolation) -> &'a mut Self {
self.interpolation = mode;
self
}
pub fn build(&self) -> Result<Gradient, CustomGradientError> {
if !self.invalid_html_colors.is_empty() {
return Err(CustomGradientError::InvalidHtmlColor(
self.invalid_html_colors.clone(),
));
}
let colors = if self.colors.is_empty() {
vec![
Color::new(0.0, 0.0, 0.0, 1.0),
Color::new(1.0, 1.0, 1.0, 1.0),
]
} else if self.colors.len() == 1 {
vec![self.colors[0].clone(), self.colors[0].clone()]
} else {
self.colors.to_vec()
};
let pos = if self.pos.is_empty() {
linspace(0.0, 1.0, colors.len())
} else if self.pos.len() == colors.len() {
for p in self.pos.windows(2) {
if p[0] > p[1] {
return Err(CustomGradientError::WrongDomain);
}
}
self.pos.to_vec()
} else if self.pos.len() == 2 {
if self.pos[0] >= self.pos[1] {
return Err(CustomGradientError::WrongDomain);
}
linspace(self.pos[0], self.pos[1], colors.len())
} else {
return Err(CustomGradientError::WrongDomainCount);
};
let mode = if self.interpolation != Interpolation::Linear && self.mode == BlendMode::Hsv {
BlendMode::Rgb
} else {
self.mode
};
let dmin = pos[0];
let dmax = pos[pos.len() - 1];
let gradient: Box<dyn GradientBase + Send + Sync> = match self.interpolation {
Interpolation::Linear => Box::new(LinearGradient::new(colors, pos, mode)),
Interpolation::CatmullRom => Box::new(CatmullRomGradient::new(colors, pos, mode)),
Interpolation::Basis => Box::new(BasisGradient::new(colors, pos, mode)),
};
Ok(Gradient {
gradient,
dmin,
dmax,
})
}
}