pub use ril::{Rgb, Rgba};
use std::{
fmt,
ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive},
};
#[derive(Clone, Debug)]
pub struct Config {
pub hues: Vec<f64>,
pub color_lightness: RangeInclusive<f64>,
pub grayscale_lightness: RangeInclusive<f64>,
pub color_saturation: f64,
pub grayscale_saturation: f64,
pub background_color: Rgba,
pub padding: f64,
pub size: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
hues: Vec::new(),
color_lightness: 0.4..=0.8,
grayscale_lightness: 0.3..=0.9,
color_saturation: 0.5,
grayscale_saturation: 0.0,
background_color: Rgba::white(),
padding: 0.08,
size: 256,
}
}
}
impl Config {
#[must_use = "ConfigBuilder does nothing on its own"]
pub fn builder() -> ConfigBuilder {
ConfigBuilder {
config: Config::default(),
}
}
}
pub struct ConfigBuilder {
config: Config,
}
pub trait NormalizableRange {
fn normalize(self) -> RangeInclusive<f64>;
}
impl NormalizableRange for RangeInclusive<f64> {
fn normalize(self) -> RangeInclusive<f64> {
let start = *self.start();
let end = *self.end();
debug_assert!(start < end, "start must be less than end");
debug_assert!(start >= 0.0 && end <= 1.0, "range must be within 0.0..=1.0");
self
}
}
impl NormalizableRange for Range<f64> {
fn normalize(self) -> RangeInclusive<f64> {
(self.start..=self.end + f64::EPSILON).normalize()
}
}
impl NormalizableRange for RangeFrom<f64> {
fn normalize(self) -> RangeInclusive<f64> {
(self.start..=1.0).normalize()
}
}
impl NormalizableRange for RangeTo<f64> {
fn normalize(self) -> RangeInclusive<f64> {
(0.0..self.end).normalize()
}
}
impl NormalizableRange for RangeToInclusive<f64> {
fn normalize(self) -> RangeInclusive<f64> {
(0.0..=self.end).normalize()
}
}
impl ConfigBuilder {
#[must_use = "This method does not modify in place"]
pub fn hues(mut self, hues: impl AsRef<[f64]>) -> Self {
self.config.hues = hues.as_ref().to_vec();
self
}
#[must_use = "This method does not modify in place"]
pub fn color_lightness(mut self, lightness: impl NormalizableRange) -> Self {
self.config.color_lightness = lightness.normalize();
self
}
#[must_use = "This method does not modify in place"]
pub fn grayscale_lightness(mut self, lightness: impl NormalizableRange) -> Self {
self.config.grayscale_lightness = lightness.normalize();
self
}
#[must_use = "This method does not modify in place"]
pub const fn color_saturation(mut self, saturation: f64) -> Self {
self.config.color_saturation = saturation;
self
}
#[must_use = "This method does not modify in place"]
pub const fn grayscale_saturation(mut self, saturation: f64) -> Self {
self.config.grayscale_saturation = saturation;
self
}
#[must_use = "This method does not modify in place"]
pub const fn background_color(mut self, color: Rgba) -> Self {
self.config.background_color = color;
self
}
#[must_use = "This method does not modify in place"]
pub const fn padding(mut self, padding: f64) -> Self {
self.config.padding = padding;
self
}
#[must_use = "This method does not modify in place"]
pub const fn size(mut self, size: u32) -> Self {
self.config.size = size;
self
}
pub fn build(self) -> Result<Config, ConfigBuilderError> {
if self
.config
.hues
.iter()
.any(|hue| !(0.0..360.0).contains(hue))
{
return Err(ConfigBuilderError::InvalidHues);
}
if self.config.color_lightness.start() < &0.0 || self.config.color_lightness.end() > &1.0 {
return Err(ConfigBuilderError::InvalidColorLightness);
}
if self.config.grayscale_lightness.start() < &0.0
|| self.config.grayscale_lightness.end() > &1.0
{
return Err(ConfigBuilderError::InvalidGrayscaleLightness);
}
if !(0.0..=1.0).contains(&self.config.color_saturation) {
return Err(ConfigBuilderError::InvalidColorSaturation);
}
if !(0.0..=1.0).contains(&self.config.grayscale_saturation) {
return Err(ConfigBuilderError::InvalidGrayscaleSaturation);
}
if !(0.0..=0.5).contains(&self.config.padding) {
return Err(ConfigBuilderError::InvalidPadding);
}
Ok(self.config)
}
}
#[derive(Clone, Debug)]
pub enum ConfigBuilderError {
InvalidHues,
InvalidColorLightness,
InvalidGrayscaleLightness,
InvalidColorSaturation,
InvalidGrayscaleSaturation,
InvalidPadding,
}
impl fmt::Display for ConfigBuilderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let content = match self {
Self::InvalidHues => "hues must be within the range [0.0, 360.0)",
Self::InvalidColorLightness => "color lightness must be within the range 0.0..=1.0",
Self::InvalidGrayscaleLightness => {
"grayscale lightness must be within the range 0.0..=1.0"
}
Self::InvalidColorSaturation => "color saturation must be within the range [0.0, 1.0]",
Self::InvalidGrayscaleSaturation => {
"grayscale saturation must be within the range [0.0, 1.0]"
}
Self::InvalidPadding => "padding must be within the range [0.0, 0.5]",
};
f.write_str(content)
}
}
impl std::error::Error for ConfigBuilderError {}