Editor entity import, save, delete, component attach, component delete.
This commit is contained in:
@@ -29,12 +29,14 @@ public:
|
||||
typedef std::function<void(EntityWrapper)> OnEntitySelectedCallback_t;
|
||||
void SetEntitySelectedCallback(OnEntitySelectedCallback_t f) { m_OnEntitySelected = f; }
|
||||
// Called when the user means to import an entity file.
|
||||
// Expects an EntityWrapper of the newly created entity in return.
|
||||
typedef std::function<EntityWrapper(boost::filesystem::path)> OnEntityImport_t;
|
||||
// @param EntityWrapper The entity to parent the imported entity to. The entity will be imported into the world of this entity.
|
||||
// @param boost::filesystem::path The path to the entity to import
|
||||
// @return EntityWrapper The newly created entity
|
||||
typedef std::function<EntityWrapper(EntityWrapper, boost::filesystem::path)> OnEntityImport_t;
|
||||
void SetEntityImportCallback(OnEntityImport_t f) { m_OnEntityImport = f; }
|
||||
// Called when the user means to save an entity to file.
|
||||
// Expects a bool indicating whether the save was successful or not in return.
|
||||
typedef std::function<bool(EntityWrapper, boost::filesystem::path)> OnEntitySave_t;
|
||||
// Permitted to throw exceptions on save failure.
|
||||
typedef std::function<void(EntityWrapper, boost::filesystem::path)> OnEntitySave_t;
|
||||
void SetEntitySaveCallback(OnEntitySave_t f) { m_OnEntitySave = f; }
|
||||
// Called when the user means to create a new entity.
|
||||
// @param EntityWrapper The parent of the entity to be created
|
||||
@@ -47,13 +49,20 @@ public:
|
||||
// Called when the user means to attach a new component to an entity.
|
||||
typedef std::function<void(EntityWrapper, const std::string&)> OnComponentAttach_t;
|
||||
void SetComponentAttachCallback(OnComponentAttach_t f) { m_OnComponentAttach = f; }
|
||||
|
||||
// Called when the user means to delete a component off an entity.
|
||||
typedef std::function<void(EntityWrapper, const std::string&)> OnComponentDelete_t;
|
||||
void SetComponentDeleteCallback(OnComponentDelete_t f) { m_OnComponentDelete = f; }
|
||||
|
||||
private:
|
||||
EventBroker* m_EventBroker;
|
||||
|
||||
// Config variables
|
||||
const boost::filesystem::path m_DefaultEntityPath = boost::filesystem::path("Schema") / boost::filesystem::path("Entities");
|
||||
|
||||
// State variables
|
||||
EntityWrapper m_CurrentSelection = EntityWrapper::Invalid;
|
||||
std::unordered_map<EntityWrapper, boost::filesystem::path> m_EntityFiles;
|
||||
std::string m_LastErrorMessage;
|
||||
|
||||
// Callbacks
|
||||
OnEntitySelectedCallback_t m_OnEntitySelected = nullptr;
|
||||
@@ -62,7 +71,17 @@ private:
|
||||
OnEntityCreate_t m_OnEntityCreate = nullptr;
|
||||
OnEntityDelete_t m_OnEntityDelete = nullptr;
|
||||
OnComponentAttach_t m_OnComponentAttach = nullptr;
|
||||
OnComponentDelete_t m_OnComponentDelete = nullptr;
|
||||
|
||||
// Utility functions
|
||||
boost::filesystem::path fileOpenDialog();
|
||||
boost::filesystem::path fileSaveDialog();
|
||||
|
||||
// Entity file handling methods
|
||||
void entityImport(World* world);
|
||||
void entitySave(EntityWrapper entity);
|
||||
|
||||
// UI drawing methods
|
||||
void drawMenu();
|
||||
void drawEntities(World* world);
|
||||
void drawEntitiesRecursive(World* world, EntityID parent);
|
||||
@@ -78,6 +97,10 @@ private:
|
||||
void drawComponentField_double(ComponentWrapper &c, const ComponentInfo::Field_t &field);
|
||||
void drawComponentField_bool(ComponentWrapper &c, const ComponentInfo::Field_t &field);
|
||||
void drawComponentField_string(ComponentWrapper &c, const ComponentInfo::Field_t &field);
|
||||
void drawModals();
|
||||
|
||||
// Custom UI elements
|
||||
bool createDeleteButton(const std::string& componentType);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "../Core/ResourceManager.h"
|
||||
#include "../Core/EntityFilePreprocessor.h"
|
||||
#include "../Core/EntityFileParser.h"
|
||||
#include "../Core/EntityFileWriter.h"
|
||||
#include "EditorGUI.h"
|
||||
#include "EditorStats.h"
|
||||
|
||||
@@ -31,5 +32,12 @@ private:
|
||||
EditorGUI* m_EditorGUI;
|
||||
EditorStats* m_EditorStats;
|
||||
|
||||
// Utility functions
|
||||
EntityWrapper importEntity(EntityWrapper parent, boost::filesystem::path filePath);
|
||||
|
||||
// GUI callbacks
|
||||
void OnEntitySelected(EntityWrapper entity);
|
||||
void OnEntitySave(EntityWrapper entity, boost::filesystem::path filePath);
|
||||
void OnComponentAttach(EntityWrapper entity, const std::string& componentType);
|
||||
void OnComponentDelete(EntityWrapper entity, const std::string& componentType);
|
||||
};
|
||||
+143
-17
@@ -28,9 +28,16 @@ void EditorGUI::drawEntities(World* world)
|
||||
}
|
||||
|
||||
float buttonWidth = (ImGui::GetContentRegionAvailWidth() - 10.f) / 3.f ;
|
||||
ImGui::Button("Create", ImVec2(buttonWidth, 0));
|
||||
if (ImGui::Button("Create", ImVec2(buttonWidth, 0))) {
|
||||
if (m_OnEntityCreate != nullptr) {
|
||||
EntityWrapper newEntity = m_OnEntityCreate(EntityWrapper(world, EntityID_Invalid));
|
||||
SelectEntity(newEntity);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine(0.f, 5.f);
|
||||
ImGui::Button("Import", ImVec2(buttonWidth, 0));
|
||||
if (ImGui::Button("Import", ImVec2(buttonWidth, 0))) {
|
||||
entityImport(world);
|
||||
}
|
||||
ImGui::SameLine(0.f, 5.f);
|
||||
ImGui::Button("Reference", ImVec2(buttonWidth, 0));
|
||||
|
||||
@@ -38,6 +45,8 @@ void EditorGUI::drawEntities(World* world)
|
||||
|
||||
drawEntitiesRecursive(world, EntityID_Invalid);
|
||||
|
||||
// Draw any potential modals before ending this scope
|
||||
drawModals();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -83,28 +92,29 @@ bool EditorGUI::drawEntityNode(EntityWrapper entity)
|
||||
// }
|
||||
//}
|
||||
|
||||
ImGui::SetNextTreeNodeOpened(true, ImGuiSetCond_Once);
|
||||
std::string nodeTitle;
|
||||
// Compose title
|
||||
std::stringstream nodeTitle;
|
||||
const std::string& entityName = entity.World->GetName(entity);
|
||||
if (!entityName.empty()) {
|
||||
nodeTitle = entityName;
|
||||
nodeTitle << entityName;
|
||||
} else {
|
||||
nodeTitle = std::string("#") + std::to_string(entity.ID);
|
||||
nodeTitle << "#" << entity.ID;
|
||||
}
|
||||
if (ImGui::TreeNode(nodeTitle.c_str())) {
|
||||
if (m_EntityFiles.count(entity) == 1) {
|
||||
nodeTitle << " (" << m_EntityFiles.at(entity).filename().string() << ")";
|
||||
}
|
||||
|
||||
ImGui::SetNextTreeNodeOpened(true, ImGuiSetCond_Once);
|
||||
if (ImGui::TreeNode(nodeTitle.str().c_str())) {
|
||||
//if (m_UIDraggingEntity != EntityID_Invalid && ImGui::IsItemHoveredRect() && ImGui::IsMouseReleased(0)) {
|
||||
// LOG_DEBUG("Changed parent of %i to %i", m_UIDraggingEntity, entity);
|
||||
// changeParent(m_UIDraggingEntity, entity);
|
||||
// m_UIDraggingEntity = EntityID_Invalid;
|
||||
//}
|
||||
|
||||
if (ImGui::BeginPopupContextItem("item context menu")) {
|
||||
if (ImGui::Button("Add")) {
|
||||
if (m_OnEntityCreate != nullptr) {
|
||||
EntityWrapper newEntity = m_OnEntityCreate(EntityWrapper(entity.World, EntityID_Invalid));
|
||||
ImGui::CloseCurrentPopup();
|
||||
SelectEntity(newEntity);
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("entity context menu")) {
|
||||
if (ImGui::Button("Save")) {
|
||||
entitySave(entity);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
@@ -116,6 +126,7 @@ bool EditorGUI::drawEntityNode(EntityWrapper entity)
|
||||
SelectEntity(EntityWrapper::Invalid);
|
||||
}
|
||||
}
|
||||
drawModals();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return true;
|
||||
@@ -170,14 +181,21 @@ void EditorGUI::drawComponents(EntityWrapper entity)
|
||||
if (!entity.HasComponent(componentType)) {
|
||||
continue;
|
||||
}
|
||||
// TODO: Add delete button here
|
||||
drawComponent(entity, pool->ComponentInfo());
|
||||
// Handle deletion with early out
|
||||
if (createDeleteButton(componentType)) {
|
||||
if (m_OnComponentDelete != nullptr) {
|
||||
m_OnComponentDelete(entity, componentType);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Draw the actual component node
|
||||
drawComponentNode(entity, pool->ComponentInfo());
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
bool EditorGUI::drawComponent(EntityWrapper entity, const ComponentInfo& ci)
|
||||
bool EditorGUI::drawComponentNode(EntityWrapper entity, const ComponentInfo& ci)
|
||||
{
|
||||
if (!ImGui::CollapsingHeader(ci.Name.c_str(), nullptr, true, true)) {
|
||||
return false;
|
||||
@@ -330,3 +348,111 @@ void EditorGUI::drawComponentField_string(ComponentWrapper &c, const ComponentIn
|
||||
// TODO: Handle drag and drop of files
|
||||
}
|
||||
|
||||
void EditorGUI::drawModals()
|
||||
{
|
||||
if (ImGui::BeginPopupModal("Import failed", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Entity import failed. Check console for more information.\n\n");
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionAvailWidth() - 120);
|
||||
if (ImGui::Button("OK", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("Save failed", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Entity save failed on an exception.\nMessage: %s\n\n", m_LastErrorMessage.c_str());
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionAvailWidth() - 120);
|
||||
if (ImGui::Button("OK", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorGUI::createDeleteButton(const std::string& componentType)
|
||||
{
|
||||
float width = ImGui::GetContentRegionAvailWidth();
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
auto pos = ImGui::GetCursorScreenPos() + ImVec2(width - 14.f, 1);
|
||||
ImRect bb = ImRect(pos, pos + ImVec2(14.f, 14.f));
|
||||
std::string idString = "#DELETE";
|
||||
idString += componentType;
|
||||
ImGuiID id = window->GetID(idString.c_str());
|
||||
bool hovered;
|
||||
bool held;
|
||||
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
|
||||
//ImU32 col = window->Color((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton);
|
||||
ImU32 col = window->Color((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||||
window->DrawList->AddCircleFilled(bb.GetCenter(), 7.f, col, 16);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
boost::filesystem::path EditorGUI::fileOpenDialog()
|
||||
{
|
||||
namespace bfs = boost::filesystem;
|
||||
nfdchar_t* outPath = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialog("xml", bfs::absolute(m_DefaultEntityPath).string().c_str(), &outPath);
|
||||
|
||||
if (result == NFD_ERROR) {
|
||||
LOG_ERROR("NFD Error: %s", NFD_GetError());
|
||||
return bfs::path();
|
||||
} else if (result == NFD_CANCEL) {
|
||||
return bfs::path();
|
||||
} else {
|
||||
return bfs::absolute(outPath);
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::path EditorGUI::fileSaveDialog()
|
||||
{
|
||||
namespace bfs = boost::filesystem;
|
||||
nfdchar_t* outPath = nullptr;
|
||||
nfdresult_t result = NFD_SaveDialog("xml", bfs::absolute(m_DefaultEntityPath).string().c_str(), &outPath);
|
||||
|
||||
if (result == NFD_ERROR) {
|
||||
LOG_ERROR("NFD Error: %s", NFD_GetError());
|
||||
return bfs::path();
|
||||
} else if (result == NFD_CANCEL) {
|
||||
return bfs::path();
|
||||
} else {
|
||||
return bfs::absolute(outPath);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorGUI::entityImport(World* world)
|
||||
{
|
||||
boost::filesystem::path filePath = fileOpenDialog();
|
||||
if (filePath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityWrapper entity = m_OnEntityImport(EntityWrapper(world, EntityID_Invalid), filePath);
|
||||
if (entity.Valid()) {
|
||||
m_EntityFiles[entity] = filePath;
|
||||
SelectEntity(entity);
|
||||
} else {
|
||||
ImGui::OpenPopup("Import failed");
|
||||
}
|
||||
}
|
||||
|
||||
void EditorGUI::entitySave(EntityWrapper entity)
|
||||
{
|
||||
boost::filesystem::path filePath;
|
||||
if (m_EntityFiles.count(entity) == 1) {
|
||||
filePath = m_EntityFiles.at(entity);
|
||||
} else {
|
||||
filePath = fileSaveDialog();
|
||||
}
|
||||
|
||||
if (filePath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m_OnEntitySave(entity, filePath);
|
||||
m_EntityFiles[entity] = filePath;
|
||||
} catch (const std::exception& e) {
|
||||
m_LastErrorMessage = e.what();
|
||||
ImGui::OpenPopup("Save failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,7 @@ EditorSystem::EditorSystem(EventBroker* eventBroker, IRenderer* renderer, Render
|
||||
m_EditorWorldSystemPipeline->AddSystem<UniformScaleSystem>(0);
|
||||
m_EditorWorldSystemPipeline->AddSystem<EditorRenderSystem>(1, m_Renderer, m_RenderFrame);
|
||||
|
||||
auto widgetEntityFile = ResourceManager::Load<EntityFile>("Schema/Entities/EditorWidget.xml");
|
||||
EntityFilePreprocessor fpp(widgetEntityFile);
|
||||
fpp.RegisterComponents(m_EditorWorld);
|
||||
EntityFileParser fp(widgetEntityFile);
|
||||
EntityID widgetID = fp.MergeEntities(m_EditorWorld);
|
||||
m_Widget = EntityWrapper(m_EditorWorld, widgetID);
|
||||
m_Widget = importEntity(EntityWrapper(m_EditorWorld, EntityID_Invalid), "Schema/Entities/EditorWidget.xml");
|
||||
|
||||
m_Camera = EntityWrapper(m_EditorWorld, m_EditorWorld->CreateEntity());
|
||||
m_EditorWorld->AttachComponent(m_Camera.ID, "Transform");
|
||||
@@ -26,6 +21,10 @@ EditorSystem::EditorSystem(EventBroker* eventBroker, IRenderer* renderer, Render
|
||||
|
||||
m_EditorGUI = new EditorGUI(m_EventBroker);
|
||||
m_EditorGUI->SetEntitySelectedCallback(std::bind(&EditorSystem::OnEntitySelected, this, std::placeholders::_1));
|
||||
m_EditorGUI->SetEntityImportCallback(std::bind(&EditorSystem::importEntity, this, std::placeholders::_1, std::placeholders::_2));
|
||||
m_EditorGUI->SetEntitySaveCallback(std::bind(&EditorSystem::OnEntitySave, this, std::placeholders::_1, std::placeholders::_2));
|
||||
m_EditorGUI->SetComponentAttachCallback(std::bind(&EditorSystem::OnComponentAttach, this, std::placeholders::_1, std::placeholders::_2));
|
||||
m_EditorGUI->SetComponentDeleteCallback(std::bind(&EditorSystem::OnComponentDelete, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
m_EditorStats = new EditorStats();
|
||||
|
||||
@@ -59,3 +58,38 @@ void EditorSystem::OnEntitySelected(EntityWrapper entity)
|
||||
{
|
||||
m_Widget["Transform"]["Position"] = Transform::AbsolutePosition(entity.World, entity.ID);
|
||||
}
|
||||
|
||||
void EditorSystem::OnEntitySave(EntityWrapper entity, boost::filesystem::path filePath)
|
||||
{
|
||||
EntityFileWriter writer(filePath);
|
||||
writer.WriteEntity(entity.World, entity.ID);
|
||||
}
|
||||
|
||||
void EditorSystem::OnComponentAttach(EntityWrapper entity, const std::string& componentType)
|
||||
{
|
||||
entity.World->AttachComponent(entity.ID, componentType);
|
||||
}
|
||||
|
||||
void EditorSystem::OnComponentDelete(EntityWrapper entity, const std::string& componentType)
|
||||
{
|
||||
entity.World->DeleteComponent(entity.ID, componentType);
|
||||
}
|
||||
|
||||
EntityWrapper EditorSystem::importEntity(EntityWrapper parent, boost::filesystem::path filePath)
|
||||
{
|
||||
if (parent.World == nullptr) {
|
||||
LOG_ERROR("Tried to import entity \"%s\" into null world!", filePath.string().c_str());
|
||||
return EntityWrapper::Invalid;
|
||||
}
|
||||
|
||||
try {
|
||||
auto entityFile = ResourceManager::Load<EntityFile>(filePath.string());
|
||||
EntityFilePreprocessor fpp(entityFile);
|
||||
fpp.RegisterComponents(parent.World);
|
||||
EntityFileParser fp(entityFile);
|
||||
EntityID newEntity = fp.MergeEntities(parent.World, parent.ID);
|
||||
return EntityWrapper(parent.World, newEntity);
|
||||
} catch (const std::exception&) {
|
||||
return EntityWrapper::Invalid;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user