diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml index 79c85c6..5ce3305 100644 --- a/.idea/material_theme_project_new.xml +++ b/.idea/material_theme_project_new.xml @@ -3,7 +3,9 @@ diff --git a/imgui.ini b/imgui.ini index 82f4e72..eff096c 100644 --- a/imgui.ini +++ b/imgui.ini @@ -14,74 +14,74 @@ Size=1920,1158 Collapsed=0 [Window][Inspector] -Pos=1553,19 -Size=367,537 +Pos=1529,19 +Size=391,636 Collapsed=0 DockId=0x0000001B,0 [Window][Scene Tree] Pos=0,19 -Size=341,444 +Size=340,763 Collapsed=0 DockId=0x0000000F,0 [Window][Viewport] -Pos=343,19 -Size=1208,659 +Pos=342,19 +Size=1185,488 Collapsed=0 -DockId=0x00000017,0 +DockId=0x00000011,0 [Window][##MainMenuBar] -Size=1920,19 +Size=1280,19 Collapsed=0 [Window][Performance Info] -Pos=1628,680 -Size=292,268 +Pos=1587,794 +Size=333,220 Collapsed=0 -DockId=0x00000019,0 +DockId=0x00000015,0 [Window][Console] -Pos=343,680 -Size=1207,497 +Pos=342,509 +Size=1185,273 Collapsed=0 -DockId=0x00000013,0 +DockId=0x00000012,0 [Window][Tilemap Editor] Pos=265,19 Size=1263,674 Collapsed=0 -DockId=0x00000017,1 +DockId=0x00000011,1 [Window][Profiler] Pos=343,955 Size=1232,222 Collapsed=0 -DockId=0x00000011,0 +DockId=0x00000008,0 [Window][Profiler Timeline] Pos=265,69 Size=623,651 Collapsed=0 -DockId=0x00000015,1 +DockId=0x00000008,1 [Window][Profiler (Unity Style)] Pos=265,430 Size=623,290 Collapsed=0 -DockId=0x00000015,1 +DockId=0x00000008,1 [Window][Profiler Timeline View] Pos=265,526 Size=1263,651 Collapsed=0 -DockId=0x00000015,1 +DockId=0x00000008,1 [Window][Color Correction] -Pos=1628,950 -Size=292,227 +Pos=1587,1016 +Size=333,161 Collapsed=0 -DockId=0x0000001A,0 +DockId=0x00000016,0 [Window][Asset Browser] Pos=0,658 @@ -113,10 +113,10 @@ Collapsed=0 DockId=0x0000000E,0 [Window][Audio Output] -Pos=1552,680 -Size=74,497 +Pos=1529,794 +Size=56,383 Collapsed=0 -DockId=0x00000014,0 +DockId=0x00000019,0 [Window][Master Bus] Pos=1003,570 @@ -128,7 +128,7 @@ DockId=0x0000000D,0 Pos=0,465 Size=341,712 Collapsed=0 -DockId=0x00000010,0 +DockId=0x00000003,0 [Window][Import Preview] Pos=584,22 @@ -136,10 +136,10 @@ Size=550,695 Collapsed=0 [Window][Lua Globals] -Pos=1553,558 -Size=367,120 +Pos=1529,657 +Size=391,135 Collapsed=0 -DockId=0x0000001C,0 +DockId=0x00000013,0 [Window][Import Assets] Pos=626,263 @@ -151,6 +151,17 @@ Pos=298,22 Size=600,209 Collapsed=0 +[Window][File Explorer] +Pos=0,784 +Size=1527,393 +Collapsed=0 +DockId=0x0000001E,0 + +[Window][ConfirmClearScene] +Pos=808,551 +Size=304,75 +Collapsed=0 + [Table][0x96376740,2] RefScale=13 Column 0 Weight=1.0000 @@ -186,33 +197,35 @@ Column 1 Width=86 Column 2 Weight=1.0000 [Docking][Data] -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 Split=Y Selected=0x12EF0F59 - DockNode ID=0x0000000F Parent=0x00000003 SizeRef=341,444 HiddenTabBar=1 Selected=0x12EF0F59 - DockNode ID=0x00000010 Parent=0x00000003 SizeRef=341,712 HiddenTabBar=1 Selected=0x30401527 - 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,659 Split=X Selected=0xC450F867 - DockNode ID=0x00000017 Parent=0x00000007 SizeRef=1208,860 CentralNode=1 HiddenTabBar=1 Selected=0xC450F867 - DockNode ID=0x00000018 Parent=0x00000007 SizeRef=367,860 Split=Y Selected=0x36DC96AB - DockNode ID=0x0000001B Parent=0x00000018 SizeRef=367,537 HiddenTabBar=1 Selected=0x36DC96AB - DockNode ID=0x0000001C Parent=0x00000018 SizeRef=367,120 HiddenTabBar=1 Selected=0x8CFF897F - DockNode ID=0x00000008 Parent=0x00000002 SizeRef=606,497 Split=X Selected=0xEA83D666 - DockNode ID=0x00000015 Parent=0x00000008 SizeRef=1283,172 Split=X Selected=0xEA83D666 - DockNode ID=0x00000011 Parent=0x00000015 SizeRef=1206,168 HiddenTabBar=1 Selected=0x9B5D3198 - DockNode ID=0x00000012 Parent=0x00000015 SizeRef=75,168 Split=X Selected=0x56009A08 - DockNode ID=0x00000013 Parent=0x00000012 SizeRef=1207,497 Selected=0xEA83D666 - DockNode ID=0x00000014 Parent=0x00000012 SizeRef=74,497 HiddenTabBar=1 Selected=0x56009A08 - DockNode ID=0x00000016 Parent=0x00000008 SizeRef=292,172 Split=Y Selected=0x3FC1A724 - DockNode ID=0x00000019 Parent=0x00000016 SizeRef=314,268 HiddenTabBar=1 Selected=0x3FC1A724 - DockNode ID=0x0000001A Parent=0x00000016 SizeRef=314,227 HiddenTabBar=1 Selected=0xA873C17F - 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 - DockNode ID=0x0000000D Parent=0x0000000B SizeRef=449,860 Selected=0x36DC96AB - DockNode ID=0x0000000E Parent=0x0000000B SizeRef=449,296 HiddenTabBar=1 Selected=0x9D7E7171 - DockNode ID=0x0000000C Parent=0x00000009 SizeRef=449,143 HiddenTabBar=1 Selected=0xB6C74292 - DockNode ID=0x0000000A Parent=0x00000006 SizeRef=449,211 HiddenTabBar=1 Selected=0xD83E5DD3 +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=0x00000017 Parent=0x00000007 SizeRef=1184,860 Split=Y Selected=0xC450F867 + DockNode ID=0x0000001D Parent=0x00000017 SizeRef=1208,763 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,488 CentralNode=1 HiddenTabBar=1 Selected=0xC450F867 + DockNode ID=0x00000012 Parent=0x00000010 SizeRef=1185,273 HiddenTabBar=1 Selected=0xEA83D666 + DockNode ID=0x0000001E Parent=0x00000017 SizeRef=1208,393 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=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 + DockNode ID=0x0000000D Parent=0x0000000B SizeRef=449,860 Selected=0x36DC96AB + DockNode ID=0x0000000E Parent=0x0000000B SizeRef=449,296 HiddenTabBar=1 Selected=0x9D7E7171 + DockNode ID=0x0000000C Parent=0x00000009 SizeRef=449,143 HiddenTabBar=1 Selected=0xB6C74292 + DockNode ID=0x0000000A Parent=0x00000006 SizeRef=449,211 HiddenTabBar=1 Selected=0xD83E5DD3 diff --git a/src/assets/icons/folder-outline.png b/src/assets/icons/folder-outline.png new file mode 100644 index 0000000..e0fc9ec Binary files /dev/null and b/src/assets/icons/folder-outline.png differ diff --git a/src/assets/icons/movie-open-outline.png b/src/assets/icons/movie-open-outline.png new file mode 100644 index 0000000..8bc8729 Binary files /dev/null and b/src/assets/icons/movie-open-outline.png differ diff --git a/src/src/Engine.cpp b/src/src/Engine.cpp index 9de8f04..f74cd83 100644 --- a/src/src/Engine.cpp +++ b/src/src/Engine.cpp @@ -550,7 +550,10 @@ void Engine::Init() ProjectManager::Init(); Logger::LogVerbose("Init Script Core"); ScriptCore::Init(); - Logger::LogVerbose("Resverving Objects"); + + Logger::LogVerbose("Init Files Core"); + FileExplorer::Init(); + Logger::LogVerbose("Reserving Objects"); // These values were AI Generated. @@ -570,7 +573,7 @@ void Engine::Init() - ProjectManager::LoadProject("C:/Users/spenc/OneDrive/Desktop", "TestProject"); + //ProjectManager::LoadProject("C:/Users/spenc/OneDrive/Desktop", "TestProject"); } core::types::Vec2 ScreenToWorld(const core::types::Vec2 &screenPos, const core::types::Vec2 &viewportSize, const core::types::Vec2 &cameraPos, float zoom) @@ -757,7 +760,16 @@ void Engine::Run() ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::MenuItem("Load Project")) + { + std::string file = OpenFileDialog(FileDialogType::Project); + Logger::LogInfo("Loading Project."); + if (!file.empty()) + { + ProjectManager::LoadProject(file); + } + selected = nullptr; + } if (ImGui::MenuItem("Create New Project")) { OpenNewProjectMenu = true; @@ -766,6 +778,8 @@ void Engine::Run() + + ImGui::Separator(); ImGui::Spacing(); @@ -915,6 +929,10 @@ void Engine::Run() ShowAssetBrowser(); DrawAudioPlayingList(); DrawImGuiWindow(); + FileExplorer::Update(); + FileExplorer::Show(nullptr); + FileExplorer::ProcessLoadQueue(); + diff --git a/src/src/core/functions/ProjectManager.cpp b/src/src/core/functions/ProjectManager.cpp index b85ae23..fbcd35f 100644 --- a/src/src/core/functions/ProjectManager.cpp +++ b/src/src/core/functions/ProjectManager.cpp @@ -1,10 +1,11 @@ -// ProjectManager.cpp - #include "ProjectManager.h" #include "../../core/utils/EngineConfig.h" #include "../../core/utils/Logging.h" #include "../../Engine.h" #include "../../core/utils/FileDialog.h" +#include "../../core/utils/utils.h" +#include "../../core/utils/AssetManager.h" + #include "SceneSerializer.h" #include @@ -16,6 +17,351 @@ #include + + + + + +bool FileExplorer::s_initialized = false; +std::string FileExplorer::SelectedPath; +fs::path FileExplorer::s_root; +fs::path FileExplorer::s_currentDir; +std::unordered_set FileExplorer::s_knownFiles; +const char* FileExplorer::ICONS_PATH = "./src/assets/icons/"; +unsigned int FileExplorer::s_folderIcon = 0; + +std::string FileExplorer::fileSearchQuery = ""; +bool FileExplorer::sortAscending = true; +int FileExplorer::sortMode = 0; +bool FileExplorer::showScenes = true; +bool FileExplorer::showImages = true; +bool FileExplorer::showAudio = true; +bool FileExplorer::showScripts = true; +bool FileExplorer::showOther = true; + +std::vector FileExplorer::s_loadQueue; +size_t FileExplorer::s_loadIndex = 0; +bool FileExplorer::s_loadingInit = false; + + +bool FileExplorer::Init() +{ + if (s_initialized) + return false; + + if (!ProjectManager::HasProject()) + return false; + + // Resolve and verify root… + s_root = fs::path(ProjectManager::ResolveResPath("res://")); + s_currentDir = s_root; + if (!fs::exists(s_root) || !fs::is_directory(s_root)) { + Logger::LogError("FileExplorer: invalid root path '%s'", s_root.string().c_str()); + return false; + } + + s_folderIcon = EngineLoadTextureIfNeeded(std::string(ICONS_PATH) + "folder-outline.png"); + + std::error_code ec; + fs::recursive_directory_iterator it(s_root, fs::directory_options::skip_permission_denied, ec); + fs::recursive_directory_iterator end; + for (; it != end; it.increment(ec)) { + 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()) + s_loadQueue.push_back(it->path()); + } + + s_initialized = true; + return true; +} + + + +void FileExplorer::Show(bool* p_open) +{ + if (!s_initialized) return; + if (!ImGui::Begin("File Explorer", p_open, + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar)) + { + ImGui::End(); + return; + } + + // --- Left pane: folder tree --- + ImGui::BeginChild("##FolderPane", ImVec2(250, 0), true); + ImGui::TextUnformatted("res://"); + ImGui::Separator(); + DrawFolderTree(s_root); + ImGui::EndChild(); + + ImGui::SameLine(); + + // --- Right pane: content + toolbar --- + ImGui::BeginChild("##ContentPane", ImVec2(0, 0), false); + + // Filters / Search + 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::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); + 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); + ImGui::EndPopup(); + } + + ImGui::Separator(); + + // “Up” button + if (s_currentDir != s_root) { + if (ImGui::Button("Up")) s_currentDir = s_currentDir.parent_path(); + 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()); + ImGui::Separator(); + + // Gather & filter + std::vector 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); + } + + // Sort + 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); + + // Draw each entry + for (auto &e : entries) { + fs::path p = e.path(); + std::string pathStr = p.string(); + AssetType type = AssetManager::AssetTypeFromPath(pathStr); + + ImGui::PushID(pathStr.c_str()); + + // --- IMAGE THUMBNAIL w/ InvisibleButton for clicks --- + if (type == AssetType::Image) { + if (auto* asset = AssetManager::GetAssetByPath(pathStr)) + if (asset->type == AssetType::Image) { + auto* img = dynamic_cast(asset); + if (img) { + if (ImGui::ImageButton("##Image", (ImTextureID)(intptr_t)img->textureID, ImVec2(iconSize, iconSize))) + { + s_currentDir = p; + } + } + } + } + else if (e.is_directory()) { + if (ImGui::ImageButton("##item", + (ImTextureID)(intptr_t)s_folderIcon, + ImVec2(iconSize, iconSize))) + { + s_currentDir = p; + } + } + 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; + } + if (ImGui::Button(label.c_str(), + ImVec2(iconSize, iconSize))) + { + SelectedPath = pathStr; + } + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + 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(); + } + + if (ImGui::BeginPopupContextItem()) { + 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(); + } + + ImGui::TextWrapped("%s", p.filename().string().c_str()); + ImGui::NextColumn(); + ImGui::PopID(); + } + + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::End(); +} + + + + +void FileExplorer::Update() +{ + if (!s_initialized) + { + Init(); + return; + } + + std::error_code ec; + std::unordered_set 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)) + { + 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; + } +} + +bool FileExplorer::LoadingDone() +{ + return s_initialized && s_loadIndex >= s_loadQueue.size(); +} + +void FileExplorer::DrawFolderTree(const fs::path &path) +{ + for (auto &e : fs::directory_iterator(path)) { + if (!e.is_directory()) continue; + auto &d = e.path(); + ImGui::PushID(d.string().c_str()); + ImGui::Image((ImTextureID)(intptr_t)s_folderIcon, ImVec2(16,16)); ImGui::SameLine(); + bool open = ImGui::TreeNodeEx(d.string().c_str(), ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth, "%s", d.filename().string().c_str()); + if (ImGui::IsItemClicked()) s_currentDir = d; + if (open) { DrawFolderTree(d); ImGui::TreePop(); } + ImGui::PopID(); + } +} + +void FileExplorer::ProcessLoadQueue() +{ + while (!s_loadQueue.empty()) { + fs::path p = s_loadQueue.back(); + s_loadQueue.pop_back(); + + // Load the asset and get the type,... Long way... + AssetManager::LoadAssetAsync(p.string(), AssetManager::AssetTypeFromExtension(AssetManager::GetFileExtension(p.string()))); + + Logger::LogVerbose("FileExplorer: imported '%s'", p.string().c_str()); + } +} + + +std::string FileExplorer::IconFileForPath(const fs::path &path) +{ + auto ext = path.extension().string(); + if (ext == ".cene") return "movie-open-outline.png"; + return "error.png"; +} + + + namespace fs = std::filesystem; std::string ProjectManager::s_currentProjectPath; @@ -102,7 +448,7 @@ bool CreateDirectories(const fs::path &baseDir) "Scenes", "Images", "Sounds", - "Fomts", + "Fonts", "Shaders", "Scripts", "Videos", @@ -129,25 +475,49 @@ bool CreateDirectories(const fs::path &baseDir) return allOk; } +bool ProjectManager::HasProject() { + if (s_currentProjectPath.empty() || s_currentProjectName.empty()) + { + return false; + + } + return true; +} + + + std::string ProjectManager::ResolveResPath(const std::string &resPath) { constexpr const char *prefix = "res://"; + // if it’s not a “res://” path, just return it if (resPath.rfind(prefix, 0) != 0) return resPath; - if (s_currentProjectPath.empty() || s_currentProjectName.empty()) + if (s_currentProjectPath.empty()) { - Logger::LogError("Cannot resolve res path: 'project not loaded!'"); + Logger::LogError("Cannot resolve res path: project not loaded!"); return resPath; } - fs::path baseDir = fs::path(s_currentProjectPath) / s_currentProjectName; - std::string relativePart = resPath.substr(strlen(prefix)); - fs::path fullPath = baseDir / relativePart; + // start from whatever s_currentProjectPath holds + fs::path baseDir{s_currentProjectPath}; - return fullPath.lexically_normal().string(); + // only append the project name if it isn't already the last path component + if (!s_currentProjectName.empty() && + baseDir.filename().string() != s_currentProjectName) + { + baseDir /= s_currentProjectName; + } + + // drop the "res://" prefix + std::string relativePart = resPath.substr(strlen(prefix)); + // build & normalize + fs::path fullPath = (baseDir / relativePart).lexically_normal(); + + return fullPath.string(); } + bool ProjectManager::Init() { s_currentProjectPath.clear(); @@ -157,63 +527,60 @@ bool ProjectManager::Init() return true; } -bool ProjectManager::LoadProject(const std::string &projectPath, - const std::string &projectName) +bool ProjectManager::LoadProject(const std::string &projectFilePath) { - fs::path baseDir = fs::path(projectPath) / projectName; - fs::path projFile = baseDir / (projectName + ".cproj"); - - if (!fs::exists(projFile)) - { - Logger::LogInfo("Project '%s' not found at '%s' creating new one.", - projectName.c_str(), projectPath.c_str()); - return SaveProject(projectPath, projectName); + fs::path projFile{ projectFilePath }; + if (projFile.extension() != ".cproj" || !fs::exists(projFile)) { + Logger::LogError("Project file not found or invalid: '%s'", + projectFilePath.c_str()); + return false; } + fs::path baseDir = projFile.parent_path(); + std::string projectName = projFile.stem().string(); + YAML::Node config; - try - { + try { config = YAML::LoadFile(projFile.string()); } - catch (const YAML::Exception &e) - { + catch (const YAML::Exception &e) { Logger::LogError("Failed to parse project file '%s': %s", projFile.string().c_str(), e.what()); return false; } - std::string savedName = config["Name"].as(projectName); - std::string createdDate = config["CreatedDate"].as("unknown"); - std::string s_defaultScene = config["s_defaultScene"].as("s_defaultScene"); + // Read settings + std::string savedName = config["Name"].as(projectName); + std::string defaultScene = config["s_defaultScene"].as(""); - Logger::LogInfo("Loading Project: '%s", savedName.c_str()); + Logger::LogInfo("Loading Project: '%s' from '%s'", + savedName.c_str(), projFile.string().c_str()); - s_currentProjectPath = projectPath; + s_currentProjectPath = baseDir.string(); s_currentProjectName = savedName; - std::string resScenePath = config["s_defaultScene"].as(""); + if (!defaultScene.empty()) { + s_defaultScene = ResolveResPath(defaultScene); + Logger::LogDebug("Loading Default Scene: %s", s_defaultScene.c_str()); - if (!resScenePath.empty()) - { - s_defaultScene = ResolveResPath(resScenePath); - Logger::LogDebug("Loading Scene: %s", s_defaultScene.c_str()); - - if (!std::filesystem::exists(s_defaultScene)) - { - Logger::LogError("Scene file does not exist: %s", s_defaultScene.c_str()); + if (!fs::exists(s_defaultScene)) { + Logger::LogError("Default scene does not exist: %s", s_defaultScene.c_str()); return false; } SceneLoader::LoadScene(s_defaultScene); } - Logger::LogInfo("Loaded project '%s'", savedName.c_str()); + Logger::LogInfo("Successfully loaded project '%s'", savedName.c_str()); CreateDirectories(baseDir); + SceneLoader::LoadScene(s_defaultScene); + return true; } + bool ProjectManager::CreateProject(const std::string &projectPath, const std::string &projectName) { @@ -228,14 +595,11 @@ bool ProjectManager::CreateProject(const std::string &projectPath, return false; } - // Set current project path before scene creation s_currentProjectPath = baseDir.string(); s_currentProjectName = projectName; - // Create initial scene - fs::path scenePath = baseDir / "Assets" / "Scenes" / "main.cene"; + fs::path scenePath = baseDir / "Assets" / "Scenes" / projectName / ".cene"; - SceneLoader::SaveScene(scenePath.string()); s_defaultScene = scenePath.string(); @@ -275,6 +639,9 @@ bool ProjectManager::CreateProject(const std::string &projectPath, CreateDirectories(baseDir); + SceneLoader::SaveScene(scenePath.string()); + + SceneLoader::LoadScene(scenePath.string()); return true; @@ -351,8 +718,16 @@ const std::string &ProjectManager::GetCurrentProjectName() const std::string &ProjectManager::GetCurrentAssetsPath() { static std::string assetsPath; - fs::path p = fs::path(s_currentProjectPath) / s_currentProjectName / "Assets"; - assetsPath = p.string(); - return assetsPath; + fs::path baseDir{s_currentProjectPath}; + if (!s_currentProjectName.empty() && + baseDir.filename().string() != s_currentProjectName) + { + baseDir /= s_currentProjectName; + } + + fs::path assetDir = (baseDir / "Assets").lexically_normal(); + assetsPath = assetDir.string(); + return assetsPath; } + diff --git a/src/src/core/functions/ProjectManager.h b/src/src/core/functions/ProjectManager.h index 2d7eae1..eb48a3b 100644 --- a/src/src/core/functions/ProjectManager.h +++ b/src/src/core/functions/ProjectManager.h @@ -2,6 +2,65 @@ #include #include +#include +#include + + + + +namespace fs = std::filesystem; + +class FileExplorer +{ +public: + // Shows the explorer window (call every frame) + static void Show(bool* p_open = nullptr); + static bool Init(); + + static void Update(); + static void ProcessLoadQueue(); + + + + static bool LoadingDone(); + + static const std::vector& GetLoadQueue() { return s_loadQueue; } + + + static std::string SelectedPath; + +private: + static bool s_initialized; + + // File browsing state + static fs::path s_root; + static fs::path s_currentDir; + + static std::unordered_set s_knownFiles; // <-- new + + + // Icons + static const char* ICONS_PATH; + static unsigned int s_folderIcon; + + // Filters & view state (unchanged)... + static std::string fileSearchQuery; + static bool sortAscending; + static int sortMode; + static bool showScenes, showImages, showAudio, showScripts, showOther; + + // Asset loading queue + static std::vector s_loadQueue; + static size_t s_loadIndex; + static bool s_loadingInit; + + static std::string IconFileForPath(const fs::path &path); + static void DrawFolderTree(const fs::path &path); + + // Initializes file queue by scanning res:// recursively + static void InitLoading(); +}; + class ProjectManager { @@ -10,8 +69,7 @@ public: /// Load an existing project at projectPath/projectName. /// Reads the .cproj, sets current path/name, and ensures folders. - static bool LoadProject(const std::string& projectPath, - const std::string& projectName); + static bool LoadProject(const std::string& projectFilePath); /// Save (or create) a project at projectPath/projectName. /// Writes the .cproj YAML, sets current path/name, and ensures folders. @@ -25,6 +83,8 @@ public: static void ShowCreateProjectPopup(); + static bool HasProject(); + static const std::string& GetCurrentProjectPath(); static const std::string& GetCurrentProjectName(); diff --git a/src/src/core/functions/SceneSerializer.cpp b/src/src/core/functions/SceneSerializer.cpp index ff38775..d055a90 100644 --- a/src/src/core/functions/SceneSerializer.cpp +++ b/src/src/core/functions/SceneSerializer.cpp @@ -43,6 +43,7 @@ void SceneLoader::SaveScene(const std::string &path) YAML::Emitter out; YAML::Emitter sceneData; + // Serialize object list only sceneData << YAML::BeginSeq; for (const auto &obj : objects) @@ -57,6 +58,8 @@ void SceneLoader::SaveScene(const std::string &path) for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) hashHex << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + + out << YAML::BeginMap; out << YAML::Key << "engine_version" << YAML::Value << g_engineConfig.version; out << YAML::Key << "scene_name" << YAML::Value << std::filesystem::path(path).stem().string(); @@ -75,7 +78,7 @@ void SceneLoader::SaveScene(const std::string &path) out << YAML::Key << "threshold" << YAML::Value << Renderer::GetColorCorrection()->threshold; out << YAML::EndMap; - AssetManager::Save(out); + //AssetManager::Save(out); out << YAML::EndMap; @@ -150,15 +153,15 @@ void SceneLoader::LoadScene(const std::string &path) Logger::LogWarning("Scene hash does not match! File may be corrupted or tampered."); } - if (root["Assets"]) - { - currentStep = "Loading Assets"; - currentDetail = "Parsing asset data..."; - loadingUI.Update(currentStep, currentDetail, 0.05f); - - Logger::LogDebug("Loading Assets"); - AssetManager::Load(root["Assets"]); - } + //if (root["Assets"]) + //{ + // currentStep = "Loading Assets"; + // currentDetail = "Parsing asset data..."; + // loadingUI.Update(currentStep, currentDetail, 0.05f); + // + // Logger::LogDebug("Loading Assets"); + // AssetManager::Load(root["Assets"]); + //} Logger::LogDebug("Reseting Scene."); currentStep = "Clearing Previous Scene"; diff --git a/src/src/core/utils/AssetManager.cpp b/src/src/core/utils/AssetManager.cpp index 31bde10..9301409 100644 --- a/src/src/core/utils/AssetManager.cpp +++ b/src/src/core/utils/AssetManager.cpp @@ -4,6 +4,7 @@ #include #include "../audio/AudioEngine.h" #include "LoadingWindow.h" +#include #define STB_IMAGE_IMPLEMENTATION #include #include "../../Entitys/Object.h" @@ -11,12 +12,25 @@ #include #include FT_FREETYPE_H +namespace fs = std::filesystem; + + std::unordered_map> AssetManager::s_Assets; std::unordered_map AssetManager::s_PathToUAID; uint64_t AssetManager::s_NextUAID = 1; +std::string AssetManager::GetFileExtension(const std::string& filepath) +{ + std::string ext = fs::path(filepath).extension().string(); // ".PNG" + if (!ext.empty() && ext.front() == '.') + ext.erase(0, 1); // "PNG" + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return ext; // "png" +} + + AssetType AssetManager::AssetTypeFromExtension(std::string ext) { @@ -75,6 +89,12 @@ AssetType AssetManager::AssetTypeFromExtension(std::string ext) +AssetType AssetManager::AssetTypeFromPath(const std::string& filepath) +{ + return AssetTypeFromExtension(GetFileExtension(filepath)); +} + + const char *MiniaudioResultToString(ma_result result) { diff --git a/src/src/core/utils/AssetManager.h b/src/src/core/utils/AssetManager.h index 6030066..7d79760 100644 --- a/src/src/core/utils/AssetManager.h +++ b/src/src/core/utils/AssetManager.h @@ -157,8 +157,12 @@ public: static uint64_t GenerateUAID(); + + static std::string GetFileExtension(const std::string& filepath); static AssetType AssetTypeFromExtension(std::string ext); + static AssetType AssetTypeFromPath(const std::string& filepath); + private: static std::unordered_map> s_Assets; static std::unordered_map s_PathToUAID; diff --git a/src/src/core/utils/Logging.cpp b/src/src/core/utils/Logging.cpp index 05f6dcf..1a7fef0 100644 --- a/src/src/core/utils/Logging.cpp +++ b/src/src/core/utils/Logging.cpp @@ -56,7 +56,7 @@ bool Logger::s_ShowWarning = true; bool Logger::s_ShowError = true; bool Logger::s_ShowDebug = false; bool Logger::s_ShowVerbose = false; -bool Logger::s_PrintToTerminal = false; +bool Logger::s_PrintToTerminal = true; const char *Logger::ToString(Level level) {