1use crate::theme::color::SimpleColor;
2use crate::theme::Color;
3use crate::utils::bounded_float::BoundedFloat;
4use indexmap::IndexMap;
5use serde::de::Error;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::collections::{BTreeMap, HashMap, HashSet};
8use std::ops::Index;
9
10#[derive(Debug, Serialize, Deserialize)]
12pub struct Gradient {
13 #[serde(flatten)]
14 #[serde(deserialize_with = "de_gradient")]
15 points: BTreeMap<BoundedFloat<0, 1>, Color>,
16}
17
18fn de_gradient<'de, D: Deserializer<'de>>(
19 des: D,
20) -> Result<BTreeMap<BoundedFloat<0, 1>, Color>, D::Error> {
21 let map = IndexMap::<String, Color>::deserialize(des)?;
22 let points = map
23 .into_iter()
24 .map(|(k, v)| {
25 let float: f32 = k.parse().map_err(|e| D::Error::custom(e))?;
26 let bounded = BoundedFloat::<0, 1>::new(float)
27 .ok_or_else(|| D::Error::custom(format!("{} not in bounds [0,1]", float)))?;
28 Ok((bounded, v))
29 })
30 .collect::<Result<BTreeMap<BoundedFloat<0, 1>, Color>, _>>()?;
31 if points.get(&BoundedFloat::<0, 1>::MIN).is_none()
32 || points.get(&BoundedFloat::<0, 1>::MAX).is_none()
33 {
34 return Err(D::Error::custom(
35 "must specify 0 value and 1 value in gradient",
36 ));
37 }
38 Ok(points)
39}
40
41fn ser_gradient<S: Serializer>(gradient: &Gradient, serde: S) -> Result<S::Ok, S::Error> {
42 gradient
43 .points
44 .iter()
45 .map(|(k, v)| (format!("{}", *k), v.clone()))
46 .collect::<HashMap<String, Color>>()
47 .serialize(serde)
48}
49
50impl Gradient {
51 pub fn new(low: Color, high: Color) -> Self {
52 Self::from_iter([
53 (BoundedFloat::<0, 1>::new(0.0).unwrap(), low),
54 (BoundedFloat::<0, 1>::new(1.0).unwrap(), high),
55 ])
56 }
57
58 fn calc_color_at(&self, bounded_float: &BoundedFloat<0, 1>) -> Option<Color> {
59 let (&high_pt, high) = self.points.range(*bounded_float..).next()?;
60 let (&low_pt, low) = self.points.range(..=*bounded_float).rev().next()?;
61 if high_pt == low_pt || high == low {
62 return Some(low.clone());
63 }
64
65 let color_pt: f32 = (*bounded_float - low_pt) / (high_pt - low_pt);
66 match (low.to_simple().ok()?, high.to_simple().ok()?) {
67 (SimpleColor::Hsla(l_h, l_s, l_l, l_a), SimpleColor::Hsla(h_h, h_s, h_l, h_a)) => {
68 let h = (h_h - l_h) * color_pt + l_h;
69 let s = (h_s - l_s) * color_pt + l_s;
70 let l = (h_l - l_l) * color_pt + l_l;
71 let a = (h_a - l_a) * color_pt + l_a;
72
73 Some(Color::Hsla {
74 h: (h * 360.0).round() as u16,
75 s: (s * 100.0).round() as u8,
76 l: (l * 100.0).round() as u8,
77 a: (a * 100.0).round() as u8,
78 })
79 }
80 (SimpleColor::Rgba(l_r, l_g, l_b, l_a), SimpleColor::Rgba(h_r, h_g, h_b, h_a)) => {
81 let r = (h_r as f32 - l_r as f32) * color_pt + l_r as f32;
82 let g = (h_g as f32 - l_g as f32) * color_pt + l_g as f32;
83 let b = (h_b as f32 - l_b as f32) * color_pt + l_b as f32;
84 let a = (h_a as f32 - l_a as f32) * color_pt + l_a as f32;
85
86 Some(Color::Rgba {
87 r: (r).round() as u8,
88 g: (g).round() as u8,
89 b: (b).round() as u8,
90 a: (a).round() as u8,
91 })
92 }
93 _ => None,
94 }
95 }
96
97 pub fn get(&self, ref bounded_float: BoundedFloat<0, 1>) -> Color {
98 self.points.get(bounded_float).cloned().unwrap_or_else(|| {
99 self.calc_color_at(bounded_float)
100 .expect("could not get a color")
101 })
102 }
103
104 pub fn get_mut(&mut self, bounded_float: BoundedFloat<0, 1>) -> &mut Color {
105 let c = self.calc_color_at(&bounded_float).unwrap();
106 self.points.entry(bounded_float).or_insert(c)
107 }
108
109 pub fn inflect_at(&mut self, bounded_float: BoundedFloat<0, 1>) {
111 let _ = self.get_mut(bounded_float);
112 }
113
114 pub fn print_gradient(&self) {
115 let mut set = HashSet::new();
116 for i in 0..=100 {
117 let color = self.get(BoundedFloat::new(i as f32 / 100.0).unwrap());
118 let [r, g, b, ..] = color.to_rgba().unwrap();
119 print!("\x1b[48;2;{};{};{}m \x1b[0m", r, g, b);
120 set.insert(color.to_rgba().unwrap());
121 }
122 println!(" resolution: {}", set.len());
123 }
124}
125
126impl FromIterator<(BoundedFloat<0, 1>, Color)> for Gradient {
127 fn from_iter<T: IntoIterator<Item = (BoundedFloat<0, 1>, Color)>>(iter: T) -> Self {
128 Self {
129 points: iter.into_iter().collect(),
130 }
131 }
132}
133
134impl<'a> IntoIterator for &'a Gradient {
135 type Item = (&'a BoundedFloat<0, 1>, &'a Color);
136 type IntoIter = <&'a BTreeMap<BoundedFloat<0, 1>, Color> as IntoIterator>::IntoIter;
137
138 fn into_iter(self) -> Self::IntoIter {
139 self.points.iter()
140 }
141}
142
143impl IntoIterator for Gradient {
144 type Item = (BoundedFloat<0, 1>, Color);
145 type IntoIter = <BTreeMap<BoundedFloat<0, 1>, Color> as IntoIterator>::IntoIter;
146
147 fn into_iter(self) -> Self::IntoIter {
148 self.points.into_iter()
149 }
150}
151#[cfg(test)]
152mod tests {
153 use crate::theme::gradient::Gradient;
154 use crate::theme::Color;
155 use crate::utils::bounded_float::BoundedFloat;
156
157 #[test]
158 fn test_gradient() {
159 for saturation in 0..=100 {
160 let light = 75;
161 let mut gradient = Gradient::new(
162 Color::hsl(0, saturation, light),
163 Color::hsl(360, saturation, light),
164 );
165 *gradient.get_mut(BoundedFloat::new(1. / 6.).unwrap()) =
166 Color::hsl(60, saturation, light);
167 *gradient.get_mut(BoundedFloat::new(2. / 6.).unwrap()) =
168 Color::hsl(120, saturation, light);
169 *gradient.get_mut(BoundedFloat::new(3. / 6.).unwrap()) =
170 Color::hsl(180, saturation, light);
171 *gradient.get_mut(BoundedFloat::new(4. / 6.).unwrap()) =
172 Color::hsl(240, saturation, light);
173 *gradient.get_mut(BoundedFloat::new(5. / 6.).unwrap()) =
174 Color::hsl(300, saturation, light);
175
176 gradient.print_gradient();
177 }
178 }
179}