Files
UnrealEngine/Engine/Plugins/Chooser/Source/ChooserEditor/Private/SPropertyAccessChainWidget.cpp
2025-05-18 13:04:45 +08:00

395 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SPropertyAccessChainWidget.h"
#include "Chooser.h"
#include "ChooserPropertyAccess.h"
#include "DetailWidgetRow.h"
#include "GraphEditorSettings.h"
#include "SClassViewer.h"
#include "IPropertyAccessEditor.h"
#include "ScopedTransaction.h"
#include "Features/IModularFeatures.h"
#define LOCTEXT_NAMESPACE "PropertyAccessChainWidget"
namespace UE::ChooserEditor
{
void SPropertyAccessChainWidget::SetPropertyBinding(IHasContextClass* HasContext, const TArray<FBindingChainElement>& InBindingChain, FChooserPropertyBinding& OutPropertyBinding)
{
Chooser::CopyPropertyChain(InBindingChain, OutPropertyBinding);
FField* Property = InBindingChain.Last().Field.ToField();
OutPropertyBinding.DisplayName = "";
if (InBindingChain.Num() == 1)
{
// direct binding to a context struct/class, set display name to the struct/clase name
if (HasContext)
{
TConstArrayView<FInstancedStruct> ContextData = HasContext->GetContextData();
if (ContextData.IsValidIndex(InBindingChain[0].ArrayIndex))
{
if (const FContextObjectTypeStruct* StructContext = ContextData[InBindingChain[0].ArrayIndex].GetPtr<FContextObjectTypeStruct>())
{
if (StructContext->Struct)
{
OutPropertyBinding.DisplayName = StructContext->Struct->GetDisplayNameText().ToString();
}
}
else if (const FContextObjectTypeClass* ClassContext = ContextData[InBindingChain[0].ArrayIndex].GetPtr<FContextObjectTypeClass>())
{
if (ClassContext->Class)
{
OutPropertyBinding.DisplayName = ClassContext->Class->GetDisplayNameText().ToString();
}
}
}
}
}
else
{
// set displayname from property name
if (Property)
{
OutPropertyBinding.DisplayName = Property->GetDisplayNameText().ToString();
static const int ShortNameLength = 5;
if (OutPropertyBinding.DisplayName.Len() < ShortNameLength && InBindingChain.Num() > 2)
{
FField* ParentProperty = InBindingChain[InBindingChain.Num() - 2].Field.ToField();
OutPropertyBinding.DisplayName = ParentProperty->GetDisplayNameText().ToString() + "." + OutPropertyBinding.DisplayName;
}
}
}
OutPropertyBinding.SetPropertyData(HasContext,Property);
OutPropertyBinding.Compile(HasContext);
}
TSharedRef<SWidget> SPropertyAccessChainWidget::CreatePropertyAccessWidget()
{
FPropertyBindingWidgetArgs Args;
Args.bAllowPropertyBindings = true;
TConstArrayView<FInstancedStruct> ContextData;
if (ContextClassOwner)
{
ContextData = ContextClassOwner->GetContextData();
}
Args.bAllowUObjectFunctions = true;
Args.bAllowOnlyThreadSafeFunctions = true;
auto CanBindProperty = [this](FProperty* Property, TConstArrayView<FBindingChainElement> InBindingChain)
{
if (TypeFilter == "" || Property == nullptr)
{
return true;
}
if (TypeFilter == "struct")
{
// special case for struct of any type
return CastField<FStructProperty>(Property) != nullptr;
}
if (TypeFilter == "object")
{
// special case for objects references of any type
return CastField<FObjectPropertyBase>(Property) != nullptr;
}
if (TypeFilter == "double")
{
// special case for doubles to bind to either floats or doubles
return Property->GetCPPType() == "float" || Property->GetCPPType() == "double" || Property->GetCPPType() == "int32";
}
else if (TypeFilter == "enum")
{
// special case for enums, to find properties of type EnumProperty or ByteProperty which have an Enum
if (const FEnumProperty* EnumProperty = CastField<const FEnumProperty>(Property))
{
return true;
}
else if (const FByteProperty* ByteProperty = CastField<const FByteProperty>(Property))
{
return ByteProperty->Enum != nullptr;
}
return false;
}
else if (TypeFilter == "bool")
{
// special case for bools, because CPPType == "bool" doesn't catch: uint8 bBool : 1
if (const FBoolProperty* BoolProperty = CastField<const FBoolProperty>(Property))
{
return true;
}
return false;
}
const FString CPPType = Property->GetCPPType();
return CPPType == TypeFilter || CPPType == AlternateTypeFilter;
};
// allow struct bindings to bind context structs directly
Args.OnCanBindToContextStructWithIndex = FOnCanBindToContextStructWithIndex::CreateLambda([this](UStruct* StructType, int32 StructIndex)
{
if (StructType)
{
if (TypeFilter == "struct" && !StructType->IsChildOf(UObject::StaticClass()))
{
// struct bindings can bind any type of struct
return true;
}
else
{
const FString CPPName = StructType->GetPrefixCPP() + StructType->GetName();
return CPPName == TypeFilter;
}
}
return false;
});
Args.OnCanBindPropertyWithBindingChain = FOnCanBindPropertyWithBindingChain::CreateLambda(CanBindProperty);
Args.OnCanBindFunction = FOnCanBindFunction::CreateLambda([CanBindProperty](UFunction* Function)
{
if (Function->NumParms !=1)
{
// only allow binding object member functions which have no parameters
return false;
}
if (FProperty* ReturnProperty = Function->GetReturnProperty())
{
return CanBindProperty(ReturnProperty, {});
}
return false;
});
Args.OnCanBindToClass = FOnCanBindToClass::CreateLambda([](UClass* InClass)
{
return true;
});
FLinearColor BindingColorValue = FLinearColor::Gray;
if (BindingColor != "")
{
const UGraphEditorSettings* GraphEditorSettings = GetDefault<UGraphEditorSettings>();
if (const FStructProperty* ColorProperty = FindFProperty<FStructProperty>(GraphEditorSettings->GetClass(), FName(BindingColor)))
{
BindingColorValue = *ColorProperty->ContainerPtrToValuePtr<FLinearColor>(GraphEditorSettings);
}
}
Args.CurrentBindingColor = MakeAttributeLambda([BindingColorValue]() {
return BindingColorValue;
});
Args.OnCanBindToSubObjectClass = FOnCanBindToSubObjectClass::CreateLambda([](UClass* InClass)
{
// CanBindToSubObjectClass does the opposite of what it's name says. True means don't allow bindings
// don't allow binding to any object propertoes (forcing use of thread safe functions to access objects)
return true;
});
Args.OnCanAcceptPropertyOrChildrenWithBindingChain = FOnCanAcceptPropertyOrChildrenWithBindingChain::CreateLambda([](FProperty* InProperty, TConstArrayView<FBindingChainElement> BindingChain)
{
// Make only blueprint visible properties visible for binding.
return InProperty->HasAnyPropertyFlags(CPF_BlueprintVisible);
});
if (OnAddBinding.IsBound())
{
Args.OnAddBinding = OnAddBinding;
}
else
{
Args.OnAddBinding = FOnAddBinding::CreateLambda(
[this](FName InPropertyName, const TArray<FBindingChainElement>& InBindingChain)
{
if (PropertyBindingValue.IsSet())
{
FChooserPropertyBinding* ContextProperty = PropertyBindingValue.Get();
UObject* TransactionObject = Cast<UObject>(ContextClassOwner);
const FScopedTransaction Transaction(NSLOCTEXT("ContextPropertyWidget", "Change Property Binding", "Change Property Binding"));
TransactionObject->Modify(true);
SetPropertyBinding(ContextClassOwner, InBindingChain, *PropertyBindingValue.Get());
OnValueChanged.ExecuteIfBound();
}
});
}
Args.CurrentBindingToolTipText = MakeAttributeLambda([this]()
{
const FText Bind = NSLOCTEXT("ContextPropertyWidget", "Bind", "Bind");
FText CurrentValue = Bind;
const FChooserPropertyBinding* PropertyValue = PropertyBindingValue.Get();
if (PropertyValue != nullptr)
{
if (!PropertyValue->CompileMessage.IsEmpty())
{
CurrentValue = PropertyValue->CompileMessage;
}
else
{
if (PropertyValue->PropertyBindingChain.Num()>0)
{
TArray<FText> BindingChainText;
BindingChainText.Reserve(PropertyValue->PropertyBindingChain.Num());
for (const FName& Name : PropertyValue->PropertyBindingChain)
{
BindingChainText.Add(FText::FromName(Name));
}
CurrentValue = FText::Join(NSLOCTEXT("ContextPropertyWidget", "PropertyPathSeparator","."), BindingChainText);
}
}
}
return CurrentValue;
});
Args.CurrentBindingText = MakeAttributeLambda([this]()
{
const FText Bind = NSLOCTEXT("ContextPropertyWidget", "Bind", "Bind");
FText CurrentValue = Bind;
const FChooserPropertyBinding* PropertyValue = PropertyBindingValue.Get();
if (PropertyValue == nullptr)
{
return FText();
}
int BindingChainLength = PropertyValue->PropertyBindingChain.Num();
if (BindingChainLength == 0)
{
if (PropertyValue->ContextIndex >= 0)
{
// direct binding to a context struct
if (ContextClassOwner)
{
TConstArrayView<FInstancedStruct> ContextData = ContextClassOwner->GetContextData();
if (ContextData.IsValidIndex(PropertyValue->ContextIndex))
{
if (const FContextObjectTypeStruct* StructType = ContextData[PropertyValue->ContextIndex].GetPtr<FContextObjectTypeStruct>())
{
if (StructType->Struct)
{
CurrentValue = FText::FromString(StructType->Struct->GetAuthoredName());
}
}
}
}
}
}
else
{
if (!PropertyValue->DisplayName.IsEmpty())
{
CurrentValue = FText::FromString(PropertyValue->DisplayName);
}
else if (BindingChainLength == 1)
{
// single property, just use the property name
CurrentValue = FText::FromName(PropertyValue->PropertyBindingChain.Last());
}
else
{
// for longer chains always show the last struct/object name, and the final property name (full path in tooltip)
CurrentValue = FText::Join(NSLOCTEXT("ContextPropertyWidget", "PropertyPathSeparator","."),
TArray<FText>({
FText::FromName(PropertyValue->PropertyBindingChain[BindingChainLength-2]),
FText::FromName(PropertyValue->PropertyBindingChain[BindingChainLength-1])
}));
}
}
return CurrentValue;
});
Args.CurrentBindingImage = MakeAttributeLambda([this]() -> const FSlateBrush*
{
static FName PropertyIcon(TEXT("Kismet.Tabs.Variables"));
static FName WarningIcon(TEXT("Icons.WarningWithColor"));
bool bHasWarning = false;
const FChooserPropertyBinding* PropertyValue = PropertyBindingValue.Get();
if (PropertyValue != nullptr)
{
bHasWarning = !PropertyValue->CompileMessage.IsEmpty();
}
return FAppStyle::GetBrush(bHasWarning ? WarningIcon : PropertyIcon);
});
TArray<FBindingContextStruct> ContextStructs;
for (const FInstancedStruct& ContextStruct : ContextData)
{
if (const FContextObjectTypeClass* ClassType = ContextStruct.GetPtr<FContextObjectTypeClass>())
{
ContextStructs.SetNum(ContextStructs.Num()+1);
ContextStructs.Last().Struct = ClassType->Class;
}
else if (const FContextObjectTypeStruct* StructType = ContextStruct.GetPtr<FContextObjectTypeStruct>())
{
ContextStructs.SetNum(ContextStructs.Num()+1);
ContextStructs.Last().Struct = StructType->Struct;
}
}
const IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature<IPropertyAccessEditor>("PropertyAccessEditor");
return PropertyAccessEditor.MakePropertyBindingWidget(ContextStructs, Args);
}
void SPropertyAccessChainWidget::UpdateWidget()
{
ChildSlot[ CreatePropertyAccessWidget() ];
}
void SPropertyAccessChainWidget::ContextClassChanged()
{
UpdateWidget();
}
void SPropertyAccessChainWidget::Construct( const FArguments& InArgs)
{
TypeFilter = InArgs._TypeFilter;
BindingColor = InArgs._BindingColor;
ContextClassOwner = InArgs._ContextClassOwner;
bAllowFunctions = InArgs._AllowFunctions;
OnValueChanged = InArgs._OnValueChanged;
PropertyBindingValue = InArgs._PropertyBindingValue;
OnAddBinding = InArgs._OnAddBinding;
UpdateWidget();
if (TypeFilter[TypeFilter.Len() - 1] == '*')
{
FString Trimmed = TypeFilter.TrimChar('*');
AlternateTypeFilter = "TObjectPtr<" + Trimmed + ">";
}
if (ContextClassOwner)
{
ContextClassOwner->OnContextClassChanged.AddSP(this, &SPropertyAccessChainWidget::ContextClassChanged);
}
}
SPropertyAccessChainWidget::~SPropertyAccessChainWidget()
{
}
}
#undef LOCTEXT_NAMESPACE