8#include <initializer_list>
10#include "absl/status/status.h"
11#include "absl/strings/ascii.h"
12#include "absl/strings/match.h"
13#include "absl/strings/str_format.h"
24#include "imgui/misc/cpp/imgui_stdlib.h"
36 : editor_manager_(editor_manager), status_(absl::OkStatus()) {}
167 for (
auto& [name, params] :
popups_) {
168 if (params.is_visible &&
173 if (top ==
nullptr) {
176 OpenPopup(top->
name.c_str());
180 ? ImGuiWindowFlags_None
181 : ImGuiWindowFlags_AlwaysAutoResize;
183 if (BeginPopupModal(top->
name.c_str(),
nullptr, popup_flags)) {
194 std::string name_str(name);
195 auto it =
popups_.find(name_str);
197 if (!it->second.is_visible) {
203 it->second.is_visible =
true;
207 "[PopupManager] Warning: Popup '%s' not registered. Available popups: ",
209 for (
const auto& [key, _] :
popups_) {
210 printf(
"'%s' ", key.c_str());
221 std::string name_str(name);
222 auto it =
popups_.find(name_str);
224 it->second.is_visible =
false;
233 std::function<
void()> draw_function,
240 popups_[name] = std::move(params);
248 std::string name_str(name);
249 auto it =
popups_.find(name_str);
251 return it->second.is_visible;
265 ImGuiIO
const& io = GetIO();
266 ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
267 SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
268 ImGuiWindowFlags flags =
269 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
270 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
271 return Begin(name,
nullptr, flags);
285 IsKeyPressed(ImGuiKey_Space)) {
298 Text(tr(
"Yet Another Zelda3 Editor - v%s"),
300 Text(tr(
"Written by: scawful"));
302 Text(tr(
"Special Thanks: Zarby89, JaredBrian"));
315 Text(tr(
"Title: %s"), current_rom->title().c_str());
322 const auto& layout = profile.lyra_layout;
325 TextUnformatted(
"Overworld Format: Lyra descriptor");
326 Text(
"ROM Format Revision: %u", layout.revision);
327 Text(
"Area Capacity: %u", layout.capacity);
328 Text(
"Active Areas: %u", layout.active_count);
330 ?
"Validation: valid"
331 :
"Validation: invalid");
332 TextUnformatted(
"Write Support: inspection only");
333 if (!layout.error.empty()) {
334 TextWrapped(
"Reason: %s", layout.error.c_str());
336 }
else if (profile.layout_kind ==
339 TextUnformatted(
"Overworld Format: older $EA tail-map expansion");
343 if (project && project->project_opened()) {
347 Text(tr(
"Write Policy: %s"),
350 Text(tr(
"Expected Hash: %s"),
351 project->rom_metadata.expected_hash.empty()
353 : project->rom_metadata.expected_hash.c_str());
357 tr(
"ROM hash mismatch detected"));
362 IsKeyPressed(ImGuiKey_Escape)) {
363 Hide(
"ROM Information");
372 using namespace ImGui;
373 namespace fs = std::filesystem;
375 auto register_widget = [](
const char* name,
const char* type) {
377 std::string(
"SaveAs/") + type +
":" + name, type, GetItemID());
381 TextUnformatted(tr(
"No ROM loaded."));
382 if (Button(tr(
"Close"))) {
396 auto source = fs::absolute(fs::path(rom->
filename()), ec);
397 if (ec || rom->
filename().empty()) {
399 tr(
"The current ROM has no usable folder. Choose Browse.");
403 auto extension = source.extension().string();
404 const auto lower_extension = absl::AsciiStrToLower(extension);
405 if (lower_extension !=
".sfc" && lower_extension !=
".smc") {
409 source.stem().empty() ? std::string(
"rom") : source.stem().string();
421 TextUnformatted(tr(
"Folder:"));
422 PushTextWrapPos(GetCursorPosX() + 480.0f);
425 SetNextItemWidth(360.0f);
427 register_widget(
"Filename",
"input");
433 const bool browse = Button(
436 register_widget(
"Browse",
"button");
439 if (!selected.empty()) {
441 const auto destination = fs::absolute(fs::path(selected), ec);
450 const bool save = Button(absl::StrFormat(
"%s Save",
ICON_MD_SAVE).c_str(),
452 register_widget(
"Save",
"button");
455 if (filename.empty() || filename ==
"." || filename ==
".." ||
458 tr(
"Enter a filename only. Use Browse to choose a different folder.");
460 const auto extension =
461 absl::AsciiStrToLower(filename.extension().string());
462 if (extension !=
".sfc" && extension !=
".smc") {
472 if (!absl::IsCancelled(status)) {
482 register_widget(
"Cancel",
"button");
483 if (cancel || IsKeyPressed(ImGuiKey_Escape)) {
489 using namespace ImGui;
494 tr(
"Controls which data is written during File > Save ROM. Dungeon "
495 "choices also control Apply Room. "
496 "Changes apply immediately."));
499 if (CollapsingHeader(tr(
"Overworld"), ImGuiTreeNodeFlags_DefaultOpen)) {
500 Checkbox(tr(
"Save Overworld Maps"),
502 Checkbox(tr(
"Save Overworld Entrances"),
504 Checkbox(tr(
"Save Overworld Exits"),
506 Checkbox(tr(
"Save Overworld Items"),
508 Checkbox(tr(
"Save Overworld Properties"),
512 if (CollapsingHeader(tr(
"Dungeon"), ImGuiTreeNodeFlags_DefaultOpen)) {
513 Checkbox(tr(
"Save Dungeon Maps"),
515 Checkbox(tr(
"Save Objects"),
517 Checkbox(tr(
"Save Sprites"),
519 Checkbox(tr(
"Save Room Headers"),
521 Checkbox(tr(
"Save Torches"),
525 Checkbox(tr(
"Save Collision"),
528 Checkbox(tr(
"Save Pot Items"),
530 if (IsItemHovered()) {
532 tr(
"Enable to save pending pot item edits, then retry Save "
533 "or Apply Room. No project reopen is needed."));
535 Checkbox(tr(
"Save Entrances"),
537 Checkbox(tr(
"Save Palettes"),
541 if (CollapsingHeader(tr(
"Graphics"), ImGuiTreeNodeFlags_DefaultOpen)) {
542 Checkbox(tr(
"Save Graphics Sheets"),
544 Checkbox(tr(
"Save All Palettes"),
549 if (CollapsingHeader(tr(
"Messages"), ImGuiTreeNodeFlags_DefaultOpen)) {
560 using namespace ImGui;
564 Text(tr(
"No ROM loaded."));
572 std::string backup_dir;
573 if (project && project->project_opened() &&
574 !project->rom_backup_folder.empty()) {
577 backup_dir = std::filesystem::path(rom->
filename()).parent_path().string();
582 TextWrapped(tr(
"Backup folder: %s"), backup_dir.c_str());
587 tr(
"A restored backup is staged in this session. Save ROM to commit "
588 "it. Discard reloads the backing ROM and abandons staged ROM-buffer "
589 "edits; resolve pending graphics, dungeon, or palette edits "
595 toast->Show(absl::StrFormat(
"Discard failed: %s", status.message()),
599 toast->Show(
"Restored backup discarded; reloaded ROM from disk",
610 toast->Show(absl::StrFormat(
"Prune failed: %s", status.message()),
620 if (backups.empty()) {
621 TextDisabled(tr(
"No backups found."));
622 }
else if (BeginTable(
"RomBackupTable", 4,
623 ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
624 ImGuiTableFlags_Resizable)) {
625 TableSetupColumn(
"Timestamp");
626 TableSetupColumn(
"Size");
627 TableSetupColumn(
"Filename");
628 TableSetupColumn(
"Actions");
631 auto format_size = [](uintmax_t bytes) {
632 if (bytes > (1024 * 1024)) {
633 return absl::StrFormat(
"%.2f MB",
634 static_cast<double>(bytes) / (1024 * 1024));
637 return absl::StrFormat(
"%.1f KB",
static_cast<double>(bytes) / 1024.0);
639 return absl::StrFormat(
"%llu B",
static_cast<unsigned long long>(bytes));
642 for (
size_t i = 0; i < backups.size(); ++i) {
643 const auto& backup = backups[i];
646 char time_buffer[32] =
"unknown";
647 if (backup.timestamp != 0) {
650 localtime_s(&local_tm, &backup.timestamp);
652 localtime_r(&backup.timestamp, &local_tm);
654 std::strftime(time_buffer,
sizeof(time_buffer),
"%Y-%m-%d %H:%M:%S",
657 TextUnformatted(time_buffer);
660 TextUnformatted(format_size(backup.size_bytes).c_str());
663 TextUnformatted(backup.filename.c_str());
666 PushID(
static_cast<int>(i));
671 toast->Show(absl::StrFormat(
"Restore failed: %s", status.message()),
675 toast->Show(
"Backup loaded; inspect it, then save ROM to commit",
684 toast->Show(absl::StrFormat(
"Open failed: %s", status.message()),
691 SetClipboardText(backup.path.c_str());
705 using namespace ImGui;
707 static std::string project_name =
"";
708 static std::string project_filepath =
"";
709 static std::string rom_filename =
"";
710 static std::string labels_filename =
"";
711 static std::string code_folder =
"";
713 InputText(tr(
"Project Name"), &project_name);
715 if (Button(absl::StrFormat(
"%s Destination Folder",
ICON_MD_FOLDER).c_str(),
720 Text(
"%s", project_filepath.empty() ?
"(Not set)" : project_filepath.c_str());
728 Text(
"%s", rom_filename.empty() ?
"(Not set)" : rom_filename.c_str());
730 if (Button(absl::StrFormat(
"%s Labels File",
ICON_MD_LABEL).c_str(),
735 Text(
"%s", labels_filename.empty() ?
"(Not set)" : labels_filename.c_str());
737 if (Button(absl::StrFormat(
"%s Code Folder",
ICON_MD_CODE).c_str(),
742 Text(
"%s", code_folder.empty() ?
"(Not set)" : code_folder.c_str());
746 if (Button(absl::StrFormat(
"%s Choose Project File Location",
ICON_MD_SAVE)
749 auto project_file_path =
751 if (!project_file_path.empty()) {
752 if (!(absl::EndsWith(project_file_path,
".yaze") ||
753 absl::EndsWith(project_file_path,
".yazeproj"))) {
754 project_file_path +=
".yaze";
756 project_filepath = project_file_path;
760 if (Button(absl::StrFormat(
"%s Create Project",
ICON_MD_ADD).c_str(),
762 if (!project_filepath.empty() && !project_name.empty() &&
763 !rom_filename.empty()) {
765 "Basic ROM Hack", rom_filename, project_name, project_filepath);
769 if (!labels_filename.empty()) {
772 if (!code_folder.empty()) {
773 project->code_folder = code_folder;
775 if (!labels_filename.empty() || !code_folder.empty()) {
784 project_filepath =
"";
786 labels_filename =
"";
799 project_filepath =
"";
801 labels_filename =
"";
814 auto status_color = [&](
const char* status) -> ImVec4 {
815 if (strcmp(status,
"Stable") == 0 || strcmp(status,
"Working") == 0) {
818 if (strcmp(status,
"Beta") == 0 || strcmp(status,
"Experimental") == 0) {
821 if (strcmp(status,
"Preview") == 0) {
824 if (strcmp(status,
"Not available") == 0) {
833 const char* persistence;
837 auto draw_table = [&](
const char* table_id,
838 std::initializer_list<FeatureRow> rows) {
839 ImGuiTableFlags flags = ImGuiTableFlags_BordersInnerH |
840 ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable;
841 if (!BeginTable(table_id, 4, flags)) {
844 TableSetupColumn(
"Feature", ImGuiTableColumnFlags_WidthStretch);
845 TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 120.0f);
846 TableSetupColumn(
"Save/Load", ImGuiTableColumnFlags_WidthFixed, 180.0f);
847 TableSetupColumn(
"Notes", ImGuiTableColumnFlags_WidthStretch);
850 for (
const auto& row : rows) {
852 TableSetColumnIndex(0);
853 TextUnformatted(row.feature);
854 TableSetColumnIndex(1);
855 TextColored(status_color(row.status),
"%s", row.status);
856 TableSetColumnIndex(2);
857 TextUnformatted(row.persistence);
858 TableSetColumnIndex(3);
859 TextWrapped(
"%s", row.notes);
866 tr(
"Status: Stable = production ready, Beta = usable with gaps, "
867 "Experimental = WIP, Preview = web parity in progress."));
868 TextDisabled(tr(
"See Settings > Feature Flags for ROM-specific toggles."));
871 if (CollapsingHeader(tr(
"Desktop App (yaze)"),
872 ImGuiTreeNodeFlags_DefaultOpen)) {
873 draw_table(
"desktop_features",
875 {
"ROM load/save",
"Stable",
"ROM + backups",
876 "Backups on save when enabled."},
877 {
"Overworld Editor",
"Stable",
"ROM",
878 "Maps/entrances/exits/items; version-gated."},
879 {
"Dungeon Editor",
"Stable",
"ROM",
880 "Room objects/tiles/palettes persist."},
881 {
"Palette Editor",
"Stable",
"ROM",
882 "Palette edits persist; JSON IO pending."},
883 {
"Graphics Editor",
"Beta",
"ROM",
884 "Sheet edits persist; tooling still expanding."},
885 {
"Sprite Editor",
"Stable",
"ROM",
"Sprite edits persist."},
886 {
"Message Editor",
"Stable",
"ROM",
"Text edits persist."},
887 {
"Screen Editor",
"Experimental",
"ROM (partial)",
888 "Save coverage incomplete."},
889 {
"Hex Editor",
"Beta",
"ROM",
"Search UX incomplete."},
890 {
"Assembly/Asar",
"Beta",
"ROM + project",
891 "Patch apply + symbol export."},
892 {
"Emulator",
"Beta",
"Runtime only",
893 "Save-state UI partially wired."},
894 {
"Music Editor",
"Experimental",
"ROM (partial)",
895 "Serialization in progress."},
896 {
"Agent UI",
"Experimental",
".yaze/agent",
897 "Requires AI provider configuration."},
898 {
"Settings/Layouts",
"Beta",
".yaze config",
899 "Layout serialization improving."},
903 if (CollapsingHeader(tr(
"z3ed CLI"))) {
904 draw_table(
"cli_features",
906 {
"ROM read/write/validate",
"Stable",
"ROM file",
907 "Direct command execution."},
908 {
"Agent workflows",
"Stable",
".yaze/proposals + sandboxes",
909 "Commit writes ROM; revert reloads."},
910 {
"Snapshots/restore",
"Stable",
"Sandbox copies",
911 "Supports YAZE_SANDBOX_ROOT override."},
912 {
"Doctor/test suites",
"Stable",
"Reports",
913 "Structured output for automation."},
914 {
"TUI/REPL",
"Stable",
"Session history",
915 "Interactive command palette + logs."},
919 if (CollapsingHeader(tr(
"Web/WASM Preview"))) {
923 {
"ROM load/save",
"Preview",
"IndexedDB + download",
924 "Drag/drop or picker; download for backups."},
925 {
"Editors (OW/Dungeon/Palette/etc.)",
"Preview",
926 "IndexedDB + download",
"Parity work in progress."},
927 {
"Hex Editor",
"Working",
"IndexedDB + download",
928 "Direct ROM editing available."},
929 {
"Asar patching",
"Preview",
"ROM",
"Basic patch apply support."},
930 {
"Emulator",
"Not available",
"N/A",
"Desktop only."},
931 {
"Collaboration",
"Experimental",
"Server",
932 "Requires yaze-server."},
933 {
"AI features",
"Preview",
"Server",
"Requires AI-enabled server."},
943 Text(tr(
"File -> Open"));
944 Text(tr(
"Select a ROM file to open"));
945 Text(tr(
"Supported ROMs (headered or unheadered):"));
946 Text(tr(
"The Legend of Zelda: A Link to the Past"));
947 Text(tr(
"US Version 1.0"));
948 Text(tr(
"JP Version 1.0"));
951 tr(
"ROM files are not bundled. Use a clean, legally obtained copy."));
959 Text(tr(
"Project Menu"));
960 Text(tr(
"Create a new project or open an existing one."));
961 Text(tr(
"Save the project to save the current state of the project."));
963 tr(
"To save a project, you need to first open a ROM and initialize your "
964 "code path and labels file. Label resource manager can be found in "
965 "the View menu. Code path is set in the Code editor after opening a "
969 Hide(
"Manage Project");
976 "YAZE lets you modify 'The Legend of Zelda: A Link to the Past' (US or "
977 "JP) ROMs with modern tooling."));
979 TextWrapped(tr(
"Release Highlights:"));
981 tr(
"AI-assisted workflows via z3ed agent and in-app panels "
982 "(Ollama/Gemini/OpenAI/Anthropic)"));
983 BulletText(tr(
"Clear feature status panels and improved help/tooltips"));
984 BulletText(tr(
"Unified .yaze storage across desktop/CLI/web"));
986 TextWrapped(tr(
"General Tips:"));
987 BulletText(tr(
"Open a clean ROM and save a backup before editing"));
988 BulletText(tr(
"Use Help (F1) for context-aware guidance and shortcuts"));
989 BulletText(tr(
"Coming from ZScream? Open Help → Coming from ZScream"));
991 "Configure AI providers (Ollama/Gemini/OpenAI/Anthropic) in Settings > "
995 Hide(
"Getting Started");
1000 TextWrapped(tr(
"Asar 65816 Assembly Integration"));
1002 tr(
"YAZE includes full Asar assembler support for ROM patching."));
1004 TextWrapped(tr(
"Features:"));
1005 BulletText(tr(
"Cross-platform ROM patching with assembly code"));
1006 BulletText(tr(
"Symbol export with addresses and opcodes"));
1007 BulletText(tr(
"Assembly validation with detailed error reporting"));
1008 BulletText(tr(
"Memory-safe patch application with size checks"));
1011 Hide(
"Asar Integration");
1016 TextWrapped(tr(
"Build Instructions"));
1017 TextWrapped(tr(
"YAZE uses modern CMake for cross-platform builds."));
1019 TextWrapped(tr(
"Quick Start (examples):"));
1020 BulletText(tr(
"cmake --preset mac-dbg | lin-dbg | win-dbg"));
1021 BulletText(tr(
"cmake --build --preset <preset> --target yaze"));
1023 TextWrapped(tr(
"AI Builds:"));
1024 BulletText(tr(
"cmake --preset mac-ai | lin-ai | win-ai"));
1025 BulletText(tr(
"cmake --build --preset <preset> --target yaze z3ed"));
1027 TextWrapped(tr(
"Docs: docs/public/build/quick-reference.md"));
1030 Hide(
"Build Instructions");
1035 TextWrapped(tr(
"Command Line Interface (z3ed)"));
1036 TextWrapped(tr(
"Scriptable ROM editing and AI agent workflows."));
1038 TextWrapped(tr(
"Commands:"));
1039 BulletText(tr(
"z3ed rom-info --rom=zelda3.sfc"));
1040 BulletText(tr(
"z3ed agent simple-chat --rom=zelda3.sfc --ai_provider=auto"));
1041 BulletText(tr(
"z3ed agent plan --rom=zelda3.sfc"));
1042 BulletText(tr(
"z3ed test-list --format json"));
1043 BulletText(tr(
"z3ed patch apply-asar patch.asm --rom=zelda3.sfc"));
1044 BulletText(tr(
"z3ed help dungeon-place-sprite"));
1046 TextWrapped(tr(
"Storage:"));
1048 tr(
"Agent plans/proposals live under ~/.yaze (see docs for details)"));
1056 TextWrapped(tr(
"Troubleshooting"));
1057 TextWrapped(tr(
"Common issues and solutions:"));
1059 BulletText(tr(
"ROM won't load: Check file format (SFC/SMC supported)"));
1061 tr(
"AI agent missing: Start Ollama or set GEMINI_API_KEY/OPENAI_API_KEY/"
1062 "ANTHROPIC_API_KEY (web uses AI_AGENT_ENDPOINT)"));
1063 BulletText(tr(
"Graphics issues: Disable experimental flags in Settings"));
1065 tr(
"Performance: Enable hardware acceleration in display settings"));
1066 BulletText(tr(
"Crashes: Check ROM file integrity and available memory"));
1067 BulletText(tr(
"Layout issues: Reset workspace layouts from View > Layouts"));
1070 Hide(
"Troubleshooting");
1075 TextWrapped(tr(
"Contributing to YAZE"));
1076 TextWrapped(tr(
"YAZE is open source and welcomes contributions!"));
1078 TextWrapped(tr(
"How to contribute:"));
1079 BulletText(tr(
"Fork the repository on GitHub"));
1080 BulletText(tr(
"Create feature branches for new work"));
1081 BulletText(tr(
"Follow C++ coding standards"));
1082 BulletText(tr(
"Include tests for new features"));
1083 BulletText(tr(
"Submit pull requests for review"));
1086 Hide(
"Contributing");
1094 if (CollapsingHeader(
1097 ImGuiTreeNodeFlags_DefaultOpen)) {
1099 tr(
"Feature status/persistence summaries across desktop/CLI/web"));
1100 BulletText(tr(
"Shortcut/help panels now match configured keybindings"));
1101 BulletText(tr(
"Refined onboarding tips and error messaging"));
1102 BulletText(tr(
"Help text refreshed across desktop, CLI, and web"));
1105 if (CollapsingHeader(
1106 absl::StrFormat(
"%s Development & Build System",
ICON_MD_BUILD)
1108 ImGuiTreeNodeFlags_DefaultOpen)) {
1109 BulletText(tr(
"Asar 65816 assembler integration for ROM patching"));
1110 BulletText(tr(
"z3ed CLI + TUI for scripting, test/doctor, and automation"));
1111 BulletText(tr(
"Modern CMake presets for desktop, AI, and web builds"));
1112 BulletText(tr(
"Unified version + storage references for 0.5.1"));
1115 if (CollapsingHeader(
1117 BulletText(tr(
"Improved project metadata + .yaze storage alignment"));
1118 BulletText(tr(
"Stronger error reporting and status feedback"));
1119 BulletText(tr(
"Performance and stability improvements across editors"));
1120 BulletText(tr(
"Expanded logging and diagnostics tooling"));
1123 if (CollapsingHeader(
1124 absl::StrFormat(
"%s Editor Features",
ICON_MD_EDIT).c_str())) {
1125 BulletText(tr(
"Music editor updates with SPC parsing/playback"));
1127 tr(
"AI agent-assisted editing workflows (multi-provider + vision)"));
1128 BulletText(tr(
"Expanded overworld/dungeon tooling and palette accuracy"));
1129 BulletText(tr(
"Web/WASM preview with collaboration hooks"));
1147 TextWrapped(tr(
"Coming from ZScream?"));
1149 "yaze covers the same Zelda 3 editing surfaces with a modern workspace, "
1150 "projects, and CLI. Use this map to find familiar tools."));
1153 if (CollapsingHeader(absl::StrFormat(
"%s Editors",
ICON_MD_APPS).c_str(),
1154 ImGuiTreeNodeFlags_DefaultOpen)) {
1155 BulletText(tr(
"Overworld Editor ≈ ZScream overworld map canvas + tile16"));
1157 tr(
"Dungeon Editor ≈ underworld rooms (objects, sprites, properties)"));
1158 BulletText(tr(
"Graphics / Sprite Editors ≈ GFX + sprite drawing"));
1159 BulletText(tr(
"Palette Editor ≈ palette groups and edits"));
1160 BulletText(tr(
"Message Editor ≈ dialogue / text"));
1162 tr(
"Screen Editor → Overworld Map ≈ pause-menu map art "
1163 "(not the playable overworld)"));
1166 if (CollapsingHeader(
1168 ImGuiTreeNodeFlags_DefaultOpen)) {
1170 tr(
"Left activity bar opens editor categories (like ZScream "
1173 tr(
"Dungeon workbench defaults to ZScream-style: selectors "
1174 "left, inspector right"));
1175 BulletText(tr(
"Ctrl+Shift+P opens the command palette for any action"));
1177 tr(
"F1 / Help drawer shows context shortcuts for the active "
1181 if (CollapsingHeader(
1182 absl::StrFormat(
"%s Projects & ROM files",
ICON_MD_FOLDER).c_str(),
1183 ImGuiTreeNodeFlags_DefaultOpen)) {
1185 tr(
".yaze / .yazeproj track ROM path, flags, and workspace "
1188 tr(
".zsproj can be opened when present — same Open ROM / "
1191 tr(
"Always keep a clean backup; Backup Before Save is on by "
1194 tr(
"ZSO / ZSCustomOverworld versions are detected from the ROM "
1195 "(vanilla, v1, v2, v3)"));
1198 if (CollapsingHeader(
1200 ImGuiTreeNodeFlags_DefaultOpen)) {
1201 BulletText(tr(
"Tile16 selector / workbench ≈ ZScream tile picker + paint"));
1203 tr(
"Map Properties gates ZSO fields (area size, BG colors, "
1204 "animated GFX) by detected version"));
1205 BulletText(tr(
"Screen Editor → Overworld Map is pause-menu art only"));
1208 if (CollapsingHeader(absl::StrFormat(
"%s Saving",
ICON_MD_SAVE).c_str())) {
1209 BulletText(tr(
"File → Save Scope chooses ROM, project, or both"));
1210 BulletText(tr(
"Autosave can be tuned under Settings → Editor Behavior"));
1211 BulletText(tr(
"ROM Backups lists timestamped copies for restore"));
1216 "Tip: first-run toasts point at this guide once per tip. Reset tips in "
1217 "Settings → Editor Behavior if you want them again."));
1225 TextWrapped(tr(
"Workspace Management"));
1227 "YAZE supports multiple ROM sessions and flexible workspace layouts."));
1230 TextWrapped(tr(
"Session Management:"));
1231 BulletText(tr(
"Ctrl+Shift+N: Create new session"));
1232 BulletText(tr(
"Ctrl+Shift+W: Close current session"));
1233 BulletText(tr(
"Ctrl+Tab: Quick session switcher"));
1234 BulletText(tr(
"Each session maintains its own ROM and editor state"));
1237 TextWrapped(tr(
"Layout Management:"));
1238 BulletText(tr(
"Drag window tabs to dock/undock"));
1239 BulletText(tr(
"Ctrl+Shift+S: Save current layout"));
1240 BulletText(tr(
"Ctrl+Shift+O: Load saved layout"));
1241 BulletText(tr(
"F11: Maximize current window"));
1244 TextWrapped(tr(
"Preset Layouts:"));
1245 BulletText(tr(
"Developer: Code, memory, testing tools"));
1246 BulletText(tr(
"Designer: Graphics, palettes, sprites"));
1247 BulletText(tr(
"Modder: All gameplay editing tools"));
1250 Hide(
"Workspace Help");
1256 TextWrapped(tr(
"You have reached the recommended session limit."));
1257 TextWrapped(tr(
"Having too many sessions open may impact performance."));
1259 TextWrapped(tr(
"Consider closing unused sessions or saving your work."));
1262 Hide(
"Session Limit Warning");
1266 Hide(
"Session Limit Warning");
1273 TextWrapped(tr(
"This will reset your current workspace layout to default."));
1274 TextWrapped(tr(
"Any custom window arrangements will be lost."));
1276 TextWrapped(tr(
"Do you want to continue?"));
1279 Hide(
"Layout Reset Confirm");
1284 Hide(
"Layout Reset Confirm");
1294 tr(
"Choose a workspace preset to quickly configure your layout:"));
1301 const char* description;
1305 PresetInfo presets[] = {
1307 "Essential cards only - maximum editing space",
1310 "Debug and development focused - CPU/Memory/Breakpoints",
1313 "Visual and artistic focused - Graphics/Palettes/Sprites",
1316 "Full-featured - All tools available for comprehensive editing",
1319 "Complete overworld editing toolkit with all map tools",
1322 "Complete dungeon editing toolkit with room tools",
1324 {
"Testing",
ICON_MD_SCIENCE,
"Quality assurance and ROM testing layout",
1330 constexpr int kPresetCount = 8;
1333 float button_width = 200.0f;
1334 float button_height = 50.0f;
1336 for (
int i = 0; i < kPresetCount; i++) {
1342 ImVec2(0.0f, 0.5f));
1343 if (Button(absl::StrFormat(
"%s %s", presets[i].icon, presets[i].name)
1345 ImVec2(button_width, button_height))) {
1347 auto preset = presets[i].getter();
1352 for (
const auto& panel_id : preset.default_visible_panels) {
1353 window_manager.OpenWindow(panel_id);
1359 if (IsItemHovered()) {
1361 TextUnformatted(presets[i].description);
1376 if (current_editor) {
1377 auto current_type = current_editor->
type();
1378 window_manager.ResetToDefaults(0, current_type);
1384 if (Button(tr(
"Close"), ImVec2(-1, 0))) {
1397 Text(tr(
"Active Sessions: %zu"), session_count);
1401 if (BeginTable(
"SessionTable", 4,
1402 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1403 TableSetupColumn(
"#", ImGuiTableColumnFlags_WidthFixed, 30.0f);
1404 TableSetupColumn(
"ROM", ImGuiTableColumnFlags_WidthStretch);
1405 TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 80.0f);
1406 TableSetupColumn(
"Actions", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1409 for (
size_t i = 0; i < session_count; i++) {
1413 TableSetColumnIndex(0);
1417 TableSetColumnIndex(1);
1418 if (i == active_session) {
1421 TextUnformatted(rom->
filename().c_str());
1423 TextDisabled(tr(
"(No ROM loaded)"));
1426 TextDisabled(tr(
"Session %zu"), i + 1);
1430 TableSetColumnIndex(2);
1431 if (i == active_session) {
1435 TextDisabled(tr(
"Inactive"));
1439 TableSetColumnIndex(3);
1440 PushID(
static_cast<int>(i));
1442 if (i != active_session) {
1443 if (SmallButton(tr(
"Switch"))) {
1449 BeginDisabled(session_count <= 1);
1450 if (SmallButton(tr(
"Close"))) {
1466 if (Button(absl::StrFormat(
"%s New Session",
ICON_MD_ADD).c_str(),
1472 if (Button(tr(
"Close"), ImVec2(-1, 0))) {
1479 SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
1480 SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX));
1483 TextWrapped(tr(
"Customize your YAZE experience - accessible anytime!"));
1488 float available_height =
1489 GetContentRegionAvail().y - 60;
1490 if (BeginChild(
"DisplaySettingsContent", ImVec2(0, available_height),
true,
1491 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1500 ImGuiIO& io = GetIO();
1502 Text(tr(
"Global Font Scale"));
1503 float font_global_scale = io.FontGlobalScale;
1504 if (SliderFloat(
"##global_scale", &font_global_scale, 0.5f, 2.0f,
"%.2f")) {
1508 io.FontGlobalScale = font_global_scale;
1516 Hide(
"Display Settings");
1521 using namespace ImGui;
1524 Text(tr(
"Feature Flags Configuration"));
1527 BeginChild(
"##FlagsContent", ImVec2(0, -30),
true);
1532 if (BeginTabBar(
"FlagCategories")) {
1533 if (BeginTabItem(tr(
"Overworld"))) {
1537 if (BeginTabItem(tr(
"Dungeon"))) {
1541 if (BeginTabItem(tr(
"Resources"))) {
1545 if (BeginTabItem(tr(
"System"))) {
1561 using namespace ImGui;
1563 Text(tr(
"Data Integrity Check Results"));
1566 BeginChild(
"##IntegrityContent", ImVec2(0, -30),
true);
1570 Text(tr(
"ROM Data Integrity:"));
1578 Text(tr(
"No issues detected."));
1589 using namespace ImGui;
1592 Text(tr(
"Editor manager unavailable."));
1602 Text(tr(
"Pot Item Save Confirmation"));
1604 TextWrapped(tr(
"Dungeon pot item saving is enabled, but %d of %d rooms are "
1609 tr(
"Saving now can overwrite pot items in unloaded rooms. Choose how to "
1613 if (Button(tr(
"Save without pot items"), ImVec2(0, 0))) {
1620 const bool save_anyway = Button(tr(
"Save anyway"), ImVec2(0, 0));
1621 if (ImGui::GetItemID() != 0) {
1623 "dungeon.pot_item_save_confirm/button:Save anyway",
"button",
1625 "Continue the pending dungeon save with pot item writes enabled");
1634 if (Button(tr(
"Cancel"), ImVec2(0, 0))) {
1642 using namespace ImGui;
1645 Text(tr(
"Editor manager unavailable."));
1661 const auto editable_path =
1662 project && project->hack_manifest.loaded() &&
1663 !project->hack_manifest.build_pipeline().dev_rom.empty()
1664 ? project->GetAbsolutePath(
1665 project->hack_manifest.build_pipeline().dev_rom)
1666 : (project ? project->rom_filename : std::string());
1668 Text(tr(
"ROM Write Confirmation"));
1671 tr(
"The loaded ROM hash does not match the project's expected hash."));
1673 Text(tr(
"Role: %s"), role.c_str());
1674 Text(tr(
"Write policy: %s"), policy.c_str());
1675 Text(tr(
"Loaded ROM: %s"),
1676 actual_path.empty() ?
"(unknown)" : actual_path.c_str());
1677 Text(tr(
"Editable target: %s"),
1678 editable_path.empty() ?
"(unset)" : editable_path.c_str());
1679 Text(tr(
"Expected: %s"), expected.empty() ?
"(unset)" : expected.c_str());
1680 Text(tr(
"Actual: %s"), actual.empty() ?
"(unknown)" : actual.c_str());
1683 "Proceeding will write to the current ROM file. This may corrupt a base "
1684 "or release ROM if it is not the intended editable project ROM."));
1687 const bool save_anyway = Button(tr(
"Save anyway"), ImVec2(0, 0));
1689 "RomWriteConfirm/button:Save anyway",
"button", GetItemID());
1694 if (!status.ok() && !absl::IsCancelled(status)) {
1696 toast->Show(absl::StrFormat(
"Save failed: %s", status.message()),
1703 if (Button(tr(
"Cancel"), ImVec2(0, 0)) || IsKeyPressed(ImGuiKey_Escape)) {
1710 using namespace ImGui;
1713 Text(tr(
"Editor manager unavailable."));
1726 tr(
"The following ROM addresses are owned by ASM hooks and will be "
1727 "overwritten on next build. Saving now will write data that asar "
1731 if (!conflicts.empty()) {
1732 if (BeginTable(
"WriteConflictTable", 3,
1733 ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
1734 ImGuiTableFlags_Resizable)) {
1735 TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1736 TableSetupColumn(
"Ownership", ImGuiTableColumnFlags_WidthFixed, 140.0f);
1737 TableSetupColumn(
"Module", ImGuiTableColumnFlags_WidthStretch);
1740 for (
const auto& conflict : conflicts) {
1743 Text(
"$%06X", conflict.address);
1748 if (!conflict.module.empty()) {
1749 TextUnformatted(conflict.module.c_str());
1751 TextDisabled(tr(
"(unknown)"));
1759 Text(tr(
"%zu conflict(s) detected."), conflicts.size());
1762 if (Button(tr(
"Save Anyway"), ImVec2(0, 0))) {
1766 if (!status.ok() && !absl::IsCancelled(status)) {
1768 toast->Show(absl::StrFormat(
"Save failed: %s", status.message()),
1775 if (Button(tr(
"Cancel"), ImVec2(0, 0)) || IsKeyPressed(ImGuiKey_Escape)) {
1782 using namespace ImGui;
1802 const std::string save_label =
1804 if (!save_label.empty()) {
1811 const std::string continue_label =
1820 IsKeyPressed(ImGuiKey_Escape)) {
The EditorManager controls the main editor window and manages the various editor classes.
void ConfirmPendingUnsavedSessionActionSaveAndContinue()
absl::Status SaveRomAs(const std::string &filename)
void CancelPendingUnsavedSessionAction()
void SwitchToSession(size_t index)
absl::Status RestoreRomBackup(const std::string &backup_path)
size_t GetActiveSessionCount() const
Rom * GetCurrentRom() const override
std::vector< editor::RomFileManager::BackupEntry > GetRomBackups() const
void MarkCurrentProjectDirty()
project::RomWritePolicy GetProjectRomWritePolicy() const
absl::Status CreateNewProjectFromRom(const std::string &template_name, const std::string &rom_path, const std::string &project_name, const std::string &project_path=std::string())
void ConfirmPendingUnsavedSessionActionDiscardAndContinue()
std::string GetCurrentRomHash() const
bool HasPendingUnsavedSessionAction() const
void CancelRomWriteConfirm()
bool IsRomBackupRestorePending() const
absl::Status PruneRomBackups()
void SetFontGlobalScale(float scale)
void ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
auto GetCurrentEditor() const -> Editor *override
WorkspaceWindowManager & window_manager()
std::string GetProjectExpectedRomHash() const
absl::Status SaveProject()
absl::Status DiscardPendingRomBackupRestore()
const std::vector< core::WriteConflict > & pending_write_conflicts() const
std::string GetPendingUnsavedSessionActionContinueLabel() const
std::string GetPendingUnsavedSessionActionPrompt() const
project::RomRole GetProjectRomRole() const
void BypassWriteConflictOnce()
void ClearPendingWriteConflicts()
size_t GetCurrentSessionIndex() const
size_t GetCurrentSessionId() const
Stable workspace identity; unlike the UI index, it does not compact.
project::YazeProject * GetCurrentProject()
bool IsRomHashMismatch() const
absl::Status ResumePendingRomSave()
int pending_pot_item_unloaded_rooms() const
int pending_pot_item_total_rooms() const
absl::Status OpenRomOrProject(const std::string &filename)
void RemoveSession(size_t index)
std::string GetPendingUnsavedSessionActionSaveLabel() const
ToastManager * toast_manager()
static PanelLayoutPreset GetLogicDebuggerPreset()
Get the "logic debugger" workspace preset (QA and debug focused)
static PanelLayoutPreset GetDungeonMasterPreset()
Get the "dungeon master" workspace preset.
static PanelLayoutPreset GetAudioEngineerPreset()
Get the "audio engineer" workspace preset (music focused)
static PanelLayoutPreset GetDesignerPreset()
Get the "designer" workspace preset (visual-focused)
static PanelLayoutPreset GetOverworldArtistPreset()
Get the "overworld artist" workspace preset.
static PanelLayoutPreset GetModderPreset()
Get the "modder" workspace preset (full-featured)
static PanelLayoutPreset GetMinimalPreset()
Get the "minimal" workspace preset (minimal cards)
static PanelLayoutPreset GetDeveloperPreset()
Get the "developer" workspace preset (debug-focused)
void HideAll(size_t session_id)
RAII guard for ImGui style vars.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_LANDSCAPE
#define ICON_MD_DOOR_SLIDING
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_DISPLAY_SETTINGS
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DESCRIPTION
#define ICON_MD_DASHBOARD
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_CONTENT_COPY
#define ICON_MD_CROP_FREE
#define ICON_MD_DELETE_SWEEP
std::string AddressOwnershipToString(AddressOwnership ownership)
PopupType
Type classification for popups to enable future filtering and organization.
ImVec4 ConvertColorToImVec4(const Color &color)
void DrawDisplaySettingsForPopup(ImGuiStyle *ref)
constexpr ImVec2 kDefaultModalSize
void TextWithSeparators(const absl::string_view &text)
std::string RomRoleToString(RomRole role)
std::string RomWritePolicyToString(RomWritePolicy policy)
std::string HexLongLong(uint64_t qword, HexStringParams params)
FileDialogOptions MakeRomFileDialogOptions(bool include_all_files)
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
Room transition destination.
Defines default panel visibility for an editor type.
std::string labels_filename
std::string GetAbsolutePath(const std::string &relative_path) const
Public YAZE API umbrella header.