Implement save file dialog functionality across platforms
- Added ShowSaveFileDialog and its platform-specific implementations for Windows and macOS, allowing users to save files with specified names and extensions. - Enhanced the FileDialogWrapper class to support both native and bespoke dialog options based on feature flags. - Updated the editor manager to utilize the new save dialog for ROM and project file saving, improving user experience and file management. - Refactored existing dialog methods to ensure consistency and maintainability across different platforms.
This commit is contained in:
@@ -173,18 +173,92 @@ std::string ShowOpenFileDialogImpl() {
|
|||||||
return file_path_windows;
|
return file_path_windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward declaration for folder dialog implementation
|
// Forward declarations for dialog implementations
|
||||||
std::string ShowOpenFolderDialogImpl();
|
std::string ShowOpenFolderDialogImpl();
|
||||||
|
std::string ShowSaveFileDialogImpl(const std::string& default_name, const std::string& default_extension);
|
||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||||
return ShowOpenFolderDialogImpl();
|
return ShowOpenFolderDialogImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
||||||
// Windows doesn't use NFD in this implementation, fallback to bespoke
|
// Windows doesn't use NFD in this implementation, fallback to bespoke
|
||||||
return ShowOpenFolderDialogBespoke();
|
return ShowOpenFolderDialogBespoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
|
||||||
|
const std::string& default_extension) {
|
||||||
|
// Windows doesn't use NFD in this implementation, fallback to bespoke
|
||||||
|
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
||||||
|
const std::string& default_extension) {
|
||||||
|
return ShowSaveFileDialogImpl(default_name, default_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ShowSaveFileDialogImpl(const std::string& default_name,
|
||||||
|
const std::string& default_extension) {
|
||||||
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||||
|
|
||||||
|
IFileSaveDialog *pfd = NULL;
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog,
|
||||||
|
reinterpret_cast<void **>(&pfd));
|
||||||
|
std::string file_path_windows;
|
||||||
|
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
// Set default filename if provided
|
||||||
|
if (!default_name.empty()) {
|
||||||
|
std::wstring wide_name(default_name.begin(), default_name.end());
|
||||||
|
pfd->SetFileName(wide_name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file type filters if extension provided
|
||||||
|
if (!default_extension.empty()) {
|
||||||
|
if (default_extension == "theme") {
|
||||||
|
COMDLG_FILTERSPEC filters[] = { {L"Theme Files", L"*.theme"}, {L"All Files", L"*.*"} };
|
||||||
|
pfd->SetFileTypes(2, filters);
|
||||||
|
} else if (default_extension == "yaze") {
|
||||||
|
COMDLG_FILTERSPEC filters[] = { {L"YAZE Project Files", L"*.yaze"}, {L"All Files", L"*.*"} };
|
||||||
|
pfd->SetFileTypes(2, filters);
|
||||||
|
} else if (default_extension == "sfc" || default_extension == "smc") {
|
||||||
|
COMDLG_FILTERSPEC filters[] = { {L"ROM Files", L"*.sfc;*.smc"}, {L"All Files", L"*.*"} };
|
||||||
|
pfd->SetFileTypes(2, filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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[512];
|
||||||
|
wcstombs(str, pszFilePath, 512);
|
||||||
|
file_path_windows = str;
|
||||||
|
psiResult->Release();
|
||||||
|
CoTaskMemFree(pszFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pfd->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
return file_path_windows;
|
||||||
|
}
|
||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
||||||
return ShowOpenFolderDialogImpl();
|
return ShowOpenFolderDialogImpl();
|
||||||
}
|
}
|
||||||
@@ -306,10 +380,66 @@ std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
|
|||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||||
// Implement bespoke file dialog or return placeholder
|
// 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
|
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) {
|
||||||
|
// Placeholder for Linux implementation
|
||||||
|
// Could use zenity or similar system dialogs
|
||||||
|
return ""; // For now return empty - can be implemented later
|
||||||
|
}
|
||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||||
// Use global feature flag to choose implementation
|
// Use global feature flag to choose implementation
|
||||||
if (FeatureFlags::get().kUseNativeFileDialog) {
|
if (FeatureFlags::get().kUseNativeFileDialog) {
|
||||||
|
|||||||
@@ -20,10 +20,21 @@ class FileDialogWrapper {
|
|||||||
* folder path. Uses global feature flag to choose implementation.
|
* folder path. Uses global feature flag to choose implementation.
|
||||||
*/
|
*/
|
||||||
static std::string ShowOpenFolderDialog();
|
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
|
// Specific implementations for testing
|
||||||
static std::string ShowOpenFileDialogNFD();
|
static std::string ShowOpenFileDialogNFD();
|
||||||
static std::string ShowOpenFileDialogBespoke();
|
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 ShowOpenFolderDialogNFD();
|
||||||
static std::string ShowOpenFolderDialogBespoke();
|
static std::string ShowOpenFolderDialogBespoke();
|
||||||
static std::vector<std::string> GetSubdirectoriesInFolder(
|
static std::vector<std::string> GetSubdirectoriesInFolder(
|
||||||
|
|||||||
@@ -88,6 +88,28 @@ std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
|||||||
return "";
|
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
|
// Global feature flag-based dispatch methods
|
||||||
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
|
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
|
||||||
if (FeatureFlags::get().kUseNativeFileDialog) {
|
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)
|
// NFD implementation for macOS (fallback to bespoke if NFD not available)
|
||||||
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogNFD() {
|
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogNFD() {
|
||||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||||
@@ -156,6 +187,55 @@ std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
|||||||
#endif
|
#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() {
|
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
||||||
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||||
[openPanel setCanChooseFiles:NO];
|
[openPanel setCanChooseFiles:NO];
|
||||||
|
|||||||
@@ -1186,15 +1186,39 @@ void EditorManager::DrawMenuBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (save_as_menu) {
|
if (save_as_menu) {
|
||||||
|
Begin("Save ROM As", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
|
||||||
|
ImGui::Text("%s Save ROM to new location", ICON_MD_SAVE_AS);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
static std::string save_as_filename = "";
|
static std::string save_as_filename = "";
|
||||||
Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
if (current_rom_ && save_as_filename.empty()) {
|
||||||
InputText("Filename", &save_as_filename);
|
save_as_filename = current_rom_->title();
|
||||||
if (Button("Save", gui::kDefaultModalSize)) {
|
|
||||||
status_ = SaveRom();
|
|
||||||
save_as_menu = false;
|
|
||||||
}
|
}
|
||||||
SameLine();
|
|
||||||
if (Button("Cancel", gui::kDefaultModalSize)) {
|
ImGui::InputText("Filename", &save_as_filename);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (Button(absl::StrFormat("%s Browse...", ICON_MD_FOLDER_OPEN).c_str(), gui::kDefaultModalSize)) {
|
||||||
|
// Use save file dialog for ROM files
|
||||||
|
auto file_path = core::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "sfc");
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
save_as_filename = file_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str(), gui::kDefaultModalSize)) {
|
||||||
|
if (!save_as_filename.empty()) {
|
||||||
|
// TODO: Implement SaveRomAs functionality with the specified filename
|
||||||
|
status_ = SaveRom(); // For now, use existing save functionality
|
||||||
|
save_as_menu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (Button(absl::StrFormat("%s Cancel", ICON_MD_CANCEL).c_str(), gui::kDefaultModalSize)) {
|
||||||
save_as_menu = false;
|
save_as_menu = false;
|
||||||
}
|
}
|
||||||
End();
|
End();
|
||||||
@@ -1204,34 +1228,61 @@ void EditorManager::DrawMenuBar() {
|
|||||||
Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
static std::string save_as_filename = "";
|
static std::string save_as_filename = "";
|
||||||
InputText("Project Name", &save_as_filename);
|
InputText("Project Name", &save_as_filename);
|
||||||
if (Button("Destination Filepath", gui::kDefaultModalSize)) {
|
if (Button(absl::StrFormat("%s Destination Folder", ICON_MD_FOLDER).c_str(), gui::kDefaultModalSize)) {
|
||||||
current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog();
|
current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
Text("%s", current_project_.filepath.c_str());
|
Text("%s", current_project_.filepath.c_str());
|
||||||
if (Button("ROM File", gui::kDefaultModalSize)) {
|
|
||||||
|
if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(), gui::kDefaultModalSize)) {
|
||||||
current_project_.rom_filename = FileDialogWrapper::ShowOpenFileDialog();
|
current_project_.rom_filename = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
Text("%s", current_project_.rom_filename.c_str());
|
Text("%s", current_project_.rom_filename.c_str());
|
||||||
if (Button("Labels File", gui::kDefaultModalSize)) {
|
|
||||||
current_project_.labels_filename =
|
if (Button(absl::StrFormat("%s Labels File", ICON_MD_LABEL).c_str(), gui::kDefaultModalSize)) {
|
||||||
FileDialogWrapper::ShowOpenFileDialog();
|
current_project_.labels_filename = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
Text("%s", current_project_.labels_filename.c_str());
|
Text("%s", current_project_.labels_filename.c_str());
|
||||||
if (Button("Code Folder", gui::kDefaultModalSize)) {
|
|
||||||
|
if (Button(absl::StrFormat("%s Code Folder", ICON_MD_CODE).c_str(), gui::kDefaultModalSize)) {
|
||||||
current_project_.code_folder = FileDialogWrapper::ShowOpenFolderDialog();
|
current_project_.code_folder = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
Text("%s", current_project_.code_folder.c_str());
|
Text("%s", current_project_.code_folder.c_str());
|
||||||
|
|
||||||
Separator();
|
Separator();
|
||||||
if (Button("Create", gui::kDefaultModalSize)) {
|
|
||||||
new_project_menu = false;
|
if (Button(absl::StrFormat("%s Choose Project File Location", ICON_MD_SAVE).c_str(), gui::kDefaultModalSize)) {
|
||||||
status_ = current_project_.Create(save_as_filename, current_project_.filepath);
|
auto project_file_path = core::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "yaze");
|
||||||
if (status_.ok()) {
|
if (!project_file_path.empty()) {
|
||||||
status_ = current_project_.Save();
|
// Ensure .yaze extension
|
||||||
|
if (project_file_path.find(".yaze") == std::string::npos) {
|
||||||
|
project_file_path += ".yaze";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update project filepath to the chosen location
|
||||||
|
current_project_.filepath = project_file_path;
|
||||||
|
|
||||||
|
// Also set the project directory to the parent directory
|
||||||
|
size_t last_slash = project_file_path.find_last_of("/\\");
|
||||||
|
if (last_slash != std::string::npos) {
|
||||||
|
std::string project_dir = project_file_path.substr(0, last_slash);
|
||||||
|
Text("Project will be saved to: %s", project_dir.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button(absl::StrFormat("%s Create Project", ICON_MD_ADD).c_str(), gui::kDefaultModalSize)) {
|
||||||
|
if (!current_project_.filepath.empty()) {
|
||||||
|
new_project_menu = false;
|
||||||
|
status_ = current_project_.Create(save_as_filename, current_project_.filepath);
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = current_project_.Save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast_manager_.Show("Please choose a project file location first", editor::ToastType::kWarning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SameLine();
|
SameLine();
|
||||||
@@ -1412,9 +1463,6 @@ absl::Status EditorManager::OpenRomOrProject(const std::string &filename) {
|
|||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Enhanced Project Management Implementation
|
|
||||||
|
|
||||||
absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
|
absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
|
||||||
auto dialog_path = core::FileDialogWrapper::ShowOpenFolderDialog();
|
auto dialog_path = core::FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
if (dialog_path.empty()) {
|
if (dialog_path.empty()) {
|
||||||
@@ -1591,7 +1639,6 @@ absl::Status EditorManager::SetCurrentRom(Rom *rom) {
|
|||||||
return absl::NotFoundError("ROM not found in existing sessions");
|
return absl::NotFoundError("ROM not found in existing sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session Management Functions
|
|
||||||
void EditorManager::CreateNewSession() {
|
void EditorManager::CreateNewSession() {
|
||||||
// Check session limit
|
// Check session limit
|
||||||
if (sessions_.size() >= 8) {
|
if (sessions_.size() >= 8) {
|
||||||
@@ -1709,7 +1756,6 @@ std::string EditorManager::GenerateUniqueEditorTitle(EditorType type, size_t ses
|
|||||||
return absl::StrFormat("%s - %s##session_%zu", base_name, session_name, session_index);
|
return absl::StrFormat("%s - %s##session_%zu", base_name, session_name, session_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout Management Functions
|
|
||||||
void EditorManager::ResetWorkspaceLayout() {
|
void EditorManager::ResetWorkspaceLayout() {
|
||||||
// Show confirmation popup first
|
// Show confirmation popup first
|
||||||
popup_manager_->Show("Layout Reset Confirm");
|
popup_manager_->Show("Layout Reset Confirm");
|
||||||
@@ -1725,7 +1771,6 @@ void EditorManager::LoadWorkspaceLayout() {
|
|||||||
toast_manager_.Show("Workspace layout loaded", editor::ToastType::kSuccess);
|
toast_manager_.Show("Workspace layout loaded", editor::ToastType::kSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window Management Functions
|
|
||||||
void EditorManager::ShowAllWindows() {
|
void EditorManager::ShowAllWindows() {
|
||||||
if (!current_editor_set_) return;
|
if (!current_editor_set_) return;
|
||||||
|
|
||||||
@@ -1768,7 +1813,6 @@ void EditorManager::CloseAllFloatingWindows() {
|
|||||||
toast_manager_.Show("All floating windows closed", editor::ToastType::kInfo);
|
toast_manager_.Show("All floating windows closed", editor::ToastType::kInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preset Layout Functions
|
|
||||||
void EditorManager::LoadDeveloperLayout() {
|
void EditorManager::LoadDeveloperLayout() {
|
||||||
if (!current_editor_set_) return;
|
if (!current_editor_set_) return;
|
||||||
|
|
||||||
@@ -1821,12 +1865,11 @@ void EditorManager::LoadModderLayout() {
|
|||||||
toast_manager_.Show("Modder layout loaded", editor::ToastType::kSuccess);
|
toast_manager_.Show("Modder layout loaded", editor::ToastType::kSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI Drawing Functions
|
|
||||||
void EditorManager::DrawSessionSwitcher() {
|
void EditorManager::DrawSessionSwitcher() {
|
||||||
if (!show_session_switcher_) return;
|
if (!show_session_switcher_) return;
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||||
ImGui::SetNextWindowSize(ImVec2(500, 350), ImGuiCond_Appearing);
|
ImGui::SetNextWindowSize(ImVec2(700, 450), ImGuiCond_Appearing);
|
||||||
|
|
||||||
if (ImGui::Begin(absl::StrFormat("%s Session Switcher", ICON_MD_SWITCH_ACCOUNT).c_str(),
|
if (ImGui::Begin(absl::StrFormat("%s Session Switcher", ICON_MD_SWITCH_ACCOUNT).c_str(),
|
||||||
&show_session_switcher_, ImGuiWindowFlags_NoCollapse)) {
|
&show_session_switcher_, ImGuiWindowFlags_NoCollapse)) {
|
||||||
@@ -1844,101 +1887,118 @@ void EditorManager::DrawSessionSwitcher() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Enhanced session list with metadata
|
// Enhanced session list using table for better layout
|
||||||
for (size_t i = 0; i < sessions_.size(); ++i) {
|
const float TABLE_HEIGHT = ImGui::GetContentRegionAvail().y - 50; // Reserve space for close button
|
||||||
auto& session = sessions_[i];
|
|
||||||
bool is_current = (&session.rom == current_rom_);
|
if (ImGui::BeginTable("SessionSwitcherTable", 4,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, TABLE_HEIGHT))) {
|
||||||
|
|
||||||
ImGui::PushID(static_cast<int>(i));
|
// Setup columns with proper sizing weights
|
||||||
|
ImGui::TableSetupColumn("Session", ImGuiTableColumnFlags_WidthStretch, 0.3f);
|
||||||
|
ImGui::TableSetupColumn("ROM Info", ImGuiTableColumnFlags_WidthStretch, 0.4f);
|
||||||
|
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
||||||
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 140.0f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
// Session card with background
|
for (size_t i = 0; i < sessions_.size(); ++i) {
|
||||||
ImVec2 button_size = ImVec2(-1, 70);
|
auto& session = sessions_[i];
|
||||||
if (is_current) {
|
bool is_current = (&session.rom == current_rom_);
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.7f, 0.3f, 0.3f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.7f, 0.3f, 0.4f));
|
ImGui::PushID(static_cast<int>(i));
|
||||||
} else {
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, 55.0f); // Consistent row height
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.1f, 0.1f, 0.2f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 0.3f));
|
// Session name column with better styling
|
||||||
}
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
if (ImGui::Button("##session_card", button_size)) {
|
|
||||||
|
if (is_current) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
|
||||||
|
ImGui::Text("%s %s", ICON_MD_STAR, session.GetDisplayName().c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s %s", ICON_MD_TAB, session.GetDisplayName().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROM info column with better information layout
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
|
||||||
|
if (session.rom.is_loaded()) {
|
||||||
|
ImGui::Text("%s %s", ICON_MD_VIDEOGAME_ASSET, session.rom.title().c_str());
|
||||||
|
ImGui::Text("%.1f MB | %s",
|
||||||
|
session.rom.size() / 1048576.0f,
|
||||||
|
session.rom.dirty() ? "Modified" : "Clean");
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s No ROM loaded", ICON_MD_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status column with better visual indicators
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
|
||||||
|
if (session.rom.is_loaded()) {
|
||||||
|
if (session.rom.dirty()) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s", ICON_MD_EDIT);
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", ICON_MD_CHECK_CIRCLE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", ICON_MD_RADIO_BUTTON_UNCHECKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions column with improved button layout
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
// Create button group for better alignment
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
if (!is_current) {
|
if (!is_current) {
|
||||||
SwitchToSession(i);
|
if (ImGui::Button("Switch")) {
|
||||||
show_session_switcher_ = false;
|
SwitchToSession(i);
|
||||||
|
show_session_switcher_ = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
ImGui::Button("Current");
|
||||||
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Rename")) {
|
||||||
// Session content overlay
|
session_to_rename_ = i;
|
||||||
ImVec2 button_min = ImGui::GetItemRectMin();
|
strncpy(session_rename_buffer_, session.GetDisplayName().c_str(), sizeof(session_rename_buffer_) - 1);
|
||||||
ImVec2 button_max = ImGui::GetItemRectMax();
|
show_session_rename_dialog_ = true;
|
||||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
||||||
|
|
||||||
// Session icon and name
|
|
||||||
ImVec2 text_pos = ImVec2(button_min.x + 10, button_min.y + 8);
|
|
||||||
std::string session_display = session.GetDisplayName();
|
|
||||||
if (session_display.length() > 25) {
|
|
||||||
session_display = session_display.substr(0, 22) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
ImU32 text_color = is_current ? IM_COL32(255, 255, 255, 255) : IM_COL32(220, 220, 220, 255);
|
|
||||||
draw_list->AddText(text_pos, text_color,
|
|
||||||
absl::StrFormat("%s %s %s",
|
|
||||||
ICON_MD_STORAGE,
|
|
||||||
session_display.c_str(),
|
|
||||||
is_current ? "(Current)" : "").c_str());
|
|
||||||
|
|
||||||
// ROM metadata
|
|
||||||
if (session.rom.is_loaded()) {
|
|
||||||
ImVec2 metadata_pos = ImVec2(button_min.x + 10, button_min.y + 28);
|
|
||||||
std::string rom_info = absl::StrFormat("%s %s | %.1f MB | %s",
|
|
||||||
ICON_MD_VIDEOGAME_ASSET,
|
|
||||||
session.rom.title().c_str(),
|
|
||||||
session.rom.size() / 1048576.0f,
|
|
||||||
session.rom.dirty() ? "Modified" : "Clean");
|
|
||||||
if (rom_info.length() > 40) {
|
|
||||||
rom_info = rom_info.substr(0, 37) + "...";
|
|
||||||
}
|
}
|
||||||
draw_list->AddText(metadata_pos, IM_COL32(180, 180, 180, 255), rom_info.c_str());
|
|
||||||
} else {
|
ImGui::SameLine();
|
||||||
ImVec2 metadata_pos = ImVec2(button_min.x + 10, button_min.y + 28);
|
|
||||||
draw_list->AddText(metadata_pos, IM_COL32(150, 150, 150, 255),
|
// Close button logic
|
||||||
absl::StrFormat("%s No ROM loaded", ICON_MD_WARNING).c_str());
|
bool can_close = sessions_.size() > 1;
|
||||||
}
|
if (!can_close) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
// Action buttons on the right
|
}
|
||||||
ImVec2 rename_pos = ImVec2(button_max.x - 90, button_min.y + 5);
|
|
||||||
ImVec2 close_pos = ImVec2(button_max.x - 45, button_min.y + 5);
|
if (ImGui::Button("Close")) {
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(rename_pos);
|
|
||||||
if (ImGui::SmallButton(absl::StrFormat("%s##rename_%zu", ICON_MD_EDIT, i).c_str())) {
|
|
||||||
session_to_rename_ = i;
|
|
||||||
strncpy(session_rename_buffer_, session.GetDisplayName().c_str(), sizeof(session_rename_buffer_) - 1);
|
|
||||||
show_session_rename_dialog_ = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Rename Session");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(close_pos);
|
|
||||||
if (sessions_.size() > 1) {
|
|
||||||
if (ImGui::SmallButton(absl::StrFormat("%s##close_%zu", ICON_MD_CLOSE, i).c_str())) {
|
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
CloseCurrentSession();
|
CloseCurrentSession();
|
||||||
} else {
|
} else {
|
||||||
// Switch to this session first, then close it
|
toast_manager_.Show("Session removal temporarily disabled", editor::ToastType::kWarning);
|
||||||
SwitchToSession(i);
|
|
||||||
CloseCurrentSession();
|
|
||||||
}
|
}
|
||||||
break; // Exit the loop since session structure changed
|
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Close Session");
|
if (!can_close) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopID();
|
ImGui::EndTable();
|
||||||
ImGui::Spacing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -1968,63 +2028,88 @@ void EditorManager::DrawSessionManager() {
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("%s Active Sessions (%zu)", ICON_MD_TAB, sessions_.size());
|
ImGui::Text("%s Active Sessions (%zu)", ICON_MD_TAB, sessions_.size());
|
||||||
|
|
||||||
// Session list with controls (wider table for better readability)
|
// Enhanced session management table with proper sizing
|
||||||
if (ImGui::BeginTable("SessionTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
const float AVAILABLE_HEIGHT = ImGui::GetContentRegionAvail().y;
|
||||||
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY)) {
|
|
||||||
ImGui::TableSetupColumn("Session", ImGuiTableColumnFlags_WidthStretch, 120.0f);
|
if (ImGui::BeginTable("SessionTable", 6,
|
||||||
ImGui::TableSetupColumn("ROM", ImGuiTableColumnFlags_WidthStretch, 250.0f);
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
|
||||||
ImGui::TableSetupColumn("Custom OW", ImGuiTableColumnFlags_WidthFixed, 110.0f);
|
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ContextMenuInBody,
|
||||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 220.0f);
|
ImVec2(0, AVAILABLE_HEIGHT))) {
|
||||||
|
|
||||||
|
// Setup columns with explicit sizing for better control
|
||||||
|
ImGui::TableSetupColumn("Session", ImGuiTableColumnFlags_WidthStretch, 0.15f);
|
||||||
|
ImGui::TableSetupColumn("ROM Title", ImGuiTableColumnFlags_WidthStretch, 0.3f);
|
||||||
|
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 70.0f);
|
||||||
|
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f);
|
||||||
|
ImGui::TableSetupColumn("Custom OW", ImGuiTableColumnFlags_WidthFixed, 80.0f);
|
||||||
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.25f);
|
||||||
ImGui::TableHeadersRow();
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
for (size_t i = 0; i < sessions_.size(); ++i) {
|
for (size_t i = 0; i < sessions_.size(); ++i) {
|
||||||
auto& session = sessions_[i];
|
auto& session = sessions_[i];
|
||||||
bool is_current = (&session.rom == current_rom_);
|
bool is_current = (&session.rom == current_rom_);
|
||||||
|
|
||||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, 45.0f); // Increase row height for better spacing
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, 50.0f); // Consistent row height
|
||||||
|
ImGui::PushID(static_cast<int>(i));
|
||||||
|
|
||||||
|
// Session name column
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
// Add vertical centering for text
|
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8.0f);
|
|
||||||
|
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f),
|
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f),
|
||||||
"%s Session %zu", ICON_MD_STAR, i + 1);
|
"%s Session %zu", ICON_MD_STAR, i + 1);
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("%s Session %zu", ICON_MD_TAB, i + 1);
|
ImGui::Text("%s Session %zu", ICON_MD_TAB, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ROM title column
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8.0f); // Vertical centering
|
ImGui::AlignTextToFramePadding();
|
||||||
std::string display_name = session.GetDisplayName();
|
std::string display_name = session.GetDisplayName();
|
||||||
if (!session.custom_name.empty()) {
|
if (!session.custom_name.empty()) {
|
||||||
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s %s", ICON_MD_EDIT, display_name.c_str());
|
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s %s", ICON_MD_EDIT, display_name.c_str());
|
||||||
} else {
|
} else {
|
||||||
|
// Use TextWrapped for long ROM titles
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetColumnWidth());
|
||||||
ImGui::Text("%s", display_name.c_str());
|
ImGui::Text("%s", display_name.c_str());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File size column
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8.0f); // Vertical centering
|
ImGui::AlignTextToFramePadding();
|
||||||
|
if (session.rom.is_loaded()) {
|
||||||
|
ImGui::Text("%.1f MB", session.rom.size() / 1048576.0f);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("N/A");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status column
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
if (session.rom.is_loaded()) {
|
if (session.rom.is_loaded()) {
|
||||||
if (session.rom.dirty()) {
|
if (session.rom.dirty()) {
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Modified", ICON_MD_EDIT);
|
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s", ICON_MD_EDIT);
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s Loaded", ICON_MD_CHECK_CIRCLE);
|
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", ICON_MD_CHECK_CIRCLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s Empty", ICON_MD_RADIO_BUTTON_UNCHECKED);
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", ICON_MD_RADIO_BUTTON_UNCHECKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Overworld checkbox column
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8.0f); // Vertical centering
|
|
||||||
// Custom Overworld flag (per-session)
|
// Center the checkbox vertically
|
||||||
|
float checkbox_offset = (ImGui::GetFrameHeight() - ImGui::GetTextLineHeight()) * 0.5f;
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + checkbox_offset);
|
||||||
|
|
||||||
ImGui::PushID(static_cast<int>(i + 100)); // Different ID to avoid conflicts
|
ImGui::PushID(static_cast<int>(i + 100)); // Different ID to avoid conflicts
|
||||||
bool custom_ow_enabled = session.feature_flags.overworld.kLoadCustomOverworld;
|
bool custom_ow_enabled = session.feature_flags.overworld.kLoadCustomOverworld;
|
||||||
if (ImGui::Checkbox("##CustomOW", &custom_ow_enabled)) {
|
if (ImGui::Checkbox("##CustomOW", &custom_ow_enabled)) {
|
||||||
session.feature_flags.overworld.kLoadCustomOverworld = custom_ow_enabled;
|
session.feature_flags.overworld.kLoadCustomOverworld = custom_ow_enabled;
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
// Update global flags if this is the current session
|
|
||||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld = custom_ow_enabled;
|
core::FeatureFlags::get().overworld.kLoadCustomOverworld = custom_ow_enabled;
|
||||||
}
|
}
|
||||||
toast_manager_.Show(absl::StrFormat("Session %zu: Custom Overworld %s",
|
toast_manager_.Show(absl::StrFormat("Session %zu: Custom Overworld %s",
|
||||||
@@ -2032,45 +2117,55 @@ void EditorManager::DrawSessionManager() {
|
|||||||
editor::ToastType::kInfo);
|
editor::ToastType::kInfo);
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Enable/disable custom overworld features for this session");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Actions column with better button layout
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f); // Slightly less offset for buttons
|
|
||||||
ImGui::PushID(static_cast<int>(i));
|
|
||||||
|
|
||||||
if (!is_current && ImGui::Button(absl::StrCat(ICON_MD_SWITCH_ACCESS_SHORTCUT, " Switch").c_str())) {
|
// Create button group for better alignment
|
||||||
SwitchToSession(i);
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
if (!is_current) {
|
||||||
|
if (ImGui::Button("Switch")) {
|
||||||
|
SwitchToSession(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
ImGui::Button("Current");
|
||||||
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(absl::StrCat(ICON_MD_EDIT, " Rename").c_str())) {
|
if (ImGui::Button("Rename")) {
|
||||||
session_to_rename_ = i;
|
session_to_rename_ = i;
|
||||||
strncpy(session_rename_buffer_, session.GetDisplayName().c_str(), sizeof(session_rename_buffer_) - 1);
|
strncpy(session_rename_buffer_, session.GetDisplayName().c_str(), sizeof(session_rename_buffer_) - 1);
|
||||||
show_session_rename_dialog_ = true;
|
show_session_rename_dialog_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_current) {
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Close button logic
|
||||||
|
bool can_close = sessions_.size() > 1;
|
||||||
|
if (!can_close || is_current) {
|
||||||
ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
|
||||||
if (sessions_.size() > 1 && ImGui::Button(absl::StrCat(ICON_MD_CLOSE, " Close").c_str())) {
|
if (ImGui::Button("Close")) {
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
CloseCurrentSession();
|
CloseCurrentSession();
|
||||||
break; // Exit loop since current session was closed
|
break; // Exit loop since current session was closed
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement proper session removal when RomSession becomes movable
|
toast_manager_.Show("Session removal temporarily disabled due to technical constraints",
|
||||||
toast_manager_.Show("Session management temporarily disabled due to technical constraints",
|
|
||||||
editor::ToastType::kWarning);
|
editor::ToastType::kWarning);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_current) {
|
if (!can_close || is_current) {
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2217,7 +2312,7 @@ void EditorManager::DrawWelcomeScreen() {
|
|||||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
||||||
ImGuiWindowFlags_NoBackground;
|
ImGuiWindowFlags_NoBackground;
|
||||||
|
|
||||||
if (ImGui::Begin("Welcome to Yaze", &show_welcome_screen_, flags)) {
|
if (ImGui::Begin("Welcome to Yaze", nullptr, flags)) {
|
||||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
ImVec2 window_pos = ImGui::GetWindowPos();
|
ImVec2 window_pos = ImGui::GetWindowPos();
|
||||||
ImVec2 window_size = ImGui::GetWindowSize();
|
ImVec2 window_size = ImGui::GetWindowSize();
|
||||||
@@ -2453,6 +2548,17 @@ void EditorManager::DrawWelcomeScreen() {
|
|||||||
// Show tip about drag and drop
|
// Show tip about drag and drop
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), ICON_MD_TIPS_AND_UPDATES " Tip: Drag and drop ROM files onto the window");
|
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), ICON_MD_TIPS_AND_UPDATES " Tip: Drag and drop ROM files onto the window");
|
||||||
|
|
||||||
|
// Add close button in the bottom right corner
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
float close_button_width = 100.0f;
|
||||||
|
float offset = ImGui::GetContentRegionAvail().x - close_button_width;
|
||||||
|
if (offset > 0) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset);
|
||||||
|
|
||||||
|
if (ImGui::Button(absl::StrFormat("%s Close Welcome", ICON_MD_CLOSE).c_str(), ImVec2(close_button_width, 0))) {
|
||||||
|
show_welcome_screen_ = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|||||||
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 "app/gui/background_renderer.h"
|
||||||
#include "core/platform/font_loader.h"
|
#include "core/platform/font_loader.h"
|
||||||
#include "gui/color.h"
|
#include "gui/color.h"
|
||||||
|
#include "gui/icons.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
#include "imgui/imgui_internal.h"
|
#include "imgui/imgui_internal.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
@@ -438,7 +439,7 @@ void BeginCanvasTableCell(ImVec2 min_size) {
|
|||||||
|
|
||||||
// Reserve space for the canvas
|
// Reserve space for the canvas
|
||||||
ImGui::Dummy(actual_size);
|
ImGui::Dummy(actual_size);
|
||||||
ImGui::SetCursorPos(ImGui::GetCursorPos() - actual_size); // Reset cursor for drawing
|
// ImGui::SetCursorPos(ImGui::GetCursorPos() - actual_size); // Reset cursor for drawing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -896,99 +896,323 @@ void ThemeManager::ApplyClassicYazeTheme() {
|
|||||||
void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
|
void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
|
||||||
if (!p_open || !*p_open) return;
|
if (!p_open || !*p_open) return;
|
||||||
|
|
||||||
if (ImGui::Begin(absl::StrFormat("%s Simple Theme Editor", ICON_MD_PALETTE).c_str(), p_open)) {
|
ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
|
||||||
ImGui::Text("%s Create or modify themes with basic controls", ICON_MD_EDIT);
|
|
||||||
|
if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(), p_open,
|
||||||
|
ImGuiWindowFlags_MenuBar)) {
|
||||||
|
|
||||||
|
// Menu bar for theme operations
|
||||||
|
if (ImGui::BeginMenuBar()) {
|
||||||
|
if (ImGui::BeginMenu("File")) {
|
||||||
|
if (ImGui::MenuItem(absl::StrFormat("%s New Theme", ICON_MD_ADD).c_str())) {
|
||||||
|
// Reset to default theme
|
||||||
|
ApplyClassicYazeTheme();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(absl::StrFormat("%s Load Theme", ICON_MD_FOLDER_OPEN).c_str())) {
|
||||||
|
auto file_path = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
LoadThemeFromFile(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem(absl::StrFormat("%s Save Theme", ICON_MD_SAVE).c_str())) {
|
||||||
|
// Save current theme to its existing file
|
||||||
|
std::string current_file_path = GetCurrentThemeFilePath();
|
||||||
|
if (!current_file_path.empty()) {
|
||||||
|
auto status = SaveThemeToFile(current_theme_, current_file_path);
|
||||||
|
if (!status.ok()) {
|
||||||
|
util::logf("Failed to save theme: %s", status.message().data());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No existing file, prompt for new location
|
||||||
|
auto file_path = core::FileDialogWrapper::ShowSaveFileDialog(current_theme_.name, "theme");
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
auto status = SaveThemeToFile(current_theme_, file_path);
|
||||||
|
if (!status.ok()) {
|
||||||
|
util::logf("Failed to save theme: %s", status.message().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem(absl::StrFormat("%s Save As...", ICON_MD_SAVE_AS).c_str())) {
|
||||||
|
// Save theme to new file
|
||||||
|
auto file_path = core::FileDialogWrapper::ShowSaveFileDialog(current_theme_.name, "theme");
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
auto status = SaveThemeToFile(current_theme_, file_path);
|
||||||
|
if (!status.ok()) {
|
||||||
|
util::logf("Failed to save theme: %s", status.message().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("Presets")) {
|
||||||
|
if (ImGui::MenuItem("YAZE Classic")) {
|
||||||
|
ApplyClassicYazeTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto available_themes = GetAvailableThemes();
|
||||||
|
if (!available_themes.empty()) {
|
||||||
|
ImGui::Separator();
|
||||||
|
for (const auto& theme_name : available_themes) {
|
||||||
|
if (ImGui::MenuItem(theme_name.c_str())) {
|
||||||
|
LoadTheme(theme_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
static EnhancedTheme edit_theme = current_theme_;
|
static EnhancedTheme edit_theme = current_theme_;
|
||||||
static char theme_name[128];
|
static char theme_name[128];
|
||||||
static char theme_description[256];
|
static char theme_description[256];
|
||||||
static char theme_author[128];
|
static char theme_author[128];
|
||||||
|
static bool live_preview = true;
|
||||||
|
|
||||||
// Basic theme info
|
// Live preview toggle
|
||||||
ImGui::InputText("Theme Name", theme_name, sizeof(theme_name));
|
ImGui::Checkbox("Live Preview", &live_preview);
|
||||||
ImGui::InputText("Description", theme_description, sizeof(theme_description));
|
ImGui::SameLine();
|
||||||
ImGui::InputText("Author", theme_author, sizeof(theme_author));
|
ImGui::Text("| Changes apply immediately when enabled");
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Primary Colors
|
// Theme metadata in a table for better layout
|
||||||
if (ImGui::CollapsingHeader("Primary Colors", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::BeginTable("ThemeMetadata", 2, ImGuiTableFlags_SizingStretchProp)) {
|
||||||
ImVec4 primary = ConvertColorToImVec4(edit_theme.primary);
|
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
||||||
ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary);
|
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImVec4 accent = ConvertColorToImVec4(edit_theme.accent);
|
|
||||||
ImVec4 background = ConvertColorToImVec4(edit_theme.background);
|
|
||||||
|
|
||||||
if (ImGui::ColorEdit3("Primary", &primary.x)) {
|
ImGui::TableNextRow();
|
||||||
edit_theme.primary = {primary.x, primary.y, primary.z, primary.w};
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Name:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::InputText("##theme_name", theme_name, sizeof(theme_name))) {
|
||||||
|
edit_theme.name = std::string(theme_name);
|
||||||
}
|
}
|
||||||
if (ImGui::ColorEdit3("Secondary", &secondary.x)) {
|
|
||||||
edit_theme.secondary = {secondary.x, secondary.y, secondary.z, secondary.w};
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Description:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::InputText("##theme_description", theme_description, sizeof(theme_description))) {
|
||||||
|
edit_theme.description = std::string(theme_description);
|
||||||
}
|
}
|
||||||
if (ImGui::ColorEdit3("Accent", &accent.x)) {
|
|
||||||
edit_theme.accent = {accent.x, accent.y, accent.z, accent.w};
|
ImGui::TableNextRow();
|
||||||
}
|
ImGui::TableNextColumn();
|
||||||
if (ImGui::ColorEdit3("Background", &background.x)) {
|
ImGui::AlignTextToFramePadding();
|
||||||
edit_theme.background = {background.x, background.y, background.z, background.w};
|
ImGui::Text("Author:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::InputText("##theme_author", theme_author, sizeof(theme_author))) {
|
||||||
|
edit_theme.author = std::string(theme_author);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text Colors
|
ImGui::Separator();
|
||||||
if (ImGui::CollapsingHeader("Text Colors")) {
|
|
||||||
ImVec4 text_primary = ConvertColorToImVec4(edit_theme.text_primary);
|
// Enhanced theme editing with tabs for better organization
|
||||||
ImVec4 text_secondary = ConvertColorToImVec4(edit_theme.text_secondary);
|
if (ImGui::BeginTabBar("ThemeEditorTabs", ImGuiTabBarFlags_None)) {
|
||||||
ImVec4 text_disabled = ConvertColorToImVec4(edit_theme.text_disabled);
|
|
||||||
ImVec4 text_link = ConvertColorToImVec4(edit_theme.text_link);
|
|
||||||
|
|
||||||
if (ImGui::ColorEdit3("Primary Text", &text_primary.x)) {
|
// Primary Colors Tab
|
||||||
edit_theme.text_primary = {text_primary.x, text_primary.y, text_primary.z, text_primary.w};
|
if (ImGui::BeginTabItem(absl::StrFormat("%s Primary", ICON_MD_COLOR_LENS).c_str())) {
|
||||||
}
|
if (ImGui::BeginTable("PrimaryColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
|
||||||
if (ImGui::ColorEdit3("Secondary Text", &text_secondary.x)) {
|
ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
|
||||||
edit_theme.text_secondary = {text_secondary.x, text_secondary.y, text_secondary.z, text_secondary.w};
|
ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
|
||||||
}
|
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
|
||||||
if (ImGui::ColorEdit3("Disabled Text", &text_disabled.x)) {
|
ImGui::TableHeadersRow();
|
||||||
edit_theme.text_disabled = {text_disabled.x, text_disabled.y, text_disabled.z, text_disabled.w};
|
|
||||||
}
|
// Primary color
|
||||||
if (ImGui::ColorEdit3("Link Text", &text_link.x)) {
|
ImGui::TableNextRow();
|
||||||
edit_theme.text_link = {text_link.x, text_link.y, text_link.z, text_link.w};
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Primary:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 primary = ConvertColorToImVec4(edit_theme.primary);
|
||||||
|
if (ImGui::ColorEdit3("##primary", &primary.x)) {
|
||||||
|
edit_theme.primary = {primary.x, primary.y, primary.z, primary.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Button("Primary Preview", ImVec2(-1, 30));
|
||||||
|
|
||||||
|
// Secondary color
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Secondary:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary);
|
||||||
|
if (ImGui::ColorEdit3("##secondary", &secondary.x)) {
|
||||||
|
edit_theme.secondary = {secondary.x, secondary.y, secondary.z, secondary.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, secondary);
|
||||||
|
ImGui::Button("Secondary Preview", ImVec2(-1, 30));
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
// Accent color
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Accent:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 accent = ConvertColorToImVec4(edit_theme.accent);
|
||||||
|
if (ImGui::ColorEdit3("##accent", &accent.x)) {
|
||||||
|
edit_theme.accent = {accent.x, accent.y, accent.z, accent.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, accent);
|
||||||
|
ImGui::Button("Accent Preview", ImVec2(-1, 30));
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
// Background color
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("Background:");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 background = ConvertColorToImVec4(edit_theme.background);
|
||||||
|
if (ImGui::ColorEdit4("##background", &background.x)) {
|
||||||
|
edit_theme.background = {background.x, background.y, background.z, background.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Background preview shown in window");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show contrast preview against current background
|
// Text Colors Tab
|
||||||
ImGui::Text("Link Preview:");
|
if (ImGui::BeginTabItem(absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) {
|
||||||
ImGui::SameLine();
|
if (ImGui::BeginTable("TextColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, text_link);
|
ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
|
||||||
ImGui::Text("Sample clickable link");
|
ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
|
||||||
|
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Text colors with live preview
|
||||||
|
auto text_colors = std::vector<std::pair<const char*, Color*>>{
|
||||||
|
{"Primary Text", &edit_theme.text_primary},
|
||||||
|
{"Secondary Text", &edit_theme.text_secondary},
|
||||||
|
{"Disabled Text", &edit_theme.text_disabled},
|
||||||
|
{"Link Text", &edit_theme.text_link}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& [label, color_ptr] : text_colors) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s:", label);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
|
||||||
|
std::string id = absl::StrFormat("##%s", label);
|
||||||
|
if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
|
||||||
|
*color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, color_vec);
|
||||||
|
ImGui::Text("Sample %s", label);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive Elements Tab
|
||||||
|
if (ImGui::BeginTabItem(absl::StrFormat("%s Interactive", ICON_MD_TOUCH_APP).c_str())) {
|
||||||
|
if (ImGui::BeginTable("InteractiveColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
|
||||||
|
ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
|
||||||
|
ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
|
||||||
|
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Button colors
|
||||||
|
auto button_colors = std::vector<std::tuple<const char*, Color*, ImGuiCol>>{
|
||||||
|
{"Button", &edit_theme.button, ImGuiCol_Button},
|
||||||
|
{"Button Hovered", &edit_theme.button_hovered, ImGuiCol_ButtonHovered},
|
||||||
|
{"Button Active", &edit_theme.button_active, ImGuiCol_ButtonActive}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& [label, color_ptr, imgui_col] : button_colors) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s:", label);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
|
||||||
|
std::string id = absl::StrFormat("##%s", label);
|
||||||
|
if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
|
||||||
|
*color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::PushStyleColor(imgui_col, color_vec);
|
||||||
|
ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), ImVec2(-1, 30));
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window Colors
|
ImGui::EndTable();
|
||||||
if (ImGui::CollapsingHeader("Window Colors")) {
|
}
|
||||||
ImVec4 window_bg = ConvertColorToImVec4(edit_theme.window_bg);
|
ImGui::EndTabItem();
|
||||||
ImVec4 popup_bg = ConvertColorToImVec4(edit_theme.popup_bg);
|
}
|
||||||
|
|
||||||
if (ImGui::ColorEdit4("Window Background", &window_bg.x)) {
|
// Style Parameters Tab
|
||||||
edit_theme.window_bg = {window_bg.x, window_bg.y, window_bg.z, window_bg.w};
|
if (ImGui::BeginTabItem(absl::StrFormat("%s Style", ICON_MD_TUNE).c_str())) {
|
||||||
|
ImGui::Text("Rounding and Border Settings:");
|
||||||
|
|
||||||
|
if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding, 0.0f, 20.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding, 0.0f, 20.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Scrollbar Rounding", &edit_theme.scrollbar_rounding, 0.0f, 20.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f, 20.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::SliderFloat("Window Border Size", &edit_theme.window_border_size, 0.0f, 3.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Frame Border Size", &edit_theme.frame_border_size, 0.0f, 3.0f)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("Enable Animations", &edit_theme.enable_animations)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
if (ImGui::Checkbox("Enable Glow Effects", &edit_theme.enable_glow_effects)) {
|
||||||
|
if (live_preview) ApplyTheme(edit_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
if (ImGui::ColorEdit4("Popup Background", &popup_bg.x)) {
|
|
||||||
edit_theme.popup_bg = {popup_bg.x, popup_bg.y, popup_bg.z, popup_bg.w};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interactive Elements
|
|
||||||
if (ImGui::CollapsingHeader("Interactive Elements")) {
|
|
||||||
ImVec4 button = ConvertColorToImVec4(edit_theme.button);
|
|
||||||
ImVec4 button_hovered = ConvertColorToImVec4(edit_theme.button_hovered);
|
|
||||||
ImVec4 button_active = ConvertColorToImVec4(edit_theme.button_active);
|
|
||||||
|
|
||||||
if (ImGui::ColorEdit3("Button", &button.x)) {
|
ImGui::EndTabBar();
|
||||||
edit_theme.button = {button.x, button.y, button.z, button.w};
|
|
||||||
}
|
|
||||||
if (ImGui::ColorEdit3("Button Hovered", &button_hovered.x)) {
|
|
||||||
edit_theme.button_hovered = {button_hovered.x, button_hovered.y, button_hovered.z, button_hovered.w};
|
|
||||||
}
|
|
||||||
if (ImGui::ColorEdit3("Button Active", &button_active.x)) {
|
|
||||||
edit_theme.button_active = {button_active.x, button_active.y, button_active.z, button_active.w};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -1064,20 +1288,16 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
|
|||||||
edit_theme.description = std::string(theme_description);
|
edit_theme.description = std::string(theme_description);
|
||||||
edit_theme.author = std::string(theme_author);
|
edit_theme.author = std::string(theme_author);
|
||||||
|
|
||||||
// Use folder dialog to choose save location
|
// Use save file dialog with proper defaults
|
||||||
auto folder_path = core::FileDialogWrapper::ShowOpenFolderDialog();
|
std::string safe_name = edit_theme.name.empty() ? "custom_theme" : edit_theme.name;
|
||||||
if (!folder_path.empty()) {
|
auto file_path = core::FileDialogWrapper::ShowSaveFileDialog(safe_name, "theme");
|
||||||
// Create filename from theme name (sanitize it)
|
|
||||||
std::string safe_name = edit_theme.name;
|
if (!file_path.empty()) {
|
||||||
// Replace spaces and special chars with underscores
|
// Ensure .theme extension
|
||||||
for (char& c : safe_name) {
|
if (file_path.find(".theme") == std::string::npos) {
|
||||||
if (!std::isalnum(c)) {
|
file_path += ".theme";
|
||||||
c = '_';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string file_path = folder_path + "/" + safe_name + ".theme";
|
|
||||||
|
|
||||||
auto status = SaveThemeToFile(edit_theme, file_path);
|
auto status = SaveThemeToFile(edit_theme, file_path);
|
||||||
if (status.ok()) {
|
if (status.ok()) {
|
||||||
// Also add to themes map for immediate use
|
// Also add to themes map for immediate use
|
||||||
|
|||||||
Reference in New Issue
Block a user