15#include "absl/strings/str_format.h"
50#include "imgui/imgui.h"
69 return std::clamp(desired_width, min_width, std::max(min_width, max_width));
87 const ImVec2 min = ImGui::GetItemRectMin();
88 const ImVec2 max = ImGui::GetItemRectMax();
99 bool standalone_window_open) {
100 return standalone_window_open
106 float total_width,
float min_canvas_width,
float min_sidebar_width,
107 float splitter_width,
bool want_left,
bool want_right) {
112 auto required_width = [&](
bool left,
bool right,
bool compact_left,
113 bool compact_right) {
114 float required = min_canvas_width;
116 left ? (compact_left ? GetCompactSidebarWidth(
false, min_sidebar_width)
120 right ? (compact_right ? GetCompactSidebarWidth(
true, min_sidebar_width)
124 required += splitter_width;
127 required += splitter_width;
161 float total_width,
float min_canvas_width,
float min_sidebar_width,
162 float splitter_width,
float stored_left_width,
float stored_right_width,
163 bool want_left,
bool want_right) {
166 total_width, min_canvas_width, min_sidebar_width, splitter_width,
167 want_left, want_right);
169 const float compact_left_width =
170 GetCompactSidebarWidth(
false, min_sidebar_width);
171 const float compact_right_width =
172 GetCompactSidebarWidth(
true, min_sidebar_width);
185 : stored_right_width)
188 const float max_left_width =
189 total_width - right_width - min_canvas_width -
192 const float max_right_width =
193 total_width - left_width - min_canvas_width -
197 left_width = ClampWorkbenchPaneWidth(
204 right_width = ClampWorkbenchPaneWidth(
211 float center_width = total_width - left_width - right_width;
213 center_width -= splitter_width;
216 center_width -= splitter_width;
218 if (center_width < min_canvas_width) {
219 float deficit = min_canvas_width - center_width;
223 left_width -= shrink;
229 right_width -= shrink;
232 center_width = std::max(
233 1.0f, total_width - left_width - right_width -
246 std::function<
void(
int)> on_room_selected,
248 std::function<
void(
int)> on_save_room,
249 std::function<
void()> on_save_all_rooms,
252 std::function<
const std::deque<int>&()> get_recent_rooms,
253 std::function<
void(
int)> forget_recent_room,
254 std::function<
void(
bool)> set_workflow_mode,
Rom* rom)
255 : room_selector_(room_selector),
256 current_room_id_(current_room_id),
257 on_room_selected_(std::move(on_room_selected)),
258 on_room_selected_with_intent_(std::move(on_room_selected_with_intent)),
259 on_save_room_(std::move(on_save_room)),
260 on_save_all_rooms_(std::move(on_save_all_rooms)),
261 get_viewer_(std::move(get_viewer)),
262 get_compare_viewer_(std::move(get_compare_viewer)),
263 get_recent_rooms_(std::move(get_recent_rooms)),
264 forget_recent_room_(std::move(forget_recent_room)),
265 set_workflow_mode_(std::move(set_workflow_mode)),
271 return "dungeon.workbench";
274 return "Dungeon Workbench";
349 std::ifstream file(path, std::ios::binary);
354 std::stringstream buffer;
355 buffer << file.rdbuf();
363 for (
const auto& layer : overlay->layers) {
377 ImGui::SetNextWindowSize(ImVec2(1120.0f, 700.0f), ImGuiCond_FirstUseEver);
378 if (!ImGui::Begin(
ICON_MD_PREVIEW " Proposal Preview##DungeonProposalPreview",
386 json_options.
filters.push_back({
"Proposal Overlay",
"json"});
387 json_options.
filters.push_back({
"All Files",
"*"});
389 const std::string path =
400 ImGui::EndDisabled();
402 ImGui::SetNextItemWidth(-1.0f);
403 if (ImGui::InputTextWithHint(
"##ProposalPath", tr(
"overlay .json path"),
405 ImGuiInputTextFlags_EnterReturnsTrue)) {
410 ImVec4(1.0f, 0.45f, 0.4f, 1.0f));
418 tr(
"Open a proposal overlay (.json) to render its rooms from the "
419 "loaded ROM with the proposed changes drawn on top. This window "
420 "is read-only: it never edits rooms or the ROM."));
426 if (!overlay.
title.empty()) {
427 ImGui::TextUnformatted(overlay.
title.c_str());
429 if (!overlay.
status.empty()) {
431 ImGui::TextDisabled(
"(%s)", overlay.
status.c_str());
433 ImGui::TextDisabled(
"%s",
434 tr(
"Read-only preview. Nothing here edits the ROM."));
439 for (
size_t i = 0; i < overlay.
layers.size(); ++i) {
440 const auto& layer = overlay.
layers[i];
442 const uint32_t c = layer.color_rgba;
444 absl::StrFormat(
"##ProposalLayerSwatch%d", i).c_str(),
445 ImVec4(((c >> 24) & 0xFF) / 255.0f, ((c >> 16) & 0xFF) / 255.0f,
446 ((c >> 8) & 0xFF) / 255.0f, 1.0f),
447 ImGuiColorEditFlags_NoTooltip, ImVec2(12, 12));
448 ImGui::SameLine(0, 4);
452 absl::StrFormat(
"%s##ProposalLayer%d", layer.label, i).c_str(),
458 ImGui::EndDisabled();
460 ImGui::SetNextItemWidth(160.0f);
463 for (
const auto& room : overlay.
rooms) {
465 if (ImGui::SmallButton(
466 absl::StrFormat(
"Open $%02X##ProposalOpenRoom", room.room_id)
474 const float notes_height =
475 overlay.
notes.empty()
477 : std::min(140.0f, ImGui::GetTextLineHeightWithSpacing() *
478 (overlay.
notes.size() + 1.5f));
479 if (ImGui::BeginChild(
"##ProposalPreviewBody", ImVec2(0.0f, -notes_height),
480 true, ImGuiWindowFlags_HorizontalScrollbar)) {
486 if (!overlay.
notes.empty() &&
487 ImGui::BeginChild(
"##ProposalPreviewNotes", ImVec2(0.0f, 0.0f),
false)) {
488 for (
const auto& note : overlay.
notes) {
489 ImGui::BulletText(
"%s", note.c_str());
492 if (!overlay.
notes.empty()) {
581 float button_size,
bool compact) {
582 const bool open = ImGui::BeginChild(
583 "##DungeonWorkbenchSidebar", ImVec2(width, height),
false,
584 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
587 const float rail_width = ImGui::GetFrameHeight() + 8.0f;
588 if (ImGui::BeginChild(
"##WorkbenchRail", ImVec2(rail_width, 0),
false))
592 if (ImGui::BeginChild(
"##NavigatorTools", ImVec2(0, 0),
false))
601 const char* label =
"Room";
624 const float action_width =
627 ImGui::GetStyle().ItemSpacing.x;
629 "##DungeonWorkbenchSidebarHeader",
"", label, label,
nullptr, compact,
630 action_width, [&]() {
634 ImGui::OpenPopup(
"##WorkbenchSidebarQuickActions");
635 if (ImGui::BeginPopup(
"##WorkbenchSidebarQuickActions")) {
636 if (ImGui::MenuItem(
"Dungeon overview"))
638 if (ImGui::MenuItem(
"Dungeon map"))
640 if (ImGui::MenuItem(
"Proposal Preview..."))
643 if (ImGui::MenuItem(
"Keyboard Shortcuts",
nullptr,
false,
658 const float size = ImGui::GetFrameHeight() + 4.0f;
659 const auto draw = [&](
const char* id,
const char* icon,
const char* label,
660 bool selected,
auto action) {
663 ImGui::GetStyleColorVec4(selected ? ImGuiCol_ButtonActive
666 ImGui::SetCursorPosX(
667 ImGui::GetCursorPosX() +
668 std::max(0.0f, (ImGui::GetContentRegionAvail().x - size) * 0.5f));
673 const ImVec2 p = ImGui::GetItemRectMin();
674 ImGui::GetWindowDrawList()->AddLine(
675 p, ImVec2(p.x, p.y + size), ImGui::GetColorU32(ImGuiCol_CheckMark),
678 if (ImGui::IsItemHovered())
679 ImGui::SetTooltip(
"%s", label);
691 constexpr Choice choices[] = {
701 for (
const auto& choice : choices)
702 draw(choice.id, choice.icon, choice.label,
sidebar_mode_ == choice.mode,
703 [
this, &choice]() {
OpenTool(choice.tool); });
708 constexpr Choice tools[] = {
716 for (
const auto& choice : tools)
717 draw(choice.id, choice.icon, choice.label,
719 [
this, &choice]() {
OpenTool(choice.tool); });
725 ImGui::TextDisabled(
"No active room");
728 ImGui::PushID(
"WorkbenchSidebarMode");
733 ImGui::TextDisabled(
"Entrance editor unavailable");
736 if (ImGui::Button(
"Focus Entrance Window") &&
741 ImGui::GetContentRegionAvail().x);
746 ImGui::EndDisabled();
769 ImGui::TextDisabled(
"Browser unavailable");
775 selector->DrawInWorkbench();
777 sprites->DrawInWorkbench();
779 doors->DrawInWorkbench();
781 items->DrawInWorkbench();
783 content->
Draw(
nullptr);
794 ImGui::TextDisabled(
ICON_MD_INFO " Load a ROM to edit dungeon rooms.");
798 ImGui::TextColored(theme.text_error_red, tr(
"Dungeon Workbench not wired"));
806 const float total_w = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
810 const float min_sidebar_w =
812 const float min_canvas_w = std::max(420.0f, min_sidebar_w + 96.0f);
866 total_w, min_canvas_w, min_sidebar_w, splitter_w,
873 const float total_h = std::max(ImGui::GetContentRegionAvail().y, 1.0f);
886 ImGui::SameLine(0, 0);
888 "##DungeonWorkbenchLeftSplitter", total_h,
890 total_w - right_w - min_canvas_w - (show_right ? splitter_w : 0),
893 ImGui::SameLine(0, 0);
897 ImGui::SameLine(0, 0);
899 "##DungeonWorkbenchRightSplitter", total_h,
901 total_w - left_w - min_canvas_w - (show_left ? splitter_w : 0),
904 ImGui::SameLine(0, 0);
908 if (primary_viewer) {
916 ImGui::TextUnformatted(tr(
"Workbench layout"));
917 ImGui::Checkbox(tr(
"Room properties above panes"),
919 ImGui::Checkbox(tr(
"Nearby rooms in spare canvas space"),
921 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16.0f);
925 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16.0f);
929 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16.0f);
931 0.20f, 0.65f,
"%.2f");
932 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16.0f);
933 ImGui::SliderFloat(tr(
"Nearby preview height"),
936 if (ImGui::Button(tr(
"Reset workbench layout"))) {
940 ImGui::TextDisabled(tr(
"Layout changes apply immediately for this session."));
945 const bool canvas_open = ImGui::BeginChild(
946 "##DungeonWorkbenchCanvas", ImVec2(width, height),
false,
947 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
949 if (primary_viewer) {
955 const float status_bar_reserved =
957 ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
959 ImGui::GetStyle().ItemSpacing.y;
962 const ImVec2 body_available = ImGui::GetContentRegionAvail();
963 const float nearby_height =
967 body_available.x - ImGui::GetStyle().WindowPadding.x * 2,
968 body_available.y - status_bar_reserved -
969 ImGui::GetStyle().WindowPadding.y * 2,
971 ImGui::GetStyle().ItemSpacing.y)
973 const float nearby_reserved =
974 nearby_height > 0.0f ? nearby_height + ImGui::GetStyle().ItemSpacing.y
976 ImGuiWindowFlags body_flags =
977 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
978 if (connected_mode) {
981 const ImVec2 content_size =
984 ImGui::SetNextWindowContentSize(
985 ImVec2(content_size.x * scale, content_size.y * scale));
986 body_flags = ImGuiWindowFlags_HorizontalScrollbar |
987 ImGuiWindowFlags_NoScrollWithMouse;
989 const bool body_open = ImGui::BeginChild(
990 "##DungeonCanvasBody",
991 ImVec2(0.0f, -status_bar_reserved - nearby_reserved),
false,
994 if (connected_mode) {
1011 if (nearby_height > 0.0f) {
1012 const float preview_y = ImGui::GetCursorPosY();
1015 if (ImGui::GetCursorPosY() == preview_y) {
1016 ImGui::Dummy(ImVec2(0.0f, nearby_height));
1020 const char* tool_mode =
1022 bool room_dirty =
false;
1023 if (
auto* rooms = primary_viewer->
rooms();
1026 room_dirty = room->HasUnsavedChanges();
1034 status.workflow_primary =
true;
1040 static std::string s_undo_desc;
1042 status.undo_desc = s_undo_desc.empty() ? nullptr : s_undo_desc.c_str();
1045 static std::string s_redo_desc;
1047 status.redo_desc = s_redo_desc.empty() ? nullptr : s_redo_desc.c_str();
1054 status.on_selection = [
this]() {
1060 ImGui::TextDisabled(tr(
"No active viewer"));
1066 if (primary_viewer) {
1072 float button_size,
bool compact,
1074 const bool open = ImGui::BeginChild(
1075 "##DungeonWorkbenchInspector", ImVec2(width, height),
true,
1076 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
1079 const float available = std::max(1.0f, ImGui::GetContentRegionAvail().y);
1080 const float spacing = ImGui::GetStyle().ItemSpacing.y;
1081 const float toggle_height = ImGui::GetFrameHeight();
1082 const float selection_height =
1085 : std::max(1.0f, available - toggle_height - 2.0f * spacing);
1086 if (ImGui::BeginChild(
"##WorkbenchSelection", ImVec2(0, selection_height),
1091 ImGui::TextDisabled(
"No active room");
1096 ImVec2(-1, toggle_height)))
1099 const ImVec2 divider = ImGui::GetCursorScreenPos();
1100 ImGui::InvisibleButton(
"##NavigatorSplit", ImVec2(-1, 6));
1101 if (ImGui::IsItemHovered() || ImGui::IsItemActive())
1102 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
1103 if (ImGui::IsItemActive())
1107 ImGui::GetWindowDrawList()->AddLine(
1108 ImVec2(divider.x, divider.y + 3),
1109 ImVec2(ImGui::GetItemRectMax().x, divider.y + 3),
1110 ImGui::GetColorU32(ImGuiCol_Separator));
1114 ImGui::TextDisabled(
"Room Matrix");
1115 if (ImGui::BeginChild(
"##NavigatorMatrix", ImVec2(0, 0),
false,
1116 ImGuiWindowFlags_NoScrollbar |
1117 ImGuiWindowFlags_NoScrollWithMouse)) {
1120 if (ImGui::Button(
"Focus Matrix Window") &&
1126 ImGui::TextDisabled(
"Room Matrix unavailable");
1139 "Selection",
nullptr, compact,
1144 "Collapse selection and room matrix"))
1177 constexpr ImGuiTableFlags kSplitFlags =
1178 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoPadOuterX |
1179 ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_BordersInnerV;
1181 if (!ImGui::BeginTable(
"##DungeonWorkbenchSplit", 2, kSplitFlags)) {
1186 ImGui::TableSetupColumn(
"Active", ImGuiTableColumnFlags_WidthStretch);
1187 ImGui::TableSetupColumn(
"Compare", ImGuiTableColumnFlags_WidthStretch);
1188 ImGui::TableNextRow();
1194 ImGui::TableNextColumn();
1195 ImGui::AlignTextToFramePadding();
1197 ImGui::TextDisabled(
1203 "##SplitActive", ImVec2(1.0f, 1.0f),
false,
1204 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
1205 if (split_active_open) {
1211 ImGui::TableNextColumn();
1212 ImGui::AlignTextToFramePadding();
1214 compare_viewer ? compare_viewer->
project() : active_project;
1215 ImGui::TextDisabled(
1221 "##SplitCompare", ImVec2(1.0f, 1.0f),
false,
1222 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
1223 if (split_compare_open) {
1224 if (compare_viewer) {
1231 ImGui::TextDisabled(tr(
"No compare viewer"));
1248 static const char*
const kShortNames[] = {
1249 "Sewers",
"HC",
"Eastern",
"Desert",
"A-Tower",
"Swamp",
"PoD",
1250 "Misery",
"Skull",
"Ice",
"Hera",
"Thieves",
"Turtle",
"GT",
1252 constexpr int kVanillaCount =
1253 static_cast<int>(
sizeof(kShortNames) /
sizeof(kShortNames[0]));
1255 auto AddRoom = [&](
int room_id,
int dungeon_id) {
1260 if (dungeon_id >= 0 && dungeon_id < kVanillaCount) {
1264 snprintf(buf,
sizeof(buf),
"Dungeon %02X", dungeon_id);
1276 if (did >= 0 && did < kVanillaCount) {
1280 snprintf(buf,
sizeof(buf),
"Dungeon %02X", did);
1299 ImGuiStyleVar_ItemSpacing,
1300 ImVec2(std::max(ImGui::GetStyle().ItemSpacing.x, 4.0f),
1301 std::max(4.0f, ImGui::GetStyle().ItemSpacing.y - 1.0f)));
1308 flags.kSaveSprites = value;
1309 flags.kSaveRoomHeaders = value;
1310 flags.kSaveChests = value;
1311 flags.kSavePotItems = value;
1312 flags.kSaveEntrances = value;
1313 flags.kSavePalettes = value;
1314 flags.kSaveCollision = value;
1315 flags.kSaveBlocks = value;
1316 flags.kSaveTorches = value;
1317 flags.kSavePits = value;
1322 ImGui::TextUnformatted(tr(
"Room content to apply"));
1323 constexpr ImGuiTableFlags kFlags =
1324 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
1325 if (ImGui::BeginTable(
"##WorkbenchSaveContentFlags", 2, kFlags,
1326 ImVec2(320.0f, 0.0f))) {
1327 auto draw_checkbox = [](
const char* label,
bool* value) {
1328 ImGui::TableNextColumn();
1329 ImGui::Checkbox(label, value);
1331 ImGui::TableNextRow();
1332 draw_checkbox(
"Room objects", &flags.kSaveObjects);
1333 draw_checkbox(
"Room sprites", &flags.kSaveSprites);
1334 ImGui::TableNextRow();
1335 draw_checkbox(
"Room properties", &flags.kSaveRoomHeaders);
1336 draw_checkbox(
"Chest contents", &flags.kSaveChests);
1337 ImGui::TableNextRow();
1338 draw_checkbox(
"Items under pots", &flags.kSavePotItems);
1339 draw_checkbox(
"Palettes", &flags.kSavePalettes);
1340 ImGui::TableNextRow();
1341 draw_checkbox(
"Custom collision", &flags.kSaveCollision);
1342 ImGui::TableNextColumn();
1343 ImGui::BeginDisabled();
1344 ImGui::Checkbox(
"Water Fill", &flags.kSaveWaterFillZones);
1345 ImGui::EndDisabled();
1346 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1348 "Water Fill saving is fixed when the project opens. Change "
1349 "save_dungeon_water_fill_zones in the .yaze file, then reopen the "
1352 ImGui::TableNextRow();
1353 draw_checkbox(
"Push blocks", &flags.kSaveBlocks);
1354 draw_checkbox(
"Torches", &flags.kSaveTorches);
1355 ImGui::TableNextRow();
1356 draw_checkbox(
"Pit damage", &flags.kSavePits);
1357 draw_checkbox(
"Entrances", &flags.kSaveEntrances);
1361 if (ImGui::SmallButton(tr(
"Select all##WorkbenchSaveContent"))) {
1365 if (ImGui::SmallButton(tr(
"Select none##WorkbenchSaveContent"))) {
1370 const auto apply_room_label =
1371 absl::StrFormat(
ICON_MD_SAVE " Apply room 0x%03X", room_id);
1382 tr(
"Apply uses the checked content. File > Save ROM "
1383 "writes the applied changes to disk."));
1390 if (ImGui::BeginPopup(
"##PitDamageSlotChooser")) {
1391 ImGui::CloseCurrentPopup();
1400 bool enabled =
false;
1401 ImGui::BeginDisabled();
1403 compact ?
"##PitDamageEnabled" : tr(
"Pit damage##PitDamageEnabled"),
1405 ImGui::EndDisabled();
1406 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1407 ImGui::SetTooltip(tr(
"Available after room data loads."));
1414 if (!membership.room_valid) {
1415 ImGui::TextDisabled(tr(
"No valid room selected."));
1419 const uint16_t current_room = membership.room_id;
1420 bool room_deals_damage = membership.deals_damage;
1421 if (ImGui::Checkbox(
1422 compact ?
"##PitDamageEnabled" : tr(
"Pit damage##PitDamageEnabled"),
1423 &room_deals_damage)) {
1425 ImGui::OpenPopup(
"##PitDamageSlotChooser");
1431 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1432 ImGui::BeginTooltip();
1433 ImGui::TextUnformatted(
1434 "Choose which room gives up or receives a pit-damage slot.");
1435 if (membership.dirty) {
1436 ImGui::TextColored(theme.status_warning,
"Pending pit damage change");
1441 ImGui::EndTooltip();
1444 auto show_status = [&]() {
1448 ImGui::PushTextWrapPos(0.0f);
1454 ImGui::PopTextWrapPos();
1457 if (!ImGui::BeginPopup(
"##PitDamageSlotChooser")) {
1463 if (membership.deals_damage) {
1464 if (!membership.suggested_replacement_room.has_value()) {
1465 ImGui::PushTextWrapPos(0.0f);
1466 ImGui::TextDisabled(tr(
"No available non-damaging room slot."));
1467 ImGui::PopTextWrapPos();
1474 ImGui::TextUnformatted(tr(
"Move pit damage to room:"));
1475 ImGui::SetNextItemWidth(88.0f);
1478 &edit_value, 88.0f,
true);
1483 if (ImGui::IsItemHovered()) {
1485 tr(
"Replacement room must not already be in the table."));
1487 const bool replace_clicked =
1488 ImGui::Button(tr(
"Disable pit damage##PitDamageReplaceCurrent"));
1490 if (replace_clicked) {
1496 ? absl::StrFormat(
"Room 0x%03X removed; slot now 0x%03X",
1498 : std::string(status.message());
1504 ImGui::CloseCurrentPopup();
1508 if (!membership.suggested_victim_room.has_value()) {
1509 ImGui::PushTextWrapPos(0.0f);
1510 ImGui::TextDisabled(tr(
"No existing pit-damage slot is available."));
1511 ImGui::PopTextWrapPos();
1518 ImGui::TextUnformatted(tr(
"Take pit damage from room:"));
1519 ImGui::SetNextItemWidth(88.0f);
1527 if (ImGui::IsItemHovered()) {
1528 ImGui::SetTooltip(tr(
"This room will stop dealing pit damage."));
1530 const bool add_clicked =
1531 ImGui::Button(tr(
"Enable pit damage##PitDamageAddCurrent"));
1539 ? absl::StrFormat(
"Room 0x%03X added; replaced 0x%03X",
1541 : std::string(status.message());
1547 ImGui::CloseCurrentPopup();
1553 if (ImGui::Button(tr(
"Cancel##PitDamageCancel"))) {
1555 ImGui::CloseCurrentPopup();
1581 if (
const auto* project = viewer.
project()) {
1590 constexpr const char* kPopupId =
"Dungeon Map##WorkbenchDungeonMapPopup";
1592 const ImVec2 viewport = ImGui::GetMainViewport()->WorkSize;
1593 ImGui::SetNextWindowSize(ImVec2(std::min(1040.0f, viewport.x * 0.85f),
1594 std::min(780.0f, viewport.y * 0.85f)),
1595 ImGuiCond_Appearing);
1596 ImGui::OpenPopup(kPopupId);
1600 bool popup_open =
true;
1601 if (ImGui::BeginPopupModal(kPopupId, &popup_open,
1602 ImGuiWindowFlags_NoSavedSettings)) {
1604 ImGui::CloseCurrentPopup();
1609 const ImVec2 map_size =
1610 ImVec2(std::max(1.0f, ImGui::GetContentRegionAvail().x),
1611 std::max(1.0f, ImGui::GetContentRegionAvail().y -
1612 ImGui::GetFrameHeightWithSpacing()));
1613 if (ImGui::BeginChild(
"##WorkbenchDungeonMapBody", map_size,
false)) {
1618 ImGui::TextDisabled(tr(
"Dungeon map unavailable"));
1622 ImGui::CloseCurrentPopup();
1712 const std::string standalone_id = content->
GetId();
1725 viewer->object_interaction().HandleMouseRelease();
1726 viewer->object_interaction().CancelPlacement();
1727 viewer->object_interaction().mode_manager().CancelCurrentMode();
1742 const std::string standalone_id = content->
GetId();
1743 if (standalone_id.empty() ||
1758 return "custom_collision";
1760 return "water_fill";
1764 return "object_coverage";
1766 return "object_selector";
1774 return "room_graphics";
1789 return "Custom Collision";
1791 return "Water Fill";
1793 return "Minecart Tracks";
1795 return "Object Coverage";
1797 return "Object Selector";
1799 return "Door Tools";
1801 return "Sprite Tools";
1803 return "Item Tools";
1805 return "Room Graphics";
1818 return "Room tag tools are not available.";
1820 return "Custom collision tools are not available.";
1822 return "Water fill tools are not available.";
1824 return "Minecart track tools are not available.";
1826 return "Object coverage is not available.";
1828 return "Object selector is not available.";
1830 return "Door tools are not available.";
1832 return "Sprite tools are not available.";
1834 return "Item tools are not available.";
1836 return "Room graphics tools are not available.";
1838 return "Palette tools are not available.";
1840 return "No Workbench tool selected.";
1842 return "Tool is not available.";
1849 const char* unavailable_message) {
1851 ImGui::TextDisabled(
"%s", unavailable_message);
1854 content->
Draw(
nullptr);
1897 if (
auto* selector =
1899 selector->DrawInWorkbench();
1938 const bool pressed = ImGui::Button(
"Room tools");
1942 "Specialized room tools");
1945 ImGui::OpenPopup(
"##WorkbenchToolPickerPopup");
1946 if (ImGui::BeginPopup(
"##WorkbenchToolPickerPopup")) {
1947 for (
const auto tool : tools) {
1949 const bool selected = ImGui::MenuItem(
1967 const bool back = ImGui::Button(
"Room");
1977 const bool can_pop_out =
1979 ImGui::BeginDisabled(!can_pop_out);
1980 const bool pop_out = ImGui::Button(
"Pop out");
1985 ImGui::EndDisabled();
1986 if (pop_out && can_pop_out) {
1991 ImGui::TextDisabled(
"%s is open in a separate window.",
2000 const bool body_open = ImGui::BeginChild(
"##WorkbenchToolInspectorBody",
2001 ImVec2(0.0f, 0.0f),
false);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
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()
void DrawChestEditorPopup()
bool IsObjectInteractionEnabled() const
bool header_read_only() const
ImVec2 GetConnectedContentSize(int center_room_id)
const project::YazeProject * project() const
void DrawProposalPreview(const DungeonProposalOverlay &overlay, const std::vector< char > &layer_visible, bool show_overlay, float scale)
void DrawDungeonCanvas(int room_id)
void DrawNearbyRoomPreviews(int room_id, float height)
void DrawGraphicsContext(float width=-1.0f)
std::string GetId() const override
Unique identifier for this panel.
void DrawPropertiesInWorkbench()
WindowContent for displaying multiple rooms in a spatial dungeon layout.
InteractionModeManager & mode_manager()
Handles room and entrance selection UI.
ImVector< int > & mutable_active_rooms()
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)
std::string loaded_proposal_path_
void DrawPitDamageControls(int room_id, bool compact=false)
bool room_dungeon_cache_built_
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)
DungeonEntrancesPanel * entrance_navigator_content_
bool pit_damage_status_error_
void FocusEntranceBrowser()
void DrawProposalPreviewWindow(DungeonCanvasViewer &viewer)
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_
std::function< void()> on_pit_damage_changed_
void DrawSidebarModeTabs()
void RequestProposalPreview()
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()
std::function< void()> on_redo_
WindowContent * object_selector_content_
void OpenObjectCoverageTool()
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)
int pit_damage_chooser_room_id_
std::string proposal_error_
std::vector< char > proposal_layer_visible_
void DrawInspectorShelfRoom(DungeonCanvasViewer &viewer)
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.
std::optional< DungeonProposalOverlay > proposal_overlay_
void OpenRoomGraphicsTool()
std::function< std::string()> undo_desc_
void FocusRoomInspector()
std::function< bool()> can_undo_
RoomTagEditorPanel * room_tag_panel_
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]
char proposal_path_[1024]
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
std::function< void(int)> on_save_room_
const char * GetSidebarModeIdForTesting() const
WindowContent * palette_editor_content_
WindowContent * room_graphics_content_
std::string pit_damage_status_message_
std::string GetId() const override
Unique identifier for this panel.
bool LoadProposalOverlay(const std::string &path)
void DrawSplitView(DungeonCanvasViewer &primary_viewer)
void OpenObjectSelectorTool()
RoomMatrixContent * room_matrix_content_
std::function< zelda3::PitDamageTable *()> get_pit_damage_table_
void DrawRoomContextRow(DungeonCanvasViewer &viewer)
void CloseToolInspector()
std::function< void(int)> prepare_room_render_context_
std::function< void()> on_save_all_rooms_
WindowContent * object_coverage_content_
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()
~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 proposal_show_overlay_
bool show_proposal_preview_
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()
float navigator_tools_fraction_
void DrawInspectorShelf(DungeonCanvasViewer &viewer, bool compact)
void BuildRoomDungeonCache()
std::function< DungeonCanvasViewer *()> get_viewer_
WindowContent * door_editor_content_
const char * GetModeName() const
Get mode name for debugging/UI.
WindowContent for placing and managing dungeon pot items.
void set_canvas_editing(bool enabled)
void set_collision_stamping(bool enabled)
void CancelNodePlacement()
void Draw(bool *p_open) override
Draw the panel content.
Browse and place dungeon objects.
void DrawInWorkbench(float preview_height=144.0f)
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.
WindowContent for placing and managing dungeon sprites.
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()
RAII guard for ImGui style colors.
RAII guard for ImGui style vars.
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
Dungeon Room Entrance or Spawn Point.
const absl::Status & load_status() const
#define ICON_MD_GRID_VIEW
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_COMPARE_ARROWS
#define ICON_MD_MORE_HORIZ
#define ICON_MD_DOOR_FRONT
#define ICON_MD_CHEVRON_LEFT
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_PEST_CONTROL
#define ICON_MD_SELECT_ALL
#define ICON_MD_INVENTORY_2
#define ICON_MD_CHEVRON_RIGHT
#define ICON_MD_WORKSPACES
#define ICON_MD_CROP_FREE
#define ICON_MD_EXPAND_MORE
const AgentUITheme & GetTheme()
constexpr float kCompactLeftSidebarScale
float GetCompactSidebarWidth(bool right_sidebar, float min_sidebar_width)
DungeonWorkbenchTestRect CaptureLastItemRectForTesting()
constexpr float kCompactLeftSidebarMinWidth
constexpr float kCompactRightSidebarMinWidth
constexpr float kCompactRightSidebarScale
float ClampWorkbenchPaneWidth(float desired_width, float min_width, float max_width)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
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)
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.
absl::StatusOr< DungeonProposalOverlay > ParseDungeonProposalOverlay(std::string_view json_text, int max_room_id)
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)
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)
float ResolveDungeonWorkbenchNearbyHeight(float width, float height, float requested_height, float item_spacing)
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)
bool CenteredIconButton(const char *label, float button_size)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
constexpr int kNumberOfRooms
absl::StatusOr< EntranceLayout > ResolveEntranceLayout(std::span< const uint8_t > data)
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::vector< ProposalRoom > rooms
std::vector< std::string > notes
std::vector< ProposalLayer > layers
float nearby_rooms_height
bool show_room_context_row
bool show_connected_canvas_view
bool show_right_inspector
DungeonWorkbenchResponsiveLayout responsive
DungeonWorkbenchTestRect replace_current
DungeonWorkbenchTestRect add_current
static constexpr float kStatusBarHeight
static constexpr float kSplitterWidth
static constexpr float kContentMinWidthSidebar
Modern project structure with comprehensive settings consolidation.
std::vector< FileDialogFilter > filters