From 1e3eb85d69935fd8cf7522d2deed3d1bd5527ef9 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Sun, 19 Apr 2026 21:15:41 +0200 Subject: [PATCH 1/3] feat(editor): expose embedded model assets in asset browser --- .../src/OvEditor/Panels/AssetBrowser.cpp | 344 ++++++++++++++---- 1 file changed, 283 insertions(+), 61 deletions(-) diff --git a/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp b/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp index 1434881a8..9a39c3bd6 100644 --- a/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp +++ b/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp @@ -9,15 +9,17 @@ #include #include #include +#include #include #include +#include #include #include -#include +#include #include -#include #include +#include #include @@ -29,6 +31,8 @@ #include #include +#include + #include #include #include @@ -761,6 +765,108 @@ namespace } }; + class EmbeddedFileContextualMenu : public OvUI::Plugins::ContextualMenu + { + public: + EmbeddedFileContextualMenu(std::string p_resourcePath) : m_resourcePath(std::move(p_resourcePath)) {} + + virtual void Execute(OvUI::Plugins::EPluginExecutionContext p_context) override + { + if (!m_widgets.empty()) + { + OvUI::Plugins::ContextualMenu::Execute(p_context); + } + } + + protected: + void OpenInAssetView() const + { + OvCore::Helpers::GUIHelpers::Open(m_resourcePath); + } + + protected: + std::string m_resourcePath; + }; + + class EmbeddedTextureContextualMenu : public EmbeddedFileContextualMenu + { + public: + EmbeddedTextureContextualMenu(const std::string& p_resourcePath) : EmbeddedFileContextualMenu(p_resourcePath) {} + + void CreateList() + { + auto& previewAction = CreateWidget("Preview"); + previewAction.ClickedEvent += [this] { + OpenInAssetView(); + }; + } + }; + + class EmbeddedMaterialContextualMenu : public EmbeddedFileContextualMenu + { + public: + EmbeddedMaterialContextualMenu(const std::string& p_resourcePath) : EmbeddedFileContextualMenu(p_resourcePath) {} + + void CreateList() + { + auto& inspectAction = CreateWidget("Inspect"); + inspectAction.ClickedEvent += [this] { + auto& materialManager = OVSERVICE(OvCore::ResourceManagement::MaterialManager); + if (auto* material = materialManager.GetResource(m_resourcePath)) + { + auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor"); + EDITOR_EXEC(DelayAction([material, &materialEditor]() { + materialEditor.SetTarget(*material); + materialEditor.Open(); + materialEditor.Focus(); + })); + } + }; + + auto& previewAction = CreateWidget("Preview"); + previewAction.ClickedEvent += [this] { + OpenInAssetView(); + }; + } + }; + + void CreateEmbeddedModelAssetEntry( + OvUI::Widgets::Layout::TreeNode& p_root, + const std::string& p_resourcePath, + OvTools::Utils::PathParser::EFileType p_fileType + ) + { + const auto embeddedPath = OvRendering::Resources::Parsers::ParseEmbeddedAssetPath(p_resourcePath); + if (!embeddedPath) + { + return; + } + + const std::string itemName = embeddedPath->assetName; + auto& itemGroup = p_root.CreateWidget(); + const uint32_t iconTextureID = EDITOR_CONTEXT(editorResources)->GetFileIcon(itemName)->GetTexture().GetID(); + itemGroup.CreateWidget(iconTextureID, OvMaths::FVector2{ 16, 16 }).lineBreak = false; + + auto& clickableText = itemGroup.CreateWidget(itemName); + + if (p_fileType == OvTools::Utils::PathParser::EFileType::TEXTURE) + { + auto& texturePreview = clickableText.AddPlugin(); + texturePreview.SetPath(p_resourcePath); + + auto& contextMenu = clickableText.AddPlugin(p_resourcePath); + contextMenu.CreateList(); + } + else if (p_fileType == OvTools::Utils::PathParser::EFileType::MATERIAL) + { + auto& contextMenu = clickableText.AddPlugin(p_resourcePath); + contextMenu.CreateList(); + } + clickableText.DoubleClickedEvent += [resourcePath = p_resourcePath] { + OvCore::Helpers::GUIHelpers::Open(resourcePath); + }; + } + FileContextualMenu& CreateFileContextualMenu( OvUI::Widgets::AWidget& p_root, OvTools::Utils::PathParser::EFileType p_fileType, @@ -1104,86 +1210,202 @@ void OvEditor::Panels::AssetBrowser::ConsiderItem(OvUI::Widgets::Layout::TreeNod } else { - auto& clickableText = itemGroup.CreateWidget(itemname); + if (fileType == OvTools::Utils::PathParser::EFileType::MODEL) + { + auto& treeNode = itemGroup.CreateWidget(itemname); - FileContextualMenu& contextMenu = CreateFileContextualMenu( - clickableText, - fileType, - path, - protectedItem - ); + FileContextualMenu& contextMenu = CreateFileContextualMenu( + treeNode, + fileType, + path, + protectedItem + ); - contextMenu.CreateList(); + contextMenu.CreateList(); - contextMenu.DestroyedEvent += [&itemGroup](std::filesystem::path p_deletedPath) { - itemGroup.Destroy(); + contextMenu.DestroyedEvent += [&itemGroup](std::filesystem::path p_deletedPath) { + itemGroup.Destroy(); - if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_deletedPath) // Modify current scene source path if the renamed file is the current scene - { - EDITOR_CONTEXT(sceneManager).ForgetCurrentSceneSourcePath(); - } - }; + if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_deletedPath) + { + EDITOR_CONTEXT(sceneManager).ForgetCurrentSceneSourcePath(); + } + }; - auto& ddSource = clickableText.AddPlugin>>( - "File", - OvTools::Utils::PathParser::GetFriendlyPath(resourceFormatPath), - std::make_pair(resourceFormatPath, &itemGroup) - ); + auto& ddSource = treeNode.AddPlugin>>( + "File", + OvTools::Utils::PathParser::GetFriendlyPath(resourceFormatPath), + std::make_pair(resourceFormatPath, &itemGroup) + ); - contextMenu.RenamedEvent += [&ddSource, &clickableText, fileType]( - std::filesystem::path p_prev, - std::filesystem::path p_newPath - ) { - if (p_newPath != p_prev) - { - if (!std::filesystem::exists(p_newPath)) + contextMenu.RenamedEvent += [&ddSource, &treeNode, fileType]( + std::filesystem::path p_prev, + std::filesystem::path p_newPath + ) { + if (p_newPath != p_prev) { - RenameAsset(p_prev, p_newPath); - const auto elementName = p_newPath.filename(); - ddSource.data.first = (std::filesystem::path{ ddSource.data.first }.parent_path() / elementName).string(); - ddSource.tooltip = OvTools::Utils::PathParser::GetFriendlyPath(ddSource.data.first); + if (!std::filesystem::exists(p_newPath)) + { + RenameAsset(p_prev, p_newPath); + const auto elementName = p_newPath.filename(); + ddSource.data.first = (std::filesystem::path{ ddSource.data.first }.parent_path() / elementName).string(); + ddSource.tooltip = OvTools::Utils::PathParser::GetFriendlyPath(ddSource.data.first); - EDITOR_EXEC(PropagateFileRename(p_prev.string(), p_newPath.string())); + EDITOR_EXEC(PropagateFileRename(p_prev.string(), p_newPath.string())); - if (fileType != OvTools::Utils::PathParser::EFileType::SCRIPT) - { - if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_prev) // Modify current scene source path if the renamed file is the current scene + if (fileType != OvTools::Utils::PathParser::EFileType::SCRIPT) { - EDITOR_CONTEXT(sceneManager).StoreCurrentSceneSourcePath(p_newPath.string()); + if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_prev) + { + EDITOR_CONTEXT(sceneManager).StoreCurrentSceneSourcePath(p_newPath.string()); + } } - } - clickableText.content = elementName.string(); + treeNode.name = elementName.string(); + } + else + { + using namespace OvWindowing::Dialogs; + + MessageBox errorMessage( + "File already exists", + "You can't rename this file because the given name is already taken", + MessageBox::EMessageType::ERROR, + MessageBox::EButtonLayout::OK + ); + } } - else + }; + + contextMenu.DuplicateEvent += [this, p_root, p_isEngineItem](std::filesystem::path newItem) { + EDITOR_EXEC(DelayAction(std::bind(&AssetBrowser::ConsiderItem, this, p_root, std::filesystem::directory_entry{ newItem }, p_isEngineItem, false), 0)); + }; + + treeNode.DoubleClickedEvent += [&contextMenu, p_isEngineItem] { + OvCore::Helpers::GUIHelpers::Open(EDITOR_EXEC(GetResourcePath(contextMenu.filePath.string(), p_isEngineItem))); + }; + + treeNode.OpenedEvent += [this, &treeNode, &contextMenu, p_isEngineItem] { + treeNode.RemoveAllWidgets(); + + const std::string modelResourcePath = EDITOR_EXEC(GetResourcePath(contextMenu.filePath.string(), p_isEngineItem)); + const auto* model = OVSERVICE(OvCore::ResourceManagement::ModelManager).GetResource(modelResourcePath); + if (!model) { - using namespace OvWindowing::Dialogs; + return; + } - MessageBox errorMessage( - "File already exists", - "You can't rename this file because the given name is already taken", - MessageBox::EMessageType::ERROR, - MessageBox::EButtonLayout::OK - ); + const auto& embeddedMaterials = model->GetEmbeddedMaterials(); + for (size_t materialIndex = 0; materialIndex < embeddedMaterials.size(); ++materialIndex) + { + const std::string materialPath = OvRendering::Resources::Parsers::MakeEmbeddedMaterialPath(modelResourcePath, static_cast(materialIndex)); + CreateEmbeddedModelAssetEntry(treeNode, materialPath, OvTools::Utils::PathParser::EFileType::MATERIAL); } - } - }; - contextMenu.DuplicateEvent += [this, &clickableText, p_root, p_isEngineItem] (std::filesystem::path newItem) { - EDITOR_EXEC(DelayAction(std::bind(&AssetBrowser::ConsiderItem, this, p_root, std::filesystem::directory_entry{ newItem }, p_isEngineItem, false), 0)); - }; + const auto& embeddedTextures = model->GetEmbeddedTextures(); + for (size_t textureIndex = 0; textureIndex < embeddedTextures.size(); ++textureIndex) + { + const auto& textureData = embeddedTextures[textureIndex]; - if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE) - { - auto& texturePreview = clickableText.AddPlugin(); - texturePreview.SetPath(resourceFormatPath); - } + using ESourceType = OvRendering::Resources::EmbeddedTextureData::ESourceType; + if (textureData.sourceType == ESourceType::EXTERNAL_FILE) + { + continue; + } - if (fileType != OvTools::Utils::PathParser::EFileType::UNKNOWN) + const std::string extension = textureData.extension.empty() ? "bin" : textureData.extension; + const std::string texturePath = OvRendering::Resources::Parsers::MakeEmbeddedTexturePath(modelResourcePath, static_cast(textureIndex), extension); + CreateEmbeddedModelAssetEntry(treeNode, texturePath, OvTools::Utils::PathParser::EFileType::TEXTURE); + } + }; + + treeNode.ClosedEvent += [&treeNode] { + treeNode.RemoveAllWidgets(); + }; + } + else { - clickableText.DoubleClickedEvent += [&contextMenu, p_isEngineItem] { - OvCore::Helpers::GUIHelpers::Open(EDITOR_EXEC(GetResourcePath(contextMenu.filePath.string(), p_isEngineItem))); + auto& clickableText = itemGroup.CreateWidget(itemname); + + FileContextualMenu& contextMenu = CreateFileContextualMenu( + clickableText, + fileType, + path, + protectedItem + ); + + contextMenu.CreateList(); + + contextMenu.DestroyedEvent += [&itemGroup](std::filesystem::path p_deletedPath) { + itemGroup.Destroy(); + + if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_deletedPath) // Modify current scene source path if the renamed file is the current scene + { + EDITOR_CONTEXT(sceneManager).ForgetCurrentSceneSourcePath(); + } }; + + auto& ddSource = clickableText.AddPlugin>>( + "File", + OvTools::Utils::PathParser::GetFriendlyPath(resourceFormatPath), + std::make_pair(resourceFormatPath, &itemGroup) + ); + + contextMenu.RenamedEvent += [&ddSource, &clickableText, fileType]( + std::filesystem::path p_prev, + std::filesystem::path p_newPath + ) { + if (p_newPath != p_prev) + { + if (!std::filesystem::exists(p_newPath)) + { + RenameAsset(p_prev, p_newPath); + const auto elementName = p_newPath.filename(); + ddSource.data.first = (std::filesystem::path{ ddSource.data.first }.parent_path() / elementName).string(); + ddSource.tooltip = OvTools::Utils::PathParser::GetFriendlyPath(ddSource.data.first); + + EDITOR_EXEC(PropagateFileRename(p_prev.string(), p_newPath.string())); + + if (fileType != OvTools::Utils::PathParser::EFileType::SCRIPT) + { + if (EDITOR_CONTEXT(sceneManager).GetCurrentSceneSourcePath() == p_prev) // Modify current scene source path if the renamed file is the current scene + { + EDITOR_CONTEXT(sceneManager).StoreCurrentSceneSourcePath(p_newPath.string()); + } + } + + clickableText.content = elementName.string(); + } + else + { + using namespace OvWindowing::Dialogs; + + MessageBox errorMessage( + "File already exists", + "You can't rename this file because the given name is already taken", + MessageBox::EMessageType::ERROR, + MessageBox::EButtonLayout::OK + ); + } + } + }; + + contextMenu.DuplicateEvent += [this, &clickableText, p_root, p_isEngineItem] (std::filesystem::path newItem) { + EDITOR_EXEC(DelayAction(std::bind(&AssetBrowser::ConsiderItem, this, p_root, std::filesystem::directory_entry{ newItem }, p_isEngineItem, false), 0)); + }; + + if (fileType == OvTools::Utils::PathParser::EFileType::TEXTURE) + { + auto& texturePreview = clickableText.AddPlugin(); + texturePreview.SetPath(resourceFormatPath); + } + + if (fileType != OvTools::Utils::PathParser::EFileType::UNKNOWN) + { + clickableText.DoubleClickedEvent += [&contextMenu, p_isEngineItem] { + OvCore::Helpers::GUIHelpers::Open(EDITOR_EXEC(GetResourcePath(contextMenu.filePath.string(), p_isEngineItem))); + }; + } } } } From 31ee5ea48f3636e9965b7fac03eecaa4c362fe72 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Sun, 19 Apr 2026 21:15:49 +0200 Subject: [PATCH 2/3] fix(editor): open embedded assets with dedicated inspect behavior --- Sources/OvEditor/src/OvEditor/Core/Editor.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp index 6c31c927d..b4ec661c3 100644 --- a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp +++ b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp @@ -10,12 +10,14 @@ #include #include +#include #include #include -#include -#include +#include + #include +#include #include #include @@ -85,6 +87,9 @@ void OvEditor::Core::Editor::SetupUI() using EFileType = OvTools::Utils::PathParser::EFileType; const auto fileType = OvTools::Utils::PathParser::GetFileType(p_path); const auto path = OvTools::Utils::PathParser::MakeNonWindowsStyle(p_path); + const auto embeddedAssetPath = ParseEmbeddedAssetPath(path); + const bool isEmbeddedTexture = embeddedAssetPath && ParseEmbeddedTextureIndex(embeddedAssetPath->assetName).has_value(); + const bool isEmbeddedMaterial = embeddedAssetPath && ParseEmbeddedMaterialIndex(embeddedAssetPath->assetName).has_value(); auto openInAssetView = [&](auto* p_resource) { @@ -95,7 +100,7 @@ void OvEditor::Core::Editor::SetupUI() assetView.Focus(); }; - if (fileType == EFileType::TEXTURE) + if (fileType == EFileType::TEXTURE || isEmbeddedTexture) { openInAssetView(OVSERVICE(TextureManager).GetResource(path)); } @@ -103,11 +108,11 @@ void OvEditor::Core::Editor::SetupUI() { openInAssetView(OVSERVICE(ModelManager).GetResource(path)); } - else if (fileType == EFileType::MATERIAL) + else if (fileType == EFileType::MATERIAL || isEmbeddedMaterial) { auto* material = OVSERVICE(MaterialManager).GetResource(path); openInAssetView(material); - if (material) + if (material && !isEmbeddedMaterial) { auto& materialEditor = EDITOR_PANEL(MaterialEditor, "Material Editor"); EDITOR_EXEC(DelayAction([material, &materialEditor]() { From d862ce12e7e8b6962bbed4aa5dbfb9d919fd8d60 Mon Sep 17 00:00:00 2001 From: Gopmyc Date: Mon, 20 Apr 2026 00:14:33 +0200 Subject: [PATCH 3/3] fix(editor): align embedded asset menus with review feedback --- Sources/OvEditor/src/OvEditor/Core/Editor.cpp | 5 +- .../src/OvEditor/Panels/AssetBrowser.cpp | 73 +++++-------------- 2 files changed, 19 insertions(+), 59 deletions(-) diff --git a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp index b4ec661c3..9f4289586 100644 --- a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp +++ b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp @@ -89,7 +89,6 @@ void OvEditor::Core::Editor::SetupUI() const auto path = OvTools::Utils::PathParser::MakeNonWindowsStyle(p_path); const auto embeddedAssetPath = ParseEmbeddedAssetPath(path); const bool isEmbeddedTexture = embeddedAssetPath && ParseEmbeddedTextureIndex(embeddedAssetPath->assetName).has_value(); - const bool isEmbeddedMaterial = embeddedAssetPath && ParseEmbeddedMaterialIndex(embeddedAssetPath->assetName).has_value(); auto openInAssetView = [&](auto* p_resource) { @@ -108,11 +107,11 @@ void OvEditor::Core::Editor::SetupUI() { openInAssetView(OVSERVICE(ModelManager).GetResource(path)); } - else if (fileType == EFileType::MATERIAL || isEmbeddedMaterial) + else if (fileType == EFileType::MATERIAL) { auto* material = OVSERVICE(MaterialManager).GetResource(path); openInAssetView(material); - if (material && !isEmbeddedMaterial) + if (material) { auto& materialEditor = EDITOR_PANEL(MaterialEditor, "Material Editor"); EDITOR_EXEC(DelayAction([material, &materialEditor]() { diff --git a/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp b/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp index 9a39c3bd6..109a70b4a 100644 --- a/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp +++ b/Sources/OvEditor/src/OvEditor/Panels/AssetBrowser.cpp @@ -770,6 +770,14 @@ namespace public: EmbeddedFileContextualMenu(std::string p_resourcePath) : m_resourcePath(std::move(p_resourcePath)) {} + void CreateList() + { + auto& openAction = CreateWidget("Open"); + openAction.ClickedEvent += [this] { + OvCore::Helpers::GUIHelpers::Open(m_resourcePath); + }; + } + virtual void Execute(OvUI::Plugins::EPluginExecutionContext p_context) override { if (!m_widgets.empty()) @@ -778,58 +786,10 @@ namespace } } - protected: - void OpenInAssetView() const - { - OvCore::Helpers::GUIHelpers::Open(m_resourcePath); - } - protected: std::string m_resourcePath; }; - class EmbeddedTextureContextualMenu : public EmbeddedFileContextualMenu - { - public: - EmbeddedTextureContextualMenu(const std::string& p_resourcePath) : EmbeddedFileContextualMenu(p_resourcePath) {} - - void CreateList() - { - auto& previewAction = CreateWidget("Preview"); - previewAction.ClickedEvent += [this] { - OpenInAssetView(); - }; - } - }; - - class EmbeddedMaterialContextualMenu : public EmbeddedFileContextualMenu - { - public: - EmbeddedMaterialContextualMenu(const std::string& p_resourcePath) : EmbeddedFileContextualMenu(p_resourcePath) {} - - void CreateList() - { - auto& inspectAction = CreateWidget("Inspect"); - inspectAction.ClickedEvent += [this] { - auto& materialManager = OVSERVICE(OvCore::ResourceManagement::MaterialManager); - if (auto* material = materialManager.GetResource(m_resourcePath)) - { - auto& materialEditor = EDITOR_PANEL(OvEditor::Panels::MaterialEditor, "Material Editor"); - EDITOR_EXEC(DelayAction([material, &materialEditor]() { - materialEditor.SetTarget(*material); - materialEditor.Open(); - materialEditor.Focus(); - })); - } - }; - - auto& previewAction = CreateWidget("Preview"); - previewAction.ClickedEvent += [this] { - OpenInAssetView(); - }; - } - }; - void CreateEmbeddedModelAssetEntry( OvUI::Widgets::Layout::TreeNode& p_root, const std::string& p_resourcePath, @@ -848,20 +808,21 @@ namespace itemGroup.CreateWidget(iconTextureID, OvMaths::FVector2{ 16, 16 }).lineBreak = false; auto& clickableText = itemGroup.CreateWidget(itemName); + clickableText.AddPlugin>>( + "File", + OvTools::Utils::PathParser::GetFriendlyPath(p_resourcePath), + std::make_pair(p_resourcePath, &itemGroup) + ); if (p_fileType == OvTools::Utils::PathParser::EFileType::TEXTURE) { auto& texturePreview = clickableText.AddPlugin(); texturePreview.SetPath(p_resourcePath); - - auto& contextMenu = clickableText.AddPlugin(p_resourcePath); - contextMenu.CreateList(); - } - else if (p_fileType == OvTools::Utils::PathParser::EFileType::MATERIAL) - { - auto& contextMenu = clickableText.AddPlugin(p_resourcePath); - contextMenu.CreateList(); } + + auto& contextMenu = clickableText.AddPlugin(p_resourcePath); + contextMenu.CreateList(); + clickableText.DoubleClickedEvent += [resourcePath = p_resourcePath] { OvCore::Helpers::GUIHelpers::Open(resourcePath); };