backend-infra-engineer: Release v0.3.1 snapshot

This commit is contained in:
scawful
2025-09-28 03:07:45 -04:00
parent e32ac75b9c
commit 4371618a9b
88 changed files with 17940 additions and 4600 deletions

View File

@@ -22,7 +22,6 @@ set(
)
set(YAZE_RESOURCE_FILES
${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml
${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf
${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf
${CMAKE_SOURCE_DIR}/assets/font/Cousine-Regular.ttf

View File

@@ -106,12 +106,14 @@ else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Link Google Test if available for integrated testing
if(YAZE_BUILD_TESTS AND TARGET gtest AND TARGET gtest_main)
target_link_libraries(yaze PRIVATE gtest gtest_main)
# Link Google Test if available for integrated testing (but NOT gtest_main to avoid main() conflicts)
if(YAZE_BUILD_TESTS AND TARGET gtest)
target_link_libraries(yaze PRIVATE gtest)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=1)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=1)
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=0)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=0)
endif()
# Conditionally link PNG if available

View File

@@ -65,7 +65,8 @@ class FeatureFlags {
// Save overworld properties to the Rom.
bool kSaveOverworldProperties = true;
// Load custom overworld data from the ROM and enable UI.
// Enable custom overworld features for vanilla ROMs or override detection.
// If ZSCustomOverworld ASM is already applied, features are auto-enabled.
bool kLoadCustomOverworld = false;
// Apply ZSCustomOverworld ASM patches when upgrading ROM versions.
@@ -134,8 +135,19 @@ struct FlagsMenu {
&FeatureFlags::get().overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&FeatureFlags::get().overworld.kSaveOverworldProperties);
Checkbox("Load Custom Overworld",
Checkbox("Enable Custom Overworld Features",
&FeatureFlags::get().overworld.kLoadCustomOverworld);
ImGui::SameLine();
if (ImGui::Button("?")) {
ImGui::OpenPopup("CustomOverworldHelp");
}
if (ImGui::BeginPopup("CustomOverworldHelp")) {
ImGui::Text("This flag enables ZSCustomOverworld features.");
ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,");
ImGui::Text("features are auto-enabled regardless of this flag.");
ImGui::Text("For vanilla ROMs, enable this to use custom features.");
ImGui::EndPopup();
}
Checkbox("Apply ZSCustomOverworld ASM",
&FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
}

View File

@@ -19,7 +19,7 @@ namespace yaze {
namespace core {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of(".");
size_t dot = filename.find_last_of('.');
if (dot == std::string::npos) {
return "";
}
@@ -27,7 +27,7 @@ std::string GetFileExtension(const std::string &filename) {
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of("/");
size_t slash = filename.find_last_of('/');
if (slash == std::string::npos) {
return filename;
}
@@ -51,13 +51,6 @@ std::string LoadFile(const std::string &filename) {
std::string LoadConfigFile(const std::string &filename) {
std::string contents;
#if defined(_WIN32)
Platform platform = Platform::kWindows;
#elif defined(__APPLE__)
Platform platform = Platform::kMacOS;
#else
Platform platform = Platform::kLinux;
#endif
std::string filepath = GetConfigDirectory() + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
@@ -125,107 +118,152 @@ std::string GetConfigDirectory() {
#ifdef _WIN32
// Forward declaration for the main implementation
std::string ShowOpenFileDialogImpl();
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
std::string FileDialogWrapper::ShowOpenFileDialog() {
return ShowOpenFileDialogImpl();
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
// Windows doesn't use NFD in this implementation, fallback to bespoke
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdu8filteritem_t filters[1] = {{"ROM Files", "sfc,smc"}};
nfdopendialogu8args_t args = {0};
args.filterList = filters;
args.filterCount = 1;
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowOpenFileDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
return ShowOpenFileDialogImpl();
// For CI/CD, just return a placeholder path
return ""; // Placeholder for bespoke implementation
}
std::string ShowOpenFileDialogImpl() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
CoUninitialize();
return file_path_windows;
}
// Forward declaration for folder dialog implementation
std::string ShowOpenFolderDialogImpl();
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme Files", "theme"},
{"YAZE Project Files", "yaze"},
{"ROM Files", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
// For CI/CD, just return a placeholder path
if (!default_name.empty() && !default_extension.empty()) {
return default_name + "." + default_extension;
}
return ""; // Placeholder for bespoke implementation
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
return ShowOpenFolderDialogImpl();
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
// Windows doesn't use NFD in this implementation, fallback to bespoke
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
return ShowOpenFolderDialogImpl();
}
std::string ShowOpenFolderDialogImpl() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string folder_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr)) {
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
if (SUCCEEDED(hr)) {
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the folder path
PWSTR pszFolderPath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
char str[128];
wcstombs(str, pszFolderPath, 128);
folder_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFolderPath);
}
}
}
}
pfd->Release();
}
CoUninitialize();
return folder_path_windows;
// For CI/CD, just return a placeholder path
return ""; // Placeholder for bespoke implementation
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
@@ -238,7 +276,7 @@ std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0) {
subdirectories.push_back(findFileData.cFileName);
subdirectories.emplace_back(findFileData.cFileName);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
@@ -255,7 +293,7 @@ std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.push_back(findFileData.cFileName);
files.emplace_back(findFileData.cFileName);
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
@@ -306,10 +344,79 @@ std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
// Implement bespoke file dialog or return placeholder
// This would contain the custom macOS implementation
// This would contain the custom Linux implementation
return ""; // Placeholder for bespoke implementation
}
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme File", "theme"},
{"Project File", "yaze"},
{"ROM File", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not available - fallback to bespoke
return ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
// Basic Linux implementation using system command
// For CI/CD, just return a placeholder path
if (!default_name.empty() && !default_extension.empty()) {
return default_name + "." + default_extension;
}
return ""; // For now return empty - full implementation can be added later
}
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
// Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {

View File

