yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
popup_manager.cc
Go to the documentation of this file.
1#include "popup_manager.h"
2
3#include <cstring>
4#include <ctime>
5#include <filesystem>
6#include <functional>
7#include <initializer_list>
8
9#include "absl/status/status.h"
10#include "absl/strings/match.h"
11#include "absl/strings/str_format.h"
15#include "app/gui/core/icons.h"
16#include "app/gui/core/input.h"
17#include "app/gui/core/style.h"
21#include "imgui/misc/cpp/imgui_stdlib.h"
22#include "util/file_util.h"
23#include "util/hex.h"
24#include "yaze.h"
25
26namespace yaze {
27namespace editor {
28
29using namespace ImGui;
30
31PopupManager::PopupManager(EditorManager* editor_manager)
32 : editor_manager_(editor_manager), status_(absl::OkStatus()) {}
33
34void PopupManager::Initialize() {
35 // ============================================================================
36 // POPUP REGISTRATION
37 // ============================================================================
38 // All popups must be registered here BEFORE any menu callbacks can trigger
39 // them. This method is called in EditorManager constructor BEFORE
40 // MenuOrchestrator and UICoordinator are created, ensuring safe
41 // initialization order.
42 //
43 // Popup Registration Format:
44 // popups_[PopupID::kConstant] = {
45 // .name = PopupID::kConstant,
46 // .type = PopupType::kXxx,
47 // .is_visible = false,
48 // .allow_resize = false/true,
49 // .draw_function = [this]() { DrawXxxPopup(); }
50 // };
51 // ============================================================================
52
53 // File Operations
54 popups_[PopupID::kSaveAs] = {PopupID::kSaveAs, PopupType::kFileOperation,
55 false, false, [this]() {
56 DrawSaveAsPopup();
57 }};
58 popups_[PopupID::kSaveScope] = {PopupID::kSaveScope, PopupType::kSettings,
59 false, true, [this]() {
60 DrawSaveScopePopup();
61 }};
62 popups_[PopupID::kNewProject] = {
63 PopupID::kNewProject, PopupType::kFileOperation, false, false, [this]() {
64 DrawNewProjectPopup();
65 }};
66 popups_[PopupID::kManageProject] = {PopupID::kManageProject,
67 PopupType::kFileOperation, false, false,
68 [this]() {
69 DrawManageProjectPopup();
70 }};
71 popups_[PopupID::kRomBackups] = {PopupID::kRomBackups,
72 PopupType::kFileOperation, false, true,
73 [this]() { DrawRomBackupManagerPopup(); }};
74
75 // Information
76 popups_[PopupID::kAbout] = {PopupID::kAbout, PopupType::kInfo, false, false,
77 [this]() {
78 DrawAboutPopup();
79 }};
80 popups_[PopupID::kRomInfo] = {PopupID::kRomInfo, PopupType::kInfo, false,
81 false, [this]() {
82 DrawRomInfoPopup();
83 }};
84 popups_[PopupID::kSupportedFeatures] = {
85 PopupID::kSupportedFeatures, PopupType::kInfo, false, false, [this]() {
86 DrawSupportedFeaturesPopup();
87 }};
88 popups_[PopupID::kOpenRomHelp] = {PopupID::kOpenRomHelp, PopupType::kHelp,
89 false, false, [this]() {
90 DrawOpenRomHelpPopup();
91 }};
92
93 // Help Documentation
94 popups_[PopupID::kGettingStarted] = {
95 PopupID::kGettingStarted, PopupType::kHelp, false, false, [this]() {
96 DrawGettingStartedPopup();
97 }};
98 popups_[PopupID::kAsarIntegration] = {
99 PopupID::kAsarIntegration, PopupType::kHelp, false, false, [this]() {
100 DrawAsarIntegrationPopup();
101 }};
102 popups_[PopupID::kBuildInstructions] = {
103 PopupID::kBuildInstructions, PopupType::kHelp, false, false, [this]() {
104 DrawBuildInstructionsPopup();
105 }};
106 popups_[PopupID::kCLIUsage] = {PopupID::kCLIUsage, PopupType::kHelp, false,
107 false, [this]() {
108 DrawCLIUsagePopup();
109 }};
110 popups_[PopupID::kTroubleshooting] = {
111 PopupID::kTroubleshooting, PopupType::kHelp, false, false, [this]() {
112 DrawTroubleshootingPopup();
113 }};
114 popups_[PopupID::kContributing] = {PopupID::kContributing, PopupType::kHelp,
115 false, false, [this]() {
116 DrawContributingPopup();
117 }};
118 popups_[PopupID::kWhatsNew] = {PopupID::kWhatsNew, PopupType::kHelp, false,
119 false, [this]() {
120 DrawWhatsNewPopup();
121 }};
122
123 // Settings
124 popups_[PopupID::kDisplaySettings] = {PopupID::kDisplaySettings,
125 PopupType::kSettings, false,
126 true, // Resizable
127 [this]() {
128 DrawDisplaySettingsPopup();
129 }};
130 popups_[PopupID::kFeatureFlags] = {
131 PopupID::kFeatureFlags, PopupType::kSettings, false, true, // Resizable
132 [this]() {
133 DrawFeatureFlagsPopup();
134 }};
135
136 // Workspace
137 popups_[PopupID::kWorkspaceHelp] = {PopupID::kWorkspaceHelp, PopupType::kHelp,
138 false, false, [this]() {
139 DrawWorkspaceHelpPopup();
140 }};
141 popups_[PopupID::kSessionLimitWarning] = {PopupID::kSessionLimitWarning,
142 PopupType::kWarning, false, false,
143 [this]() {
144 DrawSessionLimitWarningPopup();
145 }};
146 popups_[PopupID::kLayoutResetConfirm] = {PopupID::kLayoutResetConfirm,
147 PopupType::kConfirmation, false,
148 false, [this]() {
149 DrawLayoutResetConfirmPopup();
150 }};
151
152 popups_[PopupID::kLayoutPresets] = {
153 PopupID::kLayoutPresets, PopupType::kSettings, false, false, [this]() {
154 DrawLayoutPresetsPopup();
155 }};
156
157 popups_[PopupID::kSessionManager] = {
158 PopupID::kSessionManager, PopupType::kSettings, false, true, [this]() {
159 DrawSessionManagerPopup();
160 }};
161
162 // Debug/Testing
163 popups_[PopupID::kDataIntegrity] = {PopupID::kDataIntegrity, PopupType::kInfo,
164 false, true, // Resizable
165 [this]() {
166 DrawDataIntegrityPopup();
167 }};
168
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(); }};
178}
179
180void PopupManager::DrawPopups() {
181 // Draw status popup if needed
182 DrawStatusPopup();
183
184 // Draw all registered popups
185 for (auto& [name, params] : popups_) {
186 if (params.is_visible) {
187 OpenPopup(name.c_str());
188
189 // Use allow_resize flag from popup definition
190 ImGuiWindowFlags popup_flags = params.allow_resize
191 ? ImGuiWindowFlags_None
192 : ImGuiWindowFlags_AlwaysAutoResize;
193
194 if (BeginPopupModal(name.c_str(), nullptr, popup_flags)) {
195 params.draw_function();
196 EndPopup();
197 }
198 }
199 }
200}
201
202void PopupManager::Show(const char* name) {
203 if (!name) {
204 return; // Safety check for null pointer
205 }
206
207 std::string name_str(name);
208 auto it = popups_.find(name_str);
209 if (it != popups_.end()) {
210 it->second.is_visible = true;
211 } else {
212 // Log warning for unregistered popup
213 printf(
214 "[PopupManager] Warning: Popup '%s' not registered. Available popups: ",
215 name);
216 for (const auto& [key, _] : popups_) {
217 printf("'%s' ", key.c_str());
218 }
219 printf("\n");
220 }
221}
222
223void PopupManager::Hide(const char* name) {
224 if (!name) {
225 return; // Safety check for null pointer
226 }
227
228 std::string name_str(name);
229 auto it = popups_.find(name_str);
230 if (it != popups_.end()) {
231 it->second.is_visible = false;
232 CloseCurrentPopup();
233 }
234}
235
236bool PopupManager::IsVisible(const char* name) const {
237 if (!name) {
238 return false; // Safety check for null pointer
239 }
240
241 std::string name_str(name);
242 auto it = popups_.find(name_str);
243 if (it != popups_.end()) {
244 return it->second.is_visible;
245 }
246 return false;
247}
248
249void PopupManager::SetStatus(const absl::Status& status) {
250 if (!status.ok()) {
251 show_status_ = true;
252 prev_status_ = status;
253 status_ = status;
254 }
255}
256
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);
265}
266
267void PopupManager::DrawStatusPopup() {
268 if (show_status_ && BeginCentered("StatusWindow")) {
269 Text("%s", ICON_MD_ERROR);
270 Text("%s", prev_status_.ToString().c_str());
271 Spacing();
272 NextColumn();
273 Columns(1);
274 Separator();
275 NewLine();
276 SameLine(128);
277 if (Button("OK", ::yaze::gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Space)) {
278 show_status_ = false;
279 status_ = absl::OkStatus();
280 }
281 SameLine();
282 if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
283 SetClipboardText(prev_status_.ToString().c_str());
284 }
285 End();
286 }
287}
288
289void PopupManager::DrawAboutPopup() {
290 Text("Yet Another Zelda3 Editor - v%s", editor_manager_->version().c_str());
291 Text("Written by: scawful");
292 Spacing();
293 Text("Special Thanks: Zarby89, JaredBrian");
294 Separator();
295
296 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
297 Hide("About");
298 }
299}
300
301void PopupManager::DrawRomInfoPopup() {
302 auto* current_rom = editor_manager_->GetCurrentRom();
303 if (!current_rom)
304 return;
305
306 Text("Title: %s", current_rom->title().c_str());
307 Text("ROM Size: %s", util::HexLongLong(current_rom->size()).c_str());
308 Text("ROM Hash: %s",
309 editor_manager_->GetCurrentRomHash().empty()
310 ? "(unknown)"
311 : editor_manager_->GetCurrentRomHash().c_str());
312
313 auto* project = editor_manager_->GetCurrentProject();
314 if (project && project->project_opened()) {
315 Separator();
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)
319 .c_str());
320 Text("Expected Hash: %s",
321 project->rom_metadata.expected_hash.empty()
322 ? "(unset)"
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");
328 }
329 }
330
331 if (Button("Close", ::yaze::gui::kDefaultModalSize) ||
332 IsKeyPressed(ImGuiKey_Escape)) {
333 Hide("ROM Information");
334 }
335}
336
337void PopupManager::DrawSaveAsPopup() {
338 using namespace ImGui;
339
340 Text("%s Save ROM to new location", ICON_MD_SAVE_AS);
341 Separator();
342
343 static std::string save_as_filename = "";
344 if (editor_manager_->GetCurrentRom() && save_as_filename.empty()) {
345 save_as_filename = editor_manager_->GetCurrentRom()->title();
346 }
347
348 InputText("Filename", &save_as_filename);
349 Separator();
350
351 if (Button(absl::StrFormat("%s Browse...", ICON_MD_FOLDER_OPEN).c_str(),
353 auto file_path =
354 util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "sfc");
355 if (!file_path.empty()) {
356 save_as_filename = file_path;
357 }
358 }
359
360 SameLine();
361 if (Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str(),
363 if (!save_as_filename.empty()) {
364 // Ensure proper file extension
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";
369 }
370
371 auto status = editor_manager_->SaveRomAs(final_filename);
372 if (status.ok()) {
373 save_as_filename = "";
374 Hide(PopupID::kSaveAs);
375 }
376 }
377 }
378
379 SameLine();
380 if (Button(absl::StrFormat("%s Cancel", ICON_MD_CANCEL).c_str(),
382 save_as_filename = "";
383 Hide(PopupID::kSaveAs);
384 }
385}
386
387void PopupManager::DrawSaveScopePopup() {
388 using namespace ImGui;
389
390 Text("%s Save Scope", ICON_MD_SAVE);
391 Separator();
392 TextWrapped(
393 "Controls which data is written during File > Save ROM. "
394 "Changes apply immediately.");
395 Separator();
396
397 if (CollapsingHeader("Overworld", ImGuiTreeNodeFlags_DefaultOpen)) {
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);
408 }
409
410 if (CollapsingHeader("Dungeon", ImGuiTreeNodeFlags_DefaultOpen)) {
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);
426 }
427
428 if (CollapsingHeader("Graphics", ImGuiTreeNodeFlags_DefaultOpen)) {
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);
433 }
434
435 if (CollapsingHeader("Messages", ImGuiTreeNodeFlags_DefaultOpen)) {
436 Checkbox("Save Message Text", &core::FeatureFlags::get().kSaveMessages);
437 }
438
439 Separator();
440 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
441 Hide(PopupID::kSaveScope);
442 }
443}
444
445void PopupManager::DrawRomBackupManagerPopup() {
446 using namespace ImGui;
447
448 auto* rom = editor_manager_->GetCurrentRom();
449 if (!rom || !rom->is_loaded()) {
450 Text("No ROM loaded.");
451 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
452 Hide(PopupID::kRomBackups);
453 }
454 return;
455 }
456
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);
462 } else {
463 backup_dir = std::filesystem::path(rom->filename()).parent_path().string();
464 }
465
466 Text("%s ROM Backups", ICON_MD_BACKUP);
467 Separator();
468 TextWrapped("Backup folder: %s", backup_dir.c_str());
469
470 if (Button(ICON_MD_DELETE_SWEEP " Prune Backups")) {
471 auto status = editor_manager_->PruneRomBackups();
472 if (!status.ok()) {
473 if (auto* toast = editor_manager_->toast_manager()) {
474 toast->Show(absl::StrFormat("Prune failed: %s", status.message()),
475 ToastType::kError);
476 }
477 } else if (auto* toast = editor_manager_->toast_manager()) {
478 toast->Show("Backups pruned", ToastType::kSuccess);
479 }
480 }
481
482 Separator();
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");
493 TableHeadersRow();
494
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));
499 }
500 if (bytes > 1024) {
501 return absl::StrFormat("%.1f KB",
502 static_cast<double>(bytes) / 1024.0);
503 }
504 return absl::StrFormat("%llu B",
505 static_cast<unsigned long long>(bytes));
506 };
507
508 for (size_t i = 0; i < backups.size(); ++i) {
509 const auto& backup = backups[i];
510 TableNextRow();
511 TableNextColumn();
512 char time_buffer[32] = "unknown";
513 if (backup.timestamp != 0) {
514 std::tm local_tm{};
515#ifdef _WIN32
516 localtime_s(&local_tm, &backup.timestamp);
517#else
518 localtime_r(&backup.timestamp, &local_tm);
519#endif
520 std::strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S",
521 &local_tm);
522 }
523 TextUnformatted(time_buffer);
524
525 TableNextColumn();
526 TextUnformatted(format_size(backup.size_bytes).c_str());
527
528 TableNextColumn();
529 TextUnformatted(backup.filename.c_str());
530
531 TableNextColumn();
532 PushID(static_cast<int>(i));
533 if (Button(ICON_MD_RESTORE " Restore")) {
534 auto status = editor_manager_->RestoreRomBackup(backup.path);
535 if (!status.ok()) {
536 if (auto* toast = editor_manager_->toast_manager()) {
537 toast->Show(
538 absl::StrFormat("Restore failed: %s", status.message()),
539 ToastType::kError);
540 }
541 } else if (auto* toast = editor_manager_->toast_manager()) {
542 toast->Show("ROM restored from backup", ToastType::kSuccess);
543 }
544 }
545 SameLine();
546 if (Button(ICON_MD_OPEN_IN_NEW " Open")) {
547 auto status = editor_manager_->OpenRomOrProject(backup.path);
548 if (!status.ok()) {
549 if (auto* toast = editor_manager_->toast_manager()) {
550 toast->Show(
551 absl::StrFormat("Open failed: %s", status.message()),
552 ToastType::kError);
553 }
554 }
555 }
556 SameLine();
557 if (Button(ICON_MD_CONTENT_COPY " Copy")) {
558 SetClipboardText(backup.path.c_str());
559 }
560 PopID();
561 }
562 EndTable();
563 }
564
565 Separator();
566 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
567 Hide(PopupID::kRomBackups);
568 }
569}
570
571void PopupManager::DrawNewProjectPopup() {
572 using namespace ImGui;
573
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 = "";
579
580 InputText("Project Name", &project_name);
581
582 if (Button(absl::StrFormat("%s Destination Folder", ICON_MD_FOLDER).c_str(),
584 project_filepath = util::FileDialogWrapper::ShowOpenFolderDialog();
585 }
586 SameLine();
587 Text("%s", project_filepath.empty() ? "(Not set)" : project_filepath.c_str());
588
589 if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(),
591 rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(
592 util::MakeRomFileDialogOptions(false));
593 }
594 SameLine();
595 Text("%s", rom_filename.empty() ? "(Not set)" : rom_filename.c_str());
596
597 if (Button(absl::StrFormat("%s Labels File", ICON_MD_LABEL).c_str(),
599 labels_filename = util::FileDialogWrapper::ShowOpenFileDialog();
600 }
601 SameLine();
602 Text("%s", labels_filename.empty() ? "(Not set)" : labels_filename.c_str());
603
604 if (Button(absl::StrFormat("%s Code Folder", ICON_MD_CODE).c_str(),
606 code_folder = util::FileDialogWrapper::ShowOpenFolderDialog();
607 }
608 SameLine();
609 Text("%s", code_folder.empty() ? "(Not set)" : code_folder.c_str());
610
611 Separator();
612
613 if (Button(absl::StrFormat("%s Choose Project File Location", ICON_MD_SAVE)
614 .c_str(),
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";
622 }
623 project_filepath = project_file_path;
624 }
625 }
626
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();
631 if (status.ok()) {
632 // Clear fields
633 project_name = "";
634 project_filepath = "";
635 rom_filename = "";
636 labels_filename = "";
637 code_folder = "";
638 Hide(PopupID::kNewProject);
639 }
640 }
641 }
642 SameLine();
643 if (Button(absl::StrFormat("%s Cancel", ICON_MD_CANCEL).c_str(),
645 // Clear fields
646 project_name = "";
647 project_filepath = "";
648 rom_filename = "";
649 labels_filename = "";
650 code_folder = "";
651 Hide(PopupID::kNewProject);
652 }
653}
654
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);
661
662 auto status_color = [&](const char* status) -> ImVec4 {
663 if (strcmp(status, "Stable") == 0 || strcmp(status, "Working") == 0) {
664 return status_ok;
665 }
666 if (strcmp(status, "Beta") == 0 || strcmp(status, "Experimental") == 0) {
667 return status_warn;
668 }
669 if (strcmp(status, "Preview") == 0) {
670 return status_info;
671 }
672 if (strcmp(status, "Not available") == 0) {
673 return status_error;
674 }
675 return status_info;
676 };
677
678 struct FeatureRow {
679 const char* feature;
680 const char* status;
681 const char* persistence;
682 const char* notes;
683 };
684
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)) {
691 return;
692 }
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);
697 TableHeadersRow();
698
699 for (const auto& row : rows) {
700 TableNextRow();
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);
709 }
710
711 EndTable();
712 };
713
714 TextDisabled(
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.");
718 Spacing();
719
720 if (CollapsingHeader("Desktop App (yaze)", ImGuiTreeNodeFlags_DefaultOpen)) {
721 draw_table(
722 "desktop_features",
723 {
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."},
749 });
750 }
751
752 if (CollapsingHeader("z3ed CLI")) {
753 draw_table(
754 "cli_features",
755 {
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."},
766 });
767 }
768
769 if (CollapsingHeader("Web/WASM Preview")) {
770 draw_table(
771 "web_features",
772 {
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."},
786 });
787 }
788
789 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
790 Hide(PopupID::kSupportedFeatures);
791 }
792}
793
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");
801 Spacing();
802 TextWrapped("ROM files are not bundled. Use a clean, legally obtained copy.");
803
804 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
805 Hide("Open a ROM");
806 }
807}
808
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.");
813 TextWrapped(
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 "
817 "folder.");
818
819 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
820 Hide("Manage Project");
821 }
822}
823
824void PopupManager::DrawGettingStartedPopup() {
825 TextWrapped("Welcome to YAZE v%s!", YAZE_VERSION_STRING);
826 TextWrapped(
827 "YAZE lets you modify 'The Legend of Zelda: A Link to the Past' (US or "
828 "JP) ROMs with modern tooling.");
829 Spacing();
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");
836 Spacing();
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 > "
842 "Agent");
843
844 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
845 Hide("Getting Started");
846 }
847}
848
849void PopupManager::DrawAsarIntegrationPopup() {
850 TextWrapped("Asar 65816 Assembly Integration");
851 TextWrapped(
852 "YAZE includes full Asar assembler support for ROM patching.");
853 Spacing();
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");
859
860 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
861 Hide("Asar Integration");
862 }
863}
864
865void PopupManager::DrawBuildInstructionsPopup() {
866 TextWrapped("Build Instructions");
867 TextWrapped("YAZE uses modern CMake for cross-platform builds.");
868 Spacing();
869 TextWrapped("Quick Start (examples):");
870 BulletText("cmake --preset mac-dbg | lin-dbg | win-dbg");
871 BulletText("cmake --build --preset <preset> --target yaze");
872 Spacing();
873 TextWrapped("AI Builds:");
874 BulletText("cmake --preset mac-ai | lin-ai | win-ai");
875 BulletText("cmake --build --preset <preset> --target yaze z3ed");
876 Spacing();
877 TextWrapped("Docs: docs/public/build/quick-reference.md");
878
879 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
880 Hide("Build Instructions");
881 }
882}
883
884void PopupManager::DrawCLIUsagePopup() {
885 TextWrapped("Command Line Interface (z3ed)");
886 TextWrapped("Scriptable ROM editing and AI agent workflows.");
887 Spacing();
888 TextWrapped("Commands:");
889 BulletText("z3ed rom-info --rom=zelda3.sfc");
890 BulletText("z3ed agent simple-chat --rom=zelda3.sfc --ai_provider=auto");
891 BulletText("z3ed agent plan --rom=zelda3.sfc");
892 BulletText("z3ed test-list --format json");
893 BulletText("z3ed patch apply-asar patch.asm --rom=zelda3.sfc");
894 BulletText("z3ed help dungeon-place-sprite");
895 Spacing();
896 TextWrapped("Storage:");
897 BulletText("Agent plans/proposals live under ~/.yaze (see docs for details)");
898
899 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
900 Hide("CLI Usage");
901 }
902}
903
904void PopupManager::DrawTroubleshootingPopup() {
905 TextWrapped("Troubleshooting");
906 TextWrapped("Common issues and solutions:");
907 Spacing();
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");
916
917 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
918 Hide("Troubleshooting");
919 }
920}
921
922void PopupManager::DrawContributingPopup() {
923 TextWrapped("Contributing to YAZE");
924 TextWrapped("YAZE is open source and welcomes contributions!");
925 Spacing();
926 TextWrapped("How to contribute:");
927 BulletText("Fork the repository on GitHub");
928 BulletText("Create feature branches for new work");
929 BulletText("Follow C++ coding standards");
930 BulletText("Include tests for new features");
931 BulletText("Submit pull requests for review");
932
933 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
934 Hide("Contributing");
935 }
936}
937
938void PopupManager::DrawWhatsNewPopup() {
939 TextWrapped("What's New in YAZE v%s", YAZE_VERSION_STRING);
940 Spacing();
941
943 absl::StrFormat("%s User Interface & Theming", ICON_MD_PALETTE)
944 .c_str(),
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");
950 }
951
953 absl::StrFormat("%s Development & Build System", ICON_MD_BUILD)
954 .c_str(),
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");
960 }
961
963 absl::StrFormat("%s Core Improvements", ICON_MD_SETTINGS).c_str())) {
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");
968 }
969
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");
976 }
977
978 Spacing();
979 if (Button(
980 absl::StrFormat("%s View Release Notes", ICON_MD_DESCRIPTION).c_str(),
981 ImVec2(-1, 30))) {
982 // Close this popup and show theme settings
983 Hide(PopupID::kWhatsNew);
984 // Could trigger release notes panel opening here
985 }
986
987 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
988 Hide(PopupID::kWhatsNew);
989 }
990}
991
992void PopupManager::DrawWorkspaceHelpPopup() {
993 TextWrapped("Workspace Management");
994 TextWrapped(
995 "YAZE supports multiple ROM sessions and flexible workspace layouts.");
996 Spacing();
997
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");
1003
1004 Spacing();
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");
1009 BulletText("F11: Maximize current window");
1010
1011 Spacing();
1012 TextWrapped("Preset Layouts:");
1013 BulletText("Developer: Code, memory, testing tools");
1014 BulletText("Designer: Graphics, palettes, sprites");
1015 BulletText("Modder: All gameplay editing tools");
1016
1017 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1018 Hide("Workspace Help");
1019 }
1020}
1021
1022void PopupManager::DrawSessionLimitWarningPopup() {
1023 TextColored(gui::GetWarningColor(), "%s Warning", ICON_MD_WARNING);
1024 TextWrapped("You have reached the recommended session limit.");
1025 TextWrapped("Having too many sessions open may impact performance.");
1026 Spacing();
1027 TextWrapped("Consider closing unused sessions or saving your work.");
1028
1029 if (Button("Understood", ::yaze::gui::kDefaultModalSize)) {
1030 Hide("Session Limit Warning");
1031 }
1032 SameLine();
1033 if (Button("Open Session Manager", ::yaze::gui::kDefaultModalSize)) {
1034 Hide("Session Limit Warning");
1035 // This would trigger the session manager to open
1036 }
1037}
1038
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.");
1044 Spacing();
1045 TextWrapped("Do you want to continue?");
1046
1047 if (Button("Reset Layout", ::yaze::gui::kDefaultModalSize)) {
1048 Hide("Layout Reset Confirm");
1049 // This would trigger the actual reset
1050 }
1051 SameLine();
1052 if (Button("Cancel", ::yaze::gui::kDefaultModalSize)) {
1053 Hide("Layout Reset Confirm");
1054 }
1055}
1056
1057void PopupManager::DrawLayoutPresetsPopup() {
1058 TextColored(gui::GetInfoColor(), "%s Layout Presets",
1060 Separator();
1061 Spacing();
1062
1063 TextWrapped("Choose a workspace preset to quickly configure your layout:");
1064 Spacing();
1065
1066 // Get named presets from LayoutPresets
1067 struct PresetInfo {
1068 const char* name;
1069 const char* icon;
1070 const char* description;
1071 std::function<PanelLayoutPreset()> getter;
1072 };
1073
1074 PresetInfo presets[] = {
1075 {"Minimal", ICON_MD_CROP_FREE,
1076 "Essential cards only - maximum editing space",
1077 []() {
1078 return LayoutPresets::GetMinimalPreset();
1079 }},
1080 {"Developer", ICON_MD_BUG_REPORT,
1081 "Debug and development focused - CPU/Memory/Breakpoints",
1082 []() {
1083 return LayoutPresets::GetDeveloperPreset();
1084 }},
1085 {"Designer", ICON_MD_PALETTE,
1086 "Visual and artistic focused - Graphics/Palettes/Sprites",
1087 []() {
1088 return LayoutPresets::GetDesignerPreset();
1089 }},
1090 {"Modder", ICON_MD_BUILD,
1091 "Full-featured - All tools available for comprehensive editing",
1092 []() {
1093 return LayoutPresets::GetModderPreset();
1094 }},
1095 {"Overworld Expert", ICON_MD_MAP,
1096 "Complete overworld editing toolkit with all map tools",
1097 []() {
1098 return LayoutPresets::GetOverworldArtistPreset();
1099 }},
1100 {"Dungeon Expert", ICON_MD_DOOR_SLIDING,
1101 "Complete dungeon editing toolkit with room tools",
1102 []() {
1103 return LayoutPresets::GetDungeonMasterPreset();
1104 }},
1105 {"Testing", ICON_MD_SCIENCE, "Quality assurance and ROM testing layout",
1106 []() {
1107 return LayoutPresets::GetLogicDebuggerPreset();
1108 }},
1109 {"Audio", ICON_MD_MUSIC_NOTE, "Music and sound editing layout",
1110 []() {
1111 return LayoutPresets::GetAudioEngineerPreset();
1112 }},
1113 };
1114
1115 constexpr int kPresetCount = 8;
1116
1117 // Draw preset buttons in a grid
1118 float button_width = 200.0f;
1119 float button_height = 50.0f;
1120
1121 for (int i = 0; i < kPresetCount; i++) {
1122 if (i % 2 != 0)
1123 SameLine();
1124
1125 {
1126 gui::StyleVarGuard align_guard(ImGuiStyleVar_ButtonTextAlign,
1127 ImVec2(0.0f, 0.5f));
1128 if (Button(
1129 absl::StrFormat("%s %s", presets[i].icon, presets[i].name).c_str(),
1130 ImVec2(button_width, button_height))) {
1131 // Apply the preset
1132 auto preset = presets[i].getter();
1133 auto& window_manager = editor_manager_->window_manager();
1134 // Hide all panels first
1135 window_manager.HideAll();
1136 // Show preset panels
1137 for (const auto& panel_id : preset.default_visible_panels) {
1138 window_manager.OpenWindow(panel_id);
1139 }
1140 Hide(PopupID::kLayoutPresets);
1141 }
1142 }
1143
1144 if (IsItemHovered()) {
1145 BeginTooltip();
1146 TextUnformatted(presets[i].description);
1147 EndTooltip();
1148 }
1149 }
1150
1151 Spacing();
1152 Separator();
1153 Spacing();
1154
1155 // Reset current editor to defaults
1156 if (Button(
1157 absl::StrFormat("%s Reset Current Editor", ICON_MD_REFRESH).c_str(),
1158 ImVec2(-1, 0))) {
1159 auto& window_manager = editor_manager_->window_manager();
1160 auto* current_editor = editor_manager_->GetCurrentEditor();
1161 if (current_editor) {
1162 auto current_type = current_editor->type();
1163 window_manager.ResetToDefaults(0, current_type);
1164 }
1165 Hide(PopupID::kLayoutPresets);
1166 }
1167
1168 Spacing();
1169 if (Button("Close", ImVec2(-1, 0))) {
1170 Hide(PopupID::kLayoutPresets);
1171 }
1172}
1173
1174void PopupManager::DrawSessionManagerPopup() {
1175 TextColored(gui::GetInfoColor(), "%s Session Manager",
1176 ICON_MD_TAB);
1177 Separator();
1178 Spacing();
1179
1180 size_t session_count = editor_manager_->GetActiveSessionCount();
1181 size_t active_session = editor_manager_->GetCurrentSessionId();
1182
1183 Text("Active Sessions: %zu", session_count);
1184 Spacing();
1185
1186 // Session table
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);
1193 TableHeadersRow();
1194
1195 for (size_t i = 0; i < session_count; i++) {
1196 TableNextRow();
1197
1198 // Session number
1199 TableSetColumnIndex(0);
1200 Text("%zu", i + 1);
1201
1202 // ROM name (simplified - show current ROM for active session)
1203 TableSetColumnIndex(1);
1204 if (i == active_session) {
1205 auto* rom = editor_manager_->GetCurrentRom();
1206 if (rom && rom->is_loaded()) {
1207 TextUnformatted(rom->filename().c_str());
1208 } else {
1209 TextDisabled("(No ROM loaded)");
1210 }
1211 } else {
1212 TextDisabled("Session %zu", i + 1);
1213 }
1214
1215 // Status indicator
1216 TableSetColumnIndex(2);
1217 if (i == active_session) {
1218 TextColored(gui::GetSuccessColor(), "%s Active",
1220 } else {
1221 TextDisabled("Inactive");
1222 }
1223
1224 // Actions
1225 TableSetColumnIndex(3);
1226 PushID(static_cast<int>(i));
1227
1228 if (i != active_session) {
1229 if (SmallButton("Switch")) {
1230 editor_manager_->SwitchToSession(i);
1231 }
1232 SameLine();
1233 }
1234
1235 BeginDisabled(session_count <= 1);
1236 if (SmallButton("Close")) {
1237 editor_manager_->RemoveSession(i);
1238 }
1239 EndDisabled();
1240
1241 PopID();
1242 }
1243
1244 EndTable();
1245 }
1246
1247 Spacing();
1248 Separator();
1249 Spacing();
1250
1251 // New session button
1252 if (Button(absl::StrFormat("%s New Session", ICON_MD_ADD).c_str(),
1253 ImVec2(-1, 0))) {
1254 editor_manager_->CreateNewSession();
1255 }
1256
1257 Spacing();
1258 if (Button("Close", ImVec2(-1, 0))) {
1259 Hide(PopupID::kSessionManager);
1260 }
1261}
1262
1263void PopupManager::DrawDisplaySettingsPopup() {
1264 // Set a comfortable default size with natural constraints
1265 SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
1266 SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX));
1267
1268 Text("%s Display & Theme Settings", ICON_MD_DISPLAY_SETTINGS);
1269 TextWrapped("Customize your YAZE experience - accessible anytime!");
1270 Separator();
1271
1272 // Create a child window for scrollable content to avoid table conflicts
1273 // Use remaining space minus the close button area
1274 float available_height =
1275 GetContentRegionAvail().y - 60; // Reserve space for close button
1276 if (BeginChild("DisplaySettingsContent", ImVec2(0, available_height), true,
1277 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1278 // Use the popup-safe version to avoid table conflicts
1279 gui::DrawDisplaySettingsForPopup();
1280
1281 Separator();
1282 gui::TextWithSeparators("Font Manager");
1283 gui::DrawFontManager();
1284
1285 // Global font scale (moved from the old display settings window)
1286 ImGuiIO& io = GetIO();
1287 Separator();
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);
1293 } else {
1294 io.FontGlobalScale = font_global_scale;
1295 }
1296 }
1297 }
1298 EndChild();
1299
1300 Separator();
1301 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1302 Hide("Display Settings");
1303 }
1304}
1305
1306void PopupManager::DrawFeatureFlagsPopup() {
1307 using namespace ImGui;
1308
1309 // Display feature flags editor using the existing FlagsMenu system
1310 Text("Feature Flags Configuration");
1311 Separator();
1312
1313 BeginChild("##FlagsContent", ImVec2(0, -30), true);
1314
1315 // Use the feature flags menu system
1316 static gui::FlagsMenu flags_menu;
1317
1318 if (BeginTabBar("FlagCategories")) {
1319 if (BeginTabItem("Overworld")) {
1320 flags_menu.DrawOverworldFlags();
1321 EndTabItem();
1322 }
1323 if (BeginTabItem("Dungeon")) {
1324 flags_menu.DrawDungeonFlags();
1325 EndTabItem();
1326 }
1327 if (BeginTabItem("Resources")) {
1328 flags_menu.DrawResourceFlags();
1329 EndTabItem();
1330 }
1331 if (BeginTabItem("System")) {
1332 flags_menu.DrawSystemFlags();
1333 EndTabItem();
1334 }
1335 EndTabBar();
1336 }
1337
1338 EndChild();
1339
1340 Separator();
1341 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1342 Hide(PopupID::kFeatureFlags);
1343 }
1344}
1345
1346void PopupManager::DrawDataIntegrityPopup() {
1347 using namespace ImGui;
1348
1349 Text("Data Integrity Check Results");
1350 Separator();
1351
1352 BeginChild("##IntegrityContent", ImVec2(0, -30), true);
1353
1354 // Placeholder for data integrity results
1355 // In a full implementation, this would show test results
1356 Text("ROM Data Integrity:");
1357 Separator();
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");
1362
1363 Spacing();
1364 Text("No issues detected.");
1365
1366 EndChild();
1367
1368 Separator();
1369 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1370 Hide(PopupID::kDataIntegrity);
1371 }
1372}
1373
1374void PopupManager::DrawDungeonPotItemSaveConfirmPopup() {
1375 using namespace ImGui;
1376
1377 if (!editor_manager_) {
1378 Text("Editor manager unavailable.");
1379 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1380 Hide(PopupID::kDungeonPotItemSaveConfirm);
1381 }
1382 return;
1383 }
1384
1385 const int unloaded = editor_manager_->pending_pot_item_unloaded_rooms();
1386 const int total = editor_manager_->pending_pot_item_total_rooms();
1387
1388 Text("Pot Item Save Confirmation");
1389 Separator();
1390 TextWrapped(
1391 "Dungeon pot item saving is enabled, but %d of %d rooms are not loaded.",
1392 unloaded, total);
1393 Spacing();
1394 TextWrapped(
1395 "Saving now can overwrite pot items in unloaded rooms. Choose how to "
1396 "proceed:");
1397
1398 Spacing();
1399 if (Button("Save without pot items", ImVec2(0, 0))) {
1400 editor_manager_->ResolvePotItemSaveConfirmation(
1401 EditorManager::PotItemSaveDecision::kSaveWithoutPotItems);
1402 Hide(PopupID::kDungeonPotItemSaveConfirm);
1403 return;
1404 }
1405 SameLine();
1406 if (Button("Save anyway", ImVec2(0, 0))) {
1407 editor_manager_->ResolvePotItemSaveConfirmation(
1408 EditorManager::PotItemSaveDecision::kSaveWithPotItems);
1409 Hide(PopupID::kDungeonPotItemSaveConfirm);
1410 return;
1411 }
1412 SameLine();
1413 if (Button("Cancel", ImVec2(0, 0))) {
1414 editor_manager_->ResolvePotItemSaveConfirmation(
1415 EditorManager::PotItemSaveDecision::kCancel);
1416 Hide(PopupID::kDungeonPotItemSaveConfirm);
1417 }
1418}
1419
1420void PopupManager::DrawRomWriteConfirmPopup() {
1421 using namespace ImGui;
1422
1423 if (!editor_manager_) {
1424 Text("Editor manager unavailable.");
1425 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1426 Hide(PopupID::kRomWriteConfirm);
1427 }
1428 return;
1429 }
1430
1431 auto role = project::RomRoleToString(editor_manager_->GetProjectRomRole());
1432 auto policy =
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()
1439 : std::string();
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());
1445
1446 Text("ROM Write Confirmation");
1447 Separator();
1448 TextWrapped(
1449 "The loaded ROM hash does not match the project's expected hash.");
1450 Spacing();
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());
1459 Spacing();
1460 TextWrapped(
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.");
1463
1464 Spacing();
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()) {
1471 toast->Show(
1472 absl::StrFormat("Save failed: %s", status.message()),
1473 ToastType::kError);
1474 }
1475 }
1476 return;
1477 }
1478 SameLine();
1479 if (Button("Cancel", ImVec2(0, 0)) || IsKeyPressed(ImGuiKey_Escape)) {
1480 editor_manager_->CancelRomWriteConfirm();
1481 Hide(PopupID::kRomWriteConfirm);
1482 }
1483}
1484
1485void PopupManager::DrawWriteConflictWarningPopup() {
1486 using namespace ImGui;
1487
1488 if (!editor_manager_) {
1489 Text("Editor manager unavailable.");
1490 if (Button("Close", ::yaze::gui::kDefaultModalSize)) {
1491 Hide(PopupID::kWriteConflictWarning);
1492 }
1493 return;
1494 }
1495
1496 const auto& conflicts = editor_manager_->pending_write_conflicts();
1497
1498 TextColored(gui::GetWarningColor(), "%s Write Conflict Warning",
1500 Separator();
1501 TextWrapped(
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 "
1504 "will replace.");
1505 Spacing();
1506
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);
1514 TableHeadersRow();
1515
1516 for (const auto& conflict : conflicts) {
1517 TableNextRow();
1518 TableNextColumn();
1519 Text("$%06X", conflict.address);
1520 TableNextColumn();
1521 TextUnformatted(
1522 core::AddressOwnershipToString(conflict.ownership).c_str());
1523 TableNextColumn();
1524 if (!conflict.module.empty()) {
1525 TextUnformatted(conflict.module.c_str());
1526 } else {
1527 TextDisabled("(unknown)");
1528 }
1529 }
1530 EndTable();
1531 }
1532 }
1533
1534 Spacing();
1535 Text("%zu conflict(s) detected.", conflicts.size());
1536 Spacing();
1537
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()),
1545 ToastType::kError);
1546 }
1547 }
1548 return;
1549 }
1550 SameLine();
1551 if (Button("Cancel", ImVec2(0, 0)) || IsKeyPressed(ImGuiKey_Escape)) {
1552 editor_manager_->ClearPendingWriteConflicts();
1553 Hide(PopupID::kWriteConflictWarning);
1554 }
1555}
1556
1557} // namespace editor
1558} // namespace yaze
auto filename() const
Definition rom.h:145
bool is_loaded() const
Definition rom.h:132
EditorType type() const
Definition editor.h:293
PopupManager(EditorManager *editor_manager)
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_DOOR_SLIDING
Definition icons.h:614
#define ICON_MD_SAVE_AS
Definition icons.h:1646
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_CODE
Definition icons.h:434
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_RESTORE
Definition icons.h:1605
#define ICON_MD_DISPLAY_SETTINGS
Definition icons.h:587
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_SCIENCE
Definition icons.h:1656
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_DASHBOARD
Definition icons.h:517
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_BACKUP
Definition icons.h:231
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_CROP_FREE
Definition icons.h:495
#define ICON_MD_DELETE_SWEEP
Definition icons.h:533
Definition input.cc:22
Rom * rom()
Get the current ROM instance.
Editor * current_editor()
Get the currently active editor.
constexpr ImVec2 kDefaultModalSize
Definition input.h:21
bool IsKeyPressed(KeyboardState state, SDL_Scancode scancode)
Check if a key is pressed using the keyboard state.
Definition sdl_compat.h:142
Public YAZE API umbrella header.