13#include "absl/strings/str_format.h"
39#include "imgui/imgui.h"
57 if (object_id < 0x100)
59 if (object_id < 0x200)
61 if (object_id >= 0xF80)
67 switch (layer_value) {
80 switch (layer_value) {
82 return "Upper layer (BG1)";
84 return "Lower layer (BG2)";
91 static constexpr const char* kNames[] = {
92 "Off",
"Parallax",
"Dark",
"On top",
"Translucent",
93 "Addition",
"Normal",
"Transparent",
"Dark room"};
94 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
95 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
99 static constexpr const char* kNames[] = {
"One",
"Both",
"Both + Scroll",
100 "Moving Floor",
"Moving Water"};
101 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
102 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
107 static const char* kNames[] = {
108 "Nothing",
"Green Rupee",
"Rock",
"Bee",
109 "Heart (4)",
"Bomb (4)",
"Heart",
"Blue Rupee",
110 "Key",
"Arrow (5)",
"Bomb (1)",
"Heart",
111 "Magic (Small)",
"Full Magic",
"Cucco",
"Green Soldier",
112 "Bush Stal",
"Blue Soldier",
"Landmine",
"Heart",
113 "Fairy",
"Heart",
"Nothing (22)",
"Hole",
114 "Warp",
"Staircase",
"Bombable",
"Switch",
116 constexpr size_t kCount =
sizeof(kNames) /
sizeof(kNames[0]);
117 return item < kCount ? kNames[item] :
"Unknown";
122 return std::clamp(desired_width, min_width, std::max(min_width, max_width));
140 const ImVec2 min = ImGui::GetItemRectMin();
141 const ImVec2 max = ImGui::GetItemRectMax();
152 return compact && detail_requested;
156 bool standalone_window_open) {
157 return standalone_window_open
163 float total_width,
float min_canvas_width,
float min_sidebar_width,
164 float splitter_width,
bool want_left,
bool want_right) {
169 auto required_width = [&](
bool left,
bool right,
bool compact_left,
170 bool compact_right) {
171 float required = min_canvas_width;
173 left ? (compact_left ? GetCompactSidebarWidth(
false, min_sidebar_width)
177 right ? (compact_right ? GetCompactSidebarWidth(
true, min_sidebar_width)
181 required += splitter_width;
184 required += splitter_width;
216 float total_width,
float min_canvas_width,
float min_sidebar_width,
217 float splitter_width,
float stored_left_width,
float stored_right_width,
218 bool want_left,
bool want_right) {
221 total_width, min_canvas_width, min_sidebar_width, splitter_width,
222 want_left, want_right);
224 const float compact_left_width =
225 GetCompactSidebarWidth(
false, min_sidebar_width);
226 const float compact_right_width =
227 GetCompactSidebarWidth(
true, min_sidebar_width);
240 : stored_right_width)
243 const float max_left_width =
244 total_width - right_width - min_canvas_width -
247 const float max_right_width =
248 total_width - left_width - min_canvas_width -
252 left_width = ClampWorkbenchPaneWidth(
259 right_width = ClampWorkbenchPaneWidth(
266 float center_width = total_width - left_width - right_width;
268 center_width -= splitter_width;
271 center_width -= splitter_width;
273 if (center_width < min_canvas_width) {
274 float deficit = min_canvas_width - center_width;
278 left_width -= shrink;
284 right_width -= shrink;
287 center_width = std::max(
288 1.0f, total_width - left_width - right_width -
301 std::function<
void(
int)> on_room_selected,
303 std::function<
void(
int)> on_save_room,
304 std::function<
void()> on_save_all_rooms,
307 std::function<
const std::deque<int>&()> get_recent_rooms,
308 std::function<
void(
int)> forget_recent_room,
309 std::function<
void(
bool)> set_workflow_mode,
Rom* rom)
310 : room_selector_(room_selector),
311 current_room_id_(current_room_id),
312 on_room_selected_(std::move(on_room_selected)),
313 on_room_selected_with_intent_(std::move(on_room_selected_with_intent)),
314 on_save_room_(std::move(on_save_room)),
315 on_save_all_rooms_(std::move(on_save_all_rooms)),
316 get_viewer_(std::move(get_viewer)),
317 get_compare_viewer_(std::move(get_compare_viewer)),
318 get_recent_rooms_(std::move(get_recent_rooms)),
319 forget_recent_room_(std::move(forget_recent_room)),
320 set_workflow_mode_(std::move(set_workflow_mode)),
326 return "dungeon.workbench";
329 return "Dungeon Workbench";
457 float button_size,
bool compact) {
458 const bool sidebar_open = ImGui::BeginChild(
"##DungeonWorkbenchSidebar",
459 ImVec2(width, height),
true);
469 const bool can_open_overview =
true;
470 const float collapse_w =
475 const float spacing = ImGui::GetStyle().ItemSpacing.x;
476 const float header_width = ImGui::GetContentRegionAvail().x;
477 const bool stack_mode_switch = compact && header_width < 220.0f;
478 const float action_cluster_w =
479 collapse_w + (can_open_overview ? (spacing + menu_w) : 0.0f);
483 "Browse",
nullptr, compact, action_cluster_w, [&]() {
484 if (can_open_overview) {
487 "Open room review tools",
true)) {
488 ImGui::OpenPopup(
"##WorkbenchSidebarQuickActions");
490 if (ImGui::BeginPopup(
"##WorkbenchSidebarQuickActions")) {
498 const bool can_open_shortcuts =
501 false, can_open_shortcuts) &&
511 "Collapse navigation pane")) {
516 ImGui::Dummy(ImVec2(0.0f, 2.0f));
522 float segment_height) {
527 const float spacing = ImGui::GetStyle().ItemSpacing.x;
528 const ImVec2 button_size(segment_height, segment_height);
534 if (ImGui::IsItemHovered()) {
535 ImGui::SetTooltip(tr(
"Rooms"));
537 ImGui::SameLine(0.0f, spacing);
542 if (ImGui::IsItemHovered()) {
543 ImGui::SetTooltip(tr(
"Entrances"));
549 ImGui::TextDisabled(tr(
"Room navigation unavailable"));
553 ImGui::PushID(
"WorkbenchSidebarMode");
570 ImGui::TextDisabled(
ICON_MD_INFO " Load a ROM to edit dungeon rooms.");
574 ImGui::TextColored(theme.text_error_red, tr(
"Dungeon Workbench not wired"));
582 const float total_w = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
586 const float min_sidebar_w =
588 const float min_canvas_w = std::max(420.0f, min_sidebar_w + 96.0f);
591 total_w, min_canvas_w, min_sidebar_w, splitter_w,
631 const float total_h = std::max(ImGui::GetContentRegionAvail().y, 1.0f);
651 ImGui::SameLine(0.0f, 0.0f);
653 "##DungeonWorkbenchLeftSplitter", total_h,
655 total_w - left_w - min_canvas_w - (show_left ? splitter_w : 0.0f),
659 ImGui::SameLine(0.0f, 0.0f);
663 ImGui::SameLine(0.0f, 0.0f);
665 "##DungeonWorkbenchRightSplitter", total_h,
667 total_w - right_w - min_canvas_w -
668 (show_right ? splitter_w : 0.0f),
672 ImGui::SameLine(0.0f, 0.0f);
682 ImGui::SameLine(0.0f, 0.0f);
684 "##DungeonWorkbenchLeftSplitter", total_h,
686 total_w - right_w - min_canvas_w -
687 (show_right ? splitter_w : 0.0f),
694 ImGui::SameLine(0.0f, 0.0f);
699 ImGui::SameLine(0.0f, 0.0f);
701 "##DungeonWorkbenchRightSplitter", total_h,
703 total_w - left_w - min_canvas_w - (show_left ? splitter_w : 0.0f),
707 ImGui::SameLine(0.0f, 0.0f);
714 if (primary_viewer) {
721 const bool canvas_open = ImGui::BeginChild(
"##DungeonWorkbenchCanvas",
722 ImVec2(width, height),
false);
724 if (primary_viewer) {
730 const float status_bar_reserved =
732 ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
734 ImGui::GetStyle().ItemSpacing.y;
737 ImGuiWindowFlags body_flags = 0;
738 if (connected_mode) {
741 const ImVec2 content_size =
744 ImGui::SetNextWindowContentSize(
745 ImVec2(content_size.x * scale, content_size.y * scale));
746 body_flags |= ImGuiWindowFlags_HorizontalScrollbar |
747 ImGuiWindowFlags_NoScrollWithMouse;
749 const bool body_open = ImGui::BeginChild(
750 "##DungeonCanvasBody", ImVec2(0.0f, -status_bar_reserved),
false,
753 if (connected_mode) {
770 const char* tool_mode =
772 bool room_dirty =
false;
773 if (
auto* rooms = primary_viewer->
rooms();
776 room_dirty = room->HasUnsavedChanges();
784 status.workflow_primary =
true;
790 static std::string s_undo_desc;
792 status.undo_desc = s_undo_desc.empty() ? nullptr : s_undo_desc.c_str();
795 static std::string s_redo_desc;
797 status.redo_desc = s_redo_desc.empty() ? nullptr : s_redo_desc.c_str();
804 status.on_selection = [
this]() {
810 ImGui::TextDisabled(tr(
"No active viewer"));
817 float button_size,
bool compact,
819 const bool inspector_open = ImGui::BeginChild(
"##DungeonWorkbenchInspector",
820 ImVec2(width, height),
true);
821 if (inspector_open) {
826 ImGui::TextDisabled(tr(
"No active viewer"));
837 const char* collapse_icon =
839 const float collapse_w =
843 const float spacing = ImGui::GetStyle().ItemSpacing.x;
844 const float action_cluster_w = swap_w + spacing + collapse_w;
848 "##DungeonWorkbenchInspectorHeader",
850 tools_mode ?
"Tools" :
"Inspect",
852 nullptr, compact, action_cluster_w, [&]() {
856 :
"Move inspector to the left side "
857 "(ZScream layout)")) {
863 ImGui::SameLine(0.0f, spacing);
866 "Collapse inspector")) {
871 ImGui::Dummy(ImVec2(0.0f, 2.0f));
875 ImGui::AlignTextToFramePadding();
876 ImGui::TextUnformatted(tr(
"Room"));
878 uint16_t requested_room_id =
static_cast<uint16_t
>(*current_room_id_);
879 ImGui::SetNextItemWidth(82.0f);
881 if (!can_jump_room) {
882 ImGui::BeginDisabled();
884 bool room_changed =
false;
888 "##WorkbenchRoomId", ImGuiDataType_U16, &requested_room_id,
"%04X",
889 ImGuiInputTextFlags_CharsHexadecimal,
892 "Current dungeon room ID");
894 if (!can_jump_room) {
895 ImGui::EndDisabled();
898 const int target_room_id = std::clamp(
static_cast<int>(requested_room_id),
905 if (ImGui::IsItemHovered()) {
906 const int preview_room_id = std::clamp(
908 ImGui::SetTooltip(tr(
"Open room 0x%03X"), preview_room_id);
942 constexpr ImGuiTableFlags kSplitFlags =
943 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoPadOuterX |
944 ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_BordersInnerV;
946 if (!ImGui::BeginTable(
"##DungeonWorkbenchSplit", 2, kSplitFlags)) {
951 ImGui::TableSetupColumn(
"Active", ImGuiTableColumnFlags_WidthStretch);
952 ImGui::TableSetupColumn(
"Compare", ImGuiTableColumnFlags_WidthStretch);
953 ImGui::TableNextRow();
959 ImGui::TableNextColumn();
960 ImGui::AlignTextToFramePadding();
969 if (split_active_open) {
975 ImGui::TableNextColumn();
976 ImGui::AlignTextToFramePadding();
978 compare_viewer ? compare_viewer->
project() : active_project;
986 if (split_compare_open) {
987 if (compare_viewer) {
994 ImGui::TextDisabled(tr(
"No compare viewer"));
1010 static const char*
const kShortNames[] = {
1011 "Sewers",
"HC",
"Eastern",
"Desert",
"A-Tower",
"Swamp",
"PoD",
1012 "Misery",
"Skull",
"Ice",
"Hera",
"Thieves",
"Turtle",
"GT",
1014 constexpr int kVanillaCount =
1015 static_cast<int>(
sizeof(kShortNames) /
sizeof(kShortNames[0]));
1017 auto AddRoom = [&](
int room_id,
int dungeon_id) {
1022 if (dungeon_id >= 0 && dungeon_id < kVanillaCount) {
1026 snprintf(buf,
sizeof(buf),
"Dungeon %02X", dungeon_id);
1032 for (
int i = 0; i < 0x84; ++i) {
1035 if (did >= 0 && did < kVanillaCount) {
1039 snprintf(buf,
sizeof(buf),
"Dungeon %02X", did);
1045 for (
int i = 0; i < 0x14; ++i) {
1057 ImGuiStyleVar_ItemSpacing,
1058 ImVec2(std::max(ImGui::GetStyle().ItemSpacing.x, 4.0f),
1059 std::max(4.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
1066 flags.kSaveSprites = value;
1067 flags.kSaveRoomHeaders = value;
1068 flags.kSaveChests = value;
1069 flags.kSavePotItems = value;
1070 flags.kSaveEntrances = value;
1071 flags.kSavePalettes = value;
1072 flags.kSaveCollision = value;
1073 flags.kSaveBlocks = value;
1074 flags.kSaveTorches = value;
1075 flags.kSavePits = value;
1081 if (ImGui::Checkbox(tr(
"Single-window Workbench"), &use_workbench)) {
1082 flags.kUseWorkbench = use_workbench;
1087 if (ImGui::IsItemHovered()) {
1088 ImGui::SetTooltip(tr(
1089 "Keep Dungeon editing in the integrated Workbench instead of separate "
1090 "high-level room panels."));
1094 ImGui::TextDisabled(tr(
"Data written by Apply Room / Apply Loaded Rooms"));
1095 constexpr ImGuiTableFlags kFlags =
1096 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
1097 if (ImGui::BeginTable(
"##WorkbenchApplyScopeFlags", 2, kFlags)) {
1098 auto draw_checkbox = [](
const char* label,
bool* value) {
1099 ImGui::TableNextColumn();
1100 ImGui::Checkbox(label, value);
1102 ImGui::TableNextRow();
1103 draw_checkbox(
"Room Objects", &flags.kSaveObjects);
1104 draw_checkbox(
"Sprites", &flags.kSaveSprites);
1105 ImGui::TableNextRow();
1106 draw_checkbox(
"Room Headers", &flags.kSaveRoomHeaders);
1107 draw_checkbox(
"Chests", &flags.kSaveChests);
1108 ImGui::TableNextRow();
1109 draw_checkbox(
"Pot Items", &flags.kSavePotItems);
1110 draw_checkbox(
"Palettes", &flags.kSavePalettes);
1111 ImGui::TableNextRow();
1112 draw_checkbox(
"Collision Maps", &flags.kSaveCollision);
1113 ImGui::TableNextColumn();
1114 ImGui::BeginDisabled();
1115 ImGui::Checkbox(
"Water Fill", &flags.kSaveWaterFillZones);
1116 ImGui::EndDisabled();
1117 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1119 "Water Fill save scope is fixed when the project opens. Change "
1120 "save_dungeon_water_fill_zones in the .yaze file, then reopen the "
1123 ImGui::TableNextRow();
1124 draw_checkbox(
"Blocks", &flags.kSaveBlocks);
1125 draw_checkbox(
"Torches", &flags.kSaveTorches);
1126 ImGui::TableNextRow();
1127 draw_checkbox(
"Pits", &flags.kSavePits);
1128 draw_checkbox(
"Entrances", &flags.kSaveEntrances);
1132 if (ImGui::SmallButton(tr(
"Select All##WorkbenchApplyScope"))) {
1136 if (ImGui::SmallButton(tr(
"Select None##WorkbenchApplyScope"))) {
1159 ImGui::TextDisabled(
1160 tr(
"Pit damage table is unavailable until ROM data loads."));
1166 if (!membership.room_valid) {
1167 ImGui::TextDisabled(tr(
"No valid room selected."));
1171 const uint16_t current_room = membership.room_id;
1172 const bool room_deals_damage = membership.deals_damage;
1174 room_deals_damage ? theme.status_warning : theme.text_secondary_gray,
1175 tr(
"%s Room 0x%03X %s pit damage"),
1177 room_deals_damage ?
"deals" :
"does not deal");
1178 ImGui::TextWrapped(tr(
1179 "RoomsWithPitDamage is fixed-capacity in vanilla. Editing replaces one "
1180 "listed room with another instead of growing or shrinking the table."));
1182 if (membership.dirty) {
1183 ImGui::TextColored(theme.status_warning,
1187 auto show_status = [&]() {
1199 if (room_deals_damage) {
1200 if (!membership.suggested_replacement_room.has_value()) {
1201 ImGui::TextDisabled(tr(
"No available non-damaging room slot."));
1207 ImGui::TextDisabled(tr(
"Remove current room by replacing it with:"));
1208 ImGui::SetNextItemWidth(88.0f);
1211 &edit_value, 88.0f,
true);
1216 if (ImGui::IsItemHovered()) {
1218 tr(
"Replacement room must not already be in the table."));
1221 const bool replace_clicked =
1222 ImGui::Button(tr(
"Replace current##PitDamageReplaceCurrent"));
1224 if (replace_clicked) {
1230 ? absl::StrFormat(
"Room 0x%03X removed; slot now 0x%03X",
1232 : std::string(status.message());
1235 if (!membership.suggested_victim_room.has_value()) {
1236 ImGui::TextDisabled(tr(
"No existing pit-damage slot is available."));
1242 ImGui::TextDisabled(tr(
"Add current room by replacing listed room:"));
1243 ImGui::SetNextItemWidth(88.0f);
1251 if (ImGui::IsItemHovered()) {
1252 ImGui::SetTooltip(tr(
"This room will stop dealing pit damage."));
1255 const bool add_clicked =
1256 ImGui::Button(tr(
"Add current##PitDamageAddCurrent"));
1264 ? absl::StrFormat(
"Room 0x%03X added; replaced 0x%03X",
1266 : std::string(status.message());
1271 ImGui::TextDisabled(
1272 tr(
"Apply Current Room / Apply Loaded Rooms writes this via "
1273 "SaveAllPits when the Pits scope is enabled."));
1279 ImGui::TextDisabled(tr(
"No active room"));
1284 auto draw_blend_combo = [&](
const char* combo_id,
1288 const char* current_name =
1291 ImGui::SetNextItemWidth(-1);
1292 if (ImGui::BeginCombo(combo_id, current_name)) {
1293 for (
int mode_int = 0; mode_int <= 4; ++mode_int) {
1295 const char* mode_name =
1297 const bool selected = current_mode == mode;
1298 if (ImGui::Selectable(mode_name, selected)) {
1299 layer_manager.SetLayerBlendMode(layer_type, mode);
1302 ImGui::SetItemDefaultFocus();
1311 const char* combo_id;
1314 static constexpr BlendRow kBlendRows[] = {
1315 {
"BG1 Layout",
"##WorkbenchBlendBG1Layout",
1317 {
"BG1 Objects",
"##WorkbenchBlendBG1Objects",
1319 {
"BG2 Layout",
"##WorkbenchBlendBG2Layout",
1321 {
"BG2 Objects",
"##WorkbenchBlendBG2Objects",
1325 constexpr ImGuiTableFlags kBlendTableFlags =
1326 ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoPadOuterX;
1327 if (ImGui::BeginTable(
"##WorkbenchLayerBlend", 2, kBlendTableFlags)) {
1328 ImGui::TableSetupColumn(
"##label", ImGuiTableColumnFlags_WidthFixed, 92.0f);
1329 ImGui::TableSetupColumn(
"##combo", ImGuiTableColumnFlags_WidthStretch);
1330 for (
const auto& row : kBlendRows) {
1331 ImGui::TableNextRow();
1332 ImGui::TableNextColumn();
1333 ImGui::AlignTextToFramePadding();
1334 ImGui::TextUnformatted(row.label);
1335 ImGui::TableNextColumn();
1336 draw_blend_combo(row.combo_id, row.layer_type);
1369 if (
const auto* project = viewer.
project()) {
1378 constexpr const char* kPopupId =
"Dungeon Map##WorkbenchDungeonMapPopup";
1380 ImGui::SetNextWindowSize(ImVec2(660.0f, 520.0f), ImGuiCond_Appearing);
1381 ImGui::OpenPopup(kPopupId);
1385 bool popup_open =
true;
1386 if (ImGui::BeginPopupModal(kPopupId, &popup_open,
1387 ImGuiWindowFlags_NoSavedSettings)) {
1389 ImGui::CloseCurrentPopup();
1394 const ImVec2 map_size =
1395 ImVec2(std::max(360.0f, ImGui::GetContentRegionAvail().x),
1396 std::max(260.0f, ImGui::GetContentRegionAvail().y - 34.0f));
1397 if (ImGui::BeginChild(
"##WorkbenchDungeonMapBody", map_size,
false)) {
1402 ImGui::TextDisabled(tr(
"Dungeon map unavailable"));
1406 ImGui::CloseCurrentPopup();
1478 const std::string standalone_id = content->
GetId();
1494 const std::string standalone_id = content->
GetId();
1495 if (standalone_id.empty() ||
1510 return "custom_collision";
1512 return "water_fill";
1516 return "object_selector";
1524 return "room_graphics";
1539 return "Custom Collision";
1541 return "Water Fill";
1543 return "Minecart Tracks";
1545 return "Object Selector";
1547 return "Door Tools";
1549 return "Sprite Tools";
1551 return "Item Tools";
1553 return "Room Graphics";
1566 return "Room tag tools are not available.";
1568 return "Custom collision tools are not available.";
1570 return "Water fill tools are not available.";
1572 return "Minecart track tools are not available.";
1574 return "Object selector is not available.";
1576 return "Door tools are not available.";
1578 return "Sprite tools are not available.";
1580 return "Item tools are not available.";
1582 return "Room graphics tools are not available.";
1584 return "Palette tools are not available.";
1586 return "No Workbench tool selected.";
1588 return "Tool is not available.";
1595 const char* unavailable_message) {
1597 ImGui::TextDisabled(
"%s", unavailable_message);
1600 content->
Draw(
nullptr);
1670 static constexpr std::array<WorkbenchTool, 10> kTools = {
1678 const ImGuiStyle& style = ImGui::GetStyle();
1680 const char* window_action_label = active_tool_is_standalone
1681 ?
"Show window##WorkbenchToolPopOut"
1682 :
"Pop out##WorkbenchToolPopOut";
1683 const float pop_out_width =
1684 ImGui::CalcTextSize(window_action_label,
nullptr,
true).x +
1685 style.FramePadding.x * 2.0f;
1686 const float picker_width =
1687 std::max(1.0f, ImGui::GetContentRegionAvail().x - pop_out_width -
1688 style.ItemSpacing.x);
1689 const std::string picker_label = absl::StrFormat(
1693 picker_label.c_str(), ImVec2(picker_width, 0.0f));
1697 "Choose a Dungeon Workbench tool");
1699 if (picker_requested) {
1700 ImGui::OpenPopup(
"##WorkbenchToolPickerPopup");
1702 if (ImGui::BeginPopup(
"##WorkbenchToolPickerPopup")) {
1703 for (
size_t index = 0; index < kTools.size(); ++index) {
1705 ImGui::SeparatorText(tr(
"Edit"));
1706 }
else if (index == 4) {
1707 ImGui::SeparatorText(tr(
"Room"));
1708 }
else if (index == 6) {
1709 ImGui::SeparatorText(tr(
"Review"));
1715 ImGui::BeginDisabled();
1723 "Open a Dungeon Workbench tool");
1726 ImGui::EndDisabled();
1728 if (selected && available) {
1731 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) &&
1739 ImGui::SameLine(0.0f, style.ItemSpacing.x);
1740 const bool can_pop_out =
1743 ImGui::BeginDisabled();
1746 window_action_label, ImVec2(pop_out_width, 0.0f));
1750 "button",
"pop_out_tool",
1751 "Open the active Workbench tool in its standalone window");
1754 ImGui::EndDisabled();
1756 if (pop_out_requested && can_pop_out) {
1770 ImGui::TextDisabled(tr(
"%s is open in a standalone window."),
1772 ImGui::TextDisabled(tr(
"Choose another tool or select Show window."));
1780 const bool body_open = ImGui::BeginChild(
"##WorkbenchToolInspectorBody",
1781 ImVec2(0.0f, 0.0f),
false);
1789 float segment_height) {
1790 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1791 const float room_width =
1793 const float selection_width =
1795 const float tools_width =
1797 const float available_width = ImGui::GetContentRegionAvail().x;
1798 const bool stack = available_width < (room_width + selection_width +
1799 tools_width + spacing * 2.0f);
1800 auto draw_mode = [&](
const char* label,
const char* automation_key,
1802 const ImVec2 size(stack ? ImGui::GetContentRegionAvail().x : width,
1804 const bool pressed =
1809 "Switch Dungeon Workbench inspector mode");
1822 ImGui::SameLine(0.0f, spacing);
1824 draw_mode(
"Selection",
"mode_selection", selection_width,
1827 ImGui::SameLine(0.0f, spacing);
1839 const bool has_entity = interaction.HasEntitySelection();
1843 ImGui::Text(
"[%03X] %s", room_id,
1847 ImGui::TextDisabled(tr(
"No room selected"));
1850 ImGui::Dummy(ImVec2(0.0f, 2.0f));
1851 if (selected_objects > 0 || has_entity) {
1854 ImGui::BulletText(tr(
"Entity selected"));
1856 if (selected_objects > 0) {
1857 ImGui::BulletText(tr(
"%zu object%s selected"), selected_objects,
1858 selected_objects == 1 ?
"" :
"s");
1866 ImGui::TextDisabled(tr(
"Nothing selected"));
1878 const bool has_selection =
1925 const std::string room_label =
1928 : std::string(
"None");
1933 ImGui::AlignTextToFramePadding();
1934 ImGui::Text(tr(
"Room 0x%03X"), room_id);
1938 snprintf(buf,
sizeof(buf),
"0x%03X", room_id);
1939 ImGui::SetClipboardText(buf);
1941 if (ImGui::IsItemHovered()) {
1942 ImGui::SetTooltip(tr(
"Copy room ID (0x%03X) to clipboard"), room_id);
1945 if (
auto* rooms = viewer.
rooms();
1946 rooms && room_id < static_cast<int>(rooms->size())) {
1947 auto& objects = (*rooms)[room_id].GetTileObjects();
1948 if (!objects.empty()) {
1949 const auto selected_indices =
1951 int requested_object_index =
1952 selected_indices.size() == 1
1953 ?
static_cast<int>(selected_indices.front())
1955 ImGui::AlignTextToFramePadding();
1956 ImGui::TextUnformatted(tr(
"Object"));
1958 ImGui::SetNextItemWidth(74.0f);
1959 bool object_index_changed =
false;
1963 "##WorkbenchObjectIndex", ImGuiDataType_S32,
1964 &requested_object_index,
"%d", 0,
1965 {
reinterpret_cast<uintptr_t
>(rooms),
1966 static_cast<uint64_t
>(room_id)});
1968 "Select and locate a room object by index");
1970 if (object_index_changed) {
1971 const size_t target_index =
static_cast<size_t>(std::clamp(
1972 requested_object_index, 0,
static_cast<int>(objects.size()) - 1));
1975 objects[target_index].y());
1978 if (ImGui::IsItemHovered()) {
1979 ImGui::SetTooltip(tr(
"Select object index 0-%zu"),
1980 objects.size() - 1);
1985 ImGui::TextUnformatted(tr(
"Room: None"));
1988 bool room_dirty =
false;
1989 if (
auto* rooms = viewer.
rooms(); rooms && room_id >= 0) {
1990 if (
const auto* room = rooms->GetIfMaterialized(room_id)) {
1991 room_dirty = room->HasUnsavedChanges();
1995 ImGui::TextColored(theme.status_warning,
1997 }
else if (room_id >= 0) {
1998 ImGui::TextDisabled(
ICON_MD_CHECK " Room matches ROM buffer");
2007 std::string project_group_name =
2010 const char* group_name =
2011 project_group_name.empty() ? nullptr : project_group_name.c_str();
2015 group_name = cache_it->second.c_str();
2019 auto* rooms = viewer.
rooms();
2020 if (rooms && room_id <
static_cast<int>(rooms->size())) {
2022 (*rooms)[room_id].blockset());
2027 room_label.c_str());
2029 ImGui::TextDisabled(
"%s", room_label.c_str());
2032 ImGui::TextDisabled(
"%s", room_label.c_str());
2055 if (
auto* rooms = viewer.
rooms();
2056 rooms && room_id >= 0 && room_id < static_cast<int>(rooms->size())) {
2057 auto& room = (*rooms)[room_id];
2059 uint8_t layout_val = room.layout_id();
2060 uint8_t blockset_val = room.blockset();
2061 uint8_t floor1_val = room.floor1();
2062 uint8_t floor2_val = room.floor2();
2063 uint8_t palette_val = room.palette();
2064 uint8_t spriteset_val = room.spriteset();
2065 uint16_t message_val = room.message_id();
2066 uint8_t bg2_val =
static_cast<uint8_t
>(room.bg2());
2067 uint8_t effect_val =
static_cast<uint8_t
>(room.effect());
2068 uint8_t collision_val =
static_cast<uint8_t
>(room.collision());
2069 uint8_t tag1_val =
static_cast<uint8_t
>(room.tag1());
2070 uint8_t tag2_val =
static_cast<uint8_t
>(room.tag2());
2072 auto render_room_graphics = [&]() {
2073 if (room.rom() && room.rom()->is_loaded()) {
2074 room.RenderRoomGraphics();
2078 constexpr float kHexW = 54.0f;
2079 constexpr ImGuiTableFlags kHeaderFlags = ImGuiTableFlags_BordersInnerV |
2080 ImGuiTableFlags_RowBg |
2081 ImGuiTableFlags_NoPadOuterX;
2082 auto draw_label = [](
const char* label) {
2083 ImGui::AlignTextToFramePadding();
2084 ImGui::TextUnformatted(label);
2086 auto draw_byte_field = [&](
const char* label,
const char* id, uint8_t value,
2087 uint8_t max_value,
const std::string& tooltip,
2089 ImGui::TableNextColumn();
2091 ImGui::TableNextColumn();
2092 ImGui::SetNextItemWidth(kHexW);
2093 uint8_t edit_value = value;
2099 if (ImGui::IsItemHovered()) {
2100 ImGui::SetTooltip(
"%s", tooltip.c_str());
2103 auto draw_word_field = [&](
const char* label,
const char* id,
2104 uint16_t value, uint16_t max_value,
2105 const std::string& tooltip,
auto apply) {
2106 ImGui::TableNextColumn();
2108 ImGui::TableNextColumn();
2109 ImGui::SetNextItemWidth(kHexW + 12.0f);
2110 uint16_t edit_value = value;
2113 apply(std::min<uint16_t>(edit_value, max_value));
2115 if (ImGui::IsItemHovered()) {
2116 ImGui::SetTooltip(
"%s", tooltip.c_str());
2121 ImGui::BeginTable(
"##WorkbenchRoomHeader", 4, kHeaderFlags)) {
2122 ImGui::TableSetupColumn(
"L1", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2123 ImGui::TableSetupColumn(
"V1", ImGuiTableColumnFlags_WidthFixed, 66.0f);
2124 ImGui::TableSetupColumn(
"L2", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2125 ImGui::TableSetupColumn(
"V2", ImGuiTableColumnFlags_WidthStretch);
2127 ImGui::TableNextRow();
2128 draw_byte_field(
"Lay",
"##RoomHeaderLayout", layout_val, 0x07,
2129 "Layout (0-7)", [&](uint8_t value) {
2130 room.SetLayoutId(value);
2131 room.MarkLayoutDirty();
2132 render_room_graphics();
2134 draw_byte_field(
"Blk",
"##RoomHeaderBlockset", blockset_val, 0x51,
2135 "Blockset (0-51)", [&](uint8_t value) {
2136 room.SetBlockset(value);
2137 render_room_graphics();
2140 ImGui::TableNextRow();
2141 draw_byte_field(
"F1",
"##RoomHeaderFloor1", floor1_val, 0x0F,
2142 "BG1 floor graphics (0-F)", [&](uint8_t value) {
2143 room.set_floor1(value);
2144 render_room_graphics();
2146 draw_byte_field(
"F2",
"##RoomHeaderFloor2", floor2_val, 0x0F,
2147 "BG2 floor graphics (0-F)", [&](uint8_t value) {
2148 room.set_floor2(value);
2149 render_room_graphics();
2152 ImGui::TableNextRow();
2153 draw_byte_field(
"Pal",
"##RoomHeaderPalette", palette_val, 0x47,
2154 "Palette set (0-47)", [&](uint8_t value) {
2155 room.SetPalette(value);
2156 render_room_graphics();
2161 draw_byte_field(
"Spr",
"##RoomHeaderSpriteset", spriteset_val, 0x8F,
2162 "Sprite graphics set (0-8F)", [&](uint8_t value) {
2163 room.SetSpriteset(value);
2164 render_room_graphics();
2167 ImGui::TableNextRow();
2168 draw_word_field(
"Msg",
"##RoomHeaderMessage", message_val, 0x0FFF,
2169 "Dungeon message ID (0-FFF)",
2170 [&](uint16_t value) { room.SetMessageId(value); });
2171 draw_byte_field(
"BG2",
"##RoomHeaderBg2", bg2_val, 0x08,
2172 std::string(
"BG2 mode: ") + GetBg2ModeName(bg2_val),
2173 [&](uint8_t value) {
2175 render_room_graphics();
2178 ImGui::TableNextRow();
2179 const char* effect_name =
2181 draw_byte_field(
"FX",
"##RoomHeaderEffect", effect_val, 0x07,
2182 std::string(
"Effect: ") + effect_name,
2183 [&](uint8_t value) {
2185 render_room_graphics();
2188 "Coll",
"##RoomHeaderCollision", collision_val, 0x04,
2189 std::string(
"Collision: ") + GetCollisionName(collision_val),
2190 [&](uint8_t value) {
2194 ImGui::TableNextRow();
2195 draw_byte_field(
"Tag1",
"##RoomHeaderTag1", tag1_val, 0x40,
2197 [&](uint8_t value) {
2199 render_room_graphics();
2201 draw_byte_field(
"Tag2",
"##RoomHeaderTag2", tag2_val, 0x40,
2203 [&](uint8_t value) {
2205 render_room_graphics();
2213 ImGui::BeginTable(
"##WorkbenchRoomDestinations", 4, kHeaderFlags)) {
2214 ImGui::TableSetupColumn(
"L1", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2215 ImGui::TableSetupColumn(
"V1", ImGuiTableColumnFlags_WidthFixed, 66.0f);
2216 ImGui::TableSetupColumn(
"L2", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2217 ImGui::TableSetupColumn(
"V2", ImGuiTableColumnFlags_WidthStretch);
2219 ImGui::TableNextRow();
2220 draw_byte_field(
"Pit",
"##RoomHeaderPit", room.holewarp(), 0xFF,
2221 "Pit/holewarp destination room",
2222 [&](uint8_t value) { room.SetHolewarp(value); });
2223 draw_byte_field(
"St1",
"##RoomHeaderStair1", room.staircase_room(0), 0xFF,
2224 "Stair destination slot 1",
2225 [&](uint8_t value) { room.SetStaircaseRoom(0, value); });
2227 ImGui::TableNextRow();
2228 draw_byte_field(
"St2",
"##RoomHeaderStair2", room.staircase_room(1), 0xFF,
2229 "Stair destination slot 2",
2230 [&](uint8_t value) { room.SetStaircaseRoom(1, value); });
2231 draw_byte_field(
"St3",
"##RoomHeaderStair3", room.staircase_room(2), 0xFF,
2232 "Stair destination slot 3",
2233 [&](uint8_t value) { room.SetStaircaseRoom(2, value); });
2235 ImGui::TableNextRow();
2236 draw_byte_field(
"St4",
"##RoomHeaderStair4", room.staircase_room(3), 0xFF,
2237 "Stair destination slot 4",
2238 [&](uint8_t value) { room.SetStaircaseRoom(3, value); });
2239 draw_byte_field(
"P1",
"##RoomHeaderStairPlane1", room.staircase_plane(0),
2240 0x03,
"Stair 1 target layer/plane (0-3)",
2241 [&](uint8_t value) { room.SetStaircasePlane(0, value); });
2243 ImGui::TableNextRow();
2244 draw_byte_field(
"P2",
"##RoomHeaderStairPlane2", room.staircase_plane(1),
2245 0x03,
"Stair 2 target layer/plane (0-3)",
2246 [&](uint8_t value) { room.SetStaircasePlane(1, value); });
2247 draw_byte_field(
"P3",
"##RoomHeaderStairPlane3", room.staircase_plane(2),
2248 0x03,
"Stair 3 target layer/plane (0-3)",
2249 [&](uint8_t value) { room.SetStaircasePlane(2, value); });
2251 ImGui::TableNextRow();
2252 draw_byte_field(
"P4",
"##RoomHeaderStairPlane4", room.staircase_plane(3),
2253 0x03,
"Stair 4 target layer/plane (0-3)",
2254 [&](uint8_t value) { room.SetStaircasePlane(3, value); });
2255 ImGui::TableNextColumn();
2256 ImGui::TableNextColumn();
2261 ImGui::TextDisabled(tr(
"Room header unavailable"));
2268 ImGui::TextColored(theme.text_info, tr(
"Placement active"));
2271 interaction.mode_manager().CancelCurrentMode();
2282 const size_t obj_count = interaction.GetSelectionCount();
2283 const bool has_entity = interaction.HasEntitySelection();
2285 if (!has_entity && obj_count == 0) {
2287 ImGui::TextDisabled(tr(
"Click an object or entity to inspect"));
2292 if (obj_count > 0) {
2294 ImGui::TextColored(theme.text_primary,
ICON_MD_WIDGETS " %zu object%s",
2295 obj_count, obj_count == 1 ?
"" :
"s");
2296 if (obj_count == 1) {
2298 ImGui::TextDisabled(tr(
"Focused selection"));
2301 const auto indices = interaction.GetSelectedObjectIndices();
2304 if (indices.size() > 1 && room_id >= 0 && viewer.
rooms()) {
2305 auto& room = (*viewer.
rooms())[room_id];
2306 auto& objects = room.GetTileObjects();
2308 " Multi-selection");
2309 for (
size_t i = 0; i < indices.size() && i < 8; ++i) {
2310 size_t idx = indices[i];
2311 if (idx < objects.size()) {
2312 auto& obj = objects[idx];
2314 ImGui::BulletText(tr(
"0x%03X %s"), obj.id_, name.c_str());
2317 if (indices.size() > 8) {
2318 ImGui::TextDisabled(tr(
" ... and %zu more"), indices.size() - 8);
2324 auto& tile_handler = interaction.entity_coordinator().tile_handler();
2325 const std::vector<size_t> selection_copy(indices.begin(), indices.end());
2326 bool has_room_stream_object =
false;
2327 bool has_special_layer_object =
false;
2328 for (
const size_t index : selection_copy) {
2329 if (index >= objects.size()) {
2333 has_room_stream_object =
true;
2335 has_special_layer_object =
true;
2338 const bool has_editable_size =
2344 static int bulk_nudge_dx = 0;
2345 static int bulk_nudge_dy = 0;
2346 ImGui::TextDisabled(tr(
"Nudge (tiles)"));
2347 ImGui::PushButtonRepeat(
true);
2349 tile_handler.MoveObjects(room_id, selection_copy, 0, -1);
2352 tile_handler.MoveObjects(room_id, selection_copy, 0, 1);
2355 tile_handler.MoveObjects(room_id, selection_copy, -1, 0);
2358 tile_handler.MoveObjects(room_id, selection_copy, 1, 0);
2359 ImGui::PopButtonRepeat();
2361 ImGui::SetNextItemWidth(60);
2362 ImGui::DragInt(
"##BulkNudgeDx", &bulk_nudge_dx, 0.25f, -63, 63,
"Δx:%d");
2364 ImGui::SetNextItemWidth(60);
2365 ImGui::DragInt(
"##BulkNudgeDy", &bulk_nudge_dy, 0.25f, -63, 63,
"Δy:%d");
2367 if (ImGui::Button(tr(
"Apply##BulkNudgeApply")) &&
2368 (bulk_nudge_dx != 0 || bulk_nudge_dy != 0)) {
2369 tile_handler.MoveObjects(room_id, selection_copy, bulk_nudge_dx,
2376 if (has_room_stream_object && !has_special_layer_object) {
2377 ImGui::TextDisabled(tr(
"Object stream (set all)"));
2378 if (ImGui::SmallButton(tr(
"Primary##BulkPlacement0")))
2379 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2381 if (ImGui::SmallButton(tr(
"BG2 overlay##BulkPlacement1")))
2382 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2384 if (ImGui::SmallButton(tr(
"BG1 overlay##BulkPlacement2")))
2385 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 2);
2386 }
else if (has_special_layer_object && !has_room_stream_object) {
2387 ImGui::TextDisabled(tr(
"Special layer (set all)"));
2388 if (ImGui::SmallButton(tr(
"Upper (BG1)##BulkPlacement0")))
2389 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2391 if (ImGui::SmallButton(tr(
"Lower (BG2)##BulkPlacement1")))
2392 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2394 ImGui::TextDisabled(tr(
"Stored placement (mixed selection)"));
2395 if (ImGui::SmallButton(tr(
"Primary / Upper (BG1)##BulkPlacement0")))
2396 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2398 if (ImGui::SmallButton(tr(
"BG2 overlay / Lower (BG2)##BulkPlacement1")))
2399 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2403 ImGui::TextDisabled(tr(
"Stacking"));
2405 tile_handler.SendToFront(room_id, selection_copy);
2408 tile_handler.SendToBack(room_id, selection_copy);
2411 tile_handler.MoveForward(room_id, selection_copy);
2414 tile_handler.MoveBackward(room_id, selection_copy);
2417 ImGui::TextDisabled(tr(
"Size"));
2418 if (!has_editable_size) {
2419 ImGui::BeginDisabled();
2422 tile_handler.ResizeObjects(room_id, selection_copy, -1);
2424 if (ImGui::SmallButton(
ICON_MD_ADD "##BulkSizeInc"))
2425 tile_handler.ResizeObjects(room_id, selection_copy, 1);
2426 if (!has_editable_size) {
2427 ImGui::EndDisabled();
2432 (void)tile_handler.DuplicateObjects(room_id, selection_copy, 1, 1);
2436 {ImGuiCol_Button, theme.status_error},
2437 {ImGuiCol_ButtonHovered,
2438 ImVec4(theme.status_error.x + 0.1f, theme.status_error.y + 0.05f,
2439 theme.status_error.z + 0.05f, 1.0f)},
2442 ImGui::OpenPopup(
"##BulkDeleteConfirm");
2445 if (ImGui::BeginPopup(
"##BulkDeleteConfirm")) {
2448 selection_copy.size(), selection_copy.size() == 1 ?
"" :
"s");
2450 if (ImGui::Button(tr(
"Cancel##BulkDelCancel"))) {
2451 ImGui::CloseCurrentPopup();
2456 {ImGuiCol_Button, theme.status_error},
2459 tile_handler.DeleteObjects(room_id, selection_copy);
2460 ImGui::CloseCurrentPopup();
2468 if (indices.size() == 1 && room_id >= 0 && viewer.
rooms()) {
2469 auto& room = (*viewer.
rooms())[room_id];
2470 auto& objects = room.GetTileObjects();
2471 const size_t idx = indices.front();
2472 if (idx < objects.size()) {
2473 auto& obj = objects[idx];
2477 int displayed_layer = obj.GetLayerValue();
2478 const int pixel_x = obj.x_ * 8;
2479 const int pixel_y = obj.y_ * 8;
2484 ImGui::TextColored(theme.text_primary,
"%s", obj_name.c_str());
2485 ImGui::TextDisabled(tr(
"%s (Type %d) #%zu in list"),
2486 GetObjectCategory(obj.id_), subtype, idx);
2489 constexpr ImGuiTableFlags kPropsFlags = ImGuiTableFlags_BordersInnerV |
2490 ImGuiTableFlags_RowBg |
2491 ImGuiTableFlags_NoPadOuterX;
2492 if (ImGui::BeginTable(
"##SelObjProps", 2, kPropsFlags)) {
2493 ImGui::TableSetupColumn(
"Prop", ImGuiTableColumnFlags_WidthFixed,
2495 ImGui::TableSetupColumn(
"Val", ImGuiTableColumnFlags_WidthStretch);
2499 uint16_t obj_id =
static_cast<uint16_t
>(obj.id_ & 0x0FFF);
2504 interaction.SetObjectId(idx,
static_cast<int16_t
>(obj_id));
2512 ImGui::SetNextItemWidth(60);
2514 ImGui::DragInt(
"##SelObjX", &pos_x, 0.1f, 0, 63,
"X:%d");
2518 "Selected room object X coordinate");
2521 ImGui::SetNextItemWidth(60);
2523 ImGui::DragInt(
"##SelObjY", &pos_y, 0.1f, 0, 63,
"Y:%d");
2527 "Selected room object Y coordinate");
2529 if (x_changed || y_changed) {
2530 int delta_x = pos_x - obj.x_;
2531 int delta_y = pos_y - obj.y_;
2532 interaction.entity_coordinator().tile_handler().MoveObjects(
2533 room_id, {idx}, delta_x, delta_y);
2537 uint8_t requested_size = obj.size_;
2539 interaction.SetObjectSize(idx, requested_size);
2543 uses_room_stream ?
"Stream" :
"Layer", [&]() {
2544 int layer = displayed_layer;
2545 const char* stream_names[] = {
"Primary",
"BG2 overlay",
2547 const char* special_layer_names[] = {
"Upper layer (BG1)",
2548 "Lower layer (BG2)"};
2549 ImGui::SetNextItemWidth(-1);
2551 "##SelObjLayer", &layer,
2552 uses_room_stream ? stream_names : special_layer_names,
2553 uses_room_stream ? IM_ARRAYSIZE(stream_names)
2554 : IM_ARRAYSIZE(special_layer_names))) {
2555 const int max_layer = uses_room_stream ? 2 : 1;
2556 layer = std::clamp(layer, 0, max_layer);
2557 if (interaction.SetObjectLayer(
2560 displayed_layer = layer;
2565 ImGui::TextDisabled(
2566 "%s", uses_room_stream ? GetObjectStreamName(displayed_layer)
2567 : GetSpecialLayerName(displayed_layer));
2572 ImGui::TextDisabled(
"(%d, %d)", pixel_x, pixel_y);
2582 if (has_entity && room_id >= 0 && viewer.
rooms()) {
2583 const auto sel = interaction.GetSelectedEntity();
2584 auto& room = (*viewer.
rooms())[room_id];
2586 " Entity Selection");
2590 const auto& doors = room.GetDoors();
2591 if (sel.index < doors.size()) {
2592 const auto& door = doors[sel.index];
2598 ImGui::TextDisabled(tr(
"Direction: %s Position: 0x%02X"),
2599 dir_name.c_str(), door.position);
2601 auto [tile_x, tile_y] = door.GetTileCoords();
2602 auto [pixel_x, pixel_y] = door.GetPixelCoords();
2603 ImGui::TextDisabled(tr(
"Tile: (%d, %d) Pixel: (%d, %d)"), tile_x,
2604 tile_y, pixel_x, pixel_y);
2609 const auto& sprites = room.GetSprites();
2610 if (sel.index < sprites.size()) {
2611 const auto& sprite = sprites[sel.index];
2615 sprite_name.c_str());
2616 ImGui::TextDisabled(tr(
"ID: 0x%02X Subtype: %d Layer: %d"),
2617 sprite.id(), sprite.subtype(), sprite.layer());
2618 ImGui::TextDisabled(tr(
"Pos: (%d, %d) Pixel: (%d, %d)"), sprite.x(),
2619 sprite.y(), sprite.x() * 16, sprite.y() * 16);
2622 if (sprite.subtype() == 0x07 && sprite.id() >= 0x01 &&
2623 sprite.id() <= 0x1A) {
2625 ImGui::TextColored(theme.text_warning_yellow,
2627 overlord_name.c_str());
2633 const auto& items = room.GetPotItems();
2634 if (sel.index < items.size()) {
2635 const auto& pot_item = items[sel.index];
2636 const char* item_name = GetPotItemName(pot_item.item);
2640 ImGui::TextDisabled(tr(
"Item ID: 0x%02X Raw Pos: 0x%04X"),
2641 pot_item.item, pot_item.position);
2642 ImGui::TextDisabled(tr(
"Pixel: (%d, %d) Tile: (%d, %d)"),
2643 pot_item.GetPixelX(), pot_item.GetPixelY(),
2644 pot_item.GetTileX(), pot_item.GetTileY());
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
void Draw(bool *p_open) override
Draw the panel content.
DungeonRoomStore * rooms() const
std::optional< int > DrawConnectedRoomMatrix(int center_room_id)
int current_room_id() const
float ConnectedCanvasScale() const
DungeonObjectInteraction & object_interaction()
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
ImVec2 GetConnectedContentSize(int center_room_id)
const project::YazeProject * project() const
void ScrollToTile(int tile_x, int tile_y)
void DrawDungeonCanvas(int room_id)
WindowContent for displaying multiple rooms in a spatial dungeon layout.
void SetSelectedObjects(const std::vector< size_t > &indices)
std::vector< size_t > GetSelectedObjectIndices() const
InteractionModeManager & mode_manager()
size_t GetSelectionCount() const
Handles room and entrance selection UI.
ImVector< int > & mutable_active_rooms()
void DrawRoomBrowser(RoomSelectionIntent single_click_intent=RoomSelectionIntent::kFocusInWorkbench)
void DrawEntranceBrowser()
static const char * GetBlocksetGroupName(uint8_t blockset)
static void Draw(const DungeonStatusBarState &state)
static DungeonStatusBarState BuildState(const DungeonCanvasViewer &viewer, const char *tool_mode, bool room_dirty)
void DrawInspector(DungeonCanvasViewer &viewer, bool compact)
bool room_dungeon_cache_built_
void DrawInspectorShelfSelection(DungeonCanvasViewer &viewer)
std::function< void(int)> on_room_selected_
void SetAllSaveFlags(bool value)
const char * GetActiveToolIdForTesting() const
bool IsStandaloneToolOpen(WorkbenchTool tool) const
DungeonWorkbenchContent(DungeonRoomSelector *room_selector, int *current_room_id, std::function< void(int)> on_room_selected, std::function< void(int, RoomSelectionIntent)> on_room_selected_with_intent, std::function< void(int)> on_save_room, std::function< void()> on_save_all_rooms, std::function< DungeonCanvasViewer *()> get_viewer, std::function< DungeonCanvasViewer *(int)> get_compare_viewer, std::function< const std::deque< int > &()> get_recent_rooms, std::function< void(int)> forget_recent_room, std::function< void(bool)> set_workflow_mode, Rom *rom=nullptr)
bool pit_damage_status_error_
void FocusEntranceBrowser()
std::function< bool(const std::string &) is_standalone_tool_open_)
std::function< bool()> can_redo_
int GetPriority() const override
Get display priority for menu ordering.
CustomCollisionPanel * custom_collision_panel_
DungeonWorkbenchPitDamageControlRects pit_damage_control_rects_
SidebarMode sidebar_mode_
uint16_t pit_damage_victim_room_id_
WorkbenchTool active_tool_
void OpenTool(WorkbenchTool tool)
bool IsWorkbenchToolAvailable(WorkbenchTool tool) const
WaterFillPanel * water_fill_panel_
void DrawInspectorHeader(float button_size, bool compact)
void FocusSelectionInspector()
void DrawInspectorCompactSummary(DungeonCanvasViewer &viewer)
void DrawSidebarModeTabs(bool stacked, float segment_height)
std::function< void()> on_redo_
void DrawInspectorPrimarySelector(float segment_height)
WindowContent * object_selector_content_
void DrawSidebarPane(float width, float height, float button_size, bool compact)
DungeonMapPanel * GetEmbeddedDungeonMap(DungeonCanvasViewer &viewer)
void DrawSidebarContent()
InspectorMode inspector_mode_
void DrawInspectorPane(float width, float height, float button_size, bool compact, DungeonCanvasViewer *viewer)
void DrawInspectorShelfRoom(DungeonCanvasViewer &viewer)
InspectorMode inspector_mode_before_tools_
WindowContent * GetWorkbenchToolContent(WorkbenchTool tool) const
const char * GetInspectorModeIdForTesting() const
WindowContent * sprite_editor_content_
std::function< int()> undo_depth_
std::function< void()> open_keyboard_shortcuts_
std::string GetEditorCategory() const override
Editor category this panel belongs to.
void OpenRoomGraphicsTool()
std::function< std::string()> undo_desc_
void FocusRoomInspector()
std::function< bool()> can_undo_
RoomTagEditorPanel * room_tag_panel_
void DrawPitDamageControls(int room_id)
void DrawWorkbenchTool(DungeonCanvasViewer &viewer, WorkbenchTool tool)
DungeonRoomSelector * room_selector_
void DrawSidebarHeader(float button_size, bool compact)
std::function< void()> on_undo_
const char * GetWorkbenchToolShortLabel(WorkbenchTool tool) const
std::function< std::string()> redo_desc_
void DrawDungeonMapPopup(DungeonCanvasViewer &viewer)
std::unique_ptr< DungeonMapPanel > embedded_dungeon_map_
char compare_search_buf_[64]
void DrawInspectorToolPanel(DungeonCanvasViewer &viewer)
WindowContent * item_editor_content_
std::function< void(int, RoomSelectionIntent)> on_room_selected_with_intent_
void SetEmbeddedEditorPanels(WindowContent *object_selector, WindowContent *door_editor, WindowContent *sprite_editor, WindowContent *item_editor, WindowContent *room_graphics, WindowContent *palette_editor)
MinecartTrackEditorPanel * minecart_track_panel_
void SetEmbeddedToolPanels(RoomTagEditorPanel *room_tags, CustomCollisionPanel *custom_collision, WaterFillPanel *water_fill, MinecartTrackEditorPanel *minecart_tracks)
void DrawCanvasPane(float width, float height, DungeonCanvasViewer *primary_viewer)
std::function< void(DungeonCanvasViewer &) on_primary_canvas_drawn_)
const char * GetWorkbenchToolId(WorkbenchTool tool) const
const char * GetWorkbenchToolUnavailableMessage(WorkbenchTool tool) const
bool inspector_selection_was_active_
std::function< void(int)> on_save_room_
WindowContent * palette_editor_content_
WindowContent * room_graphics_content_
std::string pit_damage_status_message_
std::string GetId() const override
Unique identifier for this panel.
void DrawSplitView(DungeonCanvasViewer &primary_viewer)
void OpenObjectSelectorTool()
std::function< zelda3::PitDamageTable *()> get_pit_damage_table_
void CloseToolInspector()
std::function< void()> on_save_all_rooms_
std::function< void(bool)> set_workflow_mode_
void ShowConnectedGraph()
std::string GetIcon() const override
Material Design icon for this panel.
std::function< DungeonCanvasViewer *(int)> get_compare_viewer_
std::unordered_map< int, std::string > room_dungeon_cache_
bool compact_inspector_detail_requested_
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
void RequestDungeonMapPopup()
std::function< void(bool)> on_inspector_side_changed_
~DungeonWorkbenchContent() override
void DrawApplyScopeControls(int room_id)
uint16_t pit_damage_replacement_room_id_
void Draw(bool *p_open) override
Draw the panel content.
void OpenCustomCollisionTool()
DungeonWorkbenchLayoutState layout_state_
bool open_dungeon_map_popup_
std::function< bool(const std::string &) open_and_focus_standalone_tool_)
bool IsToolInspectorActiveForTesting() const
std::function< void(int)> forget_recent_room_
std::function< const std::deque< int > &()> get_recent_rooms_
void DrawInspectorToolPicker()
void DrawInspectorShelf(DungeonCanvasViewer &viewer, bool compact)
void BuildRoomDungeonCache()
void DrawLayerCompositingControls(DungeonCanvasViewer &viewer, int room_id)
std::function< DungeonCanvasViewer *()> get_viewer_
WindowContent * door_editor_content_
const char * GetModeName() const
Get mode name for debugging/UI.
bool IsPlacementActive() const
Check if any placement mode is active.
void Draw(bool *p_open) override
Draw the panel content.
WindowContent showing all room tag slots and their usage across rooms.
void SetCurrentRoomId(int room_id)
void Draw(bool *p_open) override
Draw the panel content.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
void Draw(bool *p_open) override
Draw the panel content.
Base interface for all logical window content components.
virtual void Draw(bool *p_open)=0
Draw the panel content.
virtual std::string GetId() const =0
Unique identifier for this panel.
void ApplyScaleSnapshot(const CanvasConfig &snapshot)
CanvasConfig & GetConfig()
static float GetTouchSafeWidgetHeight()
static bool BeginContentChild(const char *id, const ImVec2 &min_size, bool border=false, ImGuiWindowFlags flags=0)
static void EndContentChild()
static void PropertyRow(const char *label, std::function< void()> widget_callback)
RAII guard for ImGui style colors.
RAII guard for ImGui style vars.
Dungeon Room Entrance or Spawn Point.
static const char * GetBlendModeName(LayerBlendMode mode)
Get blend mode name.
LayerBlendMode GetLayerBlendMode(LayerType layer) const
zelda3_bg2_effect
Background layer 2 effects.
#define ICON_MD_SUMMARIZE
#define ICON_MD_VIEW_QUILT
#define ICON_MD_ALT_ROUTE
#define ICON_MD_VIEW_LIST
#define ICON_MD_ARROW_FORWARD
#define ICON_MD_SWAP_HORIZ
#define ICON_MD_COMPARE_ARROWS
#define ICON_MD_OPEN_WITH
#define ICON_MD_OPEN_IN_FULL
#define ICON_MD_FLIP_TO_FRONT
#define ICON_MD_ARROW_DOWNWARD
#define ICON_MD_MORE_HORIZ
#define ICON_MD_KEYBOARD_ARROW_DOWN
#define ICON_MD_DOOR_FRONT
#define ICON_MD_CHEVRON_LEFT
#define ICON_MD_ARROW_UPWARD
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_KEYBOARD_ARROW_UP
#define ICON_MD_ARROW_BACK
#define ICON_MD_SELECT_ALL
#define ICON_MD_CONTENT_COPY
#define ICON_MD_INVENTORY_2
#define ICON_MD_VIEW_SIDEBAR
#define ICON_MD_CHEVRON_RIGHT
#define ICON_MD_FLIP_TO_BACK
#define ICON_MD_WORKSPACES
#define ICON_MD_CROP_FREE
#define ICON_MD_ARROW_DROP_DOWN
const AgentUITheme & GetTheme()
constexpr float kCompactLeftSidebarScale
const char * GetObjectStreamName(int layer_value)
const char * GetCollisionName(int value)
float GetCompactSidebarWidth(bool right_sidebar, float min_sidebar_width)
DungeonWorkbenchTestRect CaptureLastItemRectForTesting()
const char * GetPotItemName(uint8_t item)
constexpr float kCompactLeftSidebarMinWidth
constexpr float kCompactRightSidebarMinWidth
const char * GetSpecialLayerName(int layer_value)
const char * GetBg2ModeName(int value)
constexpr float kCompactRightSidebarScale
float ClampWorkbenchPaneWidth(float desired_width, float min_width, float max_width)
const char * GetObjectCategory(int object_id)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
std::string GetDungeonNameForRoom(const project::YazeProject *project, int room_id)
bool BeginInspectorSection(const char *label, bool default_open)
void DrawPaneHeader(const char *table_id, const char *icon, const char *title, const char *compact_title, const char *subtitle, bool compact, float action_width, const std::function< void()> &draw_actions)
float CalcIconButtonWidth(const char *icon, float button_height)
bool DrawObjectSizeControls(const zelda3::RoomObject &object, uint8_t *requested_size)
bool DrawActionButton(const char *label, const ImVec2 &size)
bool HasEditableRoomObjectSize(std::span< const zelda3::RoomObject > objects, std::span< const size_t > selected_indices)
void DrawInspectorSectionHeader(const char *label)
bool DrawHeaderIconAction(const char *id, const char *icon, float button_size, const char *tooltip, bool active=false)
constexpr const char * kConnected
Editors are the view controllers for the application.
DungeonWorkbenchPaneLayout ResolveDungeonWorkbenchPaneLayout(float total_width, float min_canvas_width, float min_sidebar_width, float splitter_width, float stored_left_width, float stored_right_width, bool want_left, bool want_right)
PitDamageMembershipState BuildPitDamageMembershipState(const zelda3::PitDamageTable *table, int room_id, uint16_t replacement_fallback, uint16_t victim_fallback)
bool ResolveCompactInspectorDetailRequest(bool compact, bool detail_requested)
absl::Status RemoveCurrentRoomFromPitDamage(zelda3::PitDamageTable *table, uint16_t current_room_id, uint16_t replacement_room_id)
RoomSelectionIntent
Intent for room selection in the dungeon editor.
DungeonWorkbenchResponsiveLayout ResolveDungeonWorkbenchResponsiveLayout(float total_width, float min_canvas_width, float min_sidebar_width, float splitter_width, bool want_left, bool want_right)
DungeonWorkbenchToolRequestTarget
bool DrawDungeonWorkbenchVerticalSplitter(const char *id, float height, float *pane_width, float min_width, float max_width, bool resize_from_left_edge, float collapse_threshold)
DungeonWorkbenchToolRequestTarget ResolveDungeonWorkbenchToolRequestTarget(bool standalone_window_open)
absl::Status AddCurrentRoomToPitDamage(zelda3::PitDamageTable *table, uint16_t current_room_id, uint16_t victim_room_id)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
std::string GetRoomTagLabel(int id)
Convenience function to get a room tag label.
LayerBlendMode
Layer blend modes for compositing.
std::string GetSpriteLabel(int id)
Convenience function to get a sprite label.
int GetObjectSubtype(int object_id)
std::string GetOverlordLabel(int id)
Convenience function to get an overlord label.
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
LayerType
Layer types for the 4-way visibility system.
bool UsesRoomObjectStream(const RoomObject &object)
std::string GetObjectName(int object_id)
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
constexpr int kNumberOfRooms
const std::string RoomEffect[8]
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
bool show_connected_canvas_view
bool show_right_inspector
DungeonWorkbenchResponsiveLayout responsive
DungeonWorkbenchTestRect replace_current
DungeonWorkbenchTestRect add_current
static constexpr float kStatusBarHeight
static constexpr float kContentMinHeightCanvas
static constexpr float kSplitterWidth
static constexpr float kContentMinWidthSidebar
Modern project structure with comprehensive settings consolidation.