diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CMaterialRenderer.h b/Sources/OvCore/include/OvCore/ECS/Components/CMaterialRenderer.h index ac18d92c2..aae8fd870 100644 --- a/Sources/OvCore/include/OvCore/ECS/Components/CMaterialRenderer.h +++ b/Sources/OvCore/include/OvCore/ECS/Components/CMaterialRenderer.h @@ -27,7 +27,7 @@ namespace OvCore::ECS::Components { public: using MaterialList = std::array; - using MaterialField = std::array, kMaxMaterialCount>; + using MaterialField = std::array, kMaxMaterialCount>; /** * Constructor diff --git a/Sources/OvCore/include/OvCore/Helpers/GUIDrawer.h b/Sources/OvCore/include/OvCore/Helpers/GUIDrawer.h index d234f8823..532e41743 100644 --- a/Sources/OvCore/include/OvCore/Helpers/GUIDrawer.h +++ b/Sources/OvCore/include/OvCore/Helpers/GUIDrawer.h @@ -6,11 +6,16 @@ #pragma once +#include +#include + #include #include #include #include +#include + #include #include #include @@ -44,6 +49,8 @@ namespace OvCore::Helpers class GUIDrawer { public: + using AssetPickerProviderCallback = std::function)>; + static const OvUI::Types::Color TitleColor; static const OvUI::Types::Color ClearButtonColor; @@ -56,6 +63,16 @@ namespace OvCore::Helpers */ static void ProvideEmptyTexture(OvRendering::Resources::Texture& p_emptyTexture); + /** + * Register the function that opens the asset picker window. + * The provider receives the desired file type and a callback to invoke with the chosen path. + * Call this once during editor startup. + * @param p_provider + */ + static void SetAssetPickerProvider( + AssetPickerProviderCallback p_provider + ); + /** * Draw a title with the title color * @param p_root @@ -95,10 +112,7 @@ namespace OvCore::Helpers template static std::string GetFormat(); - - private: - static OvRendering::Resources::Texture* __EMPTY_TEXTURE; }; } -#include "OvCore/Helpers/GUIDrawer.inl" \ No newline at end of file +#include "OvCore/Helpers/GUIDrawer.inl" diff --git a/Sources/OvCore/src/OvCore/ECS/Components/CMaterialRenderer.cpp b/Sources/OvCore/src/OvCore/ECS/Components/CMaterialRenderer.cpp index a9f154e93..ac1859401 100644 --- a/Sources/OvCore/src/OvCore/ECS/Components/CMaterialRenderer.cpp +++ b/Sources/OvCore/src/OvCore/ECS/Components/CMaterialRenderer.cpp @@ -11,17 +11,11 @@ #include #include #include +#include #include -#include - -#include -#include -#include -#include #include -#include -#include +#include #include OvCore::ECS::Components::CMaterialRenderer::CMaterialRenderer(ECS::Actor & p_owner) : AComponent(p_owner) @@ -146,46 +140,13 @@ void OvCore::ECS::Components::CMaterialRenderer::OnDeserialize(tinyxml2::XMLDocu OvCore::Helpers::Serializer::DeserializeUint32(p_doc, p_node, "visibility_flags", reinterpret_cast(m_visibilityFlags)); } -std::array CustomMaterialDrawer(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvCore::Resources::Material*& p_data) +std::array CustomMaterialDrawer(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvCore::Resources::Material*& p_data) { - using namespace OvCore::Helpers; - - std::array widgets; - - widgets[0] = &p_root.CreateWidget(p_name, GUIDrawer::TitleColor); - - std::string displayedText = (p_data ? p_data->path : std::string("Empty")); - auto & rightSide = p_root.CreateWidget(); - - auto& widget = rightSide.CreateWidget(displayedText); - - widgets[1] = &widget; - - widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data](auto p_receivedData) - { - if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == OvTools::Utils::PathParser::EFileType::MATERIAL) - { - if (auto resource = OVSERVICE(OvCore::ResourceManagement::MaterialManager).GetResource(p_receivedData.first); resource) - { - p_data = resource; - widget.content = p_receivedData.first; - } - } - }; - - widget.lineBreak = false; - - auto & resetButton = rightSide.CreateWidget("Clear"); - resetButton.idleBackgroundColor = GUIDrawer::ClearButtonColor; - resetButton.ClickedEvent += [&widget, &p_data] - { - p_data = nullptr; - widget.content = "Empty"; - }; - - widgets[2] = &resetButton; - - return widgets; + const size_t before = p_root.GetWidgets().size(); + OvCore::Helpers::GUIDrawer::DrawMaterial(p_root, p_name, p_data, nullptr); + auto& widgets = p_root.GetWidgets(); + // DrawMaterial adds exactly 2 widgets: [before]=TextColored title, [before+1]=Group rightSide + return { widgets[before].first, widgets[before + 1].first }; } void OvCore::ECS::Components::CMaterialRenderer::OnInspector(OvUI::Internal::WidgetContainer & p_root) @@ -239,12 +200,11 @@ void OvCore::ECS::Components::CMaterialRenderer::UpdateMaterialList() { if (m_materialFields[i][0]) { - bool enabled = !m_materialNames[i].empty(); + const bool enabled = !m_materialNames[i].empty(); m_materialFields[i][0]->enabled = enabled; m_materialFields[i][1]->enabled = enabled; - m_materialFields[i][2]->enabled = enabled; - const auto formattedName = std::format("Material [{}]: <{}>", i, m_materialNames[i]); - reinterpret_cast(m_materialFields[i][0]) ->content = formattedName; + static_cast(m_materialFields[i][0])->content = + std::format("Material [{}]: <{}>", i, m_materialNames[i]); } } } diff --git a/Sources/OvCore/src/OvCore/Helpers/GUIDrawer.cpp b/Sources/OvCore/src/OvCore/Helpers/GUIDrawer.cpp index 2f1304e90..febf96e48 100644 --- a/Sources/OvCore/src/OvCore/Helpers/GUIDrawer.cpp +++ b/Sources/OvCore/src/OvCore/Helpers/GUIDrawer.cpp @@ -5,6 +5,7 @@ */ #include +#include #include @@ -28,18 +29,31 @@ #include #include "OvCore/Helpers/GUIDrawer.h" +#include "OvUI/Widgets/Buttons/AButton.h" const OvUI::Types::Color OvCore::Helpers::GUIDrawer::TitleColor = { 0.85f, 0.65f, 0.0f }; const OvUI::Types::Color OvCore::Helpers::GUIDrawer::ClearButtonColor = { 0.5f, 0.0f, 0.0f }; const float OvCore::Helpers::GUIDrawer::_MIN_FLOAT = -999999999.f; const float OvCore::Helpers::GUIDrawer::_MAX_FLOAT = +999999999.f; -OvRendering::Resources::Texture* OvCore::Helpers::GUIDrawer::__EMPTY_TEXTURE = nullptr; + +namespace +{ + OvRendering::Resources::Texture* __EMPTY_TEXTURE = nullptr; + OvCore::Helpers::GUIDrawer::AssetPickerProviderCallback __ASSET_PICKER_PROVIDER; +} void OvCore::Helpers::GUIDrawer::ProvideEmptyTexture(OvRendering::Resources::Texture& p_emptyTexture) { __EMPTY_TEXTURE = &p_emptyTexture; } +void OvCore::Helpers::GUIDrawer::SetAssetPickerProvider( + AssetPickerProviderCallback p_provider +) +{ + __ASSET_PICKER_PROVIDER = std::move(p_provider); +} + void OvCore::Helpers::GUIDrawer::CreateTitle(OvUI::Internal::WidgetContainer& p_root, const std::string & p_name) { p_root.CreateWidget(p_name, TitleColor); @@ -101,42 +115,89 @@ void OvCore::Helpers::GUIDrawer::DrawColor(OvUI::Internal::WidgetContainer & p_r dispatcher.RegisterReference(p_color); } -OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawMesh(OvUI::Internal::WidgetContainer & p_root, const std::string & p_name, OvRendering::Resources::Model *& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +namespace { - CreateTitle(p_root, p_name); + void AddSelectButton( + OvUI::Widgets::Buttons::AButton& p_button, + OvTools::Utils::PathParser::EFileType p_fileType, + std::function p_onSelect) + { + auto token = std::make_shared(true); + p_button.ClickedEvent += [p_fileType, p_onSelect = std::move(p_onSelect), token = std::move(token)] + { + if (__ASSET_PICKER_PROVIDER) + { + std::weak_ptr weak = token; + __ASSET_PICKER_PROVIDER(p_fileType, [p_onSelect, weak](const std::string& p_path) + { + if (!weak.expired()) p_onSelect(p_path); + }); + } + }; + } + + template + OvUI::Widgets::Texts::Text& DrawResourceWidget( + OvUI::Internal::WidgetContainer& p_root, + const std::string& p_name, + TResource*& p_data, + OvTools::Utils::PathParser::EFileType p_fileType, + OvTools::Eventing::Event<>* p_updateNotifier) + { + OvCore::Helpers::GUIDrawer::CreateTitle(p_root, p_name); - std::string displayedText = (p_data ? p_data->path : std::string("Empty")); - auto& rightSide = p_root.CreateWidget(); + std::string displayedText = (p_data ? p_data->path : std::string("Empty")); + auto& rightSide = p_root.CreateWidget(); - auto& widget = rightSide.CreateWidget(displayedText); + auto& widget = rightSide.CreateWidget(displayedText); - widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data, p_updateNotifier](auto p_receivedData) - { - if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == OvTools::Utils::PathParser::EFileType::MODEL) + widget.AddPlugin>>("File").DataReceivedEvent += + [&widget, &p_data, p_updateNotifier, p_fileType](auto p_receivedData) + { + if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == p_fileType) + { + if (auto resource = OVSERVICE(TResourceManager).GetResource(p_receivedData.first); resource) + { + p_data = resource; + widget.content = p_receivedData.first; + if (p_updateNotifier) + p_updateNotifier->Invoke(); + } + } + }; + + widget.lineBreak = false; + + auto& selectButton = rightSide.CreateWidget("..."); + selectButton.lineBreak = false; + AddSelectButton(selectButton, p_fileType, [&widget, &p_data, p_updateNotifier](const std::string& p_path) { - if (auto resource = OVSERVICE(OvCore::ResourceManagement::ModelManager).GetResource(p_receivedData.first); resource) + if (auto resource = OVSERVICE(TResourceManager).GetResource(p_path); resource) { p_data = resource; - widget.content = p_receivedData.first; + widget.content = p_path; if (p_updateNotifier) p_updateNotifier->Invoke(); } - } - }; + }); - widget.lineBreak = false; - - auto& resetButton = rightSide.CreateWidget("Clear"); - resetButton.idleBackgroundColor = ClearButtonColor; - resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] - { - p_data = nullptr; - widget.content = "Empty"; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - }; + auto& resetButton = rightSide.CreateWidget("Clear"); + resetButton.idleBackgroundColor = OvCore::Helpers::GUIDrawer::ClearButtonColor; + resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] + { + p_data = nullptr; + widget.content = "Empty"; + if (p_updateNotifier) + p_updateNotifier->Invoke(); + }; + + return widget; + } +} - return widget; +OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawMesh(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Model*& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +{ + return DrawResourceWidget(p_root, p_name, p_data, OvTools::Utils::PathParser::EFileType::MODEL, p_updateNotifier); } OvUI::Widgets::Visual::Image& OvCore::Helpers::GUIDrawer::DrawTexture(OvUI::Internal::WidgetContainer & p_root, const std::string & p_name, OvRendering::Resources::Texture *& p_data, OvTools::Eventing::Event<>* p_updateNotifier) @@ -164,7 +225,20 @@ OvUI::Widgets::Visual::Image& OvCore::Helpers::GUIDrawer::DrawTexture(OvUI::Inte widget.lineBreak = false; - auto& resetButton = rightSide.CreateWidget("Clear"); + auto& selectButton = rightSide.CreateWidget("..."); + selectButton.lineBreak = false; + AddSelectButton(selectButton, OvTools::Utils::PathParser::EFileType::TEXTURE, [&widget, &p_data, p_updateNotifier](const std::string& p_path) + { + if (auto resource = OVSERVICE(OvCore::ResourceManagement::TextureManager).GetResource(p_path); resource) + { + p_data = resource; + widget.textureID.id = resource->GetTexture().GetID(); + if (p_updateNotifier) + p_updateNotifier->Invoke(); + } + }); + + auto& resetButton = rightSide.CreateWidget("Clear"); resetButton.idleBackgroundColor = ClearButtonColor; resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] { @@ -177,112 +251,55 @@ OvUI::Widgets::Visual::Image& OvCore::Helpers::GUIDrawer::DrawTexture(OvUI::Inte return widget; } -OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawShader(OvUI::Internal::WidgetContainer & p_root, const std::string & p_name, OvRendering::Resources::Shader *& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawShader(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Shader*& p_data, OvTools::Eventing::Event<>* p_updateNotifier) { - CreateTitle(p_root, p_name); - - std::string displayedText = (p_data ? p_data->path : std::string("Empty")); - auto& rightSide = p_root.CreateWidget(); - - auto& widget = rightSide.CreateWidget(displayedText); - - widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data, p_updateNotifier](auto p_receivedData) - { - if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == OvTools::Utils::PathParser::EFileType::SHADER) - { - if (auto resource = OVSERVICE(OvCore::ResourceManagement::ShaderManager).GetResource(p_receivedData.first); resource) - { - p_data = resource; - widget.content = p_receivedData.first; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - } - } - }; - - widget.lineBreak = false; + return DrawResourceWidget(p_root, p_name, p_data, OvTools::Utils::PathParser::EFileType::SHADER, p_updateNotifier); +} - auto& resetButton = rightSide.CreateWidget("Clear"); - resetButton.idleBackgroundColor = ClearButtonColor; - resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] - { - p_data = nullptr; - widget.content = "Empty"; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - }; +OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawMaterial(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvCore::Resources::Material*& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +{ + return DrawResourceWidget(p_root, p_name, p_data, OvTools::Utils::PathParser::EFileType::MATERIAL, p_updateNotifier); +} - return widget; +OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawSound(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvAudio::Resources::Sound*& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +{ + return DrawResourceWidget(p_root, p_name, p_data, OvTools::Utils::PathParser::EFileType::SOUND, p_updateNotifier); } -OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawMaterial(OvUI::Internal::WidgetContainer & p_root, const std::string & p_name, OvCore::Resources::Material *& p_data, OvTools::Eventing::Event<>* p_updateNotifier) +OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawAsset(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::string& p_data, OvTools::Eventing::Event<>* p_updateNotifier) { CreateTitle(p_root, p_name); - std::string displayedText = (p_data ? p_data->path : std::string("Empty")); + const std::string displayedText = (p_data.empty() ? std::string("Empty") : p_data); auto& rightSide = p_root.CreateWidget(); auto& widget = rightSide.CreateWidget(displayedText); widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data, p_updateNotifier](auto p_receivedData) { - if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == OvTools::Utils::PathParser::EFileType::MATERIAL) - { - if (auto resource = OVSERVICE(OvCore::ResourceManagement::MaterialManager).GetResource(p_receivedData.first); resource) - { - p_data = resource; - widget.content = p_receivedData.first; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - } - } + p_data = p_receivedData.first; + widget.content = p_receivedData.first; + if (p_updateNotifier) + p_updateNotifier->Invoke(); }; widget.lineBreak = false; - auto& resetButton = rightSide.CreateWidget("Clear"); - resetButton.idleBackgroundColor = ClearButtonColor; - resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] + auto& selectButton = rightSide.CreateWidget("..."); + selectButton.lineBreak = false; + AddSelectButton(selectButton, OvTools::Utils::PathParser::EFileType::UNKNOWN, [&widget, &p_data, p_updateNotifier](const std::string& p_path) { - p_data = nullptr; - widget.content = "Empty"; + p_data = p_path; + widget.content = p_path; if (p_updateNotifier) p_updateNotifier->Invoke(); - }; - - return widget; -} - -OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawSound(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvAudio::Resources::Sound*& p_data, OvTools::Eventing::Event<>* p_updateNotifier) -{ - CreateTitle(p_root, p_name); - - std::string displayedText = (p_data ? p_data->path : std::string("Empty")); - auto & rightSide = p_root.CreateWidget(); - - auto & widget = rightSide.CreateWidget(displayedText); - - widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data, p_updateNotifier](auto p_receivedData) - { - if (OvTools::Utils::PathParser::GetFileType(p_receivedData.first) == OvTools::Utils::PathParser::EFileType::SOUND) - { - if (auto resource = OVSERVICE(OvCore::ResourceManagement::SoundManager).GetResource(p_receivedData.first); resource) - { - p_data = resource; - widget.content = p_receivedData.first; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - } - } - }; - - widget.lineBreak = false; + }); - auto & resetButton = rightSide.CreateWidget("Clear"); + auto& resetButton = rightSide.CreateWidget("Clear"); resetButton.idleBackgroundColor = ClearButtonColor; resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] { - p_data = nullptr; + p_data = ""; widget.content = "Empty"; if (p_updateNotifier) p_updateNotifier->Invoke(); @@ -291,38 +308,6 @@ OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawSound(OvUI::Internal return widget; } -OvUI::Widgets::Texts::Text& OvCore::Helpers::GUIDrawer::DrawAsset(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::string& p_data, OvTools::Eventing::Event<>* p_updateNotifier) -{ - CreateTitle(p_root, p_name); - - const std::string displayedText = (p_data.empty() ? std::string("Empty") : p_data); - auto& rightSide = p_root.CreateWidget(); - - auto& widget = rightSide.CreateWidget(displayedText); - - widget.AddPlugin>>("File").DataReceivedEvent += [&widget, &p_data, p_updateNotifier](auto p_receivedData) - { - p_data = p_receivedData.first; - widget.content = p_receivedData.first; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - }; - - widget.lineBreak = false; - - auto& resetButton = rightSide.CreateWidget("Clear"); - resetButton.idleBackgroundColor = ClearButtonColor; - resetButton.ClickedEvent += [&widget, &p_data, p_updateNotifier] - { - p_data = ""; - widget.content = "Empty"; - if (p_updateNotifier) - p_updateNotifier->Invoke(); - }; - - return widget; -} - void OvCore::Helpers::GUIDrawer::DrawBoolean(OvUI::Internal::WidgetContainer & p_root, const std::string & p_name, std::function p_gatherer, std::function p_provider) { CreateTitle(p_root, p_name); diff --git a/Sources/OvEditor/include/OvEditor/Core/Editor.h b/Sources/OvEditor/include/OvEditor/Core/Editor.h index 9b97cfd30..ba5e2316a 100644 --- a/Sources/OvEditor/include/OvEditor/Core/Editor.h +++ b/Sources/OvEditor/include/OvEditor/Core/Editor.h @@ -6,10 +6,13 @@ #pragma once +#include + #include #include #include #include +#include #include #include @@ -103,5 +106,6 @@ namespace OvEditor::Core OvEditor::Core::PanelsManager m_panelsManager; OvEditor::Core::EditorActions m_editorActions; OvTools::Utils::OptRef m_lastFocusedView; + std::unique_ptr m_assetPicker; }; } \ No newline at end of file diff --git a/Sources/OvEditor/include/OvEditor/Panels/AssetPicker.h b/Sources/OvEditor/include/OvEditor/Panels/AssetPicker.h new file mode 100644 index 000000000..5e7534eeb --- /dev/null +++ b/Sources/OvEditor/include/OvEditor/Panels/AssetPicker.h @@ -0,0 +1,66 @@ +/** +* @project: Overload +* @author: Overload Tech. +* @licence: MIT +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace OvUI::Widgets +{ + namespace InputFields { class InputText; } + namespace Layout { class Group; } + namespace Texts { class TextClickable; } +} + +namespace OvEditor::Panels +{ + /** + * A floating panel that lets the user pick an asset of a given type. + * Open it with Open(fileType, callback) — on selection the callback receives the + * resource-format path and the window closes automatically. + */ + class AssetPicker : public OvUI::Panels::PanelWindow + { + public: + AssetPicker( + const std::string& p_title, + bool p_opened, + const OvUI::Settings::PanelWindowSettings& p_windowSettings + ); + + /** + * Open the picker filtered by the given file type. + * @param p_fileType Asset type to show (UNKNOWN = all known types) + * @param p_buttonMin Screen-space top-left of the button that triggered the picker + * @param p_buttonMax Screen-space bottom-right of the button that triggered the picker + * @param p_callback Called with the selected resource path when the user picks an asset + */ + void Open( + OvTools::Utils::PathParser::EFileType p_fileType, + std::function p_callback + ); + + private: + void Populate(); + void FilterList(const std::string& p_search); + + private: + OvTools::Utils::PathParser::EFileType m_fileType = OvTools::Utils::PathParser::EFileType::UNKNOWN; + std::function m_callback; + + OvUI::Widgets::InputFields::InputText* m_searchField = nullptr; + OvUI::Widgets::Layout::Group* m_assetListGroup = nullptr; + + /* Each entry: (resource-format path, corresponding widget) */ + std::vector> m_items; + }; +} diff --git a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp index 6b41884b3..5d7986f4d 100644 --- a/Sources/OvEditor/src/OvEditor/Core/Editor.cpp +++ b/Sources/OvEditor/src/OvEditor/Core/Editor.cpp @@ -6,8 +6,11 @@ #include +#include + #include #include +#include #include #include #include @@ -24,13 +27,14 @@ #include #include #include +#include using namespace OvCore::ResourceManagement; using namespace OvEditor::Panels; using namespace OvRendering::Resources::Loaders; using namespace OvRendering::Resources::Parsers; -OvEditor::Core::Editor::Editor(Context& p_context) : +OvEditor::Core::Editor::Editor(Context& p_context) : m_context(p_context), m_panelsManager(m_canvas), m_editorActions(m_context, m_panelsManager) @@ -74,6 +78,20 @@ void OvEditor::Core::Editor::SetupUI() m_canvas.MakeDockspace(true); m_context.uiManager->SetCanvas(m_canvas); + + m_assetPicker = std::make_unique( + "Asset Picker", + false, + OvUI::Settings::PanelWindowSettings{ .closable = true } + ); + + m_canvas.AddPanel(*m_assetPicker); + + OvCore::Helpers::GUIDrawer::SetAssetPickerProvider( + [this](OvTools::Utils::PathParser::EFileType p_type, std::function p_callback) { + m_assetPicker->Open(p_type, std::move(p_callback)); + } + ); } void OvEditor::Core::Editor::PreUpdate() @@ -84,16 +102,9 @@ void OvEditor::Core::Editor::PreUpdate() void OvEditor::Core::Editor::Update(float p_deltaTime) { - // Disable ImGui mouse update if the mouse cursor is disabled. - // i.e. when locked during gameplay, or when a view is being interacted - if (m_context.window->GetCursorMode() == OvWindowing::Cursor::ECursorMode::DISABLED) - { - ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouse; - } - else - { - ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_NoMouse); - } + // Disable mouse input when the cursor is locked during gameplay or view interaction. + const bool mouseEnabled = m_context.window->GetCursorMode() != OvWindowing::Cursor::ECursorMode::DISABLED; + m_context.uiManager->EnableMouse(mouseEnabled); HandleGlobalShortcuts(); UpdateCurrentEditorMode(p_deltaTime); diff --git a/Sources/OvEditor/src/OvEditor/Panels/AssetPicker.cpp b/Sources/OvEditor/src/OvEditor/Panels/AssetPicker.cpp new file mode 100644 index 000000000..25b8ead45 --- /dev/null +++ b/Sources/OvEditor/src/OvEditor/Panels/AssetPicker.cpp @@ -0,0 +1,145 @@ +/** +* @project: Overload +* @author: Overload Tech. +* @licence: MIT +*/ + +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +using namespace OvEditor::Panels; +using namespace OvTools::Utils; + +namespace +{ + bool ContainsCaseInsensitive(const std::string& p_str, const std::string& p_search) + { + if (p_search.empty()) + return true; + + return std::search( + p_str.begin(), p_str.end(), + p_search.begin(), p_search.end(), + [](char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); } + ) != p_str.end(); + } +} + +AssetPicker::AssetPicker( + const std::string& p_title, + bool p_opened, + const OvUI::Settings::PanelWindowSettings& p_windowSettings) + : PanelWindow(p_title, p_opened, p_windowSettings) +{ + minSize = { 250.f, 250.f }; + + m_searchField = &CreateWidget("", "Search"); + m_searchField->ContentChangedEvent += [this](const std::string& p_text) + { + FilterList(p_text); + }; + + CreateWidget(); + m_assetListGroup = &CreateWidget(); +} + +void AssetPicker::Open(PathParser::EFileType p_fileType, std::function p_callback) +{ + m_fileType = p_fileType; + m_callback = std::move(p_callback); + m_searchField->content = ""; + Populate(); + ScrollToTop(); + + const ImVec2 display = ImGui::GetIO().DisplaySize; + const float winW = minSize.x; + const float winH = minSize.y; + + const ImVec2 buttonMin = ImGui::GetItemRectMin(); + const ImVec2 buttonMax = ImGui::GetItemRectMax(); + + // Default: top-left corner of the window aligned with the bottom-left of the button + float x = buttonMin.x; + float y = buttonMax.y; + + // Not enough room below → open above the button instead + if (y + winH > display.y) + y = buttonMin.y - winH; + + // Not enough room to the right → right-align to button's right edge + if (x + winW > display.x) + x = buttonMax.x - winW; + + // Keep fully on-screen + x = std::max(0.f, x); + y = std::max(0.f, y); + + SetPosition({ x, y }); + + PanelWindow::Open(); + Focus(); +} + +void AssetPicker::Populate() +{ + m_assetListGroup->RemoveAllWidgets(); + m_items.clear(); + + const auto collectFromDirectory = [&](const std::filesystem::path& p_directory, bool p_isEngine) + { + if (!std::filesystem::exists(p_directory)) + return; + + std::error_code ec; + for (const auto& entry : std::filesystem::recursive_directory_iterator( + p_directory, std::filesystem::directory_options::skip_permission_denied, ec)) + { + if (!entry.is_regular_file()) + continue; + + const std::string path = entry.path().string(); + const PathParser::EFileType fileType = PathParser::GetFileType(path); + + if (fileType == PathParser::EFileType::UNKNOWN) + continue; + + if (m_fileType != PathParser::EFileType::UNKNOWN && fileType != m_fileType) + continue; + + const std::string resourcePath = EDITOR_EXEC(GetResourcePath(path, p_isEngine)); + const std::string filename = PathParser::GetElementName(resourcePath); + + auto& item = m_assetListGroup->CreateWidget(filename); + item.tooltip = resourcePath; + item.ClickedEvent += [this, resourcePath] + { + if (m_callback) + m_callback(resourcePath); + Close(); + }; + + m_items.emplace_back(resourcePath, &item); + } + }; + + collectFromDirectory(EDITOR_CONTEXT(engineAssetsPath), true); + collectFromDirectory(EDITOR_CONTEXT(projectAssetsPath), false); +} + +void AssetPicker::FilterList(const std::string& p_search) +{ + for (auto& [path, widget] : m_items) + widget->enabled = ContainsCaseInsensitive(path, p_search); +} diff --git a/Sources/OvUI/include/OvUI/Core/UIManager.h b/Sources/OvUI/include/OvUI/Core/UIManager.h index 1c4e44d91..f8fa7608c 100644 --- a/Sources/OvUI/include/OvUI/Core/UIManager.h +++ b/Sources/OvUI/include/OvUI/Core/UIManager.h @@ -102,6 +102,13 @@ namespace OvUI::Core */ void ResetLayout(const std::string & p_config) const; + /** + * Enable or disable mouse input in ImGui. + * Typically disabled when the cursor is locked during gameplay. + * @param p_value + */ + void EnableMouse(bool p_value); + /** * Return true if the docking system is enabled */ diff --git a/Sources/OvUI/src/OvUI/Core/UIManager.cpp b/Sources/OvUI/src/OvUI/Core/UIManager.cpp index c2d757c7a..b6362b6bc 100644 --- a/Sources/OvUI/src/OvUI/Core/UIManager.cpp +++ b/Sources/OvUI/src/OvUI/Core/UIManager.cpp @@ -129,6 +129,14 @@ float OvUI::Core::UIManager::GetEditorLayoutAutosaveFrequency(float p_frequeny) return ImGui::GetIO().IniSavingRate; } +void OvUI::Core::UIManager::EnableMouse(bool p_value) +{ + if (p_value) + ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + else + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouse; +} + void OvUI::Core::UIManager::EnableDocking(bool p_value) { m_dockingState = p_value;