2026-05-18 09:01:14 -05:00
|
|
|
/*
|
|
|
|
|
* Persistence logic for dungeon state.
|
|
|
|
|
* Handles saving and loading the complete dungeon state
|
|
|
|
|
* (settings and layout data) to and from disk using JSON.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-05-18 10:16:47 -05:00
|
|
|
use rfd::FileDialog;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
2026-04-20 10:23:49 -05:00
|
|
|
use crate::layout::DungeonLayout;
|
|
|
|
|
use crate::ui::UiSettings;
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
pub struct DungeonSave {
|
|
|
|
|
pub settings: UiSettings,
|
2026-04-28 10:13:06 -05:00
|
|
|
pub layouts: Vec<DungeonLayout>,
|
|
|
|
|
pub svgs: Vec<String>,
|
2026-04-20 10:23:49 -05:00
|
|
|
}
|
|
|
|
|
|
2026-05-18 10:30:59 -05:00
|
|
|
// Saves dungeon state to a user-selected JSON file.
|
2026-04-20 10:23:49 -05:00
|
|
|
pub fn save_dungeon(
|
|
|
|
|
settings: &UiSettings,
|
2026-04-28 10:13:06 -05:00
|
|
|
layouts: Vec<DungeonLayout>,
|
|
|
|
|
svgs: Vec<String>,
|
2026-04-20 10:23:49 -05:00
|
|
|
) -> Result<PathBuf, String> {
|
2026-05-22 11:41:32 -05:00
|
|
|
let dialog = FileDialog::new()
|
2026-04-20 10:23:49 -05:00
|
|
|
.set_title("Save Dungeon State")
|
|
|
|
|
.add_filter("Dungeon File", &["dungeon"])
|
2026-05-22 11:41:32 -05:00
|
|
|
.set_file_name("my_dungeon.dungeon");
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
let path = std::thread::spawn(move || dialog.save_file())
|
|
|
|
|
.join()
|
|
|
|
|
.map_err(|_| "Dialog thread panicked".to_string())?;
|
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
|
|
|
let path = dialog.save_file();
|
|
|
|
|
|
|
|
|
|
let path = path.ok_or_else(|| "Save canceled".to_string())?;
|
2026-04-20 10:23:49 -05:00
|
|
|
|
|
|
|
|
let save_data = DungeonSave {
|
|
|
|
|
settings: settings.clone(),
|
2026-04-28 10:13:06 -05:00
|
|
|
layouts,
|
|
|
|
|
svgs,
|
2026-04-20 10:23:49 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let json = serde_json::to_string_pretty(&save_data)
|
|
|
|
|
.map_err(|e| format!("Failed to serialize dungeon: {e}"))?;
|
|
|
|
|
|
|
|
|
|
fs::write(&path, json).map_err(|e| format!("Failed to write file: {e}"))?;
|
|
|
|
|
|
|
|
|
|
Ok(path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 10:30:59 -05:00
|
|
|
// Loads dungeon state from a user-selected JSON file.
|
2026-04-20 10:23:49 -05:00
|
|
|
pub fn load_dungeon() -> Result<DungeonSave, String> {
|
2026-05-22 11:41:32 -05:00
|
|
|
let dialog = FileDialog::new()
|
2026-04-20 10:23:49 -05:00
|
|
|
.set_title("Load Dungeon State")
|
2026-05-22 11:41:32 -05:00
|
|
|
.add_filter("Dungeon File", &["dungeon"]);
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
let path = std::thread::spawn(move || dialog.pick_file())
|
|
|
|
|
.join()
|
|
|
|
|
.map_err(|_| "Dialog thread panicked".to_string())?;
|
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
|
|
|
let path = dialog.pick_file();
|
|
|
|
|
|
|
|
|
|
let path = path.ok_or_else(|| "Load canceled".to_string())?;
|
2026-04-20 10:23:49 -05:00
|
|
|
|
|
|
|
|
let content = fs::read_to_string(path).map_err(|e| format!("Failed to read file: {e}"))?;
|
|
|
|
|
|
|
|
|
|
let save_data: DungeonSave =
|
|
|
|
|
serde_json::from_str(&content).map_err(|e| format!("Failed to parse dungeon file: {e}"))?;
|
|
|
|
|
|
|
|
|
|
Ok(save_data)
|
|
|
|
|
}
|