Files
UnrealEngine/Engine/Plugins/Experimental/Water/Source/Editor/Private/WaterSplineMetadataDetails.cpp
2025-05-18 13:04:45 +08:00

349 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WaterSplineMetadataDetails.h"
#include "WaterSplineMetadata.h"
#include "IDetailGroup.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "ComponentVisualizer.h"
#include "Editor.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(WaterSplineMetadataDetails)
#define LOCTEXT_NAMESPACE "FWaterSplineMetadataDetails"
UWaterSplineMetadataDetailsFactory::UWaterSplineMetadataDetailsFactory(const FObjectInitializer& ObjInitializer)
: Super(ObjInitializer)
{
}
UClass* UWaterSplineMetadataDetailsFactory::GetMetadataClass() const
{
return UWaterSplineMetadata::StaticClass();
}
TSharedPtr<ISplineMetadataDetails> UWaterSplineMetadataDetailsFactory::Create()
{
return MakeShared<FWaterSplineMetadataDetails>();
}
FText FWaterSplineMetadataDetails::GetDisplayName() const
{
return LOCTEXT("WaterSplineMetadataDetails", "Water");
}
template<class T>
bool UpdateMultipleValue(TOptional<T>& CurrentValue, T InValue)
{
if (!CurrentValue.IsSet())
{
CurrentValue = InValue;
}
else if (CurrentValue.IsSet() && CurrentValue.GetValue() != InValue)
{
CurrentValue.Reset();
return false;
}
return true;
}
void FWaterSplineMetadataDetails::Update(USplineComponent* InSplineComponent, const TSet<int32>& InSelectedKeys)
{
SplineComp = InSplineComponent;
SelectedKeys = InSelectedKeys;
DepthValue.Reset();
RiverWidthValue.Reset();
VelocityValue.Reset();
AudioIntensityValue.Reset();
if (InSplineComponent)
{
bool bUpdateDepth = true;
bool bUpdateRiverWidth = true;
bool bUpdateVelocity = true;
bool bUpdateAudioIntensity = true;
UWaterSplineMetadata* Metadata = Cast<UWaterSplineMetadata>(InSplineComponent->GetSplinePointsMetadata());
if (Metadata)
{
for (int32 Index : InSelectedKeys)
{
if (bUpdateDepth)
{
bUpdateDepth = UpdateMultipleValue(DepthValue, Metadata->Depth.Points[Index].OutVal);
}
if (bUpdateRiverWidth)
{
bUpdateRiverWidth = UpdateMultipleValue(RiverWidthValue, Metadata->RiverWidth.Points[Index].OutVal);
}
if (bUpdateVelocity)
{
bUpdateVelocity = UpdateMultipleValue(VelocityValue, Metadata->WaterVelocityScalar.Points[Index].OutVal);
}
if (bUpdateAudioIntensity)
{
bUpdateAudioIntensity = UpdateMultipleValue(AudioIntensityValue, Metadata->AudioIntensity.Points[Index].OutVal);
}
}
}
}
}
template<class T>
void SetValues(FWaterSplineMetadataDetails& Details, TArray<FInterpCurvePoint<T>>& Points, const T& NewValue, ETextCommit::Type CommitInfo, const FText& TransactionText)
{
// Scope the transaction to only include the value change and none of the derived data changes that might arise from NotifyPropertyModified
{
const FScopedTransaction Transaction(TransactionText);
Details.SplineComp->GetSplinePointsMetadata()->Modify();
for (int32 Index : Details.SelectedKeys)
{
Points[Index].OutVal = NewValue;
}
}
Details.SplineComp->UpdateSpline();
Details.SplineComp->bSplineHasBeenEdited = true;
static FProperty* SplineCurvesProperty = FindFProperty<FProperty>(USplineComponent::StaticClass(), USplineComponent::GetSplinePropertyName());
EPropertyChangeType::Type PropertyChangeType = CommitInfo == ETextCommit::OnEnter ? EPropertyChangeType::ValueSet : EPropertyChangeType::Interactive;
FComponentVisualizer::NotifyPropertyModified(Details.SplineComp, SplineCurvesProperty, PropertyChangeType);
Details.Update(Details.SplineComp, Details.SelectedKeys);
GEditor->RedrawLevelEditingViewports(true);
}
template <typename T>
bool CheckIfDifferent(const FWaterSplineMetadataDetails& Details, const TArray<FInterpCurvePoint<T>>& Points, const T& NewValue)
{
bool bIsModified = false;
for (int32 Index : Details.SelectedKeys)
{
bIsModified |= Points[Index].OutVal != NewValue;
}
return bIsModified;
}
void FWaterSplineMetadataDetails::OnBeginSliderMovement()
{
EditSliderValueTransaction = MakeUnique<FScopedTransaction>(LOCTEXT("EditWaterProperty", "Edit Water Property"));
}
void FWaterSplineMetadataDetails::OnEndSliderMovement(float NewValue)
{
EditSliderValueTransaction.Reset();
}
UWaterSplineMetadata* FWaterSplineMetadataDetails::GetMetadata() const
{
UWaterSplineMetadata* Metadata = SplineComp ? Cast<UWaterSplineMetadata>(SplineComp->GetSplinePointsMetadata()) : nullptr;
return Metadata;
}
void FWaterSplineMetadataDetails::OnSetDepth(float NewValue, ETextCommit::Type CommitInfo)
{
if (UWaterSplineMetadata* Metadata = GetMetadata())
{
if (CheckIfDifferent(*this, Metadata->Depth.Points, NewValue) || CommitInfo == ETextCommit::OnEnter)
{
SetValues(*this, Metadata->Depth.Points, NewValue, CommitInfo, LOCTEXT("SetSplineDepth", "Set spline point water depth"));
}
}
}
void FWaterSplineMetadataDetails::OnSetRiverWidth(float NewValue, ETextCommit::Type CommitInfo)
{
if (UWaterSplineMetadata* Metadata = GetMetadata())
{
if (CheckIfDifferent(*this, Metadata->RiverWidth.Points, NewValue) || CommitInfo == ETextCommit::OnEnter)
{
SetValues(*this, Metadata->RiverWidth.Points, NewValue, CommitInfo, LOCTEXT("SetSplineWaterWidth", "Set spline point river width"));
}
}
}
void FWaterSplineMetadataDetails::OnSetVelocity(float NewValue, ETextCommit::Type CommitInfo)
{
if (UWaterSplineMetadata* Metadata = GetMetadata())
{
if (CheckIfDifferent(*this, Metadata->WaterVelocityScalar.Points, NewValue) || CommitInfo == ETextCommit::OnEnter)
{
SetValues(*this, Metadata->WaterVelocityScalar.Points, NewValue, CommitInfo, LOCTEXT("SetSplineWaterVelocity", "Set spline point water velocity"));
}
}
}
void FWaterSplineMetadataDetails::OnSetAudioIntensity(float NewValue, ETextCommit::Type CommitInfo)
{
if (UWaterSplineMetadata* Metadata = GetMetadata())
{
if (CheckIfDifferent(*this, Metadata->AudioIntensity.Points, NewValue) || CommitInfo == ETextCommit::OnEnter)
{
SetValues(*this, Metadata->AudioIntensity.Points, NewValue, CommitInfo, LOCTEXT("SetSpline point audio intensity", "Set spline point audio intensity"));
}
}
}
EVisibility FWaterSplineMetadataDetails::IsDepthVisible() const
{
UWaterSplineMetadata* Metadata = GetMetadata();
return (Metadata && Metadata->CanEditDepth()) ? IsEnabled() : EVisibility::Collapsed;
}
EVisibility FWaterSplineMetadataDetails::IsRiverWidthVisible() const
{
UWaterSplineMetadata* Metadata = GetMetadata();
return (Metadata && Metadata->CanEditRiverWidth()) ? IsEnabled() : EVisibility::Collapsed;
}
EVisibility FWaterSplineMetadataDetails::IsVelocityVisible() const
{
UWaterSplineMetadata* Metadata = GetMetadata();
return (Metadata && Metadata->CanEditVelocity()) ? IsEnabled() : EVisibility::Collapsed;
}
void FWaterSplineMetadataDetails::GenerateChildContent(IDetailGroup& DetailGroup)
{
DetailGroup.AddWidgetRow()
.RowTag("Depth")
.FilterString(LOCTEXT("Depth", "Depth"))
.Visibility(TAttribute<EVisibility>(this, &FWaterSplineMetadataDetails::IsDepthVisible))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("Depth", "Depth"))
.ToolTipText(LOCTEXT("RiverDepth_Tooltip", "The depth of the river at this spline point. Affects the terrain carving and collision"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SNumericEntryBox<float>)
.Value(this, &FWaterSplineMetadataDetails::GetDepth)
.AllowSpin(true)
.MinValue(0.0f)
// Because we have no upper limit in MaxSliderValue, we need to "unspecify" the max value here, otherwise the spinner has a limited range,
// with TNumericLimits<NumericType>::Max() as the MaxValue and the spinning increment is huge
.MaxValue(TOptional<float>())
.MinSliderValue(0.0f)
.MaxSliderValue(TOptional<float>()) // No upper limit
.OnBeginSliderMovement(this, &FWaterSplineMetadataDetails::OnBeginSliderMovement)
.OnEndSliderMovement(this, &FWaterSplineMetadataDetails::OnEndSliderMovement)
.UndeterminedString(LOCTEXT("Multiple", "Multiple"))
.OnValueCommitted(this, &FWaterSplineMetadataDetails::OnSetDepth)
.OnValueChanged(this, &FWaterSplineMetadataDetails::OnSetDepth, ETextCommit::Default)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
DetailGroup.AddWidgetRow()
.RowTag("RiverWidth")
.FilterString(LOCTEXT("RiverWidth", "River Width"))
.Visibility(TAttribute<EVisibility>(this, &FWaterSplineMetadataDetails::IsRiverWidthVisible))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("RiverWidth", "River Width"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.ToolTipText(LOCTEXT("RiverWidth_Tooltip", "The width of the river at this spline point. Affects the visuals, terrain carving, and collision."))
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SNumericEntryBox<float>)
.Value(this, &FWaterSplineMetadataDetails::GetRiverWidth)
.AllowSpin(true)
.OnBeginSliderMovement(this, &FWaterSplineMetadataDetails::OnBeginSliderMovement)
.OnEndSliderMovement(this, &FWaterSplineMetadataDetails::OnEndSliderMovement)
.MinValue(0.0f)
// Because we have no upper limit in MaxSliderValue, we need to "unspecify" the max value here, otherwise the spinner has a limited range,
// with TNumericLimits<NumericType>::Max() as the MaxValue and the spinning increment is huge
.MaxValue(TOptional<float>())
.MinSliderValue(0.0f)
.MaxSliderValue(TOptional<float>()) // No upper limit
.UndeterminedString(LOCTEXT("Multiple", "Multiple"))
.OnValueCommitted(this, &FWaterSplineMetadataDetails::OnSetRiverWidth)
.OnValueChanged(this, &FWaterSplineMetadataDetails::OnSetRiverWidth, ETextCommit::Default)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
DetailGroup.AddWidgetRow()
.RowTag("Velocity")
.FilterString(LOCTEXT("Velocity", "Velocity"))
.Visibility(TAttribute<EVisibility>(this, &FWaterSplineMetadataDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("Velocity", "Velocity"))
.ToolTipText(LOCTEXT("RiverVelocity_Tooltip", "The river flow velocity at this spline point. Affects the visuals of the flowing water."))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SNumericEntryBox<float>)
.Value(this, &FWaterSplineMetadataDetails::GetVelocity)
.AllowSpin(true)
.OnBeginSliderMovement(this, &FWaterSplineMetadataDetails::OnBeginSliderMovement)
.OnEndSliderMovement(this, &FWaterSplineMetadataDetails::OnEndSliderMovement)
.MinValue(TOptional<float>())
.MaxValue(TOptional<float>())
.MinSliderValue(TOptional<float>()) // No lower limit
.MaxSliderValue(TOptional<float>()) // No upper limit
.UndeterminedString(LOCTEXT("Multiple", "Multiple"))
.OnValueCommitted(this, &FWaterSplineMetadataDetails::OnSetVelocity)
.OnValueChanged(this, &FWaterSplineMetadataDetails::OnSetVelocity, ETextCommit::Default)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
DetailGroup.AddWidgetRow()
.RowTag("AudioIntensity")
.FilterString(LOCTEXT("AudioIntensity", "Audio Intensity"))
.Visibility(TAttribute<EVisibility>(this, &FWaterSplineMetadataDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("AudioIntensity", "Audio Intensity"))
.ToolTipText(LOCTEXT("SplineAudioIntensity_Tooltip", "The audio intensity of the water at this spline point."))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SNumericEntryBox<float>)
.Value(this, &FWaterSplineMetadataDetails::GetAudioIntensity)
.AllowSpin(true)
.OnBeginSliderMovement(this, &FWaterSplineMetadataDetails::OnBeginSliderMovement)
.OnEndSliderMovement(this, &FWaterSplineMetadataDetails::OnEndSliderMovement)
.MinValue(0.0f)
// Because we have no upper limit in MaxSliderValue, we need to "unspecify" the max value here, otherwise the spinner has a limited range,
// with TNumericLimits<NumericType>::Max() as the MaxValue and the spinning increment is huge
.MaxValue(TOptional<float>())
.MinSliderValue(0.0f)
.MaxSliderValue(TOptional<float>()) // No upper limit
.UndeterminedString(LOCTEXT("Multiple", "Multiple"))
.OnValueCommitted(this, &FWaterSplineMetadataDetails::OnSetAudioIntensity)
.OnValueChanged(this, &FWaterSplineMetadataDetails::OnSetAudioIntensity, ETextCommit::Default)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
#undef LOCTEXT_NAMESPACE