tinterm 0.2.0

A powerful library for vibrant solid and gradient text with shimmer animations in terminal outputs.
Documentation
use crate::color::Color;
use std::io::{self, Write};
use std::thread;
use std::time::Duration;

pub trait Shimmer {
    fn shimmer(&self, color: Color, background: Option<Color>) -> ShimmerText;
    fn shimmer_gradient(
        &self,
        start_color: Color,
        end_color: Color,
        background: Option<Color>,
    ) -> ShimmerText;
    fn shine(&self, color: Color) -> ShimmerText;
    fn glow(&self, color: Color, intensity: u8) -> ShimmerText;
}

#[derive(Clone)]
pub struct ShimmerText {
    text: String,
    color: Color,
    background: Option<Color>,
    gradient: Option<(Color, Color)>,
    animation_type: AnimationType,
    speed: Duration,
    intensity: u8,
}

#[derive(Clone, Copy)]
enum AnimationType {
    Shimmer,
    Shine,
    Glow,
    Gradient,
}

impl ShimmerText {
    fn new(text: String, color: Color, animation_type: AnimationType) -> Self {
        Self {
            text,
            color,
            background: None,
            gradient: None,
            animation_type,
            speed: Duration::from_millis(150), // Slower default for better visibility
            intensity: 255,
        }
    }

    pub fn speed(mut self, millis: u64) -> Self {
        self.speed = Duration::from_millis(millis);
        self
    }

    pub fn background(mut self, color: Color) -> Self {
        self.background = Some(color);
        self
    }

    pub fn intensity(mut self, intensity: u8) -> Self {
        self.intensity = intensity;
        self
    }

    pub fn animate(&self, duration_secs: u64) {
        let total_frames = (duration_secs * 1000) / self.speed.as_millis() as u64;

        for frame in 0..total_frames {
            // Clear the line and return to beginning
            print!("\r\x1b[K");

            // Render the frame
            match self.animation_type {
                AnimationType::Shimmer => self.render_shimmer_frame(frame),
                AnimationType::Shine => self.render_shine_frame(frame),
                AnimationType::Glow => self.render_glow_frame(frame),
                AnimationType::Gradient => self.render_gradient_frame(frame),
            }

            // Flush output to ensure immediate display
            io::stdout().flush().unwrap();

            // Sleep for the frame duration
            thread::sleep(self.speed);
        }

        // Final newline after animation
        println!();
    }

    pub fn static_render(&self) -> String {
        match self.animation_type {
            AnimationType::Shimmer => self.render_shimmer_static(),
            AnimationType::Shine => self.render_shine_static(),
            AnimationType::Glow => self.render_glow_static(),
            AnimationType::Gradient => self.render_gradient_static(),
        }
    }

    fn render_shimmer_frame(&self, frame: u64) {
        let text_len = self.visible_char_count();
        if text_len == 0 {
            return;
        }

        // Move the highlight position more slowly for better visibility
        let highlight_pos = (frame as usize) % (text_len + 6);
        let mut result = String::new();

        if let Some(bg) = self.background {
            result.push_str(&format!("\x1b[48;2;{};{};{}m", bg.r, bg.g, bg.b));
        }

        let mut visible_index = 0;
        for ch in self.text.chars() {
            if ch == '\n' {
                result.push(ch);
                continue;
            }

            let is_highlight = visible_index >= highlight_pos.saturating_sub(3)
                && visible_index <= highlight_pos.saturating_add(3);

            if is_highlight {
                let distance = visible_index.abs_diff(highlight_pos) as f32;
                let brightness_factor = 2.2 - (distance * 0.15); // More dramatic brightness change
                let bright_color = self.brighten_color(self.color, brightness_factor);
                result.push_str(&format!(
                    "\x1b[38;2;{};{};{}m\x1b[1m",
                    bright_color.r, bright_color.g, bright_color.b
                ));
            } else {
                result.push_str(&format!(
                    "\x1b[38;2;{};{};{}m",
                    self.color.r, self.color.g, self.color.b
                ));
            }

            result.push(ch);
            visible_index += 1;
        }

        result.push_str("\x1b[0m");
        print!("{}", result);
    }

    fn render_shine_frame(&self, frame: u64) {
        let text_len = self.visible_char_count();
        if text_len == 0 {
            return;
        }

        let wave_pos =
            (frame as f64 * 0.3).sin() * (text_len as f64 / 2.0) + (text_len as f64 / 2.0);
        let mut result = String::new();

        if let Some(bg) = self.background {
            result.push_str(&format!("\x1b[48;2;{};{};{}m", bg.r, bg.g, bg.b));
        }

        let mut visible_index = 0;
        for ch in self.text.chars() {
            if ch == '\n' {
                result.push(ch);
                continue;
            }

            let distance = (visible_index as f64 - wave_pos).abs();
            let brightness = if distance < 3.0 {
                1.0 + (0.8 * (1.0 - distance / 3.0))
            } else {
                1.0
            };

            let bright_color = self.brighten_color(self.color, brightness as f32);
            result.push_str(&format!(
                "\x1b[38;2;{};{};{}m",
                bright_color.r, bright_color.g, bright_color.b
            ));
            result.push(ch);
            visible_index += 1;
        }

        result.push_str("\x1b[0m");
        print!("{}", result);
    }

