Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ee70fbd
fix: resolve AssetDragDropPayload corruption by using fixed-size char…
jcardonne Jul 6, 2025
61be7ea
fix: toolbar margin wrong on high DPI
Thyodas Jul 8, 2025
c6f0c69
fix(drag-drop): fix asset filtering
iMeaNz Jul 12, 2025
7f753bf
feat(drag-drop): now possible to file drop onto a folder
iMeaNz Jul 12, 2025
9493ebb
feat(drag-drop): now normalize the path in the constructor of asset l…
iMeaNz Jul 12, 2025
32a2b3e
feat(drag-drop): now possible to drag and drop an asset inside a subf…
iMeaNz Jul 12, 2025
6f485a2
feat(drag-drop): add util function to normalize paths
iMeaNz Jul 12, 2025
83ad8b5
fix(drag-drop): fix model drag and drop in the scene hierarchy window
iMeaNz Jul 12, 2025
096ea86
refactor(drag-drop): improve texture creation when drag and dropping
iMeaNz Jul 12, 2025
889c85d
feat(drag-drop): add drag and drop from asset manager to editor scene
iMeaNz Jul 12, 2025
174f6db
chore(drag-drop): add source file
iMeaNz Jul 12, 2025
16aa0fb
feat(drag-drop): add texture preview when dragging + fix: now clears …
iMeaNz Jul 12, 2025
129a2c4
feat(drag-drop): highlight hovered entity when dragging over editor s…
iMeaNz Jul 12, 2025
43c6c34
fix(drag-drop): fix entities not moving when clicking outside editor …
iMeaNz Jul 12, 2025
bba3827
fix(drag-drop): now only remove selected tag if an entiy was being ho…
iMeaNz Jul 12, 2025
4aa6c93
fix(drag-drop): parent-child creation when dragging now works properl…
iMeaNz Jul 12, 2025
eb28fc5
fix(drag-drop): normalize path in setPath too
iMeaNz Jul 13, 2025
c1409b4
feat(drag-drop): now possible to drag and drop on breadcrumbs
iMeaNz Jul 13, 2025
884c224
feat(drag-drop): drag drop texture/material on entity in scene tree
iMeaNz Jul 15, 2025
65ce3a0
refactor(drag-drop): file drop code cleaning
iMeaNz Jul 15, 2025
6396821
fix(drag-drop): fix compilation
iMeaNz Jul 16, 2025
9361f9f
fix(drag-drop): fix what rebase broke
iMeaNz Jul 24, 2025
1672339
fix(drag-drop): fix tests to comply with the way we now handle asset …
iMeaNz Jul 24, 2025
a2e0688
fix(drag-drop): fix code rabbit issues
iMeaNz Jul 24, 2025
1ce70a3
refactor(drag-drop): add source file for path utils + use filesystem …
iMeaNz Jul 24, 2025
fb494e8
fix(drag-drop): use std::string in asset drag drop payload instead of…
iMeaNz Jul 24, 2025
6589c39
style(drag-drop): use switch instead of ifs
iMeaNz Jul 24, 2025
f3bc8bd
fix(drag-drop): use importAssetAuto in drag and drop + temp fix for a…
Thyodas Jul 24, 2025
0cd2b58
fix(drag-drop): @ is not needed anymore when no path is given
iMeaNz Jul 24, 2025
f5e308a
style(drag-drop): put asset drag drop handling of the scene in its ow…
iMeaNz Jul 24, 2025
c08fcae
fix(drag-drop): fix tests
iMeaNz Jul 24, 2025
91034aa
style(drag-drop): rename normalizePath to make it clear we remove pre…
iMeaNz Jul 24, 2025
24761b9
feat(drag-drop): add moveAsset func to AssetCatalog
iMeaNz Jul 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions common/Path.cpp
Original file line number Diff line number Diff line change
@@ -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 normalizePathAndRemovePrefixSlash(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);
}
}
25 changes: 4 additions & 21 deletions common/Path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -69,5 +52,5 @@ namespace nexo {
inline static std::filesystem::path m_executableRootPathCached;
};


std::string normalizePathAndRemovePrefixSlash(const std::string &rawPath);
} // namespace nexo
4 changes: 4 additions & 0 deletions editor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,12 +48,14 @@ 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
Comment thread
iMeaNz marked this conversation as resolved.
editor/src/DocumentWindows/AssetManager/Init.cpp
editor/src/DocumentWindows/AssetManager/Show.cpp
editor/src/DocumentWindows/AssetManager/Shutdown.cpp
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
Expand All @@ -77,6 +80,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
Expand Down
29 changes: 27 additions & 2 deletions editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
#include <imgui.h>
#include <assets/AssetRef.hpp>
#include "utils/TransparentStringHash.hpp"
#include <core/event/WindowEvent.hpp>
#include "assets/Asset.hpp"

