8#include "absl/strings/match.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
15#include "imgui/imgui.h"
18#include "yaze_config.h"
30 const std::string& right) {
31 if (left.empty() || right.empty()) {
35 std::error_code equivalent_error;
36 if (std::filesystem::equivalent(left, right, equivalent_error)) {
40 std::error_code left_error;
41 std::error_code right_error;
42 const auto left_path =
43 std::filesystem::absolute(left, left_error).lexically_normal();
44 const auto right_path =
45 std::filesystem::absolute(right, right_error).lexically_normal();
46 return !left_error && !right_error && left_path == right_path;
52 const std::string& filepath) {
53 if (project !=
nullptr && project->
filepath == filepath) {
56 std::string key = std::filesystem::path(filepath).stem().string();
57 return key.empty() ?
"project" : key;
73 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
74 if (!ImGui::Begin(absl::StrFormat(
"%s Project Editor###ProjectFileEditor",
83 if (ImGui::BeginTable(
"ProjectEditorToolbar", 10,
84 ImGuiTableFlags_SizingFixedFit)) {
85 ImGui::TableNextColumn();
93 ImGui::TableNextColumn();
101 std::string(status.message().data(), status.message().size()),
107 ImGui::TableNextColumn();
110 ImGui::BeginDisabled();
111 if (ImGui::Button(absl::StrFormat(
"%s Save",
ICON_MD_SAVE).c_str())) {
117 std::string(status.message().data(), status.message().size()),
122 ImGui::EndDisabled();
124 ImGui::TableNextColumn();
125 if (ImGui::Button(absl::StrFormat(
"%s Save As",
ICON_MD_SAVE_AS).c_str())) {
134 std::string(status.message().data(), status.message().size()),
140 ImGui::TableNextColumn();
143 ImGui::TableNextColumn();
146 absl::StrFormat(
"%s Import Labels",
ICON_MD_LABEL).c_str())) {
153 std::string(status.message().data(), status.message().size()),
157 if (ImGui::IsItemHovered()) {
158 ImGui::SetTooltip(tr(
"Import labels from ZScream DefaultNames.txt"));
161 ImGui::TableNextColumn();
168 ImGui::TableNextColumn();
171 ImGui::TableNextColumn();
173 ImGui::TextDisabled(
"%s",
filepath_.c_str());
175 ImGui::TextDisabled(tr(
"No file loaded"));
186 {.bg = ImVec4(0.3f, 0.2f, 0.2f, 0.5f)},
true);
188 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
191 ImGui::BulletText(
"%s", error.c_str());
197 ImVec2 editor_size = ImGui::GetContentRegionAvail();
245 auto storage_or = platform::WasmStorage::LoadProject(key);
246 if (storage_or.ok()) {
252 return absl::OkStatus();
257 if (!file.is_open()) {
258 return absl::InvalidArgumentError(
259 absl::StrFormat(
"Cannot open file: %s",
filepath));
262 std::stringstream buffer;
263 buffer << file.rdbuf();
273 return absl::OkStatus();
278 return absl::InvalidArgumentError(
"No file path specified");
287 if (!absl::EndsWith(final_path,
".yaze")) {
288 final_path +=
".yaze";
297 const std::string key = ProjectStorageKey(
project_, final_path);
298 const bool replace_existing =
300 auto storage_status =
301 platform::WasmStorage::SaveProject(key, contents, replace_existing);
302 if (!storage_status.ok()) {
303 return storage_status;
314 return absl::OkStatus();
316 const bool replace_existing =
317 PathsReferToSameProjectFile(
filepath_, final_path);
334 return absl::OkStatus();
340 return absl::FailedPreconditionError(
341 "Project-file draft has unsaved changes; use Save or Save As first");
343 return absl::OkStatus();
349 const std::string template_content = absl::StrFormat(
350 R
"(# yaze Project File
369ollama_host=http://localhost:11434
370use_custom_prompt=false
374rom_backup_folder=backups
377patches_folder=patches
378labels_filename=labels.txt
379symbols_filename=symbols.txt
381custom_objects_folder=
382# Optional: ASM integration (e.g. Oracle-of-Secrets hack_manifest.json)
392# REMOVED: kLogInstructions - DisassemblyViewer is always active
393kSaveOverworldMaps=true
394kSaveOverworldEntrances=true
395kSaveOverworldExits=true
396kSaveOverworldItems=true
397kSaveOverworldProperties=true
399kSaveDungeonObjects=true
400kSaveDungeonSprites=true
401kSaveDungeonRoomHeaders=true
402kSaveDungeonTorches=true
404kSaveDungeonBlocks=true
405kSaveDungeonCollision=true
406kSaveDungeonChests=true
407kSaveDungeonPotItems=true
408kSaveDungeonPalettes=true
409kSaveGraphicsSheet=true
413kLoadCustomOverworld=false
418autosave_interval_secs=300
420backup_retention_count=20
421backup_keep_daily=true
422backup_keep_daily_days=14
426# expanded_message_start=0x178000
427# expanded_message_end=0x17FFFF
428# expanded_music_hook=0x008919
429# expanded_music_main=0x1A9EF5
430# expanded_music_aux=0x1ACCA7
431# overworld_messages_expanded=0x1417F8
432# overworld_map16_expanded=0x1E8000
433# overworld_map32_tr_expanded=0x020000
434# overworld_map32_bl_expanded=0x1F0000
435# overworld_map32_br_expanded=0x1F8000
436# overworld_entrance_map_expanded=0x0DB55F
437# overworld_entrance_pos_expanded=0x0DB35F
438# overworld_entrance_id_expanded=0x0DB75F
439# overworld_entrance_flag_expanded=0x0DB895
440# overworld_ptr_marker_expanded=0x1423FF
441# overworld_ptr_magic_expanded=0xEA
442# overworld_gfx_ptr1=0x004F80
443# overworld_gfx_ptr2=0x00505F
444# overworld_gfx_ptr3=0x00513E
447# object_0x31=track_LR.bin,track_UD.bin,track_corner_TL.bin
448# object_0x32=furnace.bin,firewood.bin,ice_chair.bin
454asm_entry_point=asm/main.asm
460persist_custom_music=true
471 return absl::OkStatus();
483 std::vector<std::string> lines = absl::StrSplit(content,
'\n');
485 std::string current_section;
488 for (
const auto& line : lines) {
490 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
493 if (trimmed.empty() || trimmed[0] ==
'#')
497 if (trimmed[0] ==
'[' && trimmed[trimmed.size() - 1] ==
']') {
498 current_section = trimmed.substr(1, trimmed.size() - 2);
501 if (current_section !=
"project" && current_section !=
"files" &&
502 current_section !=
"feature_flags" &&
503 current_section !=
"workspace" &&
504 current_section !=
"workspace_settings" && current_section !=
"rom" &&
505 current_section !=
"rom_addresses" &&
506 current_section !=
"custom_objects" &&
507 current_section !=
"dungeon_overlay" && current_section !=
"build" &&
508 current_section !=
"agent_settings" && current_section !=
"music" &&
509 current_section !=
"keybindings" &&
510 current_section !=
"editor_visibility") {
512 "Line %d: Unknown section [%s]", line_num, current_section));
518 size_t equals_pos = trimmed.find(
'=');
519 if (equals_pos == std::string::npos) {
521 "Line %d: Invalid format, expected key=value", line_num));
535 if (!text.empty() && text.back() ==
'\n') {
545 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
tr(
"Validation Errors:"));
547 ImGui::BulletText(
"%s", error.c_str());
553 return absl::UnimplementedError(
554 "File-based label import is not supported in the web build");
557 return absl::FailedPreconditionError(
558 "No project loaded. Open a project first.");
564 return absl::CancelledError(
"No file selected");
568 std::ifstream input_file(file);
569 if (!input_file.is_open()) {
570 return absl::InvalidArgumentError(
571 absl::StrFormat(
"Cannot open file: %s", file));
574 std::stringstream buffer;
575 buffer << input_file.rdbuf();
void SetShowWhitespaces(bool aValue)
bool IsTextChanged() const
std::string GetText() const
void Render(const char *aTitle, const ImVec2 &aSize=ImVec2(), bool aBorder=false)
void SetText(const std::string &aText)
void SetTabSize(int aValue)
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
SaveGuardCallback save_guard_callback_
void ApplySyntaxHighlighting()
ToastManager * toast_manager_
absl::Status SaveFileAs(const std::string &filepath)
Save to a new file path.
absl::Status LoadFile(const std::string &filepath)
Load a project file into the editor.
ProjectFileEditorState CaptureState() const
bool IsModified() const
Get whether the file has unsaved changes.
std::string GetDocumentText() const
void RestoreState(const ProjectFileEditorState &state, project::YazeProject *project)
absl::Status CanReplaceDocument() const
std::vector< std::string > validation_errors_
absl::Status SaveFile()
Save the current editor contents to disk.
absl::Status ImportLabelsFromZScream()
Import labels from a ZScream DefaultNames.txt file.
SaveCompleteCallback save_complete_callback_
project::YazeProject * project_
absl::Status NewFile()
Create a new empty project file.
void ResetForProject(project::YazeProject *project)
void ShowValidationErrors()
const std::string & filepath() const
Get the current filepath.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
RAII guard for ImGui child windows with optional styling.
static RecentFilesManager & GetInstance()
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...
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DESCRIPTION
bool PathsReferToSameProjectFile(const std::string &left, const std::string &right)
const char * tr(const char *source)
absl::Status WriteProjectFileAtomically(absl::string_view target_path, absl::string_view contents, bool replace_existing)
#define RETURN_IF_ERROR(expr)
static const LanguageDefinition & C()
std::vector< std::string > validation_errors
Modern project structure with comprehensive settings consolidation.
std::string MakeStorageKey(absl::string_view suffix) const
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.