Skip to content

Instantly share code, notes, and snippets.

@Ryan-DowlingSoka
Last active March 6, 2025 22:21
Show Gist options
  • Save Ryan-DowlingSoka/6ce3f3bf8a38d9c3f5d44a3e9b4feb39 to your computer and use it in GitHub Desktop.
Save Ryan-DowlingSoka/6ce3f3bf8a38d9c3f5d44a3e9b4feb39 to your computer and use it in GitHub Desktop.
Example customization for handling a GetOptions style picker inside a struct.
#include "JoePropertyCustomizations.h"
#include "DetailWidgetRow.h"
#include "IDetailChildrenBuilder.h"
#include "PropertyCustomizationHelpers.h"
#include "SSearchableComboBox.h"
TSharedRef<IPropertyTypeCustomization> FJoeCustomStructPropertyCustomization::MakeInstance()
{
return MakeShareable(new FJoeCustomStructPropertyCustomization);
}
void FJoeCustomStructPropertyCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle,
class FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
if (StructPropertyHandle->HasMetaData(FName(TEXT("ShowOnlyInnerProperties"))))
{
return;
}
HeaderRow.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
StructPropertyHandle->CreatePropertyValueWidget()
];
}
void FJoeCustomStructPropertyCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle,
class IDetailChildrenBuilder& ChildBuilder,
IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
//Change this to your struct and your name for the label property.
TSharedPtr<IPropertyHandle> LabelHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FJoeCustomStruct, Label));
uint32 NumProperties = 0;
StructPropertyHandle->GetNumChildren(NumProperties);
for (uint32 i = 0; i < NumProperties; i++)
{
auto ChildHandle = StructPropertyHandle->GetChildHandle(i);
if (ChildHandle->GetProperty() == LabelHandle->GetProperty())
{
//Special case the label parameter.
auto NameWidget = LabelHandle->CreatePropertyNameWidget();
ChildBuilder.AddCustomRow(LabelHandle->GetPropertyDisplayName())
.NameContent()
[
LabelHandle->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(125.f)
.MaxDesiredWidth(600.f)
[
SAssignNew(ComboBox, SSearchableComboBox)
.OptionsSource(&Labels)
.OnComboBoxOpening(this, &FJoeCustomStructPropertyCustomization::GenerateLabels, StructPropertyHandle)
.OnGenerateWidget(this, &FJoeCustomStructPropertyCustomization::MakeItemWidget)
.OnSelectionChanged_Lambda([=](const TSharedPtr<FString> NewChoice, ESelectInfo::Type SelectType)
{
if(NewChoice).IsValid())
{
const FScopedTransaction Transaction(TEXT("Set Label Value"));
LabelHandle->NotifyPreChange();
LabelHandle->SetValue(*NewChoice);
LabelHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
}
})
.Content()
[
SNew(STextBlock)
.Text_Lambda([=]()
{
FName CurrentValue = NAME_None;
LabelHandle->GetValue(CurrentValue);
FString CurrentValueString = CurrentValue.ToString();
return FText::FromString(CurrentValueString);
})
]
];
}
else
{
//Add any other parameters in the struct.
ChildBuilder.AddProperty(ChildHandle.ToSharedRef());
}
}
}
void FJoeCustomStructPropertyCustomization::GenerateLabels(TSharedRef<IPropertyHandle> StructPropertyHandle)
{
const TSharedPtr<IPropertyHandle> SUDSScriptHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FJoeCustomStruct, SUDSScript));
UObject* SUDSScriptPtr;
const FPropertyAccess::Result Result = SUDSScriptHandle->GetValue(SUDSScriptPtr);
Labels.Reset();
if(Result == FPropertyAccess::Success)
{
if (USUDSScript* SUDSScript = Cast<USUDSScript>(SUDSScriptPtr))
{
//Early out if SUDSScript isn't set.
if (!SUDSScript) return;
TArray<FName> LabelNames = SUDSScript->GetLabels();
Algo::Transform(LabelNames, Labels,[&](const FName& InLabel)
{
return MakeShared<FString>(InLabel.ToString());
});
}
}
if(ComboBox.IsValid())
ComboBox->RefreshOptions();
}
TSharedRef<SWidget> FJoeCustomStructPropertyCustomization::MakeItemWidget( TSharedPtr<FString> StringItem )
{
check( StringItem.IsValid() );
return SNew(STextBlock)
.Text(FText::FromString(*(StringItem.Get())));
}
#pragma once
#include "IPropertyTypeCustomization.h"
//Copy this into your Editor BeginModule:
//FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
//PropertyModule.RegisterCustomPropertyTypeLayout(FJoeCustomStruct::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FJoeCustomStructPropertyCustomization::MakeInstance) );
//Copy this into your module includes into your Module Editor.Build.cs
//"ToolWidgets"
//This isn't needed, remove it when you remove the USUDSScript and the FJoeCustomStruct
#include "JoePropertyCustomizations.generated.h"
//Struct should exist in game code. not here.
//SUDSScript class is a stub.
class SSearchableComboBox;
class SWidget;
UCLASS(BlueprintType)
class USUDSScript : public UClass
{
GENERATED_BODY()
public:
UFUNCTION()
TArray<FName> GetLabels()
{
return {FName(TEXT("A")), FName(TEXT("B")), FName(TEXT("C"))};
}
};
USTRUCT(BlueprintType)
struct FJoeCustomStruct
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UObject> SUDSScript = nullptr; //This would be your SUDSScript ref.
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName Label = NAME_None;
};
class IDetailLayoutBuilder; //Forward Declaration.
class FJoeCustomStructPropertyCustomization : public IPropertyTypeCustomization
{
public:
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
void GenerateLabels(TSharedRef<IPropertyHandle> StructPropertyHandle);
TSharedRef<SWidget> MakeItemWidget( TSharedPtr<FString> StringItem );
private:
TArray<TSharedPtr<FString>> Labels;
TSharedPtr<SSearchableComboBox> ComboBox;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment