diff --git a/Tetragrama/Components/InspectorViewUIComponent.cpp b/Tetragrama/Components/InspectorViewUIComponent.cpp index 69676b60..33cf9516 100644 --- a/Tetragrama/Components/InspectorViewUIComponent.cpp +++ b/Tetragrama/Components/InspectorViewUIComponent.cpp @@ -2,16 +2,252 @@ #include #include #include -#include #include -#include +#include #include +#include // clang-format on using namespace ZEngine::ECS; using namespace ZEngine::ECS::Components; using namespace ZEngine::Core::Maths; +namespace +{ + using namespace ZEngine::ECS; + using ZEngine::Core::Maths::Vec3f; + + bool SectionHeader(const char* id, const char* label, bool* open) + { + ImGui::PushID(id); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float w = ImGui::GetContentRegionAvail().x; + float h = 22.f; + ImDrawList* dl = ImGui::GetWindowDrawList(); + bool hov = ImGui::IsMouseHoveringRect(pos, {pos.x + w, pos.y + h}); + dl->AddRectFilled(pos, {pos.x + w, pos.y + h}, ImGui::GetColorU32(hov ? ImGuiCol_HeaderHovered : ImGuiCol_Header)); + ImU32 tri = ImGui::GetColorU32(ImGuiCol_Text); + float tx = pos.x + 8.f, ty = pos.y + h * 0.5f; + if (*open) + { + dl->AddTriangleFilled({tx, ty - 4.f}, {tx + 7.f, ty - 4.f}, {tx + 3.5f, ty + 4.f}, tri); + } + else + { + dl->AddTriangleFilled({tx, ty - 4.f}, {tx, ty + 4.f}, {tx + 7.f, ty}, tri); + } + dl->AddText({tx + 14.f, pos.y + (h - ImGui::GetTextLineHeight()) * 0.5f}, ImGui::GetColorU32(ImGuiCol_Text), label); + ImGui::InvisibleButton("##hdr", {w, h}); + if (ImGui::IsItemClicked()) + { + *open = !*open; + } + ImGui::PopID(); + return *open; + } + + int64_t ReadEnum(const void* ptr, uint32_t size) + { + switch (size) + { + case 1: + return *static_cast(ptr); + case 2: + return *static_cast(ptr); + case 4: + return *static_cast(ptr); + case 8: + return *static_cast(ptr); + default: + return 0; + } + } + + void WriteEnum(void* ptr, uint32_t size, int64_t value) + { + switch (size) + { + case 1: + *static_cast(ptr) = static_cast(value); + break; + case 2: + *static_cast(ptr) = static_cast(value); + break; + case 4: + *static_cast(ptr) = static_cast(value); + break; + case 8: + *static_cast(ptr) = value; + break; + default: + break; + } + } + + // Draws the three components of a Vec3f with the R/G/B axis bars. + void Vec3Row(float* v) + { + constexpr float kGap = 4.f; + float avail = ImGui::GetContentRegionAvail().x; + float fw = (avail - kGap * 2.f) / 3.f; + ImU32 border[3] = { + IM_COL32(215, 90, 80, 255), + IM_COL32(100, 200, 110, 255), + IM_COL32(90, 140, 230, 255), + }; + const char* ids[3] = {"##ax0", "##ax1", "##ax2"}; + ImDrawList* dl = ImGui::GetWindowDrawList(); + + for (int i = 0; i < 3; ++i) + { + if (i > 0) + { + ImGui::SameLine(0.f, kGap); + } + ImGui::SetNextItemWidth(fw); + ImGui::DragFloat(ids[i], &v[i], 0.05f, 0.f, 0.f, "%.3f"); + ImVec2 p0 = ImGui::GetItemRectMin(); + dl->AddRectFilled(p0, {p0.x + 3.f, ImGui::GetItemRectMax().y}, border[i]); + } + } + + void DrawField(const FieldDescriptor& field, void* ptr) + { + ImGui::TableSetColumnIndex(0); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); + ImGui::TextUnformatted(field.Name); + ImGui::PopStyleColor(); + if (field.Tooltip && ImGui::IsItemHovered()) + { + ImGui::SetTooltip("%s", field.Tooltip); + } + + ImGui::TableSetColumnIndex(1); + ImGui::PushID(field.Name); + ImGui::SetNextItemWidth(-1.f); + + switch (field.Type) + { + case FieldType::Bool: + ImGui::Checkbox("##v", static_cast(ptr)); + break; + case FieldType::Int32: + ImGui::DragInt("##v", static_cast(ptr)); + break; + case FieldType::UInt32: + ImGui::DragScalar("##v", ImGuiDataType_U32, ptr); + break; + case FieldType::Float: + ImGui::DragFloat("##v", static_cast(ptr), 0.05f, field.Min, field.Max); + break; + case FieldType::Vec3f: + Vec3Row(static_cast(ptr)); + break; + case FieldType::String: + ImGui::InputText("##v", static_cast(ptr), field.StringCap); + break; + case FieldType::AssetUUID: + { + char text[37] = {}; + uuids::to_string(*static_cast(ptr), text); + ImGui::InputText("##v", text, sizeof(text), ImGuiInputTextFlags_ReadOnly); + break; + } + case FieldType::Enum: + { + int64_t value = ReadEnum(ptr, field.Size); + const char* current = "unknown"; + for (uint32_t i = 0; i < field.EnumCount; ++i) + { + if (field.EnumValues[i].Value == value) + { + current = field.EnumValues[i].Name; + } + } + if (ImGui::BeginCombo("##v", current)) + { + for (uint32_t i = 0; i < field.EnumCount; ++i) + { + bool selected = (field.EnumValues[i].Value == value); + if (ImGui::Selectable(field.EnumValues[i].Name, selected)) + { + WriteEnum(ptr, field.Size, field.EnumValues[i].Value); + } + } + ImGui::EndCombo(); + } + break; + } + default: + ImGui::TextDisabled("(unsupported type)"); + break; + } + + ImGui::PopID(); + } + + void DrawEntityComponents(Scene& scene, EntityID id) + { + static constexpr ImGuiTableFlags kTableFlags = ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_NoPadOuterX; + + const ArchetypeMask mask = scene.GetMask(id); + + ComponentReflectionRegistry::Get().ForEach([&](const ComponentMeta& meta) { + if (!MaskHas(mask, meta.TypeID)) + { + return; + } + + void* raw = scene.GetComponentRaw(id, meta.TypeID); + if (!raw) + { + return; + } + + // Section open state is per component type, not per entity. + static bool s_open[ARCHETYPE_MASK_CAPACITY] = {}; + static bool s_init = [] { + for (bool& b : s_open) + { + b = true; + } + return true; + }(); + (void) s_init; + + if (!SectionHeader(meta.TypeName, meta.TypeName, &s_open[meta.TypeID])) + return; + + ImGui::PushID(static_cast(meta.TypeID)); + if (ImGui::BeginTable("##fields", 2, kTableFlags)) + { + ImGui::TableSetupColumn("##lbl", ImGuiTableColumnFlags_WidthFixed, 96.f); + ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + + for (uint32_t i = 0; i < meta.FieldCount; ++i) + { + const FieldDescriptor& field = meta.Fields[i]; + if (field.Hidden) + { + continue; + } + + ImGui::TableNextRow(); + ImGui::BeginDisabled(field.ReadOnly); + DrawField(field, static_cast(raw) + field.Offset); + ImGui::EndDisabled(); + } + + ImGui::TableNextRow(); + ImGui::EndTable(); + } + ImGui::PopID(); + ImGui::Spacing(); + }); + } +} // namespace + namespace Tetragrama::Components { InspectorViewUIComponent::InspectorViewUIComponent() = default; @@ -61,7 +297,6 @@ namespace Tetragrama::Components return; } - auto* nc = actor->GetComponent(); { constexpr float kBtnW = 112.f; constexpr float kMargin = 10.f; @@ -73,18 +308,6 @@ namespace Tetragrama::Components float card_w = ImGui::GetContentRegionAvail().x; ImGui::SetCursorPos({kMargin, 6.f}); - if (nc) - { - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.f, 0.f, 0.f, 0.f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 2.f)); - char buf[128] = {}; - snprintf(buf, sizeof(buf), "%s", nc->Value); - ImGui::SetNextItemWidth(card_w - kMargin - kBtnW - 8.f); - if (ImGui::InputText("##actor_name", buf, sizeof(buf))) - snprintf(nc->Value, sizeof(nc->Value), "%s", buf); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - } ImGui::SetCursorPos({kMargin, 24.f}); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); @@ -111,143 +334,7 @@ namespace Tetragrama::Components ImGui::Spacing(); - auto SectionHeader = [](const char* id, const char* label, bool* open) -> bool { - ImGui::PushID(id); - ImVec2 pos = ImGui::GetCursorScreenPos(); - float w = ImGui::GetContentRegionAvail().x; - float h = 22.f; - ImDrawList* dl = ImGui::GetWindowDrawList(); - bool hov = ImGui::IsMouseHoveringRect(pos, {pos.x + w, pos.y + h}); - dl->AddRectFilled(pos, {pos.x + w, pos.y + h}, ImGui::GetColorU32(hov ? ImGuiCol_HeaderHovered : ImGuiCol_Header)); - ImU32 tri = ImGui::GetColorU32(ImGuiCol_Text); - float tx = pos.x + 8.f, ty = pos.y + h * 0.5f; - if (*open) - dl->AddTriangleFilled({tx, ty - 4.f}, {tx + 7.f, ty - 4.f}, {tx + 3.5f, ty + 4.f}, tri); - else - dl->AddTriangleFilled({tx, ty - 4.f}, {tx, ty + 4.f}, {tx + 7.f, ty}, tri); - dl->AddText({tx + 14.f, pos.y + (h - ImGui::GetTextLineHeight()) * 0.5f}, ImGui::GetColorU32(ImGuiCol_Text), label); - ImGui::InvisibleButton("##hdr", {w, h}); - if (ImGui::IsItemClicked()) - *open = !*open; - ImGui::PopID(); - return *open; - }; - - auto XYZRow = [](const char* label, Vec3f& v, Vec3f reset_vals, float speed, auto onChange) { - ImGui::TableSetColumnIndex(0); - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); - ImGui::TextUnformatted(label); - ImGui::PopStyleColor(); - - ImGui::TableSetColumnIndex(1); - ImGui::PushID(label); - - const float kGap = 4.f; - const float kReset = 18.f; - float avail = ImGui::GetContentRegionAvail().x - kReset - kGap; - float fw = (avail - kGap * 2.f) / 3.f; - - ImU32 border[3] = { - IM_COL32(215, 90, 80, 255), - IM_COL32(100, 200, 110, 255), - IM_COL32(90, 140, 230, 255), - }; - const char* ids[3] = {"##ax0", "##ax1", "##ax2"}; - float* vals[3] = {&v.x, &v.y, &v.z}; - ImDrawList* dl = ImGui::GetWindowDrawList(); - - bool changed = false; - for (int i = 0; i < 3; ++i) - { - if (i > 0) - ImGui::SameLine(0.f, kGap); - ImGui::SetNextItemWidth(fw); - changed |= ImGui::DragFloat(ids[i], vals[i], speed, 0.f, 0.f, "%.3f"); - ImVec2 p0 = ImGui::GetItemRectMin(); - dl->AddRectFilled(p0, {p0.x + 3.f, ImGui::GetItemRectMax().y}, border[i]); - } - - ImGui::SameLine(0.f, kGap); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive)); - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); - if (ImGui::SmallButton("↺")) - { - v = reset_vals; - changed = true; - } - ImGui::PopStyleColor(4); - - ImGui::PopID(); - if (changed) - onChange(v); - }; - - static ImGuiTableFlags kTableFlags = ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_NoPadOuterX; - - auto* tc = actor->GetComponent(); - if (tc) - { - static bool s_transform_open = true; - if (SectionHeader("transform_hdr", "Transform", &s_transform_open)) - { - if (ImGui::BeginTable("##transform_tbl", 2, kTableFlags)) - { - ImGui::TableSetupColumn("##lbl", ImGuiTableColumnFlags_WidthFixed, 72.f); - ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch); - - constexpr float kDeg = 180.f / 3.14159265f; - constexpr float kRad = 3.14159265f / 180.f; - - ImGui::TableNextRow(); - ImGui::TableNextRow(); - XYZRow("Location", tc->Position, {0.f, 0.f, 0.f}, 0.05f, [tc](Vec3f& v) { tc->Position = v; }); - - ImGui::TableNextRow(); - Vec3f rot_deg = {tc->Rotation.x * kDeg, tc->Rotation.y * kDeg, tc->Rotation.z * kDeg}; - XYZRow("Rotation", rot_deg, {0.f, 0.f, 0.f}, 0.5f, [tc, kRad](Vec3f& v) { tc->Rotation = {v.x * kRad, v.y * kRad, v.z * kRad}; }); - - ImGui::TableNextRow(); - XYZRow("Scale", tc->Scale, {1.f, 1.f, 1.f}, 0.01f, [tc](Vec3f& v) { tc->Scale = v; }); - - ImGui::TableNextRow(); - ImGui::EndTable(); - } - ImGui::Spacing(); - } - } - - auto* mc = actor->GetComponent(); - if (mc) - { - static bool s_mesh_open = true; - if (SectionHeader("mesh_hdr", "Mesh", &s_mesh_open)) - { - if (ImGui::BeginTable("##mesh_tbl", 2, kTableFlags)) - { - ImGui::TableSetupColumn("##lbl", ImGuiTableColumnFlags_WidthFixed, 72.f); - ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); - ImGui::TextUnformatted("UUID"); - ImGui::PopStyleColor(); - - ImGui::TableSetColumnIndex(1); - std::string uuid_str = uuids::to_string(mc->MeshUUID); - ImGui::SetNextItemWidth(-1.f); - ImGui::InputText("##uuid", const_cast(uuid_str.c_str()), uuid_str.size() + 1, ImGuiInputTextFlags_ReadOnly); - - ImGui::TableNextRow(); - ImGui::EndTable(); - } - ImGui::Spacing(); - } - } + DrawEntityComponents(*ctx->Scene, actor->GetEntityID()); ImGui::End(); }