    fn render_glow_frame(&self, frame: u64) {
        let pulse = ((frame as f64 * 0.3).sin() + 1.0) / 2.0; // Faster pulsing
        let glow_intensity = 0.7 + (pulse * 0.6); // More intense glow variation

        let glowing_color = self.brighten_color(self.color, glow_intensity as f32);

        let mut result = String::new();
        if let Some(bg) = self.background {
            result.push_str(&format!("\x1b[48;2;{};{};{}m", bg.r, bg.g, bg.b));
        }

        result.push_str(&format!(
            "\x1b[38;2;{};{};{}m{}\x1b[0m",
            glowing_color.r, glowing_color.g, glowing_color.b, self.text
        ));
        print!("{}", result);
    }

    fn render_gradient_frame(&self, frame: u64) {
        if let Some((start, end)) = self.gradient {
            let shift = (frame as f64 * 0.05).sin() * 0.3 + 0.5;
            let text_len = self.visible_char_count();
            let mut result = String::new();

            if let Some(bg) = self.background {
                result.push_str(&format!("\x1b[48;2;{};{};{}m", bg.r, bg.g, bg.b));
            }

            let mut visible_index = 0;
            for ch in self.text.chars() {
                if ch == '\n' {
                    result.push(ch);
                    continue;
                }

                let progress = if text_len > 1 {
                    (visible_index as f64 / (text_len - 1) as f64 + shift) % 1.0
                } else {
                    shift
                };

                let color = start.interpolate(&end, progress as f32);
                result.push_str(&format!("\x1b[38;2;{};{};{}m", color.r, color.g, color.b));
                result.push(ch);
                visible_index += 1;
            }

            result.push_str("\x1b[0m");
            print!("{}", result);
        }
    }

    fn render_shimmer_static(&self) -> String {
        format!(
            "\x1b[38;2;{};{};{}m\x1b[1m{}\x1b[0m",
            self.color.r, self.color.g, self.color.b, self.text
        )
    }

    fn render_shine_static(&self) -> String {
        let bright_color = self.brighten_color(self.color, 1.3);
        format!(
            "\x1b[38;2;{};{};{}m{}\x1b[0m",
            bright_color.r, bright_color.g, bright_color.b, self.text
        )
    }

    fn render_glow_static(&self) -> String {
        let glowing_color = self.brighten_color(self.color, 1.2);
        format!(
            "\x1b[38;2;{};{};{}m{}\x1b[0m",
            glowing_color.r, glowing_color.g, glowing_color.b, self.text
        )
    }

    fn render_gradient_static(&self) -> String {
        if let Some((start, end)) = self.gradient {
            let text_len = self.visible_char_count();
            let mut result = String::new();

            let mut visible_index = 0;
            for ch in self.text.chars() {
                if ch == '\n' {
                    result.push(ch);
                    continue;
                }

                let progress = if text_len > 1 {
                    visible_index as f32 / (text_len - 1) as f32
                } else {
                    0.0
                };

                let color = start.interpolate(&end, progress);
                result.push_str(&format!("\x1b[38;2;{};{};{}m", color.r, color.g, color.b));
                result.push(ch);
                visible_index += 1;
            }

            result.push_str("\x1b[0m");
            result
        } else {
            self.text.clone()
        }
    }

    fn visible_char_count(&self) -> usize {
        self.text.chars().filter(|&c| c != '\n').count()
    }

    fn brighten_color(&self, color: Color, factor: f32) -> Color {
        let factor = factor.clamp(0.0, 2.0);
        Color {
            r: ((color.r as f32 * factor).min(255.0)) as u8,
            g: ((color.g as f32 * factor).min(255.0)) as u8,
            b: ((color.b as f32 * factor).min(255.0)) as u8,
        }
    }
}

impl Shimmer for str {
    fn shimmer(&self, color: Color, background: Option<Color>) -> ShimmerText {
        let mut shimmer = ShimmerText::new(self.to_string(), color, AnimationType::Shimmer);
        if let Some(bg) = background {
            shimmer = shimmer.background(bg);
        }
        shimmer
    }

    fn shimmer_gradient(
        &self,
        start_color: Color,
        end_color: Color,
        background: Option<Color>,
    ) -> ShimmerText {
        let mut shimmer = ShimmerText::new(self.to_string(), start_color, AnimationType::Gradient);
        shimmer.gradient = Some((start_color, end_color));
        if let Some(bg) = background {
            shimmer = shimmer.background(bg);
        }
        shimmer
    }

    fn shine(&self, color: Color) -> ShimmerText {
        ShimmerText::new(self.to_string(), color, AnimationType::Shine)
    }

    fn glow(&self, color: Color, intensity: u8) -> ShimmerText {
        ShimmerText::new(self.to_string(), color, AnimationType::Glow).intensity(intensity)
    }
}

impl Shimmer for String {
    fn shimmer(&self, color: Color, background: Option<Color>) -> ShimmerText {
        let mut shimmer = ShimmerText::new(self.clone(), color, AnimationType::Shimmer);
        if let Some(bg) = background {
            shimmer = shimmer.background(bg);
        }
        shimmer
    }

    fn shimmer_gradient(
        &self,
        start_color: Color,
        end_color: Color,
        background: Option<Color>,
    ) -> ShimmerText {
        let mut shimmer = ShimmerText::new(self.clone(), start_color, AnimationType::Gradient);
        shimmer.gradient = Some((start_color, end_color));
        if let Some(bg) = background {
            shimmer = shimmer.background(bg);
        }
        shimmer
    }

    fn shine(&self, color: Color) -> ShimmerText {
        ShimmerText::new(self.clone(), color, AnimationType::Shine)
    }

    fn glow(&self, color: Color, intensity: u8) -> ShimmerText {
        ShimmerText::new(self.clone(), color, AnimationType::Glow).intensity(intensity)
    }
}

impl std::fmt::Display for ShimmerText {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.static_render())
    }
}