// Copyright Epic Games, Inc. All Rights Reserved. #include "ChaosClothAsset/ConnectableValueCustomization.h" #include "ChaosClothAsset/ClothAssetEditorStyle.h" #include "ChaosClothAsset/ClothDataflowTools.h" #include "ChaosClothAsset/ImportedValueCustomization.h" #include "ChaosClothAsset/WeightedValue.h" #include "Dataflow/DataflowNode.h" #include "Dataflow/DataflowNodeParameters.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #define LOCTEXT_NAMESPACE "ChaosClothAssetWeightedValueCustomization" namespace UE::Chaos::ClothAsset { namespace Private { static const FString OverridePrefix = TEXT("_Override"); // UE_DEPRECATED(5.5, "Override properties are no longer used.") static const FString BuildFabricMaps = TEXT("BuildFabricMaps"); static const FString CouldUseFabrics = TEXT("CouldUseFabrics"); } // UE_DEPRECATED(5.5, "Override properties are no longer used.") bool FConnectableValueCustomization::IsOverrideProperty(const TSharedPtr& Property) { PRAGMA_DISABLE_DEPRECATION_WARNINGS const FStringView PropertyPath = Property ? Property->GetPropertyPath() : FStringView(); return PropertyPath.EndsWith(Private::OverridePrefix, ESearchCase::CaseSensitive); PRAGMA_ENABLE_DEPRECATION_WARNINGS } // UE_DEPRECATED(5.5, "Override properties are no longer used.") bool FConnectableValueCustomization::IsOverridePropertyOf(const TSharedPtr& OverrideProperty, const TSharedPtr& Property) { PRAGMA_DISABLE_DEPRECATION_WARNINGS const FStringView OverridePropertyPath = OverrideProperty ? OverrideProperty->GetPropertyPath() : FStringView(); const FStringView PropertyPath = Property ? Property->GetPropertyPath() : FStringView(); return OverridePropertyPath == FString(PropertyPath) + Private::OverridePrefix; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool FConnectableValueCustomization::BuildFabricMapsProperty(const TSharedPtr& Property) { const FStringView PropertyPath = Property ? Property->GetPropertyPath() : FStringView(); return PropertyPath.EndsWith(Private::BuildFabricMaps, ESearchCase::CaseSensitive); } bool FConnectableValueCustomization::CouldUseFabricsProperty(const TSharedPtr& Property) { const FStringView PropertyPath = Property ? Property->GetPropertyPath() : FStringView(); return PropertyPath.EndsWith(Private::CouldUseFabrics, ESearchCase::CaseSensitive); } TSharedRef FConnectableValueCustomization::MakeInstance() { return MakeShareable(new FConnectableValueCustomization); } FConnectableValueCustomization::FConnectableValueCustomization() = default; FConnectableValueCustomization::~FConnectableValueCustomization() = default; void FConnectableValueCustomization::CustomizeChildren( TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { for (int32 ChildIndex = 0; ChildIndex < SortedChildHandles.Num(); ++ChildIndex) { TSharedRef ChildHandle = SortedChildHandles[ChildIndex]; if (CouldUseFabricsProperty(ChildHandle)) { bool bCouldUseFabrics = false; ChildHandle->GetValue(bCouldUseFabrics); if (bCouldUseFabrics) { FImportedValueCustomization::CustomizeChildren(PropertyHandle, ChildBuilder, CustomizationUtils); } return; } } } void FConnectableValueCustomization::MakeHeaderRow(TSharedRef& StructPropertyHandle, FDetailWidgetRow& Row) { const TWeakPtr StructWeakHandlePtr = StructPropertyHandle; TSharedPtr ValueHorizontalBox; TSharedPtr NameHorizontalBox; Row.NameContent() [ SAssignNew(NameHorizontalBox, SHorizontalBox) .IsEnabled(this, &FMathStructCustomization::IsValueEnabled, StructWeakHandlePtr) ] .ValueContent() // Make enough space for each child handle .MinDesiredWidth(125.f * SortedChildHandles.Num()) .MaxDesiredWidth(125.f * SortedChildHandles.Num()) [ SAssignNew(ValueHorizontalBox, SHorizontalBox) .IsEnabled(this, &FMathStructCustomization::IsValueEnabled, StructWeakHandlePtr) ]; for (int32 ChildIndex = 0; ChildIndex < SortedChildHandles.Num(); ++ChildIndex) { TSharedRef ChildHandle = SortedChildHandles[ChildIndex]; if (CouldUseFabricsProperty(ChildHandle)) { bool bValue = false; ChildHandle->GetValue(bValue); if(!bValue) { break; } } else if (BuildFabricMapsProperty(ChildHandle)) { AddToggledCheckBox(ChildHandle, NameHorizontalBox, UE::Chaos::ClothAsset::FClothAssetEditorStyle::Get().GetBrush("ClassIcon.ChaosClothPreset")); } } NameHorizontalBox->AddSlot().VAlign(VAlign_Center) .Padding(FMargin(4.f, 2.f, 4.0f, 2.f)) .HAlign(HAlign_Right) .AutoWidth() [ StructPropertyHandle->CreatePropertyNameWidget() ]; for (int32 ChildIndex = 0; ChildIndex < SortedChildHandles.Num(); ++ChildIndex) { TSharedRef ChildHandle = SortedChildHandles[ChildIndex]; PRAGMA_DISABLE_DEPRECATION_WARNINGS if (IsOverrideProperty(ChildHandle)) { continue; // Skip overrides } PRAGMA_ENABLE_DEPRECATION_WARNINGS const bool bLastChild = SortedChildHandles.Num() - 1 == ChildIndex; TSharedRef ChildWidget = MakeChildWidget(StructPropertyHandle, ChildHandle); if(ChildWidget != SNullWidget::NullWidget) { ValueHorizontalBox->AddSlot() .Padding(FMargin(0.f, 2.f, bLastChild ? 0.f : 3.f, 2.f)) [ ChildWidget ]; } } } TSharedRef FConnectableValueCustomization::MakeChildWidget( TSharedRef& StructurePropertyHandle, TSharedRef& PropertyHandle) { const FFieldClass* PropertyClass = PropertyHandle->GetPropertyClass(); if (PropertyClass == FStrProperty::StaticClass()) { TWeakPtr HandleWeakPtr = PropertyHandle; return SNew(SEditableTextBox) .ToolTipText(PropertyHandle->GetToolTipText()) .Text_Lambda([HandleWeakPtr, StructurePropertyHandle]() -> FText { using namespace UE::Chaos::ClothAsset; FString Text; if (const TSharedPtr HandlePtr = HandleWeakPtr.Pin()) { const FDataflowNode* const DataflowNode = FClothDataflowTools::GetPropertyOwnerDataflowNode(StructurePropertyHandle); if (ensure(DataflowNode)) { void* Data; if (HandlePtr->GetValueData(Data) == FPropertyAccess::Success) // GetValueData could return false with multiple selections { Text = *static_cast(Data); // Default value if the property isn't an input, or isn't connected if (const FDataflowInput* const DataflowInput = DataflowNode->FindInput(Data)) { UE::Dataflow::FContextThreaded Context; Text = DataflowInput->GetValue(Context, Text); } } } } return FText::FromString(Text); }) .OnTextCommitted_Lambda([HandleWeakPtr](const FText& Text, ETextCommit::Type) { if (const TSharedPtr HandlePtr = HandleWeakPtr.Pin()) { FString TextString = Text.ToString(); FClothDataflowTools::MakeCollectionName(TextString); HandlePtr->SetValue(TextString, EPropertyValueSetFlags::DefaultFlags); } }) .OnVerifyTextChanged_Lambda([](const FText& Text, FText& OutErrorMessage) -> bool { bool bIsValidCollectionName = false; FString TextString = Text.ToString(); bIsValidCollectionName = FClothDataflowTools::MakeCollectionName(TextString); if (!bIsValidCollectionName) { OutErrorMessage = LOCTEXT("NotValidCollectioName", "To be a valid collection name, this text string musn't start by an underscore,\n" "contain whitespaces, or any of the following character: \"',/.:|&!~@#(){}[]=;^%$`"); } return bIsValidCollectionName; }) .IsEnabled_Lambda([HandleWeakPtr, StructurePropertyHandle]() -> bool { if (const TSharedPtr HandlePtr = HandleWeakPtr.Pin()) { const FDataflowNode* const DataflowNode = FClothDataflowTools::GetPropertyOwnerDataflowNode(StructurePropertyHandle); if (ensure(DataflowNode)) { void* Data; if (HandlePtr->GetValueData(Data) == FPropertyAccess::Success) { if (const FDataflowInput* const DataflowInput = DataflowNode->FindInput(Data)) { return !DataflowInput->HasAnyConnections(); } } } } return true; }) .Font(IPropertyTypeCustomizationUtils::GetRegularFont()); } return SNullWidget::NullWidget; } } // End namespace UE::Chaos::ClothAsset #undef LOCTEXT_NAMESPACE