// 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(ComponentOuter)) { BlueprintSCS = OuterBlueprint->SimpleConstructionScript; } else if (UBlueprintGeneratedClass const* const GeneratedClass = Cast(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 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(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 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(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(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(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 FSceneComponentDetails::MakeInstance() { return MakeShareable( new FSceneComponentDetails ); } ESlateCheckBoxState::Type FSceneComponentDetails::IsMobilityActive(TWeakPtr 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 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 MobilityHandle, EComponentMobility::Type InMobility) { if ( MobilityHandle.IsValid() && InCheckedState == ESlateCheckBoxState::Checked) { MobilityHandle.Pin()->SetValue((uint8)InMobility); } } FText FSceneComponentDetails::GetMobilityToolTip(TWeakPtr 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 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>::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt) { if (!ObjectIt->IsValid()) { continue; } USceneComponent const* SceneComponent = Cast((*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 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 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 MobilityPropertyWeak(MobilityProperty); TSharedPtr 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 >& 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 >& 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 > 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(SceneComponentObjects[ComponentIndex].Get()); UBlueprint* Blueprint = (SceneComponent ? Cast(SceneComponent->GetOuter()) : NULL); if (SceneComponent->AttachParent == NULL && SceneComponent->GetOuter()->HasAnyFlags(RF_ClassDefaultObject)) { bShouldShowTransform = false; } else if (Blueprint && Blueprint->SimpleConstructionScript) { const TArray& 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 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 MobilityProperty) { if (!MobilityProperty.IsValid() || CachedSelectedSceneComponents.Num() == 0) { return; } // in case GetValue() fails (like with multiple values selected) uint8 MobilityValue = Cast(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 >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt) { NumMobilityChanges += SetDecedentMobility(Cast(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 >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt) { USceneComponent const* SelectedSceneComponent = Cast(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 >::TConstIterator ObjectIt(CachedSelectedSceneComponents); ObjectIt; ++ObjectIt) { NumMobilityChanges += SetAncestorMobility(Cast(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