// Copyright Epic Games, Inc. All Rights Reserved. #include "SInstancedStructPicker.h" #include "DetailLayoutBuilder.h" #include "Editor.h" #include "IPropertyUtilities.h" #include "PropertyCustomizationHelpers.h" #include "Modules/ModuleManager.h" #include "PropertyHandle.h" #include "ScopedTransaction.h" #include "StructUtils/InstancedStruct.h" #include "StructUtils/UserDefinedStruct.h" #include "Styling/SlateIconFinder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SBox.h" #define LOCTEXT_NAMESPACE "StructUtilsEditor" namespace UE::StructUtils::Private { FPropertyAccess::Result GetCommonScriptStruct(const TSharedPtr& StructProperty, const UScriptStruct*& OutCommonStruct) { bool bHasResult = false; bool bHasMultipleValues = false; StructProperty->EnumerateConstRawData([&OutCommonStruct, &bHasResult, &bHasMultipleValues](const void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/) { if (const FInstancedStruct* InstancedStruct = static_cast(RawData)) { const UScriptStruct* Struct = InstancedStruct->GetScriptStruct(); if (!bHasResult) { OutCommonStruct = Struct; } else if (OutCommonStruct != Struct) { bHasMultipleValues = true; } bHasResult = true; } return true; }); if (bHasMultipleValues) { return FPropertyAccess::MultipleValues; } return bHasResult ? FPropertyAccess::Success : FPropertyAccess::Fail; } } // UE::StructUtils::Private //////////////////////////////////// bool FInstancedStructFilter::IsStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const UScriptStruct* InStruct, TSharedRef InFilterFuncs) { bool bStructAllowed = true; if (!AllowedStructs.IsEmpty()) { bStructAllowed = false; for (const TSoftObjectPtr& AllowedStruct : AllowedStructs) { if (InStruct->IsChildOf(AllowedStruct.Get())) { bStructAllowed = true; break; } } } if (!DisallowedStructs.IsEmpty()) { for (const TSoftObjectPtr& DisallowedStruct : DisallowedStructs) { if (InStruct->IsChildOf(DisallowedStruct.Get())) { bStructAllowed = false; break; } } } if (!bStructAllowed) { return false; } if (InStruct->IsA()) { return bAllowUserDefinedStructs; } if (InStruct == BaseStruct) { return bAllowBaseStruct; } static const FName NAME_HiddenMetaTag = "Hidden"; if (InStruct->HasMetaData(NAME_HiddenMetaTag)) { return false; } if (AssetReferenceFilter.IsValid()) { if (!AssetReferenceFilter->PassesFilter(FAssetData(InStruct))) { return false; } } // Query the native struct to see if it has the correct parent type (if any) const UScriptStruct* Struct = BaseStruct.Get(); return !Struct || InStruct->IsChildOf(Struct); } bool FInstancedStructFilter::IsUnloadedStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const FSoftObjectPath& InStructPath, TSharedRef InFilterFuncs) { // User Defined Structs don't support inheritance, so only include them requested return bAllowUserDefinedStructs; } //////////////////////////////////// void SInstancedStructPicker::Construct(const FArguments& InArgs, TSharedPtr InStructProperty, TSharedPtr InPropertyUtils) { static const FName NAME_BaseStruct = "BaseStruct"; static const FName NAME_StructTypeConst = "StructTypeConst"; OnStructPicked = InArgs._OnStructPicked; AllowedStructs = InArgs._AllowedStructs; DisallowedStructs = InArgs._DisallowedStructs; StructProperty = MoveTemp(InStructProperty); PropUtils = MoveTemp(InPropertyUtils); if (!StructProperty.IsValid() || !PropUtils.IsValid()) { return; } BaseScriptStruct = nullptr; { const FString& BaseStructName = StructProperty->GetMetaData(NAME_BaseStruct); if (!BaseStructName.IsEmpty()) { if (UScriptStruct* Struct = UClass::TryFindTypeSlow(BaseStructName)) { BaseScriptStruct = Struct; } else { BaseScriptStruct = LoadObject(nullptr, *BaseStructName); } } } ChildSlot [ SAssignNew(ComboButton, SComboButton) .OnGetMenuContent(this, &SInstancedStructPicker::GenerateStructPicker) .ContentPadding(0) .IsEnabled(StructProperty->IsEditable() && !StructProperty->HasMetaData(NAME_StructTypeConst)) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(SImage) .Image(this, &SInstancedStructPicker::GetDisplayValueIcon) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SInstancedStructPicker::GetDisplayValueString) .ToolTipText(this, &SInstancedStructPicker::GetTooltipText) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] ]; } FText SInstancedStructPicker::GetDisplayValueString() const { const UScriptStruct* CommonStruct = nullptr; const FPropertyAccess::Result Result = UE::StructUtils::Private::GetCommonScriptStruct(StructProperty, CommonStruct); if (Result == FPropertyAccess::Success) { if (CommonStruct) { return CommonStruct->GetDisplayNameText(); } return LOCTEXT("NullScriptStruct", "None"); } if (Result == FPropertyAccess::MultipleValues) { return LOCTEXT("MultipleValues", "Multiple Values"); } return FText::GetEmpty(); } FText SInstancedStructPicker::GetTooltipText() const { const UScriptStruct* CommonStruct = nullptr; const FPropertyAccess::Result Result = UE::StructUtils::Private::GetCommonScriptStruct(StructProperty, CommonStruct); if (CommonStruct && Result == FPropertyAccess::Success) { return CommonStruct->GetToolTipText(); } return GetDisplayValueString(); } const FSlateBrush* SInstancedStructPicker::GetDisplayValueIcon() const { const UScriptStruct* CommonStruct = nullptr; if (UE::StructUtils::Private::GetCommonScriptStruct(StructProperty, CommonStruct) == FPropertyAccess::Success) { return FSlateIconFinder::FindIconBrushForClass(CommonStruct, "ClassIcon.Object"); } return nullptr; } TSharedRef SInstancedStructPicker::GenerateStructPicker() { static const FName NAME_ExcludeBaseStruct = "ExcludeBaseStruct"; static const FName NAME_HideViewOptions = "HideViewOptions"; static const FName NAME_ShowTreeView = "ShowTreeView"; const bool bExcludeBaseStruct = StructProperty->HasMetaData(NAME_ExcludeBaseStruct); const bool bAllowNone = !(StructProperty->GetMetaDataProperty()->PropertyFlags & CPF_NoClear); const bool bHideViewOptions = StructProperty->HasMetaData(NAME_HideViewOptions); const bool bShowTreeView = StructProperty->HasMetaData(NAME_ShowTreeView); TSharedRef StructFilter = MakeShared(); StructFilter->BaseStruct = BaseScriptStruct; StructFilter->bAllowUserDefinedStructs = BaseScriptStruct.IsExplicitlyNull(); // Only allow user defined structs when BaseStruct is not set. StructFilter->bAllowBaseStruct = !bExcludeBaseStruct; if (GEditor && StructProperty) { FAssetReferenceFilterContext AssetReferenceFilterContext; TArray OuterPackages; StructProperty->GetOuterPackages(OuterPackages); for (UPackage* OuterPackage : OuterPackages) { AssetReferenceFilterContext.AddReferencingAsset(FAssetData(OuterPackage)); } StructFilter->AssetReferenceFilter = GEditor->MakeAssetReferenceFilter(AssetReferenceFilterContext); auto SoftPointerTransform = [](const UScriptStruct* InStruct) -> TSoftObjectPtr { return InStruct; }; Algo::Transform(PropertyCustomizationHelpers::GetStructsFromMetadataString(StructProperty->GetMetaData("AllowedClasses")), StructFilter->AllowedStructs, SoftPointerTransform); Algo::Transform(PropertyCustomizationHelpers::GetStructsFromMetadataString(StructProperty->GetMetaData("DisallowedClasses")), StructFilter->DisallowedStructs, SoftPointerTransform); StructFilter->AllowedStructs.Append(AllowedStructs); StructFilter->DisallowedStructs.Append(DisallowedStructs); TArray OwningObjects; StructProperty->GetOuterObjects(OwningObjects); for (UObject* OwningObject : OwningObjects) { if (OwningObject != nullptr) { const FString GetAllowedClassesFunctionName = StructProperty->GetMetaData("GetAllowedClasses"); if (!GetAllowedClassesFunctionName.IsEmpty()) { const UFunction* GetAllowedClassesFunction = OwningObject ? OwningObject->FindFunction(*GetAllowedClassesFunctionName) : nullptr; if (GetAllowedClassesFunction != nullptr) { DECLARE_DELEGATE_RetVal(TArray>, FGetAllowedClasses); StructFilter->AllowedStructs.Append(FGetAllowedClasses::CreateUFunction(OwningObject, GetAllowedClassesFunction->GetFName()).Execute()); } } const FString GetDisallowedClassesFunctionName = StructProperty->GetMetaData("GetDisallowedClasses"); if (!GetDisallowedClassesFunctionName.IsEmpty()) { const UFunction* GetDisallowedClassesFunction = OwningObject ? OwningObject->FindFunction(*GetDisallowedClassesFunctionName) : nullptr; if (GetDisallowedClassesFunction != nullptr) { DECLARE_DELEGATE_RetVal(TArray>, FGetDisallowedClasses); StructFilter->DisallowedStructs.Append(FGetDisallowedClasses::CreateUFunction(OwningObject, GetDisallowedClassesFunction->GetFName()).Execute()); } } } } } else { StructFilter->AllowedStructs = AllowedStructs; StructFilter->DisallowedStructs = DisallowedStructs; } const UScriptStruct* SelectedStruct = nullptr; UE::StructUtils::Private::GetCommonScriptStruct(StructProperty, SelectedStruct); FStructViewerInitializationOptions Options; Options.bShowNoneOption = bAllowNone; Options.StructFilter = StructFilter; Options.NameTypeToDisplay = EStructViewerNameTypeToDisplay::DisplayName; Options.DisplayMode = bShowTreeView ? EStructViewerDisplayMode::TreeView : EStructViewerDisplayMode::ListView; Options.bAllowViewOptions = !bHideViewOptions; Options.SelectedStruct = SelectedStruct; Options.PropertyHandle = StructProperty; const FOnStructPicked OnPicked(FOnStructPicked::CreateSP(this, &SInstancedStructPicker::StructPicked)); return SNew(SBox) .WidthOverride(280) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .MaxHeight(500) [ FModuleManager::LoadModuleChecked("StructViewer").CreateStructViewer(Options, OnPicked) ] ]; } void SInstancedStructPicker::StructPicked(const UScriptStruct* InStruct) { if (StructProperty && StructProperty->IsValidHandle()) { FScopedTransaction Transaction(LOCTEXT("OnStructPicked", "Set Struct")); StructProperty->NotifyPreChange(); StructProperty->EnumerateRawData([InStruct](void* RawData, const int32 /*DataIndex*/, const int32 /*NumDatas*/) { if (FInstancedStruct* InstancedStruct = static_cast(RawData)) { InstancedStruct->InitializeAs(InStruct); } return true; }); StructProperty->NotifyPostChange(EPropertyChangeType::ValueSet); StructProperty->NotifyFinishedChangingProperties(); // After the type has changed, let's expand, so that the user can edit the newly appeared child properties StructProperty->SetExpanded(true); // Property tree will be invalid after changing the struct type, force update. if (PropUtils.IsValid()) { PropUtils->ForceRefresh(); } } ComboButton->SetIsOpen(false); OnStructPicked.ExecuteIfBound(InStruct); } #undef LOCTEXT_NAMESPACE