// Copyright Epic Games, Inc. All Rights Reserved. #include "PropertyBindingExtension.h" #include "Containers/StridedView.h" #include "DetailLayoutBuilder.h" #include "EdGraphSchema_K2.h" #include "Features/IModularFeatures.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "IPropertyAccessEditor.h" #include "IPropertyUtilities.h" #include "Kismet2/BlueprintEditorUtils.h" #include "PropertyBagDetails.h" #include "PropertyBindingBindableStructDescriptor.h" #include "PropertyBindingBinding.h" #include "PropertyBindingBindingCollectionOwner.h" #include "PropertyBindingDataView.h" #include "PropertyBindingPath.h" #include "PropertyBindingTypes.h" #include "ScopedTransaction.h" #include "StructUtils/InstancedStruct.h" #include "StructUtils/InstancedStructContainer.h" #include "Styling/AppStyle.h" #include "UObject/WeakInterfacePtr.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SBox.h" #define LOCTEXT_NAMESPACE "SmartObjectEditor" struct FStateTreeBlueprintPropertyRef; struct FStateTreeEditorNode; namespace UE::PropertyBinding { /** Helper struct to Begin/End Sections */ struct FMenuSectionHelper { PROPERTYBINDINGUTILSEDITOR_API explicit FMenuSectionHelper(FMenuBuilder& MenuBuilder); PROPERTYBINDINGUTILSEDITOR_API ~FMenuSectionHelper(); PROPERTYBINDINGUTILSEDITOR_API void SetSection(const FText& InSection); private: FText CurrentSection; FMenuBuilder& MenuBuilder; bool bSectionOpened = false; }; IPropertyBindingBindingCollectionOwner* FindBindingsOwner(UObject* InObject) { IPropertyBindingBindingCollectionOwner* Result = nullptr; for (UObject* Outer = InObject; Outer; Outer = Outer->GetOuter()) { if (IPropertyBindingBindingCollectionOwner* BindingOwner = Cast(Outer)) { Result = BindingOwner; break; } } return Result; } UStruct* ResolveLeafValueStructType(FPropertyBindingDataView ValueView, TConstArrayView InBindingChain) { if (ValueView.GetMemory() == nullptr) { return nullptr; } FPropertyBindingPath Path; for (const FBindingChainElement& Element : InBindingChain) { if (const FProperty* Property = Element.Field.Get()) { Path.AddPathSegment(Property->GetFName(), Element.ArrayIndex); } else if (Element.Field.Get()) { // Cannot handle function calls return nullptr; } } TArray Indirections; if (!Path.ResolveIndirectionsWithValue(ValueView, Indirections) || Indirections.IsEmpty()) { return nullptr; } // Last indirection points to the value of the leaf property, check the type. const FPropertyBindingPathIndirection& LastIndirection = Indirections.Last(); UStruct* Result = nullptr; if (LastIndirection.GetContainerAddress()) { if (const FStructProperty* StructProperty = CastField(LastIndirection.GetProperty())) { // Get the type of the instanced struct's value. if (StructProperty->Struct == TBaseStructure::Get()) { const FInstancedStruct& InstancedStruct = *static_cast(LastIndirection.GetPropertyAddress()); Result = const_cast(InstancedStruct.GetScriptStruct()); } } else if (CastField(LastIndirection.GetProperty())) { // Get type of the instanced object. if (const UObject* Object = *static_cast(LastIndirection.GetPropertyAddress())) { Result = Object->GetClass(); } } } return Result; } void MakeStructPropertyPathFromBindingChain(const FGuid StructID, TConstArrayView InBindingChain, FPropertyBindingDataView DataView, FPropertyBindingPath& OutPath) { OutPath.Reset(); OutPath.SetStructID(StructID); for (const FBindingChainElement& Element : InBindingChain) { if (const FProperty* Property = Element.Field.Get()) { OutPath.AddPathSegment(Property->GetFName(), Element.ArrayIndex); } else if (const UFunction* Function = Element.Field.Get()) { OutPath.AddPathSegment(Function->GetFName()); } } OutPath.UpdateSegmentsFromValue(DataView); } TSharedPtr MakeStructPropertyPathFromPropertyHandle( const TSharedPtr& InPropertyHandle , FPropertyBindingPath& OutPath , const FGuid InFallbackStructID) { OutPath.Reset(); FGuid StructID; TArray PathSegments; TSharedPtr BindablePropertyHandle; TSharedPtr CurrentPropertyHandle = InPropertyHandle; while (CurrentPropertyHandle.IsValid()) { if (const FProperty* Property = CurrentPropertyHandle->GetProperty()) { FPropertyBindingPathSegment& Segment = PathSegments.InsertDefaulted_GetRef(0); // Traversing from leaf to root, insert in reverse. // Store path up to the property which has ID. Segment.SetName(Property->GetFName()); Segment.SetArrayIndex(CurrentPropertyHandle->GetIndexInArray()); // Store type of the object (e.g. for instanced objects or instanced structs). if (const FObjectProperty* ObjectProperty = CastField(Property)) { if (ObjectProperty->HasAnyPropertyFlags(CPF_PersistentInstance | CPF_InstancedReference)) { const UObject* Object = nullptr; if (CurrentPropertyHandle->GetValue(Object) == FPropertyAccess::Success) { if (Object) { Segment.SetInstanceStruct(Object->GetClass(), EPropertyBindingPropertyAccessType::ObjectInstance); } } } } else if (const FStructProperty* StructProperty = CastField(Property)) { void* Address = nullptr; if (CurrentPropertyHandle->GetValueData(Address) == FPropertyAccess::Success) { if (Address) { if (StructProperty->Struct == TBaseStructure::Get()) { FInstancedStruct& Struct = *static_cast(Address); Segment.SetInstanceStruct(Struct.GetScriptStruct(), EPropertyBindingPropertyAccessType::StructInstance); } else if (StructProperty->Struct == TBaseStructure::Get()) { FSharedStruct& Struct = *static_cast(Address); Segment.SetInstanceStruct(Struct.GetScriptStruct(), EPropertyBindingPropertyAccessType::SharedStruct); } else if (StructProperty->Struct == TBaseStructure::Get()) { FInstancedStructContainer& StructContainer = *static_cast(Address); check(StructContainer.IsValidIndex(Segment.GetArrayIndex())); const FConstStructView StructView = StructContainer[Segment.GetArrayIndex()]; Segment.SetInstanceStruct(StructView.GetScriptStruct(), EPropertyBindingPropertyAccessType::StructInstanceContainer); } } } } // Array access is represented as: "Array, PropertyInArray[Index]", we're traversing from leaf to root, skip the node without index. // Advancing the node before ID test, since the array is on the instance data, the ID will be on the Array node. if (Segment.GetArrayIndex() != INDEX_NONE) { TSharedPtr ParentPropertyHandle = CurrentPropertyHandle->GetParentHandle(); if (ParentPropertyHandle.IsValid()) { const FProperty* ParentProperty = ParentPropertyHandle->GetProperty(); if (ParentProperty && ParentProperty->IsA() && Property->GetFName() == ParentProperty->GetFName()) { CurrentPropertyHandle = ParentPropertyHandle; } } } // Bindable property must have node ID if (const FString* IDString = CurrentPropertyHandle->GetInstanceMetaData(MetaDataStructIDName)) { LexFromString(StructID, **IDString); BindablePropertyHandle = CurrentPropertyHandle; break; } } CurrentPropertyHandle = CurrentPropertyHandle->GetParentHandle(); } if (!StructID.IsValid() && InFallbackStructID.IsValid()) { StructID = InFallbackStructID; } if (StructID.IsValid()) { OutPath = FPropertyBindingPath(StructID, PathSegments); } return BindablePropertyHandle; } bool HasMetaData(FName MetaData, const TSharedRef& InPropertyHandle) { TSharedPtr CurrentPropertyHandle = InPropertyHandle; while (CurrentPropertyHandle.IsValid()) { if (const FProperty* MetaDataProperty = CurrentPropertyHandle->GetMetaDataProperty()) { if (const FStructProperty* StructProperty = CastField(MetaDataProperty)) { if (StructProperty->Struct->HasMetaData(MetaData)) { return true; } } else if (const FObjectPropertyBase* ObjectProperty = CastField(MetaDataProperty)) { if (ObjectProperty->PropertyClass->HasMetaData(MetaData)) { return true; } } } if (CurrentPropertyHandle->HasMetaData(MetaData)) { return true; } CurrentPropertyHandle = CurrentPropertyHandle->GetParentHandle(); } return false; } FMenuSectionHelper::FMenuSectionHelper(FMenuBuilder& MenuBuilder): MenuBuilder(MenuBuilder) { } FMenuSectionHelper::~FMenuSectionHelper() { if (bSectionOpened) { MenuBuilder.EndSection(); } } void FMenuSectionHelper::SetSection(const FText& InSection) { if (!InSection.IdenticalTo(CurrentSection)) { if (bSectionOpened) { MenuBuilder.EndSection(); } CurrentSection = InSection; MenuBuilder.BeginSection(NAME_None, CurrentSection); bSectionOpened = true; } } FText GetPropertyTypeText(const FProperty* Property) { FEdGraphPinType PinType; const UEdGraphSchema_K2* Schema = GetDefault(); Schema->ConvertPropertyToPinType(Property, PinType); const FName PinSubCategory = PinType.PinSubCategory; const UObject* PinSubCategoryObject = PinType.PinSubCategoryObject.Get(); if (PinSubCategory != UEdGraphSchema_K2::PSC_Bitmask && PinSubCategoryObject) { if (const UField* Field = Cast(PinSubCategoryObject)) { return Field->GetDisplayNameText(); } return FText::FromString(PinSubCategoryObject->GetName()); } return UEdGraphSchema_K2::GetCategoryText(PinType.PinCategory, NAME_None, true); } TSharedRef MakeBindingPropertyInfoWidget(const FText& InDisplayText, const FEdGraphPinType& InPinType) { const FSlateBrush* Icon = FBlueprintEditorUtils::GetIconFromPin(InPinType, /*bIsLarge*/true); const FLinearColor IconColor = GetDefault()->GetPinTypeColor(InPinType); return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SSpacer) .Size(FVector2D(18.0f, 0.0f)) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(1.0f, 0.0f) [ SNew(SImage) .Image(Icon) .ColorAndOpacity(IconColor) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4.0f, 0.0f) [ SNew(STextBlock) .Text(InDisplayText) ]; } FCachedBindingData::FCachedBindingData(IPropertyBindingBindingCollectionOwner* InPropertyBindingsOwner , const FPropertyBindingPath& InTargetPath , const TSharedPtr& InPropertyHandle , const TConstArrayView> InAccessibleStructs ) : WeakBindingsOwner(InPropertyBindingsOwner) , TargetPath(InTargetPath) , PropertyHandle(InPropertyHandle) , AccessibleStructs(InAccessibleStructs) { } void FCachedBindingData::AddBinding(TConstArrayView InBindingChain) { if (InBindingChain.IsEmpty()) { return; } if (!TargetPath.GetStructID().IsValid()) { return; } IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return; } FPropertyBindingBindingCollection* BindingsCollection = BindingOwner->GetEditorPropertyBindings(); if (BindingsCollection == nullptr) { return; } FScopedTransaction Transaction(LOCTEXT("BindingData_AddBinding", "Add Binding")); // First item in the binding chain is the index in AccessibleStructs. const int32 SourceStructIndex = InBindingChain[0].ArrayIndex; TConstStructView BindableStruct = GetBindableStructDescriptor(SourceStructIndex); const FGuid StructID = BindableStruct.Get().ID; // Remove struct index. const TConstArrayView SourceBindingChain(InBindingChain.begin() + 1, InBindingChain.Num() - 1); // GetBindingDataViewByID can fail but we still need to call AddBindingInternal FPropertyBindingDataView DataView; BindingOwner->GetBindingDataViewByID(StructID, DataView); // If SourceBindingChain is empty at this stage, it means that the binding points to the source struct itself. FPropertyBindingPath SourcePath; MakeStructPropertyPathFromBindingChain(StructID, SourceBindingChain, DataView, SourcePath); UObject* BindingOwnerObject = WeakBindingsOwner.GetObject(); BindingOwnerObject->Modify(); // Allow derived classes to handle the bindings const bool bBindingHandled = AddBindingInternal(BindableStruct, SourcePath, TargetPath); if (bBindingHandled == false) { BindingsCollection->AddBinding(SourcePath, TargetPath); } UpdateData(); BindingOwner->OnPropertyBindingChanged(SourcePath, TargetPath); } bool FCachedBindingData::HasBinding(const FPropertyBindingBindingCollection::ESearchMode SearchMode) const { IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return false; } const FPropertyBindingBindingCollection* EditorBindings = BindingOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return false; } return EditorBindings->HasBinding(TargetPath, SearchMode); } const FPropertyBindingBinding* FCachedBindingData::FindBinding(FPropertyBindingBindingCollection::ESearchMode SearchMode) const { IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return nullptr; } const FPropertyBindingBindingCollection* EditorBindings = BindingOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return nullptr; } return EditorBindings->FindBinding(TargetPath, SearchMode); } void FCachedBindingData::RemoveBinding(const FPropertyBindingBindingCollection::ESearchMode RemoveMode) { IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return; } FPropertyBindingBindingCollection* EditorBindings = BindingOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return; } FScopedTransaction Transaction(LOCTEXT("BindingData_RemoveBinding", "Remove Binding")); UObject* OwnerObject = WeakBindingsOwner.GetObject(); OwnerObject->Modify(); EditorBindings->RemoveBindings(TargetPath, RemoveMode); UpdateData(); const FPropertyBindingPath SourcePath; // Null path BindingOwner->OnPropertyBindingChanged(SourcePath, TargetPath); } bool FCachedBindingData::GetPinTypeAndIconForProperty(const FProperty& InProperty, FPropertyBindingDataView InTargetDataView , FEdGraphPinType& OutPinType, FName& OutIconName) const { return false; } bool FCachedBindingData::GetPinTypeAndIconForProperty(const FProperty& InProperty, FPropertyBindingDataView InTargetDataView , FEdGraphPinType& OutPinType, const FSlateBrush*& OutIconBrush) const { return false; } bool FCachedBindingData::IsPropertyReference(const FProperty& InProperty) { return false; } void FCachedBindingData::UpdateSourcePropertyPath(TConstStructView InDescriptor, const FPropertyBindingPath& InSourcePath, FString& OutString) { } void FCachedBindingData::UpdatePropertyReferenceTooltip(const FProperty& InProperty, FTextBuilder& InOutTextBuilder) const { } void FCachedBindingData::UpdateData() { SourceStructName = FText::GetEmpty(); FormatableText = FText::GetEmpty(); FormatableTooltipText = FText::GetEmpty(); Color = FLinearColor::White; Image = nullptr; bIsCachedDataValid = false; if (!PropertyHandle.IsValid()) { return; } const FProperty* Property = PropertyHandle->GetProperty(); if (Property == nullptr) { return; } IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return; } FPropertyBindingBindingCollection* EditorBindings = BindingOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return; } const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); FEdGraphPinType PinType; const FSlateBrush* Icon = nullptr; Schema->ConvertPropertyToPinType(Property, PinType); FPropertyBindingDataView TargetDataView; BindingOwner->GetBindingDataViewByID(TargetPath.GetStructID(), TargetDataView); const bool bIsPropertyReference = IsPropertyReference(*Property); const bool bFound = GetPinTypeAndIconForProperty(*Property, TargetDataView, PinType, Icon); if (!bFound) { Schema->ConvertPropertyToPinType(Property, PinType); Icon = FAppStyle::GetBrush("Kismet.Tabs.Variables"); } FTextBuilder TooltipBuilder; if (const FPropertyBindingBinding* CurrentBinding = EditorBindings->FindBinding(TargetPath)) { const FPropertyBindingPath& SourcePath = CurrentBinding->GetSourcePath(); FString SourcePropertyPathAsString = SourcePath.ToString(); // If source is a bound PropertyFunction, it will not be present in AccessibleStructs thus it has to be accessed through bindings owner. TInstancedStruct SourceDesc; if (BindingOwner->GetBindableStructByID(SourcePath.GetStructID(), SourceDesc)) { // Allow derived class to provide a different source path UpdateSourcePropertyPath(SourceDesc, SourcePath, SourcePropertyPathAsString); // Check that the binding is valid. bool bIsValidBinding = false; FPropertyBindingDataView SourceDataView; const FProperty* SourceLeafProperty = nullptr; const UStruct* SourceStruct = nullptr; if (BindingOwner->GetBindingDataViewByID(SourcePath.GetStructID(), SourceDataView) && TargetDataView.IsValid()) { TArray SourceIndirections; TArray TargetIndirections; // Resolve source and target properties. // Source path can be empty, when the binding binds directly to a context struct/class. // Target path must always point to a valid property (at least one indirection). if (SourcePath.ResolveIndirectionsWithValue(SourceDataView, SourceIndirections, /*error*/nullptr, /*bHandleRedirects*/true) && TargetPath.ResolveIndirectionsWithValue(TargetDataView, TargetIndirections, /*error*/nullptr, /*bHandleRedirects*/true) && !TargetIndirections.IsEmpty()) { const FPropertyBindingPathIndirection TargetLeafIndirection = TargetIndirections.Last(); if (SourceIndirections.Num() > 0) { // Binding to a source property. const FPropertyBindingPathIndirection SourceLeafIndirection = SourceIndirections.Last(); SourceLeafProperty = SourceLeafIndirection.GetProperty(); bIsValidBinding = ArePropertiesCompatible(SourceLeafProperty, TargetLeafIndirection.GetProperty(), SourceLeafIndirection.GetPropertyAddress(), TargetLeafIndirection.GetPropertyAddress()); } else { // Binding to a source context struct. SourceStruct = SourceDataView.GetStruct(); bIsValidBinding = ArePropertyAndContextStructCompatible(SourceStruct, TargetLeafIndirection.GetProperty()); } } } FormatableText = FText::FormatNamed(LOCTEXT("ValidSourcePath", "{SourceStruct}{PropertyPath}") , TEXT("PropertyPath") , SourcePropertyPathAsString.IsEmpty() ? FText() : FText::FromString(TEXT(".") + SourcePropertyPathAsString)); SourceStructName = FText::FromString(SourceDesc.Get().Name.ToString()); if (bIsValidBinding) { if (SourcePropertyPathAsString.IsEmpty()) { if (CurrentBinding->GetPropertyFunctionNode().IsValid()) { TooltipBuilder.AppendLine(LOCTEXT("ExistingBindingToFunctionTooltip", "Property is bound to function {SourceStruct}.")); } else { TooltipBuilder.AppendLine(LOCTEXT("ExistingBindingTooltip", "Property is bound to {SourceStruct}.")); } } else { if (CurrentBinding->GetPropertyFunctionNode().IsValid()) { TooltipBuilder.AppendLineFormat(LOCTEXT("ExistingBindingToFunctionWithPropertyTooltip", "Property is bound to function {SourceStruct} property {PropertyPath}."), { {TEXT("PropertyPath"), FText::FromString(SourcePropertyPathAsString)} }); } else { TooltipBuilder.AppendLineFormat(LOCTEXT("ExistingBindingWithPropertyTooltip", "Property is bound to {SourceStruct} property {PropertyPath}."), { {TEXT("PropertyPath"), FText::FromString(SourcePropertyPathAsString)} }); } } // Update pin type with source property so property reference that can bind // to multiple types display the bound one. if (bIsPropertyReference) { Schema->ConvertPropertyToPinType(SourceLeafProperty, PinType); } Image = Icon; Color = Schema->GetPinTypeColor(PinType); bIsCachedDataValid = true; } else { FText SourceType; if (SourceLeafProperty) { SourceType = GetPropertyTypeText(SourceLeafProperty); } else if (SourceStruct) { SourceType = SourceStruct->GetDisplayNameText(); } FText TargetType = GetPropertyTypeText(Property); if (SourcePath.IsPathEmpty()) { TooltipBuilder.AppendLineFormat(LOCTEXT("MismatchingBindingTooltip", "Property is bound to {SourceStruct}, but binding source type '{SourceType}' does not match property type '{TargetType}'."), { {TEXT("SourceType"), SourceType}, {TEXT("TargetType"), TargetType} }); } else { TooltipBuilder.AppendLineFormat(LOCTEXT("MismatchingBindingTooltipWithProperty", "Property is bound to {SourceStruct} property {PropertyPath}, but binding source type '{SourceType}' does not match property type '{TargetType}'."), { {TEXT("PropertyPath"), FText::FromString(SourcePropertyPathAsString)}, {TEXT("SourceType"), SourceType}, {TEXT("TargetType"), TargetType} }); } Image = FCoreStyle::Get().GetBrush("Icons.ErrorWithColor"); Color = FLinearColor::White; } } else { // Missing source FormatableText = FText::Format(LOCTEXT("MissingSource", "???.{0}"), FText::FromString(SourcePropertyPathAsString)); TooltipBuilder.AppendLineFormat(LOCTEXT("MissingBindingTooltip", "Missing binding source for property path '{0}'."), FText::FromString(SourcePropertyPathAsString)); Image = FCoreStyle::Get().GetBrush("Icons.ErrorWithColor"); Color = FLinearColor::White; } CachedSourcePath = SourcePath; } else { // No bindings FormatableText = FText::GetEmpty(); TooltipBuilder.AppendLineFormat(LOCTEXT("BindTooltip", "Bind {0} to value from another property."), UE::PropertyBinding::GetPropertyTypeText(Property)); Image = Icon; Color = Schema->GetPinTypeColor(PinType); bIsCachedDataValid = true; CachedSourcePath.Reset(); } if (bIsPropertyReference) { UpdatePropertyReferenceTooltip(*Property, TooltipBuilder); } FormatableTooltipText = TooltipBuilder.ToText(); bIsDataCached = true; } bool FCachedBindingData::CanBindToContextStruct(const UStruct* InStruct, int32 InStructIndex) { ConditionallyUpdateData(); return CanBindToContextStructInternal(InStruct, InStructIndex); } void FCachedBindingData::GetSourceDataViewForNewBinding(TNotNull InBindingsOwner, TConstStructView InDescriptor , FPropertyBindingDataView& OutSourceDataView) { InBindingsOwner->GetBindingDataViewByID(InDescriptor.Get().ID, OutSourceDataView); } bool FCachedBindingData::CanBindToProperty(const FProperty* SourceProperty, TConstArrayView InBindingChain) { ConditionallyUpdateData(); // Special case for binding widget calling OnCanBindProperty with Args.Property (i.e. self). if (PropertyHandle->GetProperty() == SourceProperty) { return true; } IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return false; } const int32 SourceStructIndex = InBindingChain[0].ArrayIndex; const TConstStructView BindableStruct = GetBindableStructDescriptor(SourceStructIndex); FPropertyBindingDataView SourceDataView; GetSourceDataViewForNewBinding(BindingOwner, BindableStruct, SourceDataView); FPropertyBindingPath SourcePath; MakeStructPropertyPathFromBindingChain(BindableStruct.Get().ID, InBindingChain, SourceDataView, SourcePath); TArray SourceIndirections; void* TargetValueAddress = nullptr; if (PropertyHandle->GetValueData(TargetValueAddress) == FPropertyAccess::Success && SourcePath.ResolveIndirectionsWithValue(SourceDataView, SourceIndirections)) { return ArePropertiesCompatible(SourceProperty, PropertyHandle->GetProperty(), SourceIndirections.Last().GetPropertyAddress(), TargetValueAddress); } return false; } bool FCachedBindingData::CanAcceptPropertyOrChildrenInternal(const FProperty& InProperty, TConstArrayView InBindingChain) { return true; } bool FCachedBindingData::AddBindingInternal(TConstStructView InDescriptor, FPropertyBindingPath& InOutSourcePath , const FPropertyBindingPath& InTargetPath) { // We will use the default behavior return false; } bool FCachedBindingData::CanAcceptPropertyOrChildren(const FProperty* SourceProperty, const TConstArrayView InBindingChain) { if (!SourceProperty) { return false; } ConditionallyUpdateData(); if (!PropertyHandle.IsValid() || PropertyHandle->GetProperty() == nullptr) { return false; } if (CanAcceptPropertyOrChildrenInternal(*SourceProperty, InBindingChain)) { return IsPropertyBindable(*SourceProperty); } return false; } void FCachedBindingData::PromoteToParameter(const FName InPropertyName, TConstStructView InStructDesc, const TSharedPtr InPropertyInfoOverride) { if (!TargetPath.GetStructID().IsValid()) { return; } IPropertyBindingBindingCollectionOwner* BindingsOwner = WeakBindingsOwner.Get(); if (!BindingsOwner) { return; } const FProperty* Property = PropertyHandle->GetProperty(); if (!Property) { return; } const FProperty* TargetProperty = nullptr; const void* TargetContainerAddress = nullptr; FPropertyBindingDataView TargetDataView; if (BindingsOwner->GetBindingDataViewByID(TargetPath.GetStructID(), TargetDataView) && TargetDataView.IsValid()) { TArray TargetIndirections; if (ensure(TargetPath.ResolveIndirectionsWithValue(TargetDataView, TargetIndirections))) { const FPropertyBindingPathIndirection& LastIndirection = TargetIndirections.Last(); TargetProperty = LastIndirection.GetProperty(); TargetContainerAddress = LastIndirection.GetContainerAddress(); } } FPropertyBindingBindingCollection* EditorBindings = BindingsOwner->GetEditorPropertyBindings(); if (!EditorBindings) { return; } const FGuid StructID = InStructDesc.Get().ID; const FScopedTransaction Transaction(LOCTEXT("PromoteToParameter", "Promote to Parameter")); TArray> PropertyCreationDescs; { FPropertyCreationDescriptor& PropertyCreationDesc = PropertyCreationDescs.AddDefaulted_GetRef(); if (InPropertyInfoOverride) { PropertyCreationDesc.PropertyDesc.Name = InPropertyName; StructUtils::SetPropertyDescFromPin(PropertyCreationDesc.PropertyDesc, InPropertyInfoOverride->PinType); } else { PropertyCreationDesc.PropertyDesc = FPropertyBagPropertyDesc(InPropertyName, Property); } BindingsOwner->OnPromotingToParameter(PropertyCreationDesc.PropertyDesc); // Set the Property & Container Address to copy if (TargetProperty && TargetContainerAddress) { PropertyCreationDesc.SourceProperty = TargetProperty; PropertyCreationDesc.SourceContainerAddress = TargetContainerAddress; } } Cast(BindingsOwner)->Modify(); BindingsOwner->CreateParametersForStruct(StructID, /*InOut*/PropertyCreationDescs); // Use the name in PropertyDescs, as it might contain a different name than the desired InPropertyName (for uniqueness) const FPropertyBindingPath SourcePath(StructID, PropertyCreationDescs[0].PropertyDesc.Name); EditorBindings->AddBinding(SourcePath, TargetPath); UpdateData(); BindingsOwner->OnPropertyBindingChanged(SourcePath, TargetPath); } void FCachedBindingData::AddPropertyInfoOverride(const FProperty& InProperty, TArray>& OutPropertyInfoOverrides) const { } bool FCachedBindingData::CanCreateParameter(const FPropertyBindingBindableStructDescriptor& InStructDesc, TArray>& OutPropertyInfoOverrides) const { const FProperty* Property = PropertyHandle->GetProperty(); if (Property == nullptr) { return false; } // Is the type supported by the property bag const FPropertyBagPropertyDesc Desc = FPropertyBagPropertyDesc(Property->GetFName(), Property); if (Desc.ValueType == EPropertyBagPropertyType::None) { return false; } IPropertyBindingBindingCollectionOwner* BindingsOwner = WeakBindingsOwner.Get(); if (BindingsOwner == nullptr) { return false; } const FPropertyBindingBindingCollection* EditorBindings = BindingsOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return false; } // Allow classes implementing IPropertyBindingBindingCollectionOwner to block parameter creation for that struct if (!BindingsOwner->CanCreateParameter(InStructDesc.ID)) { return false; } // Allow derived classes to push overrides AddPropertyInfoOverride(*Property, OutPropertyInfoOverrides); return true; } bool FCachedBindingData::ArePropertyAndContextStructCompatible(const UStruct* SourceStruct, const FProperty* TargetProperty) const { if (const FStructProperty* TargetStructProperty = CastField(TargetProperty)) { return TargetStructProperty->Struct == SourceStruct; } if (const FObjectProperty* TargetObjectProperty = CastField(TargetProperty)) { return SourceStruct != nullptr && SourceStruct->IsChildOf(TargetObjectProperty->PropertyClass); } return false; } bool FCachedBindingData::ArePropertiesCompatible(const FProperty* SourceProperty, const FProperty* TargetProperty, const void* SourcePropertyValue, const void* TargetPropertyValue) const { bool bAreCompatible = false; if (DeterminePropertiesCompatibilityInternal(SourceProperty, TargetProperty, SourcePropertyValue, TargetPropertyValue, bAreCompatible)) { return bAreCompatible; } // Note: We support type promotion here return GetPropertyCompatibility(SourceProperty, TargetProperty) != EPropertyCompatibility::Incompatible; } UStruct* FCachedBindingData::ResolveIndirection(TConstArrayView InBindingChain) { const IPropertyBindingBindingCollectionOwner* PropertyBindingsOwner = WeakBindingsOwner.Get(); if (!PropertyBindingsOwner) { return nullptr; } const int32 SourceStructIndex = InBindingChain[0].ArrayIndex; TArray SourceBindingChain(InBindingChain); SourceBindingChain.RemoveAt(0); FPropertyBindingDataView DataView; if (PropertyBindingsOwner->GetBindingDataViewByID(GetBindableStructDescriptor(SourceStructIndex).Get().ID, DataView)) { return ResolveLeafValueStructType(DataView, InBindingChain); } return nullptr; } bool FCachedBindingData::CanBindToContextStructInternal(const UStruct* InStruct, int32 InStructIndex) { return ArePropertyAndContextStructCompatible(InStruct, PropertyHandle->GetProperty()); } bool FCachedBindingData::GetPropertyFunctionText(FConstStructView InPropertyFunctionStructView, FText& OutText) const { return false; } bool FCachedBindingData::GetPropertyFunctionTooltipText(FConstStructView InPropertyFunctionStructView, FText& OutText) const { return false; } bool FCachedBindingData::GetPropertyFunctionIconColor(FConstStructView InPropertyFunctionStructView, FLinearColor& OutColor) const { return false; } bool FCachedBindingData::GetPropertyFunctionImage(FConstStructView InPropertyFunctionStructView, const FSlateBrush*& OutImage) const { return false; } bool FCachedBindingData::DeterminePropertiesCompatibilityInternal(const FProperty* InSourceProperty, const FProperty* InTargetProperty , const void* InSourcePropertyValue, const void* InTargetPropertyValue, bool& bOutAreCompatible) const { return false; } bool ExecuteOnFunctionStructView(IPropertyBindingBindingCollectionOwner* InBindingOwner, const FPropertyBindingPath& InTargetPath, TFunctionRef InFunction) { if (InBindingOwner) { if (const FPropertyBindingBindingCollection* EditorBindings = InBindingOwner->GetEditorPropertyBindings()) { if (const FPropertyBindingBinding* CurrentBinding = EditorBindings->FindBinding(InTargetPath, FPropertyBindingBindingCollection::ESearchMode::Exact)) { const FConstStructView PropertyFunctionStructView = CurrentBinding->GetPropertyFunctionNode(); if (PropertyFunctionStructView.IsValid()) { return InFunction(PropertyFunctionStructView); } } } } return false; } FText FCachedBindingData::GetText() { ConditionallyUpdateData(); if (bIsCachedDataValid) { // Bound PropertyFunction is allowed to override its display name. FText CustomText; if (ExecuteOnFunctionStructView(WeakBindingsOwner.Get(), TargetPath, [&CustomText, this](FConstStructView StructView) { return GetPropertyFunctionText(StructView, CustomText); })) { return CustomText; } } return FText::FormatNamed(FormatableText, TEXT("SourceStruct"), SourceStructName); } FText FCachedBindingData::GetTooltipText() { ConditionallyUpdateData(); if (bIsCachedDataValid) { // If the source property is a PropertyFunction and it overrides its display name, it's been used in the tooltip text. FText CustomText; if (ExecuteOnFunctionStructView(WeakBindingsOwner.Get(), TargetPath, [&CustomText, this](FConstStructView StructView) { return GetPropertyFunctionTooltipText(StructView, CustomText); })) { return CustomText; } } return FText::FormatNamed(FormatableTooltipText, TEXT("SourceStruct"), SourceStructName); } FLinearColor FCachedBindingData::GetColor() { ConditionallyUpdateData(); if (bIsCachedDataValid) { // Bound PropertyFunction is allowed to override its icon color if the binding leads directly into it's single output property. if (CachedSourcePath.NumSegments() == 1) { FLinearColor CustomColor; if (ExecuteOnFunctionStructView(WeakBindingsOwner.Get(), TargetPath, [&CustomColor, this](FConstStructView StructView) { return GetPropertyFunctionIconColor(StructView, CustomColor); })) { return CustomColor; } } } return Color; } const FSlateBrush* FCachedBindingData::GetImage() { ConditionallyUpdateData(); if (bIsCachedDataValid) { // Bound PropertyFunction is allowed to override its icon. const FSlateBrush* CustomImage; if (ExecuteOnFunctionStructView(WeakBindingsOwner.Get(), TargetPath, [&CustomImage, this](FConstStructView StructView) { return GetPropertyFunctionImage(StructView, CustomImage); })) { return CustomImage; } } return Image; } void FCachedBindingData::ConditionallyUpdateData() { IPropertyBindingBindingCollectionOwner* BindingOwner = WeakBindingsOwner.Get(); if (BindingOwner == nullptr) { return; } const FPropertyBindingBindingCollection* EditorBindings = BindingOwner->GetEditorPropertyBindings(); if (EditorBindings == nullptr) { return; } const FPropertyBindingPath* CurrentSourcePath = EditorBindings->GetBindingSource(TargetPath); bool bPathsIdentical = false; if (CurrentSourcePath) { bPathsIdentical = CachedSourcePath == *CurrentSourcePath; } else { bPathsIdentical = CachedSourcePath.IsPathEmpty(); } if (!bIsDataCached || !bPathsIdentical) { UpdateData(); } } bool IsPropertyBindable(const FProperty& Property) { const bool bIsUserEditable = Property.HasAnyPropertyFlags(CPF_Edit); if (!bIsUserEditable) { UE_LOG(LogPropertyBindingUtils, Verbose, TEXT("Property %s is not bindable because it's not user-settable in the editor"), *Property.GetName()); return false; } const bool bPrivateOrProtected = !Property.HasAnyPropertyFlags(CPF_NativeAccessSpecifierPrivate | CPF_NativeAccessSpecifierProtected); const bool bPrivateButBlueprintAccessible = Property.GetBoolMetaData(FBlueprintMetadata::MD_AllowPrivateAccess); if (!bPrivateOrProtected && !bPrivateButBlueprintAccessible) { UE_LOG(LogPropertyBindingUtils, Verbose, TEXT("Property %s is not bindable because it's either private or protected and not private-accessible to blueprints"), *Property.GetName()); return false; } return true; } } // UE::PropertyBinding bool FPropertyBindingExtension::IsPropertyExtendable(const UClass* InObjectClass, const IPropertyHandle& InPropertyHandle) const { const FProperty* Property = InPropertyHandle.GetProperty(); if (Property == nullptr || Property->HasAnyPropertyFlags(GetDisallowedPropertyFlags())) { return false; } // Does the container or property support bindings if (UE::PropertyBinding::HasMetaData(UE::PropertyBinding::MetaDataNoBindingName, InPropertyHandle.AsShared())) { return false; } TArray OuterObjects; InPropertyHandle.GetOuterObjects(OuterObjects); if (OuterObjects.Num() == 1) { // Only allow to bind when one object is selected. if (const IPropertyBindingBindingCollectionOwner* BindingsOwner = UE::PropertyBinding::FindBindingsOwner(OuterObjects[0])) { FPropertyBindingPath TargetPath; // Figure out the structs we're editing, and property path relative to current property. const FGuid FallbackStructID = BindingsOwner->GetFallbackStructID(); const TSharedPtr SharedHandle = UE::PropertyBinding::MakeStructPropertyPathFromPropertyHandle( InPropertyHandle.AsShared(), TargetPath, FallbackStructID); if (!TargetPath.GetStructID().IsValid()) { return false; } else if (const IPropertyHandle* BindablePropertyHandle = SharedHandle.Get()) { if (!CanBindToProperty(TargetPath, *BindablePropertyHandle)) { return false; } } } } return true; } void FPropertyBindingExtension::ExtendWidgetRow(FDetailWidgetRow& InWidgetRow, const IDetailLayoutBuilder& InDetailBuilder, const UClass* InObjectClass, TSharedPtr InPropertyHandle) { if (!IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) { return; } using namespace UE::PropertyBinding; IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); IPropertyBindingBindingCollectionOwner* BindingsOwner = nullptr; // Array of structs we can bind to. TArray BindingContextStructs; TArray> AccessibleStructs; // The struct and property where we're binding. FPropertyBindingPath TargetPath; TArray OuterObjects; InPropertyHandle->GetOuterObjects(OuterObjects); if (OuterObjects.Num() == 1) { // Only allow to bind when one object is selected. BindingsOwner = FindBindingsOwner(OuterObjects[0]); if (BindingsOwner) { const FGuid FallbackStructID = BindingsOwner->GetFallbackStructID(); // Figure out the structs we're editing, and property path relative to current property. MakeStructPropertyPathFromPropertyHandle(InPropertyHandle, TargetPath, FallbackStructID); BindingsOwner->GetBindableStructs(TargetPath.GetStructID(), AccessibleStructs); BindingsOwner->AppendBindablePropertyFunctionStructs(AccessibleStructs); TMap SectionNames; for (TInstancedStruct InstancedStruct : AccessibleStructs) { const FPropertyBindingBindableStructDescriptor& StructDesc = InstancedStruct.Get(); const UStruct* Struct = StructDesc.Struct; FBindingContextStruct& ContextStruct = BindingContextStructs.AddDefaulted_GetRef(); ContextStruct.DisplayText = FText::FromString(StructDesc.Name.ToString()); ContextStruct.Struct = const_cast(Struct); ContextStruct.Category = StructDesc.Category; UpdateContextStruct(InstancedStruct, ContextStruct, SectionNames); } } } TSharedPtr CachedBindingData = CreateCachedBindingData(BindingsOwner, TargetPath, InPropertyHandle, AccessibleStructs); // Wrap value widget auto IsValueVisible = TAttribute::Create([CachedBindingData]() -> EVisibility { return CachedBindingData->HasBinding(FPropertyBindingBindingCollection::ESearchMode::Exact) ? EVisibility::Collapsed : EVisibility::Visible; }); TSharedPtr ValueWidget = InWidgetRow.ValueContent().Widget; InWidgetRow.ValueContent() [ SNew(SBox) .Visibility(IsValueVisible) [ ValueWidget.ToSharedRef() ] ]; FPropertyBindingWidgetArgs Args; Args.Property = InPropertyHandle->GetProperty(); Args.OnCanBindPropertyWithBindingChain = FOnCanBindPropertyWithBindingChain::CreateLambda([CachedBindingData](FProperty* InProperty, TConstArrayView InBindingChain) { return CachedBindingData->CanBindToProperty(InProperty, InBindingChain); }); Args.OnCanBindToContextStructWithIndex = FOnCanBindToContextStructWithIndex::CreateLambda([CachedBindingData](const UStruct* InStruct, int32 InStructIndex) { return CachedBindingData->CanBindToContextStruct(InStruct, InStructIndex); }); Args.OnCanAcceptPropertyOrChildrenWithBindingChain = FOnCanAcceptPropertyOrChildrenWithBindingChain::CreateLambda([CachedBindingData](FProperty* InProperty, TConstArrayView InBindingChain) { return CachedBindingData->CanAcceptPropertyOrChildren(InProperty, InBindingChain); }); Args.OnCanBindToClass = FOnCanBindToClass::CreateLambda([](UClass* InClass) { return true; }); Args.OnAddBinding = FOnAddBinding::CreateLambda([CachedBindingData, &InDetailBuilder](FName InPropertyName, TConstArrayView InBindingChain) { CachedBindingData->AddBinding(InBindingChain); InDetailBuilder.GetPropertyUtilities()->RequestForceRefresh(); }); Args.OnRemoveBinding = FOnRemoveBinding::CreateLambda([CachedBindingData, &InDetailBuilder](FName InPropertyName) { CachedBindingData->RemoveBinding(FPropertyBindingBindingCollection::ESearchMode::Exact); InDetailBuilder.GetPropertyUtilities()->RequestForceRefresh(); }); Args.OnCanRemoveBinding = FOnCanRemoveBinding::CreateLambda([CachedBindingData](FName InPropertyName) { return CachedBindingData->HasBinding(FPropertyBindingBindingCollection::ESearchMode::Exact); }); Args.CurrentBindingText = MakeAttributeLambda([CachedBindingData]() { return CachedBindingData->GetText(); }); Args.CurrentBindingToolTipText = MakeAttributeLambda([CachedBindingData]() { return CachedBindingData->GetTooltipText(); }); Args.CurrentBindingImage = MakeAttributeLambda([CachedBindingData]() -> const FSlateBrush* { return CachedBindingData->GetImage(); }); Args.CurrentBindingColor = MakeAttributeLambda([CachedBindingData]() -> FLinearColor { return CachedBindingData->GetColor(); }); if (BindingsOwner) { Args.OnResolveIndirection = FOnResolveIndirection::CreateLambda([CachedBindingData](TConstArrayView InBindingChain) { return CachedBindingData->ResolveIndirection(InBindingChain); }); } Args.BindButtonStyle = &FAppStyle::Get().GetWidgetStyle("HoverHintOnly"); Args.bAllowNewBindings = false; Args.bAllowArrayElementBindings = CanBindToArrayElements(); Args.bAllowUObjectFunctions = false; if (CanPromoteToParameter(InPropertyHandle)) { auto PromoteToParameterAndRefresh = [CachedBindingData, &InDetailBuilder](FName InPropertyName, TConstStructView InStructDesc, TSharedPtr InPropertyInfoOverride) { CachedBindingData->PromoteToParameter(InPropertyName, InStructDesc, InPropertyInfoOverride); InDetailBuilder.GetPropertyUtilities()->RequestForceRefresh(); }; Args.MenuExtender = MakeShared(); Args.MenuExtender->AddMenuExtension( TEXT("BindingActions"), EExtensionHook::After, /*CommandList*/ nullptr, FMenuExtensionDelegate::CreateLambda([CachedBindingData, AccessibleStructs = MoveTemp(AccessibleStructs), InPropertyHandle, PromoteToParameterAndRefresh](FMenuBuilder& MenuBuilder) { const FProperty* Property = InPropertyHandle->GetProperty(); check(Property); const FName PropertyName = Property->GetFName(); const TSharedRef CachedBindingDataRef = CachedBindingData.ToSharedRef(); auto AddMenuEntry = [PropertyName, PromoteToParameterAndRefresh](FMenuBuilder& InMenuBuilder, const FPropertyBindingBindableStructDescriptor& ContextStruct, const TSharedRef& InCachedBindingData, FMenuSectionHelper& InSectionHelper) { TArray> PropertyInfoOverrides; if (InCachedBindingData->CanCreateParameter(ContextStruct, /*out*/PropertyInfoOverrides)) { const FString Section = ContextStruct.GetSection(); if (!Section.IsEmpty()) { InSectionHelper.SetSection(FText::FromString(Section)); } if (PropertyInfoOverrides.IsEmpty()) { InMenuBuilder.AddMenuEntry(FExecuteAction::CreateLambda( [PromoteToParameterAndRefresh, PropertyName, ContextStruct]() { PromoteToParameterAndRefresh(PropertyName, TConstStructView(ContextStruct), nullptr); }), MakeContextStructWidget(ContextStruct)); } else { InMenuBuilder.AddSubMenu(MakeContextStructWidget(ContextStruct), FNewMenuDelegate::CreateLambda([InCachedBindingData, PropertyName, ContextStruct, PropertyInfoOverrides = MoveTemp(PropertyInfoOverrides), PromoteToParameterAndRefresh](FMenuBuilder& InSubMenuBuilder) { FMenuSectionHelper SectionHelper(InSubMenuBuilder); SectionHelper.SetSection(LOCTEXT("RefTypeParams", "Reference Types")); for (const TSharedPtr& PropertyInfoOverride : PropertyInfoOverrides) { InSubMenuBuilder.AddMenuEntry( FExecuteAction::CreateLambda( [PromoteToParameterAndRefresh, PropertyName, &ContextStruct, PropertyInfoOverride]() { PromoteToParameterAndRefresh(PropertyName, TConstStructView(ContextStruct), PropertyInfoOverride); }), MakeBindingPropertyInfoWidget(PropertyInfoOverride->TypeNameText, PropertyInfoOverride->PinType)); } })); } } }; if (AccessibleStructs.Num() > 1) { MenuBuilder.AddSubMenu( LOCTEXT("PromoteToParameter", "Promote to Parameter"), LOCTEXT("PromoteToParameterTooltip", "Create a new parameter of the same type as the property, copy value over, and bind the property to the new parameter."), FNewMenuDelegate::CreateLambda([CachedBindingDataRef, &AccessibleStructs, &InPropertyHandle, PropertyName, AddMenuEntry](FMenuBuilder& InMenuBuilder) { FMenuSectionHelper SectionHelper(InMenuBuilder); for (const TInstancedStruct& InstancedContextStruct : AccessibleStructs) { AddMenuEntry(InMenuBuilder, InstancedContextStruct.Get(), CachedBindingDataRef, SectionHelper); } }) ); } else if (AccessibleStructs.Num() == 1) { TArray> PropertyInfoOverrides; if (CachedBindingDataRef->CanCreateParameter(AccessibleStructs[0].Get(), /*out*/PropertyInfoOverrides)) { MenuBuilder.AddMenuEntry( LOCTEXT("PromoteToParameter", "Promote to Parameter"), LOCTEXT("PromoteToParameterTooltip", "Create a new parameter of the same type as the property, copy value over, and bind the property to the new parameter."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Plus"), FUIAction(FExecuteAction::CreateLambda( [PromoteToParameterAndRefresh, PropertyName, AccessibleStruct = AccessibleStructs[0]]() { PromoteToParameterAndRefresh(PropertyName, TConstStructView(AccessibleStruct), TSharedPtr()); }))); } } }) ); } CustomizeDetailWidgetRow(InWidgetRow, InDetailBuilder, BindingsOwner, InPropertyHandle, TargetPath, CachedBindingData); CustomizePropertyBindingWidgetArgs(Args, BindingsOwner, TargetPath, CachedBindingData); InWidgetRow.ExtensionContent() [ PropertyAccessEditor.MakePropertyBindingWidget(BindingContextStructs, Args) ]; } TSharedPtr FPropertyBindingExtension::CreateCachedBindingData(IPropertyBindingBindingCollectionOwner* InBindingsOwner, const FPropertyBindingPath& InTargetPath, const TSharedPtr& InPropertyHandle, const TConstArrayView> InAccessibleStructs) const { return MakeShared(InBindingsOwner, InTargetPath, InPropertyHandle, InAccessibleStructs); } bool FPropertyBindingExtension::GetPromotionToParameterOverrideInternal(const FProperty& InProperty, bool& bOutOverride) const { return false; } void FPropertyBindingExtension::UpdateContextStruct(TConstStructView InStructDesc, FBindingContextStruct& InOutContextStruct , TMap& InOutSectionNames) const { // nothing to do } void FPropertyBindingExtension::CustomizeDetailWidgetRow(FDetailWidgetRow& InWidgetRow, const IDetailLayoutBuilder& InDetailBuilder, IPropertyBindingBindingCollectionOwner* InBindingsOwner, TSharedPtr InPropertyHandle, const FPropertyBindingPath& InTargetPath, TSharedPtr InCachedBindingData) const { // ResetToDefault { InWidgetRow.CustomResetToDefault = FResetToDefaultOverride::Create( MakeAttributeLambda([InCachedBindingData, InPropertyHandle]() { return InPropertyHandle->CanResetToDefault() || InCachedBindingData->HasBinding(FPropertyBindingBindingCollection::ESearchMode::Includes); }), FSimpleDelegate::CreateLambda([InCachedBindingData, &InDetailBuilder, InPropertyHandle]() { if (InCachedBindingData->HasBinding(FPropertyBindingBindingCollection::ESearchMode::Includes)) { InCachedBindingData->RemoveBinding(FPropertyBindingBindingCollection::ESearchMode::Includes); InDetailBuilder.GetPropertyUtilities()->RequestForceRefresh(); } if (InPropertyHandle->CanResetToDefault()) { InPropertyHandle->ResetToDefault(); } }), false); } } void FPropertyBindingExtension::CustomizePropertyBindingWidgetArgs(FPropertyBindingWidgetArgs& Args, IPropertyBindingBindingCollectionOwner* InBindingsOwner, const FPropertyBindingPath& InTargetPath, TSharedPtr InCachedBindingData) { } bool FPropertyBindingExtension::CanBindToProperty(const FPropertyBindingPath& InTargetPath, const IPropertyHandle& InPropertyHandle) const { return true; } uint64 FPropertyBindingExtension::GetDisallowedPropertyFlags() const { return CPF_PersistentInstance | CPF_EditorOnly | CPF_Config | CPF_Deprecated; } bool FPropertyBindingExtension::CanPromoteToParameter(const TSharedPtr& InPropertyHandle) const { const FProperty* Property = InPropertyHandle->GetProperty(); if (Property == nullptr) { return false; } if (bool bCanPromoteOverride = false; GetPromotionToParameterOverrideInternal(*Property, bCanPromoteOverride)) { return bCanPromoteOverride; } // Property Bag picker only detects Blueprint Types, so only allow properties that are blueprint types // FPropertyBagInstanceDataDetails::OnPropertyNameContent uses SPinTypeSelector to generate the property type picker. // UEdGraphSchema_K2::GetVariableTypeTree (GatherPinsImpl: FindEnums, FindStructs, FindObjectsAndInterfaces) is used there which only allows bp types. // The below behavior mirrors the behavior in the pin gathering but for properties if (const FEnumProperty* EnumProperty = CastField(Property)) { if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(EnumProperty->GetEnum())) { return false; } } else if (const FStructProperty* StructProperty = CastField(Property)) { if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(StructProperty->Struct, /*bForInternalUse*/true)) { return false; } } else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(ObjectProperty->PropertyClass)) { return false; } } else if (const FInterfaceProperty* InterfaceProperty = CastField(Property)) { if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(InterfaceProperty->InterfaceClass)) { return false; } } // Is the type supported by the property bag const FPropertyBagPropertyDesc Desc = FPropertyBagPropertyDesc(Property->GetFName(), Property); if (Desc.ValueType == EPropertyBagPropertyType::None) { return false; } if (UE::PropertyBinding::HasMetaData(UE::PropertyBinding::MetaDataNoPromoteToParameter, InPropertyHandle.ToSharedRef())) { return false; } return true; } bool FPropertyBindingExtension::CanBindToArrayElements() const { return false; } TSharedRef FPropertyBindingExtension::MakeContextStructWidget(const FPropertyBindingBindableStructDescriptor& InContextStruct) { FEdGraphPinType PinType; UStruct* Struct = const_cast(InContextStruct.Struct.Get()); if (UClass* Class = Cast(Struct)) { PinType.PinCategory = UEdGraphSchema_K2::PC_Object; PinType.PinSubCategory = NAME_None; PinType.PinSubCategoryObject = Class; } else if (UScriptStruct* ScriptStruct = Cast(Struct)) { PinType.PinCategory = UEdGraphSchema_K2::PC_Struct; PinType.PinSubCategory = NAME_None; PinType.PinSubCategoryObject = ScriptStruct; } const FSlateBrush* Icon = FBlueprintEditorUtils::GetIconFromPin(PinType, true); const FLinearColor IconColor = GetDefault()->GetPinTypeColor(PinType); return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SSpacer) .Size(FVector2D(18.0f, 0.0f)) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(1.0f, 0.0f) [ SNew(SImage) .Image(Icon) .ColorAndOpacity(IconColor) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromName(InContextStruct.Name)) ]; } #undef LOCTEXT_NAMESPACE