1use crossterm::style::{Color, Stylize};
2
3pub fn generate(text: &str, start: Color, end: Color) -> String {
12 let chars: Vec<char> = text.chars().collect();
13 let steps = chars.len().max(1);
14 let mut result = String::with_capacity(text.len() * 10); for (i, c) in chars.iter().enumerate() {
17 let r = interpolate(get_r(&start), get_r(&end), i, steps);
18 let g = interpolate(get_g(&start), get_g(&end), i, steps);
19 let b = interpolate(get_b(&start), get_b(&end), i, steps);
20 let color = Color::Rgb { r, g, b };
21 result.push_str(&c.to_string().with(color).to_string());
22 }
23
24 #[allow(dead_code)]
26 #[derive(Clone, Copy)]
27 pub enum Easing {
28 Linear,
29 EaseIn,
30 EaseOut,
31 EaseInOut,
32 }
33
34 #[allow(dead_code)]
35 #[inline(always)]
36 fn apply_easing(t: f32, mode: Easing) -> f32 {
37 match mode {
38 Easing::Linear => t,
39 Easing::EaseIn => t * t,
40 Easing::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
41 Easing::EaseInOut => {
42 if t < 0.5 {
43 2.0 * t * t
44 } else {
45 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
46 }
47 }
48 }
49 }
50
51 #[allow(dead_code)]
54 pub fn multi_color_eased(text: &str, colors: Vec<Color>, ease: Easing) -> String {
55 let chars: Vec<char> = text.chars().collect();
56 let steps = chars.len().max(1);
57 let segments = colors.len().saturating_sub(1).max(1);
58 let mut result = String::with_capacity(text.len() * 10);
59
60 for (i, c) in chars.iter().enumerate() {
61 let t_raw = i as f32 / (steps - 1).max(1) as f32;
62 let t = apply_easing(t_raw, ease).clamp(0.0, 1.0);
63 let seg_float = t * segments as f32;
64 let seg = seg_float.floor() as usize;
65 let seg_t = seg_float - seg as f32;
66
67 let from = colors.get(seg).unwrap_or(&colors[0]);
68 let to = colors.get(seg + 1).unwrap_or(from);
69
70 let r = interpolate(get_r(from), get_r(to), (seg_t * 100.0) as usize, 100);
71 let g = interpolate(get_g(from), get_g(to), (seg_t * 100.0) as usize, 100);
72 let b = interpolate(get_b(from), get_b(to), (seg_t * 100.0) as usize, 100);
73
74 let color = Color::Rgb { r, g, b };
75 result.push_str(&c.to_string().with(color).to_string());
76 }
77
78 result
79 }
80
81 #[allow(dead_code)]
83 pub fn palette_viridis() -> Vec<Color> {
84 vec![
85 Color::Rgb { r: 68, g: 1, b: 84 },
86 Color::Rgb {
87 r: 59,
88 g: 82,
89 b: 139,
90 },
91 Color::Rgb {
92 r: 33,
93 g: 145,
94 b: 140,
95 },
96 Color::Rgb {
97 r: 94,
98 g: 201,
99 b: 97,
100 },
101 Color::Rgb {
102 r: 253,
103 g: 231,
104 b: 37,
105 },
106 ]
107 }
108
109 #[allow(dead_code)]
111 pub fn palette_magma() -> Vec<Color> {
112 vec![
113 Color::Rgb { r: 0, g: 0, b: 4 },
114 Color::Rgb {
115 r: 73,
116 g: 15,
117 b: 99,
118 },
119 Color::Rgb {
120 r: 187,
121 g: 55,
122 b: 84,
123 },
124 Color::Rgb {
125 r: 249,
126 g: 142,
127 b: 8,
128 },
129 Color::Rgb {
130 r: 252,
131 g: 253,
132 b: 191,
133 },
134 ]
135 }
136
137 result
138}
139
140#[inline(always)]
145pub fn two_color(text: &str, start: Color, end: Color) -> String {
146 generate(text, start, end)
147}
148
149pub fn three_color(text: &str, start: Color, mid: Color, end: Color) -> String {
153 let chars: Vec<char> = text.chars().collect();
154 let total = chars.len().max(1);
155 let midpoint = total / 2;
156 let mut result = String::with_capacity(text.len() * 10);
157
158 for (i, c) in chars.iter().enumerate() {
159 let (from, to, step, steps) = if i < midpoint {
160 (start, mid, i, midpoint)
161 } else {
162 (mid, end, i - midpoint, total - midpoint)
163 };
164
165 let r = interpolate(get_r(&from), get_r(&to), step, steps);
166 let g = interpolate(get_g(&from), get_g(&to), step, steps);
167 let b = interpolate(get_b(&from), get_b(&to), step, steps);
168 let color = Color::Rgb { r, g, b };
169 result.push_str(&c.to_string().with(color).to_string());
170 }
171
172 result
173}
174
175pub fn multi_color(text: &str, colors: Vec<Color>) -> String {
179 let chars: Vec<char> = text.chars().collect();
180 let steps = chars.len().max(1);
181 let segments = colors.len().saturating_sub(1).max(1);
182 let mut result = String::with_capacity(text.len() * 10);
183
184 for (i, c) in chars.iter().enumerate() {
185 let t = i as f32 / (steps - 1).max(1) as f32;
186 let seg_float = t * segments as f32;
187 let seg = seg_float.floor() as usize;
188 let seg_t = seg_float - seg as f32;
189
190 let from = colors.get(seg).unwrap_or(&colors[0]);
191 let to = colors.get(seg + 1).unwrap_or(from);
192
193 let r = interpolate(get_r(from), get_r(to), (seg_t * 100.0) as usize, 100);
194 let g = interpolate(get_g(from), get_g(to), (seg_t * 100.0) as usize, 100);
195 let b = interpolate(get_b(from), get_b(to), (seg_t * 100.0) as usize, 100);
196
197 let color = Color::Rgb { r, g, b };
198 result.push_str(&c.to_string().with(color).to_string());
199 }
200
201 result
202}
203
204#[inline(always)]
213fn get_r(c: &Color) -> u8 {
214 match c {
215 Color::Rgb { r, .. } => *r,
216 _ => 255,
217 }
218}
219
220#[inline(always)]
223fn get_g(c: &Color) -> u8 {
224 match c {
225 Color::Rgb { g, .. } => *g,
226 _ => 255,
227 }
228}
229
230#[inline(always)]
233fn get_b(c: &Color) -> u8 {
234 match c {
235 Color::Rgb { b, .. } => *b,
236 _ => 255,
237 }
238}
239
240#[inline(always)]
246fn interpolate(start: u8, end: u8, step: usize, total: usize) -> u8 {
247 let start_f = start as f32;
248 let end_f = end as f32;
249 let ratio = step as f32 / (total - 1).max(1) as f32;
250 (start_f + (end_f - start_f) * ratio).round() as u8
251}