Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp
roey borsteinas 3439827f02 Expose some water body component and spline component properties (on water bodies) to UEFN.
#rb jonathan.bard, luc.eygasier
#preflight 62827c6e046b81bf93c2e566

#ROBOMERGE-OWNER: roey.borsteinas
#ROBOMERGE-AUTHOR: roey.borsteinas
#ROBOMERGE-SOURCE: CL 20228520 via CL 20260053 via CL 20260058 via CL 20260066
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690)

[CL 20263011 by roey borsteinas in ue5-main branch]
2022-05-18 12:55:21 -04:00

1602 lines
51 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SplineComponentDetails.h"
#include "SplineMetadataDetailsFactory.h"
#include "Misc/MessageDialog.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Class.h"
#include "UObject/UnrealType.h"
#include "Layout/Visibility.h"
#include "Misc/Attribute.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SWrapBox.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SButton.h"
#include "ComponentVisualizer.h"
#include "Editor/UnrealEdEngine.h"
#include "UnrealEdGlobals.h"
#include "IDetailCustomNodeBuilder.h"
#include "IDetailChildrenBuilder.h"
#include "DetailWidgetRow.h"
#include "DetailLayoutBuilder.h"
#include "Components/SplineComponent.h"
#include "DetailCategoryBuilder.h"
#include "SplineComponentVisualizer.h"
#include "Widgets/Input/SVectorInputBox.h"
#include "Widgets/Input/SRotatorInputBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "ScopedTransaction.h"
#include "Editor.h"
#include "LevelEditorViewport.h"
#include "Engine/Blueprint.h"
#include "BlueprintEditor.h"
#include "BlueprintEditorModule.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#define LOCTEXT_NAMESPACE "SplineComponentDetails"
DEFINE_LOG_CATEGORY_STATIC(LogSplineComponentDetails, Log, All)
USplineMetadataDetailsFactoryBase::USplineMetadataDetailsFactoryBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
class FSplinePointDetails : public IDetailCustomNodeBuilder, public TSharedFromThis<FSplinePointDetails>
{
public:
FSplinePointDetails(USplineComponent* InOwningSplineComponent);
//~ Begin IDetailCustomNodeBuilder interface
virtual void SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren) override;
virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override;
virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override;
virtual void Tick(float DeltaTime) override;
virtual bool RequiresTick() const override { return true; }
virtual bool InitiallyCollapsed() const override { return false; }
virtual FName GetName() const override;
//~ End IDetailCustomNodeBuilder interface
static bool bAlreadyWarnedInvalidIndex;
private:
template <typename T>
struct TSharedValue
{
TSharedValue() : bInitialized(false) {}
void Reset()
{
bInitialized = false;
}
void Add(T InValue)
{
if (!bInitialized)
{
Value = InValue;
bInitialized = true;
}
else
{
if (Value.IsSet() && InValue != Value.GetValue()) { Value.Reset(); }
}
}
TOptional<T> Value;
bool bInitialized;
};
struct FSharedVectorValue
{
FSharedVectorValue() : bInitialized(false) {}
void Reset()
{
bInitialized = false;
}
bool IsValid() const { return bInitialized; }
void Add(const FVector& V)
{
if (!bInitialized)
{
X = V.X;
Y = V.Y;
Z = V.Z;
bInitialized = true;
}
else
{
if (X.IsSet() && V.X != X.GetValue()) { X.Reset(); }
if (Y.IsSet() && V.Y != Y.GetValue()) { Y.Reset(); }
if (Z.IsSet() && V.Z != Z.GetValue()) { Z.Reset(); }
}
}
TOptional<float> X;
TOptional<float> Y;
TOptional<float> Z;
bool bInitialized;
};
struct FSharedRotatorValue
{
FSharedRotatorValue() : bInitialized(false) {}
void Reset()
{
bInitialized = false;
}
bool IsValid() const { return bInitialized; }
void Add(const FRotator& R)
{
if (!bInitialized)
{
Roll = R.Roll;
Pitch = R.Pitch;
Yaw = R.Yaw;
bInitialized = true;
}
else
{
if (Roll.IsSet() && R.Roll != Roll.GetValue()) { Roll.Reset(); }
if (Pitch.IsSet() && R.Pitch != Pitch.GetValue()) { Pitch.Reset(); }
if (Yaw.IsSet() && R.Yaw != Yaw.GetValue()) { Yaw.Reset(); }
}
}
TOptional<float> Roll;
TOptional<float> Pitch;
TOptional<float> Yaw;
bool bInitialized;
};
EVisibility IsEnabled() const { return (SelectedKeys.Num() > 0) ? EVisibility::Visible : EVisibility::Collapsed; }
EVisibility IsDisabled() const { return (SelectedKeys.Num() == 0) ? EVisibility::Visible : EVisibility::Collapsed; }
bool IsOnePointSelected() const { return SelectedKeys.Num() == 1; }
bool ArePointsSelected() const { return (SelectedKeys.Num() > 0); };
bool AreNoPointsSelected() const { return (SelectedKeys.Num() == 0); };
TOptional<float> GetInputKey() const { return InputKey.Value; }
TOptional<float> GetPositionX() const { return Position.X; }
TOptional<float> GetPositionY() const { return Position.Y; }
TOptional<float> GetPositionZ() const { return Position.Z; }
TOptional<float> GetArriveTangentX() const { return ArriveTangent.X; }
TOptional<float> GetArriveTangentY() const { return ArriveTangent.Y; }
TOptional<float> GetArriveTangentZ() const { return ArriveTangent.Z; }
TOptional<float> GetLeaveTangentX() const { return LeaveTangent.X; }
TOptional<float> GetLeaveTangentY() const { return LeaveTangent.Y; }
TOptional<float> GetLeaveTangentZ() const { return LeaveTangent.Z; }
TOptional<float> GetRotationRoll() const { return Rotation.Roll; }
TOptional<float> GetRotationPitch() const { return Rotation.Pitch; }
TOptional<float> GetRotationYaw() const { return Rotation.Yaw; }
TOptional<float> GetScaleX() const { return Scale.X; }
TOptional<float> GetScaleY() const { return Scale.Y; }
TOptional<float> GetScaleZ() const { return Scale.Z; }
void OnSetInputKey(float NewValue, ETextCommit::Type CommitInfo);
void OnSetPosition(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis);
void OnSetArriveTangent(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis);
void OnSetLeaveTangent(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis);
void OnSetRotation(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis);
void OnSetScale(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis);
FText GetPointType() const;
void OnSplinePointTypeChanged(TSharedPtr<FString> NewValue, ESelectInfo::Type SelectInfo);
TSharedRef<SWidget> OnGenerateComboWidget(TSharedPtr<FString> InComboString);
void GenerateSplinePointSelectionControls(IDetailChildrenBuilder& ChildrenBuilder);
FReply OnSelectFirstLastSplinePoint(bool bFirst);
FReply OnSelectPrevNextSplinePoint(bool bNext, bool bAddToSelection);
FReply OnSelectAllSplinePoints();
USplineComponent* GetSplineComponentToVisualize() const;
void UpdateValues();
enum class ESplinePointProperty
{
Location,
Rotation,
Scale,
ArriveTangent,
LeaveTangent
};
TSharedRef<SWidget> BuildSplinePointPropertyLabel(ESplinePointProperty SplinePointProp);
void OnSetTransformEditingAbsolute(ESplinePointProperty SplinePointProp, bool bIsAbsolute);
bool IsTransformEditingAbsolute(ESplinePointProperty SplinePointProperty) const;
bool IsTransformEditingRelative(ESplinePointProperty SplinePointProperty) const;
FText GetSplinePointPropertyText(ESplinePointProperty SplinePointProp) const;
void SetSplinePointProperty(ESplinePointProperty SplinePointProp, FVector NewValue, EAxisList::Type Axis, bool bCommitted);
FUIAction CreateCopyAction(ESplinePointProperty SplinePointProp);
FUIAction CreatePasteAction(ESplinePointProperty SplinePointProp);
bool OnCanCopy(ESplinePointProperty SplinePointProp) const { return true; }
void OnCopy(ESplinePointProperty SplinePointProp);
void OnPaste(ESplinePointProperty SplinePointProp);
void OnBeginPositionSlider();
void OnBeginScaleSlider();
void OnEndSlider(float);
USplineComponent* SplineComp;
USplineComponent* SplineCompArchetype;
TSet<int32> SelectedKeys;
TSharedValue<float> InputKey;
FSharedVectorValue Position;
FSharedVectorValue ArriveTangent;
FSharedVectorValue LeaveTangent;
FSharedVectorValue Scale;
FSharedRotatorValue Rotation;
TSharedValue<ESplinePointType::Type> PointType;
TSharedPtr<FSplineComponentVisualizer> SplineVisualizer;
FProperty* SplineCurvesProperty;
TArray<TSharedPtr<FString>> SplinePointTypes;
TSharedPtr<ISplineMetadataDetails> SplineMetaDataDetails;
FSimpleDelegate OnRegenerateChildren;
bool bEditingLocationAbsolute = false;
bool bEditingRotationAbsolute = false;
bool bInSliderTransaction = false;
};
bool FSplinePointDetails::bAlreadyWarnedInvalidIndex = false;
FSplinePointDetails::FSplinePointDetails(USplineComponent* InOwningSplineComponent)
: SplineComp(nullptr)
{
TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(InOwningSplineComponent->GetClass());
SplineVisualizer = StaticCastSharedPtr<FSplineComponentVisualizer>(Visualizer);
check(SplineVisualizer.IsValid());
SplineCurvesProperty = FindFProperty<FProperty>(USplineComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(USplineComponent, SplineCurves));
const TArray<ESplinePointType::Type> EnabledSplinePointTypes = InOwningSplineComponent->GetEnabledSplinePointTypes();
UEnum* SplinePointTypeEnum = StaticEnum<ESplinePointType::Type>();
check(SplinePointTypeEnum);
for (int32 EnumIndex = 0; EnumIndex < SplinePointTypeEnum->NumEnums() - 1; ++EnumIndex)
{
const int32 Value = SplinePointTypeEnum->GetValueByIndex(EnumIndex);
if (EnabledSplinePointTypes.Contains(Value))
{
SplinePointTypes.Add(MakeShareable(new FString(SplinePointTypeEnum->GetNameStringByIndex(EnumIndex))));
}
}
check(InOwningSplineComponent);
if (InOwningSplineComponent->IsTemplate())
{
// For blueprints, SplineComp will be set to the preview actor in UpdateValues().
SplineComp = nullptr;
SplineCompArchetype = InOwningSplineComponent;
}
else
{
SplineComp = InOwningSplineComponent;
SplineCompArchetype = nullptr;
}
bAlreadyWarnedInvalidIndex = false;
}
void FSplinePointDetails::SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren)
{
OnRegenerateChildren = InOnRegenerateChildren;
}
void FSplinePointDetails::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow)
{
}
void FSplinePointDetails::GenerateSplinePointSelectionControls(IDetailChildrenBuilder& ChildrenBuilder)
{
FMargin ButtonPadding(2.f, 0.f);
ChildrenBuilder.AddCustomRow(LOCTEXT("SelectSplinePoints", "Select Spline Points"))
.RowTag("SelectSplinePoints")
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("SelectSplinePoints", "Select Spline Points"))
]
.ValueContent()
.VAlign(VAlign_Fill)
.MaxDesiredWidth(170.f)
.MinDesiredWidth(170.f)
[
SNew(SHorizontalBox)
.Clipping(EWidgetClipping::ClipToBounds)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.SelectFirst")
.ContentPadding(2.0f)
.ToolTipText(LOCTEXT("SelectFirstSplinePointToolTip", "Select first spline point."))
.OnClicked(this, &FSplinePointDetails::OnSelectFirstLastSplinePoint, true)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.AddPrev")
.ContentPadding(2.f)
.ToolTipText(LOCTEXT("SelectAddPrevSplinePointToolTip", "Add previous spline point to current selection."))
.OnClicked(this, &FSplinePointDetails::OnSelectPrevNextSplinePoint, false, true)
.IsEnabled(this, &FSplinePointDetails::ArePointsSelected)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.SelectPrev")
.ContentPadding(2.f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("SelectPrevSplinePointToolTip", "Select previous spline point."))
.OnClicked(this, &FSplinePointDetails::OnSelectPrevNextSplinePoint, false, false)
.IsEnabled(this, &FSplinePointDetails::ArePointsSelected)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.SelectAll")
.ContentPadding(2.f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("SelectAllSplinePointToolTip", "Select all spline points."))
.OnClicked(this, &FSplinePointDetails::OnSelectAllSplinePoints)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.SelectNext")
.ContentPadding(2.f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("SelectNextSplinePointToolTip", "Select next spline point."))
.OnClicked(this, &FSplinePointDetails::OnSelectPrevNextSplinePoint, true, false)
.IsEnabled(this, &FSplinePointDetails::ArePointsSelected)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.AddNext")
.ContentPadding(2.f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("SelectAddNextSplinePointToolTip", "Add next spline point to current selection."))
.OnClicked(this, &FSplinePointDetails::OnSelectPrevNextSplinePoint, true, true)
.IsEnabled(this, &FSplinePointDetails::ArePointsSelected)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(ButtonPadding)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SplineComponentDetails.SelectLast")
.ContentPadding(2.f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ToolTipText(LOCTEXT("SelectLastSplinePointToolTip", "Select last spline point."))
.OnClicked(this, &FSplinePointDetails::OnSelectFirstLastSplinePoint, false)
]
];
}
void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
{
// Select spline point buttons
GenerateSplinePointSelectionControls(ChildrenBuilder);
// Message which is shown when no points are selected
ChildrenBuilder.AddCustomRow(LOCTEXT("NoneSelected", "None selected"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsDisabled))
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("NoPointsSelected", "No spline points are selected."))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
];
if (!SplineComp)
{
return;
}
// Input key
ChildrenBuilder.AddCustomRow(LOCTEXT("InputKey", "Input Key"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("InputKey", "Input Key"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SNumericEntryBox<float>)
.IsEnabled(TAttribute<bool>(this, &FSplinePointDetails::IsOnePointSelected))
.Value(this, &FSplinePointDetails::GetInputKey)
.UndeterminedString(LOCTEXT("Multiple", "Multiple"))
.OnValueCommitted(this, &FSplinePointDetails::OnSetInputKey)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
// Position
if (SplineComp->AllowsSpinePointLocationEditing())
{
ChildrenBuilder.AddCustomRow(LOCTEXT("Location", "Location"))
.CopyAction(CreateCopyAction(ESplinePointProperty::Location))
.PasteAction(CreatePasteAction(ESplinePointProperty::Location))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
BuildSplinePointPropertyLabel(ESplinePointProperty::Location)
]
.ValueContent()
.MinDesiredWidth(375.0f)
.MaxDesiredWidth(375.0f)
[
SNew(SVectorInputBox)
.X(this, &FSplinePointDetails::GetPositionX)
.Y(this, &FSplinePointDetails::GetPositionY)
.Z(this, &FSplinePointDetails::GetPositionZ)
.AllowSpin(true)
.bColorAxisLabels(true)
.SpinDelta(1.f)
.OnXChanged(this, &FSplinePointDetails::OnSetPosition, ETextCommit::Default, EAxis::X)
.OnYChanged(this, &FSplinePointDetails::OnSetPosition, ETextCommit::Default, EAxis::Y)
.OnZChanged(this, &FSplinePointDetails::OnSetPosition, ETextCommit::Default, EAxis::Z)
.OnXCommitted(this, &FSplinePointDetails::OnSetPosition, EAxis::X)
.OnYCommitted(this, &FSplinePointDetails::OnSetPosition, EAxis::Y)
.OnZCommitted(this, &FSplinePointDetails::OnSetPosition, EAxis::Z)
.OnBeginSliderMovement(this, &FSplinePointDetails::OnBeginPositionSlider)
.OnEndSliderMovement(this, &FSplinePointDetails::OnEndSlider)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
// Rotation
if (SplineComp->AllowsSplinePointRotationEditing())
{
ChildrenBuilder.AddCustomRow(LOCTEXT("Rotation", "Rotation"))
.CopyAction(CreateCopyAction(ESplinePointProperty::Rotation))
.PasteAction(CreatePasteAction(ESplinePointProperty::Rotation))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
BuildSplinePointPropertyLabel(ESplinePointProperty::Rotation)
]
.ValueContent()
.MinDesiredWidth(375.0f)
.MaxDesiredWidth(375.0f)
[
SNew(SRotatorInputBox)
.Roll(this, &FSplinePointDetails::GetRotationRoll)
.Pitch(this, &FSplinePointDetails::GetRotationPitch)
.Yaw(this, &FSplinePointDetails::GetRotationYaw)
.AllowSpin(false)
.bColorAxisLabels(false)
.OnRollCommitted(this, &FSplinePointDetails::OnSetRotation, EAxis::X)
.OnPitchCommitted(this, &FSplinePointDetails::OnSetRotation, EAxis::Y)
.OnYawCommitted(this, &FSplinePointDetails::OnSetRotation, EAxis::Z)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
// Scale
if (SplineComp->AllowsSplinePointScaleEditing())
{
ChildrenBuilder.AddCustomRow(LOCTEXT("Scale", "Scale"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.CopyAction(CreateCopyAction(ESplinePointProperty::Scale))
.PasteAction(CreatePasteAction(ESplinePointProperty::Scale))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("ScaleLabel", "Scale"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(375.0f)
.MaxDesiredWidth(375.0f)
[
SNew(SVectorInputBox)
.X(this, &FSplinePointDetails::GetScaleX)
.Y(this, &FSplinePointDetails::GetScaleY)
.Z(this, &FSplinePointDetails::GetScaleZ)
.AllowSpin(true)
.bColorAxisLabels(true)
.OnXChanged(this, &FSplinePointDetails::OnSetScale, ETextCommit::Default, EAxis::X)
.OnYChanged(this, &FSplinePointDetails::OnSetScale, ETextCommit::Default, EAxis::Y)
.OnZChanged(this, &FSplinePointDetails::OnSetScale, ETextCommit::Default, EAxis::Z)
.OnXCommitted(this, &FSplinePointDetails::OnSetScale, EAxis::X)
.OnYCommitted(this, &FSplinePointDetails::OnSetScale, EAxis::Y)
.OnZCommitted(this, &FSplinePointDetails::OnSetScale, EAxis::Z)
.OnBeginSliderMovement(this, &FSplinePointDetails::OnBeginScaleSlider)
.OnEndSliderMovement(this, &FSplinePointDetails::OnEndSlider)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
// ArriveTangent
if (SplineComp->AllowsSplinePointArriveTangentEditing())
{
ChildrenBuilder.AddCustomRow(LOCTEXT("ArriveTangent", "Arrive Tangent"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.CopyAction(CreateCopyAction(ESplinePointProperty::ArriveTangent))
.PasteAction(CreatePasteAction(ESplinePointProperty::ArriveTangent))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("ArriveTangent", "Arrive Tangent"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(375.0f)
.MaxDesiredWidth(375.0f)
[
SNew(SVectorInputBox)
.X(this, &FSplinePointDetails::GetArriveTangentX)
.Y(this, &FSplinePointDetails::GetArriveTangentY)
.Z(this, &FSplinePointDetails::GetArriveTangentZ)
.AllowSpin(false)
.bColorAxisLabels(false)
.OnXCommitted(this, &FSplinePointDetails::OnSetArriveTangent, EAxis::X)
.OnYCommitted(this, &FSplinePointDetails::OnSetArriveTangent, EAxis::Y)
.OnZCommitted(this, &FSplinePointDetails::OnSetArriveTangent, EAxis::Z)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
// LeaveTangent
if (SplineComp->AllowsSplinePointLeaveTangentEditing())
{
ChildrenBuilder.AddCustomRow(LOCTEXT("LeaveTangent", "Leave Tangent"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.CopyAction(CreateCopyAction(ESplinePointProperty::LeaveTangent))
.PasteAction(CreatePasteAction(ESplinePointProperty::LeaveTangent))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("LeaveTangent", "Leave Tangent"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(375.0f)
.MaxDesiredWidth(375.0f)
[
SNew(SVectorInputBox)
.X(this, &FSplinePointDetails::GetLeaveTangentX)
.Y(this, &FSplinePointDetails::GetLeaveTangentY)
.Z(this, &FSplinePointDetails::GetLeaveTangentZ)
.AllowSpin(false)
.bColorAxisLabels(false)
.OnXCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, EAxis::X)
.OnYCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, EAxis::Y)
.OnZCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, EAxis::Z)
.Font(IDetailLayoutBuilder::GetDetailFont())
];
}
// Type
if (SplineComp->GetEnabledSplinePointTypes().Num() > 1)
{
ChildrenBuilder.AddCustomRow(LOCTEXT("Type", "Type"))
.Visibility(TAttribute<EVisibility>(this, &FSplinePointDetails::IsEnabled))
.NameContent()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("Type", "Type"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(125.0f)
.MaxDesiredWidth(125.0f)
[
SNew(SComboBox<TSharedPtr<FString>>)
.OptionsSource(&SplinePointTypes)
.OnGenerateWidget(this, &FSplinePointDetails::OnGenerateComboWidget)
.OnSelectionChanged(this, &FSplinePointDetails::OnSplinePointTypeChanged)
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FSplinePointDetails::GetPointType)
]
];
}
if (SplineVisualizer.IsValid() && SplineVisualizer->GetSelectedKeys().Num() > 0)
{
for (TObjectIterator<UClass> ClassIterator; ClassIterator; ++ClassIterator)
{
if (ClassIterator->IsChildOf(USplineMetadataDetailsFactoryBase::StaticClass()) && !ClassIterator->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists))
{
USplineMetadataDetailsFactoryBase* Factory = ClassIterator->GetDefaultObject<USplineMetadataDetailsFactoryBase>();
const USplineMetadata* SplineMetadata = SplineComp->GetSplinePointsMetadata();
if (SplineMetadata && SplineMetadata->GetClass() == Factory->GetMetadataClass())
{
SplineMetaDataDetails = Factory->Create();
IDetailGroup& Group = ChildrenBuilder.AddGroup(SplineMetaDataDetails->GetName(), SplineMetaDataDetails->GetDisplayName());
SplineMetaDataDetails->GenerateChildContent(Group);
break;
}
}
}
}
}
void FSplinePointDetails::Tick(float DeltaTime)
{
UpdateValues();
}
void FSplinePointDetails::UpdateValues()
{
// If this is a blueprint spline, always update the spline component based on
// the spline component visualizer's currently edited spline component.
if (SplineCompArchetype)
{
USplineComponent* EditedSplineComp = SplineVisualizer.IsValid() ? SplineVisualizer->GetEditedSplineComponent() : nullptr;
if (!EditedSplineComp || (EditedSplineComp->GetArchetype() != SplineCompArchetype))
{
return;
}
SplineComp = EditedSplineComp;
}
if (!SplineComp || !SplineVisualizer.IsValid())
{
return;
}
bool bNeedsRebuild = false;
const TSet<int32>& NewSelectedKeys = SplineVisualizer->GetSelectedKeys();
if (NewSelectedKeys.Num() != SelectedKeys.Num())
{
bNeedsRebuild = true;
}
SelectedKeys = NewSelectedKeys;
// Cache values to be shown by the details customization.
// An unset optional value represents 'multiple values' (in the case where multiple points are selected).
InputKey.Reset();
Position.Reset();
ArriveTangent.Reset();
LeaveTangent.Reset();
Rotation.Reset();
Scale.Reset();
PointType.Reset();
// Only display point details when there are selected keys
if (SelectedKeys.Num() > 0)
{
bool bValidIndices = true;
for (int32 Index : SelectedKeys)
{
if (Index < 0 ||
Index >= SplineComp->GetSplinePointsPosition().Points.Num() ||
Index >= SplineComp->GetSplinePointsRotation().Points.Num() ||
Index >= SplineComp->GetSplinePointsScale().Points.Num())
{
bValidIndices = false;
if (!bAlreadyWarnedInvalidIndex)
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Spline component details selected keys contains invalid index %d for spline %s with %d points, %d rotations, %d scales"),
Index,
*SplineComp->GetPathName(),
SplineComp->GetSplinePointsPosition().Points.Num(),
SplineComp->GetSplinePointsRotation().Points.Num(),
SplineComp->GetSplinePointsScale().Points.Num());
bAlreadyWarnedInvalidIndex = true;
}
break;
}
}
if (bValidIndices)
{
for (int32 Index : SelectedKeys)
{
const FTransform SplineToWorld = SplineComp->GetComponentToWorld();
if (bEditingLocationAbsolute)
{
const FVector AbsoluteLocation = SplineToWorld.TransformPosition(SplineComp->GetSplinePointsPosition().Points[Index].OutVal);
Position.Add(AbsoluteLocation);
}
else
{
Position.Add(SplineComp->GetSplinePointsPosition().Points[Index].OutVal);
}
if (bEditingRotationAbsolute)
{
const FQuat AbsoluteRotation = SplineToWorld.TransformRotation(SplineComp->GetSplinePointsRotation().Points[Index].OutVal);
Rotation.Add(AbsoluteRotation.Rotator());
}
else
{
Rotation.Add(SplineComp->GetSplinePointsRotation().Points[Index].OutVal.Rotator());
}
InputKey.Add(SplineComp->GetSplinePointsPosition().Points[Index].InVal);
Scale.Add(SplineComp->GetSplinePointsScale().Points[Index].OutVal);
ArriveTangent.Add(SplineComp->GetSplinePointsPosition().Points[Index].ArriveTangent);
LeaveTangent.Add(SplineComp->GetSplinePointsPosition().Points[Index].LeaveTangent);
PointType.Add(ConvertInterpCurveModeToSplinePointType(SplineComp->GetSplinePointsPosition().Points[Index].InterpMode));
}
if (SplineMetaDataDetails)
{
SplineMetaDataDetails->Update(SplineComp, SelectedKeys);
}
}
}
if (bNeedsRebuild)
{
OnRegenerateChildren.ExecuteIfBound();
}
}
FName FSplinePointDetails::GetName() const
{
static const FName Name("SplinePointDetails");
return Name;
}
void FSplinePointDetails::OnSetInputKey(float NewValue, ETextCommit::Type CommitInfo)
{
if ((CommitInfo != ETextCommit::OnEnter && CommitInfo != ETextCommit::OnUserMovedFocus) || !SplineComp)
{
return;
}
check(SelectedKeys.Num() == 1);
const int32 Index = *SelectedKeys.CreateConstIterator();
TArray<FInterpCurvePoint<FVector>>& Positions = SplineComp->GetSplinePointsPosition().Points;
const int32 NumPoints = Positions.Num();
bool bModifyOtherPoints = false;
if ((Index > 0 && NewValue <= Positions[Index - 1].InVal) ||
(Index < NumPoints - 1 && NewValue >= Positions[Index + 1].InVal))
{
const FText Title(LOCTEXT("InputKeyTitle", "Input key out of range"));
const FText Message(LOCTEXT("InputKeyMessage", "Spline input keys must be numerically ascending. Would you like to modify other input keys in the spline in order to be able to set this value?"));
// Ensure input keys remain ascending
if (FMessageDialog::Open(EAppMsgType::YesNo, Message, &Title) == EAppReturnType::No)
{
return;
}
bModifyOtherPoints = true;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointInputKey", "Set spline point input key"));
SplineComp->Modify();
TArray<FInterpCurvePoint<FQuat>>& Rotations = SplineComp->GetSplinePointsRotation().Points;
TArray<FInterpCurvePoint<FVector>>& Scales = SplineComp->GetSplinePointsScale().Points;
if (bModifyOtherPoints)
{
// Shuffle the previous or next input keys down or up so the input value remains in sequence
if (Index > 0 && NewValue <= Positions[Index - 1].InVal)
{
float Delta = (NewValue - Positions[Index].InVal);
for (int32 PrevIndex = 0; PrevIndex < Index; PrevIndex++)
{
Positions[PrevIndex].InVal += Delta;
Rotations[PrevIndex].InVal += Delta;
Scales[PrevIndex].InVal += Delta;
}
}
else if (Index < NumPoints - 1 && NewValue >= Positions[Index + 1].InVal)
{
float Delta = (NewValue - Positions[Index].InVal);
for (int32 NextIndex = Index + 1; NextIndex < NumPoints; NextIndex++)
{
Positions[NextIndex].InVal += Delta;
Rotations[NextIndex].InVal += Delta;
Scales[NextIndex].InVal += Delta;
}
}
}
Positions[Index].InVal = NewValue;
Rotations[Index].InVal = NewValue;
Scales[Index].InVal = NewValue;
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty);
UpdateValues();
GEditor->RedrawLevelEditingViewports(true);
}
void FSplinePointDetails::OnSetPosition(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointPosition", "Set spline point position"), !bInSliderTransaction);
SplineComp->Modify();
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsPosition().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point location: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsPosition().Points.Num());
continue;
}
if (bEditingLocationAbsolute)
{
const FTransform SplineToWorld = SplineComp->GetComponentToWorld();
const FVector RelativePos = SplineComp->GetSplinePointsPosition().Points[Index].OutVal;
FVector AbsolutePos = SplineToWorld.TransformPosition(RelativePos);
AbsolutePos.SetComponentForAxis(Axis, NewValue);
FVector PointPosition = SplineToWorld.InverseTransformPosition(AbsolutePos);
SplineComp->GetSplinePointsPosition().Points[Index].OutVal = PointPosition;
}
else
{
FVector PointPosition = SplineComp->GetSplinePointsPosition().Points[Index].OutVal;
PointPosition.SetComponentForAxis(Axis, NewValue);
SplineComp->GetSplinePointsPosition().Points[Index].OutVal = PointPosition;
}
}
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty, EPropertyChangeType::ValueSet);
UpdateValues();
}
GEditor->RedrawLevelEditingViewports(true);
}
void FSplinePointDetails::OnSetArriveTangent(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointTangent", "Set spline point tangent"));
SplineComp->Modify();
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsPosition().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point arrive tangent: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsPosition().Points.Num());
continue;
}
FVector PointTangent = SplineComp->GetSplinePointsPosition().Points[Index].ArriveTangent;
PointTangent.SetComponentForAxis(Axis, NewValue);
SplineComp->GetSplinePointsPosition().Points[Index].ArriveTangent = PointTangent;
SplineComp->GetSplinePointsPosition().Points[Index].InterpMode = CIM_CurveUser;
}
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty, EPropertyChangeType::ValueSet);
UpdateValues();
}
GEditor->RedrawLevelEditingViewports(true);
}
void FSplinePointDetails::OnSetLeaveTangent(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointTangent", "Set spline point tangent"));
SplineComp->Modify();
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsPosition().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point leave tangent: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsPosition().Points.Num());
continue;
}
FVector PointTangent = SplineComp->GetSplinePointsPosition().Points[Index].LeaveTangent;
PointTangent.SetComponentForAxis(Axis, NewValue);
SplineComp->GetSplinePointsPosition().Points[Index].LeaveTangent = PointTangent;
SplineComp->GetSplinePointsPosition().Points[Index].InterpMode = CIM_CurveUser;
}
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty, EPropertyChangeType::ValueSet);
UpdateValues();
}
GEditor->RedrawLevelEditingViewports(true);
}
void FSplinePointDetails::OnSetRotation(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointRotation", "Set spline point rotation"));
SplineComp->Modify();
FQuat SplineComponentRotation = SplineComp->GetComponentQuat();
FQuat NewRotationRelative;
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsRotation().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point rotation: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsRotation().Points.Num());
continue;
}
FInterpCurvePoint<FVector>& EditedPoint = SplineComp->GetSplinePointsPosition().Points[Index];
FInterpCurvePoint<FQuat>& EditedRotPoint = SplineComp->GetSplinePointsRotation().Points[Index];
const FQuat CurrentRotationRelative = EditedRotPoint.OutVal;
if (bEditingRotationAbsolute)
{
FRotator AbsoluteRot = (SplineComponentRotation * CurrentRotationRelative).Rotator();
switch (Axis)
{
case EAxis::X: AbsoluteRot.Roll = NewValue; break;
case EAxis::Y: AbsoluteRot.Pitch = NewValue; break;
case EAxis::Z: AbsoluteRot.Yaw = NewValue; break;
}
NewRotationRelative = SplineComponentRotation.Inverse() * AbsoluteRot.Quaternion();
}
else
{
FRotator NewRotationRotator(CurrentRotationRelative);
switch (Axis)
{
case EAxis::X: NewRotationRotator.Roll = NewValue; break;
case EAxis::Y: NewRotationRotator.Pitch = NewValue; break;
case EAxis::Z: NewRotationRotator.Yaw = NewValue; break;
}
NewRotationRelative = NewRotationRotator.Quaternion();
}
SplineComp->GetSplinePointsRotation().Points[Index].OutVal = NewRotationRelative;
FQuat DeltaRotate(NewRotationRelative * CurrentRotationRelative.Inverse());
// Rotate tangent according to delta rotation
FVector NewTangent = SplineComponentRotation.RotateVector(EditedPoint.LeaveTangent); // convert local-space tangent vector to world-space
NewTangent = DeltaRotate.RotateVector(NewTangent); // apply world-space delta rotation to world-space tangent
NewTangent = SplineComponentRotation.Inverse().RotateVector(NewTangent); // convert world-space tangent vector back into local-space
EditedPoint.LeaveTangent = NewTangent;
EditedPoint.ArriveTangent = NewTangent;
}
SplineVisualizer->SetCachedRotation(NewRotationRelative);
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty, EPropertyChangeType::ValueSet);
UpdateValues();
}
GEditor->RedrawLevelEditingViewports(true);
}
void FSplinePointDetails::OnSetScale(float NewValue, ETextCommit::Type CommitInfo, EAxis::Type Axis)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointScale", "Set spline point scale"));
SplineComp->Modify();
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsScale().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point scale: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsScale().Points.Num());
continue;
}
FVector PointScale = SplineComp->GetSplinePointsScale().Points[Index].OutVal;
PointScale.SetComponentForAxis(Axis, NewValue);
SplineComp->GetSplinePointsScale().Points[Index].OutVal = PointScale;
}
if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus)
{
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty, EPropertyChangeType::ValueSet);
UpdateValues();
}
GEditor->RedrawLevelEditingViewports(true);
}
FText FSplinePointDetails::GetPointType() const
{
if (PointType.Value.IsSet())
{
const UEnum* SplinePointTypeEnum = StaticEnum<ESplinePointType::Type>();
check(SplinePointTypeEnum);
return SplinePointTypeEnum->GetDisplayNameTextByValue(PointType.Value.GetValue());
}
return LOCTEXT("MultipleTypes", "Multiple Types");
}
void FSplinePointDetails::OnSplinePointTypeChanged(TSharedPtr<FString> NewValue, ESelectInfo::Type SelectInfo)
{
if (!SplineComp)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSplinePointType", "Set spline point type"));
SplineComp->Modify();
EInterpCurveMode Mode = ConvertSplinePointTypeToInterpCurveMode((ESplinePointType::Type)SplinePointTypes.Find(NewValue));
for (int32 Index : SelectedKeys)
{
if (Index < 0 || Index >= SplineComp->GetSplinePointsPosition().Points.Num())
{
UE_LOG(LogSplineComponentDetails, Error, TEXT("Set spline point type: invalid index %d in selected points for spline component %s which contains %d spline points."),
Index, *SplineComp->GetPathName(), SplineComp->GetSplinePointsPosition().Points.Num());
continue;
}
SplineComp->GetSplinePointsPosition().Points[Index].InterpMode = Mode;
}
SplineComp->UpdateSpline();
SplineComp->bSplineHasBeenEdited = true;
FComponentVisualizer::NotifyPropertyModified(SplineComp, SplineCurvesProperty);
UpdateValues();
GEditor->RedrawLevelEditingViewports(true);
}
USplineComponent* FSplinePointDetails::GetSplineComponentToVisualize() const
{
if (SplineCompArchetype)
{
check(SplineCompArchetype->IsTemplate());
FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>("Kismet");
const UClass* BPClass;
if (const AActor* OwningCDO = SplineCompArchetype->GetOwner())
{
// Native component template
BPClass = OwningCDO->GetClass();
}
else
{
// Non-native component template
BPClass = Cast<UClass>(SplineCompArchetype->GetOuter());
}
if (BPClass)
{
if (UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(BPClass))
{
if (FBlueprintEditor* BlueprintEditor = StaticCast<FBlueprintEditor*>(GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Blueprint, false)))
{
const AActor* PreviewActor = BlueprintEditor->GetPreviewActor();
TArray<UObject*> Instances;
SplineCompArchetype->GetArchetypeInstances(Instances);
for (UObject* Instance : Instances)
{
USplineComponent* SplineCompInstance = Cast<USplineComponent>(Instance);
if (SplineCompInstance->GetOwner() == PreviewActor)
{
return SplineCompInstance;
}
}
}
}
}
// If we failed to find an archetype instance, must return nullptr
// since component visualizer cannot visualize the archetype.
return nullptr;
}
return SplineComp;
}
FReply FSplinePointDetails::OnSelectFirstLastSplinePoint(bool bFirst)
{
if (SplineVisualizer.IsValid())
{
bool bActivateComponentVis = false;
if (!SplineComp)
{
SplineComp = GetSplineComponentToVisualize();
bActivateComponentVis = true;
}
if (SplineComp)
{
if (SplineVisualizer->HandleSelectFirstLastSplinePoint(SplineComp, bFirst))
{
if (bActivateComponentVis)
{
TSharedPtr<FComponentVisualizer> Visualizer = StaticCastSharedPtr<FComponentVisualizer>(SplineVisualizer);
GUnrealEd->ComponentVisManager.SetActiveComponentVis(GCurrentLevelEditingViewportClient, Visualizer);
}
}
}
}
return FReply::Handled();
}
FReply FSplinePointDetails::OnSelectPrevNextSplinePoint(bool bNext, bool bAddToSelection)
{
if (SplineVisualizer.IsValid())
{
SplineVisualizer->OnSelectPrevNextSplinePoint(bNext, bAddToSelection);
}
return FReply::Handled();
}
FReply FSplinePointDetails::OnSelectAllSplinePoints()
{
if (SplineVisualizer.IsValid())
{
bool bActivateComponentVis = false;
if (!SplineComp)
{
SplineComp = GetSplineComponentToVisualize();
bActivateComponentVis = true;
}
if (SplineComp)
{
if (SplineVisualizer->HandleSelectAllSplinePoints(SplineComp))
{
if (bActivateComponentVis)
{
TSharedPtr<FComponentVisualizer> Visualizer = StaticCastSharedPtr<FComponentVisualizer>(SplineVisualizer);
GUnrealEd->ComponentVisManager.SetActiveComponentVis(GCurrentLevelEditingViewportClient, Visualizer);
}
}
}
}
return FReply::Handled();
}
TSharedRef<SWidget> FSplinePointDetails::OnGenerateComboWidget(TSharedPtr<FString> InComboString)
{
return SNew(STextBlock)
.Text(FText::FromString(*InComboString))
.Font(IDetailLayoutBuilder::GetDetailFont());
}
TSharedRef<SWidget> FSplinePointDetails::BuildSplinePointPropertyLabel(ESplinePointProperty SplinePointProp)
{
FText Label;
switch (SplinePointProp)
{
case ESplinePointProperty::Rotation:
Label = LOCTEXT("RotationLabel", "Rotation");
break;
case ESplinePointProperty::Location:
Label = LOCTEXT("LocationLabel", "Location");
break;
default:
return SNullWidget::NullWidget;
}
FMenuBuilder MenuBuilder(true, NULL, NULL);
FUIAction SetRelativeLocationAction
(
FExecuteAction::CreateSP(this, &FSplinePointDetails::OnSetTransformEditingAbsolute, SplinePointProp, false),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FSplinePointDetails::IsTransformEditingRelative, SplinePointProp)
);
FUIAction SetWorldLocationAction
(
FExecuteAction::CreateSP(this, &FSplinePointDetails::OnSetTransformEditingAbsolute, SplinePointProp, true),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FSplinePointDetails::IsTransformEditingAbsolute, SplinePointProp)
);
MenuBuilder.BeginSection(TEXT("TransformType"), FText::Format(LOCTEXT("TransformType", "{0} Type"), Label));
MenuBuilder.AddMenuEntry
(
FText::Format(LOCTEXT("RelativeLabel", "Relative"), Label),
FText::Format(LOCTEXT("RelativeLabel_ToolTip", "{0} is relative to its parent"), Label),
FSlateIcon(),
SetRelativeLocationAction,
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry
(
FText::Format(LOCTEXT("WorldLabel", "World"), Label),
FText::Format(LOCTEXT("WorldLabel_ToolTip", "{0} is relative to the world"), Label),
FSlateIcon(),
SetWorldLocationAction,
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.EndSection();
return
SNew(SComboButton)
.ContentPadding(0)
.ButtonStyle(FAppStyle::Get(), "NoBorder")
.ForegroundColor(FSlateColor::UseForeground())
.MenuContent()
[
MenuBuilder.MakeWidget()
]
.ButtonContent()
[
SNew(SBox)
.Padding(FMargin(0.0f, 0.0f, 2.0f, 0.0f))
[
SNew(STextBlock)
.Text(this, &FSplinePointDetails::GetSplinePointPropertyText, SplinePointProp)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
];
}
void FSplinePointDetails::OnSetTransformEditingAbsolute(ESplinePointProperty SplinePointProp, bool bIsAbsolute)
{
if (SplinePointProp == ESplinePointProperty::Location)
{
bEditingLocationAbsolute = bIsAbsolute;
}
else if (SplinePointProp == ESplinePointProperty::Rotation)
{
bEditingRotationAbsolute = bIsAbsolute;
}
else
{
return;
}
UpdateValues();
}
bool FSplinePointDetails::IsTransformEditingAbsolute(ESplinePointProperty SplinePointProp) const
{
if (SplinePointProp == ESplinePointProperty::Location)
{
return bEditingLocationAbsolute;
}
else if (SplinePointProp == ESplinePointProperty::Rotation)
{
return bEditingRotationAbsolute;
}
return false;
}
bool FSplinePointDetails::IsTransformEditingRelative(ESplinePointProperty SplinePointProp) const
{
if (SplinePointProp == ESplinePointProperty::Location)
{
return !bEditingLocationAbsolute;
}
else if (SplinePointProp == ESplinePointProperty::Rotation)
{
return !bEditingRotationAbsolute;
}
return false;
}
FText FSplinePointDetails::GetSplinePointPropertyText(ESplinePointProperty SplinePointProp) const
{
if (SplinePointProp == ESplinePointProperty::Location)
{
return bEditingLocationAbsolute ? LOCTEXT("AbsoluteLocation", "Absolute Location") : LOCTEXT("Location", "Location");
}
else if (SplinePointProp == ESplinePointProperty::Rotation)
{
return bEditingRotationAbsolute ? LOCTEXT("AbsoluteRotation", "Absolute Rotation") : LOCTEXT("Rotation", "Rotation");
}
return FText::GetEmpty();
}
void FSplinePointDetails::SetSplinePointProperty(ESplinePointProperty SplinePointProp, FVector NewValue, EAxisList::Type Axis, bool bCommitted)
{
switch (SplinePointProp)
{
case ESplinePointProperty::Location:
OnSetPosition(NewValue.X, ETextCommit::Default, EAxis::X);
OnSetPosition(NewValue.Y, ETextCommit::Default, EAxis::Y);
OnSetPosition(NewValue.Z, ETextCommit::OnEnter, EAxis::Z);
break;
case ESplinePointProperty::Rotation:
OnSetRotation(NewValue.X, ETextCommit::Default, EAxis::X);
OnSetRotation(NewValue.Y, ETextCommit::Default, EAxis::Y);
OnSetRotation(NewValue.Z, ETextCommit::OnEnter, EAxis::Z);
break;
case ESplinePointProperty::Scale:
OnSetScale(NewValue.X, ETextCommit::Default, EAxis::X);
OnSetScale(NewValue.Y, ETextCommit::Default, EAxis::Y);
OnSetScale(NewValue.Z, ETextCommit::OnEnter, EAxis::Z);
break;
case ESplinePointProperty::ArriveTangent:
OnSetArriveTangent(NewValue.X, ETextCommit::Default, EAxis::X);
OnSetArriveTangent(NewValue.Y, ETextCommit::Default, EAxis::Y);
OnSetArriveTangent(NewValue.Z, ETextCommit::OnEnter, EAxis::Z);
break;
case ESplinePointProperty::LeaveTangent:
OnSetLeaveTangent(NewValue.X, ETextCommit::Default, EAxis::X);
OnSetLeaveTangent(NewValue.Y, ETextCommit::Default, EAxis::Y);
OnSetLeaveTangent(NewValue.Z, ETextCommit::OnEnter, EAxis::Z);
break;
default:
break;
}
}
FUIAction FSplinePointDetails::CreateCopyAction(ESplinePointProperty SplinePointProp)
{
return
FUIAction
(
FExecuteAction::CreateSP(this, &FSplinePointDetails::OnCopy, SplinePointProp),
FCanExecuteAction::CreateSP(this, &FSplinePointDetails::OnCanCopy, SplinePointProp)
);
}
FUIAction FSplinePointDetails::CreatePasteAction(ESplinePointProperty SplinePointProp)
{
return
FUIAction(FExecuteAction::CreateSP(this, &FSplinePointDetails::OnPaste, SplinePointProp));
}
void FSplinePointDetails::OnCopy(ESplinePointProperty SplinePointProp)
{
FString CopyStr;
switch (SplinePointProp)
{
case ESplinePointProperty::Location:
CopyStr = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), Position.X.GetValue(), Position.Y.GetValue(), Position.Z.GetValue());
break;
case ESplinePointProperty::Rotation:
CopyStr = FString::Printf(TEXT("(Pitch=%f,Yaw=%f,Roll=%f)"), Rotation.Pitch.GetValue(), Rotation.Yaw.GetValue(), Rotation.Roll.GetValue());
break;
case ESplinePointProperty::Scale:
CopyStr = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), Scale.X.GetValue(), Scale.Y.GetValue(), Scale.Z.GetValue());
break;
case ESplinePointProperty::ArriveTangent:
CopyStr = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), ArriveTangent.X.GetValue(), ArriveTangent.Y.GetValue(), ArriveTangent.Z.GetValue());
break;
case ESplinePointProperty::LeaveTangent:
CopyStr = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), LeaveTangent.X.GetValue(), LeaveTangent.Y.GetValue(), LeaveTangent.Z.GetValue());
break;
default:
break;
}
if (!CopyStr.IsEmpty())
{
FPlatformApplicationMisc::ClipboardCopy(*CopyStr);
}
}
void FSplinePointDetails::OnPaste(ESplinePointProperty SplinePointProp)
{
FString PastedText;
FPlatformApplicationMisc::ClipboardPaste(PastedText);
switch (SplinePointProp)
{
case ESplinePointProperty::Location:
{
FVector NewLocation;
if (NewLocation.InitFromString(PastedText))
{
FScopedTransaction Transaction(LOCTEXT("PasteLocation", "Paste Location"));
SetSplinePointProperty(ESplinePointProperty::Location, NewLocation, EAxisList::All, true);
}
break;
}
case ESplinePointProperty::Rotation:
{
FVector NewRotation;
PastedText.ReplaceInline(TEXT("Pitch="), TEXT("X="));
PastedText.ReplaceInline(TEXT("Yaw="), TEXT("Y="));
PastedText.ReplaceInline(TEXT("Roll="), TEXT("Z="));
if (NewRotation.InitFromString(PastedText))
{
FScopedTransaction Transaction(LOCTEXT("PasteRotation", "Paste Rotation"));
SetSplinePointProperty(ESplinePointProperty::Rotation, NewRotation, EAxisList::All, true);
}
break;
}
case ESplinePointProperty::Scale:
{
FVector NewScale;
if (NewScale.InitFromString(PastedText))
{
FScopedTransaction Transaction(LOCTEXT("PasteScale", "Paste Scale"));
SetSplinePointProperty(ESplinePointProperty::Scale, NewScale, EAxisList::All, true);
}
break;
}
case ESplinePointProperty::ArriveTangent:
{
FVector NewArrive;
if (NewArrive.InitFromString(PastedText))
{
FScopedTransaction Transaction(LOCTEXT("PasteArriveTangent", "Paste Arrive Tangent"));
SetSplinePointProperty(ESplinePointProperty::ArriveTangent, NewArrive, EAxisList::All, true);
}
break;
}
case ESplinePointProperty::LeaveTangent:
{
FVector NewLeave;
if (NewLeave.InitFromString(PastedText))
{
FScopedTransaction Transaction(LOCTEXT("PasteLeaveTangent", "Paste Leave Tangent"));
SetSplinePointProperty(ESplinePointProperty::LeaveTangent, NewLeave, EAxisList::All, true);
}
break;
}
default:
break;
}
}
void FSplinePointDetails::OnBeginPositionSlider()
{
bInSliderTransaction = true;
SplineComp->Modify();
GEditor->BeginTransaction(LOCTEXT("SetSplinePointPosition", "Set spline point position"));
}
void FSplinePointDetails::OnBeginScaleSlider()
{
bInSliderTransaction = true;
SplineComp->Modify();
GEditor->BeginTransaction(LOCTEXT("SetSplinePointScale", "Set spline point scale"));
}
void FSplinePointDetails::OnEndSlider(float)
{
bInSliderTransaction = false;
GEditor->EndTransaction();
}
////////////////////////////////////
TSharedRef<IDetailCustomization> FSplineComponentDetails::MakeInstance()
{
return MakeShareable(new FSplineComponentDetails);
}
void FSplineComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
// Hide the SplineCurves property
TSharedPtr<IPropertyHandle> SplineCurvesProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USplineComponent, SplineCurves));
SplineCurvesProperty->MarkHiddenByCustomization();
TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
if (ObjectsBeingCustomized.Num() == 1)
{
if (USplineComponent* SplineComp = Cast<USplineComponent>(ObjectsBeingCustomized[0]))
{
// Set the spline points details as important in order to have it on top
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("Selected Points", FText::GetEmpty(), ECategoryPriority::Important);
TSharedRef<FSplinePointDetails> SplinePointDetails = MakeShareable(new FSplinePointDetails(SplineComp));
Category.AddCustomBuilder(SplinePointDetails);
}
}
}
#undef LOCTEXT_NAMESPACE