Better handling of property mutability, and also, walk into structs.

This commit is contained in:
2026-04-04 23:57:59 -04:00
parent bd138e2790
commit c949a4db05
19 changed files with 172 additions and 50 deletions

View File

@@ -281,7 +281,7 @@ public:
} }
// Set the 'Class' property. // Set the 'Class' property.
TArray<FWingProperty> Props = FWingProperty::GetAll(Factory, CPF_Edit); TArray<FWingProperty> Props = FWingProperty::GetVisible(Factory);
FWingProperty::Remove(Props, TEXT("BlueprintType")); FWingProperty::Remove(Props, TEXT("BlueprintType"));
if (Props.Num() != 1) if (Props.Num() != 1)
{ {

View File

@@ -29,7 +29,7 @@ public:
UObject* Target = F.Walk(Object).Cast<UObject>(); UObject* Target = F.Walk(Object).Cast<UObject>();
if (!Target) return; if (!Target) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, CPF_Edit, false); TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, false);
// Group by category, preserving within-category order. // Group by category, preserving within-category order.
TSortedMap<FString, TArray<FWingProperty>> Categories; TSortedMap<FString, TArray<FWingProperty>> Categories;

View File

@@ -32,7 +32,7 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return; if (!Obj) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, false); TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, false);
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout); FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
if (!P) return; if (!P) return;

View File

@@ -35,10 +35,10 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return; if (!Obj) return;
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true); TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout); FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
if (!P) return; if (!P) return;
if (P->SetText(Value, WingOut::Stdout)) if (P->SetText(Value, WingOut::Stdout))
WingOut::Stdout.Print(TEXT("OK\n")); WingOut::Stdout.Print(TEXT("OK\n"));
} }

View File

@@ -38,7 +38,7 @@ public:
return; return;
} }
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true); TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
// Validation pass — resolve all properties before modifying anything. // Validation pass — resolve all properties before modifying anything.
for (const auto& Pair : Properties.Json->Values) for (const auto& Pair : Properties.Json->Values)

View File

@@ -66,7 +66,7 @@ public:
// Parse the json array, turning it into an array of spawn node entries. // Parse the json array, turning it into an array of spawn node entries.
TArray<FSpawnNodeEntry> Entries; TArray<FSpawnNodeEntry> Entries;
FSpawnNodeEntry Entry; FSpawnNodeEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None); TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array) for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
{ {
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return; if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return;

View File

@@ -91,7 +91,7 @@ public:
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>(); UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return; if (!Node) return;
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, CPF_Edit, true); TArray<FWingProperty> All = FWingProperty::GetDetails(Node, true);
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"), WingOut::Stdout); FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"), WingOut::Stdout);
if (!P) return; if (!P) return;
@@ -121,7 +121,7 @@ public:
} }
FSetNodeDefaultEntry Entry; FSetNodeDefaultEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None); TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array) for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{ {
if (!FWingProperty::PopulateFromJson(Props, *PinVal, false, WingOut::Stdout)) continue; if (!FWingProperty::PopulateFromJson(Props, *PinVal, false, WingOut::Stdout)) continue;

View File

@@ -57,7 +57,7 @@ public:
int32 SuccessCount = 0; int32 SuccessCount = 0;
FMoveNodeEntry Entry; FMoveNodeEntry Entry;
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None); TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array) for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
{ {
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) continue; if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) continue;

View File

@@ -57,7 +57,7 @@ public:
int32 TotalCount = Connections.Array.Num(); int32 TotalCount = Connections.Array.Num();
FConnectPinsEntry Entry; FConnectPinsEntry Entry;
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None); TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array) for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
{ {
if (!FWingProperty::PopulateFromJson(EntryProps, *ConnVal, false, WingOut::Stdout)) if (!FWingProperty::PopulateFromJson(EntryProps, *ConnVal, false, WingOut::Stdout))

View File

@@ -57,7 +57,7 @@ public:
int32 TotalDisconnected = 0; int32 TotalDisconnected = 0;
FDisconnectPinEntry Entry; FDisconnectPinEntry Entry;
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None); TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array) for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
{ {
if (!FWingProperty::PopulateFromJson(EntryProps, *DiscVal, false, WingOut::Stdout)) continue; if (!FWingProperty::PopulateFromJson(EntryProps, *DiscVal, false, WingOut::Stdout)) continue;

View File

@@ -50,7 +50,7 @@ bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass)
TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClass) TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClass)
{ {
return FWingProperty::GetNames(FactoryClass, CPF_Edit); return FWingProperty::GetVisibleNames(FactoryClass);
} }
UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors) UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors)

