modcli/output/
gradient.rs

1use crossterm::style::{Color, Stylize};
2
3/// Generates a horizontal gradient between two colors.
4/// This can be used for printing rainbow text or gradual transitions.
5/// This function returns a single string with the gradient applied.
6/// The text is split into characters, and each character is colored
7/// according to its position in the gradient.
8/// The gradient is calculated by interpolating between the start and end colors
9/// based on the character's index.
10/// The result is a string where each character is styled with its corresponding color.
11pub 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); // estimate
15
16    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    result
25}
26
27/// Alias for generate()
28/// This function is a convenience function that calls the `generate` function
29/// with the provided text and colors.
30/// It returns the generated gradient string.
31#[inline(always)]
32pub fn two_color(text: &str, start: Color, end: Color) -> String {
33    generate(text, start, end)
34}
35
36/// Generates a 3-color gradient (start -> mid, mid -> end)
37/// This function creates a gradient that transitions from the start color to the mid color,
38/// and then from the mid color to the end color.
39pub fn three_color(text: &str, start: Color, mid: Color, end: Color) -> String {
40    let chars: Vec<char> = text.chars().collect();
41    let total = chars.len().max(1);
42    let midpoint = total / 2;
43    let mut result = String::with_capacity(text.len() * 10);
44
45    for (i, c) in chars.iter().enumerate() {
46        let (from, to, step, steps) = if i < midpoint {
47            (start, mid, i, midpoint)
48        } else {
49            (mid, end, i - midpoint, total - midpoint)
50        };
51
52        let r = interpolate(get_r(&from), get_r(&to), step, steps);
53        let g = interpolate(get_g(&from), get_g(&to), step, steps);
54        let b = interpolate(get_b(&from), get_b(&to), step, steps);
55        let color = Color::Rgb { r, g, b };
56        result.push_str(&c.to_string().with(color).to_string());
57    }
58
59    result
60}
61
62/// Generates a gradient from a vector of colors, distributed across text.
63/// This function creates a gradient that transitions through multiple colors.
64/// The colors are evenly distributed across the text.
65pub fn multi_color(text: &str, colors: Vec<Color>) -> String {
66    let chars: Vec<char> = text.chars().collect();
67    let steps = chars.len().max(1);
68    let segments = colors.len().saturating_sub(1).max(1);
69    let mut result = String::with_capacity(text.len() * 10);
70
71    for (i, c) in chars.iter().enumerate() {
72        let t = i as f32 / (steps - 1).max(1) as f32;
73        let seg_float = t * segments as f32;
74        let seg = seg_float.floor() as usize;
75        let seg_t = seg_float - seg as f32;
76
77        let from = colors.get(seg).unwrap_or(&colors[0]);
78        let to = colors.get(seg + 1).unwrap_or(from);
79
80        let r = interpolate(get_r(from), get_r(to), (seg_t * 100.0) as usize, 100);
81        let g = interpolate(get_g(from), get_g(to), (seg_t * 100.0) as usize, 100);
82        let b = interpolate(get_b(from), get_b(to), (seg_t * 100.0) as usize, 100);
83
84        let color = Color::Rgb { r, g, b };
85        result.push_str(&c.to_string().with(color).to_string());
86    }
87
88    result
89}
90
91// Internal RGB helpers
92/// Gets the red, green, and blue components of a color.
93/// These functions extract the respective color components from a Color.
94/// If the color is not an RGB color, it returns 255.
95/// This is useful for interpolating colors in the gradient.
96/// The functions use pattern matching to check the color type.
97/// Get the red component of a color.
98/// This function extracts the red component from a Color.
99#[inline(always)]
100fn get_r(c: &Color) -> u8 {
101    match c {
102        Color::Rgb { r, .. } => *r,
103        _ => 255,
104    }
105}
106
107/// Get the green component of a color.
108/// This function extracts the green component from a Color.
109#[inline(always)]
110fn get_g(c: &Color) -> u8 {
111    match c {
112        Color::Rgb { g, .. } => *g,
113        _ => 255,
114    }
115}
116
117/// Get the blue component of a color.
118/// This function extracts the blue component from a Color.
119#[inline(always)]
120fn get_b(c: &Color) -> u8 {
121    match c {
122        Color::Rgb { b, .. } => *b,
123        _ => 255,
124    }
125}
126
127/// Interpolates between two color components.
128/// This function calculates the interpolated value between two color components.
129/// It takes the start and end values, the current step, and the total number of steps.
130/// The interpolation is done using a linear formula.
131/// The result is rounded to the nearest integer and returned as a u8.
132#[inline(always)]
133fn interpolate(start: u8, end: u8, step: usize, total: usize) -> u8 {
134    let start_f = start as f32;
135    let end_f = end as f32;
136    let ratio = step as f32 / (total - 1).max(1) as f32;
137    (start_f + (end_f - start_f) * ratio).round() as u8
138}