3#include "absl/strings/str_format.h"
9#include "imgui/imgui.h"
20 const ImGuiIO& io = ImGui::GetIO();
28 if (ImGui::IsKeyPressed(ImGuiKey_Escape) &&
41 ImVec2 mouse_pos = io.MousePos;
45 ImVec2 canvas_mouse_pos =
46 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
49 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
53 static_cast<int>(canvas_mouse_pos.y));
58 static_cast<int>(canvas_mouse_pos.y));
63 static_cast<int>(canvas_mouse_pos.y));
67 auto [room_x, room_y] =
69 static_cast<int>(canvas_mouse_pos.y));
81 if (!io.KeyShift && !io.KeyCtrl) {
89 state.
rect_start_x =
static_cast<int>(canvas_mouse_pos.x);
90 state.rect_start_y =
static_cast<int>(canvas_mouse_pos.y);
91 state.rect_end_x = state.rect_start_x;
92 state.rect_end_y = state.rect_start_y;
102 state.drag_current = canvas_mouse_pos;
117 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
123 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
134 ImVec2 drag_delta = ImVec2(state.drag_current.x - state.drag_start.x,
135 state.drag_current.y - state.drag_start.y);
138 int tile_delta_x =
static_cast<int>(drag_delta.x) / 8;
139 int tile_delta_y =
static_cast<int>(drag_delta.y) / 8;
142 if (tile_delta_x != 0 || tile_delta_y != 0) {
143 auto& objects = room.GetTileObjects();
144 for (
size_t index : selected_indices) {
145 if (index < objects.size()) {
146 objects[index].x_ += tile_delta_x;
147 objects[index].y_ += tile_delta_y;
151 std::clamp(
static_cast<int>(objects[index].x_), 0, 63);
153 std::clamp(
static_cast<int>(objects[index].y_), 0, 63);
158 room.MarkObjectsDirty();
178 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
181 const ImGuiIO& io = ImGui::GetIO();
183 const ImVec2 mouse_pos =
184 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
191 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
193 static_cast<int>(mouse_pos.y));
200 !ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
208 }
else if (io.KeyCtrl) {
223 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
227 const auto& objects = room.GetTileObjects();
235 if (dim_table.IsLoaded()) {
236 auto [w_tiles, h_tiles] =
237 dim_table.GetSelectionDimensions(obj.
id_, obj.
size_);
238 return std::make_pair(w_tiles * 8, h_tiles * 8);
244 return std::make_pair(16, 16);
255 ImGuiIO& io = ImGui::GetIO();
257 int cursor_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
258 int cursor_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
260 if (entity_at_cursor.has_value()) {
267 if (hovered_index !=
static_cast<size_t>(-1) &&
268 hovered_index < objects.size()) {
269 const auto&
object = objects[hovered_index];
272 int layer =
object.GetLayerValue();
275 const char* subtype_names[] = {
"Unknown",
"Type 1",
"Type 2",
"Type 3"};
276 const char* subtype_name =
277 (subtype >= 0 && subtype <= 3) ? subtype_names[subtype] :
"Unknown";
281 tooltip += object_name;
282 tooltip +=
" (" + std::string(subtype_name) +
")";
284 tooltip +=
"ID: 0x" + absl::StrFormat(
"%03X",
object.id_);
285 tooltip +=
" | Layer: " + std::to_string(layer + 1);
286 tooltip +=
" | Pos: (" + std::to_string(
object.x_) +
", " +
287 std::to_string(
object.y_) +
")";
288 tooltip +=
"\nSize: " + std::to_string(
object.size_) +
" (0x" +
289 absl::StrFormat(
"%02X",
object.size_) +
")";
298 ImGui::SetTooltip(
"%s", tooltip.c_str());
307 const std::vector<zelda3::RoomObject>& objects) {
317 ImGuiIO& io = ImGui::GetIO();
319 int cursor_canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
320 int cursor_canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
322 if (entity_at_cursor.has_value()) {
327 if (hovered_index ==
static_cast<size_t>(-1) ||
328 hovered_index >= objects.size()) {
337 const auto&
object = objects[hovered_index];
339 ImDrawList* draw_list = ImGui::GetWindowDrawList();
344 auto [obj_x, obj_y] =
347 int pixel_width, pixel_height;
349 if (dim_table.IsLoaded()) {
350 auto [w_tiles, h_tiles] =
351 dim_table.GetSelectionDimensions(
object.id_,
object.size_);
352 pixel_width = w_tiles * 8;
353 pixel_height = h_tiles * 8;
356 pixel_width = dims.first;
357 pixel_height = dims.second;
364 ImVec2 obj_start(canvas_pos.x + obj_x * scale, canvas_pos.y + obj_y * scale);
365 ImVec2 obj_end(obj_start.x + pixel_width * scale,
366 obj_start.y + pixel_height * scale);
369 constexpr float margin = 2.0f;
370 obj_start.x -= margin;
371 obj_start.y -= margin;
379 ImVec4 hover_fill = ImVec4(layer_color.x, layer_color.y, layer_color.z,
382 ImVec4 hover_border =
383 ImVec4(layer_color.x, layer_color.y, layer_color.z,
388 draw_list->AddRectFilled(obj_start, obj_end, ImGui::GetColorU32(hover_fill));
391 draw_list->AddRect(obj_start, obj_end, ImGui::GetColorU32(hover_border), 0.0f,
400 if (current_room_id_ < 0 || current_room_id_ >= 296)
407 new_object.
x_ = room_x;
408 new_object.y_ = room_y;
412 room.AddTileObject(new_object);
438 selected_indices.empty() || !
rooms_)
440 if (current_room_id_ < 0 || current_room_id_ >= 296)
444 ImDrawList* draw_list = ImGui::GetWindowDrawList();
447 ImVec2 drag_delta = ImVec2(state.drag_current.x - state.drag_start.x,
448 state.drag_current.y - state.drag_start.y);
451 const auto& objects = room.GetTileObjects();
454 for (
size_t index : selected_indices) {
455 if (index < objects.size()) {
456 const auto&
object = objects[index];
463 ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x,
464 canvas_pos.y + canvas_y + drag_delta.y);
465 ImVec2 preview_end(preview_start.x + obj_width,
466 preview_start.y + obj_height);
469 draw_list->AddRectFilled(preview_start, preview_end,
470 ImGui::GetColorU32(theme.dungeon_drag_preview));
471 draw_list->AddRect(preview_start, preview_end,
472 ImGui::GetColorU32(theme.dungeon_selection_secondary),
491 int room_x,
int room_y)
const {
493 return {room_x * 8, room_y * 8};
497 int canvas_x,
int canvas_y)
const {
499 return {canvas_x / 8, canvas_y / 8};
506 int scaled_width =
static_cast<int>(canvas_size.x * global_scale);
507 int scaled_height =
static_cast<int>(canvas_size.y * global_scale);
509 return (canvas_x >= -margin && canvas_y >= -margin &&
510 canvas_x <= scaled_width + margin &&
511 canvas_y <= scaled_height + margin);
515 std::array<zelda3::Room, dungeon_coords::kRoomCount>* rooms,
int room_id) {
527 if (loaded &&
object.id_ >= 0) {
555 if (!room.IsLoaded()) {
562 width = std::max(width, 16);
563 height = std::max(height, 16);
567 std::make_unique<gfx::BackgroundBuffer>(width, height);
570 const uint8_t* gfx_data = room.get_gfx_buffer().data();
586 if (bitmap.size() > 0) {
606 if (hovered ==
static_cast<size_t>(-1)) {
610 const ImGuiIO& io = ImGui::GetIO();
615 }
else if (io.KeyCtrl) {
625 if (indices.empty() || !
rooms_)
627 if (current_room_id_ < 0 || current_room_id_ >= 296)
635 std::sort(indices.rbegin(), indices.rend());
638 for (
size_t index : indices) {
639 room.RemoveTileObject(index);
651 if (indices.empty() || !
rooms_)
653 if (current_room_id_ < 0 || current_room_id_ >= 296)
657 const auto& objects = room.GetTileObjects();
661 for (
size_t index : indices) {
662 if (index < objects.size()) {
673 if (current_room_id_ < 0 || current_room_id_ >= 296)
681 const ImGuiIO& io = ImGui::GetIO();
682 ImVec2 mouse_pos = io.MousePos;
684 ImVec2 canvas_mouse_pos =
685 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
686 auto [paste_x, paste_y] =
688 static_cast<int>(canvas_mouse_pos.y));
698 new_obj.x_ = obj.x_ + offset_x;
699 new_obj.y_ = obj.y_ + offset_y;
702 new_obj.x_ = std::clamp(
static_cast<int>(new_obj.x_), 0, 63);
703 new_obj.y_ = std::clamp(
static_cast<int>(new_obj.y_), 0, 63);
705 room.AddTileObject(new_obj);
740 const ImGuiIO& io = ImGui::GetIO();
742 ImVec2 mouse_pos = io.MousePos;
745 ImVec2 canvas_mouse_pos =
746 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
749 auto [room_x, room_y] =
751 static_cast<int>(canvas_mouse_pos.y));
754 if (room_x < 0 || room_x >= 64 || room_y < 0 || room_y >= 64)
763 int obj_width, obj_height;
767 obj_height = 16 + size * 16;
770 obj_width = 16 + size * 16;
773 obj_width = std::min(obj_width, 256);
774 obj_height = std::min(obj_height, 256);
777 ImDrawList* draw_list = ImGui::GetWindowDrawList();
781 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale,
782 canvas_pos.y + snap_canvas_y * scale);
783 ImVec2 preview_end(preview_start.x + obj_width * scale,
784 preview_start.y + obj_height * scale);
787 bool drew_bitmap =
false;
792 if (bitmap.texture()) {
794 ImVec2 bitmap_end(preview_start.x + bitmap.width() * scale,
795 preview_start.y + bitmap.height() * scale);
798 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
799 preview_start, bitmap_end, ImVec2(0, 0), ImVec2(1, 1),
800 IM_COL32(255, 255, 255, 180));
803 ImVec4 preview_outline = ImVec4(theme.dungeon_selection_primary.x,
804 theme.dungeon_selection_primary.y,
805 theme.dungeon_selection_primary.z,
807 draw_list->AddRect(preview_start, bitmap_end,
808 ImGui::GetColorU32(preview_outline), 0.0f, 0, 2.0f);
816 ImVec4 preview_fill = ImVec4(theme.dungeon_selection_primary.x,
817 theme.dungeon_selection_primary.y,
818 theme.dungeon_selection_primary.z,
820 draw_list->AddRectFilled(preview_start, preview_end,
821 ImGui::GetColorU32(preview_fill));
824 ImVec4 preview_outline = ImVec4(theme.dungeon_selection_primary.x,
825 theme.dungeon_selection_primary.y,
826 theme.dungeon_selection_primary.z,
828 draw_list->AddRect(preview_start, preview_end,
829 ImGui::GetColorU32(preview_outline), 0.0f, 0, 2.0f);
834 ImVec2 text_pos(preview_start.x + 2, preview_start.y + 2);
837 ImVec2 text_size = ImGui::CalcTextSize(id_text.c_str());
838 draw_list->AddRectFilled(
840 ImVec2(text_pos.x + text_size.x + 4, text_pos.y + text_size.y + 2),
841 IM_COL32(0, 0, 0, 180));
842 draw_list->AddText(ImVec2(text_pos.x + 2, text_pos.y + 1),
843 ImGui::GetColorU32(theme.text_primary), id_text.c_str());
846 constexpr float crosshair_size = 8.0f;
847 ImVec2 center(preview_start.x + (obj_width * scale) / 2,
848 preview_start.y + (obj_height * scale) / 2);
849 ImVec4 crosshair_color =
850 ImVec4(theme.text_primary.x, theme.text_primary.y, theme.text_primary.z,
852 ImU32 crosshair = ImGui::GetColorU32(crosshair_color);
853 draw_list->AddLine(ImVec2(center.x - crosshair_size, center.y),
854 ImVec2(center.x + crosshair_size, center.y), crosshair,
856 draw_list->AddLine(ImVec2(center.x, center.y - crosshair_size),
857 ImVec2(center.x, center.y + crosshair_size), crosshair,
862 const ImGuiIO& io = ImGui::GetIO();
865 if (io.MouseWheel == 0.0f)
877 if (hovered ==
static_cast<size_t>(-1))
884 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
891 auto& objects = room.GetTileObjects();
894 int resize_delta = (io.MouseWheel > 0.0f) ? 1 : -1;
898 for (
size_t index : selected_indices) {
899 if (index >= objects.size())
902 auto&
object = objects[index];
905 int current_size =
static_cast<int>(
object.size_);
906 int new_size = current_size + resize_delta;
909 new_size = std::clamp(new_size, 0, 15);
912 object.size_ =
static_cast<uint8_t
>(new_size);
915 room.MarkObjectsDirty();
925 if (dim_table.IsLoaded()) {
926 auto [w_tiles, h_tiles] = dim_table.GetDimensions(
object.id_,
object.size_);
927 return {w_tiles * 8, h_tiles * 8};
941 int size =
object.size_ & 0x0F;
943 if (
object.id_ >= 0x60 &&
object.id_ <= 0x7F) {
946 height = 16 + size * 16;
949 width = 16 + size * 16;
952 return {width, height};
956 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
957 return static_cast<size_t>(-1);
960 const ImGuiIO& io = ImGui::GetIO();
962 ImVec2 mouse_pos = io.MousePos;
963 ImVec2 canvas_mouse_pos =
964 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
967 auto [room_x, room_y] =
969 static_cast<int>(canvas_mouse_pos.y));
973 const auto& objects = room.GetTileObjects();
979 for (
size_t i = objects.size(); i > 0; --i) {
980 size_t index = i - 1;
981 const auto&
object = objects[index];
990 int obj_x =
object.x_;
991 int obj_y =
object.y_;
1000 int mouse_pixel_x =
static_cast<int>(canvas_mouse_pos.x);
1001 int mouse_pixel_y =
static_cast<int>(canvas_mouse_pos.y);
1003 int obj_pixel_x = obj_x * 8;
1004 int obj_pixel_y = obj_y * 8;
1006 if (mouse_pixel_x >= obj_pixel_x && mouse_pixel_x < obj_pixel_x + width &&
1007 mouse_pixel_y >= obj_pixel_y && mouse_pixel_y < obj_pixel_y + height) {
1012 return static_cast<size_t>(-1);
1017 if (indices.empty() || !
rooms_)
1019 if (current_room_id_ < 0 || current_room_id_ >= 296)
1023 if (target_layer < 0 || target_layer > 2) {
1030 auto& objects = room.GetTileObjects();
1033 for (
size_t index : indices) {
1034 if (index < objects.size()) {
1035 objects[index].layer_ =
1040 room.MarkObjectsDirty();
1048 if (indices.empty() || !
rooms_)
1050 if (current_room_id_ < 0 || current_room_id_ >= 296)
1056 auto& objects = room.GetTileObjects();
1060 std::vector<zelda3::RoomObject> selected_objects;
1061 std::vector<zelda3::RoomObject> other_objects;
1063 for (
size_t i = 0; i < objects.size(); ++i) {
1064 if (std::find(indices.begin(), indices.end(), i) != indices.end()) {
1065 selected_objects.push_back(objects[i]);
1067 other_objects.push_back(objects[i]);
1073 objects.insert(objects.end(), other_objects.begin(), other_objects.end());
1074 objects.insert(objects.end(), selected_objects.begin(),
1075 selected_objects.end());
1079 for (
size_t i = 0; i < selected_objects.size(); ++i) {
1084 room.MarkObjectsDirty();
1091 if (indices.empty() || !
rooms_)
1093 if (current_room_id_ < 0 || current_room_id_ >= 296)
1099 auto& objects = room.GetTileObjects();
1102 std::vector<zelda3::RoomObject> selected_objects;
1103 std::vector<zelda3::RoomObject> other_objects;
1105 for (
size_t i = 0; i < objects.size(); ++i) {
1106 if (std::find(indices.begin(), indices.end(), i) != indices.end()) {
1107 selected_objects.push_back(objects[i]);
1109 other_objects.push_back(objects[i]);
1115 objects.insert(objects.end(), selected_objects.begin(),
1116 selected_objects.end());
1117 objects.insert(objects.end(), other_objects.begin(), other_objects.end());
1121 for (
size_t i = 0; i < selected_objects.size(); ++i) {
1125 room.MarkObjectsDirty();
1132 if (indices.empty() || !
rooms_)
1134 if (current_room_id_ < 0 || current_room_id_ >= 296)
1138 auto& objects = room.GetTileObjects();
1142 std::sort(indices.begin(), indices.end());
1145 bool all_at_end =
true;
1146 for (
size_t idx : indices) {
1147 if (idx < objects.size() - 1) {
1158 std::vector<size_t> new_indices;
1161 for (
auto it = indices.rbegin(); it != indices.rend(); ++it) {
1163 if (idx < objects.size() - 1) {
1165 std::swap(objects[idx], objects[idx + 1]);
1166 new_indices.push_back(idx + 1);
1168 new_indices.push_back(idx);
1174 for (
size_t idx : new_indices) {
1178 room.MarkObjectsDirty();
1185 if (indices.empty() || !
rooms_)
1187 if (current_room_id_ < 0 || current_room_id_ >= 296)
1191 auto& objects = room.GetTileObjects();
1195 std::sort(indices.begin(), indices.end());
1198 bool all_at_start =
true;
1199 for (
size_t idx : indices) {
1201 all_at_start =
false;
1211 std::vector<size_t> new_indices;
1214 for (
size_t idx : indices) {
1217 std::swap(objects[idx], objects[idx - 1]);
1218 new_indices.push_back(idx - 1);
1220 new_indices.push_back(idx);
1226 for (
size_t idx : new_indices) {
1230 room.MarkObjectsDirty();
1241 if (ImGui::IsAnyItemActive())
1245 if (ImGui::IsKeyPressed(ImGuiKey_1)) {
1247 }
else if (ImGui::IsKeyPressed(ImGuiKey_2)) {
1249 }
else if (ImGui::IsKeyPressed(ImGuiKey_3)) {
1256 auto& io = ImGui::GetIO();
1257 if (io.KeyCtrl && io.KeyShift) {
1258 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
1260 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
1263 }
else if (io.KeyCtrl) {
1264 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
1266 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
1298 const ImGuiIO& io = ImGui::GetIO();
1300 ImVec2 mouse_pos = io.MousePos;
1303 int canvas_x =
static_cast<int>(mouse_pos.x - canvas_pos.x);
1304 int canvas_y =
static_cast<int>(mouse_pos.y - canvas_pos.y);
1316 canvas_x, canvas_y, direction);
1321 state.snapped_door_position = position;
1324 auto [tile_x, tile_y] =
1329 int door_width_px = dims.width_tiles * 8;
1330 int door_height_px = dims.height_tiles * 8;
1336 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1339 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale,
1340 canvas_pos.y + snap_canvas_y * scale);
1341 ImVec2 preview_end(preview_start.x + door_width_px * scale,
1342 preview_start.y + door_height_px * scale);
1347 ImU32 fill_color = IM_COL32(theme.dungeon_selection_primary.x * 255,
1348 theme.dungeon_selection_primary.y * 255,
1349 theme.dungeon_selection_primary.z * 255,
1351 draw_list->AddRectFilled(preview_start, preview_end, fill_color);
1354 ImVec4 outline_color = ImVec4(theme.dungeon_selection_primary.x,
1355 theme.dungeon_selection_primary.y,
1356 theme.dungeon_selection_primary.z, 0.9f);
1357 draw_list->AddRect(preview_start, preview_end,
1358 ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
1361 const char* type_name =
1363 const char* dir_name =
1366 snprintf(label,
sizeof(label),
"%s (%s)", type_name, dir_name);
1368 ImVec2 text_pos(preview_start.x, preview_start.y - 16 * scale);
1369 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 200), label);
1376 if (current_room_id_ < 0 || current_room_id_ >= 296)
1389 canvas_x, canvas_y, direction);
1405 new_door.
byte1 = byte1;
1406 new_door.
byte2 = byte2;
1410 room.AddDoor(new_door);
1421 uint8_t sprite_id) {
1440 const ImGuiIO& io = ImGui::GetIO();
1442 ImVec2 mouse_pos = io.MousePos;
1446 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1447 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1450 int snapped_x = (canvas_x / 16) * 16;
1451 int snapped_y = (canvas_y / 16) * 16;
1454 ImVec2 rect_min(canvas_pos.x + snapped_x * scale,
1455 canvas_pos.y + snapped_y * scale);
1456 ImVec2 rect_max(rect_min.x + 16 * scale, rect_min.y + 16 * scale);
1459 ImU32 fill_color = IM_COL32(50, 200, 50, 100);
1460 ImU32 outline_color = IM_COL32(50, 255, 50, 200);
1477 if (current_room_id_ < 0 || current_room_id_ >= 296)
1485 int sprite_x = canvas_x /
static_cast<int>(16 * scale);
1486 int sprite_y = canvas_y /
static_cast<int>(16 * scale);
1489 sprite_x = std::clamp(sprite_x, 0, 31);
1490 sprite_y = std::clamp(sprite_y, 0, 31);
1496 static_cast<uint8_t
>(sprite_x),
1497 static_cast<uint8_t
>(sprite_y), 0, 0);
1501 room.GetSprites().push_back(new_sprite);
1531 const ImGuiIO& io = ImGui::GetIO();
1533 ImVec2 mouse_pos = io.MousePos;
1537 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1538 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1541 int snapped_x = (canvas_x / 8) * 8;
1542 int snapped_y = (canvas_y / 8) * 8;
1545 ImVec2 rect_min(canvas_pos.x + snapped_x * scale,
1546 canvas_pos.y + snapped_y * scale);
1547 ImVec2 rect_max(rect_min.x + 16 * scale, rect_min.y + 16 * scale);
1550 ImU32 fill_color = IM_COL32(200, 200, 50, 100);
1551 ImU32 outline_color = IM_COL32(255, 255, 50, 200);
1567 if (current_room_id_ < 0 || current_room_id_ >= 296)
1575 int pixel_x = canvas_x /
static_cast<int>(scale);
1576 int pixel_y = canvas_y /
static_cast<int>(scale);
1581 int encoded_x = pixel_x / 4;
1582 int encoded_y = pixel_y / 16;
1585 encoded_x = std::clamp(encoded_x, 0, 255);
1586 encoded_y = std::clamp(encoded_y, 0, 255);
1592 new_item.
position =
static_cast<uint16_t
>((encoded_y << 8) | encoded_x);
1597 room.GetPotItems().push_back(new_item);
1632 int canvas_x,
int canvas_y)
const {
1633 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
1634 return std::nullopt;
1642 int room_x =
static_cast<int>(canvas_x / scale);
1643 int room_y =
static_cast<int>(canvas_y / scale);
1646 const auto& doors = room.GetDoors();
1647 for (
size_t i = 0; i < doors.size(); ++i) {
1648 const auto& door = doors[i];
1651 auto [tile_x, tile_y] = door.GetTileCoords();
1657 int door_x = tile_x * 8;
1658 int door_y = tile_y * 8;
1659 int door_w = dims.width_tiles * 8;
1660 int door_h = dims.height_tiles * 8;
1663 if (room_x >= door_x && room_x < door_x + door_w && room_y >= door_y &&
1664 room_y < door_y + door_h) {
1671 const auto& sprites = room.GetSprites();
1672 for (
size_t i = 0; i < sprites.size(); ++i) {
1673 const auto& sprite = sprites[i];
1676 int sprite_x = sprite.x() * 16;
1677 int sprite_y = sprite.y() * 16;
1680 if (room_x >= sprite_x && room_x < sprite_x + 16 && room_y >= sprite_y &&
1681 room_y < sprite_y + 16) {
1687 const auto& pot_items = room.GetPotItems();
1689 for (
size_t i = 0; i < pot_items.size(); ++i) {
1690 const auto& pot_item = pot_items[i];
1693 int item_x = pot_item.GetPixelX();
1694 int item_y = pot_item.GetPixelY();
1697 if (room_x >= item_x && room_x < item_x + 16 && room_y >= item_y &&
1698 room_y < item_y + 16) {
1703 return std::nullopt;
1710 const ImGuiIO& io = ImGui::GetIO();
1712 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1713 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1716 if (entity.has_value()) {
1726 ImVec2(
static_cast<float>(canvas_x),
static_cast<float>(canvas_y));
1727 state.entity_drag_current = state.entity_drag_start;
1742 const ImGuiIO& io = ImGui::GetIO();
1745 if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
1750 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1751 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1756 canvas_x, canvas_y, direction)) {
1759 canvas_x, canvas_y, direction);
1765 auto& doors = room.GetDoors();
1778 room.MarkObjectsDirty();
1788 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1789 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1792 int tile_x = canvas_x / 16;
1793 int tile_y = canvas_y / 16;
1796 tile_x = std::clamp(tile_x, 0, 31);
1797 tile_y = std::clamp(tile_y, 0, 31);
1801 auto& sprites = room.GetSprites();
1820 state.entity_drag_current =
1821 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1828 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
1833 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1839 const char* label =
"";
1843 const auto& doors = room.GetDoors();
1848 auto [tile_x, tile_y] = door.GetTileCoords();
1854 int drag_x =
static_cast<int>(state.entity_drag_current.x);
1855 int drag_y =
static_cast<int>(state.entity_drag_current.y);
1858 bool is_inner =
false;
1862 drag_x, drag_y, dir);
1863 auto [snap_x, snap_y] =
1871 pos = ImVec2(canvas_pos.x + tile_x * 8 * scale,
1872 canvas_pos.y + tile_y * 8 * scale);
1874 ImVec2(dims.width_tiles * 8 * scale, dims.height_tiles * 8 * scale);
1875 color = IM_COL32(255, 165, 0, 180);
1881 const auto& sprites = room.GetSprites();
1887 int pixel_x = sprite.x() * 16;
1888 int pixel_y = sprite.y() * 16;
1893 int tile_x =
static_cast<int>(state.entity_drag_current.x) / 16;
1894 int tile_y =
static_cast<int>(state.entity_drag_current.y) / 16;
1895 tile_x = std::clamp(tile_x, 0, 31);
1896 tile_y = std::clamp(tile_y, 0, 31);
1897 pixel_x = tile_x * 16;
1898 pixel_y = tile_y * 16;
1901 pos = ImVec2(canvas_pos.x + pixel_x * scale,
1902 canvas_pos.y + pixel_y * scale);
1903 size = ImVec2(16 * scale, 16 * scale);
1904 color = IM_COL32(0, 255, 0, 180);
1911 const auto& pot_items = room.GetPotItems();
1917 int pixel_x = pot_item.GetPixelX();
1918 int pixel_y = pot_item.GetPixelY();
1920 pos = ImVec2(canvas_pos.x + pixel_x * scale,
1921 canvas_pos.y + pixel_y * scale);
1922 size = ImVec2(16 * scale, 16 * scale);
1923 color = IM_COL32(255, 255, 0, 180);
1933 static float pulse = 0.0f;
1934 pulse += ImGui::GetIO().DeltaTime * 3.0f;
1935 float alpha = 0.5f + 0.3f * sinf(pulse);
1938 (color & 0x00FFFFFF) | (
static_cast<ImU32
>(alpha * 100) << 24);
1939 draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y),
1941 draw_list->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), color, 0.0f,
1945 ImVec2 text_pos(pos.x, pos.y - 14 * scale);
1946 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 220), label);
1961 bool is_inner =
false;
1962 int drag_x =
static_cast<int>(state.entity_drag_current.x);
1963 int drag_y =
static_cast<int>(state.entity_drag_current.y);
1974 drag_x, drag_y, direction);
1976 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1984 for (uint8_t i = 0; i < 6; ++i) {
1985 uint8_t pos = start_pos + i;
1986 auto [tile_x, tile_y] =
1988 float pixel_x = tile_x * 8.0f;
1989 float pixel_y = tile_y * 8.0f;
1991 ImVec2 snap_start(canvas_pos.x + pixel_x * scale,
1992 canvas_pos.y + pixel_y * scale);
1993 ImVec2 snap_end(snap_start.x + dims.width_pixels() * scale,
1994 snap_start.y + dims.height_pixels() * scale);
1996 if (pos == nearest_snap) {
1998 ImVec4 highlight = ImVec4(theme.dungeon_selection_primary.x,
1999 theme.dungeon_selection_primary.y,
2000 theme.dungeon_selection_primary.z, 0.75f);
2001 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(highlight),
2005 ImVec4 ghost = ImVec4(1.0f, 1.0f, 1.0f, 0.25f);
2006 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(ghost), 0.0f,
Handles object selection, placement, and interaction within the dungeon canvas.
void BringSelectedForward()
void HandleDeleteSelected()
void SetCurrentRoom(std::array< zelda3::Room, dungeon_coords::kRoomCount > *rooms, int room_id)
std::pair< int, int > CalculateObjectBounds(const zelda3::RoomObject &object)
InteractionContext interaction_context_
void PlaceDoorAtPosition(int canvas_x, int canvas_y)
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin=32) const
void HandleCanvasMouseInput()
std::unique_ptr< gfx::BackgroundBuffer > ghost_preview_buffer_
std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y) const
size_t GetHoveredObjectIndex() const
void DrawDoorGhostPreview()
void SetItemPlacementMode(bool enabled, uint8_t item_id=0)
void SendSelectedBackward()
bool IsObjectInSelectBox(const zelda3::RoomObject &object) const
void DrawHoverHighlight(const std::vector< zelda3::RoomObject > &objects)
void PlaceSpriteAtPosition(int canvas_x, int canvas_y)
void DrawSpriteGhostPreview()
std::function< void(const zelda3::RoomObject &) object_placed_callback_)
InteractionCoordinator entity_coordinator_
void PlaceObjectAtPosition(int room_x, int room_y)
ObjectSelection selection_
void DrawItemGhostPreview()
zelda3::RoomObject preview_object_
std::unique_ptr< zelda3::ObjectDrawer > object_drawer_
void SetSpritePlacementMode(bool enabled, uint8_t sprite_id=0)
void DrawEntitySelectionHighlights()
zelda3::DoorType GetPreviewDoorType() const
void UpdateSelectedObjects()
void DrawSelectionHighlights()
void HandleLayerKeyboardShortcuts()
void DrawObjectSelectRect()
void SendSelectedToBack()
bool TrySelectObjectAtCursor()
bool TrySelectEntityAtCursor()
SelectedEntity selected_entity_
uint8_t GetPreviewSpriteId() const
std::array< zelda3::Room, dungeon_coords::kRoomCount > * rooms_
void SendSelectedToLayer(int target_layer)
std::vector< zelda3::RoomObject > clipboard_
void DrawDoorSnapIndicators()
void ClearEntitySelection()
void HandlePasteObjects()
std::optional< SelectedEntity > GetEntityAtPosition(int canvas_x, int canvas_y) const
void HandleScrollWheelResize()
void SelectEntity(EntityType type, size_t index)
void SelectObjectsInRect()
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
gfx::PaletteGroup current_palette_group_
std::pair< int, int > CanvasToRoomCoordinates(int canvas_x, int canvas_y) const
void RenderGhostPreviewBitmap()
uint8_t GetPreviewItemId() const
void SetPreviewObject(const zelda3::RoomObject &object, bool loaded)
void HandleCopySelected()
void PlaceItemAtPosition(int canvas_x, int canvas_y)
InteractionModeManager mode_manager_
void CheckForObjectSelection()
void SendSelectedToFront()
void SetContext(InteractionContext *ctx)
Set the shared interaction context.
void SetMode(InteractionMode mode)
Set interaction mode.
InteractionMode GetMode() const
Get current interaction mode.
ModeState & GetModeState()
Get mutable reference to mode state.
bool IsPlacementActive() const
Check if any placement mode is active.
bool IsObjectPlacementActive() const
Check if object placement mode is active.
bool IsRectangleSelectionActive() const
Check if a rectangle selection is in progress.
void UpdateRectangleSelection(int canvas_x, int canvas_y)
Update rectangle selection endpoint.
static std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y)
Convert room tile coordinates to canvas pixel coordinates.
bool IsObjectSelected(size_t index) const
Check if an object is selected.
std::vector< size_t > GetSelectedIndices() const
Get all selected object indices.
void SelectObject(size_t index, SelectionMode mode=SelectionMode::Single)
Select a single object by index.
void ClearSelection()
Clear all selections.
void EndRectangleSelection(const std::vector< zelda3::RoomObject > &objects, SelectionMode mode=SelectionMode::Single)
Complete rectangle selection operation.
void BeginRectangleSelection(int canvas_x, int canvas_y)
Begin a rectangle selection operation.
void DrawSelectionHighlights(gui::Canvas *canvas, const std::vector< zelda3::RoomObject > &objects, std::function< std::pair< int, int >(const zelda3::RoomObject &)> dimension_calculator)
Draw selection highlights for all selected objects.
ImVec4 GetLayerTypeColor(const zelda3::RoomObject &object) const
Get selection highlight color based on object layer and type.
void DrawRectangleSelectionBox(gui::Canvas *canvas)
Draw the active rectangle selection box.
bool HasSelection() const
Check if any objects are selected.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void ProcessTextureQueue(IRenderer *renderer)
auto global_scale() const
bool IsMouseHovering() const
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.
static ObjectDimensionTable & Get()
Draws dungeon objects to background buffers using game patterns.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
A class for managing sprites in the overworld and underworld.
#define ICON_MD_DRAG_INDICATOR
#define ICON_MD_TOUCH_APP
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
EntityType
Type of entity that can be selected in the dungeon editor.
constexpr DoorDimensions GetDoorDimensions(DoorDirection dir)
Get door dimensions based on direction.
DoorType
Door types from ALTTP.
int GetObjectSubtype(int object_id)
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
std::string GetObjectName(int object_id)
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
DoorDirection
Door direction on room walls.
void NotifyEntityChanged() const
Notify that entity has changed.
std::array< zelda3::Room, dungeon_coords::kRoomCount > * rooms
void NotifyInvalidateCache() const
Notify that cache invalidation is needed.
void NotifyMutation() const
Notify that a mutation is about to happen.
std::optional< zelda3::DoorType > preview_door_type
zelda3::DoorDirection detected_door_direction
std::optional< uint8_t > preview_item_id
std::optional< uint8_t > preview_sprite_id
std::optional< zelda3::RoomObject > preview_object
Represents a selected entity in the dungeon editor.
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)