// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "IntervalStructCustomization.h" #include "Widgets/Text/STextBlock.h" #include "Editor.h" #include "Widgets/Layout/SBox.h" #include "DetailWidgetRow.h" #include "DetailLayoutBuilder.h" #include "Widgets/Input/SNumericEntryBox.h" #define LOCTEXT_NAMESPACE "IntervalStructCustomization" /* Helper traits for getting a metadata property based on the template parameter type *****************************************************************************/ namespace IntervalMetadata { template struct FGetHelper { }; template <> struct FGetHelper { static float GetMetaData(const UProperty* Property, const TCHAR* Key) { return Property->GetFloatMetaData(Key); } }; template <> struct FGetHelper { static int32 GetMetaData(const UProperty* Property, const TCHAR* Key) { return Property->GetIntMetaData(Key); } }; } /* FIntervalStructCustomization static interface *****************************************************************************/ template TSharedRef FIntervalStructCustomization::MakeInstance() { return MakeShareable(new FIntervalStructCustomization); } /* IPropertyTypeCustomization interface *****************************************************************************/ template void FIntervalStructCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { // Get handles to the properties we're interested in MinValueHandle = StructPropertyHandle->GetChildHandle(TEXT("Min")); MaxValueHandle = StructPropertyHandle->GetChildHandle(TEXT("Max")); check(MinValueHandle.IsValid()); check(MaxValueHandle.IsValid()); // Get min/max metadata values if defined auto Property = StructPropertyHandle->GetProperty(); check(Property != nullptr); if (Property->HasMetaData(TEXT("UIMin"))) { MinAllowedValue = TOptional(IntervalMetadata::FGetHelper::GetMetaData(Property, TEXT("UIMin"))); } if (Property->HasMetaData(TEXT("UIMax"))) { MaxAllowedValue = TOptional(IntervalMetadata::FGetHelper::GetMetaData(Property, TEXT("UIMax"))); } bAllowInvertedInterval = Property->HasMetaData(TEXT("AllowInvertedInterval")); bClampToMinMaxLimits = Property->HasMetaData(TEXT("ClampToMinMaxLimits")); // Build the widgets HeaderRow.NameContent() [ StructPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(200.0f) //.MaxDesiredWidth(200.0f) [ SNew(SBox) .Padding(FMargin(0.0f, 3.0f, 0.0f, 2.0f)) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(0.0f, 0.0f, 5.0f, 0.0f)) .VAlign(VAlign_Center) [ SNew(SNumericEntryBox) .Value(this, &FIntervalStructCustomization::OnGetValue, EIntervalField::Min) .MinValue(MinAllowedValue) .MinSliderValue(MinAllowedValue) .MaxValue(this, &FIntervalStructCustomization::OnGetMaxValue) .MaxSliderValue(this, &FIntervalStructCustomization::OnGetMaxValue) .OnValueCommitted(this, &FIntervalStructCustomization::OnValueCommitted, EIntervalField::Min) .OnValueChanged(this, &FIntervalStructCustomization::OnValueChanged, EIntervalField::Min) .OnBeginSliderMovement(this, &FIntervalStructCustomization::OnBeginSliderMovement) .OnEndSliderMovement(this, &FIntervalStructCustomization::OnEndSliderMovement) .UndeterminedString(LOCTEXT("MultipleValues", "Multiple Values")) .Font(IDetailLayoutBuilder::GetDetailFont()) .AllowSpin(true) .IsEnabled(this, &FIntervalStructCustomization::IsPropertyEnabled, EIntervalField::Min) .LabelVAlign(VAlign_Center) .Label() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("MinLabel", "Min")) ] ] +SHorizontalBox::Slot() .Padding(FMargin(0.0f, 0.0f, 5.0f, 0.0f)) .VAlign(VAlign_Center) [ SNew(SNumericEntryBox) .Value(this, &FIntervalStructCustomization::OnGetValue, EIntervalField::Max) .MinValue(this, &FIntervalStructCustomization::OnGetMinValue) .MinSliderValue(this, &FIntervalStructCustomization::OnGetMinValue) .MaxValue(MaxAllowedValue) .MaxSliderValue(MaxAllowedValue) .OnValueCommitted(this, &FIntervalStructCustomization::OnValueCommitted, EIntervalField::Max) .OnValueChanged(this, &FIntervalStructCustomization::OnValueChanged, EIntervalField::Max) .OnBeginSliderMovement(this, &FIntervalStructCustomization::OnBeginSliderMovement) .OnEndSliderMovement(this, &FIntervalStructCustomization::OnEndSliderMovement) .UndeterminedString(LOCTEXT("MultipleValues", "Multiple Values")) .Font(IDetailLayoutBuilder::GetDetailFont()) .AllowSpin(true) .IsEnabled(this, &FIntervalStructCustomization::IsPropertyEnabled, EIntervalField::Max) .LabelVAlign(VAlign_Center) .Label() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("MaxLabel", "Max")) ] ] ] ]; } template void FIntervalStructCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { // Don't display children, as editing them directly can break the constraints } /* FIntervalStructCustomization callbacks *****************************************************************************/ template static TOptional GetValue(IPropertyHandle* Handle) { NumericType Value; if (Handle->GetValue(Value) == FPropertyAccess::Success) { return TOptional(Value); } return TOptional(); } template TOptional FIntervalStructCustomization::OnGetValue(EIntervalField Field) const { return GetValue((Field == EIntervalField::Min) ? MinValueHandle.Get() : MaxValueHandle.Get()); } template TOptional FIntervalStructCustomization::OnGetMinValue() const { if (bClampToMinMaxLimits) { return GetValue(MinValueHandle.Get()); } return MinAllowedValue; } template TOptional FIntervalStructCustomization::OnGetMaxValue() const { if (bClampToMinMaxLimits) { return GetValue(MaxValueHandle.Get()); } return MaxAllowedValue; } template void FIntervalStructCustomization::SetValue(NumericType NewValue, EIntervalField Field, EPropertyValueSetFlags::Type Flags) { IPropertyHandle* Handle = (Field == EIntervalField::Min) ? MinValueHandle.Get() : MaxValueHandle.Get(); IPropertyHandle* OtherHandle = (Field == EIntervalField::Min) ? MaxValueHandle.Get() : MinValueHandle.Get(); const TOptional OtherValue = GetValue(OtherHandle); const bool bOutOfRange = OtherValue.IsSet() && ((Field == EIntervalField::Min && NewValue > OtherValue.GetValue()) || (Field == EIntervalField::Max && NewValue < OtherValue.GetValue())); // The order of execution of the Intertactive change vs commit is super important for the Undo history // So Interactive we must do, Handle, Other Handle // For Commit: we must do the reverse to properly close the scope of traction if (!bOutOfRange || bAllowInvertedInterval) { if (Flags == EPropertyValueSetFlags::InteractiveChange) { ensure(Handle->SetValue(NewValue, Flags) == FPropertyAccess::Success); if (OtherValue.IsSet()) { ensure(OtherHandle->SetValue(OtherValue.GetValue(), Flags) == FPropertyAccess::Success); } } else { if (OtherValue.IsSet()) { ensure(OtherHandle->SetValue(OtherValue.GetValue(), Flags) == FPropertyAccess::Success); } ensure(Handle->SetValue(NewValue, Flags) == FPropertyAccess::Success); } } else if (!bClampToMinMaxLimits) { if (Flags == EPropertyValueSetFlags::InteractiveChange) { ensure(Handle->SetValue(NewValue, Flags) == FPropertyAccess::Success); ensure(OtherHandle->SetValue(NewValue, Flags) == FPropertyAccess::Success); } else { ensure(OtherHandle->SetValue(NewValue, Flags) == FPropertyAccess::Success); ensure(Handle->SetValue(NewValue, Flags) == FPropertyAccess::Success); } } } template void FIntervalStructCustomization::OnValueCommitted(NumericType NewValue, ETextCommit::Type CommitType, EIntervalField Field) { if (!bIsUsingSlider || (bIsUsingSlider && ShouldAllowSpin())) { SetValue(NewValue, Field); } } template void FIntervalStructCustomization::OnValueChanged(NumericType NewValue, EIntervalField Field) { if (bIsUsingSlider && ShouldAllowSpin()) { SetValue(NewValue, Field, EPropertyValueSetFlags::InteractiveChange); } } template void FIntervalStructCustomization::OnBeginSliderMovement() { bIsUsingSlider = true; if (ShouldAllowSpin()) { GEditor->BeginTransaction(LOCTEXT("SetIntervalProperty", "Set Interval Property")); } } template void FIntervalStructCustomization::OnEndSliderMovement(NumericType /*NewValue*/) { bIsUsingSlider = false; if (ShouldAllowSpin()) { GEditor->EndTransaction(); } } template bool FIntervalStructCustomization::ShouldAllowSpin() const { return true; } template bool FIntervalStructCustomization::IsPropertyEnabled(EIntervalField Field) const { IPropertyHandle* Handle = (Field == EIntervalField::Min) ? MinValueHandle.Get() : MaxValueHandle.Get(); if (Handle == nullptr) { return false; } return !Handle->IsEditConst(); } /* Only explicitly instantiate the types which are supported *****************************************************************************/ template class FIntervalStructCustomization; template class FIntervalStructCustomization; #undef LOCTEXT_NAMESPACE