loaders 0.0.0

A fully-featured, customisable progress bar and loading indicator library for Rust CLI and terminal applications
Documentation
//! Built-in progress bar themes.

use crate::style::color::{Color, ColorSpec};
use crate::style::style::ProgressStyle;

/// Built-in progress bar themes.
///
/// Themes are lightweight factories for `ProgressStyle` values.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Theme {
    /// ASCII default theme.
    Default,
    /// Heavy Unicode line theme.
    Unicode,
    /// Braille-inspired theme.
    Braille,
    /// Block character theme.
    Block,
    /// Minimal unboxed theme.
    Minimal,
    /// Color-enhanced default theme.
    Colorful,
    /// Shaded gradient theme.
    Gradient,
    /// Retro terminal theme.
    Retro,
    /// Dot-based theme.
    Dots,
    /// Rounded dot and line theme.
    Rounded,
    /// Small shaded-square theme.
    Shaded,
    /// Arrow-heavy theme.
    Arrows,
}

impl Theme {
    /// Returns a configured style for this theme.
    ///
    /// # Examples
    ///
    /// ```rust
    /// let style = loaders::Theme::Unicode.style();
    /// assert!(format!("{style:?}").contains("━"));
    /// ```
    pub fn style(&self) -> ProgressStyle {
        match self {
            Self::Default => themed("[{bar:30}] {pos}/{len} {msg}", "#>-"),
            Self::Unicode => themed("[{bar:30}] {pos}/{len} {msg}", "━╸░"),
            Self::Braille => themed("[{bar:30}] {pos}/{len} {msg}", "⣿⣷⣦"),
            Self::Block => themed("[{bar:30}] {pos}/{len} {msg}", "█▉▒"),
            Self::Minimal => themed("{bar:30} {pos}/{len} {msg}", "=>."),
            Self::Colorful => themed("[{bar:30.green/red}] {percent}% {msg}", "#>-")
                .color(ColorSpec::new().set_fg(Color::BrightGreen).set_bold(true)),
            Self::Gradient => themed("[{bar:30}] {percent}% {msg}", "█▓▒░"),
            Self::Retro => themed("[{bar:30}] {pos}/{len} {msg}", "=>-"),
            Self::Dots => themed("[{bar:30}] {percent}% {msg}", "•∘ "),
            Self::Rounded => themed("[{bar:30}] {percent}% {msg}", "●○─"),
            Self::Shaded => themed("[{bar:30}] {percent}% {msg}", "▪▫ "),
            Self::Arrows => themed("[{bar:30}] {percent}% {msg}", ">->"),
        }
    }

    /// Returns the human-readable theme name.
    ///
    /// # Examples
    ///
    /// ```rust
    /// assert_eq!(loaders::Theme::Default.name(), "default");
    /// ```
    pub fn name(&self) -> &'static str {
        match self {
            Self::Default => "default",
            Self::Unicode => "unicode",
            Self::Braille => "braille",
            Self::Block => "block",
            Self::Minimal => "minimal",
            Self::Colorful => "colorful",
            Self::Gradient => "gradient",
            Self::Retro => "retro",
            Self::Dots => "dots",
            Self::Rounded => "rounded",
            Self::Shaded => "shaded",
            Self::Arrows => "arrows",
        }
    }

    /// Returns all built-in themes.
    ///
    /// # Examples
    ///
    /// ```rust
    /// assert!(loaders::Theme::all().len() >= 12);
    /// ```
    pub fn all() -> &'static [Theme] {
        static THEMES: [Theme; 12] = [
            Theme::Default,
            Theme::Unicode,
            Theme::Braille,
            Theme::Block,
            Theme::Minimal,
            Theme::Colorful,
            Theme::Gradient,
            Theme::Retro,
            Theme::Dots,
            Theme::Rounded,
            Theme::Shaded,
            Theme::Arrows,
        ];
        &THEMES
    }
}

fn themed(template: &str, chars: &str) -> ProgressStyle {
    match ProgressStyle::with_template(template) {
        Ok(style) => style.progress_chars(chars),
        Err(_) => ProgressStyle::default_bar().progress_chars(chars),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    #[test]
    fn test_all_themes_return_valid_style() {
        for theme in Theme::all() {
            assert!(!theme.style().parsed_template.parts.is_empty());
        }
    }

    #[test]
    fn test_theme_names_unique() {
        let names: HashSet<&str> = Theme::all().iter().map(Theme::name).collect();
        assert_eq!(names.len(), Theme::all().len());
    }

    #[test]
    fn test_theme_all_count() {
        assert_eq!(Theme::all().len(), 12);
    }

    #[test]
    fn test_default_theme_style() {
        assert_eq!(Theme::Default.style().progress_chars, "#>-");
    }

    #[test]
    fn test_colorful_theme_has_color() {
        assert!(Theme::Colorful.style().color_spec.is_some());
    }
}