yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_file_editor.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <sstream>
6
7#include "absl/strings/match.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_split.h"
11#include "app/gui/core/icons.h"
12#include "core/project.h"
13#include "imgui/imgui.h"
14#include "util/file_util.h"
15
16#ifdef __EMSCRIPTEN__
18#endif
19namespace yaze {
20namespace editor {
21
27
29 if (!active_)
30 return;
31
32 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
33 if (!ImGui::Begin(absl::StrFormat("%s Project Editor###ProjectFileEditor",
35 .c_str(),
36 &active_)) {
37 ImGui::End();
38 return;
39 }
40
41 // Toolbar
42 if (ImGui::BeginTable("ProjectEditorToolbar", 10,
43 ImGuiTableFlags_SizingFixedFit)) {
44 ImGui::TableNextColumn();
45 if (ImGui::Button(absl::StrFormat("%s New", ICON_MD_NOTE_ADD).c_str())) {
46 NewFile();
47 }
48
49 ImGui::TableNextColumn();
50 if (ImGui::Button(
51 absl::StrFormat("%s Open", ICON_MD_FOLDER_OPEN).c_str())) {
53 if (!file.empty()) {
54 auto status = LoadFile(file);
55 if (!status.ok() && toast_manager_) {
57 std::string(status.message().data(), status.message().size()),
59 }
60 }
61 }
62
63 ImGui::TableNextColumn();
64 bool can_save = !filepath_.empty() && IsModified();
65 if (!can_save)
66 ImGui::BeginDisabled();
67 if (ImGui::Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str())) {
68 auto status = SaveFile();
69 if (status.ok() && toast_manager_) {
70 toast_manager_->Show("Project file saved", ToastType::kSuccess);
71 } else if (!status.ok() && toast_manager_) {
73 std::string(status.message().data(), status.message().size()),
75 }
76 }
77 if (!can_save)
78 ImGui::EndDisabled();
79
80 ImGui::TableNextColumn();
81 if (ImGui::Button(absl::StrFormat("%s Save As", ICON_MD_SAVE_AS).c_str())) {
83 filepath_.empty() ? "project" : filepath_, "yaze");
84 if (!file.empty()) {
85 auto status = SaveFileAs(file);
86 if (status.ok() && toast_manager_) {
87 toast_manager_->Show("Project file saved", ToastType::kSuccess);
88 } else if (!status.ok() && toast_manager_) {
90 std::string(status.message().data(), status.message().size()),
92 }
93 }
94 }
95
96 ImGui::TableNextColumn();
97 ImGui::Text("|");
98
99 ImGui::TableNextColumn();
100 // Import ZScream Labels button
101 if (ImGui::Button(
102 absl::StrFormat("%s Import Labels", ICON_MD_LABEL).c_str())) {
103 auto status = ImportLabelsFromZScream();
104 if (status.ok() && toast_manager_) {
105 toast_manager_->Show("Labels imported successfully",
107 } else if (!status.ok() && toast_manager_) {
109 std::string(status.message().data(), status.message().size()),
111 }
112 }
113 if (ImGui::IsItemHovered()) {
114 ImGui::SetTooltip("Import labels from ZScream DefaultNames.txt");
115 }
116
117 ImGui::TableNextColumn();
118 if (ImGui::Button(
119 absl::StrFormat("%s Validate", ICON_MD_CHECK_CIRCLE).c_str())) {
121 show_validation_ = true;
122 }
123
124 ImGui::TableNextColumn();
125 ImGui::Checkbox("Show Validation", &show_validation_);
126
127 ImGui::TableNextColumn();
128 if (!filepath_.empty()) {
129 ImGui::TextDisabled("%s", filepath_.c_str());
130 } else {
131 ImGui::TextDisabled("No file loaded");
132 }
133
134 ImGui::EndTable();
135 }
136
137 ImGui::Separator();
138
139 // Validation errors panel
140 if (show_validation_ && !validation_errors_.empty()) {
141 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.2f, 0.2f, 0.5f));
142 if (ImGui::BeginChild("ValidationErrors", ImVec2(0, 100), true)) {
143 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
144 "%s Validation Errors:", ICON_MD_ERROR);
145 for (const auto& error : validation_errors_) {
146 ImGui::BulletText("%s", error.c_str());
147 }
148 }
149 ImGui::EndChild();
150 ImGui::PopStyleColor();
151 }
152
153 // Main editor
154 ImVec2 editor_size = ImGui::GetContentRegionAvail();
155 text_editor_.Render("##ProjectEditor", editor_size);
156
157 ImGui::End();
158}
159
160absl::Status ProjectFileEditor::LoadFile(const std::string& filepath) {
161#ifdef __EMSCRIPTEN__
162 std::string key = std::filesystem::path(filepath).stem().string();
163 if (key.empty()) {
164 key = "project";
165 }
166 auto storage_or = platform::WasmStorage::LoadProject(key);
167 if (storage_or.ok()) {
168 text_editor_.SetText(storage_or.value());
170 modified_ = false;
172 return absl::OkStatus();
173 }
174#endif
175
176 std::ifstream file(filepath);
177 if (!file.is_open()) {
178 return absl::InvalidArgumentError(
179 absl::StrFormat("Cannot open file: %s", filepath));
180 }
181
182 std::stringstream buffer;
183 buffer << file.rdbuf();
184 file.close();
185
186 text_editor_.SetText(buffer.str());
188 modified_ = false;
189
191
192 return absl::OkStatus();
193}
194
196 if (filepath_.empty()) {
197 return absl::InvalidArgumentError("No file path specified");
198 }
199
200 return SaveFileAs(filepath_);
201}
202
203absl::Status ProjectFileEditor::SaveFileAs(const std::string& filepath) {
204 // Ensure .yaze extension
205 std::string final_path = filepath;
206 if (!absl::EndsWith(final_path, ".yaze")) {
207 final_path += ".yaze";
208 }
209
210#ifdef __EMSCRIPTEN__
211 std::string key = std::filesystem::path(final_path).stem().string();
212 if (key.empty()) {
213 key = "project";
214 }
215 auto storage_status =
216 platform::WasmStorage::SaveProject(key, text_editor_.GetText());
217 if (!storage_status.ok()) {
218 return storage_status;
219 }
220 filepath_ = final_path;
221 modified_ = false;
222 auto& recent_mgr = project::RecentFilesManager::GetInstance();
223 recent_mgr.AddFile(filepath_);
224 recent_mgr.Save();
225 return absl::OkStatus();
226#else
227 std::ofstream file(final_path);
228 if (!file.is_open()) {
229 return absl::InvalidArgumentError(
230 absl::StrFormat("Cannot create file: %s", final_path));
231 }
232
233 file << text_editor_.GetText();
234 file.close();
235
236 filepath_ = final_path;
237 modified_ = false;
238
239 // Add to recent files
240 auto& recent_mgr = project::RecentFilesManager::GetInstance();
241 recent_mgr.AddFile(filepath_);
242 recent_mgr.Save();
243
244 return absl::OkStatus();
245#endif
246}
247
249 // Create a template project file
250 const char* template_content = R"(# yaze Project File
251# Format Version: 2.0
252
253[project]
254name=New Project
255project_id=
256description=
257author=
258license=
259version=1.0
260created_date=
261last_modified=
262yaze_version=0.5.0
263created_by=YAZE
264tags=
265
266[agent_settings]
267ai_provider=auto
268ai_model=
269ollama_host=http://localhost:11434
270use_custom_prompt=false
271
272[files]
273rom_filename=
274rom_backup_folder=backups
275code_folder=asm
276assets_folder=assets
277patches_folder=patches
278labels_filename=labels.txt
279symbols_filename=symbols.txt
280output_folder=build
281additional_roms=
282
283[feature_flags]
284# REMOVED: kLogInstructions - DisassemblyViewer is always active
285kSaveDungeonMaps=true
286kSaveGraphicsSheet=true
287kLoadCustomOverworld=false
288
289[workspace]
290font_global_scale=1.0
291autosave_enabled=true
292autosave_interval_secs=300
293theme=dark
294
295[build]
296build_script=
297output_folder=build
298build_target=
299asm_entry_point=asm/main.asm
300asm_sources=asm
301build_number=0
302last_build_hash=
303
304[music]
305persist_custom_music=true
306storage_key=
307last_saved_at=
308)";
309
310 text_editor_.SetText(template_content);
311 filepath_.clear();
312 modified_ = true;
313 validation_errors_.clear();
314}
315
317 // TODO: Implement custom syntax highlighting for INI format
318 // For now, use C language definition which provides some basic highlighting
319}
320
322 validation_errors_.clear();
323
324 std::string content = text_editor_.GetText();
325 std::vector<std::string> lines = absl::StrSplit(content, '\n');
326
327 std::string current_section;
328 int line_num = 0;
329
330 for (const auto& line : lines) {
331 line_num++;
332 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
333
334 // Skip empty lines and comments
335 if (trimmed.empty() || trimmed[0] == '#')
336 continue;
337
338 // Check for section headers
339 if (trimmed[0] == '[' && trimmed[trimmed.size() - 1] == ']') {
340 current_section = trimmed.substr(1, trimmed.size() - 2);
341
342 // Validate known sections
343 if (current_section != "project" && current_section != "files" &&
344 current_section != "feature_flags" &&
345 current_section != "workspace" &&
346 current_section != "workspace_settings" &&
347 current_section != "build" && current_section != "agent_settings" &&
348 current_section != "music" && current_section != "keybindings" &&
349 current_section != "editor_visibility") {
350 validation_errors_.push_back(absl::StrFormat(
351 "Line %d: Unknown section [%s]", line_num, current_section));
352 }
353 continue;
354 }
355
356 // Check for key=value pairs
357 size_t equals_pos = trimmed.find('=');
358 if (equals_pos == std::string::npos) {
359 validation_errors_.push_back(absl::StrFormat(
360 "Line %d: Invalid format, expected key=value", line_num));
361 continue;
362 }
363 }
364
366 toast_manager_->Show("Project file validation passed", ToastType::kSuccess);
367 }
368}
369
371 if (validation_errors_.empty())
372 return;
373
374 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Validation Errors:");
375 for (const auto& error : validation_errors_) {
376 ImGui::BulletText("%s", error.c_str());
377 }
378}
379
381#ifdef __EMSCRIPTEN__
382 return absl::UnimplementedError(
383 "File-based label import is not supported in the web build");
384#else
385 if (!project_) {
386 return absl::FailedPreconditionError(
387 "No project loaded. Open a project first.");
388 }
389
390 // Show file dialog for DefaultNames.txt
392 if (file.empty()) {
393 return absl::CancelledError("No file selected");
394 }
395
396 // Read the file contents
397 std::ifstream input_file(file);
398 if (!input_file.is_open()) {
399 return absl::InvalidArgumentError(
400 absl::StrFormat("Cannot open file: %s", file));
401 }
402
403 std::stringstream buffer;
404 buffer << input_file.rdbuf();
405 input_file.close();
406
407 // Import using the project's method
408 auto status = project_->ImportLabelsFromZScreamContent(buffer.str());
409 if (!status.ok()) {
410 return status;
411 }
412
413 // Save the project to persist the imported labels
414 return project_->Save();
415#endif
416}
417
418} // namespace editor
419} // namespace yaze
void SetShowWhitespaces(bool aValue)
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)
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.
void NewFile()
Create a new empty project file.
bool IsModified() const
Get whether the file has unsaved changes.
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.
const std::string & filepath() const
Get the current filepath.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
static RecentFilesManager & GetInstance()
Definition project.h:312
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 ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_NOTE_ADD
Definition icons.h:1330
#define ICON_MD_SAVE_AS
Definition icons.h:1646
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_SAVE
Definition icons.h:1644
static const LanguageDefinition & C()
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.
Definition project.cc:1443