From c82c1f2b2098c39e1073820bfd2d76691d83fe1d Mon Sep 17 00:00:00 2001 From: grimsace Date: Mon, 20 Apr 2026 10:23:49 -0500 Subject: [PATCH] added basic laod save functionality --- src/exporter.rs | 2 +- src/layout.rs | 21 +++++++++--------- src/main.rs | 18 +++++++++++++++ src/saveandload.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++ src/ui.rs | 12 ++++++++++ 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 src/saveandload.rs diff --git a/src/exporter.rs b/src/exporter.rs index f7201f9..acfd87f 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -529,7 +529,7 @@ fn raster_target_size(settings: &UiSettings) -> (u32, u32) { } // Build an SVG document for the current layout. -fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String { +pub fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String { let g = ExportGeometry::new(settings.cols, settings.rows); let wall_w = (g.cell / 5.0).max(1.0); let door_w = (g.cell / 10.0).max(1.0); diff --git a/src/layout.rs b/src/layout.rs index 7e4c216..c2fece0 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use std::collections::{HashSet, VecDeque}; use crate::seed; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Room { pub x: usize, pub y: usize, @@ -17,7 +18,7 @@ impl Room { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Corridor { #[allow(dead_code)] pub id: u64, @@ -27,7 +28,7 @@ pub struct Corridor { pub width: usize, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Door { pub from: (usize, usize), pub to: (usize, usize), @@ -39,7 +40,7 @@ pub struct Door { pub manual: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Window { pub cell: (usize, usize), pub side: WindowSide, @@ -47,20 +48,20 @@ pub struct Window { pub span_width: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TextLabel { pub cell: (usize, usize), pub text: String, pub font_size: u16, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AreaMarker { pub cell: (usize, usize), pub size: usize, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum WindowSide { Left, Right, @@ -68,7 +69,7 @@ pub enum WindowSide { Bottom, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct DoorSettings { pub frequency_percent: usize, pub room_hallway_percent: usize, @@ -77,7 +78,7 @@ pub struct DoorSettings { pub allow_middle_corridor_doors: bool, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct WindowSettings { pub enabled: bool, pub min_width: usize, @@ -87,7 +88,7 @@ pub struct WindowSettings { pub allow_internal_windows: bool, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DungeonLayout { pub rooms: Vec, pub corridors: Vec, diff --git a/src/main.rs b/src/main.rs index f61f6e6..310a922 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod exporter; mod layout; +mod saveandload; mod seed; mod settings; mod startend; @@ -139,6 +140,23 @@ impl eframe::App for DungeonApp { } else if panel_result.reset_clicked || panel_result.settings_changed { self.push_undo_snapshot(); self.regenerate_layout(); + } else if panel_result.save_clicked { + let svg = exporter::build_svg(&self.layout, &self.settings); + if let Err(err) = saveandload::save_dungeon(&self.settings, &self.layout, svg) { + eprintln!("{err}"); + } + } else if panel_result.load_clicked { + match saveandload::load_dungeon() { + Ok(data) => { + self.push_undo_snapshot(); + self.settings = data.settings; + self.layout = data.layout; + // Don't regenerate_layout() here to preserve the exact loaded state. + } + Err(err) => { + eprintln!("{err}"); + } + } } if ctx.input(|i| i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) { self.pending_delete = true; diff --git a/src/saveandload.rs b/src/saveandload.rs new file mode 100644 index 0000000..63e8080 --- /dev/null +++ b/src/saveandload.rs @@ -0,0 +1,55 @@ +use rfd::FileDialog; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +use crate::layout::DungeonLayout; +use crate::ui::UiSettings; + +#[derive(Serialize, Deserialize)] +pub struct DungeonSave { + pub settings: UiSettings, + pub layout: DungeonLayout, + pub svg: String, +} + +pub fn save_dungeon( + settings: &UiSettings, + layout: &DungeonLayout, + svg: String, +) -> Result { + let path = FileDialog::new() + .set_title("Save Dungeon State") + .add_filter("Dungeon File", &["dungeon"]) + .set_file_name("my_dungeon.dungeon") + .save_file() + .ok_or_else(|| "Save canceled".to_string())?; + + let save_data = DungeonSave { + settings: settings.clone(), + layout: layout.clone(), + svg, + }; + + 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) +} + +pub fn load_dungeon() -> Result { + let path = FileDialog::new() + .set_title("Load Dungeon State") + .add_filter("Dungeon File", &["dungeon"]) + .pick_file() + .ok_or_else(|| "Load canceled".to_string())?; + + 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) +} diff --git a/src/ui.rs b/src/ui.rs index 256d744..dfd756e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -226,6 +226,8 @@ pub struct SidePanelResult { pub reset_clicked: bool, pub clear_clicked: bool, pub export_clicked: bool, + pub save_clicked: bool, + pub load_clicked: bool, } pub fn draw_side_panel( @@ -354,6 +356,16 @@ fn draw_generate_tab( } }); + ui.add_space(8.0); + ui.horizontal(|ui| { + if ui.button("Save State").clicked() { + result.save_clicked = true; + } + if ui.button("Load State").clicked() { + result.load_clicked = true; + } + }); + ui.add_space(12.0); ui.separator(); ui.add_space(8.0);