#include #include #include #include #include #include #include namespace { [[nodiscard]] std::string Lowercase(std::string_view value) { std::string result{value}; std::ranges::transform(result, result.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return result; } [[nodiscard]] bool HasExtension(std::string_view path, std::initializer_list extensions) { const std::string extension = Lowercase(std::filesystem::path{path}.extension().string()); return std::ranges::find(extensions, extension) != extensions.end(); } // Cooker outputs and bookkeeping sit next to the sources in the runtime folders, nobody picks those [[nodiscard]] bool IsHidden(const std::filesystem::path& path) { return path.filename() == "manifest.json" || HasExtension(path.string(), {".dmodel", ".dtex", ".spv"}); } [[nodiscard]] const FSMount* FindMount(const std::string& scheme) { for (const FSMount& mount : AssetFS::GetInstance().GetMounts()) { if (mount.scheme == scheme) { return &mount; } } return nullptr; } } bool AssetBrowser::IsModel(std::string_view path) { return HasExtension(path, {".fbx", ".glb", ".gltf", ".obj"}); } void AssetBrowser::Draw(bool* open) { if (!ImGui::Begin("Assets", open)) { ImGui::End(); return; } const auto& mounts = AssetFS::GetInstance().GetMounts(); if (mounts.empty()) { ImGui::TextDisabled("No asset folders are mounted"); ImGui::End(); return; } if (FindMount(m_Scheme) == nullptr) { Navigate(mounts.front().scheme, {}); } for (const FSMount& mount : mounts) { if (ImGui::RadioButton(mount.scheme.c_str(), m_Scheme == mount.scheme)) { Navigate(mount.scheme, {}); } ImGui::SameLine(); } if (ImGui::Button("Refresh")) { m_Dirty = true; } ImGui::SameLine(); ImGui::SetNextItemWidth(-FLT_MIN); ImGuiUtils::InputString("##Filter", m_Filter, 0, "Search this folder"); ImGui::BeginDisabled(m_Folder.empty()); if (ImGui::ArrowButton("##Up", ImGuiDir_Up)) { Navigate(m_Scheme, m_Folder.parent_path()); } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::TextUnformatted((m_Scheme + "://" + m_Folder.generic_string()).c_str()); ImGui::Separator(); if (m_Dirty) { Refresh(); } if (ImGui::BeginChild("Entries")) { const std::string filter = Lowercase(m_Filter); for (const Entry& entry : m_Entries) { if ((filter.empty() || Lowercase(entry.name).find(filter) != std::string::npos) && DrawEntry(entry)) { // Entered a folder, the list is rebuilt next frame break; } } } ImGui::EndChild(); ImGui::End(); } bool AssetBrowser::DrawEntry(const Entry& entry) { bool navigated = false; const std::string virtualPath = m_Scheme + "://" + (m_Folder / entry.name).generic_string(); const std::string label = (entry.directory ? "[dir] " : IsModel(entry.name) ? "[model] " : "") + entry.name; ImGui::PushID(entry.name.c_str()); if (ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick) && entry.directory && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { Navigate(m_Scheme, m_Folder / entry.name); navigated = true; } if (!entry.directory && ImGui::BeginDragDropSource()) { ImGui::SetDragDropPayload(ImGuiUtils::AssetPayload, virtualPath.c_str(), virtualPath.size() + 1); ImGui::TextUnformatted(virtualPath.c_str()); if (IsModel(entry.name)) { ImGui::TextDisabled("Drop it in the viewport to place it"); } ImGui::EndDragDropSource(); } if (ImGui::BeginPopupContextItem("EntryMenu")) { if (ImGui::MenuItem("Copy Path")) { ImGui::SetClipboardText(virtualPath.c_str()); } ImGui::EndPopup(); } ImGui::SetItemTooltip("%s", virtualPath.c_str()); ImGui::PopID(); return navigated; } void AssetBrowser::Navigate(const std::string& scheme, const std::filesystem::path& folder) { m_Scheme = scheme; m_Folder = folder; m_Filter.clear(); m_Dirty = true; } void AssetBrowser::Refresh() { m_Dirty = false; m_Entries.clear(); const FSMount* mount = FindMount(m_Scheme); if (mount == nullptr) { return; } std::error_code error; for (const auto& item : std::filesystem::directory_iterator(mount->root / m_Folder, error)) { if (IsHidden(item.path())) { continue; } m_Entries.push_back({ .name = item.path().filename().string(), .directory = item.is_directory(error), }); } std::ranges::sort(m_Entries, [](const Entry& a, const Entry& b) { if (a.directory != b.directory) { return a.directory; } return Lowercase(a.name) < Lowercase(b.name); }); }