View File

@@ -1,5 +1,6 @@
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingActorComponent.h" #include "WingActorComponent.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
@@ -29,6 +30,7 @@ WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
if (Step.Equals(TEXT("component"), ESearchCase::IgnoreCase)) return &WingFetcher::Component; if (Step.Equals(TEXT("component"), ESearchCase::IgnoreCase)) return &WingFetcher::Component;
if (Step.Equals(TEXT("widget"), ESearchCase::IgnoreCase)) return &WingFetcher::Widget; if (Step.Equals(TEXT("widget"), ESearchCase::IgnoreCase)) return &WingFetcher::Widget;
if (Step.Equals(TEXT("levelblueprint"), ESearchCase::IgnoreCase)) return &WingFetcher::LevelBlueprint; if (Step.Equals(TEXT("levelblueprint"), ESearchCase::IgnoreCase)) return &WingFetcher::LevelBlueprint;
if (Step.Equals(TEXT("structprop"), ESearchCase::IgnoreCase)) return &WingFetcher::StructProp;
return nullptr; return nullptr;
} }
@@ -370,3 +372,50 @@ WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
SetObj(LevelBP); SetObj(LevelBP);
return *this; return *this;
} }
WingFetcher& WingFetcher::StructProp(const FString& Value)
{
if (bError) return *this;
FName InternalID = WingUtils::CheckInternalizeID(Value, Errors);
if (InternalID.IsNone()) return SetError();
if (!Obj)
{
TypeMismatch(TEXT("structprop"), TEXT("UObject"));
return SetError();
}
FStructProperty* StructProp = nullptr;
// The "host" is the object containing this property.
UObject *HostObject = Obj;
void *HostBase = Obj;
UStruct *HostType = Obj->GetClass();
bool HostEditable = true;
// If we are *already* inside a UWingStructPointer, update the host
// fields, to make it possible to navigate even further inside.
if (UWingStructPointer *SPtr = ::Cast<UWingStructPointer>(Obj))
{
HostObject = SPtr->Object;
HostBase = SPtr->StructBase;
HostType = SPtr->StructType;
HostEditable = SPtr->Editable;
}
StructProp = FindFProperty<FStructProperty>(HostType, InternalID);
if (!StructProp)
{
Errors.Printf(TEXT("ERROR: No struct property '%s' found on %s\n"), *Value, *HostType->GetName());
return SetError();
}
UWingStructPointer* Ptr = NewObject<UWingStructPointer>();
Ptr->Object = HostObject;
Ptr->StructType = StructProp->Struct;
Ptr->StructBase = StructProp->ContainerPtrToValuePtr<void>(HostBase);
Ptr->Editable = HostEditable && StructProp->HasAllPropertyFlags(CPF_Edit);
SetObj(Ptr);
return *this;
}

View File

@@ -250,7 +250,7 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary) void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary)
{ {
TArray<FWingProperty> Props = FWingProperty::GetDetails(Node, CPF_Edit, false); TArray<FWingProperty> Props = FWingProperty::GetDetails(Node, false);
FString PrimaryCategory; FString PrimaryCategory;
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node)) if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))

View File