namespace nexo::editor {

class AssetManagerWindow final : public ADocumentWindow {
class AssetManagerWindow final : public ADocumentWindow, LISTENS_TO(event::EventFileDrop) {
public:
using ADocumentWindow::ADocumentWindow;

Expand All @@ -30,6 +32,8 @@ namespace nexo::editor {
void show() override;
void update() override;

void handleEvent(event::EventFileDrop& event) override;

private:
struct LayoutSettings {
struct LayoutSizes {
Expand Down Expand Up @@ -74,7 +78,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<std::pair<std::string, std::string>> m_folderStructure; // Pairs of (path, name)
char m_searchBuffer[256] = "";

Expand Down Expand Up @@ -110,5 +115,25 @@ namespace nexo::editor {
const ImVec2& itemPos,
const ImVec2& itemSize
);

std::vector<std::string> m_pendingDroppedFiles;
bool m_showDropIndicator = false;

void handleDroppedFiles();
const assets::AssetLocation getAssetLocation(const std::filesystem::path &path) const;
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
assets::AssetID id; ///< ID of the asset
std::string path; ///< Path to the asset
std::string name; ///< Display name of the asset
};
}
111 changes: 111 additions & 0 deletions editor/src/DocumentWindows/AssetManager/FileDrop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//// 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: 30/06/2025
// Description: Implementation of file drop handling for asset manager
//
///////////////////////////////////////////////////////////////////////////////

#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"
#include <filesystem>
#include <algorithm>

namespace nexo::editor {

static assets::AssetType getAssetTypeFromExtension(const std::string &extension)
{
static const std::set<std::string> 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<std::string> modelExtensions = {
".gltf", ".glb", ".fbx", ".obj", ".dae", ".3ds", ".stl", ".ply", ".blend", ".x3d", ".ifc"
};
if (modelExtensions.contains(extension))
return assets::AssetType::MODEL;
return assets::AssetType::UNKNOWN;
}
Comment on lines +27 to +40

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use AssetImporter::importAssetAuto or other appropriate methods with AssetImporter to import the asset without having to check on your side what's the type

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I did something wrong but I tried to do that :

assets::AssetLocation location = getAssetLocation(path);

assets::AssetImporter importer;
assets::ImporterFileInput fileInput{path};
try {
    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());
}

And it does not work, it seems to be trying to import a model (i get the log in the output)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give the log and the path of the file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2025-07-24 23:10:29.742 (   4.591s) [main thread     ]           FileDrop.cpp:51       3| Creating asset location: nexo@ (current folder: '', hovered: '')

"/home/mehdy/Documents/Nexo/nexo/game-engine/resources/nexo.png" <---- This is the path i'm sending to the importer

2025-07-24 23:10:29.755 (   4.604s) [main thread     ]      ModelImporter.cpp:317   INFO| Loaded material: Diffuse = No, Normal = No, Metallic = No, Roughness = No

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it works? I just tested it works, it justs logs an error because it tries every importer to see if they can import the file. We just need to remove this log if we try every importer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You tried with a model ?
Because with a texture it does not work, it imports a model instead

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image image

This is what i get when i try to import the file on the right

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with a texture, it works, the texture appears in the asset manager window. It tries to import a model because it tries to import EVERY type until it finds an importer that works. We just need to suppress this log in the case where we try every importers in importAssetAuto

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send me the full function you used


const assets::AssetLocation AssetManagerWindow::getAssetLocation(const std::filesystem::path &path) const
{
std::string assetName = path.stem().string();
std::filesystem::path folderPath;
std::string targetFolder = !m_hoveredFolder.empty() ? m_hoveredFolder : m_currentFolder;

std::string assetPath = targetFolder;
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)
{
m_pendingDroppedFiles.insert(m_pendingDroppedFiles.end(),
event.files.begin(),
event.files.end());
}

void AssetManagerWindow::handleDroppedFiles()
{
if (m_pendingDroppedFiles.empty())
return;

for (const auto& filePath : m_pendingDroppedFiles)
importDroppedFile(filePath);
m_pendingDroppedFiles.clear();

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;
}

std::string extension = path.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);

assets::AssetType assetType = getAssetTypeFromExtension(extension);
if (assetType == assets::AssetType::UNKNOWN) {
LOG(NEXO_WARN, "Unsupported file type: {}", extension);
return;
}

assets::AssetLocation location = getAssetLocation(path);

assets::AssetImporter importer;
assets::ImporterFileInput fileInput{path};
try {
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());
}
}
}
57 changes: 27 additions & 30 deletions editor/src/DocumentWindows/AssetManager/FolderTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::less<>> uniqueFolderPaths;

std::unordered_set<std::string> 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();
Expand Down
Loading