165 lines
5.1 KiB
C++
165 lines
5.1 KiB
C++
#include <destrum/Util/AssetBrowser.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cctype>
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <destrum/FS/AssetFS.h>
|
|
#include <destrum/Util/ImGuiUtils.h>
|
|
|
|
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<char>(std::tolower(c)); });
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] bool HasExtension(std::string_view path, std::initializer_list<std::string_view> 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);
|
|
});
|
|
}
|