diff --git a/src/core/systems/AssetManager.cpp b/src/core/systems/AssetManager.cpp index 6411e6b..1fa0387 100644 --- a/src/core/systems/AssetManager.cpp +++ b/src/core/systems/AssetManager.cpp @@ -1,189 +1,252 @@ +// AssetManager.cpp #include "AssetManager.h" #include "assets/Texture2D.h" #include "Logger.h" + #include #include -#include #include +#include namespace fs = std::filesystem; -namespace OX { +namespace OX +{ + // statics + std::unordered_map > AssetManager::s_LoadedAssets; + std::unordered_map AssetManager::s_MetadataMap; + // Map from actual file path to virtual ID + std::unordered_map AssetManager::s_PathToID; + std::mutex AssetManager::s_AssetMutex; -std::unordered_map> AssetManager::s_LoadedAssets; -std::unordered_map AssetManager::s_MetadataMap; -std::shared_ptr AssetManager::s_FileTree = std::make_shared(); + std::shared_ptr AssetManager::s_FileTree = std::make_shared(); -std::mutex AssetManager::s_TextureQueueMutex; -std::queue AssetManager::s_TextureUploadQueue; + std::filesystem::path AssetManager::s_ProjectRoot; + std::atomic AssetManager::s_Scanning{false}; + std::thread AssetManager::s_ScanThread; -fs::path AssetManager::s_ProjectRoot; -std::atomic AssetManager::s_Scanning = false; -std::thread AssetManager::s_ScanThread; + std::queue AssetManager::s_TextureQueue; + std::mutex AssetManager::s_QueueMutex; -void AssetManager::Init(const std::string& projectRoot) { - s_ProjectRoot = fs::absolute(projectRoot); - s_Scanning = true; + void AssetManager::Init(const std::string &projectRoot) + { + s_ProjectRoot = fs::absolute(projectRoot); + s_Scanning = true; - s_FileTree = std::make_shared(); - s_FileTree->name = "res://"; - s_FileTree->path = "res://"; - s_FileTree->isDirectory = true; - - s_ScanThread = std::thread([=] { - BackgroundScan(s_ProjectRoot); - s_Scanning = false; - }); -} - -void AssetManager::Shutdown() { - if (s_ScanThread.joinable()) - s_ScanThread.join(); -} - -void AssetManager::Tick() { - std::lock_guard lock(s_TextureQueueMutex); - if (!s_TextureUploadQueue.empty()) { - auto pending = s_TextureUploadQueue.front(); - s_TextureUploadQueue.pop(); - - GLuint texID; - glGenTextures(1, &texID); - glBindTexture(GL_TEXTURE_2D, texID); - - GLenum format = pending.channels == 4 ? GL_RGBA : GL_RGB; - glTexImage2D(GL_TEXTURE_2D, 0, format, pending.width, pending.height, 0, format, GL_UNSIGNED_BYTE, pending.data); - glGenerateMipmap(GL_TEXTURE_2D); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - stbi_image_free(pending.data); - - auto tex = std::make_shared(); - tex->SetFromGL(texID, pending.width, pending.height); - s_LoadedAssets[pending.id] = tex; - Logger::LogInfo("%u | %u", tex->GetID(), texID); + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; + s_ScanThread = std::thread(BackgroundScan); } -} -void AssetManager::Rescan() { - if (s_Scanning) return; - s_Scanning = true; + void AssetManager::Shutdown() + { + if (s_ScanThread.joinable()) { + s_ScanThread.join(); + } + } - s_FileTree = std::make_shared(); - s_FileTree->name = "res://"; - s_FileTree->path = "res://"; - s_FileTree->isDirectory = true; + void AssetManager::Rescan() + { + if (s_Scanning) return; + s_Scanning = true; - s_ScanThread = std::thread([=] { - BackgroundScan(s_ProjectRoot); - s_Scanning = false; - }); -} + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; -void AssetManager::BackgroundScan(const fs::path& root) { - for (const auto& entry : fs::recursive_directory_iterator(root)) { - const auto& path = entry.path(); - std::string resPath = MakeVirtualPath(path); + s_ScanThread = std::thread(BackgroundScan); + } - if (entry.is_directory()) { - AddToTree(resPath, true); - } else { - AddToTree(resPath, false); + void AssetManager::BackgroundScan() + { + while (s_Scanning) { + for (auto &entry: fs::recursive_directory_iterator(s_ProjectRoot)) { + const auto &path = entry.path(); + std::string resPath = MakeVirtualPath(path); - std::string type = DetectAssetType(path); - if (!type.empty()) { - if (type == "texture2D") { - int w, h, ch; - stbi_set_flip_vertically_on_load(1); - unsigned char* data = stbi_load(path.string().c_str(), &w, &h, &ch, 0); - if (data) { - std::lock_guard lock(s_TextureQueueMutex); - s_TextureUploadQueue.push({ resPath, path.string(), w, h, ch, data }); - s_MetadataMap[resPath] = { type, path.string() }; + if (entry.is_directory()) { + // you can also guard directories if you like: + // std::lock_guard g(s_AssetMutex); + // if (s_MetadataMap.count(resPath)) continue; + AddToTree(resPath, true); + } else { + { + // quick check: have we already seen this asset? + std::lock_guard lock(s_AssetMutex); + if (s_MetadataMap.find(resPath) != s_MetadataMap.end()) + continue; + // reserve a spot so other threads know it's in-flight + s_MetadataMap[resPath] = {"", ""}; + } + + AddToTree(resPath, false); + + auto type = DetectAssetType(path); + if (type == "texture2D") { + int w, h, ch; + stbi_set_flip_vertically_on_load(1); + unsigned char *data = stbi_load( + path.string().c_str(), &w, &h, &ch, 0 + ); + if (data) { + { + std::lock_guard lock(s_AssetMutex); + // now fill in the real metadata + s_MetadataMap[resPath] = { type, path.string() }; + } + std::lock_guard qlock(s_QueueMutex); + s_TextureQueue.push({resPath, w, h, ch, data}); + } } } } } + + s_Scanning = false; } -} -void AssetManager::AddToTree(const std::string& resPath, bool isDir) { - std::vector parts; - size_t pos = 0, next; - while ((next = resPath.find('/', pos)) != std::string::npos) { - if (next != pos) parts.push_back(resPath.substr(pos, next - pos)); - pos = next + 1; - } - if (pos < resPath.length()) parts.push_back(resPath.substr(pos)); - auto current = s_FileTree; - for (size_t i = 1; i < parts.size(); ++i) { - auto& name = parts[i]; - auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto& child) { - return child->name == name; - }); + void AssetManager::Tick() + { + std::lock_guard qlock(s_QueueMutex); + while (!s_TextureQueue.empty()) { + auto pending = s_TextureQueue.front(); + s_TextureQueue.pop(); + + GLuint texID; + glGenTextures(1, &texID); + glBindTexture(GL_TEXTURE_2D, texID); + GLenum fmt = (pending.channels == 4 ? GL_RGBA : GL_RGB); + glTexImage2D(GL_TEXTURE_2D, 0, fmt, pending.width, pending.height, 0, fmt, GL_UNSIGNED_BYTE, pending.data); + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + stbi_image_free(pending.data); + + auto tex = std::make_shared(); + tex->SetFromGL(texID, pending.width, pending.height); + + std::lock_guard lock(s_AssetMutex); + // Store asset by ID + s_LoadedAssets[pending.id] = tex; + // Also map original file path to this ID + auto meta = s_MetadataMap[pending.id]; + s_PathToID[meta.absolutePath] = pending.id; - if (it == current->children.end()) { - auto newNode = std::make_shared(); - newNode->name = name; - newNode->path = current->path + "/" + name; - newNode->isDirectory = (i < parts.size() - 1) || isDir; - current->children.push_back(newNode); - current = newNode; - } else { - current = *it; } } -} -std::string AssetManager::MakeVirtualPath(const fs::path& full) { - fs::path rel = fs::relative(full, s_ProjectRoot); - return "res://" + rel.generic_string(); -} + // Get asset by virtual ID or file path + std::shared_ptr AssetManager::Get(const std::string &keyOrPath) + { + std::string key = keyOrPath; + // Normalize accidental triple slashes in resource path: "res:///..." -> "res://..." + const std::string triple = "res:///"; + if (key.rfind(triple, 0) == 0) { + key = std::string("res://") + key.substr(triple.size()); + } -std::string AssetManager::DetectAssetType(const fs::path& ext) { - auto e = ext.extension().string(); - if (e == ".png" || e == ".jpg") return "texture2D"; - return ""; -} + std::lock_guard lock(s_AssetMutex); -std::shared_ptr AssetManager::Get(const std::string& resPath) { - auto it = s_LoadedAssets.find(resPath); - return (it != s_LoadedAssets.end()) ? it->second : nullptr; -} - -void AssetManager::SaveAssetPack(const std::string& outputPath) { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq; - - for (const auto& [resPath, meta] : s_MetadataMap) { - fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot); - - out << YAML::BeginMap; - out << YAML::Key << "id" << YAML::Value << resPath; - out << YAML::Key << "type" << YAML::Value << meta.type; - out << YAML::Key << "path" << YAML::Value << relative.generic_string(); - out << YAML::EndMap; + // 1) Direct ID lookup + auto it = s_LoadedAssets.find(key); + if (it != s_LoadedAssets.end()) { + return it->second; + } + // 2) Path-to-ID mapping + auto pit = s_PathToID.find(key); + if (pit != s_PathToID.end()) { + auto ait = s_LoadedAssets.find(pit->second); + if (ait != s_LoadedAssets.end()) { + return ait->second; + } + } + // 3) Convert filesystem path to virtual and lookup + try { + std::string virt = MakeVirtualPath(fs::absolute(key)); + // Normalize if virt had leading slash + if (!virt.empty() && virt.front() == '/') virt.erase(0, 1); + auto vit = s_LoadedAssets.find(virt); + if (vit != s_LoadedAssets.end()) { + return vit->second; + } + } catch (...) { + // ignore + } + return nullptr; } - out << YAML::EndSeq; - out << YAML::EndMap; - std::ofstream fout(outputPath); - fout << out.c_str(); - fout.close(); + std::shared_ptr AssetManager::GetFileTree() + { + return s_FileTree; + } - Logger::LogInfo("Saved asset pack: %s", outputPath.c_str()); -} + void AssetManager::SaveAssetPack(const std::string &outputPath) + { + YAML::Emitter out; + out << YAML::BeginMap << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq; { + std::lock_guard lock(s_AssetMutex); + for (auto &[id, meta]: s_MetadataMap) { + fs::path rel = fs::relative(meta.absolutePath, s_ProjectRoot); + out << YAML::BeginMap + << YAML::Key << "id" << YAML::Value << id + << YAML::Key << "type" << YAML::Value << meta.type + << YAML::Key << "path" << YAML::Value << rel.generic_string() + << YAML::EndMap; + } + } + out << YAML::EndSeq << YAML::EndMap; -std::shared_ptr AssetManager::GetFileTree() { - return s_FileTree; -} + std::ofstream fout(outputPath); + fout << out.c_str(); + Logger::LogInfo("Saved asset pack: %s", outputPath.c_str()); + } + std::string AssetManager::MakeVirtualPath(const fs::path &full) + { + auto rel = fs::relative(full, s_ProjectRoot); + return "res://" + rel.generic_string(); + } + + void AssetManager::AddToTree(const std::string &resPath, bool isDir) + { + std::vector parts; + size_t pos = 0, next; + while ((next = resPath.find('/', pos)) != std::string::npos) { + if (next != pos) parts.push_back(resPath.substr(pos, next - pos)); + pos = next + 1; + } + if (pos < resPath.size()) parts.push_back(resPath.substr(pos)); + + auto current = s_FileTree; + for (size_t i = 1; i < parts.size(); ++i) { + auto &name = parts[i]; + auto it = std::find_if(current->children.begin(), current->children.end(), + [&](auto &c) { return c->name == name; }); + if (it == current->children.end()) { + auto node = std::make_shared(); + node->name = name; + node->path = current->path + "/" + name; + node->isDirectory = (i + 1 < parts.size()) || isDir; + current->children.push_back(node); + current = node; + } else { + current = *it; + } + } + } + + std::string AssetManager::DetectAssetType(const fs::path &path) + { + auto e = path.extension().string(); + if (e == ".png" || e == ".jpg" || e == ".jpeg") return "texture2D"; + return ""; + } } // namespace OX diff --git a/src/core/systems/AssetManager.h b/src/core/systems/AssetManager.h index a6bcca8..c9daae5 100644 --- a/src/core/systems/AssetManager.h +++ b/src/core/systems/AssetManager.h @@ -1,16 +1,18 @@ +/// AssetManager.h #pragma once + #include #include #include -#include #include #include #include #include #include +#include +#include namespace OX { - class Asset; struct ResourceTreeNode { @@ -22,12 +24,16 @@ namespace OX { class AssetManager { public: + // -- Lifecycle -- static void Init(const std::string& projectRoot); static void Shutdown(); - static void Tick(); // Main-thread + static void Tick(); static void Rescan(); - static std::shared_ptr Get(const std::string& resPath); + // -- Queries -- + // Lookup by virtual path ("res://…") or real FS path. + static std::shared_ptr Get(const std::string& path); + static std::shared_ptr GetFileTree(); static void SaveAssetPack(const std::string& outputPath); @@ -36,29 +42,33 @@ namespace OX { std::string type; std::string absolutePath; }; - struct PendingTexture { std::string id; - std::string path; int width, height, channels; unsigned char* data; }; - static void BackgroundScan(const std::filesystem::path& root); + static void BackgroundScan(); static void AddToTree(const std::string& virtualPath, bool isDir); - static std::string DetectAssetType(const std::filesystem::path& ext); + static std::string DetectAssetType(const std::filesystem::path& path); static std::string MakeVirtualPath(const std::filesystem::path& full); + // loaded assets & metadata + static std::unordered_map s_PathToID; static std::unordered_map> s_LoadedAssets; - static std::unordered_map s_MetadataMap; + static std::unordered_map s_MetadataMap; + static std::mutex s_AssetMutex; + + // directory tree static std::shared_ptr s_FileTree; - static std::mutex s_TextureQueueMutex; - static std::queue s_TextureUploadQueue; - + // background scan static std::filesystem::path s_ProjectRoot; - static std::atomic s_Scanning; - static std::thread s_ScanThread; - }; + static std::atomic s_Scanning; + static std::thread s_ScanThread; -} // namespace OX + // texture upload queue + static std::queue s_TextureQueue; + static std::mutex s_QueueMutex; + }; +} diff --git a/src/core/systems/assets/Texture2D.h b/src/core/systems/assets/Texture2D.h index 16d548d..b9a5c32 100644 --- a/src/core/systems/assets/Texture2D.h +++ b/src/core/systems/assets/Texture2D.h @@ -4,7 +4,6 @@ #pragma once #include "systems/Asset.h" #include -#include "GL/glew.h" namespace OX { class Texture2D : public Asset { @@ -17,7 +16,7 @@ namespace OX { void SetFromGL(uint32_t texID, int width, int height); - GLuint GetID() const { return m_TextureID; } + uint32_t GetID() const { return m_TextureID; } int GetWidth() const { return m_Width; } int GetHeight() const { return m_Height; } diff --git a/src/editor/Editor.cpp b/src/editor/Editor.cpp index 869781c..21c5fdd 100644 --- a/src/editor/Editor.cpp +++ b/src/editor/Editor.cpp @@ -36,7 +36,8 @@ namespace OX ImGui_ImplGlfw_InitForOpenGL(core.GetWindow().GetHandle(), true); ImGui_ImplOpenGL3_Init("#version 330 core"); - primaryViewport = new Viewport(); // The first time ive ever use the new keywork... + primaryViewport = new Viewport(); // The first time ive ever use the new keyword... + fileBrowser = new FileBrowser(); } void Editor::Update(Core &core) @@ -76,7 +77,7 @@ namespace OX ImGui::DockSpace(dockspaceID, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode); LoggerWindow::Draw(); - FileBrowser::Draw(); + fileBrowser->Draw(); primaryViewport->Draw(core); @@ -159,6 +160,8 @@ namespace OX delete primaryViewport; primaryViewport = nullptr; + delete fileBrowser; + fileBrowser = nullptr; Logger::LogOk("Editor::Shutdown"); ImGui_ImplOpenGL3_Shutdown(); diff --git a/src/editor/Editor.h b/src/editor/Editor.h index 7834577..bd1b7d8 100644 --- a/src/editor/Editor.h +++ b/src/editor/Editor.h @@ -12,6 +12,7 @@ namespace OX { class Viewport; + class FileBrowser; class Editor final : public Layer { @@ -28,6 +29,7 @@ namespace OX private: Viewport* primaryViewport; + FileBrowser* fileBrowser; }; } // OX diff --git a/src/editor/Windows/FileBrowser.cpp b/src/editor/Windows/FileBrowser.cpp index 92509cd..6218871 100644 --- a/src/editor/Windows/FileBrowser.cpp +++ b/src/editor/Windows/FileBrowser.cpp @@ -1,99 +1,147 @@ +// File: src/FileBrowser.cpp #include "FileBrowser.h" -#include "systems/AssetManager.h" -#include "systems/assets/Texture2D.h" -#include "imgui.h" #include namespace OX { - static std::string s_CurrentPath = "res://"; + FileBrowser::FileBrowser() = default; - static void DrawGrid(const std::shared_ptr &node) + FileBrowser::~FileBrowser() = default; + + void FileBrowser::SetFilter(const std::string &f) { _filter = f; } + void FileBrowser::SetFileSelectedCallback(FileSelectedCallback cb) { _onFileSelected = std::move(cb); } + + void FileBrowser::Draw(const char *title) { - const float thumbSize = 64.0f; - const float padding = 8.0f; - const float labelHeight = 20.0f; - const float cellWidth = thumbSize + padding * 2; - const float cellHeight = thumbSize + labelHeight + padding; + ImGui::Begin(title); - ImVec2 region = ImGui::GetContentRegionAvail(); - int columns = std::max(1, (int) (region.x / cellWidth)); + // --- toolbar now contains back button, inline path, filter & view toggle all on one row --- + DrawToolbar(); - ImGui::BeginChild("FileGrid", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding); - ImGui::Columns(columns, nullptr, false); + // main content + auto root = AssetManager::GetFileTree(); + std::function(std::shared_ptr, const std::string &)> find = + [&](auto node, const std::string &path)-> std::shared_ptr + { + if (node->path == path) return node; + for (auto &c: node->children) { + if (c->isDirectory) { + if (auto f = find(c, path)) return f; + } + } + return nullptr; + }; + auto node = find(root, _currentPath); + if (!node) { + ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_TextDisabled], + "Path not found: %s", _currentPath.c_str()); + } else if (_gridMode) { + DrawGridView(node); + } else { + DrawListView(node); + } - for (const auto &child: node->children) { - ImGui::PushID(child->path.c_str()); // Unique ID per item + ImGui::End(); + } + // ——— Combined toolbar & inline path ——— + void FileBrowser::DrawToolbar() + { + // back button + if (_currentPath != "res://") { + if (ImGui::Button("Back")) { + auto pos = _currentPath.find_last_of('/'); + _currentPath = (pos != std::string::npos && pos > 6) + ? _currentPath.substr(0, pos) + : "res://"; + } + } + ImGui::SameLine(); + + // inline, non-interactive path text + ImGui::Text("%s", _currentPath.c_str()); + + ImGui::SameLine(); + + // filter input takes all remaining space + float avail = ImGui::GetContentRegionAvail().x; + ImGui::PushItemWidth(avail * 0.5f); + //ImGui::InputTextWithHint("##filter", "Filter files...", _filter.c_str(), ImGuiInputTextFlags_AutoSelectAll); + + + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + // grid/list toggle + if (_gridMode) { + if (ImGui::Button("List View")) _gridMode = false; + } else { + if (ImGui::Button("Grid View")) _gridMode = true; + } + + ImGui::Separator(); + } + + // ——— Polished grid view ——— + void FileBrowser::DrawGridView(const std::shared_ptr &node) + { + const float cellW = _cfg.thumbnailSize + _cfg.padding * 2; + ImVec2 avail = ImGui::GetContentRegionAvail(); + int cols = std::max(1, int(avail.x / cellW)); + + ImGui::BeginChild("GridRegion", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding); + ImGui::Columns(cols, nullptr, false); + + for (auto &c: node->children) { + if (!_filter.empty() && !PassesFilter(c->name)) continue; + if (!c) return; + ImGui::PushID(c->path.c_str()); ImGui::BeginGroup(); - std::string tooltip; - std::string label = child->name; - ImVec2 cellSize(cellWidth, cellHeight); - - ImGui::InvisibleButton("Cell", cellSize); + // invisible button as hit target + ImVec2 btnSize(cellW, _cfg.thumbnailSize + _cfg.labelHeight + _cfg.padding); + ImGui::InvisibleButton("cell", btnSize); bool hovered = ImGui::IsItemHovered(); bool clicked = ImGui::IsItemClicked(); - // Background - ImVec2 min = ImGui::GetItemRectMin(); - ImVec2 max = ImGui::GetItemRectMax(); - ImGui::GetWindowDrawList()->AddRectFilled(min, max, IM_COL32(40, 40, 40, 255), 4.0f); + // background rect + ImVec2 mn = ImGui::GetItemRectMin(); + ImVec2 mx = ImGui::GetItemRectMax(); + ImU32 bg = hovered ? _cfg.highlightColor : _cfg.bgColor; + ImGui::GetWindowDrawList() + ->AddRectFilled(mn, mx, bg, 4.0f); - ImVec2 center = ImVec2(min.x + padding, min.y + padding); - - // === Thumbnail === - if (child->isDirectory) { - ImGui::SetCursorScreenPos(center); - ImGui::Text("[Folder]"); + // thumbnail or icon + ImGui::SetCursorScreenPos({mn.x + _cfg.padding, mn.y + _cfg.padding}); + if (c->isDirectory) { + ImGui::Image(GetIconTexture(*c), + {_cfg.thumbnailSize, _cfg.thumbnailSize}); } else { - auto asset = AssetManager::Get(child->path); - if (asset) { - std::string type = asset->GetTypeName(); - if (type == "texture2D") { - auto tex = std::static_pointer_cast(asset); - if (tex->GetID() != 0) { - ImGui::SetCursorScreenPos(center); - ImGui::Image((ImTextureID) (intptr_t) tex->GetID(), ImVec2(thumbSize, thumbSize), - ImVec2(0, 1), ImVec2(1, 0)); - - tooltip += "Name: " + child->name + "\n"; - tooltip += "ID: " + std::to_string(tex->GetID()) + "\n"; - tooltip += "Size: " + std::to_string(tex->GetWidth()) + "x" + std::to_string( - tex->GetHeight()) + "\n"; - tooltip += "Path: " + child->path + "\n"; - - label += " (ID: " + std::to_string(tex->GetID()) + ")"; - } else { - ImGui::SetCursorScreenPos(center); - ImGui::Text("[Loading]"); - } - } else { - ImGui::SetCursorScreenPos(center); - ImGui::Text("[Type: %s]", type.c_str()); - } + auto asset = AssetManager::Get(c->path); + if (asset && asset->GetTypeName() == "texture2D") { + auto tex = std::static_pointer_cast(asset); + ImGui::Image((ImTextureID) (intptr_t) tex->GetID(), + {_cfg.thumbnailSize, _cfg.thumbnailSize}, + {0, 1}, {1, 0}); } else { - ImGui::SetCursorScreenPos(center); - ImGui::Text("[Unloaded]"); + ImGui::Dummy({_cfg.thumbnailSize, _cfg.thumbnailSize}); } } - - // === Label === - ImGui::SetCursorScreenPos(ImVec2(min.x + padding, max.y - labelHeight)); - ImGui::PushTextWrapPos(min.x + cellWidth); - ImGui::TextUnformatted(label.c_str()); + // label + ImGui::SetCursorScreenPos({mn.x + _cfg.padding, mx.y - _cfg.labelHeight}); + ImGui::PushTextWrapPos(mx.x); + ImGui::TextUnformatted(c->name.c_str()); ImGui::PopTextWrapPos(); - // Click handling - if (clicked && child->isDirectory) { - s_CurrentPath = child->path; - } - - if (hovered && !tooltip.empty()) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted(tooltip.c_str()); - ImGui::EndTooltip(); + // click handling + if (clicked) { + if (c->isDirectory) { + _currentPath = c->path; + } else if (_onFileSelected) { + _onFileSelected(c->path); + } } ImGui::EndGroup(); @@ -105,49 +153,35 @@ namespace OX ImGui::EndChild(); } - - static std::shared_ptr FindNode(const std::shared_ptr &root, - const std::string &targetPath) + // ——— Simple list view ——— + void FileBrowser::DrawListView(const std::shared_ptr &node) { - if (root->path == targetPath) return root; - for (const auto &child: root->children) { - if (child->isDirectory) { - auto found = FindNode(child, targetPath); - if (found) return found; - } - } - return nullptr; - } + ImGui::BeginChild("ListRegion", ImVec2(0, 0), false); - void FileBrowser::Draw() - { - ImGui::Begin("File Browser"); - - // === Back Button === - if (s_CurrentPath != "res://") { - if (ImGui::Button("..")) { - size_t lastSlash = s_CurrentPath.find_last_of('/'); - if (lastSlash != std::string::npos && lastSlash > 6) { - // skip 'res://' - s_CurrentPath = s_CurrentPath.substr(0, lastSlash); - } else { - s_CurrentPath = "res://"; + for (auto &c: node->children) { + if (!_filter.empty() && !PassesFilter(c->name)) continue; + if (ImGui::Selectable(c->name.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick)) { + if (ImGui::IsMouseDoubleClicked(0)) { + if (c->isDirectory) { + _currentPath = c->path; + } else if (_onFileSelected) { + _onFileSelected(c->path); + } } } } - ImGui::SameLine(); - ImGui::Text("Current Path: %s", s_CurrentPath.c_str()); - ImGui::Separator(); + ImGui::EndChild(); + } - auto root = AssetManager::GetFileTree(); - auto currentNode = FindNode(root, s_CurrentPath); - if (currentNode) { - DrawGrid(currentNode); - } else { - ImGui::Text("Path not found: %s", s_CurrentPath.c_str()); - } + bool FileBrowser::PassesFilter(const std::string &name) const + { + return _filter.empty() || + (name.find(_filter) != std::string::npos); + } - ImGui::End(); + ImTextureID FileBrowser::GetIconTexture(const ResourceTreeNode &node) + { + return 0; } } // namespace OX diff --git a/src/editor/Windows/FileBrowser.h b/src/editor/Windows/FileBrowser.h index 1e34df9..fac5cef 100644 --- a/src/editor/Windows/FileBrowser.h +++ b/src/editor/Windows/FileBrowser.h @@ -1,12 +1,65 @@ +// File: src/FileBrowser.h #pragma once +#include #include +#include +#include +#include "systems/AssetManager.h" // for ResourceTreeNode +#include "systems/assets/Texture2D.h" // for Texture2D +#include "imgui.h" namespace OX { - class FileBrowser { - public: - static void Draw(); + /// Configuration for look & feel + struct FileBrowserConfig + { + float thumbnailSize = 64.0f; + float padding = 8.0f; + float labelHeight = 20.0f; + ImU32 bgColor = IM_COL32(40,40,40,255); + ImU32 highlightColor = IM_COL32(75,75,75,255); + bool showGrid = true; + bool showThumbnails = true; + bool allowMultiple = false; + // … add more theming or behavioral flags here }; + /// FileBrowser widget, supports grid & list, filtering, breadcrumbs, callbacks. + class FileBrowser + { + public: + using FileSelectedCallback = std::function; + + FileBrowser(); + ~FileBrowser(); + + /// Draw the entire browser window + void Draw(const char* title = "File Browser"); + + /// Set a filter string (wildcards, substrings, etc.) + void SetFilter(const std::string& filter); + + /// Called whenever the user clicks on a file (not directory) + void SetFileSelectedCallback(FileSelectedCallback cb); + + /// Access to tweak colors/sizes + FileBrowserConfig& Config() { return _cfg; } + + private: + // helpers + void DrawToolbar(); + void DrawBreadcrumbs(); + void DrawGridView(const std::shared_ptr& node); + void DrawListView(const std::shared_ptr& node); + bool PassesFilter(const std::string& name) const; + ImTextureID GetIconTexture(const ResourceTreeNode&); // stub for folder/file icons + + // state + FileBrowserConfig _cfg; + std::string _currentPath = "res://"; + std::string _filter; + bool _gridMode = true; + FileSelectedCallback _onFileSelected; + }; } // namespace OX