VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
AssetBrowser.cpp
1#include "AssetBrowser.hpp"
2#include <algorithm>
3#include <nlohmann/json.hpp>
4#include <fstream>
5#include <imgui_internal.h>
6
7#include "components/ErrorUtils.hpp"
8#include "components/PathUtils.hpp"
9#include "components/AssetTypes.hpp"
10
11namespace vex {
12
13 AssetBrowser::AssetBrowser(const std::string& rootPath)
14 : m_rootPath(rootPath), m_currentPath(rootPath) {
15 }
16
17 ImTextureID AssetBrowser::GetIcon(const std::filesystem::directory_entry& entry, const BrowserIcons& icons) {
18 if (entry.is_directory()) return icons.folder;
19
20 std::string ext = entry.path().extension().string();
21 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
22
23 if (AssetExtensions::IsValid(ext, AssetExtensions::Texture)) return icons.texture;
24 if (AssetExtensions::IsValid(ext, AssetExtensions::Mesh)) return icons.mesh;
25 if (AssetExtensions::IsValid(ext, AssetExtensions::Audio)) return icons.audio;
26
27 if (ext == ".txt" || ext == ".md") return icons.text;
28 if (ext == ".json"){
29 int type = GetJSONAssetType(entry.path().string());
30 if (type == 1) return icons.scene;
31 if (type == 2) return icons.ui;
32 return icons.text;
33 }
34
35 return icons.file;
36 }
37
38 std::string AssetBrowser::GetExtension(const std::string path){
39 std::filesystem::path p(path);
40 std::string ext = p.extension().string();
41 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
42 return ext;
43 }
44
45 std::string AssetBrowser::Draw(const BrowserIcons& icons, float& thumbnailSize, bool showBottomBar, float height) {
46 std::string selectedFile = "";
47 std::string actionFile = "";
48
49 std::filesystem::path relative = m_currentPath.lexically_relative(m_rootPath);
50
51 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 4.0f));
52
53 if (ImGui::Button("Assets")) {
54 m_currentPath = m_rootPath;
55 }
56
57 if (ImGui::BeginDragDropTarget()) {
58 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_ITEM")) {
59 std::string sourcePathStr = (const char*)payload->Data;
60 std::filesystem::path sourcePath(sourcePathStr);
61 std::filesystem::path destPath = m_rootPath / sourcePath.filename();
62 try {
63 if (sourcePath != destPath) {
64 std::filesystem::rename(sourcePath, destPath);
65 }
66 } catch (const std::filesystem::filesystem_error& e) {
67 vex::log("Error moving file to root: %s", e.what());
68 }
69 }
70 ImGui::EndDragDropTarget();
71 }
72
73 std::filesystem::path accumPath = m_rootPath;
74 for (auto it = relative.begin(); it != relative.end() && relative != "."; ++it) {
75 ImGui::SameLine();
76 ImGui::Text(">");
77 ImGui::SameLine();
78 accumPath /= *it;
79 if (ImGui::Button(it->string().c_str())) {
80 m_currentPath = accumPath;
81 }
82 if (ImGui::BeginDragDropTarget()) {
83 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_ITEM")) {
84 std::string sourcePathStr = (const char*)payload->Data;
85 std::filesystem::path sourcePath(sourcePathStr);
86 std::filesystem::path destPath = accumPath / sourcePath.filename();
87 try {
88 if (sourcePath != destPath) {
89 std::filesystem::rename(sourcePath, destPath);
90 }
91 } catch (const std::filesystem::filesystem_error& e) {
92 vex::log("Error moving file to breadcrumb: %s", e.what());
93 }
94 }
95 ImGui::EndDragDropTarget();
96 }
97 }
98 ImGui::PopStyleVar();
99
100 ImGui::SameLine(ImGui::GetWindowWidth() - 200.0f);
101 ImGui::SetNextItemWidth(180.0f);
102 ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
103
104 ImGui::Separator();
105
106 if (ImGui::BeginPopupContextWindow("AssetBrowserBgContext", ImGuiMouseButton_Right | ImGuiPopupFlags_NoOpenOverItems)) {
107 if (ImGui::MenuItem("Paste", nullptr, false, !m_copiedPath.empty())) {
108 if (std::filesystem::exists(m_copiedPath)) {
109 try {
110 std::filesystem::path dest = m_currentPath / m_copiedPath.filename();
111 int i = 1;
112 std::string stem = m_copiedPath.stem().string();
113 std::string ext = m_copiedPath.extension().string();
114 while (std::filesystem::exists(dest)) {
115 dest = m_currentPath / (stem + " (" + std::to_string(i++) + ")" + ext);
116 }
117
118 std::filesystem::copy(m_copiedPath, dest, std::filesystem::copy_options::recursive);
119
120 if (m_isCut) {
121 std::filesystem::remove_all(m_copiedPath);
122 m_copiedPath.clear();
123 m_isCut = false;
124 }
125 } catch (const std::filesystem::filesystem_error& e) {
127 }
128 }
129 }
130 ImGui::EndPopup();
131 }
132
133 bool isDialog = false;
134 if (ImGuiWindow* window = ImGui::GetCurrentWindow()) {
135 isDialog = (strcmp(window->Name, "Assets") != 0);
136 }
137 if (isDialog) showBottomBar = false;
138
139 float bottomBarHeight = showBottomBar ? (ImGui::GetFrameHeightWithSpacing() + 5.0f) : 0.0f;
140 float contentHeight = height;
141 if (contentHeight <= 0.0f) {
142 contentHeight = isDialog ? (ImGui::GetContentRegionAvail().y - 120.0f) : -bottomBarHeight;
143 if (contentHeight < 100.0f && isDialog) contentHeight = 100.0f;
144 }
145
146 ImGui::BeginChild("BrowserContent", ImVec2(0, contentHeight), false, ImGuiWindowFlags_HorizontalScrollbar);
147
148 float cellSize = thumbnailSize + m_padding;
149 float panelWidth = ImGui::GetContentRegionAvail().x;
150 int columnCount = (int)(panelWidth / cellSize);
151 if (columnCount < 1) columnCount = 1;
152
153 if (ImGui::BeginTable("BrowserGrid", columnCount)) {
154 std::vector<std::filesystem::directory_entry> folders;
155 std::vector<std::filesystem::directory_entry> files;
156
157 try {
158 for (const auto& entry : std::filesystem::directory_iterator(m_currentPath)) {
159 if (entry.is_directory()) {
160 folders.push_back(entry);
161 } else {
162 files.push_back(entry);
163 }
164 }
165 } catch (const std::filesystem::filesystem_error& e) {
167 }
168
169 auto sortAlpha = [](const std::filesystem::directory_entry& a, const std::filesystem::directory_entry& b) {
170 return a.path().filename().string() < b.path().filename().string();
171 };
172 std::sort(folders.begin(), folders.end(), sortAlpha);
173 std::sort(files.begin(), files.end(), sortAlpha);
174
175 auto renderEntry = [&](const std::filesystem::directory_entry& entry) {
176 std::string filename = entry.path().filename().string();
177 if (m_searchBuffer[0] != '\0') {
178 std::string lowerFilename = filename;
179 std::string lowerSearch = m_searchBuffer;
180 std::transform(lowerFilename.begin(), lowerFilename.end(), lowerFilename.begin(), [](unsigned char c){ return std::tolower(c); });
181 std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(), [](unsigned char c){ return std::tolower(c); });
182 if (lowerFilename.find(lowerSearch) == std::string::npos) {
183 return;
184 }
185 }
186
187 ImGui::TableNextColumn();
188 ImGui::PushID(filename.c_str());
189
190 float columnWidth = ImGui::GetContentRegionAvail().x;
191
192 ImTextureID iconID = GetIcon(entry, icons);
193 if (iconID == 0) iconID = icons.file;
194
195 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
196
197 ImGui::ImageButton("##Icon", iconID, { thumbnailSize, thumbnailSize });
198
199 if (ImGui::BeginDragDropSource()) {
200 std::string fullPath = entry.path().string();
201
202 ImGui::SetDragDropPayload("ASSET_ITEM", fullPath.c_str(), fullPath.size() + 1);
203
204 ImGui::Text("Move %s", entry.path().filename().string().c_str());
205 ImGui::Image(iconID, { thumbnailSize/2, thumbnailSize/2 });
206
207 ImGui::EndDragDropSource();
208 }
209
210 if (entry.is_directory()) {
211 if (ImGui::BeginDragDropTarget()) {
212 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_ITEM")) {
213 std::string sourcePathStr = (const char*)payload->Data;
214 std::filesystem::path sourcePath(sourcePathStr);
215 std::filesystem::path targetFolder = entry.path();
216 std::filesystem::path destPath = targetFolder / sourcePath.filename();
217
218 try {
219 bool isSubFolder = (targetFolder.string().find(sourcePath.string()) == 0);
220
221 if (sourcePath != destPath && !isSubFolder) {
222 std::filesystem::rename(sourcePath, destPath);
223 }
224 } catch (const std::filesystem::filesystem_error& e) {
225 vex::log("Error moving file: %s", e.what());
226 }
227 }
228 ImGui::EndDragDropTarget();
229 }
230 }
231
232 if (ImGui::IsItemHovered()) {
233 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
234 if (entry.is_directory()) {
235 m_currentPath /= entry.path().filename();
236 } else {
237 actionFile = entry.path().string();
238 }
239 } else if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
240 selectedFile = entry.path().string();
241 }
242 }
243
244 ImGui::PopStyleColor();
245
246 if (ImGui::BeginPopupContextItem()) {
247 if (ImGui::MenuItem("Open")) {
248 if (entry.is_directory()) m_currentPath /= entry.path().filename();
249 else actionFile = entry.path().string();
250 }
251 ImGui::Separator();
252 if (ImGui::MenuItem("Copy")) {
253 m_copiedPath = entry.path();
254 m_isCut = false;
255 }
256 if (ImGui::MenuItem("Cut")) {
257 m_copiedPath = entry.path();
258 m_isCut = true;
259 }
260 if (ImGui::MenuItem("Duplicate")) {
261 try {
262 std::filesystem::path src = entry.path();
263 std::filesystem::path dest = src;
264 int i = 1;
265 while(std::filesystem::exists(dest)) {
266 dest = src.parent_path() / (src.stem().string() + "_Copy" + std::to_string(i++) + src.extension().string());
267 }
268 std::filesystem::copy(src, dest, std::filesystem::copy_options::recursive);
269 } catch(const std::filesystem::filesystem_error& e) {
271 }
272 }
273 if (ImGui::MenuItem("Rename")) {
274 m_renaming = true;
275 m_renamePath = entry.path();
276 strncpy(m_renameBuffer, filename.c_str(), sizeof(m_renameBuffer));
277 }
278 ImGui::Separator();
279 if (ImGui::MenuItem("Delete")) {
280 try { std::filesystem::remove_all(entry.path()); } catch(...) {}
281 }
282 ImGui::EndPopup();
283 }
284
285 if (m_renaming && m_renamePath == entry.path()) {
286 ImGui::SetNextItemWidth(cellSize);
287 if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
288 std::filesystem::path newPath = entry.path().parent_path() / m_renameBuffer;
289 try {
290 std::filesystem::rename(entry.path(), newPath);
291 } catch (const std::filesystem::filesystem_error& e) {
293 }
294 m_renaming = false;
295 }
296 if (!ImGui::IsItemActive() && (ImGui::IsMouseClicked(0) || ImGui::IsMouseClicked(1))) {
297 m_renaming = false;
298 }
299 }
300 else {
301 float columnWidth = ImGui::GetContentRegionAvail().x;
302 float textWidth = ImGui::CalcTextSize(filename.c_str()).x;
303
304 if (textWidth < (columnWidth * 1.1f)) {
305 float textOffset = (columnWidth - textWidth) * 0.5f;
306 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + textOffset);
307 ImGui::Text("%s", filename.c_str());
308 } else {
309 ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + columnWidth);
310 ImGui::TextWrapped("%s", filename.c_str());
311 ImGui::PopTextWrapPos();
312 }
313 }
314
315 ImGui::PopID();
316 };
317
318 for (const auto& entry : folders) { renderEntry(entry); }
319 for (const auto& entry : files) { renderEntry(entry); }
320
321 ImGui::EndTable();
322 }
323 ImGui::EndChild();
324
325 if (showBottomBar) {
326 ImGui::Separator();
327 ImGui::SetNextItemWidth(150.0f);
328 ImGui::SliderFloat("Icon Size", &thumbnailSize, 16.0f, 256.0f, "%.0f");
329 }
330
331 return actionFile;
332 }
333
334 int AssetBrowser::GetJSONAssetType(const std::string path){
335 std::ifstream f(path);
336 nlohmann::json data = nlohmann::json::parse(f);
337
338 if (data.contains("environment") || data.contains("objects")){
339 return 1;
340 } else if (data.contains("root")) {
341 return 2;
342 }
343 return 0;
344 }
345
346
347}
Class responsible for displaying and navigating project assets within an ImGUI window.
AssetBrowser(const std::string &rootPath)
Constructs the AssetBrowser.
std::string Draw(const BrowserIcons &icons, float &thumbnailSize, bool showBottomBar=true, float height=0.0f)
Draws the Asset Browser UI and handles interaction.
int GetJSONAssetType(const std::string path)
Determines the type of a JSON asset (e.g., scene, material, etc.).
ImTextureID GetIcon(const std::filesystem::directory_entry &entry, const BrowserIcons &icons)
Gets the appropriate icon texture ID for a given directory entry.
std::string GetExtension(const std::string path)
Extracts the file extension from a path.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
void VEX_EXPORT handle_exception(const std::exception &e)
Handles an exception based on build configuration.
Structure holding texture IDs for various asset types displayed in the browser.