Files
UnrealEngine/Engine/Source/Editor/MaterialEditor/Private/MaterialPropertyHelpers.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1658 lines
60 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MaterialPropertyHelpers.h"
#include "Misc/MessageDialog.h"
#include "Misc/Guid.h"
#include "UObject/UnrealType.h"
#include "Layout/Margin.h"
#include "Misc/Attribute.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/SToolTip.h"
#include "Styling/AppStyle.h"
#include "Materials/MaterialEnumeration.h"
#include "Materials/MaterialInterface.h"
#include "MaterialEditor/DEditorFontParameterValue.h"
#include "MaterialEditor/DEditorMaterialLayersParameterValue.h"
#include "MaterialEditor/DEditorParameterCollectionParameterValue.h"
#include "MaterialEditor/DEditorRuntimeVirtualTextureParameterValue.h"
#include "MaterialEditor/DEditorScalarParameterValue.h"
#include "MaterialEditor/DEditorStaticComponentMaskParameterValue.h"
#include "MaterialEditor/DEditorStaticSwitchParameterValue.h"
#include "MaterialEditor/DEditorTextureParameterValue.h"
#include "MaterialEditor/DEditorVectorParameterValue.h"
#include "MaterialEditor/MaterialEditorInstanceConstant.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialExpressionParameter.h"
#include "Materials/MaterialExpressionTextureSampleParameter.h"
#include "Materials/MaterialExpressionFontSampleParameter.h"
#include "Materials/MaterialExpressionMaterialAttributeLayers.h"
#include "Materials/MaterialExpressionChannelMaskParameter.h"
#include "Materials/MaterialParameterCollection.h"
#include "EditorSupportDelegates.h"
#include "DetailWidgetRow.h"
#include "PropertyHandle.h"
#include "IDetailPropertyRow.h"
#include "DetailLayoutBuilder.h"
#include "IDetailGroup.h"
#include "DetailCategoryBuilder.h"
#include "PropertyCustomizationHelpers.h"
#include "ScopedTransaction.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialFunctionInstance.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialFunctionInterface.h"
#include "Modules/ModuleManager.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetToolsModule.h"
#include "Factories/MaterialInstanceConstantFactoryNew.h"
#include "StaticParameterSet.h"
#include "MaterialEditor/MaterialEditorPreviewParameters.h"
#include "Factories/MaterialFunctionInstanceFactory.h"
#include "SMaterialLayersFunctionsTree.h"
#include "Materials/MaterialFunctionMaterialLayer.h"
#include "Materials/MaterialFunctionMaterialLayerBlend.h"
#include "Factories/MaterialFunctionMaterialLayerFactory.h"
#include "Factories/MaterialFunctionMaterialLayerBlendFactory.h"
#include "Curves/CurveLinearColorAtlas.h"
#include "Curves/CurveLinearColor.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MaterialPropertyHelpers)
#define LOCTEXT_NAMESPACE "MaterialPropertyHelper"
FText FMaterialPropertyHelpers::LayerID = LOCTEXT("LayerID", "Layer Asset");
FText FMaterialPropertyHelpers::BlendID = LOCTEXT("BlendID", "Blend Asset");
FName FMaterialPropertyHelpers::LayerParamName = FName("Global Material Layers Parameter Values");
void SLayerHandle::Construct(const FArguments& InArgs)
{
OwningStack = InArgs._OwningStack;
ChildSlot
[
InArgs._Content.Widget
];
}
FReply SLayerHandle::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
{
TSharedPtr<FDragDropOperation> DragDropOp = CreateDragDropOperation(OwningStack.Pin());
if (DragDropOp.IsValid())
{
OwningStack.Pin()->OnLayerDragDetected();
return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef());
}
}
}
return FReply::Unhandled();
}
TSharedPtr<FLayerDragDropOp> SLayerHandle::CreateDragDropOperation(TSharedPtr<IDraggableItem> InOwningStack)
{
TSharedPtr<FLayerDragDropOp> Operation = MakeShareable(new FLayerDragDropOp(InOwningStack));
return Operation;
}
EVisibility FMaterialPropertyHelpers::ShouldShowExpression(UDEditorParameterValue* Parameter, UMaterialEditorInstanceConstant* MaterialEditorInstance, FGetShowHiddenParameters ShowHiddenDelegate)
{
bool bShowHidden = true;
ShowHiddenDelegate.ExecuteIfBound(bShowHidden);
bool bIsCooked = false;
if (MaterialEditorInstance->SourceInstance)
{
if (UMaterial* Material = MaterialEditorInstance->SourceInstance->GetMaterial())
{
bIsCooked = Material->GetPackage()->bIsCookedForEditor;
}
}
const bool bShouldShowExpression = bShowHidden || MaterialEditorInstance->VisibleExpressions.Contains(Parameter->ParameterInfo) || bIsCooked;
if (MaterialEditorInstance->bShowOnlyOverrides)
{
return (IsOverriddenExpression(Parameter) && bShouldShowExpression) ? EVisibility::Visible : EVisibility::Collapsed;
}
return bShouldShowExpression ? EVisibility::Visible: EVisibility::Collapsed;
}
void FMaterialPropertyHelpers::OnMaterialLayerAssetChanged(const struct FAssetData& InAssetData, int32 Index, EMaterialParameterAssociation MaterialType, TSharedPtr<class IPropertyHandle> InHandle, FMaterialLayersFunctions* InMaterialFunction)
{
const FScopedTransaction Transaction(LOCTEXT("SetLayerorBlendAsset", "Set Layer or Blend Asset"));
InHandle->NotifyPreChange();
const FName FilterTag = FName(TEXT("MaterialFunctionUsage"));
if (InAssetData.TagsAndValues.Contains(FilterTag) || InAssetData.AssetName == NAME_None)
{
UMaterialFunctionInterface* LayerFunction = Cast<UMaterialFunctionInterface>(InAssetData.GetAsset());
switch (MaterialType)
{
case EMaterialParameterAssociation::LayerParameter:
if (InMaterialFunction->Layers[Index] != LayerFunction)
{
InMaterialFunction->Layers[Index] = LayerFunction;
InMaterialFunction->UnlinkLayerFromParent(Index);
}
break;
case EMaterialParameterAssociation::BlendParameter:
if (InMaterialFunction->Blends[Index] != LayerFunction)
{
InMaterialFunction->Blends[Index] = LayerFunction;
InMaterialFunction->UnlinkLayerFromParent(Index + 1); // Blend indices are offset by 1, no blend for base layer
}
break;
default:
break;
}
}
InHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
}
bool FMaterialPropertyHelpers::FilterLayerAssets(const struct FAssetData& InAssetData, FMaterialLayersFunctions* LayerFunction, EMaterialParameterAssociation MaterialType, int32 Index)
{
bool bShouldAssetBeFilteredOut = true;
const FName FilterTag = FName(TEXT("MaterialFunctionUsage"));
FAssetDataTagMapSharedView::FFindTagResult MaterialFunctionUsage = InAssetData.TagsAndValues.FindTag(FilterTag);
if (MaterialFunctionUsage.IsSet())
{
static const UEnum* FunctionUsageEnum = StaticEnum<EMaterialFunctionUsage>();
if (!FunctionUsageEnum)
{
return bShouldAssetBeFilteredOut;
}
const FName BaseTag = FName(TEXT("Base"));
FAssetDataTagMapSharedView::FFindTagResult Base = InAssetData.TagsAndValues.FindTag(BaseTag);
FSoftObjectPath AssetPath = LayerFunction->LayerStackCache != nullptr && Base.IsSet() ? FSoftObjectPath(Base.GetValue()) : InAssetData.ToSoftObjectPath();
FString RightPath;
bool bLegacyShouldRestrict = false;
switch (MaterialType)
{
case EMaterialParameterAssociation::LayerParameter:
{
if (MaterialFunctionUsage.GetValue() == FunctionUsageEnum->GetNameStringByValue((int64)EMaterialFunctionUsage::Default)
|| MaterialFunctionUsage.GetValue() == FunctionUsageEnum->GetNameStringByValue((int64)EMaterialFunctionUsage::MaterialLayer))
{
//If we are using the new layer stack node, we only need to ensure whether the asset is usable.
if (LayerFunction->LayerStackCache)
{
bShouldAssetBeFilteredOut = !LayerFunction->LayerStackCache->AvailableLayerPaths.Contains(AssetPath);
}
else
{
bShouldAssetBeFilteredOut = false;
bLegacyShouldRestrict = LayerFunction->EditorOnly.RestrictToLayerRelatives[Index];
if (bLegacyShouldRestrict && LayerFunction->Layers[Index] != nullptr)
{
RightPath = LayerFunction->Layers[Index]->GetBaseFunction()->GetFName().ToString();
if (RightPath.IsEmpty())
{
RightPath = LayerFunction->Layers[Index]->GetFName().ToString();
}
}
}
}
break;
}
case EMaterialParameterAssociation::BlendParameter:
{
if (MaterialFunctionUsage.GetValue() == FunctionUsageEnum->GetNameStringByValue((int64)EMaterialFunctionUsage::MaterialLayerBlend))
{
if (LayerFunction->LayerStackCache)
{
bShouldAssetBeFilteredOut = !LayerFunction->LayerStackCache->AvailableBlendPaths.Contains(AssetPath);
}
else
{
bShouldAssetBeFilteredOut = false;
bLegacyShouldRestrict = LayerFunction->EditorOnly.RestrictToBlendRelatives[Index];
if (bLegacyShouldRestrict && LayerFunction->Blends[Index] != nullptr)
{
RightPath = LayerFunction->Blends[Index]->GetBaseFunction()->GetFName().ToString();
if (RightPath.IsEmpty())
{
RightPath = LayerFunction->Blends[Index]->GetFName().ToString();
}
}
}
}
break;
}
default:
break;
}
if (bLegacyShouldRestrict)
{
FString BaseString;
FString DiscardString;
FString CleanString;
if (Base.IsSet())
{
BaseString = Base.GetValue();
BaseString.Split(".", &DiscardString, &CleanString);
CleanString.Split("'", &CleanString, &DiscardString);
}
else
{
CleanString = InAssetData.AssetName.ToString();
}
bShouldAssetBeFilteredOut = CleanString != RightPath;
}
}
return bShouldAssetBeFilteredOut;
}
FReply FMaterialPropertyHelpers::OnClickedSaveNewMaterialInstance(UMaterialInterface* Parent, UObject* EditorObject)
{
const FString DefaultSuffix = TEXT("_Inst");
TArray<FEditorParameterGroup> ParameterGroups;
UMaterialEditorInstanceConstant* MaterialInstanceEditor = Cast<UMaterialEditorInstanceConstant>(EditorObject);
if (MaterialInstanceEditor)
{
ParameterGroups = MaterialInstanceEditor->ParameterGroups;
}
UMaterialEditorPreviewParameters* MaterialEditor = Cast<UMaterialEditorPreviewParameters>(EditorObject);
if (MaterialEditor)
{
ParameterGroups = MaterialEditor->ParameterGroups;
}
if (!MaterialEditor && !MaterialInstanceEditor)
{
return FReply::Unhandled();
}
if (Parent)
{
// Create an appropriate and unique name
FString Name;
FString PackageName;
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(Parent->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name);
UMaterialInstanceConstantFactoryNew* Factory = NewObject<UMaterialInstanceConstantFactoryNew>();
Factory->InitialParent = Parent;
UObject* Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialInstanceConstant::StaticClass(), Factory);
UMaterialInstanceConstant* ChildInstance = Cast<UMaterialInstanceConstant>(Child);
CopyMaterialToInstance(ChildInstance, ParameterGroups);
}
return FReply::Handled();
}
void FMaterialPropertyHelpers::CopyMaterialToInstance(UMaterialInstanceConstant* ChildInstance, TArray<FEditorParameterGroup> &ParameterGroups)
{
if (ChildInstance)
{
if (ChildInstance->IsTemplate(RF_ClassDefaultObject) == false)
{
ChildInstance->MarkPackageDirty();
ChildInstance->ClearParameterValuesEditorOnly();
FMaterialInstanceParameterUpdateContext UpdateContext(ChildInstance);
//propagate changes to the base material so the instance will be updated if it has a static permutation resource
for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++)
{
FEditorParameterGroup& Group = ParameterGroups[GroupIdx];
for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++)
{
UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx];
if (Parameter && Parameter->bOverride)
{
FMaterialParameterMetadata ParameterResult;
if (Parameter->GetValue(ParameterResult))
{
UpdateContext.SetParameterValueEditorOnly(Parameter->ParameterInfo, ParameterResult);
}
// This is called to initialize newly created child MIs
// Don't need to copy layers from parent, since they will not initially be changed from parent values
}
}
}
}
}
}
void FMaterialPropertyHelpers::TransitionAndCopyParameters(UMaterialInstanceConstant* ChildInstance, TArray<FEditorParameterGroup> &ParameterGroups, bool bForceCopy)
{
if (ChildInstance)
{
if (ChildInstance->IsTemplate(RF_ClassDefaultObject) == false)
{
ChildInstance->MarkPackageDirty();
ChildInstance->ClearParameterValuesEditorOnly();
//propagate changes to the base material so the instance will be updated if it has a static permutation resource
//FStaticParameterSet NewStaticParameters;
FMaterialInstanceParameterUpdateContext UpdateContext(ChildInstance);
for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++)
{
FEditorParameterGroup& Group = ParameterGroups[GroupIdx];
for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++)
{
UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx];
if (Parameter && (Parameter->bOverride || bForceCopy))
{
FMaterialParameterMetadata EditorValue;
if (Parameter->GetValue(EditorValue))
{
FMaterialParameterInfo TransitionedScalarInfo = FMaterialParameterInfo();
TransitionedScalarInfo.Name = Parameter->ParameterInfo.Name;
UpdateContext.SetParameterValueEditorOnly(TransitionedScalarInfo, EditorValue);
}
}
}
}
}
}
}
FReply FMaterialPropertyHelpers::OnClickedSaveNewFunctionInstance(class UMaterialFunctionInterface* Object, class UMaterialInterface* PreviewMaterial, UObject* EditorObject)
{
const FString DefaultSuffix = TEXT("_Inst");
TArray<FEditorParameterGroup> ParameterGroups;
UMaterialEditorInstanceConstant* MaterialInstanceEditor = Cast<UMaterialEditorInstanceConstant>(EditorObject);
UMaterialInterface* FunctionPreviewMaterial = nullptr;
if (MaterialInstanceEditor)
{
ParameterGroups = MaterialInstanceEditor->ParameterGroups;
FunctionPreviewMaterial = MaterialInstanceEditor->SourceInstance;
}
UMaterialEditorPreviewParameters* MaterialEditor = Cast<UMaterialEditorPreviewParameters>(EditorObject);
if (MaterialEditor)
{
ParameterGroups = MaterialEditor->ParameterGroups;
FunctionPreviewMaterial = PreviewMaterial;
}
if (!MaterialEditor && !MaterialInstanceEditor)
{
return FReply::Unhandled();
}
if (Object)
{
UMaterial* EditedMaterial = Cast<UMaterial>(FunctionPreviewMaterial);
if (EditedMaterial)
{
UMaterialInstanceConstant* ProxyMaterial = NewObject<UMaterialInstanceConstant>(GetTransientPackage(), NAME_None, RF_Transactional);
ProxyMaterial->SetParentEditorOnly(EditedMaterial);
ProxyMaterial->PreEditChange(NULL);
ProxyMaterial->PostEditChange();
CopyMaterialToInstance(ProxyMaterial, ParameterGroups);
FunctionPreviewMaterial = ProxyMaterial;
}
// Create an appropriate and unique name
FString Name;
FString PackageName;
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(Object->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name);
UObject* Child;
if (Object->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer)
{
UMaterialFunctionMaterialLayerInstanceFactory* LayerFactory = NewObject<UMaterialFunctionMaterialLayerInstanceFactory>();
LayerFactory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionMaterialLayerInstance::StaticClass(), LayerFactory);
}
else if (Object->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend)
{
UMaterialFunctionMaterialLayerBlendInstanceFactory* BlendFactory = NewObject<UMaterialFunctionMaterialLayerBlendInstanceFactory>();
BlendFactory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionMaterialLayerBlendInstance::StaticClass(), BlendFactory);
}
else
{
UMaterialFunctionInstanceFactory* Factory = NewObject<UMaterialFunctionInstanceFactory>();
Factory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionInstance::StaticClass(), Factory);
}
UMaterialFunctionInstance* ChildInstance = Cast<UMaterialFunctionInstance>(Child);
if (ChildInstance)
{
if (ChildInstance->IsTemplate(RF_ClassDefaultObject) == false)
{
ChildInstance->MarkPackageDirty();
ChildInstance->SetParent(Object);
UMaterialInstance* EditedInstance = Cast<UMaterialInstance>(FunctionPreviewMaterial);
if (EditedInstance)
{
ChildInstance->ScalarParameterValues = EditedInstance->ScalarParameterValues;
ChildInstance->VectorParameterValues = EditedInstance->VectorParameterValues;
ChildInstance->DoubleVectorParameterValues = EditedInstance->DoubleVectorParameterValues;
ChildInstance->TextureParameterValues = EditedInstance->TextureParameterValues;
ChildInstance->TextureCollectionParameterValues = EditedInstance->TextureCollectionParameterValues;
ChildInstance->RuntimeVirtualTextureParameterValues = EditedInstance->RuntimeVirtualTextureParameterValues;
ChildInstance->SparseVolumeTextureParameterValues = EditedInstance->SparseVolumeTextureParameterValues;
ChildInstance->FontParameterValues = EditedInstance->FontParameterValues;
const FStaticParameterSet& StaticParameters = EditedInstance->GetStaticParameters();
ChildInstance->StaticSwitchParameterValues = StaticParameters.StaticSwitchParameters;
ChildInstance->StaticComponentMaskParameterValues = StaticParameters.EditorOnly.StaticComponentMaskParameters;
}
}
}
}
return FReply::Handled();
}
FReply FMaterialPropertyHelpers::OnClickedSaveNewLayerInstance(class UMaterialFunctionInterface* Object, TSharedPtr<FSortedParamData> InSortedData)
{
const FString DefaultSuffix = TEXT("_Inst");
TArray<FEditorParameterGroup> ParameterGroups;
UMaterialInterface* FunctionPreviewMaterial = nullptr;
if (Object)
{
FunctionPreviewMaterial = Object->GetPreviewMaterial();
}
for (TSharedPtr<FSortedParamData> Group : InSortedData->Children)
{
FEditorParameterGroup DuplicatedGroup = FEditorParameterGroup();
DuplicatedGroup.GroupAssociation = Group->Group.GroupAssociation;
DuplicatedGroup.GroupName = Group->Group.GroupName;
DuplicatedGroup.GroupSortPriority = Group->Group.GroupSortPriority;
for (UDEditorParameterValue* Parameter : Group->Group.Parameters)
{
if (Parameter->ParameterInfo.Index == InSortedData->ParameterInfo.Index)
{
DuplicatedGroup.Parameters.Add(Parameter);
}
}
ParameterGroups.Add(DuplicatedGroup);
}
if (Object)
{
UMaterialInterface* EditedMaterial = FunctionPreviewMaterial;
if (EditedMaterial)
{
UMaterialInstanceConstant* ProxyMaterial = NewObject<UMaterialInstanceConstant>(GetTransientPackage(), NAME_None, RF_Transactional);
ProxyMaterial->SetParentEditorOnly(EditedMaterial);
ProxyMaterial->PreEditChange(NULL);
ProxyMaterial->PostEditChange();
TransitionAndCopyParameters(ProxyMaterial, ParameterGroups);
FunctionPreviewMaterial = ProxyMaterial;
}
// Create an appropriate and unique name
FString Name;
FString PackageName;
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(Object->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name);
UObject* Child;
if (Object->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer)
{
UMaterialFunctionMaterialLayerInstanceFactory* LayerFactory = NewObject<UMaterialFunctionMaterialLayerInstanceFactory>();
LayerFactory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionMaterialLayerInstance::StaticClass(), LayerFactory);
}
else if (Object->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend)
{
UMaterialFunctionMaterialLayerBlendInstanceFactory* BlendFactory = NewObject<UMaterialFunctionMaterialLayerBlendInstanceFactory>();
BlendFactory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionMaterialLayerBlendInstance::StaticClass(), BlendFactory);
}
else
{
UMaterialFunctionInstanceFactory* Factory = NewObject<UMaterialFunctionInstanceFactory>();
Factory->InitialParent = Object;
Child = AssetToolsModule.Get().CreateAssetWithDialog(Name, FPackageName::GetLongPackagePath(PackageName), UMaterialFunctionInstance::StaticClass(), Factory);
}
UMaterialFunctionInstance* ChildInstance = Cast<UMaterialFunctionInstance>(Child);
if (ChildInstance)
{
if (ChildInstance->IsTemplate(RF_ClassDefaultObject) == false)
{
ChildInstance->MarkPackageDirty();
ChildInstance->SetParent(Object);
UMaterialInstance* EditedInstance = Cast<UMaterialInstance>(FunctionPreviewMaterial);
if (EditedInstance)
{
ChildInstance->ScalarParameterValues = EditedInstance->ScalarParameterValues;
ChildInstance->VectorParameterValues = EditedInstance->VectorParameterValues;
ChildInstance->DoubleVectorParameterValues = EditedInstance->DoubleVectorParameterValues;
ChildInstance->TextureParameterValues = EditedInstance->TextureParameterValues;
ChildInstance->TextureCollectionParameterValues = EditedInstance->TextureCollectionParameterValues;
ChildInstance->RuntimeVirtualTextureParameterValues = EditedInstance->RuntimeVirtualTextureParameterValues;
ChildInstance->SparseVolumeTextureParameterValues = EditedInstance->SparseVolumeTextureParameterValues;
ChildInstance->FontParameterValues = EditedInstance->FontParameterValues;
const FStaticParameterSet& StaticParameters = EditedInstance->GetStaticParameters();
ChildInstance->StaticSwitchParameterValues = StaticParameters.StaticSwitchParameters;
ChildInstance->StaticComponentMaskParameterValues = StaticParameters.EditorOnly.StaticComponentMaskParameters;
}
}
}
}
return FReply::Handled();
}
bool FMaterialPropertyHelpers::ShouldCreatePropertyRowForParameter(UDEditorParameterValue* Parameter)
{
if (UDEditorMaterialLayersParameterValue* LayersParam = Cast<UDEditorMaterialLayersParameterValue>(Parameter))
{
// No widget for layers
return false;
}
else if (UDEditorParameterCollectionParameterValue* ParamCollectionParam = Cast<UDEditorParameterCollectionParameterValue>(Parameter))
{
// No widget for MPCs, special override category in MI
return false;
}
else if (FMaterialPropertyHelpers::UsesCustomPrimitiveData(Parameter))
{
// No widget for CPD
return false;
}
return true;
}
bool FMaterialPropertyHelpers::ShouldCreateChildPropertiesForParameter(UDEditorParameterValue* Parameter)
{
if (UDEditorStaticComponentMaskParameterValue* CompMaskParam = Cast<UDEditorStaticComponentMaskParameterValue>(Parameter))
{
// Static component mask has a checkbox for each channel
return false;
}
return true;
}
void FMaterialPropertyHelpers::ConfigurePropertyRowForParameter(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, UMaterialEditorInstanceConstant* MaterialEditorInstance, const FMaterialParameterPropertyRowOverrides& RowOverrides /*= FMaterialParameterPropertyRowOverrides()*/)
{
// Set name and tooltip
PropertyRow
.DisplayName(FText::FromName(Parameter->ParameterInfo.Name))
.ToolTip(FMaterialPropertyHelpers::GetParameterTooltip(Parameter, MaterialEditorInstance))
.OverrideResetToDefault(FMaterialPropertyHelpers::CreateResetToDefaultOverride(Parameter, MaterialEditorInstance));
// Set Visibility
if (RowOverrides.Visibility.IsSet())
{
PropertyRow.Visibility(RowOverrides.Visibility);
}
else
{
PropertyRow.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateStatic(&FMaterialPropertyHelpers::ShouldShowExpression, Parameter, MaterialEditorInstance, RowOverrides.ShowHiddenDelegate)));
}
// Set EditCondition
TAttribute<bool> EditConditionValue = RowOverrides.EditConditionValue;
if (!EditConditionValue.IsSet())
{
EditConditionValue = TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateStatic(&FMaterialPropertyHelpers::IsOverriddenExpression, Parameter));
}
FOnBooleanValueChanged OnEditConditionValueChanged;
if (RowOverrides.OnEditConditionValueChanged.IsSet())
{
OnEditConditionValueChanged = RowOverrides.OnEditConditionValueChanged.GetValue();
}
else
{
OnEditConditionValueChanged = FOnBooleanValueChanged::CreateStatic(&FMaterialPropertyHelpers::OnOverrideParameter, Parameter, MaterialEditorInstance);
}
PropertyRow.EditCondition(EditConditionValue, OnEditConditionValueChanged);
}
void FMaterialPropertyHelpers::SetPropertyRowParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, UMaterialEditorParameters* MaterialEditorParameters)
{
bool bUseDefaultWidget = false;
// Set custom widgets if this parameter needs one. Otherwise, the default widget for the property will be used
if (UDEditorStaticComponentMaskParameterValue* CompMaskParam = Cast<UDEditorStaticComponentMaskParameterValue>(Parameter))
{
// Custom widget for static component masks
SetPropertyRowMaskParameterWidget(PropertyRow, Parameter, ParameterValueProperty);
}
else if (UDEditorTextureParameterValue* TextureParam = Cast<UDEditorTextureParameterValue>(Parameter))
{
// Custom widget for textures
SetPropertyRowTextureParameterWidget(PropertyRow, Parameter, ParameterValueProperty, MaterialEditorParameters);
}
else if (UDEditorVectorParameterValue* VectorParam = Cast<UDEditorVectorParameterValue>(Parameter))
{
// Don't display custom primitive data parameters in the details panel.
// This data is pulled from the primitive and can't be changed on the material.
if (VectorParam->bUseCustomPrimitiveData)
{
return;
}
// Set channel names if specified
static const FName Red("R");
static const FName Green("G");
static const FName Blue("B");
static const FName Alpha("A");
if (!VectorParam->ChannelNames.R.IsEmpty())
{
ParameterValueProperty->GetChildHandle(Red)->SetPropertyDisplayName(VectorParam->ChannelNames.R);
}
if (!VectorParam->ChannelNames.G.IsEmpty())
{
ParameterValueProperty->GetChildHandle(Green)->SetPropertyDisplayName(VectorParam->ChannelNames.G);
}
if (!VectorParam->ChannelNames.B.IsEmpty())
{
ParameterValueProperty->GetChildHandle(Blue)->SetPropertyDisplayName(VectorParam->ChannelNames.B);
}
if (!VectorParam->ChannelNames.A.IsEmpty())
{
ParameterValueProperty->GetChildHandle(Alpha)->SetPropertyDisplayName(VectorParam->ChannelNames.A);
}
// Custom widget for channel masks
if (VectorParam->bIsUsedAsChannelMask)
{
SetPropertyRowVectorChannelMaskParameterWidget(PropertyRow, Parameter, ParameterValueProperty, MaterialEditorParameters);
}
else
{
bUseDefaultWidget = true;
}
}
else if (UDEditorScalarParameterValue* ScalarParam = Cast<UDEditorScalarParameterValue>(Parameter))
{
// Don't display custom primitive data parameters in the details panel.
// This data is pulled from the primitive and can't be changed on the material.
if (ScalarParam->bUseCustomPrimitiveData)
{
return;
}
// Atlas position params handled separately
if (ScalarParam->AtlasData.bIsUsedAsAtlasPosition)
{
SetPropertyRowScalarAtlasPositionParameterWidget(PropertyRow, Parameter, ParameterValueProperty, MaterialEditorParameters);
}
else
{
// Configure slider if specified
if (ScalarParam->SliderMax > ScalarParam->SliderMin)
{
ParameterValueProperty->SetInstanceMetaData("UIMin", FString::Printf(TEXT("%f"), ScalarParam->SliderMin));
ParameterValueProperty->SetInstanceMetaData("UIMax", FString::Printf(TEXT("%f"), ScalarParam->SliderMax));
}
// Custom widget for enums
TSoftObjectPtr<UObject> Enumeration;
if (!ScalarParam->Enumeration.IsNull())
{
Enumeration = ScalarParam->Enumeration;
}
else if (ScalarParam->EnumerationIndex >= 0)
{
if (UMaterialEditorInstanceConstant* MaterialEditorInstance = Cast<UMaterialEditorInstanceConstant>(MaterialEditorParameters))
{
Enumeration = MaterialEditorInstance->GetEnumerationObject(ScalarParam->EnumerationIndex);
}
}
if (!Enumeration.IsNull())
{
SetPropertyRowScalarEnumerationParameterWidget(PropertyRow, Parameter, ParameterValueProperty, Enumeration);
}
else
{
bUseDefaultWidget = true;
}
}
}
else
{
bUseDefaultWidget = true;
}
// Fix up property name if not using a custom widget. Otherwise, the name will display as "Parameter Value"
if (bUseDefaultWidget)
{
if (FDetailWidgetDecl* NameWidgetPtr = PropertyRow.CustomNameWidget())
{
// Already customized, just set the name widget
(*NameWidgetPtr)
[
ParameterValueProperty->CreatePropertyNameWidget()
];
}
else
{
// Not customized, grab default widgets and customize
TSharedPtr<SWidget> NameWidget;
TSharedPtr<SWidget> ValueWidget;
PropertyRow.GetDefaultWidgets(NameWidget, ValueWidget);
const bool bShowChildren = true;
PropertyRow.CustomWidget(bShowChildren)
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
[
ValueWidget.ToSharedRef()
];
}
}
}
void FMaterialPropertyHelpers::SetPropertyRowMaskParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty)
{
TSharedPtr<SHorizontalBox> ValueHorizontalBox;
// Set up the custom widget
PropertyRow.CustomWidget()
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(200.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew(ValueHorizontalBox, SHorizontalBox)
]
];
// Add checkboxes for each mask channel
auto AddChannelWidgets =
[&ValueHorizontalBox](TSharedPtr<IPropertyHandle> MaskProperty)
{
FMargin NameWidgetPadding = FMargin(10.0f, 0.0f, 2.0f, 0.0f);
if (ValueHorizontalBox->NumSlots() == 0)
{
// No left padding on the first widget
NameWidgetPadding.Left = 0.0f;
}
ValueHorizontalBox->AddSlot()
.HAlign(HAlign_Left)
.Padding(NameWidgetPadding)
.AutoWidth()
[
MaskProperty->CreatePropertyNameWidget()
];
ValueHorizontalBox->AddSlot()
.HAlign(HAlign_Left)
.AutoWidth()
[
MaskProperty->CreatePropertyValueWidget()
];
};
AddChannelWidgets(ParameterValueProperty->GetChildHandle("R"));
AddChannelWidgets(ParameterValueProperty->GetChildHandle("G"));
AddChannelWidgets(ParameterValueProperty->GetChildHandle("B"));
AddChannelWidgets(ParameterValueProperty->GetChildHandle("A"));
}
void FMaterialPropertyHelpers::SetPropertyRowVectorChannelMaskParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, UMaterialEditorParameters* MaterialEditorParameters)
{
// Combo box hooks for converting between our "enum" and colors
FOnGetPropertyComboBoxStrings GetMaskStrings = FOnGetPropertyComboBoxStrings::CreateStatic(&FMaterialPropertyHelpers::GetVectorChannelMaskComboBoxStrings);
FOnGetPropertyComboBoxValue GetMaskValue = FOnGetPropertyComboBoxValue::CreateStatic(&FMaterialPropertyHelpers::GetVectorChannelMaskValue, Parameter);
FOnPropertyComboBoxValueSelected SetMaskValue = FOnPropertyComboBoxValueSelected::CreateStatic(&FMaterialPropertyHelpers::SetVectorChannelMaskValue, ParameterValueProperty, Parameter, (UObject*)MaterialEditorParameters);
// Widget replaces color picker with combo box
const bool bShowChildren = true;
PropertyRow.CustomWidget(bShowChildren)
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(200.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.AutoWidth()
[
PropertyCustomizationHelpers::MakePropertyComboBox(ParameterValueProperty, GetMaskStrings, GetMaskValue, SetMaskValue)
]
]
];
}
void FMaterialPropertyHelpers::SetPropertyRowScalarAtlasPositionParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, UMaterialEditorParameters* MaterialEditorParameters)
{
const FText ParameterName = FText::FromName(Parameter->ParameterInfo.Name);
UDEditorScalarParameterValue* AtlasParameter = Cast<UDEditorScalarParameterValue>(Parameter);
// Create the value widget first for GetDesiredWidth
TSharedRef<SObjectPropertyEntryBox> ObjectPropertyEntryBox = SNew(SObjectPropertyEntryBox)
.ObjectPath_UObject(AtlasParameter, &UDEditorScalarParameterValue::GetAtlasCurvePath)
.AllowedClass(UCurveLinearColor::StaticClass())
.NewAssetFactories(TArray<UFactory*>())
.DisplayThumbnail(true)
.ThumbnailPool(UThumbnailManager::Get().GetSharedThumbnailPool())
.OnShouldFilterAsset(FOnShouldFilterAsset::CreateStatic(&FMaterialPropertyHelpers::OnShouldFilterCurveAsset, AtlasParameter->AtlasData.Atlas))
.OnShouldSetAsset(FOnShouldSetAsset::CreateStatic(&FMaterialPropertyHelpers::OnShouldSetCurveAsset, AtlasParameter->AtlasData.Atlas))
.OnObjectChanged(FOnSetObject::CreateStatic(&FMaterialPropertyHelpers::SetPositionFromCurveAsset, AtlasParameter->AtlasData.Atlas, AtlasParameter, ParameterValueProperty, (UObject*)MaterialEditorParameters))
.DisplayCompactSize(true);
float MinDesiredWidth, MaxDesiredWidth;
ObjectPropertyEntryBox->GetDesiredWidth(MinDesiredWidth, MaxDesiredWidth);
// Set up the custom widget
PropertyRow.CustomWidget()
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(MinDesiredWidth)
.MaxDesiredWidth(MaxDesiredWidth)
[
ObjectPropertyEntryBox
];
}
void FMaterialPropertyHelpers::SetPropertyRowScalarEnumerationParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, const TSoftObjectPtr<UObject>& SoftEnumeration)
{
if (SoftEnumeration.IsNull())
{
return;
}
// Scalar parameters can present an enumeration combo box.
PropertyRow.CustomWidget()
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
[
FMaterialPropertyHelpers::CreateScalarEnumerationParameterValueWidget(SoftEnumeration, ParameterValueProperty)
];
}
void FMaterialPropertyHelpers::SetPropertyRowTextureParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, UMaterialEditorParameters* MaterialEditorParameters)
{
TWeakObjectPtr<UMaterialExpressionTextureSampleParameter> SamplerExpression;
// Cache the SamplerExpression for asset filtering below
UDEditorTextureParameterValue* TextureParam = Cast<UDEditorTextureParameterValue>(Parameter);
if (TextureParam)
{
UMaterial* Material = Cast<UMaterial>(MaterialEditorParameters->GetMaterialInterface());
if (Material != nullptr)
{
UMaterialExpressionTextureSampleParameter* Expression = Material->FindExpressionByGUID<UMaterialExpressionTextureSampleParameter>(TextureParam->ExpressionId);
if (Expression != nullptr)
{
SamplerExpression = Expression;
}
}
}
// Create the value widget first for GetDesiredWidth
TSharedRef<SObjectPropertyEntryBox> ObjectPropertyEntryBox = SNew(SObjectPropertyEntryBox)
.PropertyHandle(ParameterValueProperty)
.AllowedClass(UTexture::StaticClass())
.ThumbnailPool(UThumbnailManager::Get().GetSharedThumbnailPool())
.OnShouldFilterAsset_Lambda([SamplerExpression](const FAssetData& AssetData)
{
if (SamplerExpression.Get())
{
bool VirtualTextured = false;
AssetData.GetTagValue<bool>("VirtualTextureStreaming", VirtualTextured);
bool ExpressionIsVirtualTextured = IsVirtualSamplerType(SamplerExpression->SamplerType);
return VirtualTextured != ExpressionIsVirtualTextured;
}
else
{
return false;
}
});
float MinDesiredWidth, MaxDesiredWidth;
ObjectPropertyEntryBox->GetDesiredWidth(MinDesiredWidth, MaxDesiredWidth);
TSharedPtr<SVerticalBox> NameVerticalBox;
// Set up the custom widget
PropertyRow.CustomWidget()
.NameContent()
[
SAssignNew(NameVerticalBox, SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
]
.ValueContent()
.MinDesiredWidth(MinDesiredWidth)
.MaxDesiredWidth(MaxDesiredWidth)
[
ObjectPropertyEntryBox
];
// Add extra widgets for specified channel names, if any
auto AddChannelNameWidgets =
[&NameVerticalBox](const FText& ChannelName, FName ChannelKey)
{
if (ChannelName.IsEmpty())
{
return;
}
NameVerticalBox->AddSlot()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(20.0, 2.0, 4.0, 2.0)
[
SNew(STextBlock)
.Text(FText::FromName(ChannelKey))
.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont")))
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.Padding(4.0, 2.0)
[
SNew(STextBlock)
.Text(ChannelName)
.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
];
};
static const FName Red("R");
static const FName Green("G");
static const FName Blue("B");
static const FName Alpha("A");
AddChannelNameWidgets(TextureParam->ChannelNames.R, Red);
AddChannelNameWidgets(TextureParam->ChannelNames.G, Green);
AddChannelNameWidgets(TextureParam->ChannelNames.B, Blue);
AddChannelNameWidgets(TextureParam->ChannelNames.A, Alpha);
}
void FMaterialPropertyHelpers::SetPropertyRowParameterCollectionParameterWidget(IDetailPropertyRow& PropertyRow, UDEditorParameterValue* Parameter, TSharedPtr<IPropertyHandle> ParameterValueProperty, class UMaterialParameterCollection* BaseParameterCollection, TArray<TSharedRef<IPropertyHandle>>&& AllPropertyHandles)
{
// Display the parameter collection's name on the widget.
ParameterValueProperty->SetPropertyDisplayName(FText::FromName(BaseParameterCollection->GetFName()));
// Create a delegate to invoke when the property value changes.
TDelegate<void(const FPropertyChangedEvent&)> OnPropertyValueChangedDelegate = TDelegate<void(const FPropertyChangedEvent&)>::CreateWeakLambda(BaseParameterCollection,
[BaseParameterCollection, PropertyHandles = MoveTemp(AllPropertyHandles)](const FPropertyChangedEvent& PropertyChangedEvent)
{
// Determine if this is the final setting of the property value.
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet)
{
return;
}
if (PropertyHandles.IsEmpty())
{
return;
}
// Get the new value from the first property handle.
UObject* Value = nullptr;
if (PropertyHandles[0]->GetValue(Value) != FPropertyAccess::Success)
{
return;
}
// Verify that the new value is valid.
if (auto* Collection = Cast<UMaterialParameterCollection>(Value))
{
if (Collection->IsInstanceOf(BaseParameterCollection->StateId))
{
// Set the same value on all of the collection's referencing parameters.
for (int32 PropertyHandleIndex = 1; PropertyHandleIndex < PropertyHandles.Num(); ++PropertyHandleIndex)
{
PropertyHandles[PropertyHandleIndex]->SetValue(Value);
}
return;
}
}
// Reset the value to the default parameter collection.
Value = BaseParameterCollection;
PropertyHandles[0]->SetValue(Value);
});
// Invoke the delegate when the property value or one of its child property values changes.
ParameterValueProperty->SetOnPropertyValueChangedWithData(OnPropertyValueChangedDelegate);
ParameterValueProperty->SetOnChildPropertyValueChangedWithData(OnPropertyValueChangedDelegate);
FSoftObjectPath BaseParameterCollectionPath = BaseParameterCollection;
// Create the value widget first for GetDesiredWidth
TSharedRef<SObjectPropertyEntryBox> ObjectPropertyEntryBox = SNew(SObjectPropertyEntryBox)
.PropertyHandle(ParameterValueProperty)
.AllowedClass(UMaterialParameterCollection::StaticClass())
.ThumbnailPool(UThumbnailManager::Get().GetSharedThumbnailPool())
.OnShouldFilterAsset_Lambda([BaseParameterCollectionPath](const FAssetData& AssetData)
{
// Allow the base MPC itself
if (AssetData.GetSoftObjectPath() == BaseParameterCollectionPath)
{
return false;
}
IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked();
// Traverse up the hierarchy of UMaterialParameterCollection::Base until we find the base material's MPC
FSoftObjectPath ParentAssetPath = FSoftObjectPath(AssetData.GetTagValueRef<FString>(UMaterialParameterCollection::GetBaseParameterCollectionMemberName()));
while (!ParentAssetPath.IsNull())
{
if (ParentAssetPath == BaseParameterCollectionPath)
{
return false;
}
FAssetData ParentAssetData = AssetRegistry.GetAssetByObjectPath(ParentAssetPath);
ParentAssetPath = FSoftObjectPath(ParentAssetData.GetTagValueRef<FString>(UMaterialParameterCollection::GetBaseParameterCollectionMemberName()));
}
// No matching MPC in hierarchy, filter this MPC out
return true;
});
float MinDesiredWidth, MaxDesiredWidth;
ObjectPropertyEntryBox->GetDesiredWidth(MinDesiredWidth, MaxDesiredWidth);
// Set up the custom widget
PropertyRow.CustomWidget()
.NameContent()
[
ParameterValueProperty->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(MinDesiredWidth)
.MaxDesiredWidth(MaxDesiredWidth)
[
ObjectPropertyEntryBox
];
}
bool FMaterialPropertyHelpers::IsOverriddenExpression(UDEditorParameterValue* Parameter)
{
return Parameter && Parameter->bOverride != 0;
}
ECheckBoxState FMaterialPropertyHelpers::IsOverriddenExpressionCheckbox(UDEditorParameterValue* Parameter)
{
return IsOverriddenExpression(Parameter) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
bool FMaterialPropertyHelpers::UsesCustomPrimitiveData(UDEditorParameterValue* Parameter)
{
if (UDEditorScalarParameterValue* ScalarParameter = Cast<UDEditorScalarParameterValue>(Parameter))
{
return ScalarParameter->bUseCustomPrimitiveData;
}
else if (UDEditorVectorParameterValue* VectorParameter = Cast<UDEditorVectorParameterValue>(Parameter))
{
return VectorParameter->bUseCustomPrimitiveData;
}
return false;
}
void FMaterialPropertyHelpers::OnOverrideParameter(bool NewValue, class UDEditorParameterValue* Parameter, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
// If the material instance disallows the creation of new shader permutations, prevent overriding the static parameter.
if (NewValue && Parameter->IsStaticParameter() && MaterialEditorInstance->SourceInstance->bDisallowStaticParameterPermutations)
{
return;
}
const FScopedTransaction Transaction( LOCTEXT( "OverrideParameter", "Override Parameter" ) );
Parameter->Modify();
Parameter->bOverride = NewValue;
// Fire off a dummy event to the material editor instance, so it knows to update the material, then refresh the viewports.
FPropertyChangedEvent OverrideEvent(NULL);
MaterialEditorInstance->PostEditChangeProperty( OverrideEvent );
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
FEditorSupportDelegates::UpdateUI.Broadcast();
}
FText FMaterialPropertyHelpers::GetParameterExpressionDescription(UDEditorParameterValue* Parameter, UObject* MaterialEditorInstance)
{
return FText::FromString(Parameter->Description);
}
FText FMaterialPropertyHelpers::GetParameterTooltip(UDEditorParameterValue* Parameter, UObject* MaterialEditorInstance)
{
const FText AssetPath = FText::FromString(Parameter->AssetPath);
FText TooltipText;
// If the material instance disallows the creation of new shader permutations, prevent overriding the static parameter.
if (Parameter->IsStaticParameter() && static_cast<UMaterialEditorInstanceConstant*>(MaterialEditorInstance)->SourceInstance->bDisallowStaticParameterPermutations)
{
return FText::FromString(TEXT("This material instance parent restricts the creation of new shader permutations. Overriding this parameter would result in the generation of additional shader permutations."));
}
else if (!Parameter->Description.IsEmpty())
{
TooltipText = FText::Format(LOCTEXT("ParameterInfoDescAndLocation", "{0} \nFound in: {1}"), FText::FromString(Parameter->Description), AssetPath);
}
else
{
TooltipText = FText::Format(LOCTEXT("ParameterInfoLocationOnly", "Found in: {0}"), AssetPath);
}
return TooltipText;
}
FResetToDefaultOverride FMaterialPropertyHelpers::CreateResetToDefaultOverride(UDEditorParameterValue* Parameter, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
UDEditorScalarParameterValue* ScalarParam = Cast<UDEditorScalarParameterValue>(Parameter);
if (ScalarParam && ScalarParam->AtlasData.bIsUsedAsAtlasPosition)
{
TAttribute<bool> IsResetVisible = TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateStatic(&FMaterialPropertyHelpers::ShouldShowResetToDefault, Parameter, MaterialEditorInstance));
FSimpleDelegate ResetHandler = FSimpleDelegate::CreateStatic(&FMaterialPropertyHelpers::ResetCurveToDefault, Parameter, MaterialEditorInstance);
return FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
}
TAttribute<bool> IsResetVisible = TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateStatic(&FMaterialPropertyHelpers::ShouldShowResetToDefault, Parameter, MaterialEditorInstance));
FSimpleDelegate ResetHandler = FSimpleDelegate::CreateStatic(&FMaterialPropertyHelpers::ResetToDefault, Parameter, MaterialEditorInstance);
return FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
}
void FMaterialPropertyHelpers::ResetToDefault(class UDEditorParameterValue* Parameter, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
const FScopedTransaction Transaction( LOCTEXT( "ResetToDefault", "Reset To Default" ) );
Parameter->Modify();
FMaterialParameterMetadata EditorValue;
if (Parameter->GetValue(EditorValue))
{
FMaterialParameterMetadata DefaultValue;
if (MaterialEditorInstance->SourceInstance->GetParameterDefaultValue(EditorValue.Value.Type, Parameter->ParameterInfo, DefaultValue))
{
Parameter->SetValue(DefaultValue.Value);
MaterialEditorInstance->CopyToSourceInstance();
}
}
}
void FMaterialPropertyHelpers::ResetLayerAssetToDefault(UDEditorParameterValue* InParameter, TEnumAsByte<EMaterialParameterAssociation> InAssociation, int32 Index, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
const FScopedTransaction Transaction(LOCTEXT("ResetToDefault", "Reset To Default"));
InParameter->Modify();
UDEditorMaterialLayersParameterValue* LayersParam = Cast<UDEditorMaterialLayersParameterValue>(InParameter);
if (LayersParam)
{
FMaterialLayersFunctions LayersValue;
if (MaterialEditorInstance->Parent->GetMaterialLayers(LayersValue))
{
FMaterialLayersFunctions StoredValue = LayersParam->ParameterValue;
if (InAssociation == EMaterialParameterAssociation::BlendParameter)
{
if (Index < LayersValue.Blends.Num())
{
StoredValue.Blends[Index] = LayersValue.Blends[Index];
}
else
{
StoredValue.Blends[Index] = nullptr;
MaterialEditorInstance->StoredBlendPreviews[Index] = nullptr;
}
}
else if (InAssociation == EMaterialParameterAssociation::LayerParameter)
{
if (Index < LayersValue.Layers.Num())
{
StoredValue.Layers[Index] = LayersValue.Layers[Index];
}
else
{
StoredValue.Layers[Index] = nullptr;
MaterialEditorInstance->StoredLayerPreviews[Index] = nullptr;
}
}
LayersParam->ParameterValue = StoredValue;
}
}
FPropertyChangedEvent OverrideEvent(NULL);
MaterialEditorInstance->PostEditChangeProperty(OverrideEvent);
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
FEditorSupportDelegates::UpdateUI.Broadcast();
}
bool FMaterialPropertyHelpers::ShouldLayerAssetShowResetToDefault(TSharedPtr<FSortedParamData> InParameterData, UMaterialInstanceConstant* InMaterialInstance)
{
if (!InParameterData->Parameter)
{
return false;
}
TArray<class UMaterialFunctionInterface*> StoredAssets;
TArray<class UMaterialFunctionInterface*> ParentAssets;
int32 Index = InParameterData->ParameterInfo.Index;
UDEditorMaterialLayersParameterValue* LayersParam = Cast<UDEditorMaterialLayersParameterValue>(InParameterData->Parameter);
if (LayersParam)
{
FMaterialLayersFunctions LayersValue;
UMaterialInterface* Parent = InMaterialInstance->Parent;
if (Parent && Parent->GetMaterialLayers(LayersValue))
{
FMaterialLayersFunctions StoredValue = LayersParam->ParameterValue;
if (InParameterData->ParameterInfo.Association == EMaterialParameterAssociation::BlendParameter)
{
StoredAssets = StoredValue.Blends;
ParentAssets = LayersValue.Blends;
}
else if (InParameterData->ParameterInfo.Association == EMaterialParameterAssociation::LayerParameter)
{
StoredAssets = StoredValue.Layers;
ParentAssets = LayersValue.Layers;
}
// Compare to the parent MaterialFunctionInterface array
if (Index < ParentAssets.Num())
{
if (StoredAssets[Index] == ParentAssets[Index])
{
return false;
}
else
{
return true;
}
}
else if (StoredAssets[Index] != nullptr)
{
return true;
}
}
}
return false;
}
bool FMaterialPropertyHelpers::ShouldShowResetToDefault(UDEditorParameterValue* InParameter, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
//const FMaterialParameterInfo& ParameterInfo = InParameter->ParameterInfo;
FMaterialParameterMetadata EditorValue;
if (InParameter->GetValue(EditorValue))
{
FMaterialParameterMetadata SourceValue;
MaterialEditorInstance->SourceInstance->GetParameterDefaultValue(EditorValue.Value.Type, InParameter->ParameterInfo, SourceValue);
if (EditorValue.Value != SourceValue.Value)
{
return true;
}
}
return false;
}
FEditorParameterGroup& FMaterialPropertyHelpers::GetParameterGroup(UMaterial* InMaterial, FName& ParameterGroup, TArray<struct FEditorParameterGroup>& ParameterGroups)
{
if (ParameterGroup == TEXT(""))
{
ParameterGroup = TEXT("None");
}
for (int32 i = 0; i < ParameterGroups.Num(); i++)
{
FEditorParameterGroup& Group = ParameterGroups[i];
if (Group.GroupName == ParameterGroup)
{
return Group;
}
}
int32 ind = ParameterGroups.AddZeroed(1);
FEditorParameterGroup& Group = ParameterGroups[ind];
Group.GroupName = ParameterGroup;
UMaterial* ParentMaterial = InMaterial;
int32 NewSortPriority;
if (ParentMaterial->GetGroupSortPriority(ParameterGroup.ToString(), NewSortPriority))
{
Group.GroupSortPriority = NewSortPriority;
}
else
{
Group.GroupSortPriority = 0;
}
Group.GroupAssociation = EMaterialParameterAssociation::GlobalParameter;
return Group;
}
void FMaterialPropertyHelpers::GetVectorChannelMaskComboBoxStrings(TArray<TSharedPtr<FString>>& OutComboBoxStrings, TArray<TSharedPtr<SToolTip>>& OutToolTips, TArray<bool>& OutRestrictedItems)
{
const UEnum* ChannelEnum = StaticEnum<EChannelMaskParameterColor::Type>();
check(ChannelEnum);
// Add RGBA string options (Note: Exclude the "::Max" entry)
const int32 NumEnums = ChannelEnum->NumEnums() - 1;
for (int32 Entry = 0; Entry < NumEnums; ++Entry)
{
FText EnumName = ChannelEnum->GetDisplayNameTextByIndex(Entry);
OutComboBoxStrings.Add(MakeShared<FString>(EnumName.ToString()));
OutToolTips.Add(SNew(SToolTip).Text(EnumName));
OutRestrictedItems.Add(false);
}
}
FString FMaterialPropertyHelpers::GetVectorChannelMaskValue(UDEditorParameterValue* InParameter)
{
UDEditorVectorParameterValue* VectorParam = Cast<UDEditorVectorParameterValue>(InParameter);
check(VectorParam && VectorParam->bIsUsedAsChannelMask);
const UEnum* ChannelEnum = StaticEnum<EChannelMaskParameterColor::Type>();
check(ChannelEnum);
// Convert from vector to RGBA string
int64 ChannelType = 0;
if (VectorParam->ParameterValue.R > 0.0f)
{
ChannelType = EChannelMaskParameterColor::Red;
}
else if (VectorParam->ParameterValue.G > 0.0f)
{
ChannelType = EChannelMaskParameterColor::Green;
}
else if (VectorParam->ParameterValue.B > 0.0f)
{
ChannelType = EChannelMaskParameterColor::Blue;
}
else
{
ChannelType = EChannelMaskParameterColor::Alpha;
}
return ChannelEnum->GetDisplayNameTextByValue(ChannelType).ToString();
}
void FMaterialPropertyHelpers::SetVectorChannelMaskValue(const FString& StringValue, TSharedPtr<IPropertyHandle> PropertyHandle, UDEditorParameterValue* InParameter, UObject* MaterialEditorInstance)
{
UDEditorVectorParameterValue* VectorParam = Cast<UDEditorVectorParameterValue>(InParameter);
check(VectorParam && VectorParam->bIsUsedAsChannelMask);
const UEnum* ChannelEnum = StaticEnum<EChannelMaskParameterColor::Type>();
check(ChannelEnum);
// Convert from RGBA string to vector
int64 ChannelValue = ChannelEnum->GetValueByNameString(StringValue);
FLinearColor NewValue;
switch (ChannelValue)
{
case EChannelMaskParameterColor::Red:
NewValue = FLinearColor(1.0f, 0.0f, 0.0f, 0.0f); break;
case EChannelMaskParameterColor::Green:
NewValue = FLinearColor(0.0f, 1.0f, 0.0f, 0.0f); break;
case EChannelMaskParameterColor::Blue:
NewValue = FLinearColor(0.0f, 0.0f, 1.0f, 0.0f); break;
default:
NewValue = FLinearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
// If changed, propagate the update
if (VectorParam->ParameterValue != NewValue)
{
const FScopedTransaction Transaction(LOCTEXT("SetVectorChannelMaskValue", "Set Vector Channel Mask Value"));
VectorParam->Modify();
PropertyHandle->NotifyPreChange();
VectorParam->ParameterValue = NewValue;
UMaterialEditorInstanceConstant* MaterialInstanceEditor = Cast<UMaterialEditorInstanceConstant>(MaterialEditorInstance);
if (MaterialInstanceEditor)
{
MaterialInstanceEditor->CopyToSourceInstance();
}
PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
}
}
TArray<UFactory*> FMaterialPropertyHelpers::GetAssetFactories(EMaterialParameterAssociation AssetType)
{
TArray<UFactory*> NewAssetFactories;
switch (AssetType)
{
case LayerParameter:
{
// NewAssetFactories.Add(NewObject<UMaterialFunctionMaterialLayerFactory>());
break;
}
case BlendParameter:
{
// NewAssetFactories.Add(NewObject<UMaterialFunctionMaterialLayerBlendFactory>());
break;
}
case GlobalParameter:
break;
default:
break;
}
return NewAssetFactories;
}
TSharedRef<SWidget> FMaterialPropertyHelpers::MakeStackReorderHandle(TSharedPtr<IDraggableItem> InOwningStack)
{
TSharedRef<SLayerHandle> Handle = SNew(SLayerHandle)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(5.0f, 0.0f)
[
SNew(SImage)
.Image(FCoreStyle::Get().GetBrush("VerticalBoxDragIndicatorShort"))
]
]
.OwningStack(InOwningStack);
return Handle;
}
bool FMaterialPropertyHelpers::OnShouldSetCurveAsset(const FAssetData& AssetData, TSoftObjectPtr<class UCurveLinearColorAtlas> InAtlas)
{
UCurveLinearColorAtlas* Atlas = Cast<UCurveLinearColorAtlas>(InAtlas.Get());
if (!Atlas)
{
return false;
}
for (UCurveLinearColor* GradientCurve : Atlas->GradientCurves)
{
if (!GradientCurve || !GradientCurve->GetOutermost())
{
continue;
}
if (GradientCurve->GetOutermost()->GetPathName() == AssetData.PackageName.ToString())
{
return true;
}
}
return false;
}
bool FMaterialPropertyHelpers::OnShouldFilterCurveAsset(const FAssetData& AssetData, TSoftObjectPtr<class UCurveLinearColorAtlas> InAtlas)
{
return !OnShouldSetCurveAsset(AssetData, InAtlas);
}
void FMaterialPropertyHelpers::SetPositionFromCurveAsset(const FAssetData& AssetData, TSoftObjectPtr<class UCurveLinearColorAtlas> InAtlas, UDEditorScalarParameterValue* InParameter, TSharedPtr<IPropertyHandle> PropertyHandle, UObject* MaterialEditorInstance)
{
UCurveLinearColorAtlas* Atlas = Cast<UCurveLinearColorAtlas>(InAtlas.Get());
UCurveLinearColor* Curve = Cast<UCurveLinearColor>(AssetData.GetAsset());
if (!Atlas || !Curve)
{
return;
}
int32 Index = Atlas->GradientCurves.Find(Curve);
if (Index == INDEX_NONE)
{
return;
}
float NewValue = (float)Index;
// If changed, propagate the update
if (InParameter->ParameterValue != NewValue)
{
const FScopedTransaction Transaction(LOCTEXT("SetScalarAtlasPositionValue", "Set Scalar Atlas Position Value"));
InParameter->Modify();
InParameter->AtlasData.Curve = TSoftObjectPtr<UCurveLinearColor>(FSoftObjectPath(Curve->GetPathName()));
InParameter->ParameterValue = NewValue;
UMaterialEditorInstanceConstant* MaterialInstanceEditor = Cast<UMaterialEditorInstanceConstant>(MaterialEditorInstance);
if (MaterialInstanceEditor)
{
MaterialInstanceEditor->CopyToSourceInstance();
}
}
}
void FMaterialPropertyHelpers::ResetCurveToDefault(UDEditorParameterValue* Parameter, UMaterialEditorInstanceConstant* MaterialEditorInstance)
{
const FScopedTransaction Transaction(LOCTEXT("ResetToDefault", "Reset To Default"));
Parameter->Modify();
const FMaterialParameterInfo& ParameterInfo = Parameter->ParameterInfo;
UDEditorScalarParameterValue* ScalarParam = Cast<UDEditorScalarParameterValue>(Parameter);
if (ScalarParam)
{
float OutValue;
if (MaterialEditorInstance->SourceInstance->GetScalarParameterDefaultValue(ParameterInfo, OutValue))
{
ScalarParam->ParameterValue = OutValue;
// Purge cached values, which will cause non-default values for the atlas data to be returned by IsScalarParameterUsedAsAtlasPosition
MaterialEditorInstance->SourceInstance->ClearParameterValuesEditorOnly();
// Update the atlas data from default values
bool TempBool;
MaterialEditorInstance->SourceInstance->IsScalarParameterUsedAsAtlasPosition(ParameterInfo, TempBool, ScalarParam->AtlasData.Curve, ScalarParam->AtlasData.Atlas);
MaterialEditorInstance->CopyToSourceInstance();
}
}
}
TSharedRef<SWidget> FMaterialPropertyHelpers::CreateScalarEnumerationParameterValueWidget(TSoftObjectPtr<UObject> const& SoftEnumeration, TSharedPtr<IPropertyHandle> ParameterValueProperty)
{
// Load the enumeration object before use.
// We support two object types: UEnum and IMaterialEnumerationProvider.
UObject* Enumeration = SoftEnumeration.LoadSynchronous();
TWeakObjectPtr<UObject> WeakEnumeration(Enumeration);
FOnGetPropertyComboBoxStrings GetStrings = FOnGetPropertyComboBoxStrings::CreateLambda([WeakEnumeration](TArray<TSharedPtr<FString>>& OutStrings, TArray<TSharedPtr<SToolTip>>&, TArray<bool>&)
{
UObject* Enumeration = WeakEnumeration.Get();
if (UEnum* Enum = Cast<UEnum>(Enumeration))
{
for (int32 EnumIndex = 0; EnumIndex < Enum->NumEnums(); ++EnumIndex)
{
OutStrings.Emplace(MakeShared<FString>(Enum->GetDisplayNameTextByIndex(EnumIndex).ToString()));
}
}
else if (IMaterialEnumerationProvider* EnumProvider = Cast<IMaterialEnumerationProvider>(Enumeration))
{
EnumProvider->ForEachEntry([&OutStrings](FName InEntryName, int32 InEntryValue)
{
OutStrings.Emplace(MakeShared<FString>(InEntryName.ToString()));
});
}
});
FOnGetPropertyComboBoxValue GetValue = FOnGetPropertyComboBoxValue::CreateLambda([WeakEnumeration, ParameterValueProperty]()
{
FString EntryName = TEXT("None");
float Value = 0.f;
ParameterValueProperty->GetValue(Value);
UObject* Enumeration = WeakEnumeration.Get();
if (UEnum* Enum = Cast<UEnum>(Enumeration))
{
EntryName = Enum->GetDisplayNameTextByValue((int64)Value).ToString();
}
else if (IMaterialEnumerationProvider* EnumProvider = Cast<IMaterialEnumerationProvider>(Enumeration))
{
EnumProvider->ForEachEntry([Value, &EntryName](FName InEntryName, int32 InEntryValue)
{
if (InEntryValue == (int32)Value)
{
EntryName = InEntryName.ToString();
}
});
}
return EntryName;
});
FOnPropertyComboBoxValueSelected SetValue = FOnPropertyComboBoxValueSelected::CreateLambda([WeakEnumeration, ParameterValueProperty](const FString& StringValue)
{
UObject* Enumeration = WeakEnumeration.Get();
if (UEnum* Enum = Cast<UEnum>(Enumeration))
{
for (int32 EnumIndex = 0; EnumIndex < Enum->NumEnums(); ++EnumIndex)
{
if (Enum->GetDisplayNameTextByIndex(EnumIndex).ToString() == StringValue)
{
const float Value = Enum->GetValueByIndex(EnumIndex);
ParameterValueProperty->SetValue(Value);
break;
}
}
}
else if (IMaterialEnumerationProvider* EnumProvider = Cast<IMaterialEnumerationProvider>(Enumeration))
{
const float Value = EnumProvider->GetValueOrDefault(*StringValue);
ParameterValueProperty->SetValue(Value);
}
});
return PropertyCustomizationHelpers::MakePropertyComboBox(ParameterValueProperty, GetStrings, GetValue, SetValue);
}
#undef LOCTEXT_NAMESPACE