655 lines
24 KiB
C++
655 lines
24 KiB
C++
#include "Inspector/Components/IInspectorComponentEditor.h"
|
|
#include "Inspector/Components/InspectorComponentEditorRegistry.h"
|
|
#include "Inspector/InspectorFieldValueApplier.h"
|
|
#include "Inspector/InspectorPresentationModel.h"
|
|
#include "Inspector/InspectorSubject.h"
|
|
#include "Scene/EditorSceneRuntime.h"
|
|
#include "Scene/EngineEditorSceneBackend.h"
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
#include <XCEngine/Scene/SceneManager.h>
|
|
#include <XCEngine/Scripting/IScriptRuntime.h>
|
|
#include <XCEngine/Scripting/ScriptComponent.h>
|
|
#include <XCEngine/Scripting/ScriptEngine.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <unordered_map>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
namespace {
|
|
|
|
using ::XCEngine::Components::GameObject;
|
|
using ::XCEngine::Components::Scene;
|
|
using ::XCEngine::Components::SceneManager;
|
|
using ::XCEngine::Scripting::ComponentReference;
|
|
using ::XCEngine::Scripting::GameObjectReference;
|
|
using ::XCEngine::Scripting::IScriptRuntime;
|
|
using ::XCEngine::Scripting::ScriptClassDescriptor;
|
|
using ::XCEngine::Scripting::ScriptComponent;
|
|
using ::XCEngine::Scripting::ScriptEngine;
|
|
using ::XCEngine::Scripting::ScriptFieldDefaultValue;
|
|
using ::XCEngine::Scripting::ScriptFieldMetadata;
|
|
using ::XCEngine::Scripting::ScriptFieldType;
|
|
using ::XCEngine::Scripting::ScriptFieldValue;
|
|
using ::XCEngine::Scripting::ScriptLifecycleMethod;
|
|
using ::XCEngine::Scripting::ScriptPhysicsMessage;
|
|
using ::XCEngine::Scripting::ScriptRuntimeContext;
|
|
using Widgets::UIEditorPropertyGridField;
|
|
using Widgets::UIEditorPropertyGridFieldKind;
|
|
using Widgets::UIEditorPropertyGridSection;
|
|
|
|
class FakeInspectorScriptRuntime final : public IScriptRuntime {
|
|
public:
|
|
void OnRuntimeStart(Scene*) override {}
|
|
void OnRuntimeStop(Scene*) override {}
|
|
|
|
bool TryGetAvailableScriptClasses(
|
|
std::vector<ScriptClassDescriptor>& outClasses) const override {
|
|
outClasses = scriptClasses;
|
|
return true;
|
|
}
|
|
|
|
bool TryGetAvailableRenderPipelineAssetClasses(
|
|
std::vector<ScriptClassDescriptor>& outClasses) const override {
|
|
outClasses = {};
|
|
return true;
|
|
}
|
|
|
|
bool TryGetClassFieldMetadata(
|
|
const std::string&,
|
|
const std::string&,
|
|
const std::string&,
|
|
std::vector<ScriptFieldMetadata>& outFields) const override {
|
|
outFields = fieldMetadata;
|
|
return !outFields.empty();
|
|
}
|
|
|
|
bool TryGetClassFieldDefaultValues(
|
|
const std::string&,
|
|
const std::string&,
|
|
const std::string&,
|
|
std::vector<ScriptFieldDefaultValue>& outFields) const override {
|
|
outFields = fieldDefaultValues;
|
|
return !outFields.empty();
|
|
}
|
|
|
|
bool TrySetManagedFieldValue(
|
|
const ScriptRuntimeContext&,
|
|
const std::string& fieldName,
|
|
const ScriptFieldValue& value) override {
|
|
managedFieldValues[fieldName] = value;
|
|
return true;
|
|
}
|
|
|
|
bool TryGetManagedFieldValue(
|
|
const ScriptRuntimeContext&,
|
|
const std::string& fieldName,
|
|
ScriptFieldValue& outValue) const override {
|
|
const auto it = managedFieldValues.find(fieldName);
|
|
if (it == managedFieldValues.end()) {
|
|
return false;
|
|
}
|
|
|
|
outValue = it->second;
|
|
return true;
|
|
}
|
|
|
|
void SyncManagedFieldsToStorage(const ScriptRuntimeContext&) override {}
|
|
bool CreateScriptInstance(const ScriptRuntimeContext&) override { return true; }
|
|
void DestroyScriptInstance(const ScriptRuntimeContext&) override {}
|
|
void InvokeMethod(
|
|
const ScriptRuntimeContext&,
|
|
ScriptLifecycleMethod,
|
|
float) override {}
|
|
void InvokePhysicsMessage(
|
|
const ScriptRuntimeContext&,
|
|
ScriptPhysicsMessage,
|
|
GameObject*) override {}
|
|
|
|
std::vector<ScriptClassDescriptor> scriptClasses = {};
|
|
std::vector<ScriptFieldMetadata> fieldMetadata = {};
|
|
std::vector<ScriptFieldDefaultValue> fieldDefaultValues = {};
|
|
std::unordered_map<std::string, ScriptFieldValue> managedFieldValues = {};
|
|
};
|
|
|
|
class ScopedScriptRuntimeOverride final {
|
|
public:
|
|
explicit ScopedScriptRuntimeOverride(IScriptRuntime& runtime)
|
|
: m_previousRuntime(ScriptEngine::Get().GetRuntime()) {
|
|
ScriptEngine::Get().OnRuntimeStop();
|
|
ScriptEngine::Get().SetRuntime(&runtime);
|
|
}
|
|
|
|
~ScopedScriptRuntimeOverride() {
|
|
ScriptEngine::Get().OnRuntimeStop();
|
|
ScriptEngine::Get().SetRuntime(m_previousRuntime);
|
|
}
|
|
|
|
private:
|
|
IScriptRuntime* m_previousRuntime = nullptr;
|
|
};
|
|
|
|
class ScopedSceneManagerReset final {
|
|
public:
|
|
ScopedSceneManagerReset() {
|
|
Reset();
|
|
}
|
|
|
|
~ScopedSceneManagerReset() {
|
|
Reset();
|
|
::XCEngine::Resources::ResourceManager::Get().Shutdown();
|
|
}
|
|
|
|
private:
|
|
static void Reset() {
|
|
SceneManager& manager = SceneManager::Get();
|
|
const auto scenes = manager.GetAllScenes();
|
|
for (Scene* scene : scenes) {
|
|
manager.UnloadScene(scene);
|
|
}
|
|
}
|
|
};
|
|
|
|
class TemporaryProjectRoot final {
|
|
public:
|
|
TemporaryProjectRoot() {
|
|
const auto uniqueSuffix =
|
|
std::chrono::steady_clock::now().time_since_epoch().count();
|
|
m_root =
|
|
std::filesystem::temp_directory_path() /
|
|
("xcui_inspector_presentation_" + std::to_string(uniqueSuffix));
|
|
}
|
|
|
|
~TemporaryProjectRoot() {
|
|
std::error_code errorCode = {};
|
|
std::filesystem::remove_all(m_root, errorCode);
|
|
}
|
|
|
|
const std::filesystem::path& Root() const {
|
|
return m_root;
|
|
}
|
|
|
|
std::filesystem::path MainScenePath() const {
|
|
return m_root / "Assets" / "Scenes" / "Main.xc";
|
|
}
|
|
|
|
private:
|
|
std::filesystem::path m_root = {};
|
|
};
|
|
|
|
void SaveMainScene(const TemporaryProjectRoot& projectRoot) {
|
|
const std::filesystem::path scenePath = projectRoot.MainScenePath();
|
|
std::filesystem::create_directories(scenePath.parent_path());
|
|
|
|
Scene scene("Main");
|
|
GameObject* parent = scene.CreateGameObject("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
ASSERT_NE(parent->GetTransform(), nullptr);
|
|
parent->GetTransform()->SetLocalPosition(::XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
|
parent->GetTransform()->SetLocalScale(::XCEngine::Math::Vector3(4.0f, 5.0f, 6.0f));
|
|
ASSERT_NE(scene.CreateGameObject("Child", parent), nullptr);
|
|
ASSERT_NE(parent->AddComponent<::XCEngine::Components::CameraComponent>(), nullptr);
|
|
scene.Save(scenePath.string());
|
|
}
|
|
|
|
void BindEngineSceneBackend(EditorSceneRuntime& runtime) {
|
|
runtime.SetBackend(CreateEngineEditorSceneBackend(
|
|
SceneManager::Get(),
|
|
::XCEngine::Resources::ResourceManager::Get()));
|
|
}
|
|
|
|
const UIEditorPropertyGridSection* FindSection(
|
|
const InspectorPresentationModel& model,
|
|
std::string_view title) {
|
|
for (const UIEditorPropertyGridSection& section : model.sections) {
|
|
if (section.title == title) {
|
|
return §ion;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const UIEditorPropertyGridField* FindField(
|
|
const UIEditorPropertyGridSection& section,
|
|
std::string_view label) {
|
|
for (const UIEditorPropertyGridField& field : section.fields) {
|
|
if (field.label == label) {
|
|
return &field;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const UIEditorPropertyGridField* FindFieldById(
|
|
const InspectorPresentationModel& model,
|
|
std::string_view fieldId) {
|
|
for (const UIEditorPropertyGridSection& section : model.sections) {
|
|
for (const UIEditorPropertyGridField& field : section.fields) {
|
|
if (field.fieldId == fieldId) {
|
|
return &field;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const InspectorPresentationComponentBinding* FindBinding(
|
|
const InspectorPresentationModel& model,
|
|
std::string_view typeName) {
|
|
for (const InspectorPresentationComponentBinding& binding :
|
|
model.componentBindings) {
|
|
if (binding.typeName == typeName) {
|
|
return &binding;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, EmptySubjectBuildsDefaultEmptyState) {
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
{},
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
EXPECT_FALSE(model.hasSelection);
|
|
EXPECT_EQ(model.title, "Nothing selected");
|
|
EXPECT_EQ(model.subtitle, "Select a hierarchy item or project asset.");
|
|
EXPECT_TRUE(model.sections.empty());
|
|
EXPECT_TRUE(model.componentBindings.empty());
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, ProjectAssetSubjectBuildsIdentityAndLocationSections) {
|
|
InspectorSubject subject = {};
|
|
subject.kind = InspectorSubjectKind::ProjectAsset;
|
|
subject.source = InspectorSelectionSource::Project;
|
|
subject.projectAsset.selection.kind = EditorSelectionKind::ProjectItem;
|
|
subject.projectAsset.selection.itemId = "asset:materials/test";
|
|
subject.projectAsset.selection.displayName = "TestMaterial";
|
|
subject.projectAsset.selection.absolutePath =
|
|
std::filesystem::path("D:/Xuanchi/Main/XCEngine/project/Assets/Materials/Test.mat");
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
subject,
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
ASSERT_TRUE(model.hasSelection);
|
|
EXPECT_EQ(model.title, "TestMaterial");
|
|
EXPECT_EQ(model.subtitle, "Asset");
|
|
ASSERT_EQ(model.sections.size(), 2u);
|
|
|
|
const auto* identity = FindSection(model, "Identity");
|
|
ASSERT_NE(identity, nullptr);
|
|
ASSERT_EQ(identity->fields.size(), 3u);
|
|
const auto* typeField = FindField(*identity, "Type");
|
|
const auto* nameField = FindField(*identity, "Name");
|
|
const auto* idField = FindField(*identity, "Id");
|
|
ASSERT_NE(typeField, nullptr);
|
|
ASSERT_NE(nameField, nullptr);
|
|
ASSERT_NE(idField, nullptr);
|
|
EXPECT_EQ(typeField->valueText, "Asset");
|
|
EXPECT_EQ(nameField->valueText, "TestMaterial");
|
|
EXPECT_EQ(idField->valueText, "asset:materials/test");
|
|
|
|
const auto* location = FindSection(model, "Location");
|
|
ASSERT_NE(location, nullptr);
|
|
ASSERT_EQ(location->fields.size(), 1u);
|
|
const auto* pathField = FindField(*location, "Path");
|
|
ASSERT_NE(pathField, nullptr);
|
|
EXPECT_NE(
|
|
pathField->valueText.find("Assets/Materials/Test.mat"),
|
|
std::string::npos);
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, SceneObjectSubjectBuildsRegisteredComponentSections) {
|
|
ScopedSceneManagerReset reset = {};
|
|
TemporaryProjectRoot projectRoot = {};
|
|
SaveMainScene(projectRoot);
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
|
|
Scene* scene = SceneManager::Get().GetActiveScene();
|
|
ASSERT_NE(scene, nullptr);
|
|
GameObject* parent = scene->Find("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
|
|
const InspectorSubject subject =
|
|
BuildInspectorSubject(EditorSession{}, runtime);
|
|
ASSERT_EQ(subject.kind, InspectorSubjectKind::SceneObject);
|
|
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
subject,
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
ASSERT_TRUE(model.hasSelection);
|
|
EXPECT_EQ(model.title, "Parent");
|
|
EXPECT_EQ(model.subtitle, "GameObject");
|
|
ASSERT_EQ(model.sections.size(), 2u);
|
|
ASSERT_EQ(model.componentBindings.size(), 2u);
|
|
|
|
const auto* transform = FindSection(model, "Transform");
|
|
ASSERT_NE(transform, nullptr);
|
|
const auto* positionField = FindField(*transform, "Position");
|
|
const auto* rotationField = FindField(*transform, "Rotation");
|
|
const auto* scaleField = FindField(*transform, "Scale");
|
|
ASSERT_NE(positionField, nullptr);
|
|
ASSERT_NE(rotationField, nullptr);
|
|
ASSERT_NE(scaleField, nullptr);
|
|
EXPECT_EQ(positionField->kind, Widgets::UIEditorPropertyGridFieldKind::Vector3);
|
|
EXPECT_DOUBLE_EQ(positionField->vector3Value.values[0], 1.0);
|
|
EXPECT_DOUBLE_EQ(positionField->vector3Value.values[1], 2.0);
|
|
EXPECT_DOUBLE_EQ(positionField->vector3Value.values[2], 3.0);
|
|
EXPECT_EQ(rotationField->kind, Widgets::UIEditorPropertyGridFieldKind::Vector3);
|
|
EXPECT_EQ(scaleField->kind, Widgets::UIEditorPropertyGridFieldKind::Vector3);
|
|
EXPECT_DOUBLE_EQ(scaleField->vector3Value.values[0], 4.0);
|
|
EXPECT_DOUBLE_EQ(scaleField->vector3Value.values[1], 5.0);
|
|
EXPECT_DOUBLE_EQ(scaleField->vector3Value.values[2], 6.0);
|
|
|
|
const auto* camera = FindSection(model, "Camera");
|
|
ASSERT_NE(camera, nullptr);
|
|
const auto* projectionField = FindField(*camera, "Projection");
|
|
const auto* primaryField = FindField(*camera, "Primary");
|
|
const auto* clearColorField = FindField(*camera, "Clear Color");
|
|
ASSERT_NE(projectionField, nullptr);
|
|
ASSERT_NE(primaryField, nullptr);
|
|
ASSERT_NE(clearColorField, nullptr);
|
|
EXPECT_EQ(projectionField->kind, Widgets::UIEditorPropertyGridFieldKind::Enum);
|
|
EXPECT_EQ(projectionField->enumValue.selectedIndex, 0u);
|
|
EXPECT_EQ(primaryField->kind, Widgets::UIEditorPropertyGridFieldKind::Bool);
|
|
EXPECT_TRUE(primaryField->boolValue);
|
|
EXPECT_EQ(clearColorField->kind, Widgets::UIEditorPropertyGridFieldKind::Color);
|
|
|
|
const auto* transformBinding = FindBinding(model, "Transform");
|
|
const auto* cameraBinding = FindBinding(model, "Camera");
|
|
ASSERT_NE(transformBinding, nullptr);
|
|
ASSERT_NE(cameraBinding, nullptr);
|
|
EXPECT_FALSE(transformBinding->removable);
|
|
EXPECT_TRUE(cameraBinding->removable);
|
|
EXPECT_EQ(transformBinding->fieldIds.size(), 3u);
|
|
EXPECT_GE(cameraBinding->fieldIds.size(), 8u);
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, CameraSkyboxMaterialBuildsAssetField) {
|
|
ScopedSceneManagerReset reset = {};
|
|
TemporaryProjectRoot projectRoot = {};
|
|
SaveMainScene(projectRoot);
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
|
|
Scene* scene = SceneManager::Get().GetActiveScene();
|
|
ASSERT_NE(scene, nullptr);
|
|
GameObject* parent = scene->Find("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
auto* camera = parent->GetComponent<::XCEngine::Components::CameraComponent>();
|
|
ASSERT_NE(camera, nullptr);
|
|
camera->SetSkyboxEnabled(true);
|
|
camera->SetSkyboxMaterialPath("Assets/Materials/Skybox.mat");
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
BuildInspectorSubject(EditorSession{}, runtime),
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
const auto* cameraSection = FindSection(model, "Camera");
|
|
ASSERT_NE(cameraSection, nullptr);
|
|
const auto* skyboxMaterialField = FindField(*cameraSection, "Skybox Material");
|
|
ASSERT_NE(skyboxMaterialField, nullptr);
|
|
EXPECT_EQ(skyboxMaterialField->kind, Widgets::UIEditorPropertyGridFieldKind::Asset);
|
|
EXPECT_EQ(
|
|
skyboxMaterialField->assetValue.assetId,
|
|
"Assets/Materials/Skybox.mat");
|
|
EXPECT_EQ(
|
|
skyboxMaterialField->assetValue.displayName,
|
|
"Skybox.mat");
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, ScriptComponentBuildsScriptSelectorSection) {
|
|
ScopedSceneManagerReset reset = {};
|
|
TemporaryProjectRoot projectRoot = {};
|
|
SaveMainScene(projectRoot);
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
|
|
Scene* scene = SceneManager::Get().GetActiveScene();
|
|
ASSERT_NE(scene, nullptr);
|
|
GameObject* parent = scene->Find("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
ASSERT_NE(parent->AddComponent<::XCEngine::Scripting::ScriptComponent>(), nullptr);
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
BuildInspectorSubject(EditorSession{}, runtime),
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
const auto* scriptSection = FindSection(model, "Script");
|
|
ASSERT_NE(scriptSection, nullptr);
|
|
const auto* scriptField = FindField(*scriptSection, "Script");
|
|
ASSERT_NE(scriptField, nullptr);
|
|
EXPECT_EQ(scriptField->kind, Widgets::UIEditorPropertyGridFieldKind::Enum);
|
|
EXPECT_FALSE(scriptField->enumValue.options.empty());
|
|
EXPECT_EQ(scriptField->enumValue.options.front(), "None");
|
|
|
|
const auto* scriptBinding = FindBinding(model, "ScriptComponent");
|
|
ASSERT_NE(scriptBinding, nullptr);
|
|
EXPECT_EQ(scriptBinding->displayName, "Script");
|
|
EXPECT_TRUE(scriptBinding->removable);
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, ScriptComponentReferenceFieldsSupportActivationAndClear) {
|
|
ScopedSceneManagerReset reset = {};
|
|
TemporaryProjectRoot projectRoot = {};
|
|
SaveMainScene(projectRoot);
|
|
|
|
FakeInspectorScriptRuntime scriptRuntime = {};
|
|
scriptRuntime.scriptClasses.push_back(ScriptClassDescriptor{
|
|
"GameScripts",
|
|
"Gameplay",
|
|
"Probe"
|
|
});
|
|
scriptRuntime.fieldMetadata = {
|
|
ScriptFieldMetadata{ "Target", ScriptFieldType::GameObject },
|
|
ScriptFieldMetadata{ "TargetScript", ScriptFieldType::Component }
|
|
};
|
|
ScopedScriptRuntimeOverride runtimeOverride(scriptRuntime);
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
|
|
Scene* scene = SceneManager::Get().GetActiveScene();
|
|
ASSERT_NE(scene, nullptr);
|
|
|
|
GameObject* parent = scene->Find("Parent");
|
|
GameObject* child = scene->Find("Child");
|
|
ASSERT_NE(parent, nullptr);
|
|
ASSERT_NE(child, nullptr);
|
|
|
|
ScriptComponent* parentScript = parent->AddComponent<ScriptComponent>();
|
|
ScriptComponent* childScript = child->AddComponent<ScriptComponent>();
|
|
ASSERT_NE(parentScript, nullptr);
|
|
ASSERT_NE(childScript, nullptr);
|
|
parentScript->SetScriptClass("GameScripts", "Gameplay", "Probe");
|
|
childScript->SetScriptClass("GameScripts", "Gameplay", "Probe");
|
|
parentScript->GetFieldStorage().SetFieldValue(
|
|
"Target",
|
|
GameObjectReference{ child->GetUUID() });
|
|
parentScript->GetFieldStorage().SetFieldValue(
|
|
"TargetScript",
|
|
ComponentReference{
|
|
child->GetUUID(),
|
|
childScript->GetScriptComponentUUID()
|
|
});
|
|
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
|
|
InspectorSubject subject = BuildInspectorSubject(EditorSession{}, runtime);
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
subject,
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
|
|
const UIEditorPropertyGridSection* fieldsSection =
|
|
FindSection(model, "Script Fields");
|
|
ASSERT_NE(fieldsSection, nullptr);
|
|
const UIEditorPropertyGridField* targetField = FindField(*fieldsSection, "Target");
|
|
const UIEditorPropertyGridField* targetScriptField =
|
|
FindField(*fieldsSection, "TargetScript");
|
|
ASSERT_NE(targetField, nullptr);
|
|
ASSERT_NE(targetScriptField, nullptr);
|
|
EXPECT_EQ(targetField->kind, UIEditorPropertyGridFieldKind::Asset);
|
|
EXPECT_EQ(targetField->assetValue.displayName, "Child");
|
|
EXPECT_EQ(targetScriptField->kind, UIEditorPropertyGridFieldKind::Asset);
|
|
EXPECT_EQ(targetScriptField->assetValue.displayName, "Child");
|
|
|
|
const InspectorPresentationComponentBinding* binding =
|
|
FindBinding(model, "ScriptComponent");
|
|
ASSERT_NE(binding, nullptr);
|
|
const std::vector<EditorSceneComponentDescriptor> descriptors =
|
|
runtime.GetSelectedComponents();
|
|
InspectorComponentEditorContext editorContext = {};
|
|
editorContext.gameObject = &subject.sceneObject.object;
|
|
editorContext.componentId = binding->componentId;
|
|
editorContext.typeName = binding->typeName;
|
|
editorContext.displayName = binding->displayName;
|
|
editorContext.removable = binding->removable;
|
|
for (const EditorSceneComponentDescriptor& descriptor : descriptors) {
|
|
if (descriptor.componentId == binding->componentId) {
|
|
editorContext.component = descriptor.view.get();
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_NE(editorContext.component, nullptr);
|
|
|
|
const IInspectorComponentEditor* editor =
|
|
InspectorComponentEditorRegistry::Get().FindEditor("ScriptComponent");
|
|
ASSERT_NE(editor, nullptr);
|
|
ASSERT_TRUE(editor->HandleFieldActivation(runtime, editorContext, *targetField));
|
|
ASSERT_TRUE(runtime.GetSelectedObjectId().has_value());
|
|
EXPECT_EQ(runtime.GetSelectedObjectId().value(), child->GetID());
|
|
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
subject = BuildInspectorSubject(EditorSession{}, runtime);
|
|
const InspectorPresentationModel refreshedModel =
|
|
BuildInspectorPresentationModel(
|
|
subject,
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
fieldsSection = FindSection(refreshedModel, "Script Fields");
|
|
ASSERT_NE(fieldsSection, nullptr);
|
|
targetField = FindField(*fieldsSection, "Target");
|
|
targetScriptField = FindField(*fieldsSection, "TargetScript");
|
|
ASSERT_NE(targetField, nullptr);
|
|
ASSERT_NE(targetScriptField, nullptr);
|
|
|
|
UIEditorPropertyGridField clearedTargetField = *targetField;
|
|
clearedTargetField.assetValue.assetId.clear();
|
|
clearedTargetField.assetValue.displayName.clear();
|
|
clearedTargetField.assetValue.statusText.clear();
|
|
ASSERT_TRUE(ApplyInspectorComponentBoundFieldValue(
|
|
runtime,
|
|
subject.sceneObject,
|
|
*FindBinding(refreshedModel, "ScriptComponent"),
|
|
clearedTargetField));
|
|
|
|
UIEditorPropertyGridField clearedTargetScriptField = *targetScriptField;
|
|
clearedTargetScriptField.assetValue.assetId.clear();
|
|
clearedTargetScriptField.assetValue.displayName.clear();
|
|
clearedTargetScriptField.assetValue.statusText.clear();
|
|
ASSERT_TRUE(ApplyInspectorComponentBoundFieldValue(
|
|
runtime,
|
|
subject.sceneObject,
|
|
*FindBinding(refreshedModel, "ScriptComponent"),
|
|
clearedTargetScriptField));
|
|
|
|
GameObjectReference targetReference = {};
|
|
ComponentReference targetScriptReference = {};
|
|
ASSERT_TRUE(parentScript->GetFieldStorage().TryGetFieldValue("Target", targetReference));
|
|
ASSERT_TRUE(parentScript->GetFieldStorage().TryGetFieldValue(
|
|
"TargetScript",
|
|
targetScriptReference));
|
|
EXPECT_EQ(targetReference.gameObjectUUID, 0u);
|
|
EXPECT_EQ(targetScriptReference.gameObjectUUID, 0u);
|
|
EXPECT_EQ(targetScriptReference.scriptComponentUUID, 0u);
|
|
}
|
|
|
|
TEST(InspectorPresentationModelTests, BoundFieldValueApplierKeepsComponentViewAliveDuringApply) {
|
|
ScopedSceneManagerReset reset = {};
|
|
TemporaryProjectRoot projectRoot = {};
|
|
SaveMainScene(projectRoot);
|
|
|
|
EditorSceneRuntime runtime = {};
|
|
BindEngineSceneBackend(runtime);
|
|
ASSERT_TRUE(runtime.Initialize(projectRoot.Root()));
|
|
Scene* scene = SceneManager::Get().GetActiveScene();
|
|
ASSERT_NE(scene, nullptr);
|
|
GameObject* parent = scene->Find("Parent");
|
|
ASSERT_NE(parent, nullptr);
|
|
ASSERT_TRUE(runtime.SetSelection(parent->GetID()));
|
|
|
|
const InspectorSubject subject =
|
|
BuildInspectorSubject(EditorSession{}, runtime);
|
|
ASSERT_EQ(subject.kind, InspectorSubjectKind::SceneObject);
|
|
|
|
const InspectorPresentationModel model =
|
|
BuildInspectorPresentationModel(
|
|
subject,
|
|
runtime,
|
|
InspectorComponentEditorRegistry::Get());
|
|
const auto* transformBinding = FindBinding(model, "Transform");
|
|
ASSERT_NE(transformBinding, nullptr);
|
|
ASSERT_FALSE(transformBinding->fieldIds.empty());
|
|
|
|
const UIEditorPropertyGridField* originalField =
|
|
FindFieldById(model, transformBinding->fieldIds.front());
|
|
ASSERT_NE(originalField, nullptr);
|
|
ASSERT_EQ(originalField->kind, Widgets::UIEditorPropertyGridFieldKind::Vector3);
|
|
|
|
UIEditorPropertyGridField editedField = *originalField;
|
|
editedField.vector3Value.values = { 8.0, 9.0, 10.0 };
|
|
|
|
bool applied = false;
|
|
EXPECT_NO_THROW(
|
|
applied = ApplyInspectorComponentBoundFieldValue(
|
|
runtime,
|
|
subject.sceneObject,
|
|
*transformBinding,
|
|
editedField));
|
|
ASSERT_TRUE(applied);
|
|
|
|
const auto* transform = parent->GetTransform();
|
|
ASSERT_NE(transform, nullptr);
|
|
const auto position = transform->GetLocalPosition();
|
|
EXPECT_FLOAT_EQ(position.x, 8.0f);
|
|
EXPECT_FLOAT_EQ(position.y, 9.0f);
|
|
EXPECT_FLOAT_EQ(position.z, 10.0f);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace XCEngine::UI::Editor::App
|