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