Files
XCEngine/new_editor/app/Features/Inspector/InspectorPresentationModel.cpp

414 lines
14 KiB
C++

#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 <XCEngine/Components/GameObject.h>
#include <algorithm>
#include <filesystem>
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<UIEditorPropertyGridField> 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<EditorSceneComponentDescriptor>& 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<UIEditorPropertyGridField> 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<EditorSceneComponentDescriptor> 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