bevy_simple_screenshot 0.1.2

A plug-and-play screenshot library for Bevy 0.17+ with ring-buffered capture and automatic saving
//! Configuration types and TOML parsing for screenshot settings.

use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Position for burn-in text overlay.
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum BurnInPosition {
    UpperLeft,
    #[default]
    UpperRight,
    LowerLeft,
    LowerRight,
}

/// Configuration for burn-in text overlay on screenshots.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct BurnInConfig {
    /// Whether burn-in is enabled (default: false)
    pub enabled: bool,

    /// Path to a TTF font file for text rendering.
    /// Required when burn-in is enabled. If not provided, the system will panic.
    pub font_path: Option<PathBuf>,

    /// Show the frame number (default: true when burn-in is enabled)
    pub show_frame: bool,

    /// Show the screenshot key (default: false)
    pub show_key: bool,

    /// Show the screenshot description (default: false)
    pub show_description: bool,

    /// Position of the burn-in text (default: UpperRight)
    pub position: BurnInPosition,

    /// Padding from the edge in pixels (default: 5)
    pub padding: u32,

    /// Font size in pixels (default: 18.0)
    pub font_size: f32,
}

impl Default for BurnInConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            font_path: None,
            show_frame: true,
            show_key: false,
            show_description: false,
            position: BurnInPosition::UpperRight,
            padding: 5,
            font_size: 18.0,
        }
    }
}

impl BurnInConfig {
    /// Create a new burn-in config with burn-in enabled.
    pub fn enabled() -> Self {
        Self {
            enabled: true,
            ..Default::default()
        }
    }

    /// Set the font path for text rendering.
    /// Required when burn-in is enabled.
    pub fn with_font(mut self, path: impl Into<PathBuf>) -> Self {
        self.font_path = Some(path.into());
        self
    }

    /// Set whether to show frame number.
    pub fn with_show_frame(mut self, show: bool) -> Self {
        self.show_frame = show;
        self
    }

    /// Set whether to show key.
    pub fn with_show_key(mut self, show: bool) -> Self {
        self.show_key = show;
        self
    }

    /// Set whether to show description.
    pub fn with_show_description(mut self, show: bool) -> Self {
        self.show_description = show;
        self
    }

    /// Set the position.
    pub fn with_position(mut self, position: BurnInPosition) -> Self {
        self.position = position;
        self
    }

    /// Set the padding in pixels.
    pub fn with_padding(mut self, padding: u32) -> Self {
        self.padding = padding;
        self
    }

    /// Set the font size in pixels.
    pub fn with_font_size(mut self, size: f32) -> Self {
        self.font_size = size;
        self
    }
}

/// Main configuration for the screenshot system.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ScreenshotConfig {
    /// Directory to save screenshots (default: ".screenshots")
    pub output_dir: PathBuf,

    /// Default ring buffer capacity per key (default: 10)
    pub buffer_capacity: usize,

    /// Image format for saving (default: PNG)
    pub format: ImageFormat,

    /// JPEG quality if format is JPEG (default: High)
    pub jpeg_quality: JpegQuality,

    /// Filename pattern with placeholders (default: "{timestamp}_{key}_{description}")
    pub filename_pattern: String,

    /// Whether to auto-save to disk (default: true)
    pub auto_save: bool,

    /// Burn-in text overlay configuration
    #[serde(default)]
    pub burn_in: BurnInConfig,

    /// Per-key overrides
    #[serde(default)]
    pub keys: HashMap<String, KeyConfig>,
}

/// Image format for saving screenshots.
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ImageFormat {
    #[default]
    Png,
    Jpeg,
}

/// JPEG quality presets.
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum JpegQuality {
    Max,    // 100
    #[default]
    High,   // 90
    Medium, // 75
    Low,    // 50
}

impl JpegQuality {
    /// Convert quality preset to u8 value.
    pub fn as_u8(self) -> u8 {
        match self {
            Self::Max => 100,
            Self::High => 90,
            Self::Medium => 75,
            Self::Low => 50,
        }
    }
}

/// Per-key configuration overrides.
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct KeyConfig {
    pub buffer_capacity: Option<usize>,
    pub format: Option<ImageFormat>,
    pub jpeg_quality: Option<JpegQuality>,
    pub auto_save: Option<bool>,
    pub burn_in: Option<BurnInConfig>,
}

impl ScreenshotConfig {
    /// Load configuration from screenshots.toml in current directory, or use defaults.
    pub fn load() -> Self {
        let path = Path::new("screenshots.toml");
        if path.exists() {
            match std::fs::read_to_string(path) {
                Ok(content) => toml::from_str(&content).unwrap_or_default(),
                Err(_) => Self::default(),
            }
        } else {
            Self::default()
        }
    }

    /// Set the output directory.
    pub fn with_output_dir(mut self, path: impl Into<PathBuf>) -> Self {
        self.output_dir = path.into();
        self
    }

    /// Set the default buffer capacity.
    pub fn with_buffer_capacity(mut self, capacity: usize) -> Self {
        self.buffer_capacity = capacity;
        self
    }

    /// Set the image format.
    pub fn with_format(mut self, format: ImageFormat) -> Self {
        self.format = format;
        self
    }

    /// Set the JPEG quality.
    pub fn with_jpeg_quality(mut self, quality: JpegQuality) -> Self {
        self.jpeg_quality = quality;
        self
    }

    /// Set whether to auto-save screenshots.
    pub fn with_auto_save(mut self, auto_save: bool) -> Self {
        self.auto_save = auto_save;
        self
    }

    /// Set the filename pattern.
    pub fn with_filename_pattern(mut self, pattern: impl Into<String>) -> Self {
        self.filename_pattern = pattern.into();
        self
    }

    /// Set the burn-in configuration.
    pub fn with_burn_in(mut self, burn_in: BurnInConfig) -> Self {
        self.burn_in = burn_in;
        self
    }
}

impl Default for ScreenshotConfig {
    fn default() -> Self {
        Self {
            output_dir: PathBuf::from(".screenshots"),
            buffer_capacity: 10,
            format: ImageFormat::Png,
            jpeg_quality: JpegQuality::High,
            filename_pattern: "{timestamp}_{key}_{description}".to_string(),
            auto_save: true,
            burn_in: BurnInConfig::default(),
            keys: HashMap::new(),
        }
    }
}