Skip to main content

tinted_builder/scheme/
base24.rs

1use crate::{utils::slugify, SchemeSystem, SchemeVariant};
2use serde::ser::{SerializeMap, SerializeStruct};
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::{collections::HashMap, fmt};
5
6pub use crate::scheme::color::Color;
7
8pub const REQUIRED_BASE24_PALETTE_KEYS: [&str; 24] = [
9    "base00", "base01", "base02", "base03", "base04", "base05", "base06", "base07", "base08",
10    "base09", "base0A", "base0B", "base0C", "base0D", "base0E", "base0F", "base10", "base11",
11    "base12", "base13", "base14", "base15", "base16", "base17",
12];
13
14#[derive(Deserialize, Serialize)]
15struct SchemeWrapper {
16    pub(crate) system: SchemeSystem,
17    pub(crate) name: String,
18    pub(crate) slug: Option<String>,
19    pub(crate) author: String,
20    pub(crate) description: Option<String>,
21    pub(crate) variant: Option<SchemeVariant>,
22    pub(crate) palette: HashMap<String, String>,
23}
24
25/// Deserialized Base24 scheme with normalized palette and metadata.
26///
27/// The `palette` map contains Base24 color keys (`base00` through `base17`) mapped to
28/// normalized `Color` values. Serialization preserves sorted palette keys and hex values
29/// with a leading `#`.
30#[derive(Debug, Clone)]
31pub struct Scheme {
32    pub system: SchemeSystem,
33    pub name: String,
34    pub slug: String,
35    pub author: String,
36    pub description: Option<String>,
37    pub variant: SchemeVariant,
38    pub palette: HashMap<String, Color>,
39}
40
41impl fmt::Display for Scheme {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        writeln!(f, "author: \"{}\"", self.author)?;
44        if let Some(ref desc) = self.description {
45            writeln!(f, "description: \"{desc}\"")?;
46        }
47        writeln!(f, "name: \"{}\"", self.name)?;
48        writeln!(f, "slug: \"{}\"", self.slug)?;
49        writeln!(f, "system: \"{}\"", self.system)?;
50        writeln!(f, "variant: \"{}\"", self.variant)?;
51        writeln!(f, "palette:")?;
52
53        let mut palette_vec: Vec<(String, Color)> = self
54            .palette
55            .clone()
56            .iter()
57            .map(|(k, v)| (k.clone(), v.clone()))
58            .collect();
59        palette_vec.sort_by_key(|k| k.0.clone());
60
61        for (key, value) in palette_vec {
62            writeln!(f, "  {key}: \"{value}\"")?;
63        }
64        Ok(())
65    }
66}
67
68impl<'de> Deserialize<'de> for Scheme {
69    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70    where
71        D: Deserializer<'de>,
72    {
73        let wrapper = SchemeWrapper::deserialize(deserializer)?;
74        let slug = wrapper
75            .slug
76            .map_or_else(|| slugify(&wrapper.name), |slug| slugify(&slug));
77        let variant = wrapper.variant.unwrap_or(SchemeVariant::Dark);
78
79        if wrapper.system != SchemeSystem::Base24 {
80            return Err(serde::de::Error::custom(format!(
81                "{} is not a valid system for a Base24 scheme",
82                wrapper.system
83            )));
84        }
85
86        let contains_all_keys = REQUIRED_BASE24_PALETTE_KEYS
87            .iter()
88            .all(|&key| wrapper.palette.contains_key(key));
89
90        if !contains_all_keys {
91            return Err(serde::de::Error::custom(
92                "base24 scheme does not contain the required palette properties",
93            ));
94        }
95
96        let palette_result: Result<HashMap<String, Color>, _> = wrapper
97            .palette
98            .into_iter()
99            .map(|(key, value)| {
100                Color::new(&value, None, None)
101                    .map(|color| (key, color))
102                    .map_err(|e| serde::de::Error::custom(e.to_string()))
103            })
104            .collect();
105
106        Ok(Self {
107            name: wrapper.name,
108            slug,
109            system: wrapper.system,
110            author: wrapper.author,
111            description: wrapper.description,
112            variant,
113            palette: palette_result?,
114        })
115    }
116}
117
118impl Serialize for Scheme {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: Serializer,
122    {
123        let mut state = serializer.serialize_struct("Scheme", 7)?;
124        state.serialize_field("system", &self.system)?;
125        state.serialize_field("name", &self.name)?;
126        state.serialize_field("slug", &self.slug)?;
127        state.serialize_field("author", &self.author)?;
128        if let Some(description) = &self.description {
129            state.serialize_field("description", description)?;
130        }
131        state.serialize_field("variant", &self.variant)?;
132
133        // Collect and sort the palette by key
134        let mut sorted_palette: Vec<(&String, &Color)> = self.palette.iter().collect();
135        sorted_palette.sort_by(|a, b| a.0.cmp(b.0));
136
137        // Serialize the sorted palette as a map within the struct
138        state.serialize_field("palette", &SortedPalette(sorted_palette))?;
139
140        state.end()
141    }
142}
143
144// Helper struct for serializing sorted palette
145struct SortedPalette<'a>(Vec<(&'a String, &'a Color)>);
146
147#[allow(clippy::elidable_lifetime_names)]
148impl<'a> Serialize for SortedPalette<'a> {
149    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150    where
151        S: Serializer,
152    {
153        let mut map = serializer.serialize_map(Some(self.0.len()))?;
154        for (key, value) in &self.0 {
155            map.serialize_entry(key, format!("#{}", &value.to_hex()).as_str())?;
156        }
157        map.end()
158    }
159}