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