Files
integration/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp

197 lines
5.5 KiB
C++

#include "WingProperty.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "WingTypes.h"
#include "Engine/Blueprint.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "UObject/EnumProperty.h"
static bool IsPinTypeProperty(FProperty* Prop)
{
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
}
FWingProperty::FWingProperty(FProperty* InProp, void* InContainer)
: Prop(InProp), Container(InContainer) {}
FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
: Prop(InProp), Container(static_cast<void*>(InContainer)) {}
FString FWingProperty::GetText() const
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
if (IsPinTypeProperty(Prop))
return UWingTypes::TypeToText(*static_cast<FEdGraphPinType*>(ValuePtr));
FString Result;
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, nullptr, PPF_None);
return Result;
}
bool FWingProperty::TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue)
{
int Index = Enum->GetIndexByNameString(Text);
if (Index == INDEX_NONE)
{
FString Prefix = Enum->GenerateEnumPrefix();
if (!Prefix.IsEmpty())
{
Index = Enum->GetIndexByNameString(Prefix + TEXT("_") + Text);
}
}
if (Index == INDEX_NONE)
{
UWingServer::Printf(TEXT("ERROR: '%s' is not a valid value for %s\n"),
*Text, *Enum->GetName());
OutValue = 0;
return false;
}
else
{
OutValue = Enum->GetValueByIndex(Index);
return true;
}
}
bool FWingProperty::SetText(const FString &Value)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
// Notify that we're modifying the containing object.
if (Prop->GetOwnerClass())
UWingServer::AddTouchedObject(static_cast<UObject*>(Container));
// Pin types get parsed by UWingTypes.
if (IsPinTypeProperty(Prop))
return UWingTypes::TextToType(Value, *static_cast<FEdGraphPinType*>(ValuePtr));
// Byte Enum types get parsed by TryParseEnum, above.
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
{
if (UEnum* Enum = ByteProp->Enum)
{
int64 EnumValue;
if (!TryParseEnum(Enum, Value, EnumValue)) return false;
ByteProp->SetPropertyValue(ValuePtr, (uint8)EnumValue);
return true;
}
}
// Regular Enum types get parsed by TryParseEnum, above.
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
{
int64 EnumValue;
if (!TryParseEnum(EnumProp->GetEnum(), Value, EnumValue)) return false;
EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(ValuePtr, EnumValue);
return true;
}
// Non-enum properties use ImportText
const TCHAR* Result = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
if (!Result)
{
UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
*Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType());
return false;
}
return true;
}
void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{
for (TFieldIterator<FProperty> It(StructType); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Emplace(*It, Container);
}
}
void FWingProperty::Collect(UObject* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{
for (TFieldIterator<FProperty> It(Container->GetClass()); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Emplace(*It, Container);
}
}
void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
{
Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; });
}
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{
if (!Obj) return {};
TArray<FWingProperty> Result;
// Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO,
// which is probably what the user intended.
//
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
{
if (BP->GeneratedClass == nullptr)
{
UWingServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
return {};
}
Obj = BP->GeneratedClass->GetDefaultObject();
}
Collect(Obj, Result, Flags);
// If it's a Material Graph node, also collect properties from
// the associated material expression.
//
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
Collect(Expr, Result, Flags);
}
}
return Result;
}
TArray<FWingProperty> FWingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags)
{
TArray<FWingProperty> Result;
Collect(StructType, Container, Result, Flags);
return Result;
}
TArray<FWingProperty> FWingProperty::FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring)
{
if (Substring.IsEmpty()) return Props;
TArray<FWingProperty> Result;
for (const FWingProperty& P : Props)
{
if (WingUtils::FormatName(P.Prop).Contains(Substring, ESearchCase::IgnoreCase))
Result.Add(P);
}
return Result;
}
FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name)
{
TArray<FWingProperty> Matches;
for (const FWingProperty& P : Props)
{
if (WingUtils::Identifies(Name, P.Prop))
Matches.Add(P);
}
if (Matches.Num() == 0)
{
UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name);
return FWingProperty();
}
if (Matches.Num() > 1)
{
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name);
return FWingProperty();
}
return Matches[0];
}