From ad2307a07daf8e694bb2c6ac79acba4d65d9c2a6 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Tue, 12 Aug 2025 09:24:34 +0530 Subject: [PATCH 1/7] feat: integrate the file browser into game engine with auto resize --- include/engine/file-browser.hpp | 51 ++++++++ src/engine/file-browser.cpp | 219 ++++++++++++++++++++++++++++++++ src/main.cpp | 31 +++++ 3 files changed, 301 insertions(+) create mode 100644 include/engine/file-browser.hpp create mode 100644 src/engine/file-browser.cpp diff --git a/include/engine/file-browser.hpp b/include/engine/file-browser.hpp new file mode 100644 index 0000000..55eb6e9 --- /dev/null +++ b/include/engine/file-browser.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace Engine { + +struct FileBrowserState { + std::string current_directory = fs::current_path().string(); + std::vector directory_contents; + + // File browser dimensions and ratios + float width = 400.0f; + float height = 600.0f; + float ratio_width = 0.4f; + float ratio_height = 0.6f; + + // Window state + bool main_window_resized = false; + int main_window_width = 1000; + int main_window_height = 800; +}; + +class FileBrowser { +public: + FileBrowser() = default; + ~FileBrowser() = default; + + void Initialize(); + void Render(FileBrowserState& state); + void UpdateOnWindowResize(FileBrowserState& state, int newWidth, int newHeight); + void InitializeImGui(void* window, void* renderer); + void ShutdownImGui(); + void ProcessEvent(void* event); // Add this method + +private: + void PopulateDirectoryContents(FileBrowserState& state); + void OpenFile(const std::string& filePath); + void RenderImGuiFrame(FileBrowserState& state); + + bool m_imguiInitialized = false; + void* m_renderer = nullptr; +}; + +} // namespace Engine \ No newline at end of file diff --git a/src/engine/file-browser.cpp b/src/engine/file-browser.cpp new file mode 100644 index 0000000..dd4804a --- /dev/null +++ b/src/engine/file-browser.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include + +namespace Engine { + +void FileBrowser::Initialize() { + // Provision for any other initialisation +} + +void FileBrowser::InitializeImGui(void* window, void* renderer) { + if (m_imguiInitialized) return; + + // Store the renderer for later use + m_renderer = renderer; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL3_InitForSDLRenderer(static_cast(window), static_cast(renderer)); + ImGui_ImplSDLRenderer3_Init(static_cast(renderer)); + + m_imguiInitialized = true; +} + +void FileBrowser::ShutdownImGui() { + if (!m_imguiInitialized) return; + + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + m_imguiInitialized = false; + m_renderer = nullptr; +} + +void FileBrowser::ProcessEvent(void* event) { + if (!m_imguiInitialized) return; + + ImGui_ImplSDL3_ProcessEvent(static_cast(event)); +} + +void FileBrowser::PopulateDirectoryContents(FileBrowserState& state) { + std::vector new_contents; + bool success = true; + + try { + for (const auto& entry : fs::directory_iterator(state.current_directory)) { + new_contents.push_back(entry); + } + } catch (const fs::filesystem_error& e) { + std::cerr << "Error accessing directory: " << e.what() << std::endl; + success = false; + } + + if (success) { + state.directory_contents = new_contents; + } else { + state.directory_contents.clear(); + } +} + +void FileBrowser::OpenFile(const std::string& filePath) { + std::ifstream file(filePath); + if (file.is_open()) { + std::cout << "File opened successfully: " << filePath << std::endl; + //File opening logic + file.close(); + } else { + std::cerr << "Failed to open file: " << filePath << std::endl; + } +} + +void FileBrowser::RenderImGuiFrame(FileBrowserState& state) { + // Populate directory contents if empty (first time or after error) + if (state.directory_contents.empty()) { + PopulateDirectoryContents(state); + } + + // Set window size constraints BEFORE Begin() + ImGui::SetNextWindowSizeConstraints(ImVec2(200, 150), ImVec2(FLT_MAX, FLT_MAX)); + + // Initialize the window + bool filebrowser_isopen = ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse); + + // Check for resizing + if (state.main_window_resized) { + ImGui::SetWindowSize(ImVec2(state.width, state.height), ImGuiCond_Always); + state.main_window_resized = false; + } else { + ImGui::SetWindowSize(ImVec2(state.width, state.height), ImGuiCond_FirstUseEver); + ImGui::SetWindowPos(ImVec2(0, 0), ImGuiCond_FirstUseEver); + } + + + // Check for manual sizing + if (filebrowser_isopen) { + ImVec2 currentSize = ImGui::GetWindowSize(); + + const float threshold = 1.0f; + if (std::fabs(currentSize.x - state.width) > threshold || + std::fabs(currentSize.y - state.height) > threshold) { + + state.width = currentSize.x; + state.height = currentSize.y; + + // Update ratio + state.ratio_width = currentSize.x / static_cast(state.main_window_width); + state.ratio_height = currentSize.y / static_cast(state.main_window_height); + } + + // UI elements + + // Go Back button + if (ImGui::Button("Go Back")) { + fs::path current_path = state.current_directory; + if (current_path.has_parent_path()) { + // Check if parent path is different before navigating + fs::path parent_path = current_path.parent_path(); + if (parent_path != current_path) { // Avoid getting stuck at root + state.current_directory = parent_path.string(); + PopulateDirectoryContents(state); + } + } + } + ImGui::SameLine(); + ImGui::TextWrapped("Current: %s", state.current_directory.c_str()); // Use TextWrapped for long paths + ImGui::Separator(); // Visual separation + + // Child Object to make listbox occupy the whole window + if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) { + + std::string selected_directory_path; // Capture path + + for (const auto& entry : state.directory_contents) { + std::string filename = entry.path().filename().string(); + std::string label; + + if (entry.is_directory()) { + label = "[D] " + filename; // Simple indicator for directory + } else if (entry.is_regular_file()) { + label = "[F] " + filename; // Simple indicator for file + } else { + label = "[?] " + filename; // Unknown type + } + label += "##" + entry.path().string(); + + if (ImGui::Selectable(label.c_str())) { + if (entry.is_directory()) { + selected_directory_path = entry.path().string(); // Store the path for processing after loop + } else if (entry.is_regular_file()) { + std::cout << "Opening file: " << entry.path() << std::endl; + OpenFile(entry.path().string()); + } + } + } + + // Process the selected directory after the loop to avoid modifying the list while iterating + if (!selected_directory_path.empty()) { + try { + if (fs::exists(selected_directory_path) && fs::is_directory(selected_directory_path)) { + state.current_directory = selected_directory_path; + PopulateDirectoryContents(state); + } else { + std::cerr << "Selected path no longer exists or is not a directory: " << selected_directory_path << std::endl; + PopulateDirectoryContents(state); + } + } catch (const fs::filesystem_error& e) { + std::cerr << "Error accessing directory: " << e.what() << std::endl; + PopulateDirectoryContents(state); + } + } + } + ImGui::EndChild(); // End File List + } + ImGui::End(); // End File Browser window +} + +void FileBrowser::Render(FileBrowserState& state) { + if (!m_imguiInitialized) { + std::cerr << "ImGui not initialized! Call InitializeImGui first." << std::endl; + return; + } + + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + RenderImGuiFrame(state); + + ImGui::Render(); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), static_cast(m_renderer)); +} + +void FileBrowser::UpdateOnWindowResize(FileBrowserState& state, int newWidth, int newHeight) { + state.main_window_width = newWidth; + state.main_window_height = newHeight; + + // Update file browser size based on new window size + state.width = static_cast(newWidth) * state.ratio_width; + state.height = static_cast(newHeight) * state.ratio_height; + + // Add minimum size conditions + if (state.width < 200.0f) state.width = 200.0f; + if (state.height < 150.0f) state.height = 150.0f; + + // Update flag + state.main_window_resized = true; +} + +} // namespace Engine diff --git a/src/main.cpp b/src/main.cpp index 08deb27..b577ccd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,11 @@ #include #include #include +#include + +// Initialise file browser instance and state +Engine::FileBrowser fileBrowser; +Engine::FileBrowserState fileBrowserState; // Initialises subsystems and initialises appState to be used by all other main // functions. @@ -30,6 +35,9 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { data.window.height); gameEngine->GetWindow().SetDimensions(data.window.width, data.window.height); + + // Update file browser state on window resize + fileBrowser.UpdateOnWindowResize(fileBrowserState, data.window.width, data.window.height); }); gameEngine->GetEvents().RegisterCallback( Engine::EventType::KeyDown, [](Engine::EventData data) { @@ -63,6 +71,19 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { data.mouse.y); }); + SPDLOG_INFO("Initializing file browser."); + fileBrowser.Initialize(); + + fileBrowser.InitializeImGui( + gameEngine->GetWindow().GetSDLWindow(), + gameEngine->GetRenderer().GetSDLRenderer() + ); + + fileBrowserState.main_window_width = 1000; + fileBrowserState.main_window_height = 800; + + // Populate initial directory contents + SPDLOG_INFO("Application initialized successfully."); *appState = gameEngine; return SDL_APP_CONTINUE; @@ -73,6 +94,11 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { // custom event handling to be added by a user. SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event) { Engine::Engine *gameEngine = static_cast(appState); + + // Process ImGui events for the file browser + extern Engine::FileBrowser fileBrowser; + fileBrowser.ProcessEvent(event); + if (event->type == SDL_EVENT_QUIT || event->type == SDL_EVENT_TERMINATING) { SPDLOG_INFO("Received SDL_EVENT_QUIT or SDL_EVENT_TERMINATING. Exiting " "application."); @@ -95,6 +121,8 @@ SDL_AppResult SDL_AppIterate(void *appState) { SPDLOG_DEBUG("Rendering all objects."); gameEngine->GetRenderManager().RenderAll(gameEngine->GetRenderer()); + fileBrowser.Render(fileBrowserState); + SPDLOG_DEBUG("Presenting rendered frame."); gameEngine->GetRenderer().Present(); @@ -107,6 +135,9 @@ void SDL_AppQuit(void *appState, SDL_AppResult result) { Engine::Engine *gameEngine = static_cast(appState); if (gameEngine != nullptr) { SPDLOG_INFO("Shutting down game engine."); + + fileBrowser.ShutdownImGui(); + gameEngine->Shutdown(); SPDLOG_INFO("Game engine shutdown completed."); } else { From 5f2776e49248707502162ab5cd0583c7b6163319 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Tue, 12 Aug 2025 09:25:48 +0530 Subject: [PATCH 2/7] chore: add .vscode/* to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b03c5ce..d5d662a 100644 --- a/.gitignore +++ b/.gitignore @@ -468,3 +468,4 @@ FodyWeavers.xsd ### VisualStudio Patch ### # Additional files built by Visual Studio +.vscode/* \ No newline at end of file From d70d5a2ba5d0551ed4b945ec8608b7cd29b1fa54 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Tue, 12 Aug 2025 09:52:04 +0530 Subject: [PATCH 3/7] chore: format and lint --- include/engine/file-browser.hpp | 35 +++---- src/engine/file-browser.cpp | 163 +++++++++++++++++++------------- src/main.cpp | 27 +++--- 3 files changed, 126 insertions(+), 99 deletions(-) diff --git a/include/engine/file-browser.hpp b/include/engine/file-browser.hpp index 55eb6e9..84613a7 100644 --- a/include/engine/file-browser.hpp +++ b/include/engine/file-browser.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include -#include -#include -#include #include +#include +#include +#include +#include namespace fs = std::filesystem; @@ -14,13 +14,13 @@ namespace Engine { struct FileBrowserState { std::string current_directory = fs::current_path().string(); std::vector directory_contents; - + // File browser dimensions and ratios float width = 400.0f; float height = 600.0f; float ratio_width = 0.4f; float ratio_height = 0.6f; - + // Window state bool main_window_resized = false; int main_window_width = 1000; @@ -28,24 +28,25 @@ struct FileBrowserState { }; class FileBrowser { -public: + public: FileBrowser() = default; ~FileBrowser() = default; void Initialize(); - void Render(FileBrowserState& state); - void UpdateOnWindowResize(FileBrowserState& state, int newWidth, int newHeight); - void InitializeImGui(void* window, void* renderer); + void Render(FileBrowserState &state); + void UpdateOnWindowResize(FileBrowserState &state, int newWidth, + int newHeight); + void InitializeImGui(void *window, void *renderer); void ShutdownImGui(); - void ProcessEvent(void* event); // Add this method + void ProcessEvent(void *event); // Add this method + + private: + void PopulateDirectoryContents(FileBrowserState &state); + void OpenFile(const std::string &filePath); + void RenderImGuiFrame(FileBrowserState &state); -private: - void PopulateDirectoryContents(FileBrowserState& state); - void OpenFile(const std::string& filePath); - void RenderImGuiFrame(FileBrowserState& state); - bool m_imguiInitialized = false; - void* m_renderer = nullptr; + void *m_renderer = nullptr; }; } // namespace Engine \ No newline at end of file diff --git a/src/engine/file-browser.cpp b/src/engine/file-browser.cpp index dd4804a..e6b1c9f 100644 --- a/src/engine/file-browser.cpp +++ b/src/engine/file-browser.cpp @@ -1,8 +1,8 @@ +#include +#include #include #include #include -#include -#include namespace Engine { @@ -10,53 +10,58 @@ void FileBrowser::Initialize() { // Provision for any other initialisation } -void FileBrowser::InitializeImGui(void* window, void* renderer) { - if (m_imguiInitialized) return; - +void FileBrowser::InitializeImGui(void *window, void *renderer) { + if (m_imguiInitialized) + return; + // Store the renderer for later use m_renderer = renderer; - + IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); + ImGuiIO &io = ImGui::GetIO(); (void)io; - + // Setup Dear ImGui style ImGui::StyleColorsDark(); - + // Setup Platform/Renderer backends - ImGui_ImplSDL3_InitForSDLRenderer(static_cast(window), static_cast(renderer)); - ImGui_ImplSDLRenderer3_Init(static_cast(renderer)); - + ImGui_ImplSDL3_InitForSDLRenderer(static_cast(window), + static_cast(renderer)); + ImGui_ImplSDLRenderer3_Init(static_cast(renderer)); + m_imguiInitialized = true; } void FileBrowser::ShutdownImGui() { - if (!m_imguiInitialized) return; - + if (!m_imguiInitialized) + return; + ImGui_ImplSDLRenderer3_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(); - + m_imguiInitialized = false; m_renderer = nullptr; } -void FileBrowser::ProcessEvent(void* event) { - if (!m_imguiInitialized) return; - - ImGui_ImplSDL3_ProcessEvent(static_cast(event)); +void FileBrowser::ProcessEvent(void *event) { + if (!m_imguiInitialized) + return; + + ImGui_ImplSDL3_ProcessEvent(static_cast(event)); } -void FileBrowser::PopulateDirectoryContents(FileBrowserState& state) { +void FileBrowser::PopulateDirectoryContents(FileBrowserState &state) { std::vector new_contents; bool success = true; - + try { - for (const auto& entry : fs::directory_iterator(state.current_directory)) { + for (const auto &entry : + fs::directory_iterator(state.current_directory)) { new_contents.push_back(entry); } - } catch (const fs::filesystem_error& e) { + } catch (const fs::filesystem_error &e) { std::cerr << "Error accessing directory: " << e.what() << std::endl; success = false; } @@ -64,85 +69,94 @@ void FileBrowser::PopulateDirectoryContents(FileBrowserState& state) { if (success) { state.directory_contents = new_contents; } else { - state.directory_contents.clear(); + state.directory_contents.clear(); } } -void FileBrowser::OpenFile(const std::string& filePath) { +void FileBrowser::OpenFile(const std::string &filePath) { std::ifstream file(filePath); if (file.is_open()) { std::cout << "File opened successfully: " << filePath << std::endl; - //File opening logic + // File opening logic file.close(); } else { std::cerr << "Failed to open file: " << filePath << std::endl; } } -void FileBrowser::RenderImGuiFrame(FileBrowserState& state) { +void FileBrowser::RenderImGuiFrame(FileBrowserState &state) { // Populate directory contents if empty (first time or after error) if (state.directory_contents.empty()) { PopulateDirectoryContents(state); } - + // Set window size constraints BEFORE Begin() - ImGui::SetNextWindowSizeConstraints(ImVec2(200, 150), ImVec2(FLT_MAX, FLT_MAX)); - + ImGui::SetNextWindowSizeConstraints(ImVec2(200, 150), + ImVec2(FLT_MAX, FLT_MAX)); + // Initialize the window - bool filebrowser_isopen = ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse); - + bool filebrowser_isopen = + ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse); + // Check for resizing if (state.main_window_resized) { - ImGui::SetWindowSize(ImVec2(state.width, state.height), ImGuiCond_Always); + ImGui::SetWindowSize(ImVec2(state.width, state.height), + ImGuiCond_Always); state.main_window_resized = false; } else { - ImGui::SetWindowSize(ImVec2(state.width, state.height), ImGuiCond_FirstUseEver); + ImGui::SetWindowSize(ImVec2(state.width, state.height), + ImGuiCond_FirstUseEver); ImGui::SetWindowPos(ImVec2(0, 0), ImGuiCond_FirstUseEver); } - // Check for manual sizing if (filebrowser_isopen) { ImVec2 currentSize = ImGui::GetWindowSize(); - + const float threshold = 1.0f; if (std::fabs(currentSize.x - state.width) > threshold || std::fabs(currentSize.y - state.height) > threshold) { - + state.width = currentSize.x; state.height = currentSize.y; - + // Update ratio - state.ratio_width = currentSize.x / static_cast(state.main_window_width); - state.ratio_height = currentSize.y / static_cast(state.main_window_height); + state.ratio_width = + currentSize.x / static_cast(state.main_window_width); + state.ratio_height = + currentSize.y / static_cast(state.main_window_height); } // UI elements - + // Go Back button if (ImGui::Button("Go Back")) { fs::path current_path = state.current_directory; if (current_path.has_parent_path()) { // Check if parent path is different before navigating fs::path parent_path = current_path.parent_path(); - if (parent_path != current_path) { // Avoid getting stuck at root + if (parent_path != + current_path) { // Avoid getting stuck at root state.current_directory = parent_path.string(); PopulateDirectoryContents(state); } } } - ImGui::SameLine(); - ImGui::TextWrapped("Current: %s", state.current_directory.c_str()); // Use TextWrapped for long paths - ImGui::Separator(); // Visual separation + ImGui::SameLine(); + ImGui::TextWrapped( + "Current: %s", + state.current_directory.c_str()); // Use TextWrapped for long paths + ImGui::Separator(); // Visual separation // Child Object to make listbox occupy the whole window - if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) { - + if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar)) { + std::string selected_directory_path; // Capture path - for (const auto& entry : state.directory_contents) { + for (const auto &entry : state.directory_contents) { std::string filename = entry.path().filename().string(); - std::string label; + std::string label; if (entry.is_directory()) { label = "[D] " + filename; // Simple indicator for directory @@ -151,30 +165,38 @@ void FileBrowser::RenderImGuiFrame(FileBrowserState& state) { } else { label = "[?] " + filename; // Unknown type } - label += "##" + entry.path().string(); + label += "##" + entry.path().string(); if (ImGui::Selectable(label.c_str())) { if (entry.is_directory()) { - selected_directory_path = entry.path().string(); // Store the path for processing after loop + selected_directory_path = + entry.path().string(); // Store the path for + // processing after loop } else if (entry.is_regular_file()) { - std::cout << "Opening file: " << entry.path() << std::endl; + std::cout << "Opening file: " << entry.path() + << std::endl; OpenFile(entry.path().string()); } } } - - // Process the selected directory after the loop to avoid modifying the list while iterating + + // Process the selected directory after the loop to avoid modifying + // the list while iterating if (!selected_directory_path.empty()) { try { - if (fs::exists(selected_directory_path) && fs::is_directory(selected_directory_path)) { + if (fs::exists(selected_directory_path) && + fs::is_directory(selected_directory_path)) { state.current_directory = selected_directory_path; PopulateDirectoryContents(state); } else { - std::cerr << "Selected path no longer exists or is not a directory: " << selected_directory_path << std::endl; + std::cerr << "Selected path no longer exists or is not " + "a directory: " + << selected_directory_path << std::endl; PopulateDirectoryContents(state); } - } catch (const fs::filesystem_error& e) { - std::cerr << "Error accessing directory: " << e.what() << std::endl; + } catch (const fs::filesystem_error &e) { + std::cerr << "Error accessing directory: " << e.what() + << std::endl; PopulateDirectoryContents(state); } } @@ -184,12 +206,13 @@ void FileBrowser::RenderImGuiFrame(FileBrowserState& state) { ImGui::End(); // End File Browser window } -void FileBrowser::Render(FileBrowserState& state) { +void FileBrowser::Render(FileBrowserState &state) { if (!m_imguiInitialized) { - std::cerr << "ImGui not initialized! Call InitializeImGui first." << std::endl; + std::cerr << "ImGui not initialized! Call InitializeImGui first." + << std::endl; return; } - + ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); @@ -197,21 +220,25 @@ void FileBrowser::Render(FileBrowserState& state) { RenderImGuiFrame(state); ImGui::Render(); - ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), static_cast(m_renderer)); + ImGui_ImplSDLRenderer3_RenderDrawData( + ImGui::GetDrawData(), static_cast(m_renderer)); } -void FileBrowser::UpdateOnWindowResize(FileBrowserState& state, int newWidth, int newHeight) { +void FileBrowser::UpdateOnWindowResize(FileBrowserState &state, int newWidth, + int newHeight) { state.main_window_width = newWidth; state.main_window_height = newHeight; - + // Update file browser size based on new window size state.width = static_cast(newWidth) * state.ratio_width; state.height = static_cast(newHeight) * state.ratio_height; - + // Add minimum size conditions - if (state.width < 200.0f) state.width = 200.0f; - if (state.height < 150.0f) state.height = 150.0f; - + if (state.width < 200.0f) + state.width = 200.0f; + if (state.height < 150.0f) + state.height = 150.0f; + // Update flag state.main_window_resized = true; } diff --git a/src/main.cpp b/src/main.cpp index b577ccd..8a4d172 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ #include #include #include -#include #include +#include // Initialise file browser instance and state Engine::FileBrowser fileBrowser; @@ -35,9 +35,10 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { data.window.height); gameEngine->GetWindow().SetDimensions(data.window.width, data.window.height); - + // Update file browser state on window resize - fileBrowser.UpdateOnWindowResize(fileBrowserState, data.window.width, data.window.height); + fileBrowser.UpdateOnWindowResize( + fileBrowserState, data.window.width, data.window.height); }); gameEngine->GetEvents().RegisterCallback( Engine::EventType::KeyDown, [](Engine::EventData data) { @@ -73,15 +74,13 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { SPDLOG_INFO("Initializing file browser."); fileBrowser.Initialize(); - - fileBrowser.InitializeImGui( - gameEngine->GetWindow().GetSDLWindow(), - gameEngine->GetRenderer().GetSDLRenderer() - ); - + + fileBrowser.InitializeImGui(gameEngine->GetWindow().GetSDLWindow(), + gameEngine->GetRenderer().GetSDLRenderer()); + fileBrowserState.main_window_width = 1000; fileBrowserState.main_window_height = 800; - + // Populate initial directory contents SPDLOG_INFO("Application initialized successfully."); @@ -94,11 +93,11 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { // custom event handling to be added by a user. SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event) { Engine::Engine *gameEngine = static_cast(appState); - + // Process ImGui events for the file browser extern Engine::FileBrowser fileBrowser; fileBrowser.ProcessEvent(event); - + if (event->type == SDL_EVENT_QUIT || event->type == SDL_EVENT_TERMINATING) { SPDLOG_INFO("Received SDL_EVENT_QUIT or SDL_EVENT_TERMINATING. Exiting " "application."); @@ -135,9 +134,9 @@ void SDL_AppQuit(void *appState, SDL_AppResult result) { Engine::Engine *gameEngine = static_cast(appState); if (gameEngine != nullptr) { SPDLOG_INFO("Shutting down game engine."); - + fileBrowser.ShutdownImGui(); - + gameEngine->Shutdown(); SPDLOG_INFO("Game engine shutdown completed."); } else { From 1ac8a358d4f8e370c7685720a96c97249ae2bed7 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Sun, 17 Aug 2025 22:28:28 +0530 Subject: [PATCH 4/7] feat!: separate ImGUI rendering to imgui-render and implement an empty simple top bar. Removes file browser implementation, but will commit with a working change --- include/engine/file-browser.hpp | 52 ----- include/engine/imgui-objects/top-bar.hpp | 8 + include/engine/imgui-render.hpp | 24 +++ src/engine/file-browser.cpp | 246 ----------------------- src/engine/imgui-objects/top-bar.cpp | 11 + src/engine/imgui-render.cpp | 46 +++++ 6 files changed, 89 insertions(+), 298 deletions(-) delete mode 100644 include/engine/file-browser.hpp create mode 100644 include/engine/imgui-objects/top-bar.hpp create mode 100644 include/engine/imgui-render.hpp delete mode 100644 src/engine/file-browser.cpp create mode 100644 src/engine/imgui-objects/top-bar.cpp create mode 100644 src/engine/imgui-render.cpp diff --git a/include/engine/file-browser.hpp b/include/engine/file-browser.hpp deleted file mode 100644 index 84613a7..0000000 --- a/include/engine/file-browser.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace Engine { - -struct FileBrowserState { - std::string current_directory = fs::current_path().string(); - std::vector directory_contents; - - // File browser dimensions and ratios - float width = 400.0f; - float height = 600.0f; - float ratio_width = 0.4f; - float ratio_height = 0.6f; - - // Window state - bool main_window_resized = false; - int main_window_width = 1000; - int main_window_height = 800; -}; - -class FileBrowser { - public: - FileBrowser() = default; - ~FileBrowser() = default; - - void Initialize(); - void Render(FileBrowserState &state); - void UpdateOnWindowResize(FileBrowserState &state, int newWidth, - int newHeight); - void InitializeImGui(void *window, void *renderer); - void ShutdownImGui(); - void ProcessEvent(void *event); // Add this method - - private: - void PopulateDirectoryContents(FileBrowserState &state); - void OpenFile(const std::string &filePath); - void RenderImGuiFrame(FileBrowserState &state); - - bool m_imguiInitialized = false; - void *m_renderer = nullptr; -}; - -} // namespace Engine \ No newline at end of file diff --git a/include/engine/imgui-objects/top-bar.hpp b/include/engine/imgui-objects/top-bar.hpp new file mode 100644 index 0000000..4fdc82e --- /dev/null +++ b/include/engine/imgui-objects/top-bar.hpp @@ -0,0 +1,8 @@ +#pragma once + + +namespace Engine { +struct TopBar { + static void Render(); +}; +} // namespace Engine \ No newline at end of file diff --git a/include/engine/imgui-render.hpp b/include/engine/imgui-render.hpp new file mode 100644 index 0000000..bce4ea1 --- /dev/null +++ b/include/engine/imgui-render.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +namespace Engine { + + class ImGuiRender { + public: + static ImGuiRender& Instance(); + + void Init(SDL_Window * window, SDL_Renderer * renderer); + void ShutDown(); + + void ProcessEvent(const SDL_Event* e); + + void BeginFrame(); + void EndFrame(); + + + private: + SDL_Window* m_window = nullptr; + SDL_Renderer* m_renderer = nullptr; + bool m_initialized = false; + }; +} // namespace Engine \ No newline at end of file diff --git a/src/engine/file-browser.cpp b/src/engine/file-browser.cpp deleted file mode 100644 index e6b1c9f..0000000 --- a/src/engine/file-browser.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include -#include -#include -#include -#include - -namespace Engine { - -void FileBrowser::Initialize() { - // Provision for any other initialisation -} - -void FileBrowser::InitializeImGui(void *window, void *renderer) { - if (m_imguiInitialized) - return; - - // Store the renderer for later use - m_renderer = renderer; - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO &io = ImGui::GetIO(); - (void)io; - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - - // Setup Platform/Renderer backends - ImGui_ImplSDL3_InitForSDLRenderer(static_cast(window), - static_cast(renderer)); - ImGui_ImplSDLRenderer3_Init(static_cast(renderer)); - - m_imguiInitialized = true; -} - -void FileBrowser::ShutdownImGui() { - if (!m_imguiInitialized) - return; - - ImGui_ImplSDLRenderer3_Shutdown(); - ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); - - m_imguiInitialized = false; - m_renderer = nullptr; -} - -void FileBrowser::ProcessEvent(void *event) { - if (!m_imguiInitialized) - return; - - ImGui_ImplSDL3_ProcessEvent(static_cast(event)); -} - -void FileBrowser::PopulateDirectoryContents(FileBrowserState &state) { - std::vector new_contents; - bool success = true; - - try { - for (const auto &entry : - fs::directory_iterator(state.current_directory)) { - new_contents.push_back(entry); - } - } catch (const fs::filesystem_error &e) { - std::cerr << "Error accessing directory: " << e.what() << std::endl; - success = false; - } - - if (success) { - state.directory_contents = new_contents; - } else { - state.directory_contents.clear(); - } -} - -void FileBrowser::OpenFile(const std::string &filePath) { - std::ifstream file(filePath); - if (file.is_open()) { - std::cout << "File opened successfully: " << filePath << std::endl; - // File opening logic - file.close(); - } else { - std::cerr << "Failed to open file: " << filePath << std::endl; - } -} - -void FileBrowser::RenderImGuiFrame(FileBrowserState &state) { - // Populate directory contents if empty (first time or after error) - if (state.directory_contents.empty()) { - PopulateDirectoryContents(state); - } - - // Set window size constraints BEFORE Begin() - ImGui::SetNextWindowSizeConstraints(ImVec2(200, 150), - ImVec2(FLT_MAX, FLT_MAX)); - - // Initialize the window - bool filebrowser_isopen = - ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse); - - // Check for resizing - if (state.main_window_resized) { - ImGui::SetWindowSize(ImVec2(state.width, state.height), - ImGuiCond_Always); - state.main_window_resized = false; - } else { - ImGui::SetWindowSize(ImVec2(state.width, state.height), - ImGuiCond_FirstUseEver); - ImGui::SetWindowPos(ImVec2(0, 0), ImGuiCond_FirstUseEver); - } - - // Check for manual sizing - if (filebrowser_isopen) { - ImVec2 currentSize = ImGui::GetWindowSize(); - - const float threshold = 1.0f; - if (std::fabs(currentSize.x - state.width) > threshold || - std::fabs(currentSize.y - state.height) > threshold) { - - state.width = currentSize.x; - state.height = currentSize.y; - - // Update ratio - state.ratio_width = - currentSize.x / static_cast(state.main_window_width); - state.ratio_height = - currentSize.y / static_cast(state.main_window_height); - } - - // UI elements - - // Go Back button - if (ImGui::Button("Go Back")) { - fs::path current_path = state.current_directory; - if (current_path.has_parent_path()) { - // Check if parent path is different before navigating - fs::path parent_path = current_path.parent_path(); - if (parent_path != - current_path) { // Avoid getting stuck at root - state.current_directory = parent_path.string(); - PopulateDirectoryContents(state); - } - } - } - ImGui::SameLine(); - ImGui::TextWrapped( - "Current: %s", - state.current_directory.c_str()); // Use TextWrapped for long paths - ImGui::Separator(); // Visual separation - - // Child Object to make listbox occupy the whole window - if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar)) { - - std::string selected_directory_path; // Capture path - - for (const auto &entry : state.directory_contents) { - std::string filename = entry.path().filename().string(); - std::string label; - - if (entry.is_directory()) { - label = "[D] " + filename; // Simple indicator for directory - } else if (entry.is_regular_file()) { - label = "[F] " + filename; // Simple indicator for file - } else { - label = "[?] " + filename; // Unknown type - } - label += "##" + entry.path().string(); - - if (ImGui::Selectable(label.c_str())) { - if (entry.is_directory()) { - selected_directory_path = - entry.path().string(); // Store the path for - // processing after loop - } else if (entry.is_regular_file()) { - std::cout << "Opening file: " << entry.path() - << std::endl; - OpenFile(entry.path().string()); - } - } - } - - // Process the selected directory after the loop to avoid modifying - // the list while iterating - if (!selected_directory_path.empty()) { - try { - if (fs::exists(selected_directory_path) && - fs::is_directory(selected_directory_path)) { - state.current_directory = selected_directory_path; - PopulateDirectoryContents(state); - } else { - std::cerr << "Selected path no longer exists or is not " - "a directory: " - << selected_directory_path << std::endl; - PopulateDirectoryContents(state); - } - } catch (const fs::filesystem_error &e) { - std::cerr << "Error accessing directory: " << e.what() - << std::endl; - PopulateDirectoryContents(state); - } - } - } - ImGui::EndChild(); // End File List - } - ImGui::End(); // End File Browser window -} - -void FileBrowser::Render(FileBrowserState &state) { - if (!m_imguiInitialized) { - std::cerr << "ImGui not initialized! Call InitializeImGui first." - << std::endl; - return; - } - - ImGui_ImplSDLRenderer3_NewFrame(); - ImGui_ImplSDL3_NewFrame(); - ImGui::NewFrame(); - - RenderImGuiFrame(state); - - ImGui::Render(); - ImGui_ImplSDLRenderer3_RenderDrawData( - ImGui::GetDrawData(), static_cast(m_renderer)); -} - -void FileBrowser::UpdateOnWindowResize(FileBrowserState &state, int newWidth, - int newHeight) { - state.main_window_width = newWidth; - state.main_window_height = newHeight; - - // Update file browser size based on new window size - state.width = static_cast(newWidth) * state.ratio_width; - state.height = static_cast(newHeight) * state.ratio_height; - - // Add minimum size conditions - if (state.width < 200.0f) - state.width = 200.0f; - if (state.height < 150.0f) - state.height = 150.0f; - - // Update flag - state.main_window_resized = true; -} - -} // namespace Engine diff --git a/src/engine/imgui-objects/top-bar.cpp b/src/engine/imgui-objects/top-bar.cpp new file mode 100644 index 0000000..b451e63 --- /dev/null +++ b/src/engine/imgui-objects/top-bar.cpp @@ -0,0 +1,11 @@ +#include +#include + +namespace Engine { +void TopBar::Render() { + if (ImGui::BeginMainMenuBar()) { + // Add menu items + ImGui::EndMainMenuBar(); + } +} +} // namespace Engine \ No newline at end of file diff --git a/src/engine/imgui-render.cpp b/src/engine/imgui-render.cpp new file mode 100644 index 0000000..99e75f9 --- /dev/null +++ b/src/engine/imgui-render.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +namespace Engine { +void ImGuiRender::Init(SDL_Window *window, SDL_Renderer *renderer) { + if (m_initialized) + return; + m_window = window; + m_renderer = renderer; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + + ImGui_ImplSDL3_InitForSDLRenderer(m_window, m_renderer); + ImGui_ImplSDLRenderer3_Init(m_renderer); + + m_initialized = true; +} + +void ImGuiRender::ShutDown() { + if (!m_initialized) + return; + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + m_window = nullptr; + m_renderer = nullptr; + m_initialized = false; +} + +void ImGuiRender::EndFrame() { + if (!m_initialized) + return; + ImGui::Render(); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), m_renderer); +} + +void ImGuiRender::ProcessEvent(const SDL_Event *e) { + if (!m_initialized || !e) + return; + ImGui_ImplSDL3_ProcessEvent(e); +} +} // namespace Engine \ No newline at end of file From 034856d59b919b306a1ec9031cfff1a6af0921ba Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Mon, 18 Aug 2025 22:05:16 +0530 Subject: [PATCH 5/7] refactor: separated functions of file browser as is into file-browser.cpp/hpp --- include/engine/imgui-objects/file-browser.hpp | 33 +++++ src/engine/imgui-objects/file-browser.cpp | 115 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 include/engine/imgui-objects/file-browser.hpp create mode 100644 src/engine/imgui-objects/file-browser.cpp diff --git a/include/engine/imgui-objects/file-browser.hpp b/include/engine/imgui-objects/file-browser.hpp new file mode 100644 index 0000000..eebc6d2 --- /dev/null +++ b/include/engine/imgui-objects/file-browser.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +namespace fs = std::filesystem; + +namespace Engine { + +struct FileBrowserState { + std::string current_directory; + std::vector directory_contents; + + // sizing state (percentages tracked elsewhere if needed) + int mainWindowWidth = 1000; + int mainWindowHeight = 800; + float filebrowserWidth = 400.f; + float filebrowserHeight = 480.f; + bool mainwindowresize = false; +}; + +class FileBrowser { +public: + void Initialize(); + void UpdateOnWindowResize(FileBrowserState& state, int w, int h); + void Render(FileBrowserState& state); + +private: + void PopulateDirectoryContents(FileBrowserState& state); + void OpenFile(const std::string& path); +}; + +} // namespace Engine \ No newline at end of file diff --git a/src/engine/imgui-objects/file-browser.cpp b/src/engine/imgui-objects/file-browser.cpp new file mode 100644 index 0000000..17ef5c4 --- /dev/null +++ b/src/engine/imgui-objects/file-browser.cpp @@ -0,0 +1,115 @@ +#include +#include +#include + +namespace Engine { + +void FileBrowser::Initialize() { + // can be used to set directory using top bar +} + +void FileBrowser::UpdateOnWindowResize(FileBrowserState& state, int w, int h) { + state.mainWindowWidth = w; + state.mainWindowHeight = h; + state.mainwindowresize = true; + + // Example: keep 40% width, 60% height + state.filebrowserWidth = 0.4f * static_cast(w); + state.filebrowserHeight = 0.6f * static_cast(h); +} + +void FileBrowser::PopulateDirectoryContents(FileBrowserState& state) { + if (state.current_directory.empty()) { + state.current_directory = fs::current_path().string(); + } + std::vector new_contents; + try { + for (const auto& entry : fs::directory_iterator(state.current_directory)) { + new_contents.push_back(entry); + } + state.directory_contents = std::move(new_contents); + } catch (const fs::filesystem_error& e) { + std::cerr << "Error accessing directory: " << e.what() << std::endl; + state.directory_contents.clear(); + } +} + +void FileBrowser::OpenFile(const std::string& path) { + std::cout << "OpenFile: " << path << std::endl; + // Opening file logic +} + +void FileBrowser::Render(FileBrowserState& state) { + // ensure contents are available + if (state.directory_contents.empty()) { + PopulateDirectoryContents(state); + } + + // Size/pos hints (use FirstUseEver so user can resize later) + if (state.mainwindowresize) { + ImGui::SetNextWindowSize(ImVec2(state.filebrowserWidth, state.filebrowserHeight), ImGuiCond_Always); + state.mainwindowresize = false; + } else { + ImGui::SetNextWindowSize(ImVec2(state.filebrowserWidth, state.filebrowserHeight), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(0, 20), ImGuiCond_FirstUseEver); // below main menu bar + } + ImGui::SetNextWindowSizeConstraints(ImVec2(50, 50), ImVec2(FLT_MAX, FLT_MAX)); + + if (ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse)) { + // Navigation row + if (ImGui::Button("Go Back")) { + fs::path p = state.current_directory; + if (p.has_parent_path()) { + auto parent = p.parent_path(); + if (parent != p) { + state.current_directory = parent.string(); + PopulateDirectoryContents(state); + } + } + } + ImGui::SameLine(); + ImGui::TextWrapped("Current: %s", state.current_directory.c_str()); + ImGui::Separator(); + + if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) { + std::string selected_dir; + for (const auto& entry : state.directory_contents) { + std::string filename = entry.path().filename().string(); + std::string label; + + if (entry.is_directory()) label = "[D] " + filename; + else if (entry.is_regular_file()) label = "[F] " + filename; + else label = "[?] " + filename; + + label += "##" + entry.path().string(); // unique ID + + if (ImGui::Selectable(label.c_str())) { + if (entry.is_directory()) { + selected_dir = entry.path().string(); + } else if (entry.is_regular_file()) { + OpenFile(entry.path().string()); + } + } + } + + if (!selected_dir.empty()) { + try { + if (fs::exists(selected_dir) && fs::is_directory(selected_dir)) { + state.current_directory = selected_dir; + PopulateDirectoryContents(state); + } else { + PopulateDirectoryContents(state); // refresh + } + } catch (const fs::filesystem_error& e) { + std::cerr << "Error accessing directory: " << e.what() << std::endl; + PopulateDirectoryContents(state); + } + } + + ImGui::EndChild(); + } + } + ImGui::End(); +} + +} // namespace Engine \ No newline at end of file From e9739d7e7417cdcc34129033aad482523865a544 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Mon, 18 Aug 2025 22:05:57 +0530 Subject: [PATCH 6/7] refactor: change main.cpp to accomodate new files/refactor chore: linter main and file-browser --- include/engine/imgui-objects/file-browser.hpp | 16 +++--- src/engine/imgui-objects/file-browser.cpp | 51 ++++++++++++------- src/engine/imgui-render.cpp | 7 +++ src/main.cpp | 23 +++++---- 4 files changed, 61 insertions(+), 36 deletions(-) diff --git a/include/engine/imgui-objects/file-browser.hpp b/include/engine/imgui-objects/file-browser.hpp index eebc6d2..bd3ec86 100644 --- a/include/engine/imgui-objects/file-browser.hpp +++ b/include/engine/imgui-objects/file-browser.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include #include +#include +#include namespace fs = std::filesystem; @@ -20,14 +20,14 @@ struct FileBrowserState { }; class FileBrowser { -public: + public: void Initialize(); - void UpdateOnWindowResize(FileBrowserState& state, int w, int h); - void Render(FileBrowserState& state); + void UpdateOnWindowResize(FileBrowserState &state, int w, int h); + void Render(FileBrowserState &state); -private: - void PopulateDirectoryContents(FileBrowserState& state); - void OpenFile(const std::string& path); + private: + void PopulateDirectoryContents(FileBrowserState &state); + void OpenFile(const std::string &path); }; } // namespace Engine \ No newline at end of file diff --git a/src/engine/imgui-objects/file-browser.cpp b/src/engine/imgui-objects/file-browser.cpp index 17ef5c4..ed090d5 100644 --- a/src/engine/imgui-objects/file-browser.cpp +++ b/src/engine/imgui-objects/file-browser.cpp @@ -8,38 +8,39 @@ void FileBrowser::Initialize() { // can be used to set directory using top bar } -void FileBrowser::UpdateOnWindowResize(FileBrowserState& state, int w, int h) { +void FileBrowser::UpdateOnWindowResize(FileBrowserState &state, int w, int h) { state.mainWindowWidth = w; state.mainWindowHeight = h; state.mainwindowresize = true; // Example: keep 40% width, 60% height - state.filebrowserWidth = 0.4f * static_cast(w); + state.filebrowserWidth = 0.4f * static_cast(w); state.filebrowserHeight = 0.6f * static_cast(h); } -void FileBrowser::PopulateDirectoryContents(FileBrowserState& state) { +void FileBrowser::PopulateDirectoryContents(FileBrowserState &state) { if (state.current_directory.empty()) { state.current_directory = fs::current_path().string(); } std::vector new_contents; try { - for (const auto& entry : fs::directory_iterator(state.current_directory)) { + for (const auto &entry : + fs::directory_iterator(state.current_directory)) { new_contents.push_back(entry); } state.directory_contents = std::move(new_contents); - } catch (const fs::filesystem_error& e) { + } catch (const fs::filesystem_error &e) { std::cerr << "Error accessing directory: " << e.what() << std::endl; state.directory_contents.clear(); } } -void FileBrowser::OpenFile(const std::string& path) { +void FileBrowser::OpenFile(const std::string &path) { std::cout << "OpenFile: " << path << std::endl; // Opening file logic } -void FileBrowser::Render(FileBrowserState& state) { +void FileBrowser::Render(FileBrowserState &state) { // ensure contents are available if (state.directory_contents.empty()) { PopulateDirectoryContents(state); @@ -47,13 +48,19 @@ void FileBrowser::Render(FileBrowserState& state) { // Size/pos hints (use FirstUseEver so user can resize later) if (state.mainwindowresize) { - ImGui::SetNextWindowSize(ImVec2(state.filebrowserWidth, state.filebrowserHeight), ImGuiCond_Always); + ImGui::SetNextWindowSize( + ImVec2(state.filebrowserWidth, state.filebrowserHeight), + ImGuiCond_Always); state.mainwindowresize = false; } else { - ImGui::SetNextWindowSize(ImVec2(state.filebrowserWidth, state.filebrowserHeight), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ImVec2(0, 20), ImGuiCond_FirstUseEver); // below main menu bar + ImGui::SetNextWindowSize( + ImVec2(state.filebrowserWidth, state.filebrowserHeight), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(0, 20), + ImGuiCond_FirstUseEver); // below main menu bar } - ImGui::SetNextWindowSizeConstraints(ImVec2(50, 50), ImVec2(FLT_MAX, FLT_MAX)); + ImGui::SetNextWindowSizeConstraints(ImVec2(50, 50), + ImVec2(FLT_MAX, FLT_MAX)); if (ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse)) { // Navigation row @@ -71,15 +78,19 @@ void FileBrowser::Render(FileBrowserState& state) { ImGui::TextWrapped("Current: %s", state.current_directory.c_str()); ImGui::Separator(); - if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar)) { + if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar)) { std::string selected_dir; - for (const auto& entry : state.directory_contents) { + for (const auto &entry : state.directory_contents) { std::string filename = entry.path().filename().string(); std::string label; - if (entry.is_directory()) label = "[D] " + filename; - else if (entry.is_regular_file()) label = "[F] " + filename; - else label = "[?] " + filename; + if (entry.is_directory()) + label = "[D] " + filename; + else if (entry.is_regular_file()) + label = "[F] " + filename; + else + label = "[?] " + filename; label += "##" + entry.path().string(); // unique ID @@ -94,14 +105,16 @@ void FileBrowser::Render(FileBrowserState& state) { if (!selected_dir.empty()) { try { - if (fs::exists(selected_dir) && fs::is_directory(selected_dir)) { + if (fs::exists(selected_dir) && + fs::is_directory(selected_dir)) { state.current_directory = selected_dir; PopulateDirectoryContents(state); } else { PopulateDirectoryContents(state); // refresh } - } catch (const fs::filesystem_error& e) { - std::cerr << "Error accessing directory: " << e.what() << std::endl; + } catch (const fs::filesystem_error &e) { + std::cerr << "Error accessing directory: " << e.what() + << std::endl; PopulateDirectoryContents(state); } } diff --git a/src/engine/imgui-render.cpp b/src/engine/imgui-render.cpp index 99e75f9..cacaee2 100644 --- a/src/engine/imgui-render.cpp +++ b/src/engine/imgui-render.cpp @@ -31,6 +31,13 @@ void ImGuiRender::ShutDown() { m_initialized = false; } +void ImGuiRender::BeginFrame() { + if (!m_initialized) return; + ImGui_ImplSDL3_NewFrame(); + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui::NewFrame(); +} + void ImGuiRender::EndFrame() { if (!m_initialized) return; diff --git a/src/main.cpp b/src/main.cpp index 8a4d172..9b23810 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,15 @@ #include #include #include -#include +#include +#include +#include #include // Initialise file browser instance and state Engine::FileBrowser fileBrowser; Engine::FileBrowserState fileBrowserState; +static Engine::ImGuiRender g_ImGui; // Initialises subsystems and initialises appState to be used by all other main // functions. @@ -75,11 +78,11 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { SPDLOG_INFO("Initializing file browser."); fileBrowser.Initialize(); - fileBrowser.InitializeImGui(gameEngine->GetWindow().GetSDLWindow(), - gameEngine->GetRenderer().GetSDLRenderer()); + g_ImGui.Init(gameEngine->GetWindow().GetSDLWindow(), + gameEngine->GetRenderer().GetSDLRenderer()); - fileBrowserState.main_window_width = 1000; - fileBrowserState.main_window_height = 800; + fileBrowserState.mainWindowWidth = 1000; + fileBrowserState.mainWindowHeight = 800; // Populate initial directory contents @@ -94,9 +97,8 @@ SDL_AppResult SDL_AppInit(void **appState, int argc, char *argv[]) { SDL_AppResult SDL_AppEvent(void *appState, SDL_Event *event) { Engine::Engine *gameEngine = static_cast(appState); - // Process ImGui events for the file browser - extern Engine::FileBrowser fileBrowser; - fileBrowser.ProcessEvent(event); + // Process ImGui events + g_ImGui.ProcessEvent(event); if (event->type == SDL_EVENT_QUIT || event->type == SDL_EVENT_TERMINATING) { SPDLOG_INFO("Received SDL_EVENT_QUIT or SDL_EVENT_TERMINATING. Exiting " @@ -120,7 +122,10 @@ SDL_AppResult SDL_AppIterate(void *appState) { SPDLOG_DEBUG("Rendering all objects."); gameEngine->GetRenderManager().RenderAll(gameEngine->GetRenderer()); + g_ImGui.BeginFrame(); + Engine::TopBar::Render(); fileBrowser.Render(fileBrowserState); + g_ImGui.EndFrame(); SPDLOG_DEBUG("Presenting rendered frame."); gameEngine->GetRenderer().Present(); @@ -135,7 +140,7 @@ void SDL_AppQuit(void *appState, SDL_AppResult result) { if (gameEngine != nullptr) { SPDLOG_INFO("Shutting down game engine."); - fileBrowser.ShutdownImGui(); + g_ImGui.ShutDown(); gameEngine->Shutdown(); SPDLOG_INFO("Game engine shutdown completed."); From 285c32efd7d60a0a683e50eaeea7f349977fb142 Mon Sep 17 00:00:00 2001 From: Andy34G7 Date: Sat, 30 Aug 2025 09:30:00 +0530 Subject: [PATCH 7/7] feat: implement tree structure and add a simple root selection menu --- include/engine/imgui-objects/file-browser.hpp | 10 +- include/engine/imgui-objects/top-bar.hpp | 5 +- src/engine/imgui-objects/file-browser.cpp | 183 ++++++++++-------- src/engine/imgui-objects/top-bar.cpp | 57 +++++- src/main.cpp | 2 +- 5 files changed, 170 insertions(+), 87 deletions(-) diff --git a/include/engine/imgui-objects/file-browser.hpp b/include/engine/imgui-objects/file-browser.hpp index bd3ec86..9451182 100644 --- a/include/engine/imgui-objects/file-browser.hpp +++ b/include/engine/imgui-objects/file-browser.hpp @@ -8,8 +8,9 @@ namespace fs = std::filesystem; namespace Engine { struct FileBrowserState { - std::string current_directory; - std::vector directory_contents; + std::string root_directory; + bool has_root = false; + std::string selected_path; // sizing state (percentages tracked elsewhere if needed) int mainWindowWidth = 1000; @@ -23,10 +24,13 @@ class FileBrowser { public: void Initialize(); void UpdateOnWindowResize(FileBrowserState &state, int w, int h); + bool SetRootDirectory(FileBrowserState &state, const std::string &path); + void Render(FileBrowserState &state); private: - void PopulateDirectoryContents(FileBrowserState &state); + void RenderDirectoryNode(FileBrowserState &state, const fs::path &dirPath, + int depth = 0); void OpenFile(const std::string &path); }; diff --git a/include/engine/imgui-objects/top-bar.hpp b/include/engine/imgui-objects/top-bar.hpp index 4fdc82e..fc61459 100644 --- a/include/engine/imgui-objects/top-bar.hpp +++ b/include/engine/imgui-objects/top-bar.hpp @@ -1,8 +1,9 @@ #pragma once - +#include +#include namespace Engine { struct TopBar { - static void Render(); + static void Render(FileBrowser &browser, FileBrowserState &state); }; } // namespace Engine \ No newline at end of file diff --git a/src/engine/imgui-objects/file-browser.cpp b/src/engine/imgui-objects/file-browser.cpp index ed090d5..55b6df8 100644 --- a/src/engine/imgui-objects/file-browser.cpp +++ b/src/engine/imgui-objects/file-browser.cpp @@ -1,37 +1,35 @@ +#include #include #include #include - namespace Engine { void FileBrowser::Initialize() { - // can be used to set directory using top bar + // can be used to set directory using top bar like vscode does with a button + // in the area } void FileBrowser::UpdateOnWindowResize(FileBrowserState &state, int w, int h) { state.mainWindowWidth = w; state.mainWindowHeight = h; state.mainwindowresize = true; - - // Example: keep 40% width, 60% height + // 40% width, 60% height state.filebrowserWidth = 0.4f * static_cast(w); state.filebrowserHeight = 0.6f * static_cast(h); } - -void FileBrowser::PopulateDirectoryContents(FileBrowserState &state) { - if (state.current_directory.empty()) { - state.current_directory = fs::current_path().string(); - } - std::vector new_contents; +bool FileBrowser::SetRootDirectory(FileBrowserState &state, + const std::string &path) { try { - for (const auto &entry : - fs::directory_iterator(state.current_directory)) { - new_contents.push_back(entry); - } - state.directory_contents = std::move(new_contents); + fs::path p(path); + if (path.empty() || !fs::exists(p) || !fs::is_directory(p)) + return false; + state.root_directory = fs::canonical(p).string(); + state.has_root = true; + state.selected_path.clear(); + return true; } catch (const fs::filesystem_error &e) { - std::cerr << "Error accessing directory: " << e.what() << std::endl; - state.directory_contents.clear(); + std::cerr << "SetRootDirectory error: " << e.what() << std::endl; + return false; } } @@ -40,13 +38,68 @@ void FileBrowser::OpenFile(const std::string &path) { // Opening file logic } -void FileBrowser::Render(FileBrowserState &state) { - // ensure contents are available - if (state.directory_contents.empty()) { - PopulateDirectoryContents(state); +void FileBrowser::RenderDirectoryNode(FileBrowserState &state, + const fs::path &dirPath, int depth) { + // Collect entries (dirs first, then files) + std::vector entries; + try { + for (const auto &entry : fs::directory_iterator(dirPath)) { + entries.push_back(entry); + } + } catch (const fs::filesystem_error &e) { + ImGui::TextDisabled(" ", e.what()); + return; + } + + std::sort(entries.begin(), entries.end(), + [](const fs::directory_entry &a, const fs::directory_entry &b) { + bool ad = a.is_directory(); + bool bd = b.is_directory(); + if (ad != bd) + return ad > bd; // dirs first + return a.path().filename().string() < + b.path().filename().string(); + }); + + for (auto &entry : entries) { + const fs::path &p = entry.path(); + std::string name = p.filename().string(); + bool isDir = entry.is_directory(); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth; + if (!isDir) + flags |= + ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (state.selected_path == p.string()) + flags |= ImGuiTreeNodeFlags_Selected; + + bool open = false; + if (isDir) { + open = ImGui::TreeNodeEx((name + "##" + p.string()).c_str(), flags); + } else { + ImGui::TreeNodeEx((name + "##" + p.string()).c_str(), flags); + } + + if (ImGui::IsItemClicked()) { + state.selected_path = p.string(); + if (!isDir) { + OpenFile(p.string()); + } + } + + if (open && isDir) { + // Limit depth if desired (optional) + if (depth < 64) { + RenderDirectoryNode(state, p, depth + 1); + } else { + ImGui::TextDisabled(" "); + } + ImGui::TreePop(); + } } +} - // Size/pos hints (use FirstUseEver so user can resize later) +void FileBrowser::Render(FileBrowserState &state) { if (state.mainwindowresize) { ImGui::SetNextWindowSize( ImVec2(state.filebrowserWidth, state.filebrowserHeight), @@ -56,70 +109,44 @@ void FileBrowser::Render(FileBrowserState &state) { ImGui::SetNextWindowSize( ImVec2(state.filebrowserWidth, state.filebrowserHeight), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ImVec2(0, 20), - ImGuiCond_FirstUseEver); // below main menu bar + ImGui::SetNextWindowPos(ImVec2(0, 20), ImGuiCond_FirstUseEver); } - ImGui::SetNextWindowSizeConstraints(ImVec2(50, 50), + ImGui::SetNextWindowSizeConstraints(ImVec2(120, 120), ImVec2(FLT_MAX, FLT_MAX)); if (ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse)) { - // Navigation row - if (ImGui::Button("Go Back")) { - fs::path p = state.current_directory; - if (p.has_parent_path()) { - auto parent = p.parent_path(); - if (parent != p) { - state.current_directory = parent.string(); - PopulateDirectoryContents(state); - } + if (!state.has_root) { + ImGui::TextWrapped( + "No root directory set.\nUse File > Open Root... in the top " + "bar to pick one."); + } else { + ImGui::TextWrapped("Root: %s", state.root_directory.c_str()); + if (!state.selected_path.empty()) { + ImGui::TextWrapped("Selected: %s", state.selected_path.c_str()); } - } - ImGui::SameLine(); - ImGui::TextWrapped("Current: %s", state.current_directory.c_str()); - ImGui::Separator(); - - if (ImGui::BeginChild("FileListRegion", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar)) { - std::string selected_dir; - for (const auto &entry : state.directory_contents) { - std::string filename = entry.path().filename().string(); - std::string label; - - if (entry.is_directory()) - label = "[D] " + filename; - else if (entry.is_regular_file()) - label = "[F] " + filename; - else - label = "[?] " + filename; - - label += "##" + entry.path().string(); // unique ID - - if (ImGui::Selectable(label.c_str())) { - if (entry.is_directory()) { - selected_dir = entry.path().string(); - } else if (entry.is_regular_file()) { - OpenFile(entry.path().string()); - } - } + ImGui::Separator(); + + // Draw tree starting at root + fs::path root(state.root_directory); + // root is first node + ImGuiTreeNodeFlags rootFlags = ImGuiTreeNodeFlags_SpanAvailWidth; + if (state.selected_path == state.root_directory) + rootFlags |= ImGuiTreeNodeFlags_Selected; + + std::string rootLabel = + (root.filename().string().empty() ? root.string() + : root.filename().string()) + + std::string("##") + state.root_directory; + bool rootOpen = ImGui::TreeNodeEx(rootLabel.c_str(), rootFlags); + + if (ImGui::IsItemClicked()) { + state.selected_path = state.root_directory; } - if (!selected_dir.empty()) { - try { - if (fs::exists(selected_dir) && - fs::is_directory(selected_dir)) { - state.current_directory = selected_dir; - PopulateDirectoryContents(state); - } else { - PopulateDirectoryContents(state); // refresh - } - } catch (const fs::filesystem_error &e) { - std::cerr << "Error accessing directory: " << e.what() - << std::endl; - PopulateDirectoryContents(state); - } + if (rootOpen) { + RenderDirectoryNode(state, root, 0); + ImGui::TreePop(); } - - ImGui::EndChild(); } } ImGui::End(); diff --git a/src/engine/imgui-objects/top-bar.cpp b/src/engine/imgui-objects/top-bar.cpp index b451e63..2616db4 100644 --- a/src/engine/imgui-objects/top-bar.cpp +++ b/src/engine/imgui-objects/top-bar.cpp @@ -1,11 +1,62 @@ #include #include +#include namespace Engine { -void TopBar::Render() { +void TopBar::Render(FileBrowser &browser, FileBrowserState &state) { + static bool openRootPopup = false; + static char pathBuffer[1024] = {0}; + static bool pathError = false; + if (ImGui::BeginMainMenuBar()) { - // Add menu items + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Open Root...")) { + openRootPopup = true; + pathError = false; + if (state.has_root) + strncpy(pathBuffer, state.root_directory.c_str(), + sizeof(pathBuffer) - 1); + else + pathBuffer[0] = '\0'; + } + if (state.has_root) { + if (ImGui::MenuItem("Clear Root")) { + state.has_root = false; + state.root_directory.clear(); + state.selected_path.clear(); + } + } + ImGui::EndMenu(); + } ImGui::EndMainMenuBar(); } + + if (openRootPopup) { + ImGui::OpenPopup("Set Root Directory"); + openRootPopup = false; + } + + if (ImGui::BeginPopupModal("Set Root Directory", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::InputText("Path", pathBuffer, sizeof(pathBuffer)); + if (pathError) { + ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), + "Invalid directory."); + } + if (ImGui::Button("Set", ImVec2(80, 0))) { + std::string candidate(pathBuffer); + if (browser.SetRootDirectory(state, candidate)) { + ImGui::CloseCurrentPopup(); + } else { + pathError = true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(80, 0))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } } -} // namespace Engine \ No newline at end of file + +} // namespace Engine \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9b23810..e918154 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -123,7 +123,7 @@ SDL_AppResult SDL_AppIterate(void *appState) { gameEngine->GetRenderManager().RenderAll(gameEngine->GetRenderer()); g_ImGui.BeginFrame(); - Engine::TopBar::Render(); + Engine::TopBar::Render(fileBrowser, fileBrowserState); fileBrowser.Render(fileBrowserState); g_ImGui.EndFrame();