20#include <initializer_list>
25#include <unordered_map>
26#include <unordered_set>
31#include <TargetConditionals.h>
35#define IMGUI_DEFINE_MATH_OPERATORS
36#include "absl/status/status.h"
37#include "absl/status/statusor.h"
38#include "absl/strings/ascii.h"
39#include "absl/strings/match.h"
40#include "absl/strings/str_format.h"
41#include "absl/strings/str_join.h"
42#include "absl/strings/str_split.h"
43#include "absl/strings/string_view.h"
44#include "imgui/imgui.h"
109#include "yaze_config.h"
132#ifdef YAZE_ENABLE_TESTING
139#ifdef YAZE_ENABLE_GTEST
147#ifdef YAZE_BUILD_AGENT_UI
155 std::vector<std::string> lines = absl::StrSplit(text,
'\n');
156 for (
auto it = lines.rbegin(); it != lines.rend(); ++it) {
157 std::string line = std::string(*it);
158 absl::StripAsciiWhitespace(&line);
170 if (status_bar !=
nullptr) {
173 if (project_panel !=
nullptr) {
182 if (status_bar !=
nullptr) {
185 if (project_panel !=
nullptr) {
192 const std::string& output_log) {
196 .output_log = output_log,
197 .timestamp = std::chrono::system_clock::now()});
201 std::unordered_map<std::string, float>* preferences,
202 const std::unordered_map<std::string, float>& drawer_widths) {
203 if (preferences ==
nullptr) {
206 constexpr std::array<const char*, 9> kDrawerWidthKeys = {
207 "right_sidebar.shared",
"agent_chat",
"proposals",
"settings",
"help",
208 "notifications",
"properties",
"project",
"tool_output",
210 for (
const char* key : kDrawerWidthKeys) {
211 preferences->erase(key);
213 for (
const auto& [key, width] : drawer_widths) {
214 preferences->insert_or_assign(key, width);
219 if (panel_id.size() > 2 && panel_id[0] ==
's' &&
220 absl::ascii_isdigit(panel_id[1])) {
221 const size_t dot = panel_id.find(
'.');
222 if (dot != absl::string_view::npos) {
223 return std::string(panel_id.substr(dot + 1));
226 return std::string(panel_id);
230 namespace fs = std::filesystem;
232 std::vector<fs::path> roots;
233 if (
const char* home = std::getenv(
"HOME");
234 home !=
nullptr && std::strlen(home) > 0) {
235 roots.push_back(fs::path(home) /
"src" /
"hobby" /
"oracle-of-secrets");
238 std::vector<fs::path> candidates;
239 std::unordered_set<std::string> seen;
240 auto add_candidate = [&](
const fs::path& path) {
242 if (!fs::exists(path, ec) || ec) {
245 std::string ext = path.extension().string();
246 absl::AsciiStrToLower(&ext);
247 if (ext !=
".yaze" && ext !=
".yazeproj") {
250 const std::string normalized = fs::weakly_canonical(path, ec).string();
251 const std::string key = normalized.empty() ? path.string() : normalized;
252 if (key.empty() || seen.count(key) > 0) {
256 candidates.push_back(path);
259 for (
const auto& root : roots) {
261 if (!fs::exists(root, ec) || ec) {
266 add_candidate(root /
"Oracle-of-Secrets.yaze");
267 add_candidate(root /
"Oracle of Secrets.yaze");
268 add_candidate(root /
"Oracle-of-Secrets.yazeproj");
269 add_candidate(root /
"Oracle of Secrets.yazeproj");
270 add_candidate(root /
"Roms" /
"Oracle of Secrets.yaze");
273 fs::recursive_directory_iterator it(
274 root, fs::directory_options::skip_permission_denied, ec);
275 fs::recursive_directory_iterator end;
279 for (; it != end; it.increment(ec)) {
284 if (it.depth() > 2) {
285 it.disable_recursion_pending();
288 add_candidate(it->path());
292 if (candidates.empty()) {
298 for (
auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
299 manager.AddFile(it->string());
307 std::string normalized = std::string(name);
308 absl::StripAsciiWhitespace(&normalized);
309 const std::string lower = absl::AsciiStrToLower(normalized);
312 const std::string name_candidate =
314 const std::string category_candidate =
316 if (name_candidate == lower || category_candidate == lower) {
350 current_type, &applied_profile)) {
361 std::max(default_width, 480.0f));
365 absl::StrFormat(
"Layout Profile: %s", applied_profile.
label),
384 clear_after_restore)) {
421 bool remove_after_restore) {
491#ifdef YAZE_BUILD_AGENT_UI
492void EditorManager::ShowAIAgent() {
497 "AI Agent is experimental. It remains available; enable experimental "
498 "features in Settings to acknowledge this warning.",
507 for (
const auto& window_id :
513void EditorManager::ShowChatHistory() {
519 : project_manager_(&toast_manager_), rom_file_manager_(&toast_manager_) {
520 std::stringstream ss;
521 ss << YAZE_VERSION_MAJOR <<
"." << YAZE_VERSION_MINOR <<
"."
522 << YAZE_VERSION_PATCH;
598 [
this](
const std::string& preset_name) {
602 [
this](
const std::string& preset_name) {
620 return editor_set ? editor_set->GetSettingsPanel() :
nullptr;
623 [
this](
const std::string& prompt) {
624#if defined(YAZE_BUILD_AGENT_UI)
634 chat->SendMessage(prompt);
640#if defined(YAZE_BUILD_AGENT_UI)
660 [
this](
const std::string& filepath,
const std::string& contents) {
664 [
this](
const std::string& filepath,
const std::string& contents) {
668 std::make_unique<workflow::ProjectWorkflowOutputPanel>());
673 if (!rom_path.empty()) {
691 absl::StrFormat(
"Failed to reload ROM: %s", status.message()),
702 absl::StrFormat(
"Failed to save project: %s", status.message()),
708 [
this](
const std::string& type) {
710 if (!folder_path.empty()) {
711 if (type ==
"code") {
716#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
717 editor_set->OpenAssemblyFolder(folder_path);
721 }
else if (type ==
"assets") {
729 folder_path.c_str()),
791 return dungeon_editor->IsWorkbenchWorkflowEnabled();
796 [
this](
bool enabled) {
800 dungeon_editor->QueueWorkbenchWorkflowMode(enabled);
804 [
this](
const std::string& category) {
814 registry.Register({
"command_palette",
819 registry.Register({
"keyboard_shortcuts",
820 "Keyboard Shortcuts",
824 registry.Register({
"open_rom_project",
825 "Open ROM / Project",
829 registry.Register({
"settings",
846 for (
auto& panel : registry_panels) {
855 dashboard_definition.
id =
"dashboard.main";
858 dashboard_definition.
category =
"Dashboard";
868 {.card_id =
"dashboard.main",
869 .display_name =
"Dashboard",
870 .window_title =
" Dashboard",
872 .category =
"Dashboard",
874 .shortcut_hint =
"Ctrl+E",
910 std::vector<std::function<void()>> actions_to_execute;
912 const int processed_count =
static_cast<int>(actions_to_execute.size());
913 for (
auto& action : actions_to_execute) {
916 if (processed_count > 0) {
918 processed_count, std::memory_order_relaxed) -
936 if (e.category.empty() ||
994 for (
const auto& [type, labels] : src) {
995 auto& out = (*dst)[type];
996 for (
const auto& [key, value] : labels) {
1002 merged_labels.clear();
1003 std::vector<std::string> source_parts;
1009 source_parts.push_back(
"rom");
1019 source_parts.push_back(
"registry");
1026 source_parts.push_back(
"project");
1029 auto* label_source = merged_labels.empty() ? &empty_labels : &merged_labels;
1030 std::string source =
1031 source_parts.empty() ?
"empty" : absl::StrJoin(source_parts,
"+");
1033 provider.SetProjectLabels(label_source);
1039 const bool prefer_hmagic =
1043 provider.SetPreferHMagicNames(prefer_hmagic);
1046 "ResourceLabelProvider refreshed (source=%s, project_open=%s)",
1058 if (!index.has_value()) {
1071 if (session->project_context.has_value()) {
1072 session->project_context->feature_flags = session->feature_flags;
1089 if (!index.has_value()) {
1130 if (!index.has_value()) {
1147 return absl::OkStatus();
1150 if (session ==
nullptr || !session->editors.HasPendingProjectDraftChanges()) {
1151 return absl::OkStatus();
1154 return absl::FailedPreconditionError(
1155 "Project editor drafts require their project context to be active");
1157 return session->editors.PrepareProjectSave();
1171 std::optional<size_t> previous_session_id) {
1176 if (index.has_value()) {
1177 auto* previous_session =
1179 if (previous_session) {
1199 size_t previous_session_count) {
1202 return absl::OkStatus();
1205 if (!provisional_session) {
1206 return absl::InternalError(
1207 "A provisional session was created but is not active");
1210 provisional_session->session_id());
1223 const std::string& filepath,
const std::string& contents) {
1228 if (!targets_current) {
1231 return absl::OkStatus();
1234 return absl::FailedPreconditionError(
1235 "Project settings also have unsaved changes; use raw Save As to "
1236 "preserve this draft, then save the project settings");
1242 if (!session || !session->rom.is_loaded()) {
1243 return absl::FailedPreconditionError(
1244 "No active ROM session for this project file");
1250 return absl::InvalidArgumentError(
1251 "Raw project file must contain a non-empty project name");
1257 parsed_project.
rom_filename, session->rom.filename()))) {
1258 return absl::FailedPreconditionError(
1259 "Raw project file cannot change the ROM backing file of a loaded "
1260 "session; use Project Management > Swap ROM");
1267 const std::string& filepath,
const std::string& contents) {
1271 return absl::OkStatus();
1278 return absl::FailedPreconditionError(
"No active ROM session");
1285 session->project_dirty =
false;
1293 return absl::OkStatus();
1301 if (!session || !session->project_file_editor_state.initialized ||
1302 session->project_file_editor_state.modified) {
1309 LOG_WARN(
"EditorManager",
"Failed to rebase raw project editor: %s",
1318absl::StatusOr<const project::YazeProject*>
1321 return absl::FailedPreconditionError(
"No session coordinator");
1324 if (!session || !session->rom.is_loaded()) {
1325 return absl::FailedPreconditionError(
"No active ROM session");
1331 return absl::FailedPreconditionError(
1332 "Active ROM session project context is not restored");
1335 if (!session->project_context.has_value()) {
1336 return absl::FailedPreconditionError(
1337 "Active ROM session has no project/save context");
1339 return &*session->project_context;
1345 case Action::kShowEmulator:
1351 case Action::kShowSettings:
1361 case Action::kShowPanelBrowser:
1367 case Action::kShowSearch:
1373 case Action::kShowShortcuts:
1379 case Action::kShowCommandPalette:
1385 case Action::kShowHelp:
1396 case Action::kShowAgentChatSidebar:
1405 case Action::kShowAgentProposalsSidebar:
1412 case Action::kOpenRom: {
1416 std::string(
"Open failed: ") + std::string(status.message()),
1421 case Action::kSaveRom:
1424 if (!absl::IsCancelled(status) && !status.ok()) {
1426 std::string(
"Save failed: ") + std::string(status.message()),
1434 auto status = current_editor->
Undo();
1437 std::string(
"Undo failed: ") + std::string(status.message()),
1445 auto status = current_editor->
Redo();
1448 std::string(
"Redo failed: ") + std::string(status.message()),
1454 case Action::kResetLayout:
1463#ifdef YAZE_ENABLE_TESTING
1465 test_manager.RegisterTestSuite(
1466 std::make_unique<test::CoreSystemsTestSuite>());
1467 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
1468 test_manager.RegisterTestSuite(
1469 std::make_unique<test::PerformanceTestSuite>());
1470 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
1471 test_manager.RegisterTestSuite(
1472 std::make_unique<test::RomDependentTestSuite>());
1475 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
1476 test_manager.RegisterTestSuite(
1477 std::make_unique<test::ZSCustomOverworldTestSuite>());
1481#ifdef YAZE_ENABLE_GTEST
1482 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
1486#ifdef YAZE_WITH_GRPC
1491 test_manager.UpdateResourceStats();
1495 const std::string& filename) {
1498 SeedOracleProjectInRecents();
1528 const bool prefer_dashboard_only =
1531 if (!prefer_dashboard_only) {
1534 if (!category.empty()) {
1552#ifdef YAZE_ENABLE_TESTING
1563 if (!filename.empty()) {
1582 const std::vector<WindowDefinition> panel_definitions = {
1583 {.id =
"emulator.cpu_debugger",
1584 .display_name =
"CPU Debugger",
1586 .category =
"Emulator",
1589 {.id =
"emulator.ppu_viewer",
1590 .display_name =
"PPU Viewer",
1592 .category =
"Emulator",
1595 {.id =
"emulator.memory_viewer",
1596 .display_name =
"Memory Viewer",
1598 .category =
"Emulator",
1601 {.id =
"emulator.breakpoints",
1602 .display_name =
"Breakpoints",
1604 .category =
"Emulator",
1607 {.id =
"emulator.performance",
1608 .display_name =
"Performance",
1610 .category =
"Emulator",
1613 {.id =
"emulator.ai_agent",
1614 .display_name =
"AI Agent",
1616 .category =
"Emulator",
1619 {.id =
"emulator.save_states",
1620 .display_name =
"Save States",
1622 .category =
"Emulator",
1625 "Command palette (placeholder: Coming Soon)")},
1626 {.id =
"emulator.keyboard_config",
1627 .display_name =
"Keyboard Config",
1629 .category =
"Emulator",
1632 {.id =
"emulator.virtual_controller",
1633 .display_name =
"Virtual Controller",
1635 .category =
"Emulator",
1638 {.id =
"emulator.apu_debugger",
1639 .display_name =
"APU Debugger",
1641 .category =
"Emulator",
1644 {.id =
"emulator.audio_mixer",
1645 .display_name =
"Audio Mixer",
1647 .category =
"Emulator",
1650 {.id =
"memory.hex_editor",
1651 .display_name =
"Hex Editor",
1653 .category =
"Memory",
1657 .legacy_ids = {
"Memory Editor"}},
1665 for (
const auto& definition : panel_definitions) {
1667 descriptor.
card_id = definition.id;
1670 descriptor.
icon = definition.icon;
1671 descriptor.
category = definition.category;
1673 descriptor.
priority = definition.priority;
1674 descriptor.
scope = definition.scope;
1679 descriptor.
on_show = definition.on_show;
1680 descriptor.
on_hide = definition.on_hide;
1682 for (
const auto& legacy_id : definition.legacy_ids) {
1687 if (definition.visible_by_default) {
1708 LOG_WARN(
"EditorManager",
"Failed to load user settings: %s",
1727 [
this](
const std::string& theme_name) {
1733 [
this](
const std::string& locale) {
1747 prefs.switch_motion_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1749 prefs.reduced_motion,
1760 MergeRightDrawerWidthPreferences(
1772 MergeRightDrawerWidthPreferences(
1787 if (ImGui::GetCurrentContext() !=
nullptr) {
1791 "ImGui context not available; skipping FontGlobalScale update");
1798 if (!last_theme.empty() && last_theme !=
"Custom Accent") {
1803#ifdef __EMSCRIPTEN__
1806 LOG_INFO(
"EditorManager",
"WASM Control and Session APIs initialized");
1838 std::optional<EditorType> target;
1844 if (!target.has_value()) {
1863 if (!target.has_value()) {
1905 const std::string& editor_name,
const std::string& panels_str) {
1906 const bool has_editor = !editor_name.empty();
1907 const bool has_panels = !panels_str.empty();
1909 if (!has_editor && !has_panels) {
1914 "Processing startup flags: editor='%s', panels='%s'",
1915 editor_name.c_str(), panels_str.c_str());
1917 std::optional<EditorType> editor_type_to_open =
1919 if (has_editor && !editor_type_to_open.has_value()) {
1920 LOG_WARN(
"EditorManager",
"Unknown editor specified via flag: %s",
1921 editor_name.c_str());
1922 }
else if (editor_type_to_open.has_value()) {
1934 bool applied_category_from_panel =
false;
1936 for (absl::string_view token :
1937 absl::StrSplit(panels_str,
',', absl::SkipWhitespace())) {
1938 if (token.empty()) {
1941 std::string panel_name = std::string(absl::StripAsciiWhitespace(token));
1942 LOG_DEBUG(
"EditorManager",
"Attempting to open panel: '%s'",
1943 panel_name.c_str());
1945 const std::string lower_name = absl::AsciiStrToLower(panel_name);
1946 if (lower_name ==
"welcome" || lower_name ==
"welcome_screen") {
1952 if (lower_name ==
"dashboard" || lower_name ==
"dashboard.main" ||
1953 lower_name ==
"editor_selection") {
1967 if (absl::StartsWith(panel_name,
"Room ")) {
1970 int room_id = std::stoi(panel_name.substr(5));
1973 }
catch (
const std::exception& e) {
1974 LOG_WARN(
"EditorManager",
"Invalid room ID format: %s",
1975 panel_name.c_str());
1981 std::optional<std::string> resolved_panel;
1983 resolved_panel = panel_name;
1985 for (
const auto& [prefixed_id, descriptor] :
1987 const std::string base_id = StripSessionPrefix(prefixed_id);
1988 const std::string card_lower = absl::AsciiStrToLower(base_id);
1989 const std::string display_lower =
1990 absl::AsciiStrToLower(descriptor.display_name);
1992 if (card_lower == lower_name || display_lower == lower_name) {
1993 resolved_panel = base_id;
1999 if (!resolved_panel.has_value()) {
2001 "Unknown panel '%s' from --open_panels (known count: %zu)",
2008 const auto* descriptor =
2010 if (descriptor !=
nullptr) {
2014 if (!ensure_status.ok()) {
2015 LOG_WARN(
"EditorManager",
"Failed to load assets for panel '%s': %s",
2016 resolved_panel->c_str(), ensure_status.message().data());
2019 if (descriptor && !applied_category_from_panel &&
2022 applied_category_from_panel =
true;
2023 }
else if (!applied_category_from_panel && descriptor &&
2024 descriptor->category.empty() && !last_known_category.empty()) {
2028 LOG_WARN(
"EditorManager",
"Failed to show panel '%s'",
2029 resolved_panel->c_str());
2050 constexpr int kTargetRevision =
2052 const int previous_revision =
2058 const bool includes_workspace_reset_revision =
2069 "Applied panel layout defaults migration %d -> %d (%s)",
2070 previous_revision, kTargetRevision,
2071 includes_workspace_reset_revision ?
"workspace reset"
2072 :
"targeted preferences");
2076 const std::string& saved_category,
2077 const std::vector<std::string>& available_categories)
const {
2078 const std::string preferred =
2080 if (preferred.empty()) {
2098 const bool sidebar_visible =
2125 std::string panels_str;
2126 for (
size_t i = 0; i < config.
open_panels.size(); ++i) {
2144 "Ignoring invalid startup overworld map target %d",
2180 if (index < session->editor_initialized.size()) {
2190 if (index < session->editor_assets_loaded.size()) {
2212 const auto visibility_it =
2216 visibility_it->second);
2254 std::unordered_set<EditorType> types;
2269 auto add_category = [&](
const std::string& category) {
2270 if (category.empty() ||
2278 bool used_startup_hints =
false;
2282 add_type(*startup_type);
2283 used_startup_hints =
true;
2288 if (panel_name.empty()) {
2292 if (
const auto* descriptor =
2294 add_category(descriptor->category);
2295 used_startup_hints =
true;
2299 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2300 for (
const auto& [prefixed_id, descriptor] :
2302 const std::string base_id = StripSessionPrefix(prefixed_id);
2303 const std::string card_lower = absl::AsciiStrToLower(base_id);
2304 const std::string display_lower =
2305 absl::AsciiStrToLower(descriptor.display_name);
2306 if (card_lower == lower_name || display_lower == lower_name) {
2307 add_category(descriptor.category);
2308 used_startup_hints =
true;
2314 if (used_startup_hints) {
2315 return std::vector<EditorType>(types.begin(), types.end());
2324 if (editor !=
nullptr && *editor->active()) {
2325 add_type(editor->type());
2336 for (
const auto& window_id :
2338 if (
const auto* descriptor =
2340 add_category(descriptor->category);
2344 return std::vector<EditorType>(types.begin(), types.end());
2349 return editor_set ? editor_set->
GetEditor(type) :
nullptr;
2353 if (category.empty() ||
2366#ifdef YAZE_BUILD_AGENT_UI
2381 const std::string& category) {
2385 if (editor ==
nullptr) {
2417 .id =
"experimental_editor",
2419 .message = absl::StrFormat(
"%s is experimental; save posture: %s",
2430 }
else if (!category.empty() &&
2432 LOG_DEBUG(
"EditorManager",
"No editor context available for category '%s'",
2441 return absl::FailedPreconditionError(
"No editor set available");
2446 return absl::OkStatus();
2448 editor->Initialize();
2449 return absl::OkStatus();
2457 return absl::FailedPreconditionError(
"No active session");
2459 if (session->game_data_loaded) {
2460 return absl::OkStatus();
2462 if (!session->rom.is_loaded()) {
2463 return absl::FailedPreconditionError(
"ROM not loaded");
2467 session->ReloadGraphicsDisplay();
2469 auto* game_data = &session->game_data;
2470 auto* editor_set = &session->editors;
2471 editor_set->SetGameData(game_data);
2475 session->game_data_loaded =
true;
2477 return absl::OkStatus();
2482 return absl::OkStatus();
2489 return absl::OkStatus();
2492 if (!session->rom.is_loaded()) {
2494 return absl::OkStatus();
2497 if (index >= session->editor_initialized.size()) {
2498 return absl::InvalidArgumentError(
"Invalid editor type");
2501 return absl::OkStatus();
2503 bool initialized_now =
false;
2504 if (!session->editor_initialized[index]) {
2508 initialized_now =
true;
2510 if (initialized_now) {
2513 return absl::OkStatus();
2517 if (index >= session->editor_initialized.size()) {
2518 return absl::InvalidArgumentError(
"Invalid editor type");
2525 bool initialized_now =
false;
2526 if (!session->editor_initialized[index]) {
2530 initialized_now =
true;
2537 bool loaded_now =
false;
2538 if (!session->editor_assets_loaded[index]) {
2547 if (initialized_now || loaded_now) {
2551 return absl::OkStatus();
2587 return absl::OkStatus();
2596 if (!current_rom || !current_editor_set) {
2597 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
2601 if (!current_session) {
2602 return absl::FailedPreconditionError(
"No active ROM session");
2606 auto start_time = std::chrono::steady_clock::now();
2608#ifdef __EMSCRIPTEN__
2610 auto loading_handle =
2612 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
2614 : app::platform::WasmLoadingManager::BeginLoading(
2615 "Loading Editor Assets");
2618 constexpr float kStartProgress = 0.10f;
2619 constexpr float kEndProgress = 1.0f;
2620 constexpr int kTotalSteps = 11;
2621 int current_step = 0;
2622 auto update_progress = [&](
const std::string& message) {
2625 kStartProgress + (kEndProgress - kStartProgress) *
2626 (
static_cast<float>(current_step) / kTotalSteps);
2627 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, progress);
2628 app::platform::WasmLoadingManager::UpdateMessage(loading_handle, message);
2631 auto cleanup_loading = [&]() {
2632 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2634 struct LoadingGuard {
2635 std::function<void()> cleanup;
2636 bool dismissed =
false;
2641 void dismiss() { dismissed =
true; }
2642 } loading_guard{cleanup_loading};
2644 (void)passed_handle;
2653 const std::unordered_set<EditorType> preload_types(
2654 preload_editor_list.begin(), preload_editor_list.end());
2662 const InitStep init_steps[] = {
2669 for (
const auto& step : init_steps) {
2670 if (!preload_types.contains(step.type)) {
2673 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
2674 editor->Initialize();
2676 if (step.mark_loaded) {
2682#ifdef __EMSCRIPTEN__
2683 update_progress(
"Loading graphics sheets...");
2689 current_session->game_data_loaded =
true;
2691 current_session->ReloadGraphicsDisplay();
2695 auto* game_data = ¤t_session->game_data;
2696 current_editor_set->SetGameData(game_data);
2701 const char* progress_message;
2703 const LoadStep load_steps[] = {
2713 for (
const auto& step : load_steps) {
2714 if (!preload_types.contains(step.type)) {
2717#ifdef __EMSCRIPTEN__
2718 update_progress(step.progress_message);
2720 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
2726#ifdef __EMSCRIPTEN__
2727 update_progress(
"Finishing up...");
2732 auto* settings = current_editor_set->GetSettingsPanel();
2743#ifdef __EMSCRIPTEN__
2745 loading_guard.dismiss();
2746 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2749 auto end_time = std::chrono::steady_clock::now();
2750 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
2751 end_time - start_time);
2752 LOG_DEBUG(
"EditorManager",
"ROM assets loaded in %lld ms", duration.count());
2754 return absl::OkStatus();
2760 if (!current_rom || !current_editor_set) {
2761 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
2765 if (!current_session) {
2766 return absl::FailedPreconditionError(
"No active ROM session");
2770#ifdef __EMSCRIPTEN__
2772 auto loading_handle =
2774 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
2776 : app::platform::WasmLoadingManager::BeginLoading(
2777 "Loading ROM (lazy assets)");
2778 auto cleanup_loading = [&]() {
2779 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2781 struct LoadingGuard {
2782 std::function<void()> cleanup;
2783 bool dismissed =
false;
2789 void dismiss() { dismissed =
true; }
2790 } loading_guard{cleanup_loading};
2792 (void)passed_handle;
2802 auto* settings = current_editor_set->GetSettingsPanel();
2809#ifdef __EMSCRIPTEN__
2810 loading_guard.dismiss();
2811 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2814 LOG_INFO(
"EditorManager",
"Lazy asset mode: editor assets deferred");
2815 return absl::OkStatus();
2834 const std::optional<std::string>& target_filename) {
2864 for (
const char* popup :
2874 const std::optional<std::string>& save_as_filename) {
2878 return absl::FailedPreconditionError(
"No active ROM session");
2884 return absl::OkStatus();
2891 if (status.ok() || !absl::IsCancelled(status) ||
2901 const bool unresolved_confirmation =
2906 if (unresolved_confirmation) {
2907 return absl::CancelledError(
"Save pending confirmation");
2921 if (filename.empty()) {
2922 return absl::InvalidArgumentError(
"No filename provided for save as");
2925 return absl::CancelledError(
"Save pending confirmation");
2936 return absl::FailedPreconditionError(
"No pending ROM save to resume");
2940 return absl::FailedPreconditionError(
2941 "Pending ROM save belongs to a different session");
2954 return absl::OkStatus();
2961 const std::string& filename) {
2962 LOG_INFO(
"EditorManager",
"OpenRomOrProject called with: '%s'",
2964 if (filename.empty()) {
2965 LOG_INFO(
"EditorManager",
"Empty filename provided, skipping load.");
2966 return absl::OkStatus();
2969#ifdef __EMSCRIPTEN__
2971 auto loading_handle =
2972 app::platform::WasmLoadingManager::BeginLoading(
"Loading ROM");
2973 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
2974 "Reading ROM file...");
2976 struct LoadingGuard {
2977 app::platform::WasmLoadingManager::LoadingHandle handle;
2978 bool dismissed =
false;
2981 app::platform::WasmLoadingManager::EndLoading(handle);
2983 void dismiss() { dismissed =
true; }
2984 } loading_guard{loading_handle};
2987 if (absl::EndsWith(filename,
".yaze") ||
2988 absl::EndsWith(filename,
".zsproj") ||
2989 absl::EndsWith(filename,
".yazeproj")) {
2998 const std::optional<size_t> previous_session_id =
3009 if (!project_status.ok()) {
3012 return project_status;
3014#ifdef __EMSCRIPTEN__
3015 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.05f);
3016 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3017 "Loading ROM data...");
3024 const std::optional<size_t> previous_session_id =
3026 const size_t previous_session_count =
3029 std::move(temp_rom), filename);
3030 if (!session_or.ok()) {
3031 return session_or.status();
3050#ifdef YAZE_ENABLE_TESTING
3051 LOG_DEBUG(
"EditorManager",
"Setting ROM in TestManager - %p ('%s')",
3059 const std::string absolute_code_folder =
3064#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3065 editor_set->OpenAssemblyFolder(absolute_code_folder);
3071#ifdef __EMSCRIPTEN__
3072 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.10f);
3073 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3074 "Initializing editors...");
3077 loading_guard.dismiss();
3082 if (!asset_status.ok()) {
3083 auto rollback_status =
3086 if (!rollback_status.ok()) {
3087 return absl::InternalError(
3088 absl::StrFormat(
"%s; session rollback failed: %s",
3089 asset_status.message(), rollback_status.message()));
3091 return asset_status;
3097 return absl::OkStatus();
3102 return absl::FailedPreconditionError(
3103 "Save or discard pending session work before creating a project");
3107 return absl::FailedPreconditionError(
3108 "Project creation dialog is not available");
3111 template_name.empty() ?
"Vanilla ROM Hack" : template_name);
3112 return absl::OkStatus();
3116 const std::string& template_name,
const std::string& rom_path,
3117 const std::string& project_name,
const std::string& project_path) {
3118 if (rom_path.empty() || project_name.empty()) {
3119 return absl::InvalidArgumentError(
"ROM path and project name are required");
3121 if (absl::EndsWith(rom_path,
".yaze") ||
3122 absl::EndsWith(rom_path,
".yazeproj") ||
3123 absl::EndsWith(rom_path,
".zsproj")) {
3124 return absl::InvalidArgumentError(
3125 "New projects require a ROM file, not a project descriptor");
3128 return absl::FailedPreconditionError(
3129 "Save or discard pending session work before creating a project");
3135 const bool reuse_active_raw_session =
3136 reusable_session && reusable_session->rom.is_loaded() &&
3138 rom_path, reusable_session->rom.filename()) &&
3141 const std::optional<size_t> previous_session_id =
3143 const size_t previous_session_count =
3147 if (!rom_status.ok()) {
3152 project_name, project_path);
3153 if (!target_status.ok()) {
3155 return target_status;
3158 if (reuse_active_raw_session) {
3167 if (!open_status.ok()) {
3174 if (!finalize_status.ok()) {
3175 auto rollback_status =
3178 if (!rollback_status.ok()) {
3179 return absl::InternalError(absl::StrFormat(
3180 "%s; session rollback failed: %s", finalize_status.message(),
3181 rollback_status.message()));
3184 return finalize_status;
3188 const std::string& project_name,
const std::string& project_path) {
3190 return absl::FailedPreconditionError(
"No session coordinator");
3193 if (!session || !session->rom.is_loaded()) {
3194 return absl::FailedPreconditionError(
3195 "Load a ROM before finalizing the project");
3202 if (created_project_shell) {
3206 auto project_rom_status =
3208 if (!project_rom_status.ok()) {
3210 return project_rom_status;
3213 if (created_project_shell) {
3219 pending_project.workspace_settings.font_global_scale =
3221 pending_project.workspace_settings.autosave_enabled =
3223 pending_project.workspace_settings.autosave_interval_secs =
3225 pending_project.workspace_settings.backup_on_save =
3228 auto finalize_status =
3230 if (!finalize_status.ok()) {
3232 return finalize_status;
3237 session->project_dirty =
false;
3251 return absl::OkStatus();
3258 return absl::OkStatus();
3265 auto open_project_from_path =
3266 [
this](
const std::string& file_path) -> absl::Status {
3267 if (file_path.empty()) {
3268 return absl::OkStatus();
3280 auto validation_status = new_project.
Validate();
3281 if (!validation_status.ok()) {
3283 validation_status.message()),
3290 const std::optional<size_t> previous_session_id =
3299 if (!project_status.ok()) {
3302 return project_status;
3305#if defined(__APPLE__) && TARGET_OS_IOS == 1
3309 return absl::OkStatus();
3312 return open_project_from_path(file_path);
3317 const std::string& rom_path) {
3326 .candidate_path = rom_path,
3328 return absl::OkStatus();
3332 if (rom_path.empty()) {
3333 return absl::InvalidArgumentError(
"ROM path cannot be empty");
3336 return absl::FailedPreconditionError(
"No session coordinator");
3339 if (!session || !session->rom.is_loaded() ||
3341 return absl::FailedPreconditionError(
"No active project ROM session");
3344 return absl::FailedPreconditionError(
3345 "Save or discard ROM edits before swapping the project ROM");
3348 if (session->project_file_editor_state.initialized &&
3349 session->project_file_editor_state.modified &&
3351 return absl::FailedPreconditionError(
3352 "Preserve the raw project-file draft before swapping ROMs");
3356 rom_path, session->session_id()));
3357 Rom replacement_rom;
3371 std::string replacement_path = rom_path;
3380 const auto development_path = std::filesystem::canonical(
3385 return absl::FailedPreconditionError(absl::StrFormat(
3386 "Cannot resolve the manifest development ROM before rebinding: %s",
3389 replacement_path = development_path.string();
3396 if (!save_status.ok()) {
3402 auto replace_status =
3404 if (!replace_status.ok()) {
3409 if (!rollback_status.ok()) {
3410 return absl::InternalError(absl::StrFormat(
3411 "ROM swap failed (%s) and restoring the project descriptor failed "
3413 replace_status.message(), rollback_status.message()));
3415 return replace_status;
3417 session->project_dirty =
false;
3423 return absl::OkStatus();
3428 return absl::FailedPreconditionError(
"No session coordinator");
3431 if (!session || !session->rom.is_loaded() ||
3434 return absl::FailedPreconditionError(
"No active project ROM to reload");
3437 return absl::FailedPreconditionError(
3438 "Save or discard ROM edits before reloading from disk");
3443 Rom replacement_rom;
3455 session && session->project_file_editor_state.initialized &&
3456 session->project_file_editor_state.modified &&
3458 return absl::FailedPreconditionError(
3459 "Raw project file also has unsaved changes; use raw Save As to "
3460 "preserve that draft before saving project settings");
3490 for (
const auto& file : manager.GetRecentFiles()) {
3502 session->project_dirty =
false;
3517 session->project_dirty =
true;
3528 return session !=
nullptr &&
3529 (session->project_dirty ||
3530 session->editors.HasPendingProjectDraftChanges());
3535 return absl::FailedPreconditionError(
"No session coordinator");
3539 return absl::FailedPreconditionError(
"No active ROM session");
3543 const bool project_dirty = session->project_dirty ||
3544 session->editors.HasPendingProjectDraftChanges();
3545 const bool project_file_dirty =
3546 session->project_file_editor_state.initialized &&
3547 session->project_file_editor_state.modified;
3549 if (project_dirty && project_file_dirty &&
3551 return absl::FailedPreconditionError(
3552 "Project settings and raw project file both have unsaved changes; "
3553 "use raw Save As to preserve the draft, then save project settings");
3556 if (project_dirty) {
3559 return absl::FailedPreconditionError(
3560 "Project settings have no project file to save");
3565 if (project_file_dirty) {
3567 return absl::FailedPreconditionError(
3568 "Project file draft has no destination; use Save As first");
3574 return absl::OkStatus();
3579 return absl::FailedPreconditionError(
"No session coordinator");
3584 if (session !=
nullptr && session->backup_restore_pending) {
3585 return absl::CancelledError(
3586 "Staged backup restore requires an explicit Save ROM");
3592 return absl::FailedPreconditionError(
3593 "Autosave left unsaved project or ROM work");
3595 return absl::OkStatus();
3601 auto lifecycle_decision =
3603 static_cast<int>(decision));
3613 if (!absl::IsCancelled(status) && !status.ok()) {
3615 absl::StrFormat(
"Failed to save ROM: %s", status.message()),
3624 :
"untitled_project";
3628 if (file_path.empty()) {
3629 return absl::OkStatus();
3636 if (filepath.empty()) {
3637 return absl::InvalidArgumentError(
"Project file path cannot be empty");
3640 std::string file_path = filepath;
3643 if (!(absl::EndsWith(file_path,
".yaze") ||
3644 absl::EndsWith(file_path,
".yazeproj"))) {
3645 file_path +=
".yaze";
3650 session && session->project_file_editor_state.initialized &&
3651 session->project_file_editor_state.modified &&
3653 session->project_file_editor_state.filepath, file_path)) {
3654 return absl::FailedPreconditionError(
3655 "Raw project draft targets the selected file; preserve it with "
3656 "raw Save As before overwriting that destination");
3668 if (save_status.ok()) {
3674 session->project_dirty =
false;
3682 manager.AddFile(file_path);
3688 absl::IsInvalidArgument(catalog_path.status())) {
3691 "Project saved as: %s. Its palette catalog cannot load from this "
3692 "folder. Copy the catalog into the new project folder, then "
3693 "update palette_catalog_file in Edit Project File.",
3704 absl::StrFormat(
"Failed to save project: %s", save_status.message()),
3715 return absl::FailedPreconditionError(
"No project open");
3722 const auto* dungeon =
3724 if (dungeon && dungeon->HasUnpublishedMinecartTrackChanges()) {
3725 return absl::FailedPreconditionError(
3726 "Minecart tracks have unpublished changes. Use Publish Tracks, then "
3727 "save remaining project and ROM changes before Build Project. "
3728 "Undo the track edits if you do not want to publish them.");
3733 return absl::FailedPreconditionError(
3734 absl::StrFormat(
"Save project and ROM changes before building: %s",
3739 if (active_rom !=
nullptr && active_rom->
is_loaded() &&
3744 return absl::FailedPreconditionError(absl::StrFormat(
3745 "Build source mismatch: active ROM %s differs from project ROM %s. "
3746 "Use Project Management > Swap ROM to bind the saved copy, or reopen "
3747 "the project's ROM before building.",
3759 return absl::FailedPreconditionError(absl::StrFormat(
3760 "Build source mismatch: manifest development ROM %s differs from "
3765 command = pipeline.build_script;
3768 if (!command.empty() &&
3769 command.find_first_of(
" \t\r\n|&;<>()$`'\"\\") == std::string::npos) {
3770 const std::filesystem::path script_path(command);
3771 if (script_path.is_absolute() || script_path.has_parent_path() ||
3772 script_path.extension() ==
".sh") {
3773 const std::string absolute_script =
3776 if (!std::filesystem::is_regular_file(absolute_script, ec)) {
3777 return absl::FailedPreconditionError(absl::StrFormat(
3778 "Manifest build script is unavailable: %s. Configure a build "
3779 "script for this project before building.",
3785 if (command.empty()) {
3786 return absl::NotFoundError(
"Project does not define a build command");
3793 return absl::FailedPreconditionError(
"No project open");
3800 if (target.empty()) {
3803 if (target.empty()) {
3804 return absl::NotFoundError(
"Project does not define a run target ROM");
3810 const std::string& summary,
const std::string& detail,
3812 bool can_cancel)
const {
3814 .can_cancel = can_cancel,
3818 .output_tail = output_tail,
3823 const std::string& summary,
const std::string& detail,
3834 if (!command_or.ok()) {
3835 return command_or.status();
3838 const std::string command = *command_or;
3839 const std::filesystem::path project_root =
3842 auto start_status = task.
Start(command, project_root.string());
3843 if (!start_status.ok()) {
3844 return start_status;
3846 auto wait_status = task.
Wait();
3848 if (!wait_status.ok()) {
3851 const std::string summary = LastNonEmptyLine(snapshot.output);
3852 return summary.empty() ? command : summary;
3856 const std::string running_detail =
3857 "Running the configured project build command";
3858 UpdateBuildWorkflowStatus(
3864 if (!result_or.ok()) {
3865 const bool unavailable =
3866 result_or.status().code() == absl::StatusCode::kFailedPrecondition ||
3867 result_or.status().code() == absl::StatusCode::kNotFound;
3868 UpdateBuildWorkflowStatus(
3871 std::string(result_or.status().message()),
3874 absl::StrFormat(
"%s: %s",
3875 unavailable ?
"Build unavailable" :
"Build failed",
3876 result_or.status().message()),
3878 return result_or.status();
3881 const std::string summary = *result_or;
3886 summary.empty() ?
"Project build completed"
3887 : absl::StrFormat(
"Project build completed: %s", summary),
3889 LOG_INFO(
"EditorManager",
"Project build completed: %s", summary.c_str());
3890 return absl::OkStatus();
3896 if (snapshot.running) {
3904 if (!command_or.ok()) {
3905 UpdateBuildWorkflowStatus(
3908 std::string(command_or.status().message()),
3911 absl::StrFormat(
"Build unavailable: %s", command_or.status().message()),
3916 const std::filesystem::path project_root =
3921 if (!start_status.ok()) {
3922 UpdateBuildWorkflowStatus(
3925 std::string(start_status.message()),
3928 absl::StrFormat(
"Build unavailable: %s", start_status.message()),
3935 UpdateBuildWorkflowStatus(
3938 "Running the configured project build command",
3952 if (!snapshot.running) {
3957 UpdateBuildWorkflowStatus(
3959 MakeBuildStatus(
"Cancelling build",
"Stopping the active project build",
3965 UpdateRunWorkflowStatus(
3968 "Preparing the project output for emulator reload",
3972 if (!run_target_or.ok()) {
3973 UpdateRunWorkflowStatus(
3976 std::string(run_target_or.status().message()),
3979 run_target_or.status().message()),
3981 return run_target_or.status();
3983 const std::string run_target = *run_target_or;
3984 if (!std::filesystem::exists(run_target)) {
3990 return absl::NotFoundError(
"Run target ROM not found");
3993#ifdef YAZE_WITH_GRPC
3995 auto load_status = emulator_backend->LoadRom(run_target);
3996 if (load_status.ok()) {
3997 AppendWorkflowHistoryEntry(
4009 absl::StrFormat(
"Reloaded project output in emulator backend: %s",
4012 return absl::OkStatus();
4019 if (!load_rom_status.ok()) {
4020 UpdateRunWorkflowStatus(
4022 MakeRunStatus(
"Run load failed", std::string(load_rom_status.message()),
4025 load_rom_status.message()),
4027 return load_rom_status;
4031 if (!reload_status.ok()) {
4032 UpdateRunWorkflowStatus(
4034 MakeRunStatus(
"Reload failed", std::string(reload_status.message()),
4037 reload_status.message()),
4039 return reload_status;
4045 AppendWorkflowHistoryEntry(
"Run",
4053 absl::StrFormat(
"Reloaded project output in emulator: %s", run_target),
4055 return absl::OkStatus();
4072 return absl::OkStatus();
4077 return absl::FailedPreconditionError(
"No project is currently open");
4085 return absl::OkStatus();
4090 return editor_set->GetOverworldData();
4097 return absl::InvalidArgumentError(
"Invalid ROM pointer");
4106 if (session && &session->rom == rom) {
4112 return absl::OkStatus();
4118 return absl::NotFoundError(
"ROM not found in existing sessions");
4129 session->CaptureRomIdentity(preserve_editing_source);
4153 return session !=
nullptr && session->backup_restore_pending;
4159 return absl::FailedPreconditionError(
"No ROM loaded");
4162 return absl::FailedPreconditionError(
4163 "Save or discard ROM edits before restoring a backup");
4166 const std::string original_filename = rom->
filename();
4170 const bool is_managed_backup =
4171 std::any_of(backups.begin(), backups.end(), [&](
const auto& backup) {
4172 return SessionCoordinator::PathsReferToSameBackingFile(backup.path,
4175 if (!is_managed_backup) {
4176 return absl::InvalidArgumentError(
4177 "Selected file is not a managed backup for the active ROM");
4187 if (!original_filename.empty()) {
4201 if (!restored_session) {
4202 return absl::InternalError(
4203 "ROM backup restored without an active session to stage it");
4205 restored_session->backup_restore_pending =
true;
4206 return absl::OkStatus();
4211 return absl::FailedPreconditionError(
"No session coordinator");
4214 if (!session || !session->rom.is_loaded()) {
4215 return absl::FailedPreconditionError(
"No ROM loaded");
4217 if (!session->backup_restore_pending) {
4218 return absl::FailedPreconditionError(
"No restored backup is staged");
4222 if (session->editors.HasPendingGraphicsChanges() ||
4223 session->HasPendingGfxGroupChanges() ||
4224 session->editors.HasPendingScreenChanges() ||
4227 return absl::FailedPreconditionError(
4228 "Resolve pending graphics, screen, dungeon, or palette edits before "
4229 "discarding the restored backup");
4232 const std::string backing_path = session->rom.filename();
4233 if (backing_path.empty()) {
4234 return absl::FailedPreconditionError(
"ROM has no backing file to reload");
4241 *session->rom.resource_label();
4248 session->backup_restore_pending =
false;
4249 session->rom.ClearDirty();
4250 return absl::OkStatus();
4301 current_session_id})) {
4333 return session !=
nullptr && session->rom.is_loaded() &&
4334 !session->rom.filename().empty();
4339 return absl::FailedPreconditionError(
"No ROM with a backing file is open");
4345 return absl::OkStatus();
4349 return absl::OkStatus();
4354 return absl::FailedPreconditionError(
"No ROM with a backing file is open");
4359 if (session->editors.HasPendingGraphicsChanges()) {
4360 return absl::FailedPreconditionError(
4361 "The Graphics editor has unsaved sheet edits; save or undo them "
4362 "before reverting");
4365 const std::string backing_path = session->rom.filename();
4371 *session->rom.resource_label();
4381 if (session !=
nullptr) {
4382 session->backup_restore_pending =
false;
4383 session->rom.ClearDirty();
4385 return absl::DataLossError(absl::StrFormat(
4386 "ROM reloaded from disk, but the session still reports %s",
4390 return absl::OkStatus();
4424 size_t session_id)
const {
4426 return std::nullopt;
4435 return std::nullopt;
4445 return absl::StrFormat(
"%s\n\nQuitting now will discard them.",
4450 const std::string session_name =
4451 source_index.has_value()
4454 const std::string work = source_index.has_value()
4458 switch (action.type) {
4461 return absl::StrFormat(
4462 "Session '%s' has %s. Opening another ROM or project will not save "
4463 "them automatically.",
4464 session_name, work);
4466 return absl::StrFormat(
4467 "Session '%s' has %s. Opening another project will not save them "
4469 session_name, work);
4471 return absl::StrFormat(
4472 "Session '%s' has %s. Switching sessions will leave them behind in "
4474 session_name, work);
4476 return absl::StrFormat(
4477 "Session '%s' has %s. Closing it now will discard them.",
4478 session_name, work);
4480 return absl::StrFormat(
4481 "Session '%s' has %s. Closing the ROM now will discard them.",
4482 session_name, work);
4484 return absl::StrFormat(
4485 "Session '%s' has %s. Reverting reloads the ROM from disk and "
4486 "discards all unsaved ROM edits. This cannot be undone.",
4487 session_name, work);
4503 return "Save Work & Open";
4505 return "Save Work & Open Project";
4507 return "Save Work & Switch";
4509 return "Save Work & Close";
4511 return "Save Work & Close ROM";
4518 :
"Save Modified Work & Quit";
4532 return "Open Without Saving";
4534 return "Open Project Without Saving";
4536 return "Switch Without Saving";
4539 return "Close Without Saving";
4541 return "Discard Edits & Revert";
4543 return "Quit Without Saving";
4562 auto save_modified_session = [
this](
size_t session_id) -> absl::Status {
4564 if (!session_index.has_value()) {
4565 return absl::FailedPreconditionError(
4566 "The ROM session is no longer available");
4569 return absl::OkStatus();
4583 return absl::FailedPreconditionError(
4584 absl::StrFormat(
"Save completed, but session still has %s",
4587 return absl::OkStatus();
4590 absl::Status save_status = absl::OkStatus();
4593 std::vector<size_t> session_ids;
4599 for (
const size_t session_id : session_ids) {
4600 save_status = save_modified_session(session_id);
4601 if (!save_status.ok()) {
4607 save_status = save_modified_session(action.source_session_id);
4610 if (!save_status.ok()) {
4611 const bool resumable_confirmation = absl::IsCancelled(save_status) &&
4614 const auto original_session_index =
4616 if (!resumable_confirmation && original_session_index.has_value()) {
4620 if (absl::IsCancelled(save_status)) {
4625 absl::StrFormat(
"Failed to save before continuing: %s",
4626 save_status.message()),
4636 original_session_id != action.target_session_id) {
4637 const auto original_session_index =
4639 if (original_session_index.has_value()) {
4671 bool has_pending_work =
false;
4682 if (!has_pending_work) {
4695 switch (action.
type) {
4700 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
4718 absl::StrFormat(
"Failed to open project: %s", status.message()),
4725 const auto target_index =
4727 if (target_index.has_value()) {
4734 const auto target_index =
4736 if (target_index.has_value()) {
4744 const auto target_index =
4746 if (target_index.has_value()) {
4753 const auto target_index =
4755 if (!target_index.has_value()) {
4768 absl::StrFormat(
"Revert failed: %s", status.message()),
4787 return session !=
nullptr &&
4789 session->editors.HasPendingProjectDraftChanges() ||
4790 (session->project_file_editor_state.initialized &&
4791 session->project_file_editor_state.modified));
4802 return session !=
nullptr &&
4803 ((session->rom.is_loaded() && session->rom.dirty()) ||
4804 session->editors.HasPendingGraphicsChanges() ||
4805 session->HasPendingGfxGroupChanges() ||
4806 session->editors.HasPendingScreenChanges() ||
4816 size_t session_index)
const {
4828 if (
auto* dungeon_editor =
4830 return dungeon_editor->HasPendingDungeonChanges();
4836 size_t session_index)
const {
4848 if (
auto* dungeon_editor =
4850 return dungeon_editor->PendingRoomCount();
4856 size_t session_index)
const {
4865 &session->game_data)
4884 size_t session_index)
const {
4887 return "unsaved work";
4892 const bool rom_dirty =
4893 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
4894 const bool pending_dungeon_changes =
4896 const bool pending_graphics_changes =
4897 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
4898 const bool pending_gfx_group_changes =
4899 session !=
nullptr && session->HasPendingGfxGroupChanges();
4900 const bool pending_screen_changes =
4901 session !=
nullptr && session->editors.HasPendingScreenChanges();
4903 const size_t pending_palette_colors =
4905 const bool project_dirty = session !=
nullptr && session->project_dirty;
4906 const bool project_editor_draft =
4907 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
4908 const bool project_file_dirty =
4909 session !=
nullptr && session->project_file_editor_state.initialized &&
4910 session->project_file_editor_state.modified;
4912 std::vector<std::string> work;
4913 if (pending_rooms > 0) {
4914 work.push_back(absl::StrFormat(
"%d unapplied dungeon room%s", pending_rooms,
4915 pending_rooms == 1 ?
"" :
"s"));
4916 }
else if (pending_dungeon_changes) {
4917 work.emplace_back(
"unapplied dungeon metadata");
4919 if (pending_palette_colors > 0) {
4920 work.push_back(absl::StrFormat(
"%zu unapplied palette color%s",
4921 pending_palette_colors,
4922 pending_palette_colors == 1 ?
"" :
"s"));
4924 if (pending_graphics_changes) {
4925 work.emplace_back(
"unapplied graphics sheet edits");
4927 if (pending_gfx_group_changes) {
4928 work.emplace_back(
"unsaved blockset/spriteset (gfx group) edits");
4930 if (pending_screen_changes) {
4931 work.emplace_back(
"unapplied Screen Editor edits");
4934 work.emplace_back(
"unsaved ROM-buffer changes");
4936 if (project_dirty) {
4937 work.emplace_back(
"unsaved project settings");
4939 if (project_editor_draft) {
4940 work.emplace_back(
"an uncommitted project editor draft");
4942 if (project_file_dirty) {
4943 work.emplace_back(
"an unsaved project-file draft");
4945 return work.empty() ?
"unsaved work" : absl::StrJoin(work,
" and ");
4949 size_t session_index)
const {
4957 const bool rom_dirty =
4958 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
4959 const bool pending_dungeon_changes =
4961 const bool pending_graphics_changes =
4962 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
4963 const bool pending_gfx_group_changes =
4964 session !=
nullptr && session->HasPendingGfxGroupChanges();
4965 const bool pending_screen_changes =
4966 session !=
nullptr && session->editors.HasPendingScreenChanges();
4968 const size_t pending_palette_colors =
4970 const bool project_dirty = session !=
nullptr && session->project_dirty;
4971 const bool project_editor_draft =
4972 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
4973 const bool project_file_dirty =
4974 session !=
nullptr && session->project_file_editor_state.initialized &&
4975 session->project_file_editor_state.modified;
4977 std::vector<std::string> tags;
4978 if (pending_rooms > 0) {
4979 tags.push_back(
"Rooms");
4980 }
else if (pending_dungeon_changes) {
4981 tags.push_back(
"Dungeon");
4983 if (pending_palette_colors > 0) {
4984 tags.push_back(
"Palette");
4986 if (pending_graphics_changes) {
4987 tags.push_back(
"Gfx");
4989 if (pending_gfx_group_changes) {
4990 tags.push_back(
"GfxGroups");
4992 if (pending_screen_changes) {
4993 tags.push_back(
"Screen");
4996 tags.push_back(
"ROM");
4998 if (project_dirty || project_editor_draft || project_file_dirty) {
4999 tags.push_back(
"Project");
5001 return absl::StrJoin(tags,
"+");
5006 if (modified_sessions <= 0) {
5007 return "No sessions have unsaved work.";
5013 return absl::StrFormat(
"Session '%s' has %s.",
5020 return absl::StrFormat(
5021 "%d sessions have unsaved ROM, graphics, screen, dungeon, palette, or "
5035 int pending_editor =
5037 if (pending_editor < 0) {
5043 if (pending_layout < 0) {
5058 EditorType type,
size_t session_index)
const {
5059 const char* base_name =
kEditorNames[
static_cast<int>(type)];
5061 base_name, session_index)
5062 : std::string(base_name);
5067#ifdef YAZE_BUILD_AGENT_UI
5080 "%s is experimental. It remains available; enable experimental "
5081 "features in Settings to acknowledge this warning.",
5099 absl::StrFormat(
"Failed to prepare %s: %s",
5113 switch (editor_type) {
5194 if (session && session->project_file_editor_state.initialized &&
5195 !session->project_file_editor_state.modified &&
5198 session->project_file_editor_state.filepath,
5203 if (session && session->project_file_editor_state.initialized) {
5210 absl::StrFormat(
"Failed to load project file: %s", status.message()),
5227 size_t session_id) {
5243 if (session_index.has_value()) {
5248 if (session->project_context.has_value()) {
5249 deps.
project = &*session->project_context;
5268 settings_panel->SetProjectContextProvider(
5274 [
this, session_id]() -> std::string {
5279 settings_panel->SetOpenMinecartTracksCallback(
5280 [
this, session_id]() -> absl::Status {
5282 return absl::FailedPreconditionError(
5283 "Minecart Tracks requires its project session to be active.");
5288 if (dungeon ==
nullptr) {
5289 return absl::NotFoundError(
5290 "Dungeon editor is unavailable in the active project "
5298 return absl::NotFoundError(
5299 "Minecart Tracks could not be opened in the active Dungeon "
5302 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()
void set_dirty(bool dirty)
const auto & vector() const
auto set_filename(std::string_view name)
static TimingManager & Get()
float GetElapsedTime() const
Get total elapsed time since first update.
const ProjectRegistry & project_registry() const
bool HasProjectRegistry() const
bool loaded() const
Check if the manifest has been loaded.
const BuildPipeline & build_pipeline() const
static absl::StatusOr< std::filesystem::path > ResolvePathForProject(const std::string &project_file, const std::string &catalog_file)
bool AdoptExistingRepository()
void set_active(bool active)
AgentChat * GetAgentChat()
void ApplyUserSettingsDefaults(bool force=false)
AgentEditor * GetAgentEditor()
void Initialize(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, RightDrawerManager *right_drawer_manager, WorkspaceWindowManager *window_manager, UserSettings *user_settings)
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.
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_
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()
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
bool CanRevertRom() const
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)
bool HasPreviousEditorLayout() const
void ResetAssetState(RomSession *session)
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)
std::string GetCurrentRomHash() const
FeatureHintManager feature_hint_manager_
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 *
void CancelRomWriteConfirm()
bool IsRomBackupRestorePending() const
std::string DescribePendingUnsavedWork(size_t session_index) const
absl::Status PruneRomBackups()
EditorContextSnapshot GetEditorContextSnapshot(const std::string &category)
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)
void RestoreCurrentRomIdentity()
ProjectFileEditor project_file_editor_
void ApplyLayoutPreset(const std::string &preset_name)
void RestoreTemporaryLayoutSnapshot(bool clear_after_restore=false)
void ApplyStartupVisibility(const AppConfig &config)
void ExecutePendingUnsavedSessionAction(const PendingUnsavedSessionAction &action)
bool WantsContinuousFrames() const
WorkspaceWindowManager window_manager_
size_t PendingPaletteColorCountForSession(size_t session_index) const
void RestorePreviousEditorLayout()
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 MaybeEmitZScreamRomLoadHints()
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
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
absl::Status BuildCurrentProject()
StartupVisibility sidebar_mode_override_
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()
void SetupSidebarCallbacks()
size_t GetCurrentSessionIndex() const
absl::StatusOr< std::string > ResolveProjectRunTarget() const
void ShowPostLoadSurface()
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)
std::unique_ptr< ActivityBar > activity_bar_
ShortcutManager shortcut_manager_
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
absl::Status RevertRomToSavedInternal()
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_
bool HasOpenEditor() const
std::unique_ptr< workflow::HackWorkflowBackend > hack_workflow_backend_
absl::Status OpenRomOrProject(const std::string &filename)
PopupManager * popup_manager()
absl::StatusOr< std::string > ResolveProjectBuildCommand()
void RestoreProjectContextAfterFailedOpen(std::optional< size_t > previous_session_id)
void RemoveSession(size_t index)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
void DetachActiveProjectContext()
UiSyncState GetUiSyncStateSnapshot() const
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)
void CaptureCurrentRomIdentity(bool preserve_editing_source=false)
std::atomic< int > pending_editor_deferred_actions_
std::unique_ptr< UICoordinator > ui_coordinator_
EditorActivator editor_activator_
absl::Status RevertRomToSaved()
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 bool IsPanelBasedEditor(EditorType type)
static bool ShouldWarnAboutExperimentalEditor(EditorType type, bool experiments_enabled)
static EditorExperimentPolicy GetExperimentPolicy(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.
void ApplyDependencies(const EditorDependencies &dependencies)
Editor * GetEditor(EditorType type) const
GfxGroupWorkspaceState * gfx_group_workspace()
SettingsPanel * GetSettingsPanel() const
std::vector< Editor * > active_editors_
Interface for editor classes.
virtual absl::Status Redo()=0
virtual EditorContextSnapshot BuildContextSnapshot() const
virtual absl::Status Undo()=0
void SetGuideCallback(std::function< void()> callback)
Opens Help → Coming from ZScream (or equivalent) from actionable tips.
void SetUserSettings(UserSettings *settings)
bool MaybeShow(std::string_view id)
void RegisterBuiltinHints()
Register built-in ZScream migrant tips. Safe to call more than once.
void SetToastManager(ToastManager *toast_manager)
Allows the user to edit graphics sheets from the game or view prototype graphics.
bool RestorePreviousEditorLayout(EditorType type)
void ProcessDeferredActions()
Process all queued deferred actions.
bool HasPreviousEditorLayout(EditorType type) const
void ResetWorkspaceLayout()
Reset the workspace layout to defaults.
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.
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)
bool ShouldBypassWriteConflict() const
std::vector< RomFileManager::BackupEntry > GetRomBackups(Rom *rom) const
absl::Status CheckRomWritePolicy(Rom *rom, const std::optional< std::string > &target_filename=std::nullopt)
Enforce project write policy; may set pending_rom_write_confirm.
absl::Status CheckRomRebindPolicy(Rom *rom)
bool HasPendingPotItemSaveConfirmation() const
void ResetPendingSaveState()
void ActivateRomIdentity(const RomSession *session)
absl::Status CheckRomOpenPolicy(Rom *rom)
Validate that the loaded ROM is a safe project target to open/edit.
PotItemSaveDecision ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
const std::vector< core::WriteConflict > & pending_write_conflicts() const
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 SetAgentCallbacks(std::function< void(const std::string &)> send_callback, std::function< void()> focus_callback)
static bool PathsReferToSameBackingFile(const std::string &lhs, const std::string &rhs)
Manages the settings UI displayed in the right sidebar.
A session-aware status bar displayed at the bottom of the application.
void SetBuildStatus(const ProjectWorkflowStatus &status)
void SetRunStatus(const ProjectWorkflowStatus &status)
void SetEnabled(bool enabled)
Enable or disable the status bar.
void Initialize(GlobalEditorContext *context)
void SetAgentToggleCallback(std::function< void()> callback)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
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 SetEventBus(EventBus *event_bus)
size_t GetActiveSessionId() const
void SetSidebarVisible(bool visible, bool notify=true)
void TriggerShowShortcuts()
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
const WindowDescriptor * GetWindowDescriptor(size_t session_id, const std::string &base_window_id) const
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 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 SetFileBrowserPath(const std::string &category, const std::string &path)
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void TriggerShowSettings()
void SetWindowBrowserCategoryWidth(float width, bool notify=true)
void SetSidebarExpanded(bool expanded, bool notify=true)
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)
void set_window_manager(editor::WorkspaceWindowManager *manager)
void set_renderer(gfx::IRenderer *renderer)
auto running() const -> bool
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
bool HasPendingTextures() const
Check if there are pending textures to process.
Defines an abstract interface for all rendering operations.
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
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 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)
static TestManager & Get()
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
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)
#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 LOG_DEBUG(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define PRINT_IF_ERROR(expression)
void SetGlobalContext(GlobalEditorContext *ctx)
void SetShowWorkflowOutputCallback(std::function< void()> callback)
void SetEventBus(::yaze::EventBus *bus)
Set the current EventBus 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.
std::vector< std::unique_ptr< WindowContent > > CreateAll()
Create new instances of all registered panels.
constexpr const char * kZScreamDungeon
constexpr const char * kZScreamOverworld
constexpr const char * kZScreamTile16
constexpr const char * kZScreamScreenMap
void UpdateBuildWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
void UpdateRunWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
std::string LastNonEmptyLine(const std::string &text)
void MergeRightDrawerWidthPreferences(std::unordered_map< std::string, float > *preferences, const std::unordered_map< std::string, float > &drawer_widths)
void AppendWorkflowHistoryEntry(const std::string &kind, const ProjectWorkflowStatus &status, const std::string &output_log)
std::string StripSessionPrefix(absl::string_view panel_id)
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)
bool IsTransientPanelVisibilityId(absl::string_view panel_id)
std::optional< EditorType > ParseEditorTypeFromString(absl::string_view name)
constexpr std::array< const char *, 15 > kEditorNames
void ConfigureMenuShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void RegisterDefaultEditorFactories(EditorRegistry *registry)
void ConfigurePanelShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
Final configuration pass: apply user rebinds, then register panel toggle shortcuts.
size_t EditorTypeIndex(EditorType type)
void ConfigureEditorShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
constexpr size_t kEditorTypeCount
absl::Status ValidateRomCollisionSourceBinding(Rom *replacement_rom, const fs::path &json_path)
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 kNumOverworldMaps
void SetPreferHmagicSpriteNames(bool prefer)
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
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > all_resource_labels
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
EditorExperimentContext experiment
std::vector< EditorContextDiagnostic > diagnostics
std::string semantic_owner
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
ExperimentalSavePosture disabled_save_posture
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.
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
size_t session_id() const
void ResetGraphicsDisplay()
std::array< bool, kEditorTypeCount > editor_initialized
std::array< bool, kEditorTypeCount > editor_assets_loaded
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
Activity bar or menu action request.
bool show_experimental_editors
int panel_layout_defaults_revision
std::string last_applied_layout_name
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
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.
WindowPresentationPolicy presentation
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
WindowPresentationPolicy presentation
WindowLifecycle window_lifecycle
static WindowPresentationPolicy Diagnostic()
static WindowPresentationPolicy CoreWorkspace()
WindowPresentationPolicy WithHost(std::string host_name) const
Returns a copy that names the hosting surface.
static WindowPresentationPolicy OptionalPopOut()
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
float autosave_interval_secs
std::vector< std::string > recent_files
Modern project structure with comprehensive settings consolidation.
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 GetDisplayName() const
WorkspaceSettings workspace_settings
std::string custom_collision_json
std::string palette_catalog_file
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