// // AssetBrowserPanel.cpp:项目资产浏览器实现。 // #include "AssetBrowserPanel.h" #include #include #include #include #include #include namespace { // 固定根:下标与 Editor 无耦合,仅面板内部使用 constexpr const char* kRootNames[] = {"Assets", "Scenes"}; constexpr const char* kRootPaths[] = {"assets", "scenes"}; // 类型过滤(下标即身份):0 = All constexpr const char* kTypeNames[] = {"All", "Models", "Textures", "Scenes"}; // 资产类别:按扩展名分派(过滤用) enum class Kind { Model, Texture, Scene, 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; } return Kind::Other; } bool PassFilter(int typeFilter, Kind kind) { switch (typeFilter) { case 1: return kind == Kind::Model; case 2: return kind == Kind::Texture; case 3: return kind == Kind::Scene; 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]"; default: return "[ ]"; } } // 相对路径规范化为正斜杠(AssetManager 键与场景文件统一 POSIX 风格) std::string ToAssetPath(const std::filesystem::path& p) { std::string s = p.generic_string(); std::replace(s.begin(), s.end(), '\\', '/'); return s; } } // namespace void AssetBrowserPanel::Draw(const EditorContext& ctx) { if (!ImGui::Begin("Asset Browser")) { ImGui::End(); return; } // ---- 顶栏:根切换 + 类型过滤 + 名字过滤 ---- if (ImGui::BeginTabBar("##roots")) { for (int i = 0; i < 2; ++i) { if (ImGui::BeginTabItem(kRootNames[i])) { m_Root = i; ImGui::EndTabItem(); } } ImGui::EndTabBar(); } ImGui::SameLine(); ImGui::SetNextItemWidth(110.0f); ImGui::Combo("##type", &m_TypeFilter, kTypeNames, 4); ImGui::SameLine(); ImGui::SetNextItemWidth(140.0f); ImGui::InputTextWithHint("##name", "filter", m_NameFilter, sizeof(m_NameFilter)); ImGui::Separator(); const std::filesystem::path root(kRootPaths[m_Root]); // ---- 左:目录树 | 右:文件列表 ---- const float treeWidth = 180.0f; ImGui::BeginChild("##tree", ImVec2(treeWidth, 0.0f), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders); if (std::filesystem::exists(root)) { ImGui::SetNextItemOpen(true, ImGuiCond_Once); DrawDirTree(ctx, root); } else { ImGui::TextDisabled("(no '%s' dir)", kRootPaths[m_Root]); } ImGui::EndChild(); ImGui::SameLine(); ImGui::BeginChild("##files", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders); if (std::filesystem::exists(root)) { DrawFileList(ctx, root); } ImGui::EndChild(); ImGui::End(); } void AssetBrowserPanel::DrawDirTree(const EditorContext& ctx, const std::filesystem::path& dir) { for (const auto& entry : std::filesystem::directory_iterator(dir)) { if (!entry.is_directory()) { continue; } const std::string name = entry.path().filename().string(); if (ImGui::TreeNode(name.c_str())) { DrawDirTree(ctx, entry.path()); ImGui::TreePop(); } } } void AssetBrowserPanel::DrawFileList(const EditorContext& ctx, const std::filesystem::path& dir) { // 收集 + 过滤 + 排序(名字序;目录迭代序不稳定) struct Item { std::filesystem::path path; Kind kind; }; std::vector items; const std::string filter = m_NameFilter; for (const auto& entry : std::filesystem::directory_iterator(dir)) { if (entry.is_directory()) { continue; } const Kind kind = KindOf(entry.path()); if (!PassFilter(m_TypeFilter, kind)) { continue; } if (!filter.empty()) { const std::string name = entry.path().filename().string(); if (name.find(filter) == std::string::npos) { continue; } } items.push_back({entry.path(), kind}); } std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) { return a.path.filename().string() < b.path.filename().string(); }); for (const auto& item : items) { 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()); // 双击预热:model/HDR 进缓存(路径去重,重复双击零成本); // 场景加载含环境恢复/选中清理流程,提示走 File 菜单 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { 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); } } // tooltip:完整相对路径(面板窄时文件名截断仍可辨识) if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", assetPath.c_str()); } ImGui::PopID(); } }