use crate::{
error::InvalidColorError,
constants::{
MIN_HSL_VALUE,
MAX_HSL_VALUE,
MIN_HSV_VALUE,
MAX_HSV_VALUE,
VALID_HEX_LENGTHS,
VALID_HEX_CHARS
}
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8
}
impl RGB {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self {
r,
g,
b
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct HSL {
pub h: f32,
pub s: f32,
pub l: f32
}
impl HSL {
pub fn new(h: f32, s: f32, l: f32) -> Result<Self, InvalidColorError> {
Self::validate_component(h, "Hue", MIN_HSL_VALUE, MAX_HSL_VALUE)?;
Self::validate_component(s, "Saturation", MIN_HSL_VALUE, MAX_HSL_VALUE)?;
Self::validate_component(l, "Lightness", MIN_HSL_VALUE, MAX_HSL_VALUE)?;
Ok(Self {
h,
s,
l
})
}
fn validate_component(value: f32, name: &str, min: f32, max: f32) -> Result<(), InvalidColorError> {
if !(min..=max).contains(&value) {
return Err(InvalidColorError::new(value.to_string(), format!("{} must be {}-{}, got {}", name, min, max, value)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct HSV {
pub h: f32,
pub s: f32,
pub v: f32
}
impl HSV {
pub fn new(h: f32, s: f32, v: f32) -> Result<Self, InvalidColorError> {
Self::validate(h, "Hue", MIN_HSV_VALUE, 360.0)?;
Self::validate(s, "Saturation", MIN_HSV_VALUE, MAX_HSV_VALUE)?;
Self::validate(v, "Value", MIN_HSV_VALUE, MAX_HSV_VALUE)?;
Ok(Self {
h,
s,
v
})
}
fn validate(value: f32, name: &str, min: f32, max: f32) -> Result<(), InvalidColorError> {
if !(min..=max).contains(&value) {
return Err(InvalidColorError::new(value.to_string(), format!("{} must be {}-{}, got {}", name, min, max, value)));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HEX {
pub value: String
}
impl HEX {
pub fn new(value: impl ToString) -> Result<Self, InvalidColorError> {
let val: String = value.to_string();
let normalized: String = val.trim_start_matches('#').to_uppercase();
if !VALID_HEX_LENGTHS.contains(&normalized.len()) {
return Err(InvalidColorError::new(&val, format!("Hex color must be 3 or 6 characters, got {}: {}", normalized.len(), val)));
}
if !normalized.chars().all(|c: char| VALID_HEX_CHARS.contains(c)) {
return Err(InvalidColorError::new(&val, format!("Hex color contains invalid characters: {}", val)));
}
Ok(Self {
value: val
})
}
pub fn normalized(&self) -> String {
self.value.trim_start_matches('#').to_uppercase()
}
}