Skip to content

Commit e0d5a71

Browse files
authored
Various UI improvements (#686)
- Inspector elements now keep the same column size consistently based on component type - Changed "Add Component..." button style to default - Table rows with only disabled widgets are now properly skipped - Introduced `AssetField` - Now using asset icons instead of "..." - No more "Empty" string in asset fields - Textures now also use `AssetField`, which also supports previews (so technically any asset type can display a preview) - Components & behaviours can now be moved up and down in the inspector (re-ordered)
1 parent ba916b0 commit e0d5a71

30 files changed

Lines changed: 760 additions & 377 deletions

File tree

Sources/OvCore/include/OvCore/ECS/Actor.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ namespace OvCore::ECS
289289
*/
290290
bool RemoveBehaviour(const std::string& p_name);
291291

292+
/**
293+
* Rename a behaviour, preserving its position in the ordering
294+
* @param p_previousName
295+
* @param p_newName
296+
*/
297+
bool RenameBehaviour(const std::string& p_previousName, const std::string& p_newName);
298+
292299
/**
293300
* Try to get the given behaviour (Returns nullptr on failure)
294301
* @param p_name
@@ -300,6 +307,11 @@ namespace OvCore::ECS
300307
*/
301308
std::unordered_map<std::string, Components::Behaviour>& GetBehaviours();
302309

310+
/**
311+
* Returns the ordered list of behaviour names (display/serialization order)
312+
*/
313+
std::vector<std::string>& GetBehavioursOrder();
314+
303315
/**
304316
* Serialize all the components
305317
*/
@@ -356,6 +368,7 @@ namespace OvCore::ECS
356368
/* Actors components */
357369
std::vector<std::shared_ptr<Components::AComponent>> m_components;
358370
std::unordered_map<std::string, Components::Behaviour> m_behaviours;
371+
std::vector<std::string> m_behavioursOrder;
359372

360373
public:
361374
Components::CTransform& transform;

Sources/OvCore/include/OvCore/ECS/Actor.inl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ namespace OvCore::ECS
1717

1818
if (auto found = GetComponent<T>(); !found)
1919
{
20-
m_components.insert(m_components.begin(), std::make_shared<T>(*this, p_args...));
21-
T& instance = *dynamic_cast<T*>(m_components.front().get());
20+
m_components.push_back(std::make_shared<T>(*this, p_args...));
21+
T& instance = *dynamic_cast<T*>(m_components.back().get());
2222
ComponentAddedEvent.Invoke(instance);
2323
if (m_playing && IsActive())
2424
{

Sources/OvCore/include/OvCore/Helpers/GUIDrawer.h

Lines changed: 10 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,18 @@
88

99
#include <functional>
1010
#include <string>
11-
#include <unordered_set>
12-
#include <vector>
1311

1412
#include <OvMaths/FVector2.h>
1513
#include <OvMaths/FVector3.h>
1614
#include <OvMaths/FVector4.h>
1715
#include <OvMaths/FQuaternion.h>
1816

19-
#include <OvTools/Utils/PathParser.h>
20-
2117
#include <OvUI/Internal/WidgetContainer.h>
2218
#include <OvUI/Widgets/Texts/Text.h>
2319
#include <OvUI/Widgets/Drags/DragSingleScalar.h>
2420
#include <OvUI/Widgets/Drags/DragMultipleScalars.h>
2521
#include <OvUI/Widgets/InputFields/InputText.h>
26-
#include <OvUI/Widgets/Visual/Image.h>
22+
#include <OvUI/Widgets/InputFields/AssetField.h>
2723
#include <OvUI/Types/Color.h>
2824

2925
namespace OvCore::Resources
@@ -46,116 +42,16 @@ namespace OvRendering::Resources
4642
namespace OvCore::Helpers
4743
{
4844
/**
49-
* Provide some helpers to draw UI elements
45+
* Provides helpers to draw UI elements for common data types and asset resources.
5046
*/
5147
class GUIDrawer
5248
{
5349
public:
54-
/**
55-
* Represents a single item in the picker.
56-
* The key uniquely identifies the item for deduplication when combining lists.
57-
*/
58-
struct PickerItem
59-
{
60-
std::string key; // unique identifier used for deduplication
61-
std::string displayName;
62-
std::string tooltip;
63-
uint32_t iconID = 0;
64-
std::function<void()> onSelected;
65-
};
66-
67-
/**
68-
* An ordered, deduplication-aware collection of picker items.
69-
* Items with the same key are silently dropped when added.
70-
*/
71-
class PickerItemList
72-
{
73-
public:
74-
/**
75-
* Add an item to the list.
76-
* @returns true if added, false if an item with the same key was already present.
77-
*/
78-
bool Add(PickerItem p_item)
79-
{
80-
if (!m_keys.insert(p_item.key).second)
81-
return false;
82-
m_items.push_back(std::move(p_item));
83-
return true;
84-
}
85-
86-
const std::vector<PickerItem>& Items() const { return m_items; }
87-
bool empty() const { return m_items.empty(); }
88-
size_t size() const { return m_items.size(); }
89-
90-
private:
91-
std::vector<PickerItem> m_items;
92-
std::unordered_set<std::string> m_keys;
93-
};
94-
95-
/**
96-
* A callback that builds a PickerItemList for a given file type.
97-
* Called internally by OpenAssetPicker — register once via SetFileItemBuilder.
98-
*/
99-
using FileItemBuilderCallback = std::function<PickerItemList(OvTools::Utils::PathParser::EFileType, std::function<void(std::string)>, bool, bool)>;
100-
101-
using PickerProviderCallback = std::function<void(PickerItemList, std::string)>;
102-
10350
static const OvUI::Types::Color TitleColor;
10451

10552
static const float _MIN_FLOAT;
10653
static const float _MAX_FLOAT;
10754

108-
/**
109-
* Defines the texture to use when there is no texture in a texture resource field
110-
* @param p_emptyTexture
111-
*/
112-
static void ProvideEmptyTexture(OvRendering::Resources::Texture& p_emptyTexture);
113-
114-
/**
115-
* Register the function that builds a PickerItemList for a given file type.
116-
* This is called internally by OpenAssetPicker.
117-
* Call this once during editor startup.
118-
* @param p_builder
119-
*/
120-
static void SetFileItemBuilder(FileItemBuilderCallback p_builder);
121-
122-
/**
123-
* Open the asset picker for the given file type.
124-
* Builds the item list via the registered file item builder, derives the window title
125-
* from the file type, and forwards everything to the registered picker provider.
126-
* Has no effect if either callback has not been registered.
127-
* @param p_fileType
128-
* @param p_onSelect
129-
* @param p_searchProjectFiles Include project assets in the results
130-
* @param p_searchEngineFiles Include engine assets in the results
131-
*/
132-
static void OpenAssetPicker(
133-
OvTools::Utils::PathParser::EFileType p_fileType,
134-
std::function<void(std::string)> p_onSelect,
135-
bool p_searchProjectFiles = true,
136-
bool p_searchEngineFiles = true
137-
);
138-
139-
/**
140-
* Register the function that opens the picker window.
141-
* Call this once during editor startup (typically in Editor::SetupUI).
142-
* @param p_provider
143-
*/
144-
static void SetPickerProvider(PickerProviderCallback p_provider);
145-
146-
/**
147-
* Open the picker with the given list of items.
148-
* Has no effect if no provider has been registered.
149-
* @param p_items
150-
* @param p_title Title displayed in the window's title bar
151-
*/
152-
static void OpenPicker(PickerItemList p_items, std::string p_title);
153-
154-
/**
155-
* Draw a title with the title color
156-
* @param p_root
157-
* @param p_name
158-
*/
15955
static void CreateTitle(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name);
16056

16157
template <typename T>
@@ -167,12 +63,14 @@ namespace OvCore::Helpers
16763
static void DrawQuat(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvMaths::FQuaternion& p_data, float p_step = 1.f, float p_min = _MIN_FLOAT, float p_max = _MAX_FLOAT);
16864
static void DrawString(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::string& p_data);
16965
static void DrawColor(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvUI::Types::Color& p_color, bool p_hasAlpha = false);
170-
static OvUI::Widgets::Texts::Text& DrawMesh(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Model*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
171-
static OvUI::Widgets::Visual::Image& DrawTexture(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Texture*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
172-
static OvUI::Widgets::Texts::Text& DrawShader(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Shader*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
173-
static OvUI::Widgets::Texts::Text& DrawMaterial(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvCore::Resources::Material*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
174-
static OvUI::Widgets::Texts::Text& DrawSound(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvAudio::Resources::Sound*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
175-
static OvUI::Widgets::Texts::Text& DrawAsset(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::string& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
66+
static OvUI::Widgets::InputFields::AssetField& DrawMesh(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Model*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
67+
static OvUI::Widgets::InputFields::AssetField& DrawTexture(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Texture*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
68+
static OvUI::Widgets::InputFields::AssetField& DrawShader(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvRendering::Resources::Shader*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
69+
static OvUI::Widgets::InputFields::AssetField& DrawMaterial(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvCore::Resources::Material*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
70+
static OvUI::Widgets::InputFields::AssetField& DrawSound(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, OvAudio::Resources::Sound*& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
71+
static OvUI::Widgets::InputFields::AssetField& DrawAsset(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::string& p_data, OvTools::Eventing::Event<>* p_updateNotifier = nullptr);
72+
73+
static OvUI::Widgets::InputFields::AssetField& DrawScene(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::function<std::string()> p_gatherer, std::function<void(std::string)> p_provider);
17674

17775
template <typename T>
17876
static void DrawScalar(OvUI::Internal::WidgetContainer& p_root, const std::string& p_name, std::function<T(void)> p_gatherer, std::function<void(T)> p_provider, float p_step = 1.f, T p_min = std::numeric_limits<T>::min(), T p_max = std::numeric_limits<T>::max());
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @project: Overload
3+
* @author: Overload Tech.
4+
* @licence: MIT
5+
*/
6+
7+
#pragma once
8+
9+
#include <cstdint>
10+
#include <functional>
11+
#include <string>
12+
#include <unordered_set>
13+
#include <vector>
14+
15+
#include <OvTools/Utils/PathParser.h>
16+
17+
namespace OvRendering::Resources
18+
{
19+
class Texture;
20+
}
21+
22+
namespace OvCore::Helpers
23+
{
24+
/**
25+
* Asset picking and opening helpers shared across the editor.
26+
* Implements a service locator pattern for UI operations.
27+
*/
28+
class GUIHelpers
29+
{
30+
public:
31+
struct PickerItem
32+
{
33+
std::string key;
34+
std::string displayName;
35+
std::string tooltip;
36+
uint32_t iconID = 0;
37+
std::function<void()> onSelected;
38+
};
39+
40+
class PickerItemList
41+
{
42+
public:
43+
bool Add(PickerItem p_item)
44+
{
45+
if (!m_keys.insert(p_item.key).second)
46+
return false;
47+
m_items.push_back(std::move(p_item));
48+
return true;
49+
}
50+
51+
const std::vector<PickerItem>& Items() const { return m_items; }
52+
bool empty() const { return m_items.empty(); }
53+
size_t size() const { return m_items.size(); }
54+
55+
private:
56+
std::vector<PickerItem> m_items;
57+
std::unordered_set<std::string> m_keys;
58+
};
59+
60+
using FileItemBuilderCallback = std::function<PickerItemList(OvTools::Utils::PathParser::EFileType, std::function<void(std::string)>, bool, bool)>;
61+
using OpenProviderCallback = std::function<void(const std::string&)>;
62+
using PickerProviderCallback = std::function<void(PickerItemList, std::string)>;
63+
using IconProviderCallback = std::function<uint32_t(OvTools::Utils::PathParser::EFileType)>;
64+
65+
static void ProvideEmptyTexture(OvRendering::Resources::Texture& p_emptyTexture);
66+
static OvRendering::Resources::Texture* GetEmptyTexture();
67+
68+
static void SetFileItemBuilder(FileItemBuilderCallback p_builder);
69+
70+
static void OpenAssetPicker(
71+
OvTools::Utils::PathParser::EFileType p_fileType,
72+
std::function<void(std::string)> p_onSelect,
73+
bool p_searchProjectFiles = true,
74+
bool p_searchEngineFiles = true
75+
);
76+
77+
static void SetOpenProvider(OpenProviderCallback p_provider);
78+
static void Open(const std::string& p_path);
79+
80+
static void SetIconProvider(IconProviderCallback p_provider);
81+
static uint32_t GetIconForFileType(OvTools::Utils::PathParser::EFileType p_fileType);
82+
83+
static void SetPickerProvider(PickerProviderCallback p_provider);
84+
static void OpenPicker(PickerItemList p_items, std::string p_title);
85+
};
86+
}

Sources/OvCore/src/OvCore/ECS/Actor.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ std::vector<std::shared_ptr<OvCore::ECS::Components::AComponent>>& OvCore::ECS::
335335
OvCore::ECS::Components::Behaviour & OvCore::ECS::Actor::AddBehaviour(const std::string & p_name)
336336
{
337337
m_behaviours.try_emplace(p_name, *this, p_name);
338+
m_behavioursOrder.push_back(p_name);
338339
Components::Behaviour& newInstance = m_behaviours.at(p_name);
339340
BehaviourAddedEvent.Invoke(newInstance);
340341
if (m_playing && IsActive())
@@ -371,14 +372,45 @@ bool OvCore::ECS::Actor::RemoveBehaviour(const std::string & p_name)
371372
if (found)
372373
{
373374
BehaviourRemovedEvent.Invoke(*found);
374-
return m_behaviours.erase(p_name);
375+
m_behaviours.erase(p_name);
376+
auto it = std::find(m_behavioursOrder.begin(), m_behavioursOrder.end(), p_name);
377+
if (it != m_behavioursOrder.end())
378+
m_behavioursOrder.erase(it);
379+
return true;
375380
}
376381
else
377382
{
378383
return false;
379384
}
380385
}
381386

387+
bool OvCore::ECS::Actor::RenameBehaviour(const std::string& p_previousName, const std::string& p_newName)
388+
{
389+
auto orderIt = std::find(m_behavioursOrder.begin(), m_behavioursOrder.end(), p_previousName);
390+
if (orderIt == m_behavioursOrder.end())
391+
return false;
392+
393+
Components::Behaviour* found = GetBehaviour(p_previousName);
394+
if (!found)
395+
return false;
396+
397+
BehaviourRemovedEvent.Invoke(*found);
398+
m_behaviours.erase(p_previousName);
399+
400+
*orderIt = p_newName;
401+
402+
m_behaviours.try_emplace(p_newName, *this, p_newName);
403+
Components::Behaviour& newInstance = m_behaviours.at(p_newName);
404+
BehaviourAddedEvent.Invoke(newInstance);
405+
if (m_playing && IsActive())
406+
{
407+
newInstance.OnAwake();
408+
newInstance.OnEnable();
409+
newInstance.OnStart();
410+
}
411+
return true;
412+
}
413+
382414
OvCore::ECS::Components::Behaviour* OvCore::ECS::Actor::GetBehaviour(const std::string& p_name)
383415
{
384416
if (auto result = m_behaviours.find(p_name); result != m_behaviours.end())
@@ -392,6 +424,11 @@ std::unordered_map<std::string, OvCore::ECS::Components::Behaviour>& OvCore::ECS
392424
return m_behaviours;
393425
}
394426

427+
std::vector<std::string>& OvCore::ECS::Actor::GetBehavioursOrder()
428+
{
429+
return m_behavioursOrder;
430+
}
431+
395432
void OvCore::ECS::Actor::OnSerialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XMLNode * p_actorsRoot)
396433
{
397434
tinyxml2::XMLNode* actorNode = p_doc.NewElement("actor");
@@ -426,21 +463,25 @@ void OvCore::ECS::Actor::OnSerialize(tinyxml2::XMLDocument & p_doc, tinyxml2::XM
426463
tinyxml2::XMLNode* behavioursNode = p_doc.NewElement("behaviours");
427464
actorNode->InsertEndChild(behavioursNode);
428465

429-
for (auto& behaviour : m_behaviours)
466+
for (auto& name : m_behavioursOrder)
430467
{
468+
auto it = m_behaviours.find(name);
469+
if (it == m_behaviours.end()) continue;
470+
auto& behaviour = it->second;
471+
431472
/* Current behaviour root */
432473
tinyxml2::XMLNode* behaviourNode = p_doc.NewElement("behaviour");
433474
behavioursNode->InsertEndChild(behaviourNode);
434475

435476
/* Behaviour type */
436-
OvCore::Helpers::Serializer::SerializeString(p_doc, behaviourNode, "type", behaviour.first);
477+
OvCore::Helpers::Serializer::SerializeString(p_doc, behaviourNode, "type", name);
437478

438479
/* Data node (Will be passed to the behaviour) */
439480
tinyxml2::XMLElement* data = p_doc.NewElement("data");
440481
behaviourNode->InsertEndChild(data);
441482

442483
/* Data serialization of the behaviour */
443-
behaviour.second.OnSerialize(p_doc, data);
484+
behaviour.OnSerialize(p_doc, data);
444485
}
445486
}
446487

Sources/OvCore/src/OvCore/ECS/Components/CMaterialRenderer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ std::array<OvUI::Widgets::AWidget*, 2> CustomMaterialDrawer(OvUI::Internal::Widg
145145
const size_t before = p_root.GetWidgets().size();
146146
OvCore::Helpers::GUIDrawer::DrawMaterial(p_root, p_name, p_data, nullptr);
147147
auto& widgets = p_root.GetWidgets();
148-
// DrawMaterial adds exactly 2 widgets: [before]=TextColored title, [before+1]=Group rightSide
148+
// DrawMaterial adds exactly 2 widgets: [before]=TextColored title, [before+1]=AssetField
149149
return { widgets[before].first, widgets[before + 1].first };
150150
}
151151

0 commit comments

Comments
 (0)