termcinema-engine 0.1.0

🧠 Core typewriter-style terminal animation engine (SVG renderer) for termcinema
Documentation
use crate::core::{ControlSpec, CursorSpec, StyleSpec};
use once_cell::sync::Lazy;
use std::convert::Into;
use std::fmt;

/// Represents a full visual theme preset.
///
/// A `ThemePreset` bundles together style, cursor, and control settings,
/// each of which affects the appearance and animation behavior of the output.
///
/// Themes can be predefined or user-resolved via name matching.
pub struct ThemePreset {
    /// Display name of the theme (used for resolution and UI).
    pub name: &'static str,

    /// Font and color settings.
    pub style: StyleSpec,

    /// Cursor shape and animation behavior.
    pub cursor: CursorSpec,

    /// Typing speed and visual effects.
    pub control: ControlSpec,
}

impl ThemePreset {
    /// Clone the style component.
    pub fn to_style(&self) -> StyleSpec {
        self.style.clone()
    }

    /// Clone the cursor component.
    pub fn to_cursor(&self) -> CursorSpec {
        self.cursor.clone()
    }

    /// Clone the control component.
    pub fn to_control(&self) -> ControlSpec {
        self.control.clone()
    }
}

/// Errors that can occur during theme resolution.
#[derive(Debug)]
pub enum ThemeError {
    /// Theme not found by name.
    NotFound(String),
}

impl fmt::Display for ThemeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ThemeError::NotFound(name) => write!(f, "Theme `{}` not found.", name),
        }
    }
}

impl std::error::Error for ThemeError {}

/// Resolves a theme by name (case-insensitive, underscores allowed).
///
/// If no name is provided, falls back to default config values.
///
/// Returns a triple: `(style, cursor, control)` used in rendering.
pub fn resolve_theme(
    name: Option<&str>,
) -> Result<(StyleSpec, CursorSpec, ControlSpec), ThemeError> {
    if let Some(name) = name {
        match get_theme_by_name(name) {
            Some(theme) => Ok((theme.to_style(), theme.to_cursor(), theme.to_control())),
            None => Err(ThemeError::NotFound(name.to_string())),
        }
    } else {
        Ok((
            StyleSpec::default(),
            CursorSpec::default(),
            ControlSpec::default(),
        ))
    }
}

// β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€πŸŽ¨ Theme Collection ───────────────

