5#include "imgui/imgui.h"
34 if (door_index.has_value()) {
38 static_cast<float>(canvas_y));
73 drag_x, drag_y, direction);
78 auto& doors = room->GetDoors();
88 room->MarkObjectsDirty();
101 if (!canvas->IsMouseHovering())
return;
103 const ImGuiIO& io = ImGui::GetIO();
104 ImVec2 canvas_pos = canvas->zero_point();
105 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
106 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
112 if (canvas->IsMouseHovering()) {
114 ImDrawList* draw_list = ImGui::GetWindowDrawList();
115 ImVec2 hint_pos(io.MousePos.x + 14.0f, io.MousePos.y + 10.0f);
116 draw_list->AddText(hint_pos, ImGui::GetColorU32(theme.status_warning),
117 "Move cursor near a wall to place door");
118 ImGui::SetTooltip(
"Door placement requires a wall-adjacent position.");
129 int door_width_px = dims.width_tiles * 8;
130 int door_height_px = dims.height_tiles * 8;
133 auto [snap_canvas_x, snap_canvas_y] =
RoomToCanvas(tile_x, tile_y);
136 ImDrawList* draw_list = ImGui::GetWindowDrawList();
139 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale,
140 canvas_pos.y + snap_canvas_y * scale);
141 ImVec2 preview_end(preview_start.x + door_width_px * scale,
142 preview_start.y + door_height_px * scale);
149 size_t current_door_count = room ? room->GetDoors().size() : 0;
153 ImVec4 base_color = theme.dungeon_selection_primary;
155 base_color = theme.status_error;
156 }
else if (near_door_limit) {
157 base_color = theme.status_warning;
161 ImVec4 fill_vec(base_color.x, base_color.y, base_color.z, 0.31f);
162 draw_list->AddRectFilled(preview_start, preview_end,
163 ImGui::GetColorU32(fill_vec));
166 ImVec4 outline_color(base_color.x, base_color.y, base_color.z, 0.9f);
167 draw_list->AddRect(preview_start, preview_end,
168 ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
173 std::string label = type_name +
" (" + dir_name +
")";
175 ImVec2 text_pos(preview_start.x, preview_start.y - 16 * scale);
176 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 200), label.c_str());
179 if ((at_door_limit || near_door_limit) &&
180 ImGui::IsMouseHoveringRect(preview_start, preview_end)) {
182 at_door_limit ?
"\nPlacement blocked" :
"\nNear limit");
193 const auto& doors = room->GetDoors();
197 auto [tile_x, tile_y] = door.GetTileCoords();
206 bool is_inner =
false;
211 auto [snap_x, snap_y] =
219 ImDrawList* draw_list = ImGui::GetWindowDrawList();
223 ImVec2 pos(canvas_pos.x + tile_x * 8 * scale,
224 canvas_pos.y + tile_y * 8 * scale);
225 ImVec2 size(dims.width_tiles * 8 * scale, dims.height_tiles * 8 * scale);
228 static float pulse = 0.0f;
229 pulse += ImGui::GetIO().DeltaTime * 3.0f;
230 float alpha = 0.5f + 0.3f * sinf(pulse);
232 ImU32 color = IM_COL32(255, 165, 0, 180);
233 ImU32 fill_color = (color & 0x00FFFFFF) | (
static_cast<ImU32
>(alpha * 100) << 24);
235 draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y),
237 draw_list->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), color, 0.0f,
241 ImVec2 text_pos(pos.x, pos.y - 14 * scale);
242 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 220),
"Door");
251 int canvas_x,
int canvas_y)
const {
255 if (!room)
return std::nullopt;
259 int room_x =
static_cast<int>(canvas_x / scale);
260 int room_y =
static_cast<int>(canvas_y / scale);
262 const auto& doors = room->GetDoors();
263 for (
size_t i = 0; i < doors.size(); ++i) {
264 const auto& door = doors[i];
266 auto [tile_x, tile_y] = door.GetTileCoords();
269 int door_x = tile_x * 8;
270 int door_y = tile_y * 8;
271 int door_w = dims.width_tiles * 8;
272 int door_h = dims.height_tiles * 8;
274 if (room_x >= door_x && room_x < door_x + door_w && room_y >= door_y &&
275 room_y < door_y + door_h) {
299 auto& doors = room->GetDoors();
304 room->MarkObjectsDirty();
359 new_door.
byte1 = byte1;
360 new_door.
byte2 = byte2;
363 room->AddDoor(new_door);
389 bool is_inner =
false;
397 uint8_t nearest_snap =
400 ImDrawList* draw_list = ImGui::GetWindowDrawList();
410 size_t door_count = snap_room ? snap_room->GetDoors().size() : 0;
415 for (uint8_t i = 0; i < 6; ++i) {
416 uint8_t pos = start_pos + i;
417 auto [tile_x, tile_y] =
419 float pixel_x = tile_x * 8.0f;
420 float pixel_y = tile_y * 8.0f;
422 ImVec2 snap_start(canvas_pos.x + pixel_x * scale,
423 canvas_pos.y + pixel_y * scale);
424 ImVec2 snap_end(snap_start.x + dims.width_pixels() * scale,
425 snap_start.y + dims.height_pixels() * scale);
427 if (pos == nearest_snap) {
431 base = theme.status_error;
432 }
else if (snap_near_limit) {
433 base = theme.status_warning;
435 base = theme.dungeon_selection_primary;
437 ImVec4 highlight(base.x, base.y, base.z, 0.75f);
438 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(highlight),
442 ImVec4 ghost = snap_at_limit
443 ? ImVec4(theme.status_error.x, theme.status_error.y,
444 theme.status_error.z, 0.20f)
445 : ImVec4(1.0f, 1.0f, 1.0f, 0.25f);
446 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(ghost), 0.0f,
void TriggerSuccessToast()
InteractionContext * ctx_
float GetCanvasScale() const
Get canvas global scale.
zelda3::Room * GetCurrentRoom() const
Get current room (convenience method)
bool HasValidContext() const
Check if context is valid.
std::pair< int, int > RoomToCanvas(int room_x, int room_y) const
Convert room tile coordinates to canvas pixel coordinates.
ImVec2 GetCanvasZeroPoint() const
Get canvas zero point (for screen coordinate conversion)
bool door_placement_mode_
void DrawSelectionHighlight() override
Draw selection highlight for selected entities.
void DrawSnapIndicators()
Draw snap position indicators during door drag.
PlacementBlockReason placement_block_reason_
void SelectDoor(size_t index)
Select door at index.
std::optional< size_t > selected_door_index_
zelda3::DoorType preview_door_type_
void DrawGhostPreview() override
Draw ghost preview during placement.
void ClearSelection()
Clear door selection.
void HandleDrag(ImVec2 current_pos, ImVec2 delta) override
Handle mouse drag.
void DeleteSelected()
Delete selected door.
uint8_t snapped_door_position_
void CancelPlacement() override
Cancel current placement.
zelda3::DoorDirection detected_door_direction_
void BeginPlacement() override
Begin placement mode.
void HandleRelease() override
Handle mouse release.
bool HandleClick(int canvas_x, int canvas_y) override
Handle mouse click at canvas position.
bool UpdateSnappedPosition(int canvas_x, int canvas_y)
Update snapped position based on cursor.
void PlaceDoorAtSnappedPosition(int canvas_x, int canvas_y)
Place door at snapped position.
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
static uint8_t GetSectionStartPosition(DoorDirection direction, bool is_inner)
Get the starting position index for outer/inner section.
static bool DetectWallFromPosition(int canvas_x, int canvas_y, DoorDirection &out_direction)
Detect which wall the cursor is near.
static bool IsValidPosition(uint8_t position, DoorDirection direction)
Check if a position is valid for door placement.
static std::pair< int, int > PositionToTileCoords(uint8_t position, DoorDirection direction)
Convert encoded position to tile coordinates.
static bool DetectWallSection(int canvas_x, int canvas_y, DoorDirection &out_direction, bool &out_is_inner)
Detect wall with inner/outer section information.
static uint8_t SnapToNearestPosition(int canvas_x, int canvas_y, DoorDirection direction)
Convert canvas coordinates to nearest valid door position.
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
constexpr DoorDimensions GetDoorDimensions(DoorDirection dir)
Get door dimensions based on direction.
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
constexpr size_t kMaxDoors
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
DoorDirection
Door direction on room walls.
@ North
Top wall (horizontal door, 4x3 tiles)
const zelda3::Room * GetCurrentRoomConst() const
Get const pointer to current room.
void NotifyEntityChanged() const
Notify that entity has changed.
void NotifyInvalidateCache(MutationDomain domain=MutationDomain::kUnknown) const
Notify that cache invalidation is needed.
void NotifyMutation(MutationDomain domain=MutationDomain::kUnknown) const
Notify that a mutation is about to happen.
Represents a door in a dungeon room.
uint8_t byte1
Original ROM byte 1 (position data)
DoorType type
Door type (determines appearance/behavior)
std::pair< uint8_t, uint8_t > EncodeBytes() const
Encode door data for ROM storage.
DoorDirection direction
Which wall the door is on.
uint8_t position
Encoded position (5-bit, 0-31)
uint8_t byte2
Original ROM byte 2 (type + direction)