use ratatui::style::{Color, Modifier, Style};
use ratatui::text::Span;
use crate::tui::theme::VerbColor;
pub fn render_verb_gradient(
text: &str,
verb_color: &VerbColor,
frame: u8,
is_complete: bool,
) -> Vec<Span<'static>> {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 {
return vec![];
}
let base_rgb = verb_color.rgb_tuple();
let glow_rgb = verb_color.glow_tuple();
let sparkle_rgb: (u8, u8, u8) = (255, 255, 255);
let dark_rgb: (u8, u8, u8) = (base_rgb.0 / 2, base_rgb.1 / 2, base_rgb.2 / 2);
let bg_base: (u8, u8, u8) = (
12 + base_rgb.0 / 15,
12 + base_rgb.1 / 15,
12 + base_rgb.2 / 15,
);
chars
.into_iter()
.enumerate()
.map(|(i, c)| {
let phase_offset = (i as f32) * std::f32::consts::PI * 0.6; let time = (frame as f32 / 12.0) * std::f32::consts::PI; let wave = ((time + phase_offset).sin() + 1.0) / 2.0;
let shimmer_time = (frame as f32 / 20.0) * std::f32::consts::PI;
let shimmer = ((shimmer_time + phase_offset * 2.0).sin() + 1.0) / 2.0;
if is_complete {
let (r, g, b) = if wave < 0.25 {
let t = wave * 4.0;
(
(dark_rgb.0 as f32 * (1.0 - t) + base_rgb.0 as f32 * t) as u8,
(dark_rgb.1 as f32 * (1.0 - t) + base_rgb.1 as f32 * t) as u8,
(dark_rgb.2 as f32 * (1.0 - t) + base_rgb.2 as f32 * t) as u8,
)
} else if wave < 0.6 {
let t = (wave - 0.25) / 0.35;
(
(base_rgb.0 as f32 * (1.0 - t) + glow_rgb.0 as f32 * t) as u8,
(base_rgb.1 as f32 * (1.0 - t) + glow_rgb.1 as f32 * t) as u8,
(base_rgb.2 as f32 * (1.0 - t) + glow_rgb.2 as f32 * t) as u8,
)
} else {
let t = ((wave - 0.6) / 0.4) * (0.6 + shimmer * 0.4);
(
(glow_rgb.0 as f32 * (1.0 - t) + sparkle_rgb.0 as f32 * t) as u8,
(glow_rgb.1 as f32 * (1.0 - t) + sparkle_rgb.1 as f32 * t) as u8,
(glow_rgb.2 as f32 * (1.0 - t) + sparkle_rgb.2 as f32 * t) as u8,
)
};
let color = Color::Rgb(r, g, b);
let bg_pulse = 0.6 + wave * 0.4; let bg_r = (bg_base.0 as f32 * bg_pulse).min(255.0) as u8;
let bg_g = (bg_base.1 as f32 * bg_pulse).min(255.0) as u8;
let bg_b = (bg_base.2 as f32 * bg_pulse).min(255.0) as u8;
Span::styled(
c.to_string(),
Style::default()
.fg(color)
.bg(Color::Rgb(bg_r, bg_g, bg_b))
.add_modifier(Modifier::BOLD),
)
} else {
let wave_mild = 0.3 + wave * 0.5; let r =
(dark_rgb.0 as f32 * (1.0 - wave_mild) + glow_rgb.0 as f32 * wave_mild) as u8;
let g =
(dark_rgb.1 as f32 * (1.0 - wave_mild) + glow_rgb.1 as f32 * wave_mild) as u8;
let b =
(dark_rgb.2 as f32 * (1.0 - wave_mild) + glow_rgb.2 as f32 * wave_mild) as u8;
Span::styled(
c.to_string(),
Style::default()
.fg(Color::Rgb(r, g, b))
.add_modifier(Modifier::BOLD),
)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_verb_gradient_empty() {
let result = render_verb_gradient("", &VerbColor::Infer, 0, true);
assert!(result.is_empty());
}
#[test]
fn test_render_verb_gradient_single_char() {
let result = render_verb_gradient("a", &VerbColor::Infer, 0, true);
assert_eq!(result.len(), 1);
}
#[test]
fn test_render_verb_gradient_multi_char() {
let result = render_verb_gradient("invoke", &VerbColor::Invoke, 0, true);
assert_eq!(result.len(), 6);
}
#[test]
fn test_render_verb_gradient_partial() {
let result = render_verb_gradient("inv", &VerbColor::Invoke, 0, false);
assert_eq!(result.len(), 3);
}
#[test]
fn test_render_verb_gradient_different_frames() {
let result0 = render_verb_gradient("a", &VerbColor::Exec, 0, true);
let result30 = render_verb_gradient("a", &VerbColor::Exec, 30, true);
let result60 = render_verb_gradient("a", &VerbColor::Exec, 60, true);
assert_eq!(result0.len(), 1);
assert_eq!(result30.len(), 1);
assert_eq!(result60.len(), 1);
}
#[test]
fn test_render_verb_gradient_all_verb_colors() {
let colors = [
VerbColor::Infer,
VerbColor::Exec,
VerbColor::Fetch,
VerbColor::Invoke,
VerbColor::Agent,
VerbColor::Spawn,
VerbColor::User,
];
for color in colors {
let result = render_verb_gradient("test", &color, 0, true);
assert_eq!(result.len(), 4, "Failed for {:?}", color);
}
}
#[test]
fn test_render_verb_gradient_unicode() {
let result = render_verb_gradient("🦋", &VerbColor::Agent, 0, true);
assert_eq!(result.len(), 1);
}
}