// Copyright Epic Games, Inc. All Rights Reserved. #pragma once // HEADER_UNIT_SKIP - Bad include #include "SAdvancedRotationInputBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SButton.h" #include "Styling/AppStyle.h" #include "Textures/SlateIcon.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandInfo.h" #if WITH_EDITOR #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "IDetailChildrenBuilder.h" #include "DetailWidgetRow.h" #include "IDetailGroup.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #endif namespace ESlateTransformComponent { enum Type : int { Location, Rotation, Scale, Max }; } /** * Generic Transform Slate control */ template class SAdvancedTransformInputBox : public SCompoundWidget { public: typedef SNumericVectorInputBox, 3> SNumericVectorInputBox3; /** Delegate for notification for a transform value change */ DECLARE_DELEGATE_OneParam(FOnTransformChanged, TransformType); /** Delegate for notification for a transform value commit */ DECLARE_DELEGATE_TwoParams(FOnTransformCommitted, TransformType, ETextCommit::Type); /** Delegate to retrieve a numerictype value */ DECLARE_DELEGATE_RetVal_ThreeParams(TOptional, FOnGetNumericValue, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type); /** Notification for numerictype value change */ DECLARE_DELEGATE_FourParams(FOnNumericValueChanged, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type, NumericType); /** Notification for numerictype value committed */ DECLARE_DELEGATE_FiveParams(FOnNumericValueCommitted, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type, NumericType, ETextCommit::Type); /** Notification for begin slider movement */ DECLARE_DELEGATE_ThreeParams(FOnBeginSliderMovement, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type); /** Notification for end slider movement */ DECLARE_DELEGATE_FourParams(FOnEndSliderMovement, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type, NumericType); /** Delegate to retrieve relative / world state */ DECLARE_DELEGATE_RetVal_OneParam(bool, FOnGetIsComponentRelative, ESlateTransformComponent::Type); /** Notification for a relative / world state change */ DECLARE_DELEGATE_TwoParams(FOnIsComponentRelativeChanged, ESlateTransformComponent::Type, bool /* is relative? */); /** Notification for the rotation representation change */ DECLARE_DELEGATE_OneParam(FOnRotationRepresentationChanged, ESlateRotationRepresentation::Type); /** Notification for the scale lock change */ DECLARE_DELEGATE_OneParam(FOnScaleLockChanged, bool /* is locked? */); /** Delegate to retrieve toggle checkstate */ DECLARE_DELEGATE_RetVal_ThreeParams(ECheckBoxState, FOnGetToggleChecked, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type); /** Notification for toggle checkstate change */ DECLARE_DELEGATE_FourParams(FOnToggleChanged, ESlateTransformComponent::Type, ESlateRotationRepresentation::Type, ESlateTransformSubComponent::Type, ECheckBoxState); /** Delegate to fire when a row or the whole transform (type == max) is being copied to the clipboard */ DECLARE_DELEGATE_OneParam(FOnCopyToClipboard, ESlateTransformComponent::Type); /** Delegate to fire when a row or the whole transform (type == max) is being pasted into from the clipboard */ DECLARE_DELEGATE_OneParam(FOnPasteFromClipboard, ESlateTransformComponent::Type); /** Delegate to fire when a row or the whole transform (type == max) needs to be reset to its default */ DECLARE_DELEGATE_OneParam(FOnResetToDefault, ESlateTransformComponent::Type); /** Delegate to determine if the row or the whole transform (type == max) differs from its default */ DECLARE_DELEGATE_RetVal_OneParam(bool, FDiffersFromDefault, ESlateTransformComponent::Type); SLATE_BEGIN_ARGS( SAdvancedTransformInputBox ) : _ConstructLocation(true) , _ConstructRotation(true) , _ConstructScale(true) , _UseQuaternionForRotation(false) , _RotationRepresentation(TSharedPtr( new ESlateRotationRepresentation::Type(ESlateRotationRepresentation::Rotator))) , _bColorAxisLabels(true) , _ShowInlineLabels(false) , _LocationLabel(NSLOCTEXT("SAdvancedTransformInputBox", "Location", "Location")) , _RotationLabel(NSLOCTEXT("SAdvancedTransformInputBox", "Rotation", "Rotation")) , _ScaleLabel(NSLOCTEXT("SAdvancedTransformInputBox", "Scale", "Scale")) , _LabelPadding( FMargin( 0.f,0.f,6.f,0.f ) ) , _Font(FAppStyle::Get().GetFontStyle("NormalFont")) , _AllowSpin(true) , _LocationSpinDelta(0.01f) , _ScaleSpinDelta(0.001f) , _AllowEditRotationRepresentation(true) , _DisplayScaleLock(true) , _IsScaleLocked(TSharedPtr(new bool(false))) , _DisplayRelativeWorld(false) , _DisplayToggle( false ) , _TogglePadding( FMargin( 1.f,0.f,1.f,0.f ) ) , _PreventThrottling(false) {} /** Whether or not to construct the location component */ SLATE_ARGUMENT( bool, ConstructLocation ) /** Whether or not to construct the rotation component */ SLATE_ARGUMENT( bool, ConstructRotation ) /** Whether or not to construct the scale component */ SLATE_ARGUMENT( bool, ConstructScale ) /** Whether to use the quaternion is the primary rotation representation or the rotator (default) */ SLATE_ARGUMENT( bool, UseQuaternionForRotation ) /** Optional representation as transform */ SLATE_ATTRIBUTE( TOptional, Transform ) /** Optional delegate to notify a change in the transform */ SLATE_EVENT( FOnTransformChanged, OnTransformChanged ) /** Optional delegate to notify a commit in the transform */ SLATE_EVENT( FOnTransformCommitted, OnTransformCommitted ) /** Representation of the rotation */ SLATE_ARGUMENT( TSharedPtr, RotationRepresentation ) /** Delegate to notify a rotation representation change */ SLATE_EVENT( FOnRotationRepresentationChanged, OnRotationRepresentationChanged ) /** Delegate to retrieve a value */ SLATE_EVENT( FOnGetNumericValue, OnGetNumericValue ) /** Delegate to notify a value change */ SLATE_EVENT( FOnNumericValueChanged, OnNumericValueChanged ) /** Delegate to notify a committed value */ SLATE_EVENT( FOnNumericValueCommitted, OnNumericValueCommitted ) /** Delegate to notify a begin slider movement */ SLATE_EVENT(FOnBeginSliderMovement, OnBeginSliderMovement) /** Delegate to notify a end slider movement */ SLATE_EVENT(FOnEndSliderMovement, OnEndSliderMovement) /** Should the axis labels be colored */ SLATE_ARGUMENT( bool, bColorAxisLabels ) /** Should we show labels inline next to all input boxes */ SLATE_ARGUMENT( bool, ShowInlineLabels ) /** The label to use for the location component */ SLATE_ARGUMENT( FText, LocationLabel ) /** The label to use for the rotation component */ SLATE_ARGUMENT( FText, RotationLabel ) /** The label to use for the scale component */ SLATE_ARGUMENT( FText, ScaleLabel ) /** Padding around the label checkbox */ SLATE_ARGUMENT( FMargin, LabelPadding ) /** Font to use for the text in this box */ SLATE_ATTRIBUTE( FSlateFontInfo, Font ) /** Whether or not values can be spun or if they should be typed in */ SLATE_ARGUMENT( bool, AllowSpin ) /** The delta amount to apply, per pixel, when a location spinner is dragged. */ SLATE_ATTRIBUTE( NumericType, LocationSpinDelta ) /** The delta amount to apply, per pixel, when a scale spinner is dragged. */ SLATE_ATTRIBUTE( NumericType, ScaleSpinDelta ) /** Whether or not to display the rotation representation picker in the label */ SLATE_ARGUMENT( bool, AllowEditRotationRepresentation ) /** Whether or not to display the lock scale button */ SLATE_ARGUMENT( bool, DisplayScaleLock ) /** The state of the scale lock */ SLATE_ARGUMENT( TSharedPtr, IsScaleLocked ) /** Delegate to notify a scale lock change */ SLATE_EVENT( FOnScaleLockChanged, OnScaleLockChanged ) /** Whether or not to display the per component buttons to switch between relative and world */ SLATE_ARGUMENT( bool, DisplayRelativeWorld ) /** Delegate to retrieve a relative / world state per component */ SLATE_EVENT( FOnGetIsComponentRelative, OnGetIsComponentRelative ) /** Delegate to notify a relative / world state change */ SLATE_EVENT( FOnIsComponentRelativeChanged, OnIsComponentRelativeChanged ) /** Whether or not to include a toggle checkbox to the left of the widget */ SLATE_ARGUMENT( bool, DisplayToggle ) /** Delegate to retrieve a toggle state */ SLATE_EVENT( FOnGetToggleChecked, OnGetToggleChecked ) /** Delegate to notify a toggle state change */ SLATE_EVENT( FOnToggleChanged, OnToggleChanged ) /** Padding around the toggle checkbox */ SLATE_ARGUMENT( FMargin, TogglePadding ) /** Delegate to react to copy to clipboard */ SLATE_EVENT( FOnCopyToClipboard, OnCopyToClipboard ) /** Delegate to react to paste from clipboard */ SLATE_EVENT( FOnPasteFromClipboard, OnPasteFromClipboard ) /** Delegate to react to reset to default */ SLATE_EVENT( FOnResetToDefault, OnResetToDefault ) /** Delegate to determine if a row (or the whole transform) differs from its default */ SLATE_EVENT( FDiffersFromDefault, DiffersFromDefault ) /** If refresh requests for the viewport should happen for all value changes **/ SLATE_ARGUMENT(bool, PreventThrottling) SLATE_END_ARGS() /** * Construct this widget * * @param InArgs The declaration data for this widget */ void Construct( const FArguments& InArgs ) { TSharedRef VerticalBox = SNew(SVerticalBox); if(InArgs._ConstructLocation) { VerticalBox->AddSlot() [ ConstructWidget(InArgs, ESlateTransformComponent::Location) ]; } if(InArgs._ConstructRotation) { VerticalBox->AddSlot() [ ConstructWidget(InArgs, ESlateTransformComponent::Rotation) ]; } if(InArgs._ConstructScale) { VerticalBox->AddSlot() [ ConstructWidget(InArgs, ESlateTransformComponent::Scale) ]; } ChildSlot [ VerticalBox ]; } /** * Construct an input widget */ static TSharedRef ConstructWidget(const FArguments& InArgs, ESlateTransformComponent::Type InComponent) { SHorizontalBox::FArguments BoxArgs; ((FSlateBaseNamedArgs&)BoxArgs) = (FSlateBaseNamedArgs)InArgs; TSharedRef HorizontalBox = SArgumentNew(BoxArgs, SHorizontalBox); if(InArgs._ShowInlineLabels) { HorizontalBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(InArgs._LabelPadding) .AutoWidth() [ ConstructLabel(InArgs, InComponent) ]; } TSharedRef InputWidget = SNullWidget::NullWidget; TAttribute> Transform = InArgs._Transform; FOnTransformChanged OnTransformChanged = InArgs._OnTransformChanged; FOnTransformCommitted OnTransformCommitted = InArgs._OnTransformCommitted; FOnGetNumericValue OnGetNumericValue = InArgs._OnGetNumericValue; FOnNumericValueChanged OnNumericValueChanged = InArgs._OnNumericValueChanged; FOnNumericValueCommitted OnNumericValueCommitted = InArgs._OnNumericValueCommitted; FOnBeginSliderMovement OnBeginSliderMovement = InArgs._OnBeginSliderMovement; FOnEndSliderMovement OnEndSliderMovement = InArgs._OnEndSliderMovement; FOnGetToggleChecked OnGetToggleChecked = InArgs._OnGetToggleChecked; FOnToggleChanged OnToggleChanged = InArgs._OnToggleChanged; FOnRotationRepresentationChanged OnRotationRepresentationChanged = InArgs._OnRotationRepresentationChanged; const bool UseQuaternionForRotation = InArgs._UseQuaternionForRotation; auto OnGetLocation = [Transform, OnGetNumericValue]() -> TOptional { if(Transform.IsBound()) { TOptional Xfo = Transform.Get(); if(Xfo.IsSet()) { return Xfo->GetLocation(); } } if(OnGetNumericValue.IsBound()) { FVector Location = FVector::OneVector; Location.X = OnGetNumericValue.Execute(ESlateTransformComponent::Location, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X).Get(Location.X); Location.Y = OnGetNumericValue.Execute(ESlateTransformComponent::Location, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y).Get(Location.Y); Location.Z = OnGetNumericValue.Execute(ESlateTransformComponent::Location, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z).Get(Location.Z); return Location; } return TOptional(); }; auto OnGetRotator = [Transform, OnGetNumericValue]() -> TOptional { if(Transform.IsBound()) { TOptional Xfo = Transform.Get(); if(Xfo.IsSet()) { return Xfo->Rotator(); } } if(OnGetNumericValue.IsBound()) { FRotator Rotator = FRotator::ZeroRotator; Rotator.Roll = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Rotator, ESlateTransformSubComponent::Roll).Get(Rotator.Roll); Rotator.Pitch = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Rotator, ESlateTransformSubComponent::Pitch).Get(Rotator.Pitch); Rotator.Yaw = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Rotator, ESlateTransformSubComponent::Yaw).Get(Rotator.Yaw); return Rotator; } return TOptional(); }; auto OnGetQuaternion = [Transform, OnGetNumericValue]() -> TOptional { if(Transform.IsBound()) { TOptional Xfo = Transform.Get(); if(Xfo.IsSet()) { return Xfo->GetRotation().GetNormalized(); } } if(OnGetNumericValue.IsBound()) { FQuat Quat = FQuat::Identity; Quat.X = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Quaternion, ESlateTransformSubComponent::X).Get(Quat.X); Quat.Y = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Quaternion, ESlateTransformSubComponent::Y).Get(Quat.Y); Quat.Z = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Quaternion, ESlateTransformSubComponent::Z).Get(Quat.Z); Quat.W = OnGetNumericValue.Execute(ESlateTransformComponent::Rotation, ESlateRotationRepresentation::Quaternion, ESlateTransformSubComponent::W).Get(Quat.W); return Quat.GetNormalized(); } return TOptional(); }; auto OnGetScale = [Transform, OnGetNumericValue]() -> TOptional { if(Transform.IsBound()) { TOptional Xfo = Transform.Get(); if(Xfo.IsSet()) { return Xfo->GetScale3D(); } } if(OnGetNumericValue.IsBound()) { FVector Scale = FVector::OneVector; Scale.X = OnGetNumericValue.Execute(ESlateTransformComponent::Scale, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X).Get(Scale.X); Scale.Y = OnGetNumericValue.Execute(ESlateTransformComponent::Scale, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y).Get(Scale.Y); Scale.Z = OnGetNumericValue.Execute(ESlateTransformComponent::Scale, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z).Get(Scale.Z); return Scale; } return TOptional(); }; auto OnGetVector = [OnGetLocation, OnGetScale](ESlateTransformComponent::Type InComponent) -> TOptional { return (InComponent == ESlateTransformComponent::Location) ? OnGetLocation() : OnGetScale(); }; auto OnGetTransform = [OnGetLocation, OnGetQuaternion, OnGetRotator, OnGetScale, UseQuaternionForRotation]() -> TransformType { TransformType Result = TransformType::Identity; Result.SetLocation(OnGetLocation().Get(Result.GetLocation())); Result.SetScale3D(OnGetScale().Get(Result.GetScale3D())); if(UseQuaternionForRotation) { Result.SetRotation(OnGetQuaternion().Get(Result.GetRotation())); } else { const FRotator Rotator = OnGetRotator().Get(Result.Rotator()); Result = TransformType(Rotator, Result.GetLocation(), Result.GetScale3D()); } Result.NormalizeRotation(); return Result; }; switch(InComponent) { case ESlateTransformComponent::Location: case ESlateTransformComponent::Scale: { TAttribute> XAttribute, YAttribute, ZAttribute; TAttribute> Vector3Attribute; if(InArgs._OnGetNumericValue.IsBound()) { XAttribute = TAttribute>::CreateLambda( [OnGetNumericValue, OnGetVector, InComponent]() -> TOptional { return OnGetNumericValue.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X); }); YAttribute = TAttribute>::CreateLambda( [OnGetNumericValue, OnGetVector, InComponent]() -> TOptional { return OnGetNumericValue.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y); }); ZAttribute = TAttribute>::CreateLambda( [OnGetNumericValue, OnGetVector, InComponent]() -> TOptional { return OnGetNumericValue.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z); }); } if(InArgs._Transform.IsBound() || InArgs._Transform.IsSet()) { Vector3Attribute = TAttribute>::CreateLambda( [OnGetVector, InComponent]() -> TOptional { return OnGetVector(InComponent); }); } typename SNumericVectorInputBox3::FOnNumericValueChanged XChanged, YChanged, ZChanged; if(OnNumericValueChanged.IsBound()) { XChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X, InValue); } ); YChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y, InValue); } ); ZChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z, InValue); } ); } FSimpleDelegate XBeginSlide, YBeginSlide, ZBeginSlide; if(OnBeginSliderMovement.IsBound()) { XBeginSlide = FSimpleDelegate::CreateLambda( [OnBeginSliderMovement, InComponent]() { OnBeginSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X); } ); YBeginSlide = FSimpleDelegate::CreateLambda( [OnBeginSliderMovement, InComponent]() { OnBeginSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y); } ); ZBeginSlide = FSimpleDelegate::CreateLambda( [OnBeginSliderMovement, InComponent]() { OnBeginSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z); } ); } typename SNumericVectorInputBox3::FOnNumericValueChanged XEndSlide, YEndSlide, ZEndSlide; if(OnEndSliderMovement.IsBound()) { XEndSlide = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnEndSliderMovement, InComponent](NumericType InValue) { OnEndSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X, InValue); } ); YEndSlide = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnEndSliderMovement, InComponent](NumericType InValue) { OnEndSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y, InValue); } ); ZEndSlide = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnEndSliderMovement, InComponent](NumericType InValue) { OnEndSliderMovement.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z, InValue); } ); } typename SNumericVectorInputBox3::FOnVectorValueChanged VectorChanged; if(OnTransformChanged.IsBound()) { VectorChanged = SNumericVectorInputBox3::FOnVectorValueChanged::CreateLambda( [OnTransformChanged, OnGetTransform, InComponent](FVector InValue) { if(OnTransformChanged.IsBound()) { TransformType Transform = OnGetTransform(); if(InComponent == ESlateTransformComponent::Location) { Transform.SetLocation(InValue); } else { Transform.SetScale3D(InValue); } OnTransformChanged.Execute(Transform); } } ); } typename SNumericVectorInputBox3::FOnNumericValueCommitted XCommitted, YCommitted, ZCommitted; if(OnNumericValueCommitted.IsBound()) { XCommitted = SNumericVectorInputBox3::FOnNumericValueCommitted::CreateLambda( [OnNumericValueCommitted, InComponent](NumericType InValue, ETextCommit::Type InCommitType) { OnNumericValueCommitted.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X, InValue, InCommitType); } ); YCommitted = SNumericVectorInputBox3::FOnNumericValueCommitted::CreateLambda( [OnNumericValueCommitted, InComponent](NumericType InValue, ETextCommit::Type InCommitType) { OnNumericValueCommitted.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y, InValue, InCommitType); } ); ZCommitted = SNumericVectorInputBox3::FOnNumericValueCommitted::CreateLambda( [OnNumericValueCommitted, InComponent](NumericType InValue, ETextCommit::Type InCommitType) { OnNumericValueCommitted.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z, InValue, InCommitType); } ); } typename SNumericVectorInputBox3::FOnVectorValueCommitted VectorCommitted; if(OnTransformCommitted.IsBound()) { VectorCommitted = SNumericVectorInputBox3::FOnVectorValueCommitted::CreateLambda( [OnTransformCommitted, OnGetTransform, InComponent](FVector InValue, ETextCommit::Type InCommitType) { if(OnTransformCommitted.IsBound()) { TransformType Transform = OnGetTransform(); if(InComponent == ESlateTransformComponent::Location) { Transform.SetLocation(InValue); } else { Transform.SetScale3D(InValue); } OnTransformCommitted.Execute(Transform, InCommitType); } } ); } TSharedPtr ScaleLockState = InArgs._IsScaleLocked; typename SNumericVectorInputBox3::FOnConstrainVector ConstrainComponents; if(InComponent == ESlateTransformComponent::Scale) { if(ScaleLockState.IsValid()) { ConstrainComponents = SNumericVectorInputBox3::FOnConstrainVector::CreateStatic( &SAdvancedTransformInputBox::ConstrainScale, TAttribute::CreateLambda([ScaleLockState]() { return *ScaleLockState.Get(); }) ); } } InputWidget = SNew(SNumericVectorInputBox3) .Font(InArgs._Font) .AllowSpin(InArgs._AllowSpin) .SpinDelta(InComponent == ESlateTransformComponent::Location ? InArgs._LocationSpinDelta : InArgs._ScaleSpinDelta) .bColorAxisLabels(InArgs._bColorAxisLabels) .X(XAttribute) .Y(YAttribute) .Z(ZAttribute) .Vector(Vector3Attribute) .OnXChanged(XChanged) .OnYChanged(YChanged) .OnZChanged(ZChanged) .OnVectorChanged(VectorChanged) .OnXCommitted(XCommitted) .OnYCommitted(YCommitted) .OnZCommitted(ZCommitted) .OnVectorCommitted(VectorCommitted) .ConstrainVector(ConstrainComponents) .DisplayToggle(InArgs._DisplayToggle) .TogglePadding(InArgs._TogglePadding) .PreventThrottling(InArgs._PreventThrottling) .OnXBeginSliderMovement(XBeginSlide) .OnYBeginSliderMovement(YBeginSlide) .OnZBeginSliderMovement(ZBeginSlide) .OnXEndSliderMovement(XEndSlide) .OnYEndSliderMovement(YEndSlide) .OnZEndSliderMovement(ZEndSlide) .ToggleXChecked_Lambda([OnGetToggleChecked, InComponent]() -> ECheckBoxState { if(OnGetToggleChecked.IsBound()) { return OnGetToggleChecked.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X); } return ECheckBoxState::Checked; }) .ToggleYChecked_Lambda([OnGetToggleChecked, InComponent]() -> ECheckBoxState { if(OnGetToggleChecked.IsBound()) { return OnGetToggleChecked.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y); } return ECheckBoxState::Checked; }) .ToggleZChecked_Lambda([OnGetToggleChecked, InComponent]() -> ECheckBoxState { if(OnGetToggleChecked.IsBound()) { return OnGetToggleChecked.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z); } return ECheckBoxState::Checked; }) .OnToggleXChanged_Lambda([OnToggleChanged, InComponent](ECheckBoxState InState) { if(OnToggleChanged.IsBound()) { OnToggleChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X, InState); } }) .OnToggleYChanged_Lambda([OnToggleChanged, InComponent](ECheckBoxState InState) { if(OnToggleChanged.IsBound()) { OnToggleChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y, InState); } }) .OnToggleZChanged_Lambda([OnToggleChanged, InComponent](ECheckBoxState InState) { if(OnToggleChanged.IsBound()) { OnToggleChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z, InState); } }); break; } case ESlateTransformComponent::Rotation: { typename SAdvancedRotationInputBox::FOnGetNumericValue RotationGetNumericValue; if(OnGetNumericValue.IsBound()) { RotationGetNumericValue = SAdvancedRotationInputBox::FOnGetNumericValue::CreateLambda( [OnGetNumericValue, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent) -> TOptional { return OnGetNumericValue.Execute(InComponent, InRepr, InSubComponent); }); } typename SAdvancedRotationInputBox::FOnNumericValueChanged RotationValueChanged; if(OnNumericValueChanged.IsBound()) { RotationValueChanged = SAdvancedRotationInputBox::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent, NumericType InValue) { OnNumericValueChanged.Execute(InComponent, InRepr, InSubComponent, InValue); }); } typename SAdvancedRotationInputBox::FOnNumericValueCommitted RotationValueCommitted; if(OnNumericValueCommitted.IsBound()) { RotationValueCommitted = SAdvancedRotationInputBox::FOnNumericValueCommitted::CreateLambda( [OnNumericValueCommitted, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent, NumericType InValue, ETextCommit::Type InCommitType) { OnNumericValueCommitted.Execute(InComponent, InRepr, InSubComponent, InValue, InCommitType); }); } typename SAdvancedRotationInputBox::FOnBeginSliderMovement RotationBeginSliderMovement; if(OnBeginSliderMovement.IsBound()) { RotationBeginSliderMovement = SAdvancedRotationInputBox::FOnBeginSliderMovement::CreateLambda( [OnBeginSliderMovement, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent) { OnBeginSliderMovement.Execute(InComponent, InRepr, InSubComponent); }); } typename SAdvancedRotationInputBox::FOnEndSliderMovement RotationEndSliderMovement; if(OnEndSliderMovement.IsBound()) { RotationEndSliderMovement = SAdvancedRotationInputBox::FOnEndSliderMovement::CreateLambda( [OnEndSliderMovement, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent, NumericType InValue) { OnEndSliderMovement.Execute(InComponent, InRepr, InSubComponent, InValue); }); } TAttribute> QuaternionAttribute; TAttribute> RotatorAttribute; if(InArgs._Transform.IsBound() || InArgs._Transform.IsSet()) { if(InArgs._UseQuaternionForRotation) { QuaternionAttribute = TAttribute>::CreateLambda( [OnGetQuaternion]() -> TOptional { return OnGetQuaternion(); }); } else { RotatorAttribute = TAttribute>::CreateLambda( [OnGetRotator]() -> TOptional { return OnGetRotator(); }); } } typename SNumericVectorInputBox3::FOnNumericValueChanged XChanged, YChanged, ZChanged; if(OnNumericValueChanged.IsBound()) { XChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::X, InValue); } ); YChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Y, InValue); } ); ZChanged = SNumericVectorInputBox3::FOnNumericValueChanged::CreateLambda( [OnNumericValueChanged, InComponent](NumericType InValue) { OnNumericValueChanged.Execute(InComponent, ESlateRotationRepresentation::EulerXYZ, ESlateTransformSubComponent::Z, InValue); } ); } typename SAdvancedRotationInputBox::FOnRotatorChanged RotatorChanged; typename SAdvancedRotationInputBox::FOnQuaternionChanged QuaternionChanged; if(OnTransformChanged.IsBound()) { if(InArgs._UseQuaternionForRotation) { QuaternionChanged = SAdvancedRotationInputBox::FOnQuaternionChanged::CreateLambda( [OnTransformChanged, OnGetTransform](FQuat InQuaternion) { TransformType Xfo = OnGetTransform(); Xfo.SetRotation(InQuaternion); OnTransformChanged.Execute(Xfo); } ); } else { RotatorChanged = SAdvancedRotationInputBox::FOnRotatorChanged::CreateLambda( [OnTransformChanged, OnGetTransform](FRotator InRotator) { TransformType Xfo = OnGetTransform(); Xfo = TransformType(InRotator, Xfo.GetLocation(), Xfo.GetScale3D()); OnTransformChanged.Execute(Xfo); } ); } } typename SAdvancedRotationInputBox::FOnRotatorCommitted RotatorCommitted; typename SAdvancedRotationInputBox::FOnQuaternionCommitted QuaternionCommitted; if(OnTransformCommitted.IsBound()) { if(InArgs._UseQuaternionForRotation) { QuaternionCommitted = SAdvancedRotationInputBox::FOnQuaternionCommitted::CreateLambda( [OnTransformCommitted, OnGetTransform](FQuat InQuaternion, ETextCommit::Type InCommitType) { TransformType Xfo = OnGetTransform(); Xfo.SetRotation(InQuaternion); OnTransformCommitted.Execute(Xfo, InCommitType); } ); } else { RotatorCommitted = SAdvancedRotationInputBox::FOnRotatorCommitted::CreateLambda( [OnTransformCommitted, OnGetTransform](FRotator InRotator, ETextCommit::Type InCommitType) { TransformType Xfo = OnGetTransform(); Xfo = TransformType(InRotator, Xfo.GetLocation(), Xfo.GetScale3D()); OnTransformCommitted.Execute(Xfo, InCommitType); } ); } } TSharedPtr RotationRepresentationPtr = InArgs._RotationRepresentation; InputWidget = SNew(SAdvancedRotationInputBox) .Font(InArgs._Font) .AllowSpin(InArgs._AllowSpin) .bColorAxisLabels(InArgs._bColorAxisLabels) .Representation_Lambda([RotationRepresentationPtr]() -> ESlateRotationRepresentation::Type { if(RotationRepresentationPtr.IsValid()) { return *RotationRepresentationPtr.Get(); } return ESlateRotationRepresentation::Rotator; }) .OnGetNumericValue(RotationGetNumericValue) .OnNumericValueChanged(RotationValueChanged) .OnNumericValueCommitted(RotationValueCommitted) .Rotator(RotatorAttribute) .OnRotatorChanged(RotatorChanged) .OnRotatorCommitted(RotatorCommitted) .Quaternion(QuaternionAttribute) .OnQuaternionChanged(QuaternionChanged) .OnQuaternionCommitted(QuaternionCommitted) .DisplayToggle(InArgs._DisplayToggle) .TogglePadding(InArgs._TogglePadding) .PreventThrottling(InArgs._PreventThrottling) .OnBeginSliderMovement(RotationBeginSliderMovement) .OnEndSliderMovement(RotationEndSliderMovement) .OnGetToggleChecked(SAdvancedRotationInputBox::FOnGetToggleChecked::CreateLambda( [OnGetToggleChecked, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent) -> ECheckBoxState { if(OnGetToggleChecked.IsBound()) { return OnGetToggleChecked.Execute(InComponent, InRepr, InSubComponent); } return ECheckBoxState::Checked; })) .OnToggleChanged(SAdvancedRotationInputBox::FOnToggleChanged::CreateLambda( [OnToggleChanged, InComponent](ESlateRotationRepresentation::Type InRepr, ESlateTransformSubComponent::Type InSubComponent, ECheckBoxState InState) { if(OnToggleChanged.IsBound()) { return OnToggleChanged.Execute(InComponent, InRepr, InSubComponent, InState); } })); break; } } if(InputWidget != SNullWidget::NullWidget) { HorizontalBox->AddSlot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .FillWidth(1.f) [ InputWidget ]; } return HorizontalBox; } /** * Constructs a label widget */ static TSharedRef ConstructLabel(const FArguments& InArgs, ESlateTransformComponent::Type InComponent) { const TAttribute EnabledAttribute = InArgs._IsEnabled; TSharedRef LabelWidget = SNullWidget::NullWidget; if(InComponent == ESlateTransformComponent::Rotation && InArgs._AllowEditRotationRepresentation) { const TArray& Labels = GetRotationRepresentationLabels(); TSharedPtr RotationRepresentationPtr = InArgs._RotationRepresentation; FOnRotationRepresentationChanged OnRotationRepresentationChanged = InArgs._OnRotationRepresentationChanged; LabelWidget = SNew(SComboButton) .IsEnabled(true) .ContentPadding(0.f) .OnGetMenuContent_Lambda([Labels, RotationRepresentationPtr, OnRotationRepresentationChanged] { FMenuBuilder MenuBuilder(true, nullptr); for (int32 LabelIndex = 0; LabelIndex < Labels.Num(); LabelIndex++) { MenuBuilder.AddMenuEntry( Labels[LabelIndex], FText(), FSlateIcon(), FUIAction ( FExecuteAction::CreateLambda([LabelIndex, RotationRepresentationPtr, OnRotationRepresentationChanged]() { if(OnRotationRepresentationChanged.IsBound()) { OnRotationRepresentationChanged.Execute((ESlateRotationRepresentation::Type)LabelIndex); } if(RotationRepresentationPtr.IsValid()) { *RotationRepresentationPtr.Get() = (ESlateRotationRepresentation::Type)LabelIndex; } }), FCanExecuteAction(), FIsActionChecked::CreateLambda([LabelIndex, RotationRepresentationPtr]() -> bool { if(RotationRepresentationPtr.IsValid()) { return LabelIndex == (int32)*RotationRepresentationPtr.Get(); } return false; }) ), NAME_None, EUserInterfaceActionType::Check); } return MenuBuilder.MakeWidget(); }) .ButtonContent() [ SNew(STextBlock) .Font(InArgs._Font) .Text_Lambda([RotationRepresentationPtr, Labels]() -> FText { if(RotationRepresentationPtr.IsValid()) { return Labels[(int32)*RotationRepresentationPtr.Get()]; } return Labels[0]; }) ]; } else { const FText& LabelText = (InComponent == ESlateTransformComponent::Rotation) ? InArgs._RotationLabel : ( (InComponent == ESlateTransformComponent::Location) ? InArgs._LocationLabel : InArgs._ScaleLabel); LabelWidget = SNew(STextBlock) .IsEnabled(EnabledAttribute) .Font(InArgs._Font) .Text(LabelText); } SHorizontalBox::FArguments BoxArgs; ((FSlateBaseNamedArgs&)BoxArgs) = (FSlateBaseNamedArgs)InArgs; BoxArgs.IsEnabled(true); TSharedRef HorizontalBox = SArgumentNew(BoxArgs, SHorizontalBox); HorizontalBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ LabelWidget ]; const TSharedRef ScaleLockWidget = ConstructScaleLockWidget(InArgs, InComponent); if(ScaleLockWidget != SNullWidget::NullWidget) { ScaleLockWidget->SetEnabled(EnabledAttribute); HorizontalBox->AddSlot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(FMargin(4.f, 0.f, 0.f, 0.f)) [ ScaleLockWidget ]; } const TSharedRef RelativeWorldWidget = ConstructRelativeWorldWidget(InArgs, InComponent); if(RelativeWorldWidget != SNullWidget::NullWidget) { // the relative to world will be always enabled... // (unless the outer scope if not enabled) HorizontalBox->AddSlot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(FMargin(4.f, 0.f, 0.f, 0.f)) [ RelativeWorldWidget ]; } return HorizontalBox; } /** * Constructs a relative / world switcher button */ static TSharedRef ConstructScaleLockWidget(const FArguments& InArgs, ESlateTransformComponent::Type InComponent ) { if(InArgs._DisplayScaleLock && InArgs._ConstructScale && InComponent == ESlateTransformComponent::Scale && InArgs._IsScaleLocked.IsValid()) { TSharedPtr LockState = InArgs._IsScaleLocked; FOnScaleLockChanged OnScaleLockChanged = InArgs._OnScaleLockChanged; static const FText TooltipText = NSLOCTEXT("SAdvancedTransformInputBox", "PreserveScaleToolTip", "When locked, all axis values scale together so the object maintains its proportions in all directions."); return SNew(SButton) .ContentPadding(0.f) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked_Lambda([OnScaleLockChanged, LockState]() -> FReply { *LockState.Get() = !*LockState.Get(); if(OnScaleLockChanged.IsBound()) { OnScaleLockChanged.Execute(*LockState.Get()); } return FReply::Handled(); }) .ToolTipText(TooltipText) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .ForegroundColor(FSlateColor::UseStyle()) .Content() [ SNew(SImage) .Image_Lambda([LockState]() -> const FSlateBrush* { return GetScaleLockIcon(*LockState.Get()); }) .ColorAndOpacity(FSlateColor::UseForeground()) ]; } return SNullWidget::NullWidget; } /** * Gather the icon for scale locked / unlocked */ static const FSlateBrush* GetScaleLockIcon(bool bIsLocked) { return bIsLocked ? FAppStyle::Get().GetBrush( TEXT("Icons.Lock") ) : FAppStyle::Get().GetBrush( TEXT("Icons.Unlock") ) ; } /** * Constructs a relative / world switcher button */ static TSharedRef ConstructRelativeWorldWidget(const FArguments& InArgs, ESlateTransformComponent::Type InComponent) { if(InArgs._DisplayRelativeWorld) { FOnGetIsComponentRelative OnGetIsComponentRelative = InArgs._OnGetIsComponentRelative;; FOnIsComponentRelativeChanged OnIsComponentRelativeChanged = InArgs._OnIsComponentRelativeChanged; static const FText TooltipFormat = NSLOCTEXT("SAdvancedTransformInputBox", "RelativeWorldTooltip", "Cycles the Transform {0} coordinate system between world and local (relative) space.\nShift clicking this button affects the whole transform."); static const FText LocationTooltip = FText::Format(TooltipFormat, NSLOCTEXT("SAdvancedTransformInputBox", "Location", "Location")); static const FText RotationTooltip = FText::Format(TooltipFormat, NSLOCTEXT("SAdvancedTransformInputBox", "Rotation", "Rotation")); static const FText ScaleTooltip = FText::Format(TooltipFormat, NSLOCTEXT("SAdvancedTransformInputBox", "Scale", "Scale")); FText TooltipText; switch(InComponent) { case ESlateTransformComponent::Location: { TooltipText = LocationTooltip; break; } case ESlateTransformComponent::Rotation: { TooltipText = RotationTooltip; break; } case ESlateTransformComponent::Scale: { TooltipText = ScaleTooltip; break; } } return SNew(SButton) .IsEnabled(true) .ContentPadding(0.f) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked_Lambda([OnIsComponentRelativeChanged, OnGetIsComponentRelative, InComponent]() -> FReply { if(OnIsComponentRelativeChanged.IsBound()) { bool bIsRelative = true; if(OnGetIsComponentRelative.IsBound()) { bIsRelative = OnGetIsComponentRelative.Execute(InComponent); } if(FSlateApplication::Get().GetModifierKeys().IsShiftDown() || FSlateApplication::Get().GetModifierKeys().IsControlDown() || FSlateApplication::Get().GetModifierKeys().IsAltDown()) { OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Location, !bIsRelative); OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Rotation, !bIsRelative); OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Scale, !bIsRelative); } else { OnIsComponentRelativeChanged.Execute(InComponent, !bIsRelative); } } return FReply::Handled(); }) .ToolTipText(TooltipText) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .ForegroundColor(FSlateColor::UseStyle()) .Content() [ SNew(SImage) .Image_Lambda([OnGetIsComponentRelative, InComponent]() -> const FSlateBrush* { bool bIsRelative = true; if(OnGetIsComponentRelative.IsBound()) { bIsRelative = OnGetIsComponentRelative.Execute(InComponent); } return GetRelativeWorldIcon(bIsRelative).GetIcon(); }) .ColorAndOpacity(FSlateColor::UseForeground()) ]; } return SNullWidget::NullWidget; } /** * Gather the icon for relative world */ static const FSlateIcon& GetRelativeWorldIcon(bool bRelative) { if (bRelative) { static FName LocalIconName("Icons.Transform"); static FSlateIcon LocalIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), LocalIconName); return LocalIcon; } static FName WorldIconName("EditorViewport.RelativeCoordinateSystem_World"); static FSlateIcon WorldIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), WorldIconName); return WorldIcon; } #if WITH_EDITOR static IDetailGroup& ConstructDetailGroup(IDetailCategoryBuilder& InBuilder, FName GroupName, const FText& LocalizedDisplayName) { return InBuilder.AddGroup(GroupName, LocalizedDisplayName, false, true); } static IDetailGroup& ConstructDetailGroup(IDetailChildrenBuilder& InBuilder, FName GroupName, const FText& LocalizedDisplayName) { return InBuilder.AddGroup(GroupName, LocalizedDisplayName); } static void ConfigureComponentWidgetRow( FDetailWidgetRow& WidgetRow, ESlateTransformComponent::Type InComponent, typename SAdvancedTransformInputBox::FArguments WidgetArgs) { if(WidgetArgs._OnCopyToClipboard.IsBound() && WidgetArgs._OnPasteFromClipboard.IsBound()) { WidgetRow .CopyAction(FUIAction( FExecuteAction::CreateLambda([WidgetArgs, InComponent]() { WidgetArgs._OnCopyToClipboard.ExecuteIfBound(InComponent); }), FCanExecuteAction()) ); WidgetRow.PasteAction(FUIAction( FExecuteAction::CreateLambda([WidgetArgs, InComponent]() { WidgetArgs._OnPasteFromClipboard.ExecuteIfBound(InComponent); }), FCanExecuteAction::CreateLambda([WidgetArgs]() -> bool { return WidgetArgs._IsEnabled.Get(); }))); } if(WidgetArgs._OnResetToDefault.IsBound() && WidgetArgs._DiffersFromDefault.IsBound()) { WidgetRow.OverrideResetToDefault(FResetToDefaultOverride::Create( TAttribute::CreateLambda([WidgetArgs, InComponent]() -> bool { if(!WidgetArgs._IsEnabled.Get()) { return false; } return WidgetArgs._DiffersFromDefault.Execute(InComponent); }), FSimpleDelegate::CreateLambda([WidgetArgs, InComponent]() { WidgetArgs._OnResetToDefault.Execute(InComponent); }) )); } if(InComponent != ESlateTransformComponent::Max) { WidgetRow .NameContent() .HAlign(HAlign_Fill) [ SAdvancedTransformInputBox::ConstructLabel(WidgetArgs, InComponent) ] .ValueContent() .MinDesiredWidth(375.f) .MaxDesiredWidth(375.f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() [ SAdvancedTransformInputBox::ConstructWidget(WidgetArgs, InComponent) ] ]; } }; static void ConfigureHeader( FDetailWidgetRow& HeaderRow, const FText& InLabel, const FText& InTooltip, typename SAdvancedTransformInputBox::FArguments WidgetArgs, TSharedPtr NameContent = TSharedPtr() ) { ConfigureComponentWidgetRow(HeaderRow, ESlateTransformComponent::Max, WidgetArgs); if(!NameContent.IsValid()) { SAssignNew(NameContent, STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(InLabel) .ToolTipText(InTooltip); } HeaderRow .Visibility(WidgetArgs._Visibility) .NameContent() [ NameContent.ToSharedRef() ]; } template static FDetailWidgetRow& ConstructGroupedTransformRows( BuilderType& InBuilder, const FText& InLabel, const FText& InTooltip, typename SAdvancedTransformInputBox::FArguments WidgetArgs, TSharedPtr NameContent = TSharedPtr() ) { IDetailGroup& Group = ConstructDetailGroup(InBuilder, *InLabel.ToString(), InLabel); FDetailWidgetRow& HeaderRow = Group.HeaderRow(); ConfigureHeader(HeaderRow, InLabel, InTooltip, WidgetArgs, NameContent); if(WidgetArgs._ConstructLocation) { FDetailWidgetRow& WidgetRow = Group.AddWidgetRow(); ConfigureComponentWidgetRow(WidgetRow, ESlateTransformComponent::Location, WidgetArgs); } if(WidgetArgs._ConstructRotation) { FDetailWidgetRow& WidgetRow = Group.AddWidgetRow(); ConfigureComponentWidgetRow(WidgetRow, ESlateTransformComponent::Rotation, WidgetArgs); } if(WidgetArgs._ConstructScale) { FDetailWidgetRow& WidgetRow = Group.AddWidgetRow(); ConfigureComponentWidgetRow(WidgetRow, ESlateTransformComponent::Scale, WidgetArgs); } return HeaderRow; } #endif static const TArray& GetRotationRepresentationLabels() { static TArray Labels; if(Labels.IsEmpty()) { Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerXYZ", "Euler XYZ")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerXZY", "Euler XZY")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerYXZ", "Euler YXZ")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerYZX", "Euler YZX")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerZXY", "Euler ZXY")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "EulerZYX", "Euler ZYX")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "Rotator", "Rotator")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "Quaternion", "Quaternion")); Labels.Add(NSLOCTEXT("SAdvancedTransformInputBox", "AxisAndAngle", "Axis And Angle")); } return Labels; } static void ConstrainScale( int32 ComponentIndex, UE::Math::TVector OldValue, UE::Math::TVector& NewValue, TAttribute ConstrainComponents) { if(!ConstrainComponents.Get()) { return; } if(FMath::IsNearlyZero(OldValue[ComponentIndex]) || FMath::IsNearlyZero(NewValue[ComponentIndex])) { return; } const NumericType Ratio = NewValue[ComponentIndex] / OldValue[ComponentIndex]; NewValue = OldValue * Ratio; } static TOptional GetNumericValueFromTransform( const TransformType& Transform, ESlateTransformComponent::Type Component, ESlateRotationRepresentation::Type Representation, ESlateTransformSubComponent::Type SubComponent) { switch(Component) { case ESlateTransformComponent::Location: { switch(SubComponent) { case ESlateTransformSubComponent::X: return Transform.GetLocation().X; case ESlateTransformSubComponent::Y: return Transform.GetLocation().Y; case ESlateTransformSubComponent::Z: return Transform.GetLocation().Z; } break; } case ESlateTransformComponent::Rotation: { switch(Representation) { case ESlateRotationRepresentation::EulerXYZ: case ESlateRotationRepresentation::EulerXZY: case ESlateRotationRepresentation::EulerYXZ: case ESlateRotationRepresentation::EulerYZX: case ESlateRotationRepresentation::EulerZXY: case ESlateRotationRepresentation::EulerZYX: { const int32 RotationOrder = int32(Representation) - int32(ESlateRotationRepresentation::EulerXYZ); const FVector Euler = AnimationCore::EulerFromQuat(Transform.GetRotation(), EEulerRotationOrder(RotationOrder), true); switch(SubComponent) { case ESlateTransformSubComponent::X: { return Euler.X; } case ESlateTransformSubComponent::Y: { return Euler.Y; } case ESlateTransformSubComponent::Z: { return Euler.Z; } } break; } case ESlateRotationRepresentation::Rotator: { switch(SubComponent) { case ESlateTransformSubComponent::Roll: return Transform.Rotator().Roll; case ESlateTransformSubComponent::Pitch: return Transform.Rotator().Pitch; case ESlateTransformSubComponent::Yaw: return Transform.Rotator().Yaw; } break; } case ESlateRotationRepresentation::Quaternion: { switch(SubComponent) { case ESlateTransformSubComponent::X: return Transform.GetRotation().X; case ESlateTransformSubComponent::Y: return Transform.GetRotation().Y; case ESlateTransformSubComponent::Z: return Transform.GetRotation().Z; case ESlateTransformSubComponent::W: return Transform.GetRotation().W; } break; } case ESlateRotationRepresentation::AxisAndAngle: { FVector Axis; NumericType Angle; Transform.GetRotation().ToAxisAndAngle(Axis, Angle); switch(SubComponent) { case ESlateTransformSubComponent::X: return Axis.X; case ESlateTransformSubComponent::Y: return Axis.Y; case ESlateTransformSubComponent::Z: return Axis.Z; case ESlateTransformSubComponent::Angle: return Angle; } break; } } break; } case ESlateTransformComponent::Scale: { switch(SubComponent) { case ESlateTransformSubComponent::X: return Transform.GetScale3D().X; case ESlateTransformSubComponent::Y: return Transform.GetScale3D().Y; case ESlateTransformSubComponent::Z: return Transform.GetScale3D().Z; } break; } } return TOptional(); } static void ApplyNumericValueChange( TransformType& Transform, NumericType Value, ESlateTransformComponent::Type Component, ESlateRotationRepresentation::Type Representation, ESlateTransformSubComponent::Type SubComponent) { switch(Component) { case ESlateTransformComponent::Location: { UE::Math::TVector Location = Transform.GetLocation(); switch(SubComponent) { case ESlateTransformSubComponent::X: { Location.X = Value; break; } case ESlateTransformSubComponent::Y: { Location.Y = Value; break; } case ESlateTransformSubComponent::Z: { Location.Z = Value; break; } } Transform.SetLocation(Location); break; } case ESlateTransformComponent::Rotation: { switch(Representation) { case ESlateRotationRepresentation::EulerXYZ: case ESlateRotationRepresentation::EulerXZY: case ESlateRotationRepresentation::EulerYXZ: case ESlateRotationRepresentation::EulerYZX: case ESlateRotationRepresentation::EulerZXY: case ESlateRotationRepresentation::EulerZYX: { const int32 RotationOrder = int32(Representation) - int32(ESlateRotationRepresentation::EulerXYZ); FVector Euler = AnimationCore::EulerFromQuat(Transform.GetRotation(), EEulerRotationOrder(RotationOrder)); switch(SubComponent) { case ESlateTransformSubComponent::X: { Euler.X = Value; break; } case ESlateTransformSubComponent::Y: { Euler.Y = Value; break; } case ESlateTransformSubComponent::Z: { Euler.Z = Value; break; } } Transform.SetRotation(AnimationCore::QuatFromEuler(Euler, EEulerRotationOrder(RotationOrder), true)); break; } case ESlateRotationRepresentation::Rotator: { FRotator Rotator = Transform.Rotator(); switch(SubComponent) { case ESlateTransformSubComponent::Roll: { Rotator.Roll = Value; break; } case ESlateTransformSubComponent::Pitch: { Rotator.Pitch = Value; break; } case ESlateTransformSubComponent::Yaw: { Rotator.Yaw = Value; break; } } Transform = TransformType(Rotator, Transform.GetLocation(), Transform.GetScale3D()); break; } case ESlateRotationRepresentation::Quaternion: { FQuat Rotation = Transform.GetRotation(); switch(SubComponent) { case ESlateTransformSubComponent::X: { Rotation.X = Value; break; } case ESlateTransformSubComponent::Y: { Rotation.Y = Value; break; } case ESlateTransformSubComponent::Z: { Rotation.Z = Value; break; } case ESlateTransformSubComponent::W: { Rotation.W = Value; break; } } Rotation.Normalize(); Transform.SetRotation(Rotation); break; } case ESlateRotationRepresentation::AxisAndAngle: { FVector Axis; NumericType Angle; Transform.GetRotation().ToAxisAndAngle(Axis, Angle); switch(SubComponent) { case ESlateTransformSubComponent::X: { Axis.X = Value; break; } case ESlateTransformSubComponent::Y: { Axis.Y = Value; break; } case ESlateTransformSubComponent::Z: { Axis.Z = Value; break; } case ESlateTransformSubComponent::Angle: { Angle = Value; break; } } Transform.SetRotation(FQuat(Axis, Angle)); break; } } break; } case ESlateTransformComponent::Scale: { UE::Math::TVector Scale3D = Transform.GetScale3D(); switch(SubComponent) { case ESlateTransformSubComponent::X: { Scale3D.X = Value; break; } case ESlateTransformSubComponent::Y: { Scale3D.Y = Value; break; } case ESlateTransformSubComponent::Z: { Scale3D.Z = Value; break; } } Transform.SetScale3D(Scale3D); break; } } } };