From 8cc4cf4543855e56f6756907b9a3174ebf522923 Mon Sep 17 00:00:00 2001 From: grimsace Date: Tue, 19 May 2026 14:22:19 -0500 Subject: [PATCH] added click and drag for door objects --- src/app.rs | 7 ++- src/interact/add_delete.rs | 125 ++++++++++++++++++++++++++++--------- src/interact/hit_test.rs | 6 +- src/interact/types.rs | 7 +++ 4 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/app.rs b/src/app.rs index 167cb53..8e34eb1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,7 +9,9 @@ use std::sync::mpsc; use std::thread; use crate::exporter; -use crate::interact::{AddCorridorDrag, DragState, HoverMarker, ResizeState, draw_grid}; +use crate::interact::{ + AddCorridorDrag, AddDoorDrag, DragState, HoverMarker, ResizeState, draw_grid, +}; use crate::layout::{ self, DoorSettings, DungeonLayout, WindowSettings, populate_random_markers, populate_stairs, }; @@ -45,6 +47,7 @@ pub struct DungeonApp { pub export_progress: Option, pub pending_delete: bool, pub add_corridor_drag: Option, + pub add_door_drag: Option, pub resize_state: Option, pub hover_room_idx: Option, pub hover_corridor_idx: Option, @@ -70,6 +73,7 @@ impl Default for DungeonApp { export_progress: None, pending_delete: false, add_corridor_drag: None, + add_door_drag: None, resize_state: None, hover_room_idx: None, hover_corridor_idx: None, @@ -398,6 +402,7 @@ impl DungeonApp { pub fn reset_transient_state(&mut self) { self.drag_state = None; self.add_corridor_drag = None; + self.add_door_drag = None; self.resize_state = None; self.clear_hover_targets(); self.pending_delete = false; diff --git a/src/interact/add_delete.rs b/src/interact/add_delete.rs index 336ffae..7127862 100644 --- a/src/interact/add_delete.rs +++ b/src/interact/add_delete.rs @@ -5,9 +5,10 @@ */ use super::{ - AddCorridorDrag, GridGeometry, HoverMarker, MarkerKind, cell_center, centered_marker_origin, - corridor_drag_at_pointer, door_edge_at_pointer, door_edges_for, marker_at_pointer, marker_rect, - normalized_edge, pointer_to_grid, room_at_pointer, stair_at_pointer, text_at_pointer, + AddCorridorDrag, AddDoorDrag, GridGeometry, HoverMarker, MarkerKind, cell_center, + centered_marker_origin, corridor_drag_at_pointer, door_edge_at_pointer, door_edges_for, + marker_at_pointer, marker_rect, normalized_edge, pointer_to_grid, room_at_pointer, + stair_at_pointer, text_at_pointer, }; use crate::app::{DungeonApp, ManualDoorKind}; use crate::layout::{ @@ -23,12 +24,14 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry if app.settings.add_tool != AddTool::None && response.secondary_clicked() { app.settings.add_tool = AddTool::None; app.add_corridor_drag = None; + app.add_door_drag = None; return; } match app.settings.add_tool { AddTool::Room => { app.add_corridor_drag = None; + app.add_door_drag = None; if response.clicked() && let Some(pointer_pos) = response.interact_pointer_pos() && let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry) @@ -38,6 +41,7 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry } } AddTool::Corridor => { + app.add_door_drag = None; if response.drag_started() && let Some(pointer_pos) = response.interact_pointer_pos() && let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry) @@ -65,6 +69,7 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry } AddTool::Staircase => { app.add_corridor_drag = None; + app.add_door_drag = None; if response.clicked() && let Some(pointer_pos) = response.interact_pointer_pos() && let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry) @@ -75,21 +80,11 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry } AddTool::Archway | AddTool::Door | AddTool::LockedDoor | AddTool::SecretDoor => { app.add_corridor_drag = None; - if response.clicked() - && let Some(pointer_pos) = response.interact_pointer_pos() - { - let kind = match app.settings.add_tool { - AddTool::Archway => ManualDoorKind::Archway, - AddTool::Door => ManualDoorKind::Regular, - AddTool::LockedDoor => ManualDoorKind::Locked, - AddTool::SecretDoor => ManualDoorKind::Secret, - _ => unreachable!(), - }; - add_door_at_pointer(app, pointer_pos, geometry, kind); - } + handle_add_door_tool(app, response, geometry); } AddTool::Text => { app.add_corridor_drag = None; + app.add_door_drag = None; if response.clicked() && let Some(pointer_pos) = response.interact_pointer_pos() && let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry) @@ -103,6 +98,7 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry | AddTool::TrapMarker | AddTool::MonsterMarker => { app.add_corridor_drag = None; + app.add_door_drag = None; if response.clicked() && let Some(pointer_pos) = response.interact_pointer_pos() && let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry) @@ -120,10 +116,88 @@ pub fn handle_add_tool(app: &mut DungeonApp, response: &egui::Response, geometry } AddTool::None => { app.add_corridor_drag = None; + app.add_door_drag = None; } } } +// Handles click-and-drag painting for manual door tools. +pub fn handle_add_door_tool( + app: &mut DungeonApp, + response: &egui::Response, + geometry: &GridGeometry, +) { + let Some(kind) = manual_door_kind_from_tool(app.settings.add_tool) else { + app.add_door_drag = None; + return; + }; + + if response.drag_started() { + app.add_door_drag = Some(AddDoorDrag { + painted_edges: Default::default(), + snapshot_taken: false, + }); + } + + if (response.drag_started() || response.dragged() || response.clicked()) + && let Some(pointer_pos) = response.interact_pointer_pos() + { + paint_door_at_pointer(app, pointer_pos, geometry, kind); + } + + if response.drag_stopped() { + app.add_door_drag = None; + } +} + +// Converts the active add tool into a manual door kind. +pub fn manual_door_kind_from_tool(tool: AddTool) -> Option { + match tool { + AddTool::Archway => Some(ManualDoorKind::Archway), + AddTool::Door => Some(ManualDoorKind::Regular), + AddTool::LockedDoor => Some(ManualDoorKind::Locked), + AddTool::SecretDoor => Some(ManualDoorKind::Secret), + _ => None, + } +} + +// Paints one door edge during a manual door drag. +pub fn paint_door_at_pointer( + app: &mut DungeonApp, + pointer_pos: egui::Pos2, + geometry: &GridGeometry, + kind: ManualDoorKind, +) { + let Some(edge) = door_edge_at_pointer(app, pointer_pos, geometry) else { + return; + }; + let target = normalized_edge(edge.0, edge.1); + + if let Some(drag) = &app.add_door_drag + && drag.painted_edges.contains(&target) + { + return; + } + + let needs_snapshot = app + .add_door_drag + .as_ref() + .map(|drag| !drag.snapshot_taken) + .unwrap_or(true); + if needs_snapshot { + app.push_undo_snapshot(); + if let Some(drag) = &mut app.add_door_drag { + drag.snapshot_taken = true; + } + } + + if add_door_at_edge(app, edge, kind) + && let Some(drag) = &mut app.add_door_drag + { + drag.painted_edges.insert(target); + } +} + // Deletes at pointer. pub fn delete_at_pointer(app: &mut DungeonApp, pointer_pos: egui::Pos2, geometry: &GridGeometry) { if app.settings.active_level_index >= app.levels.len() { @@ -450,22 +524,16 @@ pub fn delete_room(app: &mut DungeonApp, room_idx: usize) { app.refresh_doors(); } -// Adds door at pointer. -pub fn add_door_at_pointer( +// Adds or updates a manual door on a specific cell edge. +pub fn add_door_at_edge( app: &mut DungeonApp, - pointer_pos: egui::Pos2, - geometry: &GridGeometry, + edge: ((usize, usize), (usize, usize)), kind: ManualDoorKind, -) { +) -> bool { if app.settings.active_level_index >= app.levels.len() { - return; + return false; } - let Some(edge) = door_edge_at_pointer(app, pointer_pos, geometry) else { - return; - }; - - app.push_undo_snapshot(); let layout = &mut app.levels[app.settings.active_level_index]; let target = normalized_edge(edge.0, edge.1); @@ -509,7 +577,7 @@ pub fn add_door_at_pointer( app.suppressed_auto_door_edges.remove(&edge); } layout.doors.extend(spawned); - return; + return true; } let corridor_cells = corridor_cells(layout, app.settings.cols, app.settings.rows); @@ -519,7 +587,7 @@ pub fn add_door_at_pointer( let b_in_corridor = corridor_cells.contains(&edge.1); if !manual_door_edge_allowed(a_room, b_room, a_in_corridor, b_in_corridor) { - return; + return false; } layout.doors.push(layout::Door { @@ -533,6 +601,7 @@ pub fn add_door_at_pointer( manual: true, }); app.suppressed_auto_door_edges.remove(&target); + true } // Checks whether manual door edge allowed. diff --git a/src/interact/hit_test.rs b/src/interact/hit_test.rs index 86f9119..5df0ea7 100644 --- a/src/interact/hit_test.rs +++ b/src/interact/hit_test.rs @@ -11,7 +11,11 @@ use eframe::egui; // Handles update hover targets. pub fn update_hover_targets(app: &mut DungeonApp, ctx: &egui::Context, geometry: &GridGeometry) { - if app.drag_state.is_some() || app.resize_state.is_some() || app.add_corridor_drag.is_some() { + if app.drag_state.is_some() + || app.resize_state.is_some() + || app.add_corridor_drag.is_some() + || app.add_door_drag.is_some() + { return; } diff --git a/src/interact/types.rs b/src/interact/types.rs index 3c7b830..b3a1304 100644 --- a/src/interact/types.rs +++ b/src/interact/types.rs @@ -4,6 +4,7 @@ */ use crate::layout; +use std::collections::HashSet; #[derive(Debug, Clone)] pub struct RoomDragState { @@ -38,6 +39,12 @@ pub struct AddCorridorDrag { pub current_cell: (usize, usize), } +#[derive(Debug, Clone)] +pub struct AddDoorDrag { + pub painted_edges: HashSet<((usize, usize), (usize, usize))>, + pub snapshot_taken: bool, +} + #[derive(Debug, Clone, Copy)] pub enum ResizeCorner { TopLeft,