// Copyright Epic Games, Inc. All Rights Reserved. #include "MaterialEditorDetailCustomization.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SEditableText.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Views/SListView.h" #include "Materials/Material.h" #include "Materials/MaterialParameterCollection.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "DetailCategoryBuilder.h" #include "Materials/MaterialFunction.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionComposite.h" #include "Materials/MaterialExpressionColorRamp.h" #include "Materials/MaterialExpressionOperator.h" #include "Materials/MaterialExpressionPinBase.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "MaterialLayersFunctionsCustomization.h" #include "PropertyEditorModule.h" #include "PropertyRestriction.h" #include "MaterialEditor/MaterialEditorPreviewParameters.h" #include "Widgets/Input/SButton.h" #include "MaterialEditor/MaterialEditorInstanceConstant.h" #include "IDetailGroup.h" #include "MaterialEditor/DEditorParameterValue.h" #include "AssetToolsModule.h" #include "Modules/ModuleManager.h" #include "Factories/MaterialInstanceConstantFactoryNew.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialEditor/DEditorFontParameterValue.h" #include "MaterialEditor/DEditorRuntimeVirtualTextureParameterValue.h" #include "MaterialEditor/DEditorScalarParameterValue.h" #include "MaterialEditor/DEditorStaticSwitchParameterValue.h" #include "MaterialEditor/DEditorStaticComponentMaskParameterValue.h" #include "MaterialEditor/DEditorTextureParameterValue.h" #include "MaterialEditor/DEditorVectorParameterValue.h" #include "MaterialPropertyHelpers.h" #include "MaterialEditor/DEditorMaterialLayersParameterValue.h" #include "PropertyCustomizationHelpers.h" #include "Curves/CurveLinearColor.h" #include "IPropertyUtilities.h" #include "MaterialDomain.h" #include "Engine/Texture.h" #include "Materials/MaterialExpressionCurveAtlasRowParameter.h" #include "Curves/CurveLinearColorAtlas.h" #include "RenderUtils.h" #include "MaterialShared.h" #include "SColorGradientEditor.h" #define LOCTEXT_NAMESPACE "MaterialEditor" // Update the blend mode names based on what is supported in legacy mode or Substrate mode UEnum* GetBlendModeEnum() { UEnum* BlendModeEnum = StaticEnum(); if (Substrate::IsSubstrateEnabled()) { // BLEND_Translucent & BLEND_TranslucentGreyTransmittance are mapped onto the same enum index BlendModeEnum->SetMetaData(TEXT("DisplayName"), TEXT("TranslucentGreyTransmittance"), BLEND_Translucent); // BLEND_Modulate & BLEND_ColoredTransmittanceOnly are mapped onto the same enum index BlendModeEnum->SetMetaData(TEXT("DisplayName"), TEXT("ColoredTransmittanceOnly"), BLEND_Modulate); // BLEND_TranslucentColoredTransmittance is only supported in Substrate mode BlendModeEnum->SetMetaData(TEXT("DisplayName"), TEXT("TranslucentColoredTransmittance"), BLEND_TranslucentColoredTransmittance); } else { // BLEND_TranslucentColoredTransmittance is not supported in legacy mode BlendModeEnum->SetMetaData(TEXT("Hidden"), TEXT("True"), BLEND_TranslucentColoredTransmittance); } return BlendModeEnum; } TSharedRef FMaterialExpressionParameterDetails::MakeInstance(FOnCollectParameterGroups InCollectGroupsDelegate) { return MakeShareable( new FMaterialExpressionParameterDetails(InCollectGroupsDelegate) ); } FMaterialExpressionParameterDetails::FMaterialExpressionParameterDetails(FOnCollectParameterGroups InCollectGroupsDelegate) : CollectGroupsDelegate(InCollectGroupsDelegate) { } FMaterialExpressionParameterDetails::FMaterialExpressionParameterDetails() { } void FMaterialExpressionParameterDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { // for expression parameters all their properties are in one category based on their class name. FName DefaultCategory = NAME_None; IDetailCategoryBuilder& Category = DetailLayout.EditCategory( DefaultCategory ); TArray< TWeakObjectPtr > Objects; DetailLayout.GetObjectsBeingCustomized(Objects); DefaultValueHandles.Reset(); ScalarParameterObjects.Reset(); const FName MaterialExpressionCategory = TEXT("MaterialExpression"); IDetailCategoryBuilder& ExpressionCategory = DetailLayout.EditCategory(MaterialExpressionCategory); for (const auto& WeakObjectPtr : Objects) { UObject* Object = WeakObjectPtr.Get(); UMaterialExpressionScalarParameter* ScalarParameter = Cast(Object); if (ScalarParameter) { // Store these for OnSliderMinMaxEdited ScalarParameterObjects.Emplace(WeakObjectPtr); DefaultValueHandles.Emplace(DetailLayout.GetProperty("DefaultValue", UMaterialExpressionScalarParameter::StaticClass())); TSharedPtr SliderMinHandle = DetailLayout.GetProperty("SliderMin", UMaterialExpressionScalarParameter::StaticClass()); if (SliderMinHandle.IsValid() && SliderMinHandle->IsValidHandle()) { // Setup a callback when SliderMin changes to update the DefaultValue slider FSimpleDelegate OnSliderMinMaxEditedDelegate = FSimpleDelegate::CreateSP(this, &FMaterialExpressionParameterDetails::OnSliderMinMaxEdited); SliderMinHandle->SetOnPropertyValueChanged(OnSliderMinMaxEditedDelegate); } TSharedPtr SliderMaxHandle = DetailLayout.GetProperty("SliderMax", UMaterialExpressionScalarParameter::StaticClass()); if (SliderMaxHandle.IsValid() && SliderMaxHandle->IsValidHandle()) { FSimpleDelegate OnSliderMinMaxEditedDelegate = FSimpleDelegate::CreateSP(this, &FMaterialExpressionParameterDetails::OnSliderMinMaxEdited); SliderMaxHandle->SetOnPropertyValueChanged(OnSliderMinMaxEditedDelegate); } OnSliderMinMaxEdited(); TSharedPtr PropertyHandle = DetailLayout.GetProperty("bUseCustomPrimitiveData", UMaterialExpressionScalarParameter::StaticClass()); if (PropertyHandle.IsValid() && PropertyHandle->IsValidHandle()) { // Rebuild the layout when the bUseCustomPrimitiveData property changes PropertyHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); } if (ScalarParameter->IsUsedAsAtlasPosition()) { UMaterialExpressionCurveAtlasRowParameter* ColorCurveParameter = Cast(ScalarParameter); TSharedPtr CurveHandle = DetailLayout.GetProperty("Curve", UMaterialExpressionCurveAtlasRowParameter::StaticClass()); if (CurveHandle.IsValid() && CurveHandle->IsValidHandle()) { const FName AtlasCategoryName = TEXT("MaterialExpressionCurveAtlasRowParameter"); IDetailCategoryBuilder& AtlasCategory = DetailLayout.EditCategory(AtlasCategoryName); TSoftObjectPtr Atlas = TSoftObjectPtr(FSoftObjectPath(ColorCurveParameter->Atlas->GetPathName())); CurveHandle->MarkHiddenByCustomization(); AtlasCategory.AddCustomRow(CurveHandle->GetPropertyDisplayName()) .NameContent() [ SNew(STextBlock) .Text(CurveHandle->GetPropertyDisplayName()) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .HAlign(HAlign_Fill) .MaxDesiredWidth(400.0f) [ SNew(SObjectPropertyEntryBox) .PropertyHandle(CurveHandle) .AllowedClass(UCurveLinearColor::StaticClass()) .NewAssetFactories(TArray()) .DisplayThumbnail(true) .ThumbnailPool(DetailLayout.GetThumbnailPool()) .OnShouldFilterAsset(FOnShouldFilterAsset::CreateStatic(&FMaterialPropertyHelpers::OnShouldFilterCurveAsset, Atlas)) .OnShouldSetAsset(FOnShouldSetAsset::CreateStatic(&FMaterialPropertyHelpers::OnShouldSetCurveAsset, Atlas)) ]; TSharedPtr AtlasHandle = DetailLayout.GetProperty("Atlas", UMaterialExpressionCurveAtlasRowParameter::StaticClass()); if (AtlasHandle.IsValid() && AtlasHandle->IsValidHandle()) { // Rebuild the layout when the bUseCustomPrimitiveData property changes AtlasHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); } } } if (ScalarParameter->bUseCustomPrimitiveData) { DetailLayout.HideCategory(TEXT("MaterialExpressionScalarParameter")); DetailLayout.HideCategory(MaterialExpressionCategory); } } UMaterialExpressionVectorParameter* VectorParameter = Cast(Object); if (VectorParameter) { TSharedPtr PropertyHandle = DetailLayout.GetProperty("bUseCustomPrimitiveData", UMaterialExpressionVectorParameter::StaticClass()); if (PropertyHandle.IsValid() && PropertyHandle->IsValidHandle()) { // Rebuild the layout when the bUseCustomPrimitiveData property changes PropertyHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); } TSharedPtr ChannelHandle = DetailLayout.GetProperty("ChannelNames", UMaterialExpressionVectorParameter::StaticClass()); TSharedPtr ValueHandle = DetailLayout.GetProperty("DefaultValue", UMaterialExpressionVectorParameter::StaticClass()); if (ChannelHandle.IsValid() && ChannelHandle->IsValidHandle()) { static const FName Red("R"); static const FName Green("G"); static const FName Blue("B"); static const FName Alpha("A"); // Rebuild the layout when the ChannelNames property changes ChannelHandle->GetChildHandle(Red)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Green)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Blue)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Alpha)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); } if (VectorParameter->bUseCustomPrimitiveData) { DetailLayout.HideCategory(TEXT("MaterialExpressionVectorParameter")); DetailLayout.HideCategory(MaterialExpressionCategory); } if (ValueHandle.IsValid() && ValueHandle->IsValidHandle() && !VectorParameter->IsUsedAsChannelMask()) { static const FName Red("R"); static const FName Green("G"); static const FName Blue("B"); static const FName Alpha("A"); if (!VectorParameter->ChannelNames.R.IsEmpty()) { ValueHandle->GetChildHandle(Red)->SetPropertyDisplayName(VectorParameter->ChannelNames.R); } if (!VectorParameter->ChannelNames.G.IsEmpty()) { ValueHandle->GetChildHandle(Green)->SetPropertyDisplayName(VectorParameter->ChannelNames.G); } if (!VectorParameter->ChannelNames.B.IsEmpty()) { ValueHandle->GetChildHandle(Blue)->SetPropertyDisplayName(VectorParameter->ChannelNames.B); } if (!VectorParameter->ChannelNames.A.IsEmpty()) { ValueHandle->GetChildHandle(Alpha)->SetPropertyDisplayName(VectorParameter->ChannelNames.A); } } if (VectorParameter->IsUsedAsChannelMask()) { TSharedPtr ChannelNameHandle = DetailLayout.GetProperty("ChannelNames", UMaterialExpressionVectorParameter::StaticClass()); ChannelNameHandle->MarkHiddenByCustomization(); } } UMaterialExpressionTextureSampleParameter* TextureParameter = Cast(Object); if (TextureParameter) { TSharedPtr ChannelHandle = DetailLayout.GetProperty("ChannelNames", UMaterialExpressionTextureSampleParameter::StaticClass()); TSharedPtr ValueHandle = DetailLayout.GetProperty("Texture", UMaterialExpressionTextureBase::StaticClass()); if (ChannelHandle.IsValid() && ChannelHandle->IsValidHandle()) { static const FName Red("R"); static const FName Green("G"); static const FName Blue("B"); static const FName Alpha("A"); // Rebuild the layout when the ChannelNames property changes ChannelHandle->GetChildHandle(Red)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Green)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Blue)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); ChannelHandle->GetChildHandle(Alpha)->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { DetailLayout.ForceRefreshDetails(); })); } if (ValueHandle.IsValid() && ValueHandle->IsValidHandle()) { IDetailPropertyRow& PropertyRow = *DetailLayout.EditDefaultProperty(ValueHandle); TSharedPtr NameWidget; TSharedPtr ValueWidget; FDetailWidgetRow DefaultRow; PropertyRow.GetDefaultWidgets(NameWidget, ValueWidget, DefaultRow); FDetailWidgetRow &DetailWidgetRow = PropertyRow.CustomWidget(); TSharedPtr NameVerticalBox; DetailWidgetRow.NameContent() [ SAssignNew(NameVerticalBox, SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromName(TextureParameter->ParameterName)) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; DetailWidgetRow.ValueContent() .MinDesiredWidth(DefaultRow.ValueWidget.MinWidth) .MaxDesiredWidth(DefaultRow.ValueWidget.MaxWidth) [ ValueWidget.ToSharedRef() ]; static const FName Red("R"); static const FName Green("G"); static const FName Blue("B"); static const FName Alpha("A"); if (!TextureParameter->ChannelNames.R.IsEmpty()) { NameVerticalBox->AddSlot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(20.0, 2.0, 4.0, 2.0) [ SNew(STextBlock) .Text(FText::FromName(Red)) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .Padding(4.0, 2.0) [ SNew(STextBlock) .Text(TextureParameter->ChannelNames.R) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; } if (!TextureParameter->ChannelNames.G.IsEmpty()) { NameVerticalBox->AddSlot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(20.0, 2.0, 4.0, 2.0) .AutoWidth() [ SNew(STextBlock) .Text(FText::FromName(Green)) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .Padding(4.0, 2.0) [ SNew(STextBlock) .Text(TextureParameter->ChannelNames.G) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; } if (!TextureParameter->ChannelNames.B.IsEmpty()) { NameVerticalBox->AddSlot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(20.0, 2.0, 4.0, 2.0) .AutoWidth() [ SNew(STextBlock) .Text(FText::FromName(Blue)) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .Padding(4.0, 2.0) [ SNew(STextBlock) .Text(TextureParameter->ChannelNames.B) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; } if (!TextureParameter->ChannelNames.A.IsEmpty()) { NameVerticalBox->AddSlot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(20.0, 2.0, 4.0, 2.0) .AutoWidth() [ SNew(STextBlock) .Text(FText::FromName(Alpha)) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .Padding(4.0, 2.0) [ SNew(STextBlock) .Text(TextureParameter->ChannelNames.A) .Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; } } } } check(ScalarParameterObjects.Num() == DefaultValueHandles.Num()); Category.AddProperty("ParameterName"); // Get a handle to the property we are about to edit GroupPropertyHandle = DetailLayout.GetProperty( "Group" ); TSharedPtr NewComboButton; TSharedPtr NewEditBox; TSharedPtr>> NewListView; if (GroupPropertyHandle->IsValidHandle()) { GroupPropertyHandle->MarkHiddenByCustomization(); PopulateGroups(); ExpressionCategory.AddCustomRow(GroupPropertyHandle->GetPropertyDisplayName()) .NameContent() [ SNew(STextBlock) .Text(GroupPropertyHandle->GetPropertyDisplayName()) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SAssignNew(NewComboButton, SComboButton) .ContentPadding(FMargin(2.0f, 2.0f)) .ButtonContent() [ SAssignNew(NewEditBox, SEditableText) .Text(this, &FMaterialExpressionParameterDetails::OnGetText) .OnTextCommitted(this, &FMaterialExpressionParameterDetails::OnTextCommitted) ] .MenuContent() [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SAssignNew(NewListView, SListView>) .ListItemsSource(&GroupsSource) .OnGenerateRow(this, &FMaterialExpressionParameterDetails::MakeDetailsGroupViewWidget) .OnSelectionChanged(this, &FMaterialExpressionParameterDetails::OnSelectionChanged) ] ] ]; ExpressionCategory.AddProperty("SortPriority"); } GroupComboButton = NewComboButton; GroupEditBox = NewEditBox; GroupListView = NewListView; } void FMaterialExpressionParameterDetails::PopulateGroups() { TArray Groups; CollectGroupsDelegate.ExecuteIfBound(&Groups); Groups.Sort([&](const FString& A, const FString& B) { return A.ToLower() < B.ToLower(); }); GroupsSource.Empty(); for (int32 GroupIdx = 0; GroupIdx < Groups.Num(); ++GroupIdx) { GroupsSource.Add(MakeShareable(new FString(Groups[GroupIdx]))); } } TSharedRef< ITableRow > FMaterialExpressionParameterDetails::MakeDetailsGroupViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(FText::FromString(*Item.Get())) ]; } void FMaterialExpressionParameterDetails::OnSelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { if (ProposedSelection.IsValid()) { GroupPropertyHandle->SetValue(*ProposedSelection.Get()); GroupListView.Pin()->ClearSelection(); GroupComboButton.Pin()->SetIsOpen(false); } } void FMaterialExpressionParameterDetails::OnTextCommitted( const FText& InText, ETextCommit::Type /*CommitInfo*/) { GroupPropertyHandle->SetValue(InText.ToString()); PopulateGroups(); } FString FMaterialExpressionParameterDetails::OnGetString() const { FString OutText; if (GroupPropertyHandle->GetValue(OutText) == FPropertyAccess::MultipleValues) { return LOCTEXT("MultipleValues", "Multiple Values").ToString(); } return OutText; } FText FMaterialExpressionParameterDetails::OnGetText() const { FString NewString = OnGetString(); return FText::FromString(NewString); } void FMaterialExpressionParameterDetails::OnSliderMinMaxEdited() { check(ScalarParameterObjects.Num() == DefaultValueHandles.Num()); for (int32 Index = 0; Index < ScalarParameterObjects.Num(); Index++) { UMaterialExpressionScalarParameter* ScalarParameter = Cast(ScalarParameterObjects[Index].Get()); if (ScalarParameter && DefaultValueHandles[Index].IsValid() && DefaultValueHandles[Index]->IsValidHandle()) { if (ScalarParameter->SliderMax > ScalarParameter->SliderMin) { // Update the values that SPropertyEditorNumeric reads // Unfortuantly there is no way to recreate the widget to actually update the UI with these new values DefaultValueHandles[Index]->SetInstanceMetaData("UIMin", FString::Printf(TEXT("%f"), ScalarParameter->SliderMin)); DefaultValueHandles[Index]->SetInstanceMetaData("UIMax", FString::Printf(TEXT("%f"), ScalarParameter->SliderMax)); } else { DefaultValueHandles[Index]->SetInstanceMetaData("UIMin", TEXT("")); DefaultValueHandles[Index]->SetInstanceMetaData("UIMax", TEXT("")); } } } } TSharedRef FMaterialExpressionCollectionParameterDetails::MakeInstance(bool bInIncludeScalars) { return MakeShareable( new FMaterialExpressionCollectionParameterDetails(bInIncludeScalars) ); } FMaterialExpressionCollectionParameterDetails::FMaterialExpressionCollectionParameterDetails(bool bInIncludeScalars) : bIncludeScalars(bInIncludeScalars) { } FText FMaterialExpressionCollectionParameterDetails::GetToolTipText() const { if (ParametersSource.Num() == 1) { return LOCTEXT("SpecifyCollection", "Specify a Collection to get parameter options"); } else { return LOCTEXT("ChooseParameter", "Choose a parameter from the collection"); } } FText FMaterialExpressionCollectionParameterDetails::GetParameterNameString() const { FString CurrentParameterName; FPropertyAccess::Result Result = ParameterNamePropertyHandle->GetValue(CurrentParameterName); if( Result == FPropertyAccess::MultipleValues ) { return NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values"); } return FText::FromString(CurrentParameterName); } bool FMaterialExpressionCollectionParameterDetails::IsParameterNameComboEnabled() const { UMaterialParameterCollection* Collection = nullptr; if (CollectionPropertyHandle->IsValidHandle()) { UObject* CollectionObject = nullptr; FPropertyAccess::Result Result = CollectionPropertyHandle->GetValue(CollectionObject); // No name combo enabled if multiple parameter collection nodes are selected. if (Result == FPropertyAccess::MultipleValues) { return false; } verify(Result == FPropertyAccess::Success); Collection = Cast(CollectionObject); } return Collection != nullptr; } void FMaterialExpressionCollectionParameterDetails::OnCollectionChanged() { PopulateParameters(TEXT("")); } void FMaterialExpressionCollectionParameterDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { // for expression parameters all their properties are in one category based on their class name. FName DefaultCategory = NAME_None; IDetailCategoryBuilder& Category = DetailLayout.EditCategory( DefaultCategory ); // Get a handle to the property we are about to edit ParameterNamePropertyHandle = DetailLayout.GetProperty( "ParameterName" ); check(ParameterNamePropertyHandle.IsValid()); CollectionPropertyHandle = DetailLayout.GetProperty( "Collection" ); check(CollectionPropertyHandle.IsValid()); // Register a changed callback on the collection property since we need to update the PropertyName vertical box when it changes FSimpleDelegate OnCollectionChangedDelegate = FSimpleDelegate::CreateSP( this, &FMaterialExpressionCollectionParameterDetails::OnCollectionChanged ); CollectionPropertyHandle->SetOnPropertyValueChanged( OnCollectionChangedDelegate ); ParameterNamePropertyHandle->MarkHiddenByCustomization(); CollectionPropertyHandle->MarkHiddenByCustomization(); PopulateParameters(TEXT("")); TSharedPtr NewComboButton; TSharedPtr>> NewListView; // This isn't strictly speaking customized, but we need it to appear before the "Parameter Name" property, // so we manually add it and set MarkHiddenByCustomization on it to avoid it being automatically added Category.AddProperty(CollectionPropertyHandle); Category.AddCustomRow( ParameterNamePropertyHandle->GetPropertyDisplayName() ) .NameContent() [ SNew( STextBlock ) .Text( ParameterNamePropertyHandle->GetPropertyDisplayName() ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SAssignNew(NewComboButton, SComboButton) .IsEnabled(this, &FMaterialExpressionCollectionParameterDetails::IsParameterNameComboEnabled) .ContentPadding(0) .ButtonContent() [ SNew(STextBlock) .Text(this, &FMaterialExpressionCollectionParameterDetails::GetParameterNameString ) ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .Padding(FMargin(4.0f)) .AutoHeight() [ SAssignNew(ParameterSearchBox, SSearchBox) .OnTextChanged(this, &FMaterialExpressionCollectionParameterDetails::OnFilterTextChanged) ] +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SAssignNew(NewListView, SListView>) .ListItemsSource(&ParametersSource) .OnGenerateRow(this, &FMaterialExpressionCollectionParameterDetails::MakeDetailsGroupViewWidget) .OnSelectionChanged(this, &FMaterialExpressionCollectionParameterDetails::OnSelectionChanged) ] ] ]; ParameterComboButton = NewComboButton; ParameterListView = NewListView; NewComboButton->SetToolTipText(GetToolTipText()); } void FMaterialExpressionCollectionParameterDetails::PopulateParameters(const FString& TextFilter) { UMaterialParameterCollection* Collection = nullptr; if (CollectionPropertyHandle->IsValidHandle()) { UObject* CollectionObject = nullptr; FPropertyAccess::Result Result = CollectionPropertyHandle->GetValue(CollectionObject); // Return an empty set of parameters if multiple paramter collection nodes are selected. if (Result == FPropertyAccess::MultipleValues) { return; } verify(Result == FPropertyAccess::Success); Collection = Cast(CollectionObject); } ParametersSource.Empty(); if (Collection) { TArray NameList; if (bIncludeScalars) { Collection->GetParameterNames(NameList, /*bVectorParameters=*/ false); } Collection->GetParameterNames(NameList, /*bVectorParameters=*/ true); NameList.Sort([](const FName& A, const FName& B) { return A.LexicalLess(B); }); for (const FName& Name : NameList) { TSharedPtr ParamNameString = MakeShareable(new FString(Name.ToString())); if (TextFilter.Len() == 0 || ParamNameString->Contains(TextFilter)) { ParametersSource.Add(ParamNameString); } } } if (ParametersSource.Num() == 0) { ParametersSource.Add(MakeShareable(new FString(LOCTEXT("NoParameter", "None").ToString()))); } } TSharedRef< ITableRow > FMaterialExpressionCollectionParameterDetails::MakeDetailsGroupViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(FText::FromString(*Item.Get())) ]; } void FMaterialExpressionCollectionParameterDetails::OnSelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { if (ProposedSelection.IsValid()) { ParameterNamePropertyHandle->SetValue(*ProposedSelection.Get()); ParameterListView.Pin()->ClearSelection(); ParameterComboButton.Pin()->SetIsOpen(false); } } void FMaterialExpressionCollectionParameterDetails::OnFilterTextChanged(const FText& InFilterText) { PopulateParameters(InFilterText.ToString()); ParameterListView.Pin()->RequestListRefresh(); } TSharedRef FMaterialDetailCustomization::MakeInstance() { return MakeShareable( new FMaterialDetailCustomization ); } void FMaterialDetailCustomization::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { static const auto CVarMaterialTranslatorEnableNew = IConsoleManager::Get().FindTConsoleVariableDataBool(TEXT("r.Material.Translator.EnableNew")); TArray > Objects; DetailLayout.GetObjectsBeingCustomized( Objects ); bool bUIMaterial = true; bool bIsShadingModelFromMaterialExpression = false; bool bIsAlphaHoldout = false; for( TWeakObjectPtr& Object : Objects ) { UMaterial* Material = Cast( Object.Get() ); if( Material ) { bUIMaterial &= Material->IsUIMaterial(); // If any Object has its shading model from material expression bIsShadingModelFromMaterialExpression |= Material->IsShadingModelFromMaterialExpression(); bIsAlphaHoldout |= Material->BlendMode == BLEND_AlphaHoldout; } else { // this shouldn't happen but just in case, let all properties through bUIMaterial = false; } } // Material category { IDetailCategoryBuilder& MaterialCategory = DetailLayout.EditCategory( TEXT("Material") ); TArray> AllProperties; MaterialCategory.GetDefaultProperties( AllProperties ); for( TSharedRef& PropertyHandle : AllProperties ) { FProperty* Property = PropertyHandle->GetProperty(); FName PropertyName = Property->GetFName(); if (bUIMaterial) { if( PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, MaterialDomain) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, BlendMode) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, OpacityMaskClipValue) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, NumCustomizedUVs) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, bEnableNewHLSLGenerator)) { DetailLayout.HideProperty( PropertyHandle ); } } if (!Substrate::IsSubstrateEnabled()) { if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterial, bIsThinSurface)) { DetailLayout.HideProperty(PropertyHandle); } } // Patch blend mode displayed names if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterial, BlendMode)) { FByteProperty* BlendModeProperty = (FByteProperty*)Property; BlendModeProperty->Enum = GetBlendModeEnum(); PropertyHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([&DetailLayout]() { // Refresh to update translucency pass restrictions below DetailLayout.ForceRefreshDetails(); })); } if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterial, bEnableNewHLSLGenerator) && !CVarMaterialTranslatorEnableNew->GetValueOnAnyThread()) { DetailLayout.HideProperty(PropertyHandle); } #if WITH_EDITORONLY_DATA // Hide the shading model if (!bIsShadingModelFromMaterialExpression && PropertyName == GET_MEMBER_NAME_CHECKED(UMaterial, UsedShadingModels)) { DetailLayout.HideProperty(PropertyHandle); } #endif } } // Translucency category { IDetailCategoryBuilder& TranslucencyCategory = DetailLayout.EditCategory(TEXT("Translucency")); TArray> AdvancedProperties; TranslucencyCategory.GetDefaultProperties(AdvancedProperties, false, true); for (TSharedRef& PropertyHandle : AdvancedProperties) { FProperty* Property = PropertyHandle->GetProperty(); FName PropertyName = Property->GetFName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterial, TranslucencyPass)) { if (bIsAlphaHoldout) { static FText RestrictReason = NSLOCTEXT("PropertyEditor", "TranslucencyPassRestriction", "Seperate translucency pass should not be used with Alpha Holdout blend mode."); TSharedPtr EnumRestriction = MakeShared(RestrictReason); FByteProperty* TranslucencyPassProperty = (FByteProperty*)Property; EnumRestriction->AddDisabledValue(TranslucencyPassProperty->Enum->GetNameStringByValue(static_cast(MTP_AfterDOF))); EnumRestriction->AddDisabledValue(TranslucencyPassProperty->Enum->GetNameStringByValue(static_cast(MTP_AfterMotionBlur))); PropertyHandle->AddRestriction(EnumRestriction.ToSharedRef()); } } } } if( bUIMaterial ) { DetailLayout.HideCategory( TEXT("TranslucencySelfShadowing") ); DetailLayout.HideCategory( TEXT("Translucency") ); DetailLayout.HideCategory( TEXT("Tessellation") ); DetailLayout.HideCategory( TEXT("PostProcessMaterial") ); DetailLayout.HideCategory( TEXT("Lightmass") ); DetailLayout.HideCategory( TEXT("Thumbnail") ); DetailLayout.HideCategory( TEXT("MaterialInterface") ); DetailLayout.HideCategory( TEXT("PhysicalMaterial") ); // Usage Category { IDetailCategoryBuilder& UsageCategory = DetailLayout.EditCategory(TEXT("Usage")); TArray> AllProperties; UsageCategory.GetDefaultProperties(AllProperties); // Hide all Usage except bUsedWithStaticMesh on UI for (TSharedRef& PropertyHandle : AllProperties) { FProperty* Property = PropertyHandle->GetProperty(); FName PropertyName = Property->GetFName(); if (PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, bUsedWithStaticMesh)) { DetailLayout.HideProperty(PropertyHandle); } } } // Mobile category { IDetailCategoryBuilder& MobileCategory = DetailLayout.EditCategory(TEXT("Mobile")); TArray> AllProperties; MobileCategory.GetDefaultProperties(AllProperties); for (TSharedRef& PropertyHandle : AllProperties) { FProperty* Property = PropertyHandle->GetProperty(); FName PropertyName = Property->GetFName(); if (PropertyName != GET_MEMBER_NAME_CHECKED(UMaterial, FloatPrecisionMode)) { DetailLayout.HideProperty(PropertyHandle); } } } } } TSharedRef FMaterialFunctionDetailCustomization::MakeInstance() { return MakeShareable(new FMaterialFunctionDetailCustomization); } void FMaterialFunctionDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { static const auto CVarMaterialTranslatorEnableNew = IConsoleManager::Get().FindTConsoleVariableDataBool(TEXT("r.Material.Translator.EnableNew")); // MaterialFunction category { IDetailCategoryBuilder& MaterialCategory = DetailLayout.EditCategory(TEXT("MaterialFunction")); TArray> AllProperties; MaterialCategory.GetDefaultProperties(AllProperties); for (TSharedRef& PropertyHandle : AllProperties) { FProperty* Property = PropertyHandle->GetProperty(); FName PropertyName = Property->GetFName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterialFunction, bEnableNewHLSLGenerator) && !CVarMaterialTranslatorEnableNew->GetValueOnAnyThread()) { DetailLayout.HideProperty(PropertyHandle); } } } // Hide Thumbnail category if material function preview domain is set to UserInterface { TArray > Objects; DetailLayout.GetObjectsBeingCustomized(Objects); bool bUIMaterial = true; for (TWeakObjectPtr& Object : Objects) { UMaterialFunction* MaterialFunction = Cast(Object.Get()); if (MaterialFunction) { bUIMaterial &= MaterialFunction->PreviewMaterialDomain == MD_UI; } else { // this shouldn't happen but just in case, let all properties through bUIMaterial = false; break; } } if (bUIMaterial) { DetailLayout.HideCategory(TEXT("Thumbnail")); } } } TSharedRef FMaterialExpressionLayersParameterDetails::MakeInstance(FOnCollectParameterGroups InCollectGroupsDelegate) { return MakeShareable(new FMaterialExpressionLayersParameterDetails(InCollectGroupsDelegate)); } FMaterialExpressionLayersParameterDetails::FMaterialExpressionLayersParameterDetails(FOnCollectParameterGroups InCollectGroupsDelegate) { CollectGroupsDelegate = InCollectGroupsDelegate; } void FMaterialExpressionLayersParameterDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { FMaterialExpressionParameterDetails::CustomizeDetails(DetailLayout); // for expression parameters all their properties are in one category based on their class name. FName LayerCategory = FName("Layers"); if(Substrate::IsMaterialLayeringSupportEnabled()) { DetailLayout.HideCategory(LayerCategory); } IDetailCategoryBuilder& Category = DetailLayout.EditCategory(LayerCategory); // Get a handle to the property we are about to edit GroupPropertyHandle = DetailLayout.GetProperty("DefaultLayers"); GroupPropertyHandle->MarkHiddenByCustomization(); TSharedRef MaterialLayersFunctionsCustomization = MakeShareable(new FMaterialLayersFunctionsCustomization(GroupPropertyHandle, &DetailLayout)); Category.AddCustomBuilder(MaterialLayersFunctionsCustomization); } TSharedRef FMaterialExpressionCompositeDetails::MakeInstance() { return MakeShareable(new FMaterialExpressionCompositeDetails()); } void FMaterialExpressionCompositeDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { FName DefaultCategory = NAME_None; IDetailCategoryBuilder& Category = DetailLayout.EditCategory(DefaultCategory); Category.AddProperty("SubgraphName"); const FName MaterialExpressionCategory = TEXT("MaterialExpression"); IDetailCategoryBuilder& ExpressionCategory = DetailLayout.EditCategory(MaterialExpressionCategory); TArray< TWeakObjectPtr > Objects; DetailLayout.GetObjectsBeingCustomized(Objects); for (const auto& WeakObjectPtr : Objects) { UObject* Object = WeakObjectPtr.Get(); UMaterialExpressionComposite* Composite = Cast(Object); if (Composite) { if (IDetailPropertyRow* PinBaseRow = ExpressionCategory.AddExternalObjectProperty({Composite->InputExpressions}, GET_MEMBER_NAME_CHECKED(UMaterialExpressionPinBase, ReroutePins))) { PinBaseRow->DisplayName(LOCTEXT("InputPinsComposite", "Input Pins")); PinBaseRow->Visibility(EVisibility::Visible); } if (IDetailPropertyRow* PinBaseRow = ExpressionCategory.AddExternalObjectProperty({Composite->OutputExpressions }, GET_MEMBER_NAME_CHECKED(UMaterialExpressionPinBase, ReroutePins))) { PinBaseRow->DisplayName(LOCTEXT("OutPinsComposite", "Output Pins")); PinBaseRow->Visibility(EVisibility::Visible); } } } } TSharedRef FMaterialExpressionColorRampCustomization::MakeInstance() { return MakeShareable(new FMaterialExpressionColorRampCustomization); } void FMaterialExpressionColorRampCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("MaterialExpressionColorRamp"); // Hide default ColorCurve details TSharedPtr GradientProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMaterialExpressionColorRamp, ColorCurve)); DetailBuilder.HideProperty(GradientProperty); TArray> CustomizedObjects; DetailBuilder.GetObjectsBeingCustomized(CustomizedObjects); if (CustomizedObjects.Num() > 0) { UMaterialExpressionColorRamp* ColorRamp = Cast(CustomizedObjects[0].Get()); if (ColorRamp) { TSharedPtr GradientEditor = SNew(SColorGradientEditor) .ViewMinInput(0.0f) .ViewMaxInput(1.0f) .ClampStopsToViewRange(true); GradientEditor->SetCurveOwner(ColorRamp->ColorCurve); // Recompile shader when the curve is modified ColorRamp->ColorCurve->OnUpdateCurve.AddUObject(ColorRamp, &UMaterialExpressionColorRamp::HandleCurvePropertyChanged); Category.AddCustomRow(FText::FromString("Color Gradient")) .NameContent() [ SNew(STextBlock) .Text(FText::FromString("Color Gradient")) .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) ] .ValueContent() .MinDesiredWidth(300.f) [ GradientEditor.ToSharedRef() ]; } } } TSharedRef FMaterialExpressionOperatorCustomization::MakeInstance() { return MakeShareable(new FMaterialExpressionOperatorCustomization); } void FMaterialExpressionOperatorCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { // Hides default detail panel property for input array TSharedRef DynamicInputsHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UMaterialExpressionOperator, DynamicInputs)); DetailLayout.HideProperty(DynamicInputsHandle); // Get Material Expression Operater ref UMaterialExpressionOperator* MaterialExpOperator = nullptr; TArray> Objects; DetailLayout.GetObjectsBeingCustomized(Objects); for (TWeakObjectPtr& Obj : Objects) { if (auto* Test = Cast(Obj.Get())) { MaterialExpOperator = Test; break; } } if (MaterialExpOperator) { DetailLayout.EditCategory("MaterialExpressionOperator"); IDetailCategoryBuilder& InputCategory = DetailLayout.EditCategory("MaterialExpressionOperatorInputs", LOCTEXT("InputsCategory", "Material Expression Operator Inputs")); // Add an entry for each input in the input array uint32 NumInputs = 0; DynamicInputsHandle->GetNumChildren(NumInputs); for (uint32 Index = 0; Index < NumInputs; Index++) { TSharedPtr ElementHandle = DynamicInputsHandle->GetChildHandle(Index); TSharedPtr ConstValueHandle = ElementHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMaterialExpressionOperatorInput, ConstValue)); FText DisplayName = FText::FromName(MaterialExpOperator->GetInputName(Index)); InputCategory.AddCustomRow(DisplayName) .IsEnabled(true) .NameContent() [ SNew(STextBlock) .Text(DisplayName) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125.0f) [ ConstValueHandle->CreatePropertyValueWidget() ]; } } } #undef LOCTEXT_NAMESPACE