20#include <initializer_list>
29#include <unordered_map>
30#include <unordered_set>
35#include <TargetConditionals.h>
39#define IMGUI_DEFINE_MATH_OPERATORS
40#include "absl/status/status.h"
41#include "absl/status/statusor.h"
42#include "absl/strings/ascii.h"
43#include "absl/strings/match.h"
44#include "absl/strings/str_format.h"
45#include "absl/strings/str_join.h"
46#include "absl/strings/str_split.h"
47#include "absl/strings/string_view.h"
48#include "imgui/imgui.h"
50#include "nlohmann/json.hpp"
126#include "yaze_config.h"
149#ifdef YAZE_ENABLE_TESTING
156#ifdef YAZE_ENABLE_GTEST
164#ifdef YAZE_BUILD_AGENT_UI
173 const std::string& editing_source_path) {
175 return absl::FailedPreconditionError(
"Save the project before building");
177 std::error_code error;
178 const auto project_file =
179 std::filesystem::absolute(project.
filepath, error).lexically_normal();
181 return absl::FailedPreconditionError(
182 "Cannot resolve the project build context: " + error.message());
184 const auto absolute = [&project_file](
const std::string& value) {
186 return std::string();
188 const std::filesystem::path path(value);
189 return (path.is_absolute() ? path : project_file.parent_path() / path)
193 std::string save_context_path;
194#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
195 if (!editing_source_path.empty()) {
199 save_context_path = save_context.path.string();
202 return std::map<std::string, std::string>{
203 {
"YAZE_PROJECT_FILE", project_file.string()},
204 {
"YAZE_PROJECT_CODE_FOLDER", absolute(project.
code_folder)},
206 {
"YAZE_NATIVE_SAVE_RECEIPT", project_file.string() +
".native-save.json"},
207 {
"YAZE_NATIVE_SAVE_CONTEXT", save_context_path},
215 std::vector<std::string> lines = absl::StrSplit(text,
'\n');
216 for (
auto it = lines.rbegin(); it != lines.rend(); ++it) {
217 std::string line = std::string(*it);
218 absl::StripAsciiWhitespace(&line);
230 if (status_bar !=
nullptr) {
233 if (project_panel !=
nullptr) {
242 if (status_bar !=
nullptr) {
245 if (project_panel !=
nullptr) {
252 const std::string& output_log) {
256 .output_log = output_log,
257 .timestamp = std::chrono::system_clock::now()});
261 std::unordered_map<std::string, float>* preferences,
262 const std::unordered_map<std::string, float>& drawer_widths) {
263 if (preferences ==
nullptr) {
266 constexpr std::array<const char*, 9> kDrawerWidthKeys = {
267 "right_sidebar.shared",
"agent_chat",
"proposals",
"settings",
"help",
268 "notifications",
"properties",
"project",
"tool_output",
270 for (
const char* key : kDrawerWidthKeys) {
271 preferences->erase(key);
273 for (
const auto& [key, width] : drawer_widths) {
274 preferences->insert_or_assign(key, width);
279 if (panel_id.size() > 2 && panel_id[0] ==
's' &&
280 absl::ascii_isdigit(panel_id[1])) {
281 const size_t dot = panel_id.find(
'.');
282 if (dot != absl::string_view::npos) {
283 return std::string(panel_id.substr(dot + 1));
286 return std::string(panel_id);
290 namespace fs = std::filesystem;
292 std::vector<fs::path> roots;
293 if (
const char* home = std::getenv(
"HOME");
294 home !=
nullptr && std::strlen(home) > 0) {
295 roots.push_back(fs::path(home) /
"src" /
"hobby" /
"oracle-of-secrets");
298 std::vector<fs::path> candidates;
299 std::unordered_set<std::string> seen;
300 auto add_candidate = [&](
const fs::path& path) {
302 if (!fs::exists(path, ec) || ec) {
305 std::string ext = path.extension().string();
306 absl::AsciiStrToLower(&ext);
307 if (ext !=
".yaze" && ext !=
".yazeproj") {
310 const std::string normalized = fs::weakly_canonical(path, ec).string();
311 const std::string key = normalized.empty() ? path.string() : normalized;
312 if (key.empty() || seen.count(key) > 0) {
316 candidates.push_back(path);
319 for (
const auto& root : roots) {
321 if (!fs::exists(root, ec) || ec) {
326 add_candidate(root /
"Oracle-of-Secrets.yaze");
327 add_candidate(root /
"Oracle of Secrets.yaze");
328 add_candidate(root /
"Oracle-of-Secrets.yazeproj");
329 add_candidate(root /
"Oracle of Secrets.yazeproj");
330 add_candidate(root /
"Roms" /
"Oracle of Secrets.yaze");
333 fs::recursive_directory_iterator it(
334 root, fs::directory_options::skip_permission_denied, ec);
335 fs::recursive_directory_iterator end;
339 for (; it != end; it.increment(ec)) {
344 if (it.depth() > 2) {
345 it.disable_recursion_pending();
348 add_candidate(it->path());
352 if (candidates.empty()) {
358 for (
auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
359 manager.AddFile(it->string());
367 std::string normalized = std::string(name);
368 absl::StripAsciiWhitespace(&normalized);
369 const std::string lower = absl::AsciiStrToLower(normalized);
372 const std::string name_candidate =
374 const std::string category_candidate =
376 if (name_candidate == lower || category_candidate == lower) {
410 current_type, &applied_profile)) {
421 std::max(default_width, 480.0f));
425 absl::StrFormat(
"Layout Profile: %s", applied_profile.
label),
444 clear_after_restore)) {
481 bool remove_after_restore) {
551#ifdef YAZE_BUILD_AGENT_UI
552void EditorManager::ShowAIAgent() {
557 "AI Agent is experimental. It remains available; enable experimental "
558 "features in Settings to acknowledge this warning.",
567 for (
const auto& window_id :
573void EditorManager::ShowChatHistory() {
584 project_manager_(&toast_manager_),
585 rom_file_manager_(&toast_manager_) {
586 std::stringstream ss;
587 ss << YAZE_VERSION_MAJOR <<
"." << YAZE_VERSION_MINOR <<
"."
588 << YAZE_VERSION_PATCH;
666 [
this](
const std::string& preset_name) {
670 [
this](
const std::string& preset_name) {
688 return editor_set ? editor_set->GetSettingsPanel() :
nullptr;
691 [
this](
const std::string& prompt) {
692#if defined(YAZE_BUILD_AGENT_UI)
693 auto* agent_editor =
agent_ui_->GetAgentEditor();
697 auto* chat = agent_editor->GetAgentChat();
701 chat->set_active(
true);
702 chat->SendMessage(prompt);
708#if defined(YAZE_BUILD_AGENT_UI)
728 [
this](
const std::string& filepath,
const std::string& contents) {
732 [
this](
const std::string& filepath,
const std::string& contents) {
736 std::make_unique<workflow::ProjectWorkflowOutputPanel>());
741 if (!rom_path.empty()) {
759 absl::StrFormat(
"Failed to reload ROM: %s", status.message()),
770 absl::StrFormat(
"Failed to save project: %s", status.message()),
776 [
this](
const std::string& type) {
778 if (!folder_path.empty()) {
779 if (type ==
"code") {
784#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
785 editor_set->OpenAssemblyFolder(folder_path);
789 }
else if (type ==
"assets") {
797 folder_path.c_str()),
859 return dungeon_editor->IsWorkbenchWorkflowEnabled();
864 [
this](
bool enabled) {
868 dungeon_editor->QueueWorkbenchWorkflowMode(enabled);
872 [
this](
const std::string& category) {
882 registry.Register({
"command_palette",
887 registry.Register({
"keyboard_shortcuts",
888 "Keyboard Shortcuts",
892 registry.Register({
"open_rom_project",
893 "Open ROM / Project",
897 registry.Register({
"settings",
914 for (
auto& panel : registry_panels) {
923 dashboard_definition.
id =
"dashboard.main";
926 dashboard_definition.
category =
"Dashboard";
936 {.card_id =
"dashboard.main",
937 .display_name =
"Dashboard",
938 .window_title =
" Dashboard",
940 .category =
"Dashboard",
942 .shortcut_hint =
"Ctrl+E",
978 std::vector<std::function<void()>> actions_to_execute;
980 const int processed_count =
static_cast<int>(actions_to_execute.size());
981 for (
auto& action : actions_to_execute) {
984 if (processed_count > 0) {
986 processed_count, std::memory_order_relaxed) -
1004 if (e.category.empty() ||
1062 for (
const auto& [type, labels] : src) {
1063 auto& out = (*dst)[type];
1064 for (
const auto& [key, value] : labels) {
1070 merged_labels.clear();
1071 std::vector<std::string> source_parts;
1077 source_parts.push_back(
"rom");
1087 source_parts.push_back(
"registry");
1094 source_parts.push_back(
"project");
1097 auto* label_source = merged_labels.empty() ? &empty_labels : &merged_labels;
1098 std::string source =
1099 source_parts.empty() ?
"empty" : absl::StrJoin(source_parts,
"+");
1101 provider.SetProjectLabels(label_source);
1107 const bool prefer_hmagic =
1111 provider.SetPreferHMagicNames(prefer_hmagic);
1114 "ResourceLabelProvider refreshed (source=%s, project_open=%s)",
1126 if (!index.has_value()) {
1139 if (session->project_context.has_value()) {
1140 session->project_context->feature_flags = session->feature_flags;
1157 if (!index.has_value()) {
1198 if (!index.has_value()) {
1214 bool publish_lyra) {
1216 return absl::OkStatus();
1219 if (session ==
nullptr || !session->editors.HasPendingProjectDraftChanges()) {
1220 return absl::OkStatus();
1223 return absl::FailedPreconditionError(
1224 "Project editor drafts require their project context to be active");
1226 return session->editors.PrepareProjectSave(publish_lyra);
1240 std::optional<size_t> previous_session_id) {
1245 if (index.has_value()) {
1246 auto* previous_session =
1248 if (previous_session) {
1268 size_t previous_session_count) {
1271 return absl::OkStatus();
1274 if (!provisional_session) {
1275 return absl::InternalError(
1276 "A provisional session was created but is not active");
1279 provisional_session->session_id());
1292 const std::string& filepath,
const std::string& contents) {
1297 if (!targets_current) {
1300 return absl::OkStatus();
1303 return absl::FailedPreconditionError(
1304 "Project settings also have unsaved changes; use raw Save As to "
1305 "preserve this draft, then save the project settings");
1311 if (!session || !session->rom.is_loaded()) {
1312 return absl::FailedPreconditionError(
1313 "No active ROM session for this project file");
1319 return absl::InvalidArgumentError(
1320 "Raw project file must contain a non-empty project name");
1326 parsed_project.
rom_filename, session->rom.filename()))) {
1327 return absl::FailedPreconditionError(
1328 "Raw project file cannot change the ROM backing file of a loaded "
1329 "session; use Project Management > Swap ROM");
1336 const std::string& filepath,
const std::string& contents) {
1340 return absl::OkStatus();
1347 return absl::FailedPreconditionError(
"No active ROM session");
1354 session->project_dirty =
false;
1362 return absl::OkStatus();
1370 if (!session || !session->project_file_editor_state.initialized ||
1371 session->project_file_editor_state.modified) {
1378 LOG_WARN(
"EditorManager",
"Failed to rebase raw project editor: %s",
1387absl::StatusOr<const project::YazeProject*>
1390 return absl::FailedPreconditionError(
"No session coordinator");
1393 if (!session || !session->rom.is_loaded()) {
1394 return absl::FailedPreconditionError(
"No active ROM session");
1400 return absl::FailedPreconditionError(
1401 "Active ROM session project context is not restored");
1404 if (!session->project_context.has_value()) {
1405 return absl::FailedPreconditionError(
1406 "Active ROM session has no project/save context");
1408 return &*session->project_context;
1414 case Action::kShowEmulator:
1420 case Action::kShowSettings:
1430 case Action::kShowPanelBrowser:
1436 case Action::kShowSearch:
1442 case Action::kShowShortcuts:
1448 case Action::kShowCommandPalette:
1454 case Action::kShowHelp:
1465 case Action::kShowAgentChatSidebar:
1474 case Action::kShowAgentProposalsSidebar:
1481 case Action::kOpenRom: {
1485 std::string(
"Open failed: ") + std::string(status.message()),
1490 case Action::kSaveRom:
1493 if (!absl::IsCancelled(status) && !status.ok()) {
1495 std::string(
"Save failed: ") + std::string(status.message()),
1503 auto status = current_editor->
Undo();
1506 std::string(
"Undo failed: ") + std::string(status.message()),
1514 auto status = current_editor->
Redo();
1517 std::string(
"Redo failed: ") + std::string(status.message()),
1523 case Action::kResetLayout:
1532#ifdef YAZE_ENABLE_TESTING
1534 test_manager.RegisterTestSuite(
1535 std::make_unique<test::CoreSystemsTestSuite>());
1536 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
1537 test_manager.RegisterTestSuite(
1538 std::make_unique<test::PerformanceTestSuite>());
1539 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
1540 test_manager.RegisterTestSuite(
1541 std::make_unique<test::RomDependentTestSuite>());
1544 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
1545 test_manager.RegisterTestSuite(
1546 std::make_unique<test::ZSCustomOverworldTestSuite>());
1550#ifdef YAZE_ENABLE_GTEST
1551 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
1555#ifdef YAZE_WITH_GRPC
1560 test_manager.UpdateResourceStats();
1564 const std::string& filename) {
1567 SeedOracleProjectInRecents();
1597 const bool prefer_dashboard_only =
1600 if (!prefer_dashboard_only) {
1603 if (!category.empty()) {
1621#ifdef YAZE_ENABLE_TESTING
1632 if (!filename.empty()) {
1653 const std::vector<WindowDefinition> panel_definitions = {
1654 {.id =
"emulator.cpu_debugger",
1655 .display_name =
"CPU Debugger",
1657 .category =
"Emulator",
1660 {.id =
"emulator.ppu_viewer",
1661 .display_name =
"PPU Viewer",
1663 .category =
"Emulator",
1666 {.id =
"emulator.memory_viewer",
1667 .display_name =
"Memory Viewer",
1669 .category =
"Emulator",
1672 {.id =
"emulator.breakpoints",
1673 .display_name =
"Breakpoints",
1675 .category =
"Emulator",
1678 {.id =
"emulator.performance",
1679 .display_name =
"Performance",
1681 .category =
"Emulator",
1684 {.id =
"emulator.ai_agent",
1685 .display_name =
"AI Agent",
1687 .category =
"Emulator",
1690 {.id =
"emulator.save_states",
1691 .display_name =
"Save States",
1693 .category =
"Emulator",
1696 "Command palette (placeholder: Coming Soon)")},
1697 {.id =
"emulator.keyboard_config",
1698 .display_name =
"Keyboard Config",
1700 .category =
"Emulator",
1703 {.id =
"emulator.virtual_controller",
1704 .display_name =
"Virtual Controller",
1706 .category =
"Emulator",
1709 {.id =
"emulator.apu_debugger",
1710 .display_name =
"APU Debugger",
1712 .category =
"Emulator",
1715 {.id =
"emulator.audio_mixer",
1716 .display_name =
"Audio Mixer",
1718 .category =
"Emulator",
1721 {.id =
"memory.hex_editor",
1722 .display_name =
"Hex Editor",
1724 .category =
"Memory",
1728 .legacy_ids = {
"Memory Editor"}},
1736 for (
const auto& definition : panel_definitions) {
1738 descriptor.
card_id = definition.id;
1741 descriptor.
icon = definition.icon;
1742 descriptor.
category = definition.category;
1744 descriptor.
priority = definition.priority;
1745 descriptor.
scope = definition.scope;
1750 descriptor.
on_show = definition.on_show;
1751 descriptor.
on_hide = definition.on_hide;
1753 for (
const auto& legacy_id : definition.legacy_ids) {
1758 if (definition.visible_by_default) {
1779 LOG_WARN(
"EditorManager",
"Failed to load user settings: %s",
1798 [
this](
const std::string& theme_name) {
1804 [
this](
const std::string& locale) {
1818 prefs.switch_motion_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1820 prefs.reduced_motion,
1831 MergeRightDrawerWidthPreferences(
1843 MergeRightDrawerWidthPreferences(
1858 if (ImGui::GetCurrentContext() !=
nullptr) {
1862 "ImGui context not available; skipping FontGlobalScale update");
1869 if (!last_theme.empty() && last_theme !=
"Custom Accent") {
1874#ifdef __EMSCRIPTEN__
1877 LOG_INFO(
"EditorManager",
"WASM Control and Session APIs initialized");
1909 std::optional<EditorType> target;
1915 if (!target.has_value()) {
1937 if (!target.has_value()) {
1979 const std::string& editor_name,
const std::string& panels_str) {
1980 const bool has_editor = !editor_name.empty();
1981 const bool has_panels = !panels_str.empty();
1983 if (!has_editor && !has_panels) {
1988 "Processing startup flags: editor='%s', panels='%s'",
1989 editor_name.c_str(), panels_str.c_str());
1991 std::optional<EditorType> editor_type_to_open =
1993 if (has_editor && !editor_type_to_open.has_value()) {
1994 LOG_WARN(
"EditorManager",
"Unknown editor specified via flag: %s",
1995 editor_name.c_str());
1996 }
else if (editor_type_to_open.has_value()) {
2008 bool applied_category_from_panel =
false;
2010 for (absl::string_view token :
2011 absl::StrSplit(panels_str,
',', absl::SkipWhitespace())) {
2012 if (token.empty()) {
2015 std::string panel_name = std::string(absl::StripAsciiWhitespace(token));
2016 LOG_DEBUG(
"EditorManager",
"Attempting to open panel: '%s'",
2017 panel_name.c_str());
2019 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2020 if (lower_name ==
"welcome" || lower_name ==
"welcome_screen") {
2026 if (lower_name ==
"dashboard" || lower_name ==
"dashboard.main" ||
2027 lower_name ==
"editor_selection") {
2041 if (absl::StartsWith(panel_name,
"Room ")) {
2044 int room_id = std::stoi(panel_name.substr(5));
2047 }
catch (
const std::exception& e) {
2048 LOG_WARN(
"EditorManager",
"Invalid room ID format: %s",
2049 panel_name.c_str());
2055 std::optional<std::string> resolved_panel;
2057 resolved_panel = panel_name;
2059 for (
const auto& [prefixed_id, descriptor] :
2061 const std::string base_id = StripSessionPrefix(prefixed_id);
2062 const std::string card_lower = absl::AsciiStrToLower(base_id);
2063 const std::string display_lower =
2064 absl::AsciiStrToLower(descriptor.display_name);
2066 if (card_lower == lower_name || display_lower == lower_name) {
2067 resolved_panel = base_id;
2073 if (!resolved_panel.has_value()) {
2075 "Unknown panel '%s' from --open_panels (known count: %zu)",
2082 const auto* descriptor =
2084 if (descriptor !=
nullptr) {
2088 if (!ensure_status.ok()) {
2089 LOG_WARN(
"EditorManager",
"Failed to load assets for panel '%s': %s",
2090 resolved_panel->c_str(), ensure_status.message().data());
2093 if (descriptor && !applied_category_from_panel &&
2096 applied_category_from_panel =
true;
2097 }
else if (!applied_category_from_panel && descriptor &&
2098 descriptor->category.empty() && !last_known_category.empty()) {
2102 LOG_WARN(
"EditorManager",
"Failed to show panel '%s'",
2103 resolved_panel->c_str());
2124 constexpr int kTargetRevision =
2126 const int previous_revision =
2132 const bool includes_workspace_reset_revision =
2143 "Applied panel layout defaults migration %d -> %d (%s)",
2144 previous_revision, kTargetRevision,
2145 includes_workspace_reset_revision ?
"workspace reset"
2146 :
"targeted preferences");
2150 const std::string& saved_category,
2151 const std::vector<std::string>& available_categories)
const {
2152 const std::string preferred =
2154 if (preferred.empty()) {
2172 const bool sidebar_visible =
2199 std::string panels_str;
2200 for (
size_t i = 0; i < config.
open_panels.size(); ++i) {
2247 if (index < session->editor_initialized.size()) {
2257 if (index < session->editor_assets_loaded.size()) {
2279 const auto visibility_it =
2283 visibility_it->second);
2321 std::unordered_set<EditorType> types;
2336 auto add_category = [&](
const std::string& category) {
2337 if (category.empty() ||
2345 bool used_startup_hints =
false;
2349 add_type(*startup_type);
2350 used_startup_hints =
true;
2355 if (panel_name.empty()) {
2359 if (
const auto* descriptor =
2361 add_category(descriptor->category);
2362 used_startup_hints =
true;
2366 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2367 for (
const auto& [prefixed_id, descriptor] :
2369 const std::string base_id = StripSessionPrefix(prefixed_id);
2370 const std::string card_lower = absl::AsciiStrToLower(base_id);
2371 const std::string display_lower =
2372 absl::AsciiStrToLower(descriptor.display_name);
2373 if (card_lower == lower_name || display_lower == lower_name) {
2374 add_category(descriptor.category);
2375 used_startup_hints =
true;
2381 if (used_startup_hints) {
2382 return std::vector<EditorType>(types.begin(), types.end());
2391 if (editor !=
nullptr && *editor->active()) {
2392 add_type(editor->type());
2403 for (
const auto& window_id :
2405 if (
const auto* descriptor =
2407 add_category(descriptor->category);
2411 return std::vector<EditorType>(types.begin(), types.end());
2416 return editor_set ? editor_set->
GetEditor(type) :
nullptr;
2420 if (category.empty() ||
2433#ifdef YAZE_BUILD_AGENT_UI
2448 const std::string& category) {
2452 if (editor ==
nullptr) {
2484 .id =
"experimental_editor",
2486 .message = absl::StrFormat(
"%s is experimental; save posture: %s",
2497 }
else if (!category.empty() &&
2499 LOG_DEBUG(
"EditorManager",
"No editor context available for category '%s'",
2508 return absl::FailedPreconditionError(
"No editor set available");
2513 return absl::OkStatus();
2515 editor->Initialize();
2516 return absl::OkStatus();
2524 return absl::FailedPreconditionError(
"No active session");
2526 if (session->game_data_loaded) {
2527 return absl::OkStatus();
2529 if (!session->rom.is_loaded()) {
2530 return absl::FailedPreconditionError(
"ROM not loaded");
2534 session->ReloadGraphicsDisplay();
2536 auto* game_data = &session->game_data;
2537 auto* editor_set = &session->editors;
2538 editor_set->SetGameData(game_data);
2542 session->game_data_loaded =
true;
2544 return absl::OkStatus();
2549 return absl::OkStatus();
2556 return absl::OkStatus();
2559 if (!session->rom.is_loaded()) {
2561 return absl::OkStatus();
2564 if (index >= session->editor_initialized.size()) {
2565 return absl::InvalidArgumentError(
"Invalid editor type");
2568 return absl::OkStatus();
2570 bool initialized_now =
false;
2571 if (!session->editor_initialized[index]) {
2575 initialized_now =
true;
2577 if (initialized_now) {
2580 return absl::OkStatus();
2584 if (index >= session->editor_initialized.size()) {
2585 return absl::InvalidArgumentError(
"Invalid editor type");
2592 bool initialized_now =
false;
2593 if (!session->editor_initialized[index]) {
2597 initialized_now =
true;
2604 bool loaded_now =
false;
2605 if (!session->editor_assets_loaded[index]) {
2614 if (initialized_now || loaded_now) {
2618 return absl::OkStatus();
2654 return absl::OkStatus();
2663 if (!current_rom || !current_editor_set) {
2664 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
2668 if (!current_session) {
2669 return absl::FailedPreconditionError(
"No active ROM session");
2673 auto start_time = std::chrono::steady_clock::now();
2675#ifdef __EMSCRIPTEN__
2677 auto loading_handle =
2679 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
2681 : app::platform::WasmLoadingManager::BeginLoading(
2682 "Loading Editor Assets");
2685 constexpr float kStartProgress = 0.10f;
2686 constexpr float kEndProgress = 1.0f;
2687 constexpr int kTotalSteps = 11;
2688 int current_step = 0;
2689 auto update_progress = [&](
const std::string& message) {
2692 kStartProgress + (kEndProgress - kStartProgress) *
2693 (
static_cast<float>(current_step) / kTotalSteps);
2694 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, progress);
2695 app::platform::WasmLoadingManager::UpdateMessage(loading_handle, message);
2698 auto cleanup_loading = [&]() {
2699 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2701 struct LoadingGuard {
2702 std::function<void()> cleanup;
2703 bool dismissed =
false;
2708 void dismiss() { dismissed =
true; }
2709 } loading_guard{cleanup_loading};
2711 (void)passed_handle;
2720 const std::unordered_set<EditorType> preload_types(
2721 preload_editor_list.begin(), preload_editor_list.end());
2729 const InitStep init_steps[] = {
2736 for (
const auto& step : init_steps) {
2737 if (!preload_types.contains(step.type)) {
2740 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
2741 editor->Initialize();
2743 if (step.mark_loaded) {
2749#ifdef __EMSCRIPTEN__
2750 update_progress(
"Loading graphics sheets...");
2756 current_session->game_data_loaded =
true;
2758 current_session->ReloadGraphicsDisplay();
2762 auto* game_data = ¤t_session->game_data;
2763 current_editor_set->SetGameData(game_data);
2768 const char* progress_message;
2770 const LoadStep load_steps[] = {
2780 for (
const auto& step : load_steps) {
2781 if (!preload_types.contains(step.type)) {
2784#ifdef __EMSCRIPTEN__
2785 update_progress(step.progress_message);
2787 if (
auto* editor = current_editor_set->GetEditor(step.type)) {
2793#ifdef __EMSCRIPTEN__
2794 update_progress(
"Finishing up...");
2799 auto* settings = current_editor_set->GetSettingsPanel();
2810#ifdef __EMSCRIPTEN__
2812 loading_guard.dismiss();
2813 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2816 auto end_time = std::chrono::steady_clock::now();
2817 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
2818 end_time - start_time);
2819 LOG_DEBUG(
"EditorManager",
"ROM assets loaded in %lld ms", duration.count());
2821 return absl::OkStatus();
2827 if (!current_rom || !current_editor_set) {
2828 return absl::FailedPreconditionError(
"No ROM or editor set loaded");
2832 if (!current_session) {
2833 return absl::FailedPreconditionError(
"No active ROM session");
2837#ifdef __EMSCRIPTEN__
2839 auto loading_handle =
2841 ?
static_cast<app::platform::WasmLoadingManager::LoadingHandle
>(
2843 : app::platform::WasmLoadingManager::BeginLoading(
2844 "Loading ROM (lazy assets)");
2845 auto cleanup_loading = [&]() {
2846 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2848 struct LoadingGuard {
2849 std::function<void()> cleanup;
2850 bool dismissed =
false;
2856 void dismiss() { dismissed =
true; }
2857 } loading_guard{cleanup_loading};
2859 (void)passed_handle;
2869 auto* settings = current_editor_set->GetSettingsPanel();
2876#ifdef __EMSCRIPTEN__
2877 loading_guard.dismiss();
2878 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2881 LOG_INFO(
"EditorManager",
"Lazy asset mode: editor assets deferred");
2882 return absl::OkStatus();
2901 const std::optional<std::string>& target_filename) {
2931 for (
const char* popup :
2941 const std::optional<std::string>& save_as_filename) {
2945 return absl::FailedPreconditionError(
"No active ROM session");
2951 return absl::OkStatus();
2958 if (status.ok() || !absl::IsCancelled(status) ||
2970 return absl::FailedPreconditionError(
"No active ROM session");
2975 return absl::FailedPreconditionError(
2976 "Expanded message source has unpublished edits; use Preview and "
2977 "Publish in the Message editor before saving");
2983 return absl::FailedPreconditionError(
2984 "The active session changed while saving project work");
2997 if (session ==
nullptr || !session->native_save_completion_pending ||
3000 return absl::OkStatus();
3003 session->native_save_completion_save_as,
3020 const bool unresolved_confirmation =
3025 if (unresolved_confirmation) {
3026 return absl::CancelledError(
"Save pending confirmation");
3040 if (filename.empty()) {
3041 return absl::InvalidArgumentError(
"No filename provided for save as");
3044 return absl::CancelledError(
"Save pending confirmation");
3055 return absl::FailedPreconditionError(
"No pending ROM save to resume");
3059 return absl::FailedPreconditionError(
3060 "Pending ROM save belongs to a different session");
3070#ifdef YAZE_WITH_JSON
3071 using nlohmann::json;
3073 json response = {{
"protocol",
"yaze-editor-control/v1"},
3075 {
"http_status", 200}};
3076 response[
"capabilities"][
"commands"] = {
"status",
3082 "overworld.stencil.apply"};
3084 const auto pid = _getpid();
3086 const auto pid = getpid();
3088 const auto rom_sha256 = [](
const Rom& rom) {
3090 reinterpret_cast<const char*
>(rom.
data()), rom.
size()));
3092 auto fail = [&](
int code,
const std::string& message) {
3093 response[
"status"] =
"error";
3094 response[
"http_status"] = code;
3095 response[
"error"] = message;
3098 const auto body = json::parse(request);
3099 const std::string command = body.at(
"command").get<std::string>();
3100 response[
"command"] = command;
3101 auto path_argument = [&]() -> std::string {
3102 const auto path = body.at(
"path").get<std::string>();
3103 if (path.empty() || path.find(
'\0') != std::string::npos ||
3104 !std::filesystem::path(path).is_absolute()) {
3105 throw std::invalid_argument(
"path must be an absolute local path");
3115 const auto hash_argument = [](
const json& value) {
3116 const auto hash = value.get<std::string>();
3117 if (hash.size() != 64 ||
3118 !std::all_of(hash.begin(), hash.end(), [](
char ch) {
3119 return (ch >=
'0' && ch <=
'9') || (ch >=
'a' && ch <=
'f');
3121 throw std::invalid_argument(
3122 "SHA256 must contain 64 lowercase hexadecimal characters");
3128 const auto expected_editor_matches = [&](
const json& expected) {
3129 for (
const auto* field : {
"pid",
"session_id"}) {
3130 if (!expected.at(field).is_number_integer() || expected.at(field) < 0 ||
3131 (std::string_view(field) ==
"pid" && expected.at(field) == 0)) {
3132 throw std::invalid_argument(
3133 "expected pid and session_id must be valid nonnegative integers");
3136 const auto expected_path = [&](
const char* field) {
3137 const auto value = expected.at(field).get<std::string>();
3138 if (value.empty() || value.find(
'\0') != std::string::npos ||
3139 !std::filesystem::path(value).is_absolute()) {
3140 throw std::invalid_argument(std::string(
"expected ") + field +
3141 " must be an absolute local path");
3145 const auto project_path = expected_path(
"project_path");
3146 const auto current_rom_path = expected_path(
"rom_path");
3147 const auto current_sha256 = hash_argument(expected.at(
"rom_sha256"));
3150 expected.at(
"pid") == pid &&
3156 current_sha256 == rom_sha256(*rom);
3158 if (command ==
"status") {
3160 }
else if (command ==
"project.open") {
3161 const auto path = path_argument();
3162 const auto extension = std::filesystem::path(path).extension();
3163 if (extension !=
".yaze" && extension !=
".zsproj" &&
3164 extension !=
".yazeproj") {
3165 fail(400,
"project.open requires a .yaze, .zsproj or .yazeproj path");
3173 fail(422, std::string(status.message()));
3175 }
else if (command ==
"project.rom.swap") {
3176 const auto path = path_argument();
3177 const auto candidate_sha256 = hash_argument(body.at(
"candidate_sha256"));
3178 const bool identity_matches =
3179 expected_editor_matches(body.at(
"expected"));
3182 fail(409,
"ROM swap blocked by pending work or action: " +
3184 }
else if (!identity_matches) {
3186 "ROM swap expected identity does not match the active editor");
3190 fail(absl::IsAborted(status) ? 409 : 422,
3191 std::string(status.message()));
3194 }
else if (command ==
"overworld.stencil.apply") {
3195 const auto path = path_argument();
3196 const auto stencil_sha256 = hash_argument(body.at(
"stencil_sha256"));
3197 const bool identity_matches =
3198 expected_editor_matches(body.at(
"expected"));
3199 std::ifstream file(path, std::ios::binary);
3200 const std::string bytes((std::istreambuf_iterator<char>(file)),
3201 std::istreambuf_iterator<char>());
3202 if (!file.good() && !file.eof()) {
3203 fail(422,
"Stencil file could not be read");
3205 fail(409,
"Stencil file does not match stencil_sha256");
3208 fail(409,
"Stencil blocked by pending work or action: " +
3210 }
else if (!identity_matches) {
3211 fail(409,
"Stencil expected identity does not match the active editor");
3213 const auto stencil = json::parse(bytes);
3214 if (stencil.at(
"schema").get<std::string>() !=
3215 "yaze.overworld_tile16_stencil.v1") {
3216 throw std::invalid_argument(
3217 "Stencil schema must be yaze.overworld_tile16_stencil.v1");
3219 const int map_id = stencil.at(
"map_id").get<
int>();
3220 std::vector<GuardedTileCell> cells;
3221 for (
const auto& cell : stencil.at(
"cells")) {
3222 const auto tile = [&](
const char* field) {
3223 const int value = cell.at(field).get<
int>();
3224 if (value < 0 || value > 0xFFFF) {
3225 throw std::invalid_argument(std::string(
"Stencil ") + field +
3226 " must be a Tile16 ID");
3228 return static_cast<uint16_t
>(value);
3230 cells.push_back({cell.at(
"x").get<
int>(), cell.at(
"y").get<
int>(),
3231 tile(
"expected"), tile(
"proposed")});
3240 fail(422, std::string(loaded.message()));
3242 fail(503,
"Overworld editor is unavailable");
3245 const auto changed =
overworld->ApplyTile16Stencil(map_id, cells);
3246 if (!changed.ok()) {
3247 fail(absl::IsFailedPrecondition(changed.status()) ? 409 : 422,
3248 std::string(changed.status().message()));
3250 response[
"map_id"] = map_id;
3251 response[
"cells"] = cells.size();
3252 response[
"cells_changed"] = *changed;
3253 response[
"stencil_sha256"] = stencil_sha256;
3257 }
else if (command ==
"dungeon.select" || command ==
"proposal.load") {
3260 if (command ==
"dungeon.select") {
3261 if (!body.at(
"room").is_number_integer()) {
3262 throw std::invalid_argument(
"room must be an integer from 0 to 295");
3264 room = body.at(
"room").get<
int>();
3265 if (room < 0 || room >= 296) {
3266 throw std::invalid_argument(
"room must be an integer from 0 to 295");
3269 path = path_argument();
3272 fail(409,
"No ROM loaded");
3276 fail(422, std::string(loaded.message()));
3277 }
else if (
auto* dungeon = dungeon_editor()) {
3279 if (command ==
"dungeon.select") {
3280 dungeon->add_room(room);
3281 dungeon->FocusRoom(room);
3282 if (dungeon->current_room_id() != room) {
3283 fail(422,
"Dungeon room selection did not complete");
3286 const auto status = dungeon->LoadProposalForReview(path);
3288 fail(422, std::string(status.message()));
3291 fail(503,
"Dungeon editor is unavailable");
3294 }
else if (command ==
"panel.hide") {
3295 const auto id = body.at(
"panel").get<std::string>();
3297 fail(404,
"Unknown panel ID for the active session: " +
id);
3300 response[
"panel"] = id;
3302 if (response[
"panel_visible"].get<bool>()) {
3303 fail(409,
"Panel did not close");
3307 fail(400,
"Unknown editor command: " + command);
3309 }
catch (
const std::exception& error) {
3310 fail(400, error.what());
3317 state[
"session_count"] =
3319 state[
"sessions"] = json::array();
3324 const auto* project =
3327 : session->project_context.has_value() ? &*session->project_context
3329 json item = {{
"session_id", session->session_id()},
3331 {
"loaded", session->rom.is_loaded()},
3332 {
"project_path", project ? project->
filepath :
""},
3333 {
"rom_dirty", session->rom.dirty()},
3335 item[
"rom_path"] = session->rom.is_loaded()
3336 ? json(session->rom.filename())
3338 state[
"sessions"].push_back(std::move(item));
3345 state[
"rom_path"] = rom->
filename();
3346 state[
"rom_title"] = rom->
title();
3347 state[
"rom_size"] = rom->
size();
3348 state[
"rom_dirty"] = rom->
dirty();
3349 state[
"rom_sha256"] = rom_sha256(*rom);
3351 state[
"rom_path"] =
nullptr;
3352 state[
"rom_sha256"] =
nullptr;
3354 state[
"room"] =
nullptr;
3355 state[
"proposal_path"] =
nullptr;
3359 state[
"room"] = dungeon->current_room_id();
3360 state[
"proposal_path"] = dungeon->proposal_review_path();
3363 state[
"visible_panels"] = json::array();
3364 for (
const auto&
id :
3367 state[
"visible_panels"].push_back(
id);
3370 response[
"state"] = std::move(state);
3371 return response.dump();
3373 return R
"({"status":"error","http_status":503,"error":"JSON support unavailable"})";
3378 const std::string& filename) {
3384 const bool is_project = absl::EndsWith(filename,
".yaze") ||
3385 absl::EndsWith(filename,
".zsproj") ||
3386 absl::EndsWith(filename,
".yazeproj");
3389 for (
size_t step = 0; step < count; ++step) {
3393 if (!session || !session->rom.is_loaded()) {
3396 bool matches =
false;
3398 const auto* project =
3401 : session->project_context.has_value() ? &*session->project_context
3405 filename, project->filepath);
3408 filename, session->filepath) ||
3410 filename, session->rom.filename());
3425 return absl::OkStatus();
3430 return absl::OkStatus();
3438 LOG_INFO(
"EditorManager",
"OpenRomOrProject called with: '%s'",
3440 if (filename.empty()) {
3441 LOG_INFO(
"EditorManager",
"Empty filename provided, skipping load.");
3442 return absl::OkStatus();
3445 return absl::OkStatus();
3448#ifdef __EMSCRIPTEN__
3450 auto loading_handle =
3451 app::platform::WasmLoadingManager::BeginLoading(
"Loading ROM");
3452 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3453 "Reading ROM file...");
3455 struct LoadingGuard {
3456 app::platform::WasmLoadingManager::LoadingHandle handle;
3457 bool dismissed =
false;
3460 app::platform::WasmLoadingManager::EndLoading(handle);
3462 void dismiss() { dismissed =
true; }
3463 } loading_guard{loading_handle};
3466 if (absl::EndsWith(filename,
".yaze") ||
3467 absl::EndsWith(filename,
".zsproj") ||
3468 absl::EndsWith(filename,
".yazeproj")) {
3477 const std::optional<size_t> previous_session_id =
3488 if (!project_status.ok()) {
3491 return project_status;
3493#ifdef __EMSCRIPTEN__
3494 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.05f);
3495 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3496 "Loading ROM data...");
3503 const std::optional<size_t> previous_session_id =
3505 const size_t previous_session_count =
3508 std::move(temp_rom), filename);
3509 if (!session_or.ok()) {
3510 return session_or.status();
3524 const bool remembered_load_options = allow_dialogs &&
3528 const bool ask_for_load_options =
3539#ifdef YAZE_ENABLE_TESTING
3540 LOG_DEBUG(
"EditorManager",
"Setting ROM in TestManager - %p ('%s')",
3548 const std::string absolute_code_folder =
3553#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3554 editor_set->OpenAssemblyFolder(absolute_code_folder);
3560#ifdef __EMSCRIPTEN__
3561 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.10f);
3562 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3563 "Initializing editors...");
3566 loading_guard.dismiss();
3571 if (!asset_status.ok()) {
3572 auto rollback_status =
3575 if (!rollback_status.ok()) {
3576 return absl::InternalError(
3577 absl::StrFormat(
"%s; session rollback failed: %s",
3578 asset_status.message(), rollback_status.message()));
3580 return asset_status;
3583 bool is_first_time_rom_path =
false;
3584 if (ask_for_load_options) {
3585 const auto& recent_files =
3587 is_first_time_rom_path =
3588 std::find(recent_files.begin(), recent_files.end(), filename) ==
3592 is_first_time_rom_path);
3595 return absl::OkStatus();
3600 return absl::FailedPreconditionError(
3601 "Save or discard pending session work before creating a project");
3605 return absl::FailedPreconditionError(
3606 "Project creation dialog is not available");
3609 template_name.empty() ?
"Vanilla ROM Hack" : template_name);
3610 return absl::OkStatus();
3614 const std::string& template_name,
const std::string& rom_path,
3615 const std::string& project_name,
const std::string& project_path) {
3616 if (rom_path.empty() || project_name.empty()) {
3617 return absl::InvalidArgumentError(
"ROM path and project name are required");
3619 if (absl::EndsWith(rom_path,
".yaze") ||
3620 absl::EndsWith(rom_path,
".yazeproj") ||
3621 absl::EndsWith(rom_path,
".zsproj")) {
3622 return absl::InvalidArgumentError(
3623 "New projects require a ROM file, not a project descriptor");
3626 return absl::FailedPreconditionError(
3627 "Save or discard pending session work before creating a project");
3633 const bool reuse_active_raw_session =
3634 reusable_session && reusable_session->rom.is_loaded() &&
3636 rom_path, reusable_session->rom.filename()) &&
3639 const std::optional<size_t> previous_session_id =
3641 const size_t previous_session_count =
3645 if (!rom_status.ok()) {
3650 project_name, project_path);
3651 if (!target_status.ok()) {
3653 return target_status;
3656 if (reuse_active_raw_session) {
3667 if (!open_status.ok()) {
3674 if (!finalize_status.ok()) {
3675 auto rollback_status =
3678 if (!rollback_status.ok()) {
3679 return absl::InternalError(absl::StrFormat(
3680 "%s; session rollback failed: %s", finalize_status.message(),
3681 rollback_status.message()));
3684 return finalize_status;
3688 const std::string& project_name,
const std::string& project_path) {
3690 return absl::FailedPreconditionError(
"No session coordinator");
3693 if (!session || !session->rom.is_loaded()) {
3694 return absl::FailedPreconditionError(
3695 "Load a ROM before finalizing the project");
3702 if (created_project_shell) {
3706 auto project_rom_status =
3708 if (!project_rom_status.ok()) {
3710 return project_rom_status;
3713 if (created_project_shell) {
3719 pending_project.workspace_settings.font_global_scale =
3721 pending_project.workspace_settings.autosave_enabled =
3723 pending_project.workspace_settings.autosave_interval_secs =
3725 pending_project.workspace_settings.backup_on_save =
3728 auto finalize_status =
3730 if (!finalize_status.ok()) {
3732 return finalize_status;
3737 session->project_dirty =
false;
3751 return absl::OkStatus();
3758 return absl::OkStatus();
3765 auto open_project_from_path =
3766 [
this](
const std::string& file_path) -> absl::Status {
3767 if (file_path.empty()) {
3768 return absl::OkStatus();
3771 return absl::OkStatus();
3783 auto validation_status = new_project.
Validate();
3784 if (!validation_status.ok()) {
3786 validation_status.message()),
3793 const std::optional<size_t> previous_session_id =
3802 if (!project_status.ok()) {
3805 return project_status;
3808#if defined(__APPLE__) && TARGET_OS_IOS == 1
3812 return absl::OkStatus();
3815 return open_project_from_path(file_path);
3820 const std::string& rom_path) {
3829 .candidate_path = rom_path,
3831 return absl::OkStatus();
3835 const std::string& rom_path,
3836 const std::optional<std::string>& expected_sha256) {
3837 if (rom_path.empty()) {
3838 return absl::InvalidArgumentError(
"ROM path cannot be empty");
3841 return absl::FailedPreconditionError(
"No session coordinator");
3844 if (!session || !session->rom.is_loaded() ||
3846 return absl::FailedPreconditionError(
"No active project ROM session");
3849 session->editors.HasPendingProjectDraftChanges()) {
3850 return absl::FailedPreconditionError(
3851 "Save or discard ROM or project edits before swapping the project ROM");
3854 if (session->project_file_editor_state.initialized &&
3855 session->project_file_editor_state.modified &&
3857 return absl::FailedPreconditionError(
3858 "Preserve the raw project-file draft before swapping ROMs");
3862 rom_path, session->session_id()));
3863 Rom replacement_rom;
3867 if (expected_sha256 &&
3870 reinterpret_cast<const char*
>(replacement_rom.
data()),
3871 replacement_rom.
size()))) {
3872 return absl::AbortedError(
"Loaded candidate ROM SHA256 does not match");
3886 std::string replacement_path = rom_path;
3895 const auto development_path = std::filesystem::canonical(
3900 return absl::FailedPreconditionError(absl::StrFormat(
3901 "Cannot resolve the manifest development ROM before rebinding: %s",
3904 replacement_path = development_path.string();
3911 if (!save_status.ok()) {
3917 auto replace_status =
3919 if (!replace_status.ok()) {
3924 if (!rollback_status.ok()) {
3925 return absl::InternalError(absl::StrFormat(
3926 "ROM swap failed (%s) and restoring the project descriptor failed "
3928 replace_status.message(), rollback_status.message()));
3930 return replace_status;
3932 session->project_dirty =
false;
3938 return absl::OkStatus();
3943 return absl::FailedPreconditionError(
"No session coordinator");
3946 if (!session || !session->rom.is_loaded() ||
3949 return absl::FailedPreconditionError(
"No active project ROM to reload");
3952 session->editors.HasPendingProjectDraftChanges()) {
3953 return absl::FailedPreconditionError(
3954 "Save or discard ROM or project edits before reloading from disk");
3959 Rom replacement_rom;
3969 return absl::FailedPreconditionError(
3970 "Save a Yaze project before creating or opening Lyra terrain");
3973 return absl::FailedPreconditionError(
3974 "Lyra terrain requires its project session to be active");
3978 return absl::FailedPreconditionError(
3979 "Load the project's ROM before creating or opening Lyra terrain");
3981 return absl::OkStatus();
3990 return absl::NotFoundError(
"Overworld editor is unavailable");
3995 overworld->HasPendingProjectDraftChanges()) {
4000 return absl::OkStatus();
4006 options.
filters.push_back({
"Lyra terrain source",
"json"});
4013 return absl::OkStatus();
4020 return absl::NotFoundError(
"Overworld editor is unavailable");
4029 return absl::OkStatus();
4036 session && session->project_file_editor_state.initialized &&
4037 session->project_file_editor_state.modified &&
4039 return absl::FailedPreconditionError(
4040 "Raw project file also has unsaved changes; use raw Save As to "
4041 "preserve that draft before saving project settings");
4050 return save_session->editors.SavePlanningProjectDraft();
4055 const bool build_owned =
4071 editors->ProjectSaveCommitted();
4077 session->project_dirty =
false;
4083 if (!status.ok() || !build_owned) {
4086 save_session->native_save_completion_pending =
true;
4088 return absl::OkStatus();
4094 bool include_recent_files) {
4111 if (!include_recent_files) {
4116 for (
const auto& file : manager.GetRecentFiles()) {
4135 if (!serialized.ok() || !input.is_open()) {
4138 const std::string on_disk((std::istreambuf_iterator<char>(input)),
4139 std::istreambuf_iterator<char>());
4140 return !input.bad() && on_disk == *serialized;
4159 session->project_dirty =
true;
4170 return session !=
nullptr &&
4171 (session->project_dirty ||
4172 session->editors.HasPendingProjectDraftChanges());
4177 return absl::FailedPreconditionError(
"No session coordinator");
4181 return absl::FailedPreconditionError(
"No active ROM session");
4189 if (session->editors.HasPendingSpriteChanges()) {
4192 RETURN_IF_ERROR(allow_dialogs ? sprite->Save() : sprite->SaveForAutosave());
4196 const bool project_dirty = session->project_dirty ||
4197 session->editors.HasPendingProjectDraftChanges();
4198 const bool project_file_dirty =
4199 session->project_file_editor_state.initialized &&
4200 session->project_file_editor_state.modified;
4202 if (project_dirty && project_file_dirty &&
4204 return absl::FailedPreconditionError(
4205 "Project settings and raw project file both have unsaved changes; "
4206 "use raw Save As to preserve the draft, then save project settings");
4209 if (project_dirty) {
4212 return absl::FailedPreconditionError(
4213 "Project settings have no project file to save");
4218 if (project_file_dirty) {
4220 return absl::FailedPreconditionError(
4221 "Project file draft has no destination; use Save As first");
4227 return absl::OkStatus();
4232 return absl::FailedPreconditionError(
"No session coordinator");
4237 if (session !=
nullptr && session->backup_restore_pending) {
4238 return absl::CancelledError(
4239 "Staged backup restore requires an explicit Save ROM");
4247 return absl::FailedPreconditionError(
4248 "Autosave left unsaved project or ROM work");
4250 return absl::OkStatus();
4256 auto lifecycle_decision =
4258 static_cast<int>(decision));
4268 if (!absl::IsCancelled(status) && !status.ok()) {
4270 absl::StrFormat(
"Failed to save ROM: %s", status.message()),
4279 :
"untitled_project";
4283 if (file_path.empty()) {
4284 return absl::OkStatus();
4291 if (filepath.empty()) {
4292 return absl::InvalidArgumentError(
"Project file path cannot be empty");
4295 std::string file_path = filepath;
4298 if (!(absl::EndsWith(file_path,
".yaze") ||
4299 absl::EndsWith(file_path,
".yazeproj"))) {
4300 file_path +=
".yaze";
4307 editors && editors->HasPendingPlanningProjectDraftChanges()) {
4308 return absl::FailedPreconditionError(
4309 "Save or discard plan edits before saving the project to another file");
4314 session && session->project_file_editor_state.initialized &&
4315 session->project_file_editor_state.modified &&
4317 session->project_file_editor_state.filepath, file_path)) {
4318 return absl::FailedPreconditionError(
4319 "Raw project draft targets the selected file; preserve it with "
4320 "raw Save As before overwriting that destination");
4334 : absl::FailedPreconditionError(
4335 "Project Lyra terrain is unavailable");
4344 lyra_editor ==
nullptr));
4348 absl::Status save_status;
4356 if (save_status.ok()) {
4358 editors->ProjectSaveCommitted();
4364 session->project_dirty =
false;
4372 manager.AddFile(file_path);
4378 absl::IsInvalidArgument(catalog_path.status())) {
4381 "Project saved as: %s. Its palette catalog cannot load from this "
4382 "folder. Copy the catalog into the new project folder, then "
4383 "update palette_catalog_file in Edit Project File.",
4394 absl::StrFormat(
"Failed to save project: %s", save_status.message()),
4400 if (!save_status.ok()) {
4404 if (!retirement.ok()) {
4405 return absl::FailedPreconditionError(
4406 "Project saved as, but its retired native-save context could not be "
4408 std::string(retirement.message()));
4415 session->native_save_completion_pending =
true;
4416 session->native_save_completion_save_as =
true;
4423 return absl::FailedPreconditionError(
"No project open");
4427 return absl::PermissionDeniedError(
4428 "Build Project is disabled by the project's read-only policy");
4435 const auto* dungeon =
4437 if (dungeon && dungeon->HasUnpublishedMinecartTrackChanges()) {
4438 return absl::FailedPreconditionError(
4439 "Minecart tracks have unpublished changes. Use Publish Tracks, then "
4440 "save remaining project and ROM changes before Build Project. "
4441 "Undo the track edits if you do not want to publish them.");
4446 return absl::FailedPreconditionError(
4447 absl::StrFormat(
"Save project and ROM changes before building: %s",
4452 if (active_rom !=
nullptr && active_rom->
is_loaded() &&
4457 return absl::FailedPreconditionError(absl::StrFormat(
4458 "Build source mismatch: active ROM %s differs from project ROM %s. "
4459 "Use Project Management > Swap ROM to bind the saved copy, or reopen "
4460 "the project's ROM before building.",
4472 return absl::FailedPreconditionError(absl::StrFormat(
4473 "Build source mismatch: manifest development ROM %s differs from "
4478 command = pipeline.build_script;
4481 if (!command.empty() &&
4482 command.find_first_of(
" \t\r\n|&;<>()$`'\"\\") == std::string::npos) {
4483 const std::filesystem::path script_path(command);
4484 if (script_path.is_absolute() || script_path.has_parent_path() ||
4485 script_path.extension() ==
".sh") {
4486 const std::string absolute_script =
4489 if (!std::filesystem::is_regular_file(absolute_script, ec)) {
4490 return absl::FailedPreconditionError(absl::StrFormat(
4491 "Manifest build script is unavailable: %s. Configure a build "
4492 "script for this project before building.",
4498 if (command.empty()) {
4499 return absl::NotFoundError(
"Project does not define a build command");
4506 return absl::FailedPreconditionError(
"No project open");
4520 if (target.empty()) {
4523 if (target.empty()) {
4524 return absl::NotFoundError(
"Project does not define a run target ROM");
4530 const std::string& summary,
const std::string& detail,
4532 bool can_cancel)
const {
4534 .can_cancel = can_cancel,
4538 .output_tail = output_tail,
4543 const std::string& summary,
const std::string& detail,
4554 if (!command_or.ok()) {
4555 return command_or.status();
4558 const std::string command = *command_or;
4559 const std::filesystem::path project_root =
4565 auto start_status = task.
Start(command, project_root.string(), environment);
4566 if (!start_status.ok()) {
4567 return start_status;
4569 auto wait_status = task.
Wait();
4571 if (!wait_status.ok()) {
4574 const std::string summary = LastNonEmptyLine(snapshot.output);
4575 return summary.empty() ? command : summary;
4579 const std::string running_detail =
4580 "Running the configured project build command";
4581 UpdateBuildWorkflowStatus(
4587 if (!result_or.ok()) {
4588 const bool unavailable =
4589 result_or.status().code() == absl::StatusCode::kFailedPrecondition ||
4590 result_or.status().code() == absl::StatusCode::kNotFound;
4591 UpdateBuildWorkflowStatus(
4594 std::string(result_or.status().message()),
4597 absl::StrFormat(
"%s: %s",
4598 unavailable ?
"Build unavailable" :
"Build failed",
4599 result_or.status().message()),
4601 return result_or.status();
4604 const std::string summary = *result_or;
4609 summary.empty() ?
"Project build completed"
4610 : absl::StrFormat(
"Project build completed: %s", summary),
4612 LOG_INFO(
"EditorManager",
"Project build completed: %s", summary.c_str());
4613 return absl::OkStatus();
4619 if (snapshot.running) {
4627 if (!command_or.ok()) {
4628 UpdateBuildWorkflowStatus(
4631 std::string(command_or.status().message()),
4634 absl::StrFormat(
"Build unavailable: %s", command_or.status().message()),
4639 const std::filesystem::path project_root =
4646 *command_or, project_root.string(), *environment)
4647 : environment.status();
4648 if (!start_status.ok()) {
4649 UpdateBuildWorkflowStatus(
4652 std::string(start_status.message()),
4655 absl::StrFormat(
"Build unavailable: %s", start_status.message()),
4662 UpdateBuildWorkflowStatus(
4665 "Running the configured project build command",
4679 if (!snapshot.running) {
4684 UpdateBuildWorkflowStatus(
4686 MakeBuildStatus(
"Cancelling build",
"Stopping the active project build",
4692 UpdateRunWorkflowStatus(
4695 "Preparing the project output for emulator reload",
4699 if (!run_target_or.ok()) {
4700 UpdateRunWorkflowStatus(
4703 std::string(run_target_or.status().message()),
4706 run_target_or.status().message()),
4708 return run_target_or.status();
4710 const std::string run_target = *run_target_or;
4711 if (!std::filesystem::exists(run_target)) {
4717 return absl::NotFoundError(
"Run target ROM not found");
4720#ifdef YAZE_WITH_GRPC
4722 auto load_status = emulator_backend->LoadRom(run_target);
4723 if (load_status.ok()) {
4724 AppendWorkflowHistoryEntry(
4736 absl::StrFormat(
"Reloaded project output in emulator backend: %s",
4739 return absl::OkStatus();
4746 if (!load_rom_status.ok()) {
4747 UpdateRunWorkflowStatus(
4749 MakeRunStatus(
"Run load failed", std::string(load_rom_status.message()),
4752 load_rom_status.message()),
4754 return load_rom_status;
4758 if (!reload_status.ok()) {
4759 UpdateRunWorkflowStatus(
4761 MakeRunStatus(
"Reload failed", std::string(reload_status.message()),
4764 reload_status.message()),
4766 return reload_status;
4772 AppendWorkflowHistoryEntry(
"Run",
4780 absl::StrFormat(
"Reloaded project output in emulator: %s", run_target),
4782 return absl::OkStatus();
4799 return absl::OkStatus();
4804 return absl::FailedPreconditionError(
"No project is currently open");
4812 return absl::OkStatus();
4817 return editor_set->GetOverworldData();
4824 return absl::InvalidArgumentError(
"Invalid ROM pointer");
4833 if (session && &session->rom == rom) {
4836 return absl::FailedPreconditionError(
4837 "The requested ROM session could not be activated");
4843 return absl::OkStatus();
4849 return absl::NotFoundError(
"ROM not found in existing sessions");
4860 session->CaptureRomIdentity(preserve_editing_source);
4884 return session !=
nullptr && session->backup_restore_pending;
4890 return absl::FailedPreconditionError(
"No ROM loaded");
4893 return absl::FailedPreconditionError(
4894 "Save or discard ROM edits before restoring a backup");
4897 const std::string original_filename = rom->
filename();
4901 const bool is_managed_backup =
4902 std::any_of(backups.begin(), backups.end(), [&](
const auto& backup) {
4903 return SessionCoordinator::PathsReferToSameBackingFile(backup.path,
4906 if (!is_managed_backup) {
4907 return absl::InvalidArgumentError(
4908 "Selected file is not a managed backup for the active ROM");
4918 if (!original_filename.empty()) {
4932 if (!restored_session) {
4933 return absl::InternalError(
4934 "ROM backup restored without an active session to stage it");
4936 restored_session->backup_restore_pending =
true;
4937 return absl::OkStatus();
4942 return absl::FailedPreconditionError(
"No session coordinator");
4945 if (!session || !session->rom.is_loaded()) {
4946 return absl::FailedPreconditionError(
"No ROM loaded");
4948 if (!session->backup_restore_pending) {
4949 return absl::FailedPreconditionError(
"No restored backup is staged");
4953 if (session->editors.HasPendingSpriteChanges() ||
4954 session->editors.HasPendingGraphicsChanges() ||
4955 session->HasPendingGfxGroupChanges() ||
4956 session->editors.HasPendingScreenChanges() ||
4959 return absl::FailedPreconditionError(
4960 "Resolve pending graphics, screen, dungeon, or palette edits before "
4961 "discarding the restored backup");
4964 const std::string backing_path = session->rom.filename();
4965 if (backing_path.empty()) {
4966 return absl::FailedPreconditionError(
"ROM has no backing file to reload");
4973 *session->rom.resource_label();
4980 session->backup_restore_pending =
false;
4981 session->rom.ClearDirty();
4982 return absl::OkStatus();
5033 current_session_id})) {
5065 return session !=
nullptr && session->rom.is_loaded() &&
5066 !session->rom.filename().empty();
5071 return absl::FailedPreconditionError(
"No ROM with a backing file is open");
5077 return absl::OkStatus();
5081 return absl::OkStatus();
5086 return absl::FailedPreconditionError(
"No ROM with a backing file is open");
5091 if (session->editors.HasPendingGraphicsChanges()) {
5092 return absl::FailedPreconditionError(
5093 "The Graphics editor has unsaved sheet edits; save or undo them "
5094 "before reverting");
5097 const std::string backing_path = session->rom.filename();
5103 *session->rom.resource_label();
5113 if (session !=
nullptr) {
5114 session->backup_restore_pending =
false;
5115 session->rom.ClearDirty();
5117 return absl::DataLossError(absl::StrFormat(
5118 "ROM reloaded from disk, but the session still reports %s",
5122 return absl::OkStatus();
5156 size_t session_id)
const {
5158 return std::nullopt;
5167 return std::nullopt;
5177 return absl::StrFormat(
"%s\n\nQuitting now will discard them.",
5182 const std::string session_name =
5183 source_index.has_value()
5186 const std::string work = source_index.has_value()
5190 switch (action.type) {
5193 return absl::StrFormat(
5194 "Session '%s' has %s. Opening another ROM or project will not save "
5195 "them automatically.",
5196 session_name, work);
5198 return absl::StrFormat(
5199 "Session '%s' has %s. Opening another project will not save them "
5201 session_name, work);
5203 return absl::StrFormat(
5204 "Session '%s' has %s. Switching sessions will leave them behind in "
5206 session_name, work);
5208 return absl::StrFormat(
5209 "Session '%s' has %s. Closing it now will discard them.",
5210 session_name, work);
5212 return absl::StrFormat(
5213 "Session '%s' has %s. Closing the ROM now will discard them.",
5214 session_name, work);
5216 return absl::StrFormat(
5217 "Session '%s' has %s. Reverting reloads the ROM from disk and "
5218 "discards all unsaved ROM edits. This cannot be undone.",
5219 session_name, work);
5235 return "Save Work & Open";
5237 return "Save Work & Open Project";
5239 return "Save Work & Switch";
5241 return "Save Work & Close";
5243 return "Save Work & Close ROM";
5250 :
"Save Modified Work & Quit";
5264 return "Open Without Saving";
5266 return "Open Project Without Saving";
5268 return "Switch Without Saving";
5271 return "Close Without Saving";
5273 return "Discard Edits & Revert";
5275 return "Quit Without Saving";
5294 auto save_modified_session = [
this](
size_t session_id) -> absl::Status {
5296 if (!session_index.has_value()) {
5297 return absl::FailedPreconditionError(
5298 "The ROM session is no longer available");
5301 return absl::OkStatus();
5306 return absl::FailedPreconditionError(
5307 "The requested ROM session could not be activated before saving");
5319 return absl::FailedPreconditionError(
5320 absl::StrFormat(
"Save completed, but session still has %s",
5323 return absl::OkStatus();
5326 absl::Status save_status = absl::OkStatus();
5329 std::vector<size_t> session_ids;
5335 for (
const size_t session_id : session_ids) {
5336 save_status = save_modified_session(session_id);
5337 if (!save_status.ok()) {
5343 save_status = save_modified_session(action.source_session_id);
5346 if (!save_status.ok()) {
5347 const bool resumable_confirmation = absl::IsCancelled(save_status) &&
5350 const auto original_session_index =
5352 if (!resumable_confirmation && original_session_index.has_value()) {
5356 if (absl::IsCancelled(save_status)) {
5361 absl::StrFormat(
"Failed to save before continuing: %s",
5362 save_status.message()),
5372 original_session_id != action.target_session_id) {
5373 const auto original_session_index =
5375 if (original_session_index.has_value()) {
5379 "Session saved, but the original ROM session could not be "
5380 "restored. Close was cancelled.",
5414 bool has_pending_work =
false;
5425 if (!has_pending_work) {
5438 switch (action.
type) {
5443 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
5462 absl::StrFormat(
"Failed to open project: %s", status.message()),
5469 const auto target_index =
5471 if (target_index.has_value()) {
5478 const auto target_index =
5480 if (target_index.has_value()) {
5488 const auto target_index =
5490 if (target_index.has_value()) {
5497 const auto target_index =
5499 if (!target_index.has_value()) {
5508 "Revert cancelled: the requested ROM session could not be "
5519 absl::StrFormat(
"Revert failed: %s", status.message()),
5538 return session !=
nullptr &&
5540 session->editors.HasPendingSpriteChanges() ||
5541 session->project_dirty ||
5542 session->editors.HasPendingProjectDraftChanges() ||
5543 (session->project_file_editor_state.initialized &&
5544 session->project_file_editor_state.modified));
5555 return session !=
nullptr &&
5556 ((session->rom.is_loaded() && session->rom.dirty()) ||
5557 session->editors.HasPendingGraphicsChanges() ||
5558 session->HasPendingGfxGroupChanges() ||
5559 session->editors.HasPendingScreenChanges() ||
5569 size_t session_index)
const {
5581 if (
auto* dungeon_editor =
5583 return dungeon_editor->HasPendingDungeonChanges();
5589 size_t session_index)
const {
5601 if (
auto* dungeon_editor =
5603 return dungeon_editor->PendingRoomCount();
5609 size_t session_index)
const {
5618 &session->game_data)
5637 size_t session_index)
const {
5640 return "unsaved work";
5645 const bool rom_dirty =
5646 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
5647 const bool pending_dungeon_changes =
5649 const bool pending_sprite_changes =
5650 session !=
nullptr && session->editors.HasPendingSpriteChanges();
5651 const bool pending_graphics_changes =
5652 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
5653 const bool pending_gfx_group_changes =
5654 session !=
nullptr && session->HasPendingGfxGroupChanges();
5655 const bool pending_screen_changes =
5656 session !=
nullptr && session->editors.HasPendingScreenChanges();
5658 const size_t pending_palette_colors =
5660 const bool project_dirty = session !=
nullptr && session->project_dirty;
5661 const bool project_editor_draft =
5662 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
5663 const bool project_file_dirty =
5664 session !=
nullptr && session->project_file_editor_state.initialized &&
5665 session->project_file_editor_state.modified;
5667 std::vector<std::string> work;
5668 if (pending_rooms > 0) {
5669 work.push_back(absl::StrFormat(
"%d unapplied dungeon room%s", pending_rooms,
5670 pending_rooms == 1 ?
"" :
"s"));
5671 }
else if (pending_dungeon_changes) {
5672 work.emplace_back(
"unapplied dungeon metadata");
5674 if (pending_palette_colors > 0) {
5675 work.push_back(absl::StrFormat(
"%zu unapplied palette color%s",
5676 pending_palette_colors,
5677 pending_palette_colors == 1 ?
"" :
"s"));
5679 if (pending_sprite_changes)
5680 work.emplace_back(
"unsaved sprite assets");
5681 if (pending_graphics_changes) {
5682 work.emplace_back(
"unapplied graphics sheet edits");
5684 if (pending_gfx_group_changes) {
5685 work.emplace_back(
"unsaved blockset/spriteset (gfx group) edits");
5687 if (pending_screen_changes) {
5688 work.emplace_back(
"unapplied Screen Editor edits");
5691 work.emplace_back(
"unsaved ROM-buffer changes");
5693 if (project_dirty) {
5694 work.emplace_back(
"unsaved project settings");
5696 if (project_editor_draft) {
5697 work.emplace_back(
"an uncommitted project editor draft");
5699 if (project_file_dirty) {
5700 work.emplace_back(
"an unsaved project-file draft");
5702 return work.empty() ?
"unsaved work" : absl::StrJoin(work,
" and ");
5706 size_t session_index)
const {
5714 const bool rom_dirty =
5715 session !=
nullptr && session->
rom.
is_loaded() && session->rom.dirty();
5716 const bool pending_dungeon_changes =
5718 const bool pending_sprite_changes =
5719 session !=
nullptr && session->editors.HasPendingSpriteChanges();
5720 const bool pending_graphics_changes =
5721 session !=
nullptr && session->editors.HasPendingGraphicsChanges();
5722 const bool pending_gfx_group_changes =
5723 session !=
nullptr && session->HasPendingGfxGroupChanges();
5724 const bool pending_screen_changes =
5725 session !=
nullptr && session->editors.HasPendingScreenChanges();
5727 const size_t pending_palette_colors =
5729 const bool project_dirty = session !=
nullptr && session->project_dirty;
5730 const bool project_editor_draft =
5731 session !=
nullptr && session->editors.HasPendingProjectDraftChanges();
5732 const bool project_file_dirty =
5733 session !=
nullptr && session->project_file_editor_state.initialized &&
5734 session->project_file_editor_state.modified;
5736 std::vector<std::string> tags;
5737 if (pending_rooms > 0) {
5738 tags.push_back(
"Rooms");
5739 }
else if (pending_dungeon_changes) {
5740 tags.push_back(
"Dungeon");
5742 if (pending_palette_colors > 0) {
5743 tags.push_back(
"Palette");
5745 if (pending_sprite_changes)
5746 tags.emplace_back(
"Sprites");
5747 if (pending_graphics_changes) {
5748 tags.push_back(
"Gfx");
5750 if (pending_gfx_group_changes) {
5751 tags.push_back(
"GfxGroups");
5753 if (pending_screen_changes) {
5754 tags.push_back(
"Screen");
5757 tags.push_back(
"ROM");
5759 if (project_dirty || project_editor_draft || project_file_dirty) {
5760 tags.push_back(
"Project");
5762 return absl::StrJoin(tags,
"+");
5767 if (modified_sessions <= 0) {
5768 return "No sessions have unsaved work.";
5774 return absl::StrFormat(
"Session '%s' has %s.",
5781 return absl::StrFormat(
5782 "%d sessions have unsaved ROM, graphics, screen, dungeon, palette, or "
5796 int pending_editor =
5798 if (pending_editor < 0) {
5804 if (pending_layout < 0) {
5819 EditorType type,
size_t session_index)
const {
5820 const char* base_name =
kEditorNames[
static_cast<int>(type)];
5822 base_name, session_index)
5823 : std::string(base_name);
5828#ifdef YAZE_BUILD_AGENT_UI
5841 "%s is experimental. It remains available; enable experimental "
5842 "features in Settings to acknowledge this warning.",
5860 absl::StrFormat(
"Failed to prepare %s: %s",
5874 switch (editor_type) {
5955 if (session && session->project_file_editor_state.initialized &&
5956 !session->project_file_editor_state.modified &&
5959 session->project_file_editor_state.filepath,
5964 if (session && session->project_file_editor_state.initialized) {
5971 absl::StrFormat(
"Failed to load project file: %s", status.message()),
5988 size_t session_id) {
6004 if (session_index.has_value()) {
6009 if (session->project_context.has_value()) {
6010 deps.
project = &*session->project_context;
6043 settings_panel->SetProjectContextProvider(
6049 [
this, session_id]() -> std::string {
6054 settings_panel->SetOpenMinecartTracksCallback(
6055 [
this, session_id]() -> absl::Status {
6057 return absl::FailedPreconditionError(
6058 "Minecart Tracks requires its project session to be active.");
6063 if (dungeon ==
nullptr) {
6064 return absl::NotFoundError(
6065 "Dungeon editor is unavailable in the active project "
6073 return absl::NotFoundError(
6074 "Minecart Tracks could not be opened in the active Dungeon "
6077 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()
Central coordinator for all agent UI components.
Snapshot GetSnapshot() const
absl::Status Start(const std::string &command, const std::string &directory, const std::map< std::string, std::string > &environment={})
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.
void ApplyProjectSaveSettings(project::YazeProject *project, bool include_recent_files)
absl::Status SaveProjectAs()
std::unique_ptr< SessionCoordinator > session_coordinator_
RomLifecycleManager rom_lifecycle_
StartupVisibility welcome_mode_override_
std::unique_ptr< AgentUiController > agent_ui_
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)
bool ProjectDescriptorMatchesDisk()
absl::Status CompletePendingProjectNativeSave()
absl::Status ValidateLyraTerrainProjectContext() const
void QueueBuildCurrentProject()
std::unique_ptr< RightDrawerManager > right_drawer_manager_
void CancelPendingUnsavedSessionAction()
void SetCurrentEditor(Editor *editor) override
bool SaveLayoutSnapshotAs(const std::string &name)
absl::Status PrepareRawProjectFileSave(const std::string &filepath, const std::string &contents)
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
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()
absl::Status SaveCurrentSession()
std::unique_ptr< LayoutManager > layout_manager_
std::unique_ptr< DashboardPanel > dashboard_panel_
absl::Status RetireNativeSaveTransfer(const std::string &state, const std::string &editing_source_path="")
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 FinishRawRomOpen(bool remembered_load_options, bool ask_for_load_options, bool is_first_time_rom_path)
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")
absl::Status RecordCompletedNativeSave(const project::YazeProject &project, const std::string &saved_rom, bool save_as, bool rom_written=true)
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)
std::string startup_editor_hint_
auto GetCurrentEditorSet() const -> EditorSet *
absl::Status OpenLyraTerrain()
void CancelRomWriteConfirm()
bool IsRomBackupRestorePending() const
std::string DescribePendingUnsavedWork(size_t session_index) const
absl::Status PruneRomBackups()
absl::Status LoadProjectWithRom(bool allow_dialogs=true)
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)
auto GetCurrentEditor() const -> Editor *override
absl::Status OpenRomOrProject(const std::string &filename, LoadOptionsPrompt prompt=LoadOptionsPrompt::kAsk)
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_
absl::Status CreateLyraTerrain()
absl::Status SwapProjectRom(const std::string &rom_path, const std::optional< std::string > &expected_sha256=std::nullopt)
size_t PendingPaletteColorCountForSession(size_t session_index) const
void RestorePreviousEditorLayout()
absl::Status OpenLyraTerrainFromPath(const std::string &path)
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::string NativeSaveEditingSourcePath() const
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()
bool defer_project_native_completion_
std::vector< std::function< void()> > deferred_actions_
void RegisterEmulatorPanels()
bool ActivateExistingSessionForOpen(const std::string &filename)
void OpenEditorAndPanelsFromFlags(const std::string &editor_name, const std::string &panels_str)
StartupVisibility dashboard_mode_override_
void BindProjectContextToSession(RomSession *session, const project::YazeProject &project)
bool ApplyRememberedRomLoadChoice()
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::Status OpenRomOrProjectInternal(const std::string &filename, bool allow_dialogs=true, LoadOptionsPrompt prompt=LoadOptionsPrompt::kAsk)
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.
std::string ExecuteEditorControl(const std::string &request)
bool ProjectFileDraftTargetsCurrentProject() const
absl::Status SaveProjectWorkBeforeRomSave(bool allow_dialogs)
void ClearTemporaryLayoutSnapshot()
ProjectWorkflowStatus MakeRunStatus(const std::string &summary, const std::string &detail, ProjectWorkflowState state) const
AgentEditor * GetAgentEditor()
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)
absl::Status PrepareActiveProjectEditorDraftsForSave(bool publish_lyra=true)
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
bool IsPlanningOnlyProjectSave(const RomSession &session)
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_
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)
absl::Status SaveActiveProjectEditingWork(bool allow_dialogs=true)
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.
T * GetEditorAs(EditorType type) const
bool HasPendingPlanningProjectDraftChanges() const
bool HasPendingBuildProjectDraftChanges() const
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.
bool HasUnpublishedMessageSourceChanges() const
Main UI class for editing overworld maps in A Link to the Past.
absl::Status SaveProjectTerrainAs(project::YazeProject *project, const std::string &project_path)
LyraTerrainCanvas lyra_canvas_
absl::Status lyra_canvas_status_
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.
Allows the user to edit sprites.
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
std::vector< std::string > GetWindowsInSession(size_t session_id) const
const std::unordered_map< std::string, WindowDescriptor > & GetAllWindowDescriptors() const
Get all panel descriptors (for layout designer, panel browser, etc.)
bool CloseWindow(size_t session_id, const std::string &base_window_id)
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)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
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()
const std::vector< std::string > & GetRecentFiles() const
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)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< NativeSaveContext > GetNativeSaveContext(const std::string &, const std::string &)
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)
absl::StatusOr< std::map< std::string, std::string > > ProjectBuildEnvironment(const project::YazeProject &project, const std::string &editing_source_path)
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.
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
std::function< project::YazeProject *()> project_context_provider
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
LoadOptionsPrompt load_options_prompt
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
ProjectFileEditorState project_file_editor_state
bool native_save_completion_pending
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
struct yaze::project::YazeProject::MusicPersistence music_persistence
bool project_opened() const
absl::StatusOr< std::string > SerializeToString() const
absl::Status LoadFromString(const std::string &content, const std::string &project_path)
void NormalizePathsToAbsolute()
std::string git_repository
core::HackManifest hack_manifest
std::string lyra_terrain_file
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
std::string assets_folder
std::string GetDisplayName() const
std::string lyra_build_request_file
WorkspaceSettings workspace_settings
absl::StatusOr< std::optional< std::string > > ResolveExpandedBuildRunTarget(const std::string &editing_source_path) const
bool IsOracleCompatible() const
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
std::vector< FileDialogFilter > filters