Last active
June 21, 2024 09:53
-
-
Save MilkyEngineer/ddc70cd4a25fba0ab8f3283a6310fedf to your computer and use it in GitHub Desktop.
Unreal Engine 5.0: Per Platform Struct Customization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "PerPlatformStructCustomization.h" | |
void FMyEditorModule::StartupModule() | |
{ | |
PropertyModule.RegisterCustomPropertyTypeLayout(FPerPlatformMyStruct::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPerPlatformStructCustomization<FPerPlatformMyStruct>::MakeInstance)); | |
} | |
void FMyEditorModule::ShutdownModule() | |
{ | |
PropertyModule.UnregisterCustomPropertyTypeLayout(FPerPlatformMyStruct::StaticStruct()->GetFName()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Allows you to build per platform properties for structs (and not just primitive types):
