#include "Features/Inspector/InspectorPresentationModel.h" #include "Features/Inspector/Components/IInspectorComponentEditor.h" #include "Features/Inspector/Components/InspectorComponentEditorRegistry.h" #include "Scene/EditorSceneRuntime.h" #include #include namespace XCEngine::UI::Editor::App { namespace { using ::XCEngine::Components::GameObject; using Widgets::UIEditorPropertyGridField; using Widgets::UIEditorPropertyGridFieldKind; using Widgets::UIEditorPropertyGridSection; std::string PathToUtf8String(const std::filesystem::path& path) { const std::u8string value = path.u8string(); return std::string(value.begin(), value.end()); } std::string ResolveSceneObjectDisplayName( const InspectorSceneObjectSubject& sceneObject) { return sceneObject.displayName.empty() ? std::string("GameObject") : sceneObject.displayName; } std::string ResolveProjectSelectionTitle( const EditorSelectionState& selection) { if (!selection.displayName.empty()) { return selection.displayName; } return selection.directory ? std::string("Folder") : std::string("Asset"); } UIEditorPropertyGridField BuildReadOnlyTextField( std::string fieldId, std::string label, std::string value) { UIEditorPropertyGridField field = {}; field.fieldId = std::move(fieldId); field.label = std::move(label); field.kind = UIEditorPropertyGridFieldKind::Text; field.valueText = std::move(value); field.readOnly = true; return field; } void AppendReadOnlySection( InspectorPresentationModel& model, std::string sectionId, std::string title, std::vector fields) { UIEditorPropertyGridSection section = {}; section.sectionId = std::move(sectionId); section.title = std::move(title); section.fields = std::move(fields); model.sections.push_back(std::move(section)); } void AppendFallbackComponentSection( InspectorPresentationModel& model, const EditorSceneComponentDescriptor& descriptor) { std::vector fields = {}; fields.push_back(BuildReadOnlyTextField( "component." + descriptor.componentId + ".type", "Type", descriptor.typeName)); fields.push_back(BuildReadOnlyTextField( "component." + descriptor.componentId + ".status", "Status", "Inspector not implemented")); AppendReadOnlySection( model, "component." + descriptor.componentId, descriptor.typeName, std::move(fields)); } void AppendComponentPresentation( InspectorPresentationModel& model, const EditorSceneComponentDescriptor& descriptor, const GameObject* gameObject, const InspectorComponentEditorRegistry& componentEditorRegistry) { InspectorPresentationComponentBinding binding = {}; binding.componentId = descriptor.componentId; binding.typeName = descriptor.typeName; binding.removable = descriptor.removable; InspectorComponentEditorContext context = {}; context.gameObject = gameObject; context.component = descriptor.component; context.componentId = descriptor.componentId; context.typeName = descriptor.typeName; context.removable = descriptor.removable; const IInspectorComponentEditor* editor = componentEditorRegistry.FindEditor(descriptor.typeName); if (editor != nullptr) { context.displayName = std::string(editor->GetDisplayName()); binding.displayName = context.displayName; const std::size_t sectionCountBefore = model.sections.size(); editor->BuildSections(context, model.sections); for (std::size_t sectionIndex = sectionCountBefore; sectionIndex < model.sections.size(); ++sectionIndex) { for (const UIEditorPropertyGridField& field : model.sections[sectionIndex].fields) { binding.fieldIds.push_back(field.fieldId); } } } else { context.displayName = descriptor.typeName; binding.displayName = descriptor.typeName; AppendFallbackComponentSection(model, descriptor); if (!model.sections.empty()) { for (const UIEditorPropertyGridField& field : model.sections.back().fields) { binding.fieldIds.push_back(field.fieldId); } } } model.componentBindings.push_back(std::move(binding)); } } // namespace InspectorPresentationModel BuildInspectorPresentationModel( const InspectorSubject& subject, const EditorSceneRuntime& sceneRuntime, const InspectorComponentEditorRegistry& componentEditorRegistry) { InspectorPresentationModel model = {}; model.hasSelection = subject.HasSelection(); switch (subject.kind) { case InspectorSubjectKind::ProjectAsset: { const EditorSelectionState& selection = subject.projectAsset.selection; const std::string title = ResolveProjectSelectionTitle(selection); const std::string typeLabel = selection.directory ? std::string("Folder") : std::string("Asset"); model.title = title; model.subtitle = typeLabel; AppendReadOnlySection( model, "project.identity", "Identity", { BuildReadOnlyTextField("project.identity.type", "Type", typeLabel), BuildReadOnlyTextField("project.identity.name", "Name", title), BuildReadOnlyTextField("project.identity.id", "Id", selection.itemId) }); AppendReadOnlySection( model, "project.location", "Location", { BuildReadOnlyTextField( "project.location.path", "Path", PathToUtf8String(selection.absolutePath)) }); return model; } case InspectorSubjectKind::SceneObject: { const InspectorSceneObjectSubject& sceneObject = subject.sceneObject; const std::string title = ResolveSceneObjectDisplayName(sceneObject); model.title = title; model.subtitle = "GameObject"; AppendReadOnlySection( model, "scene.identity", "Identity", { BuildReadOnlyTextField("scene.identity.type", "Type", "GameObject"), BuildReadOnlyTextField("scene.identity.name", "Name", title), BuildReadOnlyTextField("scene.identity.id", "Id", sceneObject.itemId) }); if (sceneObject.gameObject != nullptr) { const GameObject* parent = sceneObject.gameObject->GetParent(); const std::string parentName = parent != nullptr ? (parent->GetName().empty() ? std::string("GameObject") : parent->GetName()) : std::string("Scene Root"); AppendReadOnlySection( model, "scene.hierarchy", "Hierarchy", { BuildReadOnlyTextField( "scene.hierarchy.children", "Children", std::to_string(sceneObject.gameObject->GetChildCount())), BuildReadOnlyTextField( "scene.hierarchy.parent", "Parent", parentName) }); } for (const EditorSceneComponentDescriptor& descriptor : sceneRuntime.GetSelectedComponents()) { AppendComponentPresentation( model, descriptor, sceneObject.gameObject, componentEditorRegistry); } return model; } case InspectorSubjectKind::None: default: model.title = "Nothing selected"; model.subtitle = "Select a hierarchy item or project asset."; return model; } } } // namespace XCEngine::UI::Editor::App