Files
igor dallavanzi 24e90f7253 support for Tsets in property bags
#rb Patrick.Boutot
[RN] property Bag now supports TSets
[FYI] Seth.Weedin, carlos.egas, eric.buchholz

[CL 36156007 by igor dallavanzi in 5.5 branch]
2024-09-10 14:32:18 -04:00

1509 lines
49 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PropertyBagDetails.h"
#include "DetailWidgetRow.h"
#include "DetailLayoutBuilder.h"
#include "IPropertyUtilities.h"
#include "IDetailChildrenBuilder.h"
#include "UObject/EnumProperty.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Input/SComboButton.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ScopedTransaction.h"
#include "StructUtils/UserDefinedStruct.h"
#include "SPinTypeSelector.h"
#include "StructUtilsMetadata.h"
#include "Templates/ValueOrError.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PropertyBagDetails)
#define LOCTEXT_NAMESPACE "StructUtilsEditor"
////////////////////////////////////
namespace UE::StructUtils
{
/** Sets property descriptor based on a Blueprint pin type. */
void SetPropertyDescFromPin(FPropertyBagPropertyDesc& Desc, const FEdGraphPinType& PinType)
{
const UPropertyBagSchema* Schema = GetDefault<UPropertyBagSchema>();
check(Schema);
// remove any existing containers
Desc.ContainerTypes.Reset();
// Fill Container types, if any
switch (PinType.ContainerType)
{
case EPinContainerType::Array:
Desc.ContainerTypes.Add(EPropertyBagContainerType::Array);
break;
case EPinContainerType::Set:
Desc.ContainerTypes.Add(EPropertyBagContainerType::Set);
break;
case EPinContainerType::Map:
ensureMsgf(false, TEXT("Unsuported container type [Map] "));
break;
default:
break;
}
// Value type
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean)
{
Desc.ValueType = EPropertyBagPropertyType::Bool;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Byte)
{
if (UEnum* Enum = Cast<UEnum>(PinType.PinSubCategoryObject))
{
Desc.ValueType = EPropertyBagPropertyType::Enum;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else
{
Desc.ValueType = EPropertyBagPropertyType::Byte;
Desc.ValueTypeObject = nullptr;
}
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Int)
{
Desc.ValueType = EPropertyBagPropertyType::Int32;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Int64)
{
Desc.ValueType = EPropertyBagPropertyType::Int64;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Real)
{
if (PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float)
{
Desc.ValueType = EPropertyBagPropertyType::Float;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double)
{
Desc.ValueType = EPropertyBagPropertyType::Double;
Desc.ValueTypeObject = nullptr;
}
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Name)
{
Desc.ValueType = EPropertyBagPropertyType::Name;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_String)
{
Desc.ValueType = EPropertyBagPropertyType::String;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Text)
{
Desc.ValueType = EPropertyBagPropertyType::Text;
Desc.ValueTypeObject = nullptr;
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Enum)
{
Desc.ValueType = EPropertyBagPropertyType::Enum;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
Desc.ValueType = EPropertyBagPropertyType::Struct;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object)
{
Desc.ValueType = EPropertyBagPropertyType::Object;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject)
{
Desc.ValueType = EPropertyBagPropertyType::SoftObject;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_Class)
{
Desc.ValueType = EPropertyBagPropertyType::Class;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass)
{
Desc.ValueType = EPropertyBagPropertyType::SoftClass;
Desc.ValueTypeObject = PinType.PinSubCategoryObject.Get();
}
else
{
ensureMsgf(false, TEXT("Unhandled pin category %s"), *PinType.PinCategory.ToString());
}
}
namespace Private
{
/** @return true property handle holds struct property of type T. */
template<typename T>
bool IsScriptStruct(const TSharedPtr<IPropertyHandle>& PropertyHandle)
{
if (!PropertyHandle)
{
return false;
}
FStructProperty* StructProperty = CastField<FStructProperty>(PropertyHandle->GetProperty());
return StructProperty && StructProperty->Struct->IsA(TBaseStructure<T>::Get()->GetClass());
}
/** @return true if the property is one of the known missing types. */
bool HasMissingType(const TSharedPtr<IPropertyHandle>& PropertyHandle)
{
if (!PropertyHandle)
{
return false;
}
// Handles Struct
if (FStructProperty* StructProperty = CastField<FStructProperty>(PropertyHandle->GetProperty()))
{
return StructProperty->Struct == FPropertyBagMissingStruct::StaticStruct();
}
// Handles Object, SoftObject, Class, SoftClass.
if (FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(PropertyHandle->GetProperty()))
{
return ObjectProperty->PropertyClass == UPropertyBagMissingObject::StaticClass();
}
// Handles Enum
if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(PropertyHandle->GetProperty()))
{
return EnumProperty->GetEnum() == StaticEnum<EPropertyBagMissingEnum>();
}
return false;
}
/** @return property bag struct common to all edited properties. */
const UPropertyBag* GetCommonBagStruct(TSharedPtr<IPropertyHandle> StructProperty)
{
const UPropertyBag* CommonBagStruct = nullptr;
if (ensure(IsScriptStruct<FInstancedPropertyBag>(StructProperty)))
{
StructProperty->EnumerateConstRawData([&CommonBagStruct](const void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/)
{
if (RawData)
{
const FInstancedPropertyBag* Bag = static_cast<const FInstancedPropertyBag*>(RawData);
const UPropertyBag* BagStruct = Bag->GetPropertyBagStruct();
if (CommonBagStruct && CommonBagStruct != BagStruct)
{
// Multiple struct types on the sources - show nothing set
CommonBagStruct = nullptr;
return false;
}
CommonBagStruct = BagStruct;
}
return true;
});
}
return CommonBagStruct;
}
/** @return property descriptors of the property bag struct common to all edited properties. */
TArray<FPropertyBagPropertyDesc> GetCommonPropertyDescs(const TSharedPtr<IPropertyHandle>& StructProperty)
{
TArray<FPropertyBagPropertyDesc> PropertyDescs;
if (const UPropertyBag* BagStruct = GetCommonBagStruct(StructProperty))
{
PropertyDescs = BagStruct->GetPropertyDescs();
}
return PropertyDescs;
}
/** Creates new property bag struct and sets all properties to use it, migrating over old values. */
void SetPropertyDescs(const TSharedPtr<IPropertyHandle>& StructProperty, const TConstArrayView<FPropertyBagPropertyDesc> PropertyDescs)
{
if (ensure(IsScriptStruct<FInstancedPropertyBag>(StructProperty)))
{
// Create new bag struct
const UPropertyBag* NewBagStruct = UPropertyBag::GetOrCreateFromDescs(PropertyDescs);
// Migrate structs to the new type, copying values over.
StructProperty->EnumerateRawData([&NewBagStruct](void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/)
{
if (RawData)
{
if (FInstancedPropertyBag* Bag = static_cast<FInstancedPropertyBag*>(RawData))
{
Bag->MigrateToNewBagStruct(NewBagStruct);
}
}
return true;
});
}
}
/** @return sanitized property name based on the input string. */
FName GetValidPropertyName(const FString& Name)
{
FName Result;
if (!Name.IsEmpty())
{
if (!FName::IsValidXName(Name, INVALID_OBJECTNAME_CHARACTERS))
{
Result = MakeObjectNameFromDisplayLabel(Name, NAME_None);
}
else
{
Result = FName(Name);
}
}
else
{
Result = FName(TEXT("Property"));
}
return Result;
}
FName GetPropertyNameSafe(const TSharedPtr<IPropertyHandle>& PropertyHandle)
{
const FProperty* Property = PropertyHandle ? PropertyHandle->GetProperty() : nullptr;
if (Property != nullptr)
{
return Property->GetFName();
}
return FName();
}
/** @return true of the property name is not used yet by the property bag structure common to all edited properties. */
bool IsUniqueName(const FName NewName, const FName OldName, const TSharedPtr<IPropertyHandle>& StructProperty)
{
if (NewName == OldName)
{
return false;
}
if (!StructProperty || !StructProperty->IsValidHandle())
{
return false;
}
bool bFound = false;
if (ensure(IsScriptStruct<FInstancedPropertyBag>(StructProperty)))
{
StructProperty->EnumerateConstRawData([&bFound, NewName](const void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/)
{
if (const FInstancedPropertyBag* Bag = static_cast<const FInstancedPropertyBag*>(RawData))
{
if (const UPropertyBag* BagStruct = Bag->GetPropertyBagStruct())
{
const bool bContains = BagStruct->GetPropertyDescs().ContainsByPredicate([NewName](const FPropertyBagPropertyDesc& Desc)
{
return Desc.Name == NewName;
});
if (bContains)
{
bFound = true;
return false; // Stop iterating
}
}
}
return true;
});
}
return !bFound;
}
/** @return Blueprint pin type from property descriptor. */
FEdGraphPinType GetPropertyDescAsPin(const FPropertyBagPropertyDesc& Desc)
{
UEnum* PropertyTypeEnum = StaticEnum<EPropertyBagPropertyType>();
check(PropertyTypeEnum);
const UPropertyBagSchema* Schema = GetDefault<UPropertyBagSchema>();
check(Schema);
FEdGraphPinType PinType;
PinType.PinSubCategory = NAME_None;
// Container type
//@todo: Handle nested containers in property selection.
const EPropertyBagContainerType ContainerType = Desc.ContainerTypes.GetFirstContainerType();
switch (ContainerType)
{
case EPropertyBagContainerType::Array:
PinType.ContainerType = EPinContainerType::Array;
break;
case EPropertyBagContainerType::Set:
PinType.ContainerType = EPinContainerType::Set;
break;
default:
PinType.ContainerType = EPinContainerType::None;
}
// Value type
switch (Desc.ValueType)
{
case EPropertyBagPropertyType::Bool:
PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
break;
case EPropertyBagPropertyType::Byte:
PinType.PinCategory = UEdGraphSchema_K2::PC_Byte;
break;
case EPropertyBagPropertyType::Int32:
PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
break;
case EPropertyBagPropertyType::Int64:
PinType.PinCategory = UEdGraphSchema_K2::PC_Int64;
break;
case EPropertyBagPropertyType::Float:
PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float;
break;
case EPropertyBagPropertyType::Double:
PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
PinType.PinSubCategory = UEdGraphSchema_K2::PC_Double;
break;
case EPropertyBagPropertyType::Name:
PinType.PinCategory = UEdGraphSchema_K2::PC_Name;
break;
case EPropertyBagPropertyType::String:
PinType.PinCategory = UEdGraphSchema_K2::PC_String;
break;
case EPropertyBagPropertyType::Text:
PinType.PinCategory = UEdGraphSchema_K2::PC_Text;
break;
case EPropertyBagPropertyType::Enum:
// @todo: some pin coloring is not correct due to this (byte-as-enum vs enum).
PinType.PinCategory = UEdGraphSchema_K2::PC_Enum;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Struct:
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Object:
PinType.PinCategory = UEdGraphSchema_K2::PC_Object;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftObject:
PinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::Class:
PinType.PinCategory = UEdGraphSchema_K2::PC_Class;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::SoftClass:
PinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
PinType.PinSubCategoryObject = const_cast<UObject*>(Desc.ValueTypeObject.Get());
break;
case EPropertyBagPropertyType::UInt32: // Warning : Type only partially supported (Blueprint does not support unsigned type)
PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
break;
case EPropertyBagPropertyType::UInt64: // Warning : Type only partially supported (Blueprint does not support unsigned type)
PinType.PinCategory = UEdGraphSchema_K2::PC_Int64;
break;
default:
ensureMsgf(false, TEXT("Unhandled value type %s"), *UEnum::GetValueAsString(Desc.ValueType));
break;
}
return PinType;
}
template<typename TFunc>
void ApplyChangesToPropertyDescs(const FText& SessionName, const TSharedPtr<IPropertyHandle>& StructProperty, const TSharedPtr<IPropertyUtilities>& PropUtils, TFunc&& Function)
{
if (!StructProperty || !PropUtils)
{
return;
}
FScopedTransaction Transaction(SessionName);
TArray<FPropertyBagPropertyDesc> PropertyDescs = GetCommonPropertyDescs(StructProperty);
StructProperty->NotifyPreChange();
Function(PropertyDescs);
SetPropertyDescs(StructProperty, PropertyDescs);
StructProperty->NotifyPostChange(EPropertyChangeType::ValueSet);
StructProperty->NotifyFinishedChangingProperties();
if (PropUtils)
{
PropUtils->ForceRefresh();
}
}
bool CanHaveMemberVariableOfType(const FEdGraphPinType& PinType)
{
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Exec
|| PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard
|| PinType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate
|| PinType.PinCategory == UEdGraphSchema_K2::PC_Delegate
|| PinType.PinCategory == UEdGraphSchema_K2::PC_Interface)
{
return false;
}
return true;
}
bool FindUserFunction(TSharedPtr<IPropertyHandle> InStructProperty, FName InFuncMetadataName, UFunction*& OutFunc, UObject*& OutTarget)
{
FProperty* MetadataProperty = InStructProperty->GetMetaDataProperty();
OutFunc = nullptr;
OutTarget = nullptr;
if (!MetadataProperty || !MetadataProperty->HasMetaData(InFuncMetadataName))
{
return false;
}
FString FunctionName = MetadataProperty->GetMetaData(InFuncMetadataName);
if (FunctionName.IsEmpty())
{
return false;
}
TArray<UObject*> OutObjects;
InStructProperty->GetOuterObjects(OutObjects);
// Check for external function references, taken from GetOptions
if (FunctionName.Contains(TEXT(".")))
{
OutFunc = FindObject<UFunction>(nullptr, *FunctionName, true);
if (ensureMsgf(OutFunc && OutFunc->HasAnyFunctionFlags(EFunctionFlags::FUNC_Static), TEXT("[%s] Didn't find function %s or expected it to be static"), *InFuncMetadataName.ToString(), *FunctionName))
{
UObject* GetOptionsCDO = OutFunc->GetOuterUClass()->GetDefaultObject();
OutTarget = GetOptionsCDO;
}
}
else if (OutObjects.Num() > 0)
{
OutTarget = OutObjects[0];
OutFunc = OutTarget->GetClass() ? OutTarget->GetClass()->FindFunctionByName(*FunctionName) : nullptr;
}
// Only support native functions
if (!ensureMsgf(OutFunc && OutFunc->IsNative(), TEXT("[%s] Didn't find function %s or expected it to be native"), *InFuncMetadataName.ToString(), *FunctionName))
{
OutFunc = nullptr;
OutTarget = nullptr;
}
return OutTarget != nullptr && OutFunc != nullptr;
}
// UFunction calling helpers.
// Use our "own" hardcoded reflection system for types used in UFunctions calls in this file.
template<typename T> struct TypeName { static const TCHAR* Get() = delete; };
#define DEFINE_TYPENAME(InType) template<> struct TypeName<InType> { static const TCHAR *Get() { return TEXT(#InType); }};
DEFINE_TYPENAME(bool)
DEFINE_TYPENAME(FGuid)
DEFINE_TYPENAME(FName)
DEFINE_TYPENAME(FEdGraphPinType)
#undef DEFINE_TYPENAME
// Wrapper around a param that store an address (const_cast for const ptr, be careful of that)
// a string identifiying the underlying cpp type and if the input value is const, mark it const.
struct FFuncParam
{
void* Value = nullptr;
const TCHAR* CppType;
bool bIsConst = false;
template<typename T>
static FFuncParam Make(T& Value)
{
FFuncParam Result;
Result.Value = &Value;
Result.CppType = TypeName<T>::Get();
return Result;
}
template<typename T>
static FFuncParam Make(const T& Value)
{
FFuncParam Result;
Result.Value = &const_cast<T&>(Value);
Result.CppType = TypeName<T>::Get();
Result.bIsConst = true;
return Result;
}
};
// Validate that the function pass as parameter has signature ReturnType(ArgsTypes...)
template <typename ReturnType, typename... ArgsTypes>
bool ValidateFunctionSignature(UFunction* InFunc)
{
if (!InFunc)
{
return false;
}
constexpr int32 NumParms = std::is_same_v <ReturnType, void> ? sizeof...(ArgsTypes) : (sizeof...(ArgsTypes) + 1);
if (NumParms != InFunc->NumParms)
{
return false;
}
const TCHAR* ArgsCppTypes[NumParms] = { TypeName<ArgsTypes>::Get() ... };
// If we have a return type, put it at the end. UFunction will have the return type after InArgs in the field iterator.
if constexpr (!std::is_same_v<ReturnType, void>)
{
ArgsCppTypes[NumParms - 1] = TypeName<ReturnType>::Get();
}
else
{
// Otherwise, check that the function doesn't have a return param
if (InFunc->GetReturnProperty() != nullptr)
{
return false;
}
}
int32 ArgCppTypesIndex = 0;
for (TFieldIterator<FProperty> It(InFunc); It && It->HasAnyPropertyFlags(EPropertyFlags::CPF_Parm); ++It)
{
const FString PropertyCppType = It->GetCPPType();
if (PropertyCppType != ArgsCppTypes[ArgCppTypesIndex])
{
return false;
}
// Also making sure that the last param is a return param, if we have a return value
if constexpr (!std::is_same_v<ReturnType, void>)
{
if (ArgCppTypesIndex == NumParms - 1 && !It->HasAnyPropertyFlags(EPropertyFlags::CPF_ReturnParm))
{
return false;
}
}
ArgCppTypesIndex++;
}
return true;
}
template <typename ReturnType, typename... ArgsTypes>
TValueOrError<ReturnType, void> CallFunc(UObject* InTargetObject, UFunction* InFunc, ArgsTypes&& ...InArgs)
{
if (!InTargetObject || !InFunc)
{
return MakeError();
}
constexpr int32 NumParms = std::is_same_v <ReturnType, void> ? sizeof...(ArgsTypes) : (sizeof...(ArgsTypes) + 1);
if (NumParms != InFunc->NumParms)
{
return MakeError();
}
FFuncParam InParams[NumParms] = { FFuncParam::Make(std::forward<ArgsTypes>(InArgs)) ... };
auto Invoke = [InTargetObject, InFunc, &InParams, NumParms](ReturnType* OutResult) -> bool
{
// Validate that the function has a return property if the return type is not void.
if (std::is_same_v<ReturnType, void> != (InFunc->GetReturnProperty() == nullptr))
{
return false;
}
// Allocating our "stack" for the function call on the stack (will be freed when this function is exited)
uint8* StackMemory = (uint8*)FMemory_Alloca(InFunc->ParmsSize);
FMemory::Memzero(StackMemory, InFunc->ParmsSize);
if constexpr (!std::is_same_v<ReturnType, void>)
{
check(OutResult != nullptr);
InParams[NumParms - 1] = FFuncParam::Make(*OutResult);
}
bool bValid = true;
int32 ParamIndex = 0;
// Initializing our "stack" with our parameters. Use the property system to make sure more complex types
// are constructed before being set.
for (TFieldIterator<FProperty> It(InFunc); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
FProperty* LocalProp = *It;
if (!LocalProp->HasAnyPropertyFlags(CPF_ZeroConstructor))
{
LocalProp->InitializeValue_InContainer(StackMemory);
}
if (bValid)
{
if (ParamIndex >= NumParms)
{
bValid = false;
continue;
}
FFuncParam& Param = InParams[ParamIndex++];
if (LocalProp->GetCPPType() != Param.CppType)
{
bValid = false;
continue;
}
LocalProp->SetValue_InContainer(StackMemory, Param.Value);
}
}
if (bValid)
{
FFrame Stack(InTargetObject, InFunc, StackMemory, nullptr, InFunc->ChildProperties);
InFunc->Invoke(InTargetObject, Stack, OutResult);
}
ParamIndex = 0;
// Copy back all non-const out params (that is not the return param, this one is already set by the invoke call)
// from the stack, also making sure that the constructed types are destroyed accordingly.
for (TFieldIterator<FProperty> It(InFunc); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
FProperty* LocalProp = *It;
FFuncParam& Param = InParams[ParamIndex++];
if (LocalProp->HasAnyPropertyFlags(CPF_OutParm) && !LocalProp->HasAnyPropertyFlags(CPF_ReturnParm) && !Param.bIsConst)
{
LocalProp->GetValue_InContainer(StackMemory, Param.Value);
}
LocalProp->DestroyValue_InContainer(StackMemory);
}
return bValid;
};
if constexpr (std::is_same_v<ReturnType, void>)
{
if (Invoke(nullptr))
{
return MakeValue();
}
else
{
return MakeError();
}
}
else
{
ReturnType OutResult{};
if (Invoke(&OutResult))
{
return MakeValue(std::move(OutResult));
}
else
{
return MakeError();
}
}
}
/** Checks if the value for a source property in a source struct has the same value that the target property in the target struct. */
bool ArePropertiesIdentical(
const FPropertyBagPropertyDesc* InSourcePropertyDesc,
const FInstancedPropertyBag& InSourceInstance,
const FPropertyBagPropertyDesc* InTargetPropertyDesc,
const FInstancedPropertyBag& InTargetInstance)
{
if (!InSourceInstance.IsValid()
|| !InTargetInstance.IsValid()
|| !InSourcePropertyDesc
|| !InSourcePropertyDesc->CachedProperty
|| !InTargetPropertyDesc
|| !InTargetPropertyDesc->CachedProperty)
{
return false;
}
if (!InSourcePropertyDesc->CompatibleType(*InTargetPropertyDesc))
{
return false;
}
const uint8* SourceValueAddress = InSourceInstance.GetValue().GetMemory() + InSourcePropertyDesc->CachedProperty->GetOffset_ForInternal();
const uint8* TargetValueAddress = InTargetInstance.GetValue().GetMemory() + InTargetPropertyDesc->CachedProperty->GetOffset_ForInternal();
return InSourcePropertyDesc->CachedProperty->Identical(SourceValueAddress, TargetValueAddress);
}
/** Copy the value for a source property in a source struct to the target property in the target struct. */
void CopyPropertyValue(const FPropertyBagPropertyDesc* InSourcePropertyDesc, const FInstancedPropertyBag& InSourceInstance, const FPropertyBagPropertyDesc* InTargetPropertyDesc, FInstancedPropertyBag& InTargetInstance)
{
if (!InSourceInstance.IsValid() || !InTargetInstance.IsValid() || !InSourcePropertyDesc || !InSourcePropertyDesc->CachedProperty || !InTargetPropertyDesc || !InTargetPropertyDesc->CachedProperty)
{
return;
}
// Can't copy if they are not compatible.
if (!InSourcePropertyDesc->CompatibleType(*InTargetPropertyDesc))
{
return;
}
const uint8* SourceValueAddress = InSourceInstance.GetValue().GetMemory() + InSourcePropertyDesc->CachedProperty->GetOffset_ForInternal();
uint8* TargetValueAddress = InTargetInstance.GetMutableValue().GetMemory() + InTargetPropertyDesc->CachedProperty->GetOffset_ForInternal();
InSourcePropertyDesc->CachedProperty->CopyCompleteValue(TargetValueAddress, SourceValueAddress);
}
} // UE::StructUtils::Private
} // UE::StructUtils
//----------------------------------------------------------------//
// FPropertyBagInstanceDataDetails
// - StructProperty is FInstancedPropertyBag
// - ChildPropertyHandle a child property of the FInstancedPropertyBag::Value (FInstancedStruct)
//----------------------------------------------------------------//
FPropertyBagInstanceDataDetails::FPropertyBagInstanceDataDetails(TSharedPtr<IPropertyHandle> InStructProperty, const TSharedPtr<IPropertyUtilities>& InPropUtils, const bool bInFixedLayout, const bool bInAllowArrays)
: FInstancedStructDataDetails(InStructProperty.IsValid() ? InStructProperty->GetChildHandle(TEXT("Value")) : nullptr)
, BagStructProperty(InStructProperty)
, PropUtils(InPropUtils)
, bFixedLayout(bInFixedLayout)
, bAllowArrays(bInAllowArrays)
{
ensure(UE::StructUtils::Private::IsScriptStruct<FInstancedPropertyBag>(BagStructProperty));
ensure(PropUtils != nullptr);
}
void FPropertyBagInstanceDataDetails::OnChildRowAdded(IDetailPropertyRow& ChildRow)
{
TSharedPtr<SWidget> NameWidget;
TSharedPtr<SWidget> ValueWidget;
FDetailWidgetRow Row;
ChildRow.GetDefaultWidgets(NameWidget, ValueWidget, Row);
TSharedPtr<IPropertyHandle> ChildPropertyHandle = ChildRow.GetPropertyHandle();
bool bSupportedType = true;
FText UnsupportedTypeWarning;
TArray<FPropertyBagPropertyDesc> PropertyDescs = UE::StructUtils::Private::GetCommonPropertyDescs(BagStructProperty);
const FProperty* Property = ChildPropertyHandle->GetProperty();
if (FPropertyBagPropertyDesc* Desc = PropertyDescs.FindByPredicate([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; }))
{
static const FName HideInDetailPanelsName(TEXT("HideInDetailPanel"));
if (Desc->ContainerTypes.Num() > 1)
{
static const FText UnsupportedTypeWarningNestedContainer = LOCTEXT("NestedContainersWarning", "This property type is not supported in the property bag UI.");
// The property editing for nested containers is not supported.
UnsupportedTypeWarning = UnsupportedTypeWarningNestedContainer;
bSupportedType = false;
// Do not allow to edit the data.
ChildRow.IsEnabled(false);
}
else if ((Desc->ValueType == EPropertyBagPropertyType::UInt32 || Desc->ValueType == EPropertyBagPropertyType::UInt64)
&& !bFixedLayout)
{
static const FText UnsupportedTypeWarningUnsigned = LOCTEXT("UnsignedTypesWarning", "Unsigned types are not supported throught the property type selection. If you change the type, you will not be able to change it back.");
// Warn that the unsinged types cannot be set via the type selection.
UnsupportedTypeWarning = UnsupportedTypeWarningUnsigned;
bSupportedType = false;
}
else if (Property->HasMetaData(HideInDetailPanelsName))
{
ChildRow.Visibility(EVisibility::Hidden);
return;
}
}
if (!bFixedLayout)
{
// Inline editable name
TSharedPtr<SInlineEditableTextBlock> InlineWidget = SNew(SInlineEditableTextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.MultiLine(false)
.Text_Lambda([ChildPropertyHandle]()
{
return FText::FromName(UE::StructUtils::Private::GetPropertyNameSafe(ChildPropertyHandle));
})
.OnVerifyTextChanged_Lambda([BagStructProperty = BagStructProperty, ChildPropertyHandle](const FText& InText, FText& OutErrorMessage)
{
const FName NewName = UE::StructUtils::Private::GetValidPropertyName(InText.ToString());
const FName OldName = UE::StructUtils::Private::GetPropertyNameSafe(ChildPropertyHandle);
bool bResult = UE::StructUtils::Private::IsUniqueName(NewName, OldName, BagStructProperty);
if (!bResult)
{
OutErrorMessage = LOCTEXT("MustBeUniqueName", "Property must have unique name");
}
return bResult;
})
.OnTextCommitted_Lambda([BagStructProperty = BagStructProperty, PropUtils = PropUtils, ChildPropertyHandle](const FText& InNewText, ETextCommit::Type InCommitType)
{
if (InCommitType == ETextCommit::OnCleared)
{
return;
}
const FName NewName = UE::StructUtils::Private::GetValidPropertyName(InNewText.ToString());
const FName OldName = UE::StructUtils::Private::GetPropertyNameSafe(ChildPropertyHandle);
if (!UE::StructUtils::Private::IsUniqueName(NewName, OldName, BagStructProperty))
{
return;
}
UE::StructUtils::Private::ApplyChangesToPropertyDescs(
LOCTEXT("OnPropertyNameChanged", "Change Property Name"), BagStructProperty, PropUtils,
[&NewName, &ChildPropertyHandle](TArray<FPropertyBagPropertyDesc>& PropertyDescs)
{
const FProperty* Property = ChildPropertyHandle->GetProperty();
if (FPropertyBagPropertyDesc* Desc = PropertyDescs.FindByPredicate([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; }))
{
Desc->Name = NewName;
}
});
});
NameWidget = SNew(SComboButton)
.OnGetMenuContent(this, &FPropertyBagInstanceDataDetails::OnPropertyNameContent, ChildRow.GetPropertyHandle(), InlineWidget)
.ContentPadding(2)
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
.ButtonContent()
[
InlineWidget.ToSharedRef()
];
}
ChildRow
.CustomWidget(/*bShowChildren*/true)
.NameContent()
[
SNew(SHorizontalBox)
// Error icon
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(0,0,2,0)
[
SNew(SBox)
.WidthOverride(12)
.HeightOverride(12)
[
SNew(SImage)
.ToolTipText_Lambda([UnsupportedTypeWarning, bSupportedType]()
{
return !bSupportedType
? UnsupportedTypeWarning
: LOCTEXT("MissingType", "The property is missing type. The Struct, Enum, or Object may have been removed.");
})
.Visibility_Lambda([ChildPropertyHandle, bSupportedType]()
{
return UE::StructUtils::Private::HasMissingType(ChildPropertyHandle) || !bSupportedType ? EVisibility::Visible : EVisibility::Collapsed;
})
.Image(FAppStyle::GetBrush("Icons.Error"))
]
]
// Name
+SHorizontalBox::Slot()
.AutoWidth()
[
NameWidget.ToSharedRef()
]
]
.ValueContent()
[
ValueWidget.ToSharedRef()
];
if (HasPropertyOverrides())
{
TWeakPtr<FPropertyBagInstanceDataDetails> WeakSelf = SharedThis<FPropertyBagInstanceDataDetails>(this);
TAttribute<bool> EditConditionValue = TAttribute<bool>::CreateLambda(
[WeakSelf, ChildPropertyHandle]() -> bool
{
if (const TSharedPtr<FPropertyBagInstanceDataDetails> Self = WeakSelf.Pin())
{
return Self->IsPropertyOverridden(ChildPropertyHandle) == EPropertyOverrideState::Yes;
}
return true;
});
FOnBooleanValueChanged OnEditConditionChanged = FOnBooleanValueChanged::CreateLambda([WeakSelf, ChildPropertyHandle](bool bNewValue)
{
if (const TSharedPtr<FPropertyBagInstanceDataDetails> Self = WeakSelf.Pin())
{
Self->SetPropertyOverride(ChildPropertyHandle, bNewValue);
}
});
ChildRow.EditCondition(std::move(EditConditionValue), std::move(OnEditConditionChanged));
FIsResetToDefaultVisible IsResetVisible = FIsResetToDefaultVisible::CreateLambda([WeakSelf](TSharedPtr<IPropertyHandle> PropertyHandle)
{
if (const TSharedPtr<FPropertyBagInstanceDataDetails> Self = WeakSelf.Pin())
{
return !Self->IsDefaultValue(PropertyHandle);
}
return false;
});
FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateLambda([WeakSelf](TSharedPtr<IPropertyHandle> PropertyHandle)
{
if (const TSharedPtr<FPropertyBagInstanceDataDetails> Self = WeakSelf.Pin())
{
Self->ResetToDefault(PropertyHandle);
}
});
FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
ChildRow.OverrideResetToDefault(ResetOverride);
}
}
FPropertyBagInstanceDataDetails::EPropertyOverrideState FPropertyBagInstanceDataDetails::IsPropertyOverridden(TSharedPtr<IPropertyHandle> ChildPropertyHandle) const
{
if (!ChildPropertyHandle)
{
return EPropertyOverrideState::Undetermined;;
}
int32 NumValues = 0;
int32 NumOverrides = 0;
const FProperty* Property = ChildPropertyHandle->GetProperty();
check(Property);
EnumeratePropertyBags(BagStructProperty,
[Property, &NumValues, &NumOverrides]
(const FInstancedPropertyBag& DefaultPropertyBag, const FInstancedPropertyBag& PropertyBag, const IPropertyBagOverrideProvider& OverrideProvider)
{
NumValues++;
if (const UPropertyBag* Bag = PropertyBag.GetPropertyBagStruct())
{
const FPropertyBagPropertyDesc* PropertyDesc = Bag->FindPropertyDescByProperty(Property);
if (PropertyDesc && OverrideProvider.IsPropertyOverridden(PropertyDesc->ID))
{
NumOverrides++;
}
}
return true;
});
if (NumOverrides == 0)
{
return EPropertyOverrideState::No;
}
else if (NumOverrides == NumValues)
{
return EPropertyOverrideState::Yes;
}
return EPropertyOverrideState::Undetermined;
}
void FPropertyBagInstanceDataDetails::SetPropertyOverride(TSharedPtr<IPropertyHandle> ChildPropertyHandle, const bool bIsOverridden)
{
if (!ChildPropertyHandle)
{
return;
}
const FProperty* Property = ChildPropertyHandle->GetProperty();
check(Property);
FScopedTransaction Transaction(FText::Format(LOCTEXT("OverrideChange", "Change Override for {0}"), FText::FromName(ChildPropertyHandle->GetProperty()->GetFName())));
PreChangeOverrides();
EnumeratePropertyBags(
BagStructProperty,
[Property, bIsOverridden]
(const FInstancedPropertyBag& DefaultPropertyBag, const FInstancedPropertyBag& PropertyBag, const IPropertyBagOverrideProvider& OverrideProvider)
{
if (const UPropertyBag* Bag = PropertyBag.GetPropertyBagStruct())
{
if (const FPropertyBagPropertyDesc* PropertyDesc = Bag->FindPropertyDescByProperty(Property))
{
OverrideProvider.SetPropertyOverride(PropertyDesc->ID, bIsOverridden);
}
}
return true;
});
PostChangeOverrides();
}
bool FPropertyBagInstanceDataDetails::IsDefaultValue(TSharedPtr<IPropertyHandle> ChildPropertyHandle) const
{
if (!ChildPropertyHandle)
{
return true;
}
int32 NumValues = 0;
int32 NumOverridden = 0;
int32 NumIdentical = 0;
const FProperty* Property = ChildPropertyHandle->GetProperty();
check(Property);
EnumeratePropertyBags(
BagStructProperty,
[Property, &NumValues, &NumOverridden, &NumIdentical]
(const FInstancedPropertyBag& DefaultPropertyBag, const FInstancedPropertyBag& PropertyBag, const IPropertyBagOverrideProvider& OverrideProvider)
{
NumValues++;
const UPropertyBag* DefaultBag = DefaultPropertyBag.GetPropertyBagStruct();
const UPropertyBag* Bag = PropertyBag.GetPropertyBagStruct();
if (Bag && DefaultBag)
{
const FPropertyBagPropertyDesc* PropertyDesc = Bag->FindPropertyDescByProperty(Property);
const FPropertyBagPropertyDesc* DefaultPropertyDesc = DefaultBag->FindPropertyDescByProperty(Property);
if (PropertyDesc
&& DefaultPropertyDesc
&& OverrideProvider.IsPropertyOverridden(PropertyDesc->ID))
{
NumOverridden++;
if (UE::StructUtils::Private::ArePropertiesIdentical(DefaultPropertyDesc, DefaultPropertyBag, PropertyDesc, PropertyBag))
{
NumIdentical++;
}
}
}
return true;
});
if (NumOverridden == NumIdentical)
{
return true;
}
return false;
}
void FPropertyBagInstanceDataDetails::ResetToDefault(TSharedPtr<IPropertyHandle> ChildPropertyHandle)
{
if (!ChildPropertyHandle)
{
return;
}
const FProperty* Property = ChildPropertyHandle->GetProperty();
check(Property);
FScopedTransaction Transaction(FText::Format(LOCTEXT("ResetToDefault", "Reset {0} to default value"), FText::FromName(ChildPropertyHandle->GetProperty()->GetFName())));
ChildPropertyHandle->NotifyPreChange();
EnumeratePropertyBags(
BagStructProperty,
[Property]
(const FInstancedPropertyBag& DefaultPropertyBag, FInstancedPropertyBag& PropertyBag, const IPropertyBagOverrideProvider& OverrideProvider)
{
const UPropertyBag* DefaultBag = DefaultPropertyBag.GetPropertyBagStruct();
const UPropertyBag* Bag = PropertyBag.GetPropertyBagStruct();
if (Bag && DefaultBag)
{
const FPropertyBagPropertyDesc* PropertyDesc = Bag->FindPropertyDescByProperty(Property);
const FPropertyBagPropertyDesc* DefaultPropertyDesc = DefaultBag->FindPropertyDescByProperty(Property);
if (PropertyDesc
&& DefaultPropertyDesc
&& OverrideProvider.IsPropertyOverridden(PropertyDesc->ID))
{
UE::StructUtils::Private::CopyPropertyValue(DefaultPropertyDesc, DefaultPropertyBag, PropertyDesc, PropertyBag);
}
}
return true;
});
ChildPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
ChildPropertyHandle->NotifyFinishedChangingProperties();
}
TSharedRef<SWidget> FPropertyBagInstanceDataDetails::OnPropertyNameContent(TSharedPtr<IPropertyHandle> ChildPropertyHandle, TSharedPtr<SInlineEditableTextBlock> InlineWidget) const
{
constexpr bool bInShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bInShouldCloseWindowAfterMenuSelection, nullptr);
auto GetFilteredVariableTypeTree = [BagStructProperty = BagStructProperty](TArray<TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>>& TypeTree, ETypeTreeFilter TypeTreeFilter)
{
// The SPinTypeSelector popup might outlive this details view, so bag struct property can be invalid here.
if (!BagStructProperty || !BagStructProperty->IsValidHandle())
{
return;
}
UFunction* IsPinTypeAcceptedFunc = nullptr;
UObject* IsPinTypeAcceptedTarget = nullptr;
if (UE::StructUtils::Private::FindUserFunction(BagStructProperty, UE::StructUtils::Metadata::IsPinTypeAcceptedName, IsPinTypeAcceptedFunc, IsPinTypeAcceptedTarget))
{
check(IsPinTypeAcceptedFunc && IsPinTypeAcceptedTarget);
// We need to make sure the signature matches perfectly: bool(FEdGraphPinType, bool)
bool bFuncIsValid = UE::StructUtils::Private::ValidateFunctionSignature<bool, FEdGraphPinType, bool>(IsPinTypeAcceptedFunc);
if (!ensureMsgf(bFuncIsValid, TEXT("[%s] Function %s does not have the right signature."), *UE::StructUtils::Metadata::IsPinTypeAcceptedName.ToString(), *IsPinTypeAcceptedFunc->GetName()))
{
return;
}
}
auto IsPinTypeAccepted = [IsPinTypeAcceptedFunc, IsPinTypeAcceptedTarget](const FEdGraphPinType& InPinType, bool bInIsChild) -> bool
{
if (IsPinTypeAcceptedFunc && IsPinTypeAcceptedTarget)
{
const TValueOrError<bool, void> bIsValid = UE::StructUtils::Private::CallFunc<bool>(IsPinTypeAcceptedTarget, IsPinTypeAcceptedFunc, InPinType, bInIsChild);
return bIsValid.HasValue() && bIsValid.GetValue();
}
else
{
return true;
}
};
check(GetDefault<UEdGraphSchema_K2>());
TArray<TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>> TempTypeTree;
GetDefault<UPropertyBagSchema>()->GetVariableTypeTree(TempTypeTree, TypeTreeFilter);
// Filter
for (TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>& PinType : TempTypeTree)
{
if (!PinType.IsValid() || !IsPinTypeAccepted(PinType->GetPinType(/*bForceLoadSubCategoryObject*/false), /*bInIsChild=*/ false))
{
continue;
}
for (int32 ChildIndex = 0; ChildIndex < PinType->Children.Num(); )
{
TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo> Child = PinType->Children[ChildIndex];
if (Child.IsValid())
{
const FEdGraphPinType& ChildPinType = Child->GetPinType(/*bForceLoadSubCategoryObject*/false);
if (!UE::StructUtils::Private::CanHaveMemberVariableOfType(ChildPinType) || !IsPinTypeAccepted(ChildPinType, /*bInIsChild=*/ true))
{
PinType->Children.RemoveAt(ChildIndex);
continue;
}
}
++ChildIndex;
}
TypeTree.Add(PinType);
}
};
auto GetPinInfo = [BagStructProperty = BagStructProperty, ChildPropertyHandle]()
{
// The SPinTypeSelector popup might outlive this details view, so bag struct property can be invalid here.
if (!BagStructProperty || !BagStructProperty->IsValidHandle() || !ChildPropertyHandle || !ChildPropertyHandle->IsValidHandle())
{
return FEdGraphPinType();
}
TArray<FPropertyBagPropertyDesc> PropertyDescs = UE::StructUtils::Private::GetCommonPropertyDescs(BagStructProperty);
const FProperty* Property = ChildPropertyHandle->GetProperty();
if (FPropertyBagPropertyDesc* Desc = PropertyDescs.FindByPredicate([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; }))
{
return UE::StructUtils::Private::GetPropertyDescAsPin(*Desc);
}
return FEdGraphPinType();
};
auto PinInfoChanged = [BagStructProperty = BagStructProperty, PropUtils = PropUtils, ChildPropertyHandle](const FEdGraphPinType& PinType)
{
// The SPinTypeSelector popup might outlive this details view, so bag struct property can be invalid here.
if (!BagStructProperty || !BagStructProperty->IsValidHandle() || !ChildPropertyHandle || !ChildPropertyHandle->IsValidHandle())
{
return;
}
UE::StructUtils::Private::ApplyChangesToPropertyDescs(
LOCTEXT("OnPropertyTypeChanged", "Change Property Type"), BagStructProperty, PropUtils,
[&PinType, &ChildPropertyHandle](TArray<FPropertyBagPropertyDesc>& PropertyDescs)
{
// Find and change struct type
const FProperty* Property = ChildPropertyHandle ? ChildPropertyHandle->GetProperty() : nullptr;
if (FPropertyBagPropertyDesc* Desc = PropertyDescs.FindByPredicate([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; }))
{
UE::StructUtils::SetPropertyDescFromPin(*Desc, PinType);
}
});
};
auto RemoveProperty = [BagStructProperty = BagStructProperty, PropUtils = PropUtils, ChildPropertyHandle]()
{
if (!BagStructProperty || !BagStructProperty->IsValidHandle() || !ChildPropertyHandle || !ChildPropertyHandle->IsValidHandle())
{
return;
}
// Extra check provided by the user to cancel a remove action. Useful to provide the user a possibility to cancel the action if
// the given property is in use elsewhere.
UFunction* CanRemovePropertyFunc = nullptr;
UObject* CanRemovePropertyTarget = nullptr;
if (UE::StructUtils::Private::FindUserFunction(BagStructProperty, UE::StructUtils::Metadata::CanRemovePropertyName, CanRemovePropertyFunc, CanRemovePropertyTarget))
{
check(CanRemovePropertyFunc && CanRemovePropertyTarget);
FName PropertyName = ChildPropertyHandle->GetProperty()->GetFName();
const UPropertyBag* PropertyBag = UE::StructUtils::Private::GetCommonBagStruct(BagStructProperty);
const FPropertyBagPropertyDesc* PropertyDesc = PropertyBag ? PropertyBag->FindPropertyDescByName(PropertyName) : nullptr;
if (!PropertyDesc)
{
return;
}
// We need to make sure the signature matches perfectly: bool(FGuid, FName)s
bool bFuncIsValid = UE::StructUtils::Private::ValidateFunctionSignature<bool, FGuid, FName>(CanRemovePropertyFunc);
if(!ensureMsgf(bFuncIsValid, TEXT("[%s] Function %s does not have the right signature."), *UE::StructUtils::Metadata::CanRemovePropertyName.ToString(), *CanRemovePropertyFunc->GetName()))
{
return;
}
const TValueOrError<bool, void> bCanRemove = UE::StructUtils::Private::CallFunc<bool>(CanRemovePropertyTarget, CanRemovePropertyFunc, PropertyDesc->ID, PropertyDesc->Name);
if (bCanRemove.HasError() || !bCanRemove.GetValue())
{
return;
}
}
UE::StructUtils::Private::ApplyChangesToPropertyDescs(
LOCTEXT("OnPropertyRemoved", "Remove Property"), BagStructProperty, PropUtils,
[&ChildPropertyHandle](TArray<FPropertyBagPropertyDesc>& PropertyDescs)
{
const FProperty* Property = ChildPropertyHandle ? ChildPropertyHandle->GetProperty() : nullptr;
PropertyDescs.RemoveAll([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; });
});
};
auto MoveProperty = [BagStructProperty = BagStructProperty, PropUtils = PropUtils, ChildPropertyHandle](const int32 Delta)
{
if (!BagStructProperty || !BagStructProperty->IsValidHandle() || !ChildPropertyHandle || !ChildPropertyHandle->IsValidHandle())
{
return;
}
UE::StructUtils::Private::ApplyChangesToPropertyDescs(
LOCTEXT("OnPropertyMoved", "Move Property"), BagStructProperty, PropUtils,
[&ChildPropertyHandle, &Delta](TArray<FPropertyBagPropertyDesc>& PropertyDescs)
{
// Move
if (PropertyDescs.Num() > 1)
{
const FProperty* Property = ChildPropertyHandle ? ChildPropertyHandle->GetProperty() : nullptr;
const int32 PropertyIndex = PropertyDescs.IndexOfByPredicate([Property](const FPropertyBagPropertyDesc& Desc){ return Desc.CachedProperty == Property; });
if (PropertyIndex != INDEX_NONE)
{
const int32 NewPropertyIndex = FMath::Clamp(PropertyIndex + Delta, 0, PropertyDescs.Num() - 1);
PropertyDescs.Swap(PropertyIndex, NewPropertyIndex);
}
}
});
};
MenuBuilder.AddWidget(
SNew(SBox)
.HAlign(HAlign_Right)
.Padding(FMargin(12, 0, 12, 0))
[
SNew(SPinTypeSelector, FGetPinTypeTree::CreateLambda(GetFilteredVariableTypeTree))
.TargetPinType_Lambda(GetPinInfo)
.OnPinTypeChanged_Lambda(PinInfoChanged)
.Schema(GetDefault<UPropertyBagSchema>())
.bAllowArrays(bAllowArrays)
.TypeTreeFilter(ETypeTreeFilter::None)
.Font( IDetailLayoutBuilder::GetDetailFont() )
],
FText::GetEmpty());
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(
LOCTEXT("Rename", "Rename"),
LOCTEXT("Rename_ToolTip", "Rename property"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Edit"),
FUIAction(FExecuteAction::CreateLambda([InlineWidget]() { InlineWidget->EnterEditingMode(); }))
);
MenuBuilder.AddMenuEntry(
LOCTEXT("Remove", "Remove"),
LOCTEXT("Remove_ToolTip", "Remove property"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Delete"),
FUIAction(FExecuteAction::CreateLambda(RemoveProperty))
);
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(
LOCTEXT("MoveUp", "Move Up"),
LOCTEXT("MoveUp_ToolTip", "Move property up in the list"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.ArrowUp"),
FUIAction(FExecuteAction::CreateLambda(MoveProperty, -1))
);
MenuBuilder.AddMenuEntry(
LOCTEXT("MoveDown", "Move Down"),
LOCTEXT("MoveDown_ToolTip", "Move property down in the list"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.ArrowDown"),
FUIAction(FExecuteAction::CreateLambda(MoveProperty, +1))
);
return MenuBuilder.MakeWidget();
}
////////////////////////////////////
TSharedRef<IPropertyTypeCustomization> FPropertyBagDetails::MakeInstance()
{
return MakeShared<FPropertyBagDetails>();
}
void FPropertyBagDetails::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
PropUtils = StructCustomizationUtils.GetPropertyUtilities();
StructProperty = StructPropertyHandle;
check(StructProperty);
static const FName NAME_ShowOnlyInnerProperties = "ShowOnlyInnerProperties";
if (const FProperty* MetaDataProperty = StructProperty->GetMetaDataProperty())
{
bFixedLayout = MetaDataProperty->HasMetaData(UE::StructUtils::Metadata::FixedLayoutName);
bAllowArrays = MetaDataProperty->HasMetaData(UE::StructUtils::Metadata::AllowArraysName)
? MetaDataProperty->GetBoolMetaData(UE::StructUtils::Metadata::AllowArraysName)
: true;
// Don't show the header if ShowOnlyInnerProperties is set
if (MetaDataProperty->HasMetaData(NAME_ShowOnlyInnerProperties))
{
return;
}
if (MetaDataProperty->HasMetaData(UE::StructUtils::Metadata::DefaultTypeName))
{
if (UEnum* Enum = StaticEnum<EPropertyBagPropertyType>())
{
int32 EnumIndex = Enum->GetIndexByNameString(MetaDataProperty->GetMetaData(UE::StructUtils::Metadata::DefaultTypeName));
if (EnumIndex != INDEX_NONE)
{
DefaultType = EPropertyBagPropertyType(Enum->GetValueByIndex(EnumIndex));
}
}
}
}
TSharedPtr<SWidget> ValueWidget = SNullWidget::NullWidget;
if (!bFixedLayout)
{
ValueWidget = MakeAddPropertyWidget(StructProperty, PropUtils, DefaultType);
}
HeaderRow
.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
.VAlign(VAlign_Center)
[
ValueWidget.ToSharedRef()
]
.ShouldAutoExpand(true);
}
void FPropertyBagDetails::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
// Show the Value (FInstancedStruct) as child rows.
const TSharedRef<FPropertyBagInstanceDataDetails> InstanceDetails = MakeShareable(new FPropertyBagInstanceDataDetails(StructProperty, PropUtils, bFixedLayout, bAllowArrays));
StructBuilder.AddCustomBuilder(InstanceDetails);
}
TSharedPtr<SWidget> FPropertyBagDetails::MakeAddPropertyWidget(TSharedPtr<IPropertyHandle> InStructProperty, TSharedPtr<IPropertyUtilities> InPropUtils, EPropertyBagPropertyType DefaultType, const FSlateColor IconColor)
{
return SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ToolTipText(LOCTEXT("AddProperty_Tooltip", "Add new property"))
.OnClicked_Lambda([StructProperty = InStructProperty, PropUtils = InPropUtils, DefaultType]()
{
constexpr int32 MaxIterations = 100;
FName NewName(TEXT("NewProperty"));
int32 Number = 1;
while (!UE::StructUtils::Private::IsUniqueName(NewName, FName(), StructProperty) && Number < MaxIterations)
{
Number++;
NewName.SetNumber(Number);
}
if (Number == MaxIterations)
{
return FReply::Handled();
}
UE::StructUtils::Private::ApplyChangesToPropertyDescs(
LOCTEXT("OnPropertyAdded", "Add Property"), StructProperty, PropUtils,
[&NewName, DefaultType](TArray<FPropertyBagPropertyDesc>& PropertyDescs)
{
PropertyDescs.Emplace(NewName, DefaultType);
});
return FReply::Handled();
})
.Content()
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(IconColor)
]
];
}
////////////////////////////////////
bool UPropertyBagSchema::SupportsPinTypeContainer(TWeakPtr<const FEdGraphSchemaAction> SchemaAction,
const FEdGraphPinType& PinType, const EPinContainerType& ContainerType) const
{
return ContainerType == EPinContainerType::None || ContainerType == EPinContainerType::Array || ContainerType == EPinContainerType::Set;
}
#undef LOCTEXT_NAMESPACE