added manual room and corredor placment

This commit is contained in:
grimsace
2026-03-12 09:58:08 -05:00
parent 7e6a234f9b
commit 3c92162b84
2 changed files with 265 additions and 1 deletions
+233 -1
View File
@@ -11,7 +11,7 @@ use std::thread;
use eframe::egui;
use egui::{Color32, Stroke};
use layout::{DoorSettings, DungeonLayout, corridor_cells, shortest_path_cells};
use ui::{UiSettings, draw_side_panel};
use ui::{AddTool, UiSettings, draw_side_panel};
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions::default();
@@ -28,6 +28,8 @@ struct DungeonApp {
layout: DungeonLayout,
drag_state: Option<DragState>,
export_rx: Option<mpsc::Receiver<Result<std::path::PathBuf, String>>>,
pending_delete: bool,
add_corridor_drag: Option<AddCorridorDrag>,
}
impl Default for DungeonApp {
@@ -52,6 +54,8 @@ impl Default for DungeonApp {
layout,
drag_state: None,
export_rx: None,
pending_delete: false,
add_corridor_drag: None,
}
}
}
@@ -81,6 +85,9 @@ impl eframe::App for DungeonApp {
if panel_result.reset_clicked || panel_result.settings_changed {
self.regenerate_layout();
}
if ctx.input(|i| i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) {
self.pending_delete = true;
}
if panel_result.export_clicked && self.export_rx.is_none() {
let target = match exporter::select_export_target(&self.settings) {
Ok(target) => target,
@@ -144,6 +151,13 @@ impl eframe::App for DungeonApp {
draw_legend_entry(ui, Color32::from_rgb(220, 70, 70), "Locked Doors");
draw_legend_entry(ui, Color32::from_rgb(70, 130, 220), "Archways");
}
ui.add_space(10.0);
ui.separator();
ui.add_space(8.0);
ui.heading("Keybinds");
ui.add_space(6.0);
ui.label("Delete / Backspace: Remove hovered room or corridor");
ui.label("Right click: Cancel add tool");
});
egui::CentralPanel::default().show(ctx, |ui| {
@@ -152,12 +166,20 @@ impl eframe::App for DungeonApp {
let canvas = response.rect.shrink(12.0);
let geometry = draw_grid(&painter, canvas, self.settings.cols, self.settings.rows);
self.handle_drag(&response, &geometry);
self.handle_add_tool(&response, &geometry);
if self.pending_delete {
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
self.delete_at_pointer(pointer_pos, &geometry);
}
self.pending_delete = false;
}
draw_layout(
&painter,
&geometry,
&self.layout,
self.settings.colorblind_mode,
);
self.draw_add_overlay(&painter, &geometry);
});
}
}
@@ -182,6 +204,9 @@ impl DungeonApp {
}
fn handle_drag(&mut self, response: &egui::Response, geometry: &GridGeometry) {
if self.settings.add_tool != AddTool::None {
return;
}
if response.drag_started()
&& let Some(pointer_pos) = response.interact_pointer_pos()
&& let Some((room_idx, offset_x, offset_y)) =
@@ -352,6 +377,190 @@ impl DungeonApp {
self.settings.rows,
);
}
fn handle_add_tool(&mut self, response: &egui::Response, geometry: &GridGeometry) {
if response.secondary_clicked() {
self.settings.add_tool = AddTool::None;
self.add_corridor_drag = None;
return;
}
match self.settings.add_tool {
AddTool::Room => {
self.add_corridor_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)
{
let cell = (grid_x.floor() as usize, grid_y.floor() as usize);
self.add_room_at_cell(cell);
self.settings.add_tool = AddTool::None;
}
}
AddTool::Corridor => {
if response.drag_started()
&& let Some(pointer_pos) = response.interact_pointer_pos()
&& let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry)
{
let cell = (grid_x.floor() as usize, grid_y.floor() as usize);
self.add_corridor_drag = Some(AddCorridorDrag {
start_cell: cell,
current_cell: cell,
});
}
if response.dragged()
&& let Some(pointer_pos) = response.interact_pointer_pos()
&& let Some((grid_x, grid_y)) = pointer_to_grid(pointer_pos, geometry)
&& let Some(drag) = &mut self.add_corridor_drag
{
drag.current_cell = (grid_x.floor() as usize, grid_y.floor() as usize);
}
if response.drag_stopped() {
if let Some(drag) = self.add_corridor_drag.take() {
self.add_corridor_between(drag.start_cell, drag.current_cell);
}
self.settings.add_tool = AddTool::None;
}
}
AddTool::None => {
self.add_corridor_drag = None;
}
}
}
fn draw_add_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) {
if self.settings.add_tool != AddTool::Corridor {
return;
}
let Some(drag) = &self.add_corridor_drag else {
return;
};
let start = cell_center(geometry, drag.start_cell.0, drag.start_cell.1);
let end = cell_center(geometry, drag.current_cell.0, drag.current_cell.1);
let stroke = Stroke::new(2.0, Color32::WHITE);
painter.circle_filled(start, 3.0, Color32::WHITE);
draw_dashed_line(painter, start, end, stroke, 8.0, 6.0);
}
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);
if cell.0 >= self.settings.cols || cell.1 >= self.settings.rows {
return;
}
let max_w = self.settings.cols.saturating_sub(cell.0);
let max_h = self.settings.rows.saturating_sub(cell.1);
let width = width.min(max_w);
let height = height.min(max_h);
if width == 0 || height == 0 {
return;
}
let new_room = layout::Room {
x: cell.0,
y: cell.1,
width,
height,
};
if self
.layout
.rooms
.iter()
.any(|room| rooms_overlap(&new_room, room))
{
return;
}
self.layout.rooms.push(new_room);
self.refresh_doors();
}
fn add_corridor_between(&mut self, start: (usize, usize), end: (usize, usize)) {
if start == end {
return;
}
let start_room = room_index_at_cell(&self.layout.rooms, start);
let end_room = room_index_at_cell(&self.layout.rooms, end);
let (Some(start_room), Some(end_room)) = (start_room, end_room) else {
return;
};
let mut blocked = HashSet::new();
for room in &self.layout.rooms {
for x in room.x..(room.x + room.width) {
for y in room.y..(room.y + room.height) {
blocked.insert((x, y));
}
}
}
blocked.remove(&start);
blocked.remove(&end);
let Some(path) =
shortest_path_cells(start, end, self.settings.cols, self.settings.rows, &blocked)
else {
return;
};
let next_id = self
.layout
.corridors
.iter()
.map(|c| c.id)
.max()
.unwrap_or(0)
.wrapping_add(1);
let width = self.settings.min_corridor_width.max(1);
self.layout.corridors.push(layout::Corridor {
id: next_id,
start_room_id: start_room,
end_room_id: end_room,
path,
width,
});
self.refresh_doors();
}
fn delete_at_pointer(&mut self, pointer_pos: egui::Pos2, geometry: &GridGeometry) {
if let Some((room_idx, _, _)) = self.room_at_pointer(pointer_pos, geometry) {
self.delete_room(room_idx);
return;
}
if let Some(corridor_drag) = self.corridor_drag_at_pointer(pointer_pos, geometry) {
if corridor_drag.corridor_index < self.layout.corridors.len() {
self.layout.corridors.remove(corridor_drag.corridor_index);
self.drag_state = None;
self.refresh_doors();
}
}
}
fn delete_room(&mut self, room_idx: usize) {
if room_idx >= self.layout.rooms.len() {
return;
}
self.layout.rooms.remove(room_idx);
self.layout
.corridors
.retain(|c| c.start_room_id != room_idx && c.end_room_id != room_idx);
for corridor in &mut self.layout.corridors {
if corridor.start_room_id > room_idx {
corridor.start_room_id -= 1;
}
if corridor.end_room_id > room_idx {
corridor.end_room_id -= 1;
}
}
self.drag_state = None;
self.refresh_doors();
}
}
#[derive(Debug, Clone)]
@@ -368,6 +577,12 @@ struct CorridorDragState {
original_cell: (usize, usize),
}
#[derive(Debug, Clone)]
struct AddCorridorDrag {
start_cell: (usize, usize),
current_cell: (usize, usize),
}
#[derive(Debug, Clone)]
enum DragState {
Room(RoomDragState),
@@ -723,6 +938,23 @@ fn room_edges_from_rooms(rooms: &[layout::Room]) -> HashSet<((usize, usize), (us
edges
}
fn room_index_at_cell(rooms: &[layout::Room], cell: (usize, usize)) -> Option<usize> {
rooms.iter().position(|room| {
cell.0 >= room.x
&& cell.0 < room.x + room.width
&& cell.1 >= room.y
&& cell.1 < room.y + room.height
})
}
fn rooms_overlap(a: &layout::Room, b: &layout::Room) -> bool {
let a_right = a.x + a.width;
let a_bottom = a.y + a.height;
let b_right = b.x + b.width;
let b_bottom = b.y + b.height;
a.x < b_right && a_right > b.x && a.y < b_bottom && a_bottom > b.y
}
fn door_edges_for(
door: &layout::Door,
cols: usize,