@@ -20,10 +20,21 @@ class FileDialogWrapper {
* folder path. Uses global feature flag to choose implementation.
*/
static std::string ShowOpenFolderDialog();
/**
* @brief ShowSaveFileDialog opens a save file dialog and returns the selected
* filepath. Uses global feature flag to choose implementation.
*/
static std::string ShowSaveFileDialog(const std::string& default_name = "",
const std::string& default_extension = "");
// Specific implementations for testing
static std::string ShowOpenFileDialogNFD();
static std::string ShowOpenFileDialogBespoke();
static std::string ShowSaveFileDialogNFD(const std::string& default_name = "",
const std::string& default_extension = "");
static std::string ShowSaveFileDialogBespoke(const std::string& default_name = "",
const std::string& default_extension = "");
static std::string ShowOpenFolderDialogNFD();
static std::string ShowOpenFolderDialogBespoke();
static std::vector<std::string> GetSubdirectoriesInFolder(

View File

@@ -88,6 +88,28 @@ std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogBespoke() {
return "";
}
std::string yaze::core::FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
NSSavePanel* savePanel = [NSSavePanel savePanel];
if (!default_name.empty()) {
[savePanel setNameFieldStringValue:[NSString stringWithUTF8String:default_name.c_str()]];
}
if (!default_extension.empty()) {
NSString* ext = [NSString stringWithUTF8String:default_extension.c_str()];
[savePanel setAllowedFileTypes:@[ext]];
}
if ([savePanel runModal] == NSModalResponseOK) {
NSURL* url = [savePanel URL];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
// Global feature flag-based dispatch methods
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
if (FeatureFlags::get().kUseNativeFileDialog) {
@@ -105,6 +127,15 @@ std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() {
}
}
std::string yaze::core::FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
}
// NFD implementation for macOS (fallback to bespoke if NFD not available)
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
@@ -156,6 +187,55 @@ std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogNFD() {
#endif
}
std::string yaze::core::FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme File", "theme"},
{"Project File", "yaze"},
{"ROM File", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not compiled in, use bespoke
return ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogBespoke() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];

View File

@@ -2,8 +2,9 @@
#include <filesystem>
#include <string>
#include <unordered_set>
#include <vector>
#include <cstring>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
@@ -23,9 +24,9 @@ static const char* DROID_SANS = "DroidSans.ttf";
static const char* NOTO_SANS_JP = "NotoSansJP.ttf";
static const char* IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 16.0f;
static const float FONT_SIZE_DROID_SANS = 18.0f;
static const float ICON_FONT_SIZE = 18.0f;
static const float FONT_SIZE_DEFAULT = 16.0F;
static const float FONT_SIZE_DROID_SANS = 18.0F;
static const float ICON_FONT_SIZE = 18.0F;
namespace {
@@ -44,7 +45,7 @@ std::string SetFontPath(const std::string& font_path) {
}
absl::Status LoadFont(const FontConfig& font_config) {
ImGuiIO& io = ImGui::GetIO();
ImGuiIO& imgui_io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(font_config.font_path);
// Check if the file exists with std library first, since ImGui IO will assert
// if the file does not exist
@@ -53,7 +54,7 @@ absl::Status LoadFont(const FontConfig& font_config) {
absl::StrFormat("Font file %s does not exist", actual_font_path));
}
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
font_config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
@@ -61,33 +62,33 @@ absl::Status LoadFont(const FontConfig& font_config) {
return absl::OkStatus();
}
absl::Status AddIconFont(const FontConfig& config) {
absl::Status AddIconFont(const FontConfig& /*config*/) {
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
ImFontConfig icons_config{};
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.GlyphOffset.y = 5.0F;
icons_config.GlyphMinAdvanceX = 13.0F;
icons_config.PixelSnapH = true;
std::string icon_font_path = SetFontPath(FONT_ICON_FILE_NAME_MD);
ImGuiIO& io = ImGui::GetIO();
if (!io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), ICON_FONT_SIZE,
ImGuiIO& imgui_io = ImGui::GetIO();
if (!imgui_io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), ICON_FONT_SIZE,
&icons_config, icons_ranges)) {
return absl::InternalError("Failed to add icon fonts");
}
return absl::OkStatus();
}
absl::Status AddJapaneseFont(const FontConfig& config) {
ImFontConfig japanese_font_config;
absl::Status AddJapaneseFont(const FontConfig& /*config*/) {
ImFontConfig japanese_font_config{};
japanese_font_config.MergeMode = true;
japanese_font_config.GlyphOffset.y = 5.0f;
japanese_font_config.GlyphMinAdvanceX = 13.0f;
japanese_font_config.GlyphOffset.y = 5.0F;
japanese_font_config.GlyphMinAdvanceX = 13.0F;
japanese_font_config.PixelSnapH = true;
std::string japanese_font_path = SetFontPath(NOTO_SANS_JP);
ImGuiIO& io = ImGui::GetIO();
if (!io.Fonts->AddFontFromFileTTF(japanese_font_path.data(), ICON_FONT_SIZE,
ImGuiIO& imgui_io = ImGui::GetIO();
if (!imgui_io.Fonts->AddFontFromFileTTF(japanese_font_path.data(), ICON_FONT_SIZE,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese())) {
imgui_io.Fonts->GetGlyphRangesJapanese())) {
return absl::InternalError("Failed to add Japanese fonts");
}
return absl::OkStatus();
@@ -97,13 +98,13 @@ absl::Status AddJapaneseFont(const FontConfig& config) {
absl::Status LoadPackageFonts() {
if (font_registry.fonts.empty()) {
// Initialize the font names and sizes
// Initialize the font names and sizes with proper ImFontConfig initialization
font_registry.fonts = {
{KARLA_REGULAR, FONT_SIZE_DEFAULT},
{ROBOTO_MEDIUM, FONT_SIZE_DEFAULT},
{COUSINE_REGULAR, FONT_SIZE_DEFAULT},
{IBM_PLEX_JP, FONT_SIZE_DEFAULT},
{DROID_SANS, FONT_SIZE_DROID_SANS},
FontConfig{KARLA_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{ROBOTO_MEDIUM, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{COUSINE_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{IBM_PLEX_JP, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{DROID_SANS, FONT_SIZE_DROID_SANS, {}, {}},
};
}
@@ -117,9 +118,9 @@ absl::Status LoadPackageFonts() {
}
absl::Status ReloadPackageFont(const FontConfig& config) {
ImGuiIO& io = ImGui::GetIO();
ImGuiIO& imgui_io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(config.font_path);
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
@@ -129,127 +130,12 @@ absl::Status ReloadPackageFont(const FontConfig& config) {
return absl::OkStatus();
}
#ifdef _WIN32
#include <Windows.h>
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam) {
// Step 3: Load the font into ImGui
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
return 1;
}
void LoadSystemFonts() {
HKEY hKey;
std::vector<std::string> fontPaths;
// Open the registry key where fonts are listed
if (RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0,
KEY_READ, &hKey) == ERROR_SUCCESS) {
DWORD valueCount;
DWORD maxValueNameSize;
DWORD maxValueDataSize;
// Query the number of entries and the maximum size of the names and values
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
// Enumerate all font entries
for (DWORD i = 0; i < valueCount; i++) {
DWORD valueNameSize = maxValueNameSize + 1; // +1 for null terminator
DWORD valueDataSize = maxValueDataSize + 1; // +1 for null terminator
DWORD valueType;
// Clear buffers
memset(valueName, 0, valueNameSize);
memset(valueData, 0, valueDataSize);
// Get the font name and file path
if (RegEnumValue(hKey, i, valueName, &valueNameSize, NULL, &valueType,
valueData, &valueDataSize) == ERROR_SUCCESS) {
if (valueType == REG_SZ) {
// Add the font file path to the vector
std::string fontPath(reinterpret_cast<char*>(valueData),
valueDataSize);
fontPaths.push_back(fontPath);
}
}
}
delete[] valueName;
delete[] valueData;
RegCloseKey(hKey);
}
ImGuiIO& io = ImGui::GetIO();
// List of common font face names
static const std::unordered_set<std::string> commonFontFaceNames = {
"arial",
"times",
"cour",
"verdana",
"tahoma",
"comic",
"Impact",
"ariblk",
"Trebuchet MS",
"Georgia",
"Palatino Linotype",
"Lucida Sans Unicode",
"Tahoma",
"Lucida Console"};
for (auto& fontPath : fontPaths) {
// Check if the font path has a "C:\" prefix
if (fontPath.substr(0, 2) != "C:") {
// Add "C:\Windows\Fonts\" prefix to the font path
fontPath = absl::StrFormat("C:\\Windows\\Fonts\\%s", fontPath.c_str());
}
// Check if the font file has a .ttf or .TTF extension
std::string extension = fontPath.substr(fontPath.find_last_of(".") + 1);
if (extension == "ttf" || extension == "TTF") {
// Get the font face name from the font path
std::string fontFaceName =
fontPath.substr(fontPath.find_last_of("\\/") + 1);
fontFaceName = fontFaceName.substr(0, fontFaceName.find_last_of("."));
// Check if the font face name is in the common font face names list
if (commonFontFaceNames.find(fontFaceName) != commonFontFaceNames.end()) {
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
// Merge icon set
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
static const float ICON_FONT_SIZE = 18.0f;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
&icons_config, icons_ranges);
}
}
}
}
#elif defined(__linux__)
#ifdef __linux__
void LoadSystemFonts() {
// Load Linux System Fonts into ImGui
// System font loading is now handled by NFD (Native File Dialog)
// This function is kept for compatibility but does nothing
}
#endif
} // namespace core

View File

@@ -77,7 +77,7 @@ absl::Status YazeProject::Create(const std::string& project_name, const std::str
metadata.created_date = ss.str();
metadata.last_modified = ss.str();
metadata.yaze_version = "0.3.0"; // TODO: Get from version header
metadata.yaze_version = "0.3.1"; // TODO: Get from version header
metadata.version = "2.0";
metadata.created_by = "YAZE";

View File

@@ -1,6 +1,8 @@
#include "dungeon_object_selector.h"
#include <algorithm>
#include <iterator>
#include <cstring>
#include "app/core/window.h"
#include "app/gfx/arena.h"
@@ -1045,7 +1047,10 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() {
static int music_id = 0;
// Copy current values
strncpy(room_name, properties.name.c_str(), sizeof(room_name) - 1);
// Safe string copy with bounds checking
size_t name_len = std::min(properties.name.length(), sizeof(room_name) - 1);
std::memcpy(room_name, properties.name.c_str(), name_len);
room_name[name_len] = '\0';
dungeon_id = properties.dungeon_id;
floor_level = properties.floor_level;
is_boss_room = properties.is_boss_room;

View File

@@ -11,6 +11,7 @@ set(
app/editor/dungeon/dungeon_room_loader.cc
app/editor/dungeon/dungeon_usage_tracker.cc
app/editor/overworld/overworld_editor.cc
app/editor/overworld/overworld_editor_manager.cc
app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc
app/editor/message/message_editor.cc

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
#include <vector>
#include "absl/status/status.h"
#include "app/core/features.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
@@ -19,10 +20,9 @@
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/system/popup_manager.h"
#include "app/editor/system/toast_manager.h"
#include "app/editor/system/settings_editor.h"
#include "app/editor/system/toast_manager.h"
#include "app/emu/emulator.h"
#include "app/core/features.h"
#include "app/rom.h"
#include "yaze_config.h"
@@ -100,34 +100,41 @@ class EditorManager {
absl::Status SetCurrentRom(Rom* rom);
auto GetCurrentRom() -> Rom* { return current_rom_; }
auto GetCurrentEditorSet() -> EditorSet* { return current_editor_set_; }
// Get current session's feature flags (falls back to global if no session)
core::FeatureFlags::Flags* GetCurrentFeatureFlags() {
size_t current_index = GetCurrentSessionIndex();
if (current_index < sessions_.size()) {
return &sessions_[current_index].feature_flags;
}
return &core::FeatureFlags::get(); // Fallback to global
return &core::FeatureFlags::get(); // Fallback to global
}
void SetFontGlobalScale(float scale) {
font_global_scale_ = scale;
ImGui::GetIO().FontGlobalScale = scale;
SaveUserSettings();
}
private:
void DrawHomepage();
void DrawWelcomeScreen();
absl::Status DrawRomSelector();
absl::Status LoadRom();
absl::Status LoadAssets();
absl::Status SaveRom();
absl::Status SaveRomAs(const std::string& filename);
absl::Status OpenRomOrProject(const std::string& filename);
// Enhanced project management
absl::Status CreateNewProject(const std::string& template_name = "Basic ROM Hack");
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();
@@ -157,9 +164,10 @@ class EditorManager {
bool show_global_search_ = false;
bool show_session_rename_dialog_ = false;
bool show_welcome_screen_ = false;
bool welcome_screen_manually_closed_ = false;
size_t session_to_rename_ = 0;
char session_rename_buffer_[256] = {};
// Testing interface
bool show_test_dashboard_ = false;
@@ -177,18 +185,17 @@ class EditorManager {
struct RomSession {
Rom rom;
EditorSet editors;
std::string custom_name; // User-defined session name
std::string filepath; // ROM filepath for duplicate detection
core::FeatureFlags::Flags feature_flags; // Per-session feature flags
std::string custom_name; // User-defined session name
std::string filepath; // ROM filepath for duplicate detection
core::FeatureFlags::Flags feature_flags; // Per-session feature flags
RomSession() = default;
explicit RomSession(Rom&& r)
: rom(std::move(r)), editors(&rom) {
explicit RomSession(Rom&& r) : rom(std::move(r)), editors(&rom) {
filepath = rom.filename();
// Initialize with default feature flags
feature_flags = core::FeatureFlags::Flags{};
}
// Get display name (custom name or ROM title)
std::string GetDisplayName() const {
if (!custom_name.empty()) {
@@ -212,20 +219,24 @@ class EditorManager {
// Settings helpers
void LoadUserSettings();
void SaveUserSettings();
void RefreshWorkspacePresets();
void SaveWorkspacePreset(const std::string& name);
void LoadWorkspacePreset(const std::string& name);
// Workspace management
void CreateNewSession();
void DuplicateCurrentSession();
void CloseCurrentSession();
void RemoveSession(size_t index);
void SwitchToSession(size_t index);
size_t GetCurrentSessionIndex() const;
size_t GetActiveSessionCount() const;
void ResetWorkspaceLayout();
// Multi-session editor management
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const;
std::string GenerateUniqueEditorTitle(EditorType type,
size_t session_index) const;
void SaveWorkspaceLayout();
void LoadWorkspaceLayout();
void ShowAllWindows();
@@ -236,12 +247,12 @@ class EditorManager {
void LoadDeveloperLayout();
void LoadDesignerLayout();
void LoadModderLayout();
// Session management helpers
bool HasDuplicateSession(const std::string& filepath);
void RenameSession(size_t index, const std::string& new_name);
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index);
// UI drawing helpers
void DrawSessionSwitcher();
void DrawSessionManager();

File diff suppressed because it is too large Load Diff

View File

@@ -64,6 +64,7 @@ class MapPropertiesSystem {
void DrawSpritePropertiesTab(int current_map);
void DrawCustomFeaturesTab(int current_map);
void DrawTileGraphicsTab(int current_map);
void DrawMusicTab(int current_map);
// Utility methods
void RefreshMapProperties();

File diff suppressed because it is too large Load Diff

View File

@@ -12,9 +12,9 @@
#include "app/gfx/tilemap.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/editor/overworld/overworld_editor_manager.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -188,6 +188,16 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
void DrawMapPropertiesPanel();
void HandleMapInteraction();
void SetupOverworldCanvasContextMenu();
// Scratch space canvas methods
absl::Status DrawScratchSpace();
absl::Status SaveCurrentSelectionToScratch(int slot);
absl::Status LoadScratchToSelection(int slot);
absl::Status ClearScratchSpace(int slot);
void DrawScratchSpaceEdits();
void DrawScratchSpacePattern();
void DrawScratchSpaceSelection();
void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot = -1);
absl::Status UpdateUsageStats();
void DrawUsageGrid();
@@ -253,6 +263,24 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
// Map properties system for UI organization
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
std::unique_ptr<OverworldEditorManager> overworld_manager_;
// Scratch space for large layouts
// Scratch space canvas for tile16 drawing (like a mini overworld)
struct ScratchSpaceSlot {
gfx::Bitmap scratch_bitmap;
std::array<std::array<int, 32>, 32> tile_data; // 32x32 grid of tile16 IDs
bool in_use = false;
std::string name = "Empty";
int width = 16; // Default 16x16 tiles
int height = 16;
// Independent selection system for scratch space
std::vector<ImVec2> selected_tiles;
std::vector<ImVec2> selected_points;
bool select_rect_active = false;
};
std::array<ScratchSpaceSlot, 4> scratch_spaces_;
int current_scratch_slot_ = 0;
gfx::Tilemap tile16_blockset_;
@@ -295,12 +323,12 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), gui::CanvasGridSize::k32x32};
gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags};
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,
ImVec2(0, 0)};
gui::zeml::Node layout_node_;
absl::Status status_;
};
} // namespace editor

View File

@@ -0,0 +1,423 @@
#include "overworld_editor_manager.h"
#include "app/gfx/snes_color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/zelda3/overworld/overworld_map.h"
namespace yaze {
namespace editor {
using namespace ImGui;
absl::Status OverworldEditorManager::DrawV3SettingsPanel() {
if (BeginTabItem("v3 Settings")) {
Text("ZSCustomOverworld v3 Settings");
Separator();
// Check if custom ASM is applied
uint8_t asm_version = GetCustomASMVersion();
if (asm_version >= 3 && asm_version != 0xFF) {
TextColored(ImVec4(0, 1, 0, 1), "Custom Overworld ASM v%d Applied", asm_version);
} else if (asm_version == 0x00) {
TextColored(ImVec4(1, 1, 0, 1), "Vanilla ROM - Custom features available via flag");
} else {
TextColored(ImVec4(1, 0, 0, 1), "Custom ASM v%d - Consider upgrading to v3", asm_version);
}
Separator();
RETURN_IF_ERROR(DrawCustomOverworldSettings());
RETURN_IF_ERROR(DrawAreaSpecificSettings());
RETURN_IF_ERROR(DrawTransitionSettings());
RETURN_IF_ERROR(DrawOverlaySettings());
EndTabItem();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawCustomOverworldSettings() {
if (TreeNode("Custom Overworld Features")) {
RETURN_IF_ERROR(DrawBooleanSetting("Enable Area-Specific Background Colors",
&enable_area_specific_bg_,
"Allows each overworld area to have its own background color"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Main Palette Override",
&enable_main_palette_,
"Allows each area to override the main palette"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Mosaic Transitions",
&enable_mosaic_,
"Enables mosaic screen transitions between areas"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Custom GFX Groups",
&enable_gfx_groups_,
"Allows each area to have custom tile GFX groups"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Subscreen Overlays",
&enable_subscreen_overlay_,
"Enables custom subscreen overlays (fog, sky, etc.)"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Animated GFX Override",
&enable_animated_gfx_,
"Allows each area to have custom animated tiles"));
Separator();
if (Button("Apply Custom Overworld ASM")) {
RETURN_IF_ERROR(ApplyCustomOverworldASM());
}
SameLine();
HOVER_HINT("Writes the custom overworld settings to ROM");
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawAreaSpecificSettings() {
if (TreeNode("Area-Specific Settings")) {
// Map selection
int map_count = zelda3::kNumOverworldMaps;
SliderInt("Map Index", &current_map_index_, 0, map_count - 1);
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
// Area size controls
RETURN_IF_ERROR(DrawAreaSizeControls());
// Background color
if (enable_area_specific_bg_) {
uint16_t bg_color = current_map->area_specific_bg_color();
RETURN_IF_ERROR(DrawColorPicker("Background Color", &bg_color));
current_map->set_area_specific_bg_color(bg_color);
}
// Main palette
if (enable_main_palette_) {
uint8_t main_palette = current_map->main_palette();
SliderInt("Main Palette", (int*)&main_palette, 0, 5);
current_map->set_main_palette(main_palette);
}
// Mosaic settings
if (enable_mosaic_) {
RETURN_IF_ERROR(DrawMosaicControls());
}
// GFX groups
if (enable_gfx_groups_) {
RETURN_IF_ERROR(DrawGfxGroupControls());
}
// Subscreen overlay
if (enable_subscreen_overlay_) {
uint16_t overlay = current_map->subscreen_overlay();
RETURN_IF_ERROR(DrawOverlaySetting("Subscreen Overlay", &overlay));
current_map->set_subscreen_overlay(overlay);
}
// Animated GFX
if (enable_animated_gfx_) {
uint8_t animated_gfx = current_map->animated_gfx();
RETURN_IF_ERROR(DrawGfxGroupSetting("Animated GFX", &animated_gfx));
current_map->set_animated_gfx(animated_gfx);
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawAreaSizeControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
const char* area_size_names[] = {"Small", "Large", "Wide", "Tall"};
int current_size = static_cast<int>(current_map->area_size());
if (Combo("Area Size", &current_size, area_size_names, 4)) {
current_map->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(current_size));
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawMosaicControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
const auto& mosaic = current_map->mosaic_expanded();
bool mosaic_up = mosaic[0];
bool mosaic_down = mosaic[1];
bool mosaic_left = mosaic[2];
bool mosaic_right = mosaic[3];
if (Checkbox("Mosaic Up", &mosaic_up)) {
current_map->set_mosaic_expanded(0, mosaic_up);
}
SameLine();
if (Checkbox("Mosaic Down", &mosaic_down)) {
current_map->set_mosaic_expanded(1, mosaic_down);
}
if (Checkbox("Mosaic Left", &mosaic_left)) {
current_map->set_mosaic_expanded(2, mosaic_left);
}
SameLine();
if (Checkbox("Mosaic Right", &mosaic_right)) {
current_map->set_mosaic_expanded(3, mosaic_right);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawGfxGroupControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
Text("Custom Tile GFX Groups:");
for (int i = 0; i < 8; i++) {
uint8_t gfx_id = current_map->custom_tileset(i);
std::string label = "GFX " + std::to_string(i);
RETURN_IF_ERROR(DrawGfxGroupSetting(label.c_str(), &gfx_id));
current_map->set_custom_tileset(i, gfx_id);
if (i < 7) SameLine();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawTransitionSettings() {
if (TreeNode("Transition Settings")) {
Text("Complex area transition calculations are automatically handled");
Text("based on neighboring area sizes (Large, Wide, Tall, Small).");
if (GetCustomASMVersion() >= 3) {
TextColored(ImVec4(0, 1, 0, 1), "Using v3+ enhanced transitions");
} else {
TextColored(ImVec4(1, 1, 0, 1), "Using vanilla/v2 transitions");
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawOverlaySettings() {
if (TreeNode("Interactive Overlay Settings")) {
Text("Interactive overlays reveal holes and change map elements.");
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
Text("Map %d has %s", current_map_index_,
current_map->has_overlay() ? "interactive overlay" : "no overlay");
if (current_map->has_overlay()) {
Text("Overlay ID: 0x%04X", current_map->overlay_id());
Text("Overlay data size: %zu bytes", current_map->overlay_data().size());
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::ApplyCustomOverworldASM() {
return overworld_->SaveCustomOverworldASM(
enable_area_specific_bg_, enable_main_palette_, enable_mosaic_,
enable_gfx_groups_, enable_subscreen_overlay_, enable_animated_gfx_);
}
bool OverworldEditorManager::ValidateV3Compatibility() {
uint8_t asm_version = GetCustomASMVersion();
return (asm_version >= 3 && asm_version != 0xFF);
}
bool OverworldEditorManager::CheckCustomASMApplied() {
uint8_t asm_version = GetCustomASMVersion();
return (asm_version != 0xFF && asm_version != 0x00);
}
uint8_t OverworldEditorManager::GetCustomASMVersion() {
return (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
}
absl::Status OverworldEditorManager::DrawBooleanSetting(const char* label, bool* setting,
const char* help_text) {
Checkbox(label, setting);
if (help_text && IsItemHovered()) {
SetTooltip("%s", help_text);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawColorPicker(const char* label, uint16_t* color) {
gfx::SnesColor snes_color(*color);
ImVec4 imgui_color = snes_color.rgb();
if (ColorEdit3(label, &imgui_color.x)) {
gfx::SnesColor new_color;
new_color.set_rgb(imgui_color);
*color = new_color.snes();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawOverlaySetting(const char* label, uint16_t* overlay) {
int overlay_int = *overlay;
if (InputInt(label, &overlay_int, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) {
*overlay = static_cast<uint16_t>(overlay_int & 0xFFFF);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawGfxGroupSetting(const char* label, uint8_t* gfx_id,
int max_value) {
int gfx_int = *gfx_id;
if (SliderInt(label, &gfx_int, 0, max_value)) {
*gfx_id = static_cast<uint8_t>(gfx_int);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawUnifiedSettingsTable() {
// Create a comprehensive settings table that combines toolset and properties
if (BeginTable("##UnifiedOverworldSettings", 6,
ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit)) {
// Setup columns with proper widths
TableSetupColumn(ICON_MD_BUILD " Tools", ImGuiTableColumnFlags_WidthFixed, 120);
TableSetupColumn(ICON_MD_MAP " World", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_IMAGE " Graphics", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_PALETTE " Palette", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_SETTINGS " Properties", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn(ICON_MD_EXTENSION " v3 Features", ImGuiTableColumnFlags_WidthFixed, 120);
TableHeadersRow();
TableNextRow();
// Tools column
TableNextColumn();
RETURN_IF_ERROR(DrawToolsetInSettings());
// World column
TableNextColumn();
Text(ICON_MD_PUBLIC " Current World");
SetNextItemWidth(80.f);
// if (Combo("##world", &current_world_, kWorldList.data(), 3)) {
// // World change logic would go here
// }
// Graphics column
TableNextColumn();
Text(ICON_MD_IMAGE " Area Graphics");
// Graphics controls would go here
// Palette column
TableNextColumn();
Text(ICON_MD_PALETTE " Area Palette");
// Palette controls would go here
// Properties column
TableNextColumn();
Text(ICON_MD_SETTINGS " Map Properties");
// Map properties would go here
// v3 Features column
TableNextColumn();
uint8_t asm_version = GetCustomASMVersion();
if (asm_version >= 3 && asm_version != 0xFF) {
TextColored(ImVec4(0, 1, 0, 1), ICON_MD_NEW_RELEASES " v3 Active");
if (Button(ICON_MD_TUNE " Settings")) {
// Open v3 settings
}
} else {
TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1), ICON_MD_UPGRADE " v3 Available");
if (Button(ICON_MD_UPGRADE " Upgrade")) {
// Trigger upgrade
}
}
EndTable();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawToolsetInSettings() {
// Compact toolset layout within the settings table
BeginGroup();
// Core editing tools in a compact grid
if (Button(ICON_MD_PAN_TOOL_ALT, ImVec2(25, 25))) {
// Set PAN mode
}
HOVER_HINT("Pan (1)");
SameLine();
if (Button(ICON_MD_DRAW, ImVec2(25, 25))) {
// Set DRAW_TILE mode
}
HOVER_HINT("Draw Tile (2)");
SameLine();
if (Button(ICON_MD_DOOR_FRONT, ImVec2(25, 25))) {
// Set ENTRANCES mode
}
HOVER_HINT("Entrances (3)");
SameLine();
if (Button(ICON_MD_DOOR_BACK, ImVec2(25, 25))) {
// Set EXITS mode
}
HOVER_HINT("Exits (4)");
// Second row
if (Button(ICON_MD_GRASS, ImVec2(25, 25))) {
// Set ITEMS mode
}
HOVER_HINT("Items (5)");
SameLine();
if (Button(ICON_MD_PEST_CONTROL_RODENT, ImVec2(25, 25))) {
// Set SPRITES mode
}
HOVER_HINT("Sprites (6)");
SameLine();
if (Button(ICON_MD_ADD_LOCATION, ImVec2(25, 25))) {
// Set TRANSPORTS mode
}
HOVER_HINT("Transports (7)");
SameLine();
if (Button(ICON_MD_MUSIC_NOTE, ImVec2(25, 25))) {
// Set MUSIC mode
}
HOVER_HINT("Music (8)");
EndGroup();
return absl::OkStatus();
}
absl::Status OverworldEditorManager::HandleCanvasSelectionTransfer() {
// This could be called to manage bidirectional selection transfer
// For now, it's a placeholder for future canvas interaction management
return absl::OkStatus();
}
absl::Status OverworldEditorManager::TransferOverworldSelectionToScratch() {
// Transfer logic would go here to copy selections from overworld to scratch
// This could be integrated with the editor's context system
return absl::OkStatus();
}
absl::Status OverworldEditorManager::TransferScratchSelectionToOverworld() {
// Transfer logic would go here to copy selections from scratch to overworld
// This could be integrated with the editor's context system
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,108 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#include <memory>
#include "absl/status/status.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
namespace yaze {
namespace editor {
// Forward declarations
enum class EditingMode;
class OverworldEditor;
/**
* @class OverworldEditorManager
* @brief Manages the complex overworld editor functionality and v3 features
*
* This class separates the complex overworld editing functionality from the main
* OverworldEditor class to improve maintainability and organization, especially
* for ZSCustomOverworld v3 features.
*/
class OverworldEditorManager {
public:
OverworldEditorManager(zelda3::Overworld* overworld, Rom* rom,
OverworldEditor* editor = nullptr)
: overworld_(overworld), rom_(rom), editor_(editor) {}
// Set editor context for mode changes
void SetEditorContext(OverworldEditor* editor) { editor_ = editor; }
// v3 Feature Management
absl::Status DrawV3SettingsPanel();
absl::Status DrawCustomOverworldSettings();
absl::Status DrawAreaSpecificSettings();
absl::Status DrawTransitionSettings();
absl::Status DrawOverlaySettings();
// Map Properties Management
absl::Status DrawMapPropertiesTable();
absl::Status DrawUnifiedSettingsTable();
absl::Status DrawToolsetInSettings();
absl::Status DrawAreaSizeControls();
absl::Status DrawMosaicControls();
absl::Status DrawPaletteControls();
absl::Status DrawGfxGroupControls();
// Save/Load Operations for v3
absl::Status SaveCustomOverworldData();
absl::Status LoadCustomOverworldData();
absl::Status ApplyCustomOverworldASM();
// Canvas Interaction Management
absl::Status HandleCanvasSelectionTransfer();
absl::Status TransferOverworldSelectionToScratch();
absl::Status TransferScratchSelectionToOverworld();
// Validation and Checks
bool ValidateV3Compatibility();
bool CheckCustomASMApplied();
uint8_t GetCustomASMVersion();
// Getters/Setters for v3 settings
auto enable_area_specific_bg() const { return enable_area_specific_bg_; }
auto enable_main_palette() const { return enable_main_palette_; }
auto enable_mosaic() const { return enable_mosaic_; }
auto enable_gfx_groups() const { return enable_gfx_groups_; }
auto enable_subscreen_overlay() const { return enable_subscreen_overlay_; }
auto enable_animated_gfx() const { return enable_animated_gfx_; }
void set_enable_area_specific_bg(bool value) { enable_area_specific_bg_ = value; }
void set_enable_main_palette(bool value) { enable_main_palette_ = value; }
void set_enable_mosaic(bool value) { enable_mosaic_ = value; }
void set_enable_gfx_groups(bool value) { enable_gfx_groups_ = value; }
void set_enable_subscreen_overlay(bool value) { enable_subscreen_overlay_ = value; }
void set_enable_animated_gfx(bool value) { enable_animated_gfx_ = value; }
private:
zelda3::Overworld* overworld_;
Rom* rom_;
OverworldEditor* editor_;
// v3 Feature flags
bool enable_area_specific_bg_ = false;
bool enable_main_palette_ = false;
bool enable_mosaic_ = false;
bool enable_gfx_groups_ = false;
bool enable_subscreen_overlay_ = false;
bool enable_animated_gfx_ = false;
// Current editing state
int current_map_index_ = 0;
// Helper methods
absl::Status DrawBooleanSetting(const char* label, bool* setting, const char* help_text = nullptr);
absl::Status DrawColorPicker(const char* label, uint16_t* color);
absl::Status DrawOverlaySetting(const char* label, uint16_t* overlay);
absl::Status DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, int max_value = 255);
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
#define YAZE_APP_EDITOR_TILE16EDITOR_H
#include <array>
#include <chrono>
#include <functional>
#include <vector>
#include "absl/status/status.h"
@@ -19,29 +21,43 @@
namespace yaze {
namespace editor {
// Constants for tile editing
constexpr int kTile16Size = 16;
constexpr int kTile8Size = 8;
constexpr int kTilesheetEditorWidth = 0x100;
constexpr int kTilesheetEditorHeight = 0x4000;
constexpr int kTile16CanvasSize = 0x20;
constexpr int kTile8CanvasHeight = 0x175;
constexpr int kNumScratchSlots = 4;
constexpr int kNumPalettes = 8;
constexpr int kTile8PixelCount = 64;
constexpr int kTile16PixelCount = 256;
/**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public gfx::GfxContext {
public:
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset)
Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
: rom_(rom), tile16_blockset_(tile16_blockset) {}
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp,
const gfx::Bitmap &current_gfx_bmp,
std::array<uint8_t, 0x200> &all_tiles_types);
absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
const gfx::Bitmap& current_gfx_bmp,
std::array<uint8_t, 0x200>& all_tiles_types);
absl::Status Update();
void DrawTile16Editor();
absl::Status UpdateTile16Transfer();
absl::Status UpdateBlockset();
absl::Status DrawToCurrentTile16(ImVec2 pos);
// Scratch space for tile16 layouts
void DrawScratchSpace();
absl::Status SaveLayoutToScratch(int slot);
absl::Status LoadLayoutFromScratch(int slot);
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap* source_tile = nullptr);
absl::Status UpdateTile16Edit();
absl::Status UpdateTransferTileCanvas();
absl::Status LoadTile8();
absl::Status SetCurrentTile(int id);
@@ -53,14 +69,69 @@ class Tile16Editor : public gfx::GfxContext {
absl::Status LoadTile16FromScratchSpace(int slot);
absl::Status ClearScratchSpace(int slot);
void set_rom(Rom *rom) { rom_ = rom; }
Rom *rom() const { return rom_; }
// Advanced editing features
absl::Status FlipTile16Horizontal();
absl::Status FlipTile16Vertical();
absl::Status RotateTile16();
absl::Status FillTile16WithTile8(int tile8_id);
absl::Status AutoTileTile16();
absl::Status ClearTile16();
// Palette management
absl::Status CyclePalette(bool forward = true);
absl::Status ApplyPaletteToAll(uint8_t palette_id);
absl::Status PreviewPaletteChange(uint8_t palette_id);
// Batch operations
absl::Status ApplyToSelection(const std::function<void(int)>& operation);
absl::Status BatchEdit(const std::vector<int>& tile_ids,
const std::function<void(int)>& operation);
// History and undo system
absl::Status Undo();
absl::Status Redo();
void SaveUndoState();
// Live preview system
void EnableLivePreview(bool enable) { live_preview_enabled_ = enable; }
absl::Status UpdateLivePreview();
// Validation and integrity checks
absl::Status ValidateTile16Data();
bool IsTile16Valid(int tile_id) const;
// Integration with overworld system
absl::Status SaveTile16ToROM();
absl::Status UpdateOverworldTilemap();
absl::Status CommitChangesToBlockset();
absl::Status CommitChangesToOverworld();
absl::Status DiscardChanges();
// Helper methods for palette management
absl::Status UpdateTile8Palette(int tile8_id);
absl::Status RefreshAllPalettes();
void DrawPaletteSettings();
// ROM data access and modification
absl::Status UpdateROMTile16Data();
absl::Status RefreshTile16Blockset();
gfx::Tile16* GetCurrentTile16Data();
absl::Status RegenerateTile16BitmapFromROM();
// Manual tile8 input controls
void DrawManualTile8Inputs();
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
// Callback for when changes are committed to notify parent editor
void set_on_changes_committed(std::function<absl::Status()> callback) {
on_changes_committed_ = callback;
}
private:
Rom *rom_ = nullptr;
Rom* rom_ = nullptr;
bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
bool transfer_blockset_loaded_ = false;
bool x_flip = false;
bool y_flip = false;
bool priority_tile = false;
@@ -78,19 +149,67 @@ class Tile16Editor : public gfx::GfxContext {
std::array<gfx::Bitmap, 4> scratch_space_;
std::array<bool, 4> scratch_space_used_ = {false, false, false, false};
// Layout scratch space for tile16 arrangements (4 slots of 8x8 grids)
struct LayoutScratch {
std::array<std::array<int, 8>, 8> tile_layout; // 8x8 grid of tile16 IDs
bool in_use = false;
std::string name = "Empty";
};
std::array<LayoutScratch, 4> layout_scratch_;
// Undo/Redo system
struct UndoState {
int tile_id;
gfx::Bitmap tile_bitmap;
gfx::Tile16 tile_data;
uint8_t palette;
bool x_flip, y_flip, priority;
};
std::vector<UndoState> undo_stack_;
std::vector<UndoState> redo_stack_;
static constexpr size_t kMaxUndoStates_ = 50;
// Live preview system
bool live_preview_enabled_ = true;
gfx::Bitmap preview_tile16_;
bool preview_dirty_ = false;
// Selection system
std::vector<int> selected_tiles_;
int selection_start_tile_ = -1;
bool multi_select_mode_ = false;
// Advanced editing state
bool auto_tile_mode_ = false;
bool grid_snap_enabled_ = true;
bool show_tile_info_ = true;
bool show_palette_preview_ = true;
// Palette management settings
bool show_palette_settings_ = false;
int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc.
uint8_t palette_normalization_mask_ = 0x0F; // Default 4-bit mask
bool auto_normalize_pixels_ = true;
// Performance tracking
std::chrono::steady_clock::time_point last_edit_time_;
bool batch_mode_ = false;
util::NotifyValue<uint32_t> notify_tile16;
util::NotifyValue<uint8_t> notify_palette;
std::array<uint8_t, 0x200> all_tiles_types_;
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32,};
gui::Canvas blockset_canvas_{
"blocksetCanvas", ImVec2(kTilesheetEditorWidth, kTilesheetEditorHeight),
gui::CanvasGridSize::k32x32};
gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
// Canvas for editing the selected tile - optimized for 2x2 grid of 8x8 tiles (16x16 total)
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas",
ImVec2(64, 64), // Fixed 64x64 display size (16x16 pixels at 4x scale)
gui::CanvasGridSize::k8x8, 4.0F}; // 8x8 grid with 4x scale for clarity
gfx::Bitmap current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
@@ -100,23 +219,18 @@ class Tile16Editor : public gfx::GfxContext {
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders};
gfx::Tilemap *tile16_blockset_ = nullptr;
gfx::Tilemap* tile16_blockset_ = nullptr;
std::vector<gfx::Bitmap> current_gfx_individual_;
PaletteEditor palette_editor_;
gfx::SnesPalette palette_;
absl::Status status_;
Rom *transfer_rom_ = nullptr;
zelda3::Overworld transfer_overworld_{transfer_rom_};
std::array<gfx::Bitmap, kNumGfxSheets> transfer_gfx_;
absl::Status transfer_status_;
// Callback to notify parent editor when changes are committed
std::function<absl::Status()> on_changes_committed_;
};
} // namespace editor

View File

@@ -38,6 +38,9 @@ void PopupManager::Initialize() {
popups_["Workspace Help"] = {"Workspace Help", false, [this]() { DrawWorkspaceHelpPopup(); }};
popups_["Session Limit Warning"] = {"Session Limit Warning", false, [this]() { DrawSessionLimitWarningPopup(); }};
popups_["Layout Reset Confirm"] = {"Reset Layout Confirmation", false, [this]() { DrawLayoutResetConfirmPopup(); }};
// Settings popups (accessible without ROM)
popups_["Display Settings"] = {"Display Settings", false, [this]() { DrawDisplaySettingsPopup(); }};
}
void PopupManager::DrawPopups() {
@@ -48,7 +51,14 @@ void PopupManager::DrawPopups() {
for (auto& [name, params] : popups_) {
if (params.is_visible) {
OpenPopup(name.c_str());
if (BeginPopupModal(name.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
// Special handling for Display Settings popup to make it resizable
ImGuiWindowFlags popup_flags = ImGuiWindowFlags_AlwaysAutoResize;
if (name == "Display Settings") {
popup_flags = ImGuiWindowFlags_None; // Allow resizing for display settings
}
if (BeginPopupModal(name.c_str(), nullptr, popup_flags)) {
params.draw_function();
EndPopup();
}
@@ -491,5 +501,46 @@ void PopupManager::DrawLayoutResetConfirmPopup() {
}
}
void PopupManager::DrawDisplaySettingsPopup() {
// Set a comfortable default size with natural constraints
SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX));
Text("%s Display & Theme Settings", ICON_MD_DISPLAY_SETTINGS);
TextWrapped("Customize your YAZE experience - accessible anytime!");
Separator();
// Create a child window for scrollable content to avoid table conflicts
// Use remaining space minus the close button area
float available_height = GetContentRegionAvail().y - 60; // Reserve space for close button
if (BeginChild("DisplaySettingsContent", ImVec2(0, available_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
// Use the popup-safe version to avoid table conflicts
gui::DrawDisplaySettingsForPopup();
Separator();
gui::TextWithSeparators("Font Manager");
gui::DrawFontManager();
// Global font scale (moved from the old display settings window)
ImGuiIO &io = GetIO();
Separator();
Text("Global Font Scale");
static float font_global_scale = io.FontGlobalScale;
if (SliderFloat("##global_scale", &font_global_scale, 0.5f, 1.8f, "%.2f")) {
if (editor_manager_) {
editor_manager_->SetFontGlobalScale(font_global_scale);
} else {
io.FontGlobalScale = font_global_scale;
}
}
}
EndChild();
Separator();
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Display Settings");
}
}
} // namespace editor
} // namespace yaze

View File

@@ -86,6 +86,9 @@ class PopupManager {
void DrawWorkspaceHelpPopup();
void DrawSessionLimitWarningPopup();
void DrawLayoutResetConfirmPopup();
// Settings popups (accessible without ROM)
void DrawDisplaySettingsPopup();
EditorManager* editor_manager_;
std::unordered_map<std::string, PopupParams> popups_;

View File

@@ -7,6 +7,8 @@
#include "app/gfx/bitmap.h"
#include "app/gui/color.h"
#include "app/gui/style.h"
#include "app/gui/canvas_utils.h"
#include "util/log.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
@@ -45,10 +47,135 @@ ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
}
} // namespace
// Canvas class implementation begins here
void Canvas::InitializeDefaults() {
// Initialize configuration with sensible defaults
config_.enable_grid = true;
config_.enable_hex_labels = false;
config_.enable_custom_labels = false;
config_.enable_context_menu = true;
config_.is_draggable = false;
config_.grid_step = 32.0f;
config_.global_scale = 1.0f;
config_.canvas_size = ImVec2(0, 0);
config_.custom_canvas_size = false;
// Initialize selection state
selection_.Clear();
// Initialize palette editor
palette_editor_ = std::make_unique<EnhancedPaletteEditor>();
// Initialize legacy compatibility variables to match config
enable_grid_ = config_.enable_grid;
enable_hex_tile_labels_ = config_.enable_hex_labels;
enable_custom_labels_ = config_.enable_custom_labels;
enable_context_menu_ = config_.enable_context_menu;
draggable_ = config_.is_draggable;
custom_step_ = config_.grid_step;
global_scale_ = config_.global_scale;
custom_canvas_size_ = config_.custom_canvas_size;
select_rect_active_ = selection_.select_rect_active;
selected_tile_pos_ = selection_.selected_tile_pos;
}
void Canvas::Cleanup() {
palette_editor_.reset();
selection_.Clear();
}
void Canvas::InitializePaletteEditor(Rom* rom) {
rom_ = rom;
if (palette_editor_) {
palette_editor_->Initialize(rom);
}
}
void Canvas::ShowPaletteEditor() {
if (palette_editor_ && bitmap_) {
auto mutable_palette = bitmap_->mutable_palette();
palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor");
}
}
void Canvas::ShowColorAnalysis() {
if (palette_editor_ && bitmap_) {
palette_editor_->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
}
}
bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
if (palette_editor_ && bitmap_) {
return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index);
}
return false;
}
// Size reporting methods for table integration
ImVec2 Canvas::GetMinimumSize() const {
return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale);
}
ImVec2 Canvas::GetPreferredSize() const {
return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale);
}
void Canvas::ReserveTableSpace(const std::string& label) {
ImVec2 size = config_.auto_resize ? GetPreferredSize() : config_.canvas_size;
CanvasUtils::ReserveCanvasSpace(size, label);
}
bool Canvas::BeginTableCanvas(const std::string& label) {
if (config_.auto_resize) {
ImVec2 preferred_size = GetPreferredSize();
CanvasUtils::SetNextCanvasSize(preferred_size, true);
}
// Begin child window that properly reports size to tables
std::string child_id = canvas_id_ + "_TableChild";
ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
bool result = ImGui::BeginChild(child_id.c_str(), child_size,
true, // Always show border for table integration
ImGuiWindowFlags_AlwaysVerticalScrollbar);
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
return result;
}
void Canvas::EndTableCanvas() {
ImGui::EndChild();
}
// Improved interaction detection methods
bool Canvas::HasValidSelection() const {
return !points_.empty() && points_.size() >= 2;
}
bool Canvas::WasClicked(ImGuiMouseButton button) const {
return ImGui::IsItemClicked(button) && HasValidSelection();
}
bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection();
}
ImVec2 Canvas::GetLastClickPosition() const {
if (HasValidSelection()) {
return points_[0]; // Return the first point of the selection
}
return ImVec2(-1, -1); // Invalid position
}
void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event,
int tile_size, float scale) {
global_scale_ = scale;
config_.global_scale = scale;
global_scale_ = scale; // Legacy compatibility
DrawBackground();
DrawContextMenu();
DrawBitmap(bitmap, 2, scale);
@@ -60,7 +187,8 @@ void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
}
void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
enable_custom_labels_ = true;
config_.enable_custom_labels = true;
enable_custom_labels_ = true; // Legacy compatibility
DrawBackground(bg_size);
DrawInfoGrid(grid_size, 8, label_id);
DrawOverlay();
@@ -69,21 +197,27 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
void Canvas::DrawBackground(ImVec2 canvas_size) {
draw_list_ = GetWindowDrawList();
canvas_p0_ = GetCursorScreenPos();
if (!custom_canvas_size_) canvas_sz_ = GetContentRegionAvail();
if (canvas_size.x != 0) canvas_sz_ = canvas_size;
canvas_p1_ = ImVec2(canvas_p0_.x + (canvas_sz_.x * global_scale_),
canvas_p0_.y + (canvas_sz_.y * global_scale_));
// Calculate canvas size using utility function
ImVec2 content_region = GetContentRegionAvail();
canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size);
if (canvas_size.x != 0) {
canvas_sz_ = canvas_size;
config_.canvas_size = canvas_size;
}
// Calculate scaled canvas bounds
ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale);
canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y);
// Draw border and background color
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
ImGui::InvisibleButton(
canvas_id_.c_str(),
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_),
kMouseFlags);
ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags);
if (draggable_ && IsItemHovered()) {
if (config_.is_draggable && IsItemHovered()) {
const ImGuiIO &io = GetIO();
const bool is_active = IsItemActive(); // Held
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
@@ -140,14 +274,20 @@ void Canvas::DrawContextMenu() {
if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) {
SetZoomToFit(*bitmap_);
}
if (MenuItem("Advanced Properties", nullptr, false)) {
ImGui::OpenPopup("Advanced Canvas Properties");
}
ImGui::Separator();
MenuItem("Show Grid", nullptr, &enable_grid_);
Selectable("Show Position Labels", &enable_hex_tile_labels_);
if (MenuItem("Bitmap Properties", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Bitmap Properties");
}
if (MenuItem("Edit Palette", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Palette Editor");
ShowPaletteEditor();
}
if (MenuItem("Color Analysis", nullptr, false) && bitmap_) {
ShowColorAnalysis();
}
if (MenuItem("Scaling Controls", nullptr, false)) {
ImGui::OpenPopup("Scaling Controls");
}
if (BeginMenu("Canvas Properties")) {
Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
@@ -178,38 +318,78 @@ void Canvas::DrawContextMenu() {
EndMenu();
}
if (BeginMenu("Change Palette")) {
Text("Work in progress");
// TODO: Get ROM data for change palette
// gui::TextWithSeparators("ROM Palette");
// ImGui::SetNextItemWidth(100.f);
// ImGui::Combo("Palette Group", (int *)&edit_palette_group_name_index_,
// gfx::kPaletteGroupAddressesKeys,
// IM_ARRAYSIZE(gfx::kPaletteGroupAddressesKeys));
// ImGui::SetNextItemWidth(100.f);
// gui::InputHexWord("Palette Group Index", &edit_palette_index_);
// auto palette_group = rom()->mutable_palette_group()->get_group(
// gfx::kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
// auto palette = palette_group->mutable_palette(edit_palette_index_);
// if (ImGui::BeginChild("Palette", ImVec2(0, 300), true)) {
// gui::SelectablePalettePipeline(edit_palette_sub_index_,
// refresh_graphics_, *palette);
// if (refresh_graphics_) {
// bitmap_->SetPaletteWithTransparent(*palette,
// edit_palette_sub_index_);
// Renderer::Get().UpdateBitmap(bitmap_);
// refresh_graphics_ = false;
// }
// ImGui::EndChild();
// }
if (BeginMenu("ROM Palette Selection") && rom_) {
Text("Select ROM Palette Group:");
// Enhanced ROM palette group selection
if (palette_editor_) {
// Use our enhanced palette editor's ROM selection
if (MenuItem("Open Enhanced Palette Manager")) {
palette_editor_->ShowROMPaletteManager();
}
ImGui::Separator();
// Quick palette group selection
const char* palette_groups[] = {
"Overworld Main", "Overworld Aux", "Overworld Animated",
"Dungeon Main", "Global Sprites", "Armor", "Swords"
};
if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_,
palette_groups, IM_ARRAYSIZE(palette_groups))) {
// Group selection changed
}
ImGui::SetNextItemWidth(100.f);
if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) {
// Palette index changed
}
// Apply button with enhanced functionality
if (ImGui::Button("Apply to Canvas") && bitmap_) {
if (palette_editor_->ApplyROMPalette(bitmap_,
edit_palette_group_name_index_,
edit_palette_index_)) {
util::logf("Applied ROM palette group %d, index %d via context menu",
edit_palette_group_name_index_, edit_palette_index_);
}
}
// Direct palette editing with SelectablePalettePipeline
if (ImGui::TreeNode("Interactive Palette Editor")) {
if (rom_ && bitmap_) {
ImGui::Text("Interactive ROM Palette Editing");
ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]);
// Get the enhanced palette editor's ROM palette if available
if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) {
auto editable_palette = const_cast<gfx::SnesPalette&>(*rom_palette);
if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) {
// Use the existing SelectablePalettePipeline for interactive editing
gui::SelectablePalettePipeline(edit_palette_sub_index_,
refresh_graphics_, editable_palette);
if (refresh_graphics_) {
bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_);
Renderer::Get().UpdateBitmap(bitmap_);
refresh_graphics_ = false;
util::logf("Applied interactive palette changes to canvas");
}
ImGui::EndChild();
}
} else {
ImGui::Text("Load ROM palettes first using Enhanced Palette Manager");
}
}
ImGui::TreePop();
}
}
EndMenu();
}
if (BeginMenu("View Palette")) {
DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true,
8);
(void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8);
EndMenu();
}
EndMenu();
@@ -235,11 +415,9 @@ void Canvas::DrawContextMenu() {
ImGui::EndPopup();
}
// Draw enhanced property dialogs
if (bitmap_) {
ShowBitmapProperties(*bitmap_);
ShowPaletteEditor(*bitmap_->mutable_palette());
}
// Draw enhanced property dialogs
ShowAdvancedCanvasProperties();
ShowScalingControls();
}
void Canvas::DrawContextMenuItem(const ContextMenuItem& item) {
@@ -275,66 +453,7 @@ void Canvas::ClearContextMenuItems() {
context_menu_items_.clear();
}
void Canvas::ShowBitmapProperties(const gfx::Bitmap& bitmap) {
if (ImGui::BeginPopupModal("Bitmap Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Bitmap Information");
ImGui::Separator();
ImGui::Text("Size: %d x %d", bitmap.width(), bitmap.height());
ImGui::Text("Depth: %d bits", bitmap.depth());
ImGui::Text("Data Size: %zu bytes", bitmap.size());
ImGui::Text("Active: %s", bitmap.is_active() ? "Yes" : "No");
ImGui::Text("Modified: %s", bitmap.modified() ? "Yes" : "No");
if (bitmap.surface()) {
ImGui::Separator();
ImGui::Text("SDL Surface");
ImGui::Text("Pitch: %d", bitmap.surface()->pitch);
ImGui::Text("Bits Per Pixel: %d", bitmap.surface()->format->BitsPerPixel);
ImGui::Text("Bytes Per Pixel: %d", bitmap.surface()->format->BytesPerPixel);
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void Canvas::ShowPaletteEditor(gfx::SnesPalette& palette) {
if (ImGui::BeginPopupModal("Palette Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Palette Editor");
ImGui::Separator();
// Display palette colors in a grid
int cols = 8;
for (int i = 0; i < palette.size(); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
if (ImGui::ColorButton("##color", display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
// Color selected - could open detailed editor
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d: 0x%04X\nR:%d G:%d B:%d",
i, color.snes(),
(int)(display_color.x * 255),
(int)(display_color.y * 255),
(int)(display_color.z * 255));
}
ImGui::PopID();
}
ImGui::Separator();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ShowPaletteEditor method removed - now handled by EnhancedPaletteEditor
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
if (!bitmap.is_active()) return;
@@ -342,17 +461,20 @@ void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
ImVec2 available = ImGui::GetContentRegionAvail();
float scale_x = available.x / bitmap.width();
float scale_y = available.y / bitmap.height();
global_scale_ = std::min(scale_x, scale_y);
config_.global_scale = std::min(scale_x, scale_y);
// Ensure minimum readable scale
if (global_scale_ < 0.25f) global_scale_ = 0.25f;
if (config_.global_scale < 0.25f) config_.global_scale = 0.25f;
global_scale_ = config_.global_scale; // Legacy compatibility
// Center the view
scrolling_ = ImVec2(0, 0);
}
void Canvas::ResetView() {
global_scale_ = 1.0f;
config_.global_scale = 1.0f;
global_scale_ = 1.0f; // Legacy compatibility
scrolling_ = ImVec2(0, 0);
}
@@ -559,8 +681,22 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
const float scaled_size = tile_size * scale;
static bool dragging = false;
constexpr int small_map_size = 0x200;
int superY = current_map / 8;
int superX = current_map % 8;
// Calculate superX and superY accounting for world offset
int superY, superX;
if (current_map < 0x40) {
// Light World
superY = current_map / 8;
superX = current_map % 8;
} else if (current_map < 0x80) {
// Dark World
superY = (current_map - 0x40) / 8;
superX = (current_map - 0x40) % 8;
} else {
// Special World
superY = (current_map - 0x80) / 8;
superX = (current_map - 0x80) % 8;
}
// Handle right click for single tile selection
if (IsMouseClicked(ImGuiMouseButton_Right)) {
@@ -642,6 +778,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) {
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x, canvas_p0_.y),
ImVec2(canvas_p0_.x + (bitmap.width() * scale),
@@ -655,14 +795,24 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale,
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
// Calculate the actual rendered size including scale and offsets
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
canvas_p0_.y + y_offset + scrolling_.y),
ImVec2(
canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale),
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)),
canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x,
canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y),
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
// Note: Content size for child windows should be set before BeginChild, not here
}
void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
@@ -671,6 +821,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
@@ -696,28 +850,15 @@ void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) {
}
void Canvas::DrawOutline(int x, int y, int w, int h) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, kOutlineRect, 0, 0, 1.5f);
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200));
}
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
}
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, color);
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
}
void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
@@ -731,6 +872,20 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
return;
}
// Pre-render all tiles to avoid timing issues
auto tilemap_size = tilemap.atlas.width() * tilemap.atlas.height() / tilemap.map_size.x;
for (int tile_id : group) {
if (tile_id >= 0 && tile_id < tilemap_size) {
gfx::RenderTile(tilemap, tile_id);
// Ensure the tile is actually rendered and active
auto tile_it = tilemap.tile_bitmaps.find(tile_id);
if (tile_it != tilemap.tile_bitmaps.end() && !tile_it->second.is_active()) {
core::Renderer::Get().RenderBitmap(&tile_it->second);
}
}
}
// Top-left and bottom-right corners of the rectangle
ImVec2 rect_top_left = selected_points_[0];
ImVec2 rect_bottom_right = selected_points_[1];
@@ -758,6 +913,11 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
int i = 0;
for (int y = 0; y < tiles_per_col + 1; ++y) {
for (int x = 0; x < tiles_per_row + 1; ++x) {
// Check bounds to prevent access violations
if (i >= static_cast<int>(group.size())) {
break;
}
int tile_id = group[i];
// Check if tile_id is within the range of tile16_individual_
@@ -770,10 +930,28 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
// Draw the tile bitmap at the calculated position
gfx::RenderTile(tilemap, tile_id);
DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale,
150.0f);
i++;
// Ensure the tile bitmap exists and is properly rendered
auto tile_it = tilemap.tile_bitmaps.find(tile_id);
if (tile_it != tilemap.tile_bitmaps.end()) {
auto& tile_bitmap = tile_it->second;
// Ensure the bitmap is active before drawing
if (tile_bitmap.is_active()) {
DrawBitmap(tile_bitmap, tile_pos_x, tile_pos_y, scale, 150);
} else {
// Force render if not active
core::Renderer::Get().RenderBitmap(&tile_bitmap);
if (tile_bitmap.is_active()) {
DrawBitmap(tile_bitmap, tile_pos_x, tile_pos_y, scale, 150);
}
}
}
}
i++;
}
// Break outer loop if we've run out of tiles
if (i >= static_cast<int>(group.size())) {
break;
}
}
@@ -789,50 +967,15 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
}
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
// Apply global scale to position and size
float scaled_x = x * global_scale_;
float scaled_y = y * global_scale_;
float scaled_w = w * global_scale_;
float scaled_h = h * global_scale_;
ImVec2 origin(canvas_p0_.x + scrolling_.x + scaled_x,
canvas_p0_.y + scrolling_.y + scaled_y);
ImVec2 size(canvas_p0_.x + scrolling_.x + scaled_x + scaled_w,
canvas_p0_.y + scrolling_.y + scaled_y + scaled_h);
draw_list_->AddRectFilled(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list_->AddRect(outline_origin, outline_size, kBlackColor);
CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale);
}
void Canvas::DrawText(std::string text, int x, int y) {
// Apply global scale to text position
float scaled_x = x * global_scale_;
float scaled_y = y * global_scale_;
draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + scaled_x + 1,
canvas_p0_.y + scrolling_.y + scaled_y + 1),
kBlackColor, text.data());
draw_list_->AddText(
ImVec2(canvas_p0_.x + scrolling_.x + scaled_x, canvas_p0_.y + scrolling_.y + scaled_y),
kWhiteColor, text.data());
CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale);
}
void Canvas::DrawGridLines(float grid_step) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
const float grid_thickness = 0.5f;
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
ImVec2(canvas_p0_.x + x, canvas_p1_.y), grid_color,
grid_thickness);
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
ImVec2(canvas_p1_.x, canvas_p0_.y + y), grid_color,
grid_thickness);
CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale);
}
void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
@@ -872,91 +1015,50 @@ void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
}
void Canvas::DrawCustomHighlight(float grid_step) {
if (highlight_tile_id != -1) {
int tile_x = highlight_tile_id % 8;
int tile_y = highlight_tile_id / 8;
ImVec2 tile_pos(canvas_p0_.x + scrolling_.x + tile_x * grid_step,
canvas_p0_.y + scrolling_.y + tile_y * grid_step);
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
draw_list_->AddRectFilled(tile_pos, tile_pos_end,
IM_COL32(255, 0, 255, 255));
}
CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step);
}
void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
// Draw grid + all lines in the canvas
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
if (enable_grid_) {
if (custom_step_ != 0.f) grid_step = custom_step_;
grid_step *= global_scale_; // Apply global scale to grid step
DrawGridLines(grid_step);
DrawCustomHighlight(grid_step);
if (enable_hex_tile_labels_) {
// Draw the hex ID of the tile in the center of the tile square
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step) {
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) {
int tile_x = (x - scrolling_.x) / grid_step;
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * 16);
std::string hex_id = absl::StrFormat("%02X", tile_id);
draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 4,
canvas_p0_.y + y + (grid_step / 2) - 4),
kWhiteColor, hex_id.data());
}
}
}
if (!enable_custom_labels_) {
return;
}
// Draw the contents of labels on the grid
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step) {
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) {
int tile_x = (x - scrolling_.x) / grid_step;
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels_[current_labels_].size()) {
break;
}
std::string label = labels_[current_labels_][tile_id];
draw_list_->AddText(
ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
kWhiteColor, label.data());
}
}
if (config_.grid_step != 0.f) grid_step = config_.grid_step;
// Create render context for utilities
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list_,
.canvas_p0 = canvas_p0_,
.canvas_p1 = canvas_p1_,
.scrolling = scrolling_,
.global_scale = config_.global_scale,
.enable_grid = config_.enable_grid,
.enable_hex_labels = config_.enable_hex_labels,
.grid_step = grid_step
};
// Use high-level utility function
CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
// Draw custom labels if enabled
if (config_.enable_custom_labels) {
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset);
draw_list_->PopClipRect();
}
}
void Canvas::DrawOverlay() {
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
for (int n = 0; n < points_.Size; n += 2) {
draw_list_->AddRect(
ImVec2(origin.x + points_[n].x, origin.y + points_[n].y),
ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y),
kWhiteColor, 1.0f);
}
if (!selected_points_.empty()) {
for (int n = 0; n < selected_points_.size(); n += 2) {
draw_list_->AddRect(ImVec2(origin.x + selected_points_[n].x,
origin.y + selected_points_[n].y),
ImVec2(origin.x + selected_points_[n + 1].x + 0x10,
origin.y + selected_points_[n + 1].y + 0x10),
kWhiteColor, 1.0f);
}
}
draw_list_->PopClipRect();
// Create render context for utilities
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list_,
.canvas_p0 = canvas_p0_,
.canvas_p1 = canvas_p1_,
.scrolling = scrolling_,
.global_scale = config_.global_scale,
.enable_grid = config_.enable_grid,
.enable_hex_labels = config_.enable_hex_labels,
.grid_step = config_.grid_step
};
// Use high-level utility function
CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
}
void Canvas::DrawLayeredElements() {
@@ -1002,7 +1104,20 @@ void Canvas::DrawLayeredElements() {
void BeginCanvas(Canvas &canvas, ImVec2 child_size) {
gui::BeginPadding(1);
ImGui::BeginChild(canvas.canvas_id().c_str(), child_size, true);
// Use improved canvas sizing for table integration
ImVec2 effective_size = child_size;
if (child_size.x == 0 && child_size.y == 0) {
// Auto-size based on canvas configuration
if (canvas.IsAutoResize()) {
effective_size = canvas.GetPreferredSize();
} else {
effective_size = canvas.GetCurrentSize();
}
}
ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
canvas.DrawBackground();
gui::EndPadding();
canvas.DrawContextMenu();
@@ -1071,4 +1186,208 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
}
}
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
const std::string& label, bool auto_resize) {
// Configure canvas for table integration
canvas.SetAutoResize(auto_resize);
if (auto_resize && bitmap.is_active()) {
// Auto-calculate size based on bitmap content
ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height());
ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale());
canvas.SetCanvasSize(preferred_size);
}
// Begin table-aware canvas
if (canvas.BeginTableCanvas(label)) {
// Draw the canvas content
canvas.DrawBackground();
canvas.DrawContextMenu();
if (bitmap.is_active()) {
canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale());
}
canvas.DrawGrid();
canvas.DrawOverlay();
}
canvas.EndTableCanvas();
}
void Canvas::ShowAdvancedCanvasProperties() {
if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Advanced Canvas Configuration");
ImGui::Separator();
// Canvas properties (read-only info)
ImGui::Text("Canvas Properties");
ImGui::Text("ID: %s", canvas_id_.c_str());
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y);
ImGui::Text("Global Scale: %.3f", config_.global_scale);
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (config_.content_size.x > 0 && config_.content_size.y > 0) {
ImVec2 min_size = GetMinimumSize();
ImVec2 preferred_size = GetPreferredSize();
ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y);
}
// Editable properties using new config system
ImGui::Separator();
ImGui::Text("View Settings");
if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) {
enable_grid_ = config_.enable_grid; // Legacy sync
}
if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) {
enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) {
enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) {
enable_context_menu_ = config_.enable_context_menu; // Legacy sync
}
if (ImGui::Checkbox("Draggable", &config_.is_draggable)) {
draggable_ = config_.is_draggable; // Legacy sync
}
if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) {
// Auto resize setting changed
}
// Grid controls
ImGui::Separator();
ImGui::Text("Grid Configuration");
if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Scale controls
ImGui::Separator();
ImGui::Text("Scale Configuration");
if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Scrolling controls
ImGui::Separator();
ImGui::Text("Scrolling Configuration");
ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y);
if (ImGui::Button("Reset Scroll")) {
scrolling_ = ImVec2(0, 0);
}
ImGui::SameLine();
if (ImGui::Button("Center View")) {
if (bitmap_) {
scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f,
-(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f);
}
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor
void Canvas::ShowScalingControls() {
if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Canvas Scaling and Display Controls");
ImGui::Separator();
// Global scale with new config system
ImGui::Text("Global Scale: %.3f", config_.global_scale);
if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Preset scale buttons
ImGui::Text("Preset Scales:");
if (ImGui::Button("0.25x")) {
config_.global_scale = 0.25f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("0.5x")) {
config_.global_scale = 0.5f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("1x")) {
config_.global_scale = 1.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("2x")) {
config_.global_scale = 2.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("4x")) {
config_.global_scale = 4.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("8x")) {
config_.global_scale = 8.0f;
global_scale_ = config_.global_scale;
}
// Grid configuration
ImGui::Separator();
ImGui::Text("Grid Configuration");
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Grid size presets
ImGui::Text("Grid Presets:");
if (ImGui::Button("8x8")) {
config_.grid_step = 8.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("16x16")) {
config_.grid_step = 16.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("32x32")) {
config_.grid_step = 32.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("64x64")) {
config_.grid_step = 64.0f;
custom_step_ = config_.grid_step;
}
// Canvas size info
ImGui::Separator();
ImGui::Text("Canvas Information");
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Scaled Size: %.0f x %.0f",
config_.canvas_size.x * config_.global_scale,
config_.canvas_size.y * config_.global_scale);
if (bitmap_) {
ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height());
ImGui::Text("Effective Scale: %.3f x %.3f",
(config_.canvas_size.x * config_.global_scale) / bitmap_->width(),
(config_.canvas_size.y * config_.global_scale) / bitmap_->height());
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ROM palette management methods removed - now handled by EnhancedPaletteEditor
} // namespace yaze::gui

View File

@@ -6,9 +6,13 @@
#include <cstdint>
#include <string>
#include <functional>
#include <memory>
#include "app/gfx/bitmap.h"
#include "app/rom.h"
#include "app/gui/canvas_utils.h"
#include "app/gui/enhanced_palette_editor.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -28,55 +32,68 @@ enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 };
/**
* @class Canvas
* @brief Represents a canvas for drawing and manipulating graphics.
* @brief Modern, robust canvas for drawing and manipulating graphics.
*
* The Canvas class provides various functions for updating and drawing graphics
* on a canvas. It supports features such as bitmap drawing, context menu
* handling, tile painting, custom grid, and more.
* Following ImGui design patterns, this Canvas class provides:
* - Modular configuration through CanvasConfig
* - Separate selection state management
* - Enhanced palette management integration
* - Performance-optimized rendering
* - Comprehensive context menu system
*/
class Canvas {
public:
Canvas() = default;
explicit Canvas(const std::string &id) : canvas_id_(id) {
context_id_ = id + "Context";
explicit Canvas(const std::string& id)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
}
explicit Canvas(const std::string &id, ImVec2 canvas_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) {
context_id_ = id + "Context";
explicit Canvas(const std::string& id, ImVec2 canvas_size)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) {
context_id_ = id + "Context";
SetCanvasGridSize(grid_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
SetGridSize(grid_size);
}
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size, float global_scale)
: custom_canvas_size_(true),
global_scale_(global_scale),
canvas_sz_(canvas_size),
canvas_id_(id) {
context_id_ = id + "Context";
SetCanvasGridSize(grid_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
config_.global_scale = global_scale;
SetGridSize(grid_size);
}
void SetCanvasGridSize(CanvasGridSize grid_size) {
void SetGridSize(CanvasGridSize grid_size) {
switch (grid_size) {
case CanvasGridSize::k8x8:
custom_step_ = 8.0f;
config_.grid_step = 8.0f;
break;
case CanvasGridSize::k16x16:
custom_step_ = 16.0f;
config_.grid_step = 16.0f;
break;
case CanvasGridSize::k32x32:
custom_step_ = 32.0f;
config_.grid_step = 32.0f;
break;
case CanvasGridSize::k64x64:
custom_step_ = 64.0f;
config_.grid_step = 64.0f;
break;
}
}
// Legacy compatibility
void SetCanvasGridSize(CanvasGridSize grid_size) { SetGridSize(grid_size); }
void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, int tile_size,
float scale = 1.0f);
@@ -106,11 +123,45 @@ class Canvas {
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
// Enhanced view and edit operations
void ShowBitmapProperties(const gfx::Bitmap& bitmap);
void ShowPaletteEditor(gfx::SnesPalette& palette);
void ShowAdvancedCanvasProperties();
void ShowScalingControls();
void SetZoomToFit(const gfx::Bitmap& bitmap);
void ResetView();
// Modular component access
CanvasConfig& GetConfig() { return config_; }
const CanvasConfig& GetConfig() const { return config_; }
CanvasSelection& GetSelection() { return selection_; }
const CanvasSelection& GetSelection() const { return selection_; }
// Enhanced palette management
void InitializePaletteEditor(Rom* rom);
void ShowPaletteEditor();
void ShowColorAnalysis();
bool ApplyROMPalette(int group_index, int palette_index);
// Initialization and cleanup
void InitializeDefaults();
void Cleanup();
// Size reporting for ImGui table integration
ImVec2 GetMinimumSize() const;
ImVec2 GetPreferredSize() const;
ImVec2 GetCurrentSize() const { return config_.canvas_size; }
void SetAutoResize(bool auto_resize) { config_.auto_resize = auto_resize; }
bool IsAutoResize() const { return config_.auto_resize; }
// Table integration helpers
void ReserveTableSpace(const std::string& label = "");
bool BeginTableCanvas(const std::string& label = "");
void EndTableCanvas();
// Improved interaction detection
bool HasValidSelection() const;
bool WasClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
bool WasDoubleClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
ImVec2 GetLastClickPosition() const;
private:
void DrawContextMenuItem(const ContextMenuItem& item);
@@ -173,19 +224,34 @@ class Canvas {
void set_global_scale(float scale) { global_scale_ = scale; }
void set_draggable(bool draggable) { draggable_ = draggable; }
// Public accessors for commonly used private members
// Modern accessors using modular structure
bool IsSelectRectActive() const { return select_rect_active_; }
const std::vector<ImVec2>& GetSelectedTiles() const { return selected_tiles_; }
ImVec2 GetSelectedTilePos() const { return selected_tile_pos_; }
void SetSelectedTilePos(ImVec2 pos) { selected_tile_pos_ = pos; }
// Configuration accessors
void SetCanvasSize(ImVec2 canvas_size) {
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
float GetGlobalScale() const { return config_.global_scale; }
void SetGlobalScale(float scale) { config_.global_scale = scale; }
bool* GetCustomLabelsEnabled() { return &config_.enable_custom_labels; }
float GetGridStep() const { return config_.grid_step; }
float GetCanvasWidth() const { return config_.canvas_size.x; }
float GetCanvasHeight() const { return config_.canvas_size.y; }
// Legacy compatibility accessors
auto select_rect_active() const { return select_rect_active_; }
auto selected_tiles() const { return selected_tiles_; }
auto selected_tile_pos() const { return selected_tile_pos_; }
void set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; }
// Public methods for commonly used private methods
void SetCanvasSize(ImVec2 canvas_size) { canvas_sz_ = canvas_size; custom_canvas_size_ = true; }
auto global_scale() const { return global_scale_; }
auto custom_labels_enabled() { return &enable_custom_labels_; }
auto custom_step() const { return custom_step_; }
auto width() const { return canvas_sz_.x; }
auto height() const { return canvas_sz_.y; }
auto global_scale() const { return config_.global_scale; }
auto custom_labels_enabled() { return &config_.enable_custom_labels; }
auto custom_step() const { return config_.grid_step; }
auto width() const { return config_.canvas_size.x; }
auto height() const { return config_.canvas_size.y; }
// Public accessors for methods that need to be accessed externally
auto canvas_id() const { return canvas_id_; }
@@ -231,50 +297,60 @@ class Canvas {
Rom *rom() const { return rom_; }
private:
bool draggable_ = false;
// Modular configuration and state
CanvasConfig config_;
CanvasSelection selection_;
std::unique_ptr<EnhancedPaletteEditor> palette_editor_;
// Core canvas state
bool is_hovered_ = false;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool select_rect_active_ = false;
bool refresh_graphics_ = false;
// Context menu system
std::vector<ContextMenuItem> context_menu_items_;
bool context_menu_enabled_ = true;
float custom_step_ = 0.0f;
float global_scale_ = 1.0f;
// Legacy members (to be gradually replaced)
int current_labels_ = 0;
int highlight_tile_id = -1;
uint16_t edit_palette_index_ = 0;
uint64_t edit_palette_group_name_index_ = 0;
uint64_t edit_palette_sub_index_ = 0;
// Core canvas state
Bitmap *bitmap_ = nullptr;
Rom *rom_ = nullptr;
ImDrawList *draw_list_ = nullptr;
// Canvas geometry and interaction state
ImVec2 scrolling_;
ImVec2 canvas_sz_;
ImVec2 canvas_p0_;
ImVec2 canvas_p1_;
ImVec2 drawn_tile_pos_;
ImVec2 mouse_pos_in_canvas_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
// Drawing and labeling
ImVector<ImVec2> points_;
ImVector<ImVec2> selected_points_;
ImVector<ImVector<std::string>> labels_;
// Identification
std::string canvas_id_ = "Canvas";
std::string context_id_ = "CanvasContext";
// Legacy compatibility (gradually being replaced by selection_)
std::vector<ImVec2> selected_tiles_;
ImVector<ImVec2> selected_points_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
bool select_rect_active_ = false;
float custom_step_ = 32.0f;
float global_scale_ = 1.0f;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool draggable_ = false;
};
void BeginCanvas(Canvas &canvas, ImVec2 child_size = ImVec2(0, 0));
@@ -288,6 +364,10 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
int height, int tile_size, bool is_loaded,
bool scrollbar, int canvas_id);
// Table-optimized canvas pipeline with automatic sizing
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
const std::string& label = "", bool auto_resize = true);
} // namespace gui
} // namespace yaze

396
src/app/gui/canvas_utils.cc Normal file
View File

@@ -0,0 +1,396 @@
#include "canvas_utils.h"
#include <cmath>
#include "app/core/window.h"
#include "app/gfx/snes_palette.h"
#include "util/log.h"
namespace yaze {
namespace gui {
namespace CanvasUtils {
using core::Renderer;
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
std::floor(pos.y / grid_step) * grid_step);
}
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size,
float global_scale) {
if (content_size.x <= 0 || content_size.y <= 0)
return global_scale;
float scale_x = (canvas_size.x * global_scale) / content_size.x;
float scale_y = (canvas_size.y * global_scale) / content_size.y;
return std::min(scale_x, scale_y);
}
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale,
int tiles_per_row) {
float scaled_tile_size = tile_size * scale;
int tile_x = static_cast<int>(mouse_pos.x / scaled_tile_size);
int tile_y = static_cast<int>(mouse_pos.y / scaled_tile_size);
return tile_x + (tile_y * tiles_per_row);
}
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
if (!rom || palette_manager.palettes_loaded) {
return palette_manager.palettes_loaded;
}
try {
const auto& palette_groups = rom->palette_group();
palette_manager.rom_palette_groups.clear();
palette_manager.palette_group_names.clear();
// Overworld palettes
if (palette_groups.overworld_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_main[0]);
palette_manager.palette_group_names.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_aux[0]);
palette_manager.palette_group_names.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.overworld_animated[0]);
palette_manager.palette_group_names.push_back("Overworld Animated");
}
// Dungeon palettes
if (palette_groups.dungeon_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.dungeon_main[0]);
palette_manager.palette_group_names.push_back("Dungeon Main");
}
// Sprite palettes
if (palette_groups.global_sprites.size() > 0) {
palette_manager.rom_palette_groups.push_back(
palette_groups.global_sprites[0]);
palette_manager.palette_group_names.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.armors[0]);
palette_manager.palette_group_names.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.swords[0]);
palette_manager.palette_group_names.push_back("Swords");
}
palette_manager.palettes_loaded = true;
util::logf("Canvas: Loaded %zu ROM palette groups",
palette_manager.rom_palette_groups.size());
return true;
} catch (const std::exception& e) {
util::logf("Canvas: Failed to load ROM palette groups: %s", e.what());
return false;
}
}
bool ApplyPaletteGroup(gfx::Bitmap* bitmap,
const CanvasPaletteManager& palette_manager,
int group_index, int palette_index) {
if (!bitmap || group_index < 0 ||
group_index >=
static_cast<int>(palette_manager.rom_palette_groups.size())) {
return false;
}
try {
const auto& selected_palette =
palette_manager.rom_palette_groups[group_index];
// Apply the palette based on the index
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
util::logf("Canvas: Applied palette group %d, index %d to bitmap",
group_index, palette_index);
return true;
} catch (const std::exception& e) {
util::logf("Canvas: Failed to apply palette: %s", e.what());
return false;
}
}
// Drawing utility functions
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color,
float global_scale) {
// Apply global scale to position and size
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
float scaled_w = w * global_scale;
float scaled_h = h * global_scale;
ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w,
canvas_p0.y + scrolling.y + scaled_y + scaled_h);
uint32_t color_u32 = IM_COL32(color.x, color.y, color.z, color.w);
draw_list->AddRectFilled(origin, size, color_u32);
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
}
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale) {
// Apply global scale to text position
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
ImVec2 text_pos(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
// Draw text with black shadow for better visibility
draw_list->AddText(ImVec2(text_pos.x + 1, text_pos.y + 1),
IM_COL32(0, 0, 0, 255), text.c_str());
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), text.c_str());
}
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int x, int y, int w, int h,
uint32_t color) {
ImVec2 origin(canvas_p0.x + scrolling.x + x, canvas_p0.y + scrolling.y + y);
ImVec2 size(canvas_p0.x + scrolling.x + x + w,
canvas_p0.y + scrolling.y + y + h);
draw_list->AddRect(origin, size, color, 0, 0, 1.5f);
}
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int x, int y, int w, int h,
ImVec4 color) {
uint32_t color_u32 =
IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
DrawCanvasOutline(draw_list, canvas_p0, scrolling, x, y, w, h, color_u32);
}
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 canvas_p1, ImVec2 scrolling, float grid_step,
float global_scale) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
const float grid_thickness = 0.5f;
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < (canvas_p1.x - canvas_p0.x); x += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y),
ImVec2(canvas_p0.x + x, canvas_p1.y), grid_color,
grid_thickness);
}
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < (canvas_p1.y - canvas_p0.y); y += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y),
ImVec2(canvas_p1.x, canvas_p0.y + y), grid_color,
grid_thickness);
}
}
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, int highlight_tile_id,
float grid_step) {
if (highlight_tile_id == -1)
return;
int tile_x = highlight_tile_id % 8;
int tile_y = highlight_tile_id / 8;
ImVec2 tile_pos(canvas_p0.x + scrolling.x + tile_x * grid_step,
canvas_p0.y + scrolling.y + tile_y * grid_step);
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
draw_list->AddRectFilled(tile_pos, tile_pos_end, IM_COL32(255, 0, 255, 255));
}
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, ImVec2 canvas_sz, float grid_step,
float global_scale) {
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < canvas_sz.x * global_scale; x += scaled_grid_step) {
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < canvas_sz.y * global_scale; y += scaled_grid_step) {
int tile_x = (x - scrolling.x) / scaled_grid_step;
int tile_y = (y - scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * 16);
char hex_id[8];
snprintf(hex_id, sizeof(hex_id), "%02X", tile_id);
draw_list->AddText(ImVec2(canvas_p0.x + x + (scaled_grid_step / 2) - 4,
canvas_p0.y + y + (scaled_grid_step / 2) - 4),
IM_COL32(255, 255, 255, 255), hex_id);
}
}
}
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size,
bool use_custom) {
return use_custom ? custom_size : content_region;
}
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale) {
return ImVec2(canvas_size.x * global_scale, canvas_size.y * global_scale);
}
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1) {
return point.x >= canvas_p0.x && point.x <= canvas_p1.x &&
point.y >= canvas_p0.y && point.y <= canvas_p1.y;
}
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale,
float padding) {
// Calculate minimum size needed to display content with padding
ImVec2 min_size = ImVec2(content_size.x * global_scale + padding * 2,
content_size.y * global_scale + padding * 2);
// Ensure minimum practical size
min_size.x = std::max(min_size.x, 64.0f);
min_size.y = std::max(min_size.y, 64.0f);
return min_size;
}
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale,
float min_scale) {
// Calculate preferred size with minimum scale constraint
float effective_scale = std::max(global_scale, min_scale);
ImVec2 preferred_size = ImVec2(content_size.x * effective_scale + 8.0f,
content_size.y * effective_scale + 8.0f);
// Cap to reasonable maximum sizes for table integration
preferred_size.x = std::min(preferred_size.x, 800.0f);
preferred_size.y = std::min(preferred_size.y, 600.0f);
return preferred_size;
}
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label) {
// Reserve space in ImGui layout so tables know the size
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
ImGui::Dummy(canvas_size);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() -
canvas_size.x); // Move back to start
}
void SetNextCanvasSize(ImVec2 size, bool auto_resize) {
if (auto_resize) {
// Use auto-sizing child window for table integration
ImGui::SetNextWindowContentSize(size);
} else {
// Fixed size
ImGui::SetNextWindowSize(size);
}
}
// High-level composite operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id) {
if (!ctx.enable_grid)
return;
ctx.draw_list->PushClipRect(ctx.canvas_p0, ctx.canvas_p1, true);
// Draw grid lines
DrawCanvasGridLines(ctx.draw_list, ctx.canvas_p0, ctx.canvas_p1,
ctx.scrolling, ctx.grid_step, ctx.global_scale);
// Draw highlight if specified
if (highlight_tile_id != -1) {
DrawCustomHighlight(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
highlight_tile_id, ctx.grid_step * ctx.global_scale);
}
// Draw hex labels if enabled
if (ctx.enable_hex_labels) {
DrawHexTileLabels(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
ImVec2(ctx.canvas_p1.x - ctx.canvas_p0.x,
ctx.canvas_p1.y - ctx.canvas_p0.y),
ctx.grid_step, ctx.global_scale);
}
ctx.draw_list->PopClipRect();
}
void DrawCanvasOverlay(const CanvasRenderContext& ctx,
const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points) {
const ImVec2 origin(ctx.canvas_p0.x + ctx.scrolling.x,
ctx.canvas_p0.y + ctx.scrolling.y);
// Draw hover points
for (int n = 0; n < points.Size; n += 2) {
ctx.draw_list->AddRect(
ImVec2(origin.x + points[n].x, origin.y + points[n].y),
ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y),
IM_COL32(255, 255, 255, 255), 1.0f);
}
// Draw selection rectangles
if (!selected_points.empty()) {
for (int n = 0; n < selected_points.size(); n += 2) {
ctx.draw_list->AddRect(ImVec2(origin.x + selected_points[n].x,
origin.y + selected_points[n].y),
ImVec2(origin.x + selected_points[n + 1].x + 0x10,
origin.y + selected_points[n + 1].y + 0x10),
IM_COL32(255, 255, 255, 255), 1.0f);
}
}
}
void DrawCanvasLabels(const CanvasRenderContext& ctx,
const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset) {
if (current_labels >= labels.size())
return;
float scaled_grid_step = ctx.grid_step * ctx.global_scale;
for (float x = fmodf(ctx.scrolling.x, scaled_grid_step);
x < (ctx.canvas_p1.x - ctx.canvas_p0.x); x += scaled_grid_step) {
for (float y = fmodf(ctx.scrolling.y, scaled_grid_step);
y < (ctx.canvas_p1.y - ctx.canvas_p0.y); y += scaled_grid_step) {
int tile_x = (x - ctx.scrolling.x) / scaled_grid_step;
int tile_y = (y - ctx.scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels[current_labels].size()) {
break;
}
const std::string& label = labels[current_labels][tile_id];
ctx.draw_list->AddText(
ImVec2(ctx.canvas_p0.x + x + (scaled_grid_step / 2) - tile_id_offset,
ctx.canvas_p0.y + y + (scaled_grid_step / 2) - tile_id_offset),
IM_COL32(255, 255, 255, 255), label.c_str());
}
}
}
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze

147
src/app/gui/canvas_utils.h Normal file
View File

@@ -0,0 +1,147 @@
#ifndef YAZE_APP_GUI_CANVAS_UTILS_H
#define YAZE_APP_GUI_CANVAS_UTILS_H
#include <string>
#include <vector>
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Configuration for canvas display and interaction
*/
struct CanvasConfig {
bool enable_grid = true;
bool enable_hex_labels = false;
bool enable_custom_labels = false;
bool enable_context_menu = true;
bool is_draggable = false;
bool auto_resize = false;
float grid_step = 32.0f;
float global_scale = 1.0f;
ImVec2 canvas_size = ImVec2(0, 0);
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
bool custom_canvas_size = false;
};
/**
* @brief Selection state for canvas interactions
*/
struct CanvasSelection {
std::vector<ImVec2> selected_tiles;
std::vector<ImVec2> selected_points;
ImVec2 selected_tile_pos = ImVec2(-1, -1);
bool select_rect_active = false;
void Clear() {
selected_tiles.clear();
selected_points.clear();
selected_tile_pos = ImVec2(-1, -1);
select_rect_active = false;
}
};
/**
* @brief Palette management state for canvas
*/
struct CanvasPaletteManager {
std::vector<gfx::SnesPalette> rom_palette_groups;
std::vector<std::string> palette_group_names;
gfx::SnesPalette original_palette;
bool palettes_loaded = false;
int current_group_index = 0;
int current_palette_index = 0;
void Clear() {
rom_palette_groups.clear();
palette_group_names.clear();
original_palette.clear();
palettes_loaded = false;
current_group_index = 0;
current_palette_index = 0;
}
};
/**
* @brief Context menu item configuration
*/
struct CanvasContextMenuItem {
std::string label;
std::string shortcut;
std::function<void()> callback;
std::function<bool()> enabled_condition = []() { return true; };
std::vector<CanvasContextMenuItem> subitems;
};
/**
* @brief Utility functions for canvas operations
*/
namespace CanvasUtils {
// Core utility functions
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale);
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row);
// Palette management utilities
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index);
// Drawing utility functions (moved from Canvas class)
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color, float global_scale);
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale);
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, uint32_t color = IM_COL32(255, 255, 255, 200));
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color);
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1,
ImVec2 scrolling, float grid_step, float global_scale);
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int highlight_tile_id, float grid_step);
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
ImVec2 canvas_sz, float grid_step, float global_scale);
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom);
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale);
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1);
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding = 4.0f);
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale = 1.0f);
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label = "");
void SetNextCanvasSize(ImVec2 size, bool auto_resize = false);
// High-level canvas operations
struct CanvasRenderContext {
ImDrawList* draw_list;
ImVec2 canvas_p0;
ImVec2 canvas_p1;
ImVec2 scrolling;
float global_scale;
bool enable_grid;
bool enable_hex_labels;
float grid_step;
};
// Composite drawing operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id = -1);
void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points);
void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset);
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_UTILS_H

