1use ratatui::style::Color;
2
3pub const GRADIENT_WARM: &[(Color, f32); 5] = &[
7 (Color::Rgb(0x1A, 0x05, 0x05), 0.0),
8 (Color::Rgb(0x3A, 0x1A, 0x15), 0.25),
9 (Color::Rgb(0x6B, 0x3A, 0x2A), 0.50),
10 (Color::Rgb(0xB5, 0x7A, 0x35), 0.75),
11 (Color::Rgb(0xE8, 0xA0, 0x35), 1.0),
12];
13
14pub const GRADIENT_COOL: &[(Color, f32); 5] = &[
18 (Color::Rgb(0x0A, 0x1A, 0x25), 0.0),
19 (Color::Rgb(0x1A, 0x3A, 0x45), 0.25),
20 (Color::Rgb(0x4D, 0x8A, 0x8A), 0.50),
21 (Color::Rgb(0x6D, 0xAE, 0xAE), 0.75),
22 (Color::Rgb(0xE8, 0xE4, 0xD9), 1.0),
23];
24
25pub fn lerp_color(a: Color, b: Color, t: f32) -> Color {
29 let t = t.clamp(0.0, 1.0);
30 match (a, b) {
31 (Color::Rgb(ar, ag, ab), Color::Rgb(br, bg, bb)) => Color::Rgb(
32 (ar as f32 + (br as f32 - ar as f32) * t) as u8,
33 (ag as f32 + (bg as f32 - ag as f32) * t) as u8,
34 (ab as f32 + (bb as f32 - ab as f32) * t) as u8,
35 ),
36 _ => a,
37 }
38}
39
40pub fn multi_stop_color(stops: &[(Color, f32)], t: f32) -> Color {
45 let t = t.clamp(0.0, 1.0);
46 if stops.is_empty() {
47 return Color::Rgb(0, 0, 0);
48 }
49 if stops.len() == 1 {
50 return stops[0].0;
51 }
52 for i in 0..stops.len() - 1 {
53 if t >= stops[i].1 && t <= stops[i + 1].1 {
54 let seg_len = stops[i + 1].1 - stops[i].1;
55 let seg_t = if seg_len == 0.0 { 0.0 } else { (t - stops[i].1) / seg_len };
56 return lerp_color(stops[i].0, stops[i + 1].0, seg_t);
57 }
58 }
59 stops.last().unwrap().0
60}
61
62pub fn gradient_horizontal(width: u16, stops: &[(Color, f32)]) -> Vec<Color> {
67 if width == 0 {
68 return Vec::new();
69 }
70 (0..width)
71 .map(|i| {
72 let t = i as f32 / (width - 1) as f32;
73 multi_stop_color(stops, t)
74 })
75 .collect()
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn lerp_identity() {
84 let a = Color::Rgb(10, 20, 30);
85 let b = Color::Rgb(100, 200, 250);
86 assert_eq!(lerp_color(a, b, 0.0), a);
87 assert_eq!(lerp_color(a, b, 1.0), b);
88 }
89
90 #[test]
91 fn lerp_midpoint() {
92 let a = Color::Rgb(0, 0, 0);
93 let b = Color::Rgb(100, 200, 0);
94 assert_eq!(lerp_color(a, b, 0.5), Color::Rgb(50, 100, 0));
95 }
96
97 #[test]
98 fn lerp_clamps_t() {
99 let a = Color::Rgb(0, 0, 0);
100 let b = Color::Rgb(100, 0, 0);
101 assert_eq!(lerp_color(a, b, -0.5), a);
102 assert_eq!(lerp_color(a, b, 1.5), b);
103 }
104
105 #[test]
106 fn lerp_non_rgb_fallback() {
107 let a = Color::Red;
108 let b = Color::Rgb(100, 0, 0);
109 assert_eq!(lerp_color(a, b, 0.5), a);
110 }
111
112 #[test]
113 fn multi_stop_single() {
114 let stops = &[(Color::Rgb(50, 50, 50), 0.0)];
115 assert_eq!(multi_stop_color(stops, 0.0), Color::Rgb(50, 50, 50));
116 assert_eq!(multi_stop_color(stops, 0.5), Color::Rgb(50, 50, 50));
117 }
118
119 #[test]
120 fn multi_stop_warm_midpoint() {
121 let c = multi_stop_color(GRADIENT_WARM, 0.5);
122 assert_eq!(c, Color::Rgb(0x6B, 0x3A, 0x2A));
124 }
125
126 #[test]
127 fn gradient_horizontal_empty() {
128 assert!(gradient_horizontal(0, GRADIENT_WARM).is_empty());
129 }
130
131 #[test]
132 fn gradient_horizontal_single() {
133 let colors = gradient_horizontal(1, GRADIENT_WARM);
134 assert_eq!(colors.len(), 1);
135 assert_eq!(colors[0], GRADIENT_WARM.last().unwrap().0);
137 }
138
139 #[test]
140 fn gradient_horizontal_width_matches() {
141 let colors = gradient_horizontal(10, GRADIENT_WARM);
142 assert_eq!(colors.len(), 10);
143 assert_eq!(colors[0], GRADIENT_WARM[0].0);
144 assert_eq!(colors[9], GRADIENT_WARM.last().unwrap().0);
145 }
146}