tinterm 0.2.0

A powerful library for vibrant solid and gradient text with shimmer animations in terminal outputs.
Documentation
/// Represents an RGB color with red, green, and blue components.
///
/// Each component is a value between 0 and 255, where:
/// - 0 represents no intensity
/// - 255 represents full intensity
///
/// # Examples
///
/// ```
/// use tinterm::Color;
///
/// // Create a red color
/// let red = Color::new(255, 0, 0);
///
/// // Use predefined colors
/// let blue = Color::BLUE;
///
/// // Create from hex string
/// let orange = Color::from_hex("#FF8000").unwrap();
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
    /// Red component (0-255)
    pub r: u8,
    /// Green component (0-255)
    pub g: u8,
    /// Blue component (0-255)
    pub b: u8,
}

impl Color {
    /// Creates a new Color with the specified RGB values.
    ///
    /// # Arguments
    ///
    /// * `r` - Red component (0-255)
    /// * `g` - Green component (0-255)
    /// * `b` - Blue component (0-255)
    ///
    /// # Examples
    ///
    /// ```
    /// use tinterm::Color;
    ///
    /// let purple = Color::new(128, 0, 128);
    /// ```
    pub fn new(r: u8, g: u8, b: u8) -> Self {
        Color { r, g, b }
    }

    /// Creates a new Color with the specified RGB values.
    ///
    /// This is an alias for [`Color::new`].
    ///
    /// # Examples
    ///
    /// ```
    /// use tinterm::Color;
    ///
    /// let cyan = Color::rgb(0, 255, 255);
    /// ```
    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
        Color { r, g, b }
    }

    pub(crate) fn interpolate(&self, other: &Color, t: f32) -> Color {
        Color {
            r: (self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
            g: (self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
            b: (self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
        }
    }

    /// Creates a new Color from a hexadecimal color string.
    ///
    /// Supports both 6-digit (e.g., "#FF0000" or "FF0000") and 3-digit (e.g., "#F00" or "F00") formats.
    /// The '#' prefix is optional.
    ///
    /// # Arguments
    ///
    /// * `hex` - A hex color string
    ///
    /// # Returns
    ///
    /// * `Ok(Color)` - If the hex string is valid
    /// * `Err(&str)` - If the hex string is invalid
    ///
    /// # Examples
    ///
    /// ```
    /// use tinterm::Color;
    ///
    /// let red = Color::from_hex("#FF0000").unwrap();
    /// let green = Color::from_hex("00FF00").unwrap();
    /// let blue = Color::from_hex("#00F").unwrap();
    /// ```
    pub fn from_hex(hex: &str) -> Result<Self, &'static str> {
        // Remove '#' if present
        let hex = hex.trim_start_matches('#');

        // Validate hex length
        if hex.len() != 6 && hex.len() != 3 {
            return Err(
                "Invalid hex color length. Expected 6 or 3 characters (e.g., FF0000 or F00)",
            );
        }

        // Validate hex characters
        if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
            return Err("Invalid hex color format. Expected hexadecimal characters (0-9, A-F)");
        }

        let (r, g, b) = if hex.len() == 3 {
            // Convert 3-digit format to 6-digit by duplicating each digit
            let r = u8::from_str_radix(
                &format!(
                    "{}{}",
                    hex.chars().nth(0).unwrap(),
                    hex.chars().nth(0).unwrap()
                ),
                16,
            )
            .map_err(|_| "Failed to parse red component")?;
            let g = u8::from_str_radix(
                &format!(
                    "{}{}",
                    hex.chars().nth(1).unwrap(),
                    hex.chars().nth(1).unwrap()
                ),
                16,
            )
            .map_err(|_| "Failed to parse green component")?;
            let b = u8::from_str_radix(
                &format!(
                    "{}{}",
                    hex.chars().nth(2).unwrap(),
                    hex.chars().nth(2).unwrap()
                ),
                16,
            )
            .map_err(|_| "Failed to parse blue component")?;
            (r, g, b)
        } else {
            // Parse 6-digit format
            let r =
                u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Failed to parse red component")?;
            let g = u8::from_str_radix(&hex[2..4], 16)
                .map_err(|_| "Failed to parse green component")?;
            let b =
                u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Failed to parse blue component")?;
            (r, g, b)
        };

        Ok(Color { r, g, b })
    }

    /// Converts the color to a hexadecimal string representation.
    ///
    /// Returns a 6-digit uppercase hex string with '#' prefix.
    ///
    /// # Examples
    ///
    /// ```
    /// use tinterm::Color;
    ///
    /// let red = Color::new(255, 0, 0);
    /// assert_eq!(red.to_hex(), "#FF0000");
    /// ```
    pub fn to_hex(&self) -> String {
        format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
    }
}