Editor entity import, save, delete, component attach, component delete.

This commit is contained in:
2016-01-18 18:55:19 +01:00
parent 9cb5f4bd2f
commit 631dd57eeb
4 changed files with 219 additions and 28 deletions
+28 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
}
+40 -6
View File
@@ -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;
}
}