859 lines
27 KiB
C++
859 lines
27 KiB
C++
#include "Editor/EditorGUI.h"
|
|
#include "Rendering/ESetCamera.h"
|
|
|
|
EditorGUI::EditorGUI(World* world, EventBroker* eventBroker)
|
|
: m_World(world)
|
|
, m_EventBroker(eventBroker)
|
|
{
|
|
EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &EditorGUI::OnKeyDown);
|
|
EVENT_SUBSCRIBE_MEMBER(m_EFileDropped, &EditorGUI::OnFileDropped);
|
|
EVENT_SUBSCRIBE_MEMBER(m_EPause, &EditorGUI::OnPause);
|
|
EVENT_SUBSCRIBE_MEMBER(m_EResume, &EditorGUI::OnResume);
|
|
EVENT_SUBSCRIBE_MEMBER(m_ELockMouse, &EditorGUI::OnLockMouse);
|
|
EVENT_SUBSCRIBE_MEMBER(m_EUnlockMouse, &EditorGUI::OnUnlockMouse);
|
|
}
|
|
|
|
void EditorGUI::Draw()
|
|
{
|
|
ImGui::ShowTestWindow();
|
|
drawMenu();
|
|
drawTools();
|
|
drawEntities(m_World);
|
|
drawComponents(m_CurrentSelection);
|
|
drawModals();
|
|
}
|
|
|
|
void EditorGUI::SelectEntity(EntityWrapper entity)
|
|
{
|
|
m_CurrentSelection = entity;
|
|
if (m_OnEntitySelected != nullptr) {
|
|
m_OnEntitySelected(entity);
|
|
}
|
|
}
|
|
|
|
void EditorGUI::drawMenu()
|
|
{
|
|
|
|
}
|
|
|
|
void EditorGUI::drawTools()
|
|
{
|
|
if (!ImGui::Begin("Tools", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
return;
|
|
}
|
|
|
|
// Widget modes
|
|
createWidgetToolButton(WidgetMode::Translate);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Translate (W)");
|
|
}
|
|
ImGui::SameLine();
|
|
createWidgetToolButton(WidgetMode::Rotate);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Rotate (E)");
|
|
}
|
|
ImGui::SameLine();
|
|
createWidgetToolButton(WidgetMode::Scale);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Scale (R)");
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::ItemSize(ImVec2(5, 0));
|
|
|
|
// Widget space
|
|
ImGui::SameLine();
|
|
GLuint spaceTexture = 0;
|
|
if (m_CurrentWidgetSpace == WidgetSpace::Global) {
|
|
spaceTexture = tryLoadTexture("Textures/Icons/Global.png");
|
|
} else if (m_CurrentWidgetSpace == WidgetSpace::Local) {
|
|
spaceTexture = tryLoadTexture("Textures/Icons/Local.png");
|
|
}
|
|
if (ImGui::ImageButton((void*)spaceTexture, ImVec2(24, 24), ImVec2(0, 1), ImVec2(1, 0), -1, ImVec4(0, 0, 0, 0), ImVec4(1, 1, 1, 1))) {
|
|
toggleWidgetSpace();
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
if (m_CurrentWidgetSpace == WidgetSpace::Global) {
|
|
ImGui::SetTooltip("Widget space: Global (X)");
|
|
} else if (m_CurrentWidgetSpace == WidgetSpace::Local) {
|
|
ImGui::SetTooltip("Widget space: Local (X)");
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::ItemSize(ImVec2(5, 0));
|
|
|
|
// Play button
|
|
ImGui::SameLine();
|
|
if (ImGui::ImageButton((void*)tryLoadTexture("Textures/Icons/Play.png"), ImVec2(24, 24), ImVec2(0, 1), ImVec2(1, 0), -1, ImVec4(0, 0, 0, 0), (!m_Paused) ? ImVec4(0, 1, 0, 1) : ImVec4(1, 1, 1, 1))) {
|
|
Events::Resume e;
|
|
e.World = m_World;
|
|
m_EventBroker->Publish(e);
|
|
}
|
|
// Pause button
|
|
ImGui::SameLine();
|
|
if (ImGui::ImageButton((void*)tryLoadTexture("Textures/Icons/Pause.png"), ImVec2(24, 24), ImVec2(0, 1), ImVec2(1, 0), -1, ImVec4(0, 0, 0, 0), (m_Paused) ? ImVec4(0, 1, 0, 1) : ImVec4(1, 1, 1, 1))) {
|
|
Events::Pause e;
|
|
e.World = m_World;
|
|
m_EventBroker->Publish(e);
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void EditorGUI::drawEntities(World* world)
|
|
{
|
|
if (!ImGui::Begin("Entities")) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
float buttonWidth = (ImGui::GetContentRegionAvailWidth() - 10.f) / 3.f ;
|
|
if (ImGui::Button("Create", ImVec2(buttonWidth, 0))) {
|
|
entityCreate(world, m_CurrentSelection);
|
|
}
|
|
ImGui::SameLine(0.f, 5.f);
|
|
if (ImGui::Button("Import", ImVec2(buttonWidth, 0))) {
|
|
entityImport(world);
|
|
}
|
|
ImGui::SameLine(0.f, 5.f);
|
|
ImGui::ButtonEx("Reference", ImVec2(buttonWidth, 0), ImGuiButtonFlags_Disabled);
|
|
|
|
// Naming
|
|
char buffer[256];
|
|
buffer[0] = '\0';
|
|
buffer[255] = '\0';
|
|
std::size_t nameLength = 0;
|
|
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll;
|
|
if (m_CurrentSelection.Valid()) {
|
|
std::string name = world->GetName(m_CurrentSelection.ID);
|
|
nameLength = name.length();
|
|
if (!name.empty()) {
|
|
memcpy(buffer, name.c_str(), std::min(sizeof(buffer) - 1, name.length() + 1));
|
|
}
|
|
} else {
|
|
flags |= ImGuiInputTextFlags_ReadOnly;
|
|
}
|
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - 7.f);
|
|
if (ImGui::InputText("", &buffer[0], sizeof(buffer), flags)) {
|
|
if (m_CurrentSelection.Valid()) {
|
|
if (m_OnEntityChangeName != nullptr) {
|
|
m_OnEntityChangeName(m_CurrentSelection, std::string(buffer));
|
|
SetDirty(m_CurrentSelection);
|
|
}
|
|
}
|
|
}
|
|
ImGui::PopItemWidth();
|
|
|
|
ImGui::ItemSize(ImVec2(0, 3));
|
|
|
|
drawEntitiesRecursive(world, EntityID_Invalid);
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void EditorGUI::drawEntitiesRecursive(World* world, EntityID parent)
|
|
{
|
|
auto entityChildren = world->GetEntityChildren();
|
|
auto range = entityChildren.equal_range(parent);
|
|
for (auto it = range.first; it != range.second; it++) {
|
|
if (EditorGUI::drawEntityNode(EntityWrapper(world, it->second))) {
|
|
drawEntitiesRecursive(world, it->second);
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::drawEntityNode(EntityWrapper entity)
|
|
{
|
|
// Custom button hitbox to select entities on top of tree node
|
|
ImVec2 pos = ImGui::GetCursorScreenPos();
|
|
float width = ImGui::GetContentRegionAvailWidth();
|
|
ImRect bb(pos + ImVec2(20, 0), pos + ImVec2(width, 14));
|
|
auto window = ImGui::GetCurrentWindow();
|
|
if (m_CurrentSelection == entity) {
|
|
const ImU32 col = window->Color(ImGuiCol_HeaderActive);
|
|
window->DrawList->AddRectFilled(bb.Min, bb.Max, col);
|
|
}
|
|
ImGuiID id = window->GetID((std::string("#SelectButton") + std::to_string(entity.ID)).c_str());
|
|
bool hovered = false;
|
|
bool held = false;
|
|
if (ImGui::ButtonBehavior(bb, id, &hovered, &held)) {
|
|
SelectEntity(entity);
|
|
}
|
|
// Handle entity dragging
|
|
if (held) {
|
|
ImVec2 entityDragDelta = ImGui::GetMouseDragDelta(0);
|
|
if (std::abs(entityDragDelta.x) > 0 && std::abs(entityDragDelta.y) > 0) {
|
|
if (m_CurrentlyDragging == EntityWrapper::Invalid) {
|
|
m_CurrentlyDragging = entity;
|
|
LOG_DEBUG("Started dragging %i", entity.ID);
|
|
}
|
|
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos + ImVec2(20, 0));
|
|
ImGui::Begin("Change parent", nullptr, ImVec2(0, 0), 0.3f, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings);
|
|
ImGui::Text(formatEntityName(entity).c_str());
|
|
ImGui::End();
|
|
}
|
|
}
|
|
// Entity context menu
|
|
std::string contextMenuUniqueID = std::string("EntityContextMenu") + std::to_string(entity.ID);
|
|
if (hovered && ImGui::IsMouseClicked(1)) {
|
|
ImGui::OpenPopup(contextMenuUniqueID.c_str());
|
|
}
|
|
if (ImGui::BeginPopup(contextMenuUniqueID.c_str())) {
|
|
ImGui::TextDisabled(formatEntityName(entity).c_str());
|
|
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
|
entitySave(entity);
|
|
} else
|
|
if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S")) {
|
|
entitySave(entity, true);
|
|
} else
|
|
if (ImGui::MenuItem("Delete", "Del")) {
|
|
entityDelete(entity);
|
|
} else
|
|
if (ImGui::MenuItem("Move to root")) {
|
|
entityChangeParent(entity, EntityWrapper::Invalid);
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::SetNextTreeNodeOpened(true, ImGuiSetCond_Once);
|
|
if (ImGui::TreeNode(formatEntityName(entity).c_str())) {
|
|
// Handle drop events for reparenting
|
|
if (m_CurrentlyDragging != EntityWrapper::Invalid && ImGui::IsItemHoveredRect() && ImGui::IsMouseReleased(0)) {
|
|
entityChangeParent(m_CurrentlyDragging, entity);
|
|
m_CurrentlyDragging = EntityWrapper::Invalid;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void EditorGUI::drawComponents(EntityWrapper entity)
|
|
{
|
|
std::stringstream title;
|
|
title << "Components";
|
|
if (entity.Valid()) {
|
|
title << " " << formatEntityName(entity);
|
|
}
|
|
title << "###Components";
|
|
if (!ImGui::Begin(title.str().c_str())) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
if (!entity.Valid()) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
auto& pools = entity.World->GetComponentPools();
|
|
// Create list of component types available to be added
|
|
std::vector<const char*> componentTypes;
|
|
for (auto& pair : pools) {
|
|
// Don't list components the entity already has attached
|
|
if (!entity.HasComponent(pair.first)) {
|
|
componentTypes.push_back(pair.first.c_str());
|
|
}
|
|
}
|
|
// Sort components in alphabetical order
|
|
std::sort(componentTypes.begin(), componentTypes.end(), compareCharArray);
|
|
// Draw combo box
|
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - 10.f);
|
|
int selectedItem = -1;
|
|
if (ImGui::Combo("", &selectedItem, componentTypes.data(), componentTypes.size(), componentTypes.size())) {
|
|
if (selectedItem != -1) {
|
|
if (m_OnComponentAttach != nullptr) {
|
|
std::string chosenComponentType(componentTypes.at(selectedItem));
|
|
m_OnComponentAttach(entity, chosenComponentType);
|
|
SetDirty(entity);
|
|
}
|
|
}
|
|
}
|
|
ImGui::PopItemWidth();
|
|
|
|
for (auto& pair : pools) {
|
|
const std::string& componentType = pair.first;
|
|
auto pool = pair.second;
|
|
// Don't show components the entity doesn't have attached
|
|
if (!entity.HasComponent(componentType)) {
|
|
continue;
|
|
}
|
|
// 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::drawComponentNode(EntityWrapper entity, const ComponentInfo& ci)
|
|
{
|
|
if (!ImGui::CollapsingHeader(ci.Name.c_str(), nullptr, true, true)) {
|
|
return false;
|
|
}
|
|
|
|
// Show component annotation
|
|
const std::string annotation = ci.Meta->Annotation;
|
|
if (!annotation.empty()) {
|
|
ImGui::TextWrapped(annotation.c_str());
|
|
}
|
|
|
|
// Draw component fields
|
|
ComponentWrapper& component = entity.World->GetComponent(entity.ID, ci.Name);
|
|
for (auto& fieldName : ci.FieldsInOrder) {
|
|
const ComponentInfo::Field_t& field = ci.Fields.at(fieldName);
|
|
|
|
// Draw the field widget based on its type
|
|
bool dirty = drawComponentField(component, field);
|
|
if (dirty) {
|
|
SetDirty(entity);
|
|
}
|
|
ImGui::SameLine();
|
|
// Draw field name
|
|
ImGui::Text(fieldName.c_str());
|
|
// Draw potential field annotation
|
|
auto fieldAnnotationIt = ci.Meta->FieldAnnotations.find(fieldName);
|
|
if (fieldAnnotationIt != ci.Meta->FieldAnnotations.end()) {
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("(?)");
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip(fieldAnnotationIt->second.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ci.Name == "Camera") {
|
|
if (ImGui::Button("Activate")) {
|
|
Events::SetCamera e;
|
|
e.CameraEntity = entity;
|
|
m_EventBroker->Publish(e);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField(ComponentWrapper& c, const ComponentInfo::Field_t& field)
|
|
{
|
|
// Push an unique widget id so different components with fields with equal names are still counted as different
|
|
ImGui::PushID((c.Info.Name + field.Name).c_str());
|
|
|
|
bool dirty = false;
|
|
|
|
if (field.Type == "Vector") {
|
|
dirty = drawComponentField_Vector(c, field);
|
|
} else if (field.Type == "Color") {
|
|
dirty = drawComponentField_Color(c, field);
|
|
//} else if (field.Type == "Quaternion") {
|
|
} else if (field.Type == "int") {
|
|
dirty = drawComponentField_int(c, field);
|
|
} else if (field.Type == "enum") {
|
|
dirty = drawComponentField_enum(c, field);
|
|
} else if (field.Type == "float") {
|
|
dirty = drawComponentField_float(c, field);
|
|
} else if (field.Type == "double") {
|
|
dirty = drawComponentField_double(c, field);
|
|
} else if (field.Type == "bool") {
|
|
dirty = drawComponentField_bool(c, field);
|
|
} else if (field.Type == "string") {
|
|
dirty = drawComponentField_string(c, field);
|
|
} else {
|
|
ImGui::TextDisabled(field.Type.c_str());
|
|
}
|
|
|
|
ImGui::PopID();
|
|
|
|
return dirty;
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_Vector(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto& val = c.Field<glm::vec3>(field.Name);
|
|
if (field.Name == "Scale") {
|
|
// Limit scale values to a minimum of 0
|
|
return ImGui::DragFloat3("", glm::value_ptr(val), 0.1f, 0.f, std::numeric_limits<float>::max());
|
|
} else if (field.Name == "Orientation") {
|
|
// Make orentations have a period of 2*Pi
|
|
glm::vec3 tempVal = glm::fmod(val, glm::vec3(glm::two_pi<float>()));
|
|
if (ImGui::SliderFloat3("", glm::value_ptr(tempVal), 0.f, glm::two_pi<float>())) {
|
|
val = tempVal;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return ImGui::DragFloat3("", glm::value_ptr(val), 0.1f, std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max());
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_Color(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto& val = c.Field<glm::vec4>(field.Name);
|
|
return ImGui::ColorEdit4("", glm::value_ptr(val), true);
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_int(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto& val = c.Field<int>(field.Name);
|
|
return ImGui::InputInt("", &val);
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_enum(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto fieldEnumDefIt = c.Info.Meta->FieldEnumDefinitions.find(field.Name);
|
|
if (fieldEnumDefIt == c.Info.Meta->FieldEnumDefinitions.end()) {
|
|
return drawComponentField_int(c, field);
|
|
}
|
|
|
|
auto& val = c.Field<int>(field.Name);
|
|
int selectedItem = -1;
|
|
std::stringstream enumKeys;
|
|
std::vector<int> enumValues;
|
|
int i = 0;
|
|
for (auto& kv : fieldEnumDefIt->second) {
|
|
enumKeys << kv.first << " (" << kv.second << ")" << '\0';
|
|
enumValues.push_back(kv.second);
|
|
if (val == kv.second) {
|
|
selectedItem = i;
|
|
}
|
|
i++;
|
|
}
|
|
if (ImGui::Combo("", &selectedItem, enumKeys.str().c_str())) {
|
|
val = enumValues.at(selectedItem);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_float(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto& val = c.Field<float>(field.Name);
|
|
return ImGui::InputFloat("", &val, 0.01f, 1.f);
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_double(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
float tempVal = static_cast<float>(c.Field<double>(field.Name));
|
|
if (ImGui::InputFloat("", &tempVal, 0.01f, 1.f)) {
|
|
c.SetField(field.Name, static_cast<double>(tempVal));
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_bool(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
auto& val = c.Field<bool>(field.Name);
|
|
return ImGui::Checkbox("", &val);
|
|
}
|
|
|
|
bool EditorGUI::drawComponentField_string(ComponentWrapper &c, const ComponentInfo::Field_t &field)
|
|
{
|
|
bool result = false;
|
|
|
|
auto& val = c.Field<std::string>(field.Name);
|
|
|
|
char tempString[1024]; // Let's just hope this is an sufficiently large buffer for strings :)
|
|
tempString[1023] = '\0'; // Null terminator just in case the string is larger than the buffer
|
|
// Copy the string into the buffer, taking the null terminator into account
|
|
memcpy(tempString, val.c_str(), std::min(val.length() + 1, sizeof(tempString) - 1));
|
|
if (ImGui::InputText("", tempString, sizeof(tempString))) {
|
|
val = std::string(tempString);
|
|
result = true;
|
|
}
|
|
|
|
// Handle file drag and drop
|
|
if (ImGui::IsItemHovered() && !m_DroppedFile.empty()) {
|
|
// Unset potential input focus or our newly set value will be overwritten!
|
|
if (ImGui::IsItemActive()) {
|
|
ImGui::SetActiveID(0, nullptr);
|
|
}
|
|
// Set the actual dropped value
|
|
val = m_DroppedFile;
|
|
m_DroppedFile = "";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void EditorGUI::drawModals()
|
|
{
|
|
for (auto& modal : m_ModalsToOpen) {
|
|
ImGui::OpenPopup(modal.c_str());
|
|
}
|
|
m_ModalsToOpen.clear();
|
|
|
|
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();
|
|
}
|
|
|
|
if (ImGui::BeginPopupModal("Confirm deletion", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
if (m_ModalData.count("Confirm deletion") == 0) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::Text("Are you sure you want to delete entity \"%s\"?", formatEntityName(m_CurrentSelection).c_str());
|
|
ImGui::ItemSize(ImVec2(5.f, 0.f));
|
|
ImGui::SetCursorPosX(ImGui::GetContentRegionAvailWidth() - 2*60);
|
|
if (ImGui::Button("Delete (Del)", ImVec2(60, 0))) {
|
|
entityDelete(boost::any_cast<EntityWrapper>(m_ModalData.at("Confirm deletion")));
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel", ImVec2(60, 0))) {
|
|
m_ModalData.erase("Confirm deletion");
|
|
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;
|
|
}
|
|
|
|
void EditorGUI::createWidgetToolButton(WidgetMode mode)
|
|
{
|
|
GLuint texture = 0;
|
|
switch (mode) {
|
|
case WidgetMode::Translate:
|
|
texture = tryLoadTexture("Textures/Icons/Translate.png");
|
|
break;
|
|
case WidgetMode::Rotate:
|
|
texture = tryLoadTexture("Textures/Icons/Rotate.png");
|
|
break;
|
|
case WidgetMode::Scale:
|
|
texture = tryLoadTexture("Textures/Icons/Scale.png");
|
|
break;
|
|
}
|
|
if (ImGui::ImageButton(
|
|
(void*)texture,
|
|
ImVec2(24, 24),
|
|
ImVec2(0, 1),
|
|
ImVec2(1, 0),
|
|
-1,
|
|
ImVec4(0, 0, 0, 0),
|
|
(m_CurrentWidgetMode == mode) ? ImVec4(0, 1, 0, 1) : ImVec4(1, 1, 1, 1)
|
|
)
|
|
) {
|
|
setWidgetMode(mode);
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::OnKeyDown(const Events::KeyDown& e)
|
|
{
|
|
if (e.ModCtrl && e.KeyCode == GLFW_KEY_S) {
|
|
if (m_CurrentSelection.Valid()) {
|
|
EntityWrapper baseParent = m_CurrentSelection;
|
|
while (baseParent.Parent().Valid()) {
|
|
baseParent = baseParent.Parent();
|
|
}
|
|
entitySave(baseParent);
|
|
}
|
|
}
|
|
|
|
if (e.ModCtrl && e.KeyCode == GLFW_KEY_N) {
|
|
entityCreate(m_World, m_CurrentSelection);
|
|
}
|
|
|
|
if (e.ModCtrl && e.KeyCode == GLFW_KEY_O) {
|
|
entityImport(m_World);
|
|
}
|
|
|
|
if (e.KeyCode == GLFW_KEY_DELETE) {
|
|
if (m_CurrentSelection.Valid()) {
|
|
entityDelete(m_CurrentSelection);
|
|
}
|
|
}
|
|
|
|
if (!m_MouseLocked) {
|
|
if (e.KeyCode == GLFW_KEY_W) {
|
|
setWidgetMode(WidgetMode::Translate);
|
|
}
|
|
if (e.KeyCode == GLFW_KEY_E) {
|
|
setWidgetMode(WidgetMode::Rotate);
|
|
}
|
|
if (e.KeyCode == GLFW_KEY_R) {
|
|
setWidgetMode(WidgetMode::Scale);
|
|
}
|
|
|
|
if (e.KeyCode == GLFW_KEY_X) {
|
|
toggleWidgetSpace();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::OnFileDropped(const Events::FileDropped& e)
|
|
{
|
|
// Make a best effort to make the path relative to the working directory of the executable
|
|
m_DroppedFile = boost::filesystem::path(e.Path).lexically_relative(boost::filesystem::current_path()).string();
|
|
// Compensate for Windows retardedness
|
|
std::replace(m_DroppedFile.begin(), m_DroppedFile.end(), '\\', '/');
|
|
// Special case for when people drop from the asset folder instead of from the symlink to the asset folders in bin
|
|
boost::algorithm::replace_first(m_DroppedFile, "../assets/", "");
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::OnPause(const Events::Pause& e)
|
|
{
|
|
if (e.World == m_World) {
|
|
m_Paused = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::OnResume(const Events::Resume& e)
|
|
{
|
|
if (e.World == m_World) {
|
|
m_Paused = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::OnLockMouse(const Events::LockMouse& e)
|
|
{
|
|
m_MouseLocked = true;
|
|
return true;
|
|
}
|
|
|
|
bool EditorGUI::OnUnlockMouse(const Events::UnlockMouse& e)
|
|
{
|
|
m_MouseLocked = false;
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
const std::string EditorGUI::formatEntityName(EntityWrapper entity)
|
|
{
|
|
if (!entity.Valid()) {
|
|
return "EntityID_Invalid";
|
|
}
|
|
|
|
std::stringstream name;
|
|
|
|
std::string entityName = entity.World->GetName(entity.ID);
|
|
if (!entityName.empty()) {
|
|
name << entityName;
|
|
} else {
|
|
name << "#" << entity.ID;
|
|
}
|
|
|
|
if (m_EntityFiles.count(entity) == 1) {
|
|
name << " (" << m_EntityFiles.at(entity).Path.filename().string() << ")";
|
|
if (m_EntityFiles.at(entity).Dirty) {
|
|
name << "*";
|
|
}
|
|
}
|
|
|
|
return name.str();
|
|
}
|
|
|
|
GLuint EditorGUI::tryLoadTexture(std::string filePath)
|
|
{
|
|
GLuint texture = 0;
|
|
try {
|
|
texture = ResourceManager::Load<Texture>(filePath)->m_Texture;
|
|
} catch (const std::exception&) { }
|
|
return texture;
|
|
}
|
|
|
|
void EditorGUI::openModal(const std::string& modal)
|
|
{
|
|
m_ModalsToOpen.insert(modal);
|
|
}
|
|
|
|
void EditorGUI::setWidgetMode(WidgetMode mode)
|
|
{
|
|
if (m_OnWidgetMode != nullptr) {
|
|
m_OnWidgetMode(mode);
|
|
}
|
|
m_CurrentWidgetMode = mode;
|
|
}
|
|
|
|
void EditorGUI::toggleWidgetSpace()
|
|
{
|
|
if (m_CurrentWidgetSpace == WidgetSpace::Global) {
|
|
m_CurrentWidgetSpace = WidgetSpace::Local;
|
|
} else if (m_CurrentWidgetSpace == WidgetSpace::Local) {
|
|
m_CurrentWidgetSpace = WidgetSpace::Global;
|
|
}
|
|
|
|
if (m_OnWidgetSpace != nullptr) {
|
|
m_OnWidgetSpace(m_CurrentWidgetSpace);
|
|
}
|
|
}
|
|
|
|
bool EditorGUI::compareCharArray(const char* c1, const char* c2)
|
|
{
|
|
return strcmp(c1, c2) < 0;
|
|
}
|
|
|
|
void EditorGUI::SetDirty(EntityWrapper entity)
|
|
{
|
|
EntityWrapper baseParent = entity;
|
|
while (baseParent.Parent().Valid()) {
|
|
baseParent = baseParent.Parent();
|
|
}
|
|
if (m_EntityFiles.find(baseParent) != m_EntityFiles.end()) {
|
|
m_EntityFiles.at(baseParent).Dirty = true;
|
|
}
|
|
}
|
|
|
|
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].Path = filePath;
|
|
SelectEntity(entity);
|
|
} else {
|
|
openModal("Import failed");
|
|
}
|
|
}
|
|
|
|
void EditorGUI::entitySave(EntityWrapper entity, bool saveAs /* = false */)
|
|
{
|
|
boost::filesystem::path filePath;
|
|
if (!saveAs && m_EntityFiles.count(entity) == 1) {
|
|
filePath = m_EntityFiles.at(entity).Path;
|
|
} else {
|
|
filePath = fileSaveDialog();
|
|
}
|
|
|
|
if (filePath.empty()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
m_OnEntitySave(entity, filePath);
|
|
m_EntityFiles[entity].Path = filePath;
|
|
m_EntityFiles[entity].Dirty = false;
|
|
} catch (const std::exception& e) {
|
|
m_LastErrorMessage = e.what();
|
|
openModal("Save failed");
|
|
}
|
|
}
|
|
|
|
void EditorGUI::entityCreate(World* world, EntityWrapper parent)
|
|
{
|
|
if (m_OnEntityCreate != nullptr) {
|
|
// Create the new entity in the world we're drawing for
|
|
if (parent.World == nullptr) {
|
|
parent.World = world;
|
|
}
|
|
EntityWrapper newEntity = m_OnEntityCreate(parent);
|
|
SelectEntity(newEntity);
|
|
}
|
|
}
|
|
|
|
void EditorGUI::entityDelete(EntityWrapper entity)
|
|
{
|
|
std::string modalName = "Confirm deletion";
|
|
|
|
if (m_ModalData.count(modalName) == 0) {
|
|
m_ModalData[modalName] = entity;
|
|
openModal(modalName);
|
|
} else {
|
|
if (boost::any_cast<EntityWrapper>(m_ModalData[modalName]) == entity) {
|
|
EntityWrapper parent = entity.Parent();
|
|
if (m_OnEntityDelete != nullptr) {
|
|
SetDirty(entity);
|
|
m_OnEntityDelete(entity);
|
|
m_EntityFiles.erase(entity);
|
|
}
|
|
if (!m_CurrentSelection.Valid()) {
|
|
SelectEntity(parent);
|
|
}
|
|
}
|
|
m_ModalData.erase(modalName);
|
|
}
|
|
}
|
|
|
|
void EditorGUI::entityChangeParent(EntityWrapper entity, EntityWrapper parent)
|
|
{
|
|
if (entity == parent || parent.IsChildOf(entity)) {
|
|
return;
|
|
}
|
|
|
|
if (m_OnEntityChangeParent != nullptr) {
|
|
SetDirty(entity);
|
|
m_OnEntityChangeParent(entity, parent);
|
|
LOG_DEBUG("Changed parent of %i to %i", entity.ID, parent.ID);
|
|
}
|
|
}
|