yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_manager.cc
Go to the documentation of this file.
1#include "project_manager.h"
2
3#include <filesystem>
4#include <fstream>
5
6#include "absl/strings/str_format.h"
8#include "core/project.h"
9#include "util/macro.h"
10
11namespace yaze {
12namespace editor {
13
15 : toast_manager_(toast_manager) {}
16
18 const std::string& template_name) {
19 // ROM-first workflow: Creating a project requires a ROM to be loaded
20 // The actual project creation happens after ROM selection in the wizard
21
22 if (template_name.empty()) {
23 // Create default project - will be configured after ROM load
25 current_project_.name = "New Project";
27
28 if (toast_manager_) {
29 toast_manager_->Show("New project created - select a ROM to continue",
31 }
32
33 // Mark that we're waiting for ROM selection
35 return absl::OkStatus();
36 }
37
38 return CreateFromTemplate(template_name, "New Project");
39}
40
41absl::Status ProjectManager::OpenProject(const std::string& filename) {
42 if (filename.empty()) {
43 // TODO: Show file dialog
44 return absl::InvalidArgumentError("No filename provided");
45 }
46
47 return LoadProjectFromFile(filename);
48}
49
50absl::Status ProjectManager::LoadProjectFromFile(const std::string& filename) {
51 if (!IsValidProjectFile(filename)) {
52 return absl::InvalidArgumentError(
53 absl::StrFormat("Invalid project file: %s", filename));
54 }
55
56 auto status = current_project_.Open(filename);
57 if (!status.ok()) {
58 if (toast_manager_) {
60 absl::StrFormat("Failed to load project: %s", status.message()),
62 }
63 return status;
64 }
65
66 if (toast_manager_) {
67 toast_manager_->Show(absl::StrFormat("Project loaded: %s",
70 }
71
72 return absl::OkStatus();
73}
74
76 if (!HasActiveProject()) {
77 return absl::FailedPreconditionError("No active project to save");
78 }
79
81}
82
83absl::Status ProjectManager::SaveProjectAs(const std::string& filename) {
84 if (filename.empty()) {
85 // TODO: Show save dialog
86 return absl::InvalidArgumentError("No filename provided for save as");
87 }
88
89 return SaveProjectToFile(filename);
90}
91
92absl::Status ProjectManager::SaveProjectToFile(const std::string& filename) {
93 auto status = current_project_.SaveAs(filename);
94 if (!status.ok()) {
95 if (toast_manager_) {
97 absl::StrFormat("Failed to save project: %s", status.message()),
99 }
100 return status;
101 }
102
103 if (toast_manager_) {
104 toast_manager_->Show(absl::StrFormat("Project saved: %s", filename),
106 }
107
108 return absl::OkStatus();
109}
110
111absl::Status ProjectManager::ImportProject(const std::string& project_path) {
112 if (project_path.empty()) {
113 return absl::InvalidArgumentError("No project path provided");
114 }
115
116#ifndef __EMSCRIPTEN__
117 if (!std::filesystem::exists(project_path)) {
118 return absl::NotFoundError(
119 absl::StrFormat("Project path does not exist: %s", project_path));
120 }
121#endif
122
123 project::YazeProject imported_project;
124
125 // Handle ZScream project imports (.zsproj files)
126 if (project_path.ends_with(".zsproj")) {
127 RETURN_IF_ERROR(imported_project.ImportZScreamProject(project_path));
128 if (toast_manager_) {
130 "ZScream project imported successfully. Please configure ROM and "
131 "folders.",
132 ToastType::kInfo, 5.0f);
133 }
134 } else {
135 // Standard yaze project import
136 RETURN_IF_ERROR(imported_project.Open(project_path));
137 if (toast_manager_) {
139 absl::StrFormat("Project imported: %s", project_path),
141 }
142 }
143
144 current_project_ = std::move(imported_project);
145 return absl::OkStatus();
146}
147
148absl::Status ProjectManager::ExportProject(const std::string& export_path) {
149 if (!HasActiveProject()) {
150 return absl::FailedPreconditionError("No active project to export");
151 }
152
153 if (export_path.empty()) {
154 return absl::InvalidArgumentError("No export path provided");
155 }
156
157 // TODO: Implement project export logic
158 // This would typically create a package with all project files
159
160 if (toast_manager_) {
161 toast_manager_->Show(absl::StrFormat("Project exported: %s", export_path),
163 }
164
165 return absl::OkStatus();
166}
167
169 if (!HasActiveProject()) {
170 return absl::FailedPreconditionError("No active project to repair");
171 }
172
173 // TODO: Implement project repair logic
174 // This would check for missing files, broken references, etc.
175
176 if (toast_manager_) {
177 toast_manager_->Show("Project repair completed", ToastType::kSuccess);
178 }
179
180 return absl::OkStatus();
181}
182
184 if (!HasActiveProject()) {
185 return absl::FailedPreconditionError("No active project to validate");
186 }
187
188 auto result = current_project_.Validate();
189 if (!result.ok()) {
190 if (toast_manager_) {
192 absl::StrFormat("Project validation failed: %s", result.message()),
194 }
195 return result;
196 }
197
198 if (toast_manager_) {
199 toast_manager_->Show("Project validation passed", ToastType::kSuccess);
200 }
201
202 return absl::OkStatus();
203}
204
206 return current_project_.name;
207}
208
211}
212
213std::vector<std::string> ProjectManager::GetAvailableTemplates() const {
214 // TODO: Scan templates directory and return available templates
215 return {"Empty Project", "Dungeon Editor Project", "Overworld Editor Project",
216 "Graphics Editor Project", "Full Editor Project"};
217}
218
220 const std::string& template_name, const std::string& project_name) {
221 if (template_name.empty() || project_name.empty()) {
222 return absl::InvalidArgumentError(
223 "Template name and project name required");
224 }
225
226 // TODO: Implement template-based project creation
227 // This would copy template files and customize them
228
230 current_project_.name = project_name;
232
233 if (toast_manager_) {
235 absl::StrFormat("Project created from template: %s", template_name),
237 }
238
239 return absl::OkStatus();
240}
241
243 const std::string& project_name) const {
244 // Convert project name to valid filename
245 std::string filename = project_name;
246 std::replace(filename.begin(), filename.end(), ' ', '_');
247 std::replace(filename.begin(), filename.end(), '/', '_');
248 std::replace(filename.begin(), filename.end(), '\\', '_');
249
250 return absl::StrFormat("%s.yaze", filename);
251}
252
253bool ProjectManager::IsValidProjectFile(const std::string& filename) const {
254 if (filename.empty()) {
255 return false;
256 }
257
258 std::string extension = std::filesystem::path(filename).extension().string();
259
260 // Accept .yazeproj by extension on all platforms. On native builds we can
261 // additionally verify it is a directory; on WASM we trust the extension.
262 if (extension == ".yazeproj") {
263#ifndef __EMSCRIPTEN__
264 return std::filesystem::is_directory(filename);
265#else
266 return true;
267#endif
268 }
269
270#ifndef __EMSCRIPTEN__
271 // Accept a file that lives inside a .yazeproj bundle directory.
272 const std::string bundle_root =
274 if (!bundle_root.empty()) {
275 return true;
276 }
277
278 if (!std::filesystem::exists(filename)) {
279 return false;
280 }
281#endif
282
283 return extension == ".yaze" || extension == ".zsproj";
284}
285
287 const std::string& project_path) {
288#ifdef __EMSCRIPTEN__
289 // WASM uses virtual storage; nothing to provision eagerly.
290 return absl::OkStatus();
291#else
292 try {
293 std::filesystem::create_directories(project_path);
294 std::filesystem::create_directories(project_path + "/assets");
295 std::filesystem::create_directories(project_path + "/scripts");
296 std::filesystem::create_directories(project_path + "/output");
297 return absl::OkStatus();
298 } catch (const std::exception& e) {
299 return absl::InternalError(
300 absl::StrFormat("Failed to create project structure: %s", e.what()));
301 }
302#endif
303}
304
305// ============================================================================
306// ROM-First Workflow Implementation
307// ============================================================================
308
309absl::Status ProjectManager::SetProjectRom(const std::string& rom_path) {
310 if (rom_path.empty()) {
311 return absl::InvalidArgumentError("ROM path cannot be empty");
312 }
313
314#ifndef __EMSCRIPTEN__
315 if (!std::filesystem::exists(rom_path)) {
316 return absl::NotFoundError(
317 absl::StrFormat("ROM file not found: %s", rom_path));
318 }
319#endif
320
323
324 if (toast_manager_) {
326 absl::StrFormat("ROM set for project: %s",
327 std::filesystem::path(rom_path).filename().string()),
329 }
330
331 return absl::OkStatus();
332}
333
335 const std::string& project_name, const std::string& project_path) {
336 if (project_name.empty()) {
337 return absl::InvalidArgumentError("Project name cannot be empty");
338 }
339
340 current_project_.name = project_name;
341
342 if (!project_path.empty()) {
343 current_project_.filepath = project_path;
344 } else {
346 }
347
350
351 // Initialize project structure if we have a directory
352 std::string project_dir;
353#ifndef __EMSCRIPTEN__
354 project_dir =
355 std::filesystem::path(current_project_.filepath).parent_path().string();
356#endif
357 if (!project_dir.empty()) {
358 auto status = InitializeProjectStructure(project_dir);
359 if (!status.ok()) {
360 if (toast_manager_) {
361 toast_manager_->Show("Could not create project directories",
363 }
364 }
365 }
366
367 if (toast_manager_) {
368 toast_manager_->Show(absl::StrFormat("Project created: %s", project_name),
370 }
371
372 return absl::OkStatus();
373}
374
380
381 if (toast_manager_) {
382 toast_manager_->Show("Project creation cancelled", ToastType::kInfo);
383 }
384 }
385}
386
392
393// ============================================================================
394// ZSCustomOverworld Presets
395// ============================================================================
396
397std::vector<project::ProjectManager::ProjectTemplate>
399 std::vector<project::ProjectManager::ProjectTemplate> templates;
400
401 // Vanilla ROM Hack
402 {
404 t.name = "Vanilla ROM Hack";
405 t.description =
406 "Standard ROM editing without custom ASM patches. "
407 "Limited to vanilla game features.";
408 t.icon = "MD_GAMEPAD";
411 templates.push_back(t);
412 }
413
414 // ZSCustomOverworld v2
415 {
417 t.name = "ZSCustomOverworld v2";
418 t.description =
419 "Basic overworld expansion with custom BG colors, "
420 "main palettes, and parent system.";
421 t.icon = "MD_MAP";
425 templates.push_back(t);
426 }
427
428 // ZSCustomOverworld v3 (Recommended)
429 {
431 t.name = "ZSCustomOverworld v3";
432 t.description =
433 "Full overworld expansion: wide/tall areas, animated GFX, "
434 "subscreen overlays, and all custom features.";
435 t.icon = "MD_TERRAIN";
445 templates.push_back(t);
446 }
447
448 // Randomizer Compatible
449 {
451 t.name = "Randomizer Compatible";
452 t.description =
453 "Minimal editing preset compatible with ALttP Randomizer. "
454 "Avoids changes that break randomizer compatibility.";
455 t.icon = "MD_SHUFFLE";
458 templates.push_back(t);
459 }
460
461 return templates;
462}
463
464absl::Status ProjectManager::ApplyZsoPreset(const std::string& preset_name) {
465 auto templates = GetZsoTemplates();
466
467 for (const auto& t : templates) {
468 if (t.name == preset_name) {
469 // Apply feature flags from template
470 current_project_.feature_flags = t.template_project.feature_flags;
471
472 if (toast_manager_) {
473 toast_manager_->Show(absl::StrFormat("Applied preset: %s", preset_name),
475 }
476
477 return absl::OkStatus();
478 }
479 }
480
481 return absl::NotFoundError(
482 absl::StrFormat("Unknown preset: %s", preset_name));
483}
484
485} // namespace editor
486} // namespace yaze
std::string GenerateProjectFilename(const std::string &project_name) const
absl::Status FinalizeProjectCreation(const std::string &project_name, const std::string &project_path)
Complete project creation after ROM is loaded.
absl::Status SaveProjectAs(const std::string &filename="")
absl::Status ExportProject(const std::string &export_path)
std::vector< std::string > GetAvailableTemplates() const
std::string GetProjectPath() const
absl::Status ApplyZsoPreset(const std::string &preset_name)
Apply a ZSO preset to the current project.
bool IsValidProjectFile(const std::string &filename) const
absl::Status SetProjectRom(const std::string &rom_path)
Set the ROM for the current project.
absl::Status CreateNewProject(const std::string &template_name="")
project::YazeProject current_project_
absl::Status OpenProject(const std::string &filename="")
void RequestRomSelection()
Request ROM selection (triggers callback)
static std::vector< project::ProjectManager::ProjectTemplate > GetZsoTemplates()
Get ZSO-specific project templates.
void CancelPendingProject()
Cancel pending project creation.
ProjectManager(ToastManager *toast_manager)
absl::Status ImportProject(const std::string &project_path)
std::function< void()> rom_selection_callback_
absl::Status LoadProjectFromFile(const std::string &filename)
absl::Status InitializeProjectStructure(const std::string &project_path)
absl::Status CreateFromTemplate(const std::string &template_name, const std::string &project_name)
absl::Status SaveProjectToFile(const std::string &filename)
std::string GetProjectName() const
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Overworld overworld
Modern project structure with comprehensive settings consolidation.
Definition project.h:120
static std::string ResolveBundleRoot(const std::string &path)
Definition project.cc:269
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:1109
std::string GetDisplayName() const
Definition project.cc:1260
absl::Status SaveAs(const std::string &new_path)
Definition project.cc:440
absl::Status Open(const std::string &project_path)
Definition project.cc:295
absl::Status Validate() const
Definition project.cc:1170
core::FeatureFlags::Flags feature_flags
Definition project.h:148