Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

391 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChaosClothAsset/WeightedValueCustomization.h"
#include "ChaosClothAsset/ClothAssetEditorStyle.h"
#include "ChaosClothAsset/WeightedValue.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Editor.h"
#define LOCTEXT_NAMESPACE "ChaosClothAssetWeightedValueCustomization"
namespace UE::Chaos::ClothAsset
{
namespace Private
{
static const FString ImportFabricBounds = TEXT("ImportFabricBounds");
static bool ImportFabricBoundsProperty(const TSharedPtr<IPropertyHandle>& Property)
{
const FStringView PropertyPath = Property ? Property->GetPropertyPath() : FStringView();
return PropertyPath.EndsWith(ImportFabricBounds, ESearchCase::CaseSensitive);
}
}
TSharedRef<IPropertyTypeCustomization> FWeightedValueCustomization::MakeInstance()
{
return MakeShareable(new FWeightedValueCustomization);
}
FWeightedValueCustomization::FWeightedValueCustomization() = default;
FWeightedValueCustomization::~FWeightedValueCustomization() = default;
void FWeightedValueCustomization::MakeHeaderRow(TSharedRef<class IPropertyHandle>& StructPropertyHandle, FDetailWidgetRow& Row)
{
const TWeakPtr<IPropertyHandle> StructWeakHandlePtr = StructPropertyHandle;
TSharedPtr<SHorizontalBox> ValueHorizontalBox;
TSharedPtr<SHorizontalBox> 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<IPropertyHandle> ChildHandle = SortedChildHandles[ChildIndex];
if (CouldUseFabricsProperty(ChildHandle))
{
bool bValue = false;
ChildHandle->GetValue(bValue);
if(!bValue)
{
break;
}
}
if (Private::ImportFabricBoundsProperty(ChildHandle))
{
AddToggledCheckBox(ChildHandle, NameHorizontalBox, FAppStyle::Get().GetBrush("Icons.Import"));
}
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<IPropertyHandle> ChildHandle = SortedChildHandles[ChildIndex];
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (IsOverrideProperty(ChildHandle))
{
continue; // Skip overrides
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// Propagate metadata to child properties so that it's reflected in the nested, individual spin boxes
ChildHandle->SetInstanceMetaData(TEXT("UIMin"), StructPropertyHandle->GetMetaData(TEXT("UIMin")));
ChildHandle->SetInstanceMetaData(TEXT("UIMax"), StructPropertyHandle->GetMetaData(TEXT("UIMax")));
ChildHandle->SetInstanceMetaData(TEXT("SliderExponent"), StructPropertyHandle->GetMetaData(TEXT("SliderExponent")));
ChildHandle->SetInstanceMetaData(TEXT("Delta"), StructPropertyHandle->GetMetaData(TEXT("Delta")));
ChildHandle->SetInstanceMetaData(TEXT("LinearDeltaSensitivity"), StructPropertyHandle->GetMetaData(TEXT("LinearDeltaSensitivity")));
ChildHandle->SetInstanceMetaData(TEXT("ShiftMultiplier"), StructPropertyHandle->GetMetaData(TEXT("ShiftMultiplier")));
ChildHandle->SetInstanceMetaData(TEXT("CtrlMultiplier"), StructPropertyHandle->GetMetaData(TEXT("CtrlMultiplier")));
ChildHandle->SetInstanceMetaData(TEXT("SupportDynamicSliderMaxValue"), StructPropertyHandle->GetMetaData(TEXT("SupportDynamicSliderMaxValue")));
ChildHandle->SetInstanceMetaData(TEXT("SupportDynamicSliderMinValue"), StructPropertyHandle->GetMetaData(TEXT("SupportDynamicSliderMinValue")));
ChildHandle->SetInstanceMetaData(TEXT("ClampMin"), StructPropertyHandle->GetMetaData(TEXT("ClampMin")));
ChildHandle->SetInstanceMetaData(TEXT("ClampMax"), StructPropertyHandle->GetMetaData(TEXT("ClampMax")));
const bool bLastChild = SortedChildHandles.Num() - 1 == ChildIndex;
TSharedRef<SWidget> ChildWidget = MakeChildWidget(StructPropertyHandle, ChildHandle);
if(ChildWidget != SNullWidget::NullWidget)
{
if (ChildHandle->GetPropertyClass() == FBoolProperty::StaticClass())
{
ValueHorizontalBox->AddSlot()
.Padding(FMargin(0.f, 2.f, bLastChild ? 0.f : 3.f, 2.f))
.AutoWidth() // keep the check box slots small
[
ChildWidget
];
}
else
{
if (ChildHandle->GetPropertyClass() == FFloatProperty::StaticClass())
{
NumericEntryBoxWidgetList.Add(ChildWidget);
}
ValueHorizontalBox->AddSlot()
.Padding(FMargin(0.f, 2.f, bLastChild ? 0.f : 3.f, 2.f))
[
ChildWidget
];
}
}
}
}
TSharedRef<SWidget> FWeightedValueCustomization::MakeChildWidget(
TSharedRef<IPropertyHandle>& StructurePropertyHandle,
TSharedRef<IPropertyHandle>& PropertyHandle)
{
const FFieldClass* PropertyClass = PropertyHandle->GetPropertyClass();
if (PropertyClass == FFloatProperty::StaticClass())
{
return MakeFloatWidget(StructurePropertyHandle, PropertyHandle);
}
if (PropertyClass == FBoolProperty::StaticClass())
{
TWeakPtr<IPropertyHandle> WeakHandlePtr = PropertyHandle;
if (!Private::ImportFabricBoundsProperty(PropertyHandle) && !BuildFabricMapsProperty(PropertyHandle) && !CouldUseFabricsProperty(PropertyHandle))
{
return
SNew(SCheckBox)
.ToolTipText(PropertyHandle->GetToolTipText())
.Type(ESlateCheckBoxType::CheckBox)
.IsChecked_Lambda([WeakHandlePtr]()->ECheckBoxState
{
bool bValue = false;
WeakHandlePtr.Pin()->GetValue(bValue);
return bValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([WeakHandlePtr](ECheckBoxState CheckBoxState)
{
WeakHandlePtr.Pin()->SetValue(CheckBoxState == ECheckBoxState::Checked, EPropertyValueSetFlags::DefaultFlags);
});
}
}
return FConnectableValueCustomization::MakeChildWidget(StructurePropertyHandle, PropertyHandle);
}
TSharedRef<SWidget> FWeightedValueCustomization::MakeFloatWidget(
TSharedRef<IPropertyHandle>& StructurePropertyHandle,
TSharedRef<IPropertyHandle>& PropertyHandle)
{
TOptional<float> MinValue, MaxValue, SliderMinValue, SliderMaxValue;
float SliderExponent, Delta;
float ShiftMultiplier = 10.f;
float CtrlMultiplier = 0.1f;
bool SupportDynamicSliderMaxValue = false;
bool SupportDynamicSliderMinValue = false;
ExtractFloatMetadata(StructurePropertyHandle, MinValue, MaxValue, SliderMinValue, SliderMaxValue, SliderExponent, Delta, ShiftMultiplier, CtrlMultiplier, SupportDynamicSliderMaxValue, SupportDynamicSliderMinValue);
TWeakPtr<IPropertyHandle> WeakHandlePtr = PropertyHandle;
return SNew(SNumericEntryBox<float>)
.IsEnabled(this, &FMathStructCustomization::IsValueEnabled, WeakHandlePtr)
.EditableTextBoxStyle(&FCoreStyle::Get().GetWidgetStyle<FEditableTextBoxStyle>("NormalEditableTextBox"))
.Value_Lambda([WeakHandlePtr]()
{
float Value = 0.;
return (WeakHandlePtr.Pin()->GetValue(Value) == FPropertyAccess::Success) ?
TOptional<float>(Value) :
TOptional<float>(); // Value couldn't be accessed, return an unset value
})
.Font(IDetailLayoutBuilder::GetDetailFont())
.UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values"))
.OnValueCommitted_Lambda([WeakHandlePtr](float Value, ETextCommit::Type)
{
WeakHandlePtr.Pin()->SetValue(Value, EPropertyValueSetFlags::DefaultFlags);
})
.OnValueChanged_Lambda([this, WeakHandlePtr](float Value)
{
if (bIsUsingSlider)
{
WeakHandlePtr.Pin()->SetValue(Value, EPropertyValueSetFlags::InteractiveChange | EPropertyValueSetFlags::NotTransactable);
}
})
.OnBeginSliderMovement_Lambda([this]()
{
bIsUsingSlider = true;
GEditor->BeginTransaction(LOCTEXT("SetVectorProperty", "Set Vector Property"));
})
.OnEndSliderMovement_Lambda([this](float Value)
{
bIsUsingSlider = false;
GEditor->EndTransaction();
})
.LabelVAlign(VAlign_Center)
// Only allow spin on handles with one object. Otherwise it is not clear what value to spin
.AllowSpin(PropertyHandle->GetNumOuterObjects() < 2)
.ShiftMultiplier(ShiftMultiplier)
.CtrlMultiplier(CtrlMultiplier)
.SupportDynamicSliderMaxValue(SupportDynamicSliderMaxValue)
.SupportDynamicSliderMinValue(SupportDynamicSliderMinValue)
.OnDynamicSliderMaxValueChanged(this, &FWeightedValueCustomization::OnDynamicSliderMaxValueChanged)
.OnDynamicSliderMinValueChanged(this, &FWeightedValueCustomization::OnDynamicSliderMinValueChanged)
.MinValue(MinValue)
.MaxValue(MaxValue)
.MinSliderValue(SliderMinValue)
.MaxSliderValue(SliderMaxValue)
.SliderExponent(SliderExponent)
.Delta(Delta)
.Label()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(FText::FromString(PropertyHandle->GetMetaData(TEXT("ChaosClothAssetShortName")))) // Case specific metadata, uses ChaosCloth prefix to avoid conflict with future engine extensions
];
}
// The following code is just a plain copy of FMathStructCustomization which
// would need changes to be able to serve as a base class for this customization.
void FWeightedValueCustomization::ExtractFloatMetadata(TSharedRef<IPropertyHandle>& PropertyHandle, TOptional<float>& MinValue, TOptional<float>& MaxValue, TOptional<float>& SliderMinValue, TOptional<float>& SliderMaxValue, float& SliderExponent, float& Delta, float& ShiftMultiplier, float& CtrlMultiplier, bool& SupportDynamicSliderMaxValue, bool& SupportDynamicSliderMinValue)
{
FProperty* Property = PropertyHandle->GetProperty();
const FString& MetaUIMinString = Property->GetMetaData(TEXT("UIMin"));
const FString& MetaUIMaxString = Property->GetMetaData(TEXT("UIMax"));
const FString& SliderExponentString = Property->GetMetaData(TEXT("SliderExponent"));
const FString& DeltaString = Property->GetMetaData(TEXT("Delta"));
const FString& ShiftMultiplierString = Property->GetMetaData(TEXT("ShiftMultiplier"));
const FString& CtrlMultiplierString = Property->GetMetaData(TEXT("CtrlMultiplier"));
const FString& SupportDynamicSliderMaxValueString = Property->GetMetaData(TEXT("SupportDynamicSliderMaxValue"));
const FString& SupportDynamicSliderMinValueString = Property->GetMetaData(TEXT("SupportDynamicSliderMinValue"));
const FString& ClampMinString = Property->GetMetaData(TEXT("ClampMin"));
const FString& ClampMaxString = Property->GetMetaData(TEXT("ClampMax"));
// If no UIMin/Max was specified then use the clamp string
const FString& UIMinString = MetaUIMinString.Len() ? MetaUIMinString : ClampMinString;
const FString& UIMaxString = MetaUIMaxString.Len() ? MetaUIMaxString : ClampMaxString;
float ClampMin = TNumericLimits<float>::Lowest();
float ClampMax = TNumericLimits<float>::Max();
if (!ClampMinString.IsEmpty())
{
TTypeFromString<float>::FromString(ClampMin, *ClampMinString);
}
if (!ClampMaxString.IsEmpty())
{
TTypeFromString<float>::FromString(ClampMax, *ClampMaxString);
}
float UIMin = TNumericLimits<float>::Lowest();
float UIMax = TNumericLimits<float>::Max();
TTypeFromString<float>::FromString(UIMin, *UIMinString);
TTypeFromString<float>::FromString(UIMax, *UIMaxString);
SliderExponent = float(1);
if (SliderExponentString.Len())
{
TTypeFromString<float>::FromString(SliderExponent, *SliderExponentString);
}
Delta = float(0);
if (DeltaString.Len())
{
TTypeFromString<float>::FromString(Delta, *DeltaString);
}
ShiftMultiplier = 10.f;
if (ShiftMultiplierString.Len())
{
TTypeFromString<float>::FromString(ShiftMultiplier, *ShiftMultiplierString);
}
CtrlMultiplier = 0.1f;
if (CtrlMultiplierString.Len())
{
TTypeFromString<float>::FromString(CtrlMultiplier, *CtrlMultiplierString);
}
const float ActualUIMin = FMath::Max(UIMin, ClampMin);
const float ActualUIMax = FMath::Min(UIMax, ClampMax);
MinValue = ClampMinString.Len() ? ClampMin : TOptional<float>();
MaxValue = ClampMaxString.Len() ? ClampMax : TOptional<float>();
SliderMinValue = (UIMinString.Len()) ? ActualUIMin : TOptional<float>();
SliderMaxValue = (UIMaxString.Len()) ? ActualUIMax : TOptional<float>();
SupportDynamicSliderMaxValue = SupportDynamicSliderMaxValueString.Len() > 0 && SupportDynamicSliderMaxValueString.ToBool();
SupportDynamicSliderMinValue = SupportDynamicSliderMinValueString.Len() > 0 && SupportDynamicSliderMinValueString.ToBool();
}
void FWeightedValueCustomization::OnDynamicSliderMaxValueChanged(float NewMaxSliderValue, TWeakPtr<SWidget> InValueChangedSourceWidget, bool IsOriginator, bool UpdateOnlyIfHigher)
{
for (TWeakPtr<SWidget>& Widget : NumericEntryBoxWidgetList)
{
TSharedPtr<SNumericEntryBox<float>> NumericBox = StaticCastSharedPtr<SNumericEntryBox<float>>(Widget.Pin());
if (NumericBox.IsValid())
{
TSharedPtr<SSpinBox<float>> SpinBox = StaticCastSharedPtr<SSpinBox<float>>(NumericBox->GetSpinBox());
if (SpinBox.IsValid())
{
if (SpinBox != InValueChangedSourceWidget)
{
if ((NewMaxSliderValue > SpinBox->GetMaxSliderValue() && UpdateOnlyIfHigher) || !UpdateOnlyIfHigher)
{
// Make sure the max slider value is not a getter otherwise we will break the link!
verifySlow(!SpinBox->IsMaxSliderValueBound());
SpinBox->SetMaxSliderValue(NewMaxSliderValue);
}
}
}
}
}
if (IsOriginator)
{
OnNumericEntryBoxDynamicSliderMaxValueChanged.Broadcast((float)NewMaxSliderValue, InValueChangedSourceWidget, false, UpdateOnlyIfHigher);
}
}
void FWeightedValueCustomization::OnDynamicSliderMinValueChanged(float NewMinSliderValue, TWeakPtr<SWidget> InValueChangedSourceWidget, bool IsOriginator, bool UpdateOnlyIfLower)
{
for (TWeakPtr<SWidget>& Widget : NumericEntryBoxWidgetList)
{
TSharedPtr<SNumericEntryBox<float>> NumericBox = StaticCastSharedPtr<SNumericEntryBox<float>>(Widget.Pin());
if (NumericBox.IsValid())
{
TSharedPtr<SSpinBox<float>> SpinBox = StaticCastSharedPtr<SSpinBox<float>>(NumericBox->GetSpinBox());
if (SpinBox.IsValid())
{
if (SpinBox != InValueChangedSourceWidget)
{
if ((NewMinSliderValue < SpinBox->GetMinSliderValue() && UpdateOnlyIfLower) || !UpdateOnlyIfLower)
{
// Make sure the min slider value is not a getter otherwise we will break the link!
verifySlow(!SpinBox->IsMinSliderValueBound());
SpinBox->SetMinSliderValue(NewMinSliderValue);
}
}
}
}
}
if (IsOriginator)
{
OnNumericEntryBoxDynamicSliderMinValueChanged.Broadcast((float)NewMinSliderValue, InValueChangedSourceWidget, false, UpdateOnlyIfLower);
}
}
} // End namespace UE::Chaos::ClothAsset
#undef LOCTEXT_NAMESPACE