You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1435 lines
45 KiB
C++
1435 lines
45 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DetailCustomizationsPrivatePCH.h"
|
|
#include "ComponentTransformDetails.h"
|
|
#include "SVectorInputBox.h"
|
|
#include "SRotatorInputBox.h"
|
|
#include "PropertyCustomizationHelpers.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "Editor/UnrealEd/Public/Kismet2/ComponentEditorUtils.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "IPropertyUtilities.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FComponentTransformDetails"
|
|
|
|
class FScopedSwitchWorldForObject
|
|
{
|
|
public:
|
|
FScopedSwitchWorldForObject( UObject* Object )
|
|
: PrevWorld( NULL )
|
|
{
|
|
bool bRequiresPlayWorld = false;
|
|
if( GUnrealEd->PlayWorld && !GIsPlayInEditorWorld )
|
|
{
|
|
UPackage* ObjectPackage = Object->GetOutermost();
|
|
bRequiresPlayWorld = !!(ObjectPackage->PackageFlags & PKG_PlayInEditor);
|
|
}
|
|
|
|
if( bRequiresPlayWorld )
|
|
{
|
|
PrevWorld = SetPlayInEditorWorld( GUnrealEd->PlayWorld );
|
|
}
|
|
}
|
|
|
|
~FScopedSwitchWorldForObject()
|
|
{
|
|
if( PrevWorld )
|
|
{
|
|
RestoreEditorWorld( PrevWorld );
|
|
}
|
|
}
|
|
|
|
private:
|
|
UWorld* PrevWorld;
|
|
};
|
|
|
|
template<typename T>
|
|
static void PropagateTransformPropertyChange(UObject* InObject, UProperty* InProperty, const T& OldValue, const T& NewValue)
|
|
{
|
|
check(InObject != NULL);
|
|
check(InProperty != NULL);
|
|
|
|
TArray<UObject*> ArchetypeInstances;
|
|
FComponentEditorUtils::GetArchetypeInstances(InObject, ArchetypeInstances);
|
|
for(int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex)
|
|
{
|
|
USceneComponent* InstancedSceneComponent = FComponentEditorUtils::GetSceneComponent(ArchetypeInstances[InstanceIndex], InObject);
|
|
if(InstancedSceneComponent != NULL)
|
|
{
|
|
// Propagate the change only if the current instanced value matches the previous default value
|
|
T* CurValue = InProperty->ContainerPtrToValuePtr<T>(InstancedSceneComponent);
|
|
if(CurValue != NULL && *CurValue == OldValue)
|
|
{
|
|
// Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer.
|
|
// Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them.
|
|
if(!InstancedSceneComponent->bCreatedByConstructionScript)
|
|
{
|
|
InstancedSceneComponent->SetFlags(RF_Transactional);
|
|
InstancedSceneComponent->Modify();
|
|
}
|
|
|
|
// We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation.
|
|
AActor* Owner = InstancedSceneComponent->GetOwner();
|
|
if(Owner != NULL)
|
|
{
|
|
Owner->Modify();
|
|
}
|
|
|
|
// Change the property value
|
|
*CurValue = NewValue;
|
|
|
|
// Re-register the component with the scene so that transforms are updated for display
|
|
InstancedSceneComponent->ReregisterComponent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FComponentTransformDetails::FComponentTransformDetails( const TArray< TWeakObjectPtr<UObject> >& InSelectedObjects, const FSelectedActorInfo& InSelectedActorInfo, IDetailLayoutBuilder& DetailBuilder )
|
|
: SelectedActorInfo( InSelectedActorInfo )
|
|
, SelectedObjects( InSelectedObjects )
|
|
, bPreserveScaleRatio( false )
|
|
, NotifyHook( DetailBuilder.GetPropertyUtilities()->GetNotifyHook() )
|
|
, bEditingRotationInUI( false )
|
|
{
|
|
GConfig->GetBool(TEXT("SelectionDetails"), TEXT("PreserveScaleRatio"), bPreserveScaleRatio, GEditorUserSettingsIni);
|
|
|
|
// Capture selected actor rotations so that we can adjust them without worrying about the Quat conversions affecting the raw values
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = InSelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
|
|
if( RootComponent )
|
|
{
|
|
FRotator& RelativeRotation = ObjectToRelativeRotationMap.FindOrAdd(Object);
|
|
RelativeRotation = RootComponent->RelativeRotation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> FComponentTransformDetails::BuildTransformFieldLabel( ETransformField::Type TransformField )
|
|
{
|
|
FText Label;
|
|
switch( TransformField )
|
|
{
|
|
case ETransformField::Rotation:
|
|
Label = LOCTEXT( "RotationLabel", "Rotation");
|
|
break;
|
|
case ETransformField::Scale:
|
|
Label = LOCTEXT( "ScaleLabel", "Scale" );
|
|
break;
|
|
case ETransformField::Location:
|
|
default:
|
|
Label = LOCTEXT("LocationLabel", "Location");
|
|
break;
|
|
}
|
|
|
|
FMenuBuilder MenuBuilder( true, NULL, NULL );
|
|
|
|
FUIAction SetRelativeLocationAction
|
|
(
|
|
FExecuteAction::CreateSP( this, &FComponentTransformDetails::OnSetRelativeTransform, TransformField ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &FComponentTransformDetails::IsRelativeTransformChecked, TransformField )
|
|
);
|
|
|
|
FUIAction SetWorldLocationAction
|
|
(
|
|
FExecuteAction::CreateSP( this, &FComponentTransformDetails::OnSetWorldTransform, TransformField ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &FComponentTransformDetails::IsWorldTransformChecked, TransformField )
|
|
);
|
|
|
|
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( FEditorStyle::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, &FComponentTransformDetails::GetTransformFieldText, TransformField)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
]
|
|
];
|
|
}
|
|
|
|
FText FComponentTransformDetails::GetTransformFieldText( ETransformField::Type TransformField ) const
|
|
{
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
return GetLocationText();
|
|
break;
|
|
case ETransformField::Rotation:
|
|
return GetRotationText();
|
|
break;
|
|
case ETransformField::Scale:
|
|
return GetScaleText();
|
|
break;
|
|
default:
|
|
return FText::GetEmpty();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnSetRelativeTransform( ETransformField::Type TransformField )
|
|
{
|
|
const bool bEnable = false;
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
OnToggleAbsoluteLocation(bEnable);
|
|
break;
|
|
case ETransformField::Rotation:
|
|
OnToggleAbsoluteRotation( bEnable );
|
|
break;
|
|
case ETransformField::Scale:
|
|
OnToggleAbsoluteScale( bEnable );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool FComponentTransformDetails::IsRelativeTransformChecked( ETransformField::Type TransformField ) const
|
|
{
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
return !bAbsoluteLocation;
|
|
break;
|
|
case ETransformField::Rotation:
|
|
return !bAbsoluteRotation;
|
|
break;
|
|
case ETransformField::Scale:
|
|
return !bAbsoluteScale;
|
|
break;
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnSetWorldTransform( ETransformField::Type TransformField )
|
|
{
|
|
const bool bEnable = true;
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
OnToggleAbsoluteLocation(bEnable);
|
|
break;
|
|
case ETransformField::Rotation:
|
|
OnToggleAbsoluteRotation(bEnable);
|
|
break;
|
|
case ETransformField::Scale:
|
|
OnToggleAbsoluteScale(bEnable);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool FComponentTransformDetails::IsWorldTransformChecked( ETransformField::Type TransformField ) const
|
|
{
|
|
return !IsRelativeTransformChecked( TransformField );
|
|
}
|
|
|
|
bool FComponentTransformDetails::OnCanCopy( ETransformField::Type TransformField ) const
|
|
{
|
|
// We can only copy values if the whole field is set. If multiple values are defined we do not copy since we are unable to determine the value
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
return CachedLocation.IsSet();
|
|
break;
|
|
case ETransformField::Rotation:
|
|
return CachedRotation.IsSet();
|
|
break;
|
|
case ETransformField::Scale:
|
|
return CachedScale.IsSet();
|
|
break;
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnCopy( ETransformField::Type TransformField )
|
|
{
|
|
CacheTransform();
|
|
|
|
FString CopyStr;
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
CopyStr = FString::Printf(TEXT("X=%f Y=%f Z=%f"), CachedLocation.X.GetValue(), CachedLocation.Y.GetValue(), CachedLocation.Z.GetValue());
|
|
break;
|
|
case ETransformField::Rotation:
|
|
CopyStr = FString::Printf(TEXT("P=%f Y=%f R=%f"), CachedRotation.Y.GetValue(), CachedRotation.Z.GetValue(), CachedRotation.X.GetValue());
|
|
break;
|
|
case ETransformField::Scale:
|
|
CopyStr = FString::Printf(TEXT("X=%f Y=%f Z=%f"), CachedScale.X.GetValue(), CachedScale.Y.GetValue(), CachedScale.Z.GetValue());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if( !CopyStr.IsEmpty() )
|
|
{
|
|
FPlatformMisc::ClipboardCopy( *CopyStr );
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnPaste( ETransformField::Type TransformField )
|
|
{
|
|
FString PastedText;
|
|
FPlatformMisc::ClipboardPaste(PastedText);
|
|
|
|
switch (TransformField)
|
|
{
|
|
case ETransformField::Location:
|
|
{
|
|
FVector Location;
|
|
if (Location.InitFromString(PastedText))
|
|
{
|
|
FScopedTransaction Transaction(LOCTEXT("PasteLocation", "Paste Location"));
|
|
OnSetLocation(Location.X, ETextCommit::Default, 0);
|
|
OnSetLocation(Location.Y, ETextCommit::Default, 1);
|
|
OnSetLocation(Location.Z, ETextCommit::Default, 2);
|
|
}
|
|
}
|
|
break;
|
|
case ETransformField::Rotation:
|
|
{
|
|
FRotator Rotation;
|
|
if (Rotation.InitFromString(PastedText))
|
|
{
|
|
FScopedTransaction Transaction(LOCTEXT("PasteRotation", "Paste Rotation"));
|
|
OnSetRotation(Rotation.Roll, ETextCommit::Default, 0);
|
|
OnSetRotation(Rotation.Pitch, ETextCommit::Default, 1);
|
|
OnSetRotation(Rotation.Yaw, ETextCommit::Default, 2);
|
|
}
|
|
}
|
|
break;
|
|
case ETransformField::Scale:
|
|
{
|
|
FVector Scale;
|
|
if (Scale.InitFromString(PastedText))
|
|
{
|
|
FScopedTransaction Transaction(LOCTEXT("PasteScale", "Paste Scale"));
|
|
OnSetScale(Scale.X, ETextCommit::Default, 0);
|
|
OnSetScale(Scale.Y, ETextCommit::Default, 1);
|
|
OnSetScale(Scale.Z, ETextCommit::Default, 2);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
FUIAction FComponentTransformDetails::CreateCopyAction( ETransformField::Type TransformField ) const
|
|
{
|
|
return
|
|
FUIAction
|
|
(
|
|
FExecuteAction::CreateSP(this, &FComponentTransformDetails::OnCopy, TransformField ),
|
|
FCanExecuteAction::CreateSP(this, &FComponentTransformDetails::OnCanCopy, TransformField )
|
|
);
|
|
}
|
|
|
|
FUIAction FComponentTransformDetails::CreatePasteAction( ETransformField::Type TransformField ) const
|
|
{
|
|
return
|
|
FUIAction( FExecuteAction::CreateSP(this, &FComponentTransformDetails::OnPaste, TransformField ) );
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder )
|
|
{
|
|
UClass* SceneComponentClass = USceneComponent::StaticClass();
|
|
|
|
FSlateFontInfo FontInfo = IDetailLayoutBuilder::GetDetailFont();
|
|
|
|
// Location
|
|
ChildrenBuilder.AddChildContent( LOCTEXT("LocationFilter", "Location").ToString() )
|
|
.CopyAction( CreateCopyAction( ETransformField::Location ) )
|
|
.PasteAction( CreatePasteAction( ETransformField::Location ) )
|
|
.NameContent()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
BuildTransformFieldLabel( ETransformField::Location )
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(125.0f * 3.0f)
|
|
.MaxDesiredWidth(125.0f * 3.0f)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SVectorInputBox )
|
|
.X( this, &FComponentTransformDetails::GetLocationX )
|
|
.Y( this, &FComponentTransformDetails::GetLocationY )
|
|
.Z( this, &FComponentTransformDetails::GetLocationZ )
|
|
.bColorAxisLabels( true )
|
|
.IsEnabled( this, &FComponentTransformDetails::GetIsEnabled )
|
|
.OnXCommitted( this, &FComponentTransformDetails::OnSetLocation, 0 )
|
|
.OnYCommitted( this, &FComponentTransformDetails::OnSetLocation, 1 )
|
|
.OnZCommitted( this, &FComponentTransformDetails::OnSetLocation, 2 )
|
|
.Font( FontInfo )
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
// Just take up space for alignment
|
|
SNew( SBox )
|
|
.WidthOverride( 18.0f )
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &FComponentTransformDetails::OnLocationResetClicked)
|
|
.Visibility(this, &FComponentTransformDetails::GetLocationResetVisibility)
|
|
.ContentPadding(FMargin(5.f, 0.f))
|
|
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
|
|
]
|
|
]
|
|
];
|
|
|
|
|
|
// Rotation
|
|
ChildrenBuilder.AddChildContent( LOCTEXT("RotationFilter", "Rotation").ToString() )
|
|
.CopyAction( CreateCopyAction(ETransformField::Rotation) )
|
|
.PasteAction( CreatePasteAction(ETransformField::Rotation) )
|
|
.NameContent()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
BuildTransformFieldLabel(ETransformField::Rotation)
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(125.0f * 3.0f)
|
|
.MaxDesiredWidth(125.0f * 3.0f)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SRotatorInputBox )
|
|
.AllowSpin( SelectedObjects.Num() == 1 )
|
|
.Roll( this, &FComponentTransformDetails::GetRotationX )
|
|
.Pitch( this, &FComponentTransformDetails::GetRotationY )
|
|
.Yaw( this, &FComponentTransformDetails::GetRotationZ )
|
|
.bColorAxisLabels( true )
|
|
.IsEnabled( this, &FComponentTransformDetails::GetIsEnabled )
|
|
.OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginRotatonSlider )
|
|
.OnEndSliderMovement( this, &FComponentTransformDetails::OnEndRotationSlider )
|
|
.OnRollChanged( this, &FComponentTransformDetails::OnSetRotation, false, 0 )
|
|
.OnPitchChanged( this, &FComponentTransformDetails::OnSetRotation, false, 1 )
|
|
.OnYawChanged( this, &FComponentTransformDetails::OnSetRotation, false, 2 )
|
|
.OnRollCommitted( this, &FComponentTransformDetails::OnRotationCommitted, 0 )
|
|
.OnPitchCommitted( this, &FComponentTransformDetails::OnRotationCommitted, 1 )
|
|
.OnYawCommitted( this, &FComponentTransformDetails::OnRotationCommitted, 2 )
|
|
.Font( FontInfo )
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
// Just take up space for alignment
|
|
SNew( SBox )
|
|
.WidthOverride( 18.0f )
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &FComponentTransformDetails::OnRotationResetClicked)
|
|
.Visibility(this, &FComponentTransformDetails::GetRotationResetVisibility)
|
|
.ContentPadding(FMargin(5.f, 0.f))
|
|
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
|
|
]
|
|
]
|
|
];
|
|
|
|
|
|
ChildrenBuilder.AddChildContent( LOCTEXT("ScaleFilter", "Scale").ToString() )
|
|
.CopyAction( CreateCopyAction(ETransformField::Scale) )
|
|
.PasteAction( CreatePasteAction(ETransformField::Scale) )
|
|
.NameContent()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
BuildTransformFieldLabel(ETransformField::Scale)
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(125.0f * 3.0f)
|
|
.MaxDesiredWidth(125.0f * 3.0f)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign( VAlign_Center )
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew( SVectorInputBox )
|
|
.X( this, &FComponentTransformDetails::GetScaleX )
|
|
.Y( this, &FComponentTransformDetails::GetScaleY )
|
|
.Z( this, &FComponentTransformDetails::GetScaleZ )
|
|
.bColorAxisLabels( true )
|
|
.IsEnabled( this, &FComponentTransformDetails::GetIsEnabled )
|
|
.OnXCommitted( this, &FComponentTransformDetails::OnSetScale, 0 )
|
|
.OnYCommitted( this, &FComponentTransformDetails::OnSetScale, 1 )
|
|
.OnZCommitted( this, &FComponentTransformDetails::OnSetScale, 2 )
|
|
.ContextMenuExtenderX( this, &FComponentTransformDetails::ExtendXScaleContextMenu )
|
|
.ContextMenuExtenderY( this, &FComponentTransformDetails::ExtendYScaleContextMenu )
|
|
.ContextMenuExtenderZ( this, &FComponentTransformDetails::ExtendZScaleContextMenu )
|
|
.Font( FontInfo )
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.MaxWidth( 18.0f )
|
|
[
|
|
// Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered
|
|
SNew( SCheckBox )
|
|
.IsChecked( this, &FComponentTransformDetails::IsPreserveScaleRatioChecked )
|
|
.IsEnabled( this, &FComponentTransformDetails::GetIsEnabled )
|
|
.OnCheckStateChanged( this, &FComponentTransformDetails::OnPreserveScaleRatioToggled )
|
|
.Style( FEditorStyle::Get(), "TransparentCheckBox" )
|
|
.ToolTipText( LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled" ) )
|
|
[
|
|
SNew( SImage )
|
|
.Image( this, &FComponentTransformDetails::GetPreserveScaleRatioImage )
|
|
.ColorAndOpacity( FSlateColor::UseForeground() )
|
|
]
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked(this, &FComponentTransformDetails::OnScaleResetClicked)
|
|
.Visibility(this, &FComponentTransformDetails::GetScaleResetVisibility)
|
|
.ContentPadding(FMargin(5.f, 0.f))
|
|
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
|
|
]
|
|
]
|
|
];
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
void FComponentTransformDetails::Tick( float DeltaTime )
|
|
{
|
|
CacheTransform();
|
|
}
|
|
|
|
bool FComponentTransformDetails::GetIsEnabled( ) const
|
|
{
|
|
return !GEditor->HasLockedActors() || SelectedActorInfo.NumSelected == 0;
|
|
}
|
|
|
|
const FSlateBrush* FComponentTransformDetails::GetPreserveScaleRatioImage() const
|
|
{
|
|
return bPreserveScaleRatio ? FEditorStyle::GetBrush( TEXT("GenericLock") ) : FEditorStyle::GetBrush( TEXT("GenericUnlock") ) ;
|
|
}
|
|
|
|
ESlateCheckBoxState::Type FComponentTransformDetails::IsPreserveScaleRatioChecked() const
|
|
{
|
|
return bPreserveScaleRatio ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
|
|
}
|
|
|
|
void FComponentTransformDetails::OnPreserveScaleRatioToggled( ESlateCheckBoxState::Type NewState )
|
|
{
|
|
bPreserveScaleRatio = (NewState == ESlateCheckBoxState::Checked) ? true : false;
|
|
GConfig->SetBool(TEXT("SelectionDetails"), TEXT("PreserveScaleRatio"), bPreserveScaleRatio, GEditorUserSettingsIni);
|
|
}
|
|
|
|
FText FComponentTransformDetails::GetLocationText() const
|
|
{
|
|
return bAbsoluteLocation ? LOCTEXT( "AbsoluteLocation", "Absolute Location" ) : LOCTEXT( "Location", "Location" );
|
|
}
|
|
|
|
FText FComponentTransformDetails::GetRotationText() const
|
|
{
|
|
return bAbsoluteRotation ? LOCTEXT( "AbsoluteRotation", "Absolute Rotation" ) : LOCTEXT( "Rotation", "Rotation" );
|
|
}
|
|
|
|
FText FComponentTransformDetails::GetScaleText() const
|
|
{
|
|
return bAbsoluteScale ? LOCTEXT( "AbsoluteScale", "Absolute Scale" ) : LOCTEXT( "Scale", "Scale" );
|
|
}
|
|
|
|
void FComponentTransformDetails::OnToggleAbsoluteLocation( bool bEnable )
|
|
{
|
|
UProperty* AbsoluteLocationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "bAbsoluteLocation" );
|
|
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent && RootComponent->bAbsoluteLocation != bEnable )
|
|
{
|
|
if( !bBeganTransaction )
|
|
{
|
|
// NOTE: One transaction per change, not per actor
|
|
GEditor->BeginTransaction( LOCTEXT("ToggleAbsoluteLocation", "Toggle Absolute Location" ) );
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
Object->PreEditChange( AbsoluteLocationProperty );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( AbsoluteLocationProperty );
|
|
}
|
|
|
|
RootComponent->bAbsoluteLocation = !RootComponent->bAbsoluteLocation;
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent( AbsoluteLocationProperty );
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
uint32 NewValue = RootComponent->bAbsoluteLocation;
|
|
uint32 OldValue = !NewValue;
|
|
PropagateTransformPropertyChange(Object, AbsoluteLocationProperty, OldValue, NewValue);
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, AbsoluteLocationProperty );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
}
|
|
|
|
void FComponentTransformDetails::OnToggleAbsoluteRotation( bool bEnable )
|
|
{
|
|
UProperty* AbsoluteRotationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "bAbsoluteRotation" );
|
|
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent && RootComponent->bAbsoluteRotation != bEnable )
|
|
{
|
|
if( !bBeganTransaction )
|
|
{
|
|
// NOTE: One transaction per change, not per actor
|
|
GEditor->BeginTransaction( LOCTEXT("ToggleAbsoluteRotation", "Toggle Absolute Rotation" ) );
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
Object->PreEditChange( AbsoluteRotationProperty );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( AbsoluteRotationProperty );
|
|
}
|
|
|
|
RootComponent->bAbsoluteRotation = !RootComponent->bAbsoluteRotation;
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent( AbsoluteRotationProperty );
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
uint32 NewValue = RootComponent->bAbsoluteRotation;
|
|
uint32 OldValue = !NewValue;
|
|
PropagateTransformPropertyChange(Object, AbsoluteRotationProperty, OldValue, NewValue);
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, AbsoluteRotationProperty );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnToggleAbsoluteScale( bool bEnable )
|
|
{
|
|
UProperty* AbsoluteScaleProperty = FindField<UProperty>( USceneComponent::StaticClass(), "bAbsoluteScale" );
|
|
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent && RootComponent->bAbsoluteScale != bEnable )
|
|
{
|
|
if( !bBeganTransaction )
|
|
{
|
|
// NOTE: One transaction per change, not per actor
|
|
GEditor->BeginTransaction( LOCTEXT("ToggleAbsoluteScale", "Toggle Absolute Scale" ) );
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
Object->PreEditChange( AbsoluteScaleProperty );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( AbsoluteScaleProperty );
|
|
}
|
|
|
|
RootComponent->bAbsoluteScale = !RootComponent->bAbsoluteScale;
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent( AbsoluteScaleProperty );
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
uint32 NewValue = RootComponent->bAbsoluteScale;
|
|
uint32 OldValue = !NewValue;
|
|
PropagateTransformPropertyChange(Object, AbsoluteScaleProperty, OldValue, NewValue);
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, AbsoluteScaleProperty );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
}
|
|
|
|
FReply FComponentTransformDetails::OnLocationResetClicked()
|
|
{
|
|
const FText TransactionName = LOCTEXT("ResetLocation", "Reset Location");
|
|
FScopedTransaction Transaction(TransactionName);
|
|
|
|
OnSetLocation(0.f, ETextCommit::Default, 0);
|
|
OnSetLocation(0.f, ETextCommit::Default, 1);
|
|
OnSetLocation(0.f, ETextCommit::Default, 2);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FComponentTransformDetails::OnRotationResetClicked()
|
|
{
|
|
const FText TransactionName = LOCTEXT("ResetRotation", "Reset Rotation");
|
|
FScopedTransaction Transaction(TransactionName);
|
|
|
|
OnSetRotation(0.f, true, 0);
|
|
OnSetRotation(0.f, true, 1);
|
|
OnSetRotation(0.f, true, 2);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FComponentTransformDetails::OnScaleResetClicked()
|
|
{
|
|
const FText TransactionName = LOCTEXT("ResetScale", "Reset Scale");
|
|
FScopedTransaction Transaction(TransactionName);
|
|
|
|
ScaleObject(1.f, 0, false, TransactionName);
|
|
ScaleObject(1.f, 1, false, TransactionName);
|
|
ScaleObject(1.f, 2, false, TransactionName);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FComponentTransformDetails::ExtendXScaleContextMenu( FMenuBuilder& MenuBuilder )
|
|
{
|
|
MenuBuilder.BeginSection( "ScaleOperations", LOCTEXT( "ScaleOperations", "Scale Operations" ) );
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT( "MirrorValueX", "Mirror X" ),
|
|
LOCTEXT( "MirrorValueX_Tooltip", "Mirror scale value on the X axis" ),
|
|
FSlateIcon(),
|
|
FUIAction( FExecuteAction::CreateSP( this, &FComponentTransformDetails::OnXScaleMirrored ), FCanExecuteAction() )
|
|
);
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void FComponentTransformDetails::ExtendYScaleContextMenu( FMenuBuilder& MenuBuilder )
|
|
{
|
|
MenuBuilder.BeginSection( "ScaleOperations", LOCTEXT( "ScaleOperations", "Scale Operations" ) );
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT( "MirrorValueY", "Mirror Y" ),
|
|
LOCTEXT( "MirrorValueY_Tooltip", "Mirror scale value on the Y axis" ),
|
|
FSlateIcon(),
|
|
FUIAction( FExecuteAction::CreateSP( this, &FComponentTransformDetails::OnYScaleMirrored ), FCanExecuteAction() )
|
|
);
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void FComponentTransformDetails::ExtendZScaleContextMenu( FMenuBuilder& MenuBuilder )
|
|
{
|
|
MenuBuilder.BeginSection( "ScaleOperations", LOCTEXT( "ScaleOperations", "Scale Operations" ) );
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT( "MirrorValueZ", "Mirror Z" ),
|
|
LOCTEXT( "MirrorValueZ_Tooltip", "Mirror scale value on the Z axis" ),
|
|
FSlateIcon(),
|
|
FUIAction( FExecuteAction::CreateSP( this, &FComponentTransformDetails::OnZScaleMirrored ), FCanExecuteAction() )
|
|
);
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void FComponentTransformDetails::OnXScaleMirrored()
|
|
{
|
|
ScaleObject( 1.0f, 0, true, LOCTEXT( "MirrorActorScaleX", "Mirror actor scale X" ) );
|
|
}
|
|
|
|
void FComponentTransformDetails::OnYScaleMirrored()
|
|
{
|
|
ScaleObject( 1.0f, 1, true, LOCTEXT( "MirrorActorScaleY", "Mirror actor scale Y" ) );
|
|
}
|
|
|
|
void FComponentTransformDetails::OnZScaleMirrored()
|
|
{
|
|
ScaleObject( 1.0f, 2, true, LOCTEXT( "MirrorActorScaleZ", "Mirror actor scale Z" ) );
|
|
}
|
|
|
|
/**
|
|
* Cache the entire transform at it is seen by the input boxes so we dont have to iterate over the selected actors multiple times
|
|
*/
|
|
void FComponentTransformDetails::CacheTransform()
|
|
{
|
|
FVector CurLoc;
|
|
FRotator CurRot;
|
|
FVector CurScale;
|
|
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
|
|
FVector Loc;
|
|
FRotator Rot;
|
|
FVector Scale;
|
|
if( RootComponent )
|
|
{
|
|
Loc = RootComponent->RelativeLocation;
|
|
Rot = bEditingRotationInUI ? ObjectToRelativeRotationMap.FindOrAdd(Object) : RootComponent->RelativeRotation;
|
|
Scale = RootComponent->RelativeScale3D;
|
|
|
|
if( ObjectIndex == 0 )
|
|
{
|
|
// Cache the current values from the first actor to see if any values differ among other actors
|
|
CurLoc = Loc;
|
|
CurRot = Rot;
|
|
CurScale = Scale;
|
|
|
|
CachedLocation.Set( Loc );
|
|
CachedRotation.Set( Rot );
|
|
CachedScale.Set( Scale );
|
|
|
|
bAbsoluteLocation = RootComponent->bAbsoluteLocation;
|
|
bAbsoluteScale = RootComponent->bAbsoluteScale;
|
|
bAbsoluteRotation = RootComponent->bAbsoluteRotation;
|
|
}
|
|
else if( CurLoc != Loc || CurRot != Rot || CurScale != Scale )
|
|
{
|
|
// Check which values differ and unset the different values
|
|
CachedLocation.X = Loc.X == CurLoc.X && CachedLocation.X.IsSet() ? Loc.X : TOptional<float>();
|
|
CachedLocation.Y = Loc.Y == CurLoc.Y && CachedLocation.Y.IsSet() ? Loc.Y : TOptional<float>();
|
|
CachedLocation.Z = Loc.Z == CurLoc.Z && CachedLocation.Z.IsSet() ? Loc.Z : TOptional<float>();
|
|
|
|
CachedRotation.X = Rot.Roll == CurRot.Roll && CachedRotation.X.IsSet() ? Rot.Roll : TOptional<float>();
|
|
CachedRotation.Y = Rot.Pitch == CurRot.Pitch && CachedRotation.Y.IsSet() ? Rot.Pitch : TOptional<float>();
|
|
CachedRotation.Z = Rot.Yaw == CurRot.Yaw && CachedRotation.Z.IsSet() ? Rot.Yaw : TOptional<float>();
|
|
|
|
CachedScale.X = Scale.X == CurScale.X && CachedScale.X.IsSet() ? Scale.X : TOptional<float>();
|
|
CachedScale.Y = Scale.Y == CurScale.Y && CachedScale.Y.IsSet() ? Scale.Y : TOptional<float>();
|
|
CachedScale.Z = Scale.Z == CurScale.Z && CachedScale.Z.IsSet() ? Scale.Z : TOptional<float>();
|
|
|
|
// If all values are unset all values are different and we can stop looking
|
|
const bool bAllValuesDiffer = !CachedLocation.IsSet() && !CachedRotation.IsSet() && !CachedScale.IsSet();
|
|
if( bAllValuesDiffer )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FComponentTransformDetails::OnSetLocation( float NewValue, ETextCommit::Type CommitInfo, int32 Axis )
|
|
{
|
|
bool bBeganTransaction = false;
|
|
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent )
|
|
{
|
|
FVector OldRelativeLocation = RootComponent->RelativeLocation;
|
|
FVector RelativeLocation = OldRelativeLocation;
|
|
float OldValue = RelativeLocation[Axis];
|
|
if( OldValue != NewValue )
|
|
{
|
|
if( !bBeganTransaction )
|
|
{
|
|
// Begin a transaction the first time an actors location is about to change.
|
|
// NOTE: One transaction per change, not per actor
|
|
if(Object->IsA<AActor>())
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetLocation", "Set actor location" ) );
|
|
}
|
|
else
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetLocation_ComponentDirect", "Modify Component(s)") );
|
|
}
|
|
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
// Begin a new movement event which will broadcast delegates before and after the actor moves
|
|
FScopedObjectMovement ActorMoveEvent( Object );
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
UProperty* RelativeLocationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "RelativeLocation" );
|
|
Object->PreEditChange( RelativeLocationProperty );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( RelativeLocationProperty );
|
|
}
|
|
|
|
RelativeLocation[Axis] = NewValue;
|
|
|
|
if( SelectedActorInfo.NumSelected == 0 )
|
|
{
|
|
// HACK: Set directly if no actors are selected since this causes Rot->Quat->Rot conversion
|
|
// (recalculates relative rotation even though this is location)
|
|
RootComponent->RelativeLocation = RelativeLocation;
|
|
}
|
|
else
|
|
{
|
|
RootComponent->SetRelativeLocation( RelativeLocation );
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent( RelativeLocationProperty );
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
PropagateTransformPropertyChange(Object, RelativeLocationProperty, OldRelativeLocation, RelativeLocation);
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, RelativeLocationProperty );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
}
|
|
|
|
CacheTransform();
|
|
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void FComponentTransformDetails::OnSetRotation( float NewValue, bool bCommitted, int32 Axis )
|
|
{
|
|
// OnSetRotation is sent from the slider or when the value changes and we dont have slider and the value is being typed.
|
|
// We should only change the value on commit when it is being typed
|
|
const bool bAllowSpin = SelectedObjects.Num() == 1;
|
|
|
|
if( bAllowSpin || bCommitted )
|
|
{
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent )
|
|
{
|
|
FRotator& RelativeRotation = ObjectToRelativeRotationMap.FindOrAdd(Object);
|
|
FRotator OldRelativeRotation = RelativeRotation;
|
|
|
|
float& ValueToChange = Axis == 0 ? RelativeRotation.Roll : Axis == 1 ? RelativeRotation.Pitch : RelativeRotation.Yaw;
|
|
|
|
if( bCommitted || ValueToChange != NewValue )
|
|
{
|
|
if( !bBeganTransaction && bCommitted )
|
|
{
|
|
// Begin a transaction the first time an actors rotation is about to change.
|
|
// NOTE: One transaction per change, not per actor
|
|
if(Object->IsA<AActor>())
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set actor rotation" ) );
|
|
}
|
|
else
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetRotation_ComponentDirect", "Modify Component(s)") );
|
|
}
|
|
|
|
if (!Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
// Broadcast the first time an actor is about to move
|
|
GEditor->BroadcastBeginObjectMovement( *Object );
|
|
}
|
|
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
UProperty* RelativeRotationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "RelativeRotation" );
|
|
if( bCommitted && !bEditingRotationInUI )
|
|
{
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
Object->PreEditChange( RelativeRotationProperty );
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( RelativeRotationProperty );
|
|
}
|
|
|
|
ValueToChange = NewValue;
|
|
|
|
if( SelectedActorInfo.NumSelected == 0 )
|
|
{
|
|
// HACK: Set directly if no actors are selected since this causes Rot->Quat->Rot conversion issues
|
|
// (recalculates relative rotation from quat which can give an equivalent but different value than the user typed)
|
|
RootComponent->RelativeRotation = RelativeRotation;
|
|
}
|
|
else
|
|
{
|
|
RootComponent->SetRelativeRotation( RelativeRotation );
|
|
}
|
|
|
|
AActor* ObjectAsActor = Cast<AActor>( Object );
|
|
if( ObjectAsActor && !ObjectAsActor->HasAnyFlags(RF_ClassDefaultObject) )
|
|
{
|
|
ObjectAsActor->ReregisterAllComponents();
|
|
}
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
PropagateTransformPropertyChange(Object, RelativeRotationProperty, OldRelativeRotation, RelativeRotation);
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent( RelativeRotationProperty, false, !bCommitted && bEditingRotationInUI ? EPropertyChangeType::Interactive : EPropertyChangeType::ValueSet );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, RelativeRotationProperty );
|
|
}
|
|
|
|
if( bCommitted )
|
|
{
|
|
if( !bEditingRotationInUI )
|
|
{
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
}
|
|
|
|
if (!Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
// The actor is done moving
|
|
GEditor->BroadcastEndObjectMovement( *Object );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bCommitted && bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
}
|
|
|
|
// Redraw
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnRotationCommitted(float NewValue, ETextCommit::Type CommitInfo, int32 Axis)
|
|
{
|
|
OnSetRotation(NewValue, true, Axis);
|
|
|
|
CacheTransform();
|
|
}
|
|
|
|
void FComponentTransformDetails::OnBeginRotatonSlider()
|
|
{
|
|
bEditingRotationInUI = true;
|
|
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
|
|
// Start a new transation when a rotator slider begins to change
|
|
// We'll end it when the slider is release
|
|
// NOTE: One transaction per change, not per actor
|
|
if(!bBeganTransaction)
|
|
{
|
|
if(Object->IsA<AActor>())
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set actor rotation" ) );
|
|
}
|
|
else
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetRotation_ComponentDirect", "Modify Component(s)") );
|
|
}
|
|
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent )
|
|
{
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
UProperty* RelativeRotationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "RelativeRotation" );
|
|
Object->PreEditChange( RelativeRotationProperty );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Just in case we couldn't start a new transaction for some reason
|
|
if(!bBeganTransaction)
|
|
{
|
|
GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set actor rotation" ) );
|
|
}
|
|
}
|
|
|
|
void FComponentTransformDetails::OnEndRotationSlider(float NewValue)
|
|
{
|
|
bEditingRotationInUI = false;
|
|
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent )
|
|
{
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
UProperty* RelativeRotationProperty = FindField<UProperty>( USceneComponent::StaticClass(), "RelativeRotation" );
|
|
FPropertyChangedEvent PropertyChangedEvent( RelativeRotationProperty );
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
GEditor->EndTransaction();
|
|
|
|
// Redraw
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void FComponentTransformDetails::OnSetScale( const float NewValue, ETextCommit::Type CommitInfo, int32 Axis )
|
|
{
|
|
ScaleObject( NewValue, Axis, false, LOCTEXT( "OnSetScale", "Set actor scale" ) );
|
|
}
|
|
|
|
void FComponentTransformDetails::ScaleObject( float NewValue, int32 Axis, bool bMirror, const FText& TransactionSessionName )
|
|
{
|
|
UProperty* RelativeScale3DProperty = FindField<UProperty>( USceneComponent::StaticClass(), "RelativeScale3D" );
|
|
|
|
bool bBeganTransaction = false;
|
|
for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex )
|
|
{
|
|
TWeakObjectPtr<UObject> ObjectPtr = SelectedObjects[ObjectIndex];
|
|
if( ObjectPtr.IsValid() )
|
|
{
|
|
UObject* Object = ObjectPtr.Get();
|
|
USceneComponent* RootComponent = FComponentEditorUtils::GetSceneComponent( Object );
|
|
if( RootComponent )
|
|
{
|
|
FVector OldRelativeScale = RootComponent->RelativeScale3D;
|
|
FVector RelativeScale = OldRelativeScale;
|
|
if( bMirror )
|
|
{
|
|
NewValue = -RelativeScale[Axis];
|
|
}
|
|
float OldValue = RelativeScale[Axis];
|
|
if( OldValue != NewValue )
|
|
{
|
|
if( !bBeganTransaction )
|
|
{
|
|
// Begin a transaction the first time an actors scale is about to change.
|
|
// NOTE: One transaction per change, not per actor
|
|
GEditor->BeginTransaction( TransactionSessionName );
|
|
bBeganTransaction = true;
|
|
}
|
|
|
|
FScopedSwitchWorldForObject WorldSwitcher( Object );
|
|
|
|
if (Object->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
// Default subobjects must be included in any undo/redo operations
|
|
Object->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
// Begin a new movement event which will broadcast delegates before and after the actor moves
|
|
FScopedObjectMovement ActorMoveEvent( Object );
|
|
|
|
Object->PreEditChange( RelativeScale3DProperty );
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPreChange( RelativeScale3DProperty );
|
|
}
|
|
|
|
// Set the new value for the corresponding axis
|
|
RelativeScale[Axis] = NewValue;
|
|
|
|
if( bPreserveScaleRatio )
|
|
{
|
|
// Account for the previous scale being zero. Just set to the new value in that case?
|
|
float Ratio = OldValue == 0.0f ? NewValue : NewValue/OldValue;
|
|
|
|
// Change values on axes besides the one being directly changed
|
|
switch( Axis )
|
|
{
|
|
case 0:
|
|
RelativeScale.Y *= Ratio;
|
|
RelativeScale.Z *= Ratio;
|
|
break;
|
|
case 1:
|
|
RelativeScale.X *= Ratio;
|
|
RelativeScale.Z *= Ratio;
|
|
break;
|
|
case 2:
|
|
RelativeScale.X *= Ratio;
|
|
RelativeScale.Y *= Ratio;
|
|
}
|
|
}
|
|
|
|
RootComponent->SetRelativeScale3D( RelativeScale );
|
|
|
|
// Build property chain so the actor knows whether we changed the X, Y or Z
|
|
FEditPropertyChain PropertyChain;
|
|
|
|
if (!bPreserveScaleRatio)
|
|
{
|
|
UStruct* VectorStruct = FindObjectChecked<UStruct>(UObject::StaticClass(), TEXT("Vector"), false);
|
|
|
|
UProperty* VectorValueProperty = NULL;
|
|
switch( Axis )
|
|
{
|
|
case 0:
|
|
VectorValueProperty = FindField<UFloatProperty>(VectorStruct, TEXT("X"));
|
|
break;
|
|
case 1:
|
|
VectorValueProperty = FindField<UFloatProperty>(VectorStruct, TEXT("Y"));
|
|
break;
|
|
case 2:
|
|
VectorValueProperty = FindField<UFloatProperty>(VectorStruct, TEXT("Z"));
|
|
}
|
|
|
|
PropertyChain.AddHead(VectorValueProperty);
|
|
}
|
|
PropertyChain.AddHead(RelativeScale3DProperty);
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(RelativeScale3DProperty, false, EPropertyChangeType::ValueSet);
|
|
FPropertyChangedChainEvent PropertyChangedChainEvent(PropertyChain, PropertyChangedEvent);
|
|
Object->PostEditChangeChainProperty( PropertyChangedChainEvent );
|
|
|
|
// For backwards compatibility, as for some reason PostEditChangeChainProperty calls PostEditChangeProperty with the property set to "X" not "RelativeScale3D"
|
|
// (it does that for all vector properties, and I don't want to be the one to change that)
|
|
if (!bPreserveScaleRatio)
|
|
{
|
|
Object->PostEditChangeProperty( PropertyChangedEvent );
|
|
}
|
|
else
|
|
{
|
|
// If the other scale values have been updated, make sure we update the transform now (as the tick will be too late)
|
|
// so they appear correct when their EditedText is fetched from the delegate.
|
|
CacheTransform();
|
|
}
|
|
|
|
// If this is a default object or subobject, propagate the change out to any current instances of this object
|
|
if(Object->HasAnyFlags(RF_ClassDefaultObject|RF_DefaultSubObject))
|
|
{
|
|
PropagateTransformPropertyChange(Object, RelativeScale3DProperty, OldRelativeScale, RelativeScale);
|
|
}
|
|
|
|
if( NotifyHook )
|
|
{
|
|
NotifyHook->NotifyPostChange( PropertyChangedEvent, RelativeScale3DProperty );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bBeganTransaction )
|
|
{
|
|
GEditor->EndTransaction();
|
|
}
|
|
|
|
CacheTransform();
|
|
|
|
// Redraw
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|