321 lines
11 KiB
Rust
321 lines
11 KiB
Rust
/*
|
|
* 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<TextResizeState> {
|
|
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<RoomResizeState> {
|
|
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<StaircaseResizeState> {
|
|
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;
|
|
}
|
|
}
|