Compare commits
2 Commits
91f8eff00d
...
7cee708801
Author | SHA1 | Date | |
---|---|---|---|
|
7cee708801 | ||
|
86c90cbf8d |
@ -48,17 +48,7 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(glfw)
|
||||
|
||||
# Assimp
|
||||
FetchContent_Declare(
|
||||
assimp
|
||||
GIT_REPOSITORY https://github.com/assimp/assimp.git
|
||||
GIT_TAG master
|
||||
)
|
||||
set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE)
|
||||
set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE)
|
||||
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(ASSIMP_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(assimp)
|
||||
|
||||
|
||||
# ImGui (Docking)
|
||||
FetchContent_Declare(
|
||||
@ -145,7 +135,6 @@ target_link_libraries(Editor PRIVATE
|
||||
Core
|
||||
ImGui
|
||||
glfw
|
||||
assimp::assimp
|
||||
GLEW::GLEW
|
||||
yaml-cpp
|
||||
${CMAKE_DL_LIBS}
|
||||
|
@ -1,196 +1,189 @@
|
||||
#include "AssetManager.h"
|
||||
#include "assets/Texture2D.h"
|
||||
#include "Logger.h"
|
||||
#include <string>
|
||||
#include <stb/stb_image.h>
|
||||
#include <GL/glew.h>
|
||||
#include <fstream>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
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::shared_ptr<ResourceTreeNode> AssetManager::s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
namespace OX {
|
||||
|
||||
std::mutex AssetManager::s_QueueMutex;
|
||||
std::queue<std::string> AssetManager::s_DeferredLoadQueue;
|
||||
std::unordered_map<std::string, std::shared_ptr<Asset>> AssetManager::s_LoadedAssets;
|
||||
std::unordered_map<std::string, AssetManager::AssetMetadata> AssetManager::s_MetadataMap;
|
||||
std::shared_ptr<ResourceTreeNode> AssetManager::s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
|
||||
fs::path AssetManager::s_ProjectRoot;
|
||||
std::atomic<bool> AssetManager::s_Scanning = false;
|
||||
std::thread AssetManager::s_ScanThread;
|
||||
std::mutex AssetManager::s_TextureQueueMutex;
|
||||
std::queue<AssetManager::PendingTexture> AssetManager::s_TextureUploadQueue;
|
||||
|
||||
void AssetManager::Init(const std::string &projectRoot)
|
||||
{
|
||||
s_ProjectRoot = fs::absolute(projectRoot);
|
||||
s_Scanning = true;
|
||||
s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
s_FileTree->name = "res://";
|
||||
s_FileTree->path = "res://";
|
||||
s_FileTree->isDirectory = true;
|
||||
fs::path AssetManager::s_ProjectRoot;
|
||||
std::atomic<bool> AssetManager::s_Scanning = false;
|
||||
std::thread AssetManager::s_ScanThread;
|
||||
|
||||
void AssetManager::Init(const std::string& projectRoot) {
|
||||
s_ProjectRoot = fs::absolute(projectRoot);
|
||||
s_Scanning = true;
|
||||
|
||||
s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
s_FileTree->name = "res://";
|
||||
s_FileTree->path = "res://";
|
||||
s_FileTree->isDirectory = true;
|
||||
|
||||
s_ScanThread = std::thread([=] {
|
||||
BackgroundScan(s_ProjectRoot);
|
||||
s_Scanning = false;
|
||||
});
|
||||
}
|
||||
|
||||
void AssetManager::Shutdown() {
|
||||
if (s_ScanThread.joinable())
|
||||
s_ScanThread.join();
|
||||
}
|
||||
|
||||
void AssetManager::Tick() {
|
||||
std::lock_guard<std::mutex> lock(s_TextureQueueMutex);
|
||||
if (!s_TextureUploadQueue.empty()) {
|
||||
auto pending = s_TextureUploadQueue.front();
|
||||
s_TextureUploadQueue.pop();
|
||||
|
||||
GLuint texID;
|
||||
glGenTextures(1, &texID);
|
||||
glBindTexture(GL_TEXTURE_2D, texID);
|
||||
|
||||
GLenum format = pending.channels == 4 ? GL_RGBA : GL_RGB;
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, pending.width, pending.height, 0, format, GL_UNSIGNED_BYTE, pending.data);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
stbi_image_free(pending.data);
|
||||
|
||||
auto tex = std::make_shared<Texture2D>();
|
||||
tex->SetFromGL(texID, pending.width, pending.height);
|
||||
s_LoadedAssets[pending.id] = tex;
|
||||
Logger::LogInfo("%u | %u", tex->GetID(), texID);
|
||||
|
||||
s_ScanThread = std::thread([=]
|
||||
{
|
||||
BackgroundScan(s_ProjectRoot);
|
||||
s_Scanning = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager::Shutdown()
|
||||
{
|
||||
if (s_ScanThread.joinable())
|
||||
s_ScanThread.join();
|
||||
}
|
||||
void AssetManager::Rescan() {
|
||||
if (s_Scanning) return;
|
||||
s_Scanning = true;
|
||||
|
||||
void AssetManager::Tick()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_QueueMutex);
|
||||
if (!s_DeferredLoadQueue.empty()) {
|
||||
auto resPath = s_DeferredLoadQueue.front();
|
||||
s_DeferredLoadQueue.pop();
|
||||
LoadAsset(resPath);
|
||||
}
|
||||
}
|
||||
s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
s_FileTree->name = "res://";
|
||||
s_FileTree->path = "res://";
|
||||
s_FileTree->isDirectory = true;
|
||||
|
||||
s_ScanThread = std::thread([=] {
|
||||
BackgroundScan(s_ProjectRoot);
|
||||
s_Scanning = false;
|
||||
});
|
||||
}
|
||||
|
||||
void AssetManager::SaveAssetPack(const std::string& outputPath)
|
||||
{
|
||||
YAML::Emitter out;
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq;
|
||||
void AssetManager::BackgroundScan(const fs::path& root) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(root)) {
|
||||
const auto& path = entry.path();
|
||||
std::string resPath = MakeVirtualPath(path);
|
||||
|
||||
for (const auto& [resPath, meta] : s_MetadataMap) {
|
||||
fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot);
|
||||
if (entry.is_directory()) {
|
||||
AddToTree(resPath, true);
|
||||
} else {
|
||||
AddToTree(resPath, false);
|
||||
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "id" << YAML::Value << resPath;
|
||||
out << YAML::Key << "type" << YAML::Value << meta.type;
|
||||
out << YAML::Key << "path" << YAML::Value << relative.generic_string();
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
|
||||
out << YAML::EndSeq;
|
||||
out << YAML::EndMap;
|
||||
|
||||
std::ofstream fout(outputPath);
|
||||
fout << out.c_str();
|
||||
fout.close();
|
||||
|
||||
Logger::LogInfo("Saved asset pack: %s", outputPath.c_str());
|
||||
}
|
||||
|
||||
|
||||
void AssetManager::Rescan()
|
||||
{
|
||||
if (s_Scanning) return;
|
||||
s_Scanning = true;
|
||||
s_FileTree = std::make_shared<ResourceTreeNode>();
|
||||
s_FileTree->name = "res://";
|
||||
s_FileTree->path = "res://";
|
||||
s_FileTree->isDirectory = true;
|
||||
|
||||
s_ScanThread = std::thread([=]
|
||||
{
|
||||
BackgroundScan(s_ProjectRoot);
|
||||
s_Scanning = false;
|
||||
});
|
||||
}
|
||||
|
||||
void AssetManager::BackgroundScan(const fs::path &root)
|
||||
{
|
||||
for (const auto &entry: fs::recursive_directory_iterator(root)) {
|
||||
const auto &path = entry.path();
|
||||
std::string resPath = MakeVirtualPath(path);
|
||||
|
||||
if (entry.is_directory()) {
|
||||
AddToTree(resPath, true);
|
||||
} else {
|
||||
AddToTree(resPath, false);
|
||||
|
||||
std::string type = DetectAssetType(path);
|
||||
if (!type.empty()) {
|
||||
std::lock_guard<std::mutex> lock(s_QueueMutex);
|
||||
s_MetadataMap[resPath] = {type, path.string()};
|
||||
s_DeferredLoadQueue.push(resPath);
|
||||
std::string type = DetectAssetType(path);
|
||||
if (!type.empty()) {
|
||||
if (type == "texture2D") {
|
||||
int w, h, ch;
|
||||
stbi_set_flip_vertically_on_load(1);
|
||||
unsigned char* data = stbi_load(path.string().c_str(), &w, &h, &ch, 0);
|
||||
if (data) {
|
||||
std::lock_guard<std::mutex> lock(s_TextureQueueMutex);
|
||||
s_TextureUploadQueue.push({ resPath, path.string(), w, h, ch, data });
|
||||
s_MetadataMap[resPath] = { type, path.string() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager::AddToTree(const std::string &resPath, bool isDir)
|
||||
{
|
||||
std::vector<std::string> parts;
|
||||
size_t pos = 0, next;
|
||||
while ((next = resPath.find('/', pos)) != std::string::npos) {
|
||||
if (next != pos) parts.push_back(resPath.substr(pos, next - pos));
|
||||
pos = next + 1;
|
||||
}
|
||||
if (pos < resPath.length()) parts.push_back(resPath.substr(pos));
|
||||
void AssetManager::AddToTree(const std::string& resPath, bool isDir) {
|
||||
std::vector<std::string> parts;
|
||||
size_t pos = 0, next;
|
||||
while ((next = resPath.find('/', pos)) != std::string::npos) {
|
||||
if (next != pos) parts.push_back(resPath.substr(pos, next - pos));
|
||||
pos = next + 1;
|
||||
}
|
||||
if (pos < resPath.length()) parts.push_back(resPath.substr(pos));
|
||||
|
||||
auto current = s_FileTree;
|
||||
for (size_t i = 1; i < parts.size(); ++i) {
|
||||
auto &name = parts[i];
|
||||
auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto &child)
|
||||
{
|
||||
return child->name == name;
|
||||
});
|
||||
auto current = s_FileTree;
|
||||
for (size_t i = 1; i < parts.size(); ++i) {
|
||||
auto& name = parts[i];
|
||||
auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto& child) {
|
||||
return child->name == name;
|
||||
});
|
||||
|
||||
if (it == current->children.end()) {
|
||||
auto newNode = std::make_shared<ResourceTreeNode>();
|
||||
newNode->name = name;
|
||||
newNode->path = current->path + "/" + name;
|
||||
newNode->isDirectory = (i < parts.size() - 1) || isDir;
|
||||
current->children.push_back(newNode);
|
||||
current = newNode;
|
||||
} else {
|
||||
current = *it;
|
||||
}
|
||||
if (it == current->children.end()) {
|
||||
auto newNode = std::make_shared<ResourceTreeNode>();
|
||||
newNode->name = name;
|
||||
newNode->path = current->path + "/" + name;
|
||||
newNode->isDirectory = (i < parts.size() - 1) || isDir;
|
||||
current->children.push_back(newNode);
|
||||
current = newNode;
|
||||
} else {
|
||||
current = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string AssetManager::MakeVirtualPath(const fs::path &full)
|
||||
{
|
||||
fs::path rel = fs::relative(full, s_ProjectRoot);
|
||||
return "res://" + rel.generic_string();
|
||||
std::string AssetManager::MakeVirtualPath(const fs::path& full) {
|
||||
fs::path rel = fs::relative(full, s_ProjectRoot);
|
||||
return "res://" + rel.generic_string();
|
||||
}
|
||||
|
||||
std::string AssetManager::DetectAssetType(const fs::path& ext) {
|
||||
auto e = ext.extension().string();
|
||||
if (e == ".png" || e == ".jpg") return "texture2D";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::shared_ptr<Asset> AssetManager::Get(const std::string& resPath) {
|
||||
auto it = s_LoadedAssets.find(resPath);
|
||||
return (it != s_LoadedAssets.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void AssetManager::SaveAssetPack(const std::string& outputPath) {
|
||||
YAML::Emitter out;
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq;
|
||||
|
||||
for (const auto& [resPath, meta] : s_MetadataMap) {
|
||||
fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot);
|
||||
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "id" << YAML::Value << resPath;
|
||||
out << YAML::Key << "type" << YAML::Value << meta.type;
|
||||
out << YAML::Key << "path" << YAML::Value << relative.generic_string();
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
|
||||
std::string AssetManager::DetectAssetType(const fs::path &ext)
|
||||
{
|
||||
auto e = ext.extension().string();
|
||||
if (e == ".png" || e == ".jpg") return "texture2D";
|
||||
if (e == ".ogg" || e == ".wav") return "audio";
|
||||
return "";
|
||||
}
|
||||
out << YAML::EndSeq;
|
||||
out << YAML::EndMap;
|
||||
|
||||
std::shared_ptr<Asset> AssetManager::Get(const std::string &resPath)
|
||||
{
|
||||
auto it = s_LoadedAssets.find(resPath);
|
||||
return (it != s_LoadedAssets.end()) ? it->second : nullptr;
|
||||
}
|
||||
std::ofstream fout(outputPath);
|
||||
fout << out.c_str();
|
||||
fout.close();
|
||||
|
||||
bool AssetManager::LoadAsset(const std::string &resPath)
|
||||
{
|
||||
auto it = s_MetadataMap.find(resPath);
|
||||
if (it == s_MetadataMap.end()) return false;
|
||||
Logger::LogInfo("Saved asset pack: %s", outputPath.c_str());
|
||||
}
|
||||
|
||||
const auto &meta = it->second;
|
||||
std::shared_ptr<Asset> asset;
|
||||
std::shared_ptr<ResourceTreeNode> AssetManager::GetFileTree() {
|
||||
return s_FileTree;
|
||||
}
|
||||
|
||||
if (meta.type == "texture2D")
|
||||
asset = std::make_shared<Texture2D>();
|
||||
|
||||
if (asset && asset->LoadFromFile(meta.absolutePath)) {
|
||||
s_LoadedAssets[resPath] = asset;
|
||||
Logger::LogDebug("Loaded asset: %s", resPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger::LogError("Failed to load: %s", resPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTreeNode> AssetManager::GetFileTree()
|
||||
{
|
||||
return s_FileTree;
|
||||
}
|
||||
} // namespace OX
|
||||
|
@ -1,12 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
@ -16,7 +15,7 @@ namespace OX {
|
||||
|
||||
struct ResourceTreeNode {
|
||||
std::string name;
|
||||
std::string path; // res://...
|
||||
std::string path;
|
||||
bool isDirectory = false;
|
||||
std::vector<std::shared_ptr<ResourceTreeNode>> children;
|
||||
};
|
||||
@ -25,15 +24,12 @@ namespace OX {
|
||||
public:
|
||||
static void Init(const std::string& projectRoot);
|
||||
static void Shutdown();
|
||||
static void Tick(); // Call every frame
|
||||
|
||||
static void SaveAssetPack(const std::string& outputPath);
|
||||
|
||||
static void Tick(); // Main-thread
|
||||
static void Rescan();
|
||||
|
||||
static std::shared_ptr<Asset> Get(const std::string& resPath);
|
||||
static std::shared_ptr<ResourceTreeNode> GetFileTree();
|
||||
static bool LoadAsset(const std::string& resPath);
|
||||
static void Rescan(); // Rebuilds file tree (slow)
|
||||
static void SaveAssetPack(const std::string& outputPath);
|
||||
|
||||
private:
|
||||
struct AssetMetadata {
|
||||
@ -41,6 +37,13 @@ namespace OX {
|
||||
std::string absolutePath;
|
||||
};
|
||||
|
||||
struct PendingTexture {
|
||||
std::string id;
|
||||
std::string path;
|
||||
int width, height, channels;
|
||||
unsigned char* data;
|
||||
};
|
||||
|
||||
static void BackgroundScan(const std::filesystem::path& root);
|
||||
static void AddToTree(const std::string& virtualPath, bool isDir);
|
||||
static std::string DetectAssetType(const std::filesystem::path& ext);
|
||||
@ -50,8 +53,8 @@ namespace OX {
|
||||
static std::unordered_map<std::string, AssetMetadata> s_MetadataMap;
|
||||
static std::shared_ptr<ResourceTreeNode> s_FileTree;
|
||||
|
||||
static std::mutex s_QueueMutex;
|
||||
static std::queue<std::string> s_DeferredLoadQueue;
|
||||
static std::mutex s_TextureQueueMutex;
|
||||
static std::queue<PendingTexture> s_TextureUploadQueue;
|
||||
|
||||
static std::filesystem::path s_ProjectRoot;
|
||||
static std::atomic<bool> s_Scanning;
|
||||
|
@ -7,42 +7,20 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
#include <GL/glew.h>
|
||||
#include "Texture2D.h"
|
||||
|
||||
namespace OX
|
||||
{
|
||||
Texture2D::Texture2D() = default;
|
||||
namespace OX {
|
||||
|
||||
Texture2D::~Texture2D()
|
||||
{
|
||||
if (m_TextureID != 0) {
|
||||
Texture2D::~Texture2D() {
|
||||
if (m_TextureID)
|
||||
glDeleteTextures(1, &m_TextureID);
|
||||
}
|
||||
}
|
||||
|
||||
bool Texture2D::LoadFromFile(const std::string &path)
|
||||
{
|
||||
stbi_set_flip_vertically_on_load(1);
|
||||
int channels;
|
||||
unsigned char *data = stbi_load(path.c_str(), &m_Width, &m_Height, &channels, 0);
|
||||
|
||||
if (!data) {
|
||||
Logger::LogError("Failed to load texture: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum format = (channels == 4) ? GL_RGBA : GL_RGB;
|
||||
|
||||
glGenTextures(1, &m_TextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, m_TextureID);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, m_Width, m_Height, 0, format, GL_UNSIGNED_BYTE, data);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
stbi_image_free(data);
|
||||
return true;
|
||||
void Texture2D::SetFromGL(uint32_t texID, int width, int height) {
|
||||
m_TextureID = texID;
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
}
|
||||
} // namespace OX
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,23 @@
|
||||
//
|
||||
// Created by spenc on 5/21/2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "systems/Asset.h"
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "GL/glew.h"
|
||||
|
||||
|
||||
namespace OX
|
||||
{
|
||||
class Texture2D : public Asset
|
||||
{
|
||||
namespace OX {
|
||||
class Texture2D : public Asset {
|
||||
public:
|
||||
Texture2D();
|
||||
|
||||
Texture2D() = default;
|
||||
~Texture2D();
|
||||
|
||||
bool LoadFromFile(const std::string &path) override;
|
||||
|
||||
std::string GetTypeName() const override { return "texture2D"; }
|
||||
bool LoadFromFile(const std::string& path) override { return false; } // Not used now
|
||||
|
||||
uint32_t GetID() const { return m_TextureID; }
|
||||
void SetFromGL(uint32_t texID, int width, int height);
|
||||
|
||||
GLuint GetID() const { return m_TextureID; }
|
||||
int GetWidth() const { return m_Width; }
|
||||
int GetHeight() const { return m_Height; }
|
||||
|
||||
@ -30,4 +26,4 @@ namespace OX
|
||||
int m_Width = 0;
|
||||
int m_Height = 0;
|
||||
};
|
||||
} // OX
|
||||
};
|
||||
|
@ -28,10 +28,7 @@ namespace OX
|
||||
operator glm::vec3() const { return {r, g, b}; }
|
||||
operator glm::vec4() const { return {r, g, b, a}; }
|
||||
|
||||
#ifdef HAS_IMGUI
|
||||
operator ImVec3() const { return {r, g, b}; }
|
||||
operator ImVec4() const { return {r, g, b, a}; }
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Arithmetic
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#ifndef EDITOR_H
|
||||
#define EDITOR_H
|
||||
#define OX_EDITOR_VERSION "Obsidian Editor (0.1.5)"
|
||||
#define OX_EDITOR_VERSION "Obsidian Editor (0.1.8)"
|
||||
#include "Layer.h"
|
||||
#include "Core.h"
|
||||
|
||||
|
@ -1,47 +1,116 @@
|
||||
//
|
||||
// Created by spenc on 5/21/2025.
|
||||
//
|
||||
|
||||
|
||||
|
||||
|
||||
#include "FileBrowser.h"
|
||||
#include "systems/AssetManager.h"
|
||||
#include "systems/assets/Texture2D.h"
|
||||
#include "imgui.h"
|
||||
#include <filesystem>
|
||||
|
||||
namespace OX {
|
||||
|
||||
namespace OX
|
||||
{
|
||||
static std::string s_CurrentPath = "res://";
|
||||
|
||||
static void DrawGrid(const std::shared_ptr<ResourceTreeNode>& node) {
|
||||
const int columns = 4;
|
||||
int itemIndex = 0;
|
||||
static void DrawGrid(const std::shared_ptr<ResourceTreeNode> &node)
|
||||
{
|
||||
const float thumbSize = 64.0f;
|
||||
const float padding = 8.0f;
|
||||
const float labelHeight = 20.0f;
|
||||
const float cellWidth = thumbSize + padding * 2;
|
||||
const float cellHeight = thumbSize + labelHeight + padding;
|
||||
|
||||
ImVec2 region = ImGui::GetContentRegionAvail();
|
||||
int columns = std::max(1, (int) (region.x / cellWidth));
|
||||
|
||||
ImGui::BeginChild("FileGrid", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
||||
ImGui::Columns(columns, nullptr, false);
|
||||
|
||||
for (const auto& child : node->children) {
|
||||
std::string label = (child->isDirectory ? "[folder] " : "[file] ") + child->name;
|
||||
for (const auto &child: node->children) {
|
||||
ImGui::PushID(child->path.c_str()); // Unique ID per item
|
||||
|
||||
if (ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(0, 64))) {
|
||||
if (child->isDirectory && ImGui::IsMouseDoubleClicked(0)) {
|
||||
s_CurrentPath = child->path;
|
||||
ImGui::BeginGroup();
|
||||
|
||||
std::string tooltip;
|
||||
std::string label = child->name;
|
||||
ImVec2 cellSize(cellWidth, cellHeight);
|
||||
|
||||
ImGui::InvisibleButton("Cell", cellSize);
|
||||
bool hovered = ImGui::IsItemHovered();
|
||||
bool clicked = ImGui::IsItemClicked();
|
||||
|
||||
// Background
|
||||
ImVec2 min = ImGui::GetItemRectMin();
|
||||
ImVec2 max = ImGui::GetItemRectMax();
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(min, max, IM_COL32(40, 40, 40, 255), 4.0f);
|
||||
|
||||
ImVec2 center = ImVec2(min.x + padding, min.y + padding);
|
||||
|
||||
// === Thumbnail ===
|
||||
if (child->isDirectory) {
|
||||
ImGui::SetCursorScreenPos(center);
|
||||
ImGui::Text("[Folder]");
|
||||
} else {
|
||||
auto asset = AssetManager::Get(child->path);
|
||||
if (asset) {
|
||||
std::string type = asset->GetTypeName();
|
||||
if (type == "texture2D") {
|
||||
auto tex = std::static_pointer_cast<Texture2D>(asset);
|
||||
if (tex->GetID() != 0) {
|
||||
ImGui::SetCursorScreenPos(center);
|
||||
ImGui::Image((ImTextureID) (intptr_t) tex->GetID(), ImVec2(thumbSize, thumbSize),
|
||||
ImVec2(0, 1), ImVec2(1, 0));
|
||||
|
||||
tooltip += "Name: " + child->name + "\n";
|
||||
tooltip += "ID: " + std::to_string(tex->GetID()) + "\n";
|
||||
tooltip += "Size: " + std::to_string(tex->GetWidth()) + "x" + std::to_string(
|
||||
tex->GetHeight()) + "\n";
|
||||
tooltip += "Path: " + child->path + "\n";
|
||||
|
||||
label += " (ID: " + std::to_string(tex->GetID()) + ")";
|
||||
} else {
|
||||
ImGui::SetCursorScreenPos(center);
|
||||
ImGui::Text("[Loading]");
|
||||
}
|
||||
} else {
|
||||
ImGui::SetCursorScreenPos(center);
|
||||
ImGui::Text("[Type: %s]", type.c_str());
|
||||
}
|
||||
} else {
|
||||
ImGui::SetCursorScreenPos(center);
|
||||
ImGui::Text("[Unloaded]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Label ===
|
||||
ImGui::SetCursorScreenPos(ImVec2(min.x + padding, max.y - labelHeight));
|
||||
ImGui::PushTextWrapPos(min.x + cellWidth);
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
// Click handling
|
||||
if (clicked && child->isDirectory) {
|
||||
s_CurrentPath = child->path;
|
||||
}
|
||||
|
||||
if (hovered && !tooltip.empty()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextUnformatted(tooltip.c_str());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::EndGroup();
|
||||
ImGui::NextColumn();
|
||||
++itemIndex;
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::Columns(1);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
static std::shared_ptr<ResourceTreeNode> FindNode(const std::shared_ptr<ResourceTreeNode>& root, const std::string& targetPath) {
|
||||
if (root->path == targetPath) return root;
|
||||
|
||||
for (const auto& child : root->children) {
|
||||
static std::shared_ptr<ResourceTreeNode> FindNode(const std::shared_ptr<ResourceTreeNode> &root,
|
||||
const std::string &targetPath)
|
||||
{
|
||||
if (root->path == targetPath) return root;
|
||||
for (const auto &child: root->children) {
|
||||
if (child->isDirectory) {
|
||||
auto found = FindNode(child, targetPath);
|
||||
if (found) return found;
|
||||
@ -50,9 +119,24 @@ namespace OX {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FileBrowser::Draw() {
|
||||
void FileBrowser::Draw()
|
||||
{
|
||||
ImGui::Begin("File Browser");
|
||||
|
||||
// === Back Button ===
|
||||
if (s_CurrentPath != "res://") {
|
||||
if (ImGui::Button("..")) {
|
||||
size_t lastSlash = s_CurrentPath.find_last_of('/');
|
||||
if (lastSlash != std::string::npos && lastSlash > 6) {
|
||||
// skip 'res://'
|
||||
s_CurrentPath = s_CurrentPath.substr(0, lastSlash);
|
||||
} else {
|
||||
s_CurrentPath = "res://";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Current Path: %s", s_CurrentPath.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
@ -66,5 +150,4 @@ namespace OX {
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace OX
|
||||
|
Loading…
Reference in New Issue
Block a user