git_iris/theme/
gradient.rs1use super::color::ThemeColor;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Gradient {
8 stops: Vec<ThemeColor>,
10}
11
12impl Gradient {
13 #[must_use]
18 pub fn new(stops: Vec<ThemeColor>) -> Self {
19 assert!(!stops.is_empty(), "gradient must have at least one color");
20 Self { stops }
21 }
22
23 #[must_use]
25 #[allow(
26 clippy::cast_precision_loss,
27 clippy::cast_possible_truncation,
28 clippy::cast_sign_loss,
29 clippy::as_conversions
30 )]
31 pub fn at(&self, t: f32) -> ThemeColor {
32 let t = t.clamp(0.0, 1.0);
33
34 if self.stops.len() == 1 {
35 return self.stops[0];
36 }
37
38 let scaled = t * (self.stops.len() - 1) as f32;
39 let idx = scaled.floor() as usize;
40 let local_t = scaled - scaled.floor();
41
42 if idx >= self.stops.len() - 1 {
43 self.stops[self.stops.len() - 1]
44 } else {
45 self.stops[idx].lerp(&self.stops[idx + 1], local_t)
46 }
47 }
48
49 #[must_use]
51 pub fn len(&self) -> usize {
52 self.stops.len()
53 }
54
55 #[must_use]
57 pub fn is_empty(&self) -> bool {
58 self.stops.is_empty()
59 }
60
61 #[must_use]
63 #[allow(clippy::cast_precision_loss, clippy::as_conversions)]
64 pub fn generate(&self, n: usize) -> Vec<ThemeColor> {
65 if n == 0 {
66 return vec![];
67 }
68 if n == 1 {
69 return vec![self.stops[0]];
70 }
71
72 (0..n)
73 .map(|i| {
74 let t = i as f32 / (n - 1) as f32;
75 self.at(t)
76 })
77 .collect()
78 }
79}
80
81impl Default for Gradient {
82 fn default() -> Self {
83 Self {
84 stops: vec![ThemeColor::FALLBACK],
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_gradient_endpoints() {
95 let gradient = Gradient::new(vec![
96 ThemeColor::new(0, 0, 0),
97 ThemeColor::new(255, 255, 255),
98 ]);
99
100 assert_eq!(gradient.at(0.0), ThemeColor::new(0, 0, 0));
101 assert_eq!(gradient.at(1.0), ThemeColor::new(255, 255, 255));
102 }
103
104 #[test]
105 fn test_gradient_midpoint() {
106 let gradient = Gradient::new(vec![
107 ThemeColor::new(0, 0, 0),
108 ThemeColor::new(255, 255, 255),
109 ]);
110
111 let mid = gradient.at(0.5);
112 assert_eq!(mid, ThemeColor::new(127, 127, 127));
113 }
114
115 #[test]
116 fn test_gradient_multi_stop() {
117 let gradient = Gradient::new(vec![
118 ThemeColor::new(255, 0, 0), ThemeColor::new(0, 255, 0), ThemeColor::new(0, 0, 255), ]);
122
123 assert_eq!(gradient.at(0.0), ThemeColor::new(255, 0, 0));
124 assert_eq!(gradient.at(0.5), ThemeColor::new(0, 255, 0));
125 assert_eq!(gradient.at(1.0), ThemeColor::new(0, 0, 255));
126 }
127
128 #[test]
129 fn test_generate() {
130 let gradient = Gradient::new(vec![
131 ThemeColor::new(0, 0, 0),
132 ThemeColor::new(255, 255, 255),
133 ]);
134
135 let colors = gradient.generate(3);
136 assert_eq!(colors.len(), 3);
137 assert_eq!(colors[0], ThemeColor::new(0, 0, 0));
138 assert_eq!(colors[1], ThemeColor::new(127, 127, 127));
139 assert_eq!(colors[2], ThemeColor::new(255, 255, 255));
140 }
141}