// Copyright Epic Games, Inc. All Rights Reserved. #include "PerPlatformPropertyCustomization.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "IDetailChildrenBuilder.h" #include "Internationalization/Internationalization.h" #include "Misc/AssertionMacros.h" #include "Misc/DataDrivenPlatformInfoRegistry.h" #include "UObject/PerPlatformProperties.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "SPerPlatformPropertiesWidget.h" #include "ScopedTransaction.h" #include "Serialization/Archive.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Tuple.h" #include "Templates/UnrealTemplate.h" #include "Textures/SlateIcon.h" #include "Types/SlateEnums.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/Text/STextBlock.h" class SWidget; #define LOCTEXT_NAMESPACE "PerPlatformPropertyCustomization" template void FPerPlatformPropertyCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { PropertyUtilities = StructCustomizationUtils.GetPropertyUtilities(); TAttribute> PlatformOverrideNames = TAttribute>::Create(TAttribute>::FGetter::CreateSP(this, &FPerPlatformPropertyCustomization::GetPlatformOverrideNames, StructPropertyHandle)); FPerPlatformPropertyCustomNodeBuilderArgs Args; Args.FilterText = StructPropertyHandle->GetPropertyDisplayName(); Args.OnGenerateNameWidget = FOnGetContent::CreateLambda([StructPropertyHandle]() { return StructPropertyHandle->CreatePropertyNameWidget(); }); Args.PlatformOverrideNames = PlatformOverrideNames; Args.OnAddPlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FPerPlatformPropertyCustomization::AddPlatformOverride, StructPropertyHandle); Args.OnRemovePlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FPerPlatformPropertyCustomization::RemovePlatformOverride, StructPropertyHandle); Args.OnGenerateWidgetForPlatformRow = FOnGenerateWidget::CreateLambda([this, StructPropertyHandle, &StructBuilder](FName PlatformGroupName) { return GetWidget(PlatformGroupName, StructPropertyHandle, StructBuilder); }); Args.IsEnabled = TAttribute::CreateLambda([StructPropertyHandle]() { return StructPropertyHandle->IsEditable(); }); StructBuilder.AddCustomBuilder(MakeShared(MoveTemp(Args))); } template TSharedRef FPerPlatformPropertyCustomization::GetWidget(FName PlatformGroupName, TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder) const { TSharedPtr EditProperty; if (PlatformGroupName == NAME_None) { EditProperty = StructPropertyHandle->GetChildHandle(FName("Default")); } else { TSharedPtr MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform")); if (MapProperty.IsValid()) { uint32 NumChildren = 0; MapProperty->GetNumChildren(NumChildren); for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ChildIdx++) { TSharedPtr ChildProperty = MapProperty->GetChildHandle(ChildIdx); if (ChildProperty.IsValid()) { TSharedPtr 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 if (EditProperty.IsValid()) { // First get the source map const TMap* 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* InstanceSourceMap = StructPropertyHandle->GetInstanceMetaDataMap(); for (const auto& It : *InstanceSourceMap) { EditProperty->SetInstanceMetaData(*It.Key.ToString(), *It.Value); } if (EditProperty->GetProperty()->IsA()) { return StructBuilder.GenerateStructValueWidget(EditProperty->AsShared()); } return EditProperty->CreatePropertyValueWidget(false); } return SNullWidget::NullWidget; } template bool FPerPlatformPropertyCustomization::AddPlatformOverride(FName PlatformGroupName, TSharedRef StructPropertyHandle) { FScopedTransaction Transaction(LOCTEXT("AddPlatformOverride", "Add Platform Override")); TSharedPtr PerPlatformProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform")); TSharedPtr DefaultProperty = StructPropertyHandle->GetChildHandle(FName("Default")); if (PerPlatformProperty.IsValid() && DefaultProperty.IsValid()) { TSharedPtr MapProperty = PerPlatformProperty->AsMap(); if (MapProperty.IsValid()) { MapProperty->AddItem(); uint32 NumChildren = 0; PerPlatformProperty->GetNumChildren(NumChildren); for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ChildIdx++) { TSharedPtr ChildProperty = PerPlatformProperty->GetChildHandle(ChildIdx); if (ChildProperty.IsValid()) { TSharedPtr KeyProperty = ChildProperty->GetKeyHandle(); if (KeyProperty.IsValid()) { FName KeyName; if (KeyProperty->GetValue(KeyName) == FPropertyAccess::Success && KeyName == NAME_None) { // Set Key KeyProperty->SetValue(PlatformGroupName); // Set Value FString PropertyValueString; DefaultProperty->GetValueAsFormattedString(PropertyValueString); ChildProperty->SetValueFromFormattedString(PropertyValueString); return true; } } } } } } return false; } template bool FPerPlatformPropertyCustomization::RemovePlatformOverride(FName PlatformGroupName, TSharedRef StructPropertyHandle) { FScopedTransaction Transaction(LOCTEXT("RemovePlatformOverride", "Remove Platform Override")); TSharedPtr MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform")); if (MapProperty.IsValid()) { TArray RawData; MapProperty->AccessRawData(RawData); for (const void* Data : RawData) { TMap* PerPlatformMap = (TMap*)(Data); check(PerPlatformMap); TArray KeyArray; PerPlatformMap->GenerateKeyArray(KeyArray); for (FName PlatformName : KeyArray) { if (PlatformName == PlatformGroupName) { PerPlatformMap->Remove(PlatformName); return true; } } } } return false; } template TArray FPerPlatformPropertyCustomization::GetPlatformOverrideNames(TSharedRef StructPropertyHandle) const { TArray PlatformOverrideNames; TSharedPtr MapProperty = StructPropertyHandle->GetChildHandle(FName("PerPlatform")); if (MapProperty.IsValid()) { TArray RawData; MapProperty->AccessRawData(RawData); for (const void* Data : RawData) { const TMap* PerPlatformMap = (const TMap*)(Data); check(PerPlatformMap); TArray KeyArray; PerPlatformMap->GenerateKeyArray(KeyArray); for (FName PlatformName : KeyArray) { PlatformOverrideNames.AddUnique(PlatformName); } } } return PlatformOverrideNames; } template TSharedRef FPerPlatformPropertyCustomization::MakeInstance() { return MakeShareable(new FPerPlatformPropertyCustomization); } /* Only explicitly instantiate the types which are supported *****************************************************************************/ template class FPerPlatformPropertyCustomization; template class FPerPlatformPropertyCustomization; template class FPerPlatformPropertyCustomization; template class FPerPlatformPropertyCustomization; #undef LOCTEXT_NAMESPACE void FPerPlatformPropertyCustomNodeBuilder::SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren) { OnRebuildChildren = InOnRegenerateChildren; } void FPerPlatformPropertyCustomNodeBuilder::SetOnToggleExpansion(FOnToggleNodeExpansion InOnToggleExpansion) { OnToggleExpansion = InOnToggleExpansion; } void FPerPlatformPropertyCustomNodeBuilder::GenerateHeaderRowContent(FDetailWidgetRow& HeaderRow) { // Build Platform menu FMenuBuilder AddPlatformMenuBuilder(true, nullptr, nullptr, true); const TArray& SortedPlatforms = FDataDrivenPlatformInfoRegistry::GetSortedPlatformInfos(EPlatformInfoType::TruePlatformsOnly); // Platform (group) names // const TArray& PlatformGroupNameArray = PlatformInfo::GetAllPlatformGroupNames(); // Sanitized platform names TArray BasePlatformNameArray; // Mapping from platform group name to individual platforms TMultiMap GroupToPlatform; TArray PlatformOverrides = Args.PlatformOverrideNames.Get(); TArray PlatformGroupNameArray; // Create mapping from platform to platform groups and remove postfixes and invalid platform names for (const FDataDrivenPlatformInfo* DDPI : SortedPlatforms) { // Add platform name if it isn't already set, and also add to group mapping if (!PlatformOverrides.Contains(DDPI->IniPlatformName)) { BasePlatformNameArray.AddUnique(DDPI->IniPlatformName); GroupToPlatform.AddUnique(DDPI->PlatformGroupName, DDPI->IniPlatformName); PlatformGroupNameArray.AddUnique(DDPI->PlatformGroupName); } } // Create section for platform groups const FName PlatformGroupSection(TEXT("PlatformGroupSection")); AddPlatformMenuBuilder.BeginSection(PlatformGroupSection, FText::FromString(TEXT("Platform Groups"))); for (const FName& GroupName : PlatformGroupNameArray) { if (!PlatformOverrides.Contains(GroupName)) { const FTextFormat Format = NSLOCTEXT("SPerPlatformPropertiesWidget", "AddOverrideGroupFor", "Add Override for Platforms part of the {0} Platform Group"); AddPlatformToMenu(GroupName, Format, AddPlatformMenuBuilder); } } AddPlatformMenuBuilder.EndSection(); for (const FName& GroupName : PlatformGroupNameArray) { // Create a section for each platform group and their respective platforms AddPlatformMenuBuilder.BeginSection(GroupName, FText::FromName(GroupName)); TArray PlatformNames; GroupToPlatform.MultiFind(GroupName, PlatformNames); // these come out reversed for whatever MultiFind reason, even tho they went in sorted Algo::Reverse(PlatformNames); const FTextFormat Format = NSLOCTEXT("SPerPlatformPropertiesWidget", "AddOverrideFor", "Add Override specifically for {0}"); for (const FName& PlatformName : PlatformNames) { AddPlatformToMenu(PlatformName, Format, AddPlatformMenuBuilder); } AddPlatformMenuBuilder.EndSection(); } HeaderRow .FilterString(Args.FilterText) .IsEnabled(Args.IsEnabled) .NameContent() [ Args.OnGenerateNameWidget.Execute() ] .ValueContent() .MinDesiredWidth(125+28.0f) [ SNew(SHorizontalBox) .ToolTipText(NSLOCTEXT("SPerPlatformPropertiesWidget", "DefaultPlatformDesc", "This property can have per-platform or platform group overrides.\nThis is the default value used when no override has been set for a platform or platform group.")) +SHorizontalBox::Slot() [ SNew(SPerPlatformPropertiesRow, NAME_None) .OnGenerateWidget(Args.OnGenerateWidgetForPlatformRow) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SComboButton) .ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton") .HasDownArrow(false) .ToolTipText(NSLOCTEXT("SPerPlatformPropertiesWidget", "AddOverrideToolTip", "Add an override for a specific platform or platform group")) .ButtonContent() [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.PlusCircle")) .ColorAndOpacity(FSlateColor::UseForeground()) ] .MenuContent() [ AddPlatformMenuBuilder.MakeWidget() ] ] ]; } void FPerPlatformPropertyCustomNodeBuilder::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) { TArray PlatformOverrides = Args.PlatformOverrideNames.Get(); for (FName PlatformName : PlatformOverrides) { FText PlatformDisplayName = FText::AsCultureInvariant(PlatformName.ToString()); FDetailWidgetRow& Row = ChildrenBuilder.AddCustomRow(PlatformDisplayName); Row.IsEnabled(Args.IsEnabled); Row.NameContent() [ SNew(STextBlock) .Text(PlatformDisplayName) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; Row.ValueContent() [ SNew(SPerPlatformPropertiesRow, PlatformName) .OnGenerateWidget(Args.OnGenerateWidgetForPlatformRow) .OnRemovePlatform(this, &FPerPlatformPropertyCustomNodeBuilder::OnRemovePlatformOverride) ]; } } FName FPerPlatformPropertyCustomNodeBuilder::GetName() const { return Args.Name; } void FPerPlatformPropertyCustomNodeBuilder::OnAddPlatformOverride(const FName PlatformName) { if (Args.OnAddPlatformOverride.IsBound() && Args.OnAddPlatformOverride.Execute(PlatformName)) { OnRebuildChildren.ExecuteIfBound(); OnToggleExpansion.ExecuteIfBound(true); } } bool FPerPlatformPropertyCustomNodeBuilder::OnRemovePlatformOverride(const FName PlatformName) { if (Args.OnRemovePlatformOverride.IsBound() && Args.OnRemovePlatformOverride.Execute(PlatformName)) { OnRebuildChildren.ExecuteIfBound(); } return true; } void FPerPlatformPropertyCustomNodeBuilder::AddPlatformToMenu(const FName PlatformName, const FTextFormat Format, FMenuBuilder& AddPlatformMenuBuilder) { const FText MenuText = FText::Format(FText::FromString(TEXT("{0}")), FText::AsCultureInvariant(PlatformName.ToString())); const FText MenuTooltipText = FText::Format(Format, FText::AsCultureInvariant(PlatformName.ToString())); AddPlatformMenuBuilder.AddMenuEntry( MenuText, MenuTooltipText, FSlateIcon(FAppStyle::GetAppStyleSetName(), "PerPlatformWidget.AddPlatform"), FUIAction(FExecuteAction::CreateSP(this, &FPerPlatformPropertyCustomNodeBuilder::OnAddPlatformOverride, PlatformName)) ); }