diff --git a/src/exporter.rs b/src/exporter.rs index 01d6296..6eb23d0 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -43,6 +43,7 @@ struct ExportGeometry { } impl ExportGeometry { + // Build export geometry with a fixed cell size and padding. fn new(cols: usize, rows: usize) -> Self { let cell = 64.0; let pad = 40.0; @@ -58,14 +59,17 @@ impl ExportGeometry { } } + // Return the left padding offset. fn left(&self) -> f32 { self.pad } + // Return the top padding offset. fn top(&self) -> f32 { self.pad } + // Compute the rectangle for a cell in export space. fn cell_rect(&self, col: usize, row: usize) -> Option { Rect::from_xywh( self.left() + col as f32 * self.cell, @@ -75,6 +79,7 @@ impl ExportGeometry { ) } + // Compute the center point of a cell in export space. fn cell_center(&self, col: usize, row: usize) -> (f32, f32) { ( self.left() + (col as f32 + 0.5) * self.cell, @@ -92,6 +97,7 @@ struct SceneData { door_edges: HashSet<((usize, usize), (usize, usize))>, } +// Collect precomputed cell and edge sets needed for rendering. fn collect_scene_data(layout: &DungeonLayout, settings: &UiSettings) -> SceneData { let mut scene = SceneData::default(); @@ -116,6 +122,7 @@ fn collect_scene_data(layout: &DungeonLayout, settings: &UiSettings) -> SceneDat scene } +// Open a dialog to select an export destination. pub fn select_export_target(settings: &UiSettings) -> Result { if settings.export_format == ExportFormat::Folder { let folder = FileDialog::new() @@ -145,6 +152,7 @@ pub fn select_export_target(settings: &UiSettings) -> Result Result { let g = ExportGeometry::new(settings.cols, settings.rows); let scene = collect_scene_data(layout, settings); @@ -251,6 +260,7 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result ImageFormat { match format { MaskFormat::Png => ImageFormat::Png, @@ -360,6 +372,7 @@ fn mask_image_format(format: MaskFormat) -> ImageFormat { } } +// Map an export format to an image encoding. fn export_image_format(format: ExportFormat) -> ImageFormat { match format { ExportFormat::Png => ImageFormat::Png, @@ -369,6 +382,7 @@ fn export_image_format(format: ExportFormat) -> ImageFormat { } } +// Convert a pixmap into a raster image at the target size. fn raster_image_from_pixmap( pixmap: &Pixmap, settings: &UiSettings, @@ -385,6 +399,7 @@ fn raster_image_from_pixmap( Ok(img) } +// Convert a pixmap into a thresholded mask image. fn raster_mask_image_from_pixmap( pixmap: &Pixmap, settings: &UiSettings, @@ -408,12 +423,14 @@ fn raster_mask_image_from_pixmap( Ok(DynamicImage::ImageRgba8(out)) } +// Compute the mask target size based on export width scaling. fn raster_mask_target_size(settings: &UiSettings, src_w: u32, src_h: u32) -> (u32, u32) { let (target_w, _target_h) = raster_target_size(settings); let scale = ((target_w as f32) / (src_w as f32)).round().max(1.0) as u32; (src_w.saturating_mul(scale), src_h.saturating_mul(scale)) } +// Compute the output raster size while honoring aspect settings. fn raster_target_size(settings: &UiSettings) -> (u32, u32) { let mut width = settings.export_width.clamp(1, 10_000); let mut height = settings.export_height.clamp(1, 10_000); @@ -426,16 +443,10 @@ fn raster_target_size(settings: &UiSettings) -> (u32, u32) { height = height.clamp(1, 10_000); } - if width == 0 { - width = 1; - } - if height == 0 { - height = 1; - } - (width, height) } +// Build an SVG document for the current layout. 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); @@ -608,6 +619,7 @@ fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String { s } +// Fill the entire pixmap with a solid color. fn fill_bg(pixmap: &mut Pixmap, color: (u8, u8, u8, u8)) { let mut paint = Paint::default(); paint.set_color_rgba8(color.0, color.1, color.2, color.3); @@ -618,10 +630,12 @@ fn fill_bg(pixmap: &mut Pixmap, color: (u8, u8, u8, u8)) { pixmap.fill_rect(rect, &paint, Transform::identity(), None); } +// Draw the default grid lines. fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) { draw_grid_color(pixmap, g, GRID); } +// Draw grid lines using a specific color. fn draw_grid_color(pixmap: &mut Pixmap, g: &ExportGeometry, color: (u8, u8, u8, u8)) { let left = g.left(); let top = g.top(); @@ -647,6 +661,7 @@ fn draw_grid_color(pixmap: &mut Pixmap, g: &ExportGeometry, color: (u8, u8, u8, } } +// Fill a single cell rectangle with a color. fn fill_rect_cell( pixmap: &mut Pixmap, g: &ExportGeometry, @@ -662,6 +677,7 @@ fn fill_rect_cell( pixmap.fill_rect(rect, &paint, Transform::identity(), None); } +// Fill an entire room rectangle with a color. fn fill_rect_room(pixmap: &mut Pixmap, g: &ExportGeometry, room: &Room, color: (u8, u8, u8, u8)) { let Some(rect) = Rect::from_xywh( g.left() + room.x as f32 * g.cell, @@ -677,6 +693,7 @@ fn fill_rect_room(pixmap: &mut Pixmap, g: &ExportGeometry, room: &Room, color: ( pixmap.fill_rect(rect, &paint, Transform::identity(), None); } +// Draw a filled circle for dot patterns. fn draw_dot(pixmap: &mut Pixmap, cx: f32, cy: f32, radius: f32, color: (u8, u8, u8, u8)) { let mut pb = PathBuilder::new(); pb.push_circle(cx, cy, radius); @@ -694,6 +711,7 @@ fn draw_dot(pixmap: &mut Pixmap, cx: f32, cy: f32, radius: f32, color: (u8, u8, ); } +// Draw a crosshatch overlay for a room. fn draw_room_crosshatch( pixmap: &mut Pixmap, g: &ExportGeometry, @@ -730,6 +748,7 @@ fn draw_room_crosshatch( } } +// Draw wall segments around occupied cells. fn draw_cell_walls( pixmap: &mut Pixmap, g: &ExportGeometry, @@ -812,6 +831,7 @@ fn draw_cell_walls( } } +// Draw a single wall segment as a filled rectangle. fn draw_wall_segment( pixmap: &mut Pixmap, x1: f32, @@ -848,6 +868,7 @@ fn draw_wall_segment( pixmap.fill_rect(rect, &paint, Transform::identity(), None); } +// Round a width to the nearest even pixel size. fn snap_even_width(value: f32) -> f32 { let mut w = value.round().max(1.0); if (w as u32) % 2 == 1 { @@ -856,6 +877,7 @@ fn snap_even_width(value: f32) -> f32 { w } +// Append SVG wall rectangles for the given cells. fn append_svg_walls( out: &mut String, g: &ExportGeometry, @@ -932,6 +954,7 @@ fn append_svg_walls( } } +// Draw a door line onto the pixmap. fn draw_door( pixmap: &mut Pixmap, g: &ExportGeometry, @@ -947,6 +970,7 @@ fn draw_door( draw_line(pixmap, x1, y1, x2, y2, width, color, style); } +// Compute the door line endpoints in export space. fn door_line_points( g: &ExportGeometry, a: (usize, usize), @@ -980,6 +1004,7 @@ fn door_line_points( } } +// Draw a styled line in the pixmap. fn draw_line( pixmap: &mut Pixmap, x1: f32, @@ -998,6 +1023,7 @@ fn draw_line( } } +// Stroke a path with optional dash patterns. fn stroke_path( pixmap: &mut Pixmap, x1: f32, @@ -1027,6 +1053,7 @@ fn stroke_path( pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); } +// Draw a dotted line using repeated circles. fn dotted_line( pixmap: &mut Pixmap, x1: f32, @@ -1054,10 +1081,12 @@ fn dotted_line( } } +// Normalize an edge tuple to a stable ordering. fn norm_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) { if a <= b { (a, b) } else { (b, a) } } +// Build a set of corridor-adjacent edges from corridor cells. fn corridor_edges_from_cells( corridor_cells: &HashSet<(usize, usize)>, ) -> HashSet<((usize, usize), (usize, usize))> { @@ -1075,6 +1104,7 @@ fn corridor_edges_from_cells( edges } +// Build a set of internal room edges from room rectangles. fn room_edges_from_rooms(rooms: &[Room]) -> HashSet<((usize, usize), (usize, usize))> { let mut edges = HashSet::new(); for room in rooms { @@ -1094,6 +1124,7 @@ fn room_edges_from_rooms(rooms: &[Room]) -> HashSet<((usize, usize), (usize, usi edges } +// Expand a door into all grid edges it spans based on width. fn door_edges_for(door: &Door, cols: usize, rows: usize) -> Vec<((usize, usize), (usize, usize))> { let mut edges = Vec::new(); if door.from.0.abs_diff(door.to.0) + door.from.1.abs_diff(door.to.1) != 1 { @@ -1131,6 +1162,7 @@ fn door_edges_for(door: &Door, cols: usize, rows: usize) -> Vec<((usize, usize), edges } +// Determine the rendered door width in cells. fn door_render_width(door: &Door) -> usize { if door.span_width { door.width.max(1) diff --git a/src/layout.rs b/src/layout.rs index 8d6e743..7c67c9c 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -11,6 +11,7 @@ pub struct Room { } impl Room { + // Return the center cell of the room. pub fn center_cell(&self) -> (usize, usize) { (self.x + (self.width / 2), self.y + (self.height / 2)) } @@ -51,6 +52,7 @@ pub struct DungeonLayout { pub doors: Vec, } +// Build a layout based on settings and a derived deterministic seed. pub fn generate_layout( cols: usize, rows: usize, @@ -214,6 +216,7 @@ pub fn generate_layout( layout } +// Populate layout doors based on corridor edges and door settings. pub fn apply_doors( layout: &mut DungeonLayout, seed: u64, @@ -302,6 +305,7 @@ pub fn apply_doors( } } +// Find the edge where a corridor path exits a room. fn room_exit_edge( path: &[(usize, usize)], room: &Room, @@ -331,6 +335,7 @@ fn room_exit_edge( None } +// Compute corridor cells while excluding room cells. pub fn corridor_cells(layout: &DungeonLayout, cols: usize, rows: usize) -> HashSet<(usize, usize)> { let mut cells = HashSet::new(); if cols == 0 || rows == 0 { @@ -411,6 +416,7 @@ pub fn corridor_cells(layout: &DungeonLayout, cols: usize, rows: usize) -> HashS cells } +// Create a connected graph of room-to-room edges with optional dead ends. fn build_room_connection_edges( centers: &[(usize, usize)], randomness: f32, @@ -470,6 +476,7 @@ fn build_room_connection_edges( edges } +// Order core rooms to create a reasonable loop backbone. fn ordered_core_rooms( core_rooms: &[usize], centers: &[(usize, usize)], @@ -504,6 +511,7 @@ fn ordered_core_rooms( ordered } +// Insert a room edge only if it has not been added yet. fn push_unique_room_edge( a: usize, b: usize, @@ -519,6 +527,7 @@ fn push_unique_room_edge( } } +// Shuffle indices in place using the provided RNG. fn shuffle_indices(indices: &mut [usize], rng: &mut SimpleRng) { if indices.len() <= 1 { return; @@ -529,6 +538,7 @@ fn shuffle_indices(indices: &mut [usize], rng: &mut SimpleRng) { } } +// Compute the shortest grid path between two cells using BFS. pub fn shortest_path_cells( start: (usize, usize), end: (usize, usize), @@ -604,6 +614,7 @@ pub fn shortest_path_cells( Some(path) } +// Generate a noisy path biased toward the target cell. fn noisy_path( start: (usize, usize), end: (usize, usize), @@ -698,6 +709,7 @@ fn noisy_path( path } +// Test whether two rooms overlap with extra padding. fn overlaps_with_padding(a: &Room, b: &Room, padding: usize) -> bool { let a_left = a.x.saturating_sub(padding); let a_top = a.y.saturating_sub(padding); @@ -712,10 +724,12 @@ fn overlaps_with_padding(a: &Room, b: &Room, padding: usize) -> bool { a_left < b_right && a_right > b_left && a_top < b_bottom && a_bottom > b_top } +// Compute Manhattan distance between two grid cells. fn manhattan_distance(a: (usize, usize), b: (usize, usize)) -> usize { a.0.abs_diff(b.0) + a.1.abs_diff(b.1) } +// Normalize a cell edge ordering. fn normalized_cell_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) { if a <= b { (a, b) } else { (b, a) } } @@ -725,6 +739,7 @@ struct SimpleRng { } impl SimpleRng { + // Create a small deterministic RNG with a fallback seed. fn new(seed: u64) -> Self { let state = if seed == 0 { 0xA5A5_A5A5_1234_5678 @@ -734,6 +749,7 @@ impl SimpleRng { Self { state } } + // Return the next random u32. fn next_u32(&mut self) -> u32 { self.state ^= self.state >> 12; self.state ^= self.state << 25; @@ -741,10 +757,12 @@ impl SimpleRng { (self.state.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 32) as u32 } + // Return the next random f32 in [0,1]. fn next_f32(&mut self) -> f32 { self.next_u32() as f32 / u32::MAX as f32 } + // Generate a random usize between min and max inclusive. fn range_inclusive(&mut self, min: usize, max: usize) -> usize { if min >= max { return min; diff --git a/src/main.rs b/src/main.rs index f9b96d3..06b6d19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use egui::{Color32, Stroke}; use layout::{DoorSettings, DungeonLayout, corridor_cells, shortest_path_cells}; use ui::{AddTool, UiSettings, draw_side_panel}; +// Boot the native app and start the egui event loop. fn main() -> eframe::Result<()> { let options = eframe::NativeOptions::default(); @@ -37,6 +38,7 @@ struct DungeonApp { } impl Default for DungeonApp { + // Build the app with persisted settings and a generated layout. fn default() -> Self { let settings = settings::load_settings().unwrap_or_default(); let layout = layout::generate_layout( @@ -69,6 +71,7 @@ impl Default for DungeonApp { } impl Drop for DungeonApp { + // Persist settings when the app is being dropped. fn drop(&mut self) { if let Err(err) = settings::save_settings(&self.settings) { eprintln!("Failed to save settings: {err}"); @@ -77,6 +80,7 @@ impl Drop for DungeonApp { } impl eframe::App for DungeonApp { + // Render UI, handle input, and update app state each frame. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let panel_result = draw_side_panel(ctx, &mut self.settings, self.export_rx.is_some()); @@ -201,6 +205,7 @@ impl eframe::App for DungeonApp { } impl DungeonApp { + // Regenerate the dungeon layout from current settings and reset drag state. fn regenerate_layout(&mut self) { self.drag_state = None; self.layout = layout::generate_layout( @@ -219,6 +224,7 @@ impl DungeonApp { ); } + // Handle dragging rooms or corridors with the primary mouse button. fn handle_drag( &mut self, ctx: &egui::Context, @@ -265,6 +271,7 @@ impl DungeonApp { } } + // Resolve the top-most room under the pointer and return its index and offset. fn room_at_pointer( &self, pointer_pos: egui::Pos2, @@ -286,6 +293,7 @@ impl DungeonApp { None } + // Move a room to follow the pointer and reroute its corridors. fn drag_room_to_pointer( &mut self, drag: RoomDragState, @@ -323,6 +331,7 @@ impl DungeonApp { self.refresh_doors(); } + // Recalculate corridor paths connected to a moved room. fn reroute_corridors_for_room(&mut self, room_idx: usize) { let empty = HashSet::new(); for corridor in &mut self.layout.corridors { @@ -338,6 +347,7 @@ impl DungeonApp { } } + // Capture corridor drag state when the pointer is over a corridor cell. fn corridor_drag_at_pointer( &self, pointer_pos: egui::Pos2, @@ -358,6 +368,7 @@ impl DungeonApp { }) } + // Reroute a corridor path through the current pointer cell. fn drag_corridor_to_pointer( &mut self, drag: CorridorDragState, @@ -392,6 +403,7 @@ impl DungeonApp { self.refresh_doors(); } + // Rebuild doors based on the current layout and settings. fn refresh_doors(&mut self) { layout::apply_doors( &mut self.layout, @@ -402,6 +414,7 @@ impl DungeonApp { ); } + // Handle add-tool interactions for rooms, corridors, and doors. fn handle_add_tool(&mut self, response: &egui::Response, geometry: &GridGeometry) { if self.settings.add_tool != AddTool::None && response.secondary_clicked() { self.settings.add_tool = AddTool::None; @@ -464,6 +477,7 @@ impl DungeonApp { } } + // Draw the overlay showing the corridor add drag preview. fn draw_add_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) { if self.settings.add_tool != AddTool::Corridor { return; @@ -479,6 +493,7 @@ impl DungeonApp { draw_dashed_line(painter, start, end, stroke, 8.0, 6.0); } + // Add a room anchored to the given grid cell if space allows. fn add_room_at_cell(&mut self, cell: (usize, usize)) { let width = self.settings.min_room_size.max(1); let height = self.settings.min_room_size.max(1); @@ -513,6 +528,7 @@ impl DungeonApp { self.refresh_doors(); } + // Add a corridor between two room cells using a shortest path. fn add_corridor_between(&mut self, start: (usize, usize), end: (usize, usize)) { if start == end { return; @@ -561,6 +577,7 @@ impl DungeonApp { self.refresh_doors(); } + // Update which room/corridor/door is currently hovered. fn update_hover_targets(&mut self, ctx: &egui::Context, geometry: &GridGeometry) { let Some(pointer_pos) = ctx.pointer_hover_pos() else { self.hover_room_idx = None; @@ -598,6 +615,7 @@ impl DungeonApp { } } + // Handle right-click resizing of rooms and return whether resizing is active. fn handle_resize( &mut self, ctx: &egui::Context, @@ -636,6 +654,7 @@ impl DungeonApp { false } + // Choose the nearest room corner and initialize resize state. fn start_resize( &self, room_idx: usize, @@ -691,6 +710,7 @@ impl DungeonApp { }) } + // Resize a room toward a target cell while honoring constraints. fn resize_room(&mut self, state: &RoomResizeState, target_cell: (usize, usize)) { let cols = self.settings.cols.max(1); let rows = self.settings.rows.max(1); @@ -741,6 +761,7 @@ impl DungeonApp { } } + // Draw dashed overlays for room resizing interactions. fn draw_resize_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) { let room_idx = self .resize_state @@ -791,6 +812,7 @@ impl DungeonApp { } } + // Draw a dashed overlay along the hovered corridor path. fn draw_corridor_hover_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) { let Some(idx) = self.hover_corridor_idx else { return; @@ -808,6 +830,7 @@ impl DungeonApp { } } + // Draw a dashed overlay along the hovered door. fn draw_door_hover_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) { let Some(idx) = self.hover_door_idx else { return; @@ -828,6 +851,7 @@ impl DungeonApp { ); } + // Delete the door, room, or corridor at the pointer position. fn delete_at_pointer(&mut self, pointer_pos: egui::Pos2, geometry: &GridGeometry) { if let Some(edge) = self.door_edge_at_pointer(pointer_pos, geometry) { let target = normalized_edge(edge.0, edge.1); @@ -887,6 +911,7 @@ impl DungeonApp { } } + // Remove a room and update corridors/doors to match. fn delete_room(&mut self, room_idx: usize) { if room_idx >= self.layout.rooms.len() { return; @@ -907,6 +932,7 @@ impl DungeonApp { self.refresh_doors(); } + // Add or update a door at the pointer edge. fn add_door_at_pointer( &mut self, pointer_pos: egui::Pos2, @@ -981,6 +1007,7 @@ impl DungeonApp { }); } + // Find the nearest cell edge under the pointer for door placement. fn door_edge_at_pointer( &self, pointer_pos: egui::Pos2, @@ -1081,6 +1108,7 @@ struct GridGeometry { rows: usize, } +// Draw the grid and return geometry metrics for hit testing. fn draw_grid(painter: &egui::Painter, area: egui::Rect, cols: usize, rows: usize) -> GridGeometry { let cols = cols.max(1); let rows = rows.max(1); @@ -1130,6 +1158,7 @@ fn draw_grid(painter: &egui::Painter, area: egui::Rect, cols: usize, rows: usize } } +// Render rooms, corridors, walls, and doors in the main canvas. fn draw_layout( painter: &egui::Painter, geometry: &GridGeometry, @@ -1345,6 +1374,7 @@ fn draw_layout( } } +// Compute the rectangle for a given grid cell. fn cell_rect(geometry: &GridGeometry, col: usize, row: usize) -> egui::Rect { let left = geometry.rect.left() + col as f32 * geometry.cell_size; let top = geometry.rect.top() + row as f32 * geometry.cell_size; @@ -1354,6 +1384,7 @@ fn cell_rect(geometry: &GridGeometry, col: usize, row: usize) -> egui::Rect { ) } +// Draw a rectangular wall segment between two points. fn draw_wall_segment( painter: &egui::Painter, from: egui::Pos2, @@ -1383,10 +1414,12 @@ fn draw_wall_segment( } } +// Normalize an edge tuple to a stable ordering. fn normalized_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) { if a <= b { (a, b) } else { (b, a) } } +// Build a set of corridor-adjacent edges from corridor cells. fn corridor_edges_from_cells( corridor_cells: &HashSet<(usize, usize)>, ) -> HashSet<((usize, usize), (usize, usize))> { @@ -1404,6 +1437,7 @@ fn corridor_edges_from_cells( edges } +// Build a set of internal room edges from room rectangles. fn room_edges_from_rooms(rooms: &[layout::Room]) -> HashSet<((usize, usize), (usize, usize))> { let mut edges = HashSet::new(); for room in rooms { @@ -1423,6 +1457,7 @@ fn room_edges_from_rooms(rooms: &[layout::Room]) -> HashSet<((usize, usize), (us edges } +// Find the index of the room containing a specific cell. fn room_index_at_cell(rooms: &[layout::Room], cell: (usize, usize)) -> Option { rooms.iter().position(|room| { cell.0 >= room.x @@ -1432,6 +1467,7 @@ fn room_index_at_cell(rooms: &[layout::Room], cell: (usize, usize)) -> Option bool { let a_right = a.x + a.width; let a_bottom = a.y + a.height; @@ -1440,6 +1476,7 @@ fn rooms_overlap(a: &layout::Room, b: &layout::Room) -> bool { a.x < b_right && a_right > b.x && a.y < b_bottom && a_bottom > b.y } +// Draw a single resize handle glyph at a corner. fn draw_resize_handle( painter: &egui::Painter, corner: egui::Pos2, @@ -1498,6 +1535,7 @@ fn draw_resize_handle( } } +// Expand a door into all grid edges it spans based on width. fn door_edges_for( door: &layout::Door, cols: usize, @@ -1539,6 +1577,7 @@ fn door_edges_for( edges } +// Determine the rendered door width in cells. fn door_render_width(door: &layout::Door) -> usize { if door.span_width { door.width.max(1) @@ -1547,6 +1586,7 @@ fn door_render_width(door: &layout::Door) -> usize { } } +// Convert a pointer position to grid coordinates if inside the grid. fn pointer_to_grid(pointer_pos: egui::Pos2, geometry: &GridGeometry) -> Option<(f32, f32)> { if !geometry.rect.contains(pointer_pos) { return None; @@ -1557,6 +1597,7 @@ fn pointer_to_grid(pointer_pos: egui::Pos2, geometry: &GridGeometry) -> Option<( Some((x, y)) } +// Recompute a path that must pass through a target cell. fn reroute_path_through_cell( original_path: &[(usize, usize)], original_cell: (usize, usize), @@ -1611,6 +1652,7 @@ fn reroute_path_through_cell( Some(full_path) } +// Remove loops in a path by truncating back to repeated cells. fn simplify_path_loops(path: &mut Vec<(usize, usize)>) { let mut out = Vec::new(); for &cell in path.iter() { @@ -1623,6 +1665,7 @@ fn simplify_path_loops(path: &mut Vec<(usize, usize)>) { *path = out; } +// Convert UI door settings into layout door settings. fn door_settings_from_ui(settings: &UiSettings) -> DoorSettings { DoorSettings { frequency_percent: settings.door_frequency_percent, @@ -1632,6 +1675,7 @@ fn door_settings_from_ui(settings: &UiSettings) -> DoorSettings { } } +// Draw a door line segment with a selected style. fn draw_door_line( painter: &egui::Painter, geometry: &GridGeometry, @@ -1676,6 +1720,7 @@ enum DoorLineStyle { Dotted, } +// Draw a line with solid, dashed, or dotted styling. fn draw_styled_line( painter: &egui::Painter, from: egui::Pos2, @@ -1693,6 +1738,7 @@ fn draw_styled_line( } } +// Draw a dashed line between two points. fn draw_dashed_line( painter: &egui::Painter, from: egui::Pos2, @@ -1721,6 +1767,7 @@ fn draw_dashed_line( } } +// Draw a dotted line between two points. fn draw_dotted_line(painter: &egui::Painter, from: egui::Pos2, to: egui::Pos2, stroke: Stroke) { let dx = to.x - from.x; let dy = to.y - from.y; @@ -1741,6 +1788,7 @@ fn draw_dotted_line(painter: &egui::Painter, from: egui::Pos2, to: egui::Pos2, s } } +// Compute the center position of a grid cell. fn cell_center(geometry: &GridGeometry, col: usize, row: usize) -> egui::Pos2 { egui::pos2( geometry.rect.left() + (col as f32 + 0.5) * geometry.cell_size, @@ -1748,6 +1796,7 @@ fn cell_center(geometry: &GridGeometry, col: usize, row: usize) -> egui::Pos2 { ) } +// Draw a crosshatch pattern inside a room for colorblind mode. fn draw_room_crosshatch( painter: &egui::Painter, geometry: &GridGeometry, @@ -1763,6 +1812,7 @@ fn draw_room_crosshatch( } } +// Draw a colored legend entry in the sidebar. fn draw_legend_entry(ui: &mut egui::Ui, color: Color32, label: &str) { ui.horizontal(|ui| { let (rect, _resp) = ui.allocate_exact_size(egui::vec2(16.0, 16.0), egui::Sense::hover()); @@ -1789,6 +1839,7 @@ enum LegendStyle { DottedLine, } +// Draw a patterned legend entry for colorblind mode. fn draw_legend_entry_colorblind(ui: &mut egui::Ui, label: &str, style: LegendStyle) { ui.horizontal(|ui| { let (rect, _resp) = ui.allocate_exact_size(egui::vec2(16.0, 16.0), egui::Sense::hover()); diff --git a/src/seed.rs b/src/seed.rs index 41fb7c6..8488d67 100644 --- a/src/seed.rs +++ b/src/seed.rs @@ -1,5 +1,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; +// Generate a random master seed from time and process id. pub fn random_seed() -> u64 { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -9,10 +10,12 @@ pub fn random_seed() -> u64 { mix64(nanos ^ pid.rotate_left(17)) } +// Derive a deterministic seed for a specific random stream. pub fn derive_seed(master_seed: u64, stream_id: u64) -> u64 { mix64(master_seed ^ stream_id.wrapping_mul(0x9E37_79B9_7F4A_7C15)) } +// Mix bits for a 64-bit seed hash. fn mix64(mut x: u64) -> u64 { x = x.wrapping_add(0x9E37_79B9_7F4A_7C15); x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); diff --git a/src/settings.rs b/src/settings.rs index 29a868d..aedd39d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -7,12 +7,14 @@ use crate::ui::UiSettings; const APP_DIR_NAME: &str = "desktop_dungeon_generator"; const SETTINGS_FILE_NAME: &str = "settings.json"; +// Load settings JSON from the user data directory. pub fn load_settings() -> Option { let path = settings_path()?; let content = fs::read_to_string(path).ok()?; serde_json::from_str::(&content).ok() } +// Save settings JSON to the user data directory. pub fn save_settings(settings: &UiSettings) -> io::Result<()> { let path = settings_path().ok_or_else(|| { io::Error::new( @@ -30,6 +32,7 @@ pub fn save_settings(settings: &UiSettings) -> io::Result<()> { fs::write(path, json) } +// Build the full path for the settings file. 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 176f0d1..c5b6d2a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -11,6 +11,7 @@ enum Tab { } impl Default for Tab { + // Start with the Generate tab selected. fn default() -> Self { Self::Generate } @@ -26,12 +27,14 @@ pub enum ExportFormat { } impl Default for ExportFormat { + // Default export format is PNG. fn default() -> Self { Self::Png } } impl ExportFormat { + // Return the file extension for this export format. pub fn extension(self) -> &'static str { match self { ExportFormat::Png => "png", @@ -42,6 +45,7 @@ impl ExportFormat { } } + // Return the UI label for this export format. pub fn label(self) -> &'static str { match self { ExportFormat::Png => ".png", @@ -52,6 +56,7 @@ impl ExportFormat { } } + // Report whether this export format supports a raster resolution. pub fn supports_resolution(self) -> bool { !matches!(self, ExportFormat::Svg) } @@ -65,12 +70,14 @@ pub enum MaskFormat { } impl Default for MaskFormat { + // Default mask format is PNG. fn default() -> Self { Self::Png } } impl MaskFormat { + // Return the file extension for this mask format. pub fn extension(self) -> &'static str { match self { MaskFormat::Png => "png", @@ -79,6 +86,7 @@ impl MaskFormat { } } + // Return the UI label for this mask format. pub fn label(self) -> &'static str { match self { MaskFormat::Png => ".png", @@ -118,6 +126,7 @@ pub struct UiSettings { } impl Default for UiSettings { + // Provide initial UI settings for a fresh session. fn default() -> Self { Self { seed: 1, @@ -169,6 +178,7 @@ pub fn draw_side_panel( settings: &mut UiSettings, export_in_progress: bool, ) -> SidePanelResult { + // Render the left side panel and return user interaction results. let mut result = SidePanelResult::default(); egui::SidePanel::left("options_panel") @@ -204,6 +214,7 @@ pub fn draw_side_panel( result } +// Render the Generate tab controls. fn draw_generate_tab( ui: &mut egui::Ui, settings: &mut UiSettings, @@ -372,6 +383,7 @@ fn draw_generate_tab( } } +// Render the Layout tab controls. fn draw_layout_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut SidePanelResult) { ui.label(RichText::new("Room Settings").strong()); ui.add_space(8.0); @@ -552,6 +564,7 @@ fn draw_layout_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut Si .changed(); } +// Render the Add tab controls. fn draw_add_tab(ui: &mut egui::Ui, settings: &mut UiSettings) { ui.label(RichText::new("Add Tools").strong()); ui.add_space(8.0);