// Copyright Epic Games, Inc. All Rights Reserved. #include "MaterialOptionsCustomization.h" #include "MaterialOptions.h" #include "PropertyHandle.h" #include "DetailWidgetRow.h" #include "IDetailChildrenBuilder.h" #include "IDetailPropertyRow.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Text/STextBlock.h" #include "RHI.h" #include "IPropertyTypeCustomization.h" #include "IPropertyUtilities.h" static const TArray DisabledProperties = { MP_Refraction, MP_FrontMaterial }; TSharedRef FPropertyEntryCustomization::MakeInstance() { return MakeShareable(new FPropertyEntryCustomization); } FPropertyEntryCustomization::FPropertyEntryCustomization() { PropertyRestriction = MakeShareable(new FPropertyRestriction(FText::FromString("Property already set on for a different entry"))); CurrentOptions = nullptr; } void FPropertyEntryCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { TSharedPtr MaterialPropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPropertyEntry, Property)); HeaderRow.NameContent() [ MaterialPropertyHandle->CreatePropertyValueWidget() ]; const TArray>& SelectedObjects = CustomizationUtils.GetPropertyUtilities()->GetSelectedObjects(); // Try and find material options instance in currently edited objects const TWeakObjectPtr* MaterialOptionsPtr = SelectedObjects.FindByPredicate([](TWeakObjectPtr Object) { return Cast(Object.Get()); }); CurrentOptions = MaterialOptionsPtr ? Cast((MaterialOptionsPtr)->Get()) : nullptr; const int32 Index = PropertyHandle->GetIndexInArray(); // Add restriction to ensure the user cannot set up two entries with the same EMaterialProperty value MaterialPropertyHandle->AddRestriction(PropertyRestriction.ToSharedRef()); // Set Parent handle on property change to update restrictions across all the entry properties TSharedPtr ParentHandle = PropertyHandle->GetParentHandle(); if (ParentHandle.IsValid()) { TSharedPtr TopParentHandle = ParentHandle->GetParentHandle(); if (TopParentHandle.IsValid()) { TopParentHandle->SetOnChildPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FPropertyEntryCustomization::UpdateRestrictions, Index)); } } UpdateRestrictions(Index); } void FPropertyEntryCustomization::CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { TSharedPtr CustomSizePropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPropertyEntry, CustomSize)); IDetailPropertyRow& SizeRow = ChildBuilder.AddProperty(CustomSizePropertyHandle.ToSharedRef()); AddTextureSizeClamping(CustomSizePropertyHandle); TSharedPtr ConstantValuePropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPropertyEntry, ConstantValue)); IDetailPropertyRow& ConstantValueRow = ChildBuilder.AddProperty(ConstantValuePropertyHandle.ToSharedRef()); } void FPropertyEntryCustomization::UpdateRestrictions(const int32 EntryIndex) { PropertyRestriction->RemoveAll(); const UEnum* PropertyEnum = StaticEnum(); if (CurrentOptions) { // Add all previously set material properties to be disabled for (int32 Index = 0; Index < CurrentOptions->Properties.Num(); ++Index) { if (Index != EntryIndex) { const FPropertyEntry& Entry = CurrentOptions->Properties[Index]; PropertyRestriction->AddDisabledValue(PropertyEnum->GetNameStringByValue(Entry.Property)); } } } for (const EMaterialProperty& Property : DisabledProperties) { PropertyRestriction->AddDisabledValue(PropertyEnum->GetNameStringByValue(Property)); } PropertyRestriction->AddHiddenValue(GET_ENUMERATOR_NAME_CHECKED(EMaterialProperty, MP_WorldPositionOffset).ToString()); } TSharedRef FMaterialOptionsCustomization::MakeInstance(int32 InNumLODs) { return MakeShareable(new FMaterialOptionsCustomization(InNumLODs)); } FMaterialOptionsCustomization::FMaterialOptionsCustomization(int32 InNumLODs) : NumLODs(InNumLODs) {} void FMaterialOptionsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { const FName CategoryName = FName(TEXT("MeshSettings")); IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(CategoryName); // Add custom LOD index selection row FDetailWidgetRow& LODsRow = CategoryBuilder.AddCustomRow(FText::FromString(TEXT("LODs"))); LODsRow.NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("LODs"))) .Font(DetailBuilder.GetDetailFont()) ]; TSharedPtr ContentBox; LODsRow.ValueContent() [ SAssignNew(ContentBox, SHorizontalBox) ]; TArray> WeakObjects; DetailBuilder.GetObjectsBeingCustomized(WeakObjects); // Try and find material options instance in currently edited objects UMaterialOptions* CurrentOptions = Cast((WeakObjects.FindByPredicate([](TWeakObjectPtr Object) { return Cast(Object.Get()); }))->Get()); TSharedRef TextureSizePropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMaterialOptions, TextureSize)); if (TextureSizePropertyHandle->IsValidHandle()) { AddTextureSizeClamping(TextureSizePropertyHandle); } TSharedRef PropertiesHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMaterialOptions, Properties)); if (PropertiesHandle->IsValidHandle()) { // Setup delegate for when the number of Material Property items changes FSimpleDelegate RefreshDelegate = FSimpleDelegate::CreateLambda([this, &DetailBuilder, CurrentOptions]() { TArray Properties; // Ensure that we set duplicate entries to MP_MAX for (FPropertyEntry& Entry : CurrentOptions->Properties) { if (Properties.Contains(Entry.Property)) { Entry.Property = MP_MAX; } else if (Entry.Property != MP_MAX && !DisabledProperties.Contains(Entry.Property)) { Properties.Add(Entry.Property); } } DetailBuilder.ForceRefreshDetails(); }); PropertiesHandle->SetOnPropertyValueChanged(RefreshDelegate); } // Only allow changes to LOD indices if we have a valid options instance and if there is actually more than one index ContentBox->SetEnabled(TAttribute::Create([this, CurrentOptions]() -> bool { return NumLODs > 1 && CurrentOptions != nullptr; })); for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex) { ContentBox->AddSlot() .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) .AutoWidth() [ SNew(SCheckBox) .IsChecked(LODIndex == 0 ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) { if (NewState == ECheckBoxState::Unchecked) { CurrentOptions->LODIndices.Remove(LODIndex); } else { CurrentOptions->LODIndices.Add(LODIndex); } }) ]; ContentBox->AddSlot() .Padding(FMargin(3.0f, 2.0f, 4.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Text(FText::FromString(FString::FromInt(LODIndex))) .Font(DetailBuilder.GetDetailFont()) ]; } } void AddTextureSizeClamping(TSharedPtr TextureSizeProperty) { TSharedPtr PropertyX = TextureSizeProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FIntPoint, X)); TSharedPtr PropertyY = TextureSizeProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FIntPoint, Y)); const FString MaxTextureResolutionString = FString::FromInt(GetMax2DTextureDimension()); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); PropertyX->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); PropertyX->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); PropertyY->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); PropertyY->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); const FString MinTextureResolutionString("1"); PropertyX->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); PropertyX->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); PropertyY->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); PropertyY->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); }