156 if (room_id < 0 || room_id >= 0x128) {
157 ImGui::Text(
"Invalid room ID: %d", room_id);
162 ImGui::Text(
"ROM not loaded");
174 constexpr int kRoomPixelWidth = 512;
175 constexpr int kRoomPixelHeight = 512;
176 constexpr int kDungeonTileSize = 8;
180 frame_opts.
canvas_size = ImVec2(kRoomPixelWidth, kRoomPixelHeight);
191 auto& room = (*rooms_)[room_id];
197 if (room.rom() && room.rom()->is_loaded()) {
198 room.ReloadGraphics(room.blockset());
224 const bool has_selection = !selected.empty();
225 const bool single_selection = selected.size() == 1;
226 const bool group_selection = selected.size() > 1;
227 const bool has_clipboard = interaction.HasClipboardData();
228 const bool placing_object = interaction.IsObjectLoaded();
229 const bool door_mode = interaction.IsDoorPlacementActive();
230 bool has_objects =
false;
231 if (
rooms_ && room_id >= 0 && room_id < 296) {
232 has_objects = !(*rooms_)[room_id].GetTileObjects().empty();
235 if (single_selection &&
rooms_) {
236 auto& room = (*rooms_)[room_id];
237 const auto& objects = room.GetTileObjects();
238 if (selected[0] < objects.size()) {
239 const auto& obj = objects[selected[0]];
240 std::string name = GetObjectName(obj.id_);
242 absl::StrFormat(
"Object 0x%02X: %s", obj.id_, name.c_str())));
246 auto enabled_if = [](
bool enabled) {
254 insert_menu.
label =
"Insert";
265 insert_menu.
subitems.push_back(insert_object_item);
275 insert_menu.
subitems.push_back(insert_sprite_item);
285 insert_menu.
subitems.push_back(insert_item_item);
288 door_mode ?
"Cancel Door Placement" :
"Door (Normal)",
290 interaction.SetDoorPlacementMode(!door_mode,
293 insert_menu.
subitems.push_back(insert_door_item);
300 interaction.HandleCopySelected();
301 interaction.HandleDeleteSelected();
309 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
315 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
322 interaction.HandleCopySelected();
323 interaction.HandlePasteObjects();
331 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
337 [&interaction]() { interaction.HandleDeleteAllObjects(); });
343 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
349 arrange_menu.
label =
"Arrange";
355 [&interaction]() { interaction.SendSelectedToFront(); },
358 arrange_menu.
subitems.push_back(bring_front_item);
362 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
364 arrange_menu.
subitems.push_back(send_back_item);
368 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
370 arrange_menu.
subitems.push_back(bring_forward_item);
374 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
376 arrange_menu.
subitems.push_back(send_backward_item);
382 layer_menu.
label =
"Send to Layer";
388 [&interaction]() { interaction.SendSelectedToLayer(0); },
"1");
390 layer_menu.
subitems.push_back(layer1_item);
394 [&interaction]() { interaction.SendSelectedToLayer(1); },
"2");
396 layer_menu.
subitems.push_back(layer2_item);
400 [&interaction]() { interaction.SendSelectedToLayer(2); },
"3");
402 layer_menu.
subitems.push_back(layer3_item);
411 auto& room = (*rooms_)[room_id];
414 ImGui::SetClipboardText(result.value().c_str());
420 if (single_selection &&
rooms_) {
421 auto& room = (*rooms_)[room_id];
422 const auto& objects = room.GetTileObjects();
423 if (selected[0] < objects.size()) {
424 const auto object = objects[selected[0]];
426 "Edit Graphics...",
ICON_MD_IMAGE, [
this, room_id,
object]() {
441 const auto& selected_entity = interaction.GetSelectedEntity();
442 const bool has_entity_selection = interaction.HasEntitySelection();
444 if (has_entity_selection &&
rooms_) {
445 auto& room = (*rooms_)[room_id];
448 std::string entity_info;
449 switch (selected_entity.type) {
451 const auto& doors = room.GetDoors();
452 if (selected_entity.index < doors.size()) {
453 const auto& door = doors[selected_entity.index];
454 entity_info = absl::StrFormat(
461 const auto& sprites = room.GetSprites();
462 if (selected_entity.index < sprites.size()) {
463 const auto& sprite = sprites[selected_entity.index];
464 entity_info = absl::StrFormat(
471 const auto& items = room.GetPotItems();
472 if (selected_entity.index < items.size()) {
473 const auto& item = items[selected_entity.index];
483 if (!entity_info.empty()) {
489 [
this, &room, selected_entity]() {
490 switch (selected_entity.type) {
491 case EntityType::Door: {
492 auto& doors = room.GetDoors();
493 if (selected_entity.index < doors.size()) {
494 doors.erase(doors.begin() +
495 static_cast<long>(selected_entity.index));
500 auto& sprites = room.GetSprites();
501 if (selected_entity.index < sprites.size()) {
502 sprites.erase(sprites.begin() +
503 static_cast<long>(selected_entity.index));
508 auto& items = room.GetPotItems();
509 if (selected_entity.index < items.size()) {
510 items.erase(items.begin() +
511 static_cast<long>(selected_entity.index));
525 if (rooms_ && rom_->is_loaded()) {
526 auto& room = (*rooms_)[room_id];
530 room_menu.
label =
"Room";
535 absl::StrFormat(
"Room 0x%03X: %s", room_id, room_label.c_str())));
537 if (save_room_callback_) {
540 [
this, room_id]() { save_room_callback_(room_id); },
"Ctrl+Shift+S"));
544 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
546 room_menu.
subitems.push_back(gui::CanvasMenuItem(
548 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); }));
549 room_menu.
subitems.push_back(gui::CanvasMenuItem(
551 [&room]() { room.RenderRoomGraphics(); },
"Ctrl+R"));
554 gui::CanvasMenuItem(
"Open Room List",
ICON_MD_LIST, [
this]() {
555 if (show_room_list_callback_)
556 show_room_list_callback_();
560 if (show_room_matrix_callback_)
561 show_room_matrix_callback_();
565 if (show_entrance_list_callback_)
566 show_entrance_list_callback_();
569 gui::CanvasMenuItem(
"Open Room Graphics",
ICON_MD_IMAGE, [
this]() {
570 if (show_room_graphics_callback_)
571 show_room_graphics_callback_();
575 if (show_dungeon_settings_callback_)
576 show_dungeon_settings_callback_();
580 room_menu.
subitems.push_back(gui::CanvasMenuItem(
582 auto& r = (*rooms_)[room_id];
585 ImGui::SetClipboardText(result.value().c_str());
589 canvas_.AddContextMenuItem(room_menu);
592 gui::CanvasMenuItem view_menu;
593 view_menu.label =
"View";
597 view_menu.subitems.push_back(gui::CanvasMenuItem(
"BG1 Layout", [
this,
599 auto& mgr = GetRoomLayerManager(room_id);
603 view_menu.subitems.push_back(
604 gui::CanvasMenuItem(
"BG1 Objects", [
this, room_id]() {
605 auto& mgr = GetRoomLayerManager(room_id);
610 view_menu.subitems.push_back(gui::CanvasMenuItem(
"BG2 Layout", [
this,
612 auto& mgr = GetRoomLayerManager(room_id);
616 view_menu.subitems.push_back(
617 gui::CanvasMenuItem(
"BG2 Objects", [
this, room_id]() {
618 auto& mgr = GetRoomLayerManager(room_id);
625 view_menu.subitems.push_back(
627 entity_visibility_.show_sprites = !entity_visibility_.show_sprites;
629 view_menu.subitems.push_back(
631 entity_visibility_.show_pot_items =
632 !entity_visibility_.show_pot_items;
636 view_menu.subitems.push_back(gui::CanvasMenuItem(
637 show_grid_ ?
"Hide Grid" :
"Show Grid",
639 [this]() { show_grid_ = !show_grid_; },
"G"));
641 gui::CanvasMenuItem grid_size_menu;
642 grid_size_menu.label =
"Grid Size";
644 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"8x8", [
this]() {
645 custom_grid_size_ = 8;
648 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"16x16", [
this]() {
649 custom_grid_size_ = 16;
652 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"32x32", [
this]() {
653 custom_grid_size_ = 32;
656 view_menu.subitems.push_back(grid_size_menu);
659 view_menu.subitems.push_back(gui::CanvasMenuItem(
660 show_coordinate_overlay_ ?
"Hide Coordinates" :
"Show Coordinates",
662 [this]() { show_coordinate_overlay_ = !show_coordinate_overlay_; }));
664 canvas_.AddContextMenuItem(view_menu);
667 gui::CanvasMenuItem overlays_menu;
668 overlays_menu.label =
"Overlays";
671 gui::CanvasMenuItem minecart_toggle(
672 show_minecart_tracks_ ?
"Hide Minecart Tracks" :
"Show Minecart Tracks",
674 [this]() { show_minecart_tracks_ = !show_minecart_tracks_; });
675 minecart_toggle.enabled_condition = [
this]() {
676 return minecart_track_panel_ !=
nullptr;
678 overlays_menu.subitems.push_back(minecart_toggle);
680 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
681 show_custom_collision_overlay_ ?
"Hide Custom Collision"
682 :
"Show Custom Collision",
684 show_custom_collision_overlay_ = !show_custom_collision_overlay_;
687 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
688 show_track_collision_overlay_ ?
"Hide Track Collision"
689 :
"Show Track Collision",
691 show_track_collision_overlay_ = !show_track_collision_overlay_;
694 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
695 show_camera_quadrant_overlay_ ?
"Hide Camera Quadrants"
696 :
"Show Camera Quadrants",
698 show_camera_quadrant_overlay_ = !show_camera_quadrant_overlay_;
701 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
702 show_minecart_sprite_overlay_ ?
"Hide Minecart Sprites"
703 :
"Show Minecart Sprites",
705 show_minecart_sprite_overlay_ = !show_minecart_sprite_overlay_;
708 if (show_track_collision_overlay_) {
709 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
710 show_track_collision_legend_ ?
"Hide Collision Legend"
711 :
"Show Collision Legend",
713 show_track_collision_legend_ = !show_track_collision_legend_;
717 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
718 show_object_bounds_ ?
"Hide Object Bounds" :
"Show Object Bounds",
720 [this]() { show_object_bounds_ = !show_object_bounds_; }));
722 canvas_.AddContextMenuItem(overlays_menu);
725 gui::CanvasMenuItem debug_menu;
726 debug_menu.label =
"Debug";
729 debug_menu.subitems.push_back(gui::CanvasMenuItem(
731 [
this]() { show_room_debug_info_ = !show_room_debug_info_; }));
733 debug_menu.subitems.push_back(gui::CanvasMenuItem(
735 [
this]() { show_texture_debug_ = !show_texture_debug_; }));
738 gui::CanvasMenuItem object_bounds_menu;
739 object_bounds_menu.label =
"Object Bounds Filter";
742 object_bounds_menu.subitems.push_back(
743 gui::CanvasMenuItem(
"Type 1 (0x00-0xFF)", [
this]() {
744 object_outline_toggles_.show_type1_objects =
745 !object_outline_toggles_.show_type1_objects;
747 object_bounds_menu.subitems.push_back(
748 gui::CanvasMenuItem(
"Type 2 (0x100-0x1FF)", [
this]() {
749 object_outline_toggles_.show_type2_objects =
750 !object_outline_toggles_.show_type2_objects;
752 object_bounds_menu.subitems.push_back(
753 gui::CanvasMenuItem(
"Type 3 (0xF00-0xFFF)", [
this]() {
754 object_outline_toggles_.show_type3_objects =
755 !object_outline_toggles_.show_type3_objects;
758 gui::CanvasMenuItem sep;
760 sep.enabled_condition = []() {
763 object_bounds_menu.subitems.push_back(sep);
765 object_bounds_menu.subitems.push_back(
766 gui::CanvasMenuItem(
"Primary (main pass)", [
this]() {
767 object_outline_toggles_.show_layer0_objects =
768 !object_outline_toggles_.show_layer0_objects;
770 object_bounds_menu.subitems.push_back(
771 gui::CanvasMenuItem(
"BG2 overlay", [
this]() {
772 object_outline_toggles_.show_layer1_objects =
773 !object_outline_toggles_.show_layer1_objects;
775 object_bounds_menu.subitems.push_back(
776 gui::CanvasMenuItem(
"BG1 overlay", [
this]() {
777 object_outline_toggles_.show_layer2_objects =
778 !object_outline_toggles_.show_layer2_objects;
781 debug_menu.subitems.push_back(object_bounds_menu);
783 debug_menu.subitems.push_back(gui::CanvasMenuItem(
785 [
this]() { show_layer_info_ = !show_layer_info_; }));
787 debug_menu.subitems.push_back(gui::CanvasMenuItem(
789 [&room]() { room.ReloadGraphics(room.blockset()); }));
791 debug_menu.subitems.push_back(gui::CanvasMenuItem(
793 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
794 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
795 room.blockset(), room.palette(), room.layout_id());
796 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
797 room.GetTileObjects().size(), room.GetSprites().size());
798 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
799 room.bg1_buffer().bitmap().width(),
800 room.bg1_buffer().bitmap().height(),
801 room.bg2_buffer().bitmap().width(),
802 room.bg2_buffer().bitmap().height());
805 canvas_.AddContextMenuItem(debug_menu);
813 if (pending_scroll_target_.has_value()) {
814 const auto [target_x, target_y] = pending_scroll_target_.value();
815 float scale = canvas_.global_scale();
820 const float pixel_x =
821 static_cast<float>(target_x * kDungeonTileSize) * scale;
822 const float pixel_y =
823 static_cast<float>(target_y * kDungeonTileSize) * scale;
824 const ImVec2 view_size = canvas_rt.canvas_sz;
825 const ImVec2 content_size(
static_cast<float>(kRoomPixelWidth) * scale,
826 static_cast<float>(kRoomPixelHeight) * scale);
828 const ImVec2 desired_scroll((view_size.x * 0.5f) - pixel_x,
829 (view_size.y * 0.5f) - pixel_y);
830 canvas_.set_scrolling(
832 canvas_rt.scrolling = canvas_.scrolling();
834 pending_scroll_target_.reset();
838 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
840 touch_handler_.Update();
844 if (!header_visible_ && show_header_hidden_metadata_hud_) {
847 snprintf(text1,
sizeof(text1),
"[%03X] %s", room_id, label.c_str());
850 bool show_meta =
false;
851 if (rooms_ && room_id >= 0 && room_id <
static_cast<int>(rooms_->size())) {
852 const auto& room = (*rooms_)[room_id];
853 if (!object_interaction_enabled_) {
854 snprintf(text2,
sizeof(text2),
"B:%02X P:%02X L:%02X S:%02X RO",
855 room.blockset(), room.palette(), room.layout_id(),
858 snprintf(text2,
sizeof(text2),
"B:%02X P:%02X L:%02X S:%02X",
859 room.blockset(), room.palette(), room.layout_id(),
863 }
else if (!object_interaction_enabled_) {
864 snprintf(text2,
sizeof(text2),
"Read-only");
868 const float pad = 10.0f;
869 const ImVec2 hud_pos(canvas_.zero_point().x + pad,
870 canvas_.zero_point().y + pad);
871 const ImVec2 hud_size(0, 0);
874 ImGui::TextUnformatted(text1);
876 ImGui::TextDisabled(
"%s", text2);
882 if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) {
883 auto& room = (*rooms_)[room_id];
884 ImGui::SetNextWindowPos(
885 ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10),
886 ImGuiCond_FirstUseEver);
887 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
888 if (ImGui::Begin(
"Room Debug Info", &show_room_debug_info_,
889 ImGuiWindowFlags_NoCollapse)) {
890 ImGui::Text(
"Room: 0x%03X (%d)", room_id, room_id);
892 ImGui::Text(
"Graphics");
893 ImGui::Text(
" Blockset: 0x%02X", room.blockset());
894 ImGui::Text(
" Palette: 0x%02X", room.palette());
895 ImGui::Text(
" Layout: 0x%02X", room.layout_id());
896 ImGui::Text(
" Spriteset: 0x%02X", room.spriteset());
898 ImGui::Text(
"Content");
899 ImGui::Text(
" Objects: %zu", room.GetTileObjects().size());
900 ImGui::Text(
" Sprites: %zu", room.GetSprites().size());
902 ImGui::Text(
"Buffers");
903 auto& bg1 = room.bg1_buffer().bitmap();
904 auto& bg2 = room.bg2_buffer().bitmap();
905 ImGui::Text(
" BG1: %dx%d %s", bg1.width(), bg1.height(),
906 bg1.texture() ?
"(has texture)" :
"(NO TEXTURE)");
907 ImGui::Text(
" BG2: %dx%d %s", bg2.width(), bg2.height(),
908 bg2.texture() ?
"(has texture)" :
"(NO TEXTURE)");
910 ImGui::Text(
"Layers (4-way)");
911 auto& layer_mgr = GetRoomLayerManager(room_id);
916 if (ImGui::Checkbox(
"BG1 Layout", &bg1l))
918 if (ImGui::Checkbox(
"BG1 Objects", &bg1o))
920 if (ImGui::Checkbox(
"BG2 Layout", &bg2l))
922 if (ImGui::Checkbox(
"BG2 Objects", &bg2o))
924 int blend =
static_cast<int>(
926 if (ImGui::SliderInt(
"BG2 Blend", &blend, 0, 4)) {
934 ImGui::Text(
"Layout Override");
935 static bool enable_override =
false;
936 ImGui::Checkbox(
"Enable Override", &enable_override);
937 if (enable_override) {
938 ImGui::SliderInt(
"Layout ID", &layout_override_, 0, 7);
940 layout_override_ = -1;
943 if (show_object_bounds_) {
945 ImGui::Text(
"Object Outline Filters");
946 ImGui::Text(
"By Type:");
947 ImGui::Checkbox(
"Type 1", &object_outline_toggles_.show_type1_objects);
948 ImGui::Checkbox(
"Type 2", &object_outline_toggles_.show_type2_objects);
949 ImGui::Checkbox(
"Type 3", &object_outline_toggles_.show_type3_objects);
950 ImGui::Text(
"By Layer:");
951 ImGui::Checkbox(
"Primary (main pass)",
952 &object_outline_toggles_.show_layer0_objects);
953 ImGui::Checkbox(
"BG2 overlay",
954 &object_outline_toggles_.show_layer1_objects);
955 ImGui::Checkbox(
"BG1 overlay",
956 &object_outline_toggles_.show_layer2_objects);
962 if (show_texture_debug_ && rooms_ && rom_->is_loaded()) {
963 ImGui::SetNextWindowPos(
964 ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10),
965 ImGuiCond_FirstUseEver);
966 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
967 if (ImGui::Begin(
"Texture Debug", &show_texture_debug_,
968 ImGuiWindowFlags_NoCollapse)) {
969 auto& room = (*rooms_)[room_id];
970 auto& bg1 = room.bg1_buffer().bitmap();
971 auto& bg2 = room.bg2_buffer().bitmap();
973 auto ensure_bitmap_texture = [
this](gfx::Bitmap& bitmap) {
974 if (!renderer_ || !bitmap.is_active() || bitmap.width() <= 0) {
977 if (!bitmap.texture()) {
980 }
else if (bitmap.modified()) {
986 ensure_bitmap_texture(bg1);
987 ensure_bitmap_texture(bg2);
992 ImGui::Text(
"BG1 Bitmap");
993 ImGui::Text(
" Size: %dx%d", bg1.width(), bg1.height());
994 ImGui::Text(
" Active: %s", bg1.is_active() ?
"YES" :
"NO");
995 ImGui::Text(
" Texture: 0x%p", bg1.texture());
996 ImGui::Text(
" Modified: %s", bg1.modified() ?
"YES" :
"NO");
999 ImGui::Text(
" Preview:");
1000 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
1004 ImGui::Text(
"BG2 Bitmap");
1005 ImGui::Text(
" Size: %dx%d", bg2.width(), bg2.height());
1006 ImGui::Text(
" Active: %s", bg2.is_active() ?
"YES" :
"NO");
1007 ImGui::Text(
" Texture: 0x%p", bg2.texture());
1008 ImGui::Text(
" Modified: %s", bg2.modified() ?
"YES" :
"NO");
1010 if (bg2.texture()) {
1011 ImGui::Text(
" Preview:");
1012 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
1018 if (show_layer_info_) {
1019 ImGui::SetNextWindowPos(
1020 ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10),
1021 ImGuiCond_FirstUseEver);
1022 ImGui::SetNextWindowSize(ImVec2(220, 0), ImGuiCond_FirstUseEver);
1023 if (ImGui::Begin(
"Layer Info", &show_layer_info_,
1024 ImGuiWindowFlags_NoCollapse)) {
1025 ImGui::Text(
"Canvas Scale: %.2f", canvas_.global_scale());
1026 ImGui::Text(
"Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height());
1027 auto& layer_mgr = GetRoomLayerManager(room_id);
1029 ImGui::Text(
"Layer Visibility (4-way):");
1032 for (
int i = 0; i < 4; ++i) {
1034 bool visible = layer_mgr.IsLayerVisible(layer);
1035 auto blend = layer_mgr.GetLayerBlendMode(layer);
1036 ImGui::Text(
" %s: %s (%s)",
1038 visible ?
"VISIBLE" :
"hidden",
1039 zelda3::RoomLayerManager::GetBlendModeName(blend));
1043 ImGui::Text(
"Draw Order:");
1044 auto draw_order = layer_mgr.GetDrawOrder();
1045 for (
int i = 0; i < 4; ++i) {
1046 ImGui::Text(
" %d: %s", i + 1,
1049 ImGui::Text(
"BG2 On Top: %s", layer_mgr.IsBG2OnTop() ?
"YES" :
"NO");
1054 if (rooms_ && rom_->is_loaded()) {
1055 auto& room = (*rooms_)[room_id];
1058 object_interaction_.SetCurrentRoom(rooms_, room_id);
1060 if (!room.AreObjectsLoaded()) {
1064 if (!room.AreSpritesLoaded()) {
1068 if (!room.ArePotItemsLoaded()) {
1069 room.LoadPotItems();
1072 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1073 bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
1075 static int last_rendered_room = -1;
1076 static bool has_rendered =
false;
1077 if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
1078 (void)LoadAndRenderRoomGraphics(room_id);
1079 last_rendered_room = room_id;
1080 has_rendered =
true;
1086 if (rom_ && rom_->is_loaded()) {
1093 DrawRoomBackgroundLayers(room_id);
1097 if (object_interaction_.IsMaskModeActive()) {
1098 DrawMaskHighlights(canvas_rt, room);
1103 RenderEntityOverlay(canvas_rt, room);
1106 if (object_interaction_enabled_) {
1107 object_interaction_.HandleCanvasMouseInput();
1108 object_interaction_.CheckForObjectSelection();
1110 .DrawSelectionHighlights();
1112 .DrawEntitySelectionHighlights();
1113 object_interaction_.DrawGhostPreview();
1118 const auto selected = object_interaction_.GetSelectedObjectIndices();
1119 if (selected.size() == 1) {
1120 const auto& objects = room.GetTileObjects();
1121 size_t idx = selected.front();
1122 if (idx < objects.size()) {
1123 const auto& obj = objects[idx];
1125 room_id, obj.x_, obj.y_);
1130 if (object_interaction_.HasEntitySelection()) {
1131 const auto sel = object_interaction_.GetSelectedEntity();
1133 const auto& sprites = room.GetSprites();
1134 if (sel.index < sprites.size()) {
1135 const auto& sprite = sprites[sel.index];
1142 HandleTouchLongPressContextMenu(canvas_rt, room);
1147 gui::RoomObjectDragPayload obj_drop;
1151 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1152 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1153 zelda3::RoomObject new_obj(
static_cast<int16_t
>(obj_drop.object_id),
1154 static_cast<uint8_t
>(tile_x),
1155 static_cast<uint8_t
>(tile_y), 0, 0);
1156 const size_t before = room.GetTileObjects().size();
1157 object_interaction_.entity_coordinator().tile_handler().PlaceObjectAt(
1158 room_id, new_obj, tile_x, tile_y);
1159 if (room.GetTileObjects().size() > before) {
1160 object_interaction_.SetSelectedObjects({before});
1166 gui::SpriteDragPayload sprite_drop;
1169 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1171 int sprite_x = (tile_x * 8) / 16;
1172 int sprite_y = (tile_y * 8) / 16;
1173 if (sprite_x >= 0 && sprite_x < 32 && sprite_y >= 0 && sprite_y < 32) {
1175 zelda3::Sprite new_sprite(
static_cast<uint8_t
>(sprite_drop.sprite_id),
1176 static_cast<uint8_t
>(sprite_x),
1177 static_cast<uint8_t
>(sprite_y), 0, 0);
1178 if (
auto* ctx = object_interaction_.entity_coordinator()
1183 room.GetSprites().push_back(new_sprite);
1184 if (
auto* ctx = object_interaction_.entity_coordinator()
1194 if (rooms_ && rom_->is_loaded()) {
1195 auto& room = (*rooms_)[room_id];
1201 if (show_object_bounds_) {
1202 DrawObjectPositionOutlines(canvas_rt, room);
1206 if (show_track_collision_overlay_) {
1208 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1209 canvas_.global_scale(), GetCollisionOverlayCache(room.id()),
1210 track_collision_config_, track_direction_map_enabled_,
1211 track_tile_order_, switch_tile_order_, show_track_collision_legend_);
1214 if (show_custom_collision_overlay_) {
1216 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1217 canvas_.global_scale(), room);
1220 if (show_water_fill_overlay_) {
1222 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1223 canvas_.global_scale(), room);
1226 if (show_camera_quadrant_overlay_) {
1228 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1229 canvas_.global_scale(), room);
1232 if (show_minecart_sprite_overlay_) {
1234 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1235 canvas_.global_scale(), room, minecart_sprite_ids_,
1236 track_collision_config_);
1239 if (show_track_gap_overlay_) {
1241 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1242 canvas_.global_scale(), room, GetCollisionOverlayCache(room.id()));
1245 if (show_track_route_overlay_) {
1247 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1248 canvas_.global_scale(), GetCollisionOverlayCache(room.id()));
1253 if (show_custom_objects_overlay_) {
1254 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1255 const ImVec2 canvas_pos = canvas_.zero_point();
1256 const float scale = canvas_.global_scale();
1261 const ImU32 fill_color =
1262 ImGui::GetColorU32(ImVec4(info.x, info.y, info.z, 0.25f));
1263 const ImU32 border_color =
1264 ImGui::GetColorU32(ImVec4(info.x, info.y, info.z, 0.8f));
1267 const ImU32 text_bg_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f));
1272 auto is_custom = [](
int id) {
1273 return id == 0x31 ||
id == 0x32;
1276 for (
const auto& obj : room.GetTileObjects()) {
1277 if (!is_custom(
static_cast<int>(obj.id_))) {
1282 const float px =
static_cast<float>(obj.x()) * 8.0f * scale;
1283 const float py =
static_cast<float>(obj.y()) * 8.0f * scale;
1286 const float box_w = 16.0f * scale;
1287 const float box_h = 16.0f * scale;
1288 const ImVec2 p0(canvas_pos.x + px, canvas_pos.y + py);
1289 const ImVec2 p1(p0.x + box_w, p0.y + box_h);
1291 draw_list->AddRectFilled(p0, p1, fill_color, 2.0f);
1292 draw_list->AddRect(p0, p1, border_color, 2.0f, 0, 1.5f);
1296 std::snprintf(label,
sizeof(label),
"0x%02X s%d",
1297 static_cast<int>(obj.id_),
1298 static_cast<int>(obj.size_ & 0x1F));
1299 const ImVec2 text_sz = ImGui::CalcTextSize(label);
1300 const ImVec2 tp(p0.x + 1.0f, p0.y - text_sz.y - 1.0f);
1301 draw_list->AddRectFilled(
1302 tp, ImVec2(tp.x + text_sz.x + 2.0f, tp.y + text_sz.y),
1303 text_bg_color, 2.0f);
1304 draw_list->AddText(tp, border_color, label);
1308 if (minecart_track_panel_) {
1309 const bool show_tracks = show_minecart_tracks_ ||
1310 minecart_track_panel_->IsPickingCoordinates();
1311 const auto& tracks = minecart_track_panel_->GetTracks();
1312 if (show_tracks && !tracks.empty()) {
1313 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1314 ImVec2 canvas_pos = canvas_.zero_point();
1315 float scale = canvas_.global_scale();
1317 const int active_track =
1318 minecart_track_panel_->IsPickingCoordinates()
1319 ? minecart_track_panel_->GetPickingTrackIndex()
1322 for (
const auto& track : tracks) {
1324 static_cast<uint16_t
>(track.start_x),
1325 static_cast<uint16_t
>(track.start_y));
1326 if (local.room_id != room_id) {
1330 ImVec4 marker_color = theme.selection_primary;
1331 if (track.id == active_track) {
1332 marker_color = theme.status_warning;
1335 const float px =
static_cast<float>(local.local_pixel_x) * scale;
1336 const float py =
static_cast<float>(local.local_pixel_y) * scale;
1337 ImVec2 center(canvas_pos.x + px, canvas_pos.y + py);
1338 const float radius = 6.0f * scale;
1340 draw_list->AddCircleFilled(center, radius,
1341 ImGui::GetColorU32(marker_color));
1342 draw_list->AddCircle(center, radius + 2.0f,
1343 ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f)), 0,
1346 std::string label = absl::StrFormat(
"T%d", track.id);
1348 ImVec2(center.x + 8.0f * scale, center.y - 6.0f * scale),
1349 ImGui::GetColorU32(theme.text_primary), label.c_str());
1356 if (show_coordinate_overlay_ && canvas_.IsMouseHovering()) {
1358 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1361 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1364 int canvas_x = tile_x * 8;
1365 int canvas_y = tile_y * 8;
1368 auto [camera_x, camera_y] =
1376 ImVec2 mouse_pos = ImGui::GetMousePos();
1377 ImVec2 overlay_pos = ImVec2(mouse_pos.x + 15, mouse_pos.y + 15);
1380 ImGui::Text(
"Tile: (%d, %d)", tile_x, tile_y);
1381 ImGui::Text(
"Pixel: (%d, %d)", canvas_x, canvas_y);
1382 ImGui::Text(
"Camera: ($%04X, $%04X)", camera_x, camera_y);
1383 ImGui::Text(
"Sprite: (%d, %d)", sprite_x, sprite_y);