#include "Features/Inspector/InspectorPresentationModel.h" #include "Features/Inspector/Components/IInspectorComponentEditor.h" #include "Features/Inspector/Components/InspectorComponentEditorUtils.h" #include "Features/Inspector/Components/InspectorComponentEditorRegistry.h" #include "Scene/EditorSceneRuntime.h" #include #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"); } std::string ResolveNoneSelectionTitle() { return "Nothing selected"; } std::string ResolveNoneSelectionSubtitle() { return "Select a hierarchy item or project 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)); } const EditorSceneComponentDescriptor* FindComponentDescriptor( const std::vector& descriptors, std::string_view componentId) { const auto it = std::find_if( descriptors.begin(), descriptors.end(), [componentId](const EditorSceneComponentDescriptor& descriptor) { return descriptor.componentId == componentId; }); return it != descriptors.end() ? &(*it) : nullptr; } InspectorComponentEditorContext BuildComponentEditorContext( const EditorSceneComponentDescriptor& descriptor, const GameObject* gameObject, const IInspectorComponentEditor* editor) { InspectorComponentEditorContext context = {}; context.gameObject = gameObject; context.component = descriptor.component; context.componentId = descriptor.componentId; context.typeName = descriptor.typeName; context.displayName = editor != nullptr ? std::string(editor->GetDisplayName()) : descriptor.typeName; context.removable = descriptor.removable; return context; } void AppendProjectStructureSignature( const EditorSelectionState& selection, std::string& signature) { AppendInspectorStructureToken(signature, "project"); AppendInspectorStructureToken(signature, selection.directory); } void AppendNoneSelectionStructureSignature(std::string& signature) { AppendInspectorStructureToken(signature, "none"); } 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 AppendFallbackComponentStructureSignature( const EditorSceneComponentDescriptor& descriptor, std::string& signature) { AppendInspectorStructureToken(signature, "fallback"); AppendInspectorStructureToken(signature, descriptor.componentId); AppendInspectorStructureToken(signature, descriptor.typeName); AppendInspectorStructureToken(signature, descriptor.removable); } void AppendComponentStructureSignature( const EditorSceneComponentDescriptor& descriptor, const GameObject* gameObject, const InspectorComponentEditorRegistry& componentEditorRegistry, std::string& signature) { const IInspectorComponentEditor* editor = componentEditorRegistry.FindEditor(descriptor.typeName); if (editor == nullptr) { AppendFallbackComponentStructureSignature(descriptor, signature); return; } const InspectorComponentEditorContext context = BuildComponentEditorContext(descriptor, gameObject, editor); AppendInspectorStructureToken(signature, descriptor.componentId); AppendInspectorStructureToken(signature, descriptor.typeName); AppendInspectorStructureToken(signature, descriptor.removable); editor->AppendStructureSignature(context, signature); } void AppendComponentPresentation( InspectorPresentationModel& model, const EditorSceneComponentDescriptor& descriptor, const GameObject* gameObject, const InspectorComponentEditorRegistry& componentEditorRegistry, std::string& structureSignature) { InspectorPresentationComponentBinding binding = {}; binding.componentId = descriptor.componentId; binding.typeName = descriptor.typeName; binding.removable = descriptor.removable; const IInspectorComponentEditor* editor = componentEditorRegistry.FindEditor(descriptor.typeName); if (editor != nullptr) { const InspectorComponentEditorContext context = BuildComponentEditorContext(descriptor, gameObject, editor); binding.displayName = context.displayName; AppendComponentStructureSignature( descriptor, gameObject, componentEditorRegistry, structureSignature); 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 { binding.displayName = descriptor.typeName; AppendFallbackComponentStructureSignature(descriptor, structureSignature); 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)); } std::string BuildSceneObjectStructureSignature( const InspectorSceneObjectSubject& sceneObject, const EditorSceneRuntime& sceneRuntime, const InspectorComponentEditorRegistry& componentEditorRegistry) { std::string signature = {}; AppendInspectorStructureToken(signature, "scene"); for (const EditorSceneComponentDescriptor& descriptor : sceneRuntime.GetSelectedComponents()) { AppendComponentStructureSignature( descriptor, sceneObject.gameObject, componentEditorRegistry, signature); } return signature; } InspectorPresentationSyncResult SyncSceneObjectPresentationValues( InspectorPresentationModel& model, const InspectorSceneObjectSubject& sceneObject, const EditorSceneRuntime& sceneRuntime, const InspectorComponentEditorRegistry& componentEditorRegistry) { InspectorPresentationSyncResult result = {}; const std::vector descriptors = sceneRuntime.GetSelectedComponents(); model.hasSelection = true; model.showHeader = false; model.title = ResolveSceneObjectDisplayName(sceneObject); model.subtitle = "GameObject"; for (const InspectorPresentationComponentBinding& binding : model.componentBindings) { const EditorSceneComponentDescriptor* descriptor = FindComponentDescriptor(descriptors, binding.componentId); if (descriptor == nullptr || descriptor->typeName != binding.typeName) { return result; } const IInspectorComponentEditor* editor = componentEditorRegistry.FindEditor(binding.typeName); if (editor == nullptr) { continue; } const InspectorComponentEditorContext context = BuildComponentEditorContext(*descriptor, sceneObject.gameObject, editor); for (const std::string& fieldId : binding.fieldIds) { const auto location = Widgets::FindUIEditorPropertyGridFieldLocation(model.sections, fieldId); if (!location.IsValid() || location.sectionIndex >= model.sections.size() || location.fieldIndex >= model.sections[location.sectionIndex].fields.size()) { return result; } result.valueChanged |= editor->SyncFieldValue( context, model.sections[location.sectionIndex].fields[location.fieldIndex]); } } result.success = true; return result; } } // 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; AppendProjectStructureSignature(selection, model.structureSignature); 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; model.showHeader = false; model.title = ResolveSceneObjectDisplayName(sceneObject); model.subtitle = "GameObject"; AppendInspectorStructureToken(model.structureSignature, "scene"); for (const EditorSceneComponentDescriptor& descriptor : sceneRuntime.GetSelectedComponents()) { AppendComponentPresentation( model, descriptor, sceneObject.gameObject, componentEditorRegistry, model.structureSignature); } return model; } case InspectorSubjectKind::None: default: model.title = ResolveNoneSelectionTitle(); model.subtitle = ResolveNoneSelectionSubtitle(); AppendNoneSelectionStructureSignature(model.structureSignature); return model; } } std::string BuildInspectorStructureSignature( const InspectorSubject& subject, const EditorSceneRuntime& sceneRuntime, const InspectorComponentEditorRegistry& componentEditorRegistry) { switch (subject.kind) { case InspectorSubjectKind::ProjectAsset: { std::string signature = {}; AppendProjectStructureSignature(subject.projectAsset.selection, signature); return signature; } case InspectorSubjectKind::SceneObject: return BuildSceneObjectStructureSignature( subject.sceneObject, sceneRuntime, componentEditorRegistry); case InspectorSubjectKind::None: default: { std::string signature = {}; AppendNoneSelectionStructureSignature(signature); return signature; } } } InspectorPresentationSyncResult SyncInspectorPresentationModelValues( InspectorPresentationModel& model, const InspectorSubject& subject, const EditorSceneRuntime& sceneRuntime, const InspectorComponentEditorRegistry& componentEditorRegistry) { switch (subject.kind) { case InspectorSubjectKind::ProjectAsset: model.hasSelection = true; model.showHeader = true; model.title = ResolveProjectSelectionTitle(subject.projectAsset.selection); model.subtitle = subject.projectAsset.selection.directory ? "Folder" : "Asset"; return InspectorPresentationSyncResult{ .success = true, .valueChanged = false }; case InspectorSubjectKind::SceneObject: return SyncSceneObjectPresentationValues( model, subject.sceneObject, sceneRuntime, componentEditorRegistry); case InspectorSubjectKind::None: default: model.hasSelection = false; model.showHeader = true; model.title = ResolveNoneSelectionTitle(); model.subtitle = ResolveNoneSelectionSubtitle(); return InspectorPresentationSyncResult{ .success = true, .valueChanged = false }; } } } // namespace XCEngine::UI::Editor::App