playa 0.1.125

Image sequence player for VFX (EXR, PNG, JPEG, TIFF). Pure Rust with optional OpenEXR support.
use eframe::egui;
use egui_ltreeview::TreeView;

/// Settings categories
#[derive(Debug, Clone, Copy, PartialEq)]
enum SettingsCategory {
    General,
    UI,
}

impl SettingsCategory {
    fn as_str(&self) -> &'static str {
        match self {
            SettingsCategory::General => "General",
            SettingsCategory::UI => "UI",
        }
    }

    fn from_str(s: &str) -> Option<Self> {
        match s {
            "General" => Some(SettingsCategory::General),
            "UI" => Some(SettingsCategory::UI),
            _ => None,
        }
    }
}

/// Application settings
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct AppSettings {
    // Playback
    pub fps: f32,
    pub loop_enabled: bool,

    // Shader
    pub current_shader: String,

    // UI
    pub show_help: bool,
    pub show_playlist: bool,
    pub dark_mode: bool,
    pub font_size: f32,

    // Cache/Workers
    pub cache_mem_percent: f32,     // 5..95; applied live to cache
    pub workers_override: u32,      // 0 = auto, N = override (applies on restart)

    // Encoding
    pub encoder_settings: crate::encode::EncoderSettings,

    // Internal
    pub selected_settings_category: Option<String>,
}

impl Default for AppSettings {
    fn default() -> Self {
        Self {
            fps: 24.0,
            loop_enabled: true,
            current_shader: "default".to_string(),
            show_help: true,
            show_playlist: true,
            dark_mode: true,
            font_size: 13.0,
            cache_mem_percent: 75.0,
            workers_override: 0,
            encoder_settings: crate::encode::EncoderSettings::default(),
            selected_settings_category: Some("UI".to_string()),
        }
    }
}

/// Render General settings category
fn render_general_settings(ui: &mut egui::Ui, _settings: &mut AppSettings) {
    ui.label("(No settings yet)");
    ui.add_space(8.0);
    ui.label("General settings will be added here in the future.");
}

/// Render UI settings category
fn render_ui_settings(ui: &mut egui::Ui, settings: &mut AppSettings) {
    ui.heading("Appearance");
    ui.add_space(8.0);

    ui.label("Font Size:");
    ui.add(
        egui::Slider::new(&mut settings.font_size, 10.0..=18.0)
            .suffix(" px")
            .step_by(0.5)
    );
    ui.add_space(16.0);

    ui.checkbox(&mut settings.dark_mode, "Dark Mode");

    ui.add_space(16.0);
    ui.heading("Performance");
    ui.add_space(8.0);

    ui.label("Cache Memory Budget:");
    ui.add(
        egui::Slider::new(&mut settings.cache_mem_percent, 5.0..=95.0)
            .suffix(" %")
            .step_by(1.0)
    );
    ui.label("Applies immediately. Controls the fraction of available RAM used by the frame cache.");

    ui.add_space(8.0);
    ui.label("Worker Threads Override (0 = Auto):");
    ui.add(
        egui::DragValue::new(&mut settings.workers_override)
            .speed(1.0)
            .range(0..=256)
    );
    ui.label("Takes effect on next launch. Defaults to ~75% of CPU cores.");
}

/// Render settings window
pub fn render_settings_window(
    ctx: &egui::Context,
    show_settings: &mut bool,
    settings: &mut AppSettings,
) {
    // Get selected category from settings or use default
    let mut selected = settings
        .selected_settings_category
        .as_ref()
        .and_then(|s| SettingsCategory::from_str(s))
        .unwrap_or(SettingsCategory::UI);

    egui::Window::new("Settings")
        .id(egui::Id::new("settings_window"))
        .open(show_settings)
        .default_size([700.0, 500.0])
        .min_size([500.0, 400.0])
        .resizable(true)
        .collapsible(false)
        .show(ctx, |ui| {
            egui::ScrollArea::both()
                .auto_shrink([false; 2])
                .show(ui, |ui| {
            ui.horizontal(|ui| {
                // Left panel: TreeView (200px fixed width)
                ui.vertical(|ui| {
                    ui.set_width(200.0);
                    ui.add_space(4.0);

                    let tree_id = ui.make_persistent_id("settings_tree_view");
                    let (_response, actions) = TreeView::new(tree_id).show(ui, |builder| {
                        builder.leaf(0, SettingsCategory::General.as_str());
                        builder.leaf(1, SettingsCategory::UI.as_str());
                    });

                    // Handle selection from actions
                    for action in actions {
                        if let egui_ltreeview::Action::SetSelected(node_ids) = action {
                            if let Some(&node_id) = node_ids.iter().next() {
                                selected = match node_id {
                                    0 => SettingsCategory::General,
                                    1 => SettingsCategory::UI,
                                    _ => selected,
                                };
                            }
                        }
                    }
                });

                ui.separator();

                // Right panel: content for selected category
                ui.vertical(|ui| {
                    ui.add_space(8.0);

                    match selected {
                        SettingsCategory::General => render_general_settings(ui, settings),
                        SettingsCategory::UI => render_ui_settings(ui, settings),
                    }
                });
            });
                });
        });

    // Save selected category
    settings.selected_settings_category = Some(selected.as_str().to_string());
}