/*
GameEngine and Editor
by John Ryland
Copyright (c) 2023
*/
////////////////////////////////////////////////////////////////////////////////////
// Entity Tree Property Editor View
#include "EntityTreePropertyEditorView.h"
#include "IconsFontAwesome5.h"
#include "SchemaEdit.h"
#include "imgui.h"
#include <algorithm>
namespace GameEngine {
// Layout of the 'SceneObject' component type
struct SceneObject
{
EntityId Self;
EntityId Parent;
EntityId FirstChild;
EntityId NextSibling;
char Name[64];
uint64_t Layer;
};
void EntityTreePropertyEditorView::AddShowMenuItem()
{
ImGui::MenuItem("Show Scene Heirarchy", NULL, &m_showObjectTreeView);
ImGui::MenuItem("Show Object Properties", NULL, &m_showPropertyEditor);
}
void EntityTreePropertyEditorView::Update()
{
ShowEntityProperties();
ShowObjectTree();
}
void EntityTreePropertyEditorView::Initialize()
{
}
void EntityTreePropertyEditorView::Shutdown()
{
}
void EntityTreePropertyEditorView::AddTreeNode(SceneObject* sceneObj)
{
ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen;
if (m_selectedEntityId == sceneObj->Self)
node_flags |= ImGuiTreeNodeFlags_Selected;
auto childId = sceneObj->FirstChild;
if (childId == -1)
node_flags |= ImGuiTreeNodeFlags_Leaf;
bool isOpen = ImGui::TreeNodeEx((void*)(intptr_t)sceneObj->Self, node_flags, "%s", sceneObj->Name);
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
m_selectedEntityId = sceneObj->Self;
if (isOpen)
{
while (childId != -1)
{
auto& entRef = m_entityComponentSystem.GetEntity(childId);
if (entRef.components.count(m_sceneObjectId))
{
SceneObject* childObj = (SceneObject*)entRef.components.at(m_sceneObjectId);
AddTreeNode(childObj);
childId = childObj->NextSibling;
}
else
{
childId = -1;
}
}
ImGui::TreePop();
}
}
void EntityTreePropertyEditorView::ShowObjectTree()
{
bool* p_open = &m_showObjectTreeView;
if (!*p_open)
return;
if (ImGui::Begin("Scene Heirarchy", p_open /*, ImGuiWindowFlags_MenuBar */ ))
{
m_sceneObjectId = m_entityComponentSystem.FindComponentTypeByName("SceneObject");
auto roots = m_entityComponentSystem.FindEntitiesWithComponentType(m_entityComponentSystem.FindComponentTypeByName("RootNode"));
assert(roots.size() <= 1);
if (roots.size())
{
auto& entRef = m_entityComponentSystem.GetEntity(roots[0]);
if (entRef.components.count(m_sceneObjectId))
AddTreeNode((SceneObject*)entRef.components.at(m_sceneObjectId));
}
}
ImGui::End();
}
void ShowFields(GameRuntime::Schema* schema, GameRuntime::VariableDescription* vars, int count, uint8_t* entData)
{
for (int i = 0; i < count; ++i)
{
auto& var = vars[i];
if (var.variableType == GameRuntime::VariableTypeEnum::E_Struct)
{
if (ImGui::TreeNodeEx(ConvertRuntimeString(schema, var.name).c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanFullWidth))
{
ShowFields(schema, schema->variables->variables + var.structDescription.variableDescriptionTableOffset, var.structDescription.numberOfFields, entData);
ImGui::TreePop();
}
}
else
{
std::string entName = ConvertRuntimeString(schema, var.name);
entName += "##" + std::to_string((size_t)entData);
std::vector<std::string> enumStrings;
std::vector<int> enumValue;
std::vector<const char*> enumStringsCStr;
static int current = 1;
static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f };
switch (var.variableType)
{
case GameRuntime::VariableTypeEnum::E_Void:
ImGui::Text("%s", entName.c_str());
break;
case GameRuntime::VariableTypeEnum::E_Bool:
ImGui::Checkbox(entName.c_str(), (bool*)entData);
break;
case GameRuntime::VariableTypeEnum::E_Enum:
for (int e = 0; e < var.enumDescription.numberOfEnumValues; e++)
{
auto& enumEnt = schema->enums->values[var.enumDescription.nameValueTableOffset + e];
enumStrings.push_back(ConvertRuntimeString(schema, enumEnt.name));
enumValue.push_back(enumEnt.value);
if (*(int*)entData == enumEnt.value)
current = e;
}
for (int e = 0; e < var.enumDescription.numberOfEnumValues; e++)
enumStringsCStr.push_back(enumStrings[e].c_str());
ImGui::Combo(entName.c_str(), ¤t, enumStringsCStr.data(), enumStringsCStr.size());
*(int*)entData = enumValue[current];
break;
case GameRuntime::VariableTypeEnum::E_String:
ImGui::InputText(entName.c_str(), (char*)entData, var.stringDescription.maximumCharacterCount);// &str[0], IM_ARRAYSIZE(str));
break;
case GameRuntime::VariableTypeEnum::E_Number:
switch (var.numberDescription.numberType)
{
case E_UInt8:
case E_UInt16:
case E_UInt32:
case E_UInt64:
case E_Int8:
case E_Int16:
case E_Int32:
case E_Int64:
ImGui::InputInt(entName.c_str(), (int*)entData, 1.0f);
break;
case E_Float32:
ImGui::InputFloat(entName.c_str(), (float*)entData, 1.0f);
break;
case E_Float64:
ImGui::InputDouble(entName.c_str(), (double*)entData, 1.0f);
break;
case E_Angle:
ImGui::SliderAngle(entName.c_str(), (float*)entData, 1.0f);
break;
case E_Ratio:
ImGui::SliderFloat(entName.c_str(), (float*)entData, 0.0f, 1.0f, "ratio = %.3f");
break;
}
break;
//case GameRuntime::E_EncodedNumber:
// break;
case GameRuntime::VariableTypeEnum::E_Color:
col4f[0] = entData[0] / 255.0;
col4f[1] = entData[1] / 255.0;
col4f[2] = entData[2] / 255.0;
col4f[3] = entData[3] / 255.0;
if (var.colorDescription.colorType == GameRuntime::E_RGBA32)
ImGui::ColorEdit4(entName.c_str(), col4f);
else
ImGui::ColorEdit3(entName.c_str(), col4f);
entData[0] = (uint8_t)(col4f[0] * 255.0);
entData[1] = (uint8_t)(col4f[1] * 255.0);
entData[2] = (uint8_t)(col4f[2] * 255.0);
entData[3] = (uint8_t)(col4f[3] * 255.0);
break;
case GameRuntime::VariableTypeEnum::E_Entity:
// TODO: combo-box selection
ImGui::InputInt(entName.c_str(), (int*)entData);
break;
case GameRuntime::VariableTypeEnum::E_Asset:
ImGui::Text("asset");
break;
case GameRuntime::VariableTypeEnum::E_Struct:
ImGui::Text("struct");
break;
case GameRuntime::VariableTypeEnum::E_Component:
ImGui::Text("component");
break;
}
}
entData += CalculateFieldSize(*schema, (size_t)entData, var);
}
}
bool AddPopup(bool openCond, const char* caption, const char* message)
{
bool okayClicked = false;
static bool dont_ask_me_next_time = false;
if (openCond)
{
if (dont_ask_me_next_time)
return true;
else
ImGui::OpenPopup(caption);
}
// Always center this window when appearing
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal(caption, NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("%s", message);
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time);
ImGui::PopStyleVar();
if (ImGui::Button("OK", ImVec2(120, 0)))
{
okayClicked = true;
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0)))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return okayClicked;
}
void EntityTreePropertyEditorView::ShowEntityProperties()
{
bool* p_open = &m_showPropertyEditor;
if (!*p_open)
return;
if (ImGui::Begin("Object Properties", p_open))
{
auto schema = m_entityComponentSystem.GetSchema();
auto entityId = m_selectedEntityId;
auto entName = "Entity #" + std::to_string(m_selectedEntityId) + " Properties:";
ImGui::Separator();
ImGui::Text("%s", entName.c_str());
auto& entRef = m_entityComponentSystem.GetEntity(entityId);
auto sceneObjectTypeId = m_entityComponentSystem.FindComponentTypeByName("SceneObject");
if (entRef.components.count(sceneObjectTypeId))
{
ImGui::Text("Name: %s", ((SceneObject*)entRef.components.at(sceneObjectTypeId))->Name);
ImGui::Separator();
}
for (auto& comp : entRef.components)
{
auto compId = comp.first;
void* entData = (void*)comp.second;
auto& compRef = m_entityComponentSystem.GetComponentType(compId);
if (compRef.name != "SceneObject")
{
if (ImGui::CollapsingHeader(compRef.name.c_str(), ImGuiTreeNodeFlags_DefaultOpen))
{
auto* vars = schema->variables->variables + compRef.fields.variableDescriptionTableOffset;
ShowFields(schema, vars, compRef.fields.numberOfFields, (uint8_t*)entData);
}
}
}
static int clicked = 0;
if (AddPopup(ImGui::Button("Delete.."), "Delete?", "All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"))
clicked++;
if (ImGui::Button("Add Component"))
clicked++;
if (clicked & 1)
{
ImGui::Begin("Components", p_open);
StringList compTypes = m_entityComponentSystem.AllComponentTypeNames();
std::vector<const char*> compTypesCStr;
for (auto& ct : compTypes)
compTypesCStr.emplace_back(ct.c_str());
static int item_current = 0;
ImGui::SameLine();
if (ImGui::ListBox(" ", &item_current, compTypesCStr.data(), compTypesCStr.size(), 10))
{
clicked++;
std::string compTypeSelected = compTypes[item_current];
auto compTypeId = m_entityComponentSystem.FindComponentTypeByName(compTypeSelected);
printf(" component selected: %s id: %lu\n", compTypeSelected.c_str(), compTypeId);
}
ImGui::End();
}
}
ImGui::End();
}
} // GameEngine namespace