19#include <initializer_list>
24#include <unordered_set>
29#include <TargetConditionals.h>
33#define IMGUI_DEFINE_MATH_OPERATORS
34#include "absl/status/status.h"
35#include "absl/status/statusor.h"
36#include "absl/strings/ascii.h"
37#include "absl/strings/match.h"
38#include "absl/strings/str_format.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/str_split.h"
41#include "absl/strings/string_view.h"
42#include "imgui/imgui.h"
102#include "yaze_config.h"
124#ifdef YAZE_ENABLE_TESTING
131#ifdef YAZE_ENABLE_GTEST
139#ifdef YAZE_BUILD_AGENT_UI
155 for (
auto it = editors_.rbegin(); it != editors_.rend(); ++it) {
156 (*it)->RollbackSaveTransaction();
162 if (editor ==
nullptr) {
163 return absl::OkStatus();
166 editors_.push_back(editor);
167 return absl::OkStatus();
171 for (
Editor* editor : editors_) {
172 editor->CommitSaveTransaction();
179 bool committed_ =
false;
183 std::initializer_list<const char*> keys) {
184 for (
const char* key : keys) {
193 constexpr absl::string_view kRoomPanelPrefix =
"dungeon.room_";
194 if (!absl::StartsWith(panel_id, kRoomPanelPrefix) ||
195 panel_id.size() <= kRoomPanelPrefix.size()) {
198 for (
char ch : panel_id.substr(kRoomPanelPrefix.size())) {
199 if (ch <
'0' || ch >
'9') {
207 std::vector<std::string> lines = absl::StrSplit(text,
'\n');
208 for (
auto it = lines.rbegin(); it != lines.rend(); ++it) {
209 std::string line = std::string(*it);
210 absl::StripAsciiWhitespace(&line);
222 if (status_bar !=
nullptr) {
225 if (project_panel !=
nullptr) {
234 if (status_bar !=
nullptr) {
237 if (project_panel !=
nullptr) {
244 const std::string& output_log) {
248 .output_log = output_log,
249 .timestamp = std::chrono::system_clock::now()});
270 std::string* warning) {
271 if (project ==
nullptr) {
282 const auto& defaults =
285 if (defaults.empty()) {
290 if (warning !=
nullptr) {
291 *warning = absl::StrFormat(
292 "Project defines custom_objects_folder but is missing "
293 "custom_object_files[0x%02X]. Seeded default mapping (%d entries).",
301 std::vector<std::string> warnings;
306 const auto rom_size = rom.
size();
307 auto warn = [&](
const std::string& message) {
308 warnings.push_back(message);
311 auto check_range = [&](
const std::string& label, uint32_t addr,
size_t span) {
312 const size_t addr_size =
static_cast<size_t>(addr);
313 if (addr_size >= rom_size || addr_size + span > rom_size) {
314 warn(absl::StrFormat(
"ROM override '%s' out of range: 0x%X (size 0x%X)",
315 label, addr, rom_size));
319 for (
const auto& [key, value] : overrides.
addresses) {
320 check_range(key, value, 1);
327 constexpr uint32_t kExpandedMessageStartDefault = 0x178000;
328 constexpr uint32_t kExpandedMessageEndDefault = 0x17FFFF;
329 const uint32_t start =
331 .value_or(kExpandedMessageStartDefault);
334 .value_or(kExpandedMessageEndDefault);
335 if (start >= rom_size || end >= rom_size) {
336 warn(absl::StrFormat(
337 "Expanded message range out of ROM bounds: 0x%X-0x%X", start, end));
338 }
else if (end < start) {
339 warn(absl::StrFormat(
"Expanded message range invalid: 0x%X-0x%X", start,
347 if (*hook < rom_size) {
349 if (opcode.ok() && opcode.value() != 0x22) {
350 warn(absl::StrFormat(
351 "Expanded music hook at 0x%X is not a JSL opcode (0x%02X)", *hook,
371 const uint32_t marker =
374 const uint32_t magic =
377 check_range(
"overworld_ptr_marker", marker, 1);
378 if (marker < rom_size) {
379 if (rom.
data()[marker] !=
static_cast<uint8_t
>(magic)) {
380 warn(absl::StrFormat(
381 "Overworld expanded pointer marker mismatch at 0x%X: expected "
382 "0x%02X, found 0x%02X",
383 marker,
static_cast<uint8_t
>(magic), rom.
data()[marker]));
393 const uint32_t flag_addr =
397 check_range(
"overworld_entrance_flag_expanded", flag_addr, 1);
398 if (flag_addr < rom_size && rom.
data()[flag_addr] == 0xB8) {
400 absl::StrFormat(
"Overworld entrance flag at 0x%X is still 0xB8 "
401 "(vanilla); expanded entrance tables may be ignored",
410 if (panel_id.size() > 2 && panel_id[0] ==
's' &&
411 absl::ascii_isdigit(panel_id[1])) {
412 const size_t dot = panel_id.find(
'.');
413 if (dot != absl::string_view::npos) {
414 return std::string(panel_id.substr(dot + 1));
417 return std::string(panel_id);
421 namespace fs = std::filesystem;
423 std::vector<fs::path> roots;
424 if (
const char* home = std::getenv(
"HOME");
425 home !=
nullptr && std::strlen(home) > 0) {
426 roots.push_back(fs::path(home) /
"src" /
"hobby" /
"oracle-of-secrets");
429 std::vector<fs::path> candidates;
430 std::unordered_set<std::string> seen;
431 auto add_candidate = [&](
const fs::path& path) {
433 if (!fs::exists(path, ec) || ec) {
436 std::string ext = path.extension().string();
437 absl::AsciiStrToLower(&ext);
438 if (ext !=
".yaze" && ext !=
".yazeproj") {
441 const std::string normalized = fs::weakly_canonical(path, ec).string();
442 const std::string key = normalized.empty() ? path.string() : normalized;
443 if (key.empty() || seen.count(key) > 0) {
447 candidates.push_back(path);
450 for (
const auto& root : roots) {
452 if (!fs::exists(root, ec) || ec) {
457 add_candidate(root /
"Oracle-of-Secrets.yaze");
458 add_candidate(root /
"Oracle of Secrets.yaze");
459 add_candidate(root /
"Oracle-of-Secrets.yazeproj");
460 add_candidate(root /
"Oracle of Secrets.yazeproj");
461 add_candidate(root /
"Roms" /
"Oracle of Secrets.yaze");
464 fs::recursive_directory_iterator it(
465 root, fs::directory_options::skip_permission_denied, ec);
466 fs::recursive_directory_iterator end;
470 for (; it != end; it.increment(ec)) {
475 if (it.depth() > 2) {
476 it.disable_recursion_pending();
479 add_candidate(it->path());
483 if (candidates.empty()) {
489 for (
auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
490 manager.AddFile(it->string());
498 std::string normalized = std::string(name);
499 absl::StripAsciiWhitespace(&normalized);
500 const std::string lower = absl::AsciiStrToLower(normalized);
503 const std::string name_candidate =
505 const std::string category_candidate =
507 if (name_candidate == lower || category_candidate == lower) {
541 current_type, &applied_profile)) {
552 std::max(default_width, 480.0f));
556 absl::StrFormat(
"Layout Profile: %s", applied_profile.
label),
575 clear_after_restore)) {
612 bool remove_after_restore) {
666#ifdef YAZE_BUILD_AGENT_UI
667void EditorManager::ShowAIAgent() {
670 "AI Agent is experimental — enable Experimental Editors in Settings",
680 for (
const auto& window_id :
686void EditorManager::ShowChatHistory() {
692 : project_manager_(&toast_manager_), rom_file_manager_(&toast_manager_) {
693 std::stringstream ss;
694 ss << YAZE_VERSION_MAJOR <<
"." << YAZE_VERSION_MINOR <<
"."
695 << YAZE_VERSION_PATCH;
770 [
this](
const std::string& preset_name) {
774 [
this](
const std::string& preset_name) {
785 [
this](
const std::string& prompt) {
786#if defined(YAZE_BUILD_AGENT_UI)
796 chat->SendMessage(prompt);
802#if defined(YAZE_BUILD_AGENT_UI)
822 [
this](
const std::string& filepath,
const std::string& contents) {
826 [
this](
const std::string& filepath,
const std::string& contents) {
830 std::make_unique<workflow::ProjectWorkflowOutputPanel>());
835 if (!rom_path.empty()) {
853 absl::StrFormat(
"Failed to reload ROM: %s", status.message()),
864 absl::StrFormat(
"Failed to save project: %s", status.message()),
870 [
this](
const std::string& type) {
872 if (!folder_path.empty()) {
873 if (type ==
"code") {
878#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
879 editor_set->OpenAssemblyFolder(folder_path);
883 }
else if (type ==
"assets") {
891 folder_path.c_str()),
953 return dungeon_editor->IsWorkbenchWorkflowEnabled();
958 [
this](
bool enabled) {
962 dungeon_editor->QueueWorkbenchWorkflowMode(enabled);
973 registry.Register({
"command_palette",
978 registry.Register({
"keyboard_shortcuts",
979 "Keyboard Shortcuts",
983 registry.Register({
"open_rom_project",
984 "Open ROM / Project",
988 registry.Register({
"settings",
1005 for (
auto& panel : registry_panels) {
1014 dashboard_definition.
id =
"dashboard.main";
1017 dashboard_definition.
category =
"Dashboard";
1025 {.card_id =
"dashboard.main",
1026 .display_name =
"Dashboard",
1027 .window_title =
" Dashboard",
1029 .category =
"Dashboard",
1030 .shortcut_hint =
"Ctrl+E",
1066 std::vector<std::function<void()>> actions_to_execute;
1068 const int processed_count =
static_cast<int>(actions_to_execute.size());
1069 for (
auto& action : actions_to_execute) {
1072 if (processed_count > 0) {
1074 processed_count, std::memory_order_relaxed) -
1076 if (remaining < 0) {
1092 if (e.category.empty() ||
1096 if (IsTransientPanelVisibilityId(e.base_panel_id)) {
1150 for (
const auto& [type, labels] : src) {
1151 auto& out = (*dst)[type];
1152 for (
const auto& [key, value] : labels) {
1158 merged_labels.clear();
1159 std::vector<std::string> source_parts;
1165 source_parts.push_back(
"rom");
1175 source_parts.push_back(
"registry");
1182 source_parts.push_back(
"project");
1185 auto* label_source = merged_labels.empty() ? &empty_labels : &merged_labels;
1186 std::string source =
1187 source_parts.empty() ?
"empty" : absl::StrJoin(source_parts,
"+");
1189 provider.SetProjectLabels(label_source);
1195 const bool prefer_hmagic =
1199 provider.SetPreferHMagicNames(prefer_hmagic);
1202 "ResourceLabelProvider refreshed (source=%s, project_open=%s)",
1214 if (!index.has_value()) {
1227 if (session->project_context.has_value()) {
1228 session->project_context->feature_flags = session->feature_flags;
1245 if (!index.has_value()) {
1286 if (!index.has_value()) {
1303 return absl::OkStatus();
1306 if (session ==
nullptr || !session->editors.HasPendingProjectDraftChanges()) {
1307 return absl::OkStatus();
1310 return absl::FailedPreconditionError(
1311 "Project editor drafts require their project context to be active");
1313 return session->editors.PrepareProjectSave();
1327 std::optional<size_t> previous_session_id) {
1332 if (index.has_value()) {
1333 auto* previous_session =
1335 if (previous_session) {
1355 size_t previous_session_count) {
1358 return absl::OkStatus();
1361 if (!provisional_session) {
1362 return absl::InternalError(
1363 "A provisional session was created but is not active");
1366 provisional_session->session_id());
1379 const std::string& filepath,
const std::string& contents) {
1384 if (!targets_current) {
1387 return absl::OkStatus();
1390 return absl::FailedPreconditionError(
1391 "Project settings also have unsaved changes; use raw Save As to "
1392 "preserve this draft, then save the project settings");
1398 if (!session || !session->rom.is_loaded()) {
1399 return absl::FailedPreconditionError(
1400 "No active ROM session for this project file");
1406 return absl::InvalidArgumentError(
1407 "Raw project file must contain a non-empty project name");
1413 parsed_project.
rom_filename, session->rom.filename()))) {
1414 return absl::FailedPreconditionError(
1415 "Raw project file cannot change the ROM backing file of a loaded "
1416 "session; use Project Management > Swap ROM");
1423 const std::string& filepath,
const std::string& contents) {
1427 return absl::OkStatus();
1434 return absl::FailedPreconditionError(
"No active ROM session");
1441 session->project_dirty =
false;
1449 return absl::OkStatus();
1457 if (!session || !session->project_file_editor_state.initialized ||
1458 session->project_file_editor_state.modified) {
1465 LOG_WARN(
"EditorManager",
"Failed to rebase raw project editor: %s",
1479 const bool needs_version_manager =
1486 if (needs_version_manager) {
1509 if (ImGui::GetCurrentContext() !=
nullptr) {
1516 const auto custom_object_state =
1520 custom_object_manager.ActivateRuntimeContext(
1522 custom_object_state);
1524 custom_object_manager.RestoreState(custom_object_state);
1601absl::StatusOr<const project::YazeProject*>
1604 return absl::FailedPreconditionError(
"No session coordinator");
1607 if (!session || !session->rom.is_loaded()) {
1608 return absl::FailedPreconditionError(
"No active ROM session");
1614 return absl::FailedPreconditionError(
1615 "Active ROM session project context is not restored");
1618 if (!session->project_context.has_value()) {
1619 return absl::FailedPreconditionError(
1620 "Active ROM session has no project/save context");
1622 return &*session->project_context;
1640 "Session %zu has no project context; saves are disabled",
1650 const auto custom_object_state =
1702 if (session !=
nullptr && !category.empty() &&
1706 const size_t session_id = session ? session->
session_id() : new_index;
1720 const size_t active_editor_index =
EditorTypeIndex(active_editor_type);
1721 const bool active_session_editor_initialized =
1722 session !=
nullptr &&
1725 if (!transient && active_editor !=
nullptr &&
1727 active_session_editor_initialized &&
1732#ifdef YAZE_ENABLE_TESTING
1740 LOG_DEBUG(
"EditorManager",
"Session switched to %zu via EventBus", new_index);
1761 const size_t session_id = session ? session->
session_id() : index;
1771 LOG_INFO(
"EditorManager",
"Session %zu created via EventBus", index);
1784 auto* closing_session =
1788 if (closing_session !=
nullptr) {
1790 closing_session->session_id());
1792 if (closing_session !=
nullptr &&
1804 palette_session = active_session;
1805 if (closing_session !=
nullptr && active_session == closing_session &&
1806 session_count > 1) {
1807 const size_t replacement_index = index > 0 ? index - 1 : 1;
1813 palette_session ? &palette_session->
game_data :
nullptr);
1825#ifdef YAZE_ENABLE_TESTING
1830 LOG_INFO(
"EditorManager",
"Session %zu closed via EventBus", index);
1849#ifdef YAZE_ENABLE_TESTING
1855 LOG_INFO(
"EditorManager",
"ROM loaded in session %zu via EventBus", index);
1861 case Action::kShowEmulator:
1867 case Action::kShowSettings:
1877 case Action::kShowPanelBrowser:
1883 case Action::kShowSearch:
1889 case Action::kShowShortcuts:
1895 case Action::kShowCommandPalette:
1901 case Action::kShowHelp:
1912 case Action::kShowAgentChatSidebar:
1921 case Action::kShowAgentProposalsSidebar:
1928 case Action::kOpenRom: {
1932 std::string(
"Open failed: ") + std::string(status.message()),
1937 case Action::kSaveRom:
1940 if (!absl::IsCancelled(status) && !status.ok()) {
1942 std::string(
"Save failed: ") + std::string(status.message()),
1950 auto status = current_editor->
Undo();
1953 std::string(
"Undo failed: ") + std::string(status.message()),
1961 auto status = current_editor->
Redo();
1964 std::string(
"Redo failed: ") + std::string(status.message()),
1970 case Action::kResetLayout:
1979#ifdef YAZE_ENABLE_TESTING
1981 test_manager.RegisterTestSuite(
1982 std::make_unique<test::CoreSystemsTestSuite>());
1983 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
1984 test_manager.RegisterTestSuite(
1985 std::make_unique<test::PerformanceTestSuite>());
1986 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
1987 test_manager.RegisterTestSuite(
1988 std::make_unique<test::RomDependentTestSuite>());
1991 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
1992 test_manager.RegisterTestSuite(
1993 std::make_unique<test::ZSCustomOverworldTestSuite>());
1997#ifdef YAZE_ENABLE_GTEST
1998 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
2002#ifdef YAZE_WITH_GRPC
2007 test_manager.UpdateResourceStats();
2011 const std::string& filename) {
2014 SeedOracleProjectInRecents();
2023 if (!filename.empty()) {
2048 const bool prefer_dashboard_only =
2051 if (!prefer_dashboard_only) {
2054 if (!category.empty()) {
2072#ifdef YAZE_ENABLE_TESTING
2083 const std::vector<WindowDefinition> panel_definitions = {
2084 {.id =
"emulator.cpu_debugger",
2085 .display_name =
"CPU Debugger",
2087 .category =
"Emulator",
2089 {.id =
"emulator.ppu_viewer",
2090 .display_name =
"PPU Viewer",
2092 .category =
"Emulator",
2094 {.id =
"emulator.memory_viewer",
2095 .display_name =
"Memory Viewer",
2097 .category =
"Emulator",
2099 {.id =
"emulator.breakpoints",
2100 .display_name =
"Breakpoints",
2102 .category =
"Emulator",
2104 {.id =
"emulator.performance",
2105 .display_name =
"Performance",
2107 .category =
"Emulator",
2109 {.id =
"emulator.ai_agent",
2110 .display_name =
"AI Agent",
2112 .category =
"Emulator",
2114 {.id =
"emulator.save_states",
2115 .display_name =
"Save States",
2117 .category =
"Emulator",
2119 {.id =
"emulator.keyboard_config",
2120 .display_name =
"Keyboard Config",
2122 .category =
"Emulator",
2124 {.id =
"emulator.virtual_controller",
2125 .display_name =
"Virtual Controller",
2127 .category =
"Emulator",
2129 {.id =
"emulator.apu_debugger",
2130 .display_name =
"APU Debugger",
2132 .category =
"Emulator",
2134 {.id =
"emulator.audio_mixer",
2135 .display_name =
"Audio Mixer",
2137 .category =
"Emulator",
2139 {.id =
"memory.hex_editor",
2140 .display_name =
"Hex Editor",
2142 .category =
"Memory",
2145 .legacy_ids = {
"Memory Editor"}},
2153 for (
const auto& definition : panel_definitions) {
2155 descriptor.
card_id = definition.id;
2158 descriptor.
icon = definition.icon;
2159 descriptor.
category = definition.category;
2161 descriptor.
priority = definition.priority;
2162 descriptor.
scope = definition.scope;
2166 descriptor.
on_show = definition.on_show;
2167 descriptor.
on_hide = definition.on_hide;
2169 for (
const auto& legacy_id : definition.legacy_ids) {
2174 if (definition.visible_by_default) {
2195 LOG_WARN(
"EditorManager",
"Failed to load user settings: %s",
2205 [
this](
const std::string& theme_name) {
2211 [
this](
const std::string& locale) {
2225 prefs.switch_motion_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
2227 prefs.reduced_motion,
2260 if (ImGui::GetCurrentContext() !=
nullptr) {
2264 "ImGui context not available; skipping FontGlobalScale update");
2271 if (!last_theme.empty() && last_theme !=
"Custom Accent") {
2276#ifdef __EMSCRIPTEN__
2279 LOG_INFO(
"EditorManager",
"WASM Control and Session APIs initialized");
2300 flags.overworld.kSaveOverworldEntrances =
2310 session->feature_flags = flags;
2334 LOG_INFO(
"EditorManager",
"ROM load options applied: preset=%s",
2343 [
this](
bool visible,
bool expanded) {
2355 [
this](
float width) {
2362 const std::string& category) {
2363 if (category.empty() ||
2372 if (it != prefs.panel_visibility_state.end()) {
2382 bool any_visible =
false;
2383 for (
const auto& desc :
2385 if (desc.visibility_flag && *desc.visibility_flag) {
2405 [
this](
const std::string& category) ->
Editor* {
2412 [
this](
const std::string& category) {
2420 [
this](
const std::string& category) {
2425 if (category ==
"Agent") {
2426#ifdef YAZE_BUILD_AGENT_UI
2441 [
this](
const std::string& category,
const std::string& path) {
2442 if (category ==
"Assembly") {
2444 editor_set->ChangeActiveAssemblyFile(path);
2472 const std::string& editor_name,
const std::string& panels_str) {
2473 const bool has_editor = !editor_name.empty();
2474 const bool has_panels = !panels_str.empty();
2476 if (!has_editor && !has_panels) {
2481 "Processing startup flags: editor='%s', panels='%s'",
2482 editor_name.c_str(), panels_str.c_str());
2484 std::optional<EditorType> editor_type_to_open =
2486 if (has_editor && !editor_type_to_open.has_value()) {
2487 LOG_WARN(
"EditorManager",
"Unknown editor specified via flag: %s",
2488 editor_name.c_str());
2489 }
else if (editor_type_to_open.has_value()) {
2501 bool applied_category_from_panel =
false;
2503 for (absl::string_view token :
2504 absl::StrSplit(panels_str,
',', absl::SkipWhitespace())) {
2505 if (token.empty()) {
2508 std::string panel_name = std::string(absl::StripAsciiWhitespace(token));
2509 LOG_DEBUG(
"EditorManager",
"Attempting to open panel: '%s'",
2510 panel_name.c_str());
2512 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2513 if (lower_name ==
"welcome" || lower_name ==
"welcome_screen") {
2519 if (lower_name ==
"dashboard" || lower_name ==
"dashboard.main" ||
2520 lower_name ==
"editor_selection") {
2534 if (absl::StartsWith(panel_name,
"Room ")) {
2537 int room_id = std::stoi(panel_name.substr(5));
2540 }
catch (
const std::exception& e) {
2541 LOG_WARN(
"EditorManager",
"Invalid room ID format: %s",
2542 panel_name.c_str());
2548 std::optional<std::string> resolved_panel;
2550 resolved_panel = panel_name;
2552 for (
const auto& [prefixed_id, descriptor] :
2554 const std::string base_id = StripSessionPrefix(prefixed_id);
2555 const std::string card_lower = absl::AsciiStrToLower(base_id);
2556 const std::string display_lower =
2557 absl::AsciiStrToLower(descriptor.display_name);
2559 if (card_lower == lower_name || display_lower == lower_name) {
2560 resolved_panel = base_id;
2566 if (!resolved_panel.has_value()) {
2568 "Unknown panel '%s' from --open_panels (known count: %zu)",
2575 const auto* descriptor =
2577 if (descriptor !=
nullptr) {
2581 if (!ensure_status.ok()) {
2582 LOG_WARN(
"EditorManager",
"Failed to load assets for panel '%s': %s",
2583 resolved_panel->c_str(), ensure_status.message().data());
2586 if (descriptor && !applied_category_from_panel &&
2589 applied_category_from_panel =
true;
2590 }
else if (!applied_category_from_panel && descriptor &&
2591 descriptor->category.empty() && !last_known_category.empty()) {
2595 LOG_WARN(
"EditorManager",
"Failed to show panel '%s'",
2596 resolved_panel->c_str());
2617 constexpr int kTargetRevision =
2619 const int previous_revision =
2625 const bool includes_workspace_reset_revision =
2636 "Applied panel layout defaults migration %d -> %d (%s)",
2637 previous_revision, kTargetRevision,
2638 includes_workspace_reset_revision ?
"workspace reset"
2639 :
"targeted preferences");
2643 const std::string& saved_category,
2644 const std::vector<std::string>& available_categories)
const {
2645 const std::string preferred =
2647 if (preferred.empty()) {
2670 const bool sidebar_visible =
2697 std::string panels_str;
2698 for (
size_t i = 0; i < config.
open_panels.size(); ++i) {
2716 "Ignoring invalid startup overworld map target %d",
2751 if (index < session->editor_initialized.size()) {
2761 if (index < session->editor_assets_loaded.size()) {
2783 const auto visibility_it =
2787 visibility_it->second);
2825 std::unordered_set<EditorType> types;
2840 auto add_category = [&](
const std::string& category) {
2841 if (category.empty() ||
2849 bool used_startup_hints =
false;
2853 add_type(*startup_type);
2854 used_startup_hints =
true;
2859 if (panel_name.empty()) {
2863 if (
const auto* descriptor =
2865 add_category(descriptor->category);
2866 used_startup_hints =
true;
2870 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2871 for (
const auto& [prefixed_id, descriptor] :
2873 const std::string base_id = StripSessionPrefix(prefixed_id);
2874 const std::string card_lower = absl::AsciiStrToLower(base_id);
2875 const std::string display_lower =
2876 absl::AsciiStrToLower(descriptor.display_name);
2877 if (card_lower == lower_name || display_lower == lower_name) {
2878 add_category(descriptor.category);
2879 used_startup_hints =
true;
2885 if (used_startup_hints) {
2886 return std::vector<EditorType>(types.begin(), types.end());
2895 if (editor !=
nullptr && *editor->active()) {
2896 add_type(editor->type());
2907 for (
const auto& window_id :
2909 if (
const auto* descriptor =
2911 add_category(descriptor->category);
2915 return std::vector<EditorType>(types.begin(), types.end());
2920 return editor_set ? editor_set->
GetEditor(type) :
nullptr;
2924 if (category.empty() ||
2937#ifdef YAZE_BUILD_AGENT_UI
2954 }
else if (!category.empty() &&
2956 LOG_DEBUG(
"EditorManager",
"No editor context available for category '%s'",
2965 return absl::FailedPreconditionError(
"No editor set available");
2970 return absl::OkStatus();
2972 editor->Initialize();
2973 return absl::OkStatus();
2981 return absl::FailedPreconditionError(
"No active session");
2983 if (session->game_data_loaded) {
2984 return absl::OkStatus();
2986 if (!session->rom.is_loaded()) {
2987 return absl::FailedPreconditionError(
"ROM not loaded");
2993 auto* game_data = &session->game_data;
2994 auto* editor_set = &session->editors;
2995 editor_set->SetGameData(game_data);
2999 session->game_data_loaded =
true;
3001 return absl::OkStatus();
3006 return absl::OkStatus();
3013 return absl::OkStatus();
3016 if (!session->rom.is_loaded()) {
3018 return absl::OkStatus();
3021 if (index >= session->editor_initialized.size()) {
3022 return absl::InvalidArgumentError(
"Invalid editor type");
3025 return absl::OkStatus();
3027 bool initialized_now =
false;
3028 if (!session->editor_initialized[index]) {
3032 initialized_now =
true;
3034 if (initialized_now) {
3037 return absl::OkStatus();
3041 if (index >= session->editor_initialized.size()) {
3042 return absl::InvalidArgumentError(
"Invalid editor type");
3049 bool initialized_now =
false;
3050 if (!session->editor_initialized[index]) {
3054 initialized_now =
true;
3061 bool loaded_now =
false;
3062 if (!session->editor_assets_loaded[index]) {
3071 if (initialized_now || loaded_now) {
3075 return absl::OkStatus();
3126 bool is_emulator_visible =
3137 if (!save_status.ok()) {
3138 LOG_WARN(
"EditorManager",
"Failed to save user settings: %s",
3139 save_status.ToString().c_str());
3149 static Rom* last_test_rom =
nullptr;
3151 if (last_test_rom != current_rom) {
3154 "EditorManager::Update - ROM changed, updating TestManager: %p -> %p",
3155 (
void*)last_test_rom, (
void*)current_rom);
3157 last_test_rom = current_rom;
3164 const bool current_session_has_unsaved_work =
3166 const auto* current_session =
3169 const bool backup_restore_requires_explicit_save =
3170 current_session !=
nullptr && current_session->backup_restore_pending;
3172 current_session_has_unsaved_work &&
3173 !backup_restore_requires_explicit_save) {
3180 }
else if (absl::IsCancelled(st)) {
3185 absl::StrFormat(
"Autosave failed: %s", st.message()),
3194 if (current_rom && current_rom->is_loaded()) {
3202 &editor_set->GetAssemblySymbols());
3218 UpdateBuildWorkflowStatus(
3223 : (snapshot.status.ok() ?
"Build succeeded" :
"Build failed"),
3224 snapshot.output_tail.empty()
3227 "Running the configured project build command")
3228 : std::string(snapshot.status.message()))
3229 : snapshot.output_tail,
3234 snapshot.output_tail, snapshot.running));
3245 if (!snapshot.status.ok()) {
3246 AppendWorkflowHistoryEntry(
3248 MakeBuildStatus(
"Build failed", std::string(snapshot.status.message()),
3253 absl::StrFormat(
"Build failed: %s", snapshot.status.message()),
3254 snapshot.status.code() == absl::StatusCode::kCancelled
3260 AppendWorkflowHistoryEntry(
3264 snapshot.output_tail.empty() ? std::string(
"Project build completed")
3265 : snapshot.output_tail,
3269 ?
"Project build completed"
3270 : absl::StrFormat(
"Project build completed: %s",
3271 snapshot.output_tail),
3327 std::unordered_set<std::string> active_editor_categories;
3331 for (
size_t session_idx = 0;
3336 if (!session || !session->rom.is_loaded()) {
3340 for (
auto* editor : session->editors.active_editors_) {
3342 std::string category =
3344 active_editor_categories.insert(category);
3350 active_editor_categories.insert(
"Emulator");
3355 const bool prefer_dashboard_only =
3359 if (!prefer_dashboard_only && sidebar_category.empty() &&
3360 !all_categories.empty()) {
3362 if (!sidebar_category.empty()) {
3374 auto has_rom_callback = [
this]() ->
bool {
3380 auto is_rom_dirty_callback = [
this]() ->
bool {
3384 auto pending_dungeon_rooms_callback = [
this]() ->
int {
3388 return dungeon_editor->PendingRoomCount();
3394 all_categories, active_editor_categories,
3395 has_rom_callback, is_rom_dirty_callback,
3396 pending_dungeon_rooms_callback);
3412 session_index, session_count,
3417 bool has_agent_info =
false;
3418#if defined(YAZE_BUILD_AGENT_UI)
3420 auto* chat = agent_editor->GetAgentChat();
3424 bool active = chat && *chat->active();
3426 has_agent_info =
true;
3430 if (!has_agent_info) {
3445 editor_opts.
tooltip =
"Click to switch editor (Ctrl+E)";
3453 std::move(editor_opts));
3461 dirty_opts.
tooltip = absl::StrFormat(
3462 "%s. Click to save ROM when ROM-buffer work is pending, otherwise open "
3463 "the Project drawer.",
3465 dirty_opts.
on_click = [
this, session_index]() {
3477 std::move(dirty_opts));
3487 "Right drawer open — Esc closes, View > Drawers switches";
3489 std::move(drawer_opts));
3521 if (ImGui::BeginMenuBar()) {
3524 const char* tooltip = sidebar_visible ?
"Hide Activity Bar (Ctrl+B)"
3525 :
"Show Activity Bar (Ctrl+B)";
3527 icon, tooltip, sidebar_visible)) {
3541 ImGui::EndMenuBar();
3550 bool visible =
true;
3551 ImGui::ShowDemoWindow(&visible);
3557 bool visible =
true;
3558 ImGui::ShowMetricsWindow(&visible);
3566 bool* hex_visibility =
3568 if (hex_visibility) {
3572 editor->set_active(*hex_visibility);
3574 *hex_visibility = *editor->active();
3580 editor->set_active(
true);
3582 if (!*editor->active()) {
3602#ifdef YAZE_ENABLE_TESTING
3603 if (show_test_dashboard_) {
3620 bool visible =
true;
3699 return absl::OkStatus();
3706 auto load_from_path = [
this](
const std::string& file_name) -> absl::Status {
3707 if (file_name.empty()) {
3708 return absl::OkStatus();
3712 if (absl::EndsWith(file_name,
".yaze") ||
3713 absl::EndsWith(file_name,
".zsproj") ||
3714 absl::EndsWith(file_name,
".yazeproj")) {
3720 if (!path_status.ok()) {
3730 const std::optional<size_t> previous_session_id =
3732 const size_t previous_session_count =
3735 std::move(temp_rom), file_name);
3736 if (!session_or.ok()) {
3737 return session_or.status();
3753#ifdef YAZE_ENABLE_TESTING
3758 const auto& recent_files = manager.GetRecentFiles();
3759 const bool is_first_time_rom_path =
3760 std::find(recent_files.begin(), recent_files.end(), file_name) ==
3763 if (!asset_status.ok()) {
3764 auto rollback_status =
3767 if (!rollback_status.ok()) {
3768 return absl::InternalError(
3769 absl::StrFormat(
"%s; session rollback failed: %s",
3770 asset_status.message(), rollback_status.message()));
3772 return asset_status;
3775 manager.AddFile(file_name);
3786 return absl::OkStatus();
3789#if defined(__APPLE__) && TARGET_OS_IOS == 1
3796 return absl::OkStatus();
3800 return load_from_path(file_name);
3807 if (!current_rom || !current_editor_set) {
3808 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
3812 if (!current_session) {
3813 return absl::FailedPreconditionError(
"No active ROM session");
3817 auto start_time = std::chrono::steady_clock::now();
3819#ifdef __EMSCRIPTEN__
3821 auto loading_handle =
3823 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
3825 : app::platform::WasmLoadingManager::BeginLoading(
3826 "Loading Editor Assets");
3829 constexpr float kStartProgress = 0.10f;
3830 constexpr float kEndProgress = 1.0f;
3831 constexpr int kTotalSteps = 11;
3832 int current_step = 0;
3833 auto update_progress = [&](
const std::string& message) {
3836 kStartProgress + (kEndProgress - kStartProgress) *
3837 (
static_cast<float>(current_step) / kTotalSteps);
3838 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, progress);
3839 app::platform::WasmLoadingManager::UpdateMessage(loading_handle, message);
3842 auto cleanup_loading = [&]() {
3843 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3845 struct LoadingGuard {
3846 std::function<void()> cleanup;
3847 bool dismissed =
false;
3852 void dismiss() { dismissed =
true; }
3853 } loading_guard{cleanup_loading};
3855 (void)passed_handle;
3864 const std::unordered_set<EditorType> preload_types(
3865 preload_editor_list.begin(), preload_editor_list.end());
3873 const InitStep init_steps[] = {
3880 for (
const auto& step : init_steps) {
3881 if (!preload_types.contains(step.type)) {
3884 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
3885 editor->Initialize();
3887 if (step.mark_loaded) {
3893#ifdef __EMSCRIPTEN__
3894 update_progress(
"Loading graphics sheets...");
3900 current_session->game_data_loaded =
true;
3904 current_session->game_data.gfx_bitmaps;
3908 auto* game_data = ¤t_session->game_data;
3909 current_editor_set->SetGameData(game_data);
3914 const char* progress_message;
3916 const LoadStep load_steps[] = {
3926 for (
const auto& step : load_steps) {
3927 if (!preload_types.contains(step.type)) {
3930#ifdef __EMSCRIPTEN__
3931 update_progress(step.progress_message);
3933 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
3939#ifdef __EMSCRIPTEN__
3940 update_progress(
"Finishing up...");
3945 auto* settings = current_editor_set->GetSettingsPanel();
3956#ifdef __EMSCRIPTEN__
3958 loading_guard.dismiss();
3959 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3962 auto end_time = std::chrono::steady_clock::now();
3963 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
3964 end_time - start_time);
3965 LOG_DEBUG(
"EditorManager",
"ROM assets loaded in %lld ms", duration.count());
3967 return absl::OkStatus();
3973 if (!current_rom || !current_editor_set) {
3974 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
3978 if (!current_session) {
3979 return absl::FailedPreconditionError(
"No active ROM session");
3983#ifdef __EMSCRIPTEN__
3985 auto loading_handle =
3987 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
3989 : app::platform::WasmLoadingManager::BeginLoading(
3990 "Loading ROM (lazy assets)");
3991 auto cleanup_loading = [&]() {
3992 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3994 struct LoadingGuard {
3995 std::function<void()> cleanup;
3996 bool dismissed =
false;
4002 void dismiss() { dismissed =
true; }
4003 } loading_guard{cleanup_loading};
4005 (void)passed_handle;
4015 auto* settings = current_editor_set->GetSettingsPanel();
4022#ifdef __EMSCRIPTEN__
4023 loading_guard.dismiss();
4024 app::platform::WasmLoadingManager::EndLoading(loading_handle);
4027 LOG_INFO(
"EditorManager",
"Lazy asset mode: editor assets deferred");
4028 return absl::OkStatus();
4047 const std::optional<std::string>& target_filename) {
4077 for (
const char* popup :
4087 const std::optional<std::string>& save_as_filename) {
4091 return absl::FailedPreconditionError(
"No active ROM session");
4097 return absl::OkStatus();
4104 if (status.ok() || !absl::IsCancelled(status) ||
4114 const bool unresolved_confirmation =
4119 if (unresolved_confirmation) {
4120 return absl::CancelledError(
"Save pending confirmation");
4134 const std::optional<std::string>& save_as_filename) {
4137 if (!current_rom || !current_editor_set) {
4138 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
4140 if (save_as_filename.has_value() && save_as_filename->empty()) {
4141 return absl::InvalidArgumentError(
"No filename provided for save as");
4143 if (save_as_filename.has_value()) {
4153 struct LifecycleProjectContextGuard {
4156 ~LifecycleProjectContextGuard() {
4168 if (current_editor_set->HasPendingGraphicsChanges()) {
4169 return absl::FailedPreconditionError(absl::StrFormat(
4170 "Save blocked: graphics sheet edits are pending, but graphics ROM "
4171 "persistence is not safely available (Save Graphics Sheets is %s). "
4172 "Discard the graphics sheet edits before saving the ROM.",
4180 if (current_editor_set->HasPendingScreenChanges()) {
4181 return absl::FailedPreconditionError(absl::StrFormat(
4182 "Save blocked: Screen Editor edits are pending, but they cannot all "
4183 "participate safely in the coordinated ROM save (Save Dungeon Maps is "
4184 "%s). Title-screen and pause-map ROM writes remain disabled until "
4185 "write/reopen/readback verification exists; discard the pending Screen "
4186 "Editor edits before saving the ROM.",
4192 return absl::CancelledError(
"Save pending confirmation");
4198 return absl::CancelledError(
"Save pending confirmation");
4201 const bool pot_items_enabled =
4205 const int loaded_rooms = current_editor_set->LoadedDungeonRoomCount();
4206 const int total_rooms = current_editor_set->TotalDungeonRoomCount();
4207 if (loaded_rooms < total_rooms) {
4215 "Save paused: pot items enabled with %d unloaded rooms",
4218 return absl::CancelledError(
"Pot item save confirmation required");
4226 struct PotItemFlagGuard {
4227 bool restore =
false;
4228 bool previous =
false;
4229 ~PotItemFlagGuard() {
4231 core::FeatureFlags::get().dungeon.kSavePotItems = previous;
4236 if (suppress_pot_items) {
4238 pot_item_guard.restore =
true;
4240 }
else if (bypass_confirm) {
4265 const auto predicted_dungeon_write_ranges =
4266 current_editor_set->CollectDungeonWriteRanges();
4269 ScopedEditorSaveTransactions editor_transactions;
4271 auto save_editor = [&editor_transactions](
Editor* editor) -> absl::Status {
4272 if (editor ==
nullptr) {
4273 return absl::OkStatus();
4276 return editor->Save();
4284 if (screen_editor !=
nullptr && (screen_editor->IsRomBackedStateValid() ||
4285 screen_editor->HasPendingScreenChanges())) {
4308 std::vector<std::pair<uint32_t, uint32_t>> write_ranges;
4309 bool diff_computed =
false;
4311 if (!current_rom->filename().empty()) {
4312 std::ifstream file(current_rom->filename(), std::ios::binary);
4313 if (file.is_open()) {
4314 file.seekg(0, std::ios::end);
4315 const std::streampos end = file.tellg();
4317 std::vector<uint8_t> disk_data(
static_cast<size_t>(end));
4318 file.seekg(0, std::ios::beg);
4319 file.read(
reinterpret_cast<char*
>(disk_data.data()),
4320 static_cast<std::streamsize
>(disk_data.size()));
4322 diff_computed =
true;
4324 current_rom->vector());
4325 if (!diff.ranges.empty()) {
4327 "ROM save diff: %zu bytes changed in %zu range(s)",
4328 diff.total_bytes_changed, diff.ranges.size());
4329 write_ranges = std::move(diff.ranges);
4336 if (write_ranges.empty() && !diff_computed) {
4337 write_ranges = predicted_dungeon_write_ranges;
4340 if (!write_ranges.empty()) {
4343 if (!conflicts.empty()) {
4350 "Save paused: %zu write conflict(s) with ASM hooks",
4353 return absl::CancelledError(
"Write conflict confirmation required");
4365 save_as_filename.has_value()
4368 if (save_status.ok()) {
4369 editor_transactions.Commit();
4370 rom_transaction.
Commit();
4374 session->backup_restore_pending =
false;
4380 if (save_as_filename.has_value()) {
4383 session->filepath = *save_as_filename;
4388 manager.AddFile(*save_as_filename);
4399 if (filename.empty()) {
4400 return absl::InvalidArgumentError(
"No filename provided for save as");
4403 return absl::CancelledError(
"Save pending confirmation");
4414 return absl::FailedPreconditionError(
"No pending ROM save to resume");
4418 return absl::FailedPreconditionError(
4419 "Pending ROM save belongs to a different session");
4432 return absl::OkStatus();
4439 const std::string& filename) {
4440 LOG_INFO(
"EditorManager",
"OpenRomOrProject called with: '%s'",
4442 if (filename.empty()) {
4443 LOG_INFO(
"EditorManager",
"Empty filename provided, skipping load.");
4444 return absl::OkStatus();
4447#ifdef __EMSCRIPTEN__
4449 auto loading_handle =
4450 app::platform::WasmLoadingManager::BeginLoading(
"Loading ROM");
4451 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
4452 "Reading ROM file...");
4454 struct LoadingGuard {
4455 app::platform::WasmLoadingManager::LoadingHandle handle;
4456 bool dismissed =
false;
4459 app::platform::WasmLoadingManager::EndLoading(handle);
4461 void dismiss() { dismissed =
true; }
4462 } loading_guard{loading_handle};
4465 if (absl::EndsWith(filename,
".yaze") ||
4466 absl::EndsWith(filename,
".zsproj") ||
4467 absl::EndsWith(filename,
".yazeproj")) {
4476 const std::optional<size_t> previous_session_id =
4487 if (!project_status.ok()) {
4490 return project_status;
4492#ifdef __EMSCRIPTEN__
4493 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.05f);
4494 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
4495 "Loading ROM data...");
4502 const std::optional<size_t> previous_session_id =
4504 const size_t previous_session_count =
4507 std::move(temp_rom), filename);
4508 if (!session_or.ok()) {
4509 return session_or.status();
4528#ifdef YAZE_ENABLE_TESTING
4529 LOG_DEBUG(
"EditorManager",
"Setting ROM in TestManager - %p ('%s')",
4537 const std::string absolute_code_folder =
4542#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
4543 editor_set->OpenAssemblyFolder(absolute_code_folder);
4549#ifdef __EMSCRIPTEN__
4550 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.10f);
4551 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
4552 "Initializing editors...");
4555 loading_guard.dismiss();
4560 if (!asset_status.ok()) {
4561 auto rollback_status =
4564 if (!rollback_status.ok()) {
4565 return absl::InternalError(
4566 absl::StrFormat(
"%s; session rollback failed: %s",
4567 asset_status.message(), rollback_status.message()));
4569 return asset_status;
4582 return absl::OkStatus();
4587 return absl::FailedPreconditionError(
4588 "Save or discard pending session work before creating a project");
4592 return absl::FailedPreconditionError(
4593 "Project creation dialog is not available");
4596 template_name.empty() ?
"Vanilla ROM Hack" : template_name);
4597 return absl::OkStatus();
4601 const std::string& template_name,
const std::string& rom_path,
4602 const std::string& project_name,
const std::string& project_path) {
4603 if (rom_path.empty() || project_name.empty()) {
4604 return absl::InvalidArgumentError(
"ROM path and project name are required");
4606 if (absl::EndsWith(rom_path,
".yaze") ||
4607 absl::EndsWith(rom_path,
".yazeproj") ||
4608 absl::EndsWith(rom_path,
".zsproj")) {
4609 return absl::InvalidArgumentError(
4610 "New projects require a ROM file, not a project descriptor");
4613 return absl::FailedPreconditionError(
4614 "Save or discard pending session work before creating a project");
4620 const bool reuse_active_raw_session =
4621 reusable_session && reusable_session->rom.is_loaded() &&
4623 rom_path, reusable_session->rom.filename()) &&
4626 const std::optional<size_t> previous_session_id =
4628 const size_t previous_session_count =
4632 if (!rom_status.ok()) {
4637 project_name, project_path);
4638 if (!target_status.ok()) {
4640 return target_status;
4643 if (reuse_active_raw_session) {
4652 if (!open_status.ok()) {
4659 if (!finalize_status.ok()) {
4660 auto rollback_status =
4663 if (!rollback_status.ok()) {
4664 return absl::InternalError(absl::StrFormat(
4665 "%s; session rollback failed: %s", finalize_status.message(),
4666 rollback_status.message()));
4669 return finalize_status;
4673 const std::string& project_name,
const std::string& project_path) {
4675 return absl::FailedPreconditionError(
"No session coordinator");
4678 if (!session || !session->rom.is_loaded()) {
4679 return absl::FailedPreconditionError(
4680 "Load a ROM before finalizing the project");
4687 if (created_project_shell) {
4691 auto project_rom_status =
4693 if (!project_rom_status.ok()) {
4695 return project_rom_status;
4698 if (created_project_shell) {
4704 pending_project.workspace_settings.font_global_scale =
4706 pending_project.workspace_settings.autosave_enabled =
4708 pending_project.workspace_settings.autosave_interval_secs =
4710 pending_project.workspace_settings.backup_on_save =
4713 auto finalize_status =
4715 if (!finalize_status.ok()) {
4717 return finalize_status;
4722 session->project_dirty =
false;
4734 return absl::OkStatus();
4741 return absl::OkStatus();
4748 auto open_project_from_path =
4749 [
this](
const std::string& file_path) -> absl::Status {
4750 if (file_path.empty()) {
4751 return absl::OkStatus();
4763 auto validation_status = new_project.
Validate();
4764 if (!validation_status.ok()) {
4766 validation_status.message()),
4773 const std::optional<size_t> previous_session_id =
4782 if (!project_status.ok()) {
4785 return project_status;
4788#if defined(__APPLE__) && TARGET_OS_IOS == 1
4792 return absl::OkStatus();
4795 return open_project_from_path(file_path);
4800 const std::string& rom_path) {
4809 .candidate_path = rom_path,
4811 return absl::OkStatus();
4817 std::string project_rom_path = pending_selection.has_value()
4818 ? pending_selection->candidate_path
4820 const std::string previous_project_rom_path =
4821 pending_selection.has_value() ? pending_selection->previous_path
4823 bool persist_selected_rom = pending_selection.has_value();
4824 auto abandon_selected_rom = [&]() {
4825 if (!persist_selected_rom) {
4833 if (project_rom_path.empty()) {
4836 "Project has no ROM file configured. Please select a ROM.",
4838#if defined(__APPLE__) && TARGET_OS_IOS == 1
4846 auto bundle_parent =
4848 if (bundle_parent.extension() ==
".yazeproj") {
4850 "ROM is downloading from iCloud. Reopen the project in a few "
4853 return absl::UnavailableError(
4854 "Project ROM is not available from iCloud yet");
4858 const std::optional<size_t> target_session_id =
4863 [
this, target_project_filepath, target_session_id,
4864 selection_generation](
const std::string& rom_path) {
4868 toast_manager_.Show(
4869 "ROM selection ignored because the active project changed",
4870 ToastType::kWarning);
4873 const bool restore_pending_transition =
4875 const std::optional<size_t> previous_session_id =
4877 auto restore_after_failure = [&]() {
4878 if (restore_pending_transition) {
4882 if (rom_path.empty()) {
4883 restore_after_failure();
4888 if (!path_status.ok()) {
4891 restore_after_failure();
4895 if (!selection_status.ok()) {
4897 absl::StrFormat(
"Failed to update project ROM: %s",
4898 selection_status.message()),
4900 restore_after_failure();
4906 absl::StrFormat(
"Failed to load project ROM: %s",
4909 restore_after_failure();
4912 return absl::OkStatus();
4916 if (rom_path.empty()) {
4917 return absl::CancelledError(
"Project ROM selection cancelled");
4920 project_rom_path = rom_path;
4921 persist_selected_rom =
true;
4925 auto backing_status =
4927 if (!backing_status.ok()) {
4928 abandon_selected_rom();
4929 return backing_status;
4935 if (!load_status.ok()) {
4938 absl::StrFormat(
"Could not load ROM '%s': %s. Please select a new ROM.",
4939 project_rom_path, load_status.message()),
4941#if defined(__APPLE__) && TARGET_OS_IOS == 1
4942 abandon_selected_rom();
4948 auto rom_parent = std::filesystem::path(project_rom_path).parent_path();
4949 if (rom_parent.extension() ==
".yazeproj") {
4951 "ROM is still downloading from iCloud. Try reopening the project "
4954 return absl::UnavailableError(
4955 "Project ROM is still downloading from iCloud");
4959 const std::optional<size_t> target_session_id =
4964 [
this, target_project_filepath, target_session_id,
4965 selection_generation](
const std::string& rom_path) {
4969 toast_manager_.Show(
4970 "ROM selection ignored because the active project changed",
4971 ToastType::kWarning);
4974 const bool restore_pending_transition =
4976 const std::optional<size_t> previous_session_id =
4978 auto restore_after_failure = [&]() {
4979 if (restore_pending_transition) {
4983 if (rom_path.empty()) {
4984 restore_after_failure();
4989 if (!path_status.ok()) {
4992 restore_after_failure();
4996 if (!selection_status.ok()) {
4998 absl::StrFormat(
"Failed to update project ROM: %s",
4999 selection_status.message()),
5001 restore_after_failure();
5007 absl::StrFormat(
"Failed to load project ROM: %s",
5010 restore_after_failure();
5013 return absl::OkStatus();
5017 if (rom_path.empty()) {
5018 return absl::CancelledError(
"Project ROM selection cancelled");
5021 project_rom_path = rom_path;
5022 persist_selected_rom =
true;
5028 if (!policy_status.ok()) {
5029 abandon_selected_rom();
5030 return policy_status;
5033 if (persist_selected_rom) {
5037 const size_t previous_session_count =
5040 std::move(temp_rom), project_rom_path);
5041 if (!session_or.ok()) {
5042 abandon_selected_rom();
5043 return session_or.status();
5057 "Project has custom object data but 'enable_custom_objects' was "
5058 "disabled. Enabling at runtime.");
5062 std::string legacy_mapping_warning;
5064 &legacy_mapping_warning)) {
5065 LOG_WARN(
"EditorManager",
"%s", legacy_mapping_warning.c_str());
5067 "Seeded default custom object mapping for object 0x31 (save project "
5079 "Feature flags applied: kEnableCustomObjects=%s, "
5080 "custom_objects_folder='%s', custom_object_files=%zu entries",
5098 "Project ROM hash mismatch detected. Check ROM Identity settings.",
5101 auto warnings = ValidateRomAddressOverrides(
5103 if (!warnings.empty()) {
5104 for (
const auto& warning : warnings) {
5105 LOG_WARN(
"EditorManager",
"%s", warning.c_str());
5115#ifdef YAZE_ENABLE_TESTING
5116 LOG_DEBUG(
"EditorManager",
"Setting ROM in TestManager - %p ('%s')",
5124 const std::string absolute_code_folder =
5127#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
5128 editor_set->OpenAssemblyFolder(absolute_code_folder);
5139 if (!asset_status.ok()) {
5140 abandon_selected_rom();
5141 auto rollback_status =
5143 if (!rollback_status.ok()) {
5144 return absl::InternalError(
5145 absl::StrFormat(
"%s; session rollback failed: %s",
5146 asset_status.message(), rollback_status.message()));
5148 return asset_status;
5151 if (persist_selected_rom) {
5153 if (!save_status.ok()) {
5154 abandon_selected_rom();
5155 auto rollback_status =
5157 if (!rollback_status.ok()) {
5158 return absl::InternalError(
5159 absl::StrFormat(
"%s; session rollback failed: %s",
5160 save_status.message(), rollback_status.message()));
5225 return absl::OkStatus();
5229 Rom&& rom,
const std::string& filepath) {
5231 return absl::FailedPreconditionError(
"No session coordinator");
5235 return absl::FailedPreconditionError(
"No active ROM session");
5238 Rom previous_rom = session->rom;
5239 const std::string previous_filepath = session->filepath;
5240 const bool game_data_was_loaded = session->game_data_loaded;
5244 const bool dungeon_was_initialized =
5245 session->editor_initialized[dungeon_index];
5246 const bool dungeon_assets_were_loaded =
5247 session->editor_assets_loaded[dungeon_index];
5251 const bool screen_was_initialized = session->editor_initialized[screen_index];
5252 const bool screen_assets_were_loaded =
5253 session->editor_assets_loaded[screen_index];
5254 auto restore_dungeon_asset_state = [&]() {
5255 if (dungeon_editor) {
5256 session->editor_initialized[dungeon_index] =
5257 session->editor_initialized[dungeon_index] || dungeon_was_initialized;
5258 session->editor_assets_loaded[dungeon_index] =
5259 session->editor_assets_loaded[dungeon_index] ||
5260 dungeon_assets_were_loaded;
5263 auto restore_screen_asset_state = [&]() {
5264 if (screen_editor) {
5265 session->editor_initialized[screen_index] =
5266 session->editor_initialized[screen_index] || screen_was_initialized;
5267 session->editor_assets_loaded[screen_index] =
5268 session->editor_assets_loaded[screen_index] ||
5269 screen_assets_were_loaded;
5273 session->editors.InvalidateScreenRomBackedState();
5275 session->rom = std::move(rom);
5276 session->filepath = filepath;
5277 session->game_data.Clear();
5278 session->game_data.set_rom(&session->rom);
5279 session->editors.SetGameData(&session->game_data);
5286 if (load_status.ok() && game_data_was_loaded && !session->game_data_loaded) {
5289 if (load_status.ok() && screen_editor && screen_assets_were_loaded) {
5290 load_status = screen_editor->RefreshRomBackedState();
5292 if (load_status.ok()) {
5293 restore_screen_asset_state();
5295 if (load_status.ok() && dungeon_editor) {
5296 load_status = dungeon_editor->RefreshRomBackedState();
5297 if (load_status.ok()) {
5298 restore_dungeon_asset_state();
5301 if (!load_status.ok()) {
5304 session->editors.InvalidateScreenRomBackedState();
5306 session->rom = std::move(previous_rom);
5307 session->filepath = previous_filepath;
5308 session->game_data.Clear();
5309 session->game_data.set_rom(&session->rom);
5310 session->editors.SetGameData(&session->game_data);
5317 if (game_data_was_loaded && !session->game_data_loaded) {
5319 if (rollback_status.ok()) {
5320 rollback_status = game_data_status;
5323 if (screen_editor && screen_assets_were_loaded) {
5324 auto screen_status = screen_editor->RefreshRomBackedState();
5325 if (screen_status.ok()) {
5326 restore_screen_asset_state();
5328 if (rollback_status.ok()) {
5329 rollback_status = screen_status;
5332 restore_screen_asset_state();
5334 if (dungeon_editor) {
5335 auto dungeon_status = dungeon_editor->RefreshRomBackedState();
5336 if (dungeon_status.ok()) {
5337 restore_dungeon_asset_state();
5339 if (rollback_status.ok()) {
5340 rollback_status = dungeon_status;
5344 if (!rollback_status.ok()) {
5345 return absl::InternalError(absl::StrFormat(
5346 "ROM reload failed (%s) and restoring prior assets failed (%s)",
5347 load_status.message(), rollback_status.message()));
5352 return absl::OkStatus();
5356 if (rom_path.empty()) {
5357 return absl::InvalidArgumentError(
"ROM path cannot be empty");
5360 return absl::FailedPreconditionError(
"No session coordinator");
5363 if (!session || !session->rom.is_loaded() ||
5365 return absl::FailedPreconditionError(
"No active project ROM session");
5368 return absl::FailedPreconditionError(
5369 "Save or discard ROM edits before swapping the project ROM");
5372 if (session->project_file_editor_state.initialized &&
5373 session->project_file_editor_state.modified &&
5375 return absl::FailedPreconditionError(
5376 "Preserve the raw project-file draft before swapping ROMs");
5380 rom_path, session->session_id()));
5381 Rom replacement_rom;
5388 if (!save_status.ok()) {
5394 auto replace_status =
5396 if (!replace_status.ok()) {
5401 if (!rollback_status.ok()) {
5402 return absl::InternalError(absl::StrFormat(
5403 "ROM swap failed (%s) and restoring the project descriptor failed "
5405 replace_status.message(), rollback_status.message()));
5407 return replace_status;
5409 session->project_dirty =
false;
5415 return absl::OkStatus();
5420 return absl::FailedPreconditionError(
"No session coordinator");
5423 if (!session || !session->rom.is_loaded() ||
5426 return absl::FailedPreconditionError(
"No active project ROM to reload");
5429 return absl::FailedPreconditionError(
5430 "Save or discard ROM edits before reloading from disk");
5435 Rom replacement_rom;
5447 session && session->project_file_editor_state.initialized &&
5448 session->project_file_editor_state.modified &&
5450 return absl::FailedPreconditionError(
5451 "Raw project file also has unsaved changes; use raw Save As to "
5452 "preserve that draft before saving project settings");
5482 for (
const auto& file : manager.GetRecentFiles()) {
5494 session->project_dirty =
false;
5509 session->project_dirty =
true;
5520 return session !=
nullptr &&
5521 (session->project_dirty ||
5522 session->editors.HasPendingProjectDraftChanges());
5527 return absl::FailedPreconditionError(
"No session coordinator");
5531 return absl::FailedPreconditionError(
"No active ROM session");
5535 const bool project_dirty = session->project_dirty ||
5536 session->editors.HasPendingProjectDraftChanges();
5537 const bool project_file_dirty =
5538 session->project_file_editor_state.initialized &&
5539 session->project_file_editor_state.modified;
5541 if (project_dirty && project_file_dirty &&
5543 return absl::FailedPreconditionError(
5544 "Project settings and raw project file both have unsaved changes; "
5545 "use raw Save As to preserve the draft, then save project settings");
5548 if (project_dirty) {
5551 return absl::FailedPreconditionError(
5552 "Project settings have no project file to save");
5557 if (project_file_dirty) {
5559 return absl::FailedPreconditionError(
5560 "Project file draft has no destination; use Save As first");
5566 return absl::OkStatus();
5571 return absl::FailedPreconditionError(
"No session coordinator");
5576 if (session !=
nullptr && session->backup_restore_pending) {
5577 return absl::CancelledError(
5578 "Staged backup restore requires an explicit Save ROM");
5584 return absl::FailedPreconditionError(
5585 "Autosave left unsaved project or ROM work");
5587 return absl::OkStatus();
5593 auto lifecycle_decision =
5595 static_cast<int>(decision));
5605 if (!absl::IsCancelled(status) && !status.ok()) {
5607 absl::StrFormat(
"Failed to save ROM: %s", status.message()),
5616 :
"untitled_project";
5620 if (file_path.empty()) {
5621 return absl::OkStatus();
5628 if (filepath.empty()) {
5629 return absl::InvalidArgumentError(
"Project file path cannot be empty");
5632 std::string file_path = filepath;
5635 if (!(absl::EndsWith(file_path,
".yaze") ||
5636 absl::EndsWith(file_path,
".yazeproj"))) {
5637 file_path +=
".yaze";
5642 session && session->project_file_editor_state.initialized &&
5643 session->project_file_editor_state.modified &&
5645 session->project_file_editor_state.filepath, file_path)) {
5646 return absl::FailedPreconditionError(
5647 "Raw project draft targets the selected file; preserve it with "
5648 "raw Save As before overwriting that destination");
5660 if (save_status.ok()) {
5666 session->project_dirty =
false;
5674 manager.AddFile(file_path);
5683 absl::StrFormat(
"Failed to save project: %s", save_status.message()),
5694 return absl::FailedPreconditionError(
"No project open");
5701 if (command.empty()) {
5702 return absl::NotFoundError(
"Project does not define a build command");
5709 return absl::FailedPreconditionError(
"No project open");
5716 if (target.empty()) {
5719 if (target.empty()) {
5720 return absl::NotFoundError(
"Project does not define a run target ROM");
5726 const std::string& summary,
const std::string& detail,
5728 bool can_cancel)
const {
5730 .can_cancel = can_cancel,
5734 .output_tail = output_tail,
5739 const std::string& summary,
const std::string& detail,
5750 if (!command_or.ok()) {
5751 return command_or.status();
5754 const std::string command = *command_or;
5755 const std::filesystem::path project_root =
5758 auto start_status = task.
Start(command, project_root.string());
5759 if (!start_status.ok()) {
5760 return start_status;
5762 auto wait_status = task.
Wait();
5764 if (!wait_status.ok()) {
5767 const std::string summary = LastNonEmptyLine(snapshot.output);
5768 return summary.empty() ? command : summary;
5772 const std::string running_detail =
5773 "Running the configured project build command";
5774 UpdateBuildWorkflowStatus(
5780 if (!result_or.ok()) {
5781 UpdateBuildWorkflowStatus(
5784 std::string(result_or.status().message()),
5787 absl::StrFormat(
"Build unavailable: %s", result_or.status().message()),
5789 return result_or.status();
5792 const std::string summary = *result_or;
5797 summary.empty() ?
"Project build completed"
5798 : absl::StrFormat(
"Project build completed: %s", summary),
5800 LOG_INFO(
"EditorManager",
"Project build completed: %s", summary.c_str());
5801 return absl::OkStatus();
5807 if (snapshot.running) {
5815 if (!command_or.ok()) {
5816 UpdateBuildWorkflowStatus(
5819 std::string(command_or.status().message()),
5822 absl::StrFormat(
"Build unavailable: %s", command_or.status().message()),
5827 const std::filesystem::path project_root =
5832 if (!start_status.ok()) {
5833 UpdateBuildWorkflowStatus(
5836 std::string(start_status.message()),
5839 absl::StrFormat(
"Build unavailable: %s", start_status.message()),
5846 UpdateBuildWorkflowStatus(
5849 "Running the configured project build command",
5863 if (!snapshot.running) {
5868 UpdateBuildWorkflowStatus(
5870 MakeBuildStatus(
"Cancelling build",
"Stopping the active project build",
5876 UpdateRunWorkflowStatus(
5879 "Preparing the project output for emulator reload",
5883 if (!run_target_or.ok()) {
5884 UpdateRunWorkflowStatus(
5887 std::string(run_target_or.status().message()),
5890 run_target_or.status().message()),
5892 return run_target_or.status();
5894 const std::string run_target = *run_target_or;
5895 if (!std::filesystem::exists(run_target)) {
5901 return absl::NotFoundError(
"Run target ROM not found");
5904#ifdef YAZE_WITH_GRPC
5906 auto load_status = emulator_backend->LoadRom(run_target);
5907 if (load_status.ok()) {
5908 AppendWorkflowHistoryEntry(
5920 absl::StrFormat(
"Reloaded project output in emulator backend: %s",
5923 return absl::OkStatus();
5930 if (!load_rom_status.ok()) {
5931 UpdateRunWorkflowStatus(
5933 MakeRunStatus(
"Run load failed", std::string(load_rom_status.message()),
5936 load_rom_status.message()),
5938 return load_rom_status;
5942 if (!reload_status.ok()) {
5943 UpdateRunWorkflowStatus(
5945 MakeRunStatus(
"Reload failed", std::string(reload_status.message()),
5948 reload_status.message()),
5950 return reload_status;
5956 AppendWorkflowHistoryEntry(
"Run",
5964 absl::StrFormat(
"Reloaded project output in emulator: %s", run_target),
5966 return absl::OkStatus();
5983 return absl::OkStatus();
5988 return absl::FailedPreconditionError(
"No project is currently open");
5996 return absl::OkStatus();
6001 return editor_set->GetOverworldData();
6008 return absl::InvalidArgumentError(
"Invalid ROM pointer");
6017 if (session && &session->rom == rom) {
6023 return absl::OkStatus();
6029 return absl::NotFoundError(
"ROM not found in existing sessions");
6053 return session !=
nullptr && session->backup_restore_pending;
6059 return absl::FailedPreconditionError(
"No ROM loaded");
6062 return absl::FailedPreconditionError(
6063 "Save or discard ROM edits before restoring a backup");
6066 const std::string original_filename = rom->
filename();
6070 const bool is_managed_backup =
6071 std::any_of(backups.begin(), backups.end(), [&](
const auto& backup) {
6072 return SessionCoordinator::PathsReferToSameBackingFile(backup.path,
6075 if (!is_managed_backup) {
6076 return absl::InvalidArgumentError(
6077 "Selected file is not a managed backup for the active ROM");
6087 if (!original_filename.empty()) {
6101 if (!restored_session) {
6102 return absl::InternalError(
6103 "ROM backup restored without an active session to stage it");
6105 restored_session->backup_restore_pending =
true;
6106 return absl::OkStatus();
6111 return absl::FailedPreconditionError(
"No session coordinator");
6114 if (!session || !session->rom.is_loaded()) {
6115 return absl::FailedPreconditionError(
"No ROM loaded");
6117 if (!session->backup_restore_pending) {
6118 return absl::FailedPreconditionError(
"No restored backup is staged");
6122 if (session->editors.HasPendingGraphicsChanges() ||
6123 session->editors.HasPendingScreenChanges() ||
6126 return absl::FailedPreconditionError(
6127 "Resolve pending graphics, screen, dungeon, or palette edits before "
6128 "discarding the restored backup");
6131 const std::string backing_path = session->rom.filename();
6132 if (backing_path.empty()) {
6133 return absl::FailedPreconditionError(
"ROM has no backing file to reload");
6140 *session->rom.resource_label();
6147 session->backup_restore_pending =
false;
6148 session->rom.ClearDirty();
6149 return absl::OkStatus();
6200 current_session_id})) {
6231 if (index == current_index ||
6240 current_session_id, target_session_id})) {
6261 size_t session_id)
const {
6263 return std::nullopt;
6272 return std::nullopt;
6282 return absl::StrFormat(
"%s\n\nQuitting now will discard them.",
6287 const std::string session_name =
6288 source_index.has_value()
6291 const std::string work = source_index.has_value()
6295 switch (action.type) {
6298 return absl::StrFormat(
6299 "Session '%s' has %s. Opening another ROM or project will not save "
6300 "them automatically.",
6301 session_name, work);
6303 return absl::StrFormat(
6304 "Session '%s' has %s. Opening another project will not save them "
6306 session_name, work);
6308 return absl::StrFormat(
6309 "Session '%s' has %s. Switching sessions will leave them behind in "
6311 session_name, work);
6313 return absl::StrFormat(
6314 "Session '%s' has %s. Closing it now will discard them.",
6315 session_name, work);
6331 return "Save Work & Open";
6333 return "Save Work & Open Project";
6335 return "Save Work & Switch";
6337 return "Save Work & Close";
6340 :
"Save Modified Work & Quit";
6354 return "Open Without Saving";
6356 return "Open Project Without Saving";
6358 return "Switch Without Saving";
6360 return "Close Without Saving";
6362 return "Quit Without Saving";
6381 auto save_modified_session = [
this](
size_t session_id) -> absl::Status {
6383 if (!session_index.has_value()) {
6384 return absl::FailedPreconditionError(
6385 "The ROM session is no longer available");
6388 return absl::OkStatus();
6402 return absl::FailedPreconditionError(
6403 absl::StrFormat(
"Save completed, but session still has %s",
6406 return absl::OkStatus();
6409 absl::Status save_status = absl::OkStatus();
6412 std::vector<size_t> session_ids;
6418 for (
const size_t session_id : session_ids) {
6419 save_status = save_modified_session(session_id);
6420 if (!save_status.ok()) {
6426 save_status = save_modified_session(action.source_session_id);
6429 if (!save_status.ok()) {
6430 const bool resumable_confirmation = absl::IsCancelled(save_status) &&
6433 const auto original_session_index =
6435 if (!resumable_confirmation && original_session_index.has_value()) {
6439 if (absl::IsCancelled(save_status)) {
6444 absl::StrFormat(
"Failed to save before continuing: %s",
6445 save_status.message()),
6455 original_session_id != action.target_session_id) {
6456 const auto original_session_index =
6458 if (original_session_index.has_value()) {
6490 const bool has_pending_work =
6493 : source_index.has_value() &&
6495 if (!has_pending_work) {
6508 switch (action.
type) {
6513 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
6531 absl::StrFormat(
"Failed to open project: %s", status.message()),
6538 const auto target_index =
6540 if (target_index.has_value()) {
6547 const auto target_index =
6549 if (target_index.has_value()) {
6569 return session !=
nullptr &&
6571 session->editors.HasPendingProjectDraftChanges() ||
6572 (session->project_file_editor_state.initialized &&
6573 session->project_file_editor_state.modified));
6584 return session !=
nullptr &&
6585 ((session->rom.is_loaded() && session->rom.dirty()) ||
6586 session->editors.HasPendingGraphicsChanges() ||
6587 session->editors.HasPendingScreenChanges() ||
6597 size_t session_index)
const {
6609 if (
auto* dungeon_editor =
6611 return dungeon_editor->HasPendingDungeonChanges();
6617 size_t session_index)
const {
6629 if (
auto* dungeon_editor =
6631 return dungeon_editor->PendingRoomCount();
6637 size_t session_index)
const {
6646 &session->game_data)
6665 size_t session_index)
const {
6668 return "unsaved work";
6673 const bool rom_dirty =
6674 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
6675 const bool pending_dungeon_changes =
6677 const bool pending_graphics_changes =
6678 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
6679 const bool pending_screen_changes =
6680 session !=
nullptr && session->editors.HasPendingScreenChanges();
6682 const size_t pending_palette_colors =
6684 const bool project_dirty = session !=
nullptr && session->project_dirty;
6685 const bool project_editor_draft =
6686 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
6687 const bool project_file_dirty =
6688 session !=
nullptr && session->project_file_editor_state.initialized &&
6689 session->project_file_editor_state.modified;
6691 std::vector<std::string> work;
6692 if (pending_rooms > 0) {
6693 work.push_back(absl::StrFormat(
"%d unapplied dungeon room%s", pending_rooms,
6694 pending_rooms == 1 ?
"" :
"s"));
6695 }
else if (pending_dungeon_changes) {
6696 work.emplace_back(
"unapplied dungeon metadata");
6698 if (pending_palette_colors > 0) {
6699 work.push_back(absl::StrFormat(
"%zu unapplied palette color%s",
6700 pending_palette_colors,
6701 pending_palette_colors == 1 ?
"" :
"s"));
6703 if (pending_graphics_changes) {
6704 work.emplace_back(
"unapplied graphics sheet edits");
6706 if (pending_screen_changes) {
6707 work.emplace_back(
"unapplied Screen Editor edits");
6710 work.emplace_back(
"unsaved ROM-buffer changes");
6712 if (project_dirty) {
6713 work.emplace_back(
"unsaved project settings");
6715 if (project_editor_draft) {
6716 work.emplace_back(
"an uncommitted project editor draft");
6718 if (project_file_dirty) {
6719 work.emplace_back(
"an unsaved project-file draft");
6721 return work.empty() ?
"unsaved work" : absl::StrJoin(work,
" and ");
6725 size_t session_index)
const {
6733 const bool rom_dirty =
6734 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
6735 const bool pending_dungeon_changes =
6737 const bool pending_graphics_changes =
6738 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
6739 const bool pending_screen_changes =
6740 session !=
nullptr && session->editors.HasPendingScreenChanges();
6742 const size_t pending_palette_colors =
6744 const bool project_dirty = session !=
nullptr && session->project_dirty;
6745 const bool project_editor_draft =
6746 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
6747 const bool project_file_dirty =
6748 session !=
nullptr && session->project_file_editor_state.initialized &&
6749 session->project_file_editor_state.modified;
6751 std::vector<std::string> tags;
6752 if (pending_rooms > 0) {
6753 tags.push_back(
"Rooms");
6754 }
else if (pending_dungeon_changes) {
6755 tags.push_back(
"Dungeon");
6757 if (pending_palette_colors > 0) {
6758 tags.push_back(
"Palette");
6760 if (pending_graphics_changes) {
6761 tags.push_back(
"Gfx");
6763 if (pending_screen_changes) {
6764 tags.push_back(
"Screen");
6767 tags.push_back(
"ROM");
6769 if (project_dirty || project_editor_draft || project_file_dirty) {
6770 tags.push_back(
"Project");
6772 return absl::StrJoin(tags,
"+");
6777 if (modified_sessions <= 0) {
6778 return "No sessions have unsaved work.";
6784 return absl::StrFormat(
"Session '%s' has %s.",
6791 return absl::StrFormat(
6792 "%d sessions have unsaved ROM, graphics, screen, dungeon, palette, or "
6806 int pending_editor =
6808 if (pending_editor < 0) {
6814 if (pending_layout < 0) {
6829 EditorType type,
size_t session_index)
const {
6830 const char* base_name =
kEditorNames[
static_cast<int>(type)];
6832 base_name, session_index)
6833 : std::string(base_name);
6842 "%s is experimental — enable Experimental Editors in Settings",
6849#ifdef YAZE_BUILD_AGENT_UI
6869 absl::StrFormat(
"Failed to prepare %s: %s",
6897 size_t session_index)
6898 : manager_(manager),
6899 prev_rom_(manager->GetCurrentRom()),
6900 prev_editor_set_(manager->GetCurrentEditorSet()),
6901 prev_session_index_(manager->GetCurrentSessionIndex()) {
6908 manager_->session_coordinator_->SwitchToSession(prev_session_index_);
6965 if (session && session->project_file_editor_state.initialized &&
6966 !session->project_file_editor_state.modified &&
6969 session->project_file_editor_state.filepath,
6974 if (session && session->project_file_editor_state.initialized) {
6981 absl::StrFormat(
"Failed to load project file: %s", status.message()),
6998 size_t session_id) {
7014 if (session_index.has_value()) {
7019 if (session->project_context.has_value()) {
7020 deps.
project = &*session->project_context;
7036 settings_panel->SetOpenMinecartTracksCallback(
7037 [
this, session_id]() -> absl::Status {
7039 return absl::FailedPreconditionError(
7040 "Minecart Tracks requires its project session to be active.");
7043 return absl::FailedPreconditionError(
7044 "Enable Custom Dungeon Objects before opening Minecart "
7051 if (dungeon ==
nullptr) {
7052 return absl::NotFoundError(
7053 "Dungeon editor is unavailable in the active project "
7061 return absl::NotFoundError(
7062 "Minecart Tracks could not be opened in the active Dungeon "
7065 return absl::OkStatus();
static Application & Instance()
void Publish(const T &event)
HandlerId Subscribe(std::function< void(const T &)> handler)
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
project::ResourceLabelManager * resource_label()
absl::StatusOr< uint8_t > ReadByte(int offset) const
void set_dirty(bool dirty)
const auto & vector() const
auto set_filename(std::string_view name)
static TimingManager & Get()
float Update()
Update the timing manager (call once per frame)
float GetElapsedTime() const
Get total elapsed time since first update.
const ProjectRegistry & project_registry() const
bool HasProjectRegistry() const
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
bool loaded() const
Check if the manifest has been loaded.
const BuildPipeline & build_pipeline() const
static RomSettings & Get()
void SetAddressOverrides(const RomAddressOverrides &overrides)
absl::Status InitializeGit()
void set_active(bool active)
AgentChat * GetAgentChat()
AgentConfigState & agent_config()
void SetRomContext(Rom *rom)
AgentUIContext * GetContext()
void SetProjectContext(project::YazeProject *project)
void ApplyUserSettingsDefaults(bool force=false)
void SetAssemblySymbolTableContext(const std::map< std::string, core::AsarSymbol > *table)
AgentEditor * GetAgentEditor()
void Initialize(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, RightDrawerManager *right_drawer_manager, WorkspaceWindowManager *window_manager, UserSettings *user_settings)
void SetAsarWrapperContext(core::AsarWrapper *asar_wrapper)
Snapshot GetSnapshot() const
absl::Status Start(const std::string &command, const std::string &directory)
DungeonEditorV2 - Simplified dungeon editor using component delegation.
static constexpr const char * kMinecartTrackEditorId
void SwitchToEditor(EditorType type, bool force_visible=false, bool from_dialog=false)
Switch to an editor, optionally forcing visibility.
void Initialize(const Dependencies &deps)
void InitializeEditorLayout(EditorType type)
Initialize the DockBuilder layout for an editor.
SessionScope(EditorManager *manager, size_t session_index)
The EditorManager controls the main editor window and manages the various editor classes.
absl::Status SaveProjectAs()
std::unique_ptr< SessionCoordinator > session_coordinator_
RomLifecycleManager rom_lifecycle_
StartupVisibility welcome_mode_override_
void ConfirmPendingUnsavedSessionActionSaveAndContinue()
void InitializeTestSuites()
std::optional< PendingRomSave > pending_rom_save_
void ApplyDefaultBackupPolicy()
std::vector< EditorType > CollectEditorsToPreload(EditorSet *editor_set) const
void SwitchToEditor(EditorType editor_type, bool force_visible=false, bool from_dialog=false) override
std::unique_ptr< GlobalEditorContext > editor_context_
void RestoreProjectEditingStateForSession(RomSession *session)
bool MaybeGuardPendingSessionAction(PendingUnsavedSessionAction action)
absl::Status SaveRomAs(const std::string &filename)
bool HasAnySessionPendingUnsavedWork() const
absl::Status OpenProjectInternal()
project::YazeProject current_project_
void HandleSessionSwitched(size_t new_index, RomSession *session, bool transient=false)
void QueueBuildCurrentProject()
std::unique_ptr< RightDrawerManager > right_drawer_manager_
void CancelPendingUnsavedSessionAction()
AgentUiController agent_ui_
void SetCurrentEditor(Editor *editor) override
bool SaveLayoutSnapshotAs(const std::string &name)
absl::Status PrepareRawProjectFileSave(const std::string &filepath, const std::string &contents)
absl::Status LoadProjectWithRom()
absl::Status LoadAssetsForMode(uint64_t loading_handle=0)
MenuBuilder menu_builder_
bool IsCurrentProjectDirty() const
float settings_dirty_timestamp_
std::string GetPreferredStartupCategory(const std::string &saved_category, const std::vector< std::string > &available_categories) const
absl::StatusOr< std::string > RunProjectBuildCommand()
absl::Status CheckRomWritePolicy(const std::optional< std::string > &target_filename=std::nullopt)
Save the current ROM file.
void InitializeServices()
void ApplyLayoutDefaultsMigrationIfNeeded()
void SwitchToSession(size_t index)
bool active_project_build_reported_
absl::Status RestoreRomBackup(const std::string &backup_path)
absl::Status EnsureGameDataLoaded()
size_t GetActiveSessionCount() const
bool SessionHasPendingRomWork(size_t session_index) const
bool pending_project_open_transition_
absl::Status OpenProject()
ProjectManager project_manager_
void CloseCurrentSession()
gfx::IRenderer * renderer_
Rom * GetCurrentRom() const override
void CancelPendingRomSave(bool hide_popups=false)
int PendingDungeonRoomCountForSession(size_t session_index) const
bool HasDuplicateSession(const std::string &filepath)
absl::Status RepairCurrentProject()
std::unique_ptr< LayoutManager > layout_manager_
std::unique_ptr< DashboardPanel > dashboard_panel_
void HandleSessionClosed(size_t index)
void ResetAssetState(RomSession *session)
void DrawSecondaryWindows()
void ProcessStartupActions(const AppConfig &config)
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const
core::VersionManager * version_manager_
std::vector< editor::RomFileManager::BackupEntry > GetRomBackups() const
int ModifiedSessionCount() const
std::string CompactPendingUnsavedWorkLabel(size_t session_index) const
std::vector< std::string > ListLayoutSnapshots() const
SharedClipboard shared_clipboard_
bool EditorInitRequiresGameData(EditorType type) const
void SyncEditorContextForCategory(const std::string &category)
void ShowProjectManagement()
Injects dependencies into all editors within an EditorSet.
absl::Status RunCurrentProject()
Editor * ResolveEditorForCategory(const std::string &category)
void DuplicateCurrentSession()
void Initialize(gfx::IRenderer *renderer, const std::string &filename="")
std::optional< size_t > runtime_feature_flags_session_id_
void MarkCurrentProjectDirty()
EditorRegistry editor_registry_
absl::Status FinalizeNewProject(const std::string &project_name, const std::string &project_path=std::string())
void SetupDialogCallbacks()
std::string DescribeAllPendingUnsavedWork() const
void MarkEditorLoaded(RomSession *session, EditorType type)
absl::Status StartPendingRomSave(const std::optional< std::string > &save_as_filename)
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 ApplyStartupVisibilityOverrides()
void ResetWorkspaceLayout()
absl::Status CreateNewProject(const std::string &template_name="Basic ROM Hack")
void ConfirmPendingUnsavedSessionActionDiscardAndContinue()
absl::Status InitializeEditorForType(EditorType type, EditorSet *editor_set, Rom *rom)
ToastManager toast_manager_
LayoutCoordinator layout_coordinator_
absl::Status LoadAssets(uint64_t loading_handle=0)
bool HasPendingUnsavedSessionAction() const
bool pending_layout_defaults_reset_
void RestoreActiveEditorLayoutAfterAssets(RomSession *session)
absl::Status OpenRomOrProjectInternal(const std::string &filename)
std::string startup_editor_hint_
auto GetCurrentEditorSet() const -> EditorSet *
uint64_t project_rom_selection_generation_
void CancelRomWriteConfirm()
bool IsRomBackupRestorePending() const
std::string DescribePendingUnsavedWork(size_t session_index) const
absl::Status PruneRomBackups()
void FinishPendingRomSaveAttempt(const absl::Status &status)
std::unique_ptr< MenuOrchestrator > menu_orchestrator_
void HandleUIActionRequest(UIActionRequestEvent::Action action)
void ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
absl::Status CommitRawProjectFileSave(const std::string &filepath, const std::string &contents)
absl::Status SwapProjectRom(const std::string &rom_path)
absl::Status SaveActiveProjectEditingWork()
auto GetCurrentEditor() const -> Editor *override
bool HasPendingRomSaveConfirmation() const
void HandleSessionRomLoaded(size_t index, Rom *rom)
ProjectFileEditor project_file_editor_
void HandleHostVisibilityChanged(bool visible)
void ApplyLayoutPreset(const std::string &preset_name)
void RestoreTemporaryLayoutSnapshot(bool clear_after_restore=false)
void ApplyStartupVisibility(const AppConfig &config)
void ExecutePendingUnsavedSessionAction(const PendingUnsavedSessionAction &action)
WorkspaceWindowManager window_manager_
size_t PendingPaletteColorCountForSession(size_t session_index) const
bool show_rom_load_options_
void RestoreEditorLayoutAfterAssets(RomSession *session, EditorType type)
absl::Status SaveProject()
absl::Status SaveRomInternal(const std::optional< std::string > &save_as_filename)
void SetStartupLoadHints(const AppConfig &config)
void RebaseCleanProjectFileDraft(const std::string &filepath)
std::optional< size_t > pending_project_open_previous_session_id_
void CaptureActiveProjectContext()
absl::Status LoadAssetsLazy(uint64_t loading_handle=0)
bool PendingRomSaveMatchesActiveSession() const
absl::StatusOr< std::string > ResolveProjectBuildCommand() const
void SetAssetLoadMode(AssetLoadMode mode)
void DismissEditorSelection() override
absl::Status DiscardPendingRomBackupRestore()
std::atomic< uint64_t > ui_sync_frame_id_
std::optional< size_t > active_project_context_session_id_
std::string GetPendingUnsavedSessionActionContinueLabel() const
std::string GetPendingUnsavedSessionActionPrompt() const
bool ApplyLayoutProfile(const std::string &profile_id)
absl::Status AutosaveActiveSession()
Editor * GetEditorByType(EditorType type, EditorSet *editor_set) const
void UpdateCurrentRomHash()
absl::Status BuildCurrentProject()
StartupVisibility sidebar_mode_override_
RomLoadOptionsDialog rom_load_options_dialog_
absl::Status LoadRom()
Load a ROM file into a new or existing session.
std::vector< std::string > startup_panel_hints_
void SetupComponentCallbacks()
std::vector< std::function< void()> > deferred_actions_
void RegisterEmulatorPanels()
void OpenEditorAndPanelsFromFlags(const std::string &editor_name, const std::string &panels_str)
StartupVisibility dashboard_mode_override_
void BindProjectContextToSession(RomSession *session, const project::YazeProject &project)
void InitializeSubsystems()
std::optional< size_t > ResolveSessionIndexById(size_t session_id) const
void ApplyCurrentProjectRuntimeContext()
void ShowProjectFileEditor()
void RefreshHackWorkflowBackend()
static bool IsPanelBasedEditor(EditorType type)
void ClearPendingWriteConflicts()
absl::Status Update()
Main update loop for the editor application.
void SetupSidebarCallbacks()
size_t GetCurrentSessionIndex() const
absl::StatusOr< std::string > ResolveProjectRunTarget() const
absl::Status ImportProject(const std::string &project_path)
absl::Status ReloadProjectRom()
size_t GetCurrentSessionId() const
Stable workspace identity; unlike the UI index, it does not compact.
bool ProjectFileDraftTargetsCurrentProject() const
void ClearTemporaryLayoutSnapshot()
absl::Status PrepareActiveProjectEditorDraftsForSave()
ProjectWorkflowStatus MakeRunStatus(const std::string &summary, const std::string &detail, ProjectWorkflowState state) const
void QueueDeferredAction(std::function< void()> action)
absl::Status ValidateProjectRomSelection(const std::string &rom_path)
SelectionPropertiesPanel selection_properties_panel_
WindowDelegate window_delegate_
bool DeleteLayoutSnapshot(const std::string &name)
void ConfigureSession(RomSession *session) override
std::unique_ptr< ActivityBar > activity_bar_
ShortcutManager shortcut_manager_
bool IsRomHashMismatch() const
bool RestoreLayoutSnapshot(const std::string &name, bool remove_after_restore=false)
void RefreshResourceLabelProvider()
bool HasPendingDungeonChangesForSession(size_t session_index) const
std::optional< PendingUnsavedSessionAction > pending_unsaved_session_action_
void MarkEditorInitialized(RomSession *session, EditorType type)
bool SessionHasPendingUnsavedWork(size_t session_index) const
void SyncLayoutScopeFromCurrentProject()
std::unique_ptr< BackgroundCommandTask > active_project_build_
bool IsCurrentProjectContextOwnedBySession(size_t session_id) const
UserSettings user_settings_
WorkspaceManager workspace_manager_
yaze::zelda3::Overworld * overworld() const
ProposalDrawer proposal_drawer_
void ConfigureEditorDependencies(EditorSet *editor_set, Rom *rom, size_t session_id)
absl::Status ResumePendingRomSave()
absl::StatusOr< const project::YazeProject * > PrepareActiveProjectContextForSave()
std::unique_ptr< ProjectManagementPanel > project_management_panel_
std::unique_ptr< workflow::HackWorkflowBackend > hack_workflow_backend_
absl::Status OpenRomOrProject(const std::string &filename)
PopupManager * popup_manager()
void RestoreProjectContextAfterFailedOpen(std::optional< size_t > previous_session_id)
void RemoveSession(size_t index)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
void DetachActiveProjectContext()
UiSyncState GetUiSyncStateSnapshot() const
void PollProjectWorkflowTasks()
absl::Status LoadRomInternal()
ProjectWorkflowStatus MakeBuildStatus(const std::string &summary, const std::string &detail, ProjectWorkflowState state, const std::string &output_tail="", bool can_cancel=false) const
void CancelQueuedProjectBuild()
AssetLoadMode asset_load_mode_
absl::Status DiscardProvisionalSessionCreatedSince(size_t previous_session_count)
absl::Status EnsureEditorAssetsLoaded(EditorType type)
std::unique_ptr< PopupManager > popup_manager_
bool RestoreProjectContextForSession(RomSession *session)
std::atomic< int > pending_editor_deferred_actions_
std::unique_ptr< UICoordinator > ui_coordinator_
EditorActivator editor_activator_
absl::Status SetCurrentRom(Rom *rom)
std::string GetPendingUnsavedSessionActionSaveLabel() const
void InitializeShortcutSystem()
std::optional< PendingProjectRomSelection > pending_project_rom_selection_
void CaptureActiveProjectEditingState()
std::unique_ptr< WindowHost > window_host_
void CaptureTemporaryLayoutSnapshot()
void HandleSessionCreated(size_t index, RomSession *session)
bool EditorRequiresGameData(EditorType type) const
ToastManager * toast_manager()
RomFileManager rom_file_manager_
void CaptureRuntimeFeatureFlags()
void ResetCurrentEditorLayout()
absl::Status ReplaceActiveSessionRom(Rom &&rom, const std::string &filepath)
static bool UpdateAllowedWithoutLoadedRom(EditorType type)
static EditorType GetEditorTypeFromCategory(const std::string &category)
static std::vector< std::string > GetAllEditorCategories()
Get all editor categories in display order for sidebar.
static bool IsPanelBasedEditor(EditorType type)
static bool IsExperimentalEditor(EditorType type)
static std::string GetEditorName(EditorType type)
static std::string GetEditorCategory(EditorType type)
Contains a complete set of editors for a single ROM instance.
size_t session_id() const
void ApplyDependencies(const EditorDependencies &dependencies)
Editor * GetEditor(EditorType type) const
void set_user_settings(UserSettings *settings)
GfxGroupWorkspaceState * gfx_group_workspace()
SettingsPanel * GetSettingsPanel() const
std::vector< Editor * > active_editors_
Interface for editor classes.
virtual absl::Status BeginSaveTransaction()
virtual void ContributeStatus(StatusBar *)
virtual absl::Status Redo()=0
virtual absl::Status Undo()=0
void ProcessDeferredActions()
Process all queued deferred actions.
void ResetWorkspaceLayout()
Reset the workspace layout to defaults.
void ProcessLayoutRebuild(EditorType current_editor_type, bool is_emulator_visible)
Process pending layout rebuild requests.
void InitializeEditorLayout(EditorType type)
Initialize layout for an editor type on first activation.
void ResetCurrentEditorLayout(EditorType editor_type, size_t session_id)
Reset current editor layout to its default configuration.
int PendingDeferredActionCount() const
Approximate pending deferred layout actions for sync diagnostics.
void ApplyLayoutPreset(const std::string &preset_name, size_t session_id)
Apply a named layout preset.
void Initialize(const Dependencies &deps)
Initialize with all dependencies.
static std::vector< std::string > GetDefaultWindows(EditorType type)
absl::Status LoadFile(const std::string &filepath)
Load a project file into the editor.
void SetProject(project::YazeProject *project)
Set the project pointer for label import operations.
ProjectFileEditorState CaptureState() const
void RestoreState(const ProjectFileEditorState &state, project::YazeProject *project)
void set_active(bool active)
Set whether the editor window is active.
absl::Status SaveFile()
Save the current editor contents to disk.
void ResetForProject(project::YazeProject *project)
void SetSaveCompleteCallback(SaveCompleteCallback callback)
void SetToastManager(ToastManager *toast_manager)
Set toast manager for notifications.
const std::string & filepath() const
Get the current filepath.
void SetSaveGuardCallback(SaveGuardCallback callback)
Panel for managing project settings, ROM versions, and snapshots.
void SetBuildStatus(const ProjectWorkflowStatus &status)
void SetRunStatus(const ProjectWorkflowStatus &status)
absl::Status FinalizeProjectCreation(const std::string &project_name, const std::string &project_path)
Complete project creation after ROM is loaded.
absl::Status ValidateProjectCreationTarget(const std::string &project_name, const std::string &project_path=std::string()) const
absl::Status SetProjectRom(const std::string &rom_path)
Set the ROM for the current project.
absl::Status CreateNewProject(const std::string &template_name="")
void CancelPendingProject()
Cancel pending project creation.
bool IsPendingRomSelection() const
Check if project is waiting for ROM selection.
absl::Status ImportProject(const std::string &project_path)
project::YazeProject & GetCurrentProject()
static float GetDefaultDrawerWidth(DrawerType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific drawer type.
std::vector< BackupEntry > ListBackups(const std::string &rom_filename) const
absl::Status LoadRom(Rom *rom, const std::string &filename)
absl::Status SaveRom(Rom *rom)
absl::Status SaveRomAs(Rom *rom, const std::string &filename)
Manages ROM and project persistence state.
void SetProjectContext(const project::YazeProject *project)
void UpdateCurrentRomHash(Rom *rom)
Recompute the hash of the current ROM.
bool ShouldBypassWriteConflict() const
std::vector< RomFileManager::BackupEntry > GetRomBackups(Rom *rom) const
void SetPotItemConfirmPending(int unloaded_rooms, int total_rooms)
Set pot-item confirmation pending (called by SaveRom when needed).
absl::Status CheckRomWritePolicy(Rom *rom, const std::optional< std::string > &target_filename=std::nullopt)
Enforce project write policy; may set pending_rom_write_confirm.
bool ShouldSuppressPotItemSave() const
bool HasPendingPotItemSaveConfirmation() const
void ResetPendingSaveState()
void CancelRomWriteConfirm()
absl::Status CheckRomOpenPolicy(Rom *rom)
Validate that the loaded ROM is a safe project target to open/edit.
void ClearPotItemBypass()
PotItemSaveDecision ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
int pending_pot_item_unloaded_rooms() const
void ConsumeWriteConflictBypass()
bool ShouldBypassPotItemConfirm() const
const std::vector< core::WriteConflict > & pending_write_conflicts() const
void SetPendingWriteConflicts(std::vector< core::WriteConflict > conflicts)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
Run Oracle-specific ROM safety preflight before saving.
void Initialize(Dependencies deps)
absl::Status PruneRomBackups(Rom *rom)
bool IsRomWriteConfirmPending() const
void ApplyDefaultBackupPolicy(bool enabled, const std::string &folder, int retention_count, bool keep_daily, int keep_daily_days)
Apply default backup policy from user settings.
void Open(Rom *rom, const std::string &rom_filename)
Open the dialog after ROM detection.
void SetConfirmCallback(std::function< void(const LoadOptions &)> callback)
Set callback for when options are confirmed.
void Draw(bool *p_open)
Draw the dialog (wrapper around Show)
The ScreenEditor class allows the user to edit a variety of screens in the game or create a custom me...
void SetAgentCallbacks(std::function< void(const std::string &)> send_callback, std::function< void()> focus_callback)
void ClearSelection()
Clear the current selection.
static bool PathsReferToSameBackingFile(const std::string &lhs, const std::string &rhs)
A session-aware status bar displayed at the bottom of the application.
void ClearProjectWorkflowStatus()
void SetBuildStatus(const ProjectWorkflowStatus &status)
void SetSessionInfo(size_t session_id, size_t total_sessions, const std::string &display_name={})
Set session information.
void SetRunStatus(const ProjectWorkflowStatus &status)
void SetRom(Rom *rom)
Set the current ROM for dirty status and filename display.
void SetActiveEditor(const std::string &name)
Set the active editor category/name for the context strip.
void SetDirtyScope(const std::string &short_label)
Compact dirty-scope chip (ROM / Project / Rooms / …).
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
void SetEnabled(bool enabled)
Enable or disable the status bar.
void Initialize(GlobalEditorContext *context)
void ClearEditorContributions()
Clear frame-scoped editor contributions.
void Draw()
Draw the status bar.
void SetAgentToggleCallback(std::function< void()> callback)
void SetAgentInfo(const std::string &provider, const std::string &model, bool active)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
bool ApplyPanelLayoutDefaultsRevision(int target_revision)
static constexpr int kLatestPanelLayoutDefaultsRevision
static constexpr int kLastWorkspaceResetPanelLayoutDefaultsRevision
void set_apply_preset_callback(std::function< void(const std::string &)> callback)
void set_apply_preset_callback(std::function< void(const std::string &)> callback)
void set_window_manager(WorkspaceWindowManager *manager)
void set_layout_manager(LayoutManager *manager)
void EnableFileBrowser(const std::string &category, const std::string &root_path="")
void SetEventBus(EventBus *event_bus)
size_t GetActiveSessionId() const
void SetSidebarVisible(bool visible, bool notify=true)
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void TriggerShowShortcuts()
void SetEditorResolver(std::function< Editor *(const std::string &)> resolver)
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
void SetPanelBrowserCategoryWidthChangedCallback(std::function< void(float)> cb)
const WindowDescriptor * GetWindowDescriptor(size_t session_id, const std::string &base_window_id) const
void ToggleSidebarVisibility()
const std::unordered_map< std::string, WindowDescriptor > & GetAllWindowDescriptors() const
Get all panel descriptors (for layout designer, panel browser, etc.)
void SetActiveCategory(const std::string &category, bool notify=true)
void SetSidebarStateChangedCallback(std::function< void(bool, bool)> cb)
void TriggerShowCommandPalette()
void RegisterRegistryWindowContent(std::unique_ptr< WindowContent > window)
Register a ContentRegistry-managed WindowContent instance.
std::string GetActiveCategory() const
void RegisterWindowContent(std::unique_ptr< WindowContent > window)
Register a WindowContent instance for central drawing.
static constexpr const char * kDashboardCategory
std::vector< std::string > GetVisibleWindowIds(size_t session_id) const
Get list of currently visible panel IDs for a session.
void RegisterWindow(size_t session_id, const WindowDescriptor &descriptor)
void SetStoredSidePanelWidth(float width, bool notify=false)
void SetCategoryChangedCallback(std::function< void(const std::string &)> cb)
void SetFileBrowserPath(const std::string &category, const std::string &path)
void SetOnWindowCategorySelectedCallback(std::function< void(const std::string &)> callback)
void SetOnWindowClickedCallback(std::function< void(const std::string &)> callback)
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void DrawVisibleWindows()
void TriggerShowSettings()
void SetSidePanelWidthChangedCallback(std::function< void(float)> cb)
void SetWindowBrowserCategoryWidth(float width, bool notify=true)
void RegisterRegistryWindowContentsForSession(size_t session_id)
Register descriptors for all registry window contents in a session.
void SetFileClickedCallback(std::function< void(const std::string &category, const std::string &path)> callback)
bool IsSidebarVisible() const
void SetSidebarExpanded(bool expanded, bool notify=true)
bool * GetWindowVisibilityFlag(size_t session_id, const std::string &base_window_id)
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
void MarkWindowRecentlyUsed(const std::string &window_id)
ScopedEditorSaveTransactions & operator=(const ScopedEditorSaveTransactions &)=delete
std::vector< Editor * > editors_
absl::Status Begin(Editor *editor)
~ScopedEditorSaveTransactions()
ScopedEditorSaveTransactions(const ScopedEditorSaveTransactions &)=delete
ScopedEditorSaveTransactions()=default
void set_window_manager(editor::WorkspaceWindowManager *manager)
bool is_audio_focus_mode() const
void set_renderer(gfx::IRenderer *renderer)
bool is_snes_initialized() const
auto running() const -> bool
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
auto mutable_gfx_sheets()
Get mutable reference to all graphics sheets.
Defines an abstract interface for all rendering operations.
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
void ActivateSession(zelda3::GameData *game_data)
Select the palette state for a ROM session without resnapshotting it.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
size_t GetModifiedColorCount() const
Get count of modified colors across all groups.
static PaletteManager & Get()
Get the singleton instance.
void ReleaseSession(const zelda3::GameData *game_data)
Forget all palette tracking for a closing or reloaded ROM session.
static MotionProfile ClampMotionProfile(int raw_profile)
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
void ClearWorkspaceTransitionState()
void ApplyTheme(const std::string &theme_name)
void SetOnThemeChangedCallback(ThemeChangedCallback callback)
static ThemeManager & Get()
void SetLanguage(const std::string &locale)
static LanguageManager & Get()
void SetOnLanguageChangedCallback(LanguageChangedCallback cb)
static RecentFilesManager & GetInstance()
void SetCurrentRom(Rom *rom)
void DrawTestDashboard(bool *show_dashboard=nullptr)
static TestManager & Get()
void UpdateResourceStats()
static void ShowOpenFileDialogAsync(const FileDialogOptions &options, std::function< void(const std::string &)> callback)
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...
void RemoveRuntimeContext(uint64_t context_id)
static const std::vector< std::string > & DefaultSubtypeFilenamesForObject(int object_id)
static CustomObjectManager & Get()
void ActivateRuntimeContext(uint64_t context_id, const State &state)
void ActivateStandaloneContext()
void RefreshFeatureFlagMappings()
static DrawRoutineRegistry & Get()
Represents the full Overworld data, light and dark world.
std::unordered_map< std::string, LabelMap > ProjectLabels
void SetHackManifest(const core::HackManifest *manifest)
Set the hack manifest reference for ASM-defined labels.
void SetProjectLabels(ProjectLabels *labels)
Set the project labels reference (typically from YazeProject)
int main(int argc, char **argv)
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_AUDIOTRACK
#define ICON_MD_DASHBOARD
#define ICON_MD_SPORTS_ESPORTS
#define ICON_MD_AUDIO_FILE
#define ICON_MD_SMART_TOY
#define ICON_MD_MENU_OPEN
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define PRINT_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr char kOverworldExpandedPtrHigh[]
constexpr char kOverworldEntrancePosExpanded[]
constexpr char kExpandedMusicHook[]
constexpr char kExpandedMusicMain[]
constexpr char kOverworldExpandedPtrMagic[]
constexpr char kExpandedMessageEnd[]
constexpr char kOverworldExpandedPtrMarker[]
constexpr char kOverworldEntranceMapExpanded[]
constexpr char kOverworldEntranceFlagExpanded[]
constexpr char kExpandedMessageStart[]
constexpr char kOverworldEntranceIdExpanded[]
constexpr char kOverworldExpandedPtrLow[]
constexpr char kExpandedMusicAux[]
void ClearWorkflowHistory()
void SetGlobalContext(GlobalEditorContext *ctx)
void SetShowWorkflowOutputCallback(std::function< void()> callback)
void SetEditorWindowContext(const std::string &category, Editor *editor)
void SetEventBus(::yaze::EventBus *bus)
Set the current EventBus instance.
void SetRom(Rom *rom)
Set the current ROM instance.
void SetGameData(::yaze::zelda3::GameData *data)
Set the current game data instance.
void AppendWorkflowHistory(const ProjectWorkflowHistoryEntry &entry)
void SetRunProjectWorkflowCallback(std::function< void()> callback)
void SetBuildWorkflowStatus(const ProjectWorkflowStatus &status)
void SetCancelBuildWorkflowCallback(std::function< void()> callback)
void SetLayoutManager(LayoutManager *manager)
void SetBuildWorkflowLog(const std::string &output)
void SetStartBuildWorkflowCallback(std::function< void()> callback)
void SetRunWorkflowStatus(const ProjectWorkflowStatus &status)
void SetHackWorkflowBackend(workflow::HackWorkflowBackend *backend)
void SetUserSettings(UserSettings *settings)
void Clear()
Clear all context state.
void SetCurrentProject(::yaze::project::YazeProject *project)
Set the current project instance.
std::vector< std::unique_ptr< WindowContent > > CreateAll()
Create new instances of all registered panels.
bool IsTransientPanelVisibilityId(absl::string_view panel_id)
void UpdateBuildWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
void UpdateRunWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
constexpr int kTrackCustomObjectId
std::string LastNonEmptyLine(const std::string &text)
zelda3::CustomObjectManager::State BuildCustomObjectRuntimeState(const project::YazeProject &project)
bool HasAnyOverride(const core::RomAddressOverrides &overrides, std::initializer_list< const char * > keys)
bool ProjectUsesCustomObjects(const project::YazeProject &project)
void AppendWorkflowHistoryEntry(const std::string &kind, const ProjectWorkflowStatus &status, const std::string &output_log)
std::string StripSessionPrefix(absl::string_view panel_id)
bool SeedLegacyTrackObjectMapping(project::YazeProject *project, std::string *warning)
std::vector< std::string > ValidateRomAddressOverrides(const core::RomAddressOverrides &overrides, const Rom &rom)
void SeedOracleProjectInRecents()
std::unique_ptr< HackWorkflowBackend > CreateHackWorkflowBackendForProject(const project::YazeProject *project)
Editors are the view controllers for the application.
std::string PreferStartupCategory(const std::string &saved_category, const std::vector< std::string > &available_categories)
const char * GetDrawerTypeName(RightDrawerManager::DrawerType type)
constexpr std::array< const char *, 14 > kEditorNames
std::optional< EditorType > ParseEditorTypeFromString(absl::string_view name)
void ConfigureMenuShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void RegisterDefaultEditorFactories(EditorRegistry *registry)
bool ShouldTickEmulatorWhenHidden(bool keep_running_in_background)
void ConfigurePanelShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
Register configurable panel shortcuts from user settings.
size_t EditorTypeIndex(EditorType type)
void ConfigureEditorShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
DiffSummary ComputeDiffRanges(const std::vector< uint8_t > &before, const std::vector< uint8_t > &after)
void RegisterZ3edTestSuites()
FileDialogOptions MakeRomFileDialogOptions(bool include_all_files)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
constexpr int kExpandedPtrTableMarker
constexpr int kNumOverworldMaps
void SetPreferHmagicSpriteNames(bool prefer)
constexpr uint8_t kExpandedPtrTableMagic
constexpr int kOverworldEntranceExpandedFlagPos
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
AssetLoadMode
Asset loading mode for editor resources.
void SetActiveFontIndex(int index)
#define RETURN_IF_ERROR(expr)
Configuration options for the application startup.
std::string startup_editor
StartupVisibility welcome_mode
std::vector< std::string > open_panels
StartupVisibility sidebar_mode
StartupVisibility dashboard_mode
bool kEnableCustomObjects
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > all_resource_labels
std::unordered_map< std::string, uint32_t > addresses
std::optional< uint32_t > GetAddress(const std::string &key) const
LayoutManager * layout_manager
std::function< EditorSet *()> get_current_editor_set
std::function< void(std::function< void()>)> queue_deferred_action
ToastManager * toast_manager
RightDrawerManager * right_drawer_manager
UICoordinator * ui_coordinator
std::function< size_t()> get_current_session_id
std::function< absl::Status(EditorType)> ensure_editor_assets_loaded
WorkspaceWindowManager * window_manager
Unified dependency container for all editor types.
project::YazeProject * project
GlobalEditorContext * global_context
ToastManager * toast_manager
SharedClipboard * shared_clipboard
gfx::IRenderer * renderer
ShortcutManager * shortcut_manager
UserSettings * user_settings
core::VersionManager * version_manager
zelda3::GameData * game_data
WorkspaceWindowManager * window_manager
PopupManager * popup_manager
GfxGroupWorkspaceState * gfx_group_workspace
std::string previous_path
bool layout_rebuild_pending
int pending_layout_actions
int pending_editor_actions
Published after ImGui::NewFrame and dockspace creation.
static JumpToMapRequestEvent Create(int map, size_t session=0)
static JumpToRoomRequestEvent Create(int room, size_t session=0)
All dependencies required by LayoutCoordinator.
ToastManager * toast_manager
UICoordinator * ui_coordinator
LayoutManager * layout_manager
WorkspaceWindowManager * window_manager
RightDrawerManager * right_drawer_manager
Built-in workflow-oriented layout profiles.
Published when panel visibility changes.
bool save_overworld_items
std::string selected_preset
bool save_overworld_exits
bool enable_custom_overworld
bool save_overworld_entrances
Published when a ROM is successfully loaded into a session.
Represents a single session, containing a ROM and its associated editors.
core::FeatureFlags::Flags feature_flags
ProjectFileEditorState project_file_editor_state
size_t session_id() const
std::array< bool, kEditorTypeCount > editor_initialized
zelda3::GameData game_data
std::unique_ptr< core::VersionManager > version_manager
std::array< bool, kEditorTypeCount > editor_assets_loaded
std::optional< project::YazeProject > project_context
Published when a session is closed.
Published when a new session is created.
Published when the active session changes.
UserSettings * user_settings
SessionCoordinator * session_coordinator
WorkspaceManager * workspace_manager
EditorRegistry * editor_registry
MenuOrchestrator * menu_orchestrator
ToastManager * toast_manager
RomFileManager * rom_file_manager
EditorManager * editor_manager
ProjectManager * project_manager
UICoordinator * ui_coordinator
PopupManager * popup_manager
WorkspaceWindowManager * window_manager
Optional behavior for an interactive status bar segment.
std::function< void()> on_click
Activity bar or menu action request.
bool show_experimental_editors
int panel_layout_defaults_revision
float sidebar_panel_width
bool sidebar_panel_expanded
std::unordered_map< std::string, float > right_panel_widths
bool show_welcome_on_startup
std::string last_theme_name
std::string sidebar_active_category
bool emulator_keep_running_in_background
std::unordered_map< std::string, std::unordered_map< std::string, bool > > panel_visibility_state
std::unordered_map< std::string, bool > pinned_panels
bool prefer_hmagic_sprite_names
float panel_browser_category_width
Declarative registration contract for editor windows.
std::string shortcut_hint
Metadata for a dockable editor window (formerly PanelInfo)
std::function< void()> on_show
std::string shortcut_hint
std::function< void()> on_hide
WindowContextScope context_scope
WindowLifecycle window_lifecycle
void DisplayLabels(bool *p_open)
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
int backup_keep_daily_days
int backup_retention_count
float autosave_interval_secs
std::vector< std::string > recent_files
Modern project structure with comprehensive settings consolidation.
std::string rom_backup_folder
std::unordered_map< int, std::vector< std::string > > custom_object_files
std::string custom_objects_folder
absl::Status RepairProject()
std::string MakeStorageKey(absl::string_view suffix) const
bool project_opened() const
absl::Status LoadFromString(const std::string &content, const std::string &project_path)
std::string git_repository
core::HackManifest hack_manifest
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
std::string assets_folder
std::string labels_filename
std::string GetDisplayName() const
WorkspaceSettings workspace_settings
std::string GetAbsolutePath(const std::string &relative_path) const
absl::Status Open(const std::string &project_path)
absl::Status Validate() const
core::FeatureFlags::Flags feature_flags
core::RomAddressOverrides rom_address_overrides