资产浏览器可视化改造 + 材质缩略图 GPU 烘焙 + 纹理上传批式化
- 资产浏览器可视化与配色:面板重构(网格/列表/缩略图)、AssetKind 分类、 EditorColors 统一配色、AssetItemView 抽出条目绘制;HierarchyPanel/ MaterialEditorPanel 同步调整 - 材质缩略图改走 GPU 回读烘焙:MaterialPreviewPass 新增 bake pass (SDR atlas → host staging copyImageToBuffer)、Renderer 提供 Request/PollMaterialPreviewBake(无 waitIdle,依赖 per-slot fence), Editor 每帧排空后注入 ThumbnailCache——脱离预览 tile 池,消除"滚出视野 即失效/池耗尽"两问题 - 缩略图失效:BakedIdentity(serial, editVersion) 与当前材质身份比对, 换贴图槽(重载新 serial)/调参数(editVersion++)不符即 Invalidate 重烘, 修"修改材质后资产浏览器缩略图不更新" - 材质加载闸门:每帧至多 1 个 + 按实测耗时冷却,替代固定 2/帧 - 纹理上传批式化:VulkanAssetUploader 新增路径纹理缓存(跨材质去重, 同一贴图只解码/上传一次)+ 同批新纹理合并为单 cmd/单 submit/单 fence; Texture 抽出 RecordPixelsUpload/CreateDeferred/ReleasePendingStaging, 析构兜底释放 staging;MaterialAsset 纹理所有权移交 uploader 缓存—— 修大材质目录首次打开卡顿
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
//
|
||||
// AssetBrowserPanel.cpp:项目资产浏览器实现。
|
||||
//
|
||||
// 条目视觉/拖拽/tooltip 单元住 AssetItemView(列表行与网格瓦片共用同一
|
||||
// 套类别色 + 缩略图);本文件只负责:根切换 / 过滤 / 目录树 / 内容区
|
||||
// 铺排 / 选中态 / 双击分派(打开或实例化)。
|
||||
//
|
||||
|
||||
#include "AssetBrowserPanel.h"
|
||||
#include "MaterialEditorPanel.h" // kMaterialDragPayload + 双击打开材质编辑器
|
||||
#include "ControllerEditorPanel.h" // kControllerDragPayload + 双击打开控制器编辑器
|
||||
|
||||
#include "AssetItemView.h" // 列表行 / 网格瓦片 / 目录条目
|
||||
#include "AssetKind.h" // EditorAsset::AssetKind / KindOfPath
|
||||
#include "MaterialEditorPanel.h" // kMaterialDragPayload + 双击打开材质编辑器
|
||||
#include "ControllerEditorPanel.h" // kControllerDragPayload + 双击打开控制器编辑器
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
@@ -13,10 +20,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
@@ -25,79 +30,26 @@ namespace {
|
||||
constexpr const char* kRootNames[] = {"Assets", "Scenes"};
|
||||
constexpr const char* kRootPaths[] = {"assets", "scenes"};
|
||||
|
||||
// 类型过滤(下标即身份):0 = All
|
||||
// 类型过滤(下标即身份):0 = All;其余对齐 AssetKind 顺序
|
||||
constexpr const char* kTypeNames[] = {"All", "Models",
|
||||
"Textures", "Scenes",
|
||||
"Materials", "Controllers",
|
||||
"Prefabs"};
|
||||
|
||||
// 资产类别:按扩展名分派(过滤用)
|
||||
enum class Kind { Model, Texture, Scene, Material, AnimClip, Prefab,
|
||||
Other };
|
||||
|
||||
Kind KindOf(const std::filesystem::path& p)
|
||||
{
|
||||
std::string ext = p.extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
if (ext == ".gltf" || ext == ".glb" || ext == ".fbx" ||
|
||||
ext == ".obj")
|
||||
{
|
||||
return Kind::Model;
|
||||
}
|
||||
if (ext == ".hdr" || ext == ".png" || ext == ".jpg" ||
|
||||
ext == ".jpeg")
|
||||
{
|
||||
return Kind::Texture;
|
||||
}
|
||||
if (ext == ".scene")
|
||||
{
|
||||
return Kind::Scene;
|
||||
}
|
||||
if (ext == ".material")
|
||||
{
|
||||
return Kind::Material;
|
||||
}
|
||||
if (ext == ".animctrl")
|
||||
{
|
||||
return Kind::AnimClip;
|
||||
}
|
||||
if (ext == ".prefab")
|
||||
{
|
||||
return Kind::Prefab;
|
||||
}
|
||||
return Kind::Other;
|
||||
}
|
||||
|
||||
bool PassFilter(int typeFilter, Kind kind)
|
||||
bool PassFilter(int typeFilter, EditorAsset::AssetKind kind)
|
||||
{
|
||||
switch (typeFilter)
|
||||
{
|
||||
case 1: return kind == Kind::Model;
|
||||
case 2: return kind == Kind::Texture;
|
||||
case 3: return kind == Kind::Scene;
|
||||
case 4: return kind == Kind::Material;
|
||||
case 5: return kind == Kind::AnimClip;
|
||||
case 6: return kind == Kind::Prefab;
|
||||
case 1: return kind == EditorAsset::AssetKind::Model;
|
||||
case 2: return kind == EditorAsset::AssetKind::Texture;
|
||||
case 3: return kind == EditorAsset::AssetKind::Scene;
|
||||
case 4: return kind == EditorAsset::AssetKind::Material;
|
||||
case 5: return kind == EditorAsset::AssetKind::AnimClip;
|
||||
case 6: return kind == EditorAsset::AssetKind::Prefab;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 列表条目前缀符号(ImGui 默认字体无图标字形,用 ASCII 标签)
|
||||
const char* KindTag(Kind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case Kind::Model: return "[M]";
|
||||
case Kind::Texture: return "[T]";
|
||||
case Kind::Scene: return "[S]";
|
||||
case Kind::Material: return "[Ma]";
|
||||
case Kind::AnimClip: return "[An]";
|
||||
case Kind::Prefab: return "[Pf]";
|
||||
default: return "[ ]";
|
||||
}
|
||||
}
|
||||
|
||||
// 相对路径规范化为正斜杠(AssetManager 键与场景文件统一 POSIX 风格)
|
||||
std::string ToAssetPath(const std::filesystem::path& p)
|
||||
{
|
||||
@@ -105,6 +57,50 @@ namespace {
|
||||
std::replace(s.begin(), s.end(), '\\', '/');
|
||||
return s;
|
||||
}
|
||||
|
||||
// 双击分派:材质/控制器 → 打开编辑器;模型/环境 → 缓存预热;
|
||||
// 场景 → 提示走 File 菜单;预制体 → 快捷实例化(授权位姿落点)
|
||||
void DispatchOpen(const EditorContext& ctx,
|
||||
const std::filesystem::path& path,
|
||||
EditorAsset::AssetKind kind)
|
||||
{
|
||||
const std::string assetPath = path.generic_string();
|
||||
if (kind == EditorAsset::AssetKind::Material && ctx.materialEditor)
|
||||
{
|
||||
ctx.materialEditor->OpenAsset(assetPath);
|
||||
}
|
||||
else if (kind == EditorAsset::AssetKind::AnimClip && ctx.controllerEditor)
|
||||
{
|
||||
ctx.controllerEditor->Open(assetPath);
|
||||
}
|
||||
else if (kind == EditorAsset::AssetKind::Model && ctx.assets)
|
||||
{
|
||||
const auto ref = ctx.assets->LoadModel(assetPath);
|
||||
ENGINE_INFO("AssetBrowser: model '{}' loaded (valid={})",
|
||||
assetPath, ref.Valid());
|
||||
}
|
||||
else if (kind == EditorAsset::AssetKind::Texture && ctx.assets)
|
||||
{
|
||||
const auto ref = ctx.assets->LoadEnvironment(assetPath);
|
||||
ENGINE_INFO("AssetBrowser: env '{}' loaded (valid={})",
|
||||
assetPath, ref.Valid());
|
||||
}
|
||||
else if (kind == EditorAsset::AssetKind::Scene)
|
||||
{
|
||||
ENGINE_INFO("AssetBrowser: load scenes via File menu ({})",
|
||||
assetPath);
|
||||
}
|
||||
else if (kind == EditorAsset::AssetKind::Prefab && ctx.scene && ctx.assets)
|
||||
{
|
||||
// 双击 = 快捷实例化(授权位姿落点,选中交给 Outliner 手动改);
|
||||
// 拖拽实例化见 Outliner 落点
|
||||
const auto root = Engine::Serialization::SceneSerializer::
|
||||
InstantiatePrefab(*ctx.scene, *ctx.assets, assetPath);
|
||||
ENGINE_INFO("AssetBrowser: prefab '{}' instantiated (valid={})",
|
||||
assetPath, root != entt::null);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AssetBrowserPanel::Draw(const EditorContext& ctx)
|
||||
@@ -152,7 +148,7 @@ void AssetBrowserPanel::Draw(const EditorContext& ctx)
|
||||
m_CurrentDir = root;
|
||||
}
|
||||
|
||||
// ---- 顶栏下:面包屑(当前目录相对根路径 + 回根按钮)----
|
||||
// ---- 顶栏下:面包屑(当前目录相对根路径 + 回根按钮)+ 视图切换 ----
|
||||
if (ImGui::Button("Root"))
|
||||
{
|
||||
m_CurrentDir = root;
|
||||
@@ -162,9 +158,28 @@ void AssetBrowserPanel::Draw(const EditorContext& ctx)
|
||||
.generic_string();
|
||||
ImGui::TextDisabled("%s/%s", kRootNames[m_Root],
|
||||
rel.empty() ? "" : rel.c_str());
|
||||
|
||||
// 视图切换(列表 / 网格)+ 网格瓦片尺寸
|
||||
ImGui::SameLine();
|
||||
const bool listMode = m_ViewMode == ViewMode::List;
|
||||
if (ImGui::RadioButton("List", listMode))
|
||||
{
|
||||
m_ViewMode = ViewMode::List;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Grid", !listMode))
|
||||
{
|
||||
m_ViewMode = ViewMode::Grid;
|
||||
}
|
||||
if (m_ViewMode == ViewMode::Grid)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
ImGui::SliderFloat("##tile", &m_TileSize, 48.0f, 160.0f, "%.0f px");
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
// ---- 左:目录树 | 右:当前目录文件列表 ----
|
||||
// ---- 左:目录树 | 右:当前目录内容(列表 / 网格)----
|
||||
const float treeWidth = 180.0f;
|
||||
ImGui::BeginChild("##tree", ImVec2(treeWidth, 0.0f),
|
||||
ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders);
|
||||
@@ -194,7 +209,7 @@ void AssetBrowserPanel::Draw(const EditorContext& ctx)
|
||||
ImGuiChildFlags_Borders);
|
||||
if (std::filesystem::exists(m_CurrentDir))
|
||||
{
|
||||
DrawFileList(ctx, m_CurrentDir);
|
||||
DrawContent(ctx, m_CurrentDir);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -227,34 +242,25 @@ void AssetBrowserPanel::DrawDirTree(const EditorContext& ctx,
|
||||
}
|
||||
}
|
||||
|
||||
void AssetBrowserPanel::DrawFileList(const EditorContext& ctx,
|
||||
const std::filesystem::path& dir)
|
||||
void AssetBrowserPanel::DrawContent(const EditorContext& ctx,
|
||||
const std::filesystem::path& dir)
|
||||
{
|
||||
// 收集 + 过滤 + 排序(名字序;目录迭代序不稳定)
|
||||
struct Item
|
||||
{
|
||||
std::filesystem::path path;
|
||||
Kind kind;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// 收集:目录(不受类型过滤影响)+ 过滤后的文件;目录迭代序不稳定 → 排序
|
||||
std::vector<fs::path> dirs;
|
||||
std::vector<std::pair<fs::path, EditorAsset::AssetKind>> files;
|
||||
const std::string filter = m_NameFilter;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir))
|
||||
std::error_code ec;
|
||||
for (const auto& entry : fs::directory_iterator(dir, ec))
|
||||
{
|
||||
if (entry.is_directory())
|
||||
{
|
||||
// 子目录也进列表:单行点击进入(树/列表双向导航)
|
||||
const std::string dname = entry.path().filename().string();
|
||||
ImGui::PushID(dname.c_str());
|
||||
ImGui::TextUnformatted("[D]");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable(dname.c_str()))
|
||||
{
|
||||
m_CurrentDir = entry.path();
|
||||
}
|
||||
ImGui::PopID();
|
||||
dirs.push_back(entry.path());
|
||||
continue;
|
||||
}
|
||||
const Kind kind = KindOf(entry.path());
|
||||
const EditorAsset::AssetKind kind =
|
||||
EditorAsset::KindOfPath(entry.path());
|
||||
if (!PassFilter(m_TypeFilter, kind))
|
||||
{
|
||||
continue;
|
||||
@@ -267,191 +273,106 @@ void AssetBrowserPanel::DrawFileList(const EditorContext& ctx,
|
||||
continue;
|
||||
}
|
||||
}
|
||||
items.push_back({entry.path(), kind});
|
||||
files.emplace_back(entry.path(), kind);
|
||||
}
|
||||
std::sort(items.begin(), items.end(),
|
||||
[](const Item& a, const Item& b)
|
||||
std::sort(dirs.begin(), dirs.end(),
|
||||
[](const fs::path& a, const fs::path& b)
|
||||
{
|
||||
return a.path.filename().string() <
|
||||
b.path.filename().string();
|
||||
return a.filename().string() < b.filename().string();
|
||||
});
|
||||
std::sort(files.begin(), files.end(),
|
||||
[](const auto& a, const auto& b)
|
||||
{
|
||||
return a.first.filename().string() <
|
||||
b.first.filename().string();
|
||||
});
|
||||
|
||||
for (const auto& item : items)
|
||||
// 双击打开延迟到循环外:Immediate GUI 语义——绘制循环内不改世界状态
|
||||
//(prefab 实例化会改场景,边遍历边打开会让后续条目的项状态错乱)
|
||||
bool pendingOpen = false;
|
||||
std::filesystem::path pendingOpenPath;
|
||||
EditorAsset::AssetKind pendingOpenKind = EditorAsset::AssetKind::Other;
|
||||
|
||||
if (m_ViewMode == ViewMode::List)
|
||||
{
|
||||
const std::string name = item.path.filename().string();
|
||||
const std::string assetPath = ToAssetPath(item.path);
|
||||
ImGui::PushID(name.c_str());
|
||||
|
||||
ImGui::TextUnformatted(KindTag(item.kind));
|
||||
ImGui::SameLine();
|
||||
ImGui::Selectable(name.c_str());
|
||||
|
||||
// .material 拖拽源:拖到 Details 材质槽球上指派(kMaterialDragPayload)
|
||||
if (item.kind == Kind::Material && ImGui::BeginDragDropSource())
|
||||
for (const auto& d : dirs)
|
||||
{
|
||||
ImGui::SetDragDropPayload(kMaterialDragPayload,
|
||||
assetPath.c_str(), assetPath.size() + 1);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
// .animctrl 拖拽源:拖到 Details Controller 槽指派
|
||||
//(kControllerDragPayload,对齐材质槽交互)
|
||||
if (item.kind == Kind::AnimClip && ImGui::BeginDragDropSource())
|
||||
{
|
||||
ImGui::SetDragDropPayload(kControllerDragPayload,
|
||||
assetPath.c_str(), assetPath.size() + 1);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
// .prefab 拖拽源:拖到 Outliner(节点=挂其下 / 空白区=挂根)
|
||||
// 实例化整棵子树(kPrefabDragPayload)
|
||||
if (item.kind == Kind::Prefab && ImGui::BeginDragDropSource())
|
||||
{
|
||||
ImGui::SetDragDropPayload(kPrefabDragPayload,
|
||||
assetPath.c_str(), assetPath.size() + 1);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
// 模型拖拽源:拖到 Outliner 落点创建带 MeshRenderer 的实体
|
||||
//(kModelDragPayload;实体名 = 文件名)
|
||||
if (item.kind == Kind::Model && ImGui::BeginDragDropSource())
|
||||
{
|
||||
ImGui::SetDragDropPayload(kModelDragPayload,
|
||||
assetPath.c_str(), assetPath.size() + 1);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
// 双击:.material 打开 Material Editor;.animctrl 打开
|
||||
// Controller Editor;model/HDR 进缓存预热(路径去重,重复
|
||||
// 双击零成本);场景加载含环境恢复/选中清理流程,提示走
|
||||
// File 菜单
|
||||
if (ImGui::IsItemHovered() &&
|
||||
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
if (item.kind == Kind::Material && ctx.materialEditor)
|
||||
if (EditorAssetView::DrawFolderRow(d))
|
||||
{
|
||||
ctx.materialEditor->OpenAsset(assetPath);
|
||||
}
|
||||
else if (item.kind == Kind::AnimClip && ctx.controllerEditor)
|
||||
{
|
||||
ctx.controllerEditor->Open(assetPath);
|
||||
}
|
||||
else if (item.kind == Kind::Model && ctx.assets)
|
||||
{
|
||||
const auto ref = ctx.assets->LoadModel(assetPath);
|
||||
ENGINE_INFO("AssetBrowser: model '{}' loaded (valid={})",
|
||||
assetPath, ref.Valid());
|
||||
}
|
||||
else if (item.kind == Kind::Texture && ctx.assets)
|
||||
{
|
||||
const auto ref = ctx.assets->LoadEnvironment(assetPath);
|
||||
ENGINE_INFO("AssetBrowser: env '{}' loaded (valid={})",
|
||||
assetPath, ref.Valid());
|
||||
}
|
||||
else if (item.kind == Kind::Scene)
|
||||
{
|
||||
ENGINE_INFO("AssetBrowser: load scenes via File menu ({})",
|
||||
assetPath);
|
||||
}
|
||||
else if (item.kind == Kind::Prefab && ctx.scene && ctx.assets)
|
||||
{
|
||||
// 双击 = 快捷实例化(授权位姿落点,选中交给 Outliner
|
||||
// 手动改);拖拽实例化见 Outliner 落点
|
||||
const auto root = Engine::Serialization::SceneSerializer::
|
||||
InstantiatePrefab(*ctx.scene, *ctx.assets, assetPath);
|
||||
ENGINE_INFO("AssetBrowser: prefab '{}' instantiated "
|
||||
"(valid={})",
|
||||
assetPath, root != entt::null);
|
||||
m_CurrentDir = d;
|
||||
m_SelectedPath.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// hover 信息卡(UE Content Browser 式):类型/大小/修改时间/加载状态
|
||||
if (ImGui::IsItemHovered())
|
||||
for (const auto& [path, kind] : files)
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::Separator();
|
||||
// 与 Kind 枚举逐位对齐(Kind::Other = 6)——漏项 = tooltip
|
||||
// 数组越界读
|
||||
static const char* kKindNames[] = {"Model", "Texture/Environment",
|
||||
"Scene", "Material",
|
||||
"Controller", "Prefab",
|
||||
"File"};
|
||||
ImGui::Text("type: %s", kKindNames[static_cast<int>(item.kind)]);
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
const auto sz = fs::file_size(item.path, ec);
|
||||
if (!ec)
|
||||
const std::string assetPath = ToAssetPath(path);
|
||||
const auto res = EditorAssetView::DrawRow(
|
||||
ctx, path, assetPath == m_SelectedPath);
|
||||
if (res.clicked)
|
||||
{
|
||||
char szBuf[32];
|
||||
if (sz >= 1024ull * 1024ull)
|
||||
{
|
||||
std::snprintf(szBuf, sizeof(szBuf), "%.1f MB",
|
||||
static_cast<double>(sz) / (1024.0 * 1024.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::snprintf(szBuf, sizeof(szBuf), "%.1f KB",
|
||||
static_cast<double>(sz) / 1024.0);
|
||||
}
|
||||
ImGui::Text("size: %s", szBuf);
|
||||
m_SelectedPath = assetPath;
|
||||
}
|
||||
const auto ftime = fs::last_write_time(item.path, ec);
|
||||
if (!ec)
|
||||
if (res.doubleClicked)
|
||||
{
|
||||
// file_clock → system_clock 近似转换(C++17 无官方转换)
|
||||
const auto sctp = std::chrono::time_point_cast<
|
||||
std::chrono::system_clock::duration>(
|
||||
ftime - fs::file_time_type::clock::now() +
|
||||
std::chrono::system_clock::now());
|
||||
const std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
|
||||
char tmBuf[32];
|
||||
std::strftime(tmBuf, sizeof(tmBuf), "%Y-%m-%d %H:%M",
|
||||
std::localtime(&tt));
|
||||
ImGui::Text("modified: %s", tmBuf);
|
||||
pendingOpen = true;
|
||||
pendingOpenPath = path;
|
||||
pendingOpenKind = kind;
|
||||
}
|
||||
// 加载状态:只对"加载"型资产有意义(查询不触发加载)。
|
||||
// Prefab 是实例化模板、Scene 走 File 菜单——显示 "not
|
||||
// loaded" 会误导(曾让用户当成 bug)
|
||||
if (ctx.assets)
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// ---- 网格视图:固定列宽表格铺排(列数按可用宽度自适应)----
|
||||
const float tile = m_TileSize;
|
||||
const float cellW = tile + ImGui::GetStyle().CellPadding.x * 2.0f;
|
||||
int cols = static_cast<int>(ImGui::GetContentRegionAvail().x / cellW);
|
||||
if (cols < 1)
|
||||
{
|
||||
cols = 1;
|
||||
}
|
||||
if (ImGui::BeginTable("##grid", cols, ImGuiTableFlags_SizingFixedFit))
|
||||
{
|
||||
for (int i = 0; i < cols; ++i)
|
||||
{
|
||||
if (item.kind == Kind::Model ||
|
||||
item.kind == Kind::Texture)
|
||||
ImGui::TableSetupColumn("##c",
|
||||
ImGuiTableColumnFlags_WidthFixed,
|
||||
cellW);
|
||||
}
|
||||
for (const auto& d : dirs)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
const std::string assetPath = ToAssetPath(d);
|
||||
if (EditorAssetView::DrawFolderTile(
|
||||
d, tile, assetPath == m_SelectedPath))
|
||||
{
|
||||
const bool loaded =
|
||||
(item.kind == Kind::Model)
|
||||
? ctx.assets->IsModelLoaded(assetPath)
|
||||
: ctx.assets->IsEnvironmentLoaded(assetPath);
|
||||
ImGui::Text("state: %s", loaded ? "loaded"
|
||||
: "not loaded");
|
||||
}
|
||||
else if (item.kind == Kind::Prefab)
|
||||
{
|
||||
ImGui::TextDisabled(
|
||||
"template — drag into scene / double-click");
|
||||
}
|
||||
// 已加载的 model 展开资产数据概览(meshes=0 即纯动画
|
||||
// 容器;未加载不显示——查询不触发加载)
|
||||
if (item.kind == Kind::Model)
|
||||
{
|
||||
if (const Engine::Model* m =
|
||||
ctx.assets->GetModelByPath(assetPath);
|
||||
m != nullptr)
|
||||
{
|
||||
ImGui::Text("meshes: %u, bones: %u, clips: %u",
|
||||
static_cast<unsigned>(m->GetMeshes().size()),
|
||||
m->GetBoneCount(),
|
||||
static_cast<unsigned>(m->GetClips().size()));
|
||||
}
|
||||
m_CurrentDir = d;
|
||||
m_SelectedPath.clear();
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled("%s", assetPath.c_str());
|
||||
ImGui::EndTooltip();
|
||||
for (const auto& [path, kind] : files)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
const std::string assetPath = ToAssetPath(path);
|
||||
const auto res = EditorAssetView::DrawTile(
|
||||
ctx, path, tile, assetPath == m_SelectedPath);
|
||||
if (res.clicked)
|
||||
{
|
||||
m_SelectedPath = assetPath;
|
||||
}
|
||||
if (res.doubleClicked)
|
||||
{
|
||||
pendingOpen = true;
|
||||
pendingOpenPath = path;
|
||||
pendingOpenKind = kind;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (pendingOpen)
|
||||
{
|
||||
DispatchOpen(ctx, pendingOpenPath, pendingOpenKind);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user