diff --git a/Cargo.toml b/Cargo.toml index 10132e0..7037907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,6 @@ edition = "2024" [dependencies] eframe = "0.31" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +dirs = "6" diff --git a/README.md b/README.md index c5aaa37..203b38a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ A Rust desktop app for generating simple tabletop dungeon layouts. - Generate control: - Master seed input (`u64`) - `Random` seed button - - `Generate New Layout` button generates using the current seed ## Generation Behavior @@ -56,3 +55,6 @@ cargo run - Rendering is vector-based in-app (`egui` painter), so zooming/scaling the preview remains crisp. - Room and corridor vectors are stored separately to support future export workflows (for example, SVG export). +- Settings are persisted on exit and restored on startup from: + - Windows: `%LOCALAPPDATA%/desktop_dungeon_generator/settings.json` + - Linux: `~/.local/share/desktop_dungeon_generator/settings.json` (or distro-equivalent local data dir) diff --git a/src/main.rs b/src/main.rs index 936d628..fd84e04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod layout; +mod persistence; mod seed; mod ui; @@ -26,7 +27,7 @@ struct DungeonApp { impl Default for DungeonApp { fn default() -> Self { - let settings = UiSettings::default(); + let settings = persistence::load_settings().unwrap_or_default(); let layout = generate_layout( settings.cols, settings.rows, @@ -43,6 +44,14 @@ impl Default for DungeonApp { } } +impl Drop for DungeonApp { + fn drop(&mut self) { + if let Err(err) = persistence::save_settings(&self.settings) { + eprintln!("Failed to save settings: {err}"); + } + } +} + impl eframe::App for DungeonApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let panel_result = draw_side_panel(ctx, &mut self.settings); diff --git a/src/persistence.rs b/src/persistence.rs new file mode 100644 index 0000000..29a868d --- /dev/null +++ b/src/persistence.rs @@ -0,0 +1,36 @@ +use std::fs; +use std::io; +use std::path::PathBuf; + +use crate::ui::UiSettings; + +const APP_DIR_NAME: &str = "desktop_dungeon_generator"; +const SETTINGS_FILE_NAME: &str = "settings.json"; + +pub fn load_settings() -> Option { + let path = settings_path()?; + let content = fs::read_to_string(path).ok()?; + serde_json::from_str::(&content).ok() +} + +pub fn save_settings(settings: &UiSettings) -> io::Result<()> { + let path = settings_path().ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + "No OS application data directory found", + ) + })?; + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let json = serde_json::to_string_pretty(settings) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + fs::write(path, json) +} + +fn settings_path() -> Option { + let base = dirs::data_local_dir().or_else(dirs::data_dir)?; + Some(base.join(APP_DIR_NAME).join(SETTINGS_FILE_NAME)) +} diff --git a/src/ui.rs b/src/ui.rs index 035cbb1..b0287ec 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,14 +1,22 @@ use crate::seed; use eframe::egui; use egui::RichText; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] enum Tab { Generate, Layout, } -#[derive(Debug, Clone)] +impl Default for Tab { + fn default() -> Self { + Self::Generate + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] pub struct UiSettings { pub seed: u64, pub cols: usize,