// Copyright Epic Games, Inc. All Rights Reserved. #include "PaintModeSettingsCustomization.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "IDetailPropertyRow.h" #include "IDetailChildrenBuilder.h" #include "DetailWidgetRow.h" #include "Widgets/Text/STextBlock.h" #include "PropertyRestriction.h" #include "Engine/Texture2D.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Input/SCheckBox.h" #include "PaintModeSettings.h" #include "PaintModePainter.h" #include "Widgets/Input/SNumericEntryBox.h" #include "PropertyCustomizationHelpers.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBox.h" #include "ScopedTransaction.h" #define LOCTEXT_NAMESPACE "PaintModePainter" TSharedRef FTexturePaintSettingsCustomization::MakeInstance() { return MakeShareable(new FTexturePaintSettingsCustomization()); } void FTexturePaintSettingsCustomization::CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { static const TArray CustomProperties = { GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, PaintTexture), GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, UVChannel), GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteRed), GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteBlue), GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteAlpha), GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteGreen) }; TMap> CustomizedProperties; /** Caches paint mode painter instance */ MeshPainter = FPaintModePainter::Get(); // Cache vertex paint settings ptr PaintSettings = &(UPaintModeSettings::Get()->TexturePaintSettings); uint32 NumChildren = 0; PropertyHandle->GetNumChildren(NumChildren); /** Add child properties except of paint texture property (needs customization)*/ for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedRef ChildHandle = PropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); const int32 ArrayIndex = CustomProperties.IndexOfByKey(ChildHandle->GetProperty()->GetFName()); if (ArrayIndex == INDEX_NONE) { IDetailPropertyRow& Property = ChildBuilder.AddProperty(ChildHandle); } else { CustomizedProperties.Add(ChildHandle->GetProperty()->GetFName(), ChildHandle); } } TSharedRef RedChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteRed)); TSharedRef GreenChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteGreen)); TSharedRef BlueChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteBlue)); TSharedRef AlphaChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, bWriteAlpha)); TArray> Channels = { RedChannel, GreenChannel, BlueChannel, AlphaChannel }; TSharedPtr ChannelsWidget; ChildBuilder.AddCustomRow(NSLOCTEXT("VertexPaintSettings", "ChannelLabel", "Channels")) .NameContent() [ SNew(STextBlock) .Text(NSLOCTEXT("VertexPaintSettings", "ChannelsLabel", "Channels")) .ToolTipText(NSLOCTEXT("VertexPaintSettings", "ChannelsToolTip", "Colors Channels which should be influenced during Painting.")) .Font(CustomizationUtils.GetRegularFont()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(ChannelsWidget, SHorizontalBox) ]; for (TSharedRef Channel : Channels) { ChannelsWidget->AddSlot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ CreateColorChannelWidget(Channel) ]; } if (CustomizedProperties.Find(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, UVChannel))) { TSharedRef UVChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, UVChannel)); ChildBuilder.AddCustomRow(NSLOCTEXT("TexturePainting", "TexturePaintingUVLabel", "Texture Painting UV Channel")) .NameContent() [ UVChannel->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(SNumericEntryBox) .Font(CustomizationUtils.GetRegularFont()) .AllowSpin(true) .Value_Lambda([=]() -> int32 { return PaintSettings->UVChannel; }) .MinValue(0) .MaxValue_Lambda([=]() -> int32 { return FPaintModePainter::Get()->GetMaxUVIndexToPaint(); }) .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([=](int32 Value) { PaintSettings->UVChannel = Value; })) .OnValueCommitted(SNumericEntryBox::FOnValueCommitted::CreateLambda([=](int32 Value, ETextCommit::Type CommitType) { PaintSettings->UVChannel = Value; })) ] ]; } /** If we have a valid texture property handle add custom UI for it */ if (CustomizedProperties.Find(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, PaintTexture))) { TSharedRef TextureProperty = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FTexturePaintSettings, PaintTexture)); TSharedPtr TextureWidget; FDetailWidgetRow& Row = ChildBuilder.AddCustomRow(NSLOCTEXT("TexturePaintSetting", "TextureSearchString", "Texture")) .NameContent() [ SNew(STextBlock) .Text(NSLOCTEXT("TexturePaintSettings", "PaintTextureLabel", "Paint Texture")) .ToolTipText(NSLOCTEXT("TexturePaintSettings", "PaintTextureToolTip", "Texture to Apply Painting to.")) .Font(CustomizationUtils.GetRegularFont()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(TextureWidget, SHorizontalBox) ]; /** Use a SObjectPropertyEntryBox to benefit from its functionality */ TextureWidget->AddSlot() [ SNew(SObjectPropertyEntryBox) .PropertyHandle(TextureProperty) .AllowedClass(UTexture2D::StaticClass()) .OnShouldFilterAsset(FOnShouldFilterAsset::CreateRaw(MeshPainter, &FPaintModePainter::ShouldFilterTextureAsset)) .OnObjectChanged(FOnSetObject::CreateRaw(MeshPainter, &FPaintModePainter::PaintTextureChanged)) .DisplayUseSelected(false) .ThumbnailPool(CustomizationUtils.GetThumbnailPool()) ]; } } /** List of property names which require customization, will be filtered out of property */ TArray FVertexPaintSettingsCustomization::CustomPropertyNames = { GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, PaintColor), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, EraseColor), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteRed), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteGreen), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteBlue), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteAlpha), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bPaintOnSpecificLOD), GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, LODIndex) }; TSharedRef FVertexPaintSettingsCustomization::MakeInstance() { return MakeShareable(new FVertexPaintSettingsCustomization); } void FVertexPaintSettingsCustomization::CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { // Cache vertex paint settings ptr PaintSettings = &(UPaintModeSettings::Get()->VertexPaintSettings); TMap> CustomizedProperties; TMap> Properties; uint32 NumChildren = 0; PropertyHandle->GetNumChildren(NumChildren); // Add child properties to UI and pick out the properties which need customization for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedRef ChildHandle = PropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); if (!CustomPropertyNames.Contains(ChildHandle->GetProperty()->GetFName())) { // Uses metadata to tag properties which should show for either vertex color of vertex blend weight painting IDetailPropertyRow& Property = ChildBuilder.AddProperty(ChildHandle); static const FName EditConditionName = "EnumCondition"; if (ChildHandle->HasMetaData(EditConditionName)) { int32 EnumCondition = ChildHandle->GetIntMetaData(EditConditionName); Property.Visibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FVertexPaintSettingsCustomization::ArePropertiesVisible, EnumCondition))); } } else { CustomizedProperties.Add(ChildHandle->GetProperty()->GetFName(), ChildHandle); } Properties.Add(ChildHandle->GetProperty()->GetFName(), ChildHandle); } /** Creates a custom widget row containing all color channel flags */ TSharedRef PaintColor = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, PaintColor)); TSharedRef EraseColor = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, EraseColor)); TSharedRef RedChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteRed)); TSharedRef GreenChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteGreen)); TSharedRef BlueChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteBlue)); TSharedRef AlphaChannel = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bWriteAlpha)); TArray> Channels = { RedChannel, GreenChannel, BlueChannel, AlphaChannel }; TSharedPtr ChannelsWidget; // Customize paint color with a swap button { TSharedPtr NameWidget; TSharedPtr ValueWidget; IDetailPropertyRow& PaintColorProp = ChildBuilder.AddProperty(PaintColor); PaintColorProp.GetDefaultWidgets(NameWidget, ValueWidget, true); FDetailWidgetRow& Row = PaintColorProp.CustomWidget(true); Row.NameContent() [ NameWidget.ToSharedRef() ]; Row.ValueContent() .MinDesiredWidth(250) .MaxDesiredWidth(0) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0) .HAlign(HAlign_Left) [ SNew(SBox) .WidthOverride(250.f) [ ValueWidget.ToSharedRef() ] ] + SHorizontalBox::Slot() .HAlign(HAlign_Center) .AutoWidth() [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ToolTipText(NSLOCTEXT("VertexPaintSettings", "SwapColors", "Swap Paint and Erase Colors")) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .OnClicked(this, &FVertexPaintSettingsCustomization::OnSwapColorsClicked) .ContentPadding(0) [ SNew(SImage).Image(FEditorStyle::GetBrush("MeshPaint.Swap")) ] ] ]; } { IDetailPropertyRow& EraseColorProp = ChildBuilder.AddProperty(EraseColor); TSharedPtr NameWidget; TSharedPtr ValueWidget; FDetailWidgetRow& Row = EraseColorProp.CustomWidget(true); Row.ValueContent().MinDesiredWidth(250 - 16.f); EraseColorProp.GetDefaultWidgets(NameWidget, ValueWidget, Row, true); } ChildBuilder.AddCustomRow(NSLOCTEXT("VertexPaintSettings", "ChannelLabel", "Channels")) .Visibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FVertexPaintSettingsCustomization::ArePropertiesVisible, 0))) .NameContent() [ SNew(STextBlock) .Text(NSLOCTEXT("VertexPaintSettings", "ChannelsLabel", "Channels")) .ToolTipText(NSLOCTEXT("VertexPaintSettings", "ChannelsToolTip", "Colors Channels which should be influenced during Painting.")) .Font(CustomizationUtils.GetRegularFont()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(ChannelsWidget, SHorizontalBox) ]; for (TSharedRef Channel : Channels) { ChannelsWidget->AddSlot() .AutoWidth() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ CreateColorChannelWidget(Channel) ]; } /** Add property restrictions to the drop-down boxes used for blend weight painting */ TSharedRef WeightTypeProperty = Properties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, TextureWeightType)); TSharedRef PaintWeightProperty = Properties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, PaintTextureWeightIndex)); TSharedRef EraseWeightProperty = Properties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, EraseTextureWeightIndex)); WeightTypeProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateRaw(this, &FVertexPaintSettingsCustomization::OnTextureWeightTypeChanged, WeightTypeProperty, PaintWeightProperty, EraseWeightProperty)); static FText RestrictReason = NSLOCTEXT("VertexPaintSettings", "TextureIndexRestriction", "Unable to paint this Texture, change Texture Weight Type"); BlendPaintEnumRestriction = MakeShareable(new FPropertyRestriction(RestrictReason)); PaintWeightProperty->AddRestriction(BlendPaintEnumRestriction.ToSharedRef()); EraseWeightProperty->AddRestriction(BlendPaintEnumRestriction.ToSharedRef()); OnTextureWeightTypeChanged(WeightTypeProperty, PaintWeightProperty, EraseWeightProperty); /** Add custom row for painting on specific LOD level with callbacks to the painter to update the data */ TSharedRef LODPaintingEnabled = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, bPaintOnSpecificLOD)); TSharedRef LODPaintingIndex = CustomizedProperties.FindChecked(GET_MEMBER_NAME_CHECKED(FVertexPaintSettings, LODIndex)); TSharedPtr LODIndexWidget = LODPaintingIndex->CreatePropertyValueWidget(); ChildBuilder.AddCustomRow(NSLOCTEXT("LODPainting", "LODPaintingLabel", "LOD Model Painting")) .Visibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FVertexPaintSettingsCustomization::ArePropertiesVisible, 0))) .NameContent() [ SNew(STextBlock) .Text(NSLOCTEXT("LODPainting", "LODPaintingSetupLabel", "LOD Model Painting")) .ToolTipText(NSLOCTEXT("LODPainting", "LODPaintingSetupToolTip", "Allows for Painting Vertex Colors on Specific LOD Models.")) .Font(CustomizationUtils.GetRegularFont()) ] .ValueContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 0.0f, 4.0f, 0.0f) .AutoWidth() [ SNew(SCheckBox) .IsChecked_Lambda([=]() -> ECheckBoxState { return (PaintSettings->bPaintOnSpecificLOD ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); }) .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState State) { FPaintModePainter::Get()->LODPaintStateChanged(State == ECheckBoxState::Checked); })) ] +SHorizontalBox::Slot() .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(SNumericEntryBox) .Font(CustomizationUtils.GetRegularFont()) .IsEnabled_Lambda([=]() -> bool { return PaintSettings->bPaintOnSpecificLOD; }) .AllowSpin(true) .Value_Lambda([=]() -> int32 { return PaintSettings->LODIndex; }) .MinValue(0) .MaxValue_Lambda([=]() -> int32 { return FPaintModePainter::Get()->GetMaxLODIndexToPaint(); }) .MaxSliderValue_Lambda([=]() -> int32 { return FPaintModePainter::Get()->GetMaxLODIndexToPaint(); }) .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([=](int32 Value) { PaintSettings->LODIndex = Value; })) .OnValueCommitted(SNumericEntryBox::FOnValueCommitted::CreateLambda([=](int32 Value, ETextCommit::Type CommitType) { PaintSettings->LODIndex = Value; FPaintModePainter::Get()->PaintLODChanged(); })) ] ]; ChildBuilder.AddCustomRow(NSLOCTEXT("LODPainting", "LODPaintingLabel", "LOD Model Painting")) .WholeRowContent() [ SNew(SBorder) .Visibility_Lambda([this]() -> EVisibility { return PaintSettings->bPaintOnSpecificLOD ? EVisibility::Collapsed : EVisibility::Visible; }) .Padding(FMargin(4.0f)) .BorderImage(FEditorStyle::GetBrush("SettingsEditor.CheckoutWarningBorder")) .BorderBackgroundColor(FColor(166, 137, 0)) [ SNew(STextBlock) .AutoWrapText(true) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text_Lambda([this]() -> FText { static const FText SkelMeshNotificationText = LOCTEXT("SkelMeshAssetPaintInfo", "Paint is propagated to Skeletal Mesh Asset(s)"); static const FText StaticMeshNotificationText = LOCTEXT("StaticMeshAssetPaintInfo", "Paint is applied to all LODs"); const bool bSkelMeshText = FPaintModePainter::Get()->GetSelectedComponents().Num() > 0; const bool bLODPaintText = !PaintSettings->bPaintOnSpecificLOD; return FText::Format(FTextFormat::FromString(TEXT("{0}{1}{2}")), bSkelMeshText ? SkelMeshNotificationText : FText::GetEmpty(), bSkelMeshText && bLODPaintText ? FText::FromString(TEXT("\n")) : FText::GetEmpty(), bLODPaintText ? StaticMeshNotificationText : FText::GetEmpty()); }) ] ]; } EVisibility FVertexPaintSettingsCustomization::ArePropertiesVisible(const int32 VisibleType) const { return ((int32)PaintSettings->MeshPaintMode == VisibleType) ? EVisibility::Visible : EVisibility::Collapsed; } void FVertexPaintSettingsCustomization::OnTextureWeightTypeChanged(TSharedRef WeightTypeProperty, TSharedRef PaintWeightProperty, TSharedRef EraseWeightProperty) { UEnum* ImportTypeEnum = StaticEnum(); uint8 EnumValue = 0; WeightTypeProperty->GetValue(EnumValue); BlendPaintEnumRestriction->RemoveAll(); for (uint8 EnumIndex = 0; EnumIndex < (ImportTypeEnum->GetMaxEnumValue() + 1); ++EnumIndex) { if ((EnumIndex + 1) > EnumValue) { FString EnumName = ImportTypeEnum->GetNameByValue(EnumIndex).ToString(); EnumName.RemoveFromStart("ETexturePaintIndex::"); BlendPaintEnumRestriction->AddDisabledValue(EnumName); } } uint8 Value = 0; PaintWeightProperty->GetValue(Value); Value = FMath::Clamp(Value, 0, EnumValue - 1); PaintWeightProperty->SetValue(Value); EraseWeightProperty->GetValue(Value); Value = FMath::Clamp(Value, 0, EnumValue - 1); EraseWeightProperty->SetValue(Value); } FReply FVertexPaintSettingsCustomization::OnSwapColorsClicked() { FScopedTransaction Transaction(NSLOCTEXT("VertexPaintSettings", "SwapColorsTransation", "Swap paint and erase colors")); UPaintModeSettings* Settings = UPaintModeSettings::Get(); Settings->Modify(); FLinearColor TempPaintColor = PaintSettings->PaintColor; PaintSettings->PaintColor = PaintSettings->EraseColor; PaintSettings->EraseColor = TempPaintColor; return FReply::Handled(); } TSharedRef FPaintModeSettingsCustomization::MakeInstance() { return MakeShareable(new FPaintModeSettingsCustomization); } void FPaintModeSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { UPaintModeSettings* Settings = UPaintModeSettings::Get(); TSharedRef PaintModeProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPaintModeSettings, PaintMode)); uint8 EnumValue = 0; PaintModeProperty->GetValue(EnumValue); FSimpleDelegate OnPaintTypeChangedDelegate = FSimpleDelegate::CreateSP(this, &FPaintModeSettingsCustomization::OnPaintTypeChanged, &DetailBuilder); PaintModeProperty->SetOnPropertyValueChanged(OnPaintTypeChangedDelegate); IDetailCategoryBuilder& TexturePaintingCategory = DetailBuilder.EditCategory(FName("TexturePainting")); TexturePaintingCategory.SetCategoryVisibility(Settings->PaintMode == EPaintMode::Textures); IDetailCategoryBuilder& VertexPaintingCategory = DetailBuilder.EditCategory(FName("VertexPainting")); VertexPaintingCategory.SetCategoryVisibility(Settings->PaintMode == EPaintMode::Vertices); } void FPaintModeSettingsCustomization::OnPaintTypeChanged(IDetailLayoutBuilder* LayoutBuilder) { LayoutBuilder->ForceRefreshDetails(); } TSharedRef CreateColorChannelWidget(TSharedRef ChannelProperty) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ ChannelProperty->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) [ ChannelProperty->CreatePropertyNameWidget() ]; } #undef LOCTEXT_NAMESPACE