Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,4 @@ FodyWeavers.xsd

### VisualStudio Patch ###
# Additional files built by Visual Studio
.vscode/*
37 changes: 37 additions & 0 deletions include/engine/imgui-objects/file-browser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>

namespace fs = std::filesystem;

namespace Engine {

struct FileBrowserState {
std::string root_directory;
bool has_root = false;
std::string selected_path;

// 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);
bool SetRootDirectory(FileBrowserState &state, const std::string &path);

void Render(FileBrowserState &state);

private:
void RenderDirectoryNode(FileBrowserState &state, const fs::path &dirPath,
int depth = 0);
void OpenFile(const std::string &path);
};

} // namespace Engine
9 changes: 9 additions & 0 deletions include/engine/imgui-objects/top-bar.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once
#include <engine/imgui-objects/file-browser.hpp>
#include <string>

namespace Engine {
struct TopBar {
static void Render(FileBrowser &browser, FileBrowserState &state);
};
} // namespace Engine
24 changes: 24 additions & 0 deletions include/engine/imgui-render.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
#include <SDL3/SDL.h>

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
155 changes: 155 additions & 0 deletions src/engine/imgui-objects/file-browser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include <algorithm>
#include <engine/imgui-objects/file-browser.hpp>
#include <imgui.h>
#include <iostream>
namespace Engine {

void FileBrowser::Initialize() {
// 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;
// 40% width, 60% height
state.filebrowserWidth = 0.4f * static_cast<float>(w);
state.filebrowserHeight = 0.6f * static_cast<float>(h);
}
bool FileBrowser::SetRootDirectory(FileBrowserState &state,
const std::string &path) {
try {
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 << "SetRootDirectory error: " << e.what() << std::endl;
return false;
}
}

void FileBrowser::OpenFile(const std::string &path) {
std::cout << "OpenFile: " << path << std::endl;
// Opening file logic
}

void FileBrowser::RenderDirectoryNode(FileBrowserState &state,
const fs::path &dirPath, int depth) {
// Collect entries (dirs first, then files)
std::vector<fs::directory_entry> entries;
try {
for (const auto &entry : fs::directory_iterator(dirPath)) {
entries.push_back(entry);
}
} catch (const fs::filesystem_error &e) {
ImGui::TextDisabled(" <error: %s>", 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(" <max depth reached>");
}
ImGui::TreePop();
}
}
}

void FileBrowser::Render(FileBrowserState &state) {
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);
}
ImGui::SetNextWindowSizeConstraints(ImVec2(120, 120),
ImVec2(FLT_MAX, FLT_MAX));

if (ImGui::Begin("File Browser", nullptr, ImGuiWindowFlags_NoCollapse)) {
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::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 (rootOpen) {
RenderDirectoryNode(state, root, 0);
ImGui::TreePop();
}
}
}
ImGui::End();
}

} // namespace Engine
62 changes: 62 additions & 0 deletions src/engine/imgui-objects/top-bar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <engine/imgui-objects/top-bar.hpp>
#include <imgui.h>
#include <filesystem>

namespace Engine {
void TopBar::Render(FileBrowser &browser, FileBrowserState &state) {
static bool openRootPopup = false;
static char pathBuffer[1024] = {0};
static bool pathError = false;

if (ImGui::BeginMainMenuBar()) {
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
53 changes: 53 additions & 0 deletions src/engine/imgui-render.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <backends/imgui_impl_sdl3.h>
#include <backends/imgui_impl_sdlrenderer3.h>
#include <engine/imgui-render.hpp>
#include <imgui.h>

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::BeginFrame() {
if (!m_initialized) return;
ImGui_ImplSDL3_NewFrame();
ImGui_ImplSDLRenderer3_NewFrame();
ImGui::NewFrame();
}

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
Loading
Loading