53 if (room_id < 0 || room_id >= 0x128) {
54 ImGui::Text(
"Invalid room ID: %d", room_id);
59 ImGui::Text(
"ROM not loaded");
72 float pixel_x = target_x * 8 * scale;
73 float pixel_y = target_y * 8 * scale;
76 ImVec2 view_size = ImGui::GetWindowSize();
77 float scroll_x = pixel_x - (view_size.x * 0.5f);
78 float scroll_y = pixel_y - (view_size.y * 0.5f);
86 ImGui::SetScrollX(scroll_x);
87 ImGui::SetScrollY(scroll_y);
100 constexpr int kRoomPixelWidth = 512;
101 constexpr int kRoomPixelHeight = 512;
102 constexpr int kDungeonTileSize = 8;
106 frame_opts.
canvas_size = ImVec2(kRoomPixelWidth, kRoomPixelHeight);
117 static int debug_frame_count = 0;
118 if (debug_frame_count++ % 60 == 0) {
120 "Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f",
124 "[DungeonCanvas]",
"Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)",
131 auto& room = (*rooms_)[room_id];
137 if (room.rom() && room.rom()->is_loaded()) {
141 room.LoadRoomGraphics(room.blockset);
142 room.RenderRoomGraphics();
152 auto draw_navigation = [&]() {
158 const int col = room_id % kRoomMatrixCols;
159 const int row = room_id / kRoomMatrixCols;
161 auto room_if_valid = [](
int candidate) -> std::optional<int> {
168 room_if_valid(row > 0 ? room_id - kRoomMatrixCols : -1);
169 const auto south = room_if_valid(
170 row < kRoomMatrixRows - 1 ? room_id + kRoomMatrixCols : -1);
171 const auto west = room_if_valid(col > 0 ? room_id - 1 : -1);
173 room_if_valid(col < kRoomMatrixCols - 1 ? room_id + 1 : -1);
175 auto nav_button = [&](
const char* id, ImGuiDir dir,
176 const std::optional<int>& target,
177 const char* tooltip) {
178 const bool enabled = target.has_value();
180 ImGui::BeginDisabled();
181 if (ImGui::ArrowButton(
id, dir) && enabled) {
190 ImGui::EndDisabled();
191 if (enabled && ImGui::IsItemHovered() && tooltip && tooltip[0] !=
'\0')
192 ImGui::SetTooltip(
"%s", tooltip);
196 nav_button(
"RoomNavNorth", ImGuiDir_Up, north,
"Go to room above");
198 nav_button(
"RoomNavSouth", ImGuiDir_Down, south,
"Go to room below");
200 nav_button(
"RoomNavWest", ImGuiDir_Left, west,
"Previous room");
202 nav_button(
"RoomNavEast", ImGuiDir_Right, east,
"Next room");
213 layer_mgr.ApplyLayerMerging(room.layer_merging());
215 uint8_t blockset_val = room.blockset;
216 uint8_t spriteset_val = room.spriteset;
217 uint8_t palette_val = room.palette;
218 uint8_t floor1_val = room.floor1();
219 uint8_t floor2_val = room.floor2();
220 int effect_val =
static_cast<int>(room.effect());
221 int tag1_val =
static_cast<int>(room.tag1());
222 int tag2_val =
static_cast<int>(room.tag2());
223 uint8_t layout_val = room.layout;
226 const char* effect_names[] = {
233 "Light Torch to See",
238 const char* tag_names[] = {
240 "NW Kill Enemy to Open",
241 "NE Kill Enemy to Open",
242 "SW Kill Enemy to Open",
243 "SE Kill Enemy to Open",
244 "W Kill Enemy to Open",
245 "E Kill Enemy to Open",
246 "N Kill Enemy to Open",
247 "S Kill Enemy to Open",
248 "Clear Quadrant to Open",
249 "Clear Full Tile to Open",
250 "NW Push Block to Open",
251 "NE Push Block to Open",
252 "SW Push Block to Open",
253 "SE Push Block to Open",
254 "W Push Block to Open",
255 "E Push Block to Open",
256 "N Push Block to Open",
257 "S Push Block to Open",
258 "Push Block to Open",
259 "Pull Lever to Open",
260 "Collect Prize to Open",
261 "Hold Switch Open Door",
262 "Toggle Switch to Open",
271 "Push Switch Exploding Wall",
273 "Open Chest (Holes 0)",
276 "Defeat Boss for Prize",
277 "SE Kill Enemy Push Block",
278 "Trigger Switch Chest",
279 "Pull Lever Exploding Wall",
280 "NW Kill Enemy for Chest",
281 "NE Kill Enemy for Chest",
282 "SW Kill Enemy for Chest",
283 "SE Kill Enemy for Chest",
284 "W Kill Enemy for Chest",
285 "E Kill Enemy for Chest",
286 "N Kill Enemy for Chest",
287 "S Kill Enemy for Chest",
288 "Clear Quadrant for Chest",
289 "Clear Full Tile for Chest",
290 "Light Torches to Open",
298 "Open Chest for Holes 8",
299 "Push Block for Chest",
300 "Clear Room for Triforce",
301 "Light Torches for Chest",
305 constexpr int kNumTags = IM_ARRAYSIZE(tag_names);
307 const char* merge_types[] = {
"Off",
"Parallax",
"Dark",
308 "On top",
"Translucent",
"Addition",
309 "Normal",
"Transparent",
"Dark room"};
310 const char* blend_modes[] = {
"Normal",
"Trans",
"Add",
"Dark",
"Off"};
315 if (ImGui::BeginTable(
"##RoomPropsTable", 2, ImGuiTableFlags_SizingStretchProp)) {
316 ImGui::TableSetupColumn(
"NavCol", ImGuiTableColumnFlags_WidthFixed, 120);
317 ImGui::TableSetupColumn(
"PropsCol", ImGuiTableColumnFlags_WidthStretch);
320 ImGui::TableNextRow();
321 ImGui::TableNextColumn();
323 ImGui::TableNextColumn();
328 ImGui::SameLine(0, 2);
332 room.SetBlockset(blockset_val);
333 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
335 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Blockset (0-51)");
338 ImGui::SameLine(0, 2);
342 room.SetPalette(palette_val);
345 if (palette_val < game_data_->paletteset_ids.size() &&
348 if (
auto palette_id_res =
rom_->
ReadWord(0xDEC4B + palette_ptr);
349 palette_id_res.ok()) {
365 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
367 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Palette (0-47)");
370 ImGui::SameLine(0, 2);
374 room.layout = layout_val;
375 room.MarkLayoutDirty();
376 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
378 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Layout (0-7)");
381 ImGui::SameLine(0, 2);
385 room.SetSpriteset(spriteset_val);
386 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
388 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Spriteset (0-8F)");
391 ImGui::SameLine(0, 2);
395 room.set_floor1(floor1_val);
396 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
398 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Floor 1 (0-F)");
401 ImGui::SameLine(0, 2);
405 room.set_floor2(floor2_val);
406 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
408 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Floor 2 (0-F)");
411 ImGui::TableNextRow();
412 ImGui::TableNextColumn();
414 ImGui::TableNextColumn();
415 constexpr int kNumEffects = IM_ARRAYSIZE(effect_names);
416 if (effect_val < 0) effect_val = 0;
417 if (effect_val >= kNumEffects) effect_val = kNumEffects - 1;
418 ImGui::SetNextItemWidth(150);
419 if (ImGui::BeginCombo(
"##Effect", effect_names[effect_val])) {
420 for (
int i = 0; i < kNumEffects; i++) {
421 if (ImGui::Selectable(effect_names[i], effect_val == i)) {
423 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
428 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Effect");
431 ImGui::SameLine(0, 2);
432 int tag1_idx = std::clamp(tag1_val, 0, kNumTags - 1);
433 ImGui::SetNextItemWidth(180);
434 if (ImGui::BeginCombo(
"##Tag1", tag_names[tag1_idx])) {
435 for (
int i = 0; i < kNumTags; i++) {
436 if (ImGui::Selectable(tag_names[i], tag1_idx == i)) {
438 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
443 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Tag 1");
446 ImGui::SameLine(0, 2);
447 int tag2_idx = std::clamp(tag2_val, 0, kNumTags - 1);
448 ImGui::SetNextItemWidth(180);
449 if (ImGui::BeginCombo(
"##Tag2", tag_names[tag2_idx])) {
450 for (
int i = 0; i < kNumTags; i++) {
451 if (ImGui::Selectable(tag_names[i], tag2_idx == i)) {
453 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
458 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Tag 2");
461 ImGui::TableNextRow();
462 ImGui::TableNextColumn();
464 ImGui::TableNextColumn();
471 auto mark_layers_dirty = [&]() {
473 auto& r = (*rooms_)[room_id];
474 r.bg1_buffer().bitmap().set_modified(
true);
475 r.bg2_buffer().bitmap().set_modified(
true);
476 r.object_bg1_buffer().bitmap().set_modified(
true);
477 r.object_bg2_buffer().bitmap().set_modified(
true);
478 r.MarkCompositeDirty();
482 if (ImGui::Checkbox(
"BG1##L", &bg1_layout)) {
486 if (ImGui::IsItemHovered()) {
487 ImGui::SetTooltip(
"BG1 Layout: Main floor tiles (rendered on top of BG2)");
490 if (ImGui::Checkbox(
"O1##O", &bg1_objects)) {
494 if (ImGui::IsItemHovered()) {
495 ImGui::SetTooltip(
"BG1 Objects: Walls, pots, interactive objects (topmost layer)");
498 if (ImGui::Checkbox(
"BG2##L2", &bg2_layout)) {
502 if (ImGui::IsItemHovered()) {
503 ImGui::SetTooltip(
"BG2 Layout: Background floor patterns (behind BG1)");
506 if (ImGui::Checkbox(
"O2##O2", &bg2_objects)) {
510 if (ImGui::IsItemHovered()) {
511 ImGui::SetTooltip(
"BG2 Objects: Background details (behind BG1)");
514 ImGui::SetNextItemWidth(60);
515 int bg2_blend =
static_cast<int>(
517 if (ImGui::Combo(
"##Bld", &bg2_blend, blend_modes, IM_ARRAYSIZE(blend_modes))) {
522 if (ImGui::IsItemHovered()) {
524 "BG2 Blend Mode (color math effect):\n"
525 "- Normal: Opaque pixels\n"
526 "- Translucent: 50% alpha\n"
527 "- Addition: Additive blending\n"
528 "Does not change layer order (BG1 always on top)");
531 ImGui::SetNextItemWidth(70);
532 int merge_val = room.layer_merging().ID;
533 if (ImGui::Combo(
"##Mrg", &merge_val, merge_types, IM_ARRAYSIZE(merge_types))) {
534 room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]);
535 layer_mgr.ApplyLayerMergingPreserveVisibility(room.layer_merging());
536 if (room.rom() && room.rom()->is_loaded()) room.RenderRoomGraphics();
538 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Merge type");
541 ImGui::TableNextRow();
542 ImGui::TableNextColumn();
544 ImGui::TableNextColumn();
561 if (is_mask_mode) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f));
562 if (ImGui::RadioButton(
"Mask", is_mask_mode))
564 if (is_mask_mode) ImGui::PopStyleColor();
565 if (ImGui::IsItemHovered()) {
567 "Mask Selection Mode\n"
568 "Only select BG2/Layer 1 overlay objects (platforms, statues, stairs)\n"
569 "These are the objects that create transparency holes in BG1");
575 if (layer_mgr.AreLayersMerged()) {
595 if (ImGui::IsItemHovered()) {
596 ImGui::SetTooltip(
"Open Object Editor panel to select objects for placement");
606 if (ImGui::IsItemHovered()) {
607 ImGui::SetTooltip(
"Open Sprite Editor panel to select sprites for placement");
617 if (ImGui::IsItemHovered()) {
618 ImGui::SetTooltip(
"Open Item Editor panel to select items for placement");
625 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.6f, 0.9f, 1.0f));
631 ImGui::PopStyleColor();
633 if (ImGui::IsItemHovered()) {
634 ImGui::SetTooltip(door_mode ?
"Click to cancel door placement" :
"Click to place doors");
647 auto& room = (*rooms_)[room_id];
651 place_menu.
label =
"Place Entity";
692 layer_menu.
label =
"Layer Visibility";
728 entity_menu.
label =
"Entity Visibility";
745 [&room]() { room.RenderRoomGraphics(); },
"Ctrl+R"));
749 grid_menu.
label =
"Grid Options";
757 grid_menu.
subitems.push_back(toggle_grid_item);
771 debug_menu.
label =
"Debug";
778 debug_menu.
subitems.push_back(room_info_item);
784 debug_menu.
subitems.push_back(texture_info_item);
791 debug_menu.
subitems.push_back(coord_overlay_item);
795 object_bounds_menu.
label =
"Show Object Bounds";
797 object_bounds_menu.
callback = [
this]() {
802 object_bounds_menu.
subitems.push_back(
807 object_bounds_menu.
subitems.push_back(
812 object_bounds_menu.
subitems.push_back(
824 object_bounds_menu.
subitems.push_back(sep);
827 object_bounds_menu.
subitems.push_back(
832 object_bounds_menu.
subitems.push_back(
837 object_bounds_menu.
subitems.push_back(
843 debug_menu.
subitems.push_back(object_bounds_menu);
849 debug_menu.
subitems.push_back(layer_info_item);
855 room.LoadRoomGraphics(room.blockset);
856 room.RenderRoomGraphics();
858 debug_menu.
subitems.push_back(force_reload_item);
863 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
864 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
865 room.blockset, room.palette, room.layout);
866 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
867 room.GetTileObjects().size(), room.GetSprites().size());
868 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
869 room.bg1_buffer().bitmap().width(),
870 room.bg1_buffer().bitmap().height(),
871 room.bg2_buffer().bitmap().width(),
872 room.bg2_buffer().bitmap().height());
874 debug_menu.
subitems.push_back(log_item);
883 const bool has_selection = !selected.empty();
884 const bool single_selection = selected.size() == 1;
885 const bool has_clipboard = interaction.HasClipboardData();
886 const bool placing_object = interaction.IsObjectLoaded();
888 if (single_selection &&
rooms_) {
889 auto& room = (*rooms_)[room_id];
890 const auto& objects = room.GetTileObjects();
891 if (selected[0] < objects.size()) {
892 const auto& obj = objects[selected[0]];
893 std::string name = GetObjectName(obj.id_);
895 absl::StrFormat(
"Object 0x%02X: %s", obj.id_, name.c_str())));
899 auto enabled_if = [](
bool enabled) {
908 interaction.HandleCopySelected();
909 interaction.HandleDeleteSelected();
917 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
924 interaction.HandleCopySelected();
925 interaction.HandlePasteObjects();
933 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
939 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
945 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
951 layer_menu.
label =
"Send to Layer";
957 [&interaction]() { interaction.SendSelectedToLayer(0); },
"1");
959 layer_menu.
subitems.push_back(layer1_item);
963 [&interaction]() { interaction.SendSelectedToLayer(1); },
"2");
965 layer_menu.
subitems.push_back(layer2_item);
969 [&interaction]() { interaction.SendSelectedToLayer(2); },
"3");
971 layer_menu.
subitems.push_back(layer3_item);
977 arrange_menu.
label =
"Arrange";
983 [&interaction]() { interaction.SendSelectedToFront(); },
"Ctrl+Shift+]");
985 arrange_menu.
subitems.push_back(bring_front_item);
989 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
991 arrange_menu.
subitems.push_back(send_back_item);
995 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
997 arrange_menu.
subitems.push_back(bring_forward_item);
1001 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
1003 arrange_menu.
subitems.push_back(send_backward_item);
1008 const auto& selected_entity = interaction.GetSelectedEntity();
1009 const bool has_entity_selection = interaction.HasEntitySelection();
1011 if (has_entity_selection &&
rooms_) {
1012 auto& room = (*rooms_)[room_id];
1015 std::string entity_info;
1016 switch (selected_entity.type) {
1018 const auto& doors = room.GetDoors();
1019 if (selected_entity.index < doors.size()) {
1020 const auto& door = doors[selected_entity.index];
1027 const auto& sprites = room.GetSprites();
1028 if (selected_entity.index < sprites.size()) {
1029 const auto& sprite = sprites[selected_entity.index];
1030 entity_info = absl::StrFormat(
ICON_MD_PERSON " Sprite: %s (0x%02X)",
1036 const auto& items = room.GetPotItems();
1037 if (selected_entity.index < items.size()) {
1038 const auto& item = items[selected_entity.index];
1047 if (!entity_info.empty()) {
1053 [
this, &room, selected_entity]() {
1054 switch (selected_entity.type) {
1055 case EntityType::Door: {
1056 auto& doors = room.GetDoors();
1057 if (selected_entity.index < doors.size()) {
1058 doors.erase(doors.begin() +
1059 static_cast<long>(selected_entity.index));
1064 auto& sprites = room.GetSprites();
1065 if (selected_entity.index < sprites.size()) {
1066 sprites.erase(sprites.begin() +
1067 static_cast<long>(selected_entity.index));
1072 auto& items = room.GetPotItems();
1073 if (selected_entity.index < items.size()) {
1074 items.erase(items.begin() +
1075 static_cast<long>(selected_entity.index));
1095 if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) {
1096 auto& room = (*rooms_)[room_id];
1097 ImGui::SetNextWindowPos(
1098 ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10),
1099 ImGuiCond_FirstUseEver);
1100 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
1101 if (ImGui::Begin(
"Room Debug Info", &show_room_debug_info_,
1102 ImGuiWindowFlags_NoCollapse)) {
1103 ImGui::Text(
"Room: 0x%03X (%d)", room_id, room_id);
1105 ImGui::Text(
"Graphics");
1106 ImGui::Text(
" Blockset: 0x%02X", room.blockset);
1107 ImGui::Text(
" Palette: 0x%02X", room.palette);
1108 ImGui::Text(
" Layout: 0x%02X", room.layout);
1109 ImGui::Text(
" Spriteset: 0x%02X", room.spriteset);
1111 ImGui::Text(
"Content");
1112 ImGui::Text(
" Objects: %zu", room.GetTileObjects().size());
1113 ImGui::Text(
" Sprites: %zu", room.GetSprites().size());
1115 ImGui::Text(
"Buffers");
1116 auto& bg1 = room.bg1_buffer().bitmap();
1117 auto& bg2 = room.bg2_buffer().bitmap();
1118 ImGui::Text(
" BG1: %dx%d %s", bg1.width(), bg1.height(),
1119 bg1.texture() ?
"(has texture)" :
"(NO TEXTURE)");
1120 ImGui::Text(
" BG2: %dx%d %s", bg2.width(), bg2.height(),
1121 bg2.texture() ?
"(has texture)" :
"(NO TEXTURE)");
1123 ImGui::Text(
"Layers (4-way)");
1124 auto& layer_mgr = GetRoomLayerManager(room_id);
1129 if (ImGui::Checkbox(
"BG1 Layout", &bg1l))
1131 if (ImGui::Checkbox(
"BG1 Objects", &bg1o))
1133 if (ImGui::Checkbox(
"BG2 Layout", &bg2l))
1135 if (ImGui::Checkbox(
"BG2 Objects", &bg2o))
1137 int blend =
static_cast<int>(
1139 if (ImGui::SliderInt(
"BG2 Blend", &blend, 0, 4)) {
1147 ImGui::Text(
"Layout Override");
1148 static bool enable_override =
false;
1149 ImGui::Checkbox(
"Enable Override", &enable_override);
1150 if (enable_override) {
1151 ImGui::SliderInt(
"Layout ID", &layout_override_, 0, 7);
1153 layout_override_ = -1;
1156 if (show_object_bounds_) {
1158 ImGui::Text(
"Object Outline Filters");
1159 ImGui::Text(
"By Type:");
1160 ImGui::Checkbox(
"Type 1", &object_outline_toggles_.show_type1_objects);
1161 ImGui::Checkbox(
"Type 2", &object_outline_toggles_.show_type2_objects);
1162 ImGui::Checkbox(
"Type 3", &object_outline_toggles_.show_type3_objects);
1163 ImGui::Text(
"By Layer:");
1164 ImGui::Checkbox(
"Layer 0",
1165 &object_outline_toggles_.show_layer0_objects);
1166 ImGui::Checkbox(
"Layer 1",
1167 &object_outline_toggles_.show_layer1_objects);
1168 ImGui::Checkbox(
"Layer 2",
1169 &object_outline_toggles_.show_layer2_objects);
1175 if (show_texture_debug_ && rooms_ && rom_->is_loaded()) {
1176 ImGui::SetNextWindowPos(
1177 ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10),
1178 ImGuiCond_FirstUseEver);
1179 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
1180 if (ImGui::Begin(
"Texture Debug", &show_texture_debug_,
1181 ImGuiWindowFlags_NoCollapse)) {
1182 auto& room = (*rooms_)[room_id];
1183 auto& bg1 = room.bg1_buffer().bitmap();
1184 auto& bg2 = room.bg2_buffer().bitmap();
1186 ImGui::Text(
"BG1 Bitmap");
1187 ImGui::Text(
" Size: %dx%d", bg1.width(), bg1.height());
1188 ImGui::Text(
" Active: %s", bg1.is_active() ?
"YES" :
"NO");
1189 ImGui::Text(
" Texture: 0x%p", bg1.texture());
1190 ImGui::Text(
" Modified: %s", bg1.modified() ?
"YES" :
"NO");
1192 if (bg1.texture()) {
1193 ImGui::Text(
" Preview:");
1194 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
1198 ImGui::Text(
"BG2 Bitmap");
1199 ImGui::Text(
" Size: %dx%d", bg2.width(), bg2.height());
1200 ImGui::Text(
" Active: %s", bg2.is_active() ?
"YES" :
"NO");
1201 ImGui::Text(
" Texture: 0x%p", bg2.texture());
1202 ImGui::Text(
" Modified: %s", bg2.modified() ?
"YES" :
"NO");
1204 if (bg2.texture()) {
1205 ImGui::Text(
" Preview:");
1206 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
1212 if (show_layer_info_) {
1213 ImGui::SetNextWindowPos(
1214 ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10),
1215 ImGuiCond_FirstUseEver);
1216 ImGui::SetNextWindowSize(ImVec2(220, 0), ImGuiCond_FirstUseEver);
1217 if (ImGui::Begin(
"Layer Info", &show_layer_info_,
1218 ImGuiWindowFlags_NoCollapse)) {
1219 ImGui::Text(
"Canvas Scale: %.2f", canvas_.global_scale());
1220 ImGui::Text(
"Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height());
1221 auto& layer_mgr = GetRoomLayerManager(room_id);
1223 ImGui::Text(
"Layer Visibility (4-way):");
1226 for (
int i = 0; i < 4; ++i) {
1228 bool visible = layer_mgr.IsLayerVisible(layer);
1229 auto blend = layer_mgr.GetLayerBlendMode(layer);
1230 ImGui::Text(
" %s: %s (%s)",
1232 visible ?
"VISIBLE" :
"hidden",
1233 zelda3::RoomLayerManager::GetBlendModeName(blend));
1237 ImGui::Text(
"Draw Order:");
1238 auto draw_order = layer_mgr.GetDrawOrder();
1239 for (
int i = 0; i < 4; ++i) {
1240 ImGui::Text(
" %d: %s", i + 1,
1243 ImGui::Text(
"BG2 On Top: %s", layer_mgr.IsBG2OnTop() ?
"YES" :
"NO");
1248 if (rooms_ && rom_->is_loaded()) {
1249 auto& room = (*rooms_)[room_id];
1252 object_interaction_.SetCurrentRoom(rooms_, room_id);
1255 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1256 bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
1259 static int last_rendered_room = -1;
1260 static bool has_rendered =
false;
1261 if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
1263 "[DungeonCanvasViewer] Loading and rendering graphics for room %d\n",
1265 (void)LoadAndRenderRoomGraphics(room_id);
1266 last_rendered_room = room_id;
1267 has_rendered =
true;
1271 if (room.GetTileObjects().empty()) {
1276 if (room.GetSprites().empty()) {
1281 if (room.GetPotItems().empty()) {
1282 room.LoadPotItems();
1288 if (rom_ && rom_->is_loaded()) {
1295 DrawRoomBackgroundLayers(room_id);
1299 if (object_interaction_.IsMaskModeActive()) {
1300 DrawMaskHighlights(canvas_rt, room);
1305 RenderEntityOverlay(canvas_rt, room);
1308 if (object_interaction_enabled_) {
1309 object_interaction_.HandleCanvasMouseInput();
1310 object_interaction_.CheckForObjectSelection();
1311 object_interaction_.DrawSelectBox();
1313 .DrawSelectionHighlights();
1315 .DrawEntitySelectionHighlights();
1316 object_interaction_.DrawGhostPreview();
1322 if (rooms_ && rom_->is_loaded()) {
1323 auto& room = (*rooms_)[room_id];
1329 if (show_object_bounds_) {
1330 DrawObjectPositionOutlines(canvas_rt, room);
1335 if (show_coordinate_overlay_ && canvas_.IsMouseHovering()) {
1336 ImVec2 mouse_pos = ImGui::GetMousePos();
1337 ImVec2 canvas_pos = canvas_.zero_point();
1338 float scale = canvas_.global_scale();
1339 if (scale <= 0.0f) scale = 1.0f;
1342 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1343 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1346 if (canvas_x >= 0 && canvas_x < kRoomPixelWidth &&
1347 canvas_y >= 0 && canvas_y < kRoomPixelHeight) {
1349 int tile_x = canvas_x / kDungeonTileSize;
1350 int tile_y = canvas_y / kDungeonTileSize;
1360 ImVec2 overlay_pos = ImVec2(mouse_pos.x + 15, mouse_pos.y + 15);
1363 std::string coord_text = absl::StrFormat(
1366 "Camera: ($%04X, $%04X)\n"
1371 sprite_x, sprite_y);
1374 ImVec2 text_size = ImGui::CalcTextSize(coord_text.c_str());
1375 ImVec2 box_min = ImVec2(overlay_pos.x - 4, overlay_pos.y - 2);
1376 ImVec2 box_max = ImVec2(overlay_pos.x + text_size.x + 8, overlay_pos.y + text_size.y + 4);
1378 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1379 draw_list->AddRectFilled(box_min, box_max, IM_COL32(0, 0, 0, 200), 4.0f);
1380 draw_list->AddRect(box_min, box_max, IM_COL32(100, 100, 100, 255), 4.0f);
1381 draw_list->AddText(overlay_pos, IM_COL32(255, 255, 255, 255), coord_text.c_str());