Refactor project structure and enhance project management features
- Updated the Rom class to use core::ResourceLabelManager for better namespace clarity. - Introduced a comprehensive YazeProject structure consolidating project metadata, settings, and resource management. - Enhanced project management capabilities with methods for creating, opening, saving, and validating projects. - Implemented support for ZScream project format import and export, improving compatibility with existing projects. - Added workspace settings and feature flags to streamline user configurations and project setup.
This commit is contained in:
@@ -1,188 +1,831 @@
|
||||
#include "project.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||
#include "util/macro.h"
|
||||
#include "yaze_config.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace core {
|
||||
|
||||
absl::Status Project::Open(const std::string& project_path) {
|
||||
namespace {
|
||||
// Helper functions for parsing key-value pairs
|
||||
std::pair<std::string, std::string> ParseKeyValue(const std::string& line) {
|
||||
size_t eq_pos = line.find('=');
|
||||
if (eq_pos == std::string::npos) return {"", ""};
|
||||
|
||||
std::string key = line.substr(0, eq_pos);
|
||||
std::string value = line.substr(eq_pos + 1);
|
||||
|
||||
// Trim whitespace
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t") + 1);
|
||||
|
||||
return {key, value};
|
||||
}
|
||||
|
||||
bool ParseBool(const std::string& value) {
|
||||
return value == "true" || value == "1" || value == "yes";
|
||||
}
|
||||
|
||||
float ParseFloat(const std::string& value) {
|
||||
try {
|
||||
return std::stof(value);
|
||||
} catch (...) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ParseStringList(const std::string& value) {
|
||||
std::vector<std::string> result;
|
||||
if (value.empty()) return result;
|
||||
|
||||
std::vector<std::string> parts = absl::StrSplit(value, ',');
|
||||
for (const auto& part : parts) {
|
||||
std::string trimmed = std::string(part);
|
||||
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
|
||||
trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
|
||||
if (!trimmed.empty()) {
|
||||
result.push_back(trimmed);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// YazeProject Implementation
|
||||
absl::Status YazeProject::Create(const std::string& project_name, const std::string& base_path) {
|
||||
name = project_name;
|
||||
filepath = base_path + "/" + project_name + ".yaze";
|
||||
|
||||
// Initialize metadata
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
|
||||
|
||||
metadata.created_date = ss.str();
|
||||
metadata.last_modified = ss.str();
|
||||
metadata.yaze_version = "0.3.0"; // TODO: Get from version header
|
||||
metadata.version = "2.0";
|
||||
metadata.created_by = "YAZE";
|
||||
|
||||
InitializeDefaults();
|
||||
|
||||
// Create project directory structure
|
||||
std::filesystem::path project_dir(base_path + "/" + project_name);
|
||||
std::filesystem::create_directories(project_dir);
|
||||
std::filesystem::create_directories(project_dir / "code");
|
||||
std::filesystem::create_directories(project_dir / "assets");
|
||||
std::filesystem::create_directories(project_dir / "patches");
|
||||
std::filesystem::create_directories(project_dir / "backups");
|
||||
std::filesystem::create_directories(project_dir / "output");
|
||||
|
||||
// Set folder paths
|
||||
code_folder = (project_dir / "code").string();
|
||||
assets_folder = (project_dir / "assets").string();
|
||||
patches_folder = (project_dir / "patches").string();
|
||||
rom_backup_folder = (project_dir / "backups").string();
|
||||
output_folder = (project_dir / "output").string();
|
||||
labels_filename = (project_dir / "labels.txt").string();
|
||||
symbols_filename = (project_dir / "symbols.txt").string();
|
||||
|
||||
return Save();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::Open(const std::string& project_path) {
|
||||
filepath = project_path;
|
||||
name = project_path.substr(project_path.find_last_of("/") + 1);
|
||||
|
||||
// Determine format and load accordingly
|
||||
if (project_path.ends_with(".yaze")) {
|
||||
format = ProjectFormat::kYazeNative;
|
||||
return LoadFromYazeFormat(project_path);
|
||||
} else if (project_path.ends_with(".zsproj")) {
|
||||
format = ProjectFormat::kZScreamCompat;
|
||||
return ImportFromZScreamFormat(project_path);
|
||||
}
|
||||
|
||||
return absl::InvalidArgumentError("Unsupported project file format");
|
||||
}
|
||||
|
||||
std::ifstream in(project_path);
|
||||
absl::Status YazeProject::Save() {
|
||||
return SaveToYazeFormat();
|
||||
}
|
||||
|
||||
if (!in.good()) {
|
||||
return absl::InternalError("Could not open project file.");
|
||||
absl::Status YazeProject::SaveAs(const std::string& new_path) {
|
||||
std::string old_filepath = filepath;
|
||||
filepath = new_path;
|
||||
|
||||
auto status = Save();
|
||||
if (!status.ok()) {
|
||||
filepath = old_filepath; // Restore on failure
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
absl::Status YazeProject::LoadFromYazeFormat(const std::string& project_path) {
|
||||
std::ifstream file(project_path);
|
||||
if (!file.is_open()) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat("Cannot open project file: %s", project_path));
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::getline(in, name);
|
||||
std::getline(in, filepath);
|
||||
std::getline(in, rom_filename_);
|
||||
std::getline(in, code_folder_);
|
||||
std::getline(in, labels_filename_);
|
||||
std::getline(in, keybindings_file);
|
||||
|
||||
while (std::getline(in, line)) {
|
||||
if (line == kEndOfProjectFile) {
|
||||
break;
|
||||
std::string current_section = "";
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
// Skip empty lines and comments
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
// Check for section headers [section_name]
|
||||
if (line[0] == '[' && line.back() == ']') {
|
||||
current_section = line.substr(1, line.length() - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto [key, value] = ParseKeyValue(line);
|
||||
if (key.empty()) continue;
|
||||
|
||||
// Parse based on current section
|
||||
if (current_section == "project") {
|
||||
if (key == "name") name = value;
|
||||
else if (key == "description") metadata.description = value;
|
||||
else if (key == "author") metadata.author = value;
|
||||
else if (key == "license") metadata.license = value;
|
||||
else if (key == "version") metadata.version = value;
|
||||
else if (key == "created_date") metadata.created_date = value;
|
||||
else if (key == "last_modified") metadata.last_modified = value;
|
||||
else if (key == "yaze_version") metadata.yaze_version = value;
|
||||
else if (key == "tags") metadata.tags = ParseStringList(value);
|
||||
}
|
||||
else if (current_section == "files") {
|
||||
if (key == "rom_filename") rom_filename = value;
|
||||
else if (key == "rom_backup_folder") rom_backup_folder = value;
|
||||
else if (key == "code_folder") code_folder = value;
|
||||
else if (key == "assets_folder") assets_folder = value;
|
||||
else if (key == "patches_folder") patches_folder = value;
|
||||
else if (key == "labels_filename") labels_filename = value;
|
||||
else if (key == "symbols_filename") symbols_filename = value;
|
||||
else if (key == "output_folder") output_folder = value;
|
||||
else if (key == "additional_roms") additional_roms = ParseStringList(value);
|
||||
}
|
||||
else if (current_section == "feature_flags") {
|
||||
if (key == "load_custom_overworld") feature_flags.overworld.kLoadCustomOverworld = ParseBool(value);
|
||||
else if (key == "apply_zs_custom_overworld_asm") feature_flags.overworld.kApplyZSCustomOverworldASM = ParseBool(value);
|
||||
else if (key == "save_dungeon_maps") feature_flags.kSaveDungeonMaps = ParseBool(value);
|
||||
else if (key == "save_graphics_sheet") feature_flags.kSaveGraphicsSheet = ParseBool(value);
|
||||
else if (key == "log_instructions") feature_flags.kLogInstructions = ParseBool(value);
|
||||
}
|
||||
else if (current_section == "workspace") {
|
||||
if (key == "font_global_scale") workspace_settings.font_global_scale = ParseFloat(value);
|
||||
else if (key == "dark_mode") workspace_settings.dark_mode = ParseBool(value);
|
||||
else if (key == "ui_theme") workspace_settings.ui_theme = value;
|
||||
else if (key == "autosave_enabled") workspace_settings.autosave_enabled = ParseBool(value);
|
||||
else if (key == "autosave_interval_secs") workspace_settings.autosave_interval_secs = ParseFloat(value);
|
||||
else if (key == "backup_on_save") workspace_settings.backup_on_save = ParseBool(value);
|
||||
else if (key == "show_grid") workspace_settings.show_grid = ParseBool(value);
|
||||
else if (key == "show_collision") workspace_settings.show_collision = ParseBool(value);
|
||||
else if (key == "last_layout_preset") workspace_settings.last_layout_preset = value;
|
||||
else if (key == "saved_layouts") workspace_settings.saved_layouts = ParseStringList(value);
|
||||
else if (key == "recent_files") workspace_settings.recent_files = ParseStringList(value);
|
||||
}
|
||||
else if (current_section == "build") {
|
||||
if (key == "build_script") build_script = value;
|
||||
else if (key == "output_folder") output_folder = value;
|
||||
else if (key == "git_repository") git_repository = value;
|
||||
else if (key == "track_changes") track_changes = ParseBool(value);
|
||||
else if (key == "build_configurations") build_configurations = ParseStringList(value);
|
||||
}
|
||||
else if (current_section.starts_with("labels_")) {
|
||||
// Resource labels: [labels_type_name] followed by key=value pairs
|
||||
std::string label_type = current_section.substr(7); // Remove "labels_" prefix
|
||||
resource_labels[label_type][key] = value;
|
||||
}
|
||||
else if (current_section == "keybindings") {
|
||||
workspace_settings.custom_keybindings[key] = value;
|
||||
}
|
||||
else if (current_section == "editor_visibility") {
|
||||
workspace_settings.editor_visibility[key] = ParseBool(value);
|
||||
}
|
||||
else if (current_section == "zscream_compatibility") {
|
||||
if (key == "original_project_file") zscream_project_file = value;
|
||||
else zscream_mappings[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
in.close();
|
||||
absl::Status YazeProject::SaveToYazeFormat() {
|
||||
// Update last modified timestamp
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
|
||||
metadata.last_modified = ss.str();
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat("Cannot create project file: %s", filepath));
|
||||
}
|
||||
|
||||
// Write header comment
|
||||
file << "# YAZE Project File\n";
|
||||
file << "# Format Version: 2.0\n";
|
||||
file << "# Generated by YAZE " << metadata.yaze_version << "\n";
|
||||
file << "# Last Modified: " << metadata.last_modified << "\n\n";
|
||||
|
||||
// Project section
|
||||
file << "[project]\n";
|
||||
file << "name=" << name << "\n";
|
||||
file << "description=" << metadata.description << "\n";
|
||||
file << "author=" << metadata.author << "\n";
|
||||
file << "license=" << metadata.license << "\n";
|
||||
file << "version=" << metadata.version << "\n";
|
||||
file << "created_date=" << metadata.created_date << "\n";
|
||||
file << "last_modified=" << metadata.last_modified << "\n";
|
||||
file << "yaze_version=" << metadata.yaze_version << "\n";
|
||||
file << "tags=" << absl::StrJoin(metadata.tags, ",") << "\n\n";
|
||||
|
||||
// Files section
|
||||
file << "[files]\n";
|
||||
file << "rom_filename=" << GetRelativePath(rom_filename) << "\n";
|
||||
file << "rom_backup_folder=" << GetRelativePath(rom_backup_folder) << "\n";
|
||||
file << "code_folder=" << GetRelativePath(code_folder) << "\n";
|
||||
file << "assets_folder=" << GetRelativePath(assets_folder) << "\n";
|
||||
file << "patches_folder=" << GetRelativePath(patches_folder) << "\n";
|
||||
file << "labels_filename=" << GetRelativePath(labels_filename) << "\n";
|
||||
file << "symbols_filename=" << GetRelativePath(symbols_filename) << "\n";
|
||||
file << "output_folder=" << GetRelativePath(output_folder) << "\n";
|
||||
file << "additional_roms=" << absl::StrJoin(additional_roms, ",") << "\n\n";
|
||||
|
||||
// Feature flags section
|
||||
file << "[feature_flags]\n";
|
||||
file << "load_custom_overworld=" << (feature_flags.overworld.kLoadCustomOverworld ? "true" : "false") << "\n";
|
||||
file << "apply_zs_custom_overworld_asm=" << (feature_flags.overworld.kApplyZSCustomOverworldASM ? "true" : "false") << "\n";
|
||||
file << "save_dungeon_maps=" << (feature_flags.kSaveDungeonMaps ? "true" : "false") << "\n";
|
||||
file << "save_graphics_sheet=" << (feature_flags.kSaveGraphicsSheet ? "true" : "false") << "\n";
|
||||
file << "log_instructions=" << (feature_flags.kLogInstructions ? "true" : "false") << "\n\n";
|
||||
|
||||
// Workspace settings section
|
||||
file << "[workspace]\n";
|
||||
file << "font_global_scale=" << workspace_settings.font_global_scale << "\n";
|
||||
file << "dark_mode=" << (workspace_settings.dark_mode ? "true" : "false") << "\n";
|
||||
file << "ui_theme=" << workspace_settings.ui_theme << "\n";
|
||||
file << "autosave_enabled=" << (workspace_settings.autosave_enabled ? "true" : "false") << "\n";
|
||||
file << "autosave_interval_secs=" << workspace_settings.autosave_interval_secs << "\n";
|
||||
file << "backup_on_save=" << (workspace_settings.backup_on_save ? "true" : "false") << "\n";
|
||||
file << "show_grid=" << (workspace_settings.show_grid ? "true" : "false") << "\n";
|
||||
file << "show_collision=" << (workspace_settings.show_collision ? "true" : "false") << "\n";
|
||||
file << "last_layout_preset=" << workspace_settings.last_layout_preset << "\n";
|
||||
file << "saved_layouts=" << absl::StrJoin(workspace_settings.saved_layouts, ",") << "\n";
|
||||
file << "recent_files=" << absl::StrJoin(workspace_settings.recent_files, ",") << "\n\n";
|
||||
|
||||
// Custom keybindings section
|
||||
if (!workspace_settings.custom_keybindings.empty()) {
|
||||
file << "[keybindings]\n";
|
||||
for (const auto& [key, value] : workspace_settings.custom_keybindings) {
|
||||
file << key << "=" << value << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
// Editor visibility section
|
||||
if (!workspace_settings.editor_visibility.empty()) {
|
||||
file << "[editor_visibility]\n";
|
||||
for (const auto& [key, value] : workspace_settings.editor_visibility) {
|
||||
file << key << "=" << (value ? "true" : "false") << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
// Resource labels sections
|
||||
for (const auto& [type, labels] : resource_labels) {
|
||||
if (!labels.empty()) {
|
||||
file << "[labels_" << type << "]\n";
|
||||
for (const auto& [key, value] : labels) {
|
||||
file << key << "=" << value << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Build settings section
|
||||
file << "[build]\n";
|
||||
file << "build_script=" << build_script << "\n";
|
||||
file << "output_folder=" << GetRelativePath(output_folder) << "\n";
|
||||
file << "git_repository=" << git_repository << "\n";
|
||||
file << "track_changes=" << (track_changes ? "true" : "false") << "\n";
|
||||
file << "build_configurations=" << absl::StrJoin(build_configurations, ",") << "\n\n";
|
||||
|
||||
// ZScream compatibility section
|
||||
if (!zscream_project_file.empty()) {
|
||||
file << "[zscream_compatibility]\n";
|
||||
file << "original_project_file=" << zscream_project_file << "\n";
|
||||
for (const auto& [key, value] : zscream_mappings) {
|
||||
file << key << "=" << value << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
file << "# End of YAZE Project File\n";
|
||||
file.close();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Project::Save() {
|
||||
RETURN_IF_ERROR(CheckForEmptyFields());
|
||||
absl::Status YazeProject::ImportZScreamProject(const std::string& zscream_project_path) {
|
||||
// Basic ZScream project import (to be expanded based on ZScream format)
|
||||
zscream_project_file = zscream_project_path;
|
||||
format = ProjectFormat::kZScreamCompat;
|
||||
|
||||
// Extract project name from path
|
||||
std::filesystem::path zs_path(zscream_project_path);
|
||||
name = zs_path.stem().string() + "_imported";
|
||||
|
||||
// Set up basic mapping for common fields
|
||||
zscream_mappings["rom_file"] = "rom_filename";
|
||||
zscream_mappings["source_code"] = "code_folder";
|
||||
zscream_mappings["project_name"] = "name";
|
||||
|
||||
InitializeDefaults();
|
||||
|
||||
// TODO: Implement actual ZScream format parsing when format is known
|
||||
// For now, just create a project structure that can be manually configured
|
||||
|
||||
std::ofstream out(filepath + "/" + name + ".yaze");
|
||||
if (!out.good()) {
|
||||
return absl::InternalError("Could not open project file.");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::ExportForZScream(const std::string& target_path) {
|
||||
// Create a simplified project file that ZScream might understand
|
||||
std::ofstream file(target_path);
|
||||
if (!file.is_open()) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat("Cannot create ZScream project file: %s", target_path));
|
||||
}
|
||||
|
||||
// Write in a simple format that ZScream might understand
|
||||
file << "# ZScream Compatible Project File\n";
|
||||
file << "# Exported from YAZE " << metadata.yaze_version << "\n\n";
|
||||
file << "name=" << name << "\n";
|
||||
file << "rom_file=" << rom_filename << "\n";
|
||||
file << "source_code=" << code_folder << "\n";
|
||||
file << "description=" << metadata.description << "\n";
|
||||
file << "author=" << metadata.author << "\n";
|
||||
file << "created_with=YAZE " << metadata.yaze_version << "\n";
|
||||
|
||||
file.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::LoadAllSettings() {
|
||||
// Consolidated loading of all settings from project file
|
||||
// This replaces scattered config loading throughout the application
|
||||
return LoadFromYazeFormat(filepath);
|
||||
}
|
||||
|
||||
absl::Status YazeProject::SaveAllSettings() {
|
||||
// Consolidated saving of all settings to project file
|
||||
return SaveToYazeFormat();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::ResetToDefaults() {
|
||||
InitializeDefaults();
|
||||
return Save();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::Validate() const {
|
||||
std::vector<std::string> errors;
|
||||
|
||||
if (name.empty()) errors.push_back("Project name is required");
|
||||
if (filepath.empty()) errors.push_back("Project file path is required");
|
||||
if (rom_filename.empty()) errors.push_back("ROM file is required");
|
||||
|
||||
// Check if files exist
|
||||
if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
|
||||
errors.push_back("ROM file does not exist: " + rom_filename);
|
||||
}
|
||||
|
||||
if (!code_folder.empty() && !std::filesystem::exists(GetAbsolutePath(code_folder))) {
|
||||
errors.push_back("Code folder does not exist: " + code_folder);
|
||||
}
|
||||
|
||||
if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
|
||||
errors.push_back("Labels file does not exist: " + labels_filename);
|
||||
}
|
||||
|
||||
if (!errors.empty()) {
|
||||
return absl::InvalidArgumentError(absl::StrJoin(errors, "; "));
|
||||
}
|
||||
|
||||
out << name << std::endl;
|
||||
out << filepath << std::endl;
|
||||
out << rom_filename_ << std::endl;
|
||||
out << code_folder_ << std::endl;
|
||||
out << labels_filename_ << std::endl;
|
||||
out << keybindings_file << std::endl;
|
||||
|
||||
out << kEndOfProjectFile << std::endl;
|
||||
|
||||
out.close();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::vector<std::string> YazeProject::GetMissingFiles() const {
|
||||
std::vector<std::string> missing;
|
||||
|
||||
if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
|
||||
missing.push_back(rom_filename);
|
||||
}
|
||||
if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
|
||||
missing.push_back(labels_filename);
|
||||
}
|
||||
if (!symbols_filename.empty() && !std::filesystem::exists(GetAbsolutePath(symbols_filename))) {
|
||||
missing.push_back(symbols_filename);
|
||||
}
|
||||
|
||||
return missing;
|
||||
}
|
||||
|
||||
absl::Status YazeProject::RepairProject() {
|
||||
// Create missing directories
|
||||
std::vector<std::string> folders = {code_folder, assets_folder, patches_folder,
|
||||
rom_backup_folder, output_folder};
|
||||
|
||||
for (const auto& folder : folders) {
|
||||
if (!folder.empty()) {
|
||||
std::filesystem::path abs_path = GetAbsolutePath(folder);
|
||||
if (!std::filesystem::exists(abs_path)) {
|
||||
std::filesystem::create_directories(abs_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create missing files with defaults
|
||||
if (!labels_filename.empty()) {
|
||||
std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
|
||||
if (!std::filesystem::exists(abs_labels)) {
|
||||
std::ofstream labels_file(abs_labels);
|
||||
labels_file << "# YAZE Resource Labels\n";
|
||||
labels_file << "# Format: [type] key=value\n\n";
|
||||
labels_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::string YazeProject::GetDisplayName() const {
|
||||
if (!metadata.description.empty()) {
|
||||
return metadata.description;
|
||||
}
|
||||
return name.empty() ? "Untitled Project" : name;
|
||||
}
|
||||
|
||||
std::string YazeProject::GetRelativePath(const std::string& absolute_path) const {
|
||||
if (absolute_path.empty() || filepath.empty()) return absolute_path;
|
||||
|
||||
std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
|
||||
std::filesystem::path abs_path(absolute_path);
|
||||
|
||||
try {
|
||||
std::filesystem::path relative = std::filesystem::relative(abs_path, project_dir);
|
||||
return relative.string();
|
||||
} catch (...) {
|
||||
return absolute_path; // Return absolute path if relative conversion fails
|
||||
}
|
||||
}
|
||||
|
||||
std::string YazeProject::GetAbsolutePath(const std::string& relative_path) const {
|
||||
if (relative_path.empty() || filepath.empty()) return relative_path;
|
||||
|
||||
std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
|
||||
std::filesystem::path abs_path = project_dir / relative_path;
|
||||
|
||||
return abs_path.string();
|
||||
}
|
||||
|
||||
bool YazeProject::IsEmpty() const {
|
||||
return name.empty() && rom_filename.empty() && code_folder.empty();
|
||||
}
|
||||
|
||||
absl::Status YazeProject::ImportFromZScreamFormat(const std::string& project_path) {
|
||||
// TODO: Implement ZScream format parsing when format specification is available
|
||||
// For now, create a basic project that can be manually configured
|
||||
|
||||
std::filesystem::path zs_path(project_path);
|
||||
name = zs_path.stem().string() + "_imported";
|
||||
zscream_project_file = project_path;
|
||||
|
||||
InitializeDefaults();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void YazeProject::InitializeDefaults() {
|
||||
// Initialize default feature flags
|
||||
feature_flags.overworld.kLoadCustomOverworld = false;
|
||||
feature_flags.overworld.kApplyZSCustomOverworldASM = false;
|
||||
feature_flags.kSaveDungeonMaps = true;
|
||||
feature_flags.kSaveGraphicsSheet = true;
|
||||
feature_flags.kLogInstructions = false;
|
||||
|
||||
// Initialize default workspace settings
|
||||
workspace_settings.font_global_scale = 1.0f;
|
||||
workspace_settings.dark_mode = true;
|
||||
workspace_settings.ui_theme = "default";
|
||||
workspace_settings.autosave_enabled = true;
|
||||
workspace_settings.autosave_interval_secs = 300.0f; // 5 minutes
|
||||
workspace_settings.backup_on_save = true;
|
||||
workspace_settings.show_grid = true;
|
||||
workspace_settings.show_collision = false;
|
||||
|
||||
// Initialize default build configurations
|
||||
build_configurations = {"Debug", "Release", "Distribution"};
|
||||
|
||||
track_changes = true;
|
||||
}
|
||||
|
||||
std::string YazeProject::GenerateProjectId() const {
|
||||
auto now = std::chrono::system_clock::now().time_since_epoch();
|
||||
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
|
||||
return absl::StrFormat("yaze_project_%lld", timestamp);
|
||||
}
|
||||
|
||||
// ProjectManager Implementation
|
||||
std::vector<ProjectManager::ProjectTemplate> ProjectManager::GetProjectTemplates() {
|
||||
return {
|
||||
{
|
||||
"Basic ROM Hack",
|
||||
"Simple project for modifying an existing ROM with basic tools",
|
||||
ICON_MD_VIDEOGAME_ASSET,
|
||||
{} // Basic defaults
|
||||
},
|
||||
{
|
||||
"Full Overworld Mod",
|
||||
"Complete overworld modification with custom graphics and maps",
|
||||
ICON_MD_MAP,
|
||||
{} // Overworld-focused settings
|
||||
},
|
||||
{
|
||||
"Dungeon Designer",
|
||||
"Focused on dungeon creation and modification",
|
||||
ICON_MD_DOMAIN,
|
||||
{} // Dungeon-focused settings
|
||||
},
|
||||
{
|
||||
"Graphics Pack",
|
||||
"Project focused on graphics, sprites, and visual modifications",
|
||||
ICON_MD_PALETTE,
|
||||
{} // Graphics-focused settings
|
||||
},
|
||||
{
|
||||
"Complete Overhaul",
|
||||
"Full-scale ROM hack with all features enabled",
|
||||
ICON_MD_BUILD,
|
||||
{} // All features enabled
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
absl::StatusOr<YazeProject> ProjectManager::CreateFromTemplate(
|
||||
const std::string& template_name,
|
||||
const std::string& project_name,
|
||||
const std::string& base_path) {
|
||||
|
||||
YazeProject project;
|
||||
auto status = project.Create(project_name, base_path);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Customize based on template
|
||||
if (template_name == "Full Overworld Mod") {
|
||||
project.feature_flags.overworld.kLoadCustomOverworld = true;
|
||||
project.feature_flags.overworld.kApplyZSCustomOverworldASM = true;
|
||||
project.metadata.description = "Overworld modification project";
|
||||
project.metadata.tags = {"overworld", "maps", "graphics"};
|
||||
} else if (template_name == "Dungeon Designer") {
|
||||
project.feature_flags.kSaveDungeonMaps = true;
|
||||
project.workspace_settings.show_grid = true;
|
||||
project.metadata.description = "Dungeon design and modification project";
|
||||
project.metadata.tags = {"dungeons", "rooms", "design"};
|
||||
} else if (template_name == "Graphics Pack") {
|
||||
project.feature_flags.kSaveGraphicsSheet = true;
|
||||
project.workspace_settings.show_grid = true;
|
||||
project.metadata.description = "Graphics and sprite modification project";
|
||||
project.metadata.tags = {"graphics", "sprites", "palettes"};
|
||||
} else if (template_name == "Complete Overhaul") {
|
||||
project.feature_flags.overworld.kLoadCustomOverworld = true;
|
||||
project.feature_flags.overworld.kApplyZSCustomOverworldASM = true;
|
||||
project.feature_flags.kSaveDungeonMaps = true;
|
||||
project.feature_flags.kSaveGraphicsSheet = true;
|
||||
project.metadata.description = "Complete ROM overhaul project";
|
||||
project.metadata.tags = {"complete", "overhaul", "full-mod"};
|
||||
}
|
||||
|
||||
status = project.Save();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
std::vector<std::string> ProjectManager::FindProjectsInDirectory(const std::string& directory) {
|
||||
std::vector<std::string> projects;
|
||||
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filename = entry.path().filename().string();
|
||||
if (filename.ends_with(".yaze") || filename.ends_with(".zsproj")) {
|
||||
projects.push_back(entry.path().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
// Directory doesn't exist or can't be accessed
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
absl::Status ProjectManager::BackupProject(const YazeProject& project) {
|
||||
if (project.filepath.empty()) {
|
||||
return absl::InvalidArgumentError("Project has no file path");
|
||||
}
|
||||
|
||||
std::filesystem::path project_path(project.filepath);
|
||||
std::filesystem::path backup_dir = project_path.parent_path() / "backups";
|
||||
std::filesystem::create_directories(backup_dir);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
|
||||
|
||||
std::string backup_filename = project.name + "_backup_" + ss.str() + ".yaze";
|
||||
std::filesystem::path backup_path = backup_dir / backup_filename;
|
||||
|
||||
try {
|
||||
std::filesystem::copy_file(project.filepath, backup_path);
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
return absl::InternalError(absl::StrFormat("Failed to backup project: %s", e.what()));
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ProjectManager::ValidateProjectStructure(const YazeProject& project) {
|
||||
return project.Validate();
|
||||
}
|
||||
|
||||
std::vector<std::string> ProjectManager::GetRecommendedFixesForProject(const YazeProject& project) {
|
||||
std::vector<std::string> recommendations;
|
||||
|
||||
if (project.rom_filename.empty()) {
|
||||
recommendations.push_back("Add a ROM file to begin editing");
|
||||
}
|
||||
|
||||
if (project.code_folder.empty()) {
|
||||
recommendations.push_back("Set up a code folder for assembly patches");
|
||||
}
|
||||
|
||||
if (project.labels_filename.empty()) {
|
||||
recommendations.push_back("Create a labels file for better organization");
|
||||
}
|
||||
|
||||
if (project.metadata.description.empty()) {
|
||||
recommendations.push_back("Add a project description for documentation");
|
||||
}
|
||||
|
||||
if (project.git_repository.empty() && project.track_changes) {
|
||||
recommendations.push_back("Consider setting up version control for your project");
|
||||
}
|
||||
|
||||
auto missing_files = project.GetMissingFiles();
|
||||
if (!missing_files.empty()) {
|
||||
recommendations.push_back("Some project files are missing - use Project > Repair to fix");
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Compatibility implementations for ResourceLabelManager and related classes
|
||||
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
|
||||
filename_ = filename;
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
// Create the file if it does not exist
|
||||
std::ofstream create_file(filename);
|
||||
if (!create_file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
create_file.close();
|
||||
file.open(filename);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
labels_loaded_ = false;
|
||||
return false;
|
||||
}
|
||||
filename_ = filename;
|
||||
|
||||
|
||||
labels_.clear();
|
||||
std::string line;
|
||||
std::string current_type = "";
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string type, key, value;
|
||||
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') &&
|
||||
std::getline(iss, value)) {
|
||||
labels_[type][key] = value;
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
// Check for type headers [type_name]
|
||||
if (line[0] == '[' && line.back() == ']') {
|
||||
current_type = line.substr(1, line.length() - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key=value pairs
|
||||
size_t eq_pos = line.find('=');
|
||||
if (eq_pos != std::string::npos && !current_type.empty()) {
|
||||
std::string key = line.substr(0, eq_pos);
|
||||
std::string value = line.substr(eq_pos + 1);
|
||||
labels_[current_type][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
labels_loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceLabelManager::SaveLabels() {
|
||||
if (!labels_loaded_) {
|
||||
return false;
|
||||
}
|
||||
if (filename_.empty()) return false;
|
||||
|
||||
std::ofstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& type_pair : labels_) {
|
||||
for (const auto& label_pair : type_pair.second) {
|
||||
file << type_pair.first << "," << label_pair.first << ","
|
||||
<< label_pair.second << std::endl;
|
||||
if (!file.is_open()) return false;
|
||||
|
||||
file << "# YAZE Resource Labels\n";
|
||||
file << "# Format: [type] followed by key=value pairs\n\n";
|
||||
|
||||
for (const auto& [type, type_labels] : labels_) {
|
||||
if (!type_labels.empty()) {
|
||||
file << "[" << type << "]\n";
|
||||
for (const auto& [key, value] : type_labels) {
|
||||
file << key << "=" << value << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceLabelManager::DisplayLabels(bool* p_open) {
|
||||
if (!labels_loaded_) {
|
||||
ImGui::Text("No labels loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p_open || !*p_open) return;
|
||||
|
||||
// Basic implementation - can be enhanced later
|
||||
if (ImGui::Begin("Resource Labels", p_open)) {
|
||||
for (const auto& type_pair : labels_) {
|
||||
if (ImGui::TreeNode(type_pair.first.c_str())) {
|
||||
for (const auto& label_pair : type_pair.second) {
|
||||
std::string label_id = type_pair.first + "_" + label_pair.first;
|
||||
ImGui::Text("%s: %s", label_pair.first.c_str(),
|
||||
label_pair.second.c_str());
|
||||
ImGui::Text("Resource Labels Manager");
|
||||
ImGui::Text("Labels loaded: %s", labels_loaded_ ? "Yes" : "No");
|
||||
ImGui::Text("Total types: %zu", labels_.size());
|
||||
|
||||
for (const auto& [type, type_labels] : labels_) {
|
||||
if (ImGui::TreeNode(type.c_str())) {
|
||||
ImGui::Text("Labels: %zu", type_labels.size());
|
||||
for (const auto& [key, value] : type_labels) {
|
||||
ImGui::Text("%s = %s", key.c_str(), value.c_str());
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Update Labels")) {
|
||||
if (SaveLabels()) {
|
||||
ImGui::Text("Labels updated successfully!");
|
||||
} else {
|
||||
ImGui::Text("Failed to update labels.");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ResourceLabelManager::EditLabel(const std::string& type,
|
||||
const std::string& key,
|
||||
const std::string& newValue) {
|
||||
void ResourceLabelManager::EditLabel(const std::string& type, const std::string& key,
|
||||
const std::string& newValue) {
|
||||
labels_[type][key] = newValue;
|
||||
}
|
||||
|
||||
void ResourceLabelManager::SelectableLabelWithNameEdit(
|
||||
bool selected, const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
std::string label = CreateOrGetLabel(type, key, defaultValue);
|
||||
ImGui::Selectable(label.c_str(), selected,
|
||||
ImGuiSelectableFlags_AllowDoubleClick);
|
||||
std::string label_id = type + "_" + key;
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
ImGui::OpenPopup(label_id.c_str());
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem(label_id.c_str())) {
|
||||
std::string* new_label = &labels_[type][key];
|
||||
if (ImGui::InputText("##Label", new_label,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
labels_[type][key] = *new_label;
|
||||
}
|
||||
if (ImGui::Button(ICON_MD_CLOSE)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
void ResourceLabelManager::SelectableLabelWithNameEdit(bool selected, const std::string& type,
|
||||
const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
// Basic implementation
|
||||
if (ImGui::Selectable(absl::StrFormat("%s: %s", key.c_str(), GetLabel(type, key).c_str()).c_str(), selected)) {
|
||||
// Handle selection
|
||||
}
|
||||
}
|
||||
|
||||
std::string ResourceLabelManager::GetLabel(const std::string& type,
|
||||
const std::string& key) {
|
||||
return labels_[type][key];
|
||||
std::string ResourceLabelManager::GetLabel(const std::string& type, const std::string& key) {
|
||||
auto type_it = labels_.find(type);
|
||||
if (type_it == labels_.end()) return "";
|
||||
|
||||
auto label_it = type_it->second.find(key);
|
||||
if (label_it == type_it->second.end()) return "";
|
||||
|
||||
return label_it->second;
|
||||
}
|
||||
|
||||
std::string ResourceLabelManager::CreateOrGetLabel(
|
||||
const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
if (labels_.find(type) == labels_.end()) {
|
||||
labels_[type] = std::unordered_map<std::string, std::string>();
|
||||
}
|
||||
if (labels_[type].find(key) == labels_[type].end()) {
|
||||
labels_[type][key] = defaultValue;
|
||||
}
|
||||
return labels_[type][key];
|
||||
std::string ResourceLabelManager::CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
auto existing = GetLabel(type, key);
|
||||
if (!existing.empty()) return existing;
|
||||
|
||||
labels_[type][key] = defaultValue;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
} // namespace yaze
|
||||
} // namespace core
|
||||
} // namespace yaze
|
||||
|
||||
@@ -2,59 +2,190 @@
|
||||
#define YAZE_APP_CORE_PROJECT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/features.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
const std::string kRecentFilesFilename = "recent_files.txt";
|
||||
constexpr char kEndOfProjectFile[] = "EndOfProjectFile";
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @struct Project
|
||||
* @brief Represents a project in the application.
|
||||
*
|
||||
* A project is a collection of files and resources that are used in the
|
||||
* creation of a Zelda3 hack that can be saved and loaded. This makes it so the
|
||||
* user can have different rom file names for a single project and keep track of
|
||||
* backups.
|
||||
* @enum ProjectFormat
|
||||
* @brief Supported project file formats
|
||||
*/
|
||||
struct Project {
|
||||
absl::Status Create(const std::string& project_name) {
|
||||
name = project_name;
|
||||
project_opened_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
absl::Status CheckForEmptyFields() {
|
||||
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
|
||||
code_folder_.empty() || labels_filename_.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Project fields cannot be empty. Please load a rom file, set your "
|
||||
"code folder, and set your labels file. See HELP for more details.");
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
absl::Status Open(const std::string& project_path);
|
||||
absl::Status Save();
|
||||
|
||||
bool project_opened_ = false;
|
||||
std::string name;
|
||||
std::string flags = "";
|
||||
std::string filepath;
|
||||
std::string rom_filename_ = "";
|
||||
std::string code_folder_ = "";
|
||||
std::string labels_filename_ = "";
|
||||
std::string keybindings_file = "";
|
||||
enum class ProjectFormat {
|
||||
kYazeNative, // .yaze - YAZE native format
|
||||
kZScreamCompat // .zsproj - ZScream compatibility format
|
||||
};
|
||||
|
||||
// Default types
|
||||
static constexpr absl::string_view kDefaultTypes[] = {
|
||||
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
|
||||
/**
|
||||
* @struct ProjectMetadata
|
||||
* @brief Enhanced metadata for project tracking
|
||||
*/
|
||||
struct ProjectMetadata {
|
||||
std::string version = "2.0";
|
||||
std::string created_by = "YAZE";
|
||||
std::string created_date;
|
||||
std::string last_modified;
|
||||
std::string yaze_version;
|
||||
std::string description;
|
||||
std::vector<std::string> tags;
|
||||
std::string author;
|
||||
std::string license;
|
||||
|
||||
// ZScream compatibility
|
||||
bool zscream_compatible = false;
|
||||
std::string zscream_version;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct WorkspaceSettings
|
||||
* @brief Consolidated workspace and UI settings
|
||||
*/
|
||||
struct WorkspaceSettings {
|
||||
// Display settings
|
||||
float font_global_scale = 1.0f;
|
||||
bool dark_mode = true;
|
||||
std::string ui_theme = "default";
|
||||
|
||||
// Layout settings
|
||||
std::string last_layout_preset;
|
||||
std::vector<std::string> saved_layouts;
|
||||
std::string window_layout_data; // ImGui .ini data
|
||||
|
||||
// Editor preferences
|
||||
bool autosave_enabled = true;
|
||||
float autosave_interval_secs = 300.0f; // 5 minutes
|
||||
bool backup_on_save = true;
|
||||
bool show_grid = true;
|
||||
bool show_collision = false;
|
||||
|
||||
// Advanced settings
|
||||
std::map<std::string, std::string> custom_keybindings;
|
||||
std::vector<std::string> recent_files;
|
||||
std::map<std::string, bool> editor_visibility;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct YazeProject
|
||||
* @brief Modern project structure with comprehensive settings consolidation
|
||||
*/
|
||||
struct YazeProject {
|
||||
// Basic project info
|
||||
ProjectMetadata metadata;
|
||||
std::string name;
|
||||
std::string filepath;
|
||||
ProjectFormat format = ProjectFormat::kYazeNative;
|
||||
|
||||
// ROM and resources
|
||||
std::string rom_filename;
|
||||
std::string rom_backup_folder;
|
||||
std::vector<std::string> additional_roms; // For multi-ROM projects
|
||||
|
||||
// Code and assets
|
||||
std::string code_folder;
|
||||
std::string assets_folder;
|
||||
std::string patches_folder;
|
||||
std::string labels_filename;
|
||||
std::string symbols_filename;
|
||||
|
||||
// Consolidated settings (previously scattered across multiple files)
|
||||
FeatureFlags::Flags feature_flags;
|
||||
WorkspaceSettings workspace_settings;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> resource_labels;
|
||||
|
||||
// Build and deployment
|
||||
std::string build_script;
|
||||
std::string output_folder;
|
||||
std::vector<std::string> build_configurations;
|
||||
|
||||
// Version control integration
|
||||
std::string git_repository;
|
||||
bool track_changes = true;
|
||||
|
||||
// ZScream compatibility (for importing existing projects)
|
||||
std::string zscream_project_file; // Path to original .zsproj if importing
|
||||
std::map<std::string, std::string> zscream_mappings; // Field mappings
|
||||
|
||||
// Methods
|
||||
absl::Status Create(const std::string& project_name, const std::string& base_path);
|
||||
absl::Status Open(const std::string& project_path);
|
||||
absl::Status Save();
|
||||
absl::Status SaveAs(const std::string& new_path);
|
||||
absl::Status ImportZScreamProject(const std::string& zscream_project_path);
|
||||
absl::Status ExportForZScream(const std::string& target_path);
|
||||
|
||||
// Settings management
|
||||
absl::Status LoadAllSettings();
|
||||
absl::Status SaveAllSettings();
|
||||
absl::Status ResetToDefaults();
|
||||
|
||||
// Validation and integrity
|
||||
absl::Status Validate() const;
|
||||
std::vector<std::string> GetMissingFiles() const;
|
||||
absl::Status RepairProject();
|
||||
|
||||
// Utilities
|
||||
std::string GetDisplayName() const;
|
||||
std::string GetRelativePath(const std::string& absolute_path) const;
|
||||
std::string GetAbsolutePath(const std::string& relative_path) const;
|
||||
bool IsEmpty() const;
|
||||
|
||||
// Project state
|
||||
bool project_opened() const { return !name.empty() && !filepath.empty(); }
|
||||
|
||||
private:
|
||||
absl::Status LoadFromYazeFormat(const std::string& project_path);
|
||||
absl::Status SaveToYazeFormat();
|
||||
absl::Status ImportFromZScreamFormat(const std::string& project_path);
|
||||
|
||||
void InitializeDefaults();
|
||||
std::string GenerateProjectId() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ProjectManager
|
||||
* @brief Enhanced project management with templates and validation
|
||||
*/
|
||||
class ProjectManager {
|
||||
public:
|
||||
// Project templates
|
||||
struct ProjectTemplate {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string icon;
|
||||
YazeProject template_project;
|
||||
};
|
||||
|
||||
static std::vector<ProjectTemplate> GetProjectTemplates();
|
||||
static absl::StatusOr<YazeProject> CreateFromTemplate(
|
||||
const std::string& template_name,
|
||||
const std::string& project_name,
|
||||
const std::string& base_path);
|
||||
|
||||
// Project discovery and management
|
||||
static std::vector<std::string> FindProjectsInDirectory(const std::string& directory);
|
||||
static absl::Status BackupProject(const YazeProject& project);
|
||||
static absl::Status RestoreProject(const std::string& backup_path);
|
||||
|
||||
// Format conversion utilities
|
||||
static absl::Status ConvertProject(const std::string& source_path,
|
||||
const std::string& target_path,
|
||||
ProjectFormat target_format);
|
||||
|
||||
// Validation and repair
|
||||
static absl::Status ValidateProjectStructure(const YazeProject& project);
|
||||
static std::vector<std::string> GetRecommendedFixesForProject(const YazeProject& project);
|
||||
};
|
||||
|
||||
// Compatibility - ResourceLabelManager (still used by ROM class)
|
||||
struct ResourceLabelManager {
|
||||
bool LoadLabels(const std::string& filename);
|
||||
bool SaveLabels();
|
||||
@@ -79,6 +210,9 @@ struct ResourceLabelManager {
|
||||
labels_;
|
||||
};
|
||||
|
||||
// Compatibility - RecentFilesManager
|
||||
const std::string kRecentFilesFilename = "recent_files.txt";
|
||||
|
||||
class RecentFilesManager {
|
||||
public:
|
||||
RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {}
|
||||
@@ -127,15 +261,7 @@ class RecentFilesManager {
|
||||
std::vector<std::string> recent_files_;
|
||||
};
|
||||
|
||||
class VersionControlManager {
|
||||
absl::Status Commit(const std::string& message);
|
||||
absl::Status Pull();
|
||||
absl::Status Push();
|
||||
} // namespace core
|
||||
} // namespace yaze
|
||||
|
||||
private:
|
||||
std::string repository_path_;
|
||||
};
|
||||
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_CORE_PROJECT_H
|
||||
#endif // YAZE_APP_CORE_PROJECT_H
|
||||
|
||||
@@ -243,7 +243,7 @@ void EditorManager::Initialize(const std::string &filename) {
|
||||
|
||||
context_.shortcut_manager.RegisterShortcut(
|
||||
"Load Last ROM", {ImGuiKey_R, ImGuiMod_Ctrl}, [this]() {
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
if (!manager.GetRecentFiles().empty()) {
|
||||
auto front = manager.GetRecentFiles().front();
|
||||
@@ -281,7 +281,7 @@ void EditorManager::Initialize(const std::string &filename) {
|
||||
|
||||
// Initialize menu items
|
||||
std::vector<gui::MenuItem> recent_files;
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
if (manager.GetRecentFiles().empty()) {
|
||||
recent_files.emplace_back("No Recent Files", "", nullptr);
|
||||
@@ -323,7 +323,7 @@ void EditorManager::Initialize(const std::string &filename) {
|
||||
[this]() { status_ = OpenProject(); });
|
||||
project_menu_subitems.emplace_back(
|
||||
"Save Project", "", [this]() { status_ = SaveProject(); },
|
||||
[this]() { return current_project_.project_opened_; });
|
||||
[this]() { return current_project_.project_opened(); });
|
||||
project_menu_subitems.emplace_back(
|
||||
"Save Workspace Layout", "", [this]() {
|
||||
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
|
||||
@@ -828,7 +828,7 @@ void EditorManager::DrawHomepage() {
|
||||
&core::FeatureFlags::get().overworld.kLoadCustomOverworld);
|
||||
|
||||
ImGui::BeginChild("Recent Files", ImVec2(-1, -1), true);
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
for (const auto &file : manager.GetRecentFiles()) {
|
||||
if (gui::ClickableText(file.c_str())) {
|
||||
@@ -1135,7 +1135,7 @@ void EditorManager::DrawMenuBar() {
|
||||
}
|
||||
Text(ICON_MD_HISTORY " Recent Files");
|
||||
Indent();
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
for (const auto &file : manager.GetRecentFiles()) {
|
||||
if (query[0] != '\0' && file.find(query) == std::string::npos) continue;
|
||||
@@ -1157,9 +1157,9 @@ void EditorManager::DrawMenuBar() {
|
||||
|
||||
if (show_resource_label_manager && current_rom_) {
|
||||
current_rom_->resource_label()->DisplayLabels(&show_resource_label_manager);
|
||||
if (current_project_.project_opened_ &&
|
||||
!current_project_.labels_filename_.empty()) {
|
||||
current_project_.labels_filename_ =
|
||||
if (current_project_.project_opened() &&
|
||||
!current_project_.labels_filename.empty()) {
|
||||
current_project_.labels_filename =
|
||||
current_rom_->resource_label()->filename_;
|
||||
}
|
||||
}
|
||||
@@ -1189,26 +1189,26 @@ void EditorManager::DrawMenuBar() {
|
||||
SameLine();
|
||||
Text("%s", current_project_.filepath.c_str());
|
||||
if (Button("ROM File", gui::kDefaultModalSize)) {
|
||||
current_project_.rom_filename_ = FileDialogWrapper::ShowOpenFileDialog();
|
||||
current_project_.rom_filename = FileDialogWrapper::ShowOpenFileDialog();
|
||||
}
|
||||
SameLine();
|
||||
Text("%s", current_project_.rom_filename_.c_str());
|
||||
Text("%s", current_project_.rom_filename.c_str());
|
||||
if (Button("Labels File", gui::kDefaultModalSize)) {
|
||||
current_project_.labels_filename_ =
|
||||
current_project_.labels_filename =
|
||||
FileDialogWrapper::ShowOpenFileDialog();
|
||||
}
|
||||
SameLine();
|
||||
Text("%s", current_project_.labels_filename_.c_str());
|
||||
Text("%s", current_project_.labels_filename.c_str());
|
||||
if (Button("Code Folder", gui::kDefaultModalSize)) {
|
||||
current_project_.code_folder_ = FileDialogWrapper::ShowOpenFolderDialog();
|
||||
current_project_.code_folder = FileDialogWrapper::ShowOpenFolderDialog();
|
||||
}
|
||||
SameLine();
|
||||
Text("%s", current_project_.code_folder_.c_str());
|
||||
Text("%s", current_project_.code_folder.c_str());
|
||||
|
||||
Separator();
|
||||
if (Button("Create", gui::kDefaultModalSize)) {
|
||||
new_project_menu = false;
|
||||
status_ = current_project_.Create(save_as_filename);
|
||||
status_ = current_project_.Create(save_as_filename, current_project_.filepath);
|
||||
if (status_.ok()) {
|
||||
status_ = current_project_.Save();
|
||||
}
|
||||
@@ -1292,7 +1292,7 @@ absl::Status EditorManager::LoadRom() {
|
||||
(void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null");
|
||||
test::TestManager::Get().SetCurrentRom(current_rom_);
|
||||
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
manager.AddFile(file_name);
|
||||
manager.Save();
|
||||
@@ -1372,52 +1372,164 @@ absl::Status EditorManager::OpenRomOrProject(const std::string &filename) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EditorManager::OpenProject() {
|
||||
Rom temp_rom;
|
||||
RETURN_IF_ERROR(temp_rom.LoadFromFile(current_project_.rom_filename_));
|
||||
|
||||
if (!temp_rom.resource_label()->LoadLabels(
|
||||
current_project_.labels_filename_)) {
|
||||
return absl::InternalError(
|
||||
"Could not load labels file, update your project file.");
|
||||
}
|
||||
// Enhanced Project Management Implementation
|
||||
|
||||
sessions_.emplace_back(std::move(temp_rom));
|
||||
RomSession &session = sessions_.back();
|
||||
for (auto *editor : session.editors.active_editors_) {
|
||||
editor->set_context(&context_);
|
||||
absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
|
||||
auto dialog_path = core::FileDialogWrapper::ShowOpenFolderDialog();
|
||||
if (dialog_path.empty()) {
|
||||
return absl::OkStatus(); // User cancelled
|
||||
}
|
||||
current_rom_ = &session.rom;
|
||||
current_editor_set_ = &session.editors;
|
||||
|
||||
// Update test manager with current ROM for ROM-dependent tests
|
||||
util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')",
|
||||
(void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null");
|
||||
test::TestManager::Get().SetCurrentRom(current_rom_);
|
||||
// Show project creation dialog
|
||||
popup_manager_->Show("Create New Project");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
manager.AddFile(current_project_.filepath + "/" + current_project_.name +
|
||||
".yaze");
|
||||
manager.Save();
|
||||
if (current_editor_set_) {
|
||||
current_editor_set_->assembly_editor_.OpenFolder(
|
||||
current_project_.code_folder_);
|
||||
absl::Status EditorManager::OpenProject() {
|
||||
auto file_path = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||
if (file_path.empty()) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
current_project_.project_opened_ = true;
|
||||
RETURN_IF_ERROR(LoadAssets());
|
||||
|
||||
core::YazeProject new_project;
|
||||
RETURN_IF_ERROR(new_project.Open(file_path));
|
||||
|
||||
// Validate project
|
||||
auto validation_status = new_project.Validate();
|
||||
if (!validation_status.ok()) {
|
||||
toast_manager_.Show(absl::StrFormat("Project validation failed: %s",
|
||||
validation_status.message()),
|
||||
editor::ToastType::kWarning, 5.0f);
|
||||
|
||||
// Ask user if they want to repair
|
||||
popup_manager_->Show("Project Repair");
|
||||
}
|
||||
|
||||
current_project_ = std::move(new_project);
|
||||
|
||||
// Load ROM if specified in project
|
||||
if (!current_project_.rom_filename.empty()) {
|
||||
Rom temp_rom;
|
||||
RETURN_IF_ERROR(temp_rom.LoadFromFile(current_project_.rom_filename));
|
||||
|
||||
if (!current_project_.labels_filename.empty()) {
|
||||
if (!temp_rom.resource_label()->LoadLabels(current_project_.labels_filename)) {
|
||||
toast_manager_.Show("Could not load labels file from project",
|
||||
editor::ToastType::kWarning);
|
||||
}
|
||||
}
|
||||
|
||||
sessions_.emplace_back(std::move(temp_rom));
|
||||
RomSession &session = sessions_.back();
|
||||
for (auto *editor : session.editors.active_editors_) {
|
||||
editor->set_context(&context_);
|
||||
}
|
||||
current_rom_ = &session.rom;
|
||||
current_editor_set_ = &session.editors;
|
||||
|
||||
// Apply project feature flags to the session
|
||||
session.feature_flags = current_project_.feature_flags;
|
||||
|
||||
// Update test manager with current ROM for ROM-dependent tests
|
||||
util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')",
|
||||
(void*)current_rom_, current_rom_ ? current_rom_->title().c_str() : "null");
|
||||
test::TestManager::Get().SetCurrentRom(current_rom_);
|
||||
|
||||
if (current_editor_set_ && !current_project_.code_folder.empty()) {
|
||||
current_editor_set_->assembly_editor_.OpenFolder(current_project_.code_folder);
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(LoadAssets());
|
||||
}
|
||||
|
||||
// Apply workspace settings
|
||||
font_global_scale_ = current_project_.workspace_settings.font_global_scale;
|
||||
autosave_enabled_ = current_project_.workspace_settings.autosave_enabled;
|
||||
autosave_interval_secs_ = current_project_.workspace_settings.autosave_interval_secs;
|
||||
ImGui::GetIO().FontGlobalScale = font_global_scale_;
|
||||
|
||||
// Add to recent files
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
manager.AddFile(current_project_.filepath);
|
||||
manager.Save();
|
||||
|
||||
toast_manager_.Show(absl::StrFormat("Project '%s' loaded successfully",
|
||||
current_project_.GetDisplayName()),
|
||||
editor::ToastType::kSuccess);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EditorManager::SaveProject() {
|
||||
if (current_project_.project_opened_) {
|
||||
RETURN_IF_ERROR(current_project_.Save());
|
||||
} else {
|
||||
new_project_menu = true;
|
||||
if (!current_project_.project_opened()) {
|
||||
return CreateNewProject();
|
||||
}
|
||||
|
||||
// Update project with current settings
|
||||
if (current_rom_ && current_editor_set_) {
|
||||
size_t session_idx = GetCurrentSessionIndex();
|
||||
if (session_idx < sessions_.size()) {
|
||||
current_project_.feature_flags = sessions_[session_idx].feature_flags;
|
||||
}
|
||||
|
||||
current_project_.workspace_settings.font_global_scale = font_global_scale_;
|
||||
current_project_.workspace_settings.autosave_enabled = autosave_enabled_;
|
||||
current_project_.workspace_settings.autosave_interval_secs = autosave_interval_secs_;
|
||||
|
||||
// Save recent files
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
current_project_.workspace_settings.recent_files.clear();
|
||||
for (const auto& file : manager.GetRecentFiles()) {
|
||||
current_project_.workspace_settings.recent_files.push_back(file);
|
||||
}
|
||||
}
|
||||
|
||||
return current_project_.Save();
|
||||
}
|
||||
|
||||
absl::Status EditorManager::SaveProjectAs() {
|
||||
auto file_path = core::FileDialogWrapper::ShowOpenFolderDialog();
|
||||
if (file_path.empty()) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
popup_manager_->Show("Save Project As");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EditorManager::ImportProject(const std::string& project_path) {
|
||||
core::YazeProject imported_project;
|
||||
|
||||
if (project_path.ends_with(".zsproj")) {
|
||||
RETURN_IF_ERROR(imported_project.ImportZScreamProject(project_path));
|
||||
toast_manager_.Show("ZScream project imported successfully. Please configure ROM and folders.",
|
||||
editor::ToastType::kInfo, 5.0f);
|
||||
} else {
|
||||
RETURN_IF_ERROR(imported_project.Open(project_path));
|
||||
}
|
||||
|
||||
current_project_ = std::move(imported_project);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EditorManager::RepairCurrentProject() {
|
||||
if (!current_project_.project_opened()) {
|
||||
return absl::FailedPreconditionError("No project is currently open");
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(current_project_.RepairProject());
|
||||
toast_manager_.Show("Project repaired successfully", editor::ToastType::kSuccess);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void EditorManager::ShowProjectHelp() {
|
||||
popup_manager_->Show("Project Help");
|
||||
}
|
||||
|
||||
absl::Status EditorManager::SetCurrentRom(Rom *rom) {
|
||||
if (!rom) {
|
||||
return absl::InvalidArgumentError("Invalid ROM pointer");
|
||||
@@ -2152,7 +2264,7 @@ void EditorManager::DrawWelcomeScreen() {
|
||||
// Recent files section (reuse homepage logic)
|
||||
ImGui::Text("Recent Files:");
|
||||
ImGui::BeginChild("RecentFiles", ImVec2(0, 100), true);
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
static core::RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
for (const auto &file : manager.GetRecentFiles()) {
|
||||
if (gui::ClickableText(file.c_str())) {
|
||||
|
||||
@@ -118,8 +118,15 @@ class EditorManager {
|
||||
absl::Status LoadAssets();
|
||||
absl::Status SaveRom();
|
||||
absl::Status OpenRomOrProject(const std::string& filename);
|
||||
|
||||
// Enhanced project management
|
||||
absl::Status CreateNewProject(const std::string& template_name = "Basic ROM Hack");
|
||||
absl::Status OpenProject();
|
||||
absl::Status SaveProject();
|
||||
absl::Status SaveProjectAs();
|
||||
absl::Status ImportProject(const std::string& project_path);
|
||||
absl::Status RepairCurrentProject();
|
||||
void ShowProjectHelp();
|
||||
|
||||
// Testing system
|
||||
void InitializeTestSuites();
|
||||
@@ -196,7 +203,7 @@ class EditorManager {
|
||||
Editor* current_editor_ = nullptr;
|
||||
EditorSet blank_editor_set_{};
|
||||
|
||||
Project current_project_;
|
||||
core::YazeProject current_project_;
|
||||
EditorContext context_;
|
||||
std::unique_ptr<PopupManager> popup_manager_;
|
||||
ToastManager toast_manager_;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
@@ -192,7 +192,7 @@ class Rom {
|
||||
return palette_groups_.dungeon_main.mutable_palette(i);
|
||||
}
|
||||
|
||||
ResourceLabelManager* resource_label() { return &resource_label_manager_; }
|
||||
core::ResourceLabelManager* resource_label() { return &resource_label_manager_; }
|
||||
zelda3_version_pointers version_constants() const {
|
||||
return kVersionConstantsMap.at(version_);
|
||||
}
|
||||
@@ -222,7 +222,7 @@ class Rom {
|
||||
std::vector<uint8_t> graphics_buffer_;
|
||||
|
||||
// Label manager for unique resource names.
|
||||
ResourceLabelManager resource_label_manager_;
|
||||
core::ResourceLabelManager resource_label_manager_;
|
||||
|
||||
// All palette groups in the game
|
||||
gfx::PaletteGroupMap palette_groups_;
|
||||
|
||||
Reference in New Issue
Block a user