diff --git a/code/client/cl_imgui.cpp b/code/client/cl_imgui.cpp index f3128b7..a9d8ce7 100644 --- a/code/client/cl_imgui.cpp +++ b/code/client/cl_imgui.cpp @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2022-2023 Gian 'myT' Schellenbaum +Copyright (C) 2022-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -99,6 +99,161 @@ void ToggleBooleanWithShortcut(bool& value, ImGuiKey key) } } +struct FileDialog +{ + char folder[MAX_QPATH]; + char fileName[MAX_QPATH]; + char filePath[MAX_QPATH]; + char extension[MAX_QPATH]; + const char* dialogName; + char** filePaths; + int fileCount; + int selectedFileIndex; + bool saveMode; +}; + +static FileDialog saveFile; +static FileDialog openFile; + +static bool PathHasExtension(const char* path, const char* extension) +{ + Q_assert(extension[0] == '.'); + + const int pathLength = strlen(path); + const int extLength = strlen(extension); + if(extLength >= pathLength) + { + return false; + } + + return Q_stricmpn(path + pathLength - extLength, extension, extLength) == 0; +} + +static void OpenFileDialog(FileDialog& dialog, const char* folder, const char* extension) +{ + if(ImGui::IsPopupOpen(dialog.dialogName)) + { + return; + } + + dialog.fileName[0] = '\0'; + Q_strncpyz(dialog.folder, folder, sizeof(dialog.folder)); + Q_strncpyz(dialog.extension, extension, sizeof(dialog.extension)); + dialog.filePaths = FS_ListFiles(folder, extension, &dialog.fileCount); + dialog.selectedFileIndex = -1; + ImGui::OpenPopup(dialog.dialogName); +} + +static bool DoFileDialog(FileDialog& dialog) +{ + bool success = false; + if(ImGui::BeginPopupModal(dialog.dialogName, NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + if(BeginTable("Files", 1)) + { + for(int i = 0; i < dialog.fileCount; i++) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + if(ImGui::Selectable(dialog.filePaths[i], i == dialog.selectedFileIndex, + ImGuiSelectableFlags_DontClosePopups)) + { + dialog.selectedFileIndex = i; + Q_strncpyz(dialog.fileName, dialog.filePaths[i], sizeof(dialog.fileName)); + } + } + ImGui::EndTable(); + } + + if(dialog.saveMode) + { + if(ImGui::InputText("File path", dialog.fileName, sizeof(dialog.fileName))) + { + dialog.selectedFileIndex = -1; + } + + if(ImGui::Button("Save") && + dialog.fileName[0] != '\0' && + dialog.fileName[0] != '.') + { + if(!PathHasExtension(dialog.fileName, dialog.extension)) + { + const int lastCharIndex = strlen(dialog.fileName) - 1; + if(dialog.fileName[lastCharIndex] == '.') + { + dialog.fileName[lastCharIndex] = '\0'; + } + Q_strcat(dialog.fileName, sizeof(dialog.fileName), dialog.extension); + } + Com_sprintf(dialog.filePath, sizeof(dialog.filePath), + "%s/%s", dialog.folder, dialog.fileName); + FS_FreeFileList(dialog.filePaths); + dialog.filePaths = NULL; + ImGui::CloseCurrentPopup(); + success = true; + } + } + else + { + if(ImGui::Button("Open") && + dialog.selectedFileIndex >= 0 && + dialog.selectedFileIndex < dialog.fileCount) + { + Com_sprintf(dialog.filePath, sizeof(dialog.filePath), + "%s/%s", dialog.folder, dialog.filePaths[dialog.selectedFileIndex]); + FS_FreeFileList(dialog.filePaths); + dialog.filePaths = NULL; + ImGui::CloseCurrentPopup(); + success = true; + } + } + + ImGui::SameLine(); + if(ImGui::Button("Cancel")) + { + FS_FreeFileList(dialog.filePaths); + dialog.filePaths = NULL; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + return success; +} + +void OpenSaveFileDialog(const char* folder, const char* extension) +{ + Q_assert(extension[0] == '.'); + OpenFileDialog(saveFile, folder, extension); +} + +bool SaveFileDialog() +{ + return DoFileDialog(saveFile); +} + +const char* GetSaveFileDialogPath() +{ + return saveFile.filePath; +} + +void OpenOpenFileDialog(const char* folder, const char* extension) +{ + Q_assert(extension[0] == '.'); + OpenFileDialog(openFile, folder, extension); +} + +bool OpenFileDialog() +{ + return DoFileDialog(openFile); +} + +const char* GetOpenFileDialogPath() +{ + return openFile.filePath; +} + struct MainMenuItem { GUI_MainMenu::Id menu; @@ -235,7 +390,7 @@ static void ImGUI_ApplyTheme() colors[ImGuiCol_NavHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.35f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.10f, 0.35f); const ImVec4 hover(0.49f, 0.75f, 0.75f, 0.35f); const ImVec4 active(0.49f, 1.00f, 1.00f, 0.55f); @@ -454,6 +609,10 @@ void CL_IMGUI_Init() { keyMap[K_F1 + i] = ImGuiKey_F1 + i; } + + saveFile.saveMode = true; + saveFile.dialogName = "Save file"; + openFile.dialogName = "Open file"; } void CL_IMGUI_Frame() diff --git a/code/client/cl_imgui.h b/code/client/cl_imgui.h index b61099f..9d85538 100644 --- a/code/client/cl_imgui.h +++ b/code/client/cl_imgui.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2022-2023 Gian 'myT' Schellenbaum +Copyright (C) 2022-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -36,6 +36,12 @@ void TableRow2(const char* item0, int item1); void TableRow2(const char* item0, float item1, const char* format = "%g"); bool IsShortcutPressed(ImGuiKey key); void ToggleBooleanWithShortcut(bool& value, ImGuiKey key); +void OpenSaveFileDialog(const char* folder, const char* extension); +bool SaveFileDialog(); // true when the user clicked the save button +const char* GetSaveFileDialogPath(); +void OpenOpenFileDialog(const char* folder, const char* extension); +bool OpenFileDialog(); // true when the user clicked the open button +const char* GetOpenFileDialogPath(); #define MAIN_MENU_LIST(M) \ M(Tools, "Tools") \