/* * Resize interaction logic. * Handles resizing of rooms, text labels, and staircases using secondary click + drag. */ use super::{ GridGeometry, ResizeCorner, ResizeState, RoomResizeState, StaircaseResizeState, TextResizeState, pointer_to_grid, reroute_corridors_for_room, resize_room_at_pointer, stair_at_pointer, text_at_pointer, }; use crate::app::DungeonApp; use crate::layout::{rects_overlap, rooms_overlap}; use crate::ui::AddTool; use eframe::egui; pub fn handle_resize( app: &mut DungeonApp, ctx: &egui::Context, response: &egui::Response, geometry: &GridGeometry, ) -> bool { if app.settings.add_tool != AddTool::None { return false; } if ctx.input(|i| i.pointer.secondary_pressed()) && app.resize_state.is_none() && let Some(pointer_pos) = response.interact_pointer_pos() { if let Some(text_idx) = text_at_pointer(app, pointer_pos, geometry) { if let Some(state) = start_text_resize(app, text_idx) { app.push_undo_snapshot(); app.resize_state = Some(ResizeState::Text(state)); return true; } } else if let Some(stair_idx) = stair_at_pointer(app, pointer_pos, geometry) { if let Some(state) = start_staircase_resize(app, stair_idx, pointer_pos, geometry) { app.push_undo_snapshot(); app.resize_state = Some(ResizeState::Staircase(state)); return true; } } else if let Some(room_idx) = resize_room_at_pointer(app, pointer_pos, geometry) && let Some(state) = start_resize(app, room_idx, pointer_pos, geometry) { app.push_undo_snapshot(); app.resize_state = Some(ResizeState::Room(state)); return true; } } if let Some(state) = app.resize_state.clone() { if response.dragged_by(egui::PointerButton::Secondary) && ctx.input(|i| i.pointer.secondary_down()) && 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); match state { ResizeState::Room(state) => resize_room(app, &state, cell), ResizeState::Text(state) => resize_text(app, &state, cell), ResizeState::Staircase(state) => resize_staircase(app, &state, cell), } } if ctx.input(|i| i.pointer.button_released(egui::PointerButton::Secondary)) { app.resize_state = None; } return true; } false } pub fn start_text_resize(app: &DungeonApp, text_idx: usize) -> Option { if app.settings.active_level_index >= app.levels.len() { return None; } let layout = &app.levels[app.settings.active_level_index]; let label = layout.text_labels.get(text_idx)?; Some(TextResizeState { text_idx, origin_cell: label.cell, original_font_size: label.font_size, }) } pub fn start_resize( app: &DungeonApp, room_idx: usize, pointer_pos: egui::Pos2, geometry: &GridGeometry, ) -> Option { if app.settings.active_level_index >= app.levels.len() { return None; } let layout = &app.levels[app.settings.active_level_index]; let original_room = layout.rooms.get(room_idx)?.clone(); let (grid_x, grid_y) = pointer_to_grid(pointer_pos, geometry)?; let center_x = original_room.x as f32 + original_room.width as f32 * 0.5; let center_y = original_room.y as f32 + original_room.height as f32 * 0.5; let pointer_x = grid_x; let pointer_y = grid_y; let resize_corner = match (pointer_x >= center_x, pointer_y >= center_y) { (false, false) => ResizeCorner::TopLeft, (true, false) => ResizeCorner::TopRight, (false, true) => ResizeCorner::BottomLeft, (true, true) => ResizeCorner::BottomRight, }; Some(RoomResizeState { room_idx, original_room, resize_corner, }) } pub fn start_staircase_resize( app: &DungeonApp, stair_idx: usize, pointer_pos: egui::Pos2, geometry: &GridGeometry, ) -> Option { if app.settings.active_level_index >= app.levels.len() { return None; } let layout = &app.levels[app.settings.active_level_index]; let original_stair = layout.stairs.get(stair_idx)?.clone(); let (grid_x, grid_y) = pointer_to_grid(pointer_pos, geometry)?; let center_x = original_stair.cell.0 as f32 + original_stair.width as f32 * 0.5; let center_y = original_stair.cell.1 as f32 + original_stair.height as f32 * 0.5; let pointer_x = grid_x; let pointer_y = grid_y; let resize_corner = match (pointer_x >= center_x, pointer_y >= center_y) { (false, false) => ResizeCorner::TopLeft, (true, false) => ResizeCorner::TopRight, (false, true) => ResizeCorner::BottomLeft, (true, true) => ResizeCorner::BottomRight, }; Some(StaircaseResizeState { stair_idx, original_stair, resize_corner, }) } pub fn resize_room(app: &mut DungeonApp, state: &RoomResizeState, target_cell: (usize, usize)) { if app.settings.active_level_index >= app.levels.len() { return; } let layout = &mut app.levels[app.settings.active_level_index]; let cols = app.settings.cols.max(1); let rows = app.settings.rows.max(1); let tx = target_cell.0.min(cols - 1) as isize; let ty = target_cell.1.min(rows - 1) as isize; let original = &state.original_room; let left = original.x as isize; let top = original.y as isize; let right = (original.x + original.width - 1) as isize; let bottom = (original.y + original.height - 1) as isize; let (x0, x1, y0, y1) = match state.resize_corner { ResizeCorner::TopLeft => (tx.min(right), right, ty.min(bottom), bottom), ResizeCorner::TopRight => (left, tx.max(left), ty.min(bottom), bottom), ResizeCorner::BottomLeft => (tx.min(right), right, top, ty.max(top)), ResizeCorner::BottomRight => (left, tx.max(left), top, ty.max(top)), }; let new_room = crate::layout::Room { x: x0 as usize, y: y0 as usize, width: (x1 - x0 + 1) as usize, height: (y1 - y0 + 1) as usize, }; if new_room.width == 0 || new_room.height == 0 { return; } if new_room.x + new_room.width > cols || new_room.y + new_room.height > rows { return; } if layout .rooms .iter() .enumerate() .any(|(idx, room)| idx != state.room_idx && rooms_overlap(&new_room, room)) { return; } if let Some(room) = layout.rooms.get_mut(state.room_idx) { room.x = new_room.x; room.y = new_room.y; room.width = new_room.width; room.height = new_room.height; reroute_corridors_for_room(app, state.room_idx); app.refresh_doors(); } } pub fn resize_text(app: &mut DungeonApp, state: &TextResizeState, target_cell: (usize, usize)) { if app.settings.active_level_index >= app.levels.len() { return; } let layout = &mut app.levels[app.settings.active_level_index]; let Some(label) = layout.text_labels.get_mut(state.text_idx) else { app.resize_state = None; return; }; let dx = target_cell.0 as isize - state.origin_cell.0 as isize; let dy = target_cell.1 as isize - state.origin_cell.1 as isize; let delta_cells = if dx.abs() >= dy.abs() { dx } else { -dy }; let new_size = (state.original_font_size as isize + (delta_cells * 4)).clamp(8, 96); label.font_size = new_size as u16; } pub fn resize_staircase( app: &mut DungeonApp, state: &StaircaseResizeState, target_cell: (usize, usize), ) { if app.settings.active_level_index >= app.levels.len() { return; } let cols = app.settings.cols.max(1); let rows = app.settings.rows.max(1); let tx = target_cell.0.min(cols - 1) as isize; let ty = target_cell.1.min(rows - 1) as isize; let original = &state.original_stair; let left = original.cell.0 as isize; let top = original.cell.1 as isize; let right = (original.cell.0 + original.width - 1) as isize; let bottom = (original.cell.1 + original.height - 1) as isize; let (x0, x1, y0, y1) = match state.resize_corner { ResizeCorner::TopLeft => (tx.min(right), right, ty.min(bottom), bottom), ResizeCorner::TopRight => (left, tx.max(left), ty.min(bottom), bottom), ResizeCorner::BottomLeft => (tx.min(right), right, top, ty.max(top)), ResizeCorner::BottomRight => (left, tx.max(left), top, ty.max(top)), }; let new_stair_cell = (x0 as usize, y0 as usize); let new_stair_width = (x1 - x0 + 1) as usize; let new_stair_height = (y1 - y0 + 1) as usize; if new_stair_width == 0 || new_stair_height == 0 { return; } let active_layout = &app.levels[app.settings.active_level_index]; if active_layout.stairs.iter().enumerate().any(|(idx, stair)| { idx != state.stair_idx && rects_overlap( new_stair_cell.0, new_stair_cell.1, new_stair_width, new_stair_height, stair.cell.0, stair.cell.1, stair.width, stair.height, ) }) { return; } if app.settings.sync_stairs_across_levels { for layout in &mut app.levels { for stair in &mut layout.stairs { if stair.cell == state.original_stair.cell { stair.cell = new_stair_cell; stair.width = new_stair_width; stair.height = new_stair_height; } } } } else { let current_idx = app.settings.active_level_index; let level_indices = vec![ Some(current_idx), current_idx.checked_sub(1), if current_idx + 1 < app.levels.len() { Some(current_idx + 1) } else { None }, ]; for idx in level_indices.into_iter().flatten() { let layout = &mut app.levels[idx]; for stair in &mut layout.stairs { if stair.cell == state.original_stair.cell { stair.cell = new_stair_cell; stair.width = new_stair_width; stair.height = new_stair_height; } } } } if let Some(ResizeState::Staircase(ref mut s)) = app.resize_state { s.original_stair.cell = new_stair_cell; s.original_stair.width = new_stair_width; s.original_stair.height = new_stair_height; } }