@@ -16,6 +16,7 @@
bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
FObjectPropertyBase *OProp = CastField<FObjectPropertyBase>(Prop); FObjectPropertyBase *OProp = CastField<FObjectPropertyBase>(Prop);
if (!OProp) if (!OProp)
{ {
@@ -47,6 +48,7 @@ bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
bool FWingProperty::SetDouble(double D, WingOut Errors) const bool FWingProperty::SetDouble(double D, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
FNumericProperty *NProp = CastField<FNumericProperty>(Prop); FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
if (!NProp) if (!NProp)
{ {
@@ -95,6 +97,7 @@ bool FWingProperty::SetDouble(double D, WingOut Errors) const
bool FWingProperty::SetInt64(int64 I, WingOut Errors) const bool FWingProperty::SetInt64(int64 I, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
FNumericProperty *NProp = CastField<FNumericProperty>(Prop); FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
if (!NProp) if (!NProp)
{ {
@@ -133,6 +136,7 @@ bool FWingProperty::SetInt64(int64 I, WingOut Errors) const
bool FWingProperty::SetBool(bool B, WingOut Errors) const bool FWingProperty::SetBool(bool B, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop)) if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
{ {
Prop->SetValue_InContainer(Container, &B); Prop->SetValue_InContainer(Container, &B);
@@ -144,6 +148,8 @@ bool FWingProperty::SetBool(bool B, WingOut Errors) const
bool FWingProperty::SetText(FString Value, WingOut Errors) const bool FWingProperty::SetText(FString Value, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
// Pin types get parsed by UWingTypes. // Pin types get parsed by UWingTypes.
if (IsPinTypeProperty(Prop)) if (IsPinTypeProperty(Prop))
{ {
@@ -201,6 +207,8 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
bool FWingProperty::SetJson(const FJsonValue &JsonValue, WingOut Errors) const bool FWingProperty::SetJson(const FJsonValue &JsonValue, WingOut Errors) const
{ {
if (!CheckEditable(Errors)) return false;
if (JsonValue.Type == EJson::String) if (JsonValue.Type == EJson::String)
{ {
return SetText(JsonValue.AsString(), Errors); return SetText(JsonValue.AsString(), Errors);
@@ -376,28 +384,35 @@ FString FWingProperty::GetCategory() const
return Result; return Result;
} }
void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props) TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj)
{
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Add(FWingProperty(*It, Obj.StructPtr));
}
}
TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags)
{ {
TArray<FWingProperty> Result; TArray<FWingProperty> Result;
GetAll(Obj, Flags, Result); for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
}
return Result; return Result;
} }
TArray<FName> FWingProperty::GetNames(UStruct *US, EPropertyFlags Flags) TArray<FWingProperty> FWingProperty::GetVisible(FWingStructAndUStruct Obj)
{
TArray<FWingProperty> Result;
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
{
if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
}
return Result;
}
TArray<FName> FWingProperty::GetVisibleNames(UStruct *US)
{ {
TArray<FName> Result; TArray<FName> Result;
for (TFieldIterator<FProperty> It(US); It; ++It) for (TFieldIterator<FProperty> It(US); It; ++It)
{ {
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue; if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
Result.Add(It->GetFName()); Result.Add(It->GetFName());
} }
return Result; return Result;
@@ -419,10 +434,20 @@ void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In,
In.SetNum(Dst); In.SetNum(Dst);
} }
TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable) TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
{ {
if (!Obj) return {}; if (!Obj) return {};
// If it's a UWingStructPointer, return the properties
// of the struct instead. Propagate editability of the host.
if (UWingStructPointer *SP = Cast<UWingStructPointer>(Obj))
{
TArray<FWingProperty> Result =
GetVisible(FWingStructAndUStruct(SP->StructBase, SP->StructType));
if (!Mutable || (!SP->Editable)) StripEditable(Result);
return Result;
}
// Blueprints don't have editable properties. So // Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO. // instead, we fetch properties from the generated CDO.
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj)) if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
@@ -446,7 +471,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
} }
} }
TArray<FWingProperty> Result = GetAll(Obj, Flags); TArray<FWingProperty> Result = GetVisible(Obj);
// If it's a Material Graph node, also collect properties from // If it's a Material Graph node, also collect properties from
// the associated material expression. // the associated material expression.
@@ -455,7 +480,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
{ {
if (UMaterialExpression* Expr = MatNode->MaterialExpression) if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{ {
GetAll(Expr, Flags, Result); Result.Append(GetVisible(Expr));
} }
} }
@@ -466,10 +491,11 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
FWingProperty::Remove(Result, TEXT("Slot")); FWingProperty::Remove(Result, TEXT("Slot"));
if (UPanelSlot* Slot = Widget->Slot) if (UPanelSlot* Slot = Widget->Slot)
{ {
GetAll(Slot, Flags, Result); Result.Append(GetVisible(Slot));
} }
} }
if (!Mutable) StripEditable(Result);
return Result; return Result;
} }
@@ -555,4 +581,20 @@ bool FWingProperty::CheckImportTextResult(const FString &Value, WingOut Errors)
} }
} }
return true; return true;
}
void FWingProperty::StripEditable(TArray<FWingProperty> &Props)
{
for (FWingProperty &Elt : Props) Elt.Editable = false;
}
bool FWingProperty::CheckEditable(WingOut Errors) const
{
if (!Editable)
{
Errors.Printf(TEXT("ERROR: Cannot edit property %s, not marked editable"),
*WingUtils::FormatName(Prop));
return false;
}
return true;
} }

