434 lines
13 KiB
C++
434 lines
13 KiB
C++
#include "WingPropHandle.h"
|
|
#include "WingServer.h"
|
|
#include "WingActorComponent.h"
|
|
#include "WingUtils.h"
|
|
#include "WingTypes.h"
|
|
#include "Dom/JsonValue.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 "UObject/EnumProperty.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get Root
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
|
|
{
|
|
FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FPropertyRowGeneratorArgs Args;
|
|
Args.bShouldShowHiddenProperties = false;
|
|
return Module.CreatePropertyRowGenerator(Args);
|
|
}
|
|
|
|
WingPropHandle::Root& WingPropHandle::GetRootForObject(UObject* Obj)
|
|
{
|
|
for (Root& R : Roots)
|
|
{
|
|
if (R.Base == (uint8*)Obj) return R;
|
|
}
|
|
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
|
|
Gen->SetObjects({Obj});
|
|
Root& R = Roots.AddDefaulted_GetRef();
|
|
R.Struct = Obj->GetClass();
|
|
R.Base = (uint8*)Obj;
|
|
R.End = R.Base + R.Struct->GetStructureSize();
|
|
R.Generator = Gen;
|
|
return R;
|
|
}
|
|
|
|
WingPropHandle::Root& WingPropHandle::GetRootForStruct(const UStruct* ScriptStruct, uint8* Data)
|
|
{
|
|
for (Root& R : Roots)
|
|
{
|
|
if (R.Base == Data) return R;
|
|
}
|
|
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
|
|
TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(ScriptStruct, Data);
|
|
Gen->SetStructure(Wrapper);
|
|
Root& R = Roots.AddDefaulted_GetRef();
|
|
R.Struct = ScriptStruct;
|
|
R.Base = Data;
|
|
R.End = Data + ScriptStruct->GetStructureSize();
|
|
R.Generator = Gen;
|
|
return R;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IsInsideRootObject
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool WingPropHandle::IsInsideRootObject(const Root& Root, IPropertyHandle& Handle)
|
|
{
|
|
// Walk up to the topmost property handle that still has a property.
|
|
// Keep the shared pointers alive so the raw pointer stays valid.
|
|
TSharedPtr<IPropertyHandle> Held;
|
|
IPropertyHandle* Top = &Handle;
|
|
while (true)
|
|
{
|
|
TSharedPtr<IPropertyHandle> Parent = Top->GetParentHandle();
|
|
if (!Parent.IsValid() || !Parent->GetProperty()) break;
|
|
Held = Parent;
|
|
Top = Held.Get();
|
|
}
|
|
|
|
// Get the address of the topmost property's data.
|
|
void* Addr = nullptr;
|
|
if (Top->GetValueData(Addr) != FPropertyAccess::Success || !Addr)
|
|
return false;
|
|
|
|
uint8* DataPtr = (uint8*)Addr;
|
|
return DataPtr >= Root.Base && DataPtr < Root.End;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// All Tree Nodes
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void WingPropHandle::AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out)
|
|
{
|
|
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(Root& Root)
|
|
{
|
|
FlatTree Result;
|
|
for (const TSharedRef<IDetailTreeNode>& TreeRoot : Root.Generator->GetRootTreeNodes())
|
|
{
|
|
AllTreeNodesRecursive(TreeRoot, Result);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// AllProperties
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, bool RootFilter, EPropertyFlags Filter)
|
|
{
|
|
Handles Result;
|
|
for (IDetailTreeNode* Node : AllTreeNodes(Root))
|
|
{
|
|
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
|
|
if (Handle.IsValid() && Handle->GetProperty())
|
|
{
|
|
if (Filter != CPF_None && !Handle->GetProperty()->HasAllPropertyFlags(Filter))
|
|
continue;
|
|
if (RootFilter && !IsInsideRootObject(Root, *Handle))
|
|
continue;
|
|
Result.Add(Handle);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, bool RootFilter, EPropertyFlags Filter)
|
|
{
|
|
if (!Obj) return {};
|
|
return AllProperties(GetRootForObject(Obj), RootFilter, Filter);
|
|
}
|
|
|
|
WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter, EPropertyFlags Filter)
|
|
{
|
|
if (!ScriptStruct || !Data) return {};
|
|
return AllProperties(GetRootForStruct(ScriptStruct, Data), RootFilter, Filter);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Named Property
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(Root& Root, FName Name, bool RootFilter)
|
|
{
|
|
for (IDetailTreeNode* Node : AllTreeNodes(Root))
|
|
{
|
|
if (Node->GetNodeName() != Name) continue;
|
|
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
|
|
if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle)))
|
|
return Handle;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter)
|
|
{
|
|
if (!Obj) return nullptr;
|
|
return NamedProperty(GetRootForObject(Obj), Name, RootFilter);
|
|
}
|
|
|
|
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter)
|
|
{
|
|
if (!ScriptStruct || !Data) return nullptr;
|
|
return NamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// GetDetails
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable)
|
|
{
|
|
bool RootFilter = false;
|
|
EPropertyFlags PropFlags = CPF_Edit;
|
|
|
|
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();
|
|
}
|
|
|
|
// UWingComponentReference is a class of our own creation, containing
|
|
// a pointer to a blueprint and a component name. Sometimes, the
|
|
// component is inherited. If so, and if you want to mutate it, you
|
|
// first have to create the override template in the child. If you're
|
|
// not mutating, you can just use the existing inherited 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 {};
|
|
}
|
|
}
|
|
|
|
// Actors have components, which flood the property listing with hundreds
|
|
// of confusing additional properties.
|
|
if (Cast<AActor>(Obj)) RootFilter = true;
|
|
|
|
// Fetch the handles.
|
|
Handles Result = AllProperties(Obj, RootFilter, PropFlags);
|
|
|
|
// Material graph nodes: also collect expression properties.
|
|
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
|
|
{
|
|
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
|
{
|
|
Result.Append(AllProperties(Expr, true, CPF_Edit));
|
|
}
|
|
}
|
|
|
|
// 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, false, CPF_Edit));
|
|
// }
|
|
// }
|
|
|
|
return Result;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// GetText / SetText
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool WingPropHandle::IsPinTypeProperty(FProperty* Prop)
|
|
{
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
|
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
|
|
}
|
|
|
|
FString WingPropHandle::GetText(IPropertyHandle& Handle)
|
|
{
|
|
// Pin types: use our human-readable format.
|
|
if (IsPinTypeProperty(Handle.GetProperty()))
|
|
{
|
|
void* Data = nullptr;
|
|
if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
|
|
UE_LOG(LogTemp, Fatal, TEXT("GetValueData failed for pin type property '%s'"),
|
|
*Handle.GetProperty()->GetName());
|
|
return UWingTypes::TypeToText(*static_cast<FEdGraphPinType*>(Data));
|
|
}
|
|
|
|
// Class properties: use our human-readable type names.
|
|
if (CastField<FClassProperty>(Handle.GetProperty()))
|
|
{
|
|
UObject* ClassObj = nullptr;
|
|
if (Handle.GetValue(ClassObj) != FPropertyAccess::Success)
|
|
UE_LOG(LogTemp, Fatal, TEXT("GetValue failed for class property '%s'"),
|
|
*Handle.GetProperty()->GetName());
|
|
if (!ClassObj) return TEXT("None");
|
|
return UWingTypes::TypeToText(ClassObj);
|
|
}
|
|
|
|
FString Result;
|
|
if (Handle.GetValueAsFormattedString(Result) != FPropertyAccess::Success)
|
|
UE_LOG(LogTemp, Fatal, TEXT("GetValueAsFormattedString failed for property '%s'"),
|
|
*Handle.GetProperty()->GetName());
|
|
return Result;
|
|
}
|
|
|
|
bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text)
|
|
{
|
|
FProperty* Prop = Handle.GetProperty();
|
|
|
|
// Pin types: parse with our type parser.
|
|
if (IsPinTypeProperty(Prop))
|
|
{
|
|
FEdGraphPinType PinType;
|
|
UWingTypes::Requirements Req;
|
|
Req.BlueprintType = true;
|
|
Req.Blueprintable = false;
|
|
Req.AllowContainer = true;
|
|
if (!UWingTypes::TextToType(Text, PinType, Req)) return false;
|
|
void* Data = nullptr;
|
|
if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
|
|
{
|
|
UWingServer::Printf(TEXT("ERROR: Cannot access data for property '%s'\n"),
|
|
*WingUtils::FormatName(Prop));
|
|
return false;
|
|
}
|
|
Handle.NotifyPreChange();
|
|
*static_cast<FEdGraphPinType*>(Data) = PinType;
|
|
Handle.NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
return true;
|
|
}
|
|
|
|
// Class properties: parse with our type parser.
|
|
if (FClassProperty* ClassProp = CastField<FClassProperty>(Prop))
|
|
{
|
|
UObject* Class = nullptr;
|
|
if (!Text.IsEmpty())
|
|
{
|
|
UWingTypes::Requirements Req;
|
|
Req.BlueprintType = true;
|
|
Req.Blueprintable = false;
|
|
Req.IsChildOf = ClassProp->MetaClass;
|
|
Class = UWingTypes::TextToOneObjectType(Text, Req);
|
|
if (!Class) return false;
|
|
}
|
|
if (Handle.SetValue(Class) != FPropertyAccess::Success)
|
|
{
|
|
UWingServer::Printf(TEXT("ERROR: Failed to set class property '%s'\n"),
|
|
*WingUtils::FormatName(Prop));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Enums: canonicalize the string with our smarter prefix matching.
|
|
FString Value = Text;
|
|
UEnum* Enum = nullptr;
|
|
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
Enum = ByteProp->Enum;
|
|
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
Enum = EnumProp->GetEnum();
|
|
if (Enum != nullptr)
|
|
{
|
|
int64 EnumValue;
|
|
if (!WingUtils::StringToEnum(Enum, Value, EnumValue)) return false;
|
|
Value = Enum->GetNameStringByValue(EnumValue);
|
|
}
|
|
|
|
FPropertyAccess::Result Result = Handle.SetValueFromFormattedString(Value);
|
|
if (Result != FPropertyAccess::Success)
|
|
{
|
|
UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
|
*Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SetJson
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtr<FJsonValue>& JsonValue)
|
|
{
|
|
FPropertyAccess::Result Result;
|
|
|
|
switch (JsonValue->Type)
|
|
{
|
|
case EJson::String:
|
|
return SetText(Handle, JsonValue->AsString());
|
|
|
|
case EJson::Boolean:
|
|
Result = Handle.SetValue(JsonValue->AsBool());
|
|
break;
|
|
|
|
case EJson::Number:
|
|
Result = Handle.SetValue(JsonValue->AsNumber());
|
|
break;
|
|
|
|
default:
|
|
Result = FPropertyAccess::Fail;
|
|
break;
|
|
}
|
|
|
|
if (Result != FPropertyAccess::Success)
|
|
{
|
|
UWingServer::Printf(TEXT("ERROR: Failed to set property '%s'\n"),
|
|
*WingUtils::FormatName(Handle.GetProperty()));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Print
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out)
|
|
{
|
|
FString Value = GetText(Handle);
|
|
Value.ReplaceInline(TEXT("\r"), TEXT(" "));
|
|
Value.ReplaceInline(TEXT("\n"), TEXT(" "));
|
|
if (Value.Len() > 100) Value = Value.Left(100) + TEXT("...");
|
|
|
|
bool bEditable = !Handle.IsEditConst();
|
|
|
|
Out.Appendf(TEXT(" %s %s %s = %s\n"),
|
|
bEditable ? TEXT("editable") : TEXT("readonly"),
|
|
*UWingTypes::TypeToText(Handle.GetProperty()),
|
|
*WingUtils::FormatName(Handle),
|
|
*Value);
|
|
}
|