2025-05-21 21:38:18 +00:00
|
|
|
// File: src/FileBrowser.cpp
|
2025-05-21 16:49:17 +00:00
|
|
|
#include "FileBrowser.h"
|
|
|
|
#include <filesystem>
|
|
|
|
|
2025-05-21 18:59:27 +00:00
|
|
|
namespace OX
|
|
|
|
{
|
2025-05-21 21:38:18 +00:00
|
|
|
FileBrowser::FileBrowser() = default;
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
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)
|
2025-05-21 18:59:27 +00:00
|
|
|
{
|
2025-05-21 21:38:18 +00:00
|
|
|
ImGui::Begin(title);
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// --- toolbar now contains back button, inline path, filter & view toggle all on one row ---
|
|
|
|
DrawToolbar();
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// 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>
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
ImGui::End();
|
|
|
|
}
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// ——— 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();
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// inline, non-interactive path text
|
|
|
|
ImGui::Text("%s", _currentPath.c_str());
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
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<ResourceTreeNode> &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);
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
for (auto &c: node->children) {
|
|
|
|
if (!_filter.empty() && !PassesFilter(c->name)) continue;
|
|
|
|
if (!c) return;
|
|
|
|
ImGui::PushID(c->path.c_str());
|
|
|
|
ImGui::BeginGroup();
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// 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();
|
2025-05-21 18:59:27 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// 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);
|
|
|
|
|
|
|
|
// thumbnail or icon
|
|
|
|
ImGui::SetCursorScreenPos({mn.x + _cfg.padding, mn.y + _cfg.padding});
|
|
|
|
if (c->isDirectory) {
|
|
|
|
ImGui::Image(GetIconTexture(*c),
|
|
|
|
{_cfg.thumbnailSize, _cfg.thumbnailSize});
|
2025-05-21 18:59:27 +00:00
|
|
|
} else {
|
2025-05-21 21:38:18 +00:00
|
|
|
auto asset = AssetManager::Get(c->path);
|
|
|
|
if (asset && asset->GetTypeName() == "texture2D") {
|
|
|
|
auto tex = std::static_pointer_cast<Texture2D>(asset);
|
|
|
|
ImGui::Image((ImTextureID) (intptr_t) tex->GetID(),
|
|
|
|
{_cfg.thumbnailSize, _cfg.thumbnailSize},
|
|
|
|
{0, 1}, {1, 0});
|
2025-05-21 18:59:27 +00:00
|
|
|
} else {
|
2025-05-21 21:38:18 +00:00
|
|
|
ImGui::Dummy({_cfg.thumbnailSize, _cfg.thumbnailSize});
|
2025-05-21 16:49:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// label
|
|
|
|
ImGui::SetCursorScreenPos({mn.x + _cfg.padding, mx.y - _cfg.labelHeight});
|
|
|
|
ImGui::PushTextWrapPos(mx.x);
|
|
|
|
ImGui::TextUnformatted(c->name.c_str());
|
2025-05-21 18:59:27 +00:00
|
|
|
ImGui::PopTextWrapPos();
|
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// click handling
|
|
|
|
if (clicked) {
|
|
|
|
if (c->isDirectory) {
|
|
|
|
_currentPath = c->path;
|
|
|
|
} else if (_onFileSelected) {
|
|
|
|
_onFileSelected(c->path);
|
|
|
|
}
|
2025-05-21 18:59:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndGroup();
|
2025-05-21 16:49:17 +00:00
|
|
|
ImGui::NextColumn();
|
2025-05-21 18:59:27 +00:00
|
|
|
ImGui::PopID();
|
2025-05-21 16:49:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::Columns(1);
|
|
|
|
ImGui::EndChild();
|
|
|
|
}
|
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
// ——— Simple list view ———
|
|
|
|
void FileBrowser::DrawListView(const std::shared_ptr<ResourceTreeNode> &node)
|
2025-05-21 18:59:27 +00:00
|
|
|
{
|
2025-05-21 21:38:18 +00:00
|
|
|
ImGui::BeginChild("ListRegion", ImVec2(0, 0), false);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2025-05-21 18:59:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
ImGui::EndChild();
|
|
|
|
}
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
bool FileBrowser::PassesFilter(const std::string &name) const
|
|
|
|
{
|
|
|
|
return _filter.empty() ||
|
|
|
|
(name.find(_filter) != std::string::npos);
|
|
|
|
}
|
2025-05-21 16:49:17 +00:00
|
|
|
|
2025-05-21 21:38:18 +00:00
|
|
|
ImTextureID FileBrowser::GetIconTexture(const ResourceTreeNode &node)
|
|
|
|
{
|
|
|
|
return 0;
|
2025-05-21 16:49:17 +00:00
|
|
|
}
|
|
|
|
} // namespace OX
|