basic start and end markers
This commit is contained in:
+152
-1
@@ -6,7 +6,7 @@ use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType};
|
||||
use rfd::FileDialog;
|
||||
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
||||
|
||||
use crate::layout::{Door, DungeonLayout, Room, Window, WindowSide, corridor_cells};
|
||||
use crate::layout::{AreaMarker, Door, DungeonLayout, Room, Window, WindowSide, corridor_cells};
|
||||
use crate::ui::{ExportFormat, MaskFormat, UiSettings};
|
||||
|
||||
const BG: (u8, u8, u8, u8) = (24, 24, 26, 255);
|
||||
@@ -20,6 +20,8 @@ const SECRET_DOOR: (u8, u8, u8, u8) = (170, 80, 170, 255);
|
||||
const ARCHWAY: (u8, u8, u8, u8) = (230, 140, 60, 255);
|
||||
const WINDOW: (u8, u8, u8, u8) = (70, 130, 220, 255);
|
||||
const WHITE: (u8, u8, u8, u8) = (255, 255, 255, 255);
|
||||
const START_MARKER: (u8, u8, u8, u8) = (60, 220, 200, 255);
|
||||
const END_MARKER: (u8, u8, u8, u8) = (240, 90, 90, 255);
|
||||
|
||||
pub enum ExportTarget {
|
||||
File(PathBuf),
|
||||
@@ -274,6 +276,13 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
||||
draw_window(&mut pixmap, &g, window, door_w, WINDOW, style);
|
||||
}
|
||||
|
||||
if let Some(marker) = layout.start_marker.as_ref() {
|
||||
draw_area_marker(&mut pixmap, &g, marker, "S", START_MARKER, settings.colorblind_mode);
|
||||
}
|
||||
if let Some(marker) = layout.end_marker.as_ref() {
|
||||
draw_area_marker(&mut pixmap, &g, marker, "E", END_MARKER, settings.colorblind_mode);
|
||||
}
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
|
||||
@@ -668,6 +677,13 @@ fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(marker) = layout.start_marker.as_ref() {
|
||||
append_svg_area_marker(&mut s, &g, marker, "S", START_MARKER, settings.colorblind_mode);
|
||||
}
|
||||
if let Some(marker) = layout.end_marker.as_ref() {
|
||||
append_svg_area_marker(&mut s, &g, marker, "E", END_MARKER, settings.colorblind_mode);
|
||||
}
|
||||
|
||||
s.push_str("</svg>\n");
|
||||
s
|
||||
}
|
||||
@@ -746,6 +762,91 @@ fn fill_rect_room(pixmap: &mut Pixmap, g: &ExportGeometry, room: &Room, color: (
|
||||
pixmap.fill_rect(rect, &paint, Transform::identity(), None);
|
||||
}
|
||||
|
||||
fn fill_rect_marker(
|
||||
pixmap: &mut Pixmap,
|
||||
g: &ExportGeometry,
|
||||
marker: &AreaMarker,
|
||||
color: (u8, u8, u8, u8),
|
||||
) {
|
||||
let Some(rect) = Rect::from_xywh(
|
||||
g.left() + marker.cell.0 as f32 * g.cell,
|
||||
g.top() + marker.cell.1 as f32 * g.cell,
|
||||
marker.size as f32 * g.cell,
|
||||
marker.size as f32 * g.cell,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let mut paint = Paint::default();
|
||||
paint.set_color_rgba8(color.0, color.1, color.2, color.3);
|
||||
pixmap.fill_rect(rect, &paint, Transform::identity(), None);
|
||||
}
|
||||
|
||||
fn draw_area_marker(
|
||||
pixmap: &mut Pixmap,
|
||||
g: &ExportGeometry,
|
||||
marker: &AreaMarker,
|
||||
label: &str,
|
||||
color: (u8, u8, u8, u8),
|
||||
colorblind_mode: bool,
|
||||
) {
|
||||
let overlay = (color.0, color.1, color.2, if colorblind_mode { 56 } else { 90 });
|
||||
fill_rect_marker(pixmap, g, marker, overlay);
|
||||
|
||||
let x = g.left() + marker.cell.0 as f32 * g.cell;
|
||||
let y = g.top() + marker.cell.1 as f32 * g.cell;
|
||||
let w = marker.size as f32 * g.cell;
|
||||
let h = marker.size as f32 * g.cell;
|
||||
draw_wall_segment(pixmap, x, y, x + w, y, 3.0, color);
|
||||
draw_wall_segment(pixmap, x, y + h, x + w, y + h, 3.0, color);
|
||||
draw_wall_segment(pixmap, x, y, x, y + h, 3.0, color);
|
||||
draw_wall_segment(pixmap, x + w, y, x + w, y + h, 3.0, color);
|
||||
|
||||
let symbol = if colorblind_mode { BLACK } else { WHITE };
|
||||
if label == "S" {
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.25,
|
||||
y + h * 0.75,
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
} else {
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.25,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.75,
|
||||
y + h * 0.25,
|
||||
x + w * 0.25,
|
||||
y + h * 0.75,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -1007,6 +1108,56 @@ fn append_svg_walls(
|
||||
}
|
||||
}
|
||||
|
||||
fn append_svg_area_marker(
|
||||
out: &mut String,
|
||||
g: &ExportGeometry,
|
||||
marker: &AreaMarker,
|
||||
label: &str,
|
||||
color: (u8, u8, u8, u8),
|
||||
colorblind_mode: bool,
|
||||
) {
|
||||
let x = g.left() + marker.cell.0 as f32 * g.cell;
|
||||
let y = g.top() + marker.cell.1 as f32 * g.cell;
|
||||
let w = marker.size as f32 * g.cell;
|
||||
let h = marker.size as f32 * g.cell;
|
||||
let alpha = if colorblind_mode { 0.22 } else { 0.35 };
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<rect x='{x}' y='{y}' width='{w}' height='{h}' fill='rgb({},{},{})' fill-opacity='{alpha}' stroke='rgb({},{},{})' stroke-width='3'/>",
|
||||
color.0, color.1, color.2, color.0, color.1, color.2
|
||||
);
|
||||
let symbol = if colorblind_mode { "black" } else { "white" };
|
||||
if label == "S" {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<polyline points='{},{} {},{} {},{}' fill='none' stroke='{symbol}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/>",
|
||||
x + w * 0.25,
|
||||
y + h * 0.75,
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75
|
||||
);
|
||||
} else {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{symbol}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.25,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{symbol}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.75,
|
||||
y + h * 0.25,
|
||||
x + w * 0.25,
|
||||
y + h * 0.75
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a door line onto the pixmap.
|
||||
fn draw_door(
|
||||
pixmap: &mut Pixmap,
|
||||
|
||||
@@ -54,6 +54,12 @@ pub struct TextLabel {
|
||||
pub font_size: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AreaMarker {
|
||||
pub cell: (usize, usize),
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WindowSide {
|
||||
Left,
|
||||
@@ -88,6 +94,8 @@ pub struct DungeonLayout {
|
||||
pub doors: Vec<Door>,
|
||||
pub windows: Vec<Window>,
|
||||
pub text_labels: Vec<TextLabel>,
|
||||
pub start_marker: Option<AreaMarker>,
|
||||
pub end_marker: Option<AreaMarker>,
|
||||
pub packed_rooms: bool,
|
||||
}
|
||||
|
||||
@@ -99,6 +107,8 @@ impl Default for DungeonLayout {
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: false,
|
||||
}
|
||||
}
|
||||
@@ -166,6 +176,8 @@ pub fn generate_layout(
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: pack_rooms_without_corridors,
|
||||
};
|
||||
}
|
||||
@@ -189,6 +201,8 @@ pub fn generate_layout(
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: pack_rooms_without_corridors,
|
||||
};
|
||||
}
|
||||
@@ -217,6 +231,8 @@ pub fn generate_layout(
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: true,
|
||||
};
|
||||
apply_doors(&mut layout, seed, door_settings, cols, rows);
|
||||
@@ -274,6 +290,8 @@ pub fn generate_layout(
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: false,
|
||||
};
|
||||
}
|
||||
@@ -328,6 +346,8 @@ pub fn generate_layout(
|
||||
doors: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
text_labels: Vec::new(),
|
||||
start_marker: None,
|
||||
end_marker: None,
|
||||
packed_rooms: false,
|
||||
};
|
||||
apply_doors(&mut layout, seed, door_settings, cols, rows);
|
||||
|
||||
+189
-1
@@ -57,6 +57,7 @@ struct DungeonApp {
|
||||
hover_corridor_idx: Option<usize>,
|
||||
hover_door_idx: Option<usize>,
|
||||
hover_text_idx: Option<usize>,
|
||||
hover_marker_kind: Option<MarkerKind>,
|
||||
}
|
||||
|
||||
impl Default for DungeonApp {
|
||||
@@ -94,6 +95,7 @@ impl Default for DungeonApp {
|
||||
hover_corridor_idx: None,
|
||||
hover_door_idx: None,
|
||||
hover_text_idx: None,
|
||||
hover_marker_kind: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,6 +205,8 @@ impl eframe::App for DungeonApp {
|
||||
draw_legend_entry_colorblind(ui, "Secret Doors", LegendStyle::DashDotDotLine);
|
||||
draw_legend_entry_colorblind(ui, "Archways", LegendStyle::DottedLine);
|
||||
draw_legend_entry_colorblind(ui, "Windows", LegendStyle::DashDotLine);
|
||||
draw_legend_entry_colorblind(ui, "Start Marker", LegendStyle::SolidLine);
|
||||
draw_legend_entry_colorblind(ui, "End Marker", LegendStyle::SolidLine);
|
||||
} else {
|
||||
draw_legend_entry(ui, Color32::from_rgb(70, 120, 160), "Rooms");
|
||||
draw_legend_entry(ui, Color32::from_rgb(210, 190, 120), "Corridors");
|
||||
@@ -212,13 +216,15 @@ impl eframe::App for DungeonApp {
|
||||
draw_legend_entry(ui, Color32::from_rgb(170, 80, 170), "Secret Doors");
|
||||
draw_legend_entry(ui, Color32::from_rgb(230, 140, 60), "Archways");
|
||||
draw_legend_entry(ui, Color32::from_rgb(70, 130, 220), "Windows");
|
||||
draw_legend_entry(ui, Color32::from_rgb(60, 220, 200), "Start Marker");
|
||||
draw_legend_entry(ui, Color32::from_rgb(240, 90, 90), "End Marker");
|
||||
}
|
||||
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("• Delete / Backspace: Remove hovered room, corridor, door, text, or marker");
|
||||
ui.label("• Ctrl+Z: Undo");
|
||||
ui.label("• Ctrl+Y: Redo");
|
||||
ui.label("• Left click + drag: Move hovered room");
|
||||
@@ -249,6 +255,7 @@ impl eframe::App for DungeonApp {
|
||||
&self.layout,
|
||||
self.settings.colorblind_mode,
|
||||
self.hover_text_idx,
|
||||
self.hover_marker_kind,
|
||||
);
|
||||
self.draw_add_overlay(&painter, &geometry);
|
||||
self.draw_resize_overlay(&painter, &geometry);
|
||||
@@ -290,6 +297,7 @@ impl DungeonApp {
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_text_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
self.pending_delete = false;
|
||||
}
|
||||
|
||||
@@ -323,6 +331,7 @@ impl DungeonApp {
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_text_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
self.pending_delete = false;
|
||||
}
|
||||
|
||||
@@ -670,6 +679,22 @@ impl DungeonApp {
|
||||
self.settings.add_tool = AddTool::None;
|
||||
}
|
||||
}
|
||||
AddTool::StartMarker | AddTool::EndMarker => {
|
||||
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);
|
||||
let kind = match self.settings.add_tool {
|
||||
AddTool::StartMarker => MarkerKind::Start,
|
||||
AddTool::EndMarker => MarkerKind::End,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.add_marker_at_cell(cell, kind);
|
||||
self.settings.add_tool = AddTool::None;
|
||||
}
|
||||
}
|
||||
AddTool::None => {
|
||||
self.add_corridor_drag = None;
|
||||
}
|
||||
@@ -797,6 +822,26 @@ impl DungeonApp {
|
||||
});
|
||||
}
|
||||
|
||||
fn add_marker_at_cell(&mut self, cell: (usize, usize), kind: MarkerKind) {
|
||||
if cell.0 >= self.settings.cols || cell.1 >= self.settings.rows {
|
||||
return;
|
||||
}
|
||||
|
||||
let size = match kind {
|
||||
MarkerKind::Start => self.settings.start_marker_size,
|
||||
MarkerKind::End => self.settings.end_marker_size,
|
||||
}
|
||||
.clamp(1, 10);
|
||||
let origin = centered_marker_origin(cell, size, self.settings.cols, self.settings.rows);
|
||||
let marker = layout::AreaMarker { cell: origin, size };
|
||||
|
||||
self.push_undo_snapshot();
|
||||
match kind {
|
||||
MarkerKind::Start => self.layout.start_marker = Some(marker),
|
||||
MarkerKind::End => self.layout.end_marker = Some(marker),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -804,6 +849,7 @@ impl DungeonApp {
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_text_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
return;
|
||||
};
|
||||
if let Some(edge) = self.door_edge_at_pointer(pointer_pos, geometry) {
|
||||
@@ -817,12 +863,22 @@ impl DungeonApp {
|
||||
self.hover_room_idx = None;
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_text_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.hover_text_idx = self.text_at_pointer(pointer_pos, geometry);
|
||||
if self.hover_text_idx.is_some() {
|
||||
self.hover_room_idx = None;
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
return;
|
||||
}
|
||||
|
||||
self.hover_marker_kind = self.marker_at_pointer(pointer_pos, geometry);
|
||||
if self.hover_marker_kind.is_some() {
|
||||
self.hover_room_idx = None;
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
@@ -833,14 +889,17 @@ impl DungeonApp {
|
||||
self.hover_room_idx = Some(room_idx);
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
} else {
|
||||
self.hover_room_idx = None;
|
||||
if let Some(corridor_drag) = self.corridor_drag_at_pointer(pointer_pos, geometry) {
|
||||
self.hover_corridor_idx = Some(corridor_drag.corridor_index);
|
||||
self.hover_door_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
} else {
|
||||
self.hover_corridor_idx = None;
|
||||
self.hover_door_idx = None;
|
||||
self.hover_marker_kind = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -854,6 +913,20 @@ impl DungeonApp {
|
||||
.position(|label| label.cell == clicked)
|
||||
}
|
||||
|
||||
fn marker_at_pointer(
|
||||
&self,
|
||||
pointer_pos: egui::Pos2,
|
||||
geometry: &GridGeometry,
|
||||
) -> Option<MarkerKind> {
|
||||
let (grid_x, grid_y) = pointer_to_grid(pointer_pos, geometry)?;
|
||||
let clicked = (grid_x.floor() as usize, grid_y.floor() as usize);
|
||||
marker_contains_cell(self.layout.start_marker.as_ref(), clicked)
|
||||
.then_some(MarkerKind::Start)
|
||||
.or_else(|| {
|
||||
marker_contains_cell(self.layout.end_marker.as_ref(), clicked).then_some(MarkerKind::End)
|
||||
})
|
||||
}
|
||||
|
||||
// Handle right-click resizing of rooms and return whether resizing is active.
|
||||
fn handle_resize(
|
||||
&mut self,
|
||||
@@ -1168,6 +1241,15 @@ impl DungeonApp {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(kind) = self.marker_at_pointer(pointer_pos, geometry) {
|
||||
self.push_undo_snapshot();
|
||||
match kind {
|
||||
MarkerKind::Start => self.layout.start_marker = None,
|
||||
MarkerKind::End => self.layout.end_marker = None,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(edge) = self.door_edge_at_pointer(pointer_pos, geometry) {
|
||||
let target = normalized_edge(edge.0, edge.1);
|
||||
if let Some(idx) = self.layout.doors.iter().position(|d| {
|
||||
@@ -1400,6 +1482,12 @@ enum ResizeCorner {
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum MarkerKind {
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RoomResizeState {
|
||||
room_idx: usize,
|
||||
@@ -1491,6 +1579,7 @@ fn draw_layout(
|
||||
layout: &DungeonLayout,
|
||||
colorblind_mode: bool,
|
||||
hover_text_idx: Option<usize>,
|
||||
hover_marker_kind: Option<MarkerKind>,
|
||||
) {
|
||||
let wall_width_px = (geometry.cell_size / 5.0).max(1.0);
|
||||
let door_width_px = (geometry.cell_size / 10.0).max(1.0);
|
||||
@@ -1742,6 +1831,29 @@ fn draw_layout(
|
||||
color,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(marker) = layout.start_marker.as_ref() {
|
||||
draw_area_marker(
|
||||
painter,
|
||||
geometry,
|
||||
marker,
|
||||
"S",
|
||||
Color32::from_rgb(60, 220, 200),
|
||||
colorblind_mode,
|
||||
hover_marker_kind == Some(MarkerKind::Start),
|
||||
);
|
||||
}
|
||||
if let Some(marker) = layout.end_marker.as_ref() {
|
||||
draw_area_marker(
|
||||
painter,
|
||||
geometry,
|
||||
marker,
|
||||
"E",
|
||||
Color32::from_rgb(240, 90, 90),
|
||||
colorblind_mode,
|
||||
hover_marker_kind == Some(MarkerKind::End),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the rectangle for a given grid cell.
|
||||
@@ -1754,6 +1866,82 @@ fn cell_rect(geometry: &GridGeometry, col: usize, row: usize) -> egui::Rect {
|
||||
)
|
||||
}
|
||||
|
||||
fn marker_rect(marker: &layout::AreaMarker, geometry: &GridGeometry) -> egui::Rect {
|
||||
let left = geometry.rect.left() + marker.cell.0 as f32 * geometry.cell_size;
|
||||
let top = geometry.rect.top() + marker.cell.1 as f32 * geometry.cell_size;
|
||||
egui::Rect::from_min_size(
|
||||
egui::pos2(left, top),
|
||||
egui::vec2(
|
||||
marker.size as f32 * geometry.cell_size,
|
||||
marker.size as f32 * geometry.cell_size,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn marker_contains_cell(marker: Option<&layout::AreaMarker>, cell: (usize, usize)) -> bool {
|
||||
let Some(marker) = marker else {
|
||||
return false;
|
||||
};
|
||||
cell.0 >= marker.cell.0
|
||||
&& cell.0 < marker.cell.0 + marker.size
|
||||
&& cell.1 >= marker.cell.1
|
||||
&& cell.1 < marker.cell.1 + marker.size
|
||||
}
|
||||
|
||||
fn centered_marker_origin(
|
||||
cell: (usize, usize),
|
||||
size: usize,
|
||||
cols: usize,
|
||||
rows: usize,
|
||||
) -> (usize, usize) {
|
||||
let size = size.clamp(1, 10).min(cols.max(1)).min(rows.max(1));
|
||||
let half = size / 2;
|
||||
let max_x = cols.saturating_sub(size);
|
||||
let max_y = rows.saturating_sub(size);
|
||||
(cell.0.saturating_sub(half).min(max_x), cell.1.saturating_sub(half).min(max_y))
|
||||
}
|
||||
|
||||
fn draw_area_marker(
|
||||
painter: &egui::Painter,
|
||||
geometry: &GridGeometry,
|
||||
marker: &layout::AreaMarker,
|
||||
label: &str,
|
||||
base_color: Color32,
|
||||
colorblind_mode: bool,
|
||||
hovered: bool,
|
||||
) {
|
||||
let rect = marker_rect(marker, geometry);
|
||||
let fill = base_color.gamma_multiply(if colorblind_mode { 0.22 } else { 0.35 });
|
||||
let stroke_color = if hovered {
|
||||
Color32::from_rgb(255, 220, 120)
|
||||
} else if colorblind_mode {
|
||||
Color32::BLACK
|
||||
} else {
|
||||
base_color
|
||||
};
|
||||
let text_color = if colorblind_mode {
|
||||
Color32::BLACK
|
||||
} else {
|
||||
Color32::WHITE
|
||||
};
|
||||
let font_size = (geometry.cell_size * marker.size as f32 * 0.5).clamp(14.0, 72.0);
|
||||
|
||||
painter.rect_filled(rect, 4.0, fill);
|
||||
painter.rect_stroke(
|
||||
rect,
|
||||
4.0,
|
||||
Stroke::new(if hovered { 3.0 } else { 2.0 }, stroke_color),
|
||||
egui::StrokeKind::Middle,
|
||||
);
|
||||
painter.text(
|
||||
rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
label,
|
||||
egui::FontId::proportional(font_size),
|
||||
text_color,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw a rectangular wall segment between two points.
|
||||
fn draw_wall_segment(
|
||||
painter: &egui::Painter,
|
||||
|
||||
@@ -8,6 +8,7 @@ enum Tab {
|
||||
Generate,
|
||||
Layout,
|
||||
Add,
|
||||
MonstersAndTraps,
|
||||
}
|
||||
|
||||
impl Default for Tab {
|
||||
@@ -131,6 +132,8 @@ pub struct UiSettings {
|
||||
pub export_show_grid: bool,
|
||||
pub add_tool: AddTool,
|
||||
pub add_text_value: String,
|
||||
pub start_marker_size: usize,
|
||||
pub end_marker_size: usize,
|
||||
active_tab: Tab,
|
||||
}
|
||||
|
||||
@@ -170,6 +173,8 @@ impl Default for UiSettings {
|
||||
export_show_grid: true,
|
||||
add_tool: AddTool::None,
|
||||
add_text_value: "Text".to_string(),
|
||||
start_marker_size: 1,
|
||||
end_marker_size: 1,
|
||||
active_tab: Tab::Generate,
|
||||
}
|
||||
}
|
||||
@@ -185,6 +190,8 @@ pub enum AddTool {
|
||||
LockedDoor,
|
||||
SecretDoor,
|
||||
Text,
|
||||
StartMarker,
|
||||
EndMarker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -213,6 +220,7 @@ pub fn draw_side_panel(
|
||||
let is_generate = settings.active_tab == Tab::Generate;
|
||||
let is_layout = settings.active_tab == Tab::Layout;
|
||||
let is_add = settings.active_tab == Tab::Add;
|
||||
let is_monsters_and_traps = settings.active_tab == Tab::MonstersAndTraps;
|
||||
|
||||
if ui.selectable_label(is_generate, "Generate").clicked() {
|
||||
settings.active_tab = Tab::Generate;
|
||||
@@ -223,6 +231,12 @@ pub fn draw_side_panel(
|
||||
if ui.selectable_label(is_add, "Add").clicked() {
|
||||
settings.active_tab = Tab::Add;
|
||||
}
|
||||
if ui
|
||||
.selectable_label(is_monsters_and_traps, "Monsters & Traps")
|
||||
.clicked()
|
||||
{
|
||||
settings.active_tab = Tab::MonstersAndTraps;
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
@@ -230,6 +244,7 @@ pub fn draw_side_panel(
|
||||
Tab::Generate => draw_generate_tab(ui, settings, &mut result, export_in_progress),
|
||||
Tab::Layout => draw_layout_tab(ui, settings, &mut result),
|
||||
Tab::Add => draw_add_tab(ui, settings),
|
||||
Tab::MonstersAndTraps => draw_monsters_and_traps_tab(ui, settings),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -744,6 +759,72 @@ fn draw_add_tab(ui: &mut egui::Ui, settings: &mut UiSettings) {
|
||||
ui.label("Note: Left click on the canvas to place the text.");
|
||||
response
|
||||
}
|
||||
AddTool::StartMarker | AddTool::EndMarker => {
|
||||
ui.label("Use the Monsters & Traps tab to place start and end markers.")
|
||||
}
|
||||
AddTool::None => ui.label("Select a tool to add rooms or corridors."),
|
||||
};
|
||||
}
|
||||
|
||||
fn draw_monsters_and_traps_tab(ui: &mut egui::Ui, settings: &mut UiSettings) {
|
||||
ui.label(RichText::new("Start / End Markers").strong());
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label("Start Marker Size");
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Slider::new(&mut settings.start_marker_size, 1..=10).show_value(false));
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut settings.start_marker_size)
|
||||
.speed(1.0)
|
||||
.range(1..=10),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
if ui
|
||||
.selectable_label(settings.add_tool == AddTool::StartMarker, "Place Start Marker")
|
||||
.clicked()
|
||||
{
|
||||
settings.add_tool = if settings.add_tool == AddTool::StartMarker {
|
||||
AddTool::None
|
||||
} else {
|
||||
AddTool::StartMarker
|
||||
};
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.label("End Marker Size");
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Slider::new(&mut settings.end_marker_size, 1..=10).show_value(false));
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut settings.end_marker_size)
|
||||
.speed(1.0)
|
||||
.range(1..=10),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
if ui
|
||||
.selectable_label(settings.add_tool == AddTool::EndMarker, "Place End Marker")
|
||||
.clicked()
|
||||
{
|
||||
settings.add_tool = if settings.add_tool == AddTool::EndMarker {
|
||||
AddTool::None
|
||||
} else {
|
||||
AddTool::EndMarker
|
||||
};
|
||||
}
|
||||
|
||||
ui.add_space(8.0);
|
||||
match settings.add_tool {
|
||||
AddTool::StartMarker => {
|
||||
ui.label("Click the grid to place the start marker. It is centered on the clicked cell.");
|
||||
}
|
||||
AddTool::EndMarker => {
|
||||
ui.label("Click the grid to place the end marker. It is centered on the clicked cell.");
|
||||
}
|
||||
_ => {
|
||||
ui.label("Monsters, traps, and bosses will live here next. For now, place start and end markers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user