142 if (room_id < 0 || room_id >= 0x128) {
143 ImGui::Text(
"Invalid room ID: %d", room_id);
148 ImGui::Text(
"ROM not loaded");
160 constexpr int kRoomPixelWidth = 512;
161 constexpr int kRoomPixelHeight = 512;
162 constexpr int kDungeonTileSize = 8;
166 frame_opts.
canvas_size = ImVec2(kRoomPixelWidth, kRoomPixelHeight);
177 auto& room = (*rooms_)[room_id];
184 if (room.rom() && room.rom()->is_loaded()) {
188 room.LoadRoomGraphics(room.blockset());
189 room.RenderRoomGraphics();
215 const bool has_selection = !selected.empty();
216 const bool single_selection = selected.size() == 1;
217 const bool group_selection = selected.size() > 1;
218 const bool has_clipboard = interaction.HasClipboardData();
219 const bool placing_object = interaction.IsObjectLoaded();
220 const bool door_mode = interaction.IsDoorPlacementActive();
221 bool has_objects =
false;
222 if (
rooms_ && room_id >= 0 && room_id < 296) {
223 has_objects = !(*rooms_)[room_id].GetTileObjects().empty();
226 if (single_selection &&
rooms_) {
227 auto& room = (*rooms_)[room_id];
228 const auto& objects = room.GetTileObjects();
229 if (selected[0] < objects.size()) {
230 const auto& obj = objects[selected[0]];
231 std::string name = GetObjectName(obj.id_);
233 absl::StrFormat(
"Object 0x%02X: %s", obj.id_, name.c_str())));
237 auto enabled_if = [](
bool enabled) {
245 insert_menu.
label =
"Insert";
256 insert_menu.
subitems.push_back(insert_object_item);
266 insert_menu.
subitems.push_back(insert_sprite_item);
276 insert_menu.
subitems.push_back(insert_item_item);
279 door_mode ?
"Cancel Door Placement" :
"Door (Normal)",
281 interaction.SetDoorPlacementMode(!door_mode,
284 insert_menu.
subitems.push_back(insert_door_item);
291 interaction.HandleCopySelected();
292 interaction.HandleDeleteSelected();
300 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
306 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
313 interaction.HandleCopySelected();
314 interaction.HandlePasteObjects();
322 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
328 [&interaction]() { interaction.HandleDeleteAllObjects(); });
334 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
340 arrange_menu.
label =
"Arrange";
346 [&interaction]() { interaction.SendSelectedToFront(); },
349 arrange_menu.
subitems.push_back(bring_front_item);
353 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
355 arrange_menu.
subitems.push_back(send_back_item);
359 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
361 arrange_menu.
subitems.push_back(bring_forward_item);
365 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
367 arrange_menu.
subitems.push_back(send_backward_item);
373 layer_menu.
label =
"Send to Layer";
379 [&interaction]() { interaction.SendSelectedToLayer(0); },
"1");
381 layer_menu.
subitems.push_back(layer1_item);
385 [&interaction]() { interaction.SendSelectedToLayer(1); },
"2");
387 layer_menu.
subitems.push_back(layer2_item);
391 [&interaction]() { interaction.SendSelectedToLayer(2); },
"3");
393 layer_menu.
subitems.push_back(layer3_item);
402 auto& room = (*rooms_)[room_id];
405 ImGui::SetClipboardText(result.value().c_str());
411 if (single_selection &&
rooms_) {
412 auto& room = (*rooms_)[room_id];
413 const auto& objects = room.GetTileObjects();
414 if (selected[0] < objects.size()) {
415 const auto object = objects[selected[0]];
417 "Edit Graphics...",
ICON_MD_IMAGE, [
this, room_id,
object]() {
432 const auto& selected_entity = interaction.GetSelectedEntity();
433 const bool has_entity_selection = interaction.HasEntitySelection();
435 if (has_entity_selection &&
rooms_) {
436 auto& room = (*rooms_)[room_id];
439 std::string entity_info;
440 switch (selected_entity.type) {
442 const auto& doors = room.GetDoors();
443 if (selected_entity.index < doors.size()) {
444 const auto& door = doors[selected_entity.index];
445 entity_info = absl::StrFormat(
452 const auto& sprites = room.GetSprites();
453 if (selected_entity.index < sprites.size()) {
454 const auto& sprite = sprites[selected_entity.index];
455 entity_info = absl::StrFormat(
462 const auto& items = room.GetPotItems();
463 if (selected_entity.index < items.size()) {
464 const auto& item = items[selected_entity.index];
474 if (!entity_info.empty()) {
480 [
this, &room, selected_entity]() {
481 switch (selected_entity.type) {
482 case EntityType::Door: {
483 auto& doors = room.GetDoors();
484 if (selected_entity.index < doors.size()) {
485 doors.erase(doors.begin() +
486 static_cast<long>(selected_entity.index));
491 auto& sprites = room.GetSprites();
492 if (selected_entity.index < sprites.size()) {
493 sprites.erase(sprites.begin() +
494 static_cast<long>(selected_entity.index));
499 auto& items = room.GetPotItems();
500 if (selected_entity.index < items.size()) {
501 items.erase(items.begin() +
502 static_cast<long>(selected_entity.index));
516 if (rooms_ && rom_->is_loaded()) {
517 auto& room = (*rooms_)[room_id];
521 room_menu.
label =
"Room";
526 absl::StrFormat(
"Room 0x%03X: %s", room_id, room_label.c_str())));
528 if (save_room_callback_) {
531 [
this, room_id]() { save_room_callback_(room_id); },
"Ctrl+Shift+S"));
535 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
537 room_menu.
subitems.push_back(gui::CanvasMenuItem(
539 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); }));
540 room_menu.
subitems.push_back(gui::CanvasMenuItem(
542 [&room]() { room.RenderRoomGraphics(); },
"Ctrl+R"));
545 gui::CanvasMenuItem(
"Open Room List",
ICON_MD_LIST, [
this]() {
546 if (show_room_list_callback_)
547 show_room_list_callback_();
551 if (show_room_matrix_callback_)
552 show_room_matrix_callback_();
556 if (show_entrance_list_callback_)
557 show_entrance_list_callback_();
560 gui::CanvasMenuItem(
"Open Room Graphics",
ICON_MD_IMAGE, [
this]() {
561 if (show_room_graphics_callback_)
562 show_room_graphics_callback_();
566 if (show_dungeon_settings_callback_)
567 show_dungeon_settings_callback_();
571 room_menu.
subitems.push_back(gui::CanvasMenuItem(
573 auto& r = (*rooms_)[room_id];
576 ImGui::SetClipboardText(result.value().c_str());
580 canvas_.AddContextMenuItem(room_menu);
583 gui::CanvasMenuItem view_menu;
584 view_menu.label =
"View";
588 view_menu.subitems.push_back(gui::CanvasMenuItem(
"BG1 Layout", [
this,
590 auto& mgr = GetRoomLayerManager(room_id);
594 view_menu.subitems.push_back(
595 gui::CanvasMenuItem(
"BG1 Objects", [
this, room_id]() {
596 auto& mgr = GetRoomLayerManager(room_id);
601 view_menu.subitems.push_back(gui::CanvasMenuItem(
"BG2 Layout", [
this,
603 auto& mgr = GetRoomLayerManager(room_id);
607 view_menu.subitems.push_back(
608 gui::CanvasMenuItem(
"BG2 Objects", [
this, room_id]() {
609 auto& mgr = GetRoomLayerManager(room_id);
616 view_menu.subitems.push_back(
618 entity_visibility_.show_sprites = !entity_visibility_.show_sprites;
620 view_menu.subitems.push_back(
622 entity_visibility_.show_pot_items =
623 !entity_visibility_.show_pot_items;
627 view_menu.subitems.push_back(gui::CanvasMenuItem(
628 show_grid_ ?
"Hide Grid" :
"Show Grid",
630 [this]() { show_grid_ = !show_grid_; },
"G"));
632 gui::CanvasMenuItem grid_size_menu;
633 grid_size_menu.label =
"Grid Size";
635 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"8x8", [
this]() {
636 custom_grid_size_ = 8;
639 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"16x16", [
this]() {
640 custom_grid_size_ = 16;
643 grid_size_menu.subitems.push_back(gui::CanvasMenuItem(
"32x32", [
this]() {
644 custom_grid_size_ = 32;
647 view_menu.subitems.push_back(grid_size_menu);
650 view_menu.subitems.push_back(gui::CanvasMenuItem(
651 show_coordinate_overlay_ ?
"Hide Coordinates" :
"Show Coordinates",
653 [this]() { show_coordinate_overlay_ = !show_coordinate_overlay_; }));
655 canvas_.AddContextMenuItem(view_menu);
658 gui::CanvasMenuItem overlays_menu;
659 overlays_menu.label =
"Overlays";
662 gui::CanvasMenuItem minecart_toggle(
663 show_minecart_tracks_ ?
"Hide Minecart Tracks" :
"Show Minecart Tracks",
665 [this]() { show_minecart_tracks_ = !show_minecart_tracks_; });
666 minecart_toggle.enabled_condition = [
this]() {
667 return minecart_track_panel_ !=
nullptr;
669 overlays_menu.subitems.push_back(minecart_toggle);
671 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
672 show_custom_collision_overlay_ ?
"Hide Custom Collision"
673 :
"Show Custom Collision",
675 show_custom_collision_overlay_ = !show_custom_collision_overlay_;
678 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
679 show_track_collision_overlay_ ?
"Hide Track Collision"
680 :
"Show Track Collision",
682 show_track_collision_overlay_ = !show_track_collision_overlay_;
685 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
686 show_camera_quadrant_overlay_ ?
"Hide Camera Quadrants"
687 :
"Show Camera Quadrants",
689 show_camera_quadrant_overlay_ = !show_camera_quadrant_overlay_;
692 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
693 show_minecart_sprite_overlay_ ?
"Hide Minecart Sprites"
694 :
"Show Minecart Sprites",
696 show_minecart_sprite_overlay_ = !show_minecart_sprite_overlay_;
699 if (show_track_collision_overlay_) {
700 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
701 show_track_collision_legend_ ?
"Hide Collision Legend"
702 :
"Show Collision Legend",
704 show_track_collision_legend_ = !show_track_collision_legend_;
708 overlays_menu.subitems.push_back(gui::CanvasMenuItem(
709 show_object_bounds_ ?
"Hide Object Bounds" :
"Show Object Bounds",
711 [this]() { show_object_bounds_ = !show_object_bounds_; }));
713 canvas_.AddContextMenuItem(overlays_menu);
716 gui::CanvasMenuItem debug_menu;
717 debug_menu.label =
"Debug";
720 debug_menu.subitems.push_back(gui::CanvasMenuItem(
722 [
this]() { show_room_debug_info_ = !show_room_debug_info_; }));
724 debug_menu.subitems.push_back(gui::CanvasMenuItem(
726 [
this]() { show_texture_debug_ = !show_texture_debug_; }));
729 gui::CanvasMenuItem object_bounds_menu;
730 object_bounds_menu.label =
"Object Bounds Filter";
733 object_bounds_menu.subitems.push_back(
734 gui::CanvasMenuItem(
"Type 1 (0x00-0xFF)", [
this]() {
735 object_outline_toggles_.show_type1_objects =
736 !object_outline_toggles_.show_type1_objects;
738 object_bounds_menu.subitems.push_back(
739 gui::CanvasMenuItem(
"Type 2 (0x100-0x1FF)", [
this]() {
740 object_outline_toggles_.show_type2_objects =
741 !object_outline_toggles_.show_type2_objects;
743 object_bounds_menu.subitems.push_back(
744 gui::CanvasMenuItem(
"Type 3 (0xF00-0xFFF)", [
this]() {
745 object_outline_toggles_.show_type3_objects =
746 !object_outline_toggles_.show_type3_objects;
749 gui::CanvasMenuItem sep;
751 sep.enabled_condition = []() {
754 object_bounds_menu.subitems.push_back(sep);
756 object_bounds_menu.subitems.push_back(
757 gui::CanvasMenuItem(
"Layer 0 (BG1)", [
this]() {
758 object_outline_toggles_.show_layer0_objects =
759 !object_outline_toggles_.show_layer0_objects;
761 object_bounds_menu.subitems.push_back(
762 gui::CanvasMenuItem(
"Layer 1 (BG2)", [
this]() {
763 object_outline_toggles_.show_layer1_objects =
764 !object_outline_toggles_.show_layer1_objects;
766 object_bounds_menu.subitems.push_back(
767 gui::CanvasMenuItem(
"Layer 2 (BG3)", [
this]() {
768 object_outline_toggles_.show_layer2_objects =
769 !object_outline_toggles_.show_layer2_objects;
772 debug_menu.subitems.push_back(object_bounds_menu);
774 debug_menu.subitems.push_back(gui::CanvasMenuItem(
776 [
this]() { show_layer_info_ = !show_layer_info_; }));
778 debug_menu.subitems.push_back(
781 room.LoadRoomGraphics(room.blockset());
782 room.RenderRoomGraphics();
785 debug_menu.subitems.push_back(gui::CanvasMenuItem(
787 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
788 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
789 room.blockset(), room.palette(), room.layout_id());
790 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
791 room.GetTileObjects().size(), room.GetSprites().size());
792 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
793 room.bg1_buffer().bitmap().width(),
794 room.bg1_buffer().bitmap().height(),
795 room.bg2_buffer().bitmap().width(),
796 room.bg2_buffer().bitmap().height());
799 canvas_.AddContextMenuItem(debug_menu);
807 if (pending_scroll_target_.has_value()) {
808 const auto [target_x, target_y] = pending_scroll_target_.value();
809 float scale = canvas_.global_scale();
814 const float pixel_x =
815 static_cast<float>(target_x * kDungeonTileSize) * scale;
816 const float pixel_y =
817 static_cast<float>(target_y * kDungeonTileSize) * scale;
818 const ImVec2 view_size = canvas_rt.canvas_sz;
819 const ImVec2 content_size(
static_cast<float>(kRoomPixelWidth) * scale,
820 static_cast<float>(kRoomPixelHeight) * scale);
822 const ImVec2 desired_scroll((view_size.x * 0.5f) - pixel_x,
823 (view_size.y * 0.5f) - pixel_y);
824 canvas_.set_scrolling(
826 canvas_rt.scrolling = canvas_.scrolling();
828 pending_scroll_target_.reset();
832 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
834 touch_handler_.Update();
838 if (!header_visible_) {
841 snprintf(text1,
sizeof(text1),
"[%03X] %s", room_id, label.c_str());
844 bool show_meta =
false;
845 if (rooms_ && room_id >= 0 && room_id <
static_cast<int>(rooms_->size())) {
846 const auto& room = (*rooms_)[room_id];
847 if (!object_interaction_enabled_) {
848 snprintf(text2,
sizeof(text2),
"B:%02X P:%02X L:%02X S:%02X RO",
849 room.blockset(), room.palette(), room.layout_id(),
852 snprintf(text2,
sizeof(text2),
"B:%02X P:%02X L:%02X S:%02X",
853 room.blockset(), room.palette(), room.layout_id(),
857 }
else if (!object_interaction_enabled_) {
858 snprintf(text2,
sizeof(text2),
"Read-only");
862 const float pad = 10.0f;
863 const ImVec2 hud_pos(canvas_.zero_point().x + pad,
864 canvas_.zero_point().y + pad);
865 const ImVec2 hud_size(0, 0);
868 ImGui::TextUnformatted(text1);
870 ImGui::TextDisabled(
"%s", text2);
876 if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) {
877 auto& room = (*rooms_)[room_id];
878 ImGui::SetNextWindowPos(
879 ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10),
880 ImGuiCond_FirstUseEver);
881 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
882 if (ImGui::Begin(
"Room Debug Info", &show_room_debug_info_,
883 ImGuiWindowFlags_NoCollapse)) {
884 ImGui::Text(
"Room: 0x%03X (%d)", room_id, room_id);
886 ImGui::Text(
"Graphics");
887 ImGui::Text(
" Blockset: 0x%02X", room.blockset());
888 ImGui::Text(
" Palette: 0x%02X", room.palette());
889 ImGui::Text(
" Layout: 0x%02X", room.layout_id());
890 ImGui::Text(
" Spriteset: 0x%02X", room.spriteset());
892 ImGui::Text(
"Content");
893 ImGui::Text(
" Objects: %zu", room.GetTileObjects().size());
894 ImGui::Text(
" Sprites: %zu", room.GetSprites().size());
896 ImGui::Text(
"Buffers");
897 auto& bg1 = room.bg1_buffer().bitmap();
898 auto& bg2 = room.bg2_buffer().bitmap();
899 ImGui::Text(
" BG1: %dx%d %s", bg1.width(), bg1.height(),
900 bg1.texture() ?
"(has texture)" :
"(NO TEXTURE)");
901 ImGui::Text(
" BG2: %dx%d %s", bg2.width(), bg2.height(),
902 bg2.texture() ?
"(has texture)" :
"(NO TEXTURE)");
904 ImGui::Text(
"Layers (4-way)");
905 auto& layer_mgr = GetRoomLayerManager(room_id);
910 if (ImGui::Checkbox(
"BG1 Layout", &bg1l))
912 if (ImGui::Checkbox(
"BG1 Objects", &bg1o))
914 if (ImGui::Checkbox(
"BG2 Layout", &bg2l))
916 if (ImGui::Checkbox(
"BG2 Objects", &bg2o))
918 int blend =
static_cast<int>(
920 if (ImGui::SliderInt(
"BG2 Blend", &blend, 0, 4)) {
928 ImGui::Text(
"Layout Override");
929 static bool enable_override =
false;
930 ImGui::Checkbox(
"Enable Override", &enable_override);
931 if (enable_override) {
932 ImGui::SliderInt(
"Layout ID", &layout_override_, 0, 7);
934 layout_override_ = -1;
937 if (show_object_bounds_) {
939 ImGui::Text(
"Object Outline Filters");
940 ImGui::Text(
"By Type:");
941 ImGui::Checkbox(
"Type 1", &object_outline_toggles_.show_type1_objects);
942 ImGui::Checkbox(
"Type 2", &object_outline_toggles_.show_type2_objects);
943 ImGui::Checkbox(
"Type 3", &object_outline_toggles_.show_type3_objects);
944 ImGui::Text(
"By Layer:");
945 ImGui::Checkbox(
"Layer 0",
946 &object_outline_toggles_.show_layer0_objects);
947 ImGui::Checkbox(
"Layer 1",
948 &object_outline_toggles_.show_layer1_objects);
949 ImGui::Checkbox(
"Layer 2",
950 &object_outline_toggles_.show_layer2_objects);
956 if (show_texture_debug_ && rooms_ && rom_->is_loaded()) {
957 ImGui::SetNextWindowPos(
958 ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10),
959 ImGuiCond_FirstUseEver);
960 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
961 if (ImGui::Begin(
"Texture Debug", &show_texture_debug_,
962 ImGuiWindowFlags_NoCollapse)) {
963 auto& room = (*rooms_)[room_id];
964 auto& bg1 = room.bg1_buffer().bitmap();
965 auto& bg2 = room.bg2_buffer().bitmap();
967 ImGui::Text(
"BG1 Bitmap");
968 ImGui::Text(
" Size: %dx%d", bg1.width(), bg1.height());
969 ImGui::Text(
" Active: %s", bg1.is_active() ?
"YES" :
"NO");
970 ImGui::Text(
" Texture: 0x%p", bg1.texture());
971 ImGui::Text(
" Modified: %s", bg1.modified() ?
"YES" :
"NO");
974 ImGui::Text(
" Preview:");
975 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
979 ImGui::Text(
"BG2 Bitmap");
980 ImGui::Text(
" Size: %dx%d", bg2.width(), bg2.height());
981 ImGui::Text(
" Active: %s", bg2.is_active() ?
"YES" :
"NO");
982 ImGui::Text(
" Texture: 0x%p", bg2.texture());
983 ImGui::Text(
" Modified: %s", bg2.modified() ?
"YES" :
"NO");
986 ImGui::Text(
" Preview:");
987 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
993 if (show_layer_info_) {
994 ImGui::SetNextWindowPos(
995 ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10),
996 ImGuiCond_FirstUseEver);
997 ImGui::SetNextWindowSize(ImVec2(220, 0), ImGuiCond_FirstUseEver);
998 if (ImGui::Begin(
"Layer Info", &show_layer_info_,
999 ImGuiWindowFlags_NoCollapse)) {
1000 ImGui::Text(
"Canvas Scale: %.2f", canvas_.global_scale());
1001 ImGui::Text(
"Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height());
1002 auto& layer_mgr = GetRoomLayerManager(room_id);
1004 ImGui::Text(
"Layer Visibility (4-way):");
1007 for (
int i = 0; i < 4; ++i) {
1009 bool visible = layer_mgr.IsLayerVisible(layer);
1010 auto blend = layer_mgr.GetLayerBlendMode(layer);
1011 ImGui::Text(
" %s: %s (%s)",
1013 visible ?
"VISIBLE" :
"hidden",
1014 zelda3::RoomLayerManager::GetBlendModeName(blend));
1018 ImGui::Text(
"Draw Order:");
1019 auto draw_order = layer_mgr.GetDrawOrder();
1020 for (
int i = 0; i < 4; ++i) {
1021 ImGui::Text(
" %d: %s", i + 1,
1024 ImGui::Text(
"BG2 On Top: %s", layer_mgr.IsBG2OnTop() ?
"YES" :
"NO");
1029 if (rooms_ && rom_->is_loaded()) {
1030 auto& room = (*rooms_)[room_id];
1033 object_interaction_.SetCurrentRoom(rooms_, room_id);
1036 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1037 bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
1040 static int last_rendered_room = -1;
1041 static bool has_rendered =
false;
1042 if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
1044 "[DungeonCanvasViewer] Loading and rendering graphics for room %d\n",
1046 (void)LoadAndRenderRoomGraphics(room_id);
1047 last_rendered_room = room_id;
1048 has_rendered =
true;
1052 if (room.GetTileObjects().empty()) {
1057 if (room.GetSprites().empty()) {
1062 if (room.GetPotItems().empty()) {
1063 room.LoadPotItems();
1069 if (rom_ && rom_->is_loaded()) {
1076 DrawRoomBackgroundLayers(room_id);
1080 if (object_interaction_.IsMaskModeActive()) {
1081 DrawMaskHighlights(canvas_rt, room);
1086 RenderEntityOverlay(canvas_rt, room);
1089 if (object_interaction_enabled_) {
1090 object_interaction_.HandleCanvasMouseInput();
1091 object_interaction_.CheckForObjectSelection();
1093 .DrawSelectionHighlights();
1095 .DrawEntitySelectionHighlights();
1096 object_interaction_.DrawGhostPreview();
1101 const auto selected = object_interaction_.GetSelectedObjectIndices();
1102 if (selected.size() == 1) {
1103 const auto& objects = room.GetTileObjects();
1104 size_t idx = selected.front();
1105 if (idx < objects.size()) {
1106 const auto& obj = objects[idx];
1108 room_id, obj.x_, obj.y_);
1113 if (object_interaction_.HasEntitySelection()) {
1114 const auto sel = object_interaction_.GetSelectedEntity();
1116 const auto& sprites = room.GetSprites();
1117 if (sel.index < sprites.size()) {
1118 const auto& sprite = sprites[sel.index];
1125 HandleTouchLongPressContextMenu(canvas_rt, room);
1130 gui::RoomObjectDragPayload obj_drop;
1134 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1135 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1136 zelda3::RoomObject new_obj(
static_cast<int16_t
>(obj_drop.object_id),
1137 static_cast<uint8_t
>(tile_x),
1138 static_cast<uint8_t
>(tile_y), 0, 0);
1139 const size_t before = room.GetTileObjects().size();
1140 object_interaction_.entity_coordinator().tile_handler().PlaceObjectAt(
1141 room_id, new_obj, tile_x, tile_y);
1142 if (room.GetTileObjects().size() > before) {
1143 object_interaction_.SetSelectedObjects({before});
1149 gui::SpriteDragPayload sprite_drop;
1152 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1154 int sprite_x = (tile_x * 8) / 16;
1155 int sprite_y = (tile_y * 8) / 16;
1156 if (sprite_x >= 0 && sprite_x < 32 && sprite_y >= 0 && sprite_y < 32) {
1158 zelda3::Sprite new_sprite(
static_cast<uint8_t
>(sprite_drop.sprite_id),
1159 static_cast<uint8_t
>(sprite_x),
1160 static_cast<uint8_t
>(sprite_y), 0, 0);
1161 if (
auto* ctx = object_interaction_.entity_coordinator()
1166 room.GetSprites().push_back(new_sprite);
1167 if (
auto* ctx = object_interaction_.entity_coordinator()
1177 if (rooms_ && rom_->is_loaded()) {
1178 auto& room = (*rooms_)[room_id];
1184 if (show_object_bounds_) {
1185 DrawObjectPositionOutlines(canvas_rt, room);
1189 if (show_track_collision_overlay_) {
1191 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1192 canvas_.global_scale(), GetCollisionOverlayCache(room.id()),
1193 track_collision_config_, track_direction_map_enabled_,
1194 track_tile_order_, switch_tile_order_, show_track_collision_legend_);
1197 if (show_custom_collision_overlay_) {
1199 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1200 canvas_.global_scale(), room);
1203 if (show_water_fill_overlay_) {
1205 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1206 canvas_.global_scale(), room);
1209 if (show_camera_quadrant_overlay_) {
1211 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1212 canvas_.global_scale(), room);
1215 if (show_minecart_sprite_overlay_) {
1217 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1218 canvas_.global_scale(), room, minecart_sprite_ids_,
1219 track_collision_config_);
1222 if (show_track_gap_overlay_) {
1224 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1225 canvas_.global_scale(), room, GetCollisionOverlayCache(room.id()));
1228 if (show_track_route_overlay_) {
1230 ImGui::GetWindowDrawList(), canvas_.zero_point(),
1231 canvas_.global_scale(), GetCollisionOverlayCache(room.id()));
1236 if (show_custom_objects_overlay_) {
1237 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1238 const ImVec2 canvas_pos = canvas_.zero_point();
1239 const float scale = canvas_.global_scale();
1240 const ImU32 fill_color =
1241 ImGui::GetColorU32(ImVec4(0.2f, 0.8f, 1.0f, 0.25f));
1242 const ImU32 border_color =
1243 ImGui::GetColorU32(ImVec4(0.2f, 0.8f, 1.0f, 0.8f));
1244 const ImU32 text_bg_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f));
1249 auto is_custom = [](
int id) {
1250 return id == 0x31 ||
id == 0x32;
1253 for (
const auto& obj : room.GetTileObjects()) {
1254 if (!is_custom(
static_cast<int>(obj.id_))) {
1259 const float px =
static_cast<float>(obj.x()) * 8.0f * scale;
1260 const float py =
static_cast<float>(obj.y()) * 8.0f * scale;
1263 const float box_w = 16.0f * scale;
1264 const float box_h = 16.0f * scale;
1265 const ImVec2 p0(canvas_pos.x + px, canvas_pos.y + py);
1266 const ImVec2 p1(p0.x + box_w, p0.y + box_h);
1268 draw_list->AddRectFilled(p0, p1, fill_color, 2.0f);
1269 draw_list->AddRect(p0, p1, border_color, 2.0f, 0, 1.5f);
1273 std::snprintf(label,
sizeof(label),
"0x%02X s%d",
1274 static_cast<int>(obj.id_),
1275 static_cast<int>(obj.size_ & 0x1F));
1276 const ImVec2 text_sz = ImGui::CalcTextSize(label);
1277 const ImVec2 tp(p0.x + 1.0f, p0.y - text_sz.y - 1.0f);
1278 draw_list->AddRectFilled(
1279 tp, ImVec2(tp.x + text_sz.x + 2.0f, tp.y + text_sz.y),
1280 text_bg_color, 2.0f);
1281 draw_list->AddText(tp, border_color, label);
1285 if (minecart_track_panel_) {
1286 const bool show_tracks = show_minecart_tracks_ ||
1287 minecart_track_panel_->IsPickingCoordinates();
1288 const auto& tracks = minecart_track_panel_->GetTracks();
1289 if (show_tracks && !tracks.empty()) {
1290 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1291 ImVec2 canvas_pos = canvas_.zero_point();
1292 float scale = canvas_.global_scale();
1294 const int active_track =
1295 minecart_track_panel_->IsPickingCoordinates()
1296 ? minecart_track_panel_->GetPickingTrackIndex()
1299 for (
const auto& track : tracks) {
1301 static_cast<uint16_t
>(track.start_x),
1302 static_cast<uint16_t
>(track.start_y));
1303 if (local.room_id != room_id) {
1307 ImVec4 marker_color = theme.selection_primary;
1308 if (track.id == active_track) {
1309 marker_color = theme.status_warning;
1312 const float px =
static_cast<float>(local.local_pixel_x) * scale;
1313 const float py =
static_cast<float>(local.local_pixel_y) * scale;
1314 ImVec2 center(canvas_pos.x + px, canvas_pos.y + py);
1315 const float radius = 6.0f * scale;
1317 draw_list->AddCircleFilled(center, radius,
1318 ImGui::GetColorU32(marker_color));
1319 draw_list->AddCircle(center, radius + 2.0f,
1320 ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f)), 0,
1323 std::string label = absl::StrFormat(
"T%d", track.id);
1325 ImVec2(center.x + 8.0f * scale, center.y - 6.0f * scale),
1326 ImGui::GetColorU32(theme.text_primary), label.c_str());
1333 if (show_coordinate_overlay_ && canvas_.IsMouseHovering()) {
1335 ImGui::GetMousePos(), canvas_.zero_point(), canvas_.global_scale());
1338 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1341 int canvas_x = tile_x * 8;
1342 int canvas_y = tile_y * 8;
1345 auto [camera_x, camera_y] =
1353 ImVec2 mouse_pos = ImGui::GetMousePos();
1354 ImVec2 overlay_pos = ImVec2(mouse_pos.x + 15, mouse_pos.y + 15);
1357 ImGui::Text(
"Tile: (%d, %d)", tile_x, tile_y);
1358 ImGui::Text(
"Pixel: (%d, %d)", canvas_x, canvas_y);
1359 ImGui::Text(
"Camera: ($%04X, $%04X)", camera_x, camera_y);
1360 ImGui::Text(
"Sprite: (%d, %d)", sprite_x, sprite_y);