New infrastructure for property manipulation.

This commit is contained in:
2026-04-02 02:27:14 -04:00
parent 7865818e69
commit 2e2bb89de0
17 changed files with 608 additions and 365 deletions

View File

@@ -0,0 +1,234 @@
#include "WingPropHandle.h"
#include "WingServer.h"
#include "WingActorComponent.h"
#include "PropertyEditorModule.h"
#include "IDetailTreeNode.h"
#include "Engine/Blueprint.h"
#include "Components/Widget.h"
#include "Components/PanelSlot.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Materials/MaterialExpression.h"
#include "DetailTreeNode.h"
#include "PropertyNode.h"
#include "ObjectPropertyNode.h"
#include "PropertyNode.h"
#include "ObjectPropertyNode.h"
#include "ItemPropertyNode.h"
#include "CategoryPropertyNode.h"
/////////////////////////////////////////////////////////////////////////////
//
// IsSubObject
//
/////////////////////////////////////////////////////////////////////////////
bool WingPropHandle::IsSubObject(const TSharedRef<IDetailTreeNode>& Node)
{
FDetailTreeNode& DetailNode = static_cast<FDetailTreeNode&>(*Node);
TSharedPtr<FPropertyNode> PropNode = DetailNode.GetPropertyNode();
if (!PropNode.IsValid()) return false;
FPropertyNode* Current = PropNode.Get();
while (true)
{
if (Current == nullptr) return false;
FPropertyNode *Parent = Current->GetParentNode();
if (Current->AsObjectNode()) return (Parent != nullptr);
Current = Parent;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Get Generator
//
/////////////////////////////////////////////////////////////////////////////
TSharedRef<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
{
FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FPropertyRowGeneratorArgs Args;
Args.bShouldShowHiddenProperties = false;
return Module.CreatePropertyRowGenerator(Args);
}
TSharedRef<IPropertyRowGenerator> WingPropHandle::GetGeneratorForObject(UObject* Obj)
{
for (auto& Pair : Generators)
{
if (Pair.Key == Obj) return Pair.Value.ToSharedRef();
}
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
Gen->SetObjects({Obj});
Generators.Add({Obj, Gen});
return Gen;
}
TSharedRef<IPropertyRowGenerator> WingPropHandle::GetGeneratorForStruct(const UStruct* ScriptStruct, uint8* Data)
{
for (auto& Pair : Generators)
{
if (Pair.Key == Data) return Pair.Value.ToSharedRef();
}
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(ScriptStruct, Data);
Gen->SetStructure(Wrapper);
Generators.Add({Data, Gen});
return Gen;
}
/////////////////////////////////////////////////////////////////////////////
//
// All Tree Nodes
//
/////////////////////////////////////////////////////////////////////////////
void WingPropHandle::AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out)
{
if (IsSubObject(Node)) return;
if (Node->GetNodeType() == EDetailNodeType::Category)
{
TArray<TSharedRef<IDetailTreeNode>> Children;
Node->GetChildren(Children);
for (const TSharedRef<IDetailTreeNode>& Child : Children)
{
AllTreeNodesRecursive(Child, Out);
}
return;
}
Out.Add(&*Node);
}
WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(TSharedRef<IPropertyRowGenerator> Generator)
{
FlatTree Result;
for (const TSharedRef<IDetailTreeNode>& Root : Generator->GetRootTreeNodes())
{
AllTreeNodesRecursive(Root, Result);
}
return Result;
}
/////////////////////////////////////////////////////////////////////////////
//
// AllProperties
//
/////////////////////////////////////////////////////////////////////////////
WingPropHandle::Handles WingPropHandle::AllProperties(TSharedRef<IPropertyRowGenerator> Generator, EPropertyFlags Filter)
{
Handles Result;
for (IDetailTreeNode* Node : AllTreeNodes(Generator))
{
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
if (Handle.IsValid() && Handle->GetProperty())
{
if (Filter == CPF_None || Handle->GetProperty()->HasAllPropertyFlags(Filter))
{
Result.Add(Handle);
}
}
}
return Result;
}
WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, EPropertyFlags Filter)
{
if (!Obj) return {};
return AllProperties(GetGeneratorForObject(Obj), Filter);
}
WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, EPropertyFlags Filter)
{
if (!ScriptStruct || !Data) return {};
return AllProperties(GetGeneratorForStruct(ScriptStruct, Data), Filter);
}
/////////////////////////////////////////////////////////////////////////////
//
// Named Property
//
/////////////////////////////////////////////////////////////////////////////
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(TSharedRef<IPropertyRowGenerator> Generator, FName Name)
{
for (IDetailTreeNode* Node : AllTreeNodes(Generator))
{
if (Node->GetNodeName() == Name)
return Node->CreatePropertyHandle();
}
return nullptr;
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name)
{
if (!Obj) return nullptr;
return NamedProperty(GetGeneratorForObject(Obj), Name);
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name)
{
if (!ScriptStruct || !Data) return nullptr;
return NamedProperty(GetGeneratorForStruct(ScriptStruct, Data), Name);
}
/////////////////////////////////////////////////////////////////////////////
//
// GetDetails
//
/////////////////////////////////////////////////////////////////////////////
WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, EPropertyFlags Filter)
{
if (!Obj) return {};
// Blueprints: redirect to the generated class CDO.
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
{
if (!BP->GeneratedClass)
{
UWingServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
return {};
}
Obj = BP->GeneratedClass->GetDefaultObject();
}
// Component references: redirect to the template.
if (UWingComponentReference* Ref = Cast<UWingComponentReference>(Obj))
{
Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate();
if (!Obj)
{
UWingServer::Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString());
return {};
}
}
Handles Result = AllProperties(Obj, Filter);
// Material graph nodes: also collect expression properties.
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
Result.Append(AllProperties(Expr, Filter));
}
}
// Widgets: hide the Slot property, add slot properties.
if (UWidget* Widget = Cast<UWidget>(Obj))
{
Result.RemoveAll([](const TSharedPtr<IPropertyHandle>& H)
{
return H->GetProperty()->GetFName() == TEXT("Slot");
});
if (UPanelSlot* Slot = Widget->Slot)
{
Result.Append(AllProperties(Slot, Filter));
}
}
return Result;
}