From 961cba13793d4b373abbb631f603a618e19a1cd5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 14:23:26 +0200 Subject: [PATCH 01/33] feat(sonar): add copy and noexcept move consructor in AssetLocation --- engine/src/assets/AssetLocation.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index 710c42651..ccc95d158 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -46,6 +46,12 @@ namespace nexo::assets { setLocation(fullLocation); } + AssetLocation(const AssetLocation&) = default; + AssetLocation& operator=(const AssetLocation&) = default; + + AssetLocation(AssetLocation&&) noexcept = default; + AssetLocation& operator=(AssetLocation&&) noexcept = default; + AssetLocation& setName(const AssetName& name) { _name = name; From 0bae801c23558e0b6eac8b5ecf221fb06886c40a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 14:24:23 +0200 Subject: [PATCH 02/33] refactor(sonar): the lambda used toi retrieve the name in the entity dropdown is now templated for easier compilation deducing --- editor/src/ImNexo/Components.cpp | 89 ------------------------------ editor/src/ImNexo/Components.hpp | 94 +++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 92 deletions(-) diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 45a76ca12..6d6fddcab 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -169,95 +169,6 @@ namespace ImNexo { return clicked; } - bool RowEntityDropdown( - const std::string &label, - nexo::ecs::Entity &targetEntity, - const std::vector& entities, - const std::function& getNameFunc - ) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label.c_str()); - - ImGui::TableNextColumn(); - IdGuard idGuard(label); - - bool changed = false; - - // Build entity-name mapping - static std::vector> entityNamePairs; - static nexo::ecs::Entity lastTargetEntity = 0; - static std::vector lastEntities; - - // Only rebuild the mapping if entities list changed or target entity changed - bool needRebuild = lastTargetEntity != targetEntity || lastEntities.size() != entities.size(); - - if (!needRebuild) { - for (size_t i = 0; i < entities.size() && !needRebuild; i++) { - needRebuild = lastEntities[i] != entities[i]; - } - } - - if (needRebuild) { - entityNamePairs.clear(); - entityNamePairs.reserve(entities.size()); - lastEntities = entities; - lastTargetEntity = targetEntity; - - for (nexo::ecs::Entity entity : entities) { - std::string name = getNameFunc(entity); - entityNamePairs.emplace_back(entity, name); - } - } - - // Find current index - int currentIndex = -1; - for (size_t i = 0; i < entityNamePairs.size(); i++) { - if (entityNamePairs[i].first == targetEntity) { - currentIndex = static_cast(i); - break; - } - } - - // Add a "None" option if we want to allow null selection - const std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; - - // Draw the combo box - ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width - if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) - { - // Optional: Add a "None" option for clearing the target - if (ImGui::Selectable("None", targetEntity == nexo::ecs::MAX_ENTITIES)) { - targetEntity = nexo::ecs::MAX_ENTITIES; - changed = true; - } - - for (size_t i = 0; i < entityNamePairs.size(); i++) - { - const bool isSelected = (currentIndex == static_cast(i)); - if (ImGui::Selectable(entityNamePairs[i].second.c_str(), isSelected)) - { - targetEntity = entityNamePairs[i].first; - changed = true; - } - - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - if (ImGui::IsItemActive()) - setItemActive(); - if (ImGui::IsItemActivated()) - setItemActivated(); - if (ImGui::IsItemDeactivated()) - setItemDeactivated(); - - return changed; - } - bool RowDragFloat(const Channels &channels) { bool modified = false; diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 7a1037d04..73b468214 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -22,6 +22,8 @@ #include "ecs/Coordinator.hpp" #include "renderer/Texture.hpp" #include "Elements.hpp" +#include "Guard.hpp" +#include "ImNexo.hpp" namespace ImNexo { @@ -107,9 +109,95 @@ namespace ImNexo { * @param getNameFunc Function that converts an entity ID to a displayable name string * @return true if an entity was selected (value changed), false otherwise */ - bool RowEntityDropdown(const std::string &label, nexo::ecs::Entity &targetEntity, - const std::vector& entities, - const std::function& getNameFunc); + template + bool RowEntityDropdown( + const std::string label, + nexo::ecs::Entity& targetEntity, + const std::vector& entities, + GetNameFunc&& getNameFunc + ) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); + + ImGui::TableNextColumn(); + IdGuard idGuard(label); + + bool changed = false; + + // Build entity-name mapping + static std::vector> entityNamePairs; + static nexo::ecs::Entity lastTargetEntity = 0; + static std::vector lastEntities; + + // Only rebuild the mapping if entities list changed or target entity changed + bool needRebuild = lastTargetEntity != targetEntity || lastEntities.size() != entities.size(); + + if (!needRebuild) { + for (size_t i = 0; i < entities.size() && !needRebuild; i++) { + needRebuild = lastEntities[i] != entities[i]; + } + } + + if (needRebuild) { + entityNamePairs.clear(); + entityNamePairs.reserve(entities.size()); + lastEntities = entities; + lastTargetEntity = targetEntity; + + for (nexo::ecs::Entity entity : entities) { + std::string name = getNameFunc(entity); + entityNamePairs.emplace_back(entity, name); + } + } + + // Find current index + int currentIndex = -1; + for (size_t i = 0; i < entityNamePairs.size(); i++) { + if (entityNamePairs[i].first == targetEntity) { + currentIndex = static_cast(i); + break; + } + } + + // Add a "None" option if we want to allow null selection + const std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + + // Draw the combo box + ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width + if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) + { + // Optional: Add a "None" option for clearing the target + if (ImGui::Selectable("None", targetEntity == nexo::ecs::MAX_ENTITIES)) { + targetEntity = nexo::ecs::MAX_ENTITIES; + changed = true; + } + + for (size_t i = 0; i < entityNamePairs.size(); i++) + { + const bool isSelected = (currentIndex == static_cast(i)); + if (ImGui::Selectable(entityNamePairs[i].second.c_str(), isSelected)) + { + targetEntity = entityNamePairs[i].first; + changed = true; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + if (ImGui::IsItemActive()) + setItemActive(); + if (ImGui::IsItemActivated()) + setItemActivated(); + if (ImGui::IsItemDeactivated()) + setItemDeactivated(); + + return changed; + } /** * @brief Draws a row with multiple channels (badge + slider pairs) From feac8df8524e46acb1af68091fecd416a248f9ca Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 14:25:20 +0200 Subject: [PATCH 03/33] style(sonar): use has_value for a more explicit code --- editor/src/DocumentWindows/EditorScene/Gizmo.cpp | 4 +++- editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 10e5202a6..8c0d451bc 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -264,7 +264,9 @@ namespace nexo::editor { auto primaryTransform = coord->tryGetComponent(primaryEntity); if (!primaryTransform) { const auto entityWithTransform = findEntityWithTransform(selectedEntities); - if (!entityWithTransform) return; // No entity with transform found + if (!entityWithTransform.has_value()) { + return; // No entity with transform found + } primaryEntity = *entityWithTransform; primaryTransform = coord->tryGetComponent(primaryEntity); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp index b455f5424..fdfb48927 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp @@ -65,7 +65,7 @@ namespace nexo::editor { const std::vector &editorSceneInConfig = findAllEditorScenes(); if (!editorSceneInConfig.empty()) { const auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); - if (!dockId) + if (!dockId.has_value()) return false; m_windowRegistry.setDockId(std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()), *dockId); return true; @@ -78,7 +78,7 @@ namespace nexo::editor { const std::string windowName = std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, currentEditorSceneWindow[0]->getSceneId()); const auto dockId = m_windowRegistry.getDockId(windowName); // If we dont find the dockId, it means the scene is floating, so we create a new dock space node - if (!dockId) { + if (!dockId.has_value()) { setupNewDockSpaceNode(windowName, std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId())); return true; } From b6d88499617a48f64c3001fc5d1b9b143e75f92b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 14:25:44 +0200 Subject: [PATCH 04/33] feat(sonar): delete copy constructor --- editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp index 172f01900..4df72cace 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp @@ -62,6 +62,9 @@ namespace nexo::editor { */ explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry); + ConsoleWindow(const ConsoleWindow&) = delete; + ConsoleWindow& operator=(const ConsoleWindow&) = delete; + /** * @brief Destructor that cleans up the ConsoleWindow. * From 60dad09ec65e321e1626ede9fd9c339ee1c6ed07 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 14:26:04 +0200 Subject: [PATCH 05/33] style(sonar): add early return --- .../AssetManager/FolderTree.cpp | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 68179b6ea..2943005ea 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -57,23 +57,24 @@ namespace nexo::editor { ImGui::EndPopup(); } - if (opened) { - // Use the precomputed children list - if (const auto it = m_folderChildren.find(path); it != m_folderChildren.end()) { - for (const auto& childPath : it->second) { - // Find the name of the child from m_folderStructure - std::string childName; - for (const auto& [p, n] : m_folderStructure) { - if (p == childPath) { - childName = n; - break; - } + if (!opened) + return; + + // Use the precomputed children list + if (const auto it = m_folderChildren.find(path); it != m_folderChildren.end()) { + for (const auto& childPath : it->second) { + // Find the name of the child from m_folderStructure + std::string childName; + for (const auto& [p, n] : m_folderStructure) { + if (p == childPath) { + childName = n; + break; } - drawFolderTreeItem(childName, childPath); } + drawFolderTreeItem(childName, childPath); } - ImGui::TreePop(); } + ImGui::TreePop(); } void AssetManagerWindow::handleNewFolderCreation() From 5f7dd923783424b21682e3ff3a35b82b8501f7ed Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 18:13:49 +0200 Subject: [PATCH 06/33] refactor(front-asset-manager): move layout settings out of the class --- .../AssetManager/AssetManagerWindow.hpp | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 16e7ac7c0..3bdfd20b1 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -23,6 +23,43 @@ namespace nexo::editor { + struct LayoutSizes { + float iconSize = 64.0f; + int iconSpacing = 8; + + ImVec2 itemSize; + ImVec2 itemStep; + int columnCount; + + static constexpr float THUMBNAIL_HEIGHT_RATIO = 0.8f; + static constexpr float TITLE_PADDING = 5.0f; + static constexpr float OVERLAY_SIZE = 6.0f; + static constexpr float OVERLAY_PADDING = 5.0f; + static constexpr float CORNER_RADIUS = 5.0f; + static constexpr float SELECTED_BOX_THICKNESS = 4.0f; + }; + + struct LayoutColors { + ImU32 thumbnailBg; + ImU32 thumbnailBgHovered; + ImU32 thumbnailBgSelected; + ImU32 thumbnailBgSelectedHovered; + + ImU32 selectedBoxColor; + + ImU32 titleBg; + ImU32 titleBgHovered; + ImU32 titleBgSelected; + ImU32 titleBgSelectedHovered; + + ImU32 titleText; + }; + + struct LayoutSettings { + LayoutSizes size; + LayoutColors color; + }; + class AssetManagerWindow final : public ADocumentWindow, LISTENS_TO(event::EventFileDrop) { public: using ADocumentWindow::ADocumentWindow; @@ -35,43 +72,11 @@ namespace nexo::editor { void handleEvent(event::EventFileDrop& event) override; private: - struct LayoutSettings { - struct LayoutSizes { - float iconSize = 64.0f; - int iconSpacing = 8; - ImVec2 itemSize; - ImVec2 itemStep; - int columnCount; - float thumbnailHeightRatio = 0.8f; - float titlePadding = 5.0f; - float overlaySize = 6.0f; - float overlayPadding = 5.0f; - float cornerRadius = 5.0f; - float selectedBoxThickness = 4.0f; - } size; - - struct LayoutColors { - ImU32 thumbnailBg; - ImU32 thumbnailBgHovered; - ImU32 thumbnailBgSelected; - ImU32 thumbnailBgSelectedHovered; - - ImU32 selectedBoxColor; - - ImU32 titleBg; - ImU32 titleBgHovered; - ImU32 titleBgSelected; - ImU32 titleBgSelectedHovered; - - ImU32 titleText; - } color; - }; - std::set m_selectedAssets; std::unordered_map, TransparentStringHash, std::equal_to<>> m_folderChildren; + LayoutSettings m_layout; - void calculateLayout(float availWidth); void drawMenuBar(); void drawAssetsGrid(); void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); @@ -84,6 +89,7 @@ namespace nexo::editor { char m_searchBuffer[256] = ""; void buildFolderStructure(); + void updateFolderChildren(); void drawFolderTree(); void drawFolderTreeItem(const std::string& name, const std::string& path); From 9ff915bb12cc29be3af738454d15f37a5967a5ab Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 18:14:32 +0200 Subject: [PATCH 07/33] refactor(front-asset-manager): set the color only once + set the children map --- editor/src/DocumentWindows/AssetManager/Init.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 9df8c5ea0..1719dd818 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -48,5 +48,21 @@ namespace nexo::editor { } // Register for file drop events Application::getInstance().getEventManager()->registerListener(this); + + m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button); + m_layout.color.thumbnailBgHovered = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + m_layout.color.thumbnailBgSelected = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.thumbnailBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); + + m_layout.color.selectedBoxColor = ImGui::GetColorU32(ImGuiCol_TabSelectedOverline); + + m_layout.color.titleBg = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.titleBgHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); + m_layout.color.titleBgSelected = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.titleBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); + + m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); + buildFolderStructure(); + updateFolderChildren(); } } From 90a48b97918bdcb5b9160228e0524d46074fd147 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 18:15:19 +0200 Subject: [PATCH 08/33] fix(front-asset-manager): properly check the children in the map + early return --- .../AssetManager/FolderTree.cpp | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 2943005ea..34536bb42 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -24,15 +24,11 @@ namespace nexo::editor { void AssetManagerWindow::drawFolderTreeItem(const std::string& name, const std::string& path) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; - - // Check if this is the selected folder if (path == m_currentFolder) flags |= ImGuiTreeNodeFlags_Selected; - - if (!m_folderChildren.contains(path)) + if (!m_folderChildren.contains(path) || m_folderChildren.at(path).empty()) flags |= ImGuiTreeNodeFlags_Leaf; - // Folder icon ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(230, 180, 80, 255)); ImGui::Text(ICON_FA_FOLDER); ImGui::PopStyleColor(); @@ -60,17 +56,9 @@ namespace nexo::editor { if (!opened) return; - // Use the precomputed children list if (const auto it = m_folderChildren.find(path); it != m_folderChildren.end()) { for (const auto& childPath : it->second) { - // Find the name of the child from m_folderStructure - std::string childName; - for (const auto& [p, n] : m_folderStructure) { - if (p == childPath) { - childName = n; - break; - } - } + const std::string childName = std::filesystem::path(childPath).filename().string(); drawFolderTreeItem(childName, childPath); } } @@ -79,76 +67,85 @@ namespace nexo::editor { void AssetManagerWindow::handleNewFolderCreation() { - if (m_folderCreationState.isCreatingFolder) { - ImGui::OpenPopup("Create New Folder"); + if (!m_folderCreationState.isCreatingFolder) + return; - // Center the popup - const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup("Create New Folder"); - if (ImGui::BeginPopupModal("Create New Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Enter name for new folder:"); - ImGui::InputText("##FolderName", m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)); + // Center the popup + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::Separator(); + if (ImGui::BeginPopupModal("Create New Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Enter name for new folder:"); + ImGui::InputText("##FolderName", m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)); - if (ImGui::Button("Create", ImVec2(120, 0))) { - if (strnlen(m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)) > 0) { - std::string newFolderPath; - if (m_folderCreationState.parentPath.empty()) - newFolderPath = m_folderCreationState.folderName; - else - newFolderPath = m_folderCreationState.parentPath + "/" + m_folderCreationState.folderName; - - // Check if folder already exists - bool folderExists = false; - for (const auto &path: m_folderStructure | std::views::keys) { - if (path == newFolderPath) { - folderExists = true; - break; - } - } + ImGui::Separator(); - if (!folderExists) { - m_folderStructure.emplace_back(newFolderPath, m_folderCreationState.folderName); - LOG(NEXO_INFO, "Created new folder: {}", newFolderPath); + if (ImGui::Button("Create", ImVec2(120, 0))) { + if (strnlen(m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)) > 0) { + std::string newFolderPath; + if (m_folderCreationState.parentPath.empty()) + newFolderPath = m_folderCreationState.folderName; + else + newFolderPath = m_folderCreationState.parentPath + "/" + m_folderCreationState.folderName; - m_folderCreationState.isCreatingFolder = false; - ImGui::CloseCurrentPopup(); - } else { - m_folderCreationState.showError = true; - m_folderCreationState.errorMessage = "Folder already exists"; + // Check if folder already exists + bool folderExists = false; + for (const auto &path: m_folderStructure | std::views::keys) { + if (path == newFolderPath) { + folderExists = true; + break; } - } else { - m_folderCreationState.showError = true; - m_folderCreationState.errorMessage = "Folder name cannot be empty"; } - } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - m_folderCreationState.isCreatingFolder = false; - ImGui::CloseCurrentPopup(); - } + if (!folderExists) { + m_folderStructure.emplace_back(newFolderPath, m_folderCreationState.folderName); + LOG(NEXO_INFO, "Created new folder: {}", newFolderPath); - // Display error message if needed - if (m_folderCreationState.showError) { - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); - ImGui::Text("%s", m_folderCreationState.errorMessage.c_str()); - ImGui::PopStyleColor(); - - // Clear error after a few seconds - if (m_folderCreationState.errorTimer <= 0.0f) { - m_folderCreationState.showError = false; - m_folderCreationState.errorTimer = 3.0f; // Reset timer + m_folderCreationState.isCreatingFolder = false; + std::sort( + m_folderStructure.begin() + 1, + m_folderStructure.end(), + [](const auto& a, const auto& b) { + return a.first < b.first; + } + ); + updateFolderChildren(); + ImGui::CloseCurrentPopup(); } else { - m_folderCreationState.errorTimer -= ImGui::GetIO().DeltaTime; + m_folderCreationState.showError = true; + m_folderCreationState.errorMessage = "Folder already exists"; } + } else { + m_folderCreationState.showError = true; + m_folderCreationState.errorMessage = "Folder name cannot be empty"; } + } - ImGui::EndPopup(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + m_folderCreationState.isCreatingFolder = false; + ImGui::CloseCurrentPopup(); } + + // Display error message if needed + if (m_folderCreationState.showError) { + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::Text("%s", m_folderCreationState.errorMessage.c_str()); + ImGui::PopStyleColor(); + + // Clear error after a few seconds + if (m_folderCreationState.errorTimer <= 0.0f) { + m_folderCreationState.showError = false; + m_folderCreationState.errorTimer = 3.0f; // Reset timer + } else { + m_folderCreationState.errorTimer -= ImGui::GetIO().DeltaTime; + } + } + + ImGui::EndPopup(); } } From c5dba0ea23ad559d7a36c6ecbf7f88ccc0e8893e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 18:16:03 +0200 Subject: [PATCH 09/33] refactor(front-asset-manager): remove some func from class and use them as static --- .../src/DocumentWindows/AssetManager/Show.cpp | 117 ++++++++---------- 1 file changed, 50 insertions(+), 67 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index b52a99160..d4e2edbe6 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -24,79 +24,66 @@ #include namespace nexo::editor { - void AssetManagerWindow::drawMenuBar() + + static constexpr ImU32 getAssetTypeOverlayColor(const assets::AssetType type) { - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("Options")) { - ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f"); - ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); + switch (type) { + case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); + case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); + default: return IM_COL32(0, 0, 0, 0); } } - void AssetManagerWindow::calculateLayout(const float availWidth) + static void calculateGridLayout(LayoutSettings &layout) { + const float availWidth = ImGui::GetContentRegionAvail().x; + // Sizes - m_layout.size.columnCount = std::max( - static_cast(availWidth / m_layout.size.itemStep.x), 1 + layout.size.columnCount = std::max( + static_cast(availWidth / layout.size.itemStep.x), 1 ); - m_layout.size.itemSize = ImVec2( - m_layout.size.iconSize + ImGui::GetFontSize() * 1.5f, // width - m_layout.size.iconSize + ImGui::GetFontSize() * 1.7f // height + layout.size.itemSize = ImVec2( + layout.size.iconSize + ImGui::GetFontSize() * 1.5f, // width + layout.size.iconSize + ImGui::GetFontSize() * 1.7f // height ); - m_layout.size.itemStep = ImVec2( - m_layout.size.itemSize.x + static_cast(m_layout.size.iconSpacing), - m_layout.size.itemSize.y + static_cast(m_layout.size.iconSpacing) + layout.size.itemStep = ImVec2( + layout.size.itemSize.x + static_cast(layout.size.iconSpacing), + layout.size.itemSize.y + static_cast(layout.size.iconSpacing) ); - // Colors - m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button); - m_layout.color.thumbnailBgHovered = ImGui::GetColorU32(ImGuiCol_ButtonHovered); - m_layout.color.thumbnailBgSelected = ImGui::GetColorU32(ImGuiCol_Header); - m_layout.color.thumbnailBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); - - m_layout.color.selectedBoxColor = ImGui::GetColorU32(ImGuiCol_TabSelectedOverline); - - m_layout.color.titleBg = ImGui::GetColorU32(ImGuiCol_Header); - m_layout.color.titleBgHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); - m_layout.color.titleBgSelected = ImGui::GetColorU32(ImGuiCol_Header); - m_layout.color.titleBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); + } - m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); + void AssetManagerWindow::drawMenuBar() + { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Options")) { + ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f"); + ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } } void AssetManagerWindow::handleSelection(const unsigned int index, const bool isSelected) { - if (ImGui::GetIO().KeyCtrl) { + if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) { if (isSelected) m_selectedAssets.erase(index); else m_selectedAssets.insert(index); - } else if (ImGui::GetIO().KeyShift) { - const unsigned int latestSelected = m_selectedAssets.empty() ? 0 : *m_selectedAssets.rbegin(); - if (latestSelected <= index) { - for (unsigned int i = latestSelected ; i <= index; ++i) { - m_selectedAssets.insert(i); - } - } else { - for (unsigned int i = index; i <= latestSelected; ++i) { - m_selectedAssets.insert(i); - } - } - } else { - m_selectedAssets.clear(); - m_selectedAssets.insert(index); + return; } - } - static ImU32 getAssetTypeOverlayColor(const assets::AssetType type) - { - switch (type) { - case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); - case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); - default: return IM_COL32(0, 0, 0, 0); + if (ImGui::IsKeyDown(ImGuiKey_ModShift) && !m_selectedAssets.empty()) { + const unsigned int latestSelected = *m_selectedAssets.rbegin(); + const auto [start, end] = std::minmax(latestSelected, index); + const auto range = std::views::iota(start, end + 1); + m_selectedAssets.insert(range.begin(), range.end()); + return; } + + m_selectedAssets.clear(); + m_selectedAssets.insert(index); } void AssetManagerWindow::drawAsset( @@ -120,7 +107,7 @@ namespace nexo::editor { const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end(); const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius); + drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); if (isSelected) { // Draw a distinctive border around selected items @@ -128,14 +115,14 @@ namespace nexo::editor { ImVec2(itemPos.x - 1, itemPos.y - 1), ImVec2(itemEnd.x + 1, itemEnd.y + 1), m_layout.color.selectedBoxColor, - m_layout.size.cornerRadius, + m_layout.size.CORNER_RADIUS, 0, - m_layout.size.selectedBoxThickness + m_layout.size.SELECTED_BOX_THICKNESS ); } // Draw thumbnail area - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); if (const ImTextureID textureId = ThumbnailCache::getInstance().getThumbnail(asset); !textureId) { drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg); @@ -155,14 +142,14 @@ namespace nexo::editor { } // Draw type overlay (maybe later modify it to an icon) - const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.overlayPadding, itemPos.y + m_layout.size.overlayPadding); + const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.OVERLAY_PADDING, itemPos.y + m_layout.size.OVERLAY_PADDING); const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); - drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.overlaySize, overlayPos.y + m_layout.size.overlaySize), overlayColor); + drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.OVERLAY_SIZE, overlayPos.y + m_layout.size.OVERLAY_SIZE), overlayColor); // Draw title const char *assetName = assetData->getMetadata().location.getName().c_str(); const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, - thumbnailEnd.y + m_layout.size.titlePadding); + thumbnailEnd.y + m_layout.size.TITLE_PADDING); // Background rectangle for text const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : m_layout.color.titleBg; @@ -250,9 +237,9 @@ namespace nexo::editor { // Background - use hover color when hovered const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius); + drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); // Calculate padding for the icon constexpr float padding = 10.0f; @@ -296,7 +283,7 @@ namespace nexo::editor { // Draw title background const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); - const float titleAreaHeight = itemSize.y * (1.0f - m_layout.size.thumbnailHeightRatio); + const float titleAreaHeight = itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); drawList->AddRectFilled( ImVec2(itemPos.x, thumbnailEnd.y), @@ -399,10 +386,6 @@ namespace nexo::editor { void AssetManagerWindow::show() { - m_hoveredFolder.clear(); - if (m_folderStructure.empty()) - buildFolderStructure(); - ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); beginRender(NEXO_WND_USTRID_ASSET_MANAGER); @@ -523,7 +506,7 @@ namespace nexo::editor { ImGui::Separator(); - calculateLayout(ImGui::GetContentRegionAvail().x); + calculateGridLayout(m_layout); drawAssetsGrid(); ImGui::EndChild(); From 76174b3d550467a0b7f5f1f1ec3723466c29847a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 18:16:31 +0200 Subject: [PATCH 10/33] feat(front-asset-manager): add updateFolderChildren method --- .../DocumentWindows/AssetManager/Update.cpp | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp index 41442d0a1..029709dd7 100644 --- a/editor/src/DocumentWindows/AssetManager/Update.cpp +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -16,6 +16,38 @@ namespace nexo::editor { + void AssetManagerWindow::updateFolderChildren() + { + m_folderChildren.clear(); + + for (const auto& [path, name] : m_folderStructure) { + if (!path.empty()) { // Skip root entry + m_folderChildren[path] = {}; + } + } + + // Build parent-child relationships + for (const auto& [path, name] : m_folderStructure) { + if (path.empty()) continue; // Skip root + + const size_t lastSlash = path.find_last_of('/'); + + if (lastSlash == std::string::npos) { + // Top-level folder - child of root + m_folderChildren[""].push_back(path); + } else { + // Nested folder - child of parent + const std::string parentPath = path.substr(0, lastSlash); + m_folderChildren[parentPath].push_back(path); + } + } + + // Sort children for consistent display order + for (auto& [parent, children] : m_folderChildren) { + std::ranges::sort(children); + } + } + void AssetManagerWindow::update() { handleDroppedFiles(); From 7e3ddffb2fa68ac6920bdd9e8780a2ecbeaba057 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 19:11:59 +0200 Subject: [PATCH 11/33] refactor(front-asset-manager): now use the popup manager for the folder creation + make folder creation logic simpler --- editor/CMakeLists.txt | 1 + .../AssetManager/AssetManagerWindow.hpp | 36 +++- .../AssetManager/FolderCreation.cpp | 90 +++++++++ .../AssetManager/FolderTree.cpp | 173 ++---------------- .../src/DocumentWindows/AssetManager/Show.cpp | 6 + .../DocumentWindows/AssetManager/Update.cpp | 42 +++++ 6 files changed, 184 insertions(+), 164 deletions(-) create mode 100644 editor/src/DocumentWindows/AssetManager/FolderCreation.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index bafafa3cd..2e18a61dd 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -56,6 +56,7 @@ set(SRCS editor/src/DocumentWindows/AssetManager/Update.cpp editor/src/DocumentWindows/AssetManager/FolderTree.cpp editor/src/DocumentWindows/AssetManager/FileDrop.cpp + editor/src/DocumentWindows/AssetManager/FolderCreation.cpp editor/src/DocumentWindows/ConsoleWindow/Init.cpp editor/src/DocumentWindows/ConsoleWindow/Log.cpp editor/src/DocumentWindows/ConsoleWindow/Show.cpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 3bdfd20b1..4753bbbe9 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -17,12 +17,32 @@ #include #include #include +#include "DocumentWindows/PopupManager.hpp" #include "utils/TransparentStringHash.hpp" #include #include "assets/Asset.hpp" namespace nexo::editor { + struct FolderCreationState { + bool isCreatingFolder = false; + std::string folderName = "New Folder"; + std::string parentPath; + bool showError = false; + std::string errorMessage; + float errorTimer = 3.0f; + + void reset() + { + isCreatingFolder = false; + folderName = "New Folder"; + parentPath = ""; + showError = false; + errorMessage = ""; + errorTimer = 3.0f; + } + }; + struct LayoutSizes { float iconSize = 64.0f; int iconSpacing = 8; @@ -88,26 +108,22 @@ namespace nexo::editor { std::vector> m_folderStructure; // Pairs of (path, name) char m_searchBuffer[256] = ""; + PopupManager m_popupManager; + void buildFolderStructure(); void updateFolderChildren(); + + void folderTreeContextMenu(); void drawFolderTree(); void drawFolderTreeItem(const std::string& name, const std::string& path); - struct FolderCreationState { - bool isCreatingFolder = false; - char folderName[256] = ""; - std::string parentPath; - bool showError = false; - std::string errorMessage; - float errorTimer = 3.0f; - }; - FolderCreationState m_folderCreationState; assets::AssetRef m_folderIcon; ImTextureID getFolderIconTexture() const; - void handleNewFolderCreation(); + void newFolderContextMenu(); + bool handleNewFolderCreation(); void drawFolder( const std::string& folderPath, const std::string& folderName, diff --git a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp new file mode 100644 index 000000000..7df497e23 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp @@ -0,0 +1,90 @@ +//// FolderCreation.cpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 27/07/2025 +// Description: Source file for the method used to create a new folder +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" +#include "ImNexo/Elements.hpp" + +namespace nexo::editor { + + bool AssetManagerWindow::handleNewFolderCreation() + { + if (m_folderCreationState.folderName.empty()) { + m_folderCreationState.showError = true; + m_folderCreationState.errorMessage = "Folder name cannot be empty"; + return false; + } + + std::string newFolderPath = (m_folderCreationState.parentPath.empty()) ? "" : m_folderCreationState.parentPath + "/"; + newFolderPath += m_folderCreationState.folderName; + + const bool folderExists = std::ranges::any_of(m_folderStructure, + [&newFolderPath](const auto& folder) { + return folder.first == newFolderPath; + } + ); + + if (folderExists) { + m_folderCreationState.showError = true; + m_folderCreationState.errorMessage = "Folder already exists"; + return false; + } + + m_folderStructure.emplace_back(newFolderPath, m_folderCreationState.folderName); + + std::sort( + m_folderStructure.begin() + 1, + m_folderStructure.end(), + [](const auto& a, const auto& b) { + return a.first < b.first; + } + ); + updateFolderChildren(); + return true; + } + + void AssetManagerWindow::newFolderContextMenu() + { + + + ImGui::Text("Enter name for the new folder:"); + ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.size()); + + ImGui::Separator(); + + if (ImNexo::Button("Create") && handleNewFolderCreation()) { + m_folderCreationState.reset(); + PopupManager::closePopupInContext(); + } + ImGui::SameLine(); + if (ImNexo::Button("Cancel")) { + m_folderCreationState.reset(); + PopupManager::closePopupInContext(); + } + + if (m_folderCreationState.showError) { + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::Text("%s", m_folderCreationState.errorMessage.c_str()); + ImGui::PopStyleColor(); + + if (m_folderCreationState.errorTimer <= 0.0f) { + m_folderCreationState.showError = false; + m_folderCreationState.errorTimer = 3.0f; // Reset timer + } else + m_folderCreationState.errorTimer -= ImGui::GetIO().DeltaTime; + } + PopupManager::closePopup(); + } + +} diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 34536bb42..1eacf67f5 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -18,9 +18,19 @@ #include "assets/AssetCatalog.hpp" #include +#include #include namespace nexo::editor { + + void AssetManagerWindow::folderTreeContextMenu() + { + if (ImGui::MenuItem("New Folder")) + m_popupManager.openPopup("Create new folder"); + + PopupManager::closePopup(); + } + void AssetManagerWindow::drawFolderTreeItem(const std::string& name, const std::string& path) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; @@ -36,21 +46,12 @@ namespace nexo::editor { bool opened = ImGui::TreeNodeEx(name.c_str(), flags); - if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen()) m_currentFolder = path; - - if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("New Folder")) { - m_folderCreationState.parentPath = path; - m_folderCreationState.isCreatingFolder = true; - ImGui::OpenPopup("Create New Folder"); - std::format_to_n( - m_folderCreationState.folderName, - sizeof(m_folderCreationState.folderName) - 1, // Ensure null termination - "New Folder" - ); - } - ImGui::EndPopup(); + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + m_folderCreationState.reset(); + m_folderCreationState.parentPath = path; + m_popupManager.openPopup("Folder Tree Context Menu"); } if (!opened) @@ -65,136 +66,8 @@ namespace nexo::editor { ImGui::TreePop(); } - void AssetManagerWindow::handleNewFolderCreation() - { - if (!m_folderCreationState.isCreatingFolder) - return; - - ImGui::OpenPopup("Create New Folder"); - - // Center the popup - const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - - if (ImGui::BeginPopupModal("Create New Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Enter name for new folder:"); - ImGui::InputText("##FolderName", m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)); - - ImGui::Separator(); - - if (ImGui::Button("Create", ImVec2(120, 0))) { - if (strnlen(m_folderCreationState.folderName, sizeof(m_folderCreationState.folderName)) > 0) { - std::string newFolderPath; - if (m_folderCreationState.parentPath.empty()) - newFolderPath = m_folderCreationState.folderName; - else - newFolderPath = m_folderCreationState.parentPath + "/" + m_folderCreationState.folderName; - - // Check if folder already exists - bool folderExists = false; - for (const auto &path: m_folderStructure | std::views::keys) { - if (path == newFolderPath) { - folderExists = true; - break; - } - } - - if (!folderExists) { - m_folderStructure.emplace_back(newFolderPath, m_folderCreationState.folderName); - LOG(NEXO_INFO, "Created new folder: {}", newFolderPath); - - m_folderCreationState.isCreatingFolder = false; - std::sort( - m_folderStructure.begin() + 1, - m_folderStructure.end(), - [](const auto& a, const auto& b) { - return a.first < b.first; - } - ); - updateFolderChildren(); - ImGui::CloseCurrentPopup(); - } else { - m_folderCreationState.showError = true; - m_folderCreationState.errorMessage = "Folder already exists"; - } - } else { - m_folderCreationState.showError = true; - m_folderCreationState.errorMessage = "Folder name cannot be empty"; - } - } - - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - m_folderCreationState.isCreatingFolder = false; - ImGui::CloseCurrentPopup(); - } - - // Display error message if needed - if (m_folderCreationState.showError) { - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); - ImGui::Text("%s", m_folderCreationState.errorMessage.c_str()); - ImGui::PopStyleColor(); - - // Clear error after a few seconds - if (m_folderCreationState.errorTimer <= 0.0f) { - m_folderCreationState.showError = false; - m_folderCreationState.errorTimer = 3.0f; // Reset timer - } else { - m_folderCreationState.errorTimer -= ImGui::GetIO().DeltaTime; - } - } - - ImGui::EndPopup(); - } - } - - void AssetManagerWindow::buildFolderStructure() - { - m_folderStructure.clear(); - // Root entry - m_folderStructure.emplace_back("", "Assets"); - m_folderChildren.clear(); // Clear the folder children map - - // First pass: build the folder structure - std::set> uniqueFolderPaths; - - std::unordered_set seen{""}; - - for (const auto assets = assets::AssetCatalog::getInstance().getAssets(); auto& ref : assets) { - if (const auto assetData = ref.lock()) { - // normalized path: e.g. "Random/Sub" - std::filesystem::path p{ assetData->getMetadata().location.getPath() }; - std::filesystem::path curr; - for (auto const& part : p) { - // skip empty or “_internal” style parts - if (auto s = part.string(); s.empty() || s.front() == '_') - continue; - curr /= part; - if (auto folderPath = curr.string(); seen.emplace(folderPath).second) { - m_folderStructure.emplace_back( - folderPath, - curr.filename().string() - ); - } - } - } - } - - std::sort( - m_folderStructure.begin() + 1, - m_folderStructure.end(), - [](auto const& a, auto const& b){ - return a.first < b.first; - } - ); - } - - void AssetManagerWindow::drawFolderTree() { - handleNewFolderCreation(); - ImGui::PushItemWidth(-1); ImGui::InputTextWithHint("##search", "Search...", m_searchBuffer, sizeof(m_searchBuffer)); ImGui::PopItemWidth(); @@ -245,18 +118,10 @@ namespace nexo::editor { bool assetsOpen = ImGui::TreeNodeEx(ICON_FA_FOLDER " Assets", headerFlags); - // Handle right-click on Assets root - if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("New Folder")) { - m_folderCreationState.parentPath = ""; - m_folderCreationState.isCreatingFolder = true; - std::format_to_n( - m_folderCreationState.folderName, - sizeof(m_folderCreationState.folderName) - 1, // Ensure null termination - "New Folder" - ); - } - ImGui::EndPopup(); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + { + m_folderCreationState.reset(); + m_popupManager.openPopup("Folder Tree Context Menu"); } if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index d4e2edbe6..c899cab22 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -510,6 +510,12 @@ namespace nexo::editor { drawAssetsGrid(); ImGui::EndChild(); + if (m_popupManager.showPopup("Folder Tree Context Menu")) + folderTreeContextMenu(); + + if (m_popupManager.showPopupModal("Create new folder")) + newFolderContextMenu(); + ImGui::End(); } } diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp index 029709dd7..980fff4cb 100644 --- a/editor/src/DocumentWindows/AssetManager/Update.cpp +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetManagerWindow.hpp" +#include "assets/AssetCatalog.hpp" namespace nexo::editor { @@ -48,6 +49,47 @@ namespace nexo::editor { } } + void AssetManagerWindow::buildFolderStructure() + { + m_folderStructure.clear(); + // Root entry + m_folderStructure.emplace_back("", "Assets"); + m_folderChildren.clear(); // Clear the folder children map + + // First pass: build the folder structure + std::set> uniqueFolderPaths; + + std::unordered_set seen{""}; + + for (const auto assets = assets::AssetCatalog::getInstance().getAssets(); auto& ref : assets) { + if (const auto assetData = ref.lock()) { + // normalized path: e.g. "Random/Sub" + std::filesystem::path p{ assetData->getMetadata().location.getPath() }; + std::filesystem::path curr; + for (auto const& part : p) { + // skip empty or “_internal” style parts + if (auto s = part.string(); s.empty() || s.front() == '_') + continue; + curr /= part; + if (auto folderPath = curr.string(); seen.emplace(folderPath).second) { + m_folderStructure.emplace_back( + folderPath, + curr.filename().string() + ); + } + } + } + } + + std::sort( + m_folderStructure.begin() + 1, + m_folderStructure.end(), + [](auto const& a, auto const& b){ + return a.first < b.first; + } + ); + } + void AssetManagerWindow::update() { handleDroppedFiles(); From f205ce19d6f9da5b0844d46a1412d3d008117539 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 19:13:07 +0200 Subject: [PATCH 12/33] style(front-asset-manager): rename the func that handles the new folder menu --- .../src/DocumentWindows/AssetManager/AssetManagerWindow.hpp | 2 +- editor/src/DocumentWindows/AssetManager/FolderCreation.cpp | 4 +--- editor/src/DocumentWindows/AssetManager/Show.cpp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 4753bbbe9..128da8192 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -122,7 +122,7 @@ namespace nexo::editor { ImTextureID getFolderIconTexture() const; - void newFolderContextMenu(); + void newFolderMenu(); bool handleNewFolderCreation(); void drawFolder( const std::string& folderPath, diff --git a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp index 7df497e23..37101cf54 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp @@ -53,10 +53,8 @@ namespace nexo::editor { return true; } - void AssetManagerWindow::newFolderContextMenu() + void AssetManagerWindow::newFolderMenu() { - - ImGui::Text("Enter name for the new folder:"); ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.size()); diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index c899cab22..40e510d13 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -514,7 +514,7 @@ namespace nexo::editor { folderTreeContextMenu(); if (m_popupManager.showPopupModal("Create new folder")) - newFolderContextMenu(); + newFolderMenu(); ImGui::End(); } From 19a0225a7b836c62d67807b59de1b669533f6af2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 19:57:47 +0200 Subject: [PATCH 13/33] refactor(front-asset-manager): make folder tree more readable and maintanable --- .../AssetManager/AssetManagerWindow.hpp | 2 +- .../AssetManager/FolderTree.cpp | 129 ++++++++++-------- 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 128da8192..a6240b74b 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -106,7 +106,7 @@ namespace nexo::editor { std::string m_currentFolder; // Currently selected folder std::string m_hoveredFolder; // Currently hovered folder std::vector> m_folderStructure; // Pairs of (path, name) - char m_searchBuffer[256] = ""; + std::string m_searchBuffer = ""; PopupManager m_popupManager; diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 1eacf67f5..73432df85 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -23,6 +23,57 @@ namespace nexo::editor { + static bool isTopLevelFolder(const std::string &path) + { + return path.empty() && path.find('/') == std::string::npos; + } + + static void drawSearchBar(std::string &searchBuffer) + { + ImGui::PushItemWidth(-1); + ImGui::InputTextWithHint("##search", "Search...", searchBuffer.data(), searchBuffer.size()); + ImGui::PopItemWidth(); + ImGui::Separator(); + } + + struct FavoriteItem { + std::string_view icon; + std::string_view name; + assets::AssetType type; + + [[nodiscard]] std::string getLabel(bool selected) const { + return std::format("{} {}{}", icon, name, selected ? " " ICON_FA_CHECK : ""); + } + }; + + static void drawFavorites(assets::AssetType &selectedType) + { + ImGuiTreeNodeFlags rootFlags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (!ImGui::TreeNodeEx(ICON_FA_STAR " Favorites", rootFlags)) + return; + + static constexpr FavoriteItem favorites[]{ + {ICON_FA_ADJUST, "Materials", assets::AssetType::MATERIAL}, + {ICON_FA_CUBE, "Models", assets::AssetType::MODEL}, + {ICON_FA_SQUARE, "Textures", assets::AssetType::TEXTURE} + }; + + for (const auto& fav : favorites) { + const bool isSelected = (fav.type == selectedType); + + ImGuiTreeNodeFlags itemFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (isSelected) itemFlags |= ImGuiTreeNodeFlags_Selected; + + const auto label = fav.getLabel(isSelected); + ImGui::TreeNodeEx(label.c_str(), itemFlags); + + if (ImGui::IsItemClicked()) { + selectedType = isSelected ? assets::AssetType::UNKNOWN : fav.type; + } + } + ImGui::TreePop(); + } + void AssetManagerWindow::folderTreeContextMenu() { if (ImGui::MenuItem("New Folder")) @@ -68,73 +119,33 @@ namespace nexo::editor { void AssetManagerWindow::drawFolderTree() { - ImGui::PushItemWidth(-1); - ImGui::InputTextWithHint("##search", "Search...", m_searchBuffer, sizeof(m_searchBuffer)); - ImGui::PopItemWidth(); - ImGui::Separator(); - - // favorites section - { - if (ImGui::TreeNodeEx(ICON_FA_STAR " Favorites", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { - struct FavoriteItem { - std::string label; - assets::AssetType type; - }; - - static const FavoriteItem favorites[] = { - {ICON_FA_ADJUST " Materials", assets::AssetType::MATERIAL}, - {ICON_FA_CUBE " Models", assets::AssetType::MODEL}, - {ICON_FA_SQUARE " Textures", assets::AssetType::TEXTURE} - }; - - for (const auto& fav : favorites) { - const bool isSelected = (fav.type == m_selectedType); - - ImGuiTreeNodeFlags itemFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; - if (isSelected) - itemFlags |= ImGuiTreeNodeFlags_Selected; - - const std::string labelName = fav.label + (isSelected ? " " ICON_FA_CHECK : ""); - ImGui::TreeNodeEx(labelName.c_str(), itemFlags); - - if (ImGui::IsItemClicked()) { - if (isSelected) - m_selectedType = assets::AssetType::UNKNOWN; - else - m_selectedType = fav.type; - } - } - ImGui::TreePop(); - } - } + drawSearchBar(m_searchBuffer); + drawFavorites(m_selectedType); // folder structure - { - ImGuiTreeNodeFlags headerFlags = ImGuiTreeNodeFlags_OpenOnDoubleClick; + ImGuiTreeNodeFlags headerFlags = ImGuiTreeNodeFlags_OpenOnDoubleClick; - if (m_currentFolder.empty()) { - headerFlags |= ImGuiTreeNodeFlags_Selected; - } + if (m_currentFolder.empty()) + headerFlags |= ImGuiTreeNodeFlags_Selected; - bool assetsOpen = ImGui::TreeNodeEx(ICON_FA_FOLDER " Assets", headerFlags); + bool assetsOpen = ImGui::TreeNodeEx(ICON_FA_FOLDER " Assets", headerFlags); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - { - m_folderCreationState.reset(); - m_popupManager.openPopup("Folder Tree Context Menu"); - } + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + { + m_folderCreationState.reset(); + m_popupManager.openPopup("Folder Tree Context Menu"); + } + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + m_currentFolder = ""; - if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) - m_currentFolder = ""; + if (!assetsOpen) + return; - if (assetsOpen) { - for (const auto& [path, name] : m_folderStructure) { - if (!path.empty() && path.find('/') == std::string::npos) { - drawFolderTreeItem(name, path); - } - } - ImGui::TreePop(); + for (const auto& [path, name] : m_folderStructure) { + if (isTopLevelFolder(path)) { + drawFolderTreeItem(name, path); } } + ImGui::TreePop(); } } From 6fbfd6589b93493bf09369fb61e8a5a9be8868c1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 20:16:56 +0200 Subject: [PATCH 14/33] fix(front-asset-manager): remove useless folder structure rebuilding + remove useless file extension extraction --- editor/src/DocumentWindows/AssetManager/FileDrop.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index a170f742f..2f6b02fb0 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -54,9 +54,6 @@ namespace nexo::editor { for (const auto& filePath : m_pendingDroppedFiles) importDroppedFile(filePath); m_pendingDroppedFiles.clear(); - - m_folderStructure.clear(); - buildFolderStructure(); } void AssetManagerWindow::importDroppedFile(const std::string& filePath) const @@ -68,9 +65,6 @@ namespace nexo::editor { return; } - std::string extension = path.extension().string(); - std::ranges::transform(extension, extension.begin(), ::tolower); - const assets::AssetLocation location = getAssetLocation(path); assets::ImporterFileInput fileInput{path}; From ef485eb4d77b28cf0b75da43b90fb4d314d168dd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:14:10 +0200 Subject: [PATCH 15/33] refactor(front-asset-manager): hanbdle selection in a separate file --- .../AssetManager/Selection.cpp | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 editor/src/DocumentWindows/AssetManager/Selection.cpp diff --git a/editor/src/DocumentWindows/AssetManager/Selection.cpp b/editor/src/DocumentWindows/AssetManager/Selection.cpp new file mode 100644 index 000000000..289ab95fe --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/Selection.cpp @@ -0,0 +1,39 @@ +//// Selection.cpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 27/07/2025 +// Description: Source file for the selection handling in the asset manager +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" + +namespace nexo::editor { + void AssetManagerWindow::handleSelection(const unsigned int index, const bool isSelected) + { + if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) { + if (isSelected) + m_selectedAssets.erase(index); + else + m_selectedAssets.insert(index); + return; + } + + if (ImGui::IsKeyDown(ImGuiKey_ModShift) && !m_selectedAssets.empty()) { + const unsigned int latestSelected = *m_selectedAssets.rbegin(); + const auto [start, end] = std::minmax(latestSelected, index); + const auto range = std::views::iota(start, end + 1); + m_selectedAssets.insert(range.begin(), range.end()); + return; + } + + m_selectedAssets.clear(); + m_selectedAssets.insert(index); + } +} From a26b853bbf94863e6911049c26c260c349c2541a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:14:36 +0200 Subject: [PATCH 16/33] refactor(front-asset-manager): handle asset grid rendering in a sepparate file --- .../AssetManager/AssetGrid.cpp | 336 ++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 editor/src/DocumentWindows/AssetManager/AssetGrid.cpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp new file mode 100644 index 000000000..bbc5fead1 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -0,0 +1,336 @@ +//// AssetGrid.cpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 27/07/2025 +// Description: Source file for the asset grid +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" +#include "assets/AssetCatalog.hpp" +#include "context/ThumbnailCache.hpp" + +namespace nexo::editor { + + static constexpr ImU32 getAssetTypeOverlayColor(const assets::AssetType type) + { + switch (type) { + case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); + case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); + default: return IM_COL32(0, 0, 0, 0); + } + } + + static void calculateGridLayout(LayoutSettings &layout) + { + const float availWidth = ImGui::GetContentRegionAvail().x; + + // Sizes + layout.size.columnCount = std::max( + static_cast(availWidth / layout.size.itemStep.x), 1 + ); + layout.size.itemSize = ImVec2( + layout.size.iconSize + ImGui::GetFontSize() * 1.5f, // width + layout.size.iconSize + ImGui::GetFontSize() * 1.7f // height + ); + layout.size.itemStep = ImVec2( + layout.size.itemSize.x + static_cast(layout.size.iconSpacing), + layout.size.itemSize.y + static_cast(layout.size.iconSpacing) + ); + } + + void AssetManagerWindow::drawAsset( + const assets::GenericAssetRef& asset, + const unsigned int index, + const ImVec2& itemPos, + const ImVec2& itemSize + ) { + const auto assetData = asset.lock(); + if (!assetData) + return; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + + ImGui::PushID(static_cast(index)); + + ImGui::SetCursorScreenPos(itemPos); + + const bool clicked = ImGui::InvisibleButton("##item", itemSize); + const bool isHovered = ImGui::IsItemHovered(); + + const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end(); + const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; + drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); + + if (isSelected) { + // Draw a distinctive border around selected items + drawList->AddRect( + ImVec2(itemPos.x - 1, itemPos.y - 1), + ImVec2(itemEnd.x + 1, itemEnd.y + 1), + m_layout.color.selectedBoxColor, + m_layout.size.CORNER_RADIUS, + 0, + m_layout.size.SELECTED_BOX_THICKNESS + ); + } + + // Draw thumbnail area + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + + if (const ImTextureID textureId = ThumbnailCache::getInstance().getThumbnail(asset); !textureId) { + drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg); + } else { + constexpr float padding = 4.0f; + const ImVec2 imageStart(itemPos.x + padding, itemPos.y + padding); + const ImVec2 imageEnd(thumbnailEnd.x - padding, thumbnailEnd.y - padding); + + drawList->AddImage( + textureId, + imageStart, + imageEnd, + ImVec2(0, 1), // UV0 (top-left) + ImVec2(1, 0), // UV1 (bottom-right) + IM_COL32(255, 255, 255, 255) // White tint + ); + } + + // Draw type overlay (maybe later modify it to an icon) + const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.OVERLAY_PADDING, itemPos.y + m_layout.size.OVERLAY_PADDING); + const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); + drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.OVERLAY_SIZE, overlayPos.y + m_layout.size.OVERLAY_SIZE), overlayColor); + + // Draw title + const char *assetName = assetData->getMetadata().location.getName().c_str(); + const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, + thumbnailEnd.y + m_layout.size.TITLE_PADDING); + + // Background rectangle for text + const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : m_layout.color.titleBg; + drawList->AddRectFilled(ImVec2(itemPos.x, thumbnailEnd.y), ImVec2(itemEnd.x, itemEnd.y), titleBgColor); + drawList->AddText(textPos, m_layout.color.titleText, assetName); + + // Handle selection when clicked + if (clicked) + handleSelection(index, isSelected); + + // On Hover show asset location + if (isHovered) + ImGui::SetTooltip("%s", assetData->getMetadata().location.getFullLocation().c_str()); + + // Handle drag source for assets + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + AssetDragDropPayload payload; + payload.type = assetData->getType(); + payload.id = assetData->getID(); + payload.path = assetData->getMetadata().location.getFullLocation(); + payload.name = assetName; + + ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); + + // Show preview while dragging + //TODO: Add asset preview thanks to thumbnail cache after rebasing + if (assetData->getType() == assets::AssetType::TEXTURE) { + const auto textureAsset = asset.as(); + if (const auto textureData = textureAsset.lock(); + textureData && textureData->getData() && textureData->getData()->texture) { + const ImTextureID textureId = textureData->getData()->texture->getId(); + ImGui::Image(textureId, {64, 64}); + } + } + + ImGui::EndDragDropSource(); + } + + ImGui::PopID(); + } + + void AssetManagerWindow::drawFolder( + const std::string& folderPath, + const std::string& folderName, + const ImVec2& itemPos, + const ImVec2& itemSize + ) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + + ImGui::PushID(("folder_" + folderPath).c_str()); + + ImGui::SetCursorScreenPos(itemPos); + + const bool clicked = ImGui::InvisibleButton("##folder", itemSize); + const bool isHovered = ImGui::IsItemHovered(); + + if (isHovered) { + m_hoveredFolder = folderPath; + } else if (m_hoveredFolder == folderPath) { + m_hoveredFolder.clear(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + const auto* data = static_cast(payload->Data); + assets::AssetCatalog::getInstance().moveAsset(data->id, folderPath); + } + ImGui::EndDragDropTarget(); + } + + // Background - use hover color when hovered + const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); + drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); + + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + + // Calculate padding for the icon + constexpr float padding = 10.0f; + + // Calculate available area dimensions + const float availWidth = thumbnailEnd.x - itemPos.x - (padding * 2.0f); + const float availHeight = thumbnailEnd.y - itemPos.y - (padding * 2.0f); + + // Maintain aspect ratio by using the smaller dimension + const float displaySize = std::min(availWidth, availHeight); + + // Calculate centered position + const float xOffset = (availWidth - displaySize) * 0.5f + padding; + const float yOffset = (availHeight - displaySize) * 0.5f + padding; + + // Final image coordinates maintaining aspect ratio + const ImVec2 imageStart( + itemPos.x + xOffset, + itemPos.y + yOffset + ); + const ImVec2 imageEnd( + imageStart.x + displaySize, + imageStart.y + displaySize + ); + + // Draw folder PNG icon + + if (const ImTextureID folderIconTexture = getFolderIconTexture()) { + drawList->AddImage( + folderIconTexture, + imageStart, + imageEnd, + ImVec2(0, 1), // UV0 (top-left) + ImVec2(1, 0), // UV1 (bottom-right) + IM_COL32(255, 255, 255, 255) // White tint for default color + ); + } + + // Calculate text size to ensure it fits + const ImVec2 textSize = ImGui::CalcTextSize(folderName.c_str()); + + // Draw title background + const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); + const float titleAreaHeight = itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); + + drawList->AddRectFilled( + ImVec2(itemPos.x, thumbnailEnd.y), + ImVec2(itemEnd.x, itemEnd.y), + titleBgColor + ); + + // Position text with proper vertical alignment + const float textY = thumbnailEnd.y + ((titleAreaHeight - textSize.y) * 0.5f); + const float textX = itemPos.x + (itemSize.x - textSize.x) * 0.5f; + + drawList->AddText( + ImVec2(textX, textY), + m_layout.color.titleText, + folderName.c_str() + ); + + if (clicked) + m_currentFolder = folderPath; // Navigate into this folder + + ImGui::PopID(); + } + + void AssetManagerWindow::drawAssetsGrid() + { + calculateGridLayout(m_layout); + + const ImVec2 startPos = ImGui::GetCursorScreenPos(); + + std::vector> subfolders; + for (auto& [path,name] : m_folderStructure) { + if (path.empty() || path.front() == '_') + continue; + + if (m_currentFolder.empty()) { + if (path.find('/') == std::string::npos) + subfolders.emplace_back(path, name); + } else { + if (std::string prefix = m_currentFolder + "/"; path.rfind(prefix, 0) == 0 && + path.find('/', prefix.size()) == std::string::npos) + { + subfolders.emplace_back(path, path.substr(prefix.size())); + } + } + } + + std::vector filtered; + for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { + if (const auto d = ref.lock()) { + const auto& folder = d->getMetadata().location.getPath(); + if (folder == "_internal") + continue; + if (m_selectedType != assets::AssetType::UNKNOWN && + d->getType() != m_selectedType) continue; + + if (folder == m_currentFolder) + filtered.push_back(ref); + } + } + + const size_t totalItems = subfolders.size() + filtered.size(); + ImGuiListClipper clipper; + const auto rows = static_cast((totalItems + m_layout.size.columnCount - 1) / m_layout.size.columnCount); + clipper.Begin(rows, m_layout.size.itemStep.y); + + while (clipper.Step()) { + for (int line = clipper.DisplayStart; line < clipper.DisplayEnd; ++line) { + const unsigned int startIdx = line * m_layout.size.columnCount; + const unsigned int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(totalItems)); + + for (unsigned int i = startIdx; i < endIdx; ++i) { + unsigned int col = i % m_layout.size.columnCount; + unsigned int row = i / m_layout.size.columnCount; + ImVec2 itemPos{ + startPos.x + static_cast(col) * m_layout.size.itemStep.x, + startPos.y + static_cast(row) * m_layout.size.itemStep.y + }; + + if (i < static_cast(subfolders.size())) { + // draw folder thumbnail + drawFolder( + subfolders[i].first, + subfolders[i].second, + itemPos, + m_layout.size.itemSize + ); + } else { + // draw asset thumbnail + const auto assetIdx = i - static_cast(subfolders.size()); + drawAsset( + filtered[assetIdx], + assetIdx, + itemPos, + m_layout.size.itemSize + ); + } + } + } + } + clipper.End(); + } +} From 2d2b68d2e7fbeaca10c3ad0f6746189572cb3a3b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:15:15 +0200 Subject: [PATCH 17/33] feat(front-asset-manager): add generic function to handle asset drop --- .../src/DocumentWindows/AssetManager/FileDrop.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 2f6b02fb0..4368fb40f 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -15,12 +15,26 @@ #include "AssetManagerWindow.hpp" #include "assets/AssetImporter.hpp" #include "assets/AssetLocation.hpp" +#include "assets/AssetCatalog.hpp" #include "Logger.hpp" #include #include namespace nexo::editor { + void AssetManagerWindow::handleAssetDrop(const std::string &path) + { + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + const auto data = static_cast(payload->Data); + assets::AssetCatalog::getInstance().moveAsset(data->id, path); + } + ImGui::EndDragDropTarget(); + } + } + assets::AssetLocation AssetManagerWindow::getAssetLocation(const std::filesystem::path &path) const { const std::string assetName = path.stem().string(); From 179affdbe945e161fa52a024a2a2a6b3d7eeec7a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:15:54 +0200 Subject: [PATCH 18/33] fix(front-asset-manager): fix wrong string size --- editor/src/DocumentWindows/AssetManager/FolderCreation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp index 37101cf54..84debfaea 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp @@ -56,7 +56,7 @@ namespace nexo::editor { void AssetManagerWindow::newFolderMenu() { ImGui::Text("Enter name for the new folder:"); - ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.size()); + ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.size() + 1); ImGui::Separator(); From 0343306664aa3b5951cd0df6752f7ca676ca15f2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:16:08 +0200 Subject: [PATCH 19/33] fix(front-asset-manager): fix wrong string size --- editor/src/DocumentWindows/AssetManager/FolderTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 73432df85..d7361f0a9 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -31,7 +31,7 @@ namespace nexo::editor { static void drawSearchBar(std::string &searchBuffer) { ImGui::PushItemWidth(-1); - ImGui::InputTextWithHint("##search", "Search...", searchBuffer.data(), searchBuffer.size()); + ImGui::InputTextWithHint("##search", "Search...", searchBuffer.data(), searchBuffer.size() + 1); ImGui::PopItemWidth(); ImGui::Separator(); } From 9cbad85c3802c2794034e3bc87af779ab25a4dec Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:16:33 +0200 Subject: [PATCH 20/33] chore(front-asset-manager): remove useless model import --- editor/src/DocumentWindows/AssetManager/Init.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 1719dd818..77be6864a 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -22,11 +22,6 @@ namespace nexo::editor { void AssetManagerWindow::setup() { - auto& catalog = assets::AssetCatalog::getInstance(); - auto asset = std::make_unique(); - assets::AssetLocation location{"my_package::My_Model@Random"}; - catalog.registerAsset(location, std::move(asset)); - { assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); From ecfc8c101af713d8c01d3132d4b8d989f530e0e4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:17:01 +0200 Subject: [PATCH 21/33] feat(front-asset-manager): move folder icon getter to utils file --- .../DocumentWindows/AssetManager/Utils.cpp | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 editor/src/DocumentWindows/AssetManager/Utils.cpp diff --git a/editor/src/DocumentWindows/AssetManager/Utils.cpp b/editor/src/DocumentWindows/AssetManager/Utils.cpp new file mode 100644 index 000000000..a7617d987 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/Utils.cpp @@ -0,0 +1,29 @@ +//// Utils.cpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 27/05/2025 +// Description: Source file for the utils function of the asset manager +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" + +namespace nexo::editor { + + ImTextureID AssetManagerWindow::getFolderIconTexture() const + { + if (const auto texRef = m_folderIcon.lock()) { + const auto &texData = texRef->getData(); + if (texData && texData->texture) { + return texData->texture->getId(); + } + } + return 0; + } +} From c394207963231d2c47f7f247355ed1098578f4c4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:17:41 +0200 Subject: [PATCH 22/33] refactor(front-asset-manager): refactor the main rendering of the asset manager to make it clearer and maintanable --- .../AssetManager/AssetManagerWindow.hpp | 10 +- .../src/DocumentWindows/AssetManager/Show.cpp | 501 ++---------------- 2 files changed, 61 insertions(+), 450 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index a6240b74b..7017f981a 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -43,7 +43,7 @@ namespace nexo::editor { } }; - struct LayoutSizes { + struct GridLayoutSizes { float iconSize = 64.0f; int iconSpacing = 8; @@ -76,8 +76,10 @@ namespace nexo::editor { }; struct LayoutSettings { - LayoutSizes size; + GridLayoutSizes size; LayoutColors color; + + float leftPanelWidth = 200.0f; }; class AssetManagerWindow final : public ADocumentWindow, LISTENS_TO(event::EventFileDrop) { @@ -98,6 +100,8 @@ namespace nexo::editor { LayoutSettings m_layout; void drawMenuBar(); + void drawPanelSplitter(); + void drawBreadcrumbs(); void drawAssetsGrid(); void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); void handleSelection(unsigned int index, bool isSelected); @@ -132,9 +136,9 @@ namespace nexo::editor { ); std::vector m_pendingDroppedFiles; - bool m_showDropIndicator = false; void handleDroppedFiles(); + void handleAssetDrop(const std::string &path); assets::AssetLocation getAssetLocation(const std::filesystem::path &path) const; void importDroppedFile(const std::string& filePath) const; }; diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 40e510d13..825f3e68d 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -16,42 +16,17 @@ #include "assets/Asset.hpp" #include "assets/AssetCatalog.hpp" #include "IconsFontAwesome.h" +#include "Path.hpp" #include "assets/Assets/Texture/Texture.hpp" #include "context/ThumbnailCache.hpp" #include "context/ActionManager.hpp" #include "context/actions/AssetActions.hpp" +#include "ImNexo/Elements.hpp" #include #include namespace nexo::editor { - static constexpr ImU32 getAssetTypeOverlayColor(const assets::AssetType type) - { - switch (type) { - case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); - case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); - default: return IM_COL32(0, 0, 0, 0); - } - } - - static void calculateGridLayout(LayoutSettings &layout) - { - const float availWidth = ImGui::GetContentRegionAvail().x; - - // Sizes - layout.size.columnCount = std::max( - static_cast(availWidth / layout.size.itemStep.x), 1 - ); - layout.size.itemSize = ImVec2( - layout.size.iconSize + ImGui::GetFontSize() * 1.5f, // width - layout.size.iconSize + ImGui::GetFontSize() * 1.7f // height - ); - layout.size.itemStep = ImVec2( - layout.size.itemSize.x + static_cast(layout.size.iconSpacing), - layout.size.itemSize.y + static_cast(layout.size.iconSpacing) - ); - } - void AssetManagerWindow::drawMenuBar() { if (ImGui::BeginMenuBar()) { @@ -64,457 +39,89 @@ namespace nexo::editor { } } - void AssetManagerWindow::handleSelection(const unsigned int index, const bool isSelected) + void AssetManagerWindow::drawPanelSplitter() { - if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) { - if (isSelected) - m_selectedAssets.erase(index); - else - m_selectedAssets.insert(index); - return; - } - - if (ImGui::IsKeyDown(ImGuiKey_ModShift) && !m_selectedAssets.empty()) { - const unsigned int latestSelected = *m_selectedAssets.rbegin(); - const auto [start, end] = std::minmax(latestSelected, index); - const auto range = std::views::iota(start, end + 1); - m_selectedAssets.insert(range.begin(), range.end()); - return; - } - - m_selectedAssets.clear(); - m_selectedAssets.insert(index); - } - - void AssetManagerWindow::drawAsset( - const assets::GenericAssetRef& asset, - const unsigned int index, - const ImVec2& itemPos, - const ImVec2& itemSize - ) { - const auto assetData = asset.lock(); - if (!assetData) - return; - ImDrawList* drawList = ImGui::GetWindowDrawList(); - const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - - ImGui::PushID(static_cast(index)); - - ImGui::SetCursorScreenPos(itemPos); - - const bool clicked = ImGui::InvisibleButton("##item", itemSize); - const bool isHovered = ImGui::IsItemHovered(); - - const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end(); - const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); - - if (isSelected) { - // Draw a distinctive border around selected items - drawList->AddRect( - ImVec2(itemPos.x - 1, itemPos.y - 1), - ImVec2(itemEnd.x + 1, itemEnd.y + 1), - m_layout.color.selectedBoxColor, - m_layout.size.CORNER_RADIUS, - 0, - m_layout.size.SELECTED_BOX_THICKNESS - ); - } - - // Draw thumbnail area - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); - - if (const ImTextureID textureId = ThumbnailCache::getInstance().getThumbnail(asset); !textureId) { - drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg); - } else { - constexpr float padding = 4.0f; - const ImVec2 imageStart(itemPos.x + padding, itemPos.y + padding); - const ImVec2 imageEnd(thumbnailEnd.x - padding, thumbnailEnd.y - padding); - - drawList->AddImage( - textureId, - imageStart, - imageEnd, - ImVec2(0, 1), // UV0 (top-left) - ImVec2(1, 0), // UV1 (bottom-right) - IM_COL32(255, 255, 255, 255) // White tint - ); - } - - // Draw type overlay (maybe later modify it to an icon) - const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.OVERLAY_PADDING, itemPos.y + m_layout.size.OVERLAY_PADDING); - const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); - drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.OVERLAY_SIZE, overlayPos.y + m_layout.size.OVERLAY_SIZE), overlayColor); - - // Draw title - const char *assetName = assetData->getMetadata().location.getName().c_str(); - const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, - thumbnailEnd.y + m_layout.size.TITLE_PADDING); - - // Background rectangle for text - const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : m_layout.color.titleBg; - drawList->AddRectFilled(ImVec2(itemPos.x, thumbnailEnd.y), ImVec2(itemEnd.x, itemEnd.y), titleBgColor); - drawList->AddText(textPos, m_layout.color.titleText, assetName); - - // Handle selection when clicked - if (clicked) - handleSelection(index, isSelected); - - // On Hover show asset location - if (isHovered) - ImGui::SetTooltip("%s", assetData->getMetadata().location.getFullLocation().c_str()); - - // Handle drag source for assets - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - AssetDragDropPayload payload; - payload.type = assetData->getType(); - payload.id = assetData->getID(); - payload.path = assetData->getMetadata().location.getFullLocation(); - payload.name = assetName; - - ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); + constexpr float splitterWidth = 5.0f; - // Show preview while dragging - //TODO: Add asset preview thanks to thumbnail cache after rebasing - if (assetData->getType() == assets::AssetType::TEXTURE) { - const auto textureAsset = asset.as(); - if (const auto textureData = textureAsset.lock(); - textureData && textureData->getData() && textureData->getData()->texture) { - const ImTextureID textureId = textureData->getData()->texture->getId(); - ImGui::Image(textureId, {64, 64}); - } - } + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Separator)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_SeparatorHovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_SeparatorActive)); - ImGui::EndDragDropSource(); - } + ImGui::Button("##Splitter", ImVec2(splitterWidth, -1)); + ImGui::PopStyleColor(3); - ImGui::PopID(); + if (ImGui::IsItemActive()) + m_layout.leftPanelWidth += ImGui::GetIO().MouseDelta.x; } - ImTextureID AssetManagerWindow::getFolderIconTexture() const + void AssetManagerWindow::drawBreadcrumbs() { - if (const auto texRef = m_folderIcon.lock()) { - const auto &texData = texRef->getData(); - if (texData && texData->texture) { - return texData->texture->getId(); - } - } - return 0; - } - - void AssetManagerWindow::drawFolder( - const std::string& folderPath, - const std::string& folderName, - const ImVec2& itemPos, - const ImVec2& itemSize - ) { - ImDrawList* drawList = ImGui::GetWindowDrawList(); - const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - - ImGui::PushID(("folder_" + folderPath).c_str()); - - ImGui::SetCursorScreenPos(itemPos); - - const bool clicked = ImGui::InvisibleButton("##folder", itemSize); - const bool isHovered = ImGui::IsItemHovered(); - - if (isHovered) { - m_hoveredFolder = folderPath; - } else if (m_hoveredFolder == folderPath) { - m_hoveredFolder.clear(); - } - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) - { - const auto* data = static_cast(payload->Data); - assets::AssetCatalog::getInstance().moveAsset(data->id, folderPath); - } - ImGui::EndDragDropTarget(); - } - - // Background - use hover color when hovered - const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); - - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); - - // Calculate padding for the icon - constexpr float padding = 10.0f; - - // Calculate available area dimensions - const float availWidth = thumbnailEnd.x - itemPos.x - (padding * 2.0f); - const float availHeight = thumbnailEnd.y - itemPos.y - (padding * 2.0f); - - // Maintain aspect ratio by using the smaller dimension - const float displaySize = std::min(availWidth, availHeight); - - // Calculate centered position - const float xOffset = (availWidth - displaySize) * 0.5f + padding; - const float yOffset = (availHeight - displaySize) * 0.5f + padding; - - // Final image coordinates maintaining aspect ratio - const ImVec2 imageStart( - itemPos.x + xOffset, - itemPos.y + yOffset - ); - const ImVec2 imageEnd( - imageStart.x + displaySize, - imageStart.y + displaySize - ); - - // Draw folder PNG icon - - if (const ImTextureID folderIconTexture = getFolderIconTexture()) { - drawList->AddImage( - folderIconTexture, - imageStart, - imageEnd, - ImVec2(0, 1), // UV0 (top-left) - ImVec2(1, 0), // UV1 (bottom-right) - IM_COL32(255, 255, 255, 255) // White tint for default color - ); - } - - // Calculate text size to ensure it fits - const ImVec2 textSize = ImGui::CalcTextSize(folderName.c_str()); - - // Draw title background - const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); - const float titleAreaHeight = itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); - - drawList->AddRectFilled( - ImVec2(itemPos.x, thumbnailEnd.y), - ImVec2(itemEnd.x, itemEnd.y), - titleBgColor - ); - - // Position text with proper vertical alignment - const float textY = thumbnailEnd.y + ((titleAreaHeight - textSize.y) * 0.5f); - const float textX = itemPos.x + (itemSize.x - textSize.x) * 0.5f; - - drawList->AddText( - ImVec2(textX, textY), - m_layout.color.titleText, - folderName.c_str() - ); - - if (clicked) - m_currentFolder = folderPath; // Navigate into this folder + ImGui::PushID("breadcrumb_root"); + if (ImGui::Button("Assets")) + m_currentFolder.clear(); + handleAssetDrop(""); ImGui::PopID(); - } - - void AssetManagerWindow::drawAssetsGrid() - { - const ImVec2 startPos = ImGui::GetCursorScreenPos(); - - std::vector> subfolders; - for (auto& [path,name] : m_folderStructure) { - if (path.empty() || path.front() == '_') - continue; - - if (m_currentFolder.empty()) { - if (path.find('/') == std::string::npos) - subfolders.emplace_back(path, name); - } else { - if (std::string prefix = m_currentFolder + "/"; path.rfind(prefix, 0) == 0 && - path.find('/', prefix.size()) == std::string::npos) - { - subfolders.emplace_back(path, path.substr(prefix.size())); - } - } - } - - std::vector filtered; - for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { - if (const auto d = ref.lock()) { - const auto& folder = d->getMetadata().location.getPath(); - if (folder == "_internal") - continue; - if (m_selectedType != assets::AssetType::UNKNOWN && - d->getType() != m_selectedType) continue; - - if (folder == m_currentFolder) - filtered.push_back(ref); - } - } - const size_t totalItems = subfolders.size() + filtered.size(); - ImGuiListClipper clipper; - const auto rows = static_cast((totalItems + m_layout.size.columnCount - 1) / m_layout.size.columnCount); - clipper.Begin(rows, m_layout.size.itemStep.y); - - while (clipper.Step()) { - for (int line = clipper.DisplayStart; line < clipper.DisplayEnd; ++line) { - const unsigned int startIdx = line * m_layout.size.columnCount; - const unsigned int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(totalItems)); - - for (unsigned int i = startIdx; i < endIdx; ++i) { - unsigned int col = i % m_layout.size.columnCount; - unsigned int row = i / m_layout.size.columnCount; - ImVec2 itemPos{ - startPos.x + static_cast(col) * m_layout.size.itemStep.x, - startPos.y + static_cast(row) * m_layout.size.itemStep.y - }; + std::string path = m_currentFolder; + std::vector crumbs = splitPath(m_currentFolder); + std::string fullPath; + for (auto &crumb : crumbs) + { + fullPath += (fullPath.empty() ? "" : "/") + crumb; + ImGui::SameLine(); ImGui::Text(" > "); ImGui::SameLine(); + ImGui::PushID(("breadcrumb_" + crumb).c_str()); + if (crumb == *std::prev(crumbs.end())) + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", crumb.c_str()); + else if (ImNexo::Button(crumb)) + m_currentFolder = fullPath; - if (i < static_cast(subfolders.size())) { - // draw folder thumbnail - drawFolder( - subfolders[i].first, - subfolders[i].second, - itemPos, - m_layout.size.itemSize - ); - } else { - // draw asset thumbnail - const auto assetIdx = i - static_cast(subfolders.size()); - drawAsset( - filtered[assetIdx], - assetIdx, - itemPos, - m_layout.size.itemSize - ); - } - } - } + handleAssetDrop(fullPath); + ImGui::PopID(); } - clipper.End(); } void AssetManagerWindow::show() { ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); - beginRender(NEXO_WND_USTRID_ASSET_MANAGER); + if (!ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar)) + return; + beginRender(NEXO_WND_USTRID_ASSET_MANAGER); drawMenuBar(); - // Calculate sizes for splitter - constexpr float splitterWidth = 5.0f; - static float leftPanelWidth = 200.0f; // Default width - - // Left panel (folder hierarchy) - ImGui::BeginChild("LeftPanel", ImVec2(leftPanelWidth, 0), true); - drawFolderTree(); - ImGui::EndChild(); - - // Splitter - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Separator)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_SeparatorHovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_SeparatorActive)); - - ImGui::Button("##Splitter", ImVec2(splitterWidth, -1)); - ImGui::PopStyleColor(3); - - // Handle splitter drag - if (ImGui::IsItemActive()) - leftPanelWidth += ImGui::GetIO().MouseDelta.x; - - // Right panel (asset grid) - ImGui::SameLine(); - ImGui::BeginChild("RightPanel", ImVec2(0, 0), true); - - // Handle file drops - if (ImGui::BeginDragDropTarget()) { - m_showDropIndicator = true; - // Only to show the drop indicator - ImGui::EndDragDropTarget(); - } else { - m_showDropIndicator = false; - } - - // Draw drop indicator - if (m_showDropIndicator || !m_pendingDroppedFiles.empty()) + // Left panel { - ImDrawList* drawList = ImGui::GetWindowDrawList(); - const ImVec2 windowPos = ImGui::GetWindowPos(); - const ImVec2 windowSize = ImGui::GetWindowSize(); - - // Draw semi-transparent overlay - drawList->AddRectFilled(windowPos, ImVec2(windowPos.x + windowSize.x, windowPos.y + windowSize.y), - IM_COL32(100, 100, 255, 50)); - - // Draw border - drawList->AddRect(windowPos, ImVec2(windowPos.x + windowSize.x, windowPos.y + windowSize.y), - IM_COL32(100, 100, 255, 200), 0.0f, 0, 3.0f); - - // Draw text - const std::string dropText = "Drop files here to import"; - const ImVec2 textSize = ImGui::CalcTextSize(dropText.c_str()); - const auto textPos = ImVec2(windowPos.x + (windowSize.x - textSize.x) * 0.5f, - windowPos.y + (windowSize.y - textSize.y) * 0.5f); - drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize() * 1.5f, textPos, - IM_COL32(255, 255, 255, 255), dropText.c_str()); + ImGui::BeginChild("LeftPanel", ImVec2(m_layout.leftPanelWidth, 0), true); + drawFolderTree(); + ImGui::EndChild(); } - ImGui::Text(ICON_FA_FOLDER " "); + drawPanelSplitter(); ImGui::SameLine(); + // Right panel { - ImGui::PushID("breadcrumb_root"); - if (ImGui::Button("Assets")) - m_currentFolder.clear(); - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) - { - const auto data = static_cast(payload->Data); - assets::AssetCatalog::getInstance().moveAsset(data->id, ""); - } - ImGui::EndDragDropTarget(); - } - ImGui::PopID(); - } + ImGui::BeginChild("RightPanel", ImVec2(0, 0), true); - // Intermediate breadcrumbs - std::string path = m_currentFolder; - size_t pos = 0; - std::string segment; - std::string fullPath; - while ((pos = path.find('/')) != std::string::npos) { - segment = path.substr(0, pos); - fullPath += (fullPath.empty() ? "" : "/") + segment; - ImGui::SameLine(); ImGui::Text(" > "); ImGui::SameLine(); + ImGui::Text(ICON_FA_FOLDER " "); + ImGui::SameLine(); - ImGui::PushID(("breadcrumb_" + fullPath).c_str()); - if (ImGui::Button(segment.c_str())) - m_currentFolder = fullPath; - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) - { - const auto data = static_cast(payload->Data); - assets::AssetCatalog::getInstance().moveAsset(data->id, fullPath); - } - ImGui::EndDragDropTarget(); - } - ImGui::PopID(); - - path.erase(0, pos + 1); - } - - if (!path.empty()) { - ImGui::SameLine(); ImGui::Text(" > "); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", path.c_str()); + drawBreadcrumbs(); + ImGui::Separator(); + drawAssetsGrid(); + ImGui::EndChild(); } - ImGui::Separator(); - - calculateGridLayout(m_layout); - drawAssetsGrid(); - ImGui::EndChild(); - - if (m_popupManager.showPopup("Folder Tree Context Menu")) - folderTreeContextMenu(); + // Popups + { + if (m_popupManager.showPopup("Folder Tree Context Menu")) + folderTreeContextMenu(); - if (m_popupManager.showPopupModal("Create new folder")) - newFolderMenu(); + if (m_popupManager.showPopupModal("Create new folder")) + newFolderMenu(); + } ImGui::End(); } From 1fab3081d7f1a973abbba35131033e0f601017be Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:18:03 +0200 Subject: [PATCH 23/33] chore(front-asset-manager): remove useless import --- editor/src/DocumentWindows/EditorScene/Init.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index 93f6cc245..d8af86ccf 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -190,12 +190,6 @@ namespace nexo::editor } lightsScene(m_sceneId); - // 9mn - assets::AssetImporter importer; - std::filesystem::path path9mn = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); - assets::ImporterFileInput fileInput9mn{path9mn}; - auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@DefaultScene/"), fileInput9mn); - // Background createAndAddEntity({0.0f, 40.0f, -2.5f}, {44.0f, 80.0f, 0.5f}, {0, 0, 0}, {0.91f, 0.91f, 0.91f, 1.0f}, system::ShapeType::Box, JPH::EMotionType::Static); From 6e3fd891881aa460b50797f08aea873bd44e74ed Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:18:33 +0200 Subject: [PATCH 24/33] fix(front-asset-manager): add missing include --- common/String.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/String.hpp b/common/String.hpp index 810010dab..6d96d18b8 100644 --- a/common/String.hpp +++ b/common/String.hpp @@ -15,10 +15,9 @@ #pragma once #include -#include +#include namespace nexo { - /** * @brief Compare two strings case-insensitively. * From 5092b70af2dca99a70c2dbc9415df178ef3f706c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:18:56 +0200 Subject: [PATCH 25/33] feat(front-asset-manager): add utils func to split a path --- common/Path.cpp | 13 +++++++++++++ common/Path.hpp | 2 ++ 2 files changed, 15 insertions(+) diff --git a/common/Path.cpp b/common/Path.cpp index f6585a125..acaeb63fd 100644 --- a/common/Path.cpp +++ b/common/Path.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "Path.hpp" +#include namespace nexo { @@ -53,4 +54,16 @@ namespace nexo { size_t end = s.find_last_not_of('/'); return s.substr(start, end - start + 1); } + + std::vector splitPath(const std::filesystem::path& path) + { + auto segments = path + | std::views::filter([&](auto const& e){ + return e != path.root_name() && e != path.root_directory(); + }) + | std::views::transform([](auto const& e){ + return e.string(); + }); + return std::vector(segments.begin(), segments.end()); + } } diff --git a/common/Path.hpp b/common/Path.hpp index 636fd7682..b1ed4246d 100644 --- a/common/Path.hpp +++ b/common/Path.hpp @@ -53,4 +53,6 @@ namespace nexo { }; std::string normalizePathAndRemovePrefixSlash(const std::string &rawPath); + + std::vector splitPath(const std::filesystem::path& path); } // namespace nexo From 24acb49cebefb00da412e15daa8be4681b311739 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 27 Jul 2025 23:19:19 +0200 Subject: [PATCH 26/33] chore(front-asset-manager): add source file --- editor/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 2e18a61dd..bfd9e9262 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -57,6 +57,9 @@ set(SRCS editor/src/DocumentWindows/AssetManager/FolderTree.cpp editor/src/DocumentWindows/AssetManager/FileDrop.cpp editor/src/DocumentWindows/AssetManager/FolderCreation.cpp + editor/src/DocumentWindows/AssetManager/Selection.cpp + editor/src/DocumentWindows/AssetManager/AssetGrid.cpp + editor/src/DocumentWindows/AssetManager/Utils.cpp editor/src/DocumentWindows/ConsoleWindow/Init.cpp editor/src/DocumentWindows/ConsoleWindow/Log.cpp editor/src/DocumentWindows/ConsoleWindow/Show.cpp From 04c955e54467e53a173bf663cf9a6d520b7fc4bd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Jul 2025 04:53:54 +0200 Subject: [PATCH 27/33] refactor(front-asset-manager): asset drawing is now more readabme and simpler --- .../AssetManager/AssetGrid.cpp | 178 +++++++++++------- .../AssetManager/AssetManagerWindow.hpp | 24 ++- .../DocumentWindows/AssetManager/Utils.cpp | 4 +- 3 files changed, 128 insertions(+), 78 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp index bbc5fead1..ff9851220 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -13,16 +13,19 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetManagerWindow.hpp" +#include "assets/Asset.hpp" #include "assets/AssetCatalog.hpp" #include "context/ThumbnailCache.hpp" +#include "ImNexo/Elements.hpp" namespace nexo::editor { static constexpr ImU32 getAssetTypeOverlayColor(const assets::AssetType type) { switch (type) { - case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); - case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); + case assets::AssetType::TEXTURE: return IM_COL32(60, 40, 40, 255); + case assets::AssetType::MODEL: return IM_COL32(40, 60, 40, 255); + case assets::AssetType::MATERIAL: return IM_COL32(40, 40, 60, 255); default: return IM_COL32(0, 0, 0, 0); } } @@ -31,7 +34,6 @@ namespace nexo::editor { { const float availWidth = ImGui::GetContentRegionAvail().x; - // Sizes layout.size.columnCount = std::max( static_cast(availWidth / layout.size.itemStep.x), 1 ); @@ -45,105 +47,135 @@ namespace nexo::editor { ); } - void AssetManagerWindow::drawAsset( + static void drawAssetThumbnail( const assets::GenericAssetRef& asset, - const unsigned int index, - const ImVec2& itemPos, - const ImVec2& itemSize + const LayoutSettings &layout, + const AssetLayoutParams& params, + const bool isSelected ) { - const auto assetData = asset.lock(); - if (!assetData) - return; ImDrawList* drawList = ImGui::GetWindowDrawList(); - const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - - ImGui::PushID(static_cast(index)); - - ImGui::SetCursorScreenPos(itemPos); - - const bool clicked = ImGui::InvisibleButton("##item", itemSize); - const bool isHovered = ImGui::IsItemHovered(); - - const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end(); - const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); - if (isSelected) { - // Draw a distinctive border around selected items - drawList->AddRect( - ImVec2(itemPos.x - 1, itemPos.y - 1), - ImVec2(itemEnd.x + 1, itemEnd.y + 1), - m_layout.color.selectedBoxColor, - m_layout.size.CORNER_RADIUS, - 0, - m_layout.size.SELECTED_BOX_THICKNESS - ); - } - - // Draw thumbnail area - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const ImU32 bgColor = isSelected ? layout.color.selectedBoxColor : layout.color.thumbnailBg; + ImNexo::ButtonBorder(bgColor, bgColor, bgColor); if (const ImTextureID textureId = ThumbnailCache::getInstance().getThumbnail(asset); !textureId) { - drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg); + drawList->AddRectFilled(params.itemPos, params.thumbnailEnd, layout.color.thumbnailBg); } else { constexpr float padding = 4.0f; - const ImVec2 imageStart(itemPos.x + padding, itemPos.y + padding); - const ImVec2 imageEnd(thumbnailEnd.x - padding, thumbnailEnd.y - padding); + const ImVec2 imageStart(params.itemPos.x + padding, params.itemPos.y + padding); + const ImVec2 imageEnd(params.thumbnailEnd.x - padding, params.thumbnailEnd.y - padding); drawList->AddImage( textureId, imageStart, imageEnd, - ImVec2(0, 1), // UV0 (top-left) - ImVec2(1, 0), // UV1 (bottom-right) - IM_COL32(255, 255, 255, 255) // White tint + ImVec2(0, 1), + ImVec2(1, 0), + IM_COL32(255, 255, 255, 255) ); } + } + + static void cropText(const std::string& assetName, std::string& displayText, float availableTextWidth) + { + const std::string ellipsis = "..."; + if (ImGui::CalcTextSize(assetName.c_str()).x <= availableTextWidth) { + displayText = assetName; + return; + } + + for (size_t length = assetName.size(); length > 0; --length) { + displayText = assetName.substr(0, length) + ellipsis; + if (ImGui::CalcTextSize(displayText.c_str()).x <= availableTextWidth) + return; + } + + displayText = ellipsis; + } + + void AssetManagerWindow::drawAssetTitle( + const std::shared_ptr& assetData, + const AssetLayoutParams& params, + bool isHovered + ) const { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + const float titleAreaHeight = params.itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const float titlePadding = std::max(2.0f, titleAreaHeight * 0.1f); + const float availableTextWidth = params.itemSize.x - (titlePadding * 2); + + ImU32 titleBgColor = (isHovered) ? + m_layout.color.titleBgHovered : + getAssetTypeOverlayColor(assetData->getType()); + + // title background + drawList->AddRectFilled( + ImVec2(params.itemPos.x, params.thumbnailEnd.y), + ImVec2(params.itemEnd.x, params.itemEnd.y), + titleBgColor + ); + + const std::string assetName = assetData->getMetadata().location.getName().data(); + const ImVec2 fullTextSize = ImGui::CalcTextSize(assetName.c_str()); + std::string displayText = assetName; + + // Crop text if it's too wide + if (fullTextSize.x > availableTextWidth) + cropText(assetName, displayText, availableTextWidth); + + const ImVec2 displayTextSize = ImGui::CalcTextSize(displayText.c_str()); + const ImVec2 textPos( + params.itemPos.x + (params.itemSize.x - displayTextSize.x) * 0.5f, + params.thumbnailEnd.y + (titleAreaHeight - displayTextSize.y) * 0.5f + ); + drawList->AddText(textPos, m_layout.color.titleText, displayText.c_str()); + + if (isHovered) { + if (fullTextSize.x > availableTextWidth) + ImGui::SetTooltip("%s\n%s", assetName.c_str(), assetData->getMetadata().location.getFullLocation().c_str()); + else + ImGui::SetTooltip("%s", assetData->getMetadata().location.getFullLocation().c_str()); + } + } + + void AssetManagerWindow::drawAsset( + const assets::GenericAssetRef& asset, + const unsigned int index, + const ImVec2& itemPos, + const ImVec2& itemSize + ) { + const auto assetData = asset.lock(); + if (!assetData) + return; - // Draw type overlay (maybe later modify it to an icon) - const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.OVERLAY_PADDING, itemPos.y + m_layout.size.OVERLAY_PADDING); - const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); - drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.OVERLAY_SIZE, overlayPos.y + m_layout.size.OVERLAY_SIZE), overlayColor); + ImGui::PushID(static_cast(index)); + ImGui::SetCursorScreenPos(itemPos); - // Draw title - const char *assetName = assetData->getMetadata().location.getName().c_str(); - const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, - thumbnailEnd.y + m_layout.size.TITLE_PADDING); + const bool clicked = ImGui::InvisibleButton("##item", itemSize); + const bool isHovered = ImGui::IsItemHovered(); + const bool isSelected = m_selectedAssets.contains(index); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const AssetLayoutParams assetLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; - // Background rectangle for text - const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : m_layout.color.titleBg; - drawList->AddRectFilled(ImVec2(itemPos.x, thumbnailEnd.y), ImVec2(itemEnd.x, itemEnd.y), titleBgColor); - drawList->AddText(textPos, m_layout.color.titleText, assetName); + drawAssetThumbnail(asset, m_layout, assetLayoutParams, isSelected); + drawAssetTitle(assetData, assetLayoutParams, isHovered); - // Handle selection when clicked if (clicked) handleSelection(index, isSelected); - // On Hover show asset location - if (isHovered) - ImGui::SetTooltip("%s", assetData->getMetadata().location.getFullLocation().c_str()); - - // Handle drag source for assets if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { AssetDragDropPayload payload; payload.type = assetData->getType(); payload.id = assetData->getID(); payload.path = assetData->getMetadata().location.getFullLocation(); - payload.name = assetName; + payload.name = assetData->getMetadata().location.getName().data(); ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); - - // Show preview while dragging - //TODO: Add asset preview thanks to thumbnail cache after rebasing - if (assetData->getType() == assets::AssetType::TEXTURE) { - const auto textureAsset = asset.as(); - if (const auto textureData = textureAsset.lock(); - textureData && textureData->getData() && textureData->getData()->texture) { - const ImTextureID textureId = textureData->getData()->texture->getId(); - ImGui::Image(textureId, {64, 64}); - } - } + ImTextureID textureID = ThumbnailCache::getInstance().getThumbnail(asset); + if (textureID) + ImGui::Image(textureID, {64, 64}, ImVec2(0, 1), ImVec2(1, 0)); ImGui::EndDragDropSource(); } @@ -215,7 +247,7 @@ namespace nexo::editor { // Draw folder PNG icon - if (const ImTextureID folderIconTexture = getFolderIconTexture()) { + if (const ImTextureID folderIconTexture = getIconTexture(m_folderIcon)) { drawList->AddImage( folderIconTexture, imageStart, diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 7017f981a..f8881dc2c 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -53,7 +53,7 @@ namespace nexo::editor { static constexpr float THUMBNAIL_HEIGHT_RATIO = 0.8f; static constexpr float TITLE_PADDING = 5.0f; - static constexpr float OVERLAY_SIZE = 6.0f; + static constexpr float OVERLAY_SIZE = 24.0f; static constexpr float OVERLAY_PADDING = 5.0f; static constexpr float CORNER_RADIUS = 5.0f; static constexpr float SELECTED_BOX_THICKNESS = 4.0f; @@ -82,6 +82,19 @@ namespace nexo::editor { float leftPanelWidth = 200.0f; }; + struct AssetTypeInfo { + ImTextureID iconTexture; + ImU32 backgroundColor; + std::string tooltip; + }; + + struct AssetLayoutParams { + ImVec2 itemPos; + ImVec2 itemSize; + ImVec2 itemEnd; + ImVec2 thumbnailEnd; + }; + class AssetManagerWindow final : public ADocumentWindow, LISTENS_TO(event::EventFileDrop) { public: using ADocumentWindow::ADocumentWindow; @@ -102,8 +115,13 @@ namespace nexo::editor { void drawMenuBar(); void drawPanelSplitter(); void drawBreadcrumbs(); + void drawAssetsGrid(); - void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); + void drawAssetTitle( + const std::shared_ptr& assetData, + const AssetLayoutParams& params, + bool isHovered + ) const; void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); void handleSelection(unsigned int index, bool isSelected); assets::AssetType m_selectedType = assets::AssetType::UNKNOWN; @@ -124,7 +142,7 @@ namespace nexo::editor { FolderCreationState m_folderCreationState; assets::AssetRef m_folderIcon; - ImTextureID getFolderIconTexture() const; + ImTextureID getIconTexture(const assets::AssetRef &texture) const; void newFolderMenu(); bool handleNewFolderCreation(); diff --git a/editor/src/DocumentWindows/AssetManager/Utils.cpp b/editor/src/DocumentWindows/AssetManager/Utils.cpp index a7617d987..5dcf2f513 100644 --- a/editor/src/DocumentWindows/AssetManager/Utils.cpp +++ b/editor/src/DocumentWindows/AssetManager/Utils.cpp @@ -16,9 +16,9 @@ namespace nexo::editor { - ImTextureID AssetManagerWindow::getFolderIconTexture() const + ImTextureID AssetManagerWindow::getIconTexture(const assets::AssetRef &texture) const { - if (const auto texRef = m_folderIcon.lock()) { + if (const auto texRef = texture.lock()) { const auto &texData = texRef->getData(); if (texData && texData->texture) { return texData->texture->getId(); From 34c8d755741bfbd295f5e0371a8376102d89d643 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Jul 2025 12:18:19 +0200 Subject: [PATCH 28/33] feat(front-asset-manager): add new folder tree class to better handle folders --- .../AssetManager/AssetGrid.cpp | 38 +-- .../AssetManager/AssetManagerWindow.hpp | 9 +- .../AssetManager/FolderCreation.cpp | 24 +- .../AssetManager/FolderManager.cpp | 231 ++++++++++++++++++ .../AssetManager/FolderManager.hpp | 64 +++++ .../AssetManager/FolderTree.cpp | 22 +- .../src/DocumentWindows/AssetManager/Init.cpp | 1 - .../DocumentWindows/AssetManager/Update.cpp | 70 +----- 8 files changed, 326 insertions(+), 133 deletions(-) create mode 100644 editor/src/DocumentWindows/AssetManager/FolderManager.cpp create mode 100644 editor/src/DocumentWindows/AssetManager/FolderManager.hpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp index ff9851220..d122da66d 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -292,36 +292,20 @@ namespace nexo::editor { calculateGridLayout(m_layout); const ImVec2 startPos = ImGui::GetCursorScreenPos(); - - std::vector> subfolders; - for (auto& [path,name] : m_folderStructure) { - if (path.empty() || path.front() == '_') - continue; - - if (m_currentFolder.empty()) { - if (path.find('/') == std::string::npos) - subfolders.emplace_back(path, name); - } else { - if (std::string prefix = m_currentFolder + "/"; path.rfind(prefix, 0) == 0 && - path.find('/', prefix.size()) == std::string::npos) - { - subfolders.emplace_back(path, path.substr(prefix.size())); - } - } - } + auto subfolders = m_folderManager.getChildren(m_currentFolder); std::vector filtered; for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { - if (const auto d = ref.lock()) { - const auto& folder = d->getMetadata().location.getPath(); - if (folder == "_internal") - continue; - if (m_selectedType != assets::AssetType::UNKNOWN && - d->getType() != m_selectedType) continue; - - if (folder == m_currentFolder) - filtered.push_back(ref); - } + const auto d = ref.lock(); + if (!d) + continue; + const auto& folder = d->getMetadata().location.getPath(); + if (folder == "_internal") + continue; + if (m_selectedType != assets::AssetType::UNKNOWN && d->getType() != m_selectedType) + continue; + if (folder == m_currentFolder) + filtered.push_back(ref); } const size_t totalItems = subfolders.size() + filtered.size(); diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index f8881dc2c..cab7bc8e1 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -21,6 +21,7 @@ #include "utils/TransparentStringHash.hpp" #include #include "assets/Asset.hpp" +#include "FolderManager.hpp" namespace nexo::editor { @@ -108,7 +109,6 @@ namespace nexo::editor { private: std::set m_selectedAssets; - std::unordered_map, TransparentStringHash, std::equal_to<>> m_folderChildren; LayoutSettings m_layout; @@ -121,19 +121,18 @@ namespace nexo::editor { const std::shared_ptr& assetData, const AssetLayoutParams& params, bool isHovered - ) const; void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); + ) const; + void drawAsset(const assets::GenericAssetRef& asset, unsigned int index, const ImVec2& itemPos, const ImVec2& itemSize); void handleSelection(unsigned int index, bool isSelected); assets::AssetType m_selectedType = assets::AssetType::UNKNOWN; std::string m_currentFolder; // Currently selected folder std::string m_hoveredFolder; // Currently hovered folder - std::vector> m_folderStructure; // Pairs of (path, name) std::string m_searchBuffer = ""; PopupManager m_popupManager; void buildFolderStructure(); - void updateFolderChildren(); void folderTreeContextMenu(); void drawFolderTree(); @@ -159,6 +158,8 @@ namespace nexo::editor { void handleAssetDrop(const std::string &path); assets::AssetLocation getAssetLocation(const std::filesystem::path &path) const; void importDroppedFile(const std::string& filePath) const; + + FolderManager m_folderManager; }; /** diff --git a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp index 84debfaea..f24d80797 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp @@ -25,31 +25,13 @@ namespace nexo::editor { return false; } - std::string newFolderPath = (m_folderCreationState.parentPath.empty()) ? "" : m_folderCreationState.parentPath + "/"; - newFolderPath += m_folderCreationState.folderName; - - const bool folderExists = std::ranges::any_of(m_folderStructure, - [&newFolderPath](const auto& folder) { - return folder.first == newFolderPath; - } - ); - - if (folderExists) { + // Replace the old logic with: + if (!m_folderManager.createFolder(m_folderCreationState.parentPath, m_folderCreationState.folderName)) { m_folderCreationState.showError = true; - m_folderCreationState.errorMessage = "Folder already exists"; + m_folderCreationState.errorMessage = "Failed to create folder (may already exist)"; return false; } - m_folderStructure.emplace_back(newFolderPath, m_folderCreationState.folderName); - - std::sort( - m_folderStructure.begin() + 1, - m_folderStructure.end(), - [](const auto& a, const auto& b) { - return a.first < b.first; - } - ); - updateFolderChildren(); return true; } diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.cpp b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp new file mode 100644 index 000000000..748fa5f47 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp @@ -0,0 +1,231 @@ +//// FolderManager.cpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 28/07/2025 +// Description: Source file for the folder manager +// +/////////////////////////////////////////////////////////////////////////////// + +#include "FolderManager.hpp" + +namespace nexo::editor { + + static bool isNameValid(const std::string &folderName) + { + return !(folderName.empty() || folderName.front() == '_' || folderName.find('/') != std::string::npos); + } + + FolderManager::FolderManager() + { + m_pathToName[""] = "Assets"; + m_children[""] = {}; + } + + void FolderManager::buildFromAssets() + { + clear(); + std::unordered_set allPaths; + + for (const auto& ref : assets::AssetCatalog::getInstance().getAssets()) { + if (const auto assetData = ref.lock()) { + const std::string& folderPath = assetData->getMetadata().location.getPath(); + if (folderPath.empty() || folderPath.front() == '_') + continue; + addPathAndParents(folderPath, allPaths); + } + } + + buildMapsFromPaths(allPaths); + } + + std::vector> FolderManager::getChildren(const std::string& path) const + { + std::vector> result; + + if (auto it = m_children.find(path); it != m_children.end()) { + result.reserve(it->second.size()); + for (const std::string& childPath : it->second) { + auto nameIt = m_pathToName.find(childPath); + if (nameIt != m_pathToName.end()) { + result.emplace_back(childPath, nameIt->second); + } + } + } + + return result; + } + + std::string FolderManager::getName(const std::string& path) const + { + if (auto it = m_pathToName.find(path); it != m_pathToName.end()) + return it->second; + return extractNameFromPath(path); + } + + bool FolderManager::exists(const std::string& path) const + { + return m_pathToName.contains(path); + } + + bool FolderManager::createFolder(const std::string& parentPath, const std::string& folderName) + { + if (!exists(parentPath)) + return false; + + std::string newFolderPath = parentPath.empty() ? folderName : parentPath + "/" + folderName; + if (exists(newFolderPath)) + return false; + + if (!isNameValid(folderName)) + return false; + + m_pathToName[newFolderPath] = folderName; + m_children[newFolderPath] = {}; + m_children[parentPath].push_back(newFolderPath); + + std::sort(m_children[parentPath].begin(), m_children[parentPath].end()); + + return true; + } + + bool FolderManager::deleteFolder(const std::string& folderPath) + { + if (folderPath.empty() || !exists(folderPath)) + return false; + + // TODO: Check if folder contains assets - you might want to prevent deletion + // if (!getFolderAssets(folderPath).empty()) return false; + + // Recursively delete all children first + auto childrenCopy = m_children[folderPath]; + for (const std::string& childPath : childrenCopy) + deleteFolder(childPath); + + // Remove from parent's children list + std::string parentPath = getParentPath(folderPath); + if (auto parentIt = m_children.find(parentPath); parentIt != m_children.end()) { + auto& parentChildren = parentIt->second; + parentChildren.erase( + std::remove(parentChildren.begin(), parentChildren.end(), folderPath), + parentChildren.end() + ); + } + + m_pathToName.erase(folderPath); + m_children.erase(folderPath); + return true; + } + + bool FolderManager::renameFolder(const std::string& folderPath, const std::string& newName) + { + if (folderPath.empty() || !exists(folderPath)) + return false; + + if (!isNameValid(newName)) + return false; + + std::string parentPath = getParentPath(folderPath); + std::string newFolderPath = parentPath.empty() ? newName : parentPath + "/" + newName; + + if (newFolderPath != folderPath && exists(newFolderPath)) + return false; + + // If the path doesn't change, just update the display name + if (newFolderPath == folderPath) { + m_pathToName[folderPath] = newName; + return true; + } + + // TODO: This gets complex if you need to update all child paths + // For now, just update the display name + m_pathToName[folderPath] = newName; + return true; + } + + std::vector FolderManager::getAllPaths() const + { + std::vector paths; + paths.reserve(m_pathToName.size()); + for (const auto& [path, name] : m_pathToName) { + paths.push_back(path); + } + std::sort(paths.begin(), paths.end()); + return paths; + } + + size_t FolderManager::getChildCount(const std::string& path) const + { + if (auto it = m_children.find(path); it != m_children.end()) { + return it->second.size(); + } + return 0; + } + + void FolderManager::clear() + { + m_pathToName.clear(); + m_children.clear(); + + m_pathToName[""] = "Assets"; + m_children[""] = {}; + } + + void FolderManager::addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths) + { + if (fullPath.empty()) return; + + std::string currentPath = ""; + std::stringstream ss(fullPath); + std::string part; + + while (std::getline(ss, part, '/')) { + if (!part.empty()) { + currentPath = currentPath.empty() ? part : currentPath + "/" + part; + allPaths.insert(currentPath); + } + } + } + + void FolderManager::buildMapsFromPaths(const std::unordered_set& allPaths) + { + // Build path->name mapping + for (const std::string& path : allPaths) { + m_pathToName[path] = extractNameFromPath(path); + m_children[path] = {}; // Initialize empty children vector + } + + // Build parent->children relationships + for (const std::string& path : allPaths) { + std::string parentPath = getParentPath(path); + m_children[parentPath].push_back(path); + } + + // Sort all children vectors + for (auto& [parent, children] : m_children) + std::sort(children.begin(), children.end()); + } + + std::string FolderManager::extractNameFromPath(const std::string& path) const + { + if (path.empty()) + return "Assets"; + + size_t lastSlash = path.find_last_of('/'); + return (lastSlash == std::string::npos) ? path : path.substr(lastSlash + 1); + } + + std::string FolderManager::getParentPath(const std::string& path) const + { + if (path.empty()) + return ""; // Root has no parent + + size_t lastSlash = path.find_last_of('/'); + return (lastSlash == std::string::npos) ? "" : path.substr(0, lastSlash); + } +} diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.hpp b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp new file mode 100644 index 000000000..2c61b1505 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp @@ -0,0 +1,64 @@ +//// FolderManager.hpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 28/07/2025 +// Description: Header file for the folder manager +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once +#include +#include +#include +#include +#include +#include + +#include "assets/AssetCatalog.hpp" + +namespace nexo::editor { + + class FolderManager { + private: + std::unordered_map m_pathToName; // path -> display name + std::unordered_map> m_children; // path -> direct children paths + + public: + FolderManager(); + + void buildFromAssets(); + + std::vector> getChildren(const std::string& path) const; + + std::string getName(const std::string& path) const; + + bool exists(const std::string& path) const; + + bool createFolder(const std::string& parentPath, const std::string& folderName); + + bool deleteFolder(const std::string& folderPath); + + bool renameFolder(const std::string& folderPath, const std::string& newName); + + std::vector getAllPaths() const; + + size_t getChildCount(const std::string& path) const; + + private: + void clear(); + + void addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths); + + void buildMapsFromPaths(const std::unordered_set& allPaths); + + std::string extractNameFromPath(const std::string& path) const; + + std::string getParentPath(const std::string& path) const; + }; + +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index d7361f0a9..ccd31f892 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -87,7 +87,10 @@ namespace nexo::editor { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; if (path == m_currentFolder) flags |= ImGuiTreeNodeFlags_Selected; - if (!m_folderChildren.contains(path) || m_folderChildren.at(path).empty()) + + // Replace the old children check with: + auto children = m_folderManager.getChildren(path); + if (children.empty()) flags |= ImGuiTreeNodeFlags_Leaf; ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(230, 180, 80, 255)); @@ -108,11 +111,9 @@ namespace nexo::editor { if (!opened) return; - if (const auto it = m_folderChildren.find(path); it != m_folderChildren.end()) { - for (const auto& childPath : it->second) { - const std::string childName = std::filesystem::path(childPath).filename().string(); - drawFolderTreeItem(childName, childPath); - } + // Replace the old iteration with: + for (const auto& [childPath, childName] : children) { + drawFolderTreeItem(childName, childPath); } ImGui::TreePop(); } @@ -122,7 +123,6 @@ namespace nexo::editor { drawSearchBar(m_searchBuffer); drawFavorites(m_selectedType); - // folder structure ImGuiTreeNodeFlags headerFlags = ImGuiTreeNodeFlags_OpenOnDoubleClick; if (m_currentFolder.empty()) @@ -141,10 +141,10 @@ namespace nexo::editor { if (!assetsOpen) return; - for (const auto& [path, name] : m_folderStructure) { - if (isTopLevelFolder(path)) { - drawFolderTreeItem(name, path); - } + // Replace the old iteration with: + auto rootChildren = m_folderManager.getChildren(""); + for (const auto& [path, name] : rootChildren) { + drawFolderTreeItem(name, path); } ImGui::TreePop(); } diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 77be6864a..8d6471563 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -58,6 +58,5 @@ namespace nexo::editor { m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); buildFolderStructure(); - updateFolderChildren(); } } diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp index 980fff4cb..25132dfc7 100644 --- a/editor/src/DocumentWindows/AssetManager/Update.cpp +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -17,77 +17,9 @@ namespace nexo::editor { - void AssetManagerWindow::updateFolderChildren() - { - m_folderChildren.clear(); - - for (const auto& [path, name] : m_folderStructure) { - if (!path.empty()) { // Skip root entry - m_folderChildren[path] = {}; - } - } - - // Build parent-child relationships - for (const auto& [path, name] : m_folderStructure) { - if (path.empty()) continue; // Skip root - - const size_t lastSlash = path.find_last_of('/'); - - if (lastSlash == std::string::npos) { - // Top-level folder - child of root - m_folderChildren[""].push_back(path); - } else { - // Nested folder - child of parent - const std::string parentPath = path.substr(0, lastSlash); - m_folderChildren[parentPath].push_back(path); - } - } - - // Sort children for consistent display order - for (auto& [parent, children] : m_folderChildren) { - std::ranges::sort(children); - } - } - void AssetManagerWindow::buildFolderStructure() { - m_folderStructure.clear(); - // Root entry - m_folderStructure.emplace_back("", "Assets"); - m_folderChildren.clear(); // Clear the folder children map - - // First pass: build the folder structure - std::set> uniqueFolderPaths; - - std::unordered_set seen{""}; - - for (const auto assets = assets::AssetCatalog::getInstance().getAssets(); auto& ref : assets) { - if (const auto assetData = ref.lock()) { - // normalized path: e.g. "Random/Sub" - std::filesystem::path p{ assetData->getMetadata().location.getPath() }; - std::filesystem::path curr; - for (auto const& part : p) { - // skip empty or “_internal” style parts - if (auto s = part.string(); s.empty() || s.front() == '_') - continue; - curr /= part; - if (auto folderPath = curr.string(); seen.emplace(folderPath).second) { - m_folderStructure.emplace_back( - folderPath, - curr.filename().string() - ); - } - } - } - } - - std::sort( - m_folderStructure.begin() + 1, - m_folderStructure.end(), - [](auto const& a, auto const& b){ - return a.first < b.first; - } - ); + m_folderManager.buildFromAssets(); } void AssetManagerWindow::update() From b78298400b4164e4d5d04a9d810376432235ce33 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Jul 2025 12:19:56 +0200 Subject: [PATCH 29/33] fix(front-asset-manager): fix return of splitPath for MSVC --- common/Path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Path.cpp b/common/Path.cpp index acaeb63fd..357b1ad7f 100644 --- a/common/Path.cpp +++ b/common/Path.cpp @@ -64,6 +64,6 @@ namespace nexo { | std::views::transform([](auto const& e){ return e.string(); }); - return std::vector(segments.begin(), segments.end()); + return {segments.begin(), segments.end()}; } } From 5186c9013ab798d75bd24229ac44ece92a360b56 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Jul 2025 12:39:09 +0200 Subject: [PATCH 30/33] fix(front-asset-manager): fix splitPath func for msvc --- common/Path.cpp | 16 ++++++++-------- editor/CMakeLists.txt | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/common/Path.cpp b/common/Path.cpp index 357b1ad7f..e2cac85db 100644 --- a/common/Path.cpp +++ b/common/Path.cpp @@ -57,13 +57,13 @@ namespace nexo { std::vector splitPath(const std::filesystem::path& path) { - auto segments = path - | std::views::filter([&](auto const& e){ - return e != path.root_name() && e != path.root_directory(); - }) - | std::views::transform([](auto const& e){ - return e.string(); - }); - return {segments.begin(), segments.end()}; + std::vector result; + + for (const auto& part : path) { + if (part != path.root_name() && part != path.root_directory()) + result.push_back(part.string()); + } + + return result; } } diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index bfd9e9262..921a6c546 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -60,6 +60,7 @@ set(SRCS editor/src/DocumentWindows/AssetManager/Selection.cpp editor/src/DocumentWindows/AssetManager/AssetGrid.cpp editor/src/DocumentWindows/AssetManager/Utils.cpp + editor/src/DocumentWindows/AssetManager/FolderManager.cpp editor/src/DocumentWindows/ConsoleWindow/Init.cpp editor/src/DocumentWindows/ConsoleWindow/Log.cpp editor/src/DocumentWindows/ConsoleWindow/Show.cpp From 336c0b8d5c67a147feb676a8cd381b16df4a1042 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Jul 2025 16:32:35 +0200 Subject: [PATCH 31/33] refactor(front-asset-manager): make drawFolder func more readable --- .../AssetManager/AssetGrid.cpp | 196 +++++++++--------- .../AssetManager/AssetManagerWindow.hpp | 1 + .../AssetManager/FolderTree.cpp | 8 - 3 files changed, 99 insertions(+), 106 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp index d122da66d..30d96abf4 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -15,6 +15,7 @@ #include "AssetManagerWindow.hpp" #include "assets/Asset.hpp" #include "assets/AssetCatalog.hpp" +#include "assets/AssetRef.hpp" #include "context/ThumbnailCache.hpp" #include "ImNexo/Elements.hpp" @@ -183,70 +184,29 @@ namespace nexo::editor { ImGui::PopID(); } - void AssetManagerWindow::drawFolder( - const std::string& folderPath, - const std::string& folderName, - const ImVec2& itemPos, - const ImVec2& itemSize - ) { + void AssetManagerWindow::drawFolderIcon(const AssetLayoutParams& params) + { ImDrawList* drawList = ImGui::GetWindowDrawList(); - const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - ImGui::PushID(("folder_" + folderPath).c_str()); - - ImGui::SetCursorScreenPos(itemPos); - - const bool clicked = ImGui::InvisibleButton("##folder", itemSize); - const bool isHovered = ImGui::IsItemHovered(); - - if (isHovered) { - m_hoveredFolder = folderPath; - } else if (m_hoveredFolder == folderPath) { - m_hoveredFolder.clear(); - } - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) - { - const auto* data = static_cast(payload->Data); - assets::AssetCatalog::getInstance().moveAsset(data->id, folderPath); - } - ImGui::EndDragDropTarget(); - } - - // Background - use hover color when hovered - const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); - - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); - - // Calculate padding for the icon constexpr float padding = 10.0f; - // Calculate available area dimensions - const float availWidth = thumbnailEnd.x - itemPos.x - (padding * 2.0f); - const float availHeight = thumbnailEnd.y - itemPos.y - (padding * 2.0f); + const float availWidth = params.thumbnailEnd.x - params.itemPos.x - (padding * 2.0f); + const float availHeight = params.thumbnailEnd.y - params.itemPos.y - (padding * 2.0f); - // Maintain aspect ratio by using the smaller dimension const float displaySize = std::min(availWidth, availHeight); - // Calculate centered position const float xOffset = (availWidth - displaySize) * 0.5f + padding; const float yOffset = (availHeight - displaySize) * 0.5f + padding; - // Final image coordinates maintaining aspect ratio const ImVec2 imageStart( - itemPos.x + xOffset, - itemPos.y + yOffset + params.itemPos.x + xOffset, + params.itemPos.y + yOffset ); const ImVec2 imageEnd( imageStart.x + displaySize, imageStart.y + displaySize ); - // Draw folder PNG icon - if (const ImTextureID folderIconTexture = getIconTexture(m_folderIcon)) { drawList->AddImage( folderIconTexture, @@ -257,44 +217,75 @@ namespace nexo::editor { IM_COL32(255, 255, 255, 255) // White tint for default color ); } + } - // Calculate text size to ensure it fits - const ImVec2 textSize = ImGui::CalcTextSize(folderName.c_str()); + static void drawFolderTitle( + const std::string& folderName, + const LayoutSettings& layout, + const AssetLayoutParams& params, + bool isHovered + ) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); - // Draw title background - const ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); - const float titleAreaHeight = itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const ImU32 titleBgColor = isHovered ? layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); + const float titleAreaHeight = params.itemSize.y * (1.0f - layout.size.THUMBNAIL_HEIGHT_RATIO); drawList->AddRectFilled( - ImVec2(itemPos.x, thumbnailEnd.y), - ImVec2(itemEnd.x, itemEnd.y), + ImVec2(params.itemPos.x, params.thumbnailEnd.y), + ImVec2(params.itemEnd.x, params.itemEnd.y), titleBgColor ); - // Position text with proper vertical alignment - const float textY = thumbnailEnd.y + ((titleAreaHeight - textSize.y) * 0.5f); - const float textX = itemPos.x + (itemSize.x - textSize.x) * 0.5f; + const ImVec2 textSize = ImGui::CalcTextSize(folderName.c_str()); + const float textY = params.thumbnailEnd.y + ((titleAreaHeight - textSize.y) * 0.5f); + const float textX = params.itemPos.x + (params.itemSize.x - textSize.x) * 0.5f; drawList->AddText( ImVec2(textX, textY), - m_layout.color.titleText, + layout.color.titleText, folderName.c_str() ); + } + + void AssetManagerWindow::drawFolder( + const std::string& folderPath, + const std::string& folderName, + const ImVec2& itemPos, + const ImVec2& itemSize + ) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const AssetLayoutParams folderLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; + + ImGui::PushID(("folder_" + folderPath).c_str()); + ImGui::SetCursorScreenPos(itemPos); + + const bool clicked = ImGui::InvisibleButton("##folder", itemSize); + const bool isHovered = ImGui::IsItemHovered(); + + if (isHovered) + m_hoveredFolder = folderPath; + else if (m_hoveredFolder == folderPath) + m_hoveredFolder.clear(); + + handleAssetDrop(folderPath); + + const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); + drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); + drawFolderIcon(folderLayoutParams); + drawFolderTitle(folderName, m_layout, folderLayoutParams, isHovered); if (clicked) - m_currentFolder = folderPath; // Navigate into this folder + m_currentFolder = folderPath; ImGui::PopID(); } - void AssetManagerWindow::drawAssetsGrid() + static const std::vector getFilteredAsset(const std::string ¤tFolder, const assets::AssetType selectedType) { - calculateGridLayout(m_layout); - - const ImVec2 startPos = ImGui::GetCursorScreenPos(); - auto subfolders = m_folderManager.getChildren(m_currentFolder); - std::vector filtered; + for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { const auto d = ref.lock(); if (!d) @@ -302,48 +293,57 @@ namespace nexo::editor { const auto& folder = d->getMetadata().location.getPath(); if (folder == "_internal") continue; - if (m_selectedType != assets::AssetType::UNKNOWN && d->getType() != m_selectedType) + if (selectedType != assets::AssetType::UNKNOWN && d->getType() != selectedType) continue; - if (folder == m_currentFolder) + if (folder == currentFolder) filtered.push_back(ref); } + return filtered; + } + + void AssetManagerWindow::drawAssetsGrid() + { + calculateGridLayout(m_layout); + + const ImVec2 startPos = ImGui::GetCursorScreenPos(); + auto subfolders = m_folderManager.getChildren(m_currentFolder); + const std::vector filtered = getFilteredAsset(m_currentFolder, m_selectedType); + const size_t totalItems = subfolders.size() + filtered.size(); + const int columnCount = m_layout.size.columnCount; + const auto rows = static_cast((totalItems + columnCount - 1) / columnCount); + ImGuiListClipper clipper; - const auto rows = static_cast((totalItems + m_layout.size.columnCount - 1) / m_layout.size.columnCount); clipper.Begin(rows, m_layout.size.itemStep.y); while (clipper.Step()) { - for (int line = clipper.DisplayStart; line < clipper.DisplayEnd; ++line) { - const unsigned int startIdx = line * m_layout.size.columnCount; - const unsigned int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(totalItems)); - - for (unsigned int i = startIdx; i < endIdx; ++i) { - unsigned int col = i % m_layout.size.columnCount; - unsigned int row = i / m_layout.size.columnCount; - ImVec2 itemPos{ - startPos.x + static_cast(col) * m_layout.size.itemStep.x, - startPos.y + static_cast(row) * m_layout.size.itemStep.y - }; - - if (i < static_cast(subfolders.size())) { - // draw folder thumbnail - drawFolder( - subfolders[i].first, - subfolders[i].second, - itemPos, - m_layout.size.itemSize - ); - } else { - // draw asset thumbnail - const auto assetIdx = i - static_cast(subfolders.size()); - drawAsset( - filtered[assetIdx], - assetIdx, - itemPos, - m_layout.size.itemSize - ); - } + unsigned int visibleStart = clipper.DisplayStart * columnCount; + unsigned int visibleEnd = std::min(clipper.DisplayEnd * columnCount, static_cast(totalItems)); + + for (unsigned int i = visibleStart; i < visibleEnd; ++i) { + unsigned int col = i % columnCount; + unsigned int row = i / columnCount; + ImVec2 itemPos{ + startPos.x + static_cast(col) * m_layout.size.itemStep.x, + startPos.y + static_cast(row) * m_layout.size.itemStep.y + }; + + if (i < subfolders.size()) { + drawFolder( + subfolders[i].first, + subfolders[i].second, + itemPos, + m_layout.size.itemSize + ); + } else { + const auto assetIdx = i - static_cast(subfolders.size()); + drawAsset( + filtered[assetIdx], + assetIdx, + itemPos, + m_layout.size.itemSize + ); } } } diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index cab7bc8e1..e2e5a5340 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -145,6 +145,7 @@ namespace nexo::editor { void newFolderMenu(); bool handleNewFolderCreation(); + void drawFolderIcon(const AssetLayoutParams& params); void drawFolder( const std::string& folderPath, const std::string& folderName, diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index ccd31f892..60da3433b 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -23,11 +23,6 @@ namespace nexo::editor { - static bool isTopLevelFolder(const std::string &path) - { - return path.empty() && path.find('/') == std::string::npos; - } - static void drawSearchBar(std::string &searchBuffer) { ImGui::PushItemWidth(-1); @@ -88,7 +83,6 @@ namespace nexo::editor { if (path == m_currentFolder) flags |= ImGuiTreeNodeFlags_Selected; - // Replace the old children check with: auto children = m_folderManager.getChildren(path); if (children.empty()) flags |= ImGuiTreeNodeFlags_Leaf; @@ -111,7 +105,6 @@ namespace nexo::editor { if (!opened) return; - // Replace the old iteration with: for (const auto& [childPath, childName] : children) { drawFolderTreeItem(childName, childPath); } @@ -141,7 +134,6 @@ namespace nexo::editor { if (!assetsOpen) return; - // Replace the old iteration with: auto rootChildren = m_folderManager.getChildren(""); for (const auto& [path, name] : rootChildren) { drawFolderTreeItem(name, path); From 9db44ac2afe0b2d4a3c2a2cda070ad6f779a108b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Jul 2025 10:18:13 +0200 Subject: [PATCH 32/33] fix(front-asset-manager): fix sonar + coderabbit issues --- .../AssetManager/AssetGrid.cpp | 34 ++++++++++++------- .../AssetManager/AssetManagerWindow.hpp | 13 ++++--- .../DocumentWindows/AssetManager/FileDrop.cpp | 2 +- .../AssetManager/FolderCreation.cpp | 9 ++--- .../AssetManager/FolderManager.cpp | 15 ++++---- .../AssetManager/FolderManager.hpp | 2 +- .../AssetManager/FolderTree.cpp | 5 ++- .../src/DocumentWindows/AssetManager/Show.cpp | 2 +- editor/src/ImNexo/Components.hpp | 2 +- engine/src/ecs/ComponentArray.hpp | 2 +- engine/src/systems/RenderBillboardSystem.cpp | 2 +- engine/src/systems/RenderCommandSystem.cpp | 2 +- 12 files changed, 52 insertions(+), 38 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp index 30d96abf4..f49b36ca8 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -27,6 +27,12 @@ namespace nexo::editor { case assets::AssetType::TEXTURE: return IM_COL32(60, 40, 40, 255); case assets::AssetType::MODEL: return IM_COL32(40, 60, 40, 255); case assets::AssetType::MATERIAL: return IM_COL32(40, 40, 60, 255); + case assets::AssetType::UNKNOWN: + case assets::AssetType::FONT: + case assets::AssetType::MUSIC: + case assets::AssetType::SCRIPT: + case assets::AssetType::SHADER: + case assets::AssetType::SOUND: default: return IM_COL32(0, 0, 0, 0); } } @@ -101,11 +107,11 @@ namespace nexo::editor { ) const { ImDrawList* drawList = ImGui::GetWindowDrawList(); - const float titleAreaHeight = params.itemSize.y * (1.0f - m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const float titleAreaHeight = params.itemSize.y * (1.0f - GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const float titlePadding = std::max(2.0f, titleAreaHeight * 0.1f); const float availableTextWidth = params.itemSize.x - (titlePadding * 2); - ImU32 titleBgColor = (isHovered) ? + ImU32 titleBgColor = isHovered ? m_layout.color.titleBgHovered : getAssetTypeOverlayColor(assetData->getType()); @@ -156,7 +162,7 @@ namespace nexo::editor { const bool isHovered = ImGui::IsItemHovered(); const bool isSelected = m_selectedAssets.contains(index); const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const AssetLayoutParams assetLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; drawAssetThumbnail(asset, m_layout, assetLayoutParams, isSelected); @@ -170,8 +176,12 @@ namespace nexo::editor { AssetDragDropPayload payload; payload.type = assetData->getType(); payload.id = assetData->getID(); - payload.path = assetData->getMetadata().location.getFullLocation(); - payload.name = assetData->getMetadata().location.getName().data(); + const auto& fullPath = assetData->getMetadata().location.getFullLocation(); + const auto& name = assetData->getMetadata().location.getName().data(); + std::strncpy(payload.path, fullPath.c_str(), sizeof(payload.path) - 1); + payload.path[sizeof(payload.path) - 1] = '\0'; + std::strncpy(payload.name, name.c_str(), sizeof(payload.name) - 1); + payload.name[sizeof(payload.name) - 1] = '\0'; ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); ImTextureID textureID = ThumbnailCache::getInstance().getThumbnail(asset); @@ -184,7 +194,7 @@ namespace nexo::editor { ImGui::PopID(); } - void AssetManagerWindow::drawFolderIcon(const AssetLayoutParams& params) + void AssetManagerWindow::drawFolderIcon(const AssetLayoutParams& params) const { ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -228,7 +238,7 @@ namespace nexo::editor { ImDrawList* drawList = ImGui::GetWindowDrawList(); const ImU32 titleBgColor = isHovered ? layout.color.titleBgHovered : IM_COL32(0, 0, 0, 0); - const float titleAreaHeight = params.itemSize.y * (1.0f - layout.size.THUMBNAIL_HEIGHT_RATIO); + const float titleAreaHeight = params.itemSize.y * (1.0f - GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); drawList->AddRectFilled( ImVec2(params.itemPos.x, params.thumbnailEnd.y), @@ -255,7 +265,7 @@ namespace nexo::editor { ) { ImDrawList* drawList = ImGui::GetWindowDrawList(); const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); - const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.THUMBNAIL_HEIGHT_RATIO); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const AssetLayoutParams folderLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; ImGui::PushID(("folder_" + folderPath).c_str()); @@ -272,7 +282,7 @@ namespace nexo::editor { handleAssetDrop(folderPath); const ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); - drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.CORNER_RADIUS); + drawList->AddRectFilled(itemPos, itemEnd, bgColor, GridLayoutSizes::CORNER_RADIUS); drawFolderIcon(folderLayoutParams); drawFolderTitle(folderName, m_layout, folderLayoutParams, isHovered); @@ -282,16 +292,16 @@ namespace nexo::editor { ImGui::PopID(); } - static const std::vector getFilteredAsset(const std::string ¤tFolder, const assets::AssetType selectedType) + static std::vector getFilteredAsset(std::string_view currentFolder, const assets::AssetType selectedType) { std::vector filtered; - for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { + for (const auto& ref : assets::AssetCatalog::getInstance().getAssets()) { const auto d = ref.lock(); if (!d) continue; const auto& folder = d->getMetadata().location.getPath(); - if (folder == "_internal") + if (folder == INTERNAL_FOLDER_PREFIX) continue; if (selectedType != assets::AssetType::UNKNOWN && d->getType() != selectedType) continue; diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index e2e5a5340..971766c85 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -25,13 +25,16 @@ namespace nexo::editor { + static constexpr std::string INTERNAL_FOLDER_PREFIX = "_internal"; + static constexpr float ERROR_DISPLAY_TIMEOUT = 3.0f; + struct FolderCreationState { bool isCreatingFolder = false; std::string folderName = "New Folder"; std::string parentPath; bool showError = false; std::string errorMessage; - float errorTimer = 3.0f; + float errorTimer = ERROR_DISPLAY_TIMEOUT; void reset() { @@ -145,7 +148,7 @@ namespace nexo::editor { void newFolderMenu(); bool handleNewFolderCreation(); - void drawFolderIcon(const AssetLayoutParams& params); + void drawFolderIcon(const AssetLayoutParams& params) const; void drawFolder( const std::string& folderPath, const std::string& folderName, @@ -156,7 +159,7 @@ namespace nexo::editor { std::vector m_pendingDroppedFiles; void handleDroppedFiles(); - void handleAssetDrop(const std::string &path); + void handleAssetDrop(const std::string &path) const; assets::AssetLocation getAssetLocation(const std::filesystem::path &path) const; void importDroppedFile(const std::string& filePath) const; @@ -172,7 +175,7 @@ namespace nexo::editor { { assets::AssetType type; ///< Type of the asset assets::AssetID id; ///< ID of the asset - std::string path; ///< Path to the asset - std::string name; ///< Display name of the asset + char path[256]; ///< Path to the asset + char name[64]; ///< Display name of the asset }; } diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 4368fb40f..408dd4cbd 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -22,7 +22,7 @@ namespace nexo::editor { - void AssetManagerWindow::handleAssetDrop(const std::string &path) + void AssetManagerWindow::handleAssetDrop(const std::string &path) const { if (ImGui::BeginDragDropTarget()) { diff --git a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp index f24d80797..754dd32dc 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderCreation.cpp @@ -25,7 +25,6 @@ namespace nexo::editor { return false; } - // Replace the old logic with: if (!m_folderManager.createFolder(m_folderCreationState.parentPath, m_folderCreationState.folderName)) { m_folderCreationState.showError = true; m_folderCreationState.errorMessage = "Failed to create folder (may already exist)"; @@ -38,8 +37,10 @@ namespace nexo::editor { void AssetManagerWindow::newFolderMenu() { ImGui::Text("Enter name for the new folder:"); - ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.size() + 1); - + constexpr size_t MAX_FOLDER_NAME_LENGTH = 256; + m_folderCreationState.folderName.resize(MAX_FOLDER_NAME_LENGTH); + ImGui::InputText("##FolderName", m_folderCreationState.folderName.data(), m_folderCreationState.folderName.capacity()); + m_folderCreationState.folderName.resize(strlen(m_folderCreationState.folderName.c_str())); ImGui::Separator(); if (ImNexo::Button("Create") && handleNewFolderCreation()) { @@ -60,7 +61,7 @@ namespace nexo::editor { if (m_folderCreationState.errorTimer <= 0.0f) { m_folderCreationState.showError = false; - m_folderCreationState.errorTimer = 3.0f; // Reset timer + m_folderCreationState.errorTimer = ERROR_DISPLAY_TIMEOUT; // Reset timer } else m_folderCreationState.errorTimer -= ImGui::GetIO().DeltaTime; } diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.cpp b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp index 748fa5f47..796980284 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderManager.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp @@ -16,7 +16,7 @@ namespace nexo::editor { - static bool isNameValid(const std::string &folderName) + static bool isNameValid(std::string_view folderName) { return !(folderName.empty() || folderName.front() == '_' || folderName.find('/') != std::string::npos); } @@ -89,7 +89,7 @@ namespace nexo::editor { m_children[newFolderPath] = {}; m_children[parentPath].push_back(newFolderPath); - std::sort(m_children[parentPath].begin(), m_children[parentPath].end()); + std::ranges::sort(m_children[parentPath]); return true; } @@ -111,10 +111,7 @@ namespace nexo::editor { std::string parentPath = getParentPath(folderPath); if (auto parentIt = m_children.find(parentPath); parentIt != m_children.end()) { auto& parentChildren = parentIt->second; - parentChildren.erase( - std::remove(parentChildren.begin(), parentChildren.end(), folderPath), - parentChildren.end() - ); + std::erase(parentChildren, folderPath); } m_pathToName.erase(folderPath); @@ -155,7 +152,7 @@ namespace nexo::editor { for (const auto& [path, name] : m_pathToName) { paths.push_back(path); } - std::sort(paths.begin(), paths.end()); + std::ranges::sort(paths); return paths; } @@ -176,7 +173,7 @@ namespace nexo::editor { m_children[""] = {}; } - void FolderManager::addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths) + void FolderManager::addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths) const { if (fullPath.empty()) return; @@ -208,7 +205,7 @@ namespace nexo::editor { // Sort all children vectors for (auto& [parent, children] : m_children) - std::sort(children.begin(), children.end()); + std::ranges::sort(children); } std::string FolderManager::extractNameFromPath(const std::string& path) const diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.hpp b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp index 2c61b1505..b76a11d44 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderManager.hpp +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp @@ -52,7 +52,7 @@ namespace nexo::editor { private: void clear(); - void addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths); + void addPathAndParents(const std::string& fullPath, std::unordered_set& allPaths) const; void buildMapsFromPaths(const std::unordered_set& allPaths); diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index 60da3433b..e8523297e 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -25,8 +25,11 @@ namespace nexo::editor { static void drawSearchBar(std::string &searchBuffer) { + constexpr size_t MAX_SEARCH_LENGTH = 256; + searchBuffer.resize(MAX_SEARCH_LENGTH); ImGui::PushItemWidth(-1); - ImGui::InputTextWithHint("##search", "Search...", searchBuffer.data(), searchBuffer.size() + 1); + ImGui::InputTextWithHint("##search", "Search...", searchBuffer.data(), searchBuffer.capacity()); + searchBuffer.resize(strlen(searchBuffer.c_str())); ImGui::PopItemWidth(); ImGui::Separator(); } diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 825f3e68d..c36108a65 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -67,7 +67,7 @@ namespace nexo::editor { std::string path = m_currentFolder; std::vector crumbs = splitPath(m_currentFolder); std::string fullPath; - for (auto &crumb : crumbs) + for (const auto &crumb : crumbs) { fullPath += (fullPath.empty() ? "" : "/") + crumb; ImGui::SameLine(); ImGui::Text(" > "); ImGui::SameLine(); diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 73b468214..0447c4a59 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -111,7 +111,7 @@ namespace ImNexo { */ template bool RowEntityDropdown( - const std::string label, + const std::string& label, nexo::ecs::Entity& targetEntity, const std::vector& entities, GetNameFunc&& getNameFunc diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 29124185b..dc3e81f45 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -256,7 +256,7 @@ namespace nexo::ecs { std::memcpy(&m_componentArray[newIndex], componentData, sizeof(T)); ++m_size; } else { - THROW_EXCEPTION(InternalError, "Component type is not trivially copyable for raw insertion"); + THROW_EXCEPTION(InternalError, "Component type is not trivially copyable, raw insertion is not supported"); } } diff --git a/engine/src/systems/RenderBillboardSystem.cpp b/engine/src/systems/RenderBillboardSystem.cpp index ff2292a0d..a58de3f41 100644 --- a/engine/src/systems/RenderBillboardSystem.cpp +++ b/engine/src/systems/RenderBillboardSystem.cpp @@ -211,7 +211,7 @@ namespace nexo::system { const auto &transform = transformComponentArray->get(entitySpan[i]); const auto &materialAsset = materialComponentArray->get(entitySpan[i]).material.lock(); const auto &billboard = billboardSpan[i]; - auto shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; + std::string shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; auto shader = renderer::ShaderLibrary::getInstance().get(shaderStr); auto cmd = createDrawCommand( entity, diff --git a/engine/src/systems/RenderCommandSystem.cpp b/engine/src/systems/RenderCommandSystem.cpp index a6812a62b..a24173b70 100644 --- a/engine/src/systems/RenderCommandSystem.cpp +++ b/engine/src/systems/RenderCommandSystem.cpp @@ -293,7 +293,7 @@ namespace nexo::system { continue; const auto &transform = transformSpan[i]; const auto &materialAsset = materialSpan[i].material.lock(); - auto shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; + std::string shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; const auto &mesh = meshSpan[i]; auto shader = renderer::ShaderLibrary::getInstance().get(shaderStr); if (!shader) From 3709b9d3af4035dbd9affbe29226350be79427d7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Jul 2025 10:26:33 +0200 Subject: [PATCH 33/33] fix(front-asset-manager): fix msvc compilation --- editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 971766c85..242f16cb6 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -25,7 +25,7 @@ namespace nexo::editor { - static constexpr std::string INTERNAL_FOLDER_PREFIX = "_internal"; + static constexpr std::string_view INTERNAL_FOLDER_PREFIX = "_internal"; static constexpr float ERROR_DISPLAY_TIMEOUT = 3.0f; struct FolderCreationState {