Improves asset loading and scene management

Adds scene management system with GameObjects and Components.

Improves asset loading by scanning assets in a background thread and queuing textures for upload. Also displays a progress bar in the file browser for texture loading.

Adds basic component system with TagComponent to identify GameObjects
by a user defined name.
This commit is contained in:
OusmBlueNinja 2025-05-22 11:51:28 -05:00
parent 3ecfa15e4a
commit 9641de9b2c
13 changed files with 536 additions and 97 deletions

View File

@ -100,6 +100,13 @@ add_library(Core STATIC
src/core/systems/assets/Texture2D.cpp
src/core/systems/assets/Texture2D.h
src/core/systems/Shader.cpp
src/core/systems/Scene/Scene.cpp
src/core/systems/Scene/Scene.h
src/core/systems/Scene/GameObject.cpp
src/core/systems/Scene/GameObject.h
src/core/systems/Scene/Components/TagComponent.cpp
src/core/systems/Scene/Components/TagComponent.h
src/core/systems/Scene/Components/Component.h
)
target_include_directories(Core PUBLIC src/core)

View File

@ -16,6 +16,10 @@ namespace OX
std::unordered_map<std::string, std::shared_ptr<Asset> > AssetManager::s_LoadedAssets;
std::unordered_map<std::string, AssetManager::AssetMetadata> AssetManager::s_MetadataMap;
std::atomic<size_t> OX::AssetManager::s_TotalTexturesToLoad{0};
std::atomic<size_t> OX::AssetManager::s_LoadedTexturesCount{0};
// Map from an actual file path to virtual ID
std::unordered_map<std::string, std::string> AssetManager::s_PathToID;
std::mutex AssetManager::s_AssetMutex;
@ -39,13 +43,13 @@ namespace OX
s_FileTree->name = "res://";
s_FileTree->path = "res://";
s_FileTree->isDirectory = true;
s_TotalTexturesToLoad.store(0);
s_LoadedTexturesCount.store(0);
s_ScanThread = std::thread(BackgroundScan);
}
void AssetManager::Shutdown()
{
s_Scanning = false;
if (s_ScanThread.joinable()) {
@ -62,56 +66,76 @@ namespace OX
s_FileTree->name = "res://";
s_FileTree->path = "res://";
s_FileTree->isDirectory = true;
s_TotalTexturesToLoad.store(0);
s_LoadedTexturesCount.store(0);
s_ScanThread = std::thread(BackgroundScan);
}
void AssetManager::BackgroundScan()
{
while (s_Scanning) {
for (auto &entry: fs::recursive_directory_iterator(s_ProjectRoot)) {
namespace fs = std::filesystem;
if (!s_Scanning) break; // Note: This is needed or shutdown takes ages.
size_t totalTextures = 0;
for (auto &entry: fs::recursive_directory_iterator(s_ProjectRoot)) {
if (!s_Scanning) break;
if (!entry.is_directory()) {
if (DetectAssetType(entry.path()) == "texture2D")
++totalTextures;
}
}
s_TotalTexturesToLoad.store(totalTextures, std::memory_order_relaxed);
s_LoadedTexturesCount.store(0, std::memory_order_relaxed);
while (s_Scanning) {
std::vector<fs::directory_entry> allEntries;
allEntries.reserve(1024);
for (auto &entry: fs::recursive_directory_iterator(s_ProjectRoot)) {
if (!s_Scanning) break;
allEntries.push_back(entry);
}
std::vector<std::pair<std::string, AssetMetadata> > newMetadata;
newMetadata.reserve(256);
for (auto &entry: allEntries) {
if (!s_Scanning) break;
const auto &path = entry.path();
std::string resPath = MakeVirtualPath(path);
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<std::mutex> 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] = {"", ""};
}
continue;
} {
std::lock_guard<std::mutex> lock(s_AssetMutex);
if (s_MetadataMap.count(resPath))
continue;
s_MetadataMap.emplace(resPath, AssetMetadata{"", ""});
}
AddToTree(resPath, false);
AddToTree(resPath, false);
if (DetectAssetType(path) == "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) continue;
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<std::mutex> lock(s_AssetMutex);
// now fill in the real metadata
s_MetadataMap[resPath] = {type, path.string()};
}
std::lock_guard<std::mutex> qlock(s_QueueMutex);
s_TextureQueue.push({resPath, w, h, ch, data});
}
newMetadata.emplace_back(resPath, AssetMetadata{"texture2D", path.string()}); {
std::lock_guard<std::mutex> qlock(s_QueueMutex);
s_TextureQueue.push({resPath, w, h, ch, data});
}
}
}
if (!newMetadata.empty()) {
std::lock_guard<std::mutex> lock(s_AssetMutex);
for (auto &kv: newMetadata)
s_MetadataMap[kv.first] = kv.second;
}
if (s_Scanning)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
s_Scanning = false;
@ -124,6 +148,8 @@ namespace OX
while (!s_TextureQueue.empty()) {
auto pending = s_TextureQueue.front();
s_TextureQueue.pop();
s_LoadedTexturesCount.fetch_add(1, std::memory_order_relaxed);
GLuint texID;
glGenTextures(1, &texID);

View File

@ -12,66 +12,85 @@
#include <vector>
#include <cstdint>
namespace OX {
namespace OX
{
class Asset;
struct ResourceTreeNode {
struct ResourceTreeNode
{
std::string name;
std::string path;
bool isDirectory = false;
std::vector<std::shared_ptr<ResourceTreeNode>> children;
std::vector<std::shared_ptr<ResourceTreeNode> > children;
};
class AssetManager {
class AssetManager
{
public:
// -- Lifecycle --
static void Init(const std::string& projectRoot);
static void Init(const std::string &projectRoot);
static void Shutdown();
static void Tick();
static void Rescan();
static size_t GetTotalTexturesToLoad() { return s_TotalTexturesToLoad.load(); }
static size_t GetLoadedTexturesCount() { return s_LoadedTexturesCount.load(); }
// -- Queries --
// Lookup by virtual path ("res://…") or real FS path.
static std::shared_ptr<Asset> Get(const std::string& path);
static std::shared_ptr<Asset> Get(const std::string &path);
static std::shared_ptr<ResourceTreeNode> GetFileTree();
static void SaveAssetPack(const std::string& outputPath);
static std::mutex s_TreeMutex;
static void SaveAssetPack(const std::string &outputPath);
static std::mutex s_TreeMutex;
private:
struct AssetMetadata {
struct AssetMetadata
{
std::string type;
std::string absolutePath;
};
struct PendingTexture {
struct PendingTexture
{
std::string id;
int width, height, channels;
unsigned char* data;
unsigned char *data;
};
static void BackgroundScan();
static void AddToTree(const std::string& virtualPath, bool isDir);
static std::string DetectAssetType(const std::filesystem::path& path);
static std::string MakeVirtualPath(const std::filesystem::path& full);
static void AddToTree(const std::string &virtualPath, bool isDir);
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<std::string, std::string> s_PathToID;
static std::unordered_map<std::string, std::shared_ptr<Asset>> s_LoadedAssets;
static std::unordered_map<std::string, AssetMetadata> s_MetadataMap;
static std::mutex s_AssetMutex;
static std::unordered_map<std::string, std::shared_ptr<Asset> > s_LoadedAssets;
static std::unordered_map<std::string, AssetMetadata> s_MetadataMap;
static std::mutex s_AssetMutex;
// directory tree
static std::shared_ptr<ResourceTreeNode> s_FileTree;
// background scan
static std::filesystem::path s_ProjectRoot;
static std::atomic<bool> s_Scanning;
static std::thread s_ScanThread;
static std::atomic<bool> s_Scanning;
static std::thread s_ScanThread;
// texture upload queue
static std::queue<PendingTexture> s_TextureQueue;
static std::mutex s_QueueMutex;
static std::mutex s_QueueMutex;
static std::atomic<size_t> s_TotalTexturesToLoad;
static std::atomic<size_t> s_LoadedTexturesCount;
};
}

View File

@ -0,0 +1,36 @@
//
// Created by spenc on 5/22/2025.
//
#pragma once
#include <yaml-cpp/yaml.h>
namespace OX
{
class GameObject;
class Component
{
public:
virtual ~Component() = default;
/// Called when this component is attached to a GameObject.
virtual void OnAttach(GameObject *owner) { m_owner = owner; }
/// Called just before this component is detached or destroyed.
virtual void OnDetach() { m_owner = nullptr; }
/// Returns the GameObject this component is attached to (or nullptr).
[[nodiscard]] GameObject *GetOwner() const { return m_owner; }
/// Serialize this components state into a YAML node.
[[nodiscard]] virtual YAML::Node Serialize() const = 0;
/// Restore this components state from a YAML node.
virtual void Deserialize(const YAML::Node &node) = 0;
protected:
GameObject *m_owner = nullptr;
};
};

View File

@ -0,0 +1,23 @@
//
// Created by spenc on 5/22/2025.
//
// src/Components/TagComponent.cpp
#include "TagComponent.h"
namespace OX
{
YAML::Node TagComponent::Serialize() const
{
YAML::Node node;
node["type"] = "TagComponent";
node["name"] = name;
return node;
}
void TagComponent::Deserialize(const YAML::Node &node)
{
if (node["name"])
name = node["name"].as<std::string>();
}
} // namespace OX

View File

@ -0,0 +1,30 @@
// src/h/Components/TagComponent.h
#pragma once
#include <string>
#include <utility>
#include <yaml-cpp/yaml.h>
#include "Component.h"
namespace OX
{
class TagComponent : public Component
{
public:
TagComponent() = default;
explicit TagComponent(std::string tag)
: name(std::move(tag))
{
}
/// The tag string youll look up by
std::string name;
/// Serialize into YAML: writes { type: "TagComponent", name: "<your tag>" }
[[nodiscard]] YAML::Node Serialize() const override;
/// Deserialize from YAML: reads `.name` from node["name"]
void Deserialize(const YAML::Node &node) override;
};
} // namespace OX

View File

@ -0,0 +1,88 @@
// src/GameObject.cpp
#include "GameObject.h"
namespace OX
{
GameObject::GameObject(const std::string &name)
: m_name(name)
{
}
GameObject::~GameObject() = default;
void GameObject::setName(const std::string &name) { m_name = name; }
const std::string &GameObject::name() const { return m_name; }
GameObject *GameObject::parent() const { return m_parent; }
void GameObject::addChild(std::unique_ptr<GameObject> child)
{
child->m_parent = this;
m_children.push_back(std::move(child));
}
std::unique_ptr<GameObject> GameObject::removeChild(GameObject *child)
{
auto it = std::find_if(m_children.begin(), m_children.end(),
[child](const std::unique_ptr<GameObject> &u) { return u.get() == child; });
if (it == m_children.end()) return nullptr;
auto out = std::move(*it);
out->m_parent = nullptr;
m_children.erase(it);
return out;
}
const std::vector<std::unique_ptr<GameObject> > &GameObject::children() const
{
return m_children;
}
void GameObject::addComponent(std::unique_ptr<Component> comp)
{
comp->OnAttach(this);
m_components.push_back(std::move(comp));
}
const std::vector<std::unique_ptr<Component> > &GameObject::components() const
{
return m_components;
}
YAML::Node GameObject::Serialize() const
{
YAML::Node node;
node["name"] = m_name;
for (auto &comp: m_components)
node["components"].push_back(comp->Serialize());
for (auto &child: m_children)
node["children"].push_back(child->Serialize());
return node;
}
void GameObject::Deserialize(const YAML::Node &node)
{
if (node["name"])
m_name = node["name"].as<std::string>();
if (node["components"]) {
for (auto &cnode: node["components"]) {
// e.g.:
// std::string type = cnode["type"].as<std::string>();
// auto comp = ComponentFactory::Create(type);
// comp->Deserialize(cnode);
// addComponent(std::move(comp));
}
}
if (node["children"]) {
for (auto &cnode: node["children"]) {
auto child = std::make_unique<GameObject>();
child->Deserialize(cnode);
addChild(std::move(child));
}
}
}
} // namespace OX

View File

@ -0,0 +1,82 @@
// src/h/GameObject.h
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <yaml-cpp/yaml.h>
#include "Components/Component.h"
namespace OX
{
class GameObject
{
public:
explicit GameObject(const std::string &name = "");
~GameObject();
// Name
void setName(const std::string &name);
const std::string &name() const;
// Hierarchy
GameObject *parent() const;
void addChild(std::unique_ptr<GameObject> child);
std::unique_ptr<GameObject> removeChild(GameObject *child);
const std::vector<std::unique_ptr<GameObject> > &children() const;
// Components
void addComponent(std::unique_ptr<Component> comp);
template<typename T>
T *getComponent() const;
template<typename T>
std::unique_ptr<Component> removeComponent();
const std::vector<std::unique_ptr<Component> > &components() const;
// Serialization
YAML::Node Serialize() const;
void Deserialize(const YAML::Node &node);
private:
std::string m_name;
GameObject *m_parent = nullptr;
std::vector<std::unique_ptr<GameObject> > m_children;
std::vector<std::unique_ptr<Component> > m_components;
};
template<typename T>
T *GameObject::getComponent() const
{
for (auto &c: m_components) {
if (auto ptr = dynamic_cast<T *>(c.get()))
return ptr;
}
return nullptr;
}
template<typename T>
std::unique_ptr<Component> GameObject::removeComponent()
{
auto it = std::find_if(m_components.begin(), m_components.end(),
[](const std::unique_ptr<Component> &c)
{
return dynamic_cast<T *>(c.get()) != nullptr;
});
if (it == m_components.end())
return nullptr;
auto out = std::move(*it);
out->OnDetach();
m_components.erase(it);
return out;
}
} // namespace OX

View File

@ -0,0 +1,60 @@
// src/Scene.cpp
#include "Scene.h"
#include <algorithm> // for std::find_if
namespace OX
{
GameObject *Scene::createRootObject(const std::string &name)
{
auto go = std::make_unique<GameObject>(name);
GameObject *ptr = go.get();
registerByTag(ptr);
m_roots.push_back(std::move(go));
return ptr;
}
bool Scene::removeRootObject(GameObject *go)
{
auto it = std::find_if(
m_roots.begin(), m_roots.end(),
[go](const std::unique_ptr<GameObject> &u) { return u.get() == go; }
);
if (it == m_roots.end())
return false;
unregisterByTag(go);
m_roots.erase(it);
return true;
}
const std::vector<std::unique_ptr<GameObject> > &Scene::roots() const
{
return m_roots;
}
GameObject *Scene::findByTag(const std::string &tag) const
{
auto it = m_tagIndex.find(tag);
return (it != m_tagIndex.end()) ? it->second : nullptr;
}
void Scene::registerByTag(GameObject *go)
{
if (auto tc = go->getComponent<TagComponent>()) {
m_tagIndex[tc->name] = go;
}
for (auto &child: go->children()) {
registerByTag(child.get());
}
}
void Scene::unregisterByTag(GameObject *go)
{
if (auto tc = go->getComponent<TagComponent>()) {
m_tagIndex.erase(tc->name);
}
for (auto &child: go->children()) {
unregisterByTag(child.get());
}
}
} // namespace OX

View File

@ -0,0 +1,44 @@
// src/h/Scene.h
#pragma once
#include <vector>
#include <memory>
#include <unordered_map>
#include <string>
#include "GameObject.h"
#include "Components/TagComponent.h"
#include "systems/MACROS.h"
namespace OX
{
class Scene
{
public:
Scene() = default;
~Scene() = default;
/// Create and own a new root GameObject. Returns the raw pointer.
GameObject *createRootObject(const std::string &name = "");
/// Remove (and destroy) a root object by pointer.
bool removeRootObject(GameObject *go);
/// Access all root objects.
const std::vector<std::unique_ptr<GameObject> > &roots() const;
/// O(1)
GameObject *findByTag(const std::string &tag) const;
/// Must be called whenever a GameObjects tag is set or changed.
void registerByTag(GameObject *go);
/// Must be called before a GameObject is destroyed or its tag changes.
void unregisterByTag(GameObject *go);
private:
std::vector<std::unique_ptr<GameObject> > m_roots;
std::unordered_map<std::string, GameObject *> m_tagIndex;
};
} // namespace OX

View File

@ -44,7 +44,6 @@ namespace OX
fileBrowser = std::make_shared<FileBrowser>();
primaryViewport = std::make_shared<Viewport>();
}
void Editor::Update(Core &core)
@ -153,8 +152,6 @@ namespace OX
// --- Render ImGui onto FBO 0 ---
{
ImGui::EndFrame();
ImGui::Render();
ImGui::UpdatePlatformWindows();
@ -164,11 +161,8 @@ namespace OX
void Editor::Shutdown(Core &core)
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
} // OX

View File

@ -5,40 +5,60 @@
namespace OX
{
FileBrowser::FileBrowser() = default;
FileBrowser::FileBrowser()
: _lastProgress(0.0f)
{
}
FileBrowser::~FileBrowser() = default;
// Call this every frame to report your current loading progress (0.0 → 1.0).
// Passing in 0 resets it and hides the bar.
void FileBrowser::SetProgress(const float p)
{
if (p <= 0.0f) {
_lastProgress = 0.0f;
} else {
_lastProgress = std::max(_lastProgress, p);
}
}
void FileBrowser::SetFilter(const std::string &f) { _filter = f; }
void FileBrowser::SetFileSelectedCallback(FileSelectedCallback cb) { _onFileSelected = std::move(cb); }
void FileBrowser::Draw(const char *title)
{
OX_PROFILE_FUNCTION();
ImGui::Begin(title);
// --- toolbar now contains back button, inline path, filter & view toggle all on one row ---
DrawToolbar();
// main content
// ——— main content ———
auto root = AssetManager::GetFileTree();
std::function<std::shared_ptr<ResourceTreeNode>(std::shared_ptr<ResourceTreeNode>, const std::string &)> find =
[&](auto node, const std::string &path)-> std::shared_ptr<ResourceTreeNode>
[&](auto node, const std::string &path) -> std::shared_ptr<ResourceTreeNode>
{
if (node->path == path) return node;
for (auto &c: node->children) {
if (c->isDirectory) {
if (auto f = find(c, path)) return f;
}
}
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) {
// ——— Grid view with nicer spacing ———
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(_cfg.padding, _cfg.padding));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(_cfg.padding, _cfg.padding));
DrawGridView(node);
ImGui::PopStyleVar(2);
} else {
DrawListView(node);
}
@ -49,7 +69,6 @@ namespace OX
// ——— Combined toolbar & inline path ———
void FileBrowser::DrawToolbar()
{
// back button
if (_currentPath != "res://") {
if (ImGui::Button("Back")) {
auto pos = _currentPath.find_last_of('/');
@ -57,31 +76,34 @@ namespace OX
? _currentPath.substr(0, pos)
: "res://";
}
ImGui::SameLine();
}
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::InputTextWithHint("##filter", "Filter files...", &_filter);
ImGui::PopItemWidth();
ImGui::SameLine();
// grid/list toggle
if (_gridMode) {
if (ImGui::Button("List View")) _gridMode = false;
} else {
if (ImGui::Button("Grid View")) _gridMode = true;
}
size_t total = AssetManager::GetTotalTexturesToLoad();
size_t loaded = AssetManager::GetLoadedTexturesCount();
if (total > 0 && loaded < total) {
float progress = float(loaded) / float(total);
ImGui::SameLine();
ImGui::ProgressBar(progress, ImVec2(-1, 0));
}
ImGui::Separator();
}
@ -89,39 +111,44 @@ namespace OX
void FileBrowser::DrawGridView(const std::shared_ptr<ResourceTreeNode> &node)
{
OX_PROFILE_FUNCTION();
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);
// Add a little padding around the child region
ImGui::BeginChild("GridRegion",
ImVec2(0, 0),
false,
ImGuiWindowFlags_AlwaysUseWindowPadding);
ImGui::Columns(cols, nullptr, false);
std::vector<std::shared_ptr<ResourceTreeNode> > children; {
std::lock_guard<std::mutex> lock(AssetManager::s_TreeMutex); // assume you add this mutex
std::lock_guard<std::mutex> lock(AssetManager::s_TreeMutex);
children = node->children;
}
for (auto &c: children) {
if (!c) return;
for (auto &c: children) {
if (!c) continue;
if (!_filter.empty() && !PassesFilter(c->name)) continue;
ImGui::PushID(c->path.c_str());
ImGui::BeginGroup();
// 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 rect
// background
ImVec2 mn = ImGui::GetItemRectMin();
ImVec2 mx = ImGui::GetItemRectMax();
ImU32 bg = hovered ? _cfg.highlightColor : _cfg.bgColor;
ImGui::GetWindowDrawList()
->AddRectFilled(mn, mx, bg, 4.0f);
// thumbnail or icon
// thumbnail
ImGui::SetCursorScreenPos({mn.x + _cfg.padding, mn.y + _cfg.padding});
if (c->isDirectory) {
ImGui::Image(GetIconTexture(*c),
@ -146,11 +173,10 @@ namespace OX
// click handling
if (clicked) {
if (c->isDirectory) {
if (c->isDirectory)
_currentPath = c->path;
} else if (_onFileSelected) {
else if (_onFileSelected)
_onFileSelected(c->path);
}
}
ImGui::EndGroup();
@ -171,11 +197,10 @@ namespace OX
if (!_filter.empty() && !PassesFilter(c->name)) continue;
if (ImGui::Selectable(c->name.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick)) {
if (ImGui::IsMouseDoubleClicked(0)) {
if (c->isDirectory) {
if (c->isDirectory)
_currentPath = c->path;
} else if (_onFileSelected) {
else if (_onFileSelected)
_onFileSelected(c->path);
}
}
}
}
@ -185,8 +210,7 @@ namespace OX
bool FileBrowser::PassesFilter(const std::string &name) const
{
return _filter.empty() ||
(name.find(_filter) != std::string::npos);
return _filter.empty() || (name.find(_filter) != std::string::npos);
}
ImTextureID FileBrowser::GetIconTexture(const ResourceTreeNode &node)

View File

@ -43,6 +43,8 @@ namespace OX {
/// Called whenever the user clicks on a file (not directory)
void SetFileSelectedCallback(FileSelectedCallback cb);
/// Access to tweak colors/sizes
FileBrowserConfig& Config() { return _cfg; }
@ -52,7 +54,8 @@ namespace OX {
void DrawBreadcrumbs();
void DrawGridView(const std::shared_ptr<ResourceTreeNode>& node);
void DrawListView(const std::shared_ptr<ResourceTreeNode>& node);
bool PassesFilter(const std::string& name) const;
[[nodiscard]] bool PassesFilter(const std::string& name) const;
void SetProgress(float p);
ImTextureID GetIconTexture(const ResourceTreeNode&); // stub for folder/file icons
// state
@ -61,5 +64,8 @@ namespace OX {
std::string _filter;
bool _gridMode = true;
FileSelectedCallback _onFileSelected;
float _lastProgress;
size_t _maxQueueSize;
};
} // namespace OX