View File

@@ -13,8 +13,8 @@ namespace gui {
struct Color {
float red;
float blue;
float green;
float blue;
float alpha;
};

View File

@@ -0,0 +1,466 @@
#include "enhanced_palette_editor.h"
#include <algorithm>
#include <map>
#include "app/core/window.h"
#include "app/gui/color.h"
#include "util/log.h"
namespace yaze {
namespace gui {
using core::Renderer;
void EnhancedPaletteEditor::Initialize(Rom* rom) {
rom_ = rom;
rom_palettes_loaded_ = false;
if (rom_) {
LoadROMPalettes();
}
}
void EnhancedPaletteEditor::ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title) {
if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Enhanced Palette Editor");
ImGui::Separator();
// Palette grid editor
DrawPaletteGrid(palette);
ImGui::Separator();
// Analysis and tools
if (ImGui::CollapsingHeader("Palette Analysis")) {
DrawPaletteAnalysis(palette);
}
if (ImGui::CollapsingHeader("ROM Palette Manager") && rom_) {
DrawROMPaletteSelector();
if (ImGui::Button("Apply ROM Palette") && !rom_palette_groups_.empty()) {
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
palette = rom_palette_groups_[current_group_index_];
}
}
}
ImGui::Separator();
// Action buttons
if (ImGui::Button("Save Backup")) {
SavePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Restore Backup")) {
RestorePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void EnhancedPaletteEditor::ShowROMPaletteManager() {
if (!show_rom_manager_) return;
if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
if (!rom_) {
ImGui::Text("No ROM loaded");
ImGui::End();
return;
}
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
DrawROMPaletteSelector();
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Separator();
ImGui::Text("Preview of %s:", palette_group_names_[current_group_index_].c_str());
const auto& preview_palette = rom_palette_groups_[current_group_index_];
DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
DrawPaletteAnalysis(preview_palette);
}
}
ImGui::End();
}
void EnhancedPaletteEditor::ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title) {
if (!show_color_analysis_) return;
if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
ImGui::Text("Bitmap Color Analysis");
ImGui::Separator();
if (!bitmap.is_active()) {
ImGui::Text("Bitmap is not active");
ImGui::End();
return;
}
// Analyze pixel distribution
std::map<uint8_t, int> pixel_counts;
const auto& data = bitmap.vector();
for (uint8_t pixel : data) {
uint8_t palette_index = pixel & 0x0F; // 4-bit palette index
pixel_counts[palette_index]++;
}
ImGui::Text("Bitmap Size: %d x %d (%zu pixels)",
bitmap.width(), bitmap.height(), data.size());
ImGui::Separator();
ImGui::Text("Pixel Distribution:");
// Show distribution as bars
int total_pixels = static_cast<int>(data.size());
for (const auto& [index, count] : pixel_counts) {
float percentage = (static_cast<float>(count) / total_pixels) * 100.0f;
ImGui::Text("Index %d: %d pixels (%.1f%%)", index, count, percentage);
// Progress bar visualization
ImGui::SameLine();
ImGui::ProgressBar(percentage / 100.0f, ImVec2(100, 0));
// Color swatch if palette is available
if (index < static_cast<int>(bitmap.palette().size())) {
ImGui::SameLine();
auto color = bitmap.palette()[index];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##color" + std::to_string(index)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("SNES Color: 0x%04X\nRGB: (%d, %d, %d)",
color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
}
}
}
ImGui::End();
}
bool EnhancedPaletteEditor::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index) {
if (!bitmap || !rom_palettes_loaded_ ||
group_index < 0 || group_index >= static_cast<int>(rom_palette_groups_.size())) {
return false;
}
try {
const auto& selected_palette = rom_palette_groups_[group_index];
// Save current palette as backup
SavePaletteBackup(bitmap->palette());
// Apply new palette
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
current_group_index_ = group_index;
current_palette_index_ = palette_index;
util::logf("Applied ROM palette: %s (index %d)",
palette_group_names_[group_index].c_str(), palette_index);
return true;
} catch (const std::exception& e) {
util::logf("Failed to apply ROM palette: %s", e.what());
return false;
}
}
const gfx::SnesPalette* EnhancedPaletteEditor::GetSelectedROMPalette() const {
if (!rom_palettes_loaded_ || current_group_index_ < 0 ||
current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
return nullptr;
}
return &rom_palette_groups_[current_group_index_];
}
void EnhancedPaletteEditor::SavePaletteBackup(const gfx::SnesPalette& palette) {
backup_palette_ = palette;
}
bool EnhancedPaletteEditor::RestorePaletteBackup(gfx::SnesPalette& palette) {
if (backup_palette_.size() == 0) {
return false;
}
palette = backup_palette_;
return true;
}
void EnhancedPaletteEditor::DrawPaletteGrid(gfx::SnesPalette& palette, int cols) {
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
// Color button with editing capability
if (ImGui::ColorButton("##color", display_color,
ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
editing_color_index_ = i;
temp_color_ = display_color;
}
// Context menu for individual colors
if (ImGui::BeginPopupContextItem()) {
ImGui::Text("Color %d (0x%04X)", i, color.snes());
ImGui::Separator();
if (ImGui::MenuItem("Edit Color")) {
editing_color_index_ = i;
temp_color_ = display_color;
}
if (ImGui::MenuItem("Copy Color")) {
// Could implement color clipboard here
}
if (ImGui::MenuItem("Reset to Black")) {
palette[i] = gfx::SnesColor(0);
}
ImGui::EndPopup();
}
// Tooltip with detailed info
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)\nClick to edit",
i, color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
ImGui::PopID();
}
// Color editor popup
if (editing_color_index_ >= 0) {
ImGui::OpenPopup("Edit Color");
if (ImGui::BeginPopupModal("Edit Color", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Editing Color %d", editing_color_index_);
if (ImGui::ColorEdit4("Color", &temp_color_.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
// Update the palette color in real-time
auto new_snes_color = gfx::SnesColor(temp_color_);
palette[editing_color_index_] = new_snes_color;
}
if (ImGui::Button("Apply")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
}
void EnhancedPaletteEditor::DrawROMPaletteSelector() {
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
if (rom_palette_groups_.empty()) {
ImGui::Text("No ROM palettes available");
return;
}
// Group selector
ImGui::Text("Palette Group:");
if (ImGui::Combo("##PaletteGroup", &current_group_index_,
[](void* data, int idx, const char** out_text) -> bool {
auto* names = static_cast<std::vector<std::string>*>(data);
if (idx < 0 || idx >= static_cast<int>(names->size())) return false;
*out_text = (*names)[idx].c_str();
return true;
}, &palette_group_names_, static_cast<int>(palette_group_names_.size()))) {
// Group changed - could trigger preview update
}
// Palette index selector
ImGui::Text("Palette Index:");
ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
// Quick palette preview
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Text("Preview:");
const auto& preview_palette = rom_palette_groups_[current_group_index_];
// Show just first 8 colors in a row
for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size()); i++) {
if (i > 0) ImGui::SameLine();
auto color = preview_palette[i];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
}
}
}
void EnhancedPaletteEditor::DrawColorEditControls(gfx::SnesColor& color, int color_index) {
ImVec4 rgba = color.rgb();
ImGui::PushID(color_index);
if (ImGui::ColorEdit4("##color_edit", &rgba.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
color = gfx::SnesColor(rgba);
}
// SNES-specific controls
ImGui::Text("SNES Color: 0x%04X", color.snes());
// Individual RGB component sliders (0-31 for SNES)
int r = (color.snes() & 0x1F);
int g = (color.snes() >> 5) & 0x1F;
int b = (color.snes() >> 10) & 0x1F;
if (ImGui::SliderInt("Red", &r, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Green", &g, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Blue", &b, 0, 31)) {
uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
color = gfx::SnesColor(new_color);
}
ImGui::PopID();
}
void EnhancedPaletteEditor::DrawPaletteAnalysis(const gfx::SnesPalette& palette) {
ImGui::Text("Palette Information:");
ImGui::Text("Size: %zu colors", palette.size());
// Color distribution analysis
std::map<uint16_t, int> color_frequency;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
color_frequency[palette[i].snes()]++;
}
ImGui::Text("Unique Colors: %zu", color_frequency.size());
if (color_frequency.size() < palette.size()) {
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Warning: Duplicate colors detected!");
if (ImGui::TreeNode("Duplicate Colors")) {
for (const auto& [snes_color, count] : color_frequency) {
if (count > 1) {
ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(16, 16));
ImGui::SameLine();
ImGui::Text("0x%04X appears %d times", snes_color, count);
}
}
ImGui::TreePop();
}
}
// Brightness analysis
float total_brightness = 0.0f;
float min_brightness = 1.0f;
float max_brightness = 0.0f;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
ImVec4 color = palette[i].rgb();
float brightness = (color.x + color.y + color.z) / 3.0f;
total_brightness += brightness;
min_brightness = std::min(min_brightness, brightness);
max_brightness = std::max(max_brightness, brightness);
}
float avg_brightness = total_brightness / palette.size();
ImGui::Separator();
ImGui::Text("Brightness Analysis:");
ImGui::Text("Average: %.2f", avg_brightness);
ImGui::Text("Range: %.2f - %.2f", min_brightness, max_brightness);
// Show brightness as progress bar
ImGui::Text("Brightness Distribution:");
ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
}
void EnhancedPaletteEditor::LoadROMPalettes() {
if (!rom_ || rom_palettes_loaded_) return;
try {
const auto& palette_groups = rom_->palette_group();
rom_palette_groups_.clear();
palette_group_names_.clear();
// Load all available palette groups
if (palette_groups.overworld_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
palette_group_names_.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
palette_group_names_.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
palette_group_names_.push_back("Overworld Animated");
}
if (palette_groups.dungeon_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
palette_group_names_.push_back("Dungeon Main");
}
if (palette_groups.global_sprites.size() > 0) {
rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
palette_group_names_.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
rom_palette_groups_.push_back(palette_groups.armors[0]);
palette_group_names_.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
rom_palette_groups_.push_back(palette_groups.swords[0]);
palette_group_names_.push_back("Swords");
}
rom_palettes_loaded_ = true;
util::logf("Enhanced Palette Editor: Loaded %zu ROM palette groups", rom_palette_groups_.size());
} catch (const std::exception& e) {
util::logf("Enhanced Palette Editor: Failed to load ROM palettes: %s", e.what());
}
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,92 @@
#ifndef YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#define YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#include <vector>
#include <map>
#include "app/gfx/snes_palette.h"
#include "app/gfx/bitmap.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Enhanced palette editor with ROM integration and analysis tools
*/
class EnhancedPaletteEditor {
public:
EnhancedPaletteEditor() = default;
/**
* @brief Initialize the palette editor with ROM data
*/
void Initialize(Rom* rom);
/**
* @brief Show the main palette editor window
*/
void ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title = "Palette Editor");
/**
* @brief Show the ROM palette manager window
*/
void ShowROMPaletteManager();
/**
* @brief Show color analysis window for a bitmap
*/
void ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title = "Color Analysis");
/**
* @brief Apply a ROM palette group to a bitmap
*/
bool ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index);
/**
* @brief Get the currently selected ROM palette
*/
const gfx::SnesPalette* GetSelectedROMPalette() const;
/**
* @brief Save current palette as backup
*/
void SavePaletteBackup(const gfx::SnesPalette& palette);
/**
* @brief Restore palette from backup
*/
bool RestorePaletteBackup(gfx::SnesPalette& palette);
// Accessors
bool IsROMLoaded() const { return rom_ != nullptr; }
int GetCurrentGroupIndex() const { return current_group_index_; }
int GetCurrentPaletteIndex() const { return current_palette_index_; }
private:
void DrawPaletteGrid(gfx::SnesPalette& palette, int cols = 8);
void DrawROMPaletteSelector();
void DrawColorEditControls(gfx::SnesColor& color, int color_index);
void DrawPaletteAnalysis(const gfx::SnesPalette& palette);
void LoadROMPalettes();
Rom* rom_ = nullptr;
std::vector<gfx::SnesPalette> rom_palette_groups_;
std::vector<std::string> palette_group_names_;
gfx::SnesPalette backup_palette_;
int current_group_index_ = 0;
int current_palette_index_ = 0;
bool rom_palettes_loaded_ = false;
bool show_color_analysis_ = false;
bool show_rom_manager_ = false;
// Color editing state
int editing_color_index_ = -1;
ImVec4 temp_color_ = ImVec4(0, 0, 0, 1);
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H