/// Retro green-on-black terminal preset.
///
/// Inspired by classic VT100-style terminals with green text on a black background.
/// Uses the VGA8 font for a highly aligned, pixel-perfect retro aesthetic.
pub static RETRO_TTY: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Retro TTY",
    style: StyleSpec {
        font_size: 18,
        font_family: "PxPlus IBM VGA8".into(),
        text_color: Some("#00FF00".into()),
        background_color: Some("#000000".into()),
    },
    cursor: CursorSpec {
        char: "|".into(),
        offset_x: 20,
        color: Some("#00FF00".into()),
        blink: true,
        blink_ms: 800,
        opacity: 1.0,
    },
    control: ControlSpec {
        frame_delay: Some(60),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// DOS Classic theme β€” black background with gray text and Courier font.
///
/// Emulates traditional DOS environments, featuring a blinking underscore cursor.
pub static DOS_CLASSIC: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "DOS Classic",
    style: StyleSpec {
        font_size: 18,
        font_family: "Courier New".into(),
        text_color: Some("#C0C0C0".into()),
        background_color: Some("#000000".into()),
    },
    cursor: CursorSpec {
        char: "_".into(),
        offset_x: 20,
        color: None,
        blink: true,
        blink_ms: 700,
        opacity: 1.0,
    },
    control: ControlSpec {
        frame_delay: Some(70),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// DOS Blue theme β€” blue background with white text.
///
/// Simulates classic UIs like `edit.com` or Norton Commander, with a block-style cursor.
pub static DOS_BLUE: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "DOS Blue",
    style: StyleSpec {
        font_size: 18,
        font_family: "PxPlus IBM VGA8".into(),
        text_color: Some("#FFFFFF".into()),
        background_color: Some("#0000AA".into()), // VGA 蓝
    },
    cursor: CursorSpec {
        char: "β–ˆ".into(),
        offset_x: 20,
        color: Some("#FFFFFF".into()),
        blink: true,
        blink_ms: 650,
        opacity: 0.9,
    },
    control: ControlSpec {
        frame_delay: Some(80),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Solarized Dark theme β€” low-contrast dark background with muted text colors.
///
/// Based on the popular Solarized palette, optimized for readability in low light.
pub static SOLARIZED_DARK: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Solarized Dark",
    style: StyleSpec {
        font_size: 18,
        font_family: "Fira Code".into(),
        text_color: Some("#839496".into()),
        background_color: Some("#002b36".into()),
    },
    cursor: CursorSpec {
        char: "▏".into(),
        offset_x: 20,
        color: Some("#93a1a1".into()),
        blink: true,
        blink_ms: 850,
        opacity: 0.9,
    },
    control: ControlSpec {
        frame_delay: Some(60),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Gruvbox Dark theme β€” warm, earthy tones on a dark background.
///
/// Popular among Vim users and terminal customizers. Cursor color matches the accent.
pub static GRUVBOX_DARK: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Gruvbox Dark",
    style: StyleSpec {
        font_size: 18,
        font_family: "Source Code Pro".into(),
        text_color: Some("#ebdbb2".into()),
        background_color: Some("#282828".into()),
    },
    cursor: CursorSpec {
        char: "▍".into(),
        offset_x: 20,
        color: Some("#fe8019".into()),
        blink: true,
        blink_ms: 750,
        opacity: 1.0,
    },
    control: ControlSpec {
        frame_delay: Some(60),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Light Shell theme β€” white background with soft gray text.
///
/// A clean, modern preset ideal for light-themed terminals or documentation demos.
pub static LIGHT_SHELL: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Light Shell",
    style: StyleSpec {
        font_size: 18,
        font_family: "Monospace".into(),
        text_color: Some("#444444".into()),
        background_color: Some("#ffffff".into()),
    },
    cursor: CursorSpec {
        char: "|".into(),
        offset_x: 20,
        color: Some("#888888".into()),
        blink: true,
        blink_ms: 1000,
        opacity: 0.7,
    },
    control: ControlSpec {
        frame_delay: Some(60),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Solarized Light theme β€” balanced warm colors on a light background.
///
/// Designed for daylight use, retaining the Solarized visual philosophy.
pub static SOLARIZED_LIGHT: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Solarized Light",
    style: StyleSpec {
        font_size: 18,
        font_family: "Monospace".into(),
        text_color: Some("#586e75".into()),
        background_color: Some("#fdf6e3".into()),
    },
    cursor: CursorSpec {
        char: "|".into(),
        offset_x: 20,
        color: Some("#657b83".into()),
        blink: true,
        blink_ms: 850,
        opacity: 0.9,
    },
    control: ControlSpec {
        frame_delay: Some(60),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Neon Night theme β€” dark background with bright magenta text.
///
/// Mimics neon signage aesthetics, suitable for retro-futuristic demos.
pub static NEON_NIGHT: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Neon Night",
    style: StyleSpec {
        font_size: 18,
        font_family: "Cascadia Mono".into(),
        text_color: Some("#FF00FF".into()),
        background_color: Some("#0f0f1a".into()),
    },
    cursor: CursorSpec {
        char: "﹏".into(),
        offset_x: 20,
        color: Some("#FF00FF".into()),
        blink: true,
        blink_ms: 650,
        opacity: 0.9,
    },
    control: ControlSpec {
        frame_delay: Some(75),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Poke Terminal β€” inspired by GameBoy or PokΓ©mon-style interfaces.
///
/// Golden yellow on dark gray, with a lightning-shaped cursor for added personality.
pub static POKE_TERMINAL: Lazy<ThemePreset> = Lazy::new(|| ThemePreset {
    name: "Poke Terminal",
    style: StyleSpec {
        font_size: 18,
        font_family: "Monospace".into(),
        text_color: Some("#FFD700".into()),
        background_color: Some("#2b2b2b".into()),
    },
    cursor: CursorSpec {
        char: "⚑".into(), // ζˆ–θ€… "β—‰" / "∎"
        offset_x: 30,
        color: Some("#FFD700".into()),
        blink: true,
        blink_ms: 800,
        opacity: 1.0,
    },
    control: ControlSpec {
        frame_delay: Some(50),
        fade_duration: Some(100),
        ..Default::default()
    },
});

/// Returns a list of all built-in theme presets.
///
/// Useful for CLI completions, GUI dropdowns, or documentation generation.
pub fn all_presets() -> Vec<&'static ThemePreset> {
    vec![
        &RETRO_TTY,
        &DOS_CLASSIC,
        &DOS_BLUE,
        &SOLARIZED_DARK,
        &GRUVBOX_DARK,
        &LIGHT_SHELL,
        &SOLARIZED_LIGHT,
        &NEON_NIGHT,
        &POKE_TERMINAL,
    ]
}

/// Looks up a theme by its display name (case-insensitive).
///
/// Accepts name normalization (`"Solarized_Light"` β†’ `"Solarized Light"`).
/// Returns `None` if no match is found.
pub fn get_theme_by_name(name: &str) -> Option<&'static ThemePreset> {
    let normalized = name.to_lowercase().replace('_', " ");
    all_presets()
        .into_iter()
        .find(|preset| preset.name.to_lowercase() == normalized)
}