feat: add "Add component" button

This commit is contained in:
2026-08-12 01:18:47 +02:00
parent 8c7302de49
commit c3c1d5ae28
5 changed files with 146 additions and 59 deletions
@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include <destrum/ObjectModel/Component.h> #include <destrum/ObjectModel/Component.h>
#include <destrum/ObjectModel/GameObject.h> #include <destrum/ObjectModel/GameObject.h>
@@ -17,6 +18,13 @@ public:
Registry()[typeName] = std::move(createFn); Registry()[typeName] = std::move(createFn);
} }
template <typename TComponent>
static void Register(const char* typeName) {
Register(std::string{typeName}, [](GameObject& owner) -> Component* {
return owner.AddComponent<TComponent>();
});
}
static Component* Create(const std::string& typeName, GameObject& owner) { static Component* Create(const std::string& typeName, GameObject& owner) {
const auto it = Registry().find(typeName); const auto it = Registry().find(typeName);
@@ -31,6 +39,15 @@ public:
return Registry().contains(typeName); return Registry().contains(typeName);
} }
[[nodiscard]] static std::vector<std::string> GetRegisteredTypeNames() {
std::vector<std::string> typeNames;
typeNames.reserve(Registry().size());
for (const auto& [typeName, _] : Registry()) {
typeNames.push_back(typeName);
}
return typeNames;
}
private: private:
static std::unordered_map<std::string, CreateFn>& Registry() { static std::unordered_map<std::string, CreateFn>& Registry() {
static std::unordered_map<std::string, CreateFn> registry; static std::unordered_map<std::string, CreateFn> registry;
@@ -0,0 +1,27 @@
#ifndef DESTRUM_ENGINECOMPONENTLIST_H
#define DESTRUM_ENGINECOMPONENTLIST_H
// Keep the component manifest in one place. Consumers define X before
// expanding one of these lists to generate registration or other metadata.
#include <destrum/Components/Animator.h>
#include <destrum/Components/MeshRendererComponent.h>
#include <destrum/Components/OrbitAndSpin.h>
#include <destrum/Components/Rotator.h>
#include <destrum/Components/Spinner.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/SphereCollider.h>
#define DESTRUM_ENGINE_COMPONENTS(X) \
X(MeshRendererComponent) \
X(Rotator) \
X(Spinner) \
X(OrbitAndSpin) \
X(Animator) \
X(Rigidbody) \
X(BoxCollider) \
X(SphereCollider) \
X(CapsuleCollider)
#endif // DESTRUM_ENGINECOMPONENTLIST_H
@@ -1,18 +1,6 @@
// destrum/Serialization/ComponentRegistry.cpp
#include <destrum/Serialization/ComponentRegistry.h> #include <destrum/Serialization/ComponentRegistry.h>
#include <destrum/Serialization/ComponentFactory.h> #include <destrum/Serialization/ComponentFactory.h>
#include <destrum/Serialization/EngineComponentList.h>
#include <destrum/Components/MeshRendererComponent.h>
#include <destrum/Components/Rotator.h>
#include <destrum/Components/Spinner.h>
#include <destrum/Components/OrbitAndSpin.h>
#include <destrum/Components/Animator.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Components/Physics/CapsuleCollider.h>
#include <destrum/Components/Physics/SphereCollider.h>
void RegisterEngineComponents() void RegisterEngineComponents()
{ {
@@ -23,47 +11,8 @@ void RegisterEngineComponents()
registered = true; registered = true;
ComponentFactory::Register("MeshRendererComponent", [](GameObject& owner) { #define DESTRUM_REGISTER_COMPONENT(ComponentType) \
return owner.AddComponent<MeshRendererComponent>(); ComponentFactory::Register<ComponentType>(#ComponentType);
}); DESTRUM_ENGINE_COMPONENTS(DESTRUM_REGISTER_COMPONENT)
// Keep the old serialized spelling readable while writing the canonical #undef DESTRUM_REGISTER_COMPONENT
// component name returned by MeshRendererComponent.
ComponentFactory::Register("MeshRenderer", [](GameObject& owner) {
return owner.AddComponent<MeshRendererComponent>();
});
ComponentFactory::Register("Rotator", [](GameObject& owner) {
return owner.AddComponent<Rotator>();
});
ComponentFactory::Register("Spinner", [](GameObject& owner) {
return owner.AddComponent<Spinner>();
});
ComponentFactory::Register("OrbitAndSpin", [](GameObject& owner) {
return owner.AddComponent<OrbitAndSpin>();
});
ComponentFactory::Register("Animator", [](GameObject& owner) {
return owner.AddComponent<Animator>();
});
ComponentFactory::Register("Rigidbody", [](GameObject& owner) {
return owner.AddComponent<Rigidbody>();
});
ComponentFactory::Register("RigidBody", [](GameObject& owner) {
return owner.AddComponent<Rigidbody>();
});
ComponentFactory::Register("BoxCollider", [](GameObject& owner) {
return owner.AddComponent<BoxCollider>();
});
ComponentFactory::Register("SphereCollider", [](GameObject& owner) {
return owner.AddComponent<SphereCollider>();
});
ComponentFactory::Register("CapsuleCollider", [](GameObject& owner) {
return owner.AddComponent<CapsuleCollider>();
});
} }
+67 -2
View File
@@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <cfloat> #include <cfloat>
#include <exception>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@@ -14,6 +15,8 @@
#include <destrum/ObjectModel/Component.h> #include <destrum/ObjectModel/Component.h>
#include <destrum/ObjectModel/GameObject.h> #include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h> #include <destrum/ObjectModel/Transform.h>
#include <destrum/Serialization/ComponentFactory.h>
#include <destrum/Serialization/ComponentRegistry.h>
namespace { namespace {
[[nodiscard]] bool IsInspectableObject(const GameObject* object) { [[nodiscard]] bool IsInspectableObject(const GameObject* object) {
@@ -121,6 +124,8 @@ namespace ImGuiUtils {
const std::vector<std::shared_ptr<GameObject>>& objectsSource, const std::vector<std::shared_ptr<GameObject>>& objectsSource,
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions, const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
ObjectId& selectedObjectId) { ObjectId& selectedObjectId) {
RegisterEngineComponents();
std::vector<GameObject*> objects; std::vector<GameObject*> objects;
objects.reserve(objectsSource.size() + pendingAdditions.size()); objects.reserve(objectsSource.size() + pendingAdditions.size());
@@ -268,9 +273,69 @@ namespace ImGuiUtils {
worldPosition.z); worldPosition.z);
} }
if (ImGui::CollapsingHeader( const bool componentsOpen = ImGui::CollapsingHeader(
"Components", "Components",
ImGuiTreeNodeFlags_DefaultOpen)) { ImGuiTreeNodeFlags_DefaultOpen);
if (componentsOpen) {
if (ImGui::Button("+ Add Component")) {
ImGui::OpenPopup("AddComponentPopup");
}
std::string addComponentError;
if (ImGui::BeginPopup("AddComponentPopup")) {
std::vector<std::string> componentTypes =
ComponentFactory::GetRegisteredTypeNames();
std::sort(componentTypes.begin(), componentTypes.end());
bool hasAvailableComponent = false;
for (const std::string& componentType : componentTypes) {
const bool alreadyAdded = std::any_of(
selectedObject->GetComponents().begin(),
selectedObject->GetComponents().end(),
[&componentType](const auto& component) {
return component != nullptr &&
!component->IsBeingDestroyed() &&
component->GetTypeName() == componentType;
});
if (alreadyAdded) {
const std::string label =
componentType + " (already added)";
ImGui::BeginDisabled();
ImGui::MenuItem(label.c_str());
ImGui::EndDisabled();
continue;
}
hasAvailableComponent = true;
if (ImGui::MenuItem(componentType.c_str())) {
try {
if (ComponentFactory::Create(
componentType,
*selectedObject) == nullptr) {
addComponentError =
"Component type is not registered: " +
componentType;
} else {
ImGui::CloseCurrentPopup();
}
} catch (const std::exception& exception) {
addComponentError = exception.what();
}
}
}
if (!hasAvailableComponent) {
ImGui::TextDisabled("All components are already added.");
}
if (!addComponentError.empty()) {
ImGui::TextWrapped("Failed to add component: %s",
addComponentError.c_str());
}
ImGui::EndPopup();
}
bool hasComponents = false; bool hasComponents = false;
for (const auto& component : selectedObject->GetComponents()) { for (const auto& component : selectedObject->GetComponents()) {
if (component == nullptr || component->IsBeingDestroyed()) { if (component == nullptr || component->IsBeingDestroyed()) {
+29
View File
@@ -13,6 +13,8 @@
#include <destrum/ObjectModel/GameObject.h> #include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h> #include <destrum/Scene/Scene.h>
#include <destrum/Scene/SceneManager.h> #include <destrum/Scene/SceneManager.h>
#include <destrum/Serialization/ComponentFactory.h>
#include <destrum/Serialization/ComponentRegistry.h>
#include <destrum/Serialization/SceneSerializer.h> #include <destrum/Serialization/SceneSerializer.h>
#include <destrum/Physics/SimplePhysicsWorld.h> #include <destrum/Physics/SimplePhysicsWorld.h>
#include <destrum/Physics/JoltPhysicsWorld.h> #include <destrum/Physics/JoltPhysicsWorld.h>
@@ -161,6 +163,32 @@ namespace {
SceneManager::GetInstance().Destroy(); SceneManager::GetInstance().Destroy();
} }
void testEngineComponentRegistration()
{
RegisterEngineComponents();
const char* canonicalNames[] = {
"MeshRendererComponent",
"Rotator",
"Spinner",
"OrbitAndSpin",
"Animator",
"Rigidbody",
"BoxCollider",
"SphereCollider",
"CapsuleCollider"
};
for (const char* name : canonicalNames) {
check(ComponentFactory::IsRegistered(name),
"all canonical engine component names must be registered");
}
check(!ComponentFactory::IsRegistered("MeshRenderer"),
"MeshRenderer legacy alias must not be registered");
check(!ComponentFactory::IsRegistered("RigidBody"),
"RigidBody legacy alias must not be registered");
}
void testAssetPathValidation() void testAssetPathValidation()
{ {
const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test"; const auto root = std::filesystem::temp_directory_path() / "destrum_asset_test";
@@ -440,6 +468,7 @@ int main()
testEventMutation(); testEventMutation();
testEventRemoval(); testEventRemoval();
testComponentRemoval(); testComponentRemoval();
testEngineComponentRegistration();
testAssetPathValidation(); testAssetPathValidation();
testLegacySceneLoad(); testLegacySceneLoad();
testSceneLoadRollback(); testSceneLoadRollback();