Skip to main content

tinted_builder/scheme/
color.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::error::TintedBuilderError;
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
7pub struct Color {
8    pub hex: (String, String, String),
9    pub rgb: (u8, u8, u8),
10    pub dec: (f32, f32, f32),
11}
12
13impl Color {
14    /// Creates a `Color` from a hex string like `"ff00ff"` or `"#ffcc00"`.
15    ///
16    /// # Errors
17    ///
18    /// Returns `Err(TintedBuilderError::HexInputFormat)` if `hex_color` is not a valid
19    /// 6-digit hexadecimal color (optionally prefixed with `#`).
20    pub fn new(hex_color: &str) -> Result<Self, TintedBuilderError> {
21        let hex_full = process_hex_input(hex_color).ok_or(TintedBuilderError::HexInputFormat)?;
22        let hex: (String, String, String) = (
23            hex_full[0..2].to_lowercase(),
24            hex_full[2..4].to_lowercase(),
25            hex_full[4..6].to_lowercase(),
26        );
27        let rgb = hex_to_rgb(&hex)?;
28        let dec: (f32, f32, f32) = (
29            f32::from(rgb.0) / 255.0,
30            f32::from(rgb.1) / 255.0,
31            f32::from(rgb.2) / 255.0,
32        );
33
34        Ok(Self { hex, rgb, dec })
35    }
36
37    #[must_use]
38    pub fn to_hex(&self) -> String {
39        format!("{}{}{}", &self.hex.0, &self.hex.1, &self.hex.2)
40    }
41}
42
43impl fmt::Display for Color {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        write!(f, "#{}", &self.to_hex())
46    }
47}
48
49fn hex_to_rgb(hex: &(String, String, String)) -> Result<(u8, u8, u8), TintedBuilderError> {
50    let r = u8::from_str_radix(hex.0.as_str(), 16)?;
51    let g = u8::from_str_radix(hex.1.as_str(), 16)?;
52    let b = u8::from_str_radix(hex.2.as_str(), 16)?;
53
54    Ok((r, g, b))
55}
56
57fn process_hex_input(input: &str) -> Option<String> {
58    // Check and process the hash prefix
59    let hex_str = input.strip_prefix('#').unwrap_or(input);
60
61    match hex_str.len() {
62        // Convert 3-length hex to 6-length by duplicating each character
63        3 => {
64            if hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
65                Some(
66                    hex_str
67                        .chars()
68                        .flat_map(|c| std::iter::repeat(c).take(2))
69                        .collect(),
70                )
71            } else {
72                None // Contains invalid characters
73            }
74        }
75        // Validate the 6-length hex value
76        6 => {
77            if hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
78                Some(hex_str.to_string())
79            } else {
80                None // Contains invalid characters
81            }
82        }
83        // Invalid length
84        _ => None,
85    }
86}