7#include <initializer_list>
9#include "absl/status/status.h"
10#include "absl/strings/match.h"
11#include "absl/strings/str_format.h"
21#include "imgui/misc/cpp/imgui_stdlib.h"
32 : editor_manager_(editor_manager), status_(absl::OkStatus()) {}
34void PopupManager::Initialize() {
54 popups_[PopupID::kSaveAs] = {PopupID::kSaveAs, PopupType::kFileOperation,
55 false,
false, [
this]() {
58 popups_[PopupID::kSaveScope] = {PopupID::kSaveScope, PopupType::kSettings,
59 false,
true, [
this]() {
62 popups_[PopupID::kNewProject] = {
63 PopupID::kNewProject, PopupType::kFileOperation,
false,
false, [
this]() {
64 DrawNewProjectPopup();
66 popups_[PopupID::kManageProject] = {PopupID::kManageProject,
67 PopupType::kFileOperation,
false,
false,
69 DrawManageProjectPopup();
71 popups_[PopupID::kRomBackups] = {PopupID::kRomBackups,
72 PopupType::kFileOperation,
false,
true,
73 [
this]() { DrawRomBackupManagerPopup(); }};
76 popups_[PopupID::kAbout] = {PopupID::kAbout, PopupType::kInfo,
false,
false,
80 popups_[PopupID::kRomInfo] = {PopupID::kRomInfo, PopupType::kInfo,
false,
84 popups_[PopupID::kSupportedFeatures] = {
85 PopupID::kSupportedFeatures, PopupType::kInfo,
false,
false, [
this]() {
86 DrawSupportedFeaturesPopup();
88 popups_[PopupID::kOpenRomHelp] = {PopupID::kOpenRomHelp, PopupType::kHelp,
89 false,
false, [
this]() {
90 DrawOpenRomHelpPopup();
94 popups_[PopupID::kGettingStarted] = {
95 PopupID::kGettingStarted, PopupType::kHelp,
false,
false, [
this]() {
96 DrawGettingStartedPopup();
98 popups_[PopupID::kAsarIntegration] = {
99 PopupID::kAsarIntegration, PopupType::kHelp,
false,
false, [
this]() {
100 DrawAsarIntegrationPopup();
102 popups_[PopupID::kBuildInstructions] = {
103 PopupID::kBuildInstructions, PopupType::kHelp,
false,
false, [
this]() {
104 DrawBuildInstructionsPopup();
106 popups_[PopupID::kCLIUsage] = {PopupID::kCLIUsage, PopupType::kHelp,
false,
110 popups_[PopupID::kTroubleshooting] = {
111 PopupID::kTroubleshooting, PopupType::kHelp,
false,
false, [
this]() {
112 DrawTroubleshootingPopup();
114 popups_[PopupID::kContributing] = {PopupID::kContributing, PopupType::kHelp,
115 false,
false, [
this]() {
116 DrawContributingPopup();
118 popups_[PopupID::kWhatsNew] = {PopupID::kWhatsNew, PopupType::kHelp,
false,
124 popups_[PopupID::kDisplaySettings] = {PopupID::kDisplaySettings,
125 PopupType::kSettings,
false,
128 DrawDisplaySettingsPopup();
130 popups_[PopupID::kFeatureFlags] = {
131 PopupID::kFeatureFlags, PopupType::kSettings,
false,
true,
133 DrawFeatureFlagsPopup();
137 popups_[PopupID::kWorkspaceHelp] = {PopupID::kWorkspaceHelp, PopupType::kHelp,
138 false,
false, [
this]() {
139 DrawWorkspaceHelpPopup();
141 popups_[PopupID::kSessionLimitWarning] = {PopupID::kSessionLimitWarning,
142 PopupType::kWarning,
false,
false,
144 DrawSessionLimitWarningPopup();
146 popups_[PopupID::kLayoutResetConfirm] = {PopupID::kLayoutResetConfirm,
147 PopupType::kConfirmation,
false,
149 DrawLayoutResetConfirmPopup();
152 popups_[PopupID::kLayoutPresets] = {
153 PopupID::kLayoutPresets, PopupType::kSettings,
false,
false, [
this]() {
154 DrawLayoutPresetsPopup();
157 popups_[PopupID::kSessionManager] = {
158 PopupID::kSessionManager, PopupType::kSettings,
false,
true, [
this]() {
159 DrawSessionManagerPopup();
163 popups_[PopupID::kDataIntegrity] = {PopupID::kDataIntegrity, PopupType::kInfo,
166 DrawDataIntegrityPopup();
169 popups_[PopupID::kDungeonPotItemSaveConfirm] = {
170 PopupID::kDungeonPotItemSaveConfirm, PopupType::kConfirmation,
false,
171 false, [
this]() { DrawDungeonPotItemSaveConfirmPopup(); }};
172 popups_[PopupID::kRomWriteConfirm] = {
173 PopupID::kRomWriteConfirm, PopupType::kConfirmation,
false,
false,
174 [
this]() { DrawRomWriteConfirmPopup(); }};
175 popups_[PopupID::kWriteConflictWarning] = {
176 PopupID::kWriteConflictWarning, PopupType::kWarning,
false,
true,
177 [
this]() { DrawWriteConflictWarningPopup(); }};
180void PopupManager::DrawPopups() {
185 for (
auto& [name, params] : popups_) {
186 if (params.is_visible) {
187 OpenPopup(
name.c_str());
190 ImGuiWindowFlags popup_flags = params.allow_resize
191 ? ImGuiWindowFlags_None
192 : ImGuiWindowFlags_AlwaysAutoResize;
194 if (BeginPopupModal(
name.c_str(),
nullptr, popup_flags)) {
195 params.draw_function();
202void PopupManager::Show(
const char* name) {
207 std::string name_str(name);
208 auto it = popups_.find(name_str);
209 if (it != popups_.end()) {
210 it->second.is_visible =
true;
214 "[PopupManager] Warning: Popup '%s' not registered. Available popups: ",
216 for (
const auto& [key, _] : popups_) {
217 printf(
"'%s' ",
key.c_str());
223void PopupManager::Hide(
const char* name) {
228 std::string name_str(name);
229 auto it = popups_.find(name_str);
230 if (it != popups_.end()) {
231 it->second.is_visible =
false;
236bool PopupManager::IsVisible(
const char* name)
const {
241 std::string name_str(name);
242 auto it = popups_.find(name_str);
243 if (it != popups_.end()) {
244 return it->second.is_visible;
249void PopupManager::SetStatus(
const absl::Status& status) {
252 prev_status_ = status;
257bool PopupManager::BeginCentered(
const char* name) {
258 ImGuiIO
const& io = GetIO();
259 ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
260 SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
261 ImGuiWindowFlags flags =
262 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
263 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
264 return Begin(name,
nullptr, flags);
267void PopupManager::DrawStatusPopup() {
268 if (show_status_ && BeginCentered(
"StatusWindow")) {
270 Text(
"%s", prev_status_.ToString().c_str());
278 show_status_ =
false;
279 status_ = absl::OkStatus();
283 SetClipboardText(prev_status_.ToString().c_str());
289void PopupManager::DrawAboutPopup() {
290 Text(
"Yet Another Zelda3 Editor - v%s", editor_manager_->version().c_str());
291 Text(
"Written by: scawful");
293 Text(
"Special Thanks: Zarby89, JaredBrian");
301void PopupManager::DrawRomInfoPopup() {
302 auto* current_rom = editor_manager_->GetCurrentRom();
306 Text(
"Title: %s", current_rom->title().c_str());
307 Text(
"ROM Size: %s", util::HexLongLong(current_rom->size()).c_str());
309 editor_manager_->GetCurrentRomHash().empty()
311 : editor_manager_->GetCurrentRomHash().c_str());
313 auto* project = editor_manager_->GetCurrentProject();
314 if (project && project->project_opened()) {
316 Text(
"Role: %s", project::RomRoleToString(project->rom_metadata.role).c_str());
317 Text(
"Write Policy: %s",
318 project::RomWritePolicyToString(project->rom_metadata.write_policy)
320 Text(
"Expected Hash: %s",
321 project->rom_metadata.expected_hash.empty()
323 : project->rom_metadata.expected_hash.c_str());
324 if (editor_manager_->IsRomHashMismatch()) {
325 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
326 TextColored(gui::ConvertColorToImVec4(theme.warning),
327 "ROM hash mismatch detected");
333 Hide(
"ROM Information");
337void PopupManager::DrawSaveAsPopup() {
338 using namespace ImGui;
343 static std::string save_as_filename =
"";
344 if (editor_manager_->GetCurrentRom() && save_as_filename.empty()) {
345 save_as_filename = editor_manager_->GetCurrentRom()->title();
348 InputText(
"Filename", &save_as_filename);
354 util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename,
"sfc");
355 if (!file_path.empty()) {
356 save_as_filename = file_path;
361 if (Button(absl::StrFormat(
"%s Save",
ICON_MD_SAVE).c_str(),
363 if (!save_as_filename.empty()) {
365 std::string final_filename = save_as_filename;
366 if (final_filename.find(
".sfc") == std::string::npos &&
367 final_filename.find(
".smc") == std::string::npos) {
368 final_filename +=
".sfc";
371 auto status = editor_manager_->SaveRomAs(final_filename);
373 save_as_filename =
"";
374 Hide(PopupID::kSaveAs);
382 save_as_filename =
"";
383 Hide(PopupID::kSaveAs);
387void PopupManager::DrawSaveScopePopup() {
388 using namespace ImGui;
393 "Controls which data is written during File > Save ROM. "
394 "Changes apply immediately.");
398 Checkbox(
"Save Overworld Maps",
399 &core::FeatureFlags::get().overworld.kSaveOverworldMaps);
400 Checkbox(
"Save Overworld Entrances",
401 &core::FeatureFlags::get().overworld.kSaveOverworldEntrances);
402 Checkbox(
"Save Overworld Exits",
403 &core::FeatureFlags::get().overworld.kSaveOverworldExits);
404 Checkbox(
"Save Overworld Items",
405 &core::FeatureFlags::get().overworld.kSaveOverworldItems);
406 Checkbox(
"Save Overworld Properties",
407 &core::FeatureFlags::get().overworld.kSaveOverworldProperties);
411 Checkbox(
"Save Dungeon Maps", &core::FeatureFlags::get().kSaveDungeonMaps);
412 Checkbox(
"Save Objects", &core::FeatureFlags::get().dungeon.kSaveObjects);
413 Checkbox(
"Save Sprites", &core::FeatureFlags::get().dungeon.kSaveSprites);
414 Checkbox(
"Save Room Headers",
415 &core::FeatureFlags::get().dungeon.kSaveRoomHeaders);
416 Checkbox(
"Save Torches", &core::FeatureFlags::get().dungeon.kSaveTorches);
417 Checkbox(
"Save Pits", &core::FeatureFlags::get().dungeon.kSavePits);
418 Checkbox(
"Save Blocks", &core::FeatureFlags::get().dungeon.kSaveBlocks);
419 Checkbox(
"Save Collision",
420 &core::FeatureFlags::get().dungeon.kSaveCollision);
421 Checkbox(
"Save Chests", &core::FeatureFlags::get().dungeon.kSaveChests);
422 Checkbox(
"Save Pot Items",
423 &core::FeatureFlags::get().dungeon.kSavePotItems);
424 Checkbox(
"Save Palettes",
425 &core::FeatureFlags::get().dungeon.kSavePalettes);
429 Checkbox(
"Save Graphics Sheets",
430 &core::FeatureFlags::get().kSaveGraphicsSheet);
431 Checkbox(
"Save All Palettes", &core::FeatureFlags::get().kSaveAllPalettes);
432 Checkbox(
"Save Gfx Groups", &core::FeatureFlags::get().kSaveGfxGroups);
436 Checkbox(
"Save Message Text", &core::FeatureFlags::get().kSaveMessages);
441 Hide(PopupID::kSaveScope);
445void PopupManager::DrawRomBackupManagerPopup() {
446 using namespace ImGui;
448 auto*
rom = editor_manager_->GetCurrentRom();
450 Text(
"No ROM loaded.");
452 Hide(PopupID::kRomBackups);
457 const auto* project = editor_manager_->GetCurrentProject();
458 std::string backup_dir;
459 if (project && project->project_opened() &&
460 !project->rom_backup_folder.empty()) {
461 backup_dir = project->GetAbsolutePath(project->rom_backup_folder);
463 backup_dir = std::filesystem::path(
rom->
filename()).parent_path().string();
468 TextWrapped(
"Backup folder: %s", backup_dir.c_str());
471 auto status = editor_manager_->PruneRomBackups();
473 if (
auto* toast = editor_manager_->toast_manager()) {
474 toast->Show(absl::StrFormat(
"Prune failed: %s", status.message()),
477 }
else if (
auto* toast = editor_manager_->toast_manager()) {
478 toast->Show(
"Backups pruned", ToastType::kSuccess);
483 auto backups = editor_manager_->GetRomBackups();
484 if (backups.empty()) {
485 TextDisabled(
"No backups found.");
486 }
else if (BeginTable(
"RomBackupTable", 4,
487 ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
488 ImGuiTableFlags_Resizable)) {
489 TableSetupColumn(
"Timestamp");
490 TableSetupColumn(
"Size");
491 TableSetupColumn(
"Filename");
492 TableSetupColumn(
"Actions");
495 auto format_size = [](uintmax_t bytes) {
496 if (bytes > (1024 * 1024)) {
497 return absl::StrFormat(
"%.2f MB",
498 static_cast<double>(bytes) / (1024 * 1024));
501 return absl::StrFormat(
"%.1f KB",
502 static_cast<double>(bytes) / 1024.0);
504 return absl::StrFormat(
"%llu B",
505 static_cast<unsigned long long>(bytes));
508 for (
size_t i = 0; i < backups.size(); ++i) {
509 const auto& backup = backups[i];
512 char time_buffer[32] =
"unknown";
513 if (backup.timestamp != 0) {
516 localtime_s(&local_tm, &backup.timestamp);
518 localtime_r(&backup.timestamp, &local_tm);
520 std::strftime(time_buffer,
sizeof(time_buffer),
"%Y-%m-%d %H:%M:%S",
523 TextUnformatted(time_buffer);
526 TextUnformatted(format_size(backup.size_bytes).c_str());
529 TextUnformatted(backup.filename.c_str());
532 PushID(
static_cast<int>(i));
534 auto status = editor_manager_->RestoreRomBackup(backup.path);
536 if (
auto* toast = editor_manager_->toast_manager()) {
538 absl::StrFormat(
"Restore failed: %s", status.message()),
541 }
else if (
auto* toast = editor_manager_->toast_manager()) {
542 toast->Show(
"ROM restored from backup", ToastType::kSuccess);
547 auto status = editor_manager_->OpenRomOrProject(backup.path);
549 if (
auto* toast = editor_manager_->toast_manager()) {
551 absl::StrFormat(
"Open failed: %s", status.message()),
558 SetClipboardText(backup.path.c_str());
567 Hide(PopupID::kRomBackups);
571void PopupManager::DrawNewProjectPopup() {
572 using namespace ImGui;
574 static std::string project_name =
"";
575 static std::string project_filepath =
"";
576 static std::string rom_filename =
"";
577 static std::string labels_filename =
"";
578 static std::string code_folder =
"";
580 InputText(
"Project Name", &project_name);
582 if (Button(absl::StrFormat(
"%s Destination Folder",
ICON_MD_FOLDER).c_str(),
584 project_filepath = util::FileDialogWrapper::ShowOpenFolderDialog();
587 Text(
"%s", project_filepath.empty() ?
"(Not set)" : project_filepath.c_str());
591 rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(
592 util::MakeRomFileDialogOptions(
false));
595 Text(
"%s", rom_filename.empty() ?
"(Not set)" : rom_filename.c_str());
597 if (Button(absl::StrFormat(
"%s Labels File",
ICON_MD_LABEL).c_str(),
599 labels_filename = util::FileDialogWrapper::ShowOpenFileDialog();
602 Text(
"%s", labels_filename.empty() ?
"(Not set)" : labels_filename.c_str());
604 if (Button(absl::StrFormat(
"%s Code Folder",
ICON_MD_CODE).c_str(),
606 code_folder = util::FileDialogWrapper::ShowOpenFolderDialog();
609 Text(
"%s", code_folder.empty() ?
"(Not set)" : code_folder.c_str());
613 if (Button(absl::StrFormat(
"%s Choose Project File Location",
ICON_MD_SAVE)
616 auto project_file_path =
617 util::FileDialogWrapper::ShowSaveFileDialog(project_name,
"yaze");
618 if (!project_file_path.empty()) {
619 if (!(absl::EndsWith(project_file_path,
".yaze") ||
620 absl::EndsWith(project_file_path,
".yazeproj"))) {
621 project_file_path +=
".yaze";
623 project_filepath = project_file_path;
627 if (Button(absl::StrFormat(
"%s Create Project",
ICON_MD_ADD).c_str(),
629 if (!project_filepath.empty() && !project_name.empty()) {
630 auto status = editor_manager_->CreateNewProject();
634 project_filepath =
"";
636 labels_filename =
"";
638 Hide(PopupID::kNewProject);
647 project_filepath =
"";
649 labels_filename =
"";
651 Hide(PopupID::kNewProject);
655void PopupManager::DrawSupportedFeaturesPopup() {
656 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
657 const ImVec4 status_ok = gui::ConvertColorToImVec4(theme.success);
658 const ImVec4 status_warn = gui::ConvertColorToImVec4(theme.warning);
659 const ImVec4 status_info = gui::ConvertColorToImVec4(theme.info);
660 const ImVec4 status_error = gui::ConvertColorToImVec4(theme.error);
662 auto status_color = [&](
const char* status) -> ImVec4 {
663 if (strcmp(status,
"Stable") == 0 || strcmp(status,
"Working") == 0) {
666 if (strcmp(status,
"Beta") == 0 || strcmp(status,
"Experimental") == 0) {
669 if (strcmp(status,
"Preview") == 0) {
672 if (strcmp(status,
"Not available") == 0) {
681 const char* persistence;
685 auto draw_table = [&](
const char* table_id,
686 std::initializer_list<FeatureRow> rows) {
687 ImGuiTableFlags flags = ImGuiTableFlags_BordersInnerH |
688 ImGuiTableFlags_RowBg |
689 ImGuiTableFlags_Resizable;
690 if (!BeginTable(table_id, 4, flags)) {
693 TableSetupColumn(
"Feature", ImGuiTableColumnFlags_WidthStretch);
694 TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 120.0f);
695 TableSetupColumn(
"Save/Load", ImGuiTableColumnFlags_WidthFixed, 180.0f);
696 TableSetupColumn(
"Notes", ImGuiTableColumnFlags_WidthStretch);
699 for (
const auto& row : rows) {
701 TableSetColumnIndex(0);
702 TextUnformatted(row.feature);
703 TableSetColumnIndex(1);
704 TextColored(status_color(row.status),
"%s", row.status);
705 TableSetColumnIndex(2);
706 TextUnformatted(row.persistence);
707 TableSetColumnIndex(3);
708 TextWrapped(
"%s", row.notes);
715 "Status: Stable = production ready, Beta = usable with gaps, "
716 "Experimental = WIP, Preview = web parity in progress.");
717 TextDisabled(
"See Settings > Feature Flags for ROM-specific toggles.");
720 if (
CollapsingHeader(
"Desktop App (yaze)", ImGuiTreeNodeFlags_DefaultOpen)) {
724 {
"ROM load/save",
"Stable",
"ROM + backups",
725 "Backups on save when enabled."},
726 {
"Overworld Editor",
"Stable",
"ROM",
727 "Maps/entrances/exits/items; version-gated."},
728 {
"Dungeon Editor",
"Stable",
"ROM",
729 "Room objects/tiles/palettes persist."},
730 {
"Palette Editor",
"Stable",
"ROM",
731 "Palette edits persist; JSON IO pending."},
732 {
"Graphics Editor",
"Beta",
"ROM",
733 "Sheet edits persist; tooling still expanding."},
734 {
"Sprite Editor",
"Stable",
"ROM",
"Sprite edits persist."},
735 {
"Message Editor",
"Stable",
"ROM",
"Text edits persist."},
736 {
"Screen Editor",
"Experimental",
"ROM (partial)",
737 "Save coverage incomplete."},
738 {
"Hex Editor",
"Beta",
"ROM",
"Search UX incomplete."},
739 {
"Assembly/Asar",
"Beta",
"ROM + project",
740 "Patch apply + symbol export."},
741 {
"Emulator",
"Beta",
"Runtime only",
742 "Save-state UI partially wired."},
743 {
"Music Editor",
"Experimental",
"ROM (partial)",
744 "Serialization in progress."},
745 {
"Agent UI",
"Experimental",
".yaze/agent",
746 "Requires AI provider configuration."},
747 {
"Settings/Layouts",
"Beta",
".yaze config",
748 "Layout serialization improving."},
756 {
"ROM read/write/validate",
"Stable",
"ROM file",
757 "Direct command execution."},
758 {
"Agent workflows",
"Stable",
".yaze/proposals + sandboxes",
759 "Commit writes ROM; revert reloads."},
760 {
"Snapshots/restore",
"Stable",
"Sandbox copies",
761 "Supports YAZE_SANDBOX_ROOT override."},
762 {
"Doctor/test suites",
"Stable",
"Reports",
763 "Structured output for automation."},
764 {
"TUI/REPL",
"Stable",
"Session history",
765 "Interactive command palette + logs."},
773 {
"ROM load/save",
"Preview",
"IndexedDB + download",
774 "Drag/drop or picker; download for backups."},
775 {
"Editors (OW/Dungeon/Palette/etc.)",
"Preview",
776 "IndexedDB + download",
"Parity work in progress."},
777 {
"Hex Editor",
"Working",
"IndexedDB + download",
778 "Direct ROM editing available."},
779 {
"Asar patching",
"Preview",
"ROM",
780 "Basic patch apply support."},
781 {
"Emulator",
"Not available",
"N/A",
"Desktop only."},
782 {
"Collaboration",
"Experimental",
"Server",
783 "Requires yaze-server."},
784 {
"AI features",
"Preview",
"Server",
785 "Requires AI-enabled server."},
790 Hide(PopupID::kSupportedFeatures);
794void PopupManager::DrawOpenRomHelpPopup() {
795 Text(
"File -> Open");
796 Text(
"Select a ROM file to open");
797 Text(
"Supported ROMs (headered or unheadered):");
798 Text(
"The Legend of Zelda: A Link to the Past");
799 Text(
"US Version 1.0");
800 Text(
"JP Version 1.0");
802 TextWrapped(
"ROM files are not bundled. Use a clean, legally obtained copy.");
809void PopupManager::DrawManageProjectPopup() {
810 Text(
"Project Menu");
811 Text(
"Create a new project or open an existing one.");
812 Text(
"Save the project to save the current state of the project.");
814 "To save a project, you need to first open a ROM and initialize your "
815 "code path and labels file. Label resource manager can be found in "
816 "the View menu. Code path is set in the Code editor after opening a "
820 Hide(
"Manage Project");
824void PopupManager::DrawGettingStartedPopup() {
827 "YAZE lets you modify 'The Legend of Zelda: A Link to the Past' (US or "
828 "JP) ROMs with modern tooling.");
830 TextWrapped(
"Release Highlights:");
832 "AI-assisted workflows via z3ed agent and in-app panels "
833 "(Ollama/Gemini/OpenAI/Anthropic)");
834 BulletText(
"Clear feature status panels and improved help/tooltips");
835 BulletText(
"Unified .yaze storage across desktop/CLI/web");
837 TextWrapped(
"General Tips:");
838 BulletText(
"Open a clean ROM and save a backup before editing");
839 BulletText(
"Use Help (F1) for context-aware guidance and shortcuts");
841 "Configure AI providers (Ollama/Gemini/OpenAI/Anthropic) in Settings > "
845 Hide(
"Getting Started");
849void PopupManager::DrawAsarIntegrationPopup() {
850 TextWrapped(
"Asar 65816 Assembly Integration");
852 "YAZE includes full Asar assembler support for ROM patching.");
854 TextWrapped(
"Features:");
855 BulletText(
"Cross-platform ROM patching with assembly code");
856 BulletText(
"Symbol export with addresses and opcodes");
857 BulletText(
"Assembly validation with detailed error reporting");
858 BulletText(
"Memory-safe patch application with size checks");
861 Hide(
"Asar Integration");
865void PopupManager::DrawBuildInstructionsPopup() {
866 TextWrapped(
"Build Instructions");
867 TextWrapped(
"YAZE uses modern CMake for cross-platform builds.");
869 TextWrapped(
"Quick Start (examples):");
870 BulletText(
"cmake --preset mac-dbg | lin-dbg | win-dbg");
871 BulletText(
"cmake --build --preset <preset> --target yaze");
873 TextWrapped(
"AI Builds:");
874 BulletText(
"cmake --preset mac-ai | lin-ai | win-ai");
875 BulletText(
"cmake --build --preset <preset> --target yaze z3ed");
877 TextWrapped(
"Docs: docs/public/build/quick-reference.md");
880 Hide(
"Build Instructions");
884void PopupManager::DrawCLIUsagePopup() {
885 TextWrapped(
"Command Line Interface (z3ed)");
886 TextWrapped(
"Scriptable ROM editing and AI agent workflows.");
888 TextWrapped(
"Commands:");
890 BulletText(
"z3ed agent simple-chat --rom=zelda3.sfc --ai_provider=auto");
891 BulletText(
"z3ed agent plan --rom=zelda3.sfc");
893 BulletText(
"z3ed patch apply-asar patch.asm --rom=zelda3.sfc");
896 TextWrapped(
"Storage:");
897 BulletText(
"Agent plans/proposals live under ~/.yaze (see docs for details)");
904void PopupManager::DrawTroubleshootingPopup() {
905 TextWrapped(
"Troubleshooting");
906 TextWrapped(
"Common issues and solutions:");
908 BulletText(
"ROM won't load: Check file format (SFC/SMC supported)");
910 "AI agent missing: Start Ollama or set GEMINI_API_KEY/OPENAI_API_KEY/"
911 "ANTHROPIC_API_KEY (web uses AI_AGENT_ENDPOINT)");
912 BulletText(
"Graphics issues: Disable experimental flags in Settings");
913 BulletText(
"Performance: Enable hardware acceleration in display settings");
914 BulletText(
"Crashes: Check ROM file integrity and available memory");
915 BulletText(
"Layout issues: Reset workspace layouts from View > Layouts");
918 Hide(
"Troubleshooting");
922void PopupManager::DrawContributingPopup() {
923 TextWrapped(
"Contributing to YAZE");
924 TextWrapped(
"YAZE is open source and welcomes contributions!");
926 TextWrapped(
"How to contribute:");
928 BulletText(
"Create feature branches for new work");
931 BulletText(
"Submit pull requests for review");
934 Hide(
"Contributing");
938void PopupManager::DrawWhatsNewPopup() {
945 ImGuiTreeNodeFlags_DefaultOpen)) {
946 BulletText(
"Feature status/persistence summaries across desktop/CLI/web");
947 BulletText(
"Shortcut/help panels now match configured keybindings");
948 BulletText(
"Refined onboarding tips and error messaging");
949 BulletText(
"Help text refreshed across desktop, CLI, and web");
953 absl::StrFormat(
"%s Development & Build System",
ICON_MD_BUILD)
955 ImGuiTreeNodeFlags_DefaultOpen)) {
956 BulletText(
"Asar 65816 assembler integration for ROM patching");
957 BulletText(
"z3ed CLI + TUI for scripting, test/doctor, and automation");
958 BulletText(
"Modern CMake presets for desktop, AI, and web builds");
959 BulletText(
"Unified version + storage references for 0.5.1");
964 BulletText(
"Improved project metadata + .yaze storage alignment");
965 BulletText(
"Stronger error reporting and status feedback");
966 BulletText(
"Performance and stability improvements across editors");
967 BulletText(
"Expanded logging and diagnostics tooling");
971 absl::StrFormat(
"%s Editor Features",
ICON_MD_EDIT).c_str())) {
972 BulletText(
"Music editor updates with SPC parsing/playback");
973 BulletText(
"AI agent-assisted editing workflows (multi-provider + vision)");
974 BulletText(
"Expanded overworld/dungeon tooling and palette accuracy");
975 BulletText(
"Web/WASM preview with collaboration hooks");
983 Hide(PopupID::kWhatsNew);
988 Hide(PopupID::kWhatsNew);
992void PopupManager::DrawWorkspaceHelpPopup() {
993 TextWrapped(
"Workspace Management");
995 "YAZE supports multiple ROM sessions and flexible workspace layouts.");
998 TextWrapped(
"Session Management:");
999 BulletText(
"Ctrl+Shift+N: Create new session");
1000 BulletText(
"Ctrl+Shift+W: Close current session");
1001 BulletText(
"Ctrl+Tab: Quick session switcher");
1002 BulletText(
"Each session maintains its own ROM and editor state");
1005 TextWrapped(
"Layout Management:");
1006 BulletText(
"Drag window tabs to dock/undock");
1007 BulletText(
"Ctrl+Shift+S: Save current layout");
1008 BulletText(
"Ctrl+Shift+O: Load saved layout");
1012 TextWrapped(
"Preset Layouts:");
1013 BulletText(
"Developer: Code, memory, testing tools");
1014 BulletText(
"Designer: Graphics, palettes, sprites");
1015 BulletText(
"Modder: All gameplay editing tools");
1018 Hide(
"Workspace Help");
1022void PopupManager::DrawSessionLimitWarningPopup() {
1024 TextWrapped(
"You have reached the recommended session limit.");
1025 TextWrapped(
"Having too many sessions open may impact performance.");
1027 TextWrapped(
"Consider closing unused sessions or saving your work.");
1030 Hide(
"Session Limit Warning");
1034 Hide(
"Session Limit Warning");
1039void PopupManager::DrawLayoutResetConfirmPopup() {
1040 TextColored(gui::GetWarningColor(),
"%s Confirm Reset",
1042 TextWrapped(
"This will reset your current workspace layout to default.");
1043 TextWrapped(
"Any custom window arrangements will be lost.");
1045 TextWrapped(
"Do you want to continue?");
1048 Hide(
"Layout Reset Confirm");
1053 Hide(
"Layout Reset Confirm");
1057void PopupManager::DrawLayoutPresetsPopup() {
1058 TextColored(gui::GetInfoColor(),
"%s Layout Presets",
1063 TextWrapped(
"Choose a workspace preset to quickly configure your layout:");
1071 std::function<PanelLayoutPreset()> getter;
1074 PresetInfo presets[] = {
1076 "Essential cards only - maximum editing space",
1078 return LayoutPresets::GetMinimalPreset();
1081 "Debug and development focused - CPU/Memory/Breakpoints",
1083 return LayoutPresets::GetDeveloperPreset();
1086 "Visual and artistic focused - Graphics/Palettes/Sprites",
1088 return LayoutPresets::GetDesignerPreset();
1091 "Full-featured - All tools available for comprehensive editing",
1093 return LayoutPresets::GetModderPreset();
1096 "Complete overworld editing toolkit with all map tools",
1098 return LayoutPresets::GetOverworldArtistPreset();
1101 "Complete dungeon editing toolkit with room tools",
1103 return LayoutPresets::GetDungeonMasterPreset();
1105 {
"Testing",
ICON_MD_SCIENCE,
"Quality assurance and ROM testing layout",
1107 return LayoutPresets::GetLogicDebuggerPreset();
1111 return LayoutPresets::GetAudioEngineerPreset();
1115 constexpr int kPresetCount = 8;
1118 float button_width = 200.0f;
1119 float button_height = 50.0f;
1121 for (
int i = 0; i < kPresetCount; i++) {
1126 gui::StyleVarGuard align_guard(ImGuiStyleVar_ButtonTextAlign,
1127 ImVec2(0.0f, 0.5f));
1129 absl::StrFormat(
"%s %s", presets[i].icon, presets[i].name).c_str(),
1130 ImVec2(button_width, button_height))) {
1132 auto preset = presets[i].getter();
1133 auto& window_manager = editor_manager_->window_manager();
1135 window_manager.HideAll();
1137 for (
const auto& panel_id : preset.default_visible_panels) {
1138 window_manager.OpenWindow(panel_id);
1140 Hide(PopupID::kLayoutPresets);
1144 if (IsItemHovered()) {
1146 TextUnformatted(presets[i].description);
1159 auto& window_manager = editor_manager_->window_manager();
1161 if (current_editor) {
1163 window_manager.ResetToDefaults(0, current_type);
1165 Hide(PopupID::kLayoutPresets);
1169 if (Button(
"Close", ImVec2(-1, 0))) {
1170 Hide(PopupID::kLayoutPresets);
1174void PopupManager::DrawSessionManagerPopup() {
1175 TextColored(gui::GetInfoColor(),
"%s Session Manager",
1180 size_t session_count = editor_manager_->GetActiveSessionCount();
1181 size_t active_session = editor_manager_->GetCurrentSessionId();
1183 Text(
"Active Sessions: %zu", session_count);
1187 if (BeginTable(
"SessionTable", 4,
1188 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1189 TableSetupColumn(
"#", ImGuiTableColumnFlags_WidthFixed, 30.0f);
1190 TableSetupColumn(
"ROM", ImGuiTableColumnFlags_WidthStretch);
1191 TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed, 80.0f);
1192 TableSetupColumn(
"Actions", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1195 for (
size_t i = 0; i < session_count; i++) {
1199 TableSetColumnIndex(0);
1203 TableSetColumnIndex(1);
1204 if (i == active_session) {
1205 auto*
rom = editor_manager_->GetCurrentRom();
1209 TextDisabled(
"(No ROM loaded)");
1212 TextDisabled(
"Session %zu", i + 1);
1216 TableSetColumnIndex(2);
1217 if (i == active_session) {
1221 TextDisabled(
"Inactive");
1225 TableSetColumnIndex(3);
1226 PushID(
static_cast<int>(i));
1228 if (i != active_session) {
1230 editor_manager_->SwitchToSession(i);
1235 BeginDisabled(session_count <= 1);
1237 editor_manager_->RemoveSession(i);
1252 if (Button(absl::StrFormat(
"%s New Session",
ICON_MD_ADD).c_str(),
1254 editor_manager_->CreateNewSession();
1258 if (Button(
"Close", ImVec2(-1, 0))) {
1259 Hide(PopupID::kSessionManager);
1263void PopupManager::DrawDisplaySettingsPopup() {
1265 SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
1266 SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX));
1269 TextWrapped(
"Customize your YAZE experience - accessible anytime!");
1274 float available_height =
1275 GetContentRegionAvail().y - 60;
1276 if (BeginChild(
"DisplaySettingsContent", ImVec2(0, available_height),
true,
1277 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1279 gui::DrawDisplaySettingsForPopup();
1282 gui::TextWithSeparators(
"Font Manager");
1283 gui::DrawFontManager();
1286 ImGuiIO& io = GetIO();
1288 Text(
"Global Font Scale");
1289 float font_global_scale = io.FontGlobalScale;
1290 if (SliderFloat(
"##global_scale", &font_global_scale, 0.5f, 2.0f,
"%.2f")) {
1291 if (editor_manager_) {
1292 editor_manager_->SetFontGlobalScale(font_global_scale);
1294 io.FontGlobalScale = font_global_scale;
1302 Hide(
"Display Settings");
1306void PopupManager::DrawFeatureFlagsPopup() {
1307 using namespace ImGui;
1310 Text(
"Feature Flags Configuration");
1313 BeginChild(
"##FlagsContent", ImVec2(0, -30),
true);
1316 static gui::FlagsMenu flags_menu;
1318 if (BeginTabBar(
"FlagCategories")) {
1319 if (BeginTabItem(
"Overworld")) {
1320 flags_menu.DrawOverworldFlags();
1323 if (BeginTabItem(
"Dungeon")) {
1324 flags_menu.DrawDungeonFlags();
1327 if (BeginTabItem(
"Resources")) {
1328 flags_menu.DrawResourceFlags();
1331 if (BeginTabItem(
"System")) {
1332 flags_menu.DrawSystemFlags();
1342 Hide(PopupID::kFeatureFlags);
1346void PopupManager::DrawDataIntegrityPopup() {
1347 using namespace ImGui;
1349 Text(
"Data Integrity Check Results");
1352 BeginChild(
"##IntegrityContent", ImVec2(0, -30),
true);
1356 Text(
"ROM Data Integrity:");
1358 TextColored(gui::GetSuccessColor(),
"✓ ROM header valid");
1359 TextColored(gui::GetSuccessColor(),
"✓ Checksum valid");
1360 TextColored(gui::GetSuccessColor(),
"✓ Graphics data intact");
1361 TextColored(gui::GetSuccessColor(),
"✓ Map data intact");
1364 Text(
"No issues detected.");
1370 Hide(PopupID::kDataIntegrity);
1374void PopupManager::DrawDungeonPotItemSaveConfirmPopup() {
1375 using namespace ImGui;
1377 if (!editor_manager_) {
1378 Text(
"Editor manager unavailable.");
1380 Hide(PopupID::kDungeonPotItemSaveConfirm);
1385 const int unloaded = editor_manager_->pending_pot_item_unloaded_rooms();
1386 const int total = editor_manager_->pending_pot_item_total_rooms();
1388 Text(
"Pot Item Save Confirmation");
1391 "Dungeon pot item saving is enabled, but %d of %d rooms are not loaded.",
1395 "Saving now can overwrite pot items in unloaded rooms. Choose how to "
1399 if (Button(
"Save without pot items", ImVec2(0, 0))) {
1400 editor_manager_->ResolvePotItemSaveConfirmation(
1401 EditorManager::PotItemSaveDecision::kSaveWithoutPotItems);
1402 Hide(PopupID::kDungeonPotItemSaveConfirm);
1406 if (Button(
"Save anyway", ImVec2(0, 0))) {
1407 editor_manager_->ResolvePotItemSaveConfirmation(
1408 EditorManager::PotItemSaveDecision::kSaveWithPotItems);
1409 Hide(PopupID::kDungeonPotItemSaveConfirm);
1413 if (Button(
"Cancel", ImVec2(0, 0))) {
1414 editor_manager_->ResolvePotItemSaveConfirmation(
1415 EditorManager::PotItemSaveDecision::kCancel);
1416 Hide(PopupID::kDungeonPotItemSaveConfirm);
1420void PopupManager::DrawRomWriteConfirmPopup() {
1421 using namespace ImGui;
1423 if (!editor_manager_) {
1424 Text(
"Editor manager unavailable.");
1426 Hide(PopupID::kRomWriteConfirm);
1431 auto role = project::RomRoleToString(editor_manager_->GetProjectRomRole());
1433 project::RomWritePolicyToString(editor_manager_->GetProjectRomWritePolicy());
1434 const auto expected = editor_manager_->GetProjectExpectedRomHash();
1435 const auto actual = editor_manager_->GetCurrentRomHash();
1436 const auto* project = editor_manager_->GetCurrentProject();
1437 const auto actual_path = editor_manager_->GetCurrentRom()
1438 ? editor_manager_->GetCurrentRom()->filename()
1440 const auto editable_path =
1441 project && project->hack_manifest.loaded() &&
1442 !project->hack_manifest.build_pipeline().dev_rom.empty()
1443 ? project->GetAbsolutePath(project->hack_manifest.build_pipeline().dev_rom)
1444 : (project ? project->rom_filename : std::string());
1446 Text(
"ROM Write Confirmation");
1449 "The loaded ROM hash does not match the project's expected hash.");
1451 Text(
"Role: %s", role.c_str());
1452 Text(
"Write policy: %s", policy.c_str());
1453 Text(
"Loaded ROM: %s",
1454 actual_path.empty() ?
"(unknown)" : actual_path.c_str());
1455 Text(
"Editable target: %s",
1456 editable_path.empty() ?
"(unset)" : editable_path.c_str());
1457 Text(
"Expected: %s", expected.empty() ?
"(unset)" : expected.c_str());
1458 Text(
"Actual: %s", actual.empty() ?
"(unknown)" : actual.c_str());
1461 "Proceeding will write to the current ROM file. This may corrupt a base "
1462 "or release ROM if it is not the intended editable project ROM.");
1465 if (Button(
"Save anyway", ImVec2(0, 0))) {
1466 editor_manager_->ConfirmRomWrite();
1467 Hide(PopupID::kRomWriteConfirm);
1468 auto status = editor_manager_->SaveRom();
1469 if (!status.ok() && !absl::IsCancelled(status)) {
1470 if (
auto* toast = editor_manager_->toast_manager()) {
1472 absl::StrFormat(
"Save failed: %s", status.message()),
1479 if (Button(
"Cancel", ImVec2(0, 0)) ||
IsKeyPressed(ImGuiKey_Escape)) {
1480 editor_manager_->CancelRomWriteConfirm();
1481 Hide(PopupID::kRomWriteConfirm);
1485void PopupManager::DrawWriteConflictWarningPopup() {
1486 using namespace ImGui;
1488 if (!editor_manager_) {
1489 Text(
"Editor manager unavailable.");
1491 Hide(PopupID::kWriteConflictWarning);
1496 const auto& conflicts = editor_manager_->pending_write_conflicts();
1498 TextColored(gui::GetWarningColor(),
"%s Write Conflict Warning",
1502 "The following ROM addresses are owned by ASM hooks and will be "
1503 "overwritten on next build. Saving now will write data that asar "
1507 if (!conflicts.empty()) {
1508 if (BeginTable(
"WriteConflictTable", 3,
1509 ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
1510 ImGuiTableFlags_Resizable)) {
1511 TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1512 TableSetupColumn(
"Ownership", ImGuiTableColumnFlags_WidthFixed, 140.0f);
1513 TableSetupColumn(
"Module", ImGuiTableColumnFlags_WidthStretch);
1516 for (
const auto& conflict : conflicts) {
1519 Text(
"$%06X", conflict.address);
1522 core::AddressOwnershipToString(conflict.ownership).c_str());
1524 if (!conflict.module.empty()) {
1525 TextUnformatted(conflict.module.c_str());
1527 TextDisabled(
"(unknown)");
1535 Text(
"%zu conflict(s) detected.", conflicts.size());
1538 if (Button(
"Save Anyway", ImVec2(0, 0))) {
1539 editor_manager_->BypassWriteConflictOnce();
1540 Hide(PopupID::kWriteConflictWarning);
1541 auto status = editor_manager_->SaveRom();
1542 if (!status.ok() && !absl::IsCancelled(status)) {
1543 if (
auto* toast = editor_manager_->toast_manager()) {
1544 toast->Show(absl::StrFormat(
"Save failed: %s", status.message()),
1551 if (Button(
"Cancel", ImVec2(0, 0)) ||
IsKeyPressed(ImGuiKey_Escape)) {
1552 editor_manager_->ClearPendingWriteConflicts();
1553 Hide(PopupID::kWriteConflictWarning);
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
#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
Rom * rom()
Get the current ROM instance.
Editor * current_editor()
Get the currently active editor.
constexpr ImVec2 kDefaultModalSize
Public YAZE API umbrella header.