Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/SceneComponentDetails.cpp
Michael Noland 75f66f85c7 Engine: Prevented Stationary mobility from showing up on anything except subclasses of ULightComponentBase
#codereview james.golding

[CL 2062699 by Michael Noland in Main branch]
2014-05-02 22:21:28 -04:00

758 lines
27 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "DetailCustomizationsPrivatePCH.h"
#include "SceneComponentDetails.h"
#include "AssetSelection.h"
#include "PropertyEditing.h"
#include "PropertyRestriction.h"
#include "IPropertyUtilities.h"
#include "ComponentTransformDetails.h"
#define LOCTEXT_NAMESPACE "SceneComponentDetails"
/**
* A helper for retrieving the simple-construction-script that this component belongs in.
*
* @param Component The component you want the SCS for.
* @return The component's blueprint SCS (NULL if one wasn't found).
*/
static USimpleConstructionScript const* GetSimpleConstructionScript(USceneComponent const* Component)
{
USimpleConstructionScript const* BlueprintSCS = NULL;
check(Component);
UObject const* ComponentOuter = Component->GetOuter();
if (UBlueprint const* const OuterBlueprint = Cast<UBlueprint const>(ComponentOuter))
{
BlueprintSCS = OuterBlueprint->SimpleConstructionScript;
}
else if (UBlueprintGeneratedClass const* const GeneratedClass = Cast<UBlueprintGeneratedClass>(ComponentOuter))
{
BlueprintSCS = GeneratedClass->SimpleConstructionScript;
}
return BlueprintSCS;
}
/**
* A static helper function for retrieving the simple-construction-script node that
* corresponds to the specified scene component template.
*
* @param ComponentObj The component you want to find a USCS_Node for
* @return A USCS_Node pointer corresponding to the specified component (NULL if we didn't find one)
*/
static USCS_Node* FindCorrespondingSCSNode(USceneComponent const* ComponentObj)
{
USimpleConstructionScript const* BlueprintSCS = GetSimpleConstructionScript(ComponentObj);
if (BlueprintSCS == NULL)
{
return NULL;
}
TArray<USCS_Node*> AllSCSNodes = BlueprintSCS->GetAllNodes();
for (int32 SCSNodeIndex = 0; SCSNodeIndex < AllSCSNodes.Num(); ++SCSNodeIndex)
{
USCS_Node* SCSNode = AllSCSNodes[SCSNodeIndex];
if (SCSNode->ComponentTemplate == ComponentObj)
{
return SCSNode;
}
}
return NULL;
}
/**
* A static helper function used to retrieve a component's scene parent
*
* @param SceneComponentObject The component you want the attached parent for
* @return A pointer to the component's scene parent (NULL if there was not one)
*/
static USceneComponent* GetAttachedParent(USceneComponent const* SceneComponentObject)
{
USceneComponent* SceneParent = SceneComponentObject->AttachParent;
if (SceneParent != NULL)
{
return NULL;
}
USCS_Node* const SCSNode = FindCorrespondingSCSNode(SceneComponentObject);
// if we didn't find a corresponding simple-construction-script node
if (SCSNode == NULL)
{
return NULL;
}
USimpleConstructionScript const* BlueprintSCS = GetSimpleConstructionScript(SceneComponentObject);
check(BlueprintSCS != NULL);
USCS_Node* const ParentSCSNode = BlueprintSCS->FindParentNode(SCSNode);
if (ParentSCSNode != NULL)
{
SceneParent = Cast<USceneComponent>(ParentSCSNode->ComponentTemplate);
}
return SceneParent;
}
/**
* A static helper function used meant as the default for determining if a
* component's Mobility should be overridden.
*
* @param CurrentMobility The component's current Mobility setting
* @param NewMobility The proposed new Mobility for the component in question
* @return True if the two mobilities are not equal (false if they are equal)
*/
static bool AreMobilitiesDifferent(EComponentMobility::Type CurrentMobility, EComponentMobility::Type NewMobility)
{
return CurrentMobility != NewMobility;
}
DECLARE_DELEGATE_RetVal_OneParam(bool, FMobilityQueryDelegate, EComponentMobility::Type);
/**
* A static helper function that recursively alters the Mobility property for all
* sub-components (descending from the specified USceneComponent)
*
* @param SceneComponentObject The component whose sub-components you want to alter
* @param NewMobilityType The Mobility type you want to switch sub-components over to
* @param ShouldOverrideMobility A delegate used to determine if a sub-component's Mobility should be overridden
* (if left unset it will default to the AreMobilitiesDifferent() function)
* @return The number of decedents that had their mobility altered.
*/
static int32 SetDecedentMobility(USceneComponent const* SceneComponentObject, EComponentMobility::Type NewMobilityType, FMobilityQueryDelegate ShouldOverrideMobility = FMobilityQueryDelegate())
{
if (!ensure(SceneComponentObject != NULL))
{
return 0;
}
TArray<USceneComponent*> AttachedChildren = SceneComponentObject->AttachChildren;
// gather children for component templates
USCS_Node* SCSNode = FindCorrespondingSCSNode(SceneComponentObject);
if (SCSNode != NULL)
{
// gather children from the SCSNode
for (int32 ChildIndex = 0; ChildIndex < SCSNode->ChildNodes.Num(); ++ChildIndex)
{
USCS_Node* SCSChild = SCSNode->ChildNodes[ChildIndex];
USceneComponent* ChildSceneComponent = Cast<USceneComponent>(SCSChild->ComponentTemplate);
if (ChildSceneComponent != NULL)
{
AttachedChildren.Add(ChildSceneComponent);
}
}
}
if (!ShouldOverrideMobility.IsBound())
{
ShouldOverrideMobility = FMobilityQueryDelegate::CreateStatic(&AreMobilitiesDifferent, NewMobilityType);
}
int32 NumDecendentsChanged = 0;
// recursively alter the mobility for children and deeper decedents
for (int32 ChildIndex = 0; ChildIndex < AttachedChildren.Num(); ++ChildIndex)
{
USceneComponent* ChildSceneComponent = AttachedChildren[ChildIndex];
if (ShouldOverrideMobility.Execute(ChildSceneComponent->Mobility))
{
// USceneComponents shouldn't be set Stationary
if ((NewMobilityType == EComponentMobility::Stationary) && ChildSceneComponent->IsA(UStaticMeshComponent::StaticClass()))
{
// make it Movable (because it is acceptable for Stationary parents to have Movable children)
ChildSceneComponent->Mobility = EComponentMobility::Movable;
}
else
{
ChildSceneComponent->Mobility = NewMobilityType;
}
++NumDecendentsChanged;
}
NumDecendentsChanged += SetDecedentMobility(ChildSceneComponent, NewMobilityType, ShouldOverrideMobility);
}
return NumDecendentsChanged;
}
/**
* A static helper function that alters the Mobility property for all ancestor
* components (ancestors of the specified USceneComponent).
*
* @param SceneComponentObject The component whose attached ancestors you want to alter
* @param NewMobilityType The Mobility type you want to switch ancestor components over to
* @param ShouldOverrideMobility A delegate used to determine if a ancestor's Mobility should be overridden
* (if left unset it will default to the AreMobilitiesDifferent() function)
* @return The number of ancestors that had their mobility altered.
*/
static int32 SetAncestorMobility(USceneComponent const* SceneComponentObject, EComponentMobility::Type NewMobilityType, FMobilityQueryDelegate ShouldOverrideMobility = FMobilityQueryDelegate())
{
if (!ensure(SceneComponentObject != NULL))
{
return 0;
}
if (!ShouldOverrideMobility.IsBound())
{
ShouldOverrideMobility = FMobilityQueryDelegate::CreateStatic(&AreMobilitiesDifferent, NewMobilityType);
}
int32 MobilityAlteredCount = 0;
while(USceneComponent* AttachedParent = GetAttachedParent(SceneComponentObject))
{
if (ShouldOverrideMobility.Execute(AttachedParent->Mobility))
{
// USceneComponents shouldn't be set Stationary
if ((NewMobilityType == EComponentMobility::Stationary) && AttachedParent->IsA(UStaticMeshComponent::StaticClass()))
{
// make it Static (because it is acceptable for Stationary children to have Static parents)
AttachedParent->Mobility = EComponentMobility::Static;
}
else
{
AttachedParent->Mobility = NewMobilityType;
}
++MobilityAlteredCount;
}
SceneComponentObject = AttachedParent;
}
return MobilityAlteredCount;
}
/**
* Walks the scene hierarchy looking for inherited components (like ones from
* a parent class). If it finds one, this returns its mobility setting.
*
* @param SceneComponent The child component who's ancestor hierarchy you want to traverse.
* @return The mobility of the first scene-component ancestor (EComponentMobility::Static if one wasn't found).
*/
static EComponentMobility::Type GetInheritedMobility(USceneComponent const* const SceneComponent)
{
// default to "static" since it doesn't restrict anything (in case we don't inherit any at all)
EComponentMobility::Type InheritedMobility = EComponentMobility::Static;
USCS_Node* ComponentNode = FindCorrespondingSCSNode(SceneComponent);
if(ComponentNode == NULL)
{
return EComponentMobility::Static;
}
USimpleConstructionScript const* const SceneSCS = ComponentNode->GetSCS();
check(SceneSCS != NULL);
do
{
bool const bIsParentInherited = !ComponentNode->ParentComponentOwnerClassName.IsNone();
// if we can't alter the parent component's mobility from the current blueprint
if (bIsParentInherited)
{
USCS_Node const* ParentNode = NULL;
UClass* ParentClass = SceneSCS->GetOwnerClass();
// ParentNode should be null, so we need to find it in the class that owns it...
// first, find the class:
while((ParentClass != NULL) && (ParentClass->GetFName() != ComponentNode->ParentComponentOwnerClassName))
{
ParentClass = ParentClass->GetSuperClass();
}
// now look through this blueprint and find the inherited parent node
if (UBlueprintGeneratedClass* BlueprintClass = Cast<UBlueprintGeneratedClass>(ParentClass))
{
for (USCS_Node const* Node : BlueprintClass->SimpleConstructionScript->GetAllNodes())
{
if (Node->VariableName == ComponentNode->ParentComponentOrVariableName)
{
ParentNode = Node;
break;
}
}
}
if (ParentNode != NULL)
{
if (USceneComponent const* const ParentComponent = Cast<USceneComponent>(ParentNode->ComponentTemplate))
{
InheritedMobility = ParentComponent->Mobility;
break;
}
}
}
ComponentNode = SceneSCS->FindParentNode(ComponentNode);
} while (ComponentNode != NULL);
return InheritedMobility;
}
/**
* Checks to see if the specified mobility is valid for the passed USceneComponent.
*
* @param MobilityValue The mobility you wish to validate.
* @param SceneComponent The component you want to check for.
* @param ProhibitedReasonOut If the mobility is invalid, this will explain why.
* @return False if the mobility in question is valid, true if it is invalid.
*/
static bool IsMobilitySettingProhibited(EComponentMobility::Type const MobilityValue, USceneComponent const* const SceneComponent, FText& ProhibitedReasonOut)
{
bool bIsProhibited = false;
switch (MobilityValue)
{
case EComponentMobility::Movable:
{
// movable is always an option (parent components can't prevent this from being movable)
bIsProhibited = false;
break;
}
case EComponentMobility::Stationary:
{
if (!SceneComponent->IsA(ULightComponentBase::StaticClass()))
{
ProhibitedReasonOut = LOCTEXT("OnlyLightsCanBeStationary", "Only light components can be stationary.");
bIsProhibited = true;
}
else if (GetInheritedMobility(SceneComponent) == EComponentMobility::Movable)
{
ProhibitedReasonOut = LOCTEXT("ParentMoreMobileRestriction", "Selected objects cannot be less mobile than their inherited parents.");
// can't be less movable than what we've inherited
bIsProhibited = true;
}
break;
}
case EComponentMobility::Static:
{
if (GetInheritedMobility(SceneComponent) != EComponentMobility::Static)
{
ProhibitedReasonOut = LOCTEXT("ParentMoreMobileRestriction", "Selected objects cannot be less mobile than their inherited parents.");
// can't be less movable than what we've inherited
bIsProhibited = true;
}
}
};
return bIsProhibited;
};
TSharedRef<IDetailCustomization> FSceneComponentDetails::MakeInstance()
{
return MakeShareable( new FSceneComponentDetails );
}
ESlateCheckBoxState::Type FSceneComponentDetails::IsMobilityActive(TWeakPtr<IPropertyHandle> MobilityHandle, EComponentMobility::Type InMobility) const
{
if ( MobilityHandle.IsValid() )
{
uint8 MobilityByte;
MobilityHandle.Pin()->GetValue(MobilityByte);
return MobilityByte == InMobility ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
}
return ESlateCheckBoxState::Unchecked;
}
FSlateColor FSceneComponentDetails::GetMobilityTextColor(TWeakPtr<IPropertyHandle> MobilityHandle, EComponentMobility::Type InMobility) const
{
if ( MobilityHandle.IsValid() )
{
uint8 MobilityByte;
MobilityHandle.Pin()->GetValue(MobilityByte);
return MobilityByte == InMobility ? FSlateColor(FLinearColor(0, 0, 0)) : FSlateColor(FLinearColor(0.72f, 0.72f, 0.72f, 1.f));
}
return FSlateColor(FLinearColor(0.72f, 0.72f, 0.72f, 1.f));
}
void FSceneComponentDetails::OnMobilityChanged(ESlateCheckBoxState::Type InCheckedState, TWeakPtr<IPropertyHandle> MobilityHandle, EComponentMobility::Type InMobility)
{
if ( MobilityHandle.IsValid() && InCheckedState == ESlateCheckBoxState::Checked)
{
MobilityHandle.Pin()->SetValue((uint8)InMobility);
}
}
FText FSceneComponentDetails::GetMobilityToolTip(TWeakPtr<IPropertyHandle> MobilityHandle) const
{
if ( MobilityHandle.IsValid() )
{
return FText::FromString(MobilityHandle.Pin()->GetToolTipText());
}
return FText::GetEmpty();
}
void FSceneComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder )
{
MakeTransformDetails( DetailBuilder );
// Put mobility property in Transform section
IDetailCategoryBuilder& TransformCategory = DetailBuilder.EditCategory( "TransformCommon", LOCTEXT("TransformCommonCategory", "Transform").ToString(), ECategoryPriority::Transform );
TSharedPtr<IPropertyHandle> MobilityProperty = DetailBuilder.GetProperty("Mobility");
uint8 RestrictedMobilityBits = 0u;
enum
{
StaticMobilityBitMask = (1u << EComponentMobility::Static),
StationaryMobilityBitMask = (1u << EComponentMobility::Stationary),
MovableMobilityBitMask = (1u << EComponentMobility::Movable),
};
// see if any of the selected objects have mobility restrictions
DetailBuilder.GetObjectsBeingCustomized( CachedSelectedSceneComponents );
for (TArray<TWeakObjectPtr<UObject>>::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt)
{
if (!ObjectIt->IsValid())
{
continue;
}
USceneComponent const* SceneComponent = Cast<USceneComponent>((*ObjectIt).Get());
if (SceneComponent == NULL)
{
continue;
}
// if we haven't restricted the "Static" option yet
if (!(RestrictedMobilityBits & StaticMobilityBitMask))
{
FText RestrictReason;
if (IsMobilitySettingProhibited(EComponentMobility::Static, SceneComponent, RestrictReason))
{
TSharedPtr<FPropertyRestriction> StaticRestriction = MakeShareable(new FPropertyRestriction(RestrictReason));
StaticRestriction->AddValue(TEXT("Static"));
MobilityProperty->AddRestriction(StaticRestriction.ToSharedRef());
RestrictedMobilityBits |= StaticMobilityBitMask;
}
}
// if we haven't restricted the "Stationary" option yet
if (!(RestrictedMobilityBits & StationaryMobilityBitMask))
{
FText RestrictReason;
if (IsMobilitySettingProhibited(EComponentMobility::Stationary, SceneComponent, RestrictReason))
{
TSharedPtr<FPropertyRestriction> StationaryRestriction = MakeShareable(new FPropertyRestriction(RestrictReason));
StationaryRestriction->AddValue(TEXT("Stationary"));
MobilityProperty->AddRestriction(StationaryRestriction.ToSharedRef());
RestrictedMobilityBits |= StationaryMobilityBitMask;
}
}
// no need to go through all of them if we can't restrict any more
if ((RestrictedMobilityBits & StaticMobilityBitMask) && (RestrictedMobilityBits & StationaryMobilityBitMask))
{
break;
}
}
FSimpleDelegate OnMobilityChangedDelegate = FSimpleDelegate::CreateSP(this, &FSceneComponentDetails::OnMobilityChanged, MobilityProperty);
MobilityProperty->SetOnPropertyValueChanged(OnMobilityChangedDelegate);
// Build Editor for Mobility
TWeakPtr<IPropertyHandle> MobilityPropertyWeak(MobilityProperty);
TSharedPtr<SUniformGridPanel> ButtonOptionsPanel;
IDetailPropertyRow& MobilityRow = TransformCategory.AddProperty(MobilityProperty);
MobilityRow.CustomWidget()
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Mobility", "Mobility"))
.ToolTipText(this, &FSceneComponentDetails::GetMobilityToolTip, MobilityPropertyWeak)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MaxDesiredWidth(0)
[
SAssignNew(ButtonOptionsPanel, SUniformGridPanel)
];
bool bShowStatic = !( RestrictedMobilityBits & StaticMobilityBitMask );
bool bShowStationary = !( RestrictedMobilityBits & StationaryMobilityBitMask );
int32 ColumnIndex = 0;
if ( bShowStatic )
{
// Static Mobility
ButtonOptionsPanel->AddSlot(0, 0)
[
SNew(SCheckBox)
.Style(FEditorStyle::Get(), "Property.ToggleButton.Start")
.IsChecked(this, &FSceneComponentDetails::IsMobilityActive, MobilityPropertyWeak, EComponentMobility::Static)
.OnCheckStateChanged(this, &FSceneComponentDetails::OnMobilityChanged, MobilityPropertyWeak, EComponentMobility::Static)
.ToolTipText(LOCTEXT("Mobility_Movable", "A static object can't be changed in game.\n● Allows Baked Lighting\n● Fastest Rendering"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(3, 2)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Mobility.Static"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(6, 2)
[
SNew(STextBlock)
.Text(LOCTEXT("Static", "Static"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.ColorAndOpacity(this, &FSceneComponentDetails::GetMobilityTextColor, MobilityPropertyWeak, EComponentMobility::Static)
]
]
];
ColumnIndex++;
}
// Stationary Mobility
if ( bShowStationary )
{
ButtonOptionsPanel->AddSlot(ColumnIndex, 0)
[
SNew(SCheckBox)
.IsChecked(this, &FSceneComponentDetails::IsMobilityActive, MobilityPropertyWeak, EComponentMobility::Stationary)
.Style(FEditorStyle::Get(), ( ColumnIndex == 0 ) ? "Property.ToggleButton.Start" : "Property.ToggleButton.Middle")
.OnCheckStateChanged(this, &FSceneComponentDetails::OnMobilityChanged, MobilityPropertyWeak, EComponentMobility::Stationary)
.ToolTipText(LOCTEXT("Mobility_Movable", "A stationary light will only have its shadowing and bounced lighting from static geometry baked by Lightmass, all other lighting will be dynamic. It can change color and intensity in game.\n● Can't Move\n● Allows Partial Baked Lighting\n● Dynamic Shadows"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(3, 2)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Mobility.Stationary"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(6, 2)
[
SNew(STextBlock)
.Text(LOCTEXT("Stationary", "Stationary"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.ColorAndOpacity(this, &FSceneComponentDetails::GetMobilityTextColor, MobilityPropertyWeak, EComponentMobility::Stationary)
]
]
];
ColumnIndex++;
}
// Movable Mobility
ButtonOptionsPanel->AddSlot(ColumnIndex, 0)
[
SNew(SCheckBox)
.IsChecked(this, &FSceneComponentDetails::IsMobilityActive, MobilityPropertyWeak, EComponentMobility::Movable)
.Style(FEditorStyle::Get(), ( ColumnIndex == 0 ) ? "Property.ToggleButton" : "Property.ToggleButton.End")
.OnCheckStateChanged(this, &FSceneComponentDetails::OnMobilityChanged, MobilityPropertyWeak, EComponentMobility::Movable)
.ToolTipText(LOCTEXT("Mobility_Movable", "Movable objects can be moved and changed in game.\n● Totally Dynamic\n● Allows Dynamic Shadows\n● Slowest Rendering"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(3, 2)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Mobility.Movable"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.Padding(6, 2)
[
SNew(STextBlock)
.Text(LOCTEXT("Movable", "Movable"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.ColorAndOpacity(this, &FSceneComponentDetails::GetMobilityTextColor, MobilityPropertyWeak, EComponentMobility::Movable)
]
]
];
}
void FSceneComponentDetails::MakeTransformDetails( IDetailLayoutBuilder& DetailBuilder )
{
const TArray<TWeakObjectPtr<AActor> >& SelectedActors = DetailBuilder.GetDetailsView().GetSelectedActors();
const FSelectedActorInfo& SelectedActorInfo = DetailBuilder.GetDetailsView().GetSelectedActorInfo();
// Hide the transform properties so they don't show up
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "bAbsoluteLocation" ) );
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "bAbsoluteRotation" ) );
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "bAbsoluteScale" ) );
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "RelativeLocation" ) );
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "RelativeRotation" ) );
DetailBuilder.HideProperty( DetailBuilder.GetProperty( "RelativeScale3D" ) );
// Determine whether or not we are editing Blueprint defaults through the CDO
bool bIsEditingBlueprintDefaults = false;
const TArray<TWeakObjectPtr<UObject> >& SelectedObjects = DetailBuilder.GetDetailsView().GetSelectedObjects();
for(int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex)
{
UObject* SelectedObject = SelectedObjects[ObjectIndex].Get();
if(SelectedObject && SelectedObject->HasAnyFlags(RF_ClassDefaultObject))
{
bIsEditingBlueprintDefaults = !!UBlueprint::GetBlueprintFromClass(SelectedObject->GetClass());
if(!bIsEditingBlueprintDefaults)
{
break;
}
}
}
// If there are any actors selected and we're not editing Blueprint defaults, the transform section is shown as part of the actor customization
if( SelectedActors.Num() == 0 || bIsEditingBlueprintDefaults)
{
TArray< TWeakObjectPtr<UObject> > SceneComponentObjects;
DetailBuilder.GetObjectsBeingCustomized( SceneComponentObjects );
// Default to showing the transform for all components unless we are viewing a non-Blueprint class default object (the transform is not used in that case)
bool bShouldShowTransform = !DetailBuilder.GetDetailsView().HasClassDefaultObject() || bIsEditingBlueprintDefaults;
for (int32 ComponentIndex = 0; bShouldShowTransform && ComponentIndex < SceneComponentObjects.Num(); ++ComponentIndex)
{
USceneComponent* SceneComponent = Cast<USceneComponent>(SceneComponentObjects[ComponentIndex].Get());
UBlueprint* Blueprint = (SceneComponent ? Cast<UBlueprint>(SceneComponent->GetOuter()) : NULL);
if (SceneComponent->AttachParent == NULL && SceneComponent->GetOuter()->HasAnyFlags(RF_ClassDefaultObject))
{
bShouldShowTransform = false;
}
else if (Blueprint && Blueprint->SimpleConstructionScript)
{
const TArray<USCS_Node*>& RootNodes = Blueprint->SimpleConstructionScript->GetRootNodes();
for(int32 RootNodeIndex = 0; bShouldShowTransform && RootNodeIndex < RootNodes.Num(); ++RootNodeIndex)
{
USCS_Node* RootNode = RootNodes[RootNodeIndex];
check(RootNode);
if(RootNode->ComponentTemplate == SceneComponent && RootNode->ParentComponentOrVariableName == NAME_None)
{
bShouldShowTransform = false;
}
}
}
}
if( bShouldShowTransform )
{
TSharedRef<FComponentTransformDetails> TransformDetails = MakeShareable( new FComponentTransformDetails( bIsEditingBlueprintDefaults ? SceneComponentObjects : DetailBuilder.GetDetailsView().GetSelectedObjects(), SelectedActorInfo, DetailBuilder ) );
IDetailCategoryBuilder& TransformCategory = DetailBuilder.EditCategory( "TransformCommon", LOCTEXT("TransformCommonCategory", "Transform").ToString(), ECategoryPriority::Transform );
TransformCategory.AddCustomBuilder( TransformDetails );
}
}
}
void FSceneComponentDetails::OnMobilityChanged(TSharedPtr<IPropertyHandle> MobilityProperty)
{
if (!MobilityProperty.IsValid() || CachedSelectedSceneComponents.Num() == 0)
{
return;
}
// in case GetValue() fails (like with multiple values selected)
uint8 MobilityValue = Cast<const USceneComponent>(CachedSelectedSceneComponents[0].Get())->Mobility;
MobilityProperty->GetValue(MobilityValue);
// Attached parent components can't be more mobile than their children. This means that
// certain mobility hierarchy structures are disallowed. So we have to walk the hierarchy
// and alter parent/child components as a result of this property change.
// track how many other components we had to change
int32 NumMobilityChanges = 0;
// Movable components can only have movable sub-components
if (MobilityValue == EComponentMobility::Movable)
{
for (TArray< TWeakObjectPtr<UObject> >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt)
{
NumMobilityChanges += SetDecedentMobility(Cast<const USceneComponent>(ObjectIt->Get()), EComponentMobility::Movable);
}
}
else if (MobilityValue == EComponentMobility::Stationary)
{
// a functor for checking if we should change a component's Mobility
struct FMobilityEqualityFunctor
{
bool operator()(EComponentMobility::Type CurrentMobility, EComponentMobility::Type CheckValue)
{
return CurrentMobility == CheckValue;
}
};
FMobilityEqualityFunctor EquivalenceFunctor;
// a delegate for checking if components are Static
FMobilityQueryDelegate IsStaticDelegate = FMobilityQueryDelegate::CreateRaw(&EquivalenceFunctor, &FMobilityEqualityFunctor::operator(), EComponentMobility::Static);
// a delegate for checking if components are Movable
FMobilityQueryDelegate IsMovableDelegate = FMobilityQueryDelegate::CreateRaw(&EquivalenceFunctor, &FMobilityEqualityFunctor::operator(), EComponentMobility::Movable);
// Stationary components can't have Movable parents, and can't have Static children
for (TArray< TWeakObjectPtr<UObject> >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt)
{
USceneComponent const* SelectedSceneComponent = Cast<const USceneComponent>(ObjectIt->Get());
// if any decedents are static, change them to stationary (or movable for static meshes)
NumMobilityChanges += SetDecedentMobility(SelectedSceneComponent, EComponentMobility::Stationary, IsStaticDelegate);
// if any ancestors are movable, change them to stationary (or static for static meshes)
NumMobilityChanges += SetAncestorMobility(SelectedSceneComponent, EComponentMobility::Stationary, IsMovableDelegate);
}
}
else // if MobilityValue == Static
{
// ensure we have the mobility we expected (in case someone adds a new one)
ensure(MobilityValue == EComponentMobility::Static);
// Static components can only have Static parents
for (TArray< TWeakObjectPtr<UObject> >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt)
{
NumMobilityChanges += SetAncestorMobility(Cast<const USceneComponent>(ObjectIt->Get()), EComponentMobility::Static);
}
}
// if we altered any components (other than the ones selected), then notify the user
if (NumMobilityChanges > 0)
{
FText NotificationText = LOCTEXT("MobilityAlteredSingularNotification", "Caused 1 component to also change Mobility");
if (NumMobilityChanges > 1)
{
NotificationText = FText::Format(LOCTEXT("MobilityAlteredPluralNotification", "Caused {0} other components to also change Mobility"), FText::AsNumber(NumMobilityChanges));
}
FNotificationInfo Info(NotificationText);
Info.bFireAndForget = true;
Info.bUseThrobber = true;
FSlateNotificationManager::Get().AddNotification(Info);
}
}
#undef LOCTEXT_NAMESPACE