Skip to content

Instantly share code, notes, and snippets.

@MilkyEngineer
Last active August 31, 2023 18:57
Show Gist options
  • Save MilkyEngineer/ddc70cd4a25fba0ab8f3283a6310fedf to your computer and use it in GitHub Desktop.
Save MilkyEngineer/ddc70cd4a25fba0ab8f3283a6310fedf to your computer and use it in GitHub Desktop.
Unreal Engine 5.0: Per Platform Struct Customization
#include "PerPlatformStructCustomization.h"
void FMyEditorModule::StartupModule()
{
PropertyModule.RegisterCustomPropertyTypeLayout(FPerPlatformMyStruct::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPerPlatformStructCustomization<FPerPlatformMyStruct>::MakeInstance));
}
void FMyEditorModule::ShutdownModule()
{
PropertyModule.UnregisterCustomPropertyTypeLayout(FPerPlatformMyStruct::StaticStruct()->GetFName());
}
USTRUCT()
struct FMyStruct
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category=MyStruct)
int32 StructData0;
UPROPERTY(EditAnywhere, Category=MyStruct)
float StructData1;
};
USTRUCT()
struct FPerPlatformMyStruct
#if CPP
: public TPerPlatformProperty<FPerPlatformMyStruct, FMyStruct, NAME_StructProperty>
#endif
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = PerPlatform)
FMyStruct Default;
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category = PerPlatform)
TMap<FName, FMyStruct> PerPlatform;
#endif
};
// This is mostly a copy of PerPlatformPropertyCustomization.h/cpp (except for the implementation of GetWidget
#pragma once
#include "IDetailChildrenBuilder.h"
#include "IStructureDetailsView.h"
#include "PerPlatformPropertyCustomization.h"
#define LOCTEXT_NAMESPACE "PerPlatformPropertyCustomization"
template<typename PerPlatformType>
class FPerPlatformStructCustomization : public IPropertyTypeCustomization
{
public:
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override {}
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override
{
PropertyUtilities = StructCustomizationUtils.GetPropertyUtilities();
TAttribute<TArray<FName>> PlatformOverrideNames = TAttribute<TArray<FName>>::Create(TAttribute<TArray<FName>>::FGetter::CreateSP(this, &FPerPlatformStructCustomization<PerPlatformType>::GetPlatformOverrideNames, StructPropertyHandle));
FPerPlatformPropertyCustomNodeBuilderArgs Args;
Args.FilterText = StructPropertyHandle->GetPropertyDisplayName();
Args.OnGenerateNameWidget = FOnGetContent::CreateLambda([StructPropertyHandle]()
{
return StructPropertyHandle->CreatePropertyNameWidget();
});
Args.PlatformOverrideNames = PlatformOverrideNames;
Args.OnAddPlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FPerPlatformStructCustomization<PerPlatformType>::AddPlatformOverride, StructPropertyHandle);
Args.OnRemovePlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FPerPlatformStructCustomization<PerPlatformType>::RemovePlatformOverride, StructPropertyHandle);
Args.OnGenerateWidgetForPlatformRow = FOnGenerateWidget::CreateSP(this, &FPerPlatformStructCustomization<PerPlatformType>::GetWidget, StructPropertyHandle);
StructBuilder.AddCustomBuilder(MakeShared<FPerPlatformPropertyCustomNodeBuilder>(MoveTemp(Args)));
}
/**
* Creates a new instance.
*
* @return A new customization for FPerPlatform structs.
*/
static TSharedRef<IPropertyTypeCustomization> MakeInstance()
{
return MakeShareable(new FPerPlatformStructCustomization<PerPlatformType>);
}
protected:
TSharedRef<SWidget> GetWidget(FName PlatformGroupName, TSharedRef<IPropertyHandle> StructPropertyHandle)
{
TSharedPtr<IPropertyHandle> EditProperty;
if (PlatformGroupName == NAME_None)
{
EditProperty = StructPropertyHandle->GetChildHandle(FName("Default"));
}
else
{
TSharedPtr<IPropertyHandle> MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform"));
if (MapProperty.IsValid())
{
uint32 NumChildren = 0;
MapProperty->GetNumChildren(NumChildren);
for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ChildIdx++)
{
TSharedPtr<IPropertyHandle> ChildProperty = MapProperty->GetChildHandle(ChildIdx);
if (ChildProperty.IsValid())
{
TSharedPtr<IPropertyHandle> KeyProperty = ChildProperty->GetKeyHandle();
if (KeyProperty.IsValid())
{
FName KeyName;
if(KeyProperty->GetValue(KeyName) == FPropertyAccess::Success && KeyName == PlatformGroupName)
{
EditProperty = ChildProperty;
break;
}
}
}
}
}
}
// Push down struct metadata to per-platform properties
{
// First get the source map
const TMap<FName, FString>* SourceMap = StructPropertyHandle->GetMetaDataProperty()->GetMetaDataMap();
// Iterate through source map, setting each key/value pair in the destination
for (const auto& It : *SourceMap)
{
EditProperty->SetInstanceMetaData(*It.Key.ToString(), *It.Value);
}
// Copy instance metadata as well
const TMap<FName, FString>* InstanceSourceMap = StructPropertyHandle->GetInstanceMetaDataMap();
for (const auto& It : *InstanceSourceMap)
{
EditProperty->SetInstanceMetaData(*It.Key.ToString(), *It.Value);
}
}
if (ensure(EditProperty.IsValid()))
{
// This logic here will create a struct view for the struct data for this PlatformGroupName
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs Args;
Args.bHideSelectionTip = true;
Args.bAllowSearch = false;
Args.bAllowFavoriteSystem = false;
uint8* StructData;
EditProperty->GetValueData((void*&)StructData);
TSharedRef<FStructOnScope> WidgetStruct = MakeShared<FStructOnScope>(PerPlatformType::ValueType::StaticStruct(), StructData);
TSharedRef<IStructureDetailsView> StructureDetailsView = PropertyEditorModule.CreateStructureDetailView(Args, FStructureDetailsViewArgs(), WidgetStruct);
TArray<UObject*> OuterObjects;
EditProperty->GetOuterObjects(OuterObjects);
StructureDetailsView->GetDetailsView()->SetObjects(OuterObjects);
StructureDetailsView->SetCustomName(FText::FromName(PlatformGroupName));
StructureDetailsView->GetOnFinishedChangingPropertiesDelegate().AddLambda([EditProperty](const FPropertyChangedEvent& PropertyChangedEvent)
{
// Ensure we notify our property, to make it update configs, kick off PostEditChangeProperty, etc.
EditProperty->NotifyPostChange(PropertyChangedEvent.ChangeType);
EditProperty->NotifyFinishedChangingProperties();
});
return StructureDetailsView->GetWidget().ToSharedRef();
}
return SNullWidget::NullWidget;
}
TArray<FName> GetPlatformOverrideNames(TSharedRef<IPropertyHandle> StructPropertyHandle) const
{
TArray<FName> PlatformOverrideNames;
TSharedPtr<IPropertyHandle> MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform"));
if (MapProperty.IsValid())
{
TArray<const void*> RawData;
MapProperty->AccessRawData(RawData);
for (const void* Data : RawData)
{
const TMap<FName, typename PerPlatformType::ValueType>* PerPlatformMap = (const TMap<FName, typename PerPlatformType::ValueType>*)(Data);
check(PerPlatformMap);
TArray<FName> KeyArray;
PerPlatformMap->GenerateKeyArray(KeyArray);
for (FName PlatformName : KeyArray)
{
PlatformOverrideNames.AddUnique(PlatformName);
}
}
}
return PlatformOverrideNames;
}
bool AddPlatformOverride(FName PlatformGroupName, TSharedRef<IPropertyHandle> StructPropertyHandle)
{
FScopedTransaction Transaction(LOCTEXT("AddPlatformOverride", "Add Platform Override"));
TSharedPtr<IPropertyHandle> PerPlatformProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform"));
TSharedPtr<IPropertyHandle> DefaultProperty = StructPropertyHandle->GetChildHandle(FName("Default"));
if (PerPlatformProperty.IsValid() && DefaultProperty.IsValid())
{
TSharedPtr<IPropertyHandleMap> MapProperty = PerPlatformProperty->AsMap();
if (MapProperty.IsValid())
{
MapProperty->AddItem();
uint32 NumChildren = 0;
PerPlatformProperty->GetNumChildren(NumChildren);
for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ChildIdx++)
{
TSharedPtr<IPropertyHandle> ChildProperty = PerPlatformProperty->GetChildHandle(ChildIdx);
if (ChildProperty.IsValid())
{
TSharedPtr<IPropertyHandle> KeyProperty = ChildProperty->GetKeyHandle();
if (KeyProperty.IsValid())
{
FName KeyName;
if (KeyProperty->GetValue(KeyName) == FPropertyAccess::Success && KeyName == NAME_None)
{
// Set Key
KeyProperty->SetValue(PlatformGroupName);
// Set Value
typename PerPlatformType::ValueType* DefaultValue;
DefaultProperty->GetValueData((void*&)DefaultValue);
typename PerPlatformType::ValueType* PlatformValue;
DefaultProperty->GetValueData((void*&)PlatformValue);
FMemory::Memcpy(PlatformValue, DefaultValue, sizeof(typename PerPlatformType::ValueType));
return true;
}
}
}
}
}
}
return false;
}
bool RemovePlatformOverride(FName PlatformGroupName, TSharedRef<IPropertyHandle> StructPropertyHandle)
{
FScopedTransaction Transaction(LOCTEXT("RemovePlatformOverride", "Remove Platform Override"));
TSharedPtr<IPropertyHandle> MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform"));
if (MapProperty.IsValid())
{
TArray<const void*> RawData;
MapProperty->AccessRawData(RawData);
for (const void* Data : RawData)
{
TMap<FName, typename PerPlatformType::ValueType>* PerPlatformMap = (TMap<FName, typename PerPlatformType::ValueType>*)(Data);
check(PerPlatformMap);
TArray<FName> KeyArray;
PerPlatformMap->GenerateKeyArray(KeyArray);
for (FName PlatformName : KeyArray)
{
if (PlatformName == PlatformGroupName)
{
PerPlatformMap->Remove(PlatformName);
return true;
}
}
}
}
return false;
}
private:
/** Cached utils used for resetting customization when layout changes */
TWeakPtr<IPropertyUtilities> PropertyUtilities;
};
#undef LOCTEXT_NAMESPACE
@MilkyEngineer
Copy link
Author

Allows you to build per platform properties for structs (and not just primitive types):
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment