From ee70fbddf8038d0cae78ac3283fd2606aead57fb Mon Sep 17 00:00:00 2001 From: Jean Cardonne Date: Sun, 6 Jul 2025 13:54:02 +0200 Subject: [PATCH 01/33] fix: resolve AssetDragDropPayload corruption by using fixed-size char arrays --- .../AssetManager/AssetManagerWindow.hpp | 19 ++ .../src/DocumentWindows/AssetManager/Show.cpp | 25 ++ .../SceneTreeWindow/DragDrop.cpp | 287 ++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index e9cf23990..11ccf8eec 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -110,5 +110,24 @@ namespace nexo::editor { const ImVec2& itemPos, const ImVec2& itemSize ); + + // File drop handling + std::vector m_pendingDroppedFiles; + bool m_showDropIndicator = false; + + void handleDroppedFiles(); + void importDroppedFile(const std::string& filePath); + }; + + /** + * @brief Payload structure for drag and drop operations from asset manager. + * + * Contains information about the asset being dragged. + */ + struct AssetDragDropPayload + { + assets::AssetType type; ///< Type of the asset + char path[256]; ///< Path to the asset + char name[128]; ///< Display name of the asset }; } diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 68e81996b..b22ec09f5 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -19,6 +19,8 @@ #include "Path.hpp" #include "assets/Assets/Texture/Texture.hpp" #include "context/ThumbnailCache.hpp" +#include +#include "Logger.hpp" #include namespace nexo::editor { @@ -169,6 +171,29 @@ namespace nexo::editor { 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(); + + // Copy strings safely into fixed-size arrays + std::string fullLocation = assetData->getMetadata().location.getFullLocation(); + std::strncpy(payload.path, fullLocation.c_str(), sizeof(payload.path) - 1); + payload.path[sizeof(payload.path) - 1] = '\0'; + + std::string assetName = assetData->getMetadata().location.getName().c_str(); + std::strncpy(payload.name, assetName.c_str(), sizeof(payload.name) - 1); + payload.name[sizeof(payload.name) - 1] = '\0'; + + ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); + + // Show preview while dragging + ImGui::Text("Asset: %s", assetData->getMetadata().location.getName().c_str()); + + ImGui::EndDragDropSource(); + } + ImGui::PopID(); } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp new file mode 100644 index 000000000..b380c14e8 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -0,0 +1,287 @@ +//// DragDrop.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: Jean CARDONNE +// Date: 2025-06-30 +// Description: Implementation of drag and drop functionality for the scene tree +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "DocumentWindows/AssetManager/AssetManagerWindow.hpp" +#include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" +#include "components/Parent.hpp" +#include "components/Transform.hpp" +#include "components/Render3D.hpp" +#include "EntityFactory3D.hpp" +#include "assets/AssetCatalog.hpp" +#include "assets/Assets/Model/Model.hpp" +#include "assets/Assets/Texture/Texture.hpp" +#include +#include +#include + +namespace nexo::editor { + + void SceneTreeWindow::handleDragSource(const SceneObject& object) + { + // Only allow dragging of entities, lights, and cameras + if (object.type == SelectionType::SCENE || object.type == SelectionType::NONE) + return; + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + // Create payload data + SceneTreeDragDropPayload payload{ + object.data.entity, + object.data.sceneProperties.sceneId, + object.type, + object.uuid, + object.uiName + }; + + // Set the payload + ImGui::SetDragDropPayload("SCENE_TREE_NODE", &payload, sizeof(payload)); + + // Show preview text while dragging + ImGui::Text("Moving: %s", object.uiName.c_str()); + + ImGui::EndDragDropSource(); + } + } + + void SceneTreeWindow::handleDropTarget(const SceneObject& object) + { + if (ImGui::BeginDragDropTarget()) + { + // Handle drops from scene tree nodes + if (const ImGuiPayload* imguiPayload = ImGui::AcceptDragDropPayload("SCENE_TREE_NODE")) + { + IM_ASSERT(imguiPayload->DataSize == sizeof(SceneTreeDragDropPayload)); + const auto& payload = *static_cast(imguiPayload->Data); + + if (canAcceptDrop(object, payload)) + { + handleDrop(object, payload); + } + } + + // Handle drops from asset manager + if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); + const auto& payload = *static_cast(assetPayload->Data); + + // Handle different asset types + if (object.type == SelectionType::SCENE) + { + auto& app = Application::getInstance(); + auto& sceneManager = app.getSceneManager(); + + if (payload.type == assets::AssetType::MODEL) + { + // Import the model + assets::AssetImporter importer; + std::filesystem::path path{payload.path}; + assets::ImporterFileInput fileInput{path}; + std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; + auto modelRef = importer.importAsset(assets::AssetLocation(assetLocationStr), fileInput); + if (modelRef) + { + // Create entity with the model + ecs::Entity newEntity = EntityFactory3D::createModel( + modelRef, + {0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // scale + {0.0f, 0.0f, 0.0f} // rotation + ); + + // Add to the scene + auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); + scene.addEntity(newEntity); + + // Record action for undo/redo + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + else if (payload.type == assets::AssetType::TEXTURE) + { + // Get the texture from the asset catalog + auto& catalog = assets::AssetCatalog::getInstance(); + assets::AssetLocation location(payload.path); + auto textureRef = catalog.getAsset(location).as(); + + if (!textureRef) + { + // If not in catalog, try to import it + assets::AssetImporter importer; + std::filesystem::path path{payload.path}; + assets::ImporterFileInput fileInput{path}; + std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; + textureRef = importer.importAsset(assets::AssetLocation(assetLocationStr), fileInput); + } + + if (textureRef) + { + // Create material with the texture + components::Material material; + material.albedoTexture = textureRef; + material.albedoColor = glm::vec4(1.0f); // White to show texture colors + + // Create billboard entity + ecs::Entity newEntity = EntityFactory3D::createBillboard( + {0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // size + material + ); + + // Add to the scene + auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); + scene.addEntity(newEntity); + + // Record action for undo/redo + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + } + } + + ImGui::EndDragDropTarget(); + } + } + + bool SceneTreeWindow::canAcceptDrop(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) + { + // Can't drop on itself + if (dropTarget.type != SelectionType::SCENE && dropTarget.data.entity == payload.entity) + return false; + + // Can't drop a parent onto its child (prevent circular references) + if (dropTarget.type != SelectionType::SCENE) + { + ecs::Entity currentEntity = dropTarget.data.entity; + auto parentComp = Application::m_coordinator->tryGetComponent(currentEntity); + while (parentComp.has_value()) + { + if (parentComp->get().parent == payload.entity) + return false; + currentEntity = parentComp->get().parent; + parentComp = Application::m_coordinator->tryGetComponent(currentEntity); + } + } + + // Allow dropping entities onto scenes or other entities + return true; + } + + void SceneTreeWindow::handleDrop(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) + { + auto& app = Application::getInstance(); + auto& sceneManager = app.getSceneManager(); + auto& coordinator = *Application::m_coordinator; + + // Get the source scene + auto& sourceScene = sceneManager.getScene(payload.sourceSceneId); + + if (dropTarget.type == SelectionType::SCENE) + { + // Dropping onto a scene - move entity to that scene + if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) + { + // Remove from source scene + sourceScene.removeEntity(payload.entity); + + // Add to target scene + auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); + targetScene.addEntity(payload.entity); + + // Update scene tag + auto& sceneTag = coordinator.getComponent(payload.entity); + sceneTag.id = dropTarget.data.sceneProperties.sceneId; + + // Remove parent relationship if moving to different scene + auto parentComp = coordinator.tryGetComponent(payload.entity); + if (parentComp.has_value()) + { + // Update parent's children list + auto parentTransform = coordinator.tryGetComponent(parentComp->get().parent); + if (parentTransform.has_value()) + { + parentTransform->get().removeChild(payload.entity); + } + + coordinator.removeComponent(payload.entity); + } + + // Record action for undo/redo + // TODO: Create a specific action for moving entities between scenes + // For now, we just perform the operation without undo support for scene moves + } + } + else + { + // Dropping onto an entity - create parent-child relationship + ecs::Entity parentEntity = dropTarget.data.entity; + ecs::Entity childEntity = payload.entity; + + // Get old parent before modifications + ecs::Entity oldParent = ecs::INVALID_ENTITY; + auto oldParentComp = coordinator.tryGetComponent(childEntity); + if (oldParentComp.has_value()) + { + oldParent = oldParentComp->get().parent; + + // Update old parent's children list + auto oldParentTransform = coordinator.tryGetComponent(oldParent); + if (oldParentTransform.has_value()) + { + oldParentTransform->get().removeChild(childEntity); + } + } + + // Set new parent + if (!oldParentComp.has_value()) + { + coordinator.addComponent(childEntity, components::ParentComponent{parentEntity}); + } + else + { + oldParentComp->get().parent = parentEntity; + } + + // Update parent's children list + auto parentTransform = coordinator.tryGetComponent(parentEntity); + if (!parentTransform.has_value()) + { + coordinator.addComponent(parentEntity, components::TransformComponent{}); + parentTransform = coordinator.tryGetComponent(parentEntity); + } + if (parentTransform.has_value()) + { + parentTransform->get().addChild(childEntity); + } + + // If moving to different scene, update scene tag + if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) + { + sourceScene.removeEntity(childEntity); + auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); + targetScene.addEntity(childEntity); + + auto& sceneTag = coordinator.getComponent(childEntity); + sceneTag.id = dropTarget.data.sceneProperties.sceneId; + } + auto action = std::make_unique(childEntity, oldParent, parentEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + +} // namespace nexo::editor \ No newline at end of file From 61be7ead7d31ab61553800f4ae4537e98ff39370 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 8 Jul 2025 09:46:44 +0200 Subject: [PATCH 02/33] fix: toolbar margin wrong on high DPI --- editor/src/DocumentWindows/EditorScene/Toolbar.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index fa354def5..468e1d7a7 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -70,8 +70,9 @@ namespace nexo::editor { void EditorScene::initialToolbarSetup(const float buttonWidth) const { ImVec2 toolbarPos = m_windowPos; - toolbarPos.x += 10.0f; - toolbarPos.y += 20.0f; + ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); + toolbarPos.x += contentMin.x + 10.0f; + toolbarPos.y += contentMin.y + 20.0f; ImGui::SetCursorScreenPos(toolbarPos); From c6f0c69f7fb8983dbe8b64e66de8695480c6d833 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 04:11:24 +0200 Subject: [PATCH 03/33] fix(drag-drop): fix asset filtering --- .../src/DocumentWindows/AssetManager/Init.cpp | 8 ++++ .../src/DocumentWindows/AssetManager/Show.cpp | 39 +++++++++++++++++++ .../EditorScene/EditorScene.hpp | 10 ++--- .../src/DocumentWindows/EditorScene/Init.cpp | 3 -- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index b49bf81cc..4c31effd6 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -26,6 +26,12 @@ namespace nexo::editor { auto asset = std::make_unique(); catalog.registerAsset(assets::AssetLocation("my_package::My_Model@Random/"), std::move(asset)); + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); + assets::ImporterFileInput fileInput{path}; + auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@DefaultScene/"), fileInput); + } { assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); @@ -39,5 +45,7 @@ namespace nexo::editor { assets::ImporterFileInput fileInput{path}; m_folderIcon = importer.importAsset(assets::AssetLocation("icon_folder@_internal"), fileInput); } + // Register for file drop events + Application::getInstance().getEventManager()->registerListener(this); } } diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index b22ec09f5..4680cddca 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -438,6 +438,45 @@ namespace nexo::editor { ImGui::SameLine(); ImGui::BeginChild("RightPanel", ImVec2(0, 0), true); + // Handle file drops + if (ImGui::BeginDragDropTarget()) + { + m_showDropIndicator = true; + + // Accept external file drops (this is a placeholder - ImGui doesn't directly support OS file drops) + // The actual files come through the EventFileDrop event + + ImGui::EndDragDropTarget(); + } + else + { + m_showDropIndicator = false; + } + + // Draw drop indicator + if (m_showDropIndicator || !m_pendingDroppedFiles.empty()) + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 windowPos = ImGui::GetWindowPos(); + 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 char* dropText = "Drop files here to import"; + ImVec2 textSize = ImGui::CalcTextSize(dropText); + ImVec2 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); + } + // Show path breadcrumb if (m_currentFolder.empty()) { ImGui::Text("Assets"); diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index fa2449626..9f03303b7 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -157,11 +157,11 @@ namespace nexo::editor void setupShortcuts(); /** - * @brief Populates the scene with default entities. - * - * Creates standard light sources (ambient, directional, point, spot) - * and a simple ground plane in the scene. - */ + * @brief Populates the scene with default entities. + * + * Creates standard light sources (ambient, directional, point, spot) + * and a simple ground plane in the scene. + */ void loadDefaultEntities() const; /** diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index 2442e8e37..341bab2b5 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -110,8 +110,6 @@ namespace nexo::editor const auto light = LightFactory::createPointLight(position, colors[i % colors.size()], 0.01, 0.0010); scene.addEntity(light); } - - assets::AssetImporter importer; } void EditorScene::loadDefaultEntities() const @@ -275,7 +273,6 @@ namespace nexo::editor } } - void EditorScene::setupWindow() { m_contentSize = ImVec2(1280, 720); From 7f753bf8ddc421bd8d84f4c34fb245e709df528c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 04:50:23 +0200 Subject: [PATCH 04/33] feat(drag-drop): now possible to file drop onto a folder --- .../AssetManager/AssetManagerWindow.hpp | 5 +- .../DocumentWindows/AssetManager/FileDrop.cpp | 143 ++++++++++++++++++ .../src/DocumentWindows/AssetManager/Show.cpp | 6 + 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 editor/src/DocumentWindows/AssetManager/FileDrop.cpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 11ccf8eec..3ea831e26 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -30,6 +30,8 @@ namespace nexo::editor { void show() override; void update() override; + void handleEvent(event::EventFileDrop& event) override; + private: struct LayoutSettings { struct LayoutSizes { @@ -74,7 +76,8 @@ namespace nexo::editor { void handleSelection(int index, bool isSelected); assets::AssetType m_selectedType = assets::AssetType::UNKNOWN; - std::string m_currentFolder; + 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] = ""; diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp new file mode 100644 index 000000000..46e404760 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -0,0 +1,143 @@ +//// FileDrop.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: Jean CARDONNE +// Date: 2025-06-30 +// Description: Implementation of file drop handling for asset manager +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" +#include "assets/AssetImporter.hpp" +#include "assets/Assets/Model/Model.hpp" +#include "assets/Assets/Texture/Texture.hpp" +#include "Logger.hpp" +#include +#include + +namespace nexo::editor { + + void AssetManagerWindow::handleEvent(event::EventFileDrop& event) + { + // Queue dropped files for processing in the next frame + m_pendingDroppedFiles.insert(m_pendingDroppedFiles.end(), + event.files.begin(), + event.files.end()); + } + + void AssetManagerWindow::handleDroppedFiles() + { + if (m_pendingDroppedFiles.empty()) + return; + + // Process each dropped file + for (const auto& filePath : m_pendingDroppedFiles) + { + importDroppedFile(filePath); + } + + m_pendingDroppedFiles.clear(); + + // Rebuild folder structure to include new assets + m_folderStructure.clear(); + buildFolderStructure(); + } + + void AssetManagerWindow::importDroppedFile(const std::string& filePath) + { + std::filesystem::path path(filePath); + + if (!std::filesystem::exists(path)) + { + LOG(NEXO_WARN, "Dropped file does not exist: {}", filePath); + return; + } + + // Get file extension + std::string extension = path.extension().string(); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + + // Determine asset type based on extension + assets::AssetType assetType = assets::AssetType::UNKNOWN; + + // Image extensions + static const std::vector imageExtensions = { + ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic", ".pnm", ".ppm", ".pgm" + }; + + // Model extensions (common ones supported by Assimp) + static const std::vector modelExtensions = { + ".gltf", ".glb", ".fbx", ".obj", ".dae", ".3ds", ".stl", ".ply", ".blend", ".x3d", ".ifc" + }; + + if (std::find(imageExtensions.begin(), imageExtensions.end(), extension) != imageExtensions.end()) + { + assetType = assets::AssetType::TEXTURE; + } + else if (std::find(modelExtensions.begin(), modelExtensions.end(), extension) != modelExtensions.end()) + { + assetType = assets::AssetType::MODEL; + } + else + { + LOG(NEXO_WARN, "Unsupported file type: {}", extension); + return; + } + + // Generate asset location + std::string filename = path.filename().string(); + std::string assetName = path.stem().string(); + + // Create location based on current folder + // The path after @ should just be the folder path, not include the asset name + std::string assetPath = m_currentFolder.empty() ? "" : m_currentFolder + "/"; + assetPath += m_hoveredFolder.empty() ? "" : m_hoveredFolder + "/"; + assetPath += assetPath.empty() ? "/" : ""; + std::string locationString = assetName + "@" + assetPath; + + LOG(NEXO_DEV, "Creating asset location: {} (current folder: '{}')", locationString, m_currentFolder); + assets::AssetLocation location(locationString); + + // Import the asset + assets::AssetImporter importer; + assets::ImporterFileInput fileInput{path}; + + try + { + if (assetType == assets::AssetType::TEXTURE) + { + auto assetRef = importer.importAsset(location, fileInput); + if (assetRef) + { + LOG(NEXO_INFO, "Successfully imported texture: {}", filename); + } + else + { + LOG(NEXO_ERROR, "Failed to import texture: {}", filename); + } + } + else if (assetType == assets::AssetType::MODEL) + { + auto assetRef = importer.importAsset(location, fileInput); + if (assetRef) + { + LOG(NEXO_INFO, "Successfully imported model: {}", filename); + } + else + { + LOG(NEXO_ERROR, "Failed to import model: {}", filename); + } + } + } + catch (const std::exception& e) + { + LOG(NEXO_ERROR, "Exception while importing {}: {}", filename, e.what()); + } + } + +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 4680cddca..bac851cdb 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -224,6 +224,12 @@ namespace nexo::editor { bool clicked = ImGui::InvisibleButton("##folder", itemSize); bool isHovered = ImGui::IsItemHovered(); + if (isHovered) { + m_hoveredFolder = folderPath; + } else if (m_hoveredFolder == folderPath) { + m_hoveredFolder.clear(); + } + // Background - use hover color when hovered ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius); From 9493ebbd24e8b0ca97e10d76cf4a4c00e61d8e4c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 07:10:34 +0200 Subject: [PATCH 05/33] feat(drag-drop): now normalize the path in the constructor of asset location --- engine/src/assets/Asset.hpp | 2 ++ engine/src/assets/AssetLocation.cpp | 3 ++- engine/src/assets/AssetLocation.hpp | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index aaf96826f..10174a591 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -149,6 +149,7 @@ namespace nexo::assets { public: virtual ~IAsset() = default; + [[nodiscard]] virtual AssetMetadata& getMetadata() = 0; [[nodiscard]] virtual const AssetMetadata& getMetadata() const = 0; [[nodiscard]] virtual AssetType getType() const = 0; [[nodiscard]] virtual AssetID getID() const = 0; @@ -190,6 +191,7 @@ namespace nexo::assets { ~Asset() override = default; + [[nodiscard]] virtual AssetMetadata& getMetadata() { return m_metadata; }; [[nodiscard]] const AssetMetadata& getMetadata() const override { return m_metadata; } [[nodiscard]] AssetType getType() const override { return getMetadata().type; } [[nodiscard]] AssetID getID() const override { return getMetadata().id; } diff --git a/engine/src/assets/AssetLocation.cpp b/engine/src/assets/AssetLocation.cpp index dd47173c7..301341fe1 100644 --- a/engine/src/assets/AssetLocation.cpp +++ b/engine/src/assets/AssetLocation.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetLocation.hpp" +#include "Path.hpp" namespace nexo::assets { @@ -23,7 +24,7 @@ namespace nexo::assets { ) { _name = name; - _path = path; + _path = normalizePath(path); _packName = packName; } } // namespace nexo::assets diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index b33e5eb47..b5480590f 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -21,6 +21,7 @@ #include "AssetName.hpp" #include "AssetPackName.hpp" +#include "Path.hpp" namespace nexo::assets { @@ -122,8 +123,9 @@ namespace nexo::assets { if (_packName) fullLocation += _packName->data() + "::"; fullLocation += _name.data(); + fullLocation += "@"; if (!_path.empty()) - fullLocation += "@" + _path; + fullLocation += _path; return fullLocation; } @@ -152,6 +154,7 @@ namespace nexo::assets { std::string extractedPath; parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); + extractedPath = normalizePath(extractedPath); try { _name = AssetName(extractedAssetName); From 32a2b3e8e0b05dfba357d39202f718042ae1a518 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 07:11:38 +0200 Subject: [PATCH 06/33] feat(drag-drop): now possible to drag and drop an asset inside a subfolder from the asset manager itself --- .../AssetManager/AssetManagerWindow.hpp | 3 + .../DocumentWindows/AssetManager/FileDrop.cpp | 5 +- .../AssetManager/FolderTree.cpp | 57 ++++---- .../src/DocumentWindows/AssetManager/Init.cpp | 7 +- .../src/DocumentWindows/AssetManager/Show.cpp | 135 +++++++++--------- 5 files changed, 103 insertions(+), 104 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 3ea831e26..512f0033a 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -18,6 +18,8 @@ #include #include #include "utils/TransparentStringHash.hpp" +#include +#include "assets/Asset.hpp" namespace nexo::editor { @@ -130,6 +132,7 @@ namespace nexo::editor { struct AssetDragDropPayload { assets::AssetType type; ///< Type of the asset + assets::AssetID id; char path[256]; ///< Path to the asset char name[128]; ///< Display name of the asset }; diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 46e404760..97d4b68d1 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -95,9 +95,8 @@ namespace nexo::editor { // Create location based on current folder // The path after @ should just be the folder path, not include the asset name - std::string assetPath = m_currentFolder.empty() ? "" : m_currentFolder + "/"; - assetPath += m_hoveredFolder.empty() ? "" : m_hoveredFolder + "/"; - assetPath += assetPath.empty() ? "/" : ""; + std::string assetPath = m_currentFolder.empty() ? "" : m_currentFolder; + assetPath += m_hoveredFolder.empty() ? "" : "/" + m_hoveredFolder; std::string locationString = assetName + "@" + assetPath; LOG(NEXO_DEV, "Creating asset location: {} (current folder: '{}')", locationString, m_currentFolder); diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index edff607ff..5b5d38b70 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -155,51 +155,48 @@ 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{""}; + const auto assets = assets::AssetCatalog::getInstance().getAssets(); - for (const auto& asset : assets) { - if (auto assetData = asset.lock()) { - std::string fullPath = assetData->getMetadata().location.getPath(); - std::filesystem::path fsPath(fullPath); - - // Extract all parent directories from the path - while (fsPath.has_parent_path()) { - fsPath = fsPath.parent_path(); - if (!fsPath.empty()) { - uniqueFolderPaths.insert(fsPath.string()); + for (auto& ref : assets) { + if (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 + auto s = part.string(); + if (s.empty() || s.front() == '_') + continue; + curr /= part; + auto folderPath = curr.string(); + if (seen.emplace(folderPath).second) { + m_folderStructure.emplace_back( + folderPath, + curr.filename().string() + ); } } } } - // Add the unique folder paths to m_folderStructure - for (const auto& folderPath : uniqueFolderPaths) { - std::filesystem::path fsPath(folderPath); - std::string folderName = fsPath.filename().string(); - m_folderStructure.emplace_back(folderPath, folderName); - } - - // Second pass: build the parent-child map - for (const auto& [path, name] : m_folderStructure) { - if (path.empty()) continue; // Skip root - - std::filesystem::path fsPath(path); - std::string parentPath = fsPath.parent_path().string(); - - // If parent path is empty, set it to "" (root) - if (parentPath.empty()) { - parentPath = ""; + std::sort( + m_folderStructure.begin() + 1, + m_folderStructure.end(), + [](auto const& a, auto const& b){ + return a.first < b.first; } - - m_folderChildren[parentPath].push_back(path); - } + ); } + void AssetManagerWindow::drawFolderTree() { handleNewFolderCreation(); diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 4c31effd6..9df8c5ea0 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -24,19 +24,20 @@ namespace nexo::editor { { auto& catalog = assets::AssetCatalog::getInstance(); auto asset = std::make_unique(); - catalog.registerAsset(assets::AssetLocation("my_package::My_Model@Random/"), std::move(asset)); + 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"); assets::ImporterFileInput fileInput{path}; - auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@DefaultScene/"), fileInput); + auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@DefaultScene"), fileInput); } { assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); assets::ImporterFileInput fileInput{path}; - auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@Random/"), fileInput); + auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@Random"), fileInput); } { diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index bac851cdb..2664da2b9 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -176,6 +176,7 @@ namespace nexo::editor { { AssetDragDropPayload payload; payload.type = assetData->getType(); + payload.id = assetData->getID(); // Copy strings safely into fixed-size arrays std::string fullLocation = assetData->getMetadata().location.getFullLocation(); @@ -230,6 +231,23 @@ namespace nexo::editor { m_hoveredFolder.clear(); } + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + // Cast back to your payload struct + const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; + + // e.g. move the asset at data->path into this folder: + std::shared_ptr asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock(); + if (asset) { + assets::AssetMetadata &metadata = asset->getMetadata(); + metadata.location.setLocation(metadata.location.getFullLocation() + folderPath + "/"); + } + } + ImGui::EndDragDropTarget(); + } + // Background - use hover color when hovered ImU32 bgColor = isHovered ? m_layout.color.thumbnailBgHovered : IM_COL32(0, 0, 0, 0); drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius); @@ -306,100 +324,79 @@ namespace nexo::editor { void AssetManagerWindow::drawAssetsGrid() { ImVec2 startPos = ImGui::GetCursorScreenPos(); - std::vector> subfolders; - // First, collect all immediate subfolders of the current folder - for (const auto& [path, name] : m_folderStructure) { - // Skip the current folder itself - if (path == m_currentFolder) + // 1) Collect immediate subfolders of the current folder + std::vector> subfolders; + for (auto& [path,name] : m_folderStructure) { + if (path.empty() || path.front() == '_') continue; - if (path.find(m_currentFolder) == 0) { - // For root folder (empty current folder) - if (m_currentFolder.empty()) { - if (path.find('/') == std::string::npos) { - subfolders.emplace_back(path, name); - } - } - // For non-root folders - else { - // Check if it's a direct child (only one more / after current folder) - std::string pathAfterCurrent = path.substr(m_currentFolder.length()); - if (pathAfterCurrent[0] == '/') { - pathAfterCurrent = pathAfterCurrent.substr(1); - } - - if (pathAfterCurrent.find('/') == std::string::npos && !pathAfterCurrent.empty()) { - subfolders.emplace_back(path, pathAfterCurrent); - } + if (m_currentFolder.empty()) { + // root level = no slash in path + if (path.find('/') == std::string::npos) + subfolders.emplace_back(path, name); + } else { + std::string prefix = m_currentFolder + "/"; + // immediate child: starts with "curr/" but has no further '/' + if (path.rfind(prefix, 0) == 0 && + path.find('/', prefix.size()) == std::string::npos) + { + subfolders.emplace_back(path, path.substr(prefix.size())); } } } - const std::vector assets = assets::AssetCatalog::getInstance().getAssets(); - - // Filter assets by currently selected folder - std::vector filteredAssets; - for (const auto& asset : assets) { - if (auto assetData = asset.lock()) { - if (assetData->getMetadata().location.getPath() == "_internal") - continue; - - if (m_selectedType != assets::AssetType::UNKNOWN && assetData->getType() != m_selectedType) - continue; - - // Check if asset is in current folder exactly (not in subfolders) - std::string assetPath = assetData->getMetadata().location.getPath(); - std::string assetDir = assetPath.substr(0, assetPath.find_last_of('/')); - - // If there's no slash, asset is in root - if (assetPath.find('/') == std::string::npos) - assetDir = ""; - - if (assetDir == m_currentFolder) - filteredAssets.push_back(asset); + // 2) Collect assets exactly in the current folder + std::vector filtered; + for (auto& ref : assets::AssetCatalog::getInstance().getAssets()) { + if (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; + + // **here’s the fix**: just compare the whole normalized folder + if (folder == m_currentFolder) + filtered.push_back(ref); } } - size_t totalItems = subfolders.size() + filteredAssets.size(); - + // 3) Layout & draw both subfolders and assets + size_t totalItems = subfolders.size() + filtered.size(); ImGuiListClipper clipper; - int rowCount = (static_cast(totalItems) + m_layout.size.columnCount - 1) / m_layout.size.columnCount; - clipper.Begin(rowCount, m_layout.size.itemStep.y); + int rows = int((totalItems + m_layout.size.columnCount - 1) / m_layout.size.columnCount); + clipper.Begin(rows, m_layout.size.itemStep.y); while (clipper.Step()) { - for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) { - int startIdx = lineIdx * m_layout.size.columnCount; - int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(totalItems)); + for (int line = clipper.DisplayStart; line < clipper.DisplayEnd; ++line) { + int startIdx = line * m_layout.size.columnCount; + int endIdx = std::min(startIdx + m_layout.size.columnCount, (int)totalItems); for (int i = startIdx; i < endIdx; ++i) { - auto col = static_cast(i % m_layout.size.columnCount); - auto row = static_cast(i / m_layout.size.columnCount); + float col = float(i % m_layout.size.columnCount); + float row = float(i / m_layout.size.columnCount); ImVec2 itemPos{ startPos.x + col * m_layout.size.itemStep.x, startPos.y + row * m_layout.size.itemStep.y }; - // Draw folder if index is in subfolder range - if (i < static_cast(subfolders.size())) { + if (i < (int)subfolders.size()) { + // draw folder thumbnail drawFolder( subfolders[i].first, subfolders[i].second, itemPos, m_layout.size.itemSize ); - } - // Otherwise draw asset - else { - int assetIdx = i - static_cast(subfolders.size()); - if (assetIdx < static_cast(filteredAssets.size())) { - drawAsset( - filteredAssets[assetIdx], - assetIdx, - itemPos, - m_layout.size.itemSize - ); - } + } else { + // draw asset thumbnail + int assetIdx = i - (int)subfolders.size(); + drawAsset( + filtered[assetIdx], + assetIdx, + itemPos, + m_layout.size.itemSize + ); } } } @@ -407,6 +404,8 @@ namespace nexo::editor { clipper.End(); } + + void AssetManagerWindow::show() { if (m_folderStructure.empty()) From 6f485a2c728e5d8c3521e810e1be8b3482d7cdef Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 07:12:04 +0200 Subject: [PATCH 07/33] feat(drag-drop): add util function to normalize paths --- common/Path.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/Path.hpp b/common/Path.hpp index 85da756a6..b010f996d 100644 --- a/common/Path.hpp +++ b/common/Path.hpp @@ -69,5 +69,16 @@ namespace nexo { inline static std::filesystem::path m_executableRootPathCached; }; + inline std::string normalizePath(const std::string &rawPath) + { + std::string_view sv{rawPath}; + // find first non-'/' and last non-'/' + auto b = sv.find_first_not_of('/'); + if (b == std::string_view::npos) + return {}; // all slashes or empty + auto e = sv.find_last_not_of('/'); + return std::string{ sv.substr(b, e - b + 1) }; + } + } // namespace nexo From 83ad8b5dfbaa51210669d961701867f6d3cdcc8c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 16:10:18 +0200 Subject: [PATCH 08/33] fix(drag-drop): fix model drag and drop in the scene hierarchy window --- .../SceneTreeWindow/DragDrop.cpp | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index b380c14e8..134a4d30a 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -65,25 +65,25 @@ namespace nexo::editor { { IM_ASSERT(imguiPayload->DataSize == sizeof(SceneTreeDragDropPayload)); const auto& payload = *static_cast(imguiPayload->Data); - + if (canAcceptDrop(object, payload)) { handleDrop(object, payload); } } - + // Handle drops from asset manager if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); const auto& payload = *static_cast(assetPayload->Data); - + // Handle different asset types if (object.type == SelectionType::SCENE) { auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); - + if (payload.type == assets::AssetType::MODEL) { // Import the model @@ -91,22 +91,24 @@ namespace nexo::editor { std::filesystem::path path{payload.path}; assets::ImporterFileInput fileInput{path}; std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; - auto modelRef = importer.importAsset(assets::AssetLocation(assetLocationStr), fileInput); - if (modelRef) + auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!modelRef) + return; + if (auto model = modelRef.as(); model) { // Create entity with the model ecs::Entity newEntity = EntityFactory3D::createModel( - modelRef, + model, {0.0f, 0.0f, 0.0f}, // position {1.0f, 1.0f, 1.0f}, // scale {0.0f, 0.0f, 0.0f} // rotation ); - + // Add to the scene auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); scene.addEntity(newEntity); - - // Record action for undo/redo + + // Record action for undo/redo TODO: Fix undo for models, it does not seem to work properly auto action = std::make_unique(newEntity); ActionManager::get().recordAction(std::move(action)); } @@ -117,7 +119,7 @@ namespace nexo::editor { auto& catalog = assets::AssetCatalog::getInstance(); assets::AssetLocation location(payload.path); auto textureRef = catalog.getAsset(location).as(); - + if (!textureRef) { // If not in catalog, try to import it @@ -127,25 +129,25 @@ namespace nexo::editor { std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; textureRef = importer.importAsset(assets::AssetLocation(assetLocationStr), fileInput); } - + if (textureRef) { // Create material with the texture components::Material material; material.albedoTexture = textureRef; material.albedoColor = glm::vec4(1.0f); // White to show texture colors - + // Create billboard entity ecs::Entity newEntity = EntityFactory3D::createBillboard( {0.0f, 0.0f, 0.0f}, // position {1.0f, 1.0f, 1.0f}, // size material ); - + // Add to the scene auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); scene.addEntity(newEntity); - + // Record action for undo/redo auto action = std::make_unique(newEntity); ActionManager::get().recordAction(std::move(action)); @@ -153,7 +155,7 @@ namespace nexo::editor { } } } - + ImGui::EndDragDropTarget(); } } @@ -190,7 +192,7 @@ namespace nexo::editor { // Get the source scene auto& sourceScene = sceneManager.getScene(payload.sourceSceneId); - + if (dropTarget.type == SelectionType::SCENE) { // Dropping onto a scene - move entity to that scene @@ -198,15 +200,15 @@ namespace nexo::editor { { // Remove from source scene sourceScene.removeEntity(payload.entity); - + // Add to target scene auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); targetScene.addEntity(payload.entity); - + // Update scene tag auto& sceneTag = coordinator.getComponent(payload.entity); sceneTag.id = dropTarget.data.sceneProperties.sceneId; - + // Remove parent relationship if moving to different scene auto parentComp = coordinator.tryGetComponent(payload.entity); if (parentComp.has_value()) @@ -217,10 +219,10 @@ namespace nexo::editor { { parentTransform->get().removeChild(payload.entity); } - + coordinator.removeComponent(payload.entity); } - + // Record action for undo/redo // TODO: Create a specific action for moving entities between scenes // For now, we just perform the operation without undo support for scene moves @@ -231,14 +233,14 @@ namespace nexo::editor { // Dropping onto an entity - create parent-child relationship ecs::Entity parentEntity = dropTarget.data.entity; ecs::Entity childEntity = payload.entity; - + // Get old parent before modifications ecs::Entity oldParent = ecs::INVALID_ENTITY; auto oldParentComp = coordinator.tryGetComponent(childEntity); if (oldParentComp.has_value()) { oldParent = oldParentComp->get().parent; - + // Update old parent's children list auto oldParentTransform = coordinator.tryGetComponent(oldParent); if (oldParentTransform.has_value()) @@ -246,7 +248,7 @@ namespace nexo::editor { oldParentTransform->get().removeChild(childEntity); } } - + // Set new parent if (!oldParentComp.has_value()) { @@ -256,7 +258,7 @@ namespace nexo::editor { { oldParentComp->get().parent = parentEntity; } - + // Update parent's children list auto parentTransform = coordinator.tryGetComponent(parentEntity); if (!parentTransform.has_value()) @@ -268,14 +270,14 @@ namespace nexo::editor { { parentTransform->get().addChild(childEntity); } - + // If moving to different scene, update scene tag if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) { sourceScene.removeEntity(childEntity); auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); targetScene.addEntity(childEntity); - + auto& sceneTag = coordinator.getComponent(childEntity); sceneTag.id = dropTarget.data.sceneProperties.sceneId; } @@ -284,4 +286,4 @@ namespace nexo::editor { } } -} // namespace nexo::editor \ No newline at end of file +} // namespace nexo::editor From 096ea86da228480d4af319e92e8c8bd82948db12 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 17:19:38 +0200 Subject: [PATCH 09/33] refactor(drag-drop): improve texture creation when drag and dropping --- .../SceneTreeWindow/DragDrop.cpp | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index 134a4d30a..635d804f6 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -86,11 +86,6 @@ namespace nexo::editor { if (payload.type == assets::AssetType::MODEL) { - // Import the model - assets::AssetImporter importer; - std::filesystem::path path{payload.path}; - assets::ImporterFileInput fileInput{path}; - std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); if (!modelRef) return; @@ -115,26 +110,13 @@ namespace nexo::editor { } else if (payload.type == assets::AssetType::TEXTURE) { - // Get the texture from the asset catalog - auto& catalog = assets::AssetCatalog::getInstance(); - assets::AssetLocation location(payload.path); - auto textureRef = catalog.getAsset(location).as(); - + auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); if (!textureRef) + return; + if (auto texture = textureRef.as(); texture) { - // If not in catalog, try to import it - assets::AssetImporter importer; - std::filesystem::path path{payload.path}; - assets::ImporterFileInput fileInput{path}; - std::string assetLocationStr = std::string(payload.name) + "@DragDrop/"; - textureRef = importer.importAsset(assets::AssetLocation(assetLocationStr), fileInput); - } - - if (textureRef) - { - // Create material with the texture components::Material material; - material.albedoTexture = textureRef; + material.albedoTexture = texture; material.albedoColor = glm::vec4(1.0f); // White to show texture colors // Create billboard entity From 889c85d2e318cbec20a6b76046786bc2957bed9d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 17:19:59 +0200 Subject: [PATCH 10/33] feat(drag-drop): add drag and drop from asset manager to editor scene --- .../DocumentWindows/EditorScene/DragDrop.cpp | 150 ++++++++++++++++++ .../EditorScene/EditorScene.hpp | 5 + .../src/DocumentWindows/EditorScene/Show.cpp | 1 + 3 files changed, 156 insertions(+) create mode 100644 editor/src/DocumentWindows/EditorScene/DragDrop.cpp diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp new file mode 100644 index 000000000..07b947c3d --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -0,0 +1,150 @@ +//// DragDrop.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: 12/07/2025 +// Description: Source file for the drag and drop feature of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "assets/Asset.hpp" +#include "assets/AssetCatalog.hpp" +#include "components/MaterialComponent.hpp" +#include "context/ActionManager.hpp" + +namespace nexo::editor { + + void EditorScene::handleDropModel(const AssetDragDropPayload &payload) + { + auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!modelRef) + return; + if (auto model = modelRef.as(); model) + { + auto& sceneManager = Application::getInstance().getSceneManager(); + // Create entity with the model + ecs::Entity newEntity = EntityFactory3D::createModel( + model, + {0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // scale + {0.0f, 0.0f, 0.0f} // rotation + ); + + // Add to the scene + auto& scene = sceneManager.getScene(m_sceneId); + scene.addEntity(newEntity); + + // Record action for undo/redo TODO: Fix undo for models, it does not seem to work properly + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + + void EditorScene::handleDropTexture(const AssetDragDropPayload &payload) + { + auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!textureRef) + return; + if (auto texture = textureRef.as(); texture) + { + auto [mx, my] = ImGui::GetMousePos(); + mx -= m_viewportBounds[0].x; + my -= m_viewportBounds[0].y; + + // Flip the y-coordinate to match opengl texture format + my = m_contentSize.y - my; + + // Check if mouse is inside viewport + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) + return; + const int entityId = sampleEntityTexture(mx, my); + if (entityId == -1) { + auto& sceneManager = Application::getInstance().getSceneManager(); + components::Material material; + material.albedoTexture = texture; + material.albedoColor = glm::vec4(1.0f); // White to show texture colors + + // Create billboard entity + ecs::Entity newEntity = EntityFactory3D::createBillboard( + {0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // size + material + ); + + // Add to the scene + auto& scene = sceneManager.getScene(m_sceneId); + scene.addEntity(newEntity); + + // Record action for undo/redo + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + return; + } + auto matComponent = Application::getInstance().m_coordinator->tryGetComponent(entityId); + if (!matComponent) + return; + auto material = matComponent->get().material.lock(); + material->getData()->albedoTexture = texture; + } + } + + void EditorScene::handleDropMaterial(const AssetDragDropPayload &payload) + { + auto materialRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!materialRef) + return; + if (auto material = materialRef.as(); material) + { + auto [mx, my] = ImGui::GetMousePos(); + mx -= m_viewportBounds[0].x; + my -= m_viewportBounds[0].y; + + // Flip the y-coordinate to match opengl texture format + my = m_contentSize.y - my; + + // Check if mouse is inside viewport + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) + return; + const int entityId = sampleEntityTexture(mx, my); + if (entityId == -1) + return; + auto matComponent = Application::getInstance().m_coordinator->tryGetComponent(entityId); + if (!matComponent) + return; + matComponent->get().material = material; + } + } + + void EditorScene::handleDropTarget() + { + if (ImGui::BeginDragDropTarget()) + { + // Handle drops from asset manager + if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); + const auto& payload = *static_cast(assetPayload->Data); + + if (payload.type == assets::AssetType::MODEL) + { + handleDropModel(payload); + } + else if (payload.type == assets::AssetType::TEXTURE) + { + handleDropTexture(payload); + } + else if (payload.type == assets::AssetType::MATERIAL) + { + handleDropMaterial(payload); + } + } + ImGui::EndDragDropTarget(); + } + } +} diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 9f03303b7..aaf5368a6 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -22,6 +22,7 @@ #include "../PopupManager.hpp" #include "ImNexo/Widgets.hpp" #include +#include "DocumentWindows/AssetManager/AssetManagerWindow.hpp" namespace nexo::editor { @@ -302,6 +303,10 @@ namespace nexo::editor void renderNewEntityPopup(); void handleSelection(); + void handleDropTarget(); + void handleDropModel(const AssetDragDropPayload &payload); + void handleDropTexture(const AssetDragDropPayload &payload); + void handleDropMaterial(const AssetDragDropPayload &payload); int sampleEntityTexture(float mx, float my) const; ecs::Entity findRootParent(ecs::Entity entityId) const; void selectEntityHierarchy(ecs::Entity entityId, const bool isCtrlPressed); diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 0ecf2ae23..50495cff4 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -150,6 +150,7 @@ namespace nexo::editor const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); ImNexo::Image(static_cast(static_cast(textureId)), m_contentSize); + handleDropTarget(); const ImVec2 viewportMin = ImGui::GetItemRectMin(); const ImVec2 viewportMax = ImGui::GetItemRectMax(); m_viewportBounds[0] = viewportMin; From 174f6db2d2b7bca5445abe511c8c9766201900e1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 17:20:20 +0200 Subject: [PATCH 11/33] chore(drag-drop): add source file --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b1c620009..0e04514bc 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -47,6 +47,7 @@ set(SRCS editor/src/DocumentWindows/EditorScene/Shutdown.cpp editor/src/DocumentWindows/EditorScene/Toolbar.cpp editor/src/DocumentWindows/EditorScene/Update.cpp + editor/src/DocumentWindows/EditorScene/DragDrop.cpp editor/src/DocumentWindows/AssetManager/Init.cpp editor/src/DocumentWindows/AssetManager/Show.cpp editor/src/DocumentWindows/AssetManager/Shutdown.cpp From 16aa0fbff26a68ca58f136edc82d5728ecc35919 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 21:36:47 +0200 Subject: [PATCH 12/33] feat(drag-drop): add texture preview when dragging + fix: now clears hovered folder every frame --- editor/src/DocumentWindows/AssetManager/Show.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 2664da2b9..a80d5dd9c 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -190,7 +190,13 @@ namespace nexo::editor { ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); // Show preview while dragging - ImGui::Text("Asset: %s", assetData->getMetadata().location.getName().c_str()); + //TODO: Add asset preview thanks to thumbnail cache after rebasing + if (assetData->getType() == assets::AssetType::TEXTURE) { + auto textureAsset = asset.as(); + auto textureData = textureAsset.lock(); + ImTextureID textureId = textureData->getData().get()->texture->getId(); + ImGui::Image(textureId, {64, 64}); + } ImGui::EndDragDropSource(); } @@ -408,6 +414,7 @@ namespace nexo::editor { void AssetManagerWindow::show() { + m_hoveredFolder.clear(); if (m_folderStructure.empty()) buildFolderStructure(); From 129a2c43d2b30ab09267ce33162f2d4715dff497 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 21:37:24 +0200 Subject: [PATCH 13/33] feat(drag-drop): highlight hovered entity when dragging over editor scene --- .../DocumentWindows/EditorScene/DragDrop.cpp | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp index 07b947c3d..6dce5f918 100644 --- a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -12,9 +12,12 @@ // /////////////////////////////////////////////////////////////////////////////// +#include +#include "Definitions.hpp" #include "EditorScene.hpp" #include "assets/Asset.hpp" #include "assets/AssetCatalog.hpp" +#include "components/Editor.hpp" #include "components/MaterialComponent.hpp" #include "context/ActionManager.hpp" @@ -123,12 +126,40 @@ namespace nexo::editor { void EditorScene::handleDropTarget() { + static ecs::Entity entityHovered = ecs::INVALID_ENTITY; if (ImGui::BeginDragDropTarget()) { // Handle drops from asset manager - if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG", ImGuiDragDropFlags_AcceptBeforeDelivery)) { IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); + auto [mx, my] = ImGui::GetMousePos(); + mx -= m_viewportBounds[0].x; + my -= m_viewportBounds[0].y; + + // Flip the y-coordinate to match opengl texture format + my = m_contentSize.y - my; + + // Check if mouse is inside viewport + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) + return; + const int entityId = sampleEntityTexture(mx, my); + if (entityId != -1 && static_cast(entityId) != entityHovered) + { + entityHovered = static_cast(entityId); + Application::getInstance().m_coordinator->addComponent(entityHovered, components::SelectedTag{}); + } + if (entityId == -1 && entityHovered != ecs::INVALID_ENTITY) + { + Application::getInstance().m_coordinator->removeComponent(entityHovered); + entityHovered = ecs::INVALID_ENTITY; + } + if (!assetPayload->IsDelivery()) + { + return; + } + Application::getInstance().m_coordinator->removeComponent(entityHovered); + entityHovered = ecs::INVALID_ENTITY; const auto& payload = *static_cast(assetPayload->Data); if (payload.type == assets::AssetType::MODEL) From 43c6c3464a6e50582b9f8521fac083193103c04a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 12 Jul 2025 22:52:19 +0200 Subject: [PATCH 14/33] fix(drag-drop): fix entities not moving when clicking outside editor scene --- engine/src/systems/TransformMatrixSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/systems/TransformMatrixSystem.cpp b/engine/src/systems/TransformMatrixSystem.cpp index 11dd04b5c..2b0e62969 100644 --- a/engine/src/systems/TransformMatrixSystem.cpp +++ b/engine/src/systems/TransformMatrixSystem.cpp @@ -29,7 +29,7 @@ namespace nexo::system { for (const ecs::Entity entity : entities) { auto &sceneTag = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered) + if (sceneTag.id != sceneRendered) continue; auto &transform = getComponent(entity); transform.localMatrix = createTransformMatrix(transform); From bba3827e694d9e3ad6284d27217efcc34a2c9a87 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 13 Jul 2025 00:14:55 +0200 Subject: [PATCH 15/33] fix(drag-drop): now only remove selected tag if an entiy was being hovered --- editor/src/DocumentWindows/EditorScene/DragDrop.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp index 6dce5f918..98afd2212 100644 --- a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -158,7 +158,8 @@ namespace nexo::editor { { return; } - Application::getInstance().m_coordinator->removeComponent(entityHovered); + if (entityHovered != ecs::INVALID_ENTITY) + Application::getInstance().m_coordinator->removeComponent(entityHovered); entityHovered = ecs::INVALID_ENTITY; const auto& payload = *static_cast(assetPayload->Data); From 4aa6c931992c6eba4b12605d62e73714a24258f6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 13 Jul 2025 00:15:33 +0200 Subject: [PATCH 16/33] fix(drag-drop): parent-child creation when dragging now works properly with the transfrom hierarchy system --- .../SceneTreeWindow/DragDrop.cpp | 82 +++++++++++++------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index 635d804f6..841e9ec5d 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -14,7 +14,9 @@ #include "SceneTreeWindow.hpp" #include "DocumentWindows/AssetManager/AssetManagerWindow.hpp" +#include "components/Uuid.hpp" #include "context/ActionManager.hpp" +#include "context/Selector.hpp" #include "context/actions/EntityActions.hpp" #include "components/Parent.hpp" #include "components/Transform.hpp" @@ -26,6 +28,8 @@ #include #include #include +#define GLM_ENABLE_EXPERIMENTAL +#include namespace nexo::editor { @@ -187,10 +191,6 @@ namespace nexo::editor { auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); targetScene.addEntity(payload.entity); - // Update scene tag - auto& sceneTag = coordinator.getComponent(payload.entity); - sceneTag.id = dropTarget.data.sceneProperties.sceneId; - // Remove parent relationship if moving to different scene auto parentComp = coordinator.tryGetComponent(payload.entity); if (parentComp.has_value()) @@ -210,50 +210,79 @@ namespace nexo::editor { // For now, we just perform the operation without undo support for scene moves } } - else + else if (dropTarget.type == SelectionType::ENTITY) { // Dropping onto an entity - create parent-child relationship ecs::Entity parentEntity = dropTarget.data.entity; - ecs::Entity childEntity = payload.entity; + ecs::Entity childEntity = payload.entity; + + auto& childTransform = coordinator.getComponent(childEntity); + glm::mat4 childWorldMat = childTransform.worldMatrix; + + auto& parentTransform = coordinator.getComponent(parentEntity); + glm::mat4 parentWorldMat = parentTransform.worldMatrix; + + // Compute the new localMatrix so that parentWorldMat * local = old world + glm::mat4 invParent = glm::inverse(parentWorldMat); + glm::mat4 newLocalMat = invParent * childWorldMat; + + glm::vec3 skew, scale, translation; + glm::quat rotation; + glm::vec4 perspective; + glm::decompose( + newLocalMat, + scale, + rotation, + translation, + skew, + perspective + ); + + childTransform.pos = translation; + childTransform.quat = rotation; + childTransform.size = scale; - // Get old parent before modifications ecs::Entity oldParent = ecs::INVALID_ENTITY; auto oldParentComp = coordinator.tryGetComponent(childEntity); if (oldParentComp.has_value()) { oldParent = oldParentComp->get().parent; - // Update old parent's children list - auto oldParentTransform = coordinator.tryGetComponent(oldParent); - if (oldParentTransform.has_value()) - { - oldParentTransform->get().removeChild(childEntity); + if (auto oldPT = coordinator.tryGetComponent(oldParent)) { + oldPT->get().removeChild(childEntity); + if (oldPT->get().children.empty() && coordinator.entityHasComponent(oldParent)) + coordinator.removeComponent(oldParent); } } - // Set new parent if (!oldParentComp.has_value()) - { coordinator.addComponent(childEntity, components::ParentComponent{parentEntity}); - } else - { oldParentComp->get().parent = parentEntity; - } - // Update parent's children list - auto parentTransform = coordinator.tryGetComponent(parentEntity); - if (!parentTransform.has_value()) - { + if (!coordinator.entityHasComponent(parentEntity)) coordinator.addComponent(parentEntity, components::TransformComponent{}); - parentTransform = coordinator.tryGetComponent(parentEntity); - } - if (parentTransform.has_value()) + auto& pt = coordinator.getComponent(parentEntity); + pt.addChild(childEntity); + + if (!coordinator.entityHasComponent(parentEntity) && + !coordinator.entityHasComponent(parentEntity)) { - parentTransform->get().addChild(childEntity); + std::string name = dropTarget.uiName; + auto it = ObjectTypeToIcon.find(dropTarget.type); + if (it != ObjectTypeToIcon.end()) + { + const std::string& icon = it->second; + if (name.rfind(icon, 0) == 0) + name.erase(0, icon.size()); + } + + coordinator.addComponent( + parentEntity, + components::RootComponent{ name, nullptr, 1 } + ); } - // If moving to different scene, update scene tag if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) { sourceScene.removeEntity(childEntity); @@ -263,6 +292,7 @@ namespace nexo::editor { auto& sceneTag = coordinator.getComponent(childEntity); sceneTag.id = dropTarget.data.sceneProperties.sceneId; } + auto action = std::make_unique(childEntity, oldParent, parentEntity); ActionManager::get().recordAction(std::move(action)); } From eb28fc50218c5d6351899df8c30c747bf355b3ac Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 13 Jul 2025 20:59:53 +0200 Subject: [PATCH 17/33] fix(drag-drop): normalize path in setPath too --- engine/src/assets/AssetLocation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index b5480590f..706f920c8 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -62,7 +62,7 @@ namespace nexo::assets { */ AssetLocation& setPath(const std::string& path) { - _path = path; + _path = normalizePath(path); return *this; } From c1409b462ee68a91e31c3f43a9c613c50fd117a5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 13 Jul 2025 21:00:13 +0200 Subject: [PATCH 18/33] feat(drag-drop): now possible to drag and drop on breadcrumbs --- .../src/DocumentWindows/AssetManager/Show.cpp | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index a80d5dd9c..808e011af 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -241,14 +241,12 @@ namespace nexo::editor { { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { - // Cast back to your payload struct const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; - // e.g. move the asset at data->path into this folder: std::shared_ptr asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock(); if (asset) { assets::AssetMetadata &metadata = asset->getMetadata(); - metadata.location.setLocation(metadata.location.getFullLocation() + folderPath + "/"); + metadata.location.setPath(folderPath); } } ImGui::EndDragDropTarget(); @@ -489,50 +487,63 @@ namespace nexo::editor { IM_COL32(255, 255, 255, 255), dropText); } - // Show path breadcrumb - if (m_currentFolder.empty()) { - ImGui::Text("Assets"); - } else { - // Display clickable breadcrumbs - ImGui::Text(ICON_FA_FOLDER " "); - ImGui::SameLine(); + ImGui::Text(ICON_FA_FOLDER " "); + ImGui::SameLine(); - // Start with root level "Assets" + { + ImGui::PushID("breadcrumb_root"); if (ImGui::Button("Assets")) - m_currentFolder = ""; - - // Split the current path into components - 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); - if (!segment.empty()) { - fullPath += (fullPath.empty() ? "" : "/") + segment; - ImGui::SameLine(); - ImGui::Text(" > "); - ImGui::SameLine(); - if (ImGui::Button(segment.c_str())) { - m_currentFolder = fullPath; - } + m_currentFolder.clear(); + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) + { + const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; + if (auto asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock()) + asset->getMetadata().location.setPath(""); } - path.erase(0, pos + 1); + ImGui::EndDragDropTarget(); } + ImGui::PopID(); + } - // Last segment - if (!path.empty()) { - ImGui::SameLine(); - ImGui::Text(" > "); - ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", path.c_str()); + // 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::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 AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; + if (auto asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock()) + asset->getMetadata().location.setPath(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()); } ImGui::Separator(); - // Calculate layout for asset grid calculateLayout(ImGui::GetContentRegionAvail().x); drawAssetsGrid(); ImGui::EndChild(); From 884c224e57c74d6fb1a8cc9eb12b0becda4a2c8a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Jul 2025 06:40:52 +0200 Subject: [PATCH 19/33] feat(drag-drop): drag drop texture/material on entity in scene tree --- .../SceneTreeWindow/DragDrop.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index 841e9ec5d..b7a897f96 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -140,6 +140,33 @@ namespace nexo::editor { } } } + else if (object.type == SelectionType::ENTITY) + { + auto matCompOpt = Application::m_coordinator->tryGetComponent(object.data.entity); + if (!matCompOpt) + { ImGui::EndDragDropTarget(); return; } + + auto& matComp = matCompOpt->get(); + + if (payload.type == assets::AssetType::TEXTURE) + { + auto texRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (auto tex = texRef.as(); tex) + { + auto mat = matComp.material.lock(); + mat->getData()->albedoTexture = tex; + } + } + else if (payload.type == assets::AssetType::MATERIAL) + { + auto matRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (auto m = matRef.as(); m) + { + auto oldMat = matComp.material; + matComp.material = m; + } + } + } } ImGui::EndDragDropTarget(); From 65ce3a0f99fcbf57536206a9e04c38920b2639f5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Jul 2025 08:28:38 +0200 Subject: [PATCH 20/33] refactor(drag-drop): file drop code cleaning --- .../AssetManager/AssetManagerWindow.hpp | 4 +- .../DocumentWindows/AssetManager/FileDrop.cpp | 126 ++++++++---------- 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 512f0033a..414b391b1 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -116,11 +116,11 @@ namespace nexo::editor { const ImVec2& itemSize ); - // File drop handling std::vector m_pendingDroppedFiles; bool m_showDropIndicator = false; void handleDroppedFiles(); + const assets::AssetLocation getAssetLocation(const std::filesystem::path &path) const; void importDroppedFile(const std::string& filePath); }; @@ -132,7 +132,7 @@ namespace nexo::editor { struct AssetDragDropPayload { assets::AssetType type; ///< Type of the asset - assets::AssetID id; + assets::AssetID id; ///< ID of the asset char path[256]; ///< Path to the asset char name[128]; ///< Display name of the asset }; diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 97d4b68d1..4160db33a 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -1,4 +1,4 @@ -//// FileDrop.cpp /////////////////////////////////////////////////////////////// +//// FileDrop.cpp ///////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -13,7 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetManagerWindow.hpp" +#include "assets/Asset.hpp" #include "assets/AssetImporter.hpp" +#include "assets/AssetLocation.hpp" #include "assets/Assets/Model/Model.hpp" #include "assets/Assets/Texture/Texture.hpp" #include "Logger.hpp" @@ -22,9 +24,45 @@ namespace nexo::editor { + static assets::AssetType getAssetTypeFromExtension(const std::string &extension) + { + static const std::set imageExtensions = { + ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic", ".pnm", ".ppm", ".pgm" + }; + if (imageExtensions.contains(extension)) + return assets::AssetType::TEXTURE; + static const std::set modelExtensions = { + ".gltf", ".glb", ".fbx", ".obj", ".dae", ".3ds", ".stl", ".ply", ".blend", ".x3d", ".ifc" + }; + if (modelExtensions.contains(extension)) + return assets::AssetType::MODEL; + return assets::AssetType::UNKNOWN; + } + + const assets::AssetLocation AssetManagerWindow::getAssetLocation(const std::filesystem::path &path) const + { + std::string assetName = path.stem().string(); + std::filesystem::path folderPath; + if (!m_currentFolder.empty()) + folderPath /= m_currentFolder; + if (!m_hoveredFolder.empty()) + folderPath /= m_hoveredFolder; + + std::string assetPath = folderPath.string(); + std::string locationString = assetName + "@" + assetPath; + + LOG(NEXO_DEV, + "Creating asset location: {} (current folder: '{}', hovered: '{}')", + locationString, + m_currentFolder, + m_hoveredFolder); + + assets::AssetLocation location(locationString); + return location; + } + void AssetManagerWindow::handleEvent(event::EventFileDrop& event) { - // Queue dropped files for processing in the next frame m_pendingDroppedFiles.insert(m_pendingDroppedFiles.end(), event.files.begin(), event.files.end()); @@ -35,15 +73,10 @@ namespace nexo::editor { if (m_pendingDroppedFiles.empty()) return; - // Process each dropped file for (const auto& filePath : m_pendingDroppedFiles) - { importDroppedFile(filePath); - } - m_pendingDroppedFiles.clear(); - // Rebuild folder structure to include new assets m_folderStructure.clear(); buildFolderStructure(); } @@ -52,91 +85,40 @@ namespace nexo::editor { { std::filesystem::path path(filePath); - if (!std::filesystem::exists(path)) - { + if (!std::filesystem::exists(path)) { LOG(NEXO_WARN, "Dropped file does not exist: {}", filePath); return; } - // Get file extension std::string extension = path.extension().string(); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); - // Determine asset type based on extension - assets::AssetType assetType = assets::AssetType::UNKNOWN; - - // Image extensions - static const std::vector imageExtensions = { - ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic", ".pnm", ".ppm", ".pgm" - }; - - // Model extensions (common ones supported by Assimp) - static const std::vector modelExtensions = { - ".gltf", ".glb", ".fbx", ".obj", ".dae", ".3ds", ".stl", ".ply", ".blend", ".x3d", ".ifc" - }; - - if (std::find(imageExtensions.begin(), imageExtensions.end(), extension) != imageExtensions.end()) - { - assetType = assets::AssetType::TEXTURE; - } - else if (std::find(modelExtensions.begin(), modelExtensions.end(), extension) != modelExtensions.end()) - { - assetType = assets::AssetType::MODEL; - } - else - { + assets::AssetType assetType = getAssetTypeFromExtension(extension); + if (assetType == assets::AssetType::UNKNOWN) { LOG(NEXO_WARN, "Unsupported file type: {}", extension); return; } - // Generate asset location - std::string filename = path.filename().string(); - std::string assetName = path.stem().string(); - - // Create location based on current folder - // The path after @ should just be the folder path, not include the asset name - std::string assetPath = m_currentFolder.empty() ? "" : m_currentFolder; - assetPath += m_hoveredFolder.empty() ? "" : "/" + m_hoveredFolder; - std::string locationString = assetName + "@" + assetPath; - - LOG(NEXO_DEV, "Creating asset location: {} (current folder: '{}')", locationString, m_currentFolder); - assets::AssetLocation location(locationString); + assets::AssetLocation location = getAssetLocation(path); - // Import the asset assets::AssetImporter importer; assets::ImporterFileInput fileInput{path}; - - try - { - if (assetType == assets::AssetType::TEXTURE) - { + try { + if (assetType == assets::AssetType::TEXTURE) { auto assetRef = importer.importAsset(location, fileInput); if (assetRef) - { - LOG(NEXO_INFO, "Successfully imported texture: {}", filename); - } + LOG(NEXO_INFO, "Successfully imported texture: {}", location.getName()); else - { - LOG(NEXO_ERROR, "Failed to import texture: {}", filename); - } - } - else if (assetType == assets::AssetType::MODEL) - { + LOG(NEXO_ERROR, "Failed to import texture: {}", location.getPath()); + } else if (assetType == assets::AssetType::MODEL) { auto assetRef = importer.importAsset(location, fileInput); if (assetRef) - { - LOG(NEXO_INFO, "Successfully imported model: {}", filename); - } + LOG(NEXO_INFO, "Successfully imported model: {}", location.getName()); else - { - LOG(NEXO_ERROR, "Failed to import model: {}", filename); - } + LOG(NEXO_ERROR, "Failed to import model: {}", location.getPath()); } - } - catch (const std::exception& e) - { - LOG(NEXO_ERROR, "Exception while importing {}: {}", filename, e.what()); + } catch (const std::exception& e) { + LOG(NEXO_ERROR, "Exception while importing {}: {}", location.getPath(), e.what()); } } - -} // namespace nexo::editor +} From 6396821116c12e5e818564ca5ce843ec09df7c17 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 16 Jul 2025 05:32:18 +0200 Subject: [PATCH 21/33] fix(drag-drop): fix compilation --- editor/src/DocumentWindows/AssetManager/FileDrop.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 4160db33a..00c196c7c 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -107,18 +107,18 @@ namespace nexo::editor { if (assetType == assets::AssetType::TEXTURE) { auto assetRef = importer.importAsset(location, fileInput); if (assetRef) - LOG(NEXO_INFO, "Successfully imported texture: {}", location.getName()); + LOG(NEXO_INFO, "Successfully imported texture: {}", location.getName().data()); else - LOG(NEXO_ERROR, "Failed to import texture: {}", location.getPath()); + LOG(NEXO_ERROR, "Failed to import texture: {}", location.getPath().data()); } else if (assetType == assets::AssetType::MODEL) { auto assetRef = importer.importAsset(location, fileInput); if (assetRef) - LOG(NEXO_INFO, "Successfully imported model: {}", location.getName()); + LOG(NEXO_INFO, "Successfully imported model: {}", location.getName().data()); else - LOG(NEXO_ERROR, "Failed to import model: {}", location.getPath()); + LOG(NEXO_ERROR, "Failed to import model: {}", location.getPath().data()); } } catch (const std::exception& e) { - LOG(NEXO_ERROR, "Exception while importing {}: {}", location.getPath(), e.what()); + LOG(NEXO_ERROR, "Exception while importing {}: {}", location.getPath().data(), e.what()); } } } From 9361f9f2c1a16bc23b1b57ed9dee0f6aeabed81c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 14:17:18 +0200 Subject: [PATCH 22/33] fix(drag-drop): fix what rebase broke --- editor/CMakeLists.txt | 2 + .../AssetManager/AssetManagerWindow.hpp | 2 +- .../DocumentWindows/AssetManager/Update.cpp | 1 + .../SceneTreeWindow/SceneTreeWindow.hpp | 21 +++++ .../DocumentWindows/SceneTreeWindow/Show.cpp | 3 + editor/src/context/actions/EntityActions.cpp | 80 +++++++++++++++++++ editor/src/context/actions/EntityActions.hpp | 17 ++++ engine/src/Application.cpp | 10 +++ engine/src/core/event/WindowEvent.hpp | 17 ++++ engine/src/renderer/Window.hpp | 3 + engine/src/renderer/opengl/OpenGlWindow.cpp | 7 ++ engine/src/renderer/opengl/OpenGlWindow.hpp | 1 + 12 files changed, 163 insertions(+), 1 deletion(-) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0e04514bc..3fe8272fb 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -54,6 +54,7 @@ set(SRCS editor/src/DocumentWindows/AssetManager/Update.cpp editor/src/DocumentWindows/AssetManager/FolderTree.cpp editor/src/DocumentWindows/AssetManager/Thumbnail.cpp + editor/src/DocumentWindows/AssetManager/FileDrop.cpp editor/src/DocumentWindows/ConsoleWindow/Init.cpp editor/src/DocumentWindows/ConsoleWindow/Log.cpp editor/src/DocumentWindows/ConsoleWindow/Show.cpp @@ -78,6 +79,7 @@ set(SRCS editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp editor/src/DocumentWindows/SceneTreeWindow/Update.cpp editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp + editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index 414b391b1..f4bd0e109 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -23,7 +23,7 @@ namespace nexo::editor { - class AssetManagerWindow final : public ADocumentWindow { + class AssetManagerWindow final : public ADocumentWindow, LISTENS_TO(event::EventFileDrop) { public: using ADocumentWindow::ADocumentWindow; diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp index 475078433..41442d0a1 100644 --- a/editor/src/DocumentWindows/AssetManager/Update.cpp +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -18,6 +18,7 @@ namespace nexo::editor { void AssetManagerWindow::update() { + handleDroppedFiles(); // Nothing to do for now } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index 98093ed20..52b1f4e17 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -425,5 +425,26 @@ namespace nexo::editor static void selectAllCallback(); static void hideSelectedCallback(); static void showAllCallback(); + + // Drag and drop functionality + void handleDragSource(const SceneObject& object); + void handleDropTarget(const SceneObject& object); + void handleDrop(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + static bool canAcceptDrop(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + }; + + /** + * @brief Payload structure for drag and drop operations in the scene tree. + * + * Contains all necessary information to perform entity/scene drag and drop + * operations including validation and hierarchy updates. + */ + struct SceneTreeDragDropPayload + { + ecs::Entity entity; ///< The entity being dragged + scene::SceneId sourceSceneId; ///< The scene the entity originated from + SelectionType type; ///< The type of object being dragged + std::string uuid; ///< UUID of the dragged object + std::string name; ///< Display name of the dragged object }; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index 0cd41baa9..0db1e3d82 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -186,6 +186,9 @@ namespace nexo::editor handleHovering(object); + handleDragSource(object); + handleDropTarget(object); + // Handles the right click on each different type of object if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp index 455ec79a4..00c263094 100644 --- a/editor/src/context/actions/EntityActions.cpp +++ b/editor/src/context/actions/EntityActions.cpp @@ -64,4 +64,84 @@ namespace nexo::editor { for (const auto &action : m_componentRestoreActions) action->undo(); } + + void EntityParentChangeAction::redo() + { + auto& coordinator = *Application::m_coordinator; + + // Handle old parent + if (m_oldParent != ecs::INVALID_ENTITY) { + auto oldParentTransform = coordinator.tryGetComponent(m_oldParent); + if (oldParentTransform.has_value()) { + oldParentTransform->get().removeChild(m_entity); + } + } + + // Handle new parent + if (m_newParent != ecs::INVALID_ENTITY) { + // Add or update parent component on entity + auto parentComp = coordinator.tryGetComponent(m_entity); + if (!parentComp.has_value()) { + coordinator.addComponent(m_entity, components::ParentComponent{m_newParent}); + } else { + parentComp->get().parent = m_newParent; + } + + // Add to new parent's children + auto newParentTransform = coordinator.tryGetComponent(m_newParent); + if (!newParentTransform.has_value()) { + coordinator.addComponent(m_newParent, components::TransformComponent{}); + newParentTransform = coordinator.tryGetComponent(m_newParent); + } + if (newParentTransform.has_value()) { + newParentTransform->get().addChild(m_entity); + } + } else { + // Remove parent component (make it a root entity) + auto parentComp = coordinator.tryGetComponent(m_entity); + if (parentComp.has_value()) { + coordinator.removeComponent(m_entity); + } + } + } + + void EntityParentChangeAction::undo() + { + auto& coordinator = *Application::m_coordinator; + + // Handle new parent (undo by removing from it) + if (m_newParent != ecs::INVALID_ENTITY) { + auto newParentTransform = coordinator.tryGetComponent(m_newParent); + if (newParentTransform.has_value()) { + newParentTransform->get().removeChild(m_entity); + } + } + + // Handle old parent (restore to it) + if (m_oldParent != ecs::INVALID_ENTITY) { + // Add or update parent component on entity + auto parentComp = coordinator.tryGetComponent(m_entity); + if (!parentComp.has_value()) { + coordinator.addComponent(m_entity, components::ParentComponent{m_oldParent}); + } else { + parentComp->get().parent = m_oldParent; + } + + // Add back to old parent's children + auto oldParentTransform = coordinator.tryGetComponent(m_oldParent); + if (!oldParentTransform.has_value()) { + coordinator.addComponent(m_oldParent, components::TransformComponent{}); + oldParentTransform = coordinator.tryGetComponent(m_oldParent); + } + if (oldParentTransform.has_value()) { + oldParentTransform->get().addChild(m_entity); + } + } else { + // Remove parent component (restore to root entity) + auto parentComp = coordinator.tryGetComponent(m_entity); + if (parentComp.has_value()) { + coordinator.removeComponent(m_entity); + } + } + } } diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index 227d0695e..ed858308c 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -152,4 +152,21 @@ namespace nexo::editor { std::vector> m_componentRestoreActions; }; + /** + * Stores information needed to undo/redo entity parent changes + * Handles hierarchy component updates + */ + class EntityParentChangeAction final : public Action { + public: + EntityParentChangeAction(ecs::Entity entity, ecs::Entity oldParent, ecs::Entity newParent) + : m_entity(entity), m_oldParent(oldParent), m_newParent(newParent) {} + + void redo() override; + void undo() override; + private: + ecs::Entity m_entity; + ecs::Entity m_oldParent; + ecs::Entity m_newParent; + }; + } diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index acb032834..c130cdb7b 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -196,6 +196,16 @@ namespace nexo { m_eventManager->emitEvent( std::make_shared(static_cast(xpos), static_cast(ypos))); }); + + m_window->setFileDropCallback([this](const int count, const char** paths) { + std::vector files; + files.reserve(count); + for (int i = 0; i < count; ++i) { + files.emplace_back(paths[i]); + } + m_eventManager->emitEvent( + std::make_shared(files)); + }); } void Application::registerSystems() diff --git a/engine/src/core/event/WindowEvent.hpp b/engine/src/core/event/WindowEvent.hpp index b018c9a95..73d8fc66a 100644 --- a/engine/src/core/event/WindowEvent.hpp +++ b/engine/src/core/event/WindowEvent.hpp @@ -163,4 +163,21 @@ namespace nexo::event { return os; } }; + + class EventFileDrop final : public Event { + public: + EventFileDrop(const std::vector& droppedFiles) : files(droppedFiles) {}; + + std::vector files; + + friend std::ostream &operator<<(std::ostream &os, const EventFileDrop &event) + { + os << "[FILE DROP EVENT] " << event.files.size() << " file(s): "; + for (size_t i = 0; i < event.files.size(); ++i) { + if (i > 0) os << ", "; + os << event.files[i]; + } + return os; + } + }; } diff --git a/engine/src/renderer/Window.hpp b/engine/src/renderer/Window.hpp index 976ffed41..115bc1b8e 100644 --- a/engine/src/renderer/Window.hpp +++ b/engine/src/renderer/Window.hpp @@ -29,6 +29,7 @@ namespace nexo::renderer { using MouseClickCallback = std::function; using MouseScrollCallback = std::function; using MouseMoveCallback = std::function; + using FileDropCallback = std::function; struct NxWindowProperty { @@ -43,6 +44,7 @@ namespace nexo::renderer { MouseClickCallback mouseClickCallback; MouseScrollCallback mouseScrollCallback; MouseMoveCallback mouseMoveCallback; + FileDropCallback fileDropCallback; NxWindowProperty(const unsigned int w, const unsigned h, const char * t) : width(w), height(h), title(t) {} }; @@ -108,6 +110,7 @@ namespace nexo::renderer { virtual void setMouseClickCallback(MouseClickCallback callback) = 0; virtual void setMouseScrollCallback(MouseScrollCallback callback) = 0; virtual void setMouseMoveCallback(MouseMoveCallback callback) = 0; + virtual void setFileDropCallback(FileDropCallback callback) = 0; // Linux specific methods #ifdef __linux__ diff --git a/engine/src/renderer/opengl/OpenGlWindow.cpp b/engine/src/renderer/opengl/OpenGlWindow.cpp index e6730e90b..3484ef9ae 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.cpp +++ b/engine/src/renderer/opengl/OpenGlWindow.cpp @@ -81,6 +81,13 @@ namespace nexo::renderer { if (props->mouseMoveCallback) props->mouseMoveCallback(xpos, ypos); }); + + glfwSetDropCallback(_openGlWindow, [](GLFWwindow *window, const int count, const char **paths) + { + const auto *props = static_cast(glfwGetWindowUserPointer(window)); + if (props->fileDropCallback) + props->fileDropCallback(count, paths); + }); } void NxOpenGlWindow::init() diff --git a/engine/src/renderer/opengl/OpenGlWindow.hpp b/engine/src/renderer/opengl/OpenGlWindow.hpp index 73f4c1023..dbeb11eb6 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.hpp +++ b/engine/src/renderer/opengl/OpenGlWindow.hpp @@ -104,6 +104,7 @@ namespace nexo::renderer { void setMouseClickCallback(MouseClickCallback callback) override { _props.mouseClickCallback = std::move(callback); } void setMouseScrollCallback(MouseScrollCallback callback) override { _props.mouseScrollCallback = std::move(callback); } void setMouseMoveCallback(MouseMoveCallback callback) override { _props.mouseMoveCallback = std::move(callback); } + void setFileDropCallback(FileDropCallback callback) override { _props.fileDropCallback = std::move(callback); } // Linux specific method #ifdef __linux__ From 1672339499d2d29c3fd4f717fd6667449ab848bc Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 14:39:12 +0200 Subject: [PATCH 23/33] fix(drag-drop): fix tests to comply with the way we now handle asset location --- tests/engine/assets/AssetImporterContext.test.cpp | 2 +- tests/engine/assets/AssetLocation.test.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/engine/assets/AssetImporterContext.test.cpp b/tests/engine/assets/AssetImporterContext.test.cpp index 36b3cc186..97451c885 100644 --- a/tests/engine/assets/AssetImporterContext.test.cpp +++ b/tests/engine/assets/AssetImporterContext.test.cpp @@ -209,7 +209,7 @@ namespace nexo::assets { TEST_F(AssetImporterContextTest, DefaultContextValues) { - EXPECT_EQ(context.location.getFullLocation(), "default"); + EXPECT_EQ(context.location.getFullLocation(), "default@"); EXPECT_EQ(context.getMainAsset(), nullptr); EXPECT_TRUE(context.getDependencies().empty()); EXPECT_TRUE(context.getParameters().is_null()); diff --git a/tests/engine/assets/AssetLocation.test.cpp b/tests/engine/assets/AssetLocation.test.cpp index d2ba86e03..985e9b218 100644 --- a/tests/engine/assets/AssetLocation.test.cpp +++ b/tests/engine/assets/AssetLocation.test.cpp @@ -51,7 +51,7 @@ namespace nexo::assets { EXPECT_EQ(location.getPackName()->get(), "myPack"); EXPECT_EQ(location.getName(), "myAsset"); EXPECT_EQ(location.getPath(), ""); - EXPECT_EQ(location.getFullLocation(), fullLocation); + EXPECT_EQ(location.getFullLocation(), fullLocation + "@"); // For proper handling in the asset mananger we always add @ } TEST(AssetLocationTest, InvalidLocationEmpty) @@ -111,7 +111,7 @@ namespace nexo::assets { AssetPackName packName("myPack"); AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test"); + EXPECT_EQ(location.getFullLocation(), "test@"); location.setLocation(name, path, packName); ASSERT_TRUE(location.getPackName().has_value()); @@ -127,7 +127,7 @@ namespace nexo::assets { const std::string path = "path/to/asset"; AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test"); + EXPECT_EQ(location.getFullLocation(), "test@"); location.setLocation(name, path); EXPECT_FALSE(location.getPackName().has_value()); @@ -142,14 +142,14 @@ namespace nexo::assets { AssetPackName packName("myPack"); AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test"); + EXPECT_EQ(location.getFullLocation(), "test@"); location.setLocation(name, "", packName); ASSERT_TRUE(location.getPackName().has_value()); EXPECT_EQ(location.getPackName()->get(), "myPack"); EXPECT_EQ(location.getName(), "myAsset"); EXPECT_EQ(location.getPath(), ""); - EXPECT_EQ(location.getFullLocation(), "myPack::myAsset"); + EXPECT_EQ(location.getFullLocation(), "myPack::myAsset@"); } TEST(AssetLocationTest, SetName) From a2e0688bf6b2f467d2101258351fd323d6f4275a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 15:04:38 +0200 Subject: [PATCH 24/33] fix(drag-drop): fix code rabbit issues --- editor/CMakeLists.txt | 2 +- .../DocumentWindows/AssetManager/FileDrop.cpp | 9 +++----- .../src/DocumentWindows/AssetManager/Show.cpp | 11 ++++----- .../DocumentWindows/EditorScene/DragDrop.cpp | 21 +++++++++-------- .../EditorScene/EditorScene.hpp | 3 +++ .../SceneTreeWindow/DragDrop.cpp | 12 ++++++++-- .../SceneTreeWindow/SceneTreeWindow.hpp | 23 ++++++++++++++++++- 7 files changed, 55 insertions(+), 26 deletions(-) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 3fe8272fb..347c9d661 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -79,7 +79,7 @@ set(SRCS editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp editor/src/DocumentWindows/SceneTreeWindow/Update.cpp editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp - editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp + editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index 00c196c7c..a360d8c0b 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -7,7 +7,7 @@ // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // // Author: Jean CARDONNE -// Date: 2025-06-30 +// Date: 30/06/2025 // Description: Implementation of file drop handling for asset manager // /////////////////////////////////////////////////////////////////////////////// @@ -43,12 +43,9 @@ namespace nexo::editor { { std::string assetName = path.stem().string(); std::filesystem::path folderPath; - if (!m_currentFolder.empty()) - folderPath /= m_currentFolder; - if (!m_hoveredFolder.empty()) - folderPath /= m_hoveredFolder; + std::string targetFolder = !m_hoveredFolder.empty() ? m_hoveredFolder : m_currentFolder; - std::string assetPath = folderPath.string(); + std::string assetPath = targetFolder; std::string locationString = assetName + "@" + assetPath; LOG(NEXO_DEV, diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 808e011af..1904cfa10 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -183,8 +183,7 @@ namespace nexo::editor { std::strncpy(payload.path, fullLocation.c_str(), sizeof(payload.path) - 1); payload.path[sizeof(payload.path) - 1] = '\0'; - std::string assetName = assetData->getMetadata().location.getName().c_str(); - std::strncpy(payload.name, assetName.c_str(), sizeof(payload.name) - 1); + std::strncpy(payload.name, assetName, sizeof(payload.name) - 1); payload.name[sizeof(payload.name) - 1] = '\0'; ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); @@ -194,8 +193,10 @@ namespace nexo::editor { if (assetData->getType() == assets::AssetType::TEXTURE) { auto textureAsset = asset.as(); auto textureData = textureAsset.lock(); - ImTextureID textureId = textureData->getData().get()->texture->getId(); - ImGui::Image(textureId, {64, 64}); + if (textureData && textureData->getData() && textureData->getData()->texture) { + ImTextureID textureId = textureData->getData()->texture->getId(); + ImGui::Image(textureId, {64, 64}); + } } ImGui::EndDragDropSource(); @@ -408,8 +409,6 @@ namespace nexo::editor { clipper.End(); } - - void AssetManagerWindow::show() { m_hoveredFolder.clear(); diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp index 98afd2212..3a3401e4a 100644 --- a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -93,6 +93,8 @@ namespace nexo::editor { if (!matComponent) return; auto material = matComponent->get().material.lock(); + if (!material) + return; material->getData()->albedoTexture = texture; } } @@ -126,7 +128,6 @@ namespace nexo::editor { void EditorScene::handleDropTarget() { - static ecs::Entity entityHovered = ecs::INVALID_ENTITY; if (ImGui::BeginDragDropTarget()) { // Handle drops from asset manager @@ -144,23 +145,23 @@ namespace nexo::editor { if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; const int entityId = sampleEntityTexture(mx, my); - if (entityId != -1 && static_cast(entityId) != entityHovered) + if (entityId != -1 && static_cast(entityId) != m_entityHovered) { - entityHovered = static_cast(entityId); - Application::getInstance().m_coordinator->addComponent(entityHovered, components::SelectedTag{}); + m_entityHovered = static_cast(entityId); + Application::getInstance().m_coordinator->addComponent(m_entityHovered, components::SelectedTag{}); } - if (entityId == -1 && entityHovered != ecs::INVALID_ENTITY) + if (entityId == -1 && m_entityHovered != ecs::INVALID_ENTITY) { - Application::getInstance().m_coordinator->removeComponent(entityHovered); - entityHovered = ecs::INVALID_ENTITY; + Application::getInstance().m_coordinator->removeComponent(m_entityHovered); + m_entityHovered = ecs::INVALID_ENTITY; } if (!assetPayload->IsDelivery()) { return; } - if (entityHovered != ecs::INVALID_ENTITY) - Application::getInstance().m_coordinator->removeComponent(entityHovered); - entityHovered = ecs::INVALID_ENTITY; + if (m_entityHovered != ecs::INVALID_ENTITY) + Application::getInstance().m_coordinator->removeComponent(m_entityHovered); + m_entityHovered = ecs::INVALID_ENTITY; const auto& payload = *static_cast(assetPayload->Data); if (payload.type == assets::AssetType::MODEL) diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index aaf5368a6..779ff807f 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -17,6 +17,7 @@ #include #include "ADocumentWindow.hpp" +#include "Definitions.hpp" #include "inputs/WindowState.hpp" #include "core/scene/SceneManager.hpp" #include "../PopupManager.hpp" @@ -104,6 +105,8 @@ namespace nexo::editor bool m_snapToGrid = false; bool m_wireframeEnabled = false; + ecs::Entity m_entityHovered = ecs::INVALID_ENTITY; + int m_sceneId = -1; std::string m_sceneUuid; int m_activeCamera = -1; diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index b7a897f96..d22a51880 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -154,6 +154,8 @@ namespace nexo::editor { if (auto tex = texRef.as(); tex) { auto mat = matComp.material.lock(); + if (!mat) + return; mat->getData()->albedoTexture = tex; } } @@ -243,10 +245,16 @@ namespace nexo::editor { ecs::Entity parentEntity = dropTarget.data.entity; ecs::Entity childEntity = payload.entity; - auto& childTransform = coordinator.getComponent(childEntity); + auto childTransformOpt = coordinator.tryGetComponent(childEntity); + if (!childTransformOpt.has_value()) + return; + auto &childTransform = childTransformOpt->get(); glm::mat4 childWorldMat = childTransform.worldMatrix; - auto& parentTransform = coordinator.getComponent(parentEntity); + auto parentTransformOpt = coordinator.tryGetComponent(parentEntity); + if (!parentTransformOpt.has_value()) + return; + auto& parentTransform = parentTransformOpt->get(); glm::mat4 parentWorldMat = parentTransform.worldMatrix; // Compute the new localMatrix so that parentWorldMat * local = old world diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index 52b1f4e17..e4abb0e70 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -426,10 +426,31 @@ namespace nexo::editor static void hideSelectedCallback(); static void showAllCallback(); - // Drag and drop functionality + /** + * @brief Handles drag source setup for scene objects. + * @param object The scene object being dragged. + */ void handleDragSource(const SceneObject& object); + + /** + * @brief Handles drop target setup for scene objects. + * @param object The scene object that can receive drops. + */ void handleDropTarget(const SceneObject& object); + + /** + * @brief Processes the drop operation. + * @param dropTarget The target scene object receiving the drop. + * @param payload The drag-and-drop payload data. + */ void handleDrop(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + + /** + * @brief Validates if a drop operation is allowed. + * @param dropTarget The target scene object. + * @param payload The drag-and-drop payload data. + * @return true if the drop is valid, false otherwise. + */ static bool canAcceptDrop(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); }; From 1ce70a3c39a4c7f30c8f07c9a6c903f322410e78 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 23:00:23 +0200 Subject: [PATCH 25/33] refactor(drag-drop): add source file for path utils + use filesystem and lexically normal to normalize paths --- common/Path.cpp | 56 +++++++++++++++++++++++++++++++++++ common/Path.hpp | 36 +++------------------- editor/CMakeLists.txt | 1 + engine/CMakeLists.txt | 1 + tests/common/CMakeLists.txt | 1 + tests/renderer/CMakeLists.txt | 1 + 6 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 common/Path.cpp diff --git a/common/Path.cpp b/common/Path.cpp new file mode 100644 index 000000000..1939ae3fb --- /dev/null +++ b/common/Path.cpp @@ -0,0 +1,56 @@ +//// Path.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: 24/07/2025 +// Description: Source file for the path utilities +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Path.hpp" + +namespace nexo { + + const std::filesystem::path& Path::getExecutablePath() + { + if (!m_executablePathCached.empty() && !m_executableRootPathCached.empty()) + return m_executablePathCached; + const boost::dll::fs::path path = boost::dll::program_location(); + m_executablePathCached = path.c_str(); + m_executableRootPathCached = m_executablePathCached.parent_path(); + return m_executablePathCached; + } + + std::filesystem::path Path::resolvePathRelativeToExe(const std::filesystem::path& path) + { + if (m_executableRootPathCached.empty()) + getExecutablePath(); + return (m_executableRootPathCached / path).lexically_normal(); + } + + void Path::resetCache() + { + m_executablePathCached.clear(); + m_executableRootPathCached.clear(); + } + + std::string normalizePath(const std::string &rawPath) + { + namespace fs = std::filesystem; + fs::path p = fs::path(rawPath).lexically_normal(); + + std::string s = p.generic_string(); + + if (s == "/" || s.empty()) + return {}; + + size_t start = s.find_first_not_of('/'); + size_t end = s.find_last_not_of('/'); + return s.substr(start, end - start + 1); + } +} diff --git a/common/Path.hpp b/common/Path.hpp index b010f996d..28c122073 100644 --- a/common/Path.hpp +++ b/common/Path.hpp @@ -28,15 +28,7 @@ namespace nexo { * @brief Get the path to the executable (e.g.: nexoEditor) * @return The path to the executable */ - static const std::filesystem::path& getExecutablePath() - { - if (!m_executablePathCached.empty() && !m_executableRootPathCached.empty()) - return m_executablePathCached; - const boost::dll::fs::path path = boost::dll::program_location(); - m_executablePathCached = path.c_str(); - m_executableRootPathCached = m_executablePathCached.parent_path(); - return m_executablePathCached; - } + static const std::filesystem::path& getExecutablePath(); /** * @brief Resolve a path relative to the executable @@ -46,21 +38,12 @@ namespace nexo { * @note Example: if assets is a folder in the same directory as the executable, you can use: resolvePathRelativeToExe("assets") * @example ../editor/src/Editor.cpp */ - static std::filesystem::path resolvePathRelativeToExe(const std::filesystem::path& path) - { - if (m_executableRootPathCached.empty()) - getExecutablePath(); - return (m_executableRootPathCached / path).lexically_normal(); - } + static std::filesystem::path resolvePathRelativeToExe(const std::filesystem::path& path); /** * @brief Reset the cached paths */ - static void resetCache() - { - m_executablePathCached.clear(); - m_executableRootPathCached.clear(); - } + static void resetCache(); private: Path() = default; @@ -69,16 +52,5 @@ namespace nexo { inline static std::filesystem::path m_executableRootPathCached; }; - inline std::string normalizePath(const std::string &rawPath) - { - std::string_view sv{rawPath}; - // find first non-'/' and last non-'/' - auto b = sv.find_first_not_of('/'); - if (b == std::string_view::npos) - return {}; // all slashes or empty - auto e = sv.find_last_not_of('/'); - return std::string{ sv.substr(b, e - b + 1) }; - } - - + std::string normalizePath(const std::string &rawPath); } // namespace nexo diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 347c9d661..7d307baa3 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS common/Exception.cpp common/math/Matrix.cpp common/math/Light.cpp + common/Path.cpp editor/main.cpp editor/src/backends/ImGuiBackend.cpp editor/src/backends/opengl/openglImGuiBackend.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 84bb280b8..c1df4ebd7 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -12,6 +12,7 @@ set(COMMON_SOURCES common/Exception.cpp common/math/Vector.cpp common/math/Projection.cpp + common/Path.cpp engine/src/Nexo.cpp engine/src/EntityFactory3D.cpp engine/src/LightFactory.cpp diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index e21b9a298..a77559e34 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -23,6 +23,7 @@ include_directories("./common") # TODO: make common a library and link it to the tests set(COMMON_SOURCES common/Exception.cpp + common/Path.cpp common/math/Matrix.cpp common/math/Vector.cpp common/math/Light.cpp diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index f35213488..de2bb675f 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories("./engine/src/renderer") set(COMMON_SOURCES common/Exception.cpp common/math/Matrix.cpp + common/Path.cpp ) set(RENDERER_SOURCES From fb494e8868258338b016550d5a7c10c31aecdcb3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 23:01:19 +0200 Subject: [PATCH 26/33] fix(drag-drop): use std::string in asset drag drop payload instead of static char arrays --- .../AssetManager/AssetManagerWindow.hpp | 4 ++-- editor/src/DocumentWindows/AssetManager/Show.cpp | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index f4bd0e109..1c4a0d9d6 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -133,7 +133,7 @@ namespace nexo::editor { { assets::AssetType type; ///< Type of the asset assets::AssetID id; ///< ID of the asset - char path[256]; ///< Path to the asset - char name[128]; ///< Display name of the asset + std::string path; ///< Path to the asset + std::string name; ///< Display name of the asset }; } diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 1904cfa10..801870cec 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -177,14 +177,8 @@ namespace nexo::editor { AssetDragDropPayload payload; payload.type = assetData->getType(); payload.id = assetData->getID(); - - // Copy strings safely into fixed-size arrays - std::string fullLocation = assetData->getMetadata().location.getFullLocation(); - std::strncpy(payload.path, fullLocation.c_str(), sizeof(payload.path) - 1); - payload.path[sizeof(payload.path) - 1] = '\0'; - - std::strncpy(payload.name, assetName, sizeof(payload.name) - 1); - payload.name[sizeof(payload.name) - 1] = '\0'; + payload.path = assetData->getMetadata().location.getFullLocation(); + payload.name = assetName; ImGui::SetDragDropPayload("ASSET_DRAG", &payload, sizeof(payload)); From 6589c39f02b913ae6ea2382dd4829b885ca2f77e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Jul 2025 23:05:27 +0200 Subject: [PATCH 27/33] style(drag-drop): use switch instead of ifs --- .../DocumentWindows/EditorScene/DragDrop.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp index 3a3401e4a..6a0f8b956 100644 --- a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -164,17 +164,19 @@ namespace nexo::editor { m_entityHovered = ecs::INVALID_ENTITY; const auto& payload = *static_cast(assetPayload->Data); - if (payload.type == assets::AssetType::MODEL) + switch(payload.type) { - handleDropModel(payload); - } - else if (payload.type == assets::AssetType::TEXTURE) - { - handleDropTexture(payload); - } - else if (payload.type == assets::AssetType::MATERIAL) - { - handleDropMaterial(payload); + case assets::AssetType::MODEL: + handleDropModel(payload); + break; + case assets::AssetType::TEXTURE: + handleDropTexture(payload); + break; + case assets::AssetType::MATERIAL: + handleDropMaterial(payload); + break; + default: + break; } } ImGui::EndDragDropTarget(); From f3bc8bd86f5a0fb568df713c51b328754f482f53 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Jul 2025 00:36:52 +0200 Subject: [PATCH 28/33] fix(drag-drop): use importAssetAuto in drag and drop + temp fix for asset importer priority --- .../DocumentWindows/AssetManager/FileDrop.cpp | 16 +++---------- engine/src/assets/AssetImporter.cpp | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp index a360d8c0b..a9df7c8ab 100644 --- a/editor/src/DocumentWindows/AssetManager/FileDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/FileDrop.cpp @@ -101,19 +101,9 @@ namespace nexo::editor { assets::AssetImporter importer; assets::ImporterFileInput fileInput{path}; try { - if (assetType == assets::AssetType::TEXTURE) { - auto assetRef = importer.importAsset(location, fileInput); - if (assetRef) - LOG(NEXO_INFO, "Successfully imported texture: {}", location.getName().data()); - else - LOG(NEXO_ERROR, "Failed to import texture: {}", location.getPath().data()); - } else if (assetType == assets::AssetType::MODEL) { - auto assetRef = importer.importAsset(location, fileInput); - if (assetRef) - LOG(NEXO_INFO, "Successfully imported model: {}", location.getName().data()); - else - LOG(NEXO_ERROR, "Failed to import model: {}", location.getPath().data()); - } + auto assetRef = importer.importAssetAuto(location, fileInput); + if (!assetRef) + LOG(NEXO_ERROR, "Failed to import asset: {}", location.getPath().data()); } catch (const std::exception& e) { LOG(NEXO_ERROR, "Exception while importing {}: {}", location.getPath().data(), e.what()); } diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index f651bc456..f7cec5aad 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -41,12 +41,25 @@ namespace nexo::assets { GenericAssetRef AssetImporter::importAssetAuto(const AssetLocation& location, const ImporterInputVariant& inputVariant) { - for (const auto& importers: m_importers | std::views::values) { - if (importers.empty()) - continue; - if (const auto asset = importAssetTryImporters(location, inputVariant, importers)) - return asset; + // Temp fix to use all importers in priority order + // TODO: change the way we store importers, maybe stop using a map + std::vector allImporters; + std::vector allImportersDetails; + for (const auto& typeIdx: m_importers | std::views::keys) { + const auto& importers = m_importers.at(typeIdx); + const auto& importerDetails = m_importersDetails.at(typeIdx); + for (int importerIdx = 0; importerIdx < static_cast(importers.size()); ++importerIdx) { + const auto& details = importerDetails[importerIdx]; + const int priority = details.priority; + size_t k = 0; + for (; k < allImporters.size() && priority <= allImportersDetails[k].priority ; ++k); + allImporters.insert(allImporters.begin() + static_cast(k), importers[importerIdx]); + allImportersDetails.insert(allImportersDetails.begin() + static_cast(k), details); + } } + + if (const auto asset = importAssetTryImporters(location, inputVariant, allImporters)) + return asset; return GenericAssetRef::null(); } From 0cd2b58774240e052d2b2206106e90a47e958237 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Jul 2025 00:39:51 +0200 Subject: [PATCH 29/33] fix(drag-drop): @ is not needed anymore when no path is given --- engine/src/assets/AssetLocation.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index 706f920c8..87b0f578a 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -123,9 +123,10 @@ namespace nexo::assets { if (_packName) fullLocation += _packName->data() + "::"; fullLocation += _name.data(); - fullLocation += "@"; - if (!_path.empty()) + if (!_path.empty()) { + fullLocation += "@"; fullLocation += _path; + } return fullLocation; } From f5e308a0072129cb98ee91adb5174ae9278507ed Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Jul 2025 00:54:46 +0200 Subject: [PATCH 30/33] style(drag-drop): put asset drag drop handling of the scene in its own func --- .../SceneTreeWindow/DragDrop.cpp | 192 ++++++++---------- .../SceneTreeWindow/SceneTreeWindow.hpp | 11 +- 2 files changed, 96 insertions(+), 107 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index d22a51880..e6550eef2 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -35,13 +35,11 @@ namespace nexo::editor { void SceneTreeWindow::handleDragSource(const SceneObject& object) { - // Only allow dragging of entities, lights, and cameras if (object.type == SelectionType::SCENE || object.type == SelectionType::NONE) return; if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - // Create payload data SceneTreeDragDropPayload payload{ object.data.entity, object.data.sceneProperties.sceneId, @@ -50,12 +48,7 @@ namespace nexo::editor { object.uiName }; - // Set the payload ImGui::SetDragDropPayload("SCENE_TREE_NODE", &payload, sizeof(payload)); - - // Show preview text while dragging - ImGui::Text("Moving: %s", object.uiName.c_str()); - ImGui::EndDragDropSource(); } } @@ -71,9 +64,7 @@ namespace nexo::editor { const auto& payload = *static_cast(imguiPayload->Data); if (canAcceptDrop(object, payload)) - { - handleDrop(object, payload); - } + handleDropFromSceneTree(object, payload); } // Handle drops from asset manager @@ -82,93 +73,7 @@ namespace nexo::editor { IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); const auto& payload = *static_cast(assetPayload->Data); - // Handle different asset types - if (object.type == SelectionType::SCENE) - { - auto& app = Application::getInstance(); - auto& sceneManager = app.getSceneManager(); - - if (payload.type == assets::AssetType::MODEL) - { - auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!modelRef) - return; - if (auto model = modelRef.as(); model) - { - // Create entity with the model - ecs::Entity newEntity = EntityFactory3D::createModel( - model, - {0.0f, 0.0f, 0.0f}, // position - {1.0f, 1.0f, 1.0f}, // scale - {0.0f, 0.0f, 0.0f} // rotation - ); - - // Add to the scene - auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); - scene.addEntity(newEntity); - - // Record action for undo/redo TODO: Fix undo for models, it does not seem to work properly - auto action = std::make_unique(newEntity); - ActionManager::get().recordAction(std::move(action)); - } - } - else if (payload.type == assets::AssetType::TEXTURE) - { - auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!textureRef) - return; - if (auto texture = textureRef.as(); texture) - { - components::Material material; - material.albedoTexture = texture; - material.albedoColor = glm::vec4(1.0f); // White to show texture colors - - // Create billboard entity - ecs::Entity newEntity = EntityFactory3D::createBillboard( - {0.0f, 0.0f, 0.0f}, // position - {1.0f, 1.0f, 1.0f}, // size - material - ); - - // Add to the scene - auto& scene = sceneManager.getScene(object.data.sceneProperties.sceneId); - scene.addEntity(newEntity); - - // Record action for undo/redo - auto action = std::make_unique(newEntity); - ActionManager::get().recordAction(std::move(action)); - } - } - } - else if (object.type == SelectionType::ENTITY) - { - auto matCompOpt = Application::m_coordinator->tryGetComponent(object.data.entity); - if (!matCompOpt) - { ImGui::EndDragDropTarget(); return; } - - auto& matComp = matCompOpt->get(); - - if (payload.type == assets::AssetType::TEXTURE) - { - auto texRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (auto tex = texRef.as(); tex) - { - auto mat = matComp.material.lock(); - if (!mat) - return; - mat->getData()->albedoTexture = tex; - } - } - else if (payload.type == assets::AssetType::MATERIAL) - { - auto matRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (auto m = matRef.as(); m) - { - auto oldMat = matComp.material; - matComp.material = m; - } - } - } + handleDropFromAssetManager(object, payload); } ImGui::EndDragDropTarget(); @@ -195,17 +100,15 @@ namespace nexo::editor { } } - // Allow dropping entities onto scenes or other entities return true; } - void SceneTreeWindow::handleDrop(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) + void SceneTreeWindow::handleDropFromSceneTree(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) { auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); auto& coordinator = *Application::m_coordinator; - // Get the source scene auto& sourceScene = sceneManager.getScene(payload.sourceSceneId); if (dropTarget.type == SelectionType::SCENE) @@ -213,10 +116,8 @@ namespace nexo::editor { // Dropping onto a scene - move entity to that scene if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) { - // Remove from source scene sourceScene.removeEntity(payload.entity); - // Add to target scene auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); targetScene.addEntity(payload.entity); @@ -224,12 +125,9 @@ namespace nexo::editor { auto parentComp = coordinator.tryGetComponent(payload.entity); if (parentComp.has_value()) { - // Update parent's children list auto parentTransform = coordinator.tryGetComponent(parentComp->get().parent); if (parentTransform.has_value()) - { parentTransform->get().removeChild(payload.entity); - } coordinator.removeComponent(payload.entity); } @@ -333,4 +231,88 @@ namespace nexo::editor { } } + void SceneTreeWindow::handleDropFromAssetManager(const SceneObject& dropTarget, const AssetDragDropPayload& payload) + { + if (dropTarget.type == SelectionType::SCENE) + { + auto& app = Application::getInstance(); + auto& sceneManager = app.getSceneManager(); + + if (payload.type == assets::AssetType::MODEL) + { + auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!modelRef) + return; + if (auto model = modelRef.as(); model) + { + ecs::Entity newEntity = EntityFactory3D::createModel( + model, + {0.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f} + ); + auto& scene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); + scene.addEntity(newEntity); + + // Record action for undo/redo TODO: Fix undo for models, it does not seem to work properly + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + else if (payload.type == assets::AssetType::TEXTURE) + { + auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (!textureRef) + return; + if (auto texture = textureRef.as(); texture) + { + components::Material material; + material.albedoTexture = texture; + material.albedoColor = glm::vec4(1.0f); + + ecs::Entity newEntity = EntityFactory3D::createBillboard( + {0.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + material + ); + + auto& scene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); + scene.addEntity(newEntity); + + auto action = std::make_unique(newEntity); + ActionManager::get().recordAction(std::move(action)); + } + } + } + else if (dropTarget.type == SelectionType::ENTITY) + { + auto matCompOpt = Application::m_coordinator->tryGetComponent(dropTarget.data.entity); + if (!matCompOpt) + { ImGui::EndDragDropTarget(); return; } + + auto& matComp = matCompOpt->get(); + + if (payload.type == assets::AssetType::TEXTURE) + { + auto texRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (auto tex = texRef.as(); tex) + { + auto mat = matComp.material.lock(); + if (!mat) + return; + mat->getData()->albedoTexture = tex; + } + } + else if (payload.type == assets::AssetType::MATERIAL) + { + auto matRef = assets::AssetCatalog::getInstance().getAsset(payload.id); + if (auto m = matRef.as(); m) + { + auto oldMat = matComp.material; + matComp.material = m; + } + } + } + } + } // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index e4abb0e70..95a87746f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -439,11 +439,18 @@ namespace nexo::editor void handleDropTarget(const SceneObject& object); /** - * @brief Processes the drop operation. + * @brief Processes the drop operation from the scene tree itself. * @param dropTarget The target scene object receiving the drop. * @param payload The drag-and-drop payload data. */ - void handleDrop(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + void handleDropFromSceneTree(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + + /** + * @brief Processes the drop operation from the asset manager. + * @param dropTarget The target scene object receiving the drop. + * @param payload The drag-and-drop payload data. + */ + void handleDropFromAssetManager(const SceneObject& dropTarget, const struct AssetDragDropPayload& payload); /** * @brief Validates if a drop operation is allowed. From c08fcae61c57134771ee84821aff10b95298e752 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Jul 2025 01:24:59 +0200 Subject: [PATCH 31/33] fix(drag-drop): fix tests --- tests/engine/assets/AssetImporterContext.test.cpp | 2 +- tests/engine/assets/AssetLocation.test.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/engine/assets/AssetImporterContext.test.cpp b/tests/engine/assets/AssetImporterContext.test.cpp index 97451c885..36b3cc186 100644 --- a/tests/engine/assets/AssetImporterContext.test.cpp +++ b/tests/engine/assets/AssetImporterContext.test.cpp @@ -209,7 +209,7 @@ namespace nexo::assets { TEST_F(AssetImporterContextTest, DefaultContextValues) { - EXPECT_EQ(context.location.getFullLocation(), "default@"); + EXPECT_EQ(context.location.getFullLocation(), "default"); EXPECT_EQ(context.getMainAsset(), nullptr); EXPECT_TRUE(context.getDependencies().empty()); EXPECT_TRUE(context.getParameters().is_null()); diff --git a/tests/engine/assets/AssetLocation.test.cpp b/tests/engine/assets/AssetLocation.test.cpp index 985e9b218..d2ba86e03 100644 --- a/tests/engine/assets/AssetLocation.test.cpp +++ b/tests/engine/assets/AssetLocation.test.cpp @@ -51,7 +51,7 @@ namespace nexo::assets { EXPECT_EQ(location.getPackName()->get(), "myPack"); EXPECT_EQ(location.getName(), "myAsset"); EXPECT_EQ(location.getPath(), ""); - EXPECT_EQ(location.getFullLocation(), fullLocation + "@"); // For proper handling in the asset mananger we always add @ + EXPECT_EQ(location.getFullLocation(), fullLocation); } TEST(AssetLocationTest, InvalidLocationEmpty) @@ -111,7 +111,7 @@ namespace nexo::assets { AssetPackName packName("myPack"); AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test@"); + EXPECT_EQ(location.getFullLocation(), "test"); location.setLocation(name, path, packName); ASSERT_TRUE(location.getPackName().has_value()); @@ -127,7 +127,7 @@ namespace nexo::assets { const std::string path = "path/to/asset"; AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test@"); + EXPECT_EQ(location.getFullLocation(), "test"); location.setLocation(name, path); EXPECT_FALSE(location.getPackName().has_value()); @@ -142,14 +142,14 @@ namespace nexo::assets { AssetPackName packName("myPack"); AssetLocation location("test"); - EXPECT_EQ(location.getFullLocation(), "test@"); + EXPECT_EQ(location.getFullLocation(), "test"); location.setLocation(name, "", packName); ASSERT_TRUE(location.getPackName().has_value()); EXPECT_EQ(location.getPackName()->get(), "myPack"); EXPECT_EQ(location.getName(), "myAsset"); EXPECT_EQ(location.getPath(), ""); - EXPECT_EQ(location.getFullLocation(), "myPack::myAsset@"); + EXPECT_EQ(location.getFullLocation(), "myPack::myAsset"); } TEST(AssetLocationTest, SetName) From 91034aa311a692e2b4282f6a4fee379cadfe7729 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Jul 2025 01:25:30 +0200 Subject: [PATCH 32/33] style(drag-drop): rename normalizePath to make it clear we remove prefix slash --- common/Path.cpp | 2 +- common/Path.hpp | 2 +- engine/src/assets/AssetLocation.cpp | 2 +- engine/src/assets/AssetLocation.hpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/Path.cpp b/common/Path.cpp index 1939ae3fb..f6585a125 100644 --- a/common/Path.cpp +++ b/common/Path.cpp @@ -39,7 +39,7 @@ namespace nexo { m_executableRootPathCached.clear(); } - std::string normalizePath(const std::string &rawPath) + std::string normalizePathAndRemovePrefixSlash(const std::string &rawPath) { namespace fs = std::filesystem; fs::path p = fs::path(rawPath).lexically_normal(); diff --git a/common/Path.hpp b/common/Path.hpp index 28c122073..636fd7682 100644 --- a/common/Path.hpp +++ b/common/Path.hpp @@ -52,5 +52,5 @@ namespace nexo { inline static std::filesystem::path m_executableRootPathCached; }; - std::string normalizePath(const std::string &rawPath); + std::string normalizePathAndRemovePrefixSlash(const std::string &rawPath); } // namespace nexo diff --git a/engine/src/assets/AssetLocation.cpp b/engine/src/assets/AssetLocation.cpp index 301341fe1..d7a63d63e 100644 --- a/engine/src/assets/AssetLocation.cpp +++ b/engine/src/assets/AssetLocation.cpp @@ -24,7 +24,7 @@ namespace nexo::assets { ) { _name = name; - _path = normalizePath(path); + _path = normalizePathAndRemovePrefixSlash(path); _packName = packName; } } // namespace nexo::assets diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index 87b0f578a..618b0e6a7 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -62,7 +62,7 @@ namespace nexo::assets { */ AssetLocation& setPath(const std::string& path) { - _path = normalizePath(path); + _path = normalizePathAndRemovePrefixSlash(path); return *this; } @@ -155,7 +155,7 @@ namespace nexo::assets { std::string extractedPath; parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); - extractedPath = normalizePath(extractedPath); + extractedPath = normalizePathAndRemovePrefixSlash(extractedPath); try { _name = AssetName(extractedAssetName); From 24761b9fec4933d620d0c4e4c9ac505f45d5fc24 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Jul 2025 02:13:56 +0200 Subject: [PATCH 33/33] feat(drag-drop): add moveAsset func to AssetCatalog --- .../src/DocumentWindows/AssetManager/Show.cpp | 13 +++---------- engine/src/assets/Asset.hpp | 2 -- engine/src/assets/AssetCatalog.cpp | 14 ++++++++++++++ engine/src/assets/AssetCatalog.hpp | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 801870cec..602c1b93e 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -237,12 +237,7 @@ namespace nexo::editor { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; - - std::shared_ptr asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock(); - if (asset) { - assets::AssetMetadata &metadata = asset->getMetadata(); - metadata.location.setPath(folderPath); - } + assets::AssetCatalog::getInstance().moveAsset(data->id, folderPath); } ImGui::EndDragDropTarget(); } @@ -493,8 +488,7 @@ namespace nexo::editor { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; - if (auto asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock()) - asset->getMetadata().location.setPath(""); + assets::AssetCatalog::getInstance().moveAsset(data->id, ""); } ImGui::EndDragDropTarget(); } @@ -520,8 +514,7 @@ namespace nexo::editor { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { const AssetDragDropPayload* data = (const AssetDragDropPayload*)payload->Data; - if (auto asset = assets::AssetCatalog::getInstance().getAsset(data->id).lock()) - asset->getMetadata().location.setPath(fullPath); + assets::AssetCatalog::getInstance().moveAsset(data->id, fullPath); } ImGui::EndDragDropTarget(); } diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 10174a591..aaf96826f 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -149,7 +149,6 @@ namespace nexo::assets { public: virtual ~IAsset() = default; - [[nodiscard]] virtual AssetMetadata& getMetadata() = 0; [[nodiscard]] virtual const AssetMetadata& getMetadata() const = 0; [[nodiscard]] virtual AssetType getType() const = 0; [[nodiscard]] virtual AssetID getID() const = 0; @@ -191,7 +190,6 @@ namespace nexo::assets { ~Asset() override = default; - [[nodiscard]] virtual AssetMetadata& getMetadata() { return m_metadata; }; [[nodiscard]] const AssetMetadata& getMetadata() const override { return m_metadata; } [[nodiscard]] AssetType getType() const override { return getMetadata().type; } [[nodiscard]] AssetID getID() const override { return getMetadata().id; } diff --git a/engine/src/assets/AssetCatalog.cpp b/engine/src/assets/AssetCatalog.cpp index b2736d0ac..1a8186ed9 100644 --- a/engine/src/assets/AssetCatalog.cpp +++ b/engine/src/assets/AssetCatalog.cpp @@ -34,6 +34,20 @@ namespace nexo::assets { } } + void AssetCatalog::moveAsset(const GenericAssetRef &asset, const std::string &path) + { + if (const auto assetData = asset.lock()) + moveAsset(assetData->getID(), path); + } + + void AssetCatalog::moveAsset(AssetID id, const std::string &path) + { + if (!m_assets.contains(id)) + return; + auto asset = m_assets.at(id); + asset->m_metadata.location.setPath(path); + } + GenericAssetRef AssetCatalog::getAsset(AssetID id) const { if (!m_assets.contains(id)) diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index cd8f2e003..c54f35878 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -24,6 +24,7 @@ #include "Assets/Texture/Texture.hpp" #include "Assets/Texture/Texture.hpp" +#include "assets/AssetRef.hpp" namespace nexo::assets { @@ -80,6 +81,21 @@ namespace nexo::assets { */ void deleteAsset(const GenericAssetRef& asset); + /** + * @brief Moves an asset to another location. + * @param asset The asset to move. + * @param path The new location for the asset. + */ + void moveAsset(const GenericAssetRef &asset, const std::string &path); + + /** + * @brief Moves an asset to another location. + * @param id The ID of the asset to move. + * @param path The new location for the asset. + */ + void moveAsset(AssetID id, const std::string &path); + + /** * @brief Get an asset by its ID. * @param id The ID of the asset to get. @@ -180,6 +196,8 @@ namespace nexo::assets { return assetRef.template as(); } + + private: std::unordered_map> m_assets; };