View File

@@ -3,6 +3,8 @@ set(
app/gui/modules/asset_browser.cc
app/gui/modules/text_editor.cc
app/gui/canvas.cc
app/gui/canvas_utils.cc
app/gui/enhanced_palette_editor.cc
app/gui/input.cc
app/gui/style.cc
app/gui/color.cc

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
#include "app/gui/background_renderer.h"
#include "core/platform/font_loader.h"
#include "gui/color.h"
#include "gui/icons.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include "util/log.h"
@@ -26,114 +27,8 @@ Color ParseColor(const std::string &color) {
}
return result;
}
absl::Status ParseThemeContents(const std::string &key,
const std::string &value, Theme &theme) {
try {
if (key == "MenuBarBg") {
theme.menu_bar_bg = ParseColor(value);
} else if (key == "TitleBgActive") {
theme.title_bg_active = ParseColor(value);
} else if (key == "TitleBgCollapsed") {
theme.title_bg_collapsed = ParseColor(value);
} else if (key == "Tab") {
theme.tab = ParseColor(value);
} else if (key == "TabHovered") {
theme.tab_hovered = ParseColor(value);
} else if (key == "TabActive") {
theme.tab_active = ParseColor(value);
}
} catch (const std::exception &e) {
return absl::InvalidArgumentError(e.what());
}
return absl::OkStatus();
}
} // namespace
absl::StatusOr<Theme> LoadTheme(const std::string &filename) {
std::string theme_contents;
try {
theme_contents = core::LoadFile(filename);
} catch (const std::exception &e) {
return absl::InternalError(e.what());
}
Theme theme;
std::istringstream theme_stream(theme_contents);
while (theme_stream.good()) {
std::string line;
std::getline(theme_stream, line);
if (line.empty()) {
continue;
}
std::istringstream line_stream(line);
std::string key;
std::string value;
std::getline(line_stream, key, '=');
std::getline(line_stream, value);
RETURN_IF_ERROR(ParseThemeContents(key, value, theme));
}
return theme;
}
absl::Status SaveTheme(const Theme &theme) {
std::ostringstream theme_stream;
theme_stream << theme.name << "Theme\n";
theme_stream << "MenuBarBg=#" << gui::ColorToHexString(theme.menu_bar_bg)
<< "\n";
theme_stream << "TitleBg=#" << gui::ColorToHexString(theme.title_bar_bg)
<< "\n";
theme_stream << "Header=#" << gui::ColorToHexString(theme.header) << "\n";
theme_stream << "HeaderHovered=#"
<< gui::ColorToHexString(theme.header_hovered) << "\n";
theme_stream << "HeaderActive=#" << gui::ColorToHexString(theme.header_active)
<< "\n";
theme_stream << "TitleBgActive=#"
<< gui::ColorToHexString(theme.title_bg_active) << "\n";
theme_stream << "TitleBgCollapsed=#"
<< gui::ColorToHexString(theme.title_bg_collapsed) << "\n";
theme_stream << "Tab=#" << gui::ColorToHexString(theme.tab) << "\n";
theme_stream << "TabHovered=#" << gui::ColorToHexString(theme.tab_hovered)
<< "\n";
theme_stream << "TabActive=#" << gui::ColorToHexString(theme.tab_active)
<< "\n";
theme_stream << "Button=#" << gui::ColorToHexString(theme.button) << "\n";
theme_stream << "ButtonHovered=#"
<< gui::ColorToHexString(theme.button_hovered) << "\n";
theme_stream << "ButtonActive=#" << gui::ColorToHexString(theme.button_active)
<< "\n";
// Save the theme to a file.
return absl::OkStatus();
}
void ApplyTheme(const Theme &theme) {
ImGuiStyle *style = &ImGui::GetStyle();
ImVec4 *colors = style->Colors;
colors[ImGuiCol_MenuBarBg] = gui::ConvertColorToImVec4(theme.menu_bar_bg);
colors[ImGuiCol_TitleBg] = gui::ConvertColorToImVec4(theme.title_bar_bg);
colors[ImGuiCol_Header] = gui::ConvertColorToImVec4(theme.header);
colors[ImGuiCol_HeaderHovered] =
gui::ConvertColorToImVec4(theme.header_hovered);
colors[ImGuiCol_HeaderActive] =
gui::ConvertColorToImVec4(theme.header_active);
colors[ImGuiCol_TitleBgActive] =
gui::ConvertColorToImVec4(theme.title_bg_active);
colors[ImGuiCol_TitleBgCollapsed] =
gui::ConvertColorToImVec4(theme.title_bg_collapsed);
colors[ImGuiCol_Tab] = gui::ConvertColorToImVec4(theme.tab);
colors[ImGuiCol_TabHovered] = gui::ConvertColorToImVec4(theme.tab_hovered);
colors[ImGuiCol_TabActive] = gui::ConvertColorToImVec4(theme.tab_active);
colors[ImGuiCol_Button] = gui::ConvertColorToImVec4(theme.button);
colors[ImGuiCol_ButtonHovered] =
gui::ConvertColorToImVec4(theme.button_hovered);
colors[ImGuiCol_ButtonActive] =
gui::ConvertColorToImVec4(theme.button_active);
}
void ColorsYaze() {
ImGuiStyle *style = &ImGui::GetStyle();
ImVec4 *colors = style->Colors;
@@ -387,7 +282,27 @@ void BeginNoPadding() {
void EndNoPadding() { ImGui::PopStyleVar(2); }
void BeginChildWithScrollbar(const char *str_id) {
ImGui::BeginChild(str_id, ImGui::GetContentRegionAvail(), true,
// Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size) {
// Set content size before beginning child to enable proper scrolling
if (content_size.x > 0 && content_size.y > 0) {
ImGui::SetNextWindowContentSize(content_size);
}
// Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}
@@ -398,6 +313,50 @@ void BeginChildBothScrollbars(int id) {
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
}
// Helper functions for table canvas management
void BeginTableCanvas(const char* table_id, int columns, ImVec2 canvas_size) {
// Use proper sizing for tables containing canvas elements
ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp;
// If canvas size is specified, use it as minimum size
ImVec2 outer_size = ImVec2(0, 0);
if (canvas_size.x > 0 || canvas_size.y > 0) {
outer_size = canvas_size;
flags |= ImGuiTableFlags_NoHostExtendY; // Prevent auto-extending past canvas size
}
ImGui::BeginTable(table_id, columns, flags, outer_size);
}
void EndTableCanvas() {
ImGui::EndTable();
}
void SetupCanvasTableColumn(const char* label, float width_ratio) {
if (width_ratio > 0) {
ImGui::TableSetupColumn(label, ImGuiTableColumnFlags_WidthStretch, width_ratio);
} else {
ImGui::TableSetupColumn(label, ImGuiTableColumnFlags_WidthStretch);
}
}
void BeginCanvasTableCell(ImVec2 min_size) {
ImGui::TableNextColumn();
// Ensure minimum size for canvas cells
if (min_size.x > 0 || min_size.y > 0) {
ImVec2 avail = ImGui::GetContentRegionAvail();
ImVec2 actual_size = ImVec2(
std::max(avail.x, min_size.x),
std::max(avail.y, min_size.y)
);
// Reserve space for the canvas
ImGui::Dummy(actual_size);
// ImGui::SetCursorPos(ImGui::GetCursorPos() - actual_size); // Reset cursor for drawing
}
}
void DrawDisplaySettings(ImGuiStyle *ref) {
// You can pass in a reference ImGuiStyle structure to compare to, revert to
// and save to (without a reference style pointer, we will use one compared
@@ -413,66 +372,121 @@ void DrawDisplaySettings(ImGuiStyle *ref) {
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);
// Enhanced theme selector
auto& theme_manager = ThemeManager::Get();
static bool show_theme_selector = false;
ImGui::Text("Theme Selection:");
// Classic YAZE button
std::string current_theme_name = theme_manager.GetCurrentThemeName();
bool is_classic_active = (current_theme_name == "Classic YAZE");
if (is_classic_active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
}
if (ImGui::Button("Classic YAZE")) {
theme_manager.ApplyClassicYazeTheme();
ref_saved_style = style;
}
if (ImGui::Button("ColorsYaze")) {
gui::ColorsYaze();
}
if (is_classic_active) {
ImGui::PopStyleColor();
}
ImGui::SameLine();
ImGui::Text(" | ");
ImGui::SameLine();
// File themes dropdown - just the raw list, no sorting
auto available_themes = theme_manager.GetAvailableThemes();
const char* current_file_theme = "";
// Find current file theme for display
for (const auto& theme_name : available_themes) {
if (theme_name == current_theme_name) {
current_file_theme = theme_name.c_str();
break;
// Enhanced theme management section
if (ImGui::CollapsingHeader("Theme Management", ImGuiTreeNodeFlags_DefaultOpen)) {
auto& theme_manager = ThemeManager::Get();
static bool show_theme_selector = false;
static bool show_theme_editor = false;
ImGui::Text("%s Current Theme:", ICON_MD_PALETTE);
ImGui::SameLine();
std::string current_theme_name = theme_manager.GetCurrentThemeName();
bool is_classic_active = (current_theme_name == "Classic YAZE");
// Current theme display with color preview
if (is_classic_active) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", current_theme_name.c_str());
} else {
ImGui::Text("%s", current_theme_name.c_str());
}
}
if (ImGui::BeginCombo("File Themes", current_file_theme)) {
for (const auto& theme_name : available_themes) {
if (ImGui::Selectable(theme_name.c_str())) {
theme_manager.LoadTheme(theme_name);
// Theme color preview
auto current_theme = theme_manager.GetCurrentTheme();
ImGui::SameLine();
ImGui::ColorButton("##primary_preview", gui::ConvertColorToImVec4(current_theme.primary),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::SameLine();
ImGui::ColorButton("##secondary_preview", gui::ConvertColorToImVec4(current_theme.secondary),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::SameLine();
ImGui::ColorButton("##accent_preview", gui::ConvertColorToImVec4(current_theme.accent),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::Spacing();
// Theme selection table for better organization
if (ImGui::BeginTable("ThemeSelectionTable", 3,
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoHostExtendY,
ImVec2(0, 80))) {
ImGui::TableSetupColumn("Built-in", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("File Themes", ImGuiTableColumnFlags_WidthStretch, 0.4f);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableHeadersRow();
ImGui::TableNextRow();
// Built-in themes column
ImGui::TableNextColumn();
if (is_classic_active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
}
if (ImGui::Button("Classic YAZE", ImVec2(-1, 30))) {
theme_manager.ApplyClassicYazeTheme();
ref_saved_style = style;
}
if (is_classic_active) {
ImGui::PopStyleColor();
}
if (ImGui::Button("Reset ColorsYaze", ImVec2(-1, 30))) {
gui::ColorsYaze();
ref_saved_style = style;
}
// File themes column
ImGui::TableNextColumn();
auto available_themes = theme_manager.GetAvailableThemes();
const char* current_file_theme = "";
// Find current file theme for display
for (const auto& theme_name : available_themes) {
if (theme_name == current_theme_name) {
current_file_theme = theme_name.c_str();
break;
}
}
ImGui::SetNextItemWidth(-1);
if (ImGui::BeginCombo("##FileThemes", current_file_theme)) {
for (const auto& theme_name : available_themes) {
bool is_selected = (theme_name == current_theme_name);
if (ImGui::Selectable(theme_name.c_str(), is_selected)) {
theme_manager.LoadTheme(theme_name);
ref_saved_style = style;
}
}
ImGui::EndCombo();
}
if (ImGui::Button("Refresh Themes", ImVec2(-1, 30))) {
theme_manager.RefreshAvailableThemes();
}
// Actions column
ImGui::TableNextColumn();
if (ImGui::Button("Theme Selector", ImVec2(-1, 30))) {
show_theme_selector = true;
}
if (ImGui::Button("Theme Editor", ImVec2(-1, 30))) {
show_theme_editor = true;
}
ImGui::EndTable();
}
// Show theme dialogs
if (show_theme_selector) {
theme_manager.ShowThemeSelector(&show_theme_selector);
}
if (show_theme_editor) {
theme_manager.ShowSimpleThemeEditor(&show_theme_editor);
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (ImGui::Button("Theme Settings")) {
show_theme_selector = true;
}
if (show_theme_selector) {
theme_manager.ShowThemeSelector(&show_theme_selector);
}
ImGui::Separator();
@@ -825,6 +839,444 @@ void DrawDisplaySettings(ImGuiStyle *ref) {
ImGui::PopItemWidth();
}
void DrawDisplaySettingsForPopup(ImGuiStyle *ref) {
// Popup-safe version of DrawDisplaySettings without problematic tables
ImGuiStyle &style = ImGui::GetStyle();
static ImGuiStyle ref_saved_style;
// Default to using internal storage as reference
static bool init = true;
if (init && ref == NULL) ref_saved_style = style;
init = false;
if (ref == NULL) ref = &ref_saved_style;
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);
// Enhanced theme management section (simplified for popup)
if (ImGui::CollapsingHeader("Theme Management", ImGuiTreeNodeFlags_DefaultOpen)) {
auto& theme_manager = ThemeManager::Get();
ImGui::Text("%s Current Theme:", ICON_MD_PALETTE);
ImGui::SameLine();
std::string current_theme_name = theme_manager.GetCurrentThemeName();
bool is_classic_active = (current_theme_name == "Classic YAZE");
// Current theme display with color preview
if (is_classic_active) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", current_theme_name.c_str());
} else {
ImGui::Text("%s", current_theme_name.c_str());
}
// Theme color preview
auto current_theme = theme_manager.GetCurrentTheme();
ImGui::SameLine();
ImGui::ColorButton("##primary_preview", gui::ConvertColorToImVec4(current_theme.primary),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::SameLine();
ImGui::ColorButton("##secondary_preview", gui::ConvertColorToImVec4(current_theme.secondary),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::SameLine();
ImGui::ColorButton("##accent_preview", gui::ConvertColorToImVec4(current_theme.accent),
ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
ImGui::Spacing();
// Simplified theme selection (no table to avoid popup conflicts)
if (is_classic_active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
}
if (ImGui::Button("Classic YAZE")) {
theme_manager.ApplyClassicYazeTheme();
ref_saved_style = style;
}
if (is_classic_active) {
ImGui::PopStyleColor();
}
ImGui::SameLine();
if (ImGui::Button("Reset ColorsYaze")) {
gui::ColorsYaze();
ref_saved_style = style;
}
// File themes dropdown
auto available_themes = theme_manager.GetAvailableThemes();
const char* current_file_theme = "";
// Find current file theme for display
for (const auto& theme_name : available_themes) {
if (theme_name == current_theme_name) {
current_file_theme = theme_name.c_str();
break;
}
}
ImGui::Text("File Themes:");
ImGui::SetNextItemWidth(-1);
if (ImGui::BeginCombo("##FileThemes", current_file_theme)) {
for (const auto& theme_name : available_themes) {
bool is_selected = (theme_name == current_theme_name);
if (ImGui::Selectable(theme_name.c_str(), is_selected)) {
theme_manager.LoadTheme(theme_name);
ref_saved_style = style;
}
}
ImGui::EndCombo();
}
if (ImGui::Button("Refresh Themes")) {
theme_manager.RefreshAvailableThemes();
}
ImGui::SameLine();
if (ImGui::Button("Open Theme Editor")) {
static bool show_theme_editor = true;
theme_manager.ShowSimpleThemeEditor(&show_theme_editor);
}
}
ImGui::Separator();
// Background effects settings
auto& bg_renderer = gui::BackgroundRenderer::Get();
bg_renderer.DrawSettingsUI();
ImGui::Separator();
if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style;
ImGui::ShowFontSelector("Fonts##Selector");
// Quick style controls before the tabbed section
if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"))
style.GrabRounding = style.FrameRounding;
// Border checkboxes (simplified layout)
bool window_border = (style.WindowBorderSize > 0.0f);
if (ImGui::Checkbox("WindowBorder", &window_border)) {
style.WindowBorderSize = window_border ? 1.0f : 0.0f;
}
ImGui::SameLine();
bool frame_border = (style.FrameBorderSize > 0.0f);
if (ImGui::Checkbox("FrameBorder", &frame_border)) {
style.FrameBorderSize = frame_border ? 1.0f : 0.0f;
}
ImGui::SameLine();
bool popup_border = (style.PopupBorderSize > 0.0f);
if (ImGui::Checkbox("PopupBorder", &popup_border)) {
style.PopupBorderSize = popup_border ? 1.0f : 0.0f;
}
// Save/Revert buttons
if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style;
ImGui::SameLine();
if (ImGui::Button("Revert Ref")) style = *ref;
ImGui::Separator();
// Add the comprehensive tabbed settings from the original DrawDisplaySettings
if (ImGui::BeginTabBar("DisplaySettingsTabs", ImGuiTabBarFlags_None)) {
if (ImGui::BeginTabItem("Sizes")) {
ImGui::SeparatorText("Main");
ImGui::SliderFloat2("WindowPadding", (float *)&style.WindowPadding, 0.0f,
20.0f, "%.0f");
ImGui::SliderFloat2("FramePadding", (float *)&style.FramePadding, 0.0f,
20.0f, "%.0f");
ImGui::SliderFloat2("ItemSpacing", (float *)&style.ItemSpacing, 0.0f,
20.0f, "%.0f");
ImGui::SliderFloat2("ItemInnerSpacing", (float *)&style.ItemInnerSpacing,
0.0f, 20.0f, "%.0f");
ImGui::SliderFloat2("TouchExtraPadding",
(float *)&style.TouchExtraPadding, 0.0f, 10.0f,
"%.0f");
ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f,
"%.0f");
ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f,
"%.0f");
ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f,
"%.0f");
ImGui::SeparatorText("Borders");
ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f,
1.0f, "%.0f");
ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f,
"%.0f");
ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f,
"%.0f");
ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f,
"%.0f");
ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f,
"%.0f");
ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f,
2.0f, "%.0f");
ImGui::SeparatorText("Rounding");
ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f,
12.0f, "%.0f");
ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f,
"%.0f");
ImGui::SeparatorText("Tables");
ImGui::SliderFloat2("CellPadding", (float *)&style.CellPadding, 0.0f,
20.0f, "%.0f");
ImGui::SliderAngle("TableAngledHeadersAngle",
&style.TableAngledHeadersAngle, -50.0f, +50.0f);
ImGui::SeparatorText("Widgets");
ImGui::SliderFloat2("WindowTitleAlign", (float *)&style.WindowTitleAlign,
0.0f, 1.0f, "%.2f");
ImGui::Combo("ColorButtonPosition", (int *)&style.ColorButtonPosition,
"Left\0Right\0");
ImGui::SliderFloat2("ButtonTextAlign", (float *)&style.ButtonTextAlign,
0.0f, 1.0f, "%.2f");
ImGui::SameLine();
ImGui::SliderFloat2("SelectableTextAlign",
(float *)&style.SelectableTextAlign, 0.0f, 1.0f,
"%.2f");
ImGui::SameLine();
ImGui::SliderFloat("SeparatorTextBorderSize",
&style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f");
ImGui::SliderFloat2("SeparatorTextAlign",
(float *)&style.SeparatorTextAlign, 0.0f, 1.0f,
"%.2f");
ImGui::SliderFloat2("SeparatorTextPadding",
(float *)&style.SeparatorTextPadding, 0.0f, 40.0f,
"%.0f");
ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f,
12.0f, "%.0f");
ImGui::SeparatorText("Tooltips");
for (int n = 0; n < 2; n++)
if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse"
: "HoverFlagsForTooltipNav")) {
ImGuiHoveredFlags *p = (n == 0) ? &style.HoverFlagsForTooltipMouse
: &style.HoverFlagsForTooltipNav;
ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p,
ImGuiHoveredFlags_DelayNone);
ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayShort", p,
ImGuiHoveredFlags_DelayShort);
ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p,
ImGuiHoveredFlags_DelayNormal);
ImGui::CheckboxFlags("ImGuiHoveredFlags_Stationary", p,
ImGuiHoveredFlags_Stationary);
ImGui::CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p,
ImGuiHoveredFlags_NoSharedDelay);
ImGui::TreePop();
}
ImGui::SeparatorText("Misc");
ImGui::SliderFloat2("DisplaySafeAreaPadding",
(float *)&style.DisplaySafeAreaPadding, 0.0f, 30.0f,
"%.0f");
ImGui::SameLine();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Colors")) {
static int output_dest = 0;
static bool output_only_modified = true;
if (ImGui::Button("Export")) {
if (output_dest == 0)
ImGui::LogToClipboard();
else
ImGui::LogToTTY();
ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE);
for (int i = 0; i < ImGuiCol_COUNT; i++) {
const ImVec4 &col = style.Colors[i];
const char *name = ImGui::GetStyleColorName(i);
if (!output_only_modified ||
memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0)
ImGui::LogText(
"colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, "
"%.2ff);" IM_NEWLINE,
name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w);
}
ImGui::LogFinish();
}
ImGui::SameLine();
ImGui::SetNextItemWidth(120);
ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
ImGui::SameLine();
ImGui::Checkbox("Only Modified Colors", &output_only_modified);
static ImGuiTextFilter filter;
filter.Draw("Filter colors", ImGui::GetFontSize() * 16);
static ImGuiColorEditFlags alpha_flags = 0;
if (ImGui::RadioButton("Opaque",
alpha_flags == ImGuiColorEditFlags_None)) {
alpha_flags = ImGuiColorEditFlags_None;
}
ImGui::SameLine();
if (ImGui::RadioButton("Alpha",
alpha_flags == ImGuiColorEditFlags_AlphaPreview)) {
alpha_flags = ImGuiColorEditFlags_AlphaPreview;
}
ImGui::SameLine();
if (ImGui::RadioButton(
"Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) {
alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf;
}
ImGui::SameLine();
ImGui::SetNextWindowSizeConstraints(
ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10),
ImVec2(FLT_MAX, FLT_MAX));
ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border,
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar |
ImGuiWindowFlags_NavFlattened);
ImGui::PushItemWidth(ImGui::GetFontSize() * -12);
for (int i = 0; i < ImGuiCol_COUNT; i++) {
const char *name = ImGui::GetStyleColorName(i);
if (!filter.PassFilter(name)) continue;
ImGui::PushID(i);
ImGui::ColorEdit4("##color", (float *)&style.Colors[i],
ImGuiColorEditFlags_AlphaBar | alpha_flags);
if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) {
// Tips: in a real user application, you may want to merge and use
// an icon font into the main font, so instead of "Save"/"Revert"
// you'd use icons! Read the FAQ and docs/FONTS.md about using icon
// fonts. It's really easy and super convenient!
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
if (ImGui::Button("Save")) {
ref->Colors[i] = style.Colors[i];
}
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
if (ImGui::Button("Revert")) {
style.Colors[i] = ref->Colors[i];
}
}
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
ImGui::TextUnformatted(name);
ImGui::PopID();
}
ImGui::PopItemWidth();
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Fonts")) {
ImGuiIO &io = ImGui::GetIO();
ImFontAtlas *atlas = io.Fonts;
ImGui::ShowFontAtlas(atlas);
// Post-baking font scaling. Note that this is NOT the nice way of
// scaling fonts, read below. (we enforce hard clamping manually as by
// default DragFloat/SliderFloat allows CTRL+Click text to get out of
// bounds).
const float MIN_SCALE = 0.3f;
const float MAX_SCALE = 2.0f;
static float window_scale = 1.0f;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::DragFloat(
"window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE,
"%.2f",
ImGuiSliderFlags_AlwaysClamp)) // Scale only this window
ImGui::SetWindowFontScale(window_scale);
ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE,
MAX_SCALE, "%.2f",
ImGuiSliderFlags_AlwaysClamp); // Scale everything
ImGui::PopItemWidth();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Rendering")) {
ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines);
ImGui::SameLine();
ImGui::Checkbox("Anti-aliased lines use texture",
&style.AntiAliasedLinesUseTex);
ImGui::SameLine();
ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill);
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
ImGui::DragFloat("Curve Tessellation Tolerance",
&style.CurveTessellationTol, 0.02f, 0.10f, 10.0f,
"%.2f");
if (style.CurveTessellationTol < 0.10f)
style.CurveTessellationTol = 0.10f;
// When editing the "Circle Segment Max Error" value, draw a preview of
// its effect on auto-tessellated circles.
ImGui::DragFloat("Circle Tessellation Max Error",
&style.CircleTessellationMaxError, 0.005f, 0.10f, 5.0f,
"%.2f", ImGuiSliderFlags_AlwaysClamp);
const bool show_samples = ImGui::IsItemActive();
if (show_samples) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos());
if (show_samples && ImGui::BeginTooltip()) {
ImGui::TextUnformatted("(R = radius, N = number of segments)");
ImGui::Spacing();
ImDrawList *draw_list = ImGui::GetWindowDrawList();
const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x;
for (int n = 0; n < 8; n++) {
const float RAD_MIN = 5.0f;
const float RAD_MAX = 70.0f;
const float rad =
RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f);
ImGui::BeginGroup();
ImGui::Text("R: %.f\nN: %d", rad,
draw_list->_CalcCircleAutoSegmentCount(rad));
const float canvas_width = std::max(min_widget_width, rad * 2.0f);
const float offset_x = floorf(canvas_width * 0.5f);
const float offset_y = floorf(RAD_MAX);
const ImVec2 p1 = ImGui::GetCursorScreenPos();
draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad,
ImGui::GetColorU32(ImGuiCol_Text));
ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2));
ImGui::EndGroup();
ImGui::SameLine();
}
ImGui::EndTooltip();
}
ImGui::SameLine();
ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f,
"%.2f"); // Not exposing zero here so user doesn't
// "lose" the UI (zero alpha clips all
// widgets). But application code could have a
// toggle to switch between zero and non-zero.
ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f,
1.0f, "%.2f");
ImGui::SameLine();
ImGui::PopItemWidth();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::PopItemWidth();
}
void TextWithSeparators(const absl::string_view &text) {
ImGui::Separator();
ImGui::Text("%s", text.data());

View File

@@ -14,35 +14,6 @@
namespace yaze {
namespace gui {
struct Theme {
std::string name;
Color menu_bar_bg;
Color title_bar_bg;
Color header;
Color header_hovered;
Color header_active;
Color title_bg_active;
Color title_bg_collapsed;
Color tab;
Color tab_hovered;
Color tab_active;
Color button;
Color button_hovered;
Color button_active;
Color clickable_text;
Color clickable_text_hovered;
};
absl::StatusOr<Theme> LoadTheme(const std::string &filename);
absl::Status SaveTheme(const Theme &theme);
void ApplyTheme(const Theme &theme);
void ColorsYaze();
TextEditor::LanguageDefinition GetAssemblyLanguageDef();
@@ -63,10 +34,18 @@ void BeginNoPadding();
void EndNoPadding();
void BeginChildWithScrollbar(const char *str_id);
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size);
void BeginChildBothScrollbars(int id);
// Table canvas management helpers for GUI elements that need proper sizing
void BeginTableCanvas(const char* table_id, int columns, ImVec2 canvas_size = ImVec2(0, 0));
void EndTableCanvas();
void SetupCanvasTableColumn(const char* label, float width_ratio = 0.0f);
void BeginCanvasTableCell(ImVec2 min_size = ImVec2(0, 0));
void DrawDisplaySettings(ImGuiStyle *ref = nullptr);
void DrawDisplaySettingsForPopup(ImGuiStyle *ref = nullptr); // Popup-safe version
void TextWithSeparators(const absl::string_view &text);

File diff suppressed because it is too large Load Diff

View File

@@ -105,6 +105,34 @@ struct EnhancedTheme {
Color plot_lines_hovered;
Color plot_histogram;
Color plot_histogram_hovered;
Color tree_lines;
// Additional ImGui colors for complete coverage
Color tab_dimmed;
Color tab_dimmed_selected;
Color tab_dimmed_selected_overline;
Color tab_selected_overline;
// Enhanced theme system - semantic colors
Color text_highlight; // For selected text, highlighted items
Color link_hover; // For hover state of links
Color code_background; // For code blocks, monospace text backgrounds
Color success_light; // Lighter variant of success color
Color warning_light; // Lighter variant of warning color
Color error_light; // Lighter variant of error color
Color info_light; // Lighter variant of info color
// UI state colors
Color active_selection; // For active/selected UI elements
Color hover_highlight; // General hover state
Color focus_border; // For focused input elements
Color disabled_overlay; // Semi-transparent overlay for disabled elements
// Editor-specific colors
Color editor_background; // Main editor canvas background
Color editor_grid; // Grid lines in editors
Color editor_cursor; // Cursor/selection in editors
Color editor_selection; // Selected area in editors
// Style parameters
float window_rounding = 0.0f;

File diff suppressed because it is too large Load Diff

View File

@@ -87,6 +87,29 @@ constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04
constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F
constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8
constexpr int overworldSpritesBeginingExpanded = 0x141438;
constexpr int overworldSpritesZeldaExpanded = 0x141578;
constexpr int overworldSpritesAgahnimExpanded = 0x1416B8;
constexpr int overworldSpritesDataStartExpanded = 0x04C881;
constexpr int overworldSpecialSpriteGFXGroupExpandedTemp = 0x0166E1;
constexpr int overworldSpecialSpritePaletteExpandedTemp = 0x016701;
constexpr int ExpandedOverlaySpace = 0x120000;
constexpr int overworldTilesType = 0x071459;
constexpr int overworldMessages = 0x03F51D;
constexpr int overworldMessagesExpanded = 0x1417F8;
constexpr int overworldItemsPointers = 0x0DC2F9;
constexpr int overworldItemsAddress = 0x0DC8B9; // 1BC2F9
constexpr int overworldItemsAddressBank = 0x0DC8BF;
constexpr int overworldItemsEndData = 0x0DC89C; // 0DC89E
constexpr int overworldBombDoorItemLocationsNew = 0x012644;
constexpr int overworldItemsPointersNew = 0x012784;
constexpr int overworldItemsStartDataNew = 0x0DC2F9;
constexpr int kOverworldCompressedMapPos = 0x058000;
constexpr int kOverworldCompressedOverflowPos = 0x137FFF;
@@ -127,9 +150,36 @@ class Overworld {
absl::Status Save(Rom *rom);
absl::Status SaveOverworldMaps();
absl::Status SaveLargeMaps();
absl::Status SaveLargeMapsExpanded();
absl::Status SaveSmallAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveLargeAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveWideAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveTallAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveEntrances();
absl::Status SaveExits();
absl::Status SaveItems();
absl::Status SaveMapOverlays();
absl::Status SaveOverworldTilesType();
absl::Status SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette,
bool enable_mosaic, bool enable_gfx_groups,
bool enable_subscreen_overlay, bool enable_animated);
absl::Status SaveAreaSpecificBGColors();
absl::Status CreateTile32Tilemap();
absl::Status SaveMap16Expanded();
@@ -138,7 +188,9 @@ class Overworld {
absl::Status SaveMap32Tiles();
absl::Status SaveMapProperties();
absl::Status SaveMusic();
absl::Status SaveAreaSizes();
void AssignMapSizes(std::vector<OverworldMap>& maps);
auto rom() const { return rom_; }
auto mutable_rom() { return rom_; }
@@ -212,6 +264,7 @@ class Overworld {
}
auto is_loaded() const { return is_loaded_; }
void set_current_map(int i) { current_map_ = i; }
void set_current_world(int world) { current_world_ = world; }
auto map_tiles() const { return map_tiles_; }
auto mutable_map_tiles() { return &map_tiles_; }
auto all_items() const { return all_items_; }

View File

@@ -19,6 +19,10 @@ constexpr int kOverworldItemsAddress = 0xDC8B9; // 1BC2F9
constexpr int kOverworldItemsBank = 0xDC8BF;
constexpr int kOverworldItemsEndData = 0xDC89C; // 0DC89E
constexpr int kOverworldBombDoorItemLocationsNew = 0x012644;
constexpr int kOverworldItemsPointersNew = 0x012784;
constexpr int kOverworldItemsStartDataNew = 0x0DC2F9;
class OverworldItem : public GameEntity {
public:
OverworldItem() = default;

View File

@@ -14,38 +14,52 @@
namespace yaze {
namespace zelda3 {
OverworldMap::OverworldMap(int index, Rom *rom)
OverworldMap::OverworldMap(int index, Rom* rom)
: index_(index), parent_(index), rom_(rom) {
LoadAreaInfo();
// Load parent ID from ROM data if available (for custom ASM versions)
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF && asm_version >= 0x03) {
// For v3+, parent ID is stored in expanded table
parent_ = (*rom_)[kOverworldMapParentIdExpanded + index_];
}
if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
// If the custom overworld ASM has NOT already been applied, manually set
// the vanilla values.
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version == 0x00) {
if (asm_version != 0xFF) {
if (asm_version == 0x03) {
LoadCustomOverworldData();
} else {
SetupCustomTileset(asm_version);
}
} else if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
// Pure vanilla ROM but flag enabled - set up custom data manually
LoadCustomOverworldData();
}
// For pure vanilla ROMs, LoadAreaInfo already handles everything
}
absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
std::vector<gfx::Tile16> &tiles16,
OverworldBlockset &world_blockset) {
std::vector<gfx::Tile16>& tiles16,
OverworldBlockset& world_blockset) {
game_state_ = game_state;
world_ = world;
if (large_map_) {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// For large maps in vanilla ROMs, we need to handle special world graphics
// This ensures proper rendering of special overworld areas like Zora's Domain
if (large_map_ && asm_version == 0xFF) {
if (parent_ != index_ && !initialized_) {
if (index_ >= kSpecialWorldMapIdStart && index_ <= 0x8A &&
index_ != 0x88) {
// Most special world areas use the special graphics group
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(parent_ - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x88) {
// Triforce room has special hardcoded values
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else {
// Fallback to standard area graphics
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
@@ -67,6 +81,11 @@ absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
void OverworldMap::LoadAreaInfo() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// ZSCustomOverworld ASM Version System:
// 0x00-0x02: Legacy versions with limited features
// 0x03: Current version with full area size expansion and custom data support
// 0xFF: Pure vanilla ROM (no ASM applied)
// Load message ID and area size based on ASM version
if (asm_version < 3 || asm_version == 0xFF) {
// v2 and vanilla: use original message table
@@ -76,6 +95,7 @@ void OverworldMap::LoadAreaInfo() {
// Load area size for v2/vanilla
if (index_ < 0x80) {
// For LW and DW, check the screen size byte
// Note: v2 had a bug where large/small values were swapped
uint8_t size_byte = (*rom_)[kOverworldScreenSize + (index_ & 0x3F)];
switch (size_byte) {
case 0:
@@ -94,6 +114,7 @@ void OverworldMap::LoadAreaInfo() {
}
} else {
// For SW, use hardcoded values for v2 compatibility
// Zora's Domain areas (0x81, 0x82, 0x89, 0x8A) are large areas
area_size_ =
(index_ == 0x81 || index_ == 0x82 || index_ == 0x89 || index_ == 0x8A)
? AreaSizeEnum::LargeArea
@@ -101,6 +122,7 @@ void OverworldMap::LoadAreaInfo() {
}
} else {
// v3: use expanded message table and area size table
// All area sizes are now stored in the expanded table, supporting all size types
message_id_ =
(*rom_)[kOverworldMessagesExpanded + (parent_ * 2)] |
((*rom_)[kOverworldMessagesExpanded + (parent_ * 2) + 1] << 8);
@@ -165,10 +187,10 @@ void OverworldMap::LoadAreaInfo() {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
} else {
// Special World (SW) areas
// Special World (SW) areas (index >= 0x80)
// Message ID already loaded above based on ASM version
// For v3, use expanded sprite tables
// For v3, use expanded sprite tables with full customization support
if (asm_version >= 3 && asm_version != 0xFF) {
sprite_graphics_[0] =
(*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
@@ -207,20 +229,24 @@ void OverworldMap::LoadAreaInfo() {
area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];
// For v2/vanilla, use original palette table and handle special cases
// These hardcoded cases are needed for vanilla compatibility
if (asm_version < 3 || asm_version == 0xFF) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
// Handle special world area cases
// Handle special world area cases based on ZScream documentation
if (index_ == 0x88 || index_ == 0x93) {
// Triforce room - special graphics and palette
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else if (index_ == 0x80) {
// Master Sword area - use special graphics group
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(parent_ - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 ||
index_ == 0x8A) {
// Zora's Domain areas - use special sprite graphics
// Zora's Domain areas - use special sprite graphics and area graphics
// Note: These are the large area maps that were causing crashes
sprite_graphics_[0] = 0x0E;
sprite_graphics_[1] = 0x0E;
sprite_graphics_[2] = 0x0E;
@@ -246,7 +272,7 @@ void OverworldMap::LoadAreaInfo() {
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x43];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x43];
} else {
// Default case
// Default case - use basic graphics
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x00];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x00];
}
@@ -571,7 +597,7 @@ void OverworldMap::LoadAreaGraphics() {
namespace palette_internal {
absl::Status SetColorsPalette(Rom &rom, int index, gfx::SnesPalette &current,
absl::Status SetColorsPalette(Rom& rom, int index, gfx::SnesPalette& current,
gfx::SnesPalette main, gfx::SnesPalette animated,
gfx::SnesPalette aux1, gfx::SnesPalette aux2,
gfx::SnesPalette hud, gfx::SnesColor bgrcolor,
@@ -692,7 +718,7 @@ absl::Status SetColorsPalette(Rom &rom, int index, gfx::SnesPalette &current,
} // namespace palette_internal
absl::StatusOr<gfx::SnesPalette> OverworldMap::GetPalette(
const gfx::PaletteGroup &palette_group, int index, int previous_index,
const gfx::PaletteGroup& palette_group, int index, int previous_index,
int limit) {
if (index == 255) {
index = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
@@ -706,10 +732,10 @@ absl::StatusOr<gfx::SnesPalette> OverworldMap::GetPalette(
absl::Status OverworldMap::LoadPalette() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
int previous_pal_id = 0;
int previous_spr_pal_id = 0;
if (index_ > 0) {
// Load previous palette ID based on ASM version
if (asm_version < 3 || asm_version == 0xFF) {
@@ -718,7 +744,7 @@ absl::Status OverworldMap::LoadPalette() {
// v3 uses expanded palette table
previous_pal_id = (*rom_)[kOverworldPalettesScreenToSetNew + parent_ - 1];
}
previous_spr_pal_id = (*rom_)[kOverworldSpritePaletteIds + parent_ - 1];
}
@@ -744,12 +770,12 @@ absl::Status OverworldMap::LoadPalette() {
pal1 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4)];
}
if (pal2 == 0xFF) {
pal2 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4) + 1];
}
if (pal3 == 0xFF) {
pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4) + 2];
@@ -762,11 +788,14 @@ absl::Status OverworldMap::LoadPalette() {
GetPalette(ow_aux_pal_group, pal2, previous_pal_id, 20));
// Set background color based on world type and area-specific settings
bool use_area_specific_bg = (*rom_)[OverworldCustomAreaSpecificBGEnabled] != 0x00;
bool use_area_specific_bg =
(*rom_)[OverworldCustomAreaSpecificBGEnabled] != 0x00;
if (use_area_specific_bg) {
// Use area-specific background color from custom array
area_specific_bg_color_ = (*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2)] |
((*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2) + 1] << 8);
area_specific_bg_color_ =
(*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2)] |
((*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2) + 1]
<< 8);
// Convert 15-bit SNES color to palette color
bgr = gfx::SnesColor(area_specific_bg_color_);
} else {
@@ -799,15 +828,16 @@ absl::Status OverworldMap::LoadPalette() {
if (pal4 == 0xFF) {
pal4 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2)];
}
if (pal4 == 0xFF) {
pal4 = 0; // Fallback to 0 if still 0xFF
}
if (pal5 == 0xFF) {
pal5 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2) + 1];
pal5 =
(*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2) + 1];
}
if (pal5 == 0xFF) {
pal5 = 0; // Fallback to 0 if still 0xFF
}
@@ -833,7 +863,7 @@ absl::Status OverworldMap::LoadPalette() {
absl::Status OverworldMap::LoadOverlay() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// Load overlays based on ROM version
if (asm_version == 0xFF) {
// Vanilla ROM - load overlay from overlay pointers
@@ -848,15 +878,15 @@ absl::Status OverworldMap::LoadOverlay() {
}
absl::Status OverworldMap::LoadVanillaOverlayData() {
// Load vanilla overlay for this map (interactive overlays for revealing holes/changing elements)
int address = (kOverlayPointersBank << 16) +
((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) +
(*rom_)[kOverlayPointers + (index_ * 2)];
// Convert SNES address to PC address
address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);
// Check if custom overlay code is present
if ((*rom_)[kOverlayData1] == 0x6B) {
// Use custom overlay data pointer
@@ -865,7 +895,7 @@ absl::Status OverworldMap::LoadVanillaOverlayData() {
(*rom_)[kOverlayData2 + (index_ * 3)];
address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);
}
// Validate address
if (address >= rom_->size()) {
has_overlay_ = false;
@@ -873,15 +903,15 @@ absl::Status OverworldMap::LoadVanillaOverlayData() {
overlay_data_.clear();
return absl::OkStatus();
}
// Parse overlay data (interactive overlays)
overlay_data_.clear();
uint8_t b = (*rom_)[address];
// Parse overlay commands until we hit END (0x60)
while (b != 0x60 && address < rom_->size()) {
overlay_data_.push_back(b);
// Handle different overlay commands
switch (b) {
case 0xA9: // LDA #$
@@ -950,28 +980,28 @@ absl::Status OverworldMap::LoadVanillaOverlayData() {
address++;
break;
}
if (address < rom_->size()) {
b = (*rom_)[address];
} else {
break;
}
}
// Add the END command if we found it
if (b == 0x60) {
overlay_data_.push_back(0x60);
}
// Set overlay ID based on map index (simplified)
overlay_id_ = index_;
has_overlay_ = !overlay_data_.empty();
return absl::OkStatus();
}
void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
int size, uint8_t *all_gfx) {
int size, uint8_t* all_gfx) {
// Ensure we don't go out of bounds
int max_offset = static_graphics_offset * size + size;
if (max_offset > rom_->graphics_buffer().size()) {
@@ -981,7 +1011,7 @@ void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
}
return;
}
for (int i = 0; i < size; i++) {
auto byte = all_gfx[i + (static_graphics_offset * size)];
switch (index) {
@@ -997,8 +1027,9 @@ void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
}
absl::Status OverworldMap::BuildTileset() {
if (current_gfx_.size() == 0) current_gfx_.resize(0x10000, 0x00);
if (current_gfx_.size() == 0)
current_gfx_.resize(0x10000, 0x00);
// Process the 8 main graphics sheets (slots 0-7)
for (int i = 0; i < 8; i++) {
if (static_graphics_[i] != 0) {
@@ -1006,7 +1037,7 @@ absl::Status OverworldMap::BuildTileset() {
rom_->graphics_buffer().data());
}
}
// Process sprite graphics (slots 8-15)
for (int i = 8; i < 16; i++) {
if (static_graphics_[i] != 0) {
@@ -1014,19 +1045,20 @@ absl::Status OverworldMap::BuildTileset() {
rom_->graphics_buffer().data());
}
}
// Process animated graphics if available (slot 16)
if (static_graphics_[16] != 0) {
ProcessGraphicsBuffer(7, static_graphics_[16], 0x1000,
rom_->graphics_buffer().data());
}
return absl::OkStatus();
}
absl::Status OverworldMap::BuildTiles16Gfx(std::vector<gfx::Tile16> &tiles16,
absl::Status OverworldMap::BuildTiles16Gfx(std::vector<gfx::Tile16>& tiles16,
int count) {
if (current_blockset_.size() == 0) current_blockset_.resize(0x100000, 0x00);
if (current_blockset_.size() == 0)
current_blockset_.resize(0x100000, 0x00);
const int offsets[] = {0x00, 0x08, 0x400, 0x408};
auto yy = 0;
@@ -1070,7 +1102,7 @@ absl::Status OverworldMap::BuildTiles16Gfx(std::vector<gfx::Tile16> &tiles16,
return absl::OkStatus();
}
absl::Status OverworldMap::BuildBitmap(OverworldBlockset &world_blockset) {
absl::Status OverworldMap::BuildBitmap(OverworldBlockset& world_blockset) {
if (bitmap_data_.size() != 0) {
bitmap_data_.clear();
}

View File

@@ -67,6 +67,9 @@ constexpr int kOverworldPalettesScreenToSetNew = 0x4C635;
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1;
constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701;
constexpr int transition_target_northExpanded = 0x1411B8;
constexpr int transition_target_westExpanded = 0x1412F8;
constexpr int kDarkWorldMapIdStart = 0x40;
constexpr int kSpecialWorldMapIdStart = 0x80;

View File

@@ -122,7 +122,7 @@ class ModernCLI {
}
void ShowVersion() {
std::cout << "z3ed v0.3.0 - Yet Another Zelda3 Editor CLI" << std::endl;
std::cout << "z3ed v0.3.1 - Yet Another Zelda3 Editor CLI" << std::endl;
std::cout << "Built with Asar integration" << std::endl;
std::cout << "Copyright (c) 2025 scawful" << std::endl;
}
@@ -434,7 +434,11 @@ class ModernCLI {
} // namespace cli
} // namespace yaze
#ifdef _WIN32
extern "C" int SDL_main(int argc, char* argv[]) {
#else
int main(int argc, char* argv[]) {
#endif
absl::SetProgramUsageMessage(
"z3ed - Yet Another Zelda3 Editor CLI Tool\n"
"\n"

View File

@@ -704,7 +704,7 @@ void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
void HelpComponent(ftxui::ScreenInteractive &screen) {
auto help_text = vbox({
text("z3ed v0.3.0") | bold | color(Color::Yellow),
text("z3ed v0.3.1") | bold | color(Color::Yellow),
text("by scawful") | color(Color::Magenta),
text("The Legend of Zelda: A Link to the Past Hacking Tool") |
color(Color::Red),
@@ -866,7 +866,7 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
auto title = border(hbox({
text("z3ed") | bold | color(Color::Blue1),
separator(),
text("v0.3.0") | bold | color(Color::Green1),
text("v0.3.1") | bold | color(Color::Green1),
separator(),
text(rom_information) | bold | color(Color::Red1),
}));

View File

@@ -24,7 +24,11 @@ DEFINE_FLAG(std::string, length, "", "The length of the data to read.");
DEFINE_FLAG(std::string, file_size, "", "The size of the file to expand to.");
DEFINE_FLAG(std::string, dest_rom, "", "The destination ROM file.");
#ifdef _WIN32
extern "C" int SDL_main(int argc, char *argv[]) {
#else
int main(int argc, char *argv[]) {
#endif
yaze::util::FlagParser flag_parser(yaze::util::global_flag_registry());
RETURN_IF_EXCEPTION(flag_parser.Parse(argc, argv));
yaze::cli::ShowMain();

View File

@@ -52,7 +52,6 @@ target_include_directories(
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
${GLEW_INCLUDE_DIRS}
${PROJECT_BINARY_DIR}
)
@@ -67,8 +66,6 @@ target_link_libraries(
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${GLEW_LIBRARIES}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui

View File

@@ -1,9 +1,10 @@
#include "yaze.h"
#include <algorithm>
#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>
#include <cstring>
#include <stdexcept>
#include "app/core/controller.h"
@@ -21,7 +22,7 @@ DEFINE_FLAG(std::string, rom_file, "",
// Static variables for library state
static bool g_library_initialized = false;
int yaze_app_main(int argc, char **argv) {
int yaze_app_main(int argc, char** argv) {
yaze::util::FlagParser parser(yaze::util::global_flag_registry());
RETURN_IF_EXCEPTION(parser.Parse(argc, argv));
std::string rom_filename = "";
@@ -52,7 +53,7 @@ yaze_status yaze_library_init() {
if (g_library_initialized) {
return YAZE_OK;
}
// Initialize SDL and other subsystems if needed
g_library_initialized = true;
return YAZE_OK;
@@ -62,7 +63,7 @@ void yaze_library_shutdown() {
if (!g_library_initialized) {
return;
}
// Cleanup subsystems
g_library_initialized = false;
@@ -111,17 +112,17 @@ yaze_status yaze_init(yaze_editor_context* context, const char* rom_filename) {
if (context == nullptr) {
return YAZE_ERROR_INVALID_ARG;
}
if (!g_library_initialized) {
yaze_status init_status = yaze_library_init();
if (init_status != YAZE_OK) {
return init_status;
}
}
context->rom = nullptr;
context->error_message = nullptr;
if (rom_filename != nullptr && strlen(rom_filename) > 0) {
context->rom = yaze_load_rom(rom_filename);
if (context->rom == nullptr) {
@@ -137,12 +138,12 @@ yaze_status yaze_shutdown(yaze_editor_context* context) {
if (context == nullptr) {
return YAZE_ERROR_INVALID_ARG;
}
if (context->rom != nullptr) {
yaze_unload_rom(context->rom);
context->rom = nullptr;
}
context->error_message = nullptr;
return YAZE_OK;
}
@@ -151,7 +152,7 @@ zelda3_rom* yaze_load_rom(const char* filename) {
if (filename == nullptr || strlen(filename) == 0) {
return nullptr;
}
auto internal_rom = std::make_unique<yaze::Rom>();
if (!internal_rom->LoadFromFile(filename).ok()) {
return nullptr;
@@ -171,7 +172,7 @@ void yaze_unload_rom(zelda3_rom* rom) {
if (rom == nullptr) {
return;
}
if (rom->impl != nullptr) {
delete static_cast<yaze::Rom*>(rom->impl);
rom->impl = nullptr;
@@ -184,27 +185,24 @@ int yaze_save_rom(zelda3_rom* rom, const char* filename) {
if (rom == nullptr || filename == nullptr) {
return YAZE_ERROR_INVALID_ARG;
}
if (rom->impl == nullptr) {
return YAZE_ERROR_NOT_INITIALIZED;
}
auto* internal_rom = static_cast<yaze::Rom*>(rom->impl);
auto status = internal_rom->SaveToFile(yaze::Rom::SaveSettings{
.backup = true,
.save_new = false,
.filename = filename
});
.backup = true, .save_new = false, .filename = filename});
if (!status.ok()) {
return YAZE_ERROR_IO;
}
rom->is_modified = false;
return YAZE_OK;
}
yaze_bitmap yaze_load_bitmap(const char *filename) {
yaze_bitmap yaze_load_bitmap(const char* filename) {
yaze_bitmap bitmap;
bitmap.width = 0;
bitmap.height = 0;
@@ -213,7 +211,7 @@ yaze_bitmap yaze_load_bitmap(const char *filename) {
return bitmap;
}
snes_color yaze_get_color_from_paletteset(const zelda3_rom *rom,
snes_color yaze_get_color_from_paletteset(const zelda3_rom* rom,
int palette_set, int palette,
int color) {
snes_color color_struct;
@@ -222,7 +220,7 @@ snes_color yaze_get_color_from_paletteset(const zelda3_rom *rom,
color_struct.blue = 0;
if (rom->impl) {
yaze::Rom *internal_rom = static_cast<yaze::Rom *>(rom->impl);
yaze::Rom* internal_rom = static_cast<yaze::Rom*>(rom->impl);
auto get_color =
internal_rom->palette_group()
.get_group(yaze::gfx::kPaletteGroupAddressesKeys[palette_set])
@@ -235,21 +233,21 @@ snes_color yaze_get_color_from_paletteset(const zelda3_rom *rom,
return color_struct;
}
zelda3_overworld *yaze_load_overworld(const zelda3_rom *rom) {
zelda3_overworld* yaze_load_overworld(const zelda3_rom* rom) {
if (rom->impl == nullptr) {
return nullptr;
}
yaze::Rom *internal_rom = static_cast<yaze::Rom *>(rom->impl);
yaze::Rom* internal_rom = static_cast<yaze::Rom*>(rom->impl);
auto internal_overworld = new yaze::zelda3::Overworld(internal_rom);
if (!internal_overworld->Load(internal_rom).ok()) {
return nullptr;
}
zelda3_overworld *overworld = new zelda3_overworld();
zelda3_overworld* overworld = new zelda3_overworld();
overworld->impl = internal_overworld;
int map_id = 0;
for (const auto &ow_map : internal_overworld->overworld_maps()) {
for (const auto& ow_map : internal_overworld->overworld_maps()) {
overworld->maps[map_id] = new zelda3_overworld_map();
overworld->maps[map_id]->id = map_id;
map_id++;
@@ -257,20 +255,21 @@ zelda3_overworld *yaze_load_overworld(const zelda3_rom *rom) {
return overworld;
}
zelda3_dungeon_room *yaze_load_all_rooms(const zelda3_rom *rom) {
zelda3_dungeon_room* yaze_load_all_rooms(const zelda3_rom* rom) {
if (rom->impl == nullptr) {
return nullptr;
}
yaze::Rom *internal_rom = static_cast<yaze::Rom *>(rom->impl);
zelda3_dungeon_room *rooms = new zelda3_dungeon_room[256];
yaze::Rom* internal_rom = static_cast<yaze::Rom*>(rom->impl);
zelda3_dungeon_room* rooms = new zelda3_dungeon_room[256];
return rooms;
}
yaze_status yaze_load_messages(const zelda3_rom* rom, zelda3_message** messages, int* message_count) {
yaze_status yaze_load_messages(const zelda3_rom* rom, zelda3_message** messages,
int* message_count) {
if (rom == nullptr || messages == nullptr || message_count == nullptr) {
return YAZE_ERROR_INVALID_ARG;
}
if (rom->impl == nullptr) {
return YAZE_ERROR_NOT_INITIALIZED;
}
@@ -279,23 +278,25 @@ yaze_status yaze_load_messages(const zelda3_rom* rom, zelda3_message** messages,
// Use LoadAllTextData from message_data.h
std::vector<yaze::editor::MessageData> message_data =
yaze::editor::ReadAllTextData(rom->data, 0);
*message_count = static_cast<int>(message_data.size());
*messages = new zelda3_message[*message_count];
for (size_t i = 0; i < message_data.size(); ++i) {
const auto& msg = message_data[i];
(*messages)[i].id = msg.ID;
(*messages)[i].rom_address = msg.Address;
(*messages)[i].length = static_cast<uint16_t>(msg.RawString.length());
// Allocate and copy string data
(*messages)[i].raw_data = new uint8_t[msg.Data.size()];
std::memcpy((*messages)[i].raw_data, msg.Data.data(), msg.Data.size());
(*messages)[i].parsed_text = new char[msg.ContentsParsed.length() + 1];
std::strcpy((*messages)[i].parsed_text, msg.ContentsParsed.c_str());
// Safe string copy with bounds checking
std::memcpy((*messages)[i].parsed_text, msg.ContentsParsed.c_str(), msg.ContentsParsed.length());
(*messages)[i].parsed_text[msg.ContentsParsed.length()] = '\0';
(*messages)[i].is_compressed = false; // TODO: Detect compression
(*messages)[i].encoding_type = 0; // TODO: Detect encoding
}
@@ -321,31 +322,36 @@ void yaze_free_bitmap(yaze_bitmap* bitmap) {
yaze_bitmap yaze_create_bitmap(int width, int height, uint8_t bpp) {
yaze_bitmap bitmap = {};
if (width <= 0 || height <= 0 || (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8)) {
if (width <= 0 || height <= 0 ||
(bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8)) {
return bitmap; // Return empty bitmap on invalid args
}
bitmap.width = width;
bitmap.height = height;
bitmap.bpp = bpp;
bitmap.data = new uint8_t[width * height]();
return bitmap;
}
snes_color yaze_rgb_to_snes_color(uint8_t r, uint8_t g, uint8_t b) {
snes_color color = {};
color.red = r; // Store full 8-bit values (existing code expects this)
color.red = r; // Store full 8-bit values (existing code expects this)
color.green = g;
color.blue = b;
return color;
}
void yaze_snes_color_to_rgb(snes_color color, uint8_t* r, uint8_t* g, uint8_t* b) {
if (r != nullptr) *r = static_cast<uint8_t>(color.red);
if (g != nullptr) *g = static_cast<uint8_t>(color.green);
if (b != nullptr) *b = static_cast<uint8_t>(color.blue);
void yaze_snes_color_to_rgb(snes_color color, uint8_t* r, uint8_t* g,
uint8_t* b) {
if (r != nullptr)
*r = static_cast<uint8_t>(color.red);
if (g != nullptr)
*g = static_cast<uint8_t>(color.green);
if (b != nullptr)
*b = static_cast<uint8_t>(color.blue);
}
// Version detection functions
@@ -353,7 +359,7 @@ zelda3_version zelda3_detect_version(const uint8_t* rom_data, size_t size) {
if (rom_data == nullptr || size < 0x100000) {
return ZELDA3_VERSION_UNKNOWN;
}
// TODO: Implement proper version detection based on ROM header
return ZELDA3_VERSION_US; // Default assumption
}
@@ -375,7 +381,8 @@ const char* zelda3_version_to_string(zelda3_version version) {
}
}
const zelda3_version_pointers* zelda3_get_version_pointers(zelda3_version version) {
const zelda3_version_pointers* zelda3_get_version_pointers(
zelda3_version version) {
switch (version) {
case ZELDA3_VERSION_US:
return &zelda3_us_pointers;