backend-infra-engineer: Release v0.3.1 snapshot
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
423
src/app/editor/overworld/overworld_editor_manager.cc
Normal file
423
src/app/editor/overworld/overworld_editor_manager.cc
Normal 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", ¤t_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", ¤t_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", ¤t_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
|
||||
108
src/app/editor/overworld/overworld_editor_manager.h
Normal file
108
src/app/editor/overworld/overworld_editor_manager.h
Normal 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
@@ -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 ¤t_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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
396
src/app/gui/canvas_utils.cc
Normal 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
147
src/app/gui/canvas_utils.h
Normal 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
|
||||
@@ -13,8 +13,8 @@ namespace gui {
|
||||
|
||||
struct Color {
|
||||
float red;
|
||||
float blue;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
};
|
||||
|
||||
|
||||
466
src/app/gui/enhanced_palette_editor.cc
Normal file
466
src/app/gui/enhanced_palette_editor.cc
Normal 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", ¤t_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", ¤t_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
|
||||
92
src/app/gui/enhanced_palette_editor.h
Normal file
92
src/app/gui/enhanced_palette_editor.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
4367
src/app/gui/icons.h
4367
src/app/gui/icons.h
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
@@ -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_; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ¤t,
|
||||
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 ¤t,
|
||||
} // 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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
103
src/yaze.cc
103
src/yaze.cc
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user