View File

@@ -331,7 +331,7 @@ void UWingServer::TryCallHandler(const FString &Line)
Handler->Configuration = Found; Handler->Configuration = Found;
// Populate the handler object with the request parameters. // Populate the handler object with the request parameters.
TArray<FWingProperty> Props = FWingProperty::GetAll(Handler, CPF_Edit); TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout)) if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
@@ -365,8 +365,6 @@ void UWingServer::AcceptNewConnections()
Client->Socket = ClientSocket; Client->Socket = ClientSocket;
Client->ThreadFuture = Async(EAsyncExecution::Thread, [this, Client]() { ClientThreadFunc(this, Client); }); Client->ThreadFuture = Async(EAsyncExecution::Thread, [this, Client]() { ClientThreadFunc(this, Client); });
Clients.Add(Client); Clients.Add(Client);
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client connected."));
} }
void UWingServer::CleanupFinishedClients() void UWingServer::CleanupFinishedClients()
@@ -448,10 +446,8 @@ void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnec
} }
Client->bDone = true; Client->bDone = true;
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client disconnected."));
} }
// ============================================================ // ============================================================
// BuildWingHandlerRegistry // BuildWingHandlerRegistry
// ============================================================ // ============================================================

View File

@@ -319,7 +319,7 @@ WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDe
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName); FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
if (Prop) if (Prop)
{ {
Result.DefaultValue = FWingProperty(Prop, CDO).GetText(); Result.DefaultValue = FWingProperty(Prop, CDO, false).GetText();
Result.DefaultSpecified = true; Result.DefaultSpecified = true;
} }
} }
@@ -494,7 +494,7 @@ bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
*WingTokenizer::ExternalizeID(Input.Name)); *WingTokenizer::ExternalizeID(Input.Name));
return false; return false;
} }
if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue, Errors)) return false; if (!FWingProperty(Prop, CDO, true).SetText(Input.DefaultValue, Errors)) return false;
} }
} }
return true; return true;

View File

@@ -54,6 +54,7 @@ public:
WingFetcher& Component(const FString& Value); WingFetcher& Component(const FString& Value);
WingFetcher& Widget(const FString& Value); WingFetcher& Widget(const FString& Value);
WingFetcher& LevelBlueprint(const FString& Value); WingFetcher& LevelBlueprint(const FString& Value);
WingFetcher& StructProp(const FString& Value);
// Return true if there haven't been any errors. // Return true if there haven't been any errors.
// Note that errors always automatically generate // Note that errors always automatically generate

View File

@@ -94,6 +94,9 @@ struct FWingStructAndUStruct
void *StructPtr; void *StructPtr;
UStruct *UStructPtr; UStruct *UStructPtr;
// Explicit constructor.
explicit FWingStructAndUStruct(void *Base, UStruct *S) : StructPtr(Base), UStructPtr(S) {}
// Copy constructor. // Copy constructor.
FWingStructAndUStruct(FWingStructAndUStruct &Src) : StructPtr(Src.StructPtr), UStructPtr(Src.UStructPtr) {} FWingStructAndUStruct(FWingStructAndUStruct &Src) : StructPtr(Src.StructPtr), UStructPtr(Src.UStructPtr) {}
@@ -103,4 +106,29 @@ struct FWingStructAndUStruct
// Construct from a UStruct pointer. // Construct from a UStruct pointer.
template<class T, typename = std::enable_if_t<!std::is_base_of_v<UObject, T>>> template<class T, typename = std::enable_if_t<!std::is_base_of_v<UObject, T>>>
FWingStructAndUStruct(T *Struct) : StructPtr(Struct), UStructPtr(Struct->StaticStruct()) {} FWingStructAndUStruct(T *Struct) : StructPtr(Struct), UStructPtr(Struct->StaticStruct()) {}
};
// WingStructPointer: Allows us to store a pointer to a
// ustruct in a variable of class UObject, through the magic
// of an extra level of indirection. If the struct is part of
// a UObject, then the Object field should be point at the
// object to ensure it doesn't get garbage collected. If the
// editable flag is false, it means the struct is for viewing
// only.
UCLASS()
class UWingStructPointer : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
UObject* Object;
UPROPERTY()
UStruct* StructType;
void *StructBase;
bool Editable;
}; };

