Optimised FileExplorere Window

This commit is contained in:
OusmBlueNinja 2025-05-12 12:15:03 -05:00
parent 0eca42dd94
commit cfcfafd5a0
13 changed files with 679 additions and 354 deletions

7
.idea/dictionaries/project.xml generated Normal file
View File

@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>lerp</w>
</words>
</dictionary>
</component>

View File

@ -25,7 +25,9 @@ file(GLOB_RECURSE SOURCE_FILES
)
# --- Define target ---
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
add_executable(${PROJECT_NAME} ${SOURCE_FILES}
src/src/core/utils/PrimitiveGenerator.cpp
src/src/core/utils/PrimitiveGenerator.h)
# --- Include directories for this target ---
target_include_directories(${PROJECT_NAME} PRIVATE

View File

@ -10,52 +10,52 @@ Collapsed=1
[Window][WindowOverViewport_11111111]
Pos=0,19
Size=32,32
Size=1920,1158
Collapsed=0
[Window][Inspector]
Pos=17,19
Size=15,17
Pos=1529,19
Size=391,653
Collapsed=0
DockId=0x0000001B,0
[Window][Scene Tree]
Pos=0,19
Size=7,15
Size=340,612
Collapsed=0
DockId=0x0000000F,0
[Window][Viewport]
Pos=9,19
Size=6,7
Pos=342,19
Size=1185,612
Collapsed=0
DockId=0x00000011,0
DockId=0x00000010,0
[Window][##MainMenuBar]
Size=1920,19
Collapsed=0
[Window][Performance Info]
Pos=25,45
Size=7,7
Pos=1587,674
Size=333,307
Collapsed=0
DockId=0x00000015,0
DockId=0x00000013,0
[Window][Console]
Pos=9,27
Size=6,7
Pos=0,633
Size=760,544
Collapsed=0
DockId=0x00000012,0
DockId=0x00000011,0
[Window][Tilemap Editor]
Pos=265,19
Size=1263,674
Collapsed=0
DockId=0x00000011,1
DockId=0x00000010,1
[Window][Profiler]
Pos=343,955
Size=1232,222
Pos=0,850
Size=1920,327
Collapsed=0
DockId=0x00000008,0
@ -78,8 +78,8 @@ Collapsed=0
DockId=0x00000008,1
[Window][Color Correction]
Pos=25,49
Size=7,7
Pos=1587,1031
Size=333,146
Collapsed=0
DockId=0x00000016,0
@ -113,8 +113,8 @@ Collapsed=0
DockId=0x0000000E,0
[Window][Audio Output]
Pos=17,45
Size=6,7
Pos=1529,674
Size=56,503
Collapsed=0
DockId=0x00000019,0
@ -136,10 +136,10 @@ Size=550,695
Collapsed=0
[Window][Lua Globals]
Pos=17,38
Size=15,7
Pos=1587,983
Size=333,46
Collapsed=0
DockId=0x00000013,0
DockId=0x00000014,0
[Window][Import Assets]
Pos=626,263
@ -152,10 +152,10 @@ Size=600,209
Collapsed=0
[Window][File Explorer]
Pos=0,36
Size=15,15
Pos=762,633
Size=765,544
Collapsed=0
DockId=0x0000001E,0
DockId=0x00000012,0
[Window][ConfirmClearScene]
Pos=808,551
@ -197,30 +197,30 @@ Column 1 Width=86
Column 2 Weight=1.0000
[Docking][Data]
DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=32,32 Split=X
DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1920,1158 Split=X
DockNode ID=0x00000005 Parent=0x11111111 SizeRef=989,1158 Split=X
DockNode ID=0x00000001 Parent=0x00000005 SizeRef=341,701 Split=Y Selected=0x12EF0F59
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=342,637 HiddenTabBar=1 Selected=0x12EF0F59
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=342,519 HiddenTabBar=1 Selected=0x36AF052B
DockNode ID=0x00000002 Parent=0x00000005 SizeRef=1577,701 Split=Y Selected=0xC450F867
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=606,684 Split=X Selected=0xC450F867
DockNode ID=0x00000007 Parent=0x00000002 SizeRef=606,829 Split=X Selected=0xC450F867
DockNode ID=0x00000017 Parent=0x00000007 SizeRef=1184,860 Split=Y Selected=0xC450F867
DockNode ID=0x0000001D Parent=0x00000017 SizeRef=1208,763 Split=X Selected=0xC450F867
DockNode ID=0x0000001D Parent=0x00000017 SizeRef=1208,283 Split=X Selected=0xC450F867
DockNode ID=0x0000000F Parent=0x0000001D SizeRef=340,399 HiddenTabBar=1 Selected=0x12EF0F59
DockNode ID=0x00000010 Parent=0x0000001D SizeRef=1185,399 Split=Y Selected=0xC450F867
DockNode ID=0x00000011 Parent=0x00000010 SizeRef=1185,526 CentralNode=1 HiddenTabBar=1 Selected=0xC450F867
DockNode ID=0x00000012 Parent=0x00000010 SizeRef=1185,235 HiddenTabBar=1 Selected=0xEA83D666
DockNode ID=0x0000001E Parent=0x00000017 SizeRef=1208,393 HiddenTabBar=1 Selected=0x9C2B5678
DockNode ID=0x00000010 Parent=0x0000001D SizeRef=1185,399 CentralNode=1 HiddenTabBar=1 Selected=0xC450F867
DockNode ID=0x0000001E Parent=0x00000017 SizeRef=1208,544 Split=X Selected=0x9C2B5678
DockNode ID=0x00000011 Parent=0x0000001E SizeRef=760,399 HiddenTabBar=1 Selected=0xEA83D666
DockNode ID=0x00000012 Parent=0x0000001E SizeRef=765,399 HiddenTabBar=1 Selected=0x9C2B5678
DockNode ID=0x00000018 Parent=0x00000007 SizeRef=391,860 Split=Y Selected=0x36DC96AB
DockNode ID=0x0000001B Parent=0x00000018 SizeRef=367,636 HiddenTabBar=1 Selected=0x36DC96AB
DockNode ID=0x0000001C Parent=0x00000018 SizeRef=367,520 Split=Y Selected=0x8CFF897F
DockNode ID=0x00000013 Parent=0x0000001C SizeRef=367,135 HiddenTabBar=1 Selected=0x8CFF897F
DockNode ID=0x00000014 Parent=0x0000001C SizeRef=367,383 Split=X Selected=0x56009A08
DockNode ID=0x00000019 Parent=0x00000014 SizeRef=56,70 HiddenTabBar=1 Selected=0x56009A08
DockNode ID=0x0000001A Parent=0x00000014 SizeRef=333,70 Split=Y Selected=0x3FC1A724
DockNode ID=0x00000015 Parent=0x0000001A SizeRef=181,220 HiddenTabBar=1 Selected=0x3FC1A724
DockNode ID=0x00000016 Parent=0x0000001A SizeRef=181,161 HiddenTabBar=1 Selected=0xA873C17F
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=606,472 HiddenTabBar=1 Selected=0xEA83D666
DockNode ID=0x0000001B Parent=0x00000018 SizeRef=367,467 HiddenTabBar=1 Selected=0x36DC96AB
DockNode ID=0x0000001C Parent=0x00000018 SizeRef=367,360 Split=X Selected=0x8CFF897F
DockNode ID=0x00000019 Parent=0x0000001C SizeRef=56,70 HiddenTabBar=1 Selected=0x56009A08
DockNode ID=0x0000001A Parent=0x0000001C SizeRef=333,70 Split=Y Selected=0x3FC1A724
DockNode ID=0x00000015 Parent=0x0000001A SizeRef=181,254 Split=Y Selected=0x3FC1A724
DockNode ID=0x00000013 Parent=0x00000015 SizeRef=333,219 HiddenTabBar=1 Selected=0x3FC1A724
DockNode ID=0x00000014 Parent=0x00000015 SizeRef=333,33 HiddenTabBar=1 Selected=0x8CFF897F
DockNode ID=0x00000016 Parent=0x0000001A SizeRef=181,104 HiddenTabBar=1 Selected=0xA873C17F
DockNode ID=0x00000008 Parent=0x00000002 SizeRef=606,327 HiddenTabBar=1 Selected=0xEA83D666
DockNode ID=0x00000006 Parent=0x11111111 SizeRef=289,1158 Split=Y Selected=0x36DC96AB
DockNode ID=0x00000009 Parent=0x00000006 SizeRef=449,488 Split=Y Selected=0x36DC96AB
DockNode ID=0x0000000B Parent=0x00000009 SizeRef=449,556 Split=Y Selected=0x36DC96AB

View File

@ -290,15 +290,15 @@ void ShowProfilerTimeline()
// Layout metrics
constexpr float rowH = 24.0f;
int rows = CountNodes(cachedFrame);
float avail = ImGui::GetContentRegionAvail().x;
const float avail = ImGui::GetContentRegionAvail().x;
float width = avail * zoom;
float height = std::min(rows * rowH, 400.0f);
// Begin child & get origin
ImGui::BeginChild("TimelineScroll", ImVec2(0, height), false, ImGuiWindowFlags_HorizontalScrollbar);
ImVec2 origin = ImGui::GetCursorScreenPos();
const ImVec2 origin = ImGui::GetCursorScreenPos();
ImDrawList *draw = ImGui::GetWindowDrawList();
ImU32 gridCol = ImGui::GetColorU32(ImGuiCol_Border);
const ImU32 gridCol = ImGui::GetColorU32(ImGuiCol_Border);
struct Line
{
@ -469,6 +469,8 @@ void DrawGizmoForObject(const std::shared_ptr<Object> &obj,
op = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_R))
op = ImGuizmo::ROTATE;
}
glm::mat4 manipulated = model;
@ -481,8 +483,24 @@ void DrawGizmoForObject(const std::shared_ptr<Object> &obj,
float angleRadians = atan2(manipulated[1][0], manipulated[0][0]); // M10, M00
obj->SetWorldPosition({pos.x, pos.y});
obj->SetLocalRotation(glm::degrees(angleRadians)); // Z
switch (op) {
case ImGuizmo::ROTATE:
obj->SetLocalRotation(glm::degrees(angleRadians));
break;
case ImGuizmo::TRANSLATE:
default:
obj->SetWorldPosition({pos.x, pos.y});
break;
}
}
}
@ -740,7 +758,7 @@ void Engine::Run()
ProjectManager::GetCurrentProjectName() + ".cene";
SceneLoader::SaveScene(path);
}
if (ImGui::MenuItem("Save Scene As"))
if (ImGui::MenuItem("Save Scene as"))
{
std::string file = CreateFileDialog(FileDialogType::Scenes);
if (!file.empty())
@ -931,14 +949,31 @@ void Engine::Run()
if (g_engineConfig.settings.show_color_correction_window)
ShowColorCorrectionWindow();
ShowAssetBrowser();
DrawAudioPlayingList();
DrawImGuiWindow();
FileExplorer::Update();
FileExplorer::Show(nullptr);
FileExplorer::ProcessLoadQueue();
{
PROFILE_ENGINE_SCOPE("ShowAssetBrowser");
ShowAssetBrowser();
}
{
PROFILE_ENGINE_SCOPE("DrawAudioPlayingList");
DrawAudioPlayingList();
}
{
PROFILE_ENGINE_SCOPE("DrawLuaGlobalsWindow");
DrawLuaGlobalsWindow();
}
{
PROFILE_ENGINE_SCOPE("FileExplorer::Update");
FileExplorer::Update();
FileExplorer::ProcessLoadQueue();
}
{
PROFILE_ENGINE_SCOPE("FileExplorer::Show");
FileExplorer::Show(nullptr);
}
@ -1608,6 +1643,10 @@ void Engine::Run()
selected = previousSelection;
previousSelection = nullptr;
}
else
{
selected = nullptr;
}
}
}

View File

@ -164,7 +164,6 @@ void Renderer::InitQuadBatch()
glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO);
glBufferStorage(GL_ARRAY_BUFFER, MAX_QUADS * sizeof(QuadInstance), nullptr, s_QuadPersistentFlags);
// Map once for persistent write access
s_QuadMappedPtr = glMapBufferRange(GL_ARRAY_BUFFER, 0, MAX_QUADS * sizeof(QuadInstance), s_QuadPersistentFlags);
if (!s_QuadMappedPtr)
{
@ -902,7 +901,12 @@ void Renderer::FlushSprites()
currentShader->SetFloat("uRotation", entry.sprite.rotationRad);
currentShader->SetInt("uTex", 0);
// Set UVs if applicable
//glm::mat4 model =
// glm::translate(glm::mat4(1.0f), glm::vec3(entry.sprite.screenPos, 0.0f)) *
// glm::rotate (glm::mat4(1.0f), entry.sprite.rotationRad, glm::vec3(0,0,1)) *
// glm::scale (glm::mat4(1.0f), glm::vec3(entry.sprite.size, 1.0f));
//currentShader->SetMat4("uModel", model);
if (entry.usesUV)
{
currentShader->SetVec2("uUVMin", glm::vec2(entry.sprite.texCoords.x, entry.sprite.texCoords.y));

View File

@ -18,6 +18,10 @@
#include "core/utils/Texture.h"
struct Mesh {
GLuint VAO;
GLsizei indexCount;
};

View File

@ -15,10 +15,70 @@
#include <imgui.h>
#include <string>
#include <cstring>
using namespace std::chrono;
#define ASSET_MANEFEST_NAME "assets.capk"
namespace {
struct CachedEntry {
fs::path path;
bool isDirectory;
AssetType type;
ImTextureID thumbID;
std::string label;
};
static std::vector<CachedEntry> cacheEntries;
struct TreeNode {
fs::path path;
std::vector<TreeNode> children;
};
static bool rebuild = true;
static TreeNode cachedTreeRoot;
static fs::path lastTreeRoot;
static bool treeDirty = true;
// Recursively build the TreeNode children
void BuildTreeNode(const fs::path& p, TreeNode& node) {
for (auto& e : fs::directory_iterator(p)) {
if (e.is_directory()) {
node.children.push_back({ e.path(), {} });
BuildTreeNode(e.path(), node.children.back());
}
}
}
// Ensure cachedTreeRoot matches s_root
void EnsureTreeCache(const fs::path& root) {
if (treeDirty || root != lastTreeRoot) {
cachedTreeRoot = { root, {} };
BuildTreeNode(root, cachedTreeRoot);
lastTreeRoot = root;
treeDirty = false;
}
}
// Draw from the cached tree
void DrawCachedTree(const TreeNode& node) {
// use folder name (or root)
auto name = node.path == lastTreeRoot
? "res://"
: node.path.filename().string().c_str();
bool open = ImGui::TreeNodeEx(
node.path.string().c_str(),
ImGuiTreeNodeFlags_OpenOnArrow,
"%s",
name
);
if (open) {
for (auto& child : node.children)
DrawCachedTree(child);
ImGui::TreePop();
}
}
}
@ -44,6 +104,12 @@ size_t FileExplorer::s_loadIndex = 0;
bool FileExplorer::s_loadingInit = false;
std::mutex FileExplorer::s_fileMutex;
std::atomic<bool> FileExplorer::s_scanInProgress{false};
steady_clock::time_point FileExplorer::s_lastScan = steady_clock::now();
const milliseconds FileExplorer::s_scanInterval = milliseconds(1000);
bool FileExplorer::Init()
{
if (s_initialized)
@ -86,6 +152,11 @@ bool FileExplorer::Init()
void FileExplorer::Show(bool* p_open)
{
if (!s_initialized) return;
// rebuild tree if the root changed
if (s_root != lastTreeRoot) treeDirty = true;
EnsureTreeCache(s_root);
if (!ImGui::Begin("File Explorer", p_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar))
{
@ -93,191 +164,281 @@ void FileExplorer::Show(bool* p_open)
return;
}
// --- Left pane: folder tree ---
ImGui::BeginChild("##FolderPane", ImVec2(250, 0), true);
ImGui::TextUnformatted("res://");
ImGui::Separator();
DrawFolderTree(s_root);
// -------------------------------------------------------------------------
// LEFT PANE: interactive foldertree with icons
// -------------------------------------------------------------------------
ImGui::BeginChild("##FolderPane", ImVec2(250,0), true);
{
// recursive lambda to draw each node
std::function<void(const TreeNode&)> drawNode = [&](const TreeNode& node) {
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow
| ImGuiTreeNodeFlags_SpanAvailWidth;
if (node.children.empty())
flags |= ImGuiTreeNodeFlags_Leaf;
if (node.path == s_currentDir)
flags |= ImGuiTreeNodeFlags_Selected;
// 1) icon
ImGui::Image((ImTextureID)(intptr_t)s_folderIcon, ImVec2(16,16));
ImGui::SameLine();
// 2) label
const char* label = (node.path == lastTreeRoot)
? "res://"
: node.path.filename().string().c_str();
bool open = ImGui::TreeNodeEx(
node.path.string().c_str(),
flags,
"%s",
label
);
// 3) click to change directory
if (ImGui::IsItemClicked()) {
s_currentDir = node.path;
treeDirty = true; // also force tree repaint?
rebuild = true; // filegrid rebuild
}
// 4) children
if (open) {
for (auto& child : node.children)
drawNode(child);
ImGui::TreePop();
}
};
drawNode(cachedTreeRoot);
}
ImGui::EndChild();
ImGui::SameLine();
// --- Right pane: content + toolbar ---
ImGui::BeginChild("##ContentPane", ImVec2(0, 0), false);
// -------------------------------------------------------------------------
// RIGHT PANE: toolbar + filegrid
// -------------------------------------------------------------------------
ImGui::BeginChild("##ContentPane", ImVec2(0,0), false);
// Filters / Search
if (ImGui::Button("Filters / Sort")) ImGui::OpenPopup("FileFilterPopup");
// --- Filters / Sort / Search state (static to persist frame-to-frame) ---
static char searchBuf[256] = "";
static fs::path lastDir;
static int lastSortMode = -1;
static bool lastSortAsc = false;
static bool lastShowScenes = true;
static bool lastShowImages = true;
static bool lastShowAudio = true;
static bool lastShowScripts = true;
static bool lastShowOther = true;
static std::string lastSearch;
static bool rebuild = true;
// Filters / Sort popup
if (ImGui::Button("Filters / Sort")) ImGui::OpenPopup("FileFilterPopup");
ImGui::SameLine();
ImGui::SetNextItemWidth(200);
char buf[256];
strncpy(buf, fileSearchQuery.c_str(), sizeof(buf));
buf[sizeof(buf)-1] = '\0';
if (ImGui::InputTextWithHint("##Search", "Search...", buf, sizeof(buf)))
fileSearchQuery = buf;
if (ImGui::InputTextWithHint("##Search","Search...",searchBuf,sizeof(searchBuf))) {
fileSearchQuery = searchBuf;
rebuild = true;
}
if (ImGui::BeginPopup("FileFilterPopup")) {
ImGui::Text("Sort By:");
if (ImGui::Selectable("Name", sortMode == 0)) sortMode = 0;
if (ImGui::Selectable("Type", sortMode == 1)) sortMode = 1;
ImGui::Checkbox("Ascending", &sortAscending);
if (ImGui::Selectable("Name", sortMode==0)) { sortMode=0; rebuild=true; }
if (ImGui::Selectable("Type", sortMode==1)) { sortMode=1; rebuild=true; }
if (ImGui::Checkbox("Ascending",&sortAscending)) rebuild=true;
ImGui::Separator();
ImGui::Text("Type:");
ImGui::Checkbox("Scenes", &showScenes);
ImGui::Checkbox("Images", &showImages);
ImGui::Checkbox("Audio", &showAudio);
ImGui::Checkbox("Scripts", &showScripts);
ImGui::Checkbox("Other", &showOther);
if (ImGui::Checkbox("Scenes", &showScenes)) rebuild=true;
if (ImGui::Checkbox("Images", &showImages)) rebuild=true;
if (ImGui::Checkbox("Audio", &showAudio)) rebuild=true;
if (ImGui::Checkbox("Scripts", &showScripts)) rebuild=true;
if (ImGui::Checkbox("Other", &showOther)) rebuild=true;
ImGui::EndPopup();
}
ImGui::Separator();
// “Up” button
// “Up” button + current path display
if (s_currentDir != s_root) {
if (ImGui::Button("Up")) s_currentDir = s_currentDir.parent_path();
if (ImGui::Button("Up")) {
s_currentDir = s_currentDir.parent_path();
rebuild = true;
}
ImGui::SameLine();
}
// Current path display
fs::path rel = fs::relative(s_currentDir, s_root);
std::string displayPath = "res://" +
(rel.empty() ? std::string() : rel.generic_string() + "/");
ImGui::TextUnformatted(displayPath.c_str());
{
fs::path rel = fs::relative(s_currentDir, s_root);
std::string displayPath = "res://"
+ (rel.empty() ? "" : rel.generic_string()+"/");
ImGui::TextUnformatted(displayPath.c_str());
}
ImGui::Separator();
// Gather & filter
std::vector<fs::directory_entry> entries;
for (auto &e : fs::directory_iterator(s_currentDir)) {
if (!e.is_directory()) {
AssetType t = AssetManager::AssetTypeFromPath(e.path().string());
switch (t) {
case AssetType::Scene: if (!showScenes) continue; break;
case AssetType::Image: if (!showImages) continue; break;
case AssetType::Audio: if (!showAudio) continue; break;
case AssetType::Script: if (!showScripts) continue; break;
default: if (!showOther) continue; break;
}
}
if (!fileSearchQuery.empty() &&
e.path().filename().string().find(fileSearchQuery) == std::string::npos)
continue;
entries.push_back(e);
}
std::sort(entries.begin(), entries.end(), [&](auto &a, auto &b) {
if (sortMode == 1) {
bool da = a.is_directory(), db = b.is_directory();
if (da != db) return sortAscending ? da > db : da < db;
return sortAscending
? a.path().extension() < b.path().extension()
: a.path().extension() > b.path().extension();
}
return sortAscending
? a.path().filename() < b.path().filename()
: a.path().filename() > b.path().filename();
});
// Icon grid setup
constexpr float iconSize = 64.0f, padding = 8.0f;
float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1, int(avail / (iconSize + padding)));
ImGui::Columns(cols, nullptr, false);
for (auto &e : entries) {
fs::path p = e.path();
std::string pathStr = p.string();
AssetType type = AssetManager::AssetTypeFromPath(pathStr);
ImGui::PushID(pathStr.c_str());
// 1) pick thumbnail
ImTextureID thumbID = 0;
if (e.is_directory()) {
thumbID = (ImTextureID)(intptr_t)s_folderIcon;
} else if (type == AssetType::Image) {
if (auto* asset = AssetManager::GetAssetByPath(pathStr))
if (asset->type == AssetType::Image)
if (auto* img = dynamic_cast<const ImageAssetInfo*>(asset))
thumbID = (ImTextureID)(intptr_t)img->textureID;
}
if (!thumbID) {
GLuint iconTex = EngineLoadTextureIfNeeded(
std::string(ICONS_PATH) + IconFileForPath(p));
thumbID = (ImTextureID)(intptr_t)iconTex;
}
// 2) DRAW the single widget with a consistent ID: "##entry"
bool clicked = false;
if (e.is_directory() || type == AssetType::Image) {
clicked = ImGui::ImageButton(
"##entry",
thumbID,
ImVec2(iconSize, iconSize));
} else {
std::string label;
switch (type) {
case AssetType::Prefab: label = "Prefab"; break;
case AssetType::Scene: label = "Scene"; break;
case AssetType::Audio: label = "Audio"; break;
case AssetType::Script: label = "Script"; break;
case AssetType::Video: label = "Video"; break;
case AssetType::Font: label = "Font"; break;
case AssetType::Shader: label = "Shader"; break;
default:
label = p.extension().string();
if (!label.empty() && label.front()=='.')
label.erase(0,1);
break;
}
clicked = ImGui::Button(
(label + std::string("##entry") + label).c_str(),
ImVec2(iconSize, iconSize));
}
// 3) handle click/navigation
if (clicked) {
if (e.is_directory())
s_currentDir = p;
else
SelectedPath = pathStr;
}
// 4) context menu tied to the same "##entry" ID
if (ImGui::BeginPopupContextItem("##entry")) {
if (e.is_directory() && ImGui::MenuItem("Open"))
s_currentDir = p;
if (!e.is_directory() && ImGui::MenuItem("Open File"))
SelectedPath = pathStr;
if (ImGui::MenuItem("Copy Path"))
ImGui::SetClipboardText(pathStr.c_str());
ImGui::EndPopup();
}
// 5) drag-drop source also tied to "##entry"
if (!e.is_directory() &&
ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
// Detect any state changes → rebuild the filegrid cache
if ( lastDir != s_currentDir
|| lastSortMode != sortMode
|| lastSortAsc != sortAscending
|| lastSearch != fileSearchQuery
|| lastShowScenes != showScenes
|| lastShowImages != showImages
|| lastShowAudio != showAudio
|| lastShowScripts != showScripts
|| lastShowOther != showOther)
{
if (auto* asset = AssetManager::GetAssetByPath(pathStr)) {
if (type == AssetType::Image)
ImGui::SetDragDropPayload(ASSET_TEXTURE,
&asset->uaid,
sizeof(asset->uaid));
else if (type == AssetType::Prefab)
ImGui::SetDragDropPayload(ASSET_PREFAB,
&asset->uaid,
sizeof(asset->uaid));
}
ImGui::TextUnformatted(p.filename().string().c_str());
ImGui::EndDragDropSource();
rebuild = true;
lastDir = s_currentDir;
lastSortMode = sortMode;
lastSortAsc = sortAscending;
lastSearch = fileSearchQuery;
lastShowScenes = showScenes;
lastShowImages = showImages;
lastShowAudio = showAudio;
lastShowScripts = showScripts;
lastShowOther = showOther;
}
// 6) label and next column
ImGui::TextWrapped("%s", p.filename().string().c_str());
ImGui::NextColumn();
if (rebuild) {
cacheEntries.clear();
cacheEntries.reserve(128);
ImGui::PopID();
}
for (auto& e : fs::directory_iterator(s_currentDir)) {
bool isDir = e.is_directory();
AssetType t = AssetManager::AssetTypeFromPath(e.path().string());
// type filters
if (!isDir) {
if ((t==AssetType::Scene && !showScenes )||
(t==AssetType::Image && !showImages )||
(t==AssetType::Audio && !showAudio )||
(t==AssetType::Script && !showScripts)||
(t==AssetType::Unknown && !showOther ))
continue;
}
// search filter
if (!fileSearchQuery.empty()
&& e.path().filename().string().find(fileSearchQuery)==std::string::npos)
continue;
CachedEntry ce;
ce.path = e.path();
ce.isDirectory = isDir;
ce.type = t;
// thumbnail
if (isDir) {
ce.thumbID = (ImTextureID)(intptr_t)s_folderIcon;
}
else if (t==AssetType::Image) {
if (auto* asset = AssetManager::GetAssetByPath(ce.path.string()))
if (auto* img = dynamic_cast<const ImageAssetInfo*>(asset))
ce.thumbID = (ImTextureID)(intptr_t)img->textureID;
}
if (!ce.thumbID) {
ce.thumbID = (ImTextureID)(intptr_t)
EngineLoadTextureIfNeeded(ICONS_PATH + IconFileForPath(ce.path));
}
// label for nonimage files
if (!isDir && t!=AssetType::Image) {
static const std::unordered_map<AssetType,const char*> L = {
{AssetType::Prefab,"Prefab"},
{AssetType::Scene, "Scene"},
{AssetType::Audio, "Audio"},
{AssetType::Script,"Script"},
{AssetType::Video, "Video"},
{AssetType::Font, "Font"},
{AssetType::Shader,"Shader"}
};
auto it = L.find(t);
if (it!=L.end()) ce.label = it->second;
else {
auto ext = ce.path.extension().string();
ce.label = ext.size()>1 ? ext.substr(1) : ext;
}
}
cacheEntries.push_back(std::move(ce));
}
// sort
std::sort(cacheEntries.begin(), cacheEntries.end(),
[&](auto& a, auto& b){
if (sortMode==1) {
if (a.isDirectory!=b.isDirectory)
return sortAscending
? a.isDirectory>b.isDirectory
: a.isDirectory<b.isDirectory;
return sortAscending
? a.path.extension()<b.path.extension()
: a.path.extension()>b.path.extension();
}
return sortAscending
? a.path.filename()<b.path.filename()
: a.path.filename()>b.path.filename();
});
rebuild = false;
}
// Icongrid draw pass
constexpr float iconSize=64.0f, padding=8.0f;
float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1,int(avail/(iconSize+padding)));
ImGui::Columns(cols,nullptr,false);
for (auto& ce : cacheEntries) {
PROFILE_ENGINE_SCOPE("entry");
const auto& p = ce.path;
const auto pathStr = p.string();
ImGui::PushID(pathStr.c_str());
bool clicked = false;
if (ce.isDirectory||ce.type==AssetType::Image) {
PROFILE_ENGINE_SCOPE("Image");
clicked = ImGui::ImageButton("##entry",
ce.thumbID,
ImVec2(iconSize,iconSize));
} else {
clicked = ImGui::Button(
(ce.label+"##entry").c_str(),
ImVec2(iconSize,iconSize)
);
}
if (clicked) {
if (ce.isDirectory) s_currentDir=p;
else SelectedPath=pathStr;
rebuild=true;
}
if (ImGui::BeginPopupContextItem("##entry")) {
if (ce.isDirectory && ImGui::MenuItem("Open"))
s_currentDir=p, rebuild=true;
if (!ce.isDirectory && ImGui::MenuItem("Open File"))
SelectedPath=pathStr;
if (ImGui::MenuItem("Copy Path"))
ImGui::SetClipboardText(pathStr.c_str());
ImGui::EndPopup();
}
if (!ce.isDirectory &&
ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
{
if (auto* asset=AssetManager::GetAssetByPath(pathStr)) {
const void* data=&asset->uaid;
const char* typeStr=
ce.type==AssetType::Image ? ASSET_TEXTURE :
ce.type==AssetType::Prefab ? ASSET_PREFAB : nullptr;
if(typeStr)
ImGui::SetDragDropPayload(typeStr,
data,
sizeof(asset->uaid));
}
ImGui::TextUnformatted(p.filename().string().c_str());
ImGui::EndDragDropSource();
}
ImGui::TextWrapped("%s",p.filename().string().c_str());
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::Columns(1);
ImGui::EndChild();
@ -287,6 +448,56 @@ void FileExplorer::Show(bool* p_open)
void FileExplorer::ScanDirectory()
{
std::error_code ec;
std::unordered_set<std::string> currentFiles;
// walk the tree once
for (auto it = fs::recursive_directory_iterator(s_root,
fs::directory_options::skip_permission_denied, ec),
end = fs::recursive_directory_iterator{};
it != end; it.increment(ec))
{
if (ec)
{
ec.clear();
continue;
}
if (!it->is_regular_file()) continue;
auto pathStr = it->path().string();
currentFiles.insert(pathStr);
{
std::lock_guard<std::mutex> lock(s_fileMutex);
if (!s_knownFiles.count(pathStr))
{
s_knownFiles.insert(pathStr);
s_loadQueue.push_back(it->path());
}
}
}
{
std::lock_guard<std::mutex> lock(s_fileMutex);
for (auto it = s_knownFiles.begin(); it != s_knownFiles.end(); )
{
if (!currentFiles.count(*it))
{
AssetManager::GetAssetByPath(*it);
it = s_knownFiles.erase(it);
}
else ++it;
}
}
}
void FileExplorer::Update()
{
if (!s_initialized)
@ -295,41 +506,17 @@ void FileExplorer::Update()
return;
}
std::error_code ec;
std::unordered_set<std::string> currentFiles;
for (auto it = fs::recursive_directory_iterator(s_root,
fs::directory_options::skip_permission_denied, ec),
end = fs::recursive_directory_iterator{};
it != end; it.increment(ec))
auto now = steady_clock::now();
if (!s_scanInProgress.load() && now - s_lastScan >= s_scanInterval)
{
if (ec) {
Logger::LogWarning("FileExplorer: skipping '%s': %s",
it->path().string().c_str(),
ec.message().c_str());
ec.clear();
continue;
}
if (!it->is_regular_file()) continue;
auto pathStr = it->path().string();
currentFiles.insert(pathStr);
if (!s_knownFiles.count(pathStr)) {
s_knownFiles.insert(pathStr);
s_loadQueue.push_back(it->path());
Logger::LogVerbose("FileExplorer: added '%s'", pathStr.c_str());
}
}
// detect deletions
for (auto it = s_knownFiles.begin(); it != s_knownFiles.end(); )
{
if (!currentFiles.count(*it)) {
Logger::LogVerbose("FileExplorer: removed '%s'", it->c_str());
AssetManager::GetAssetByPath(*it);
it = s_knownFiles.erase(it);
}
else ++it;
s_lastScan = now;
s_scanInProgress = true;
// fireandforget background scan
std::thread([](){
ScanDirectory();
s_scanInProgress = false;
}).detach();
}
}
@ -340,6 +527,8 @@ bool FileExplorer::LoadingDone()
void FileExplorer::DrawFolderTree(const fs::path &path)
{
PROFILE_ENGINE_SCOPE("DrawTree");
for (auto &e : fs::directory_iterator(path)) {
if (!e.is_directory()) continue;
auto &d = e.path();

View File

@ -4,7 +4,8 @@
#include <filesystem>
#include <vector>
#include <unordered_set>
#include <mutex>
#include <atomic>
@ -54,6 +55,16 @@ private:
static size_t s_loadIndex;
static bool s_loadingInit;
static void ScanDirectory();
static std::mutex s_fileMutex;
// backgroundscan control
static std::atomic<bool> s_scanInProgress;
static std::chrono::steady_clock::time_point s_lastScan;
static const std::chrono::milliseconds s_scanInterval;
static std::string IconFileForPath(const fs::path &path);
static void DrawFolderTree(const fs::path &path);
@ -100,4 +111,7 @@ private:
static std::string s_currentProjectName;
static std::string s_defaultScene;
};

View File

@ -4,116 +4,114 @@
#include <ostream>
#include <glm/glm.hpp>
namespace core
{
namespace types
namespace core::types {
struct Vec3
{
float x{0}, y{0}, z{0};
struct Vec3
Vec3() = default;
Vec3(const float x, const float y, const float z) : x(x), y(y), z(z) {}
explicit Vec3(const float v) : x(v), y(v), z(v) {}
explicit Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}
operator glm::vec3() const { return { x, y, z }; }
Vec3 operator+(const Vec3 &rhs) const { return {x + rhs.x, y + rhs.y, z + rhs.z}; }
Vec3 operator-(const Vec3 &rhs) const { return {x - rhs.x, y - rhs.y, z - rhs.z}; }
Vec3 operator*(const float scalar) const { return {x * scalar, y * scalar, z * scalar}; }
Vec3 operator/(const float scalar) const { return {x / scalar, y / scalar, z / scalar}; }
Vec3 operator*(const Vec3 &rhs) const { return {x * rhs.x, y * rhs.y, z * rhs.z}; }
Vec3 &operator+=(const Vec3 &rhs) { x += rhs.x; y += rhs.y; z += rhs.z; return *this; }
Vec3 &operator-=(const Vec3 &rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; }
Vec3 &operator*=(const float scalar) { x *= scalar; y *= scalar; z *= scalar; return *this; }
Vec3 &operator/=(const float scalar) { x /= scalar; y /= scalar; z /= scalar; return *this; }
Vec3 operator-() const { return {-x, -y, -z}; }
bool operator==(const Vec3 &rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
bool operator!=(const Vec3 &rhs) const { return !(*this == rhs); }
float Length() const { return std::sqrt(x * x + y * y + z * z); }
float LengthSquared() const { return x * x + y * y + z * z; }
Vec3 Normalized() const
{
float x{0}, y{0}, z{0};
const float len = Length();
return len != 0 ? *this / len : Vec3(0.0f);
}
Vec3() = default;
Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
Vec3(float v) : x(v), y(v), z(v) {}
Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}
operator glm::vec3() const { return {x, y, z}; }
Vec3 operator+(const Vec3 &rhs) const { return {x + rhs.x, y + rhs.y, z + rhs.z}; }
Vec3 operator-(const Vec3 &rhs) const { return {x - rhs.x, y - rhs.y, z - rhs.z}; }
Vec3 operator*(float scalar) const { return {x * scalar, y * scalar, z * scalar}; }
Vec3 operator/(float scalar) const { return {x / scalar, y / scalar, z / scalar}; }
Vec3 operator*(const Vec3 &rhs) const { return {x * rhs.x, y * rhs.y, z * rhs.z}; }
Vec3 &operator+=(const Vec3 &rhs) { x += rhs.x; y += rhs.y; z += rhs.z; return *this; }
Vec3 &operator-=(const Vec3 &rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; }
Vec3 &operator*=(float scalar) { x *= scalar; y *= scalar; z *= scalar; return *this; }
Vec3 &operator/=(float scalar) { x /= scalar; y /= scalar; z /= scalar; return *this; }
Vec3 operator-() const { return {-x, -y, -z}; }
bool operator==(const Vec3 &rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
bool operator!=(const Vec3 &rhs) const { return !(*this == rhs); }
float Length() const { return std::sqrt(x * x + y * y + z * z); }
float LengthSquared() const { return x * x + y * y + z * z; }
Vec3 Normalized() const
void Normalize()
{
if (const float len = Length(); len != 0)
{
float len = Length();
return len != 0 ? *this / len : Vec3(0.0f);
x /= len;
y /= len;
z /= len;
}
}
void Normalize()
{
float len = Length();
if (len != 0)
{
x /= len;
y /= len;
z /= len;
}
float Dot(const Vec3 &rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; }
Vec3 Cross(const Vec3 &rhs) const
{
return Vec3(
y * rhs.z - z * rhs.y,
z * rhs.x - x * rhs.z,
x * rhs.y - y * rhs.x
);
}
void Clamp(const Vec3 &min, const Vec3 &max)
{
x = std::max(min.x, std::min(x, max.x));
y = std::max(min.y, std::min(y, max.y));
z = std::max(min.z, std::min(z, max.z));
}
static Vec3 Lerp(const Vec3 &a, const Vec3 &b, const float t)
{
return a + (b - a) * t;
}
static float Distance(const Vec3 &a, const Vec3 &b)
{
return (a - b).Length();
}
friend std::ostream &operator<<(std::ostream &os, const Vec3 &v)
{
return os << "(" << v.x << ", " << v.y << ", " << v.z << ")";
}
float& operator[](const int i) {
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default: throw std::out_of_range("Vec3 index out of range");
}
}
float Dot(const Vec3 &rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; }
Vec3 Cross(const Vec3 &rhs) const
{
return Vec3(
y * rhs.z - z * rhs.y,
z * rhs.x - x * rhs.z,
x * rhs.y - y * rhs.x
);
const float& operator[](const int i) const {
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default: throw std::out_of_range("Vec3 index out of range");
}
}
void Clamp(const Vec3 &min, const Vec3 &max)
{
x = std::max(min.x, std::min(x, max.x));
y = std::max(min.y, std::min(y, max.y));
z = std::max(min.z, std::min(z, max.z));
}
static Vec3 Lerp(const Vec3 &a, const Vec3 &b, float t)
{
return a + (b - a) * t;
}
};
static float Distance(const Vec3 &a, const Vec3 &b)
{
return (a - b).Length();
}
inline Vec3 operator*(const float scalar, const Vec3 &v) { return v * scalar; }
friend std::ostream &operator<<(std::ostream &os, const Vec3 &v)
{
return os << "(" << v.x << ", " << v.y << ", " << v.z << ")";
}
inline float& operator[](int i) {
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default: throw std::out_of_range("Vec3 index out of range");
}
}
inline const float& operator[](int i) const {
switch (i) {
case 0: return x;
case 1: return y;
case 2: return z;
default: throw std::out_of_range("Vec3 index out of range");
}
}
};
inline Vec3 operator*(float scalar, const Vec3 &v) { return v * scalar; }
} // namespace types
} // namespace core
}

View File

@ -0,0 +1,46 @@
#include "PrimitiveGenerator.h"
#include <cmath>
using core::types::Vec3;
namespace core::utils {
void Primitive::CreateQuad(float width, float height,
std::vector<Vec3>& outVerts, std::vector<uint32_t>& outIndices)
{
outVerts.clear(); outIndices.clear();
float hw = width * 0.5f, hh = height * 0.5f;
outVerts.emplace_back(-hw, -hh, 0.0f);
outVerts.emplace_back( hw, -hh, 0.0f);
outVerts.emplace_back( hw, hh, 0.0f);
outVerts.emplace_back(-hw, hh, 0.0f);
outIndices = { 0, 1, 2,
2, 3, 0 };
}
void Primitive::CreateCircle(float radius, int segments,
std::vector<Vec3>& outVerts, std::vector<uint32_t>& outIndices)
{
outVerts.clear(); outIndices.clear();
if (segments < 3) segments = 3;
outVerts.emplace_back(0.0f, 0.0f, 0.0f);
const float step = 2.0f * static_cast<float>(M_PI) / static_cast<float>(segments);
for (int i = 0; i <= segments; ++i) {
float a = step * static_cast<float>(i);
outVerts.emplace_back(std::cos(a) * radius,
std::sin(a) * radius,
0.0f);
}
for (uint32_t i = 1; i < static_cast<uint32_t>(segments + 1); ++i) {
outIndices.push_back(0);
outIndices.push_back(i);
outIndices.push_back(i+1);
}
}
} // namespace core::utils

View File

@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include "../types/Vec3.h"
namespace core::utils {
struct Primitive {
// vertices in outVerts, triangle indices in outIndices
// Quad centered at origin, width x height
static void CreateQuad(float width, float height,
std::vector<core::types::Vec3>& outVerts,
std::vector<uint32_t>& outIndices);
// Filled circle (trianglefan) in XY plane, centered at origin
// segments controls tesselation
static void CreateCircle(float radius, int segments,
std::vector<core::types::Vec3>& outVerts,
std::vector<uint32_t>& outIndices);
};
}

View File

@ -10,7 +10,7 @@
#include "../../core/utils/EngineConfig.h"
#include <imgui.h>
void DrawImGuiWindow()
void DrawLuaGlobalsWindow()
{
if (!g_engineConfig.settings.show_lua_globals_window)
return;

View File

@ -1,4 +1,4 @@
#pragma once
void DrawImGuiWindow();
void DrawLuaGlobalsWindow();