14#include "absl/strings/str_format.h"
40#include "imgui/imgui.h"
58 if (object_id < 0x100)
60 if (object_id < 0x200)
62 if (object_id >= 0xF80)
68 switch (layer_value) {
81 switch (layer_value) {
83 return "Upper layer (BG1)";
85 return "Lower layer (BG2)";
92 static constexpr const char* kNames[] = {
93 "Off",
"Parallax",
"Dark",
"On top",
"Translucent",
94 "Addition",
"Normal",
"Transparent",
"Dark room"};
95 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
96 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
100 static constexpr const char* kNames[] = {
"One",
"Both",
"Both + Scroll",
101 "Moving Floor",
"Moving Water"};
102 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
103 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
108 static const char* kNames[] = {
109 "Nothing",
"Green Rupee",
"Rock",
"Bee",
110 "Heart (4)",
"Bomb (4)",
"Heart",
"Blue Rupee",
111 "Key",
"Arrow (5)",
"Bomb (1)",
"Heart",
112 "Magic (Small)",
"Full Magic",
"Cucco",
"Green Soldier",
113 "Bush Stal",
"Blue Soldier",
"Landmine",
"Heart",
114 "Fairy",
"Heart",
"Nothing (22)",
"Hole",
115 "Warp",
"Staircase",
"Bombable",
"Switch",
117 constexpr size_t kCount =
sizeof(kNames) /
sizeof(kNames[0]);
118 return item < kCount ? kNames[item] :
"Unknown";
123 return std::clamp(desired_width, min_width, std::max(min_width, max_width));
142 const ImVec2 min = ImGui::GetItemRectMin();
143 const ImVec2 max = ImGui::GetItemRectMax();
154 float total_width,
float min_canvas_width,
float min_sidebar_width,
155 float splitter_width,
bool want_left,
bool want_right) {
160 auto required_width = [&](
bool left,
bool right,
bool compact_left,
161 bool compact_right) {
162 float required = min_canvas_width;
164 left ? (compact_left ? GetCompactSidebarWidth(
false, min_sidebar_width)
168 right ? (compact_right ? GetCompactSidebarWidth(
true, min_sidebar_width)
172 required += splitter_width;
175 required += splitter_width;
207 float total_width,
float min_canvas_width,
float min_sidebar_width,
208 float splitter_width,
float stored_left_width,
float stored_right_width,
209 bool want_left,
bool want_right) {
212 total_width, min_canvas_width, min_sidebar_width, splitter_width,
213 want_left, want_right);
215 const float compact_left_width =
216 GetCompactSidebarWidth(
false, min_sidebar_width);
217 const float compact_right_width =
218 GetCompactSidebarWidth(
true, min_sidebar_width);
231 : stored_right_width)
234 const float max_left_width =
235 total_width - right_width - min_canvas_width -
238 const float max_right_width =
239 total_width - left_width - min_canvas_width -
243 left_width = ClampWorkbenchPaneWidth(
250 right_width = ClampWorkbenchPaneWidth(
257 float center_width = total_width - left_width - right_width;
259 center_width -= splitter_width;
262 center_width -= splitter_width;
264 if (center_width < min_canvas_width) {
265 float deficit = min_canvas_width - center_width;
269 left_width -= shrink;
275 right_width -= shrink;
278 center_width = std::max(
279 1.0f, total_width - left_width - right_width -
292 std::function<
void(
int)> on_room_selected,
294 std::function<
void(
int)> on_save_room,
295 std::function<
void()> on_save_all_rooms,
298 std::function<
const std::deque<int>&()> get_recent_rooms,
299 std::function<
void(
int)> forget_recent_room,
300 std::function<
void(
bool)> set_workflow_mode,
Rom* rom)
301 : room_selector_(room_selector),
302 current_room_id_(current_room_id),
303 on_room_selected_(std::move(on_room_selected)),
304 on_room_selected_with_intent_(std::move(on_room_selected_with_intent)),
305 on_save_room_(std::move(on_save_room)),
306 on_save_all_rooms_(std::move(on_save_all_rooms)),
307 get_viewer_(std::move(get_viewer)),
308 get_compare_viewer_(std::move(get_compare_viewer)),
309 get_recent_rooms_(std::move(get_recent_rooms)),
310 forget_recent_room_(std::move(forget_recent_room)),
311 set_workflow_mode_(std::move(set_workflow_mode)),
317 return "dungeon.workbench";
320 return "Dungeon Workbench";
444 float button_size,
bool compact) {
445 const bool sidebar_open = ImGui::BeginChild(
"##DungeonWorkbenchSidebar",
446 ImVec2(width, height),
true);
456 const bool can_open_overview =
true;
457 const float collapse_w =
462 const float spacing = ImGui::GetStyle().ItemSpacing.x;
463 const float header_width = ImGui::GetContentRegionAvail().x;
464 const bool stack_mode_switch = compact && header_width < 220.0f;
465 const float action_cluster_w =
466 collapse_w + (can_open_overview ? (spacing + menu_w) : 0.0f);
470 "Browse",
nullptr, compact, action_cluster_w, [&]() {
471 if (can_open_overview) {
474 "Open room review tools",
true)) {
475 ImGui::OpenPopup(
"##WorkbenchSidebarQuickActions");
477 if (ImGui::BeginPopup(
"##WorkbenchSidebarQuickActions")) {
490 "Collapse navigation pane")) {
495 ImGui::Dummy(ImVec2(0.0f, 2.0f));
501 float segment_height) {
506 const float spacing = ImGui::GetStyle().ItemSpacing.x;
507 const ImVec2 button_size(segment_height, segment_height);
513 if (ImGui::IsItemHovered()) {
514 ImGui::SetTooltip(tr(
"Rooms"));
516 ImGui::SameLine(0.0f, spacing);
521 if (ImGui::IsItemHovered()) {
522 ImGui::SetTooltip(tr(
"Entrances"));
528 ImGui::TextDisabled(tr(
"Room navigation unavailable"));
532 ImGui::PushID(
"WorkbenchSidebarMode");
549 ImGui::TextDisabled(
ICON_MD_INFO " Load a ROM to edit dungeon rooms.");
553 ImGui::TextColored(theme.text_error_red, tr(
"Dungeon Workbench not wired"));
561 const float total_w = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
565 const float min_sidebar_w =
567 const float min_canvas_w = std::max(420.0f, min_sidebar_w + 96.0f);
570 total_w, min_canvas_w, min_sidebar_w, splitter_w,
607 const float total_h = std::max(ImGui::GetContentRegionAvail().y, 1.0f);
627 ImGui::SameLine(0.0f, 0.0f);
629 "##DungeonWorkbenchLeftSplitter", total_h,
631 total_w - left_w - min_canvas_w - (show_left ? splitter_w : 0.0f),
635 ImGui::SameLine(0.0f, 0.0f);
639 ImGui::SameLine(0.0f, 0.0f);
641 "##DungeonWorkbenchRightSplitter", total_h,
643 total_w - right_w - min_canvas_w -
644 (show_right ? splitter_w : 0.0f),
648 ImGui::SameLine(0.0f, 0.0f);
658 ImGui::SameLine(0.0f, 0.0f);
660 "##DungeonWorkbenchLeftSplitter", total_h,
662 total_w - right_w - min_canvas_w -
663 (show_right ? splitter_w : 0.0f),
670 ImGui::SameLine(0.0f, 0.0f);
675 ImGui::SameLine(0.0f, 0.0f);
677 "##DungeonWorkbenchRightSplitter", total_h,
679 total_w - left_w - min_canvas_w - (show_left ? splitter_w : 0.0f),
683 ImGui::SameLine(0.0f, 0.0f);
690 if (primary_viewer) {
697 bool left_sidebar_visible) {
698 const bool canvas_open = ImGui::BeginChild(
"##DungeonWorkbenchCanvas",
699 ImVec2(width, height),
false);
701 if (primary_viewer) {
702 const bool show_recent_tabs =
704 if (show_recent_tabs) {
716 const float status_bar_reserved =
718 ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
720 ImGui::GetStyle().ItemSpacing.y;
723 ImGuiWindowFlags body_flags = 0;
724 if (connected_mode) {
727 const ImVec2 content_size =
730 ImGui::SetNextWindowContentSize(
731 ImVec2(content_size.x * scale, content_size.y * scale));
732 body_flags |= ImGuiWindowFlags_HorizontalScrollbar |
733 ImGuiWindowFlags_NoScrollWithMouse;
735 const bool body_open = ImGui::BeginChild(
736 "##DungeonCanvasBody", ImVec2(0.0f, -status_bar_reserved),
false,
739 if (connected_mode) {
753 const char* tool_mode =
755 bool room_dirty =
false;
756 if (
auto* rooms = primary_viewer->
rooms();
759 room_dirty = room->HasUnsavedChanges();
767 status.workflow_primary =
true;
773 static std::string s_undo_desc;
775 status.undo_desc = s_undo_desc.empty() ? nullptr : s_undo_desc.c_str();
778 static std::string s_redo_desc;
780 status.redo_desc = s_redo_desc.empty() ? nullptr : s_redo_desc.c_str();
788 ImGui::TextDisabled(tr(
"No active viewer"));
798 const size_t object_count = interaction.GetSelectionCount();
799 const bool has_entity = interaction.HasEntitySelection();
800 if (object_count == 0 && !has_entity) {
808 ImGuiStyleVar_FramePadding,
809 ImVec2(std::max(4.0f, ImGui::GetStyle().FramePadding.x),
810 std::max(3.0f, ImGui::GetStyle().FramePadding.y - 1.0f)));
812 ImGuiStyleVar_ItemSpacing,
813 ImVec2(std::max(ImGui::GetStyle().ItemSpacing.x, 4.0f),
814 std::max(3.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
816 const float spacing = ImGui::GetStyle().ItemSpacing.x;
817 bool first_button =
true;
818 auto start_action = [&](
const char* label) {
819 const float button_width = ImGui::CalcTextSize(label).x +
820 (ImGui::GetStyle().FramePadding.x * 2.0f) + 8.0f;
822 const float next_x = ImGui::GetItemRectMax().x + spacing + button_width;
824 ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;
825 if (next_x <= max_x) {
826 ImGui::SameLine(0.0f, spacing);
829 first_button =
false;
832 auto draw_action = [&](
const char* label,
bool enabled,
833 const auto& on_press) {
836 ImGui::BeginDisabled();
842 ImGui::EndDisabled();
851 const char* local_tool_label =
nullptr;
853 if (object_count == 0 && has_entity) {
854 switch (selection.
type) {
872 const bool can_copy_selection = snapshot.
object_count > 0 ||
876 [&]() { interaction.HandleCopySelected(); });
878 [&]() { interaction.HandlePasteObjects(); });
880 [&]() { interaction.HandleDeleteSelected(); });
882 interaction.ClearSelection();
883 interaction.ClearEntitySelection();
887 if (local_tool_label !=
nullptr) {
888 draw_action(local_tool_label,
true, [&]() {
OpenTool(local_tool); });
891 ImGui::Dummy(ImVec2(0.0f, 2.0f));
895 float button_size,
bool compact,
897 const bool inspector_open = ImGui::BeginChild(
"##DungeonWorkbenchInspector",
898 ImVec2(width, height),
true);
899 if (inspector_open) {
904 ImGui::TextDisabled(tr(
"No active viewer"));
915 const char* collapse_icon =
917 const float collapse_w =
921 const float spacing = ImGui::GetStyle().ItemSpacing.x;
922 const float action_cluster_w = swap_w + spacing + collapse_w;
925 "##DungeonWorkbenchInspectorHeader",
ICON_MD_TUNE,
"Inspect",
"Inspect",
926 nullptr, compact, action_cluster_w, [&]() {
930 :
"Move inspector to the left side "
931 "(ZScream layout)")) {
937 ImGui::SameLine(0.0f, spacing);
940 "Collapse inspector")) {
945 ImGui::Dummy(ImVec2(0.0f, 2.0f));
957 rooms = viewer->rooms();
961 if (recent.empty()) {
966 std::vector<int> recent_ids(recent.begin(), recent.end());
967 std::vector<int> to_forget;
969 constexpr ImGuiTabBarFlags kFlags = ImGuiTabBarFlags_AutoSelectNewTabs |
970 ImGuiTabBarFlags_FittingPolicyScroll |
971 ImGuiTabBarFlags_TabListPopupButton;
974 const ImVec2 frame_pad = ImGui::GetStyle().FramePadding;
976 const float extra_y = is_touch ? 6.0f : 1.0f;
977 const float extra_x = is_touch ? 4.0f : 0.0f;
979 ImGuiStyleVar_FramePadding,
980 ImVec2(frame_pad.x + extra_x, frame_pad.y + extra_y));
985 for (
int room_id : recent_ids) {
987 const ImGuiTabItemFlags tab_flags =
989 const auto room_name =
991 const bool room_dirty =
995 if (room_name.empty() || room_name ==
"Unknown") {
996 snprintf(tab_label,
sizeof(tab_label),
"%03X%s##recent_%03X", room_id,
997 room_dirty ?
"*" :
"", room_id);
999 snprintf(tab_label,
sizeof(tab_label),
"%03X%s %.12s##recent_%03X",
1000 room_id, room_dirty ?
"*" :
"", room_name.c_str(), room_id);
1002 const bool selected = ImGui::BeginTabItem(tab_label, &open, tab_flags);
1005 to_forget.push_back(room_id);
1008 if (ImGui::IsItemHovered()) {
1011 ImGui::SetTooltip(
"[%03X] %s%s", room_id, label.c_str(),
1012 room_dirty ?
"\nPending room changes" :
"");
1019 if (ImGui::BeginPopupContextItem()) {
1030 to_forget.push_back(room_id);
1036 ImGui::EndTabItem();
1044 for (
int rid : to_forget) {
1078 constexpr ImGuiTableFlags kSplitFlags =
1079 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoPadOuterX |
1080 ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_BordersInnerV;
1082 if (!ImGui::BeginTable(
"##DungeonWorkbenchSplit", 2, kSplitFlags)) {
1087 ImGui::TableSetupColumn(
"Active", ImGuiTableColumnFlags_WidthStretch);
1088 ImGui::TableSetupColumn(
"Compare", ImGuiTableColumnFlags_WidthStretch);
1089 ImGui::TableNextRow();
1095 ImGui::TableNextColumn();
1096 ImGui::AlignTextToFramePadding();
1098 ImGui::TextDisabled(
1105 if (split_active_open) {
1111 ImGui::TableNextColumn();
1112 ImGui::AlignTextToFramePadding();
1114 compare_viewer ? compare_viewer->
project() : active_project;
1115 ImGui::TextDisabled(
1122 if (split_compare_open) {
1123 if (compare_viewer) {
1130 ImGui::TextDisabled(tr(
"No compare viewer"));
1146 static const char*
const kShortNames[] = {
1147 "Sewers",
"HC",
"Eastern",
"Desert",
"A-Tower",
"Swamp",
"PoD",
1148 "Misery",
"Skull",
"Ice",
"Hera",
"Thieves",
"Turtle",
"GT",
1150 constexpr int kVanillaCount =
1151 static_cast<int>(
sizeof(kShortNames) /
sizeof(kShortNames[0]));
1153 auto AddRoom = [&](
int room_id,
int dungeon_id) {
1158 if (dungeon_id >= 0 && dungeon_id < kVanillaCount) {
1162 snprintf(buf,
sizeof(buf),
"Dungeon %02X", dungeon_id);
1168 for (
int i = 0; i < 0x84; ++i) {
1171 if (did >= 0 && did < kVanillaCount) {
1175 snprintf(buf,
sizeof(buf),
"Dungeon %02X", did);
1181 for (
int i = 0; i < 0x14; ++i) {
1193 ImGuiStyleVar_ItemSpacing,
1194 ImVec2(std::max(ImGui::GetStyle().ItemSpacing.x, 4.0f),
1195 std::max(4.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
1202 flags.kSaveSprites = value;
1203 flags.kSaveRoomHeaders = value;
1204 flags.kSaveChests = value;
1205 flags.kSavePotItems = value;
1206 flags.kSavePalettes = value;
1207 flags.kSaveCollision = value;
1208 flags.kSaveWaterFillZones = value;
1209 flags.kSaveBlocks = value;
1210 flags.kSaveTorches = value;
1211 flags.kSavePits = value;
1217 if (ImGui::Checkbox(tr(
"Single-window Workbench"), &use_workbench)) {
1218 flags.kUseWorkbench = use_workbench;
1223 if (ImGui::IsItemHovered()) {
1224 ImGui::SetTooltip(tr(
1225 "Keep Dungeon editing in the integrated Workbench instead of separate "
1226 "high-level room panels."));
1230 ImGui::TextDisabled(tr(
"Data written by Apply Room / Apply Loaded Rooms"));
1231 constexpr ImGuiTableFlags kFlags =
1232 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
1233 if (ImGui::BeginTable(
"##WorkbenchApplyScopeFlags", 2, kFlags)) {
1234 auto draw_checkbox = [](
const char* label,
bool* value) {
1235 ImGui::TableNextColumn();
1236 ImGui::Checkbox(label, value);
1238 ImGui::TableNextRow();
1239 draw_checkbox(
"Room Objects", &flags.kSaveObjects);
1240 draw_checkbox(
"Sprites", &flags.kSaveSprites);
1241 ImGui::TableNextRow();
1242 draw_checkbox(
"Room Headers", &flags.kSaveRoomHeaders);
1243 draw_checkbox(
"Chests", &flags.kSaveChests);
1244 ImGui::TableNextRow();
1245 draw_checkbox(
"Pot Items", &flags.kSavePotItems);
1246 draw_checkbox(
"Palettes", &flags.kSavePalettes);
1247 ImGui::TableNextRow();
1248 draw_checkbox(
"Collision Maps", &flags.kSaveCollision);
1249 draw_checkbox(
"Water Fill", &flags.kSaveWaterFillZones);
1250 ImGui::TableNextRow();
1251 draw_checkbox(
"Blocks", &flags.kSaveBlocks);
1252 draw_checkbox(
"Torches", &flags.kSaveTorches);
1253 ImGui::TableNextRow();
1254 draw_checkbox(
"Pits", &flags.kSavePits);
1255 ImGui::TableNextColumn();
1259 if (ImGui::SmallButton(tr(
"Select All##WorkbenchApplyScope"))) {
1263 if (ImGui::SmallButton(tr(
"Select None##WorkbenchApplyScope"))) {
1286 ImGui::TextDisabled(
1287 tr(
"Pit damage table is unavailable until ROM data loads."));
1293 if (!membership.room_valid) {
1294 ImGui::TextDisabled(tr(
"No valid room selected."));
1298 const uint16_t current_room = membership.room_id;
1299 const bool room_deals_damage = membership.deals_damage;
1301 room_deals_damage ? theme.status_warning : theme.text_secondary_gray,
1302 tr(
"%s Room 0x%03X %s pit damage"),
1304 room_deals_damage ?
"deals" :
"does not deal");
1305 ImGui::TextWrapped(tr(
1306 "RoomsWithPitDamage is fixed-capacity in vanilla. Editing replaces one "
1307 "listed room with another instead of growing or shrinking the table."));
1309 if (membership.dirty) {
1310 ImGui::TextColored(theme.status_warning,
1314 auto show_status = [&]() {
1326 if (room_deals_damage) {
1327 if (!membership.suggested_replacement_room.has_value()) {
1328 ImGui::TextDisabled(tr(
"No available non-damaging room slot."));
1334 ImGui::TextDisabled(tr(
"Remove current room by replacing it with:"));
1335 ImGui::SetNextItemWidth(88.0f);
1338 &edit_value, 88.0f,
true);
1343 if (ImGui::IsItemHovered()) {
1345 tr(
"Replacement room must not already be in the table."));
1348 const bool replace_clicked =
1349 ImGui::Button(tr(
"Replace current##PitDamageReplaceCurrent"));
1351 if (replace_clicked) {
1357 ? absl::StrFormat(
"Room 0x%03X removed; slot now 0x%03X",
1359 : std::string(status.message());
1362 if (!membership.suggested_victim_room.has_value()) {
1363 ImGui::TextDisabled(tr(
"No existing pit-damage slot is available."));
1369 ImGui::TextDisabled(tr(
"Add current room by replacing listed room:"));
1370 ImGui::SetNextItemWidth(88.0f);
1378 if (ImGui::IsItemHovered()) {
1379 ImGui::SetTooltip(tr(
"This room will stop dealing pit damage."));
1382 const bool add_clicked =
1383 ImGui::Button(tr(
"Add current##PitDamageAddCurrent"));
1391 ? absl::StrFormat(
"Room 0x%03X added; replaced 0x%03X",
1393 : std::string(status.message());
1398 ImGui::TextDisabled(
1399 tr(
"Apply Current Room / Apply Loaded Rooms writes this via "
1400 "SaveAllPits when the Pits scope is enabled."));
1406 ImGui::TextDisabled(tr(
"No active room"));
1411 auto draw_blend_combo = [&](
const char* combo_id,
1415 const char* current_name =
1418 ImGui::SetNextItemWidth(-1);
1419 if (ImGui::BeginCombo(combo_id, current_name)) {
1420 for (
int mode_int = 0; mode_int <= 4; ++mode_int) {
1422 const char* mode_name =
1424 const bool selected = current_mode == mode;
1425 if (ImGui::Selectable(mode_name, selected)) {
1426 layer_manager.SetLayerBlendMode(layer_type, mode);
1429 ImGui::SetItemDefaultFocus();
1438 const char* combo_id;
1441 static constexpr BlendRow kBlendRows[] = {
1442 {
"BG1 Layout",
"##WorkbenchBlendBG1Layout",
1444 {
"BG1 Objects",
"##WorkbenchBlendBG1Objects",
1446 {
"BG2 Layout",
"##WorkbenchBlendBG2Layout",
1448 {
"BG2 Objects",
"##WorkbenchBlendBG2Objects",
1452 constexpr ImGuiTableFlags kBlendTableFlags =
1453 ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoPadOuterX;
1454 if (ImGui::BeginTable(
"##WorkbenchLayerBlend", 2, kBlendTableFlags)) {
1455 ImGui::TableSetupColumn(
"##label", ImGuiTableColumnFlags_WidthFixed, 92.0f);
1456 ImGui::TableSetupColumn(
"##combo", ImGuiTableColumnFlags_WidthStretch);
1457 for (
const auto& row : kBlendRows) {
1458 ImGui::TableNextRow();
1459 ImGui::TableNextColumn();
1460 ImGui::AlignTextToFramePadding();
1461 ImGui::TextUnformatted(row.label);
1462 ImGui::TableNextColumn();
1463 draw_blend_combo(row.combo_id, row.layer_type);
1496 if (
const auto* project = viewer.
project()) {
1505 constexpr const char* kPopupId =
"Dungeon Map##WorkbenchDungeonMapPopup";
1507 ImGui::SetNextWindowSize(ImVec2(660.0f, 520.0f), ImGuiCond_Appearing);
1508 ImGui::OpenPopup(kPopupId);
1512 bool popup_open =
true;
1513 if (ImGui::BeginPopupModal(kPopupId, &popup_open,
1514 ImGuiWindowFlags_NoSavedSettings)) {
1516 ImGui::CloseCurrentPopup();
1521 const ImVec2 map_size =
1522 ImVec2(std::max(360.0f, ImGui::GetContentRegionAvail().x),
1523 std::max(260.0f, ImGui::GetContentRegionAvail().y - 34.0f));
1524 if (ImGui::BeginChild(
"##WorkbenchDungeonMapBody", map_size,
false)) {
1529 ImGui::TextDisabled(tr(
"Dungeon map unavailable"));
1533 ImGui::CloseCurrentPopup();
1583 return "custom_collision";
1585 return "water_fill";
1589 return "object_selector";
1597 return "room_graphics";
1670 return "Custom Collision";
1672 return "Water Fill";
1674 return "Minecart Tracks";
1676 return "Object Selector";
1678 return "Door Tools";
1680 return "Sprite Tools";
1682 return "Item Tools";
1684 return "Room Graphics";
1697 return "Room tag tools are not available.";
1699 return "Custom collision tools are not available.";
1701 return "Water fill tools are not available.";
1703 return "Minecart track tools are not available.";
1705 return "Object selector is not available.";
1707 return "Door tools are not available.";
1709 return "Sprite tools are not available.";
1711 return "Item tools are not available.";
1713 return "Room graphics tools are not available.";
1715 return "Palette tools are not available.";
1717 return "No Workbench tool selected.";
1719 return "Tool is not available.";
1726 const char* unavailable_message) {
1728 ImGui::TextDisabled(
"%s", unavailable_message);
1731 content->
Draw(
nullptr);
1813 constexpr ImGuiTableFlags kFlags =
1814 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
1815 if (!ImGui::BeginTable(
"##WorkbenchToolStrip", 5, kFlags)) {
1818 for (
int row = 0; row < 2; ++row) {
1819 ImGui::TableNextRow();
1820 for (
int col = 0; col < 5; ++col) {
1821 ImGui::TableNextColumn();
1826 std::snprintf(btn_id,
sizeof(btn_id),
"%s##StripTool_%s",
1829 ImGui::BeginDisabled();
1835 ImGui::EndDisabled();
1837 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1857 ImGui::Dummy(ImVec2(0.0f, 2.0f));
1869 const float available_h = std::max(180.0f, ImGui::GetContentRegionAvail().y);
1870 const bool body_open =
1871 ImGui::BeginChild(
"##WorkbenchToolDrawerBody", ImVec2(0.0f, available_h),
1872 true, ImGuiWindowFlags_HorizontalScrollbar);
1880 float segment_height) {
1881 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1882 const float room_width =
1884 const float selection_width =
1886 const float tools_width =
1888 const float available_width = ImGui::GetContentRegionAvail().x;
1889 const bool stack = available_width < (room_width + selection_width +
1890 tools_width + spacing * 2.0f);
1891 auto draw_mode = [&](
const char* label,
float width,
InspectorMode mode) {
1892 const ImVec2 size(stack ? ImGui::GetContentRegionAvail().x : width,
1898 ImGui::SameLine(0.0f, spacing);
1904 const ImVec2 tools_size(
1905 stack ? ImGui::GetContentRegionAvail().x : tools_width, segment_height);
1919 const bool has_entity = interaction.HasEntitySelection();
1923 ImGui::Text(
"[%03X] %s", room_id,
1927 ImGui::TextDisabled(tr(
"No room selected"));
1930 ImGui::Dummy(ImVec2(0.0f, 2.0f));
1931 if (selected_objects > 0 || has_entity) {
1934 ImGui::BulletText(tr(
"Entity selected"));
1936 if (selected_objects > 0) {
1937 ImGui::BulletText(tr(
"%zu object%s selected"), selected_objects,
1938 selected_objects == 1 ?
"" :
"s");
1945 ImGui::TextDisabled(tr(
"Nothing selected"));
1958 const bool has_selection =
2002 const std::string room_label =
2005 : std::string(
"None");
2010 ImGui::AlignTextToFramePadding();
2011 ImGui::TextUnformatted(tr(
"Room"));
2013 uint16_t requested_room_id =
static_cast<uint16_t
>(room_id);
2014 ImGui::SetNextItemWidth(74.0f);
2016 if (!can_jump_room) {
2017 ImGui::BeginDisabled();
2022 const int target_room_id = std::clamp(
static_cast<int>(requested_room_id),
2031 if (!can_jump_room) {
2032 ImGui::EndDisabled();
2034 if (ImGui::IsItemHovered()) {
2035 const int preview_room_id = std::clamp(
2037 ImGui::SetTooltip(tr(
"Open room 0x%03X"), preview_room_id);
2042 snprintf(buf,
sizeof(buf),
"0x%03X", room_id);
2043 ImGui::SetClipboardText(buf);
2045 if (ImGui::IsItemHovered()) {
2046 ImGui::SetTooltip(tr(
"Copy room ID (0x%03X) to clipboard"), room_id);
2049 ImGui::TextUnformatted(tr(
"Room: None"));
2052 bool room_dirty =
false;
2053 if (
auto* rooms = viewer.
rooms(); rooms && room_id >= 0) {
2054 if (
const auto* room = rooms->GetIfMaterialized(room_id)) {
2055 room_dirty = room->HasUnsavedChanges();
2059 ImGui::TextColored(theme.status_warning,
2061 }
else if (room_id >= 0) {
2062 ImGui::TextDisabled(
ICON_MD_CHECK " Room matches ROM buffer");
2071 std::string project_group_name =
2074 const char* group_name =
2075 project_group_name.empty() ? nullptr : project_group_name.c_str();
2079 group_name = cache_it->second.c_str();
2083 auto* rooms = viewer.
rooms();
2084 if (rooms && room_id <
static_cast<int>(rooms->size())) {
2086 (*rooms)[room_id].blockset());
2091 room_label.c_str());
2093 ImGui::TextDisabled(
"%s", room_label.c_str());
2096 ImGui::TextDisabled(
"%s", room_label.c_str());
2119 if (
auto* rooms = viewer.
rooms();
2120 rooms && room_id >= 0 && room_id < static_cast<int>(rooms->size())) {
2121 auto& room = (*rooms)[room_id];
2123 uint8_t layout_val = room.layout_id();
2124 uint8_t blockset_val = room.blockset();
2125 uint8_t floor1_val = room.floor1();
2126 uint8_t floor2_val = room.floor2();
2127 uint8_t palette_val = room.palette();
2128 uint8_t spriteset_val = room.spriteset();
2129 uint16_t message_val = room.message_id();
2130 uint8_t bg2_val =
static_cast<uint8_t
>(room.bg2());
2131 uint8_t effect_val =
static_cast<uint8_t
>(room.effect());
2132 uint8_t collision_val =
static_cast<uint8_t
>(room.collision());
2133 uint8_t tag1_val =
static_cast<uint8_t
>(room.tag1());
2134 uint8_t tag2_val =
static_cast<uint8_t
>(room.tag2());
2136 auto render_room_graphics = [&]() {
2137 if (room.rom() && room.rom()->is_loaded()) {
2138 room.RenderRoomGraphics();
2142 constexpr float kHexW = 54.0f;
2143 constexpr ImGuiTableFlags kHeaderFlags = ImGuiTableFlags_BordersInnerV |
2144 ImGuiTableFlags_RowBg |
2145 ImGuiTableFlags_NoPadOuterX;
2146 auto draw_label = [](
const char* label) {
2147 ImGui::AlignTextToFramePadding();
2148 ImGui::TextUnformatted(label);
2150 auto draw_byte_field = [&](
const char* label,
const char* id, uint8_t value,
2151 uint8_t max_value,
const std::string& tooltip,
2153 ImGui::TableNextColumn();
2155 ImGui::TableNextColumn();
2156 ImGui::SetNextItemWidth(kHexW);
2157 uint8_t edit_value = value;
2163 if (ImGui::IsItemHovered()) {
2164 ImGui::SetTooltip(
"%s", tooltip.c_str());
2167 auto draw_word_field = [&](
const char* label,
const char* id,
2168 uint16_t value, uint16_t max_value,
2169 const std::string& tooltip,
auto apply) {
2170 ImGui::TableNextColumn();
2172 ImGui::TableNextColumn();
2173 ImGui::SetNextItemWidth(kHexW + 12.0f);
2174 uint16_t edit_value = value;
2177 apply(std::min<uint16_t>(edit_value, max_value));
2179 if (ImGui::IsItemHovered()) {
2180 ImGui::SetTooltip(
"%s", tooltip.c_str());
2184 if (ImGui::BeginTable(
"##WorkbenchRoomHeader", 4, kHeaderFlags)) {
2185 ImGui::TableSetupColumn(
"L1", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2186 ImGui::TableSetupColumn(
"V1", ImGuiTableColumnFlags_WidthFixed, 66.0f);
2187 ImGui::TableSetupColumn(
"L2", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2188 ImGui::TableSetupColumn(
"V2", ImGuiTableColumnFlags_WidthStretch);
2190 ImGui::TableNextRow();
2191 draw_byte_field(
"Lay",
"##RoomHeaderLayout", layout_val, 0x07,
2192 "Layout (0-7)", [&](uint8_t value) {
2193 room.SetLayoutId(value);
2194 room.MarkLayoutDirty();
2195 render_room_graphics();
2197 draw_byte_field(
"Blk",
"##RoomHeaderBlockset", blockset_val, 0x51,
2198 "Blockset (0-51)", [&](uint8_t value) {
2199 room.SetBlockset(value);
2200 render_room_graphics();
2203 ImGui::TableNextRow();
2204 draw_byte_field(
"F1",
"##RoomHeaderFloor1", floor1_val, 0x0F,
2205 "BG1 floor graphics (0-F)", [&](uint8_t value) {
2206 room.set_floor1(value);
2207 render_room_graphics();
2209 draw_byte_field(
"F2",
"##RoomHeaderFloor2", floor2_val, 0x0F,
2210 "BG2 floor graphics (0-F)", [&](uint8_t value) {
2211 room.set_floor2(value);
2212 render_room_graphics();
2215 ImGui::TableNextRow();
2216 draw_byte_field(
"Pal",
"##RoomHeaderPalette", palette_val, 0x47,
2217 "Palette set (0-47)", [&](uint8_t value) {
2218 room.SetPalette(value);
2219 render_room_graphics();
2224 draw_byte_field(
"Spr",
"##RoomHeaderSpriteset", spriteset_val, 0x8F,
2225 "Sprite graphics set (0-8F)", [&](uint8_t value) {
2226 room.SetSpriteset(value);
2227 render_room_graphics();
2230 ImGui::TableNextRow();
2231 draw_word_field(
"Msg",
"##RoomHeaderMessage", message_val, 0x0FFF,
2232 "Dungeon message ID (0-FFF)",
2233 [&](uint16_t value) { room.SetMessageId(value); });
2234 draw_byte_field(
"BG2",
"##RoomHeaderBg2", bg2_val, 0x08,
2235 std::string(
"BG2 mode: ") + GetBg2ModeName(bg2_val),
2236 [&](uint8_t value) {
2238 render_room_graphics();
2241 ImGui::TableNextRow();
2242 const char* effect_name =
2244 draw_byte_field(
"FX",
"##RoomHeaderEffect", effect_val, 0x07,
2245 std::string(
"Effect: ") + effect_name,
2246 [&](uint8_t value) {
2248 render_room_graphics();
2251 "Coll",
"##RoomHeaderCollision", collision_val, 0x04,
2252 std::string(
"Collision: ") + GetCollisionName(collision_val),
2253 [&](uint8_t value) {
2257 ImGui::TableNextRow();
2258 draw_byte_field(
"Tag1",
"##RoomHeaderTag1", tag1_val, 0x40,
2260 [&](uint8_t value) {
2262 render_room_graphics();
2264 draw_byte_field(
"Tag2",
"##RoomHeaderTag2", tag2_val, 0x40,
2266 [&](uint8_t value) {
2268 render_room_graphics();
2274 ImGui::Dummy(ImVec2(0.0f, 2.0f));
2276 if (ImGui::BeginTable(
"##WorkbenchRoomDestinations", 4, kHeaderFlags)) {
2277 ImGui::TableSetupColumn(
"L1", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2278 ImGui::TableSetupColumn(
"V1", ImGuiTableColumnFlags_WidthFixed, 66.0f);
2279 ImGui::TableSetupColumn(
"L2", ImGuiTableColumnFlags_WidthFixed, 44.0f);
2280 ImGui::TableSetupColumn(
"V2", ImGuiTableColumnFlags_WidthStretch);
2282 ImGui::TableNextRow();
2283 draw_byte_field(
"Pit",
"##RoomHeaderPit", room.holewarp(), 0xFF,
2284 "Pit/holewarp destination room",
2285 [&](uint8_t value) { room.SetHolewarp(value); });
2286 draw_byte_field(
"St1",
"##RoomHeaderStair1", room.staircase_room(0), 0xFF,
2287 "Stair destination slot 1",
2288 [&](uint8_t value) { room.SetStaircaseRoom(0, value); });
2290 ImGui::TableNextRow();
2291 draw_byte_field(
"St2",
"##RoomHeaderStair2", room.staircase_room(1), 0xFF,
2292 "Stair destination slot 2",
2293 [&](uint8_t value) { room.SetStaircaseRoom(1, value); });
2294 draw_byte_field(
"St3",
"##RoomHeaderStair3", room.staircase_room(2), 0xFF,
2295 "Stair destination slot 3",
2296 [&](uint8_t value) { room.SetStaircaseRoom(2, value); });
2298 ImGui::TableNextRow();
2299 draw_byte_field(
"St4",
"##RoomHeaderStair4", room.staircase_room(3), 0xFF,
2300 "Stair destination slot 4",
2301 [&](uint8_t value) { room.SetStaircaseRoom(3, value); });
2302 draw_byte_field(
"P1",
"##RoomHeaderStairPlane1", room.staircase_plane(0),
2303 0x03,
"Stair 1 target layer/plane (0-3)",
2304 [&](uint8_t value) { room.SetStaircasePlane(0, value); });
2306 ImGui::TableNextRow();
2307 draw_byte_field(
"P2",
"##RoomHeaderStairPlane2", room.staircase_plane(1),
2308 0x03,
"Stair 2 target layer/plane (0-3)",
2309 [&](uint8_t value) { room.SetStaircasePlane(1, value); });
2310 draw_byte_field(
"P3",
"##RoomHeaderStairPlane3", room.staircase_plane(2),
2311 0x03,
"Stair 3 target layer/plane (0-3)",
2312 [&](uint8_t value) { room.SetStaircasePlane(2, value); });
2314 ImGui::TableNextRow();
2315 draw_byte_field(
"P4",
"##RoomHeaderStairPlane4", room.staircase_plane(3),
2316 0x03,
"Stair 4 target layer/plane (0-3)",
2317 [&](uint8_t value) { room.SetStaircasePlane(3, value); });
2318 ImGui::TableNextColumn();
2319 ImGui::TableNextColumn();
2324 ImGui::TextDisabled(tr(
"Room header unavailable"));
2331 ImGui::TextColored(theme.text_info, tr(
"Placement active"));
2334 interaction.mode_manager().CancelCurrentMode();
2345 const size_t obj_count = interaction.GetSelectionCount();
2346 const bool has_entity = interaction.HasEntitySelection();
2348 if (!has_entity && obj_count == 0) {
2350 ImGui::TextDisabled(tr(
"Click an object or entity to inspect"));
2355 if (obj_count > 0) {
2357 ImGui::TextColored(theme.text_primary,
ICON_MD_WIDGETS " %zu object%s",
2358 obj_count, obj_count == 1 ?
"" :
"s");
2359 if (obj_count == 1) {
2361 ImGui::TextDisabled(tr(
"Focused selection"));
2364 const auto indices = interaction.GetSelectedObjectIndices();
2367 if (indices.size() > 1 && room_id >= 0 && viewer.
rooms()) {
2368 auto& room = (*viewer.
rooms())[room_id];
2369 auto& objects = room.GetTileObjects();
2371 " Multi-selection");
2372 for (
size_t i = 0; i < indices.size() && i < 8; ++i) {
2373 size_t idx = indices[i];
2374 if (idx < objects.size()) {
2375 auto& obj = objects[idx];
2377 ImGui::BulletText(tr(
"0x%03X %s"), obj.id_, name.c_str());
2380 if (indices.size() > 8) {
2381 ImGui::TextDisabled(tr(
" ... and %zu more"), indices.size() - 8);
2387 auto& tile_handler = interaction.entity_coordinator().tile_handler();
2388 const std::vector<size_t> selection_copy(indices.begin(), indices.end());
2389 bool has_room_stream_object =
false;
2390 bool has_special_layer_object =
false;
2391 for (
const size_t index : selection_copy) {
2392 if (index >= objects.size()) {
2396 has_room_stream_object =
true;
2398 has_special_layer_object =
true;
2405 static int bulk_nudge_dx = 0;
2406 static int bulk_nudge_dy = 0;
2407 ImGui::TextDisabled(tr(
"Nudge (tiles)"));
2408 ImGui::PushButtonRepeat(
true);
2410 tile_handler.MoveObjects(room_id, selection_copy, 0, -1);
2413 tile_handler.MoveObjects(room_id, selection_copy, 0, 1);
2416 tile_handler.MoveObjects(room_id, selection_copy, -1, 0);
2419 tile_handler.MoveObjects(room_id, selection_copy, 1, 0);
2420 ImGui::PopButtonRepeat();
2422 ImGui::SetNextItemWidth(60);
2423 ImGui::DragInt(
"##BulkNudgeDx", &bulk_nudge_dx, 0.25f, -63, 63,
"Δx:%d");
2425 ImGui::SetNextItemWidth(60);
2426 ImGui::DragInt(
"##BulkNudgeDy", &bulk_nudge_dy, 0.25f, -63, 63,
"Δy:%d");
2428 if (ImGui::Button(tr(
"Apply##BulkNudgeApply")) &&
2429 (bulk_nudge_dx != 0 || bulk_nudge_dy != 0)) {
2430 tile_handler.MoveObjects(room_id, selection_copy, bulk_nudge_dx,
2437 if (has_room_stream_object && !has_special_layer_object) {
2438 ImGui::TextDisabled(tr(
"Object stream (set all)"));
2439 if (ImGui::SmallButton(tr(
"Primary##BulkPlacement0")))
2440 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2442 if (ImGui::SmallButton(tr(
"BG2 overlay##BulkPlacement1")))
2443 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2445 if (ImGui::SmallButton(tr(
"BG1 overlay##BulkPlacement2")))
2446 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 2);
2447 }
else if (has_special_layer_object && !has_room_stream_object) {
2448 ImGui::TextDisabled(tr(
"Special layer (set all)"));
2449 if (ImGui::SmallButton(tr(
"Upper (BG1)##BulkPlacement0")))
2450 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2452 if (ImGui::SmallButton(tr(
"Lower (BG2)##BulkPlacement1")))
2453 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2455 ImGui::TextDisabled(tr(
"Stored placement (mixed selection)"));
2456 if (ImGui::SmallButton(tr(
"Primary / Upper (BG1)##BulkPlacement0")))
2457 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 0);
2459 if (ImGui::SmallButton(tr(
"BG2 overlay / Lower (BG2)##BulkPlacement1")))
2460 tile_handler.UpdateObjectsLayer(room_id, selection_copy, 1);
2464 ImGui::TextDisabled(tr(
"Stacking"));
2466 tile_handler.SendToFront(room_id, selection_copy);
2469 tile_handler.SendToBack(room_id, selection_copy);
2472 tile_handler.MoveForward(room_id, selection_copy);
2475 tile_handler.MoveBackward(room_id, selection_copy);
2478 ImGui::TextDisabled(tr(
"Size"));
2480 tile_handler.ResizeObjects(room_id, selection_copy, -1);
2482 if (ImGui::SmallButton(
ICON_MD_ADD "##BulkSizeInc"))
2483 tile_handler.ResizeObjects(room_id, selection_copy, 1);
2487 (void)tile_handler.DuplicateObjects(room_id, selection_copy, 1, 1);
2491 {ImGuiCol_Button, theme.status_error},
2492 {ImGuiCol_ButtonHovered,
2493 ImVec4(theme.status_error.x + 0.1f, theme.status_error.y + 0.05f,
2494 theme.status_error.z + 0.05f, 1.0f)},
2497 ImGui::OpenPopup(
"##BulkDeleteConfirm");
2500 if (ImGui::BeginPopup(
"##BulkDeleteConfirm")) {
2503 selection_copy.size(), selection_copy.size() == 1 ?
"" :
"s");
2505 if (ImGui::Button(tr(
"Cancel##BulkDelCancel"))) {
2506 ImGui::CloseCurrentPopup();
2511 {ImGuiCol_Button, theme.status_error},
2514 tile_handler.DeleteObjects(room_id, selection_copy);
2515 ImGui::CloseCurrentPopup();
2523 if (indices.size() == 1 && room_id >= 0 && viewer.
rooms()) {
2524 auto& room = (*viewer.
rooms())[room_id];
2525 auto& objects = room.GetTileObjects();
2526 const size_t idx = indices.front();
2527 if (idx < objects.size()) {
2528 auto& obj = objects[idx];
2532 int displayed_layer = obj.GetLayerValue();
2533 const int pixel_x = obj.x_ * 8;
2534 const int pixel_y = obj.y_ * 8;
2539 ImGui::TextColored(theme.text_primary,
"%s", obj_name.c_str());
2540 ImGui::TextDisabled(tr(
"%s (Type %d) #%zu in list"),
2541 GetObjectCategory(obj.id_), subtype, idx);
2544 constexpr ImGuiTableFlags kPropsFlags = ImGuiTableFlags_BordersInnerV |
2545 ImGuiTableFlags_RowBg |
2546 ImGuiTableFlags_NoPadOuterX;
2547 if (ImGui::BeginTable(
"##SelObjProps", 2, kPropsFlags)) {
2548 ImGui::TableSetupColumn(
"Prop", ImGuiTableColumnFlags_WidthFixed,
2550 ImGui::TableSetupColumn(
"Val", ImGuiTableColumnFlags_WidthStretch);
2554 uint16_t obj_id =
static_cast<uint16_t
>(obj.id_ & 0x0FFF);
2559 interaction.SetObjectId(idx,
static_cast<int16_t
>(obj_id));
2567 ImGui::SetNextItemWidth(60);
2569 ImGui::DragInt(
"##SelObjX", &pos_x, 0.1f, 0, 63,
"X:%d");
2571 ImGui::SetNextItemWidth(60);
2573 ImGui::DragInt(
"##SelObjY", &pos_y, 0.1f, 0, 63,
"Y:%d");
2574 if (x_changed || y_changed) {
2575 int delta_x = pos_x - obj.x_;
2576 int delta_y = pos_y - obj.y_;
2577 interaction.entity_coordinator().tile_handler().MoveObjects(
2578 room_id, {idx}, delta_x, delta_y);
2584 uint8_t size = obj.size_ & 0x0F;
2588 interaction.SetObjectSize(idx, size);
2593 uses_room_stream ?
"Stream" :
"Layer", [&]() {
2594 int layer = displayed_layer;
2595 const char* stream_names[] = {
"Primary",
"BG2 overlay",
2597 const char* special_layer_names[] = {
"Upper layer (BG1)",
2598 "Lower layer (BG2)"};
2599 ImGui::SetNextItemWidth(-1);
2601 "##SelObjLayer", &layer,
2602 uses_room_stream ? stream_names : special_layer_names,
2603 uses_room_stream ? IM_ARRAYSIZE(stream_names)
2604 : IM_ARRAYSIZE(special_layer_names))) {
2605 const int max_layer = uses_room_stream ? 2 : 1;
2606 layer = std::clamp(layer, 0, max_layer);
2607 if (interaction.SetObjectLayer(
2610 displayed_layer = layer;
2615 ImGui::TextDisabled(
2616 "%s", uses_room_stream ? GetObjectStreamName(displayed_layer)
2617 : GetSpecialLayerName(displayed_layer));
2622 ImGui::TextDisabled(
"(%d, %d)", pixel_x, pixel_y);
2632 if (has_entity && room_id >= 0 && viewer.
rooms()) {
2633 const auto sel = interaction.GetSelectedEntity();
2634 auto& room = (*viewer.
rooms())[room_id];
2636 " Entity Selection");
2640 const auto& doors = room.GetDoors();
2641 if (sel.index < doors.size()) {
2642 const auto& door = doors[sel.index];
2648 ImGui::TextDisabled(tr(
"Direction: %s Position: 0x%02X"),
2649 dir_name.c_str(), door.position);
2651 auto [tile_x, tile_y] = door.GetTileCoords();
2652 auto [pixel_x, pixel_y] = door.GetPixelCoords();
2653 ImGui::TextDisabled(tr(
"Tile: (%d, %d) Pixel: (%d, %d)"), tile_x,
2654 tile_y, pixel_x, pixel_y);
2659 const auto& sprites = room.GetSprites();
2660 if (sel.index < sprites.size()) {
2661 const auto& sprite = sprites[sel.index];
2665 sprite_name.c_str());
2666 ImGui::TextDisabled(tr(
"ID: 0x%02X Subtype: %d Layer: %d"),
2667 sprite.id(), sprite.subtype(), sprite.layer());
2668 ImGui::TextDisabled(tr(
"Pos: (%d, %d) Pixel: (%d, %d)"), sprite.x(),
2669 sprite.y(), sprite.x() * 16, sprite.y() * 16);
2672 if (sprite.subtype() == 0x07 && sprite.id() >= 0x01 &&
2673 sprite.id() <= 0x1A) {
2675 ImGui::TextColored(theme.text_warning_yellow,
2677 overlord_name.c_str());
2683 const auto& items = room.GetPotItems();
2684 if (sel.index < items.size()) {
2685 const auto& pot_item = items[sel.index];
2686 const char* item_name = GetPotItemName(pot_item.item);
2690 ImGui::TextDisabled(tr(
"Item ID: 0x%02X Raw Pos: 0x%04X"),
2691 pot_item.item, pot_item.position);
2692 ImGui::TextDisabled(tr(
"Pixel: (%d, %d) Tile: (%d, %d)"),
2693 pot_item.GetPixelX(), pot_item.GetPixelY(),
2694 pot_item.GetTileX(), pot_item.GetTileY());
2708 constexpr ImGuiTableFlags kFlags =
2709 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
2710 if (!ImGui::BeginTable(
"##WorkbenchToolsGrid", 2, kFlags)) {
2714 auto draw_tool_button = [&](
const char* label,
WorkbenchTool tool,
2715 bool enabled =
true) {
2717 ImGui::BeginDisabled();
2725 ImGui::EndDisabled();
2732 ImGui::TableNextRow();
2733 ImGui::TableNextColumn();
2736 ImGui::TableNextColumn();
2740 ImGui::TableNextRow();
2741 ImGui::TableNextColumn();
2744 ImGui::TableNextColumn();
2751 if (ImGui::BeginTable(
"##WorkbenchRoomToolsGrid", 2, kFlags)) {
2752 auto room_tool_button = [&](
const char* label,
WorkbenchTool tool,
2754 ImGui::TableNextColumn();
2756 ImGui::BeginDisabled();
2764 ImGui::EndDisabled();
2768 ImGui::TableNextRow();
2774 ImGui::TableNextRow();
2783 if (ImGui::BeginTable(
"##WorkbenchReviewGrid", 2, kFlags)) {
2784 ImGui::TableNextRow();
2785 ImGui::TableNextColumn();
2790 ImGui::TableNextColumn();
2796 ImGui::TableNextRow();
2797 ImGui::TableNextColumn();
2802 ImGui::TableNextColumn();
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 DrawDungeonCanvas(int room_id)
WindowContent for displaying multiple rooms in a spatial dungeon layout.
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)
zelda3::Room * GetIfMaterialized(int room_id)
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
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()> can_redo_
int GetPriority() const override
Get display priority for menu ordering.
CustomCollisionPanel * custom_collision_panel_
DungeonWorkbenchPitDamageControlRects pit_damage_control_rects_
bool IsToolDrawerActiveForTesting() const
void DrawSelectionShelf(DungeonCanvasViewer &viewer)
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 DrawRecentRoomTabs()
void DrawInspectorToolStrip()
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)
const char * GetWorkbenchToolIcon(WorkbenchTool tool) const
void DrawInspectorShelfRoom(DungeonCanvasViewer &viewer)
const char * GetInspectorModeIdForTesting() const
WindowContent * sprite_editor_content_
std::function< int()> undo_depth_
std::string GetEditorCategory() const override
Editor category this panel belongs to.
void OpenRoomGraphicsTool()
std::function< std::string()> undo_desc_
void DrawInspectorShelfTools(DungeonCanvasViewer &viewer)
void FocusRoomInspector()
void DrawCanvasPane(float width, float height, DungeonCanvasViewer *primary_viewer, bool left_sidebar_visible)
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)
void DrawInspectorToolDrawer(DungeonCanvasViewer &viewer)
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]
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)
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_
const char * GetWorkbenchToolTitle(WorkbenchTool tool) const
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_
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_
bool show_shortcut_legend_
std::function< void(int)> forget_recent_room_
std::function< const std::deque< int > &()> get_recent_rooms_
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.
static void Draw(bool *p_open)
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.
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)
static bool IsTouchDevice()
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
bool HasUnsavedChanges() const
zelda3_bg2_effect
Background layer 2 effects.
#define ICON_MD_GRID_VIEW
#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_TRAVEL_EXPLORE
#define ICON_MD_CONTENT_PASTE
#define ICON_MD_KEYBOARD_ARROW_DOWN
#define ICON_MD_INVENTORY
#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_EDIT_NOTE
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_CONTENT_COPY
#define ICON_MD_INVENTORY_2
#define ICON_MD_WATER_DROP
#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
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 DrawActionButton(const char *label, const ImVec2 &size)
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
constexpr const char * kWorkbench
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)
DungeonSelectionSnapshot BuildDungeonSelectionSnapshot(const DungeonObjectInteraction &interaction, const DungeonRoomStore *rooms, int room_id)
PitDamageMembershipState BuildPitDamageMembershipState(const zelda3::PitDamageTable *table, int room_id, uint16_t replacement_fallback, uint16_t victim_fallback)
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.
std::string GetDungeonSelectionSummaryText(const DungeonSelectionSnapshot &snapshot)
DungeonWorkbenchResponsiveLayout ResolveDungeonWorkbenchResponsiveLayout(float total_width, float min_canvas_width, float min_sidebar_width, float splitter_width, bool want_left, bool want_right)
bool DrawDungeonWorkbenchVerticalSplitter(const char *id, float height, float *pane_width, float min_width, float max_width, bool resize_from_left_edge, float collapse_threshold)
absl::Status AddCurrentRoomToPitDamage(zelda3::PitDamageTable *table, uint16_t current_room_id, uint16_t victim_room_id)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
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
Represents a selected entity in the dungeon editor.
static constexpr float kStatusBarHeight
static constexpr float kContentMinHeightCanvas
static constexpr float kSplitterWidth
static constexpr float kContentMinWidthSidebar
Modern project structure with comprehensive settings consolidation.