View File

@@ -12,11 +12,12 @@ struct FWingProperty
FProperty* Prop = nullptr; FProperty* Prop = nullptr;
void* Container = nullptr; void* Container = nullptr;
bool Editable = false;
// Construct a property reference. // Construct a property reference.
// //
FWingProperty(FProperty* InProp, void* InContainer) FWingProperty(FProperty* InProp, void* InContainer, bool Edit)
: Prop(InProp), Container(InContainer) {} : Prop(InProp), Container(InContainer), Editable(Edit) {}
// Construct a null property reference. // Construct a null property reference.
// //
@@ -57,7 +58,7 @@ struct FWingProperty
// //
FString GetTruncatedText(int32 MaxLen) const; FString GetTruncatedText(int32 MaxLen) const;
// Print the property's type, name, and value. // Print the property's editflag, type, name, and value.
// //
void Print(WingOut Out) const; void Print(WingOut Out) const;
@@ -69,16 +70,22 @@ struct FWingProperty
// //
explicit operator bool() const { return Prop != nullptr; } explicit operator bool() const { return Prop != nullptr; }
// Get the raw properties of the specified object or struct. // Get all the properties of the specified object or struct.
// //
// This gets the properties that are literally present in the // This gets the properties that are literally present in the
// specified object or struct. No special interpretation is done. // specified object or struct. No special interpretation is done.
// //
static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags); static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj);
// Get all the visible properties of the specified object or struct.
//
// This gets the properties that have CPF_Edit marked on them.
//
static TArray<FWingProperty> GetVisible(FWingStructAndUStruct Obj);
// Get just the names of the properties of the specified struct/class. // Get just the names of the properties of the specified struct/class.
// //
static TArray<FName> GetNames(UStruct *US, EPropertyFlags Flags); static TArray<FName> GetVisibleNames(UStruct *US);
// Remove any properties with the specified name. // Remove any properties with the specified name.
// //
@@ -98,12 +105,10 @@ struct FWingProperty
// click a material graph node, the details panel shows you properties // click a material graph node, the details panel shows you properties
// of the node, but also properties of the material expression. // of the node, but also properties of the material expression.
// //
// When editing an inherited ActorComponent, you're actually editing // If you do not specify mutable, then all properties will be marked
// properties that *override* the original properties of the ActorComponent. // non-editable.
// The 'mutable' flag tells it whether to create overrides for these
// properties.
// //
static TArray<FWingProperty> GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable); static TArray<FWingProperty> GetDetails(UObject* Obj, bool Mutable);
// Functions to populate properties from a JSON object. // Functions to populate properties from a JSON object.
// //
@@ -113,8 +118,9 @@ struct FWingProperty
bool AllOptional, WingOut Errors); bool AllOptional, WingOut Errors);
private: private:
static void GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props); static void StripEditable(TArray<FWingProperty> &Props);
static bool IsPinTypeProperty(FProperty *Prop); static bool IsPinTypeProperty(FProperty *Prop);
void PrintExpectsReceived(const TCHAR *Type, WingOut Errors) const; void PrintExpectsReceived(const TCHAR *Type, WingOut Errors) const;
bool CheckImportTextResult(const FString &Value, WingOut Errors) const; bool CheckImportTextResult(const FString &Value, WingOut Errors) const;
bool CheckEditable(WingOut Errors) const;
}; };