8#include "imgui/imgui.h"
19 const ImGuiIO& io = ImGui::GetIO();
39 ImVec2 mouse_pos = io.MousePos;
43 ImVec2 canvas_mouse_pos =
44 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
47 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
51 static_cast<int>(canvas_mouse_pos.y));
56 static_cast<int>(canvas_mouse_pos.y));
61 static_cast<int>(canvas_mouse_pos.y));
65 auto [room_x, room_y] =
67 static_cast<int>(canvas_mouse_pos.y));
79 if (!io.KeyShift && !io.KeyCtrl) {
87 state.
rect_start_x =
static_cast<int>(canvas_mouse_pos.x);
88 state.rect_start_y =
static_cast<int>(canvas_mouse_pos.y);
89 state.rect_end_x = state.rect_start_x;
90 state.rect_end_y = state.rect_start_y;
99 state.drag_current = canvas_mouse_pos;
114 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
120 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
131 ImVec2 drag_delta = ImVec2(state.drag_current.x - state.drag_start.x,
132 state.drag_current.y - state.drag_start.y);
135 int tile_delta_x =
static_cast<int>(drag_delta.x) / 8;
136 int tile_delta_y =
static_cast<int>(drag_delta.y) / 8;
139 if (tile_delta_x != 0 || tile_delta_y != 0) {
140 auto& objects = room.GetTileObjects();
141 for (
size_t index : selected_indices) {
142 if (index < objects.size()) {
143 objects[index].x_ += tile_delta_x;
144 objects[index].y_ += tile_delta_y;
148 std::clamp(
static_cast<int>(objects[index].x_), 0, 63);
150 std::clamp(
static_cast<int>(objects[index].y_), 0, 63);
155 room.MarkObjectsDirty();
175 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
178 const ImGuiIO& io = ImGui::GetIO();
180 const ImVec2 mouse_pos =
181 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
188 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
190 static_cast<int>(mouse_pos.y));
197 !ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
204 }
else if (io.KeyCtrl) {
219 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
223 const auto& objects = room.GetTileObjects();
231 if (dim_table.IsLoaded()) {
232 auto [w_tiles, h_tiles] = dim_table.GetSelectionDimensions(obj.
id_, obj.
size_);
233 return std::make_pair(w_tiles * 8, h_tiles * 8);
239 return std::make_pair(16, 16);
250 ImGuiIO& io = ImGui::GetIO();
252 int cursor_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
253 int cursor_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
255 if (entity_at_cursor.has_value()) {
262 if (hovered_index !=
static_cast<size_t>(-1) &&
263 hovered_index < objects.size()) {
264 const auto&
object = objects[hovered_index];
267 int layer =
object.GetLayerValue();
270 const char* subtype_names[] = {
"Unknown",
"Type 1",
"Type 2",
"Type 3"};
271 const char* subtype_name = (subtype >= 0 && subtype <= 3)
272 ? subtype_names[subtype] :
"Unknown";
276 tooltip += object_name;
277 tooltip +=
" (" + std::string(subtype_name) +
")";
279 tooltip +=
"ID: 0x" + absl::StrFormat(
"%03X",
object.id_);
280 tooltip +=
" | Layer: " + std::to_string(layer + 1);
281 tooltip +=
" | Pos: (" + std::to_string(
object.x_) +
", " +
282 std::to_string(
object.y_) +
")";
283 tooltip +=
"\nSize: " + std::to_string(
object.size_) +
284 " (0x" + absl::StrFormat(
"%02X",
object.size_) +
")";
293 ImGui::SetTooltip(
"%s", tooltip.c_str());
302 const std::vector<zelda3::RoomObject>& objects) {
310 ImGuiIO& io = ImGui::GetIO();
312 int cursor_canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
313 int cursor_canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
315 if (entity_at_cursor.has_value()) {
320 if (hovered_index ==
static_cast<size_t>(-1) || hovered_index >= objects.size()) {
329 const auto&
object = objects[hovered_index];
331 ImDrawList* draw_list = ImGui::GetWindowDrawList();
338 int pixel_width, pixel_height;
340 if (dim_table.IsLoaded()) {
341 auto [w_tiles, h_tiles] = dim_table.GetSelectionDimensions(
object.id_,
object.size_);
342 pixel_width = w_tiles * 8;
343 pixel_height = h_tiles * 8;
346 pixel_width = dims.first;
347 pixel_height = dims.second;
354 ImVec2 obj_start(canvas_pos.x + obj_x * scale,
355 canvas_pos.y + obj_y * scale);
356 ImVec2 obj_end(obj_start.x + pixel_width * scale,
357 obj_start.y + pixel_height * scale);
360 constexpr float margin = 2.0f;
361 obj_start.x -= margin;
362 obj_start.y -= margin;
370 ImVec4 hover_fill = ImVec4(
371 layer_color.x, layer_color.y, layer_color.z,
374 ImVec4 hover_border = ImVec4(
375 layer_color.x, layer_color.y, layer_color.z,
380 draw_list->AddRectFilled(obj_start, obj_end, ImGui::GetColorU32(hover_fill));
383 draw_list->AddRect(obj_start, obj_end, ImGui::GetColorU32(hover_border), 0.0f, 0, 1.5f);
390 if (current_room_id_ < 0 || current_room_id_ >= 296)
397 new_object.
x_ = room_x;
398 new_object.y_ = room_y;
402 room.AddTileObject(new_object);
428 selected_indices.empty() || !
rooms_)
430 if (current_room_id_ < 0 || current_room_id_ >= 296)
434 ImDrawList* draw_list = ImGui::GetWindowDrawList();
437 ImVec2 drag_delta = ImVec2(state.drag_current.x - state.drag_start.x,
438 state.drag_current.y - state.drag_start.y);
441 const auto& objects = room.GetTileObjects();
444 for (
size_t index : selected_indices) {
445 if (index < objects.size()) {
446 const auto&
object = objects[index];
453 ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x,
454 canvas_pos.y + canvas_y + drag_delta.y);
455 ImVec2 preview_end(preview_start.x + obj_width,
456 preview_start.y + obj_height);
459 draw_list->AddRectFilled(preview_start, preview_end,
460 ImGui::GetColorU32(theme.dungeon_drag_preview));
461 draw_list->AddRect(preview_start, preview_end,
462 ImGui::GetColorU32(theme.dungeon_selection_secondary),
481 int room_x,
int room_y)
const {
483 return {room_x * 8, room_y * 8};
487 int canvas_x,
int canvas_y)
const {
489 return {canvas_x / 8, canvas_y / 8};
496 int scaled_width =
static_cast<int>(canvas_size.x * global_scale);
497 int scaled_height =
static_cast<int>(canvas_size.y * global_scale);
499 return (canvas_x >= -margin && canvas_y >= -margin &&
500 canvas_x <= scaled_width + margin &&
501 canvas_y <= scaled_height + margin);
505 std::array<zelda3::Room, dungeon_coords::kRoomCount>* rooms,
int room_id) {
517 if (loaded &&
object.id_ >= 0) {
545 if (!room.IsLoaded()) {
552 width = std::max(width, 16);
553 height = std::max(height, 16);
557 std::make_unique<gfx::BackgroundBuffer>(width, height);
560 const uint8_t* gfx_data = room.get_gfx_buffer().data();
575 if (bitmap.size() > 0) {
594 if (hovered ==
static_cast<size_t>(-1)) {
598 const ImGuiIO& io = ImGui::GetIO();
603 }
else if (io.KeyCtrl) {
613 if (indices.empty() || !
rooms_)
615 if (current_room_id_ < 0 || current_room_id_ >= 296)
623 std::sort(indices.rbegin(), indices.rend());
626 for (
size_t index : indices) {
627 room.RemoveTileObject(index);
639 if (indices.empty() || !
rooms_)
641 if (current_room_id_ < 0 || current_room_id_ >= 296)
645 const auto& objects = room.GetTileObjects();
649 for (
size_t index : indices) {
650 if (index < objects.size()) {
661 if (current_room_id_ < 0 || current_room_id_ >= 296)
669 const ImGuiIO& io = ImGui::GetIO();
670 ImVec2 mouse_pos = io.MousePos;
672 ImVec2 canvas_mouse_pos =
673 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
674 auto [paste_x, paste_y] =
676 static_cast<int>(canvas_mouse_pos.y));
686 new_obj.x_ = obj.x_ + offset_x;
687 new_obj.y_ = obj.y_ + offset_y;
690 new_obj.x_ = std::clamp(
static_cast<int>(new_obj.x_), 0, 63);
691 new_obj.y_ = std::clamp(
static_cast<int>(new_obj.y_), 0, 63);
693 room.AddTileObject(new_obj);
728 const ImGuiIO& io = ImGui::GetIO();
730 ImVec2 mouse_pos = io.MousePos;
733 ImVec2 canvas_mouse_pos =
734 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
737 auto [room_x, room_y] =
739 static_cast<int>(canvas_mouse_pos.y));
742 if (room_x < 0 || room_x >= 64 || room_y < 0 || room_y >= 64)
751 int obj_width, obj_height;
755 obj_height = 16 + size * 16;
758 obj_width = 16 + size * 16;
761 obj_width = std::min(obj_width, 256);
762 obj_height = std::min(obj_height, 256);
765 ImDrawList* draw_list = ImGui::GetWindowDrawList();
769 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale,
770 canvas_pos.y + snap_canvas_y * scale);
771 ImVec2 preview_end(preview_start.x + obj_width * scale,
772 preview_start.y + obj_height * scale);
775 bool drew_bitmap =
false;
780 if (bitmap.texture()) {
782 ImVec2 bitmap_end(preview_start.x + bitmap.width() * scale,
783 preview_start.y + bitmap.height() * scale);
786 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
787 preview_start, bitmap_end, ImVec2(0, 0), ImVec2(1, 1),
788 IM_COL32(255, 255, 255, 180));
791 ImVec4 preview_outline = ImVec4(theme.dungeon_selection_primary.x,
792 theme.dungeon_selection_primary.y,
793 theme.dungeon_selection_primary.z,
795 draw_list->AddRect(preview_start, bitmap_end,
796 ImGui::GetColorU32(preview_outline), 0.0f, 0, 2.0f);
804 ImVec4 preview_fill = ImVec4(theme.dungeon_selection_primary.x,
805 theme.dungeon_selection_primary.y,
806 theme.dungeon_selection_primary.z,
808 draw_list->AddRectFilled(preview_start, preview_end,
809 ImGui::GetColorU32(preview_fill));
812 ImVec4 preview_outline = ImVec4(theme.dungeon_selection_primary.x,
813 theme.dungeon_selection_primary.y,
814 theme.dungeon_selection_primary.z,
816 draw_list->AddRect(preview_start, preview_end,
817 ImGui::GetColorU32(preview_outline), 0.0f, 0, 2.0f);
822 ImVec2 text_pos(preview_start.x + 2, preview_start.y + 2);
825 ImVec2 text_size = ImGui::CalcTextSize(id_text.c_str());
826 draw_list->AddRectFilled(text_pos,
827 ImVec2(text_pos.x + text_size.x + 4,
828 text_pos.y + text_size.y + 2),
829 IM_COL32(0, 0, 0, 180));
830 draw_list->AddText(ImVec2(text_pos.x + 2, text_pos.y + 1),
831 ImGui::GetColorU32(theme.text_primary), id_text.c_str());
834 constexpr float crosshair_size = 8.0f;
835 ImVec2 center(preview_start.x + (obj_width * scale) / 2,
836 preview_start.y + (obj_height * scale) / 2);
837 ImVec4 crosshair_color =
838 ImVec4(theme.text_primary.x, theme.text_primary.y, theme.text_primary.z,
840 ImU32 crosshair = ImGui::GetColorU32(crosshair_color);
841 draw_list->AddLine(ImVec2(center.x - crosshair_size, center.y),
842 ImVec2(center.x + crosshair_size, center.y), crosshair,
844 draw_list->AddLine(ImVec2(center.x, center.y - crosshair_size),
845 ImVec2(center.x, center.y + crosshair_size), crosshair,
850 const ImGuiIO& io = ImGui::GetIO();
853 if (io.MouseWheel == 0.0f)
865 if (hovered ==
static_cast<size_t>(-1))
872 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
879 auto& objects = room.GetTileObjects();
882 int resize_delta = (io.MouseWheel > 0.0f) ? 1 : -1;
886 for (
size_t index : selected_indices) {
887 if (index >= objects.size())
890 auto&
object = objects[index];
893 int current_size =
static_cast<int>(
object.size_);
894 int new_size = current_size + resize_delta;
897 new_size = std::clamp(new_size, 0, 15);
900 object.size_ =
static_cast<uint8_t
>(new_size);
903 room.MarkObjectsDirty();
913 if (dim_table.IsLoaded()) {
914 auto [w_tiles, h_tiles] = dim_table.GetDimensions(
object.id_,
object.size_);
915 return {w_tiles * 8, h_tiles * 8};
929 int size =
object.size_ & 0x0F;
931 if (
object.id_ >= 0x60 &&
object.id_ <= 0x7F) {
934 height = 16 + size * 16;
937 width = 16 + size * 16;
940 return {width, height};
944 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
945 return static_cast<size_t>(-1);
948 const ImGuiIO& io = ImGui::GetIO();
950 ImVec2 mouse_pos = io.MousePos;
951 ImVec2 canvas_mouse_pos =
952 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
955 auto [room_x, room_y] =
957 static_cast<int>(canvas_mouse_pos.y));
961 const auto& objects = room.GetTileObjects();
967 for (
size_t i = objects.size(); i > 0; --i) {
968 size_t index = i - 1;
969 const auto&
object = objects[index];
978 int obj_x =
object.x_;
979 int obj_y =
object.y_;
988 int mouse_pixel_x =
static_cast<int>(canvas_mouse_pos.x);
989 int mouse_pixel_y =
static_cast<int>(canvas_mouse_pos.y);
991 int obj_pixel_x = obj_x * 8;
992 int obj_pixel_y = obj_y * 8;
994 if (mouse_pixel_x >= obj_pixel_x && mouse_pixel_x < obj_pixel_x + width &&
995 mouse_pixel_y >= obj_pixel_y && mouse_pixel_y < obj_pixel_y + height) {
1000 return static_cast<size_t>(-1);
1005 if (indices.empty() || !
rooms_)
1007 if (current_room_id_ < 0 || current_room_id_ >= 296)
1011 if (target_layer < 0 || target_layer > 2) {
1018 auto& objects = room.GetTileObjects();
1021 for (
size_t index : indices) {
1022 if (index < objects.size()) {
1023 objects[index].layer_ =
1028 room.MarkObjectsDirty();
1036 if (indices.empty() || !
rooms_)
1038 if (current_room_id_ < 0 || current_room_id_ >= 296)
1044 auto& objects = room.GetTileObjects();
1048 std::vector<zelda3::RoomObject> selected_objects;
1049 std::vector<zelda3::RoomObject> other_objects;
1051 for (
size_t i = 0; i < objects.size(); ++i) {
1052 if (std::find(indices.begin(), indices.end(), i) != indices.end()) {
1053 selected_objects.push_back(objects[i]);
1055 other_objects.push_back(objects[i]);
1061 objects.insert(objects.end(), other_objects.begin(), other_objects.end());
1062 objects.insert(objects.end(), selected_objects.begin(), selected_objects.end());
1066 for (
size_t i = 0; i < selected_objects.size(); ++i) {
1070 room.MarkObjectsDirty();
1077 if (indices.empty() || !
rooms_)
1079 if (current_room_id_ < 0 || current_room_id_ >= 296)
1085 auto& objects = room.GetTileObjects();
1088 std::vector<zelda3::RoomObject> selected_objects;
1089 std::vector<zelda3::RoomObject> other_objects;
1091 for (
size_t i = 0; i < objects.size(); ++i) {
1092 if (std::find(indices.begin(), indices.end(), i) != indices.end()) {
1093 selected_objects.push_back(objects[i]);
1095 other_objects.push_back(objects[i]);
1101 objects.insert(objects.end(), selected_objects.begin(), selected_objects.end());
1102 objects.insert(objects.end(), other_objects.begin(), other_objects.end());
1106 for (
size_t i = 0; i < selected_objects.size(); ++i) {
1110 room.MarkObjectsDirty();
1117 if (indices.empty() || !
rooms_)
1119 if (current_room_id_ < 0 || current_room_id_ >= 296)
1123 auto& objects = room.GetTileObjects();
1127 std::sort(indices.begin(), indices.end());
1130 bool all_at_end =
true;
1131 for (
size_t idx : indices) {
1132 if (idx < objects.size() - 1) {
1137 if (all_at_end)
return;
1142 std::vector<size_t> new_indices;
1145 for (
auto it = indices.rbegin(); it != indices.rend(); ++it) {
1147 if (idx < objects.size() - 1) {
1149 std::swap(objects[idx], objects[idx + 1]);
1150 new_indices.push_back(idx + 1);
1152 new_indices.push_back(idx);
1158 for (
size_t idx : new_indices) {
1162 room.MarkObjectsDirty();
1169 if (indices.empty() || !
rooms_)
1171 if (current_room_id_ < 0 || current_room_id_ >= 296)
1175 auto& objects = room.GetTileObjects();
1179 std::sort(indices.begin(), indices.end());
1182 bool all_at_start =
true;
1183 for (
size_t idx : indices) {
1185 all_at_start =
false;
1189 if (all_at_start)
return;
1194 std::vector<size_t> new_indices;
1197 for (
size_t idx : indices) {
1200 std::swap(objects[idx], objects[idx - 1]);
1201 new_indices.push_back(idx - 1);
1203 new_indices.push_back(idx);
1209 for (
size_t idx : new_indices) {
1213 room.MarkObjectsDirty();
1224 if (ImGui::IsAnyItemActive())
1228 if (ImGui::IsKeyPressed(ImGuiKey_1)) {
1230 }
else if (ImGui::IsKeyPressed(ImGuiKey_2)) {
1232 }
else if (ImGui::IsKeyPressed(ImGuiKey_3)) {
1239 auto& io = ImGui::GetIO();
1240 if (io.KeyCtrl && io.KeyShift) {
1241 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
1243 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
1246 }
else if (io.KeyCtrl) {
1247 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
1249 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
1281 const ImGuiIO& io = ImGui::GetIO();
1283 ImVec2 mouse_pos = io.MousePos;
1286 int canvas_x =
static_cast<int>(mouse_pos.x - canvas_pos.x);
1287 int canvas_y =
static_cast<int>(mouse_pos.y - canvas_pos.y);
1304 state.snapped_door_position = position;
1307 auto [tile_x, tile_y] =
1312 int door_width_px = dims.width_tiles * 8;
1313 int door_height_px = dims.height_tiles * 8;
1319 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1322 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale,
1323 canvas_pos.y + snap_canvas_y * scale);
1324 ImVec2 preview_end(preview_start.x + door_width_px * scale,
1325 preview_start.y + door_height_px * scale);
1330 ImU32 fill_color = IM_COL32(theme.dungeon_selection_primary.x * 255,
1331 theme.dungeon_selection_primary.y * 255,
1332 theme.dungeon_selection_primary.z * 255,
1334 draw_list->AddRectFilled(preview_start, preview_end, fill_color);
1337 ImVec4 outline_color = ImVec4(theme.dungeon_selection_primary.x,
1338 theme.dungeon_selection_primary.y,
1339 theme.dungeon_selection_primary.z, 0.9f);
1340 draw_list->AddRect(preview_start, preview_end,
1341 ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
1347 snprintf(label,
sizeof(label),
"%s (%s)", type_name, dir_name);
1349 ImVec2 text_pos(preview_start.x, preview_start.y - 16 * scale);
1350 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 200), label);
1357 if (current_room_id_ < 0 || current_room_id_ >= 296)
1386 new_door.
byte1 = byte1;
1387 new_door.
byte2 = byte2;
1391 room.AddDoor(new_door);
1420 const ImGuiIO& io = ImGui::GetIO();
1422 ImVec2 mouse_pos = io.MousePos;
1426 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1427 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1430 int snapped_x = (canvas_x / 16) * 16;
1431 int snapped_y = (canvas_y / 16) * 16;
1434 ImVec2 rect_min(canvas_pos.x + snapped_x * scale,
1435 canvas_pos.y + snapped_y * scale);
1436 ImVec2 rect_max(rect_min.x + 16 * scale, rect_min.y + 16 * scale);
1439 ImU32 fill_color = IM_COL32(50, 200, 50, 100);
1440 ImU32 outline_color = IM_COL32(50, 255, 50, 200);
1443 canvas_->
draw_list()->AddRect(rect_min, rect_max, outline_color, 0.0f, 0, 2.0f);
1447 canvas_->
draw_list()->AddText(rect_min, IM_COL32(255, 255, 255, 255), label.c_str());
1454 if (current_room_id_ < 0 || current_room_id_ >= 296)
1458 if (scale <= 0.0f) scale = 1.0f;
1461 int sprite_x = canvas_x /
static_cast<int>(16 * scale);
1462 int sprite_y = canvas_y /
static_cast<int>(16 * scale);
1465 sprite_x = std::clamp(sprite_x, 0, 31);
1466 sprite_y = std::clamp(sprite_y, 0, 31);
1472 static_cast<uint8_t
>(sprite_x),
1473 static_cast<uint8_t
>(sprite_y),
1478 room.GetSprites().push_back(new_sprite);
1507 const ImGuiIO& io = ImGui::GetIO();
1509 ImVec2 mouse_pos = io.MousePos;
1513 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1514 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1517 int snapped_x = (canvas_x / 8) * 8;
1518 int snapped_y = (canvas_y / 8) * 8;
1521 ImVec2 rect_min(canvas_pos.x + snapped_x * scale,
1522 canvas_pos.y + snapped_y * scale);
1523 ImVec2 rect_max(rect_min.x + 16 * scale, rect_min.y + 16 * scale);
1526 ImU32 fill_color = IM_COL32(200, 200, 50, 100);
1527 ImU32 outline_color = IM_COL32(255, 255, 50, 200);
1530 canvas_->
draw_list()->AddRect(rect_min, rect_max, outline_color, 0.0f, 0, 2.0f);
1534 canvas_->
draw_list()->AddText(rect_min, IM_COL32(255, 255, 255, 255), label.c_str());
1541 if (current_room_id_ < 0 || current_room_id_ >= 296)
1545 if (scale <= 0.0f) scale = 1.0f;
1548 int pixel_x = canvas_x /
static_cast<int>(scale);
1549 int pixel_y = canvas_y /
static_cast<int>(scale);
1554 int encoded_x = pixel_x / 4;
1555 int encoded_y = pixel_y / 16;
1558 encoded_x = std::clamp(encoded_x, 0, 255);
1559 encoded_y = std::clamp(encoded_y, 0, 255);
1565 new_item.
position =
static_cast<uint16_t
>((encoded_y << 8) | encoded_x);
1570 room.GetPotItems().push_back(new_item);
1605 int canvas_x,
int canvas_y)
const {
1606 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
1607 return std::nullopt;
1613 if (scale <= 0.0f) scale = 1.0f;
1614 int room_x =
static_cast<int>(canvas_x / scale);
1615 int room_y =
static_cast<int>(canvas_y / scale);
1618 const auto& doors = room.GetDoors();
1619 for (
size_t i = 0; i < doors.size(); ++i) {
1620 const auto& door = doors[i];
1623 auto [tile_x, tile_y] = door.GetTileCoords();
1629 int door_x = tile_x * 8;
1630 int door_y = tile_y * 8;
1631 int door_w = dims.width_tiles * 8;
1632 int door_h = dims.height_tiles * 8;
1635 if (room_x >= door_x && room_x < door_x + door_w &&
1636 room_y >= door_y && room_y < door_y + door_h) {
1643 const auto& sprites = room.GetSprites();
1644 for (
size_t i = 0; i < sprites.size(); ++i) {
1645 const auto& sprite = sprites[i];
1648 int sprite_x = sprite.x() * 16;
1649 int sprite_y = sprite.y() * 16;
1652 if (room_x >= sprite_x && room_x < sprite_x + 16 &&
1653 room_y >= sprite_y && room_y < sprite_y + 16) {
1659 const auto& pot_items = room.GetPotItems();
1661 for (
size_t i = 0; i < pot_items.size(); ++i) {
1662 const auto& pot_item = pot_items[i];
1665 int item_x = pot_item.GetPixelX();
1666 int item_y = pot_item.GetPixelY();
1669 if (room_x >= item_x && room_x < item_x + 16 &&
1670 room_y >= item_y && room_y < item_y + 16) {
1675 return std::nullopt;
1682 const ImGuiIO& io = ImGui::GetIO();
1684 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1685 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1688 if (entity.has_value()) {
1698 static_cast<float>(canvas_y));
1699 state.entity_drag_current = state.entity_drag_start;
1714 const ImGuiIO& io = ImGui::GetIO();
1717 if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
1722 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1723 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1730 canvas_x, canvas_y, direction);
1736 auto& doors = room.GetDoors();
1749 room.MarkObjectsDirty();
1759 int canvas_x =
static_cast<int>(io.MousePos.x - canvas_pos.x);
1760 int canvas_y =
static_cast<int>(io.MousePos.y - canvas_pos.y);
1763 int tile_x = canvas_x / 16;
1764 int tile_y = canvas_y / 16;
1767 tile_x = std::clamp(tile_x, 0, 31);
1768 tile_y = std::clamp(tile_y, 0, 31);
1772 auto& sprites = room.GetSprites();
1791 state.entity_drag_current = ImVec2(io.MousePos.x - canvas_pos.x,
1792 io.MousePos.y - canvas_pos.y);
1799 if (!
rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
1804 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1810 const char* label =
"";
1814 const auto& doors = room.GetDoors();
1818 auto [tile_x, tile_y] = door.GetTileCoords();
1824 int drag_x =
static_cast<int>(state.entity_drag_current.x);
1825 int drag_y =
static_cast<int>(state.entity_drag_current.y);
1828 bool is_inner =
false;
1838 pos = ImVec2(canvas_pos.x + tile_x * 8 * scale,
1839 canvas_pos.y + tile_y * 8 * scale);
1840 size = ImVec2(dims.width_tiles * 8 * scale, dims.height_tiles * 8 * scale);
1841 color = IM_COL32(255, 165, 0, 180);
1847 const auto& sprites = room.GetSprites();
1852 int pixel_x = sprite.x() * 16;
1853 int pixel_y = sprite.y() * 16;
1858 int tile_x =
static_cast<int>(state.entity_drag_current.x) / 16;
1859 int tile_y =
static_cast<int>(state.entity_drag_current.y) / 16;
1860 tile_x = std::clamp(tile_x, 0, 31);
1861 tile_y = std::clamp(tile_y, 0, 31);
1862 pixel_x = tile_x * 16;
1863 pixel_y = tile_y * 16;
1866 pos = ImVec2(canvas_pos.x + pixel_x * scale,
1867 canvas_pos.y + pixel_y * scale);
1868 size = ImVec2(16 * scale, 16 * scale);
1869 color = IM_COL32(0, 255, 0, 180);
1876 const auto& pot_items = room.GetPotItems();
1881 int pixel_x = pot_item.GetPixelX();
1882 int pixel_y = pot_item.GetPixelY();
1884 pos = ImVec2(canvas_pos.x + pixel_x * scale,
1885 canvas_pos.y + pixel_y * scale);
1886 size = ImVec2(16 * scale, 16 * scale);
1887 color = IM_COL32(255, 255, 0, 180);
1897 static float pulse = 0.0f;
1898 pulse += ImGui::GetIO().DeltaTime * 3.0f;
1899 float alpha = 0.5f + 0.3f * sinf(pulse);
1901 ImU32 fill_color = (color & 0x00FFFFFF) | (
static_cast<ImU32
>(alpha * 100) << 24);
1902 draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), fill_color);
1903 draw_list->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), color, 0.0f, 0, 2.0f);
1906 ImVec2 text_pos(pos.x, pos.y - 14 * scale);
1907 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 220), label);
1922 bool is_inner =
false;
1923 int drag_x =
static_cast<int>(state.entity_drag_current.x);
1924 int drag_y =
static_cast<int>(state.entity_drag_current.y);
1933 drag_x, drag_y, direction);
1935 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1943 for (uint8_t i = 0; i < 6; ++i) {
1944 uint8_t pos = start_pos + i;
1946 float pixel_x = tile_x * 8.0f;
1947 float pixel_y = tile_y * 8.0f;
1949 ImVec2 snap_start(canvas_pos.x + pixel_x * scale,
1950 canvas_pos.y + pixel_y * scale);
1951 ImVec2 snap_end(snap_start.x + dims.width_pixels() * scale,
1952 snap_start.y + dims.height_pixels() * scale);
1954 if (pos == nearest_snap) {
1956 ImVec4 highlight = ImVec4(theme.dungeon_selection_primary.x,
1957 theme.dungeon_selection_primary.y,
1958 theme.dungeon_selection_primary.z, 0.75f);
1959 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(highlight), 0.0f, 0, 2.5f);
1962 ImVec4 ghost = ImVec4(1.0f, 1.0f, 1.0f, 0.25f);
1963 draw_list->AddRect(snap_start, snap_end, ImGui::GetColorU32(ghost), 0.0f, 0, 1.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)