Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp
2025-05-18 13:04:45 +08:00

7407 lines
250 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SSCSEditor.h"
#include "ActorEditorUtils.h"
#include "AddToProjectConfig.h"
#include "Algo/Find.h"
#include "AssetRegistry/AssetData.h"
#include "AssetSelection.h"
#include "BPVariableDragDropAction.h"
#include "BlueprintEditor.h"
#include "BlueprintEditorModule.h"
#include "BlueprintEditorSettings.h"
#include "ClassIconFinder.h"
#include "ClassViewerFilter.h"
#include "ComponentAssetBroker.h"
#include "ComponentInstanceDataCache.h"
#include "ComponentVisualizerManager.h"
#include "Components/ChildActorComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Containers/EnumAsByte.h"
#include "CoreGlobals.h"
#include "CreateBlueprintFromActorDialog.h"
#include "Dialogs/Dialogs.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "EdGraph/EdGraph.h"
#include "EdGraphSchema_K2.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/Engine.h"
#include "Engine/EngineTypes.h"
#include "Engine/InheritableComponentHandler.h"
#include "Engine/MemberReference.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/World.h"
#include "FeaturedClasses.inl"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/GenericCommands.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Notifications/NotificationManager.h"
#include "GameProjectGenerationModule.h"
#include "GraphEditorActions.h"
#include "IDocumentation.h"
#include "ISCSEditorUICustomization.h"
#include "Input/DragAndDrop.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_ComponentBoundEvent.h"
#include "K2Node_Variable.h"
#include "K2Node_VariableGet.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/ChildActorComponentEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "Kismet2/ComponentEditorUtils.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Layout/Children.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "Layout/WidgetPath.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Logging/MessageLog.h"
#include "Math/Color.h"
#include "Math/NumericLimits.h"
#include "Math/Rotator.h"
#include "Math/Transform.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector.h"
#include "Math/Vector2D.h"
#include "Misc/CString.h"
#include "Misc/FeedbackContext.h"
#include "Misc/Guid.h"
#include "ObjectTools.h"
#include "PropertyPath.h"
#include "SCSEditorExtensionContext.h"
#include "SPositiveActionButton.h"
#include "SSCSEditorMenuContext.h"
#include "ScopedTransaction.h"
#include "Selection.h"
#include "Settings/EditorStyleSettings.h"
#include "SlateOptMacros.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateIconFinder.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Subsystems/PanelExtensionSubsystem.h"
#include "Templates/Function.h"
#include "Textures/SlateIcon.h"
#include "ThumbnailRendering/ThumbnailManager.h"
#include "ToolMenu.h"
#include "ToolMenuContext.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuSection.h"
#include "ToolMenus.h"
#include "Toolkits/ToolkitManager.h"
#include "Trace/Detail/Channel.h"
#include "TutorialMetaData.h"
#include "Types/ISlateMetaData.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UObjectHash.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakFieldPtr.h"
#include "UnrealEdGlobals.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/SExpanderArrow.h"
#include "Widgets/Views/SHeaderRow.h"
class FClassViewerInitializationOptions;
class FExtender;
class ITableRow;
class IToolkit;
class SWidget;
class SWindow;
class UEdGraphNode;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "SSCSEditor"
DEFINE_LOG_CATEGORY_STATIC(LogSCSEditor, Log, All);
static const FName SCS_ColumnName_ComponentClass( "ComponentClass" );
static const FName SCS_ColumnName_Asset( "Asset" );
static const FName SCS_ColumnName_Mobility( "Mobility" );
static const FName SCS_ContextMenuName( "Kismet.SCSEditorContextMenu" );
PRAGMA_DISABLE_DEPRECATION_WARNINGS
//////////////////////////////////////////////////////////////////////////
// SSCSEditorDragDropTree
void SSCSEditorDragDropTree::Construct( const FArguments& InArgs )
{
SCSEditor = InArgs._SCSEditor;
STreeView<FSCSEditorTreeNodePtrType>::FArguments BaseArgs;
BaseArgs.OnGenerateRow( InArgs._OnGenerateRow )
.OnItemScrolledIntoView( InArgs._OnItemScrolledIntoView )
.OnGetChildren( InArgs._OnGetChildren )
.OnSetExpansionRecursive( InArgs._OnSetExpansionRecursive )
.TreeItemsSource( InArgs._TreeItemsSource )
.ItemHeight( InArgs._ItemHeight )
.OnContextMenuOpening( InArgs._OnContextMenuOpening )
.OnMouseButtonDoubleClick( InArgs._OnMouseButtonDoubleClick )
.OnSelectionChanged( InArgs._OnSelectionChanged )
.OnExpansionChanged( InArgs._OnExpansionChanged )
.SelectionMode( InArgs._SelectionMode )
.HeaderRow( InArgs._HeaderRow )
.ClearSelectionOnClick( InArgs._ClearSelectionOnClick )
.ExternalScrollbar( InArgs._ExternalScrollbar )
.OnEnteredBadState( InArgs._OnTableViewBadState )
.HighlightParentNodesForSelection(true);
STreeView<FSCSEditorTreeNodePtrType>::Construct( BaseArgs );
}
FReply SSCSEditorDragDropTree::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
FReply Handled = FReply::Unhandled();
if (SCSEditor != nullptr)
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (Operation.IsValid() && (Operation->IsOfType<FExternalDragOperation>() || Operation->IsOfType<FAssetDragDropOp>()))
{
Handled = AssetUtil::CanHandleAssetDrag(DragDropEvent);
if (!Handled.IsEventHandled())
{
if (Operation->IsOfType<FAssetDragDropOp>())
{
const TSharedPtr<FAssetDragDropOp> AssetDragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
for (const FAssetData& AssetData : AssetDragDropOp->GetAssets())
{
if (UClass* AssetClass = AssetData.GetClass())
{
if (AssetClass->IsChildOf(UClass::StaticClass()))
{
Handled = FReply::Handled();
break;
}
}
}
}
}
}
}
return Handled;
}
FReply SSCSEditor::TryHandleAssetDragDropOperation(const FDragDropEvent& DragDropEvent)
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (Operation.IsValid() && (Operation->IsOfType<FExternalDragOperation>() || Operation->IsOfType<FAssetDragDropOp>()))
{
TArray< FAssetData > DroppedAssetData = AssetUtil::ExtractAssetDataFromDrag(Operation);
const int32 NumAssets = DroppedAssetData.Num();
if (NumAssets > 0)
{
GWarn->BeginSlowTask(LOCTEXT("LoadingAssets", "Loading Asset(s)"), true);
bool bMarkBlueprintAsModified = false;
for (int32 DroppedAssetIdx = 0; DroppedAssetIdx < NumAssets; ++DroppedAssetIdx)
{
const FAssetData& AssetData = DroppedAssetData[DroppedAssetIdx];
if (!AssetData.IsAssetLoaded())
{
GWarn->StatusUpdate(DroppedAssetIdx, NumAssets, FText::Format(LOCTEXT("LoadingAsset", "Loading Asset {0}"), FText::FromName(AssetData.AssetName)));
}
UClass* AssetClass = AssetData.GetClass();
UObject* Asset = AssetData.GetAsset();
UBlueprint* BPClass = Cast<UBlueprint>(Asset);
UClass* PotentialComponentClass = nullptr;
UClass* PotentialActorClass = nullptr;
if ((BPClass != nullptr) && (BPClass->GeneratedClass != nullptr))
{
if (BPClass->GeneratedClass->IsChildOf(UActorComponent::StaticClass()))
{
PotentialComponentClass = BPClass->GeneratedClass;
}
else if (BPClass->GeneratedClass->IsChildOf(AActor::StaticClass()))
{
PotentialActorClass = BPClass->GeneratedClass;
}
}
else if (AssetClass && AssetClass->IsChildOf(UClass::StaticClass()))
{
UClass* AssetAsClass = CastChecked<UClass>(Asset);
if (AssetAsClass->IsChildOf(UActorComponent::StaticClass()))
{
PotentialComponentClass = AssetAsClass;
}
else if (AssetAsClass->IsChildOf(AActor::StaticClass()))
{
PotentialActorClass = AssetAsClass;
}
}
FAddNewComponentParams NewComponentParams;
NewComponentParams.bSkipMarkBlueprintModified = true;
NewComponentParams.bSetFocusToNewItem = (DroppedAssetIdx == NumAssets - 1); // Only set focus to the last item created
TSubclassOf<UActorComponent> MatchingComponentClassForAsset = FComponentAssetBrokerage::GetPrimaryComponentForAsset(AssetClass);
if (MatchingComponentClassForAsset != nullptr)
{
AddNewComponent(MatchingComponentClassForAsset, Asset, NewComponentParams);
bMarkBlueprintAsModified = true;
}
else if ((PotentialComponentClass != nullptr) && !PotentialComponentClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract | CLASS_NewerVersionExists))
{
if (PotentialComponentClass->HasMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent))
{
AddNewComponent(PotentialComponentClass, nullptr, NewComponentParams);
bMarkBlueprintAsModified = true;
}
}
else if ((PotentialActorClass != nullptr) && !PotentialActorClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract | CLASS_NewerVersionExists | CLASS_NotPlaceable))
{
AddNewComponent(UChildActorComponent::StaticClass(), PotentialActorClass, NewComponentParams);
bMarkBlueprintAsModified = true;
}
}
// Optimization: Only mark the blueprint as modified at the end
if (bMarkBlueprintAsModified && EditorMode == EComponentEditorMode::BlueprintSCS)
{
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr);
Blueprint->Modify();
SaveSCSCurrentState(Blueprint->SimpleConstructionScript);
bAllowTreeUpdates = true;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
UpdateTree();
GWarn->EndSlowTask();
}
return FReply::Handled();
}
return FReply::Unhandled();
}
FReply SSCSEditorDragDropTree::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
if (SCSEditor != nullptr)
{
return SCSEditor->TryHandleAssetDragDropOperation(DragDropEvent);
}
else
{
return FReply::Unhandled();
}
}
//////////////////////////////////////////////////////////////////////////
// FSCSRowDragDropOp - The drag-drop operation triggered when dragging a row in the components tree
class FSCSRowDragDropOp : public FKismetVariableDragDropAction
{
public:
DRAG_DROP_OPERATOR_TYPE(FSCSRowDragDropOp, FKismetVariableDragDropAction)
/** Available drop actions */
enum EDropActionType
{
DropAction_None,
DropAction_AttachTo,
DropAction_DetachFrom,
DropAction_MakeNewRoot,
DropAction_AttachToOrMakeNewRoot
};
// FGraphEditorDragDropAction interface
virtual void HoverTargetChanged() override;
virtual FReply DroppedOnNode(const FVector2f& ScreenPosition, const FVector2f& GraphPosition) override;
virtual FReply DroppedOnPanel(const TSharedRef< class SWidget >& Panel, const FVector2f& ScreenPosition, const FVector2f& GraphPosition, UEdGraph& Graph) override;
// End of FGraphEditorDragDropAction
/** Node(s) that we started the drag from */
TArray<FSCSEditorTreeNodePtrType> SourceNodes;
/** The type of drop action that's pending while dragging */
EDropActionType PendingDropAction;
static TSharedRef<FSCSRowDragDropOp> New(FName InVariableName, UStruct* InVariableSource, FNodeCreationAnalytic AnalyticCallback);
};
TSharedRef<FSCSRowDragDropOp> FSCSRowDragDropOp::New(FName InVariableName, UStruct* InVariableSource, FNodeCreationAnalytic AnalyticCallback)
{
TSharedPtr<FSCSRowDragDropOp> Operation = MakeShareable(new FSCSRowDragDropOp);
Operation->VariableName = InVariableName;
Operation->VariableSource = InVariableSource;
Operation->AnalyticCallback = AnalyticCallback;
Operation->Construct();
return Operation.ToSharedRef();
}
void FSCSRowDragDropOp::HoverTargetChanged()
{
bool bHoverHandled = false;
FSlateColor IconTint = FLinearColor::White;
const FSlateBrush* ErrorSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
if(SourceNodes.Num() > 1)
{
// Display an error message if attempting to drop multiple source items onto a node
UEdGraphNode* VarNodeUnderCursor = Cast<UK2Node_Variable>(GetHoveredNode());
if (VarNodeUnderCursor != NULL)
{
// Icon/text to draw on tooltip
FText Message = LOCTEXT("InvalidMultiDropTarget", "Cannot replace node with multiple nodes");
SetSimpleFeedbackMessage(ErrorSymbol, IconTint, Message);
bHoverHandled = true;
}
}
if (!bHoverHandled)
{
if (FChildActorComponentEditorUtils::ContainsChildActorSubtreeNode(SourceNodes))
{
// @todo - Add support for drag/drop of child actor template components to create variable get nodes in a local Blueprint event/function graph
FText Message = LOCTEXT("ChildActorDragDropAddVariableNode_Unsupported", "This operation is not currently supported for one or more of the selected components.");
SetSimpleFeedbackMessage(ErrorSymbol, IconTint, Message);
}
else if (FProperty* VariableProperty = GetVariableProperty())
{
const FSlateBrush* PrimarySymbol = nullptr;
const FSlateBrush* SecondarySymbol = nullptr;
FSlateColor PrimaryColor;
FSlateColor SecondaryColor;
GetDefaultStatusSymbol(/*out*/ PrimarySymbol, /*out*/ PrimaryColor, /*out*/ SecondarySymbol, /*out*/ SecondaryColor);
//Create feedback message with the function name.
SetSimpleFeedbackMessage(PrimarySymbol, PrimaryColor, VariableProperty->GetDisplayNameText(), SecondarySymbol, SecondaryColor);
}
else
{
FText Message = LOCTEXT("CannotFindProperty", "Cannot find corresponding variable (make sure component has been assigned to one)");
SetSimpleFeedbackMessage(ErrorSymbol, IconTint, Message);
}
bHoverHandled = true;
}
if(!bHoverHandled)
{
FKismetVariableDragDropAction::HoverTargetChanged();
}
}
FReply FSCSRowDragDropOp::DroppedOnNode(const FVector2f& ScreenPosition, const FVector2f& GraphPosition)
{
// Only allow dropping on another node if there is only a single source item
if(SourceNodes.Num() == 1)
{
FKismetVariableDragDropAction::DroppedOnNode(ScreenPosition, GraphPosition);
}
return FReply::Handled();
}
FReply FSCSRowDragDropOp::DroppedOnPanel(const TSharedRef< class SWidget >& Panel, const FVector2f& ScreenPosition, const FVector2f& GraphPosition, UEdGraph& Graph)
{
const FScopedTransaction Transaction(LOCTEXT("SCSEditorAddMultipleNodes", "Add Component Nodes"));
TArray<UK2Node_VariableGet*> OriginalVariableNodes;
Graph.GetNodesOfClass<UK2Node_VariableGet>(OriginalVariableNodes);
FVector2f GraphPositionToPlace = GraphPosition;
// Add source items to the graph in turn
for (FSCSEditorTreeNodePtrType& SourceNode : SourceNodes)
{
VariableName = SourceNode->GetVariableName();
FKismetVariableDragDropAction::DroppedOnPanel(Panel, ScreenPosition, GraphPositionToPlace, Graph);
GraphPositionToPlace.Y += 50;
}
TArray<UK2Node_VariableGet*> ResultVariableNodes;
Graph.GetNodesOfClass<UK2Node_VariableGet>(ResultVariableNodes);
if (ResultVariableNodes.Num() - OriginalVariableNodes.Num() > 1)
{
TSet<const UEdGraphNode*> NodeSelection;
// Because there is more than one new node, lets grab all the nodes at the bottom of the list and add them to a set for selection
for (int32 NodeIdx = ResultVariableNodes.Num() - 1; NodeIdx >= OriginalVariableNodes.Num(); --NodeIdx)
{
NodeSelection.Add(ResultVariableNodes[NodeIdx]);
}
Graph.SelectNodeSet(NodeSelection);
}
return FReply::Handled();
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNode
FSCSEditorTreeNode::FSCSEditorTreeNode(FSCSEditorTreeNode::ENodeType InNodeType)
: NodeType(InNodeType)
, FilterFlags((uint8)EFilteredState::Unknown)
{
}
FName FSCSEditorTreeNode::GetNodeID() const
{
FName ItemName = GetVariableName();
if (ItemName == NAME_None)
{
UActorComponent* ComponentTemplateOrInstance = GetComponentTemplate();
if (ComponentTemplateOrInstance != nullptr)
{
ItemName = ComponentTemplateOrInstance->GetFName();
}
}
return ItemName;
}
FName FSCSEditorTreeNode::GetVariableName() const
{
return NAME_None;
}
FString FSCSEditorTreeNode::GetDisplayString() const
{
return TEXT("GetDisplayString not overridden");
}
FText FSCSEditorTreeNode::GetDisplayName() const
{
return LOCTEXT("GetDisplayNameNotOverridden", "GetDisplayName not overridden");
}
class USCS_Node* FSCSEditorTreeNode::GetSCSNode() const
{
return nullptr;
}
FSCSEditorTreeNode::ENodeType FSCSEditorTreeNode::GetNodeType() const
{
return NodeType;
}
bool FSCSEditorTreeNode::IsAttachedTo(FSCSEditorTreeNodePtrType InNodePtr) const
{
FSCSEditorTreeNodePtrType TestParentPtr = ParentNodePtr;
while(TestParentPtr.IsValid())
{
if(TestParentPtr == InNodePtr)
{
return true;
}
TestParentPtr = TestParentPtr->ParentNodePtr;
}
return false;
}
bool FSCSEditorTreeNode::MatchesFilterType(const UClass* InFilterType) const
{
// All nodes will pass the type filter by default.
return true;
}
bool FSCSEditorTreeNode::RefreshFilteredState(const UClass* InFilterType, const TArray<FString>& InFilterTerms, bool bRecursive)
{
bool bHasAnyVisibleChildren = false;
if (bRecursive)
{
for (FSCSEditorTreeNodePtrType Child : GetChildren())
{
bHasAnyVisibleChildren |= Child->RefreshFilteredState(InFilterType, InFilterTerms, bRecursive);
}
}
// Don't check a root actor node - it doesn't have a valid variable name. Let it recache based on children and hide itself based on their filter states.
if (GetNodeType() == FSCSEditorTreeNode::RootActorNode)
{
SetCachedFilterState(bHasAnyVisibleChildren, /*bUpdateParent =*/!bRecursive);
return bHasAnyVisibleChildren;
}
bool bIsFilteredOut = InFilterType && !MatchesFilterType(InFilterType);
if (!bIsFilteredOut &&
GetNodeType() != FSCSEditorTreeNode::SeparatorNode)
{
FString DisplayStr = GetDisplayString();
for (const FString& FilterTerm : InFilterTerms)
{
if (!DisplayStr.Contains(FilterTerm))
{
bIsFilteredOut = true;
}
}
}
// if we're not recursing, then assume this is for a new node and we need to update the parent
// otherwise, assume the parent was hit as part of the recursion
const bool bUpdateParent = !bRecursive;
SetCachedFilterState(!bIsFilteredOut, bUpdateParent);
return !bIsFilteredOut;
}
void FSCSEditorTreeNode::SetCachedFilterState(bool bMatchesFilter, bool bUpdateParent)
{
bool bFlagsChanged = false;
if ((FilterFlags & EFilteredState::Unknown) == EFilteredState::Unknown)
{
FilterFlags = 0x00;
bFlagsChanged = true;
}
if (bMatchesFilter)
{
bFlagsChanged |= (FilterFlags & EFilteredState::MatchesFilter) == 0;
FilterFlags |= EFilteredState::MatchesFilter;
}
else
{
bFlagsChanged |= (FilterFlags & EFilteredState::MatchesFilter) != 0;
FilterFlags &= ~EFilteredState::MatchesFilter;
}
const bool bHadChildMatch = (FilterFlags & EFilteredState::ChildMatches) != 0;
// refresh the cached child state (don't update the parent, we'll do that below if it's needed)
RefreshCachedChildFilterState(/*bUpdateParent =*/false);
bFlagsChanged |= bHadChildMatch != ((FilterFlags & EFilteredState::ChildMatches) != 0);
if (bUpdateParent && bFlagsChanged)
{
ApplyFilteredStateToParent();
}
}
void FSCSEditorTreeNode::RefreshCachedChildFilterState(bool bUpdateParent)
{
const bool bContainedMatch = !IsFlaggedForFiltration();
FilterFlags &= ~EFilteredState::ChildMatches;
for (FSCSEditorTreeNodePtrType Child : Children)
{
// Separator nodes should not contribute to child matches for the parent nodes
if (Child->GetNodeType() == FSCSEditorTreeNode::SeparatorNode)
{
continue;
}
if (!Child->IsFlaggedForFiltration())
{
FilterFlags |= EFilteredState::ChildMatches;
break;
}
}
const bool bContainsMatch = !IsFlaggedForFiltration();
const bool bStateChange = bContainedMatch != bContainsMatch;
if (bUpdateParent && bStateChange)
{
ApplyFilteredStateToParent();
}
}
void FSCSEditorTreeNode::ApplyFilteredStateToParent()
{
FSCSEditorTreeNode* Child = this;
while (Child->ParentNodePtr.IsValid())
{
FSCSEditorTreeNode* Parent = Child->ParentNodePtr.Get();
if ( !IsFlaggedForFiltration() )
{
if ((Parent->FilterFlags & EFilteredState::ChildMatches) == 0)
{
Parent->FilterFlags |= EFilteredState::ChildMatches;
}
else
{
// all parents from here on up should have the flag
break;
}
}
// have to see if this was the only child contributing to this flag
else if (Parent->FilterFlags & EFilteredState::ChildMatches)
{
Parent->FilterFlags &= ~EFilteredState::ChildMatches;
for (const FSCSEditorTreeNodePtrType& Sibling : Parent->Children)
{
if (Sibling.Get() == Child)
{
continue;
}
if (Sibling->FilterFlags & EFilteredState::FilteredInMask)
{
Parent->FilterFlags |= EFilteredState::ChildMatches;
break;
}
}
if (Parent->FilterFlags & EFilteredState::ChildMatches)
{
// another child added the flag back
break;
}
}
Child = Parent;
}
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FindClosestParent(TArray<FSCSEditorTreeNodePtrType> InNodes)
{
uint32 MinDepth = MAX_uint32;
FSCSEditorTreeNodePtrType ClosestParentNodePtr;
for(int32 i = 0; i < InNodes.Num() && MinDepth > 1; ++i)
{
if(InNodes[i].IsValid())
{
uint32 CurDepth = 0;
if(InNodes[i]->FindChild(GetComponentTemplate(), true, &CurDepth).IsValid())
{
if(CurDepth < MinDepth)
{
MinDepth = CurDepth;
ClosestParentNodePtr = InNodes[i];
}
}
}
}
return ClosestParentNodePtr;
}
void FSCSEditorTreeNode::SetActorRootNode(FSCSEditorActorNodePtrType InActorNodePtr)
{
ActorRootNodePtr = InActorNodePtr;
for (FSCSEditorTreeNodePtrType& ChildPtr : Children)
{
if (ChildPtr.IsValid())
{
ChildPtr->SetActorRootNode(InActorNodePtr);
}
}
}
void FSCSEditorTreeNode::AddChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
// Ensure the node is not already parented elsewhere
if(InChildNodePtr->GetParent().IsValid())
{
InChildNodePtr->GetParent()->RemoveChild(InChildNodePtr);
}
// If this is an actor node, start a new subtree
if (IsActorNode())
{
ActorRootNodePtr = StaticCastSharedRef<FSCSEditorTreeNodeActorBase>(AsShared());
}
// Link the child node to the actor root node for this subtree
InChildNodePtr->SetActorRootNode(ActorRootNodePtr);
// Add the given node as a child and link its parent
Children.AddUnique(InChildNodePtr);
InChildNodePtr->ParentNodePtr = AsShared();
if (InChildNodePtr->FilterFlags != EFilteredState::Unknown && !InChildNodePtr->IsFlaggedForFiltration())
{
FSCSEditorTreeNodePtrType AncestorPtr = InChildNodePtr->ParentNodePtr;
while (AncestorPtr.IsValid() && (AncestorPtr->FilterFlags & EFilteredState::ChildMatches) == 0)
{
AncestorPtr->FilterFlags |= EFilteredState::ChildMatches;
AncestorPtr = AncestorPtr->GetParent();
}
}
if (InChildNodePtr->IsComponentNode())
{
USCS_Node* SCS_Node = GetSCSNode();
const UActorComponent* ComponentTemplate = GetObject<UActorComponent>();
// Add a child node to the SCS tree node if not already present
USCS_Node* SCS_ChildNode = InChildNodePtr->GetSCSNode();
if (SCS_ChildNode != NULL)
{
// Get the SCS instance that owns the child node
USimpleConstructionScript* SCS = SCS_ChildNode->GetSCS();
if (SCS != NULL)
{
// If the parent is also a valid SCS node
if (SCS_Node != NULL)
{
// If the parent and child are both owned by the same SCS instance
if (SCS_Node->GetSCS() == SCS)
{
// Add the child into the parent's list of children
if (!SCS_Node->GetChildNodes().Contains(SCS_ChildNode))
{
SCS_Node->AddChildNode(SCS_ChildNode);
}
}
else
{
// Adds the child to the SCS root set if not already present
SCS->AddNode(SCS_ChildNode);
// Set parameters to parent this node to the "inherited" SCS node
SCS_ChildNode->SetParent(SCS_Node);
}
}
else if (ComponentTemplate != NULL)
{
// Adds the child to the SCS root set if not already present
SCS->AddNode(SCS_ChildNode);
// Set parameters to parent this node to the native component template
SCS_ChildNode->SetParent(Cast<const USceneComponent>(ComponentTemplate));
}
else
{
// Adds the child to the SCS root set if not already present
SCS->AddNode(SCS_ChildNode);
}
}
}
else if (IsInstancedComponent())
{
USceneComponent* ChildInstance = Cast<USceneComponent>(InChildNodePtr->GetComponentTemplate());
if (ensure(ChildInstance != nullptr))
{
USceneComponent* ParentInstance = Cast<USceneComponent>(GetComponentTemplate());
if (ensure(ParentInstance != nullptr))
{
// Handle attachment at the instance level
if (ChildInstance->GetAttachParent() != ParentInstance)
{
AActor* Owner = ParentInstance->GetOwner();
if (Owner->GetRootComponent() == ChildInstance)
{
Owner->SetRootComponent(ParentInstance);
}
ChildInstance->AttachToComponent(ParentInstance, FAttachmentTransformRules::KeepWorldTransform);
}
}
}
}
}
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::AddChild(USCS_Node* InSCSNode, bool bInIsInherited)
{
// Ensure that the given SCS node is valid
check(InSCSNode != NULL);
// If it doesn't already exist as a child node
FSCSEditorTreeNodePtrType ChildNodePtr = FindChild(InSCSNode);
if(!ChildNodePtr.IsValid())
{
// Add a child node to the SCS editor tree
ChildNodePtr = MakeShareable(new FSCSEditorTreeNodeComponent(InSCSNode, bInIsInherited));
AddChild(ChildNodePtr);
}
return ChildNodePtr;
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::AddChildFromComponent(UActorComponent* InComponentTemplate)
{
// Ensure that the given component template is valid
check(InComponentTemplate != NULL);
// If it doesn't already exist in the SCS editor tree
FSCSEditorTreeNodePtrType ChildNodePtr = FindChild(InComponentTemplate);
if(!ChildNodePtr.IsValid())
{
// Add a child node to the SCS editor tree
ChildNodePtr = FactoryNodeFromComponent(InComponentTemplate);
AddChild(ChildNodePtr);
}
return ChildNodePtr;
}
// Tries to find a SCS node that was likely responsible for creating the specified instance component. Note: This is not always possible to do!
USCS_Node* FSCSEditorTreeNode::FindSCSNodeForInstance(const UActorComponent* InstanceComponent, UClass* ClassToSearch)
{
if ((ClassToSearch != nullptr) && InstanceComponent->IsCreatedByConstructionScript())
{
for (UClass* TestClass = ClassToSearch; TestClass->ClassGeneratedBy != nullptr; TestClass = TestClass->GetSuperClass())
{
if (UBlueprint* TestBP = Cast<UBlueprint>(TestClass->ClassGeneratedBy))
{
if (TestBP->SimpleConstructionScript != nullptr)
{
if (USCS_Node* Result = TestBP->SimpleConstructionScript->FindSCSNode(InstanceComponent->GetFName()))
{
return Result;
}
}
}
}
}
return nullptr;
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FactoryNodeFromComponent(UActorComponent* InComponent)
{
check(InComponent);
bool bComponentIsInAnInstance = false;
AActor* Owner = InComponent->GetOwner();
if ((Owner != nullptr) && !Owner->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
bComponentIsInAnInstance = true;
}
if (bComponentIsInAnInstance)
{
if (InComponent->CreationMethod == EComponentCreationMethod::Instance)
{
return MakeShareable(new FSCSEditorTreeNodeInstanceAddedComponent(Owner, InComponent));
}
else
{
return MakeShareable(new FSCSEditorTreeNodeInstancedInheritedComponent(Owner, InComponent));
}
}
// Not an instanced component, either an SCS node or a native component in BP edit mode
return MakeShareable(new FSCSEditorTreeNodeComponent(InComponent));
}
void FSCSEditorTreeNode::CloseOngoingCreateTransaction()
{
OngoingCreateTransaction.Reset();
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FindChild(const USCS_Node* InSCSNode, bool bRecursiveSearch, uint32* OutDepth) const
{
FSCSEditorTreeNodePtrType Result;
// Ensure that the given SCS node is valid
if(InSCSNode != NULL)
{
// Look for a match in our set of child nodes
for(int32 ChildIndex = 0; ChildIndex < Children.Num() && !Result.IsValid(); ++ChildIndex)
{
if(InSCSNode == Children[ChildIndex]->GetSCSNode())
{
Result = Children[ChildIndex];
}
else if(bRecursiveSearch)
{
Result = Children[ChildIndex]->FindChild(InSCSNode, true, OutDepth);
}
}
}
if(OutDepth && Result.IsValid())
{
*OutDepth += 1;
}
return Result;
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FindChild(const UActorComponent* InComponentTemplate, bool bRecursiveSearch, uint32* OutDepth) const
{
FSCSEditorTreeNodePtrType Result;
// Ensure that the given component template is valid
if(InComponentTemplate != NULL)
{
// Look for a match in our set of child nodes
for(int32 ChildIndex = 0; ChildIndex < Children.Num() && !Result.IsValid(); ++ChildIndex)
{
if(InComponentTemplate == Children[ChildIndex]->GetComponentTemplate())
{
Result = Children[ChildIndex];
}
else if(bRecursiveSearch)
{
Result = Children[ChildIndex]->FindChild(InComponentTemplate, true, OutDepth);
}
}
}
if(OutDepth && Result.IsValid())
{
*OutDepth += 1;
}
return Result;
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNode::FindChild(const FName& InVariableOrInstanceName, bool bRecursiveSearch, uint32* OutDepth) const
{
FSCSEditorTreeNodePtrType Result;
// Ensure that the given name is valid
if(InVariableOrInstanceName != NAME_None)
{
// Look for a match in our set of child nodes
for(int32 ChildIndex = 0; ChildIndex < Children.Num() && !Result.IsValid(); ++ChildIndex)
{
FName ItemName = Children[ChildIndex]->GetVariableName();
if(ItemName == NAME_None && Children[ChildIndex]->GetNodeType() == ComponentNode)
{
UActorComponent* ComponentTemplateOrInstance = Children[ChildIndex]->GetComponentTemplate();
check(ComponentTemplateOrInstance != nullptr);
ItemName = ComponentTemplateOrInstance->GetFName();
}
if(InVariableOrInstanceName == ItemName)
{
Result = Children[ChildIndex];
}
else if(bRecursiveSearch)
{
Result = Children[ChildIndex]->FindChild(InVariableOrInstanceName, true, OutDepth);
}
}
}
if(OutDepth && Result.IsValid())
{
*OutDepth += 1;
}
return Result;
}
void FSCSEditorTreeNode::RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
// Remove the given node as a child and reset its parent link
Children.Remove(InChildNodePtr);
InChildNodePtr->ParentNodePtr.Reset();
InChildNodePtr->RemoveMeAsChild();
// Reset its actor root link
InChildNodePtr->ActorRootNodePtr.Reset();
if (InChildNodePtr->IsFlaggedForFiltration())
{
RefreshCachedChildFilterState(/*bUpdateParent =*/true);
}
}
void FSCSEditorTreeNode::OnRequestRename(TUniquePtr<FScopedTransaction> InOngoingCreateTransaction)
{
OngoingCreateTransaction = MoveTemp(InOngoingCreateTransaction); // Take responsibility to end the 'create + give initial name' transaction.
RenameRequestedDelegate.ExecuteIfBound();
}
void FSCSEditorTreeNode::OnCompleteRename(const FText& InNewName)
{
// If a 'create + give initial name' transaction exists, end it, the object is expected to have its initial name.
CloseOngoingCreateTransaction();
}
UObject* FSCSEditorTreeNode::GetOrCreateEditableObjectForBlueprint(UBlueprint* InBlueprint) const
{
if (CanEdit())
{
return WeakObjectPtr.Get();
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeComponentBase
FName FSCSEditorTreeNodeComponentBase::GetVariableName() const
{
FName VariableName = NAME_None;
USCS_Node* SCS_Node = GetSCSNode();
UActorComponent* ComponentTemplate = GetComponentTemplate();
if (IsInstancedComponent() && (SCS_Node == nullptr) && (ComponentTemplate != nullptr))
{
if (ComponentTemplate->GetOwner())
{
SCS_Node = FindSCSNodeForInstance(ComponentTemplate, ComponentTemplate->GetOwner()->GetClass());
}
}
if (SCS_Node != NULL)
{
// Use the same variable name as is obtained by the compiler
VariableName = SCS_Node->GetVariableName();
}
else if (ComponentTemplate != NULL)
{
// Try to find the component anchor variable name (first looks for an exact match then scans for any matching variable that points to the archetype in the CDO)
VariableName = FComponentEditorUtils::FindVariableNameGivenComponentInstance(ComponentTemplate);
}
return VariableName;
}
FString FSCSEditorTreeNodeComponentBase::GetDisplayString() const
{
FName VariableName = GetVariableName();
UActorComponent* ComponentTemplate = GetComponentTemplate();
UBlueprint* Blueprint = GetBlueprint();
UClass* VariableOwner = (Blueprint != nullptr) ? Blueprint->SkeletonGeneratedClass : nullptr;
FProperty* VariableProperty = FindFProperty<FProperty>(VariableOwner, VariableName);
bool const bHasValidVarName = (VariableName != NAME_None);
bool const bIsArrayVariable = bHasValidVarName && (VariableOwner != nullptr) &&
VariableProperty && VariableProperty->IsA<FArrayProperty>();
// Only display SCS node variable names in the tree if they have not been autogenerated
if (bHasValidVarName && !bIsArrayVariable)
{
if (IsNativeComponent() && GetDefault<UEditorStyleSettings>()->bShowNativeComponentNames)
{
FStringFormatNamedArguments Args;
Args.Add(TEXT("VarName"), VariableProperty && VariableProperty->IsNative() ? VariableProperty->GetDisplayNameText().ToString() : VariableName.ToString());
Args.Add(TEXT("CompName"), ComponentTemplate->GetName());
return FString::Format(TEXT("{VarName} ({CompName})"), Args);
}
else
{
return VariableName.ToString();
}
}
else if ( ComponentTemplate != nullptr )
{
return ComponentTemplate->GetFName().ToString();
}
else
{
FString UnnamedString = LOCTEXT("UnnamedToolTip", "Unnamed").ToString();
FString NativeString = IsNativeComponent() ? LOCTEXT("NativeToolTip", "Native ").ToString() : TEXT("");
if (ComponentTemplate != NULL)
{
return FString::Printf(TEXT("[%s %s%s]"), *UnnamedString, *NativeString, *ComponentTemplate->GetClass()->GetName());
}
else
{
return FString::Printf(TEXT("[%s %s]"), *UnnamedString, *NativeString);
}
}
}
bool FSCSEditorTreeNodeComponentBase::CanReparent() const
{
if (FChildActorComponentEditorUtils::IsChildActorSubtreeNode(AsShared()))
{
// Cannot reparent nodes within a child actor node subtree.
return false;
}
return !IsInheritedComponent() && !IsDefaultSceneRoot() && IsSceneComponent();
}
UBlueprint* FSCSEditorTreeNodeComponentBase::GetBlueprint() const
{
if (const USCS_Node* SCS_Node = GetSCSNode())
{
if (const USimpleConstructionScript* SCS = SCS_Node->GetSCS())
{
return SCS->GetBlueprint();
}
}
else if (const UActorComponent* ActorComponent = GetObject<UActorComponent>())
{
if (const AActor* Actor = ActorComponent->GetOwner())
{
return UBlueprint::GetBlueprintFromClass(Actor->GetClass());
}
}
return nullptr;
}
FSCSEditorChildActorNodePtrType FSCSEditorTreeNodeComponentBase::GetChildActorNode()
{
if (!ChildActorNodePtr.IsValid() || ChildActorNodePtr->GetChildActorComponent() != GetObject<UActorComponent>())
{
if (const UChildActorComponent* ChildActorComponent = GetObject<UChildActorComponent>())
{
AActor* ChildActor = IsInstanced() ? ChildActorComponent->GetChildActor() : ChildActorComponent->GetChildActorTemplate();
ChildActorNodePtr = MakeShareable(new FSCSEditorTreeNodeChildActor(ChildActor));
check(ChildActorNodePtr.IsValid());
ChildActorNodePtr->SetOwnerNode(this->AsShared());
}
}
return ChildActorNodePtr;
}
bool FSCSEditorTreeNodeComponentBase::MatchesFilterType(const UClass* InFilterType) const
{
check(InFilterType);
if (const UActorComponent* ComponentObject = GetObject<UActorComponent>())
{
const UClass* ComponentClass = ComponentObject->GetClass();
check(ComponentClass);
if (ComponentClass->IsChildOf(InFilterType))
{
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeInstancedInheritedComponent
FSCSEditorTreeNodeInstancedInheritedComponent::FSCSEditorTreeNodeInstancedInheritedComponent(AActor* Owner, UActorComponent* ComponentInstance)
{
check(ComponentInstance != nullptr);
InstancedComponentOwnerPtr = Owner;
SetObject(ComponentInstance);
}
bool FSCSEditorTreeNodeInstancedInheritedComponent::IsNativeComponent() const
{
if (UActorComponent* Template = GetComponentTemplate())
{
return Template->CreationMethod == EComponentCreationMethod::Native;
}
else
{
return false;
}
}
bool FSCSEditorTreeNodeInstancedInheritedComponent::IsRootComponent() const
{
UActorComponent* Template = GetComponentTemplate();
if (AActor* OwnerActor = InstancedComponentOwnerPtr.Get())
{
if (OwnerActor->GetRootComponent() == Template)
{
return true;
}
}
return false;
}
bool FSCSEditorTreeNodeInstancedInheritedComponent::IsInheritedSCSNode() const
{
return false;
}
bool FSCSEditorTreeNodeInstancedInheritedComponent::IsDefaultSceneRoot() const
{
return false;
}
bool FSCSEditorTreeNodeInstancedInheritedComponent::CanEdit() const
{
UActorComponent* Template = GetComponentTemplate();
return (Template ? Template->IsEditableWhenInherited() : false);
}
FText FSCSEditorTreeNodeInstancedInheritedComponent::GetDisplayName() const
{
FName VariableName = GetVariableName();
if (VariableName != NAME_None)
{
return FText::FromName(VariableName);
}
return FText::GetEmpty();
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeInstanceAddedComponent
FSCSEditorTreeNodeInstanceAddedComponent::FSCSEditorTreeNodeInstanceAddedComponent(AActor* Owner, UActorComponent* InComponentTemplate)
{
check(InComponentTemplate);
InstancedComponentName = InComponentTemplate->GetFName();
InstancedComponentOwnerPtr = Owner;
SetObject(InComponentTemplate);
}
bool FSCSEditorTreeNodeInstanceAddedComponent::IsRootComponent() const
{
bool bIsRoot = true;
UActorComponent* Template = GetComponentTemplate();
if (Template != NULL)
{
AActor* CDO = Template->GetOwner();
if (CDO != NULL)
{
// Evaluate to TRUE if we have a valid component reference that matches the native root component
bIsRoot = (Template == CDO->GetRootComponent());
}
}
return bIsRoot;
}
bool FSCSEditorTreeNodeInstanceAddedComponent::IsDefaultSceneRoot() const
{
if (USceneComponent* SceneComponent = Cast<USceneComponent>(GetComponentTemplate()))
{
return SceneComponent->GetFName() == USceneComponent::GetDefaultSceneRootVariableName();
}
return false;
}
FString FSCSEditorTreeNodeInstanceAddedComponent::GetDisplayString() const
{
return InstancedComponentName.ToString();
}
FText FSCSEditorTreeNodeInstanceAddedComponent::GetDisplayName() const
{
return FText::FromName(InstancedComponentName);
}
void FSCSEditorTreeNodeInstanceAddedComponent::RemoveMeAsChild()
{
USceneComponent* ChildInstance = Cast<USceneComponent>(GetComponentTemplate());
if (ensure(ChildInstance))
{
// Handle detachment at the instance level
ChildInstance->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
}
}
void FSCSEditorTreeNodeInstanceAddedComponent::OnCompleteRename(const FText& InNewName)
{
// If the 'rename' was part of an ongoing component creation, ensure the transaction is ended when the local object goes out of scope. (Must complete after the rename transaction below)
TUniquePtr<FScopedTransaction> ScopedCreateTransaction(MoveTemp(OngoingCreateTransaction));
// If a 'create' transaction is opened, the rename will be folded into it and will be invisible to the 'undo' as create + give a name is really just one operation from the user point of view.
FScopedTransaction TransactionContext(LOCTEXT("RenameComponentVariable", "Rename Component Variable"));
UActorComponent* ComponentInstance = GetComponentTemplate();
if(ComponentInstance == nullptr)
{
return;
}
ERenameFlags RenameFlags = REN_DontCreateRedirectors;
// name collision could occur due to e.g. our archetype being updated and causing a conflict with our ComponentInstance:
FString NewNameAsString = InNewName.ToString();
if(StaticFindObject(UObject::StaticClass(), ComponentInstance->GetOuter(), *NewNameAsString) == nullptr)
{
ComponentInstance->Rename(*NewNameAsString, nullptr, RenameFlags);
InstancedComponentName = *NewNameAsString;
}
else
{
UObject* Collision = StaticFindObject(UObject::StaticClass(), ComponentInstance->GetOuter(), *NewNameAsString);
if(Collision != ComponentInstance)
{
// use whatever name the ComponentInstance currently has:
InstancedComponentName = ComponentInstance->GetFName();
}
}
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeComponent
FSCSEditorTreeNodeComponent::FSCSEditorTreeNodeComponent(USCS_Node* InSCSNode, bool bInIsInheritedSCS)
: bIsInheritedSCS(bInIsInheritedSCS)
, SCSNodePtr(InSCSNode)
{
SetObject(( InSCSNode != nullptr ) ? InSCSNode->ComponentTemplate : nullptr);
}
FSCSEditorTreeNodeComponent::FSCSEditorTreeNodeComponent(UActorComponent* InComponentTemplate)
: bIsInheritedSCS(false)
, SCSNodePtr(nullptr)
{
check(InComponentTemplate != nullptr);
SetObject(InComponentTemplate);
AActor* Owner = InComponentTemplate->GetOwner();
if (Owner != nullptr)
{
ensureMsgf(Owner->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject), TEXT("Use a different node class for instanced components"));
}
}
bool FSCSEditorTreeNodeComponent::IsNativeComponent() const
{
return GetSCSNode() == NULL && GetComponentTemplate() != NULL;
}
bool FSCSEditorTreeNodeComponent::IsRootComponent() const
{
bool bIsRoot = true;
USCS_Node* SCS_Node = GetSCSNode();
UActorComponent* ComponentTemplate = GetComponentTemplate();
if (SCS_Node != NULL)
{
USimpleConstructionScript* SCS = SCS_Node->GetSCS();
if (SCS != NULL)
{
// Evaluate to TRUE if we have an SCS node reference, it is contained in the SCS root set and does not have an external parent
bIsRoot = SCS->GetRootNodes().Contains(SCS_Node) && SCS_Node->ParentComponentOrVariableName == NAME_None;
}
}
else if (ComponentTemplate != NULL)
{
AActor* CDO = ComponentTemplate->GetOwner();
if (CDO != NULL)
{
// Evaluate to TRUE if we have a valid component reference that matches the native root component
bIsRoot = (ComponentTemplate == CDO->GetRootComponent());
}
}
return bIsRoot;
}
bool FSCSEditorTreeNodeComponent::IsInheritedSCSNode() const
{
return bIsInheritedSCS;
}
bool FSCSEditorTreeNodeComponent::IsDefaultSceneRoot() const
{
if (USCS_Node* SCS_Node = GetSCSNode())
{
USimpleConstructionScript* SCS = SCS_Node->GetSCS();
if (SCS != nullptr)
{
return SCS_Node == SCS->GetDefaultSceneRootNode();
}
}
return false;
}
bool FSCSEditorTreeNodeComponent::CanEdit() const
{
bool bCanEdit = false;
if (!IsNativeComponent())
{
USCS_Node* SCS_Node = GetSCSNode();
bCanEdit = (SCS_Node != nullptr);
}
else if (UActorComponent* ComponentTemplate = GetComponentTemplate())
{
bCanEdit = (FComponentEditorUtils::GetPropertyForEditableNativeComponent(ComponentTemplate) != nullptr);
}
return bCanEdit;
}
FText FSCSEditorTreeNodeComponent::GetDisplayName() const
{
FName VariableName = GetVariableName();
if (VariableName != NAME_None)
{
return FText::FromName(VariableName);
}
return FText::GetEmpty();
}
class USCS_Node* FSCSEditorTreeNodeComponent::GetSCSNode() const
{
return SCSNodePtr.Get();
}
UObject* FSCSEditorTreeNodeComponent::GetOrCreateEditableObjectForBlueprint(UBlueprint* InBlueprint) const
{
if (CanEdit() && !IsNativeComponent() && IsInheritedSCSNode())
{
return INTERNAL_GetOverridenComponentTemplate(InBlueprint);
}
return FSCSEditorTreeNode::GetOrCreateEditableObjectForBlueprint(InBlueprint);
}
UActorComponent* FSCSEditorTreeNode::FindComponentInstanceInActor(const AActor* InActor) const
{
USCS_Node* SCS_Node = GetSCSNode();
UActorComponent* ComponentTemplate = GetComponentTemplate();
UActorComponent* ComponentInstance = NULL;
if (InActor != NULL)
{
if (SCS_Node != NULL)
{
FName VariableName = SCS_Node->GetVariableName();
if (VariableName != NAME_None)
{
UWorld* World = InActor->GetWorld();
FObjectPropertyBase* Property = FindFProperty<FObjectPropertyBase>(InActor->GetClass(), VariableName);
if (Property != NULL)
{
// Return the component instance that's stored in the property with the given variable name
ComponentInstance = Cast<UActorComponent>(Property->GetObjectPropertyValue_InContainer(InActor));
}
else if (World != nullptr && World->WorldType == EWorldType::EditorPreview)
{
// If this is the preview actor, return the cached component instance that's being used for the preview actor prior to recompiling the Blueprint
ComponentInstance = SCS_Node->EditorComponentInstance.Get();
}
}
}
else if (ComponentTemplate != NULL)
{
TInlineComponentArray<UActorComponent*> Components;
InActor->GetComponents(Components);
ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components);
}
}
return ComponentInstance;
}
void FSCSEditorTreeNodeComponent::OnCompleteRename(const FText& InNewName)
{
// If the 'rename' was part of the creation process, we need to complete the creation transaction as the component has a user confirmed name. (Must complete after rename transaction below)
TUniquePtr<FScopedTransaction> ScopedOngoingCreateTransaction(MoveTemp(OngoingCreateTransaction));
// If a 'create' transaction is opened, the rename will be folded into it and will be invisible to the 'undo' as 'create + give initial name' means creating an object from the user point of view.
FScopedTransaction RenameTransaction(LOCTEXT("RenameComponentVariable", "Rename Component Variable"));
FBlueprintEditorUtils::RenameComponentMemberVariable(GetBlueprint(), GetSCSNode(), FName(*InNewName.ToString()));
}
void FSCSEditorTreeNodeComponent::RemoveMeAsChild()
{
// Bypass removal logic if we're part of a child actor subtree
if (FChildActorComponentEditorUtils::IsChildActorSubtreeNode(AsShared()))
{
return;
}
// Remove the SCS node from the SCS tree, if present
if (USCS_Node* SCS_ChildNode = GetSCSNode())
{
USimpleConstructionScript* SCS = SCS_ChildNode->GetSCS();
if (SCS != NULL)
{
SCS->RemoveNode(SCS_ChildNode);
}
}
}
UActorComponent* FSCSEditorTreeNodeComponent::INTERNAL_GetOverridenComponentTemplate(UBlueprint* Blueprint) const
{
UActorComponent* OverriddenComponent = nullptr;
FComponentKey Key(GetSCSNode());
const bool bBlueprintCanOverrideComponentFromKey = Key.IsValid()
&& Blueprint
&& Blueprint->ParentClass
&& Blueprint->ParentClass->IsChildOf(Key.GetComponentOwner());
if (bBlueprintCanOverrideComponentFromKey)
{
const bool bCreateIfNecessary = true;
UInheritableComponentHandler* InheritableComponentHandler = Blueprint->GetInheritableComponentHandler(bCreateIfNecessary);
if (InheritableComponentHandler)
{
OverriddenComponent = InheritableComponentHandler->GetOverridenComponentTemplate(Key);
if (!OverriddenComponent && bCreateIfNecessary)
{
OverriddenComponent = InheritableComponentHandler->CreateOverridenComponentTemplate(Key);
}
}
}
return OverriddenComponent;
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeActorBase
FSCSEditorTreeNodePtrType FSCSEditorTreeNodeActorBase::GetOwnerNode() const
{
return OwnerNodePtr;
}
void FSCSEditorTreeNodeActorBase::SetOwnerNode(FSCSEditorTreeNodePtrType NewOwnerNode)
{
OwnerNodePtr = NewOwnerNode;
}
FSCSEditorTreeNodePtrType FSCSEditorTreeNodeActorBase::GetSceneRootNode() const
{
return SceneRootNodePtr;
}
void FSCSEditorTreeNodeActorBase::SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode)
{
if (SceneRootNodePtr.IsValid())
{
ComponentNodes.Remove(SceneRootNodePtr);
}
SceneRootNodePtr = NewSceneRootNode;
if (!ComponentNodes.Contains(SceneRootNodePtr))
{
ComponentNodes.Add(SceneRootNodePtr);
}
}
const TArray<FSCSEditorTreeNodePtrType>& FSCSEditorTreeNodeActorBase::GetComponentNodes() const
{
return ComponentNodes;
}
FName FSCSEditorTreeNodeActorBase::GetNodeID() const
{
if (const AActor* Actor = GetObject<AActor>())
{
return Actor->GetFName();
}
return NAME_None;
}
bool FSCSEditorTreeNodeActorBase::IsInstanced() const
{
if (const AActor* Actor = GetObject<AActor>())
{
return !Actor->IsTemplate();
}
return false;
}
void FSCSEditorTreeNodeActorBase::AddChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
if (InChildNodePtr.IsValid() && InChildNodePtr->IsComponentNode())
{
ComponentNodes.Add(InChildNodePtr);
if (!SceneRootNodePtr.IsValid() && InChildNodePtr->IsSceneComponent())
{
SetSceneRootNode(InChildNodePtr);
}
}
Super::AddChild(InChildNodePtr);
}
void FSCSEditorTreeNodeActorBase::RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
if (InChildNodePtr.IsValid() && InChildNodePtr->IsComponentNode())
{
ComponentNodes.Remove(InChildNodePtr);
if (InChildNodePtr == SceneRootNodePtr)
{
SceneRootNodePtr.Reset();
}
}
Super::RemoveChild(InChildNodePtr);
}
UBlueprint* FSCSEditorTreeNodeActorBase::GetBlueprint() const
{
if (const AActor* Actor = GetObject<AActor>())
{
return UBlueprint::GetBlueprintFromClass(Actor->GetClass());
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeRootActor
void FSCSEditorTreeNodeRootActor::OnCompleteRename(const FText& InNewName)
{
if (AActor* Actor = GetMutableObject<AActor>())
{
if (Actor->IsActorLabelEditable() && !InNewName.ToString().Equals(Actor->GetActorLabel(), ESearchCase::CaseSensitive))
{
const FScopedTransaction Transaction(LOCTEXT("SCSEditorRenameActorTransaction", "Rename Actor"));
FActorLabelUtilities::RenameExistingActor(Actor, InNewName.ToString());
}
}
// Not expected to reach here with an ongoing create transaction, but if it does, end it.
CloseOngoingCreateTransaction();
}
void FSCSEditorTreeNodeRootActor::AddChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
if (InChildNodePtr.IsValid() && InChildNodePtr->IsComponentNode())
{
TSharedPtr<FSCSEditorTreeNodeSeparator> NewSeparatorNodePtr;
const bool bIsSceneComponentNode = InChildNodePtr->IsSceneComponent();
// Make sure separators are shown
if (bIsSceneComponentNode && !SceneComponentSeparatorNodePtr.IsValid())
{
NewSeparatorNodePtr = MakeShareable(new FSCSEditorTreeNodeSeparator());
SceneComponentSeparatorNodePtr = NewSeparatorNodePtr;
}
else if (!bIsSceneComponentNode && !NonSceneComponentSeparatorNodePtr.IsValid())
{
NewSeparatorNodePtr = MakeShareable(new FSCSEditorTreeNodeSeparator());
NewSeparatorNodePtr->AddFilteredComponentType(USceneComponent::StaticClass());
NonSceneComponentSeparatorNodePtr = NewSeparatorNodePtr;
}
if (NewSeparatorNodePtr.IsValid())
{
FSCSEditorTreeNodeActorBase::AddChild(NewSeparatorNodePtr);
NewSeparatorNodePtr->RefreshFilteredState(CachedFilterType, CachedFilterTerms, false);
}
}
FSCSEditorTreeNodeActorBase::AddChild(InChildNodePtr);
}
void FSCSEditorTreeNodeRootActor::RemoveChild(FSCSEditorTreeNodePtrType InChildNodePtr)
{
const TArray<FSCSEditorTreeNodePtrType>& ComponentNodePtrs = GetComponentNodes();
const int32 IndexOfFirstSceneComponent = ComponentNodePtrs.IndexOfByPredicate([](const FSCSEditorTreeNodePtrType& NodePtr)
{
return NodePtr->IsSceneComponent();
});
{
TSharedPtr<FSCSEditorTreeNodeSeparator> SceneComponentSeparatorNodeSharedPtr = SceneComponentSeparatorNodePtr.Pin();
if (IndexOfFirstSceneComponent == -1 && SceneComponentSeparatorNodeSharedPtr.IsValid())
{
FSCSEditorTreeNodeActorBase::RemoveChild(SceneComponentSeparatorNodeSharedPtr);
}
}
const int32 IndexOfFirstNonSceneComponent = ComponentNodePtrs.IndexOfByPredicate([](const FSCSEditorTreeNodePtrType& NodePtr)
{
return !NodePtr->IsSceneComponent();
});
{
TSharedPtr<FSCSEditorTreeNodeSeparator> NonSceneComponentSeparatorNodeSharedPtr = NonSceneComponentSeparatorNodePtr.Pin();
if (IndexOfFirstNonSceneComponent == -1 && NonSceneComponentSeparatorNodeSharedPtr.IsValid())
{
FSCSEditorTreeNodeActorBase::RemoveChild(NonSceneComponentSeparatorNodeSharedPtr);
}
}
FSCSEditorTreeNodeActorBase::RemoveChild(InChildNodePtr);
}
bool FSCSEditorTreeNodeRootActor::RefreshFilteredState(const UClass* InFilterType, const TArray<FString>& InFilterTerms, bool bRecursive)
{
CachedFilterType = InFilterType;
CachedFilterTerms = InFilterTerms;
return FSCSEditorTreeNodeActorBase::RefreshFilteredState(InFilterType, InFilterTerms, bRecursive);
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeChildActor
bool FSCSEditorTreeNodeChildActor::IsFlaggedForFiltration() const
{
// Not filtered out if any children are flagged as such
for (const FSCSEditorTreeNodePtrType& Child : GetChildren())
{
if (!Child->IsFlaggedForFiltration())
{
return false;
}
}
FSCSEditorTreeNodePtrType OwnerNode = GetOwnerNode();
if (OwnerNode.IsValid())
{
// Mirror the owning node's filtered state
return OwnerNode->IsFlaggedForFiltration();
}
return false;
}
UChildActorComponent* FSCSEditorTreeNodeChildActor::GetChildActorComponent() const
{
FSCSEditorTreeNodePtrType OwnerNode = GetOwnerNode();
if (OwnerNode.IsValid())
{
return Cast<UChildActorComponent>(OwnerNode->GetComponentTemplate());
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorTreeNodeSeparator
bool FSCSEditorTreeNodeSeparator::MatchesFilterType(const UClass* InFilterType) const
{
check(InFilterType);
for (const UClass* FilteredType : FilteredTypes)
{
// If types match here, it means we should filter out the separator, so return false.
if (InFilterType->IsChildOf(FilteredType))
{
return false;
}
}
return true;
}
void FSCSEditorTreeNodeSeparator::AddFilteredComponentType(const TSubclassOf<UActorComponent>& InFilterType)
{
if (const UClass* FilterType = InFilterType.Get())
{
FilteredTypes.Add(FilterType);
}
}
//////////////////////////////////////////////////////////////////////////
// SSCS_RowWidget
void SSCS_RowWidget::Construct( const FArguments& InArgs, TSharedPtr<SSCSEditor> InSCSEditor, FSCSEditorTreeNodePtrType InNodePtr, TSharedPtr<STableViewBase> InOwnerTableView )
{
check(InNodePtr.IsValid());
SCSEditor = InSCSEditor;
TreeNodePtr = InNodePtr;
bool bIsSeparator = InNodePtr->GetNodeType() == FSCSEditorTreeNode::SeparatorNode;
FSuperRowType::FArguments Args = FSuperRowType::FArguments()
.Style(bIsSeparator ?
&FAppStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.NoHoverTableRow") :
&FAppStyle::Get().GetWidgetStyle<FTableRowStyle>("SceneOutliner.TableViewRow")) //@todo create editor style for the SCS tree
.Padding(FMargin(0.f, 4.f, 0.f, 4.f))
.ShowSelection(!bIsSeparator)
.OnDragDetected(this, &SSCS_RowWidget::HandleOnDragDetected)
.OnDragEnter(this, &SSCS_RowWidget::HandleOnDragEnter)
.OnDragLeave(this, &SSCS_RowWidget::HandleOnDragLeave)
.OnCanAcceptDrop(this, &SSCS_RowWidget::HandleOnCanAcceptDrop)
.OnAcceptDrop(this, &SSCS_RowWidget::HandleOnAcceptDrop);
SMultiColumnTableRow<FSCSEditorTreeNodePtrType>::Construct( Args, InOwnerTableView.ToSharedRef() );
}
SSCS_RowWidget::~SSCS_RowWidget()
{
TSharedPtr<SSCSEditor> Editor = SCSEditor.Pin();
if(Editor.IsValid())
{
// Ensure to end any owned ongoing transaction if the node is still in 'editing initial name' mode when deleted.
GetNode()->CloseOngoingCreateTransaction();
// Ask SCSEditor if Node is still active, if it isn't it might have been collected so we can't do anything to it
USCS_Node* SCS_Node = GetNode()->GetSCSNode();
if(SCS_Node != NULL && Editor->IsNodeInSimpleConstructionScript(SCS_Node))
{
// Clear the delegate when widget goes away
SCS_Node->SetOnNameChanged(FSCSNodeNameChanged());
}
}
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SWidget> SSCS_RowWidget::GenerateWidgetForColumn( const FName& ColumnName )
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if(ColumnName == SCS_ColumnName_ComponentClass)
{
InlineWidget =
SNew(SInlineEditableTextBlock)
.Text(this, &SSCS_RowWidget::GetNameLabel)
.OnVerifyTextChanged( this, &SSCS_RowWidget::OnNameTextVerifyChanged )
.OnTextCommitted( this, &SSCS_RowWidget::OnNameTextCommit )
.IsSelected( this, &SSCS_RowWidget::IsSelectedExclusively )
.IsReadOnly(!NodePtr->CanRename() || (SCSEditor.IsValid() && !SCSEditor.Pin()->IsEditingAllowed()) || FChildActorComponentEditorUtils::IsChildActorSubtreeNode(NodePtr));
NodePtr->SetRenameRequestedDelegate(FSCSEditorTreeNode::FOnRenameRequested::CreateSP(InlineWidget.Get(), &SInlineEditableTextBlock::EnterEditingMode));
TSharedRef<SToolTip> Tooltip = CreateToolTipWidget();
return SNew(SHorizontalBox)
.ToolTip(Tooltip)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SExpanderArrow, SharedThis(this))
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(GetIconBrush())
.ColorAndOpacity(this, &SSCS_RowWidget::GetColorTintForIcon)
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(4.0f, 0.0f)
[
InlineWidget.ToSharedRef()
];
}
else if(ColumnName == SCS_ColumnName_Asset)
{
return
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Visibility(this, &SSCS_RowWidget::GetAssetVisibility)
.Text(this, &SSCS_RowWidget::GetAssetName)
.ToolTipText(this, &SSCS_RowWidget::GetAssetPath)
];
}
else if (ColumnName == SCS_ColumnName_Mobility)
{
if (NodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode)
{
TSharedPtr<SToolTip> MobilityTooltip = SNew(SToolTip)
.Text(this, &SSCS_RowWidget::GetMobilityToolTipText);
return SNew(SHorizontalBox)
.ToolTip(MobilityTooltip)
.Visibility(EVisibility::Visible) // so we still get tooltip text for an empty SHorizontalBox
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.FillWidth(1.0f)
[
SNew(SImage)
.Image(this, &SSCS_RowWidget::GetMobilityIconImage)
.ToolTip(MobilityTooltip)
];
}
else
{
return SNew(SSpacer);
}
}
else
{
return SNew(STextBlock)
.Text( LOCTEXT("UnknownColumn", "Unknown Column") );
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSCS_RowWidget::AddToToolTipInfoBox(const TSharedRef<SVerticalBox>& InfoBox, const FText& Key, TSharedRef<SWidget> ValueIcon, const TAttribute<FText>& Value, bool bImportant)
{
InfoBox->AddSlot()
.AutoHeight()
.Padding(0.0f, 1.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), bImportant ? "SCSEditor.ComponentTooltip.ImportantLabel" : "SCSEditor.ComponentTooltip.Label")
.Text(FText::Format(LOCTEXT("AssetViewTooltipFormat", "{0}:"), Key))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
ValueIcon
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), bImportant ? "SCSEditor.ComponentTooltip.ImportantValue" : "SCSEditor.ComponentTooltip.Value")
.Text(Value)
]
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SToolTip> SSCS_RowWidget::CreateToolTipWidget() const
{
// Create a box to hold every line of info in the body of the tooltip
TSharedRef<SVerticalBox> InfoBox = SNew(SVerticalBox);
//
if (FSCSEditorTreeNode* TreeNode = GetNode().Get())
{
if (TreeNode->IsComponentNode())
{
// Add the tooltip
if (UActorComponent* Template = TreeNode->GetComponentTemplate())
{
UClass* TemplateClass = Template->GetClass();
FText ClassTooltip = TemplateClass->GetToolTipText(/*bShortTooltip=*/ true);
InfoBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(FMargin(0.0f, 2.0f, 0.0f, 4.0f))
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "SCSEditor.ComponentTooltip.ClassDescription")
.Text(ClassTooltip)
.WrapTextAt(400.0f)
];
}
// Add introduction point
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipAddType", "Source"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget::GetComponentAddSourceToolTipText)), false);
if (TreeNode->IsInheritedComponent())
{
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipIntroducedIn", "Introduced in"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget::GetIntroducedInToolTipText)), false);
}
// Add Underlying Component Name for Native Components
if (TreeNode->IsNativeComponent())
{
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipNativeComponentName", "Native Component Name"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget::GetNativeComponentNameToolTipText)), false);
}
// Add mobility
TSharedRef<SImage> MobilityIcon = SNew(SImage).Image(this, &SSCS_RowWidget::GetMobilityIconImage);
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipMobility", "Mobility"), MobilityIcon, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget::GetMobilityToolTipText)), false);
// Add asset if applicable to this node
if (GetAssetVisibility() == EVisibility::Visible)
{
InfoBox->AddSlot()[SNew(SSpacer).Size(FVector2D(1.0f, 8.0f))];
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipAsset", "Asset"), SNullWidget::NullWidget, TAttribute<FText>(this, &SSCS_RowWidget::GetAssetName), false);
}
// If the component is marked as editor only, then display that info here
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipEditorOnly", "Editor Only"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget::GetComponentEditorOnlyTooltipText)), false);
}
}
TSharedRef<SBorder> TooltipContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.Padding(0.0f)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(2.0f)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "SCSEditor.ComponentTooltip.Title")
.Text(this, &SSCS_RowWidget::GetTooltipText)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.Padding(2.0f)
[
InfoBox
]
]
];
return IDocumentation::Get()->CreateToolTip(TAttribute<FText>(this, &SSCS_RowWidget::GetTooltipText), TooltipContent, InfoBox, GetDocumentationLink(), GetDocumentationExcerptName());
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
FSlateBrush const* SSCS_RowWidget::GetMobilityIconImage() const
{
if (FSCSEditorTreeNode* TreeNode = GetNode().Get())
{
if (USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(TreeNode->GetComponentTemplate()))
{
if (SceneComponentTemplate->Mobility == EComponentMobility::Movable)
{
return FAppStyle::GetBrush(TEXT("ClassIcon.MovableMobilityIcon"));
}
else if (SceneComponentTemplate->Mobility == EComponentMobility::Stationary)
{
return FAppStyle::GetBrush(TEXT("ClassIcon.StationaryMobilityIcon"));
}
// static components don't get an icon (because static is the most common
// mobility type, and we'd like to keep the icon clutter to a minimum)
}
}
return nullptr;
}
FText SSCS_RowWidget::GetMobilityToolTipText() const
{
FText MobilityToolTip = LOCTEXT("ErrorNoMobilityTooltip", "Invalid component");
if (FSCSEditorTreeNode* TreeNode = TreeNodePtr.Get())
{
if (USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(TreeNode->GetComponentTemplate()))
{
if (SceneComponentTemplate->Mobility == EComponentMobility::Movable)
{
MobilityToolTip = LOCTEXT("MovableMobilityTooltip", "Movable");
}
else if (SceneComponentTemplate->Mobility == EComponentMobility::Stationary)
{
MobilityToolTip = LOCTEXT("StationaryMobilityTooltip", "Stationary");
}
else if (SceneComponentTemplate->Mobility == EComponentMobility::Static)
{
MobilityToolTip = LOCTEXT("StaticMobilityTooltip", "Static");
}
else
{
// make sure we're the mobility type we're expecting (we've handled Movable & Stationary)
ensureMsgf(false, TEXT("Unhandled mobility type [%d], is this a new type that we don't handle here?"), SceneComponentTemplate->Mobility.GetValue());
MobilityToolTip = LOCTEXT("UnknownMobilityTooltip", "Component with unknown mobility");
}
}
else
{
MobilityToolTip = LOCTEXT("NoMobilityTooltip", "Non-scene component");
}
}
return MobilityToolTip;
}
FText SSCS_RowWidget::GetComponentAddSourceToolTipText() const
{
FText NodeType;
if (FSCSEditorTreeNode* TreeNode = TreeNodePtr.Get())
{
if (TreeNode->IsInheritedComponent())
{
if (TreeNode->IsNativeComponent())
{
NodeType = LOCTEXT("InheritedNativeComponent", "Inherited (C++)");
}
else
{
NodeType = LOCTEXT("InheritedBlueprintComponent", "Inherited (Blueprint)");
}
}
else
{
if (TreeNode->IsInstancedComponent())
{
NodeType = LOCTEXT("ThisInstanceAddedComponent", "This actor instance");
}
else
{
NodeType = LOCTEXT("ThisBlueprintAddedComponent", "This Blueprint");
}
}
}
return NodeType;
}
FText SSCS_RowWidget::GetNativeComponentNameToolTipText() const
{
const FSCSEditorTreeNode* TreeNode = TreeNodePtr.Get();
const UActorComponent* Template = TreeNode ? TreeNode->GetComponentTemplate() : nullptr;
if (Template)
{
return FText::FromName(Template->GetFName());
}
else
{
return FText::GetEmpty();
}
}
FText SSCS_RowWidget::GetComponentEditorOnlyTooltipText() const
{
FText ComponentType = LOCTEXT("ComponentEditorOnlyFalse", "False");
if (FSCSEditorTreeNode* TreeNode = GetNode().Get())
{
if (TreeNode->IsComponentNode())
{
if (const UActorComponent* Template = TreeNode->GetComponentTemplate())
{
UBlueprint* Blueprint = GetBlueprint();
FObjectProperty* Prop = Blueprint ? FindFProperty<FObjectProperty>(Blueprint->SkeletonGeneratedClass, TreeNode->GetVariableName()) : nullptr;
if(Template->bIsEditorOnly || (Prop && Prop->HasAnyPropertyFlags(CPF_EditorOnly)))
{
ComponentType = LOCTEXT("ComponentEditorOnlyTrue", "True");
}
}
}
}
return ComponentType;
}
FText SSCS_RowWidget::GetIntroducedInToolTipText() const
{
FText IntroducedInTooltip = LOCTEXT("IntroducedInThisBPTooltip", "this class");
if (FSCSEditorTreeNode* TreeNode = TreeNodePtr.Get())
{
if (TreeNode->IsInheritedComponent())
{
if (UActorComponent* ComponentTemplate = TreeNode->GetComponentTemplate())
{
UClass* BestClass = nullptr;
AActor* OwningActor = ComponentTemplate->GetOwner();
if (TreeNode->IsNativeComponent() && (OwningActor != nullptr))
{
for (UClass* TestClass = OwningActor->GetClass(); TestClass != AActor::StaticClass(); TestClass = TestClass->GetSuperClass())
{
if (TreeNode->FindComponentInstanceInActor(Cast<AActor>(TestClass->GetDefaultObject())))
{
BestClass = TestClass;
}
else
{
break;
}
}
}
else if (!TreeNode->IsNativeComponent())
{
USCS_Node* SCSNode = TreeNode->GetSCSNode();
if ((SCSNode == nullptr) && (OwningActor != nullptr))
{
SCSNode = FSCSEditorTreeNode::FindSCSNodeForInstance(ComponentTemplate, OwningActor->GetClass());
}
if (SCSNode != nullptr)
{
if (UBlueprint* OwningBP = SCSNode->GetSCS()->GetBlueprint())
{
BestClass = OwningBP->GeneratedClass;
}
}
else if (OwningActor != nullptr)
{
if (UBlueprint* OwningBP = UBlueprint::GetBlueprintFromClass(OwningActor->GetClass()))
{
BestClass = OwningBP->GeneratedClass;
}
}
}
if (BestClass == nullptr)
{
if (ComponentTemplate->IsCreatedByConstructionScript())
{
IntroducedInTooltip = LOCTEXT("IntroducedInUnknownError", "Unknown Blueprint Class (via an Add Component call)");
}
else
{
IntroducedInTooltip = LOCTEXT("IntroducedInNativeError", "Unknown native source (via C++ code)");
}
}
else if (TreeNode->IsInstancedComponent() && ComponentTemplate->CreationMethod == EComponentCreationMethod::Native && !ComponentTemplate->HasAnyFlags(RF_DefaultSubObject))
{
IntroducedInTooltip = FText::Format(LOCTEXT("IntroducedInCPPErrorFmt", "{0} (via C++ code)"), FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass));
}
else if (TreeNode->IsInstancedComponent() && ComponentTemplate->CreationMethod == EComponentCreationMethod::UserConstructionScript)
{
IntroducedInTooltip = FText::Format(LOCTEXT("IntroducedInUCSErrorFmt", "{0} (via an Add Component call)"), FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass));
}
else
{
IntroducedInTooltip = FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass);
}
}
else
{
IntroducedInTooltip = LOCTEXT("IntroducedInNoTemplateError", "[no component template found]");
}
}
else if (TreeNode->IsInstancedComponent())
{
IntroducedInTooltip = LOCTEXT("IntroducedInThisActorInstanceTooltip", "this actor instance");
}
}
return IntroducedInTooltip;
}
FText SSCS_RowWidget::GetAssetName() const
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
FText AssetName = LOCTEXT("None", "None");
if(NodePtr.IsValid() && NodePtr->GetComponentTemplate())
{
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(NodePtr->GetComponentTemplate());
if(Asset != NULL)
{
AssetName = FText::FromString(Asset->GetName());
}
}
return AssetName;
}
FText SSCS_RowWidget::GetAssetPath() const
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
FText AssetName = LOCTEXT("None", "None");
if(NodePtr.IsValid() && NodePtr->GetComponentTemplate())
{
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(NodePtr->GetComponentTemplate());
if(Asset != NULL)
{
AssetName = FText::FromString(Asset->GetPathName());
}
}
return AssetName;
}
EVisibility SSCS_RowWidget::GetAssetVisibility() const
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if(NodePtr.IsValid() && NodePtr->GetComponentTemplate() && FComponentAssetBrokerage::SupportsAssets(NodePtr->GetComponentTemplate()))
{
return EVisibility::Visible;
}
else
{
return EVisibility::Hidden;
}
}
const FSlateBrush* SSCS_RowWidget::GetIconBrush() const
{
const FSlateBrush* ComponentIcon = FAppStyle::GetBrush("SCS.NativeComponent");
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if (NodePtr.IsValid())
{
if (UActorComponent* ComponentTemplate = NodePtr->GetComponentTemplate())
{
ComponentIcon = FSlateIconFinder::FindIconBrushForClass(ComponentTemplate->GetClass(), TEXT("SCS.Component"));
}
}
return ComponentIcon;
}
FSlateColor SSCS_RowWidget::GetColorTintForIcon() const
{
return GetColorTintForIcon(GetNode());
}
FSlateColor SSCS_RowWidget::GetColorTintForIcon(FSCSEditorTreeNodePtrType InNode)
{
const FLinearColor InheritedBlueprintComponentColor(0.08f, 0.35f, 0.6f);
const FLinearColor InstancedInheritedBlueprintComponentColor(0.08f, 0.35f, 0.6f);
const FLinearColor InheritedNativeComponentColor(0.7f, 0.9f, 0.7f);
const FLinearColor IntroducedHereColor(FLinearColor::White);
if (InNode->IsInheritedComponent())
{
if (InNode->IsNativeComponent())
{
return InheritedNativeComponentColor;
}
else if (InNode->IsInstancedComponent())
{
return InstancedInheritedBlueprintComponentColor;
}
else
{
return InheritedBlueprintComponentColor;
}
}
else
{
return IntroducedHereColor;
}
}
TSharedPtr<SWidget> SSCS_RowWidget::BuildSceneRootDropActionMenu(FSCSEditorTreeNodePtrType DroppedNodePtr)
{
check(SCSEditor.IsValid());
FMenuBuilder MenuBuilder(true, SCSEditor.Pin()->CommandList);
MenuBuilder.BeginSection("SceneRootNodeDropActions", LOCTEXT("SceneRootNodeDropActionContextMenu", "Drop Actions"));
{
const FText DroppedVariableNameText = FText::FromName( DroppedNodePtr->GetVariableName() );
const FText NodeVariableNameText = FText::FromName( GetNode()->GetVariableName() );
bool bDroppedInSameBlueprint = true;
if (SCSEditor.Pin()->GetEditorMode() == EComponentEditorMode::BlueprintSCS)
{
bDroppedInSameBlueprint = DroppedNodePtr->GetBlueprint() == GetBlueprint();
}
MenuBuilder.AddMenuEntry(
LOCTEXT("DropActionLabel_AttachToRootNode", "Attach"),
bDroppedInSameBlueprint
? FText::Format( LOCTEXT("DropActionToolTip_AttachToRootNode", "Attach {0} to {1}."), DroppedVariableNameText, NodeVariableNameText )
: FText::Format( LOCTEXT("DropActionToolTip_AttachToRootNodeFromCopy", "Copy {0} to a new variable and attach it to {1}."), DroppedVariableNameText, NodeVariableNameText ),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SSCS_RowWidget::OnAttachToDropAction, DroppedNodePtr),
FCanExecuteAction()));
FSCSEditorTreeNodePtrType NodePtr = GetNode();
const bool bIsDefaultSceneRoot = NodePtr->IsDefaultSceneRoot();
FText NewRootNodeText = bIsDefaultSceneRoot
? FText::Format(LOCTEXT("DropActionToolTip_MakeNewRootNodeAndDelete", "Make {0} the new root. The default root will be deleted."), DroppedVariableNameText)
: FText::Format(LOCTEXT("DropActionToolTip_MakeNewRootNode", "Make {0} the new root."), DroppedVariableNameText);
FText NewRootNodeFromCopyText = bIsDefaultSceneRoot
? FText::Format(LOCTEXT("DropActionToolTip_MakeNewRootNodeFromCopyAndDelete", "Copy {0} to a new variable and make it the new root. The default root will be deleted."), DroppedVariableNameText)
: FText::Format(LOCTEXT("DropActionToolTip_MakeNewRootNodeFromCopy", "Copy {0} to a new variable and make it the new root."), DroppedVariableNameText);
MenuBuilder.AddMenuEntry(
LOCTEXT("DropActionLabel_MakeNewRootNode", "Make New Root"),
bDroppedInSameBlueprint ? NewRootNodeText : NewRootNodeFromCopyText,
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SSCS_RowWidget::OnMakeNewRootDropAction, DroppedNodePtr),
FCanExecuteAction()));
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
FReply SSCS_RowWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && GetNode()->GetNodeType() != FSCSEditorTreeNode::SeparatorNode)
{
FReply Reply = SMultiColumnTableRow<FSCSEditorTreeNodePtrType>::OnMouseButtonDown( MyGeometry, MouseEvent );
return Reply.DetectDrag( SharedThis(this) , EKeys::LeftMouseButton );
}
else
{
return FReply::Unhandled();
}
}
FReply SSCS_RowWidget::HandleOnDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
TSharedPtr<SSCSEditor> SCSEditorPtr = SCSEditor.Pin();
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)
&& SCSEditorPtr.IsValid()
&& SCSEditorPtr->IsEditingAllowed()) //can only drag when editing
{
TArray<TSharedPtr<FSCSEditorTreeNode>> SelectedNodePtrs = SCSEditorPtr->GetSelectedNodes();
if (SelectedNodePtrs.Num() == 0)
{
SelectedNodePtrs.Add(GetNode());
}
TSharedPtr<FSCSEditorTreeNode> FirstNode = SelectedNodePtrs[0];
if (FirstNode->IsComponentNode())
{
// Do not use the Blueprint from FirstNode, it may still be referencing the parent.
UBlueprint* Blueprint = GetBlueprint();
const FName VariableName = FirstNode->GetVariableName();
UStruct* VariableScope = (Blueprint != nullptr) ? Blueprint->SkeletonGeneratedClass : nullptr;
TSharedRef<FSCSRowDragDropOp> Operation = FSCSRowDragDropOp::New(VariableName, VariableScope, FNodeCreationAnalytic());
Operation->SetCtrlDrag(true); // Always put a getter
Operation->PendingDropAction = FSCSRowDragDropOp::DropAction_None;
Operation->SourceNodes = SelectedNodePtrs;
return FReply::Handled().BeginDragDrop(Operation);
}
}
return FReply::Unhandled();
}
void SSCS_RowWidget::HandleOnDragEnter( const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return;
}
TSharedPtr<FSCSRowDragDropOp> DragRowOp = DragDropEvent.GetOperationAs<FSCSRowDragDropOp>();
if (DragRowOp.IsValid())
{
check(SCSEditor.IsValid());
FText Message;
FSlateColor IconColor = FLinearColor::White;
for (const FSCSEditorTreeNodePtrType& SelectedNodePtr : DragRowOp->SourceNodes)
{
if (!SelectedNodePtr->CanReparent())
{
// We set the tooltip text here because it won't change across entry/leave events
if (DragRowOp->SourceNodes.Num() == 1)
{
if (FChildActorComponentEditorUtils::IsChildActorSubtreeNode(SelectedNodePtr))
{
Message = LOCTEXT("DropActionToolTip_Error_CannotReparent_ChildActorSubTreeNodes", "The selected component is part of a child actor template and cannot be reordered here.");
}
else if (!SelectedNodePtr->IsSceneComponent())
{
Message = LOCTEXT("DropActionToolTip_Error_CannotReparent_NotSceneComponent", "The selected component is not a scene component and cannot be attached to other components.");
}
else if (SelectedNodePtr->IsInheritedComponent())
{
Message = LOCTEXT("DropActionToolTip_Error_CannotReparent_Inherited", "The selected component is inherited and cannot be reordered here.");
}
else
{
Message = LOCTEXT("DropActionToolTip_Error_CannotReparent", "The selected component cannot be moved.");
}
}
else
{
Message = LOCTEXT("DropActionToolTip_Error_CannotReparentMultiple", "One or more of the selected components cannot be attached.");
}
break;
}
}
if (Message.IsEmpty())
{
FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditor.Pin()->GetSceneRootNode();
check(SceneRootNodePtr.IsValid());
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if (!NodePtr->IsComponentNode())
{
// Don't show a feedback message if over a node that makes no sense, such as a separator or the instance node
Message = LOCTEXT("DropActionToolTip_FriendlyError_DragToAComponent", "Drag to another component in order to attach to that component or become the root component.\nDrag to a Blueprint graph in order to drop a reference.");
}
else if (FChildActorComponentEditorUtils::IsChildActorSubtreeNode(NodePtr))
{
// Can't drag onto components within a child actor node's subtree
Message = LOCTEXT("DropActionToolTip_Error_ChildActorSubTree", "Cannot attach to this component as it is part of a child actor template.");
}
// Validate each selected node being dragged against the node that belongs to this row. Exit the loop if we have a valid tooltip OR a valid pending drop action once all nodes in the selection have been validated.
for (auto SourceNodeIter = DragRowOp->SourceNodes.CreateConstIterator(); SourceNodeIter && (Message.IsEmpty() || DragRowOp->PendingDropAction != FSCSRowDragDropOp::DropAction_None); ++SourceNodeIter)
{
FSCSEditorTreeNodePtrType DraggedNodePtr = *SourceNodeIter;
check(DraggedNodePtr.IsValid());
// Reset the pending drop action each time through the loop
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_None;
// Get the component template objects associated with each node
USceneComponent* HoveredTemplate = Cast<USceneComponent>(NodePtr->GetComponentTemplate());
USceneComponent* DraggedTemplate = Cast<USceneComponent>(DraggedNodePtr->GetComponentTemplate());
if (DraggedNodePtr == NodePtr)
{
// Attempted to drag and drop onto self
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_Error_CannotAttachToSelfWithMultipleSelection", "Cannot attach the selected components here because it would result in {0} being attached to itself. Remove it from the selection and try again."), DraggedNodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_Error_CannotAttachToSelf", "Cannot attach {0} to itself."), DraggedNodePtr->GetDisplayName());
}
}
else if (NodePtr->IsAttachedTo(DraggedNodePtr))
{
// Attempted to drop a parent onto a child
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_Error_CannotAttachToChildWithMultipleSelection", "Cannot attach the selected components here because it would result in {0} being attached to one of its children. Remove it from the selection and try again."), DraggedNodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_Error_CannotAttachToChild", "Cannot attach {0} to one of its children."), DraggedNodePtr->GetDisplayName());
}
}
else if (HoveredTemplate == NULL || DraggedTemplate == NULL)
{
if (HoveredTemplate == nullptr)
{
// Can't attach to non-USceneComponent types
Message = LOCTEXT("DropActionToolTip_Error_NotAttachable_NotSceneComponent", "Cannot attach to this component as it is not a scene component.");
}
else
{
// Can't attach non-USceneComponent types
Message = LOCTEXT("DropActionToolTip_Error_NotAttachable", "Cannot attach to this component.");
}
}
else if (NodePtr == SceneRootNodePtr)
{
bool bCanMakeNewRoot = false;
bool bCanAttachToRoot = !DraggedNodePtr->IsDirectlyAttachedTo(NodePtr)
&& HoveredTemplate->CanAttachAsChild(DraggedTemplate, NAME_None)
&& DraggedTemplate->Mobility >= HoveredTemplate->Mobility
&& (!HoveredTemplate->IsEditorOnly() || DraggedTemplate->IsEditorOnly());
if (!NodePtr->CanReparent() && (!NodePtr->IsDefaultSceneRoot() || NodePtr->IsInheritedComponent()))
{
// Cannot make the dropped node the new root if we cannot reparent the current root
Message = LOCTEXT("DropActionToolTip_Error_CannotReparentRootNode", "The root component in this Blueprint is inherited and cannot be replaced.");
}
else if (DraggedTemplate->IsEditorOnly() && !HoveredTemplate->IsEditorOnly())
{
// can't have a new root that's editor-only (when children would be around in-game)
Message = LOCTEXT("DropActionToolTip_Error_CannotReparentEditorOnly", "Cannot re-parent game components under editor-only ones.");
}
else if (DraggedTemplate->Mobility > HoveredTemplate->Mobility)
{
// can't have a new root that's movable if the existing root is static or stationary
Message = LOCTEXT("DropActionToolTip_Error_CannotReparentNonMovable", "Cannot replace a non-movable scene root with a movable component.");
}
else if (DragRowOp->SourceNodes.Num() > 1)
{
Message = LOCTEXT("DropActionToolTip_Error_CannotAssignMultipleRootNodes", "Cannot replace the scene root with multiple components. Please select only a single component and try again.");
}
else
{
bCanMakeNewRoot = true;
}
if (bCanMakeNewRoot && bCanAttachToRoot)
{
// User can choose to either attach to the current root or make the dropped node the new root
Message = LOCTEXT("DropActionToolTip_AttachToOrMakeNewRoot", "Drop here to see available actions.");
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_AttachToOrMakeNewRoot;
}
else if (SCSEditor.Pin()->GetEditorMode() == EComponentEditorMode::BlueprintSCS && DraggedNodePtr->GetBlueprint() != GetBlueprint())
{
if (bCanMakeNewRoot)
{
if (NodePtr->IsDefaultSceneRoot())
{
// Only available action is to copy the dragged node to the other Blueprint and make it the new root
// Default root will be deleted
Message = FText::Format(LOCTEXT("DropActionToolTip_DropMakeNewRootNodeFromCopyAndDelete", "Drop here to copy {0} to a new variable and make it the new root. The default root will be deleted."), DraggedNodePtr->GetDisplayName());
}
else
{
// Only available action is to copy the dragged node to the other Blueprint and make it the new root
Message = FText::Format(LOCTEXT("DropActionToolTip_DropMakeNewRootNodeFromCopy", "Drop here to copy {0} to a new variable and make it the new root."), DraggedNodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_MakeNewRoot;
}
else if (bCanAttachToRoot)
{
// Only available action is to copy the dragged node(s) to the other Blueprint and attach it to the root
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachComponentsToThisNodeFromCopyWithMultipleSelection", "Drop here to copy the selected components to new variables and attach them to {0}."), NodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNodeFromCopy", "Drop here to copy {0} to a new variable and attach it to {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_AttachTo;
}
}
else if (bCanMakeNewRoot)
{
if (NodePtr->IsDefaultSceneRoot())
{
// Only available action is to make the dragged node the new root
// Default root will be deleted
Message = FText::Format(LOCTEXT("DropActionToolTip_DropMakeNewRootNodeAndDelete", "Drop here to make {0} the new root. The default root will be deleted."), DraggedNodePtr->GetDisplayName());
}
else
{
// Only available action is to make the dragged node the new root
Message = FText::Format(LOCTEXT("DropActionToolTip_DropMakeNewRootNode", "Drop here to make {0} the new root."), DraggedNodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_MakeNewRoot;
}
else if (bCanAttachToRoot)
{
// Only available action is to attach the dragged node(s) to the root
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNodeWithMultipleSelection", "Drop here to attach the selected components to {0}."), NodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNode", "Drop here to attach {0} to {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_AttachTo;
}
}
else if (DraggedNodePtr->IsDirectlyAttachedTo(NodePtr)) // if dropped onto parent
{
// Detach the dropped node(s) from the current node and reattach to the root node
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_DetachFromThisNodeWithMultipleSelection", "Drop here to detach the selected components from {0}."), NodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_DetachFromThisNode", "Drop here to detach {0} from {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_DetachFrom;
}
else if (!DraggedTemplate->IsEditorOnly() && HoveredTemplate->IsEditorOnly())
{
// can't have a game component child nested under an editor-only one
Message = LOCTEXT("DropActionToolTip_Error_CannotAttachToEditorOnly", "Cannot attach game components to editor-only ones.");
}
else if ((DraggedTemplate->Mobility == EComponentMobility::Static) && ((HoveredTemplate->Mobility == EComponentMobility::Movable) || (HoveredTemplate->Mobility == EComponentMobility::Stationary)))
{
// Can't attach Static components to mobile ones
Message = LOCTEXT("DropActionToolTip_Error_CannotAttachStatic", "Cannot attach Static components to movable ones.");
}
else if ((DraggedTemplate->Mobility == EComponentMobility::Stationary) && (HoveredTemplate->Mobility == EComponentMobility::Movable))
{
// Can't attach Static components to mobile ones
Message = LOCTEXT("DropActionToolTip_Error_CannotAttachStationary", "Cannot attach Stationary components to movable ones.");
}
else if ((NodePtr->IsInstancedComponent() && HoveredTemplate->CreationMethod == EComponentCreationMethod::Native && !HoveredTemplate->HasAnyFlags(RF_DefaultSubObject)))
{
// Can't attach to post-construction C++-added components as they exist outside of the CDO and are not known at SCS execution time
Message = LOCTEXT("DropActionToolTip_Error_CannotAttachCPPAdded", "Cannot attach to components added in post-construction C++ code.");
}
else if (NodePtr->IsInstancedComponent() && HoveredTemplate->CreationMethod == EComponentCreationMethod::UserConstructionScript)
{
// Can't attach to UCS-added components as they exist outside of the CDO and are not known at SCS execution time
Message = LOCTEXT("DropActionToolTip_Error_CannotAttachUCSAdded", "Cannot attach to components added in the Construction Script.");
}
else if (HoveredTemplate->CanAttachAsChild(DraggedTemplate, NAME_None))
{
// Attach the dragged node(s) to this node
if (DraggedNodePtr->GetBlueprint() != GetBlueprint())
{
if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNodeFromCopyWithMultipleSelection", "Drop here to copy the selected nodes to new variables and attach them to {0}."), NodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNodeFromCopy", "Drop here to copy {0} to a new variable and attach it to {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
}
else if (DragRowOp->SourceNodes.Num() > 1)
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNodeWithMultipleSelection", "Drop here to attach the selected components to {0}."), NodePtr->GetDisplayName());
}
else
{
Message = FText::Format(LOCTEXT("DropActionToolTip_AttachToThisNode", "Drop here to attach {0} to {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_AttachTo;
}
else
{
// The dropped node cannot be attached to the current node
Message = FText::Format(LOCTEXT("DropActionToolTip_Error_TooManyAttachments", "Unable to attach {0} to {1}."), DraggedNodePtr->GetDisplayName(), NodePtr->GetDisplayName());
}
}
}
const FSlateBrush* StatusSymbol = DragRowOp->PendingDropAction != FSCSRowDragDropOp::DropAction_None
? FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))
: FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
if (Message.IsEmpty())
{
DragRowOp->SetFeedbackMessage(nullptr);
}
else
{
DragRowOp->SetSimpleFeedbackMessage(StatusSymbol, FLinearColor::White, Message);
}
}
else if ( Operation->IsOfType<FExternalDragOperation>() || Operation->IsOfType<FAssetDragDropOp>() )
{
// defer to the tree widget's handler for this type of operation
TSharedPtr<SSCSEditor> PinnedEditor = SCSEditor.Pin();
if ( PinnedEditor.IsValid() && PinnedEditor->SCSTreeWidget.IsValid() )
{
// The widget geometry is irrelevant to the tree widget's OnDragEnter
PinnedEditor->SCSTreeWidget->OnDragEnter( FGeometry(), DragDropEvent );
}
}
}
void SSCS_RowWidget::HandleOnDragLeave(const FDragDropEvent& DragDropEvent)
{
TSharedPtr<FSCSRowDragDropOp> DragRowOp = DragDropEvent.GetOperationAs<FSCSRowDragDropOp>();
if (DragRowOp.IsValid())
{
bool bCanReparentAllNodes = true;
for(auto SourceNodeIter = DragRowOp->SourceNodes.CreateConstIterator(); SourceNodeIter && bCanReparentAllNodes; ++SourceNodeIter)
{
FSCSEditorTreeNodePtrType DraggedNodePtr = *SourceNodeIter;
check(DraggedNodePtr.IsValid());
bCanReparentAllNodes = DraggedNodePtr->CanReparent();
}
// Only clear the tooltip text if all dragged nodes support it
if(bCanReparentAllNodes)
{
TSharedPtr<SWidget> NoWidget;
DragRowOp->SetFeedbackMessage(NoWidget);
DragRowOp->PendingDropAction = FSCSRowDragDropOp::DropAction_None;
}
}
}
TOptional<EItemDropZone> SSCS_RowWidget::HandleOnCanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone, FSCSEditorTreeNodePtrType TargetItem)
{
TOptional<EItemDropZone> ReturnDropZone;
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (Operation.IsValid())
{
if (Operation->IsOfType<FSCSRowDragDropOp>() && ( Cast<USceneComponent>(GetNode()->GetComponentTemplate()) != nullptr ))
{
TSharedPtr<FSCSRowDragDropOp> DragRowOp = StaticCastSharedPtr<FSCSRowDragDropOp>(Operation);
check(DragRowOp.IsValid());
if (DragRowOp->PendingDropAction != FSCSRowDragDropOp::DropAction_None)
{
ReturnDropZone = EItemDropZone::OntoItem;
}
}
else if (Operation->IsOfType<FExternalDragOperation>() || Operation->IsOfType<FAssetDragDropOp>())
{
ReturnDropZone = EItemDropZone::OntoItem;
}
}
return ReturnDropZone;
}
FReply SSCS_RowWidget::HandleOnAcceptDrop( const FDragDropEvent& DragDropEvent, EItemDropZone DropZone, FSCSEditorTreeNodePtrType TargetItem )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return FReply::Handled();
}
if (Operation->IsOfType<FSCSRowDragDropOp>() && (Cast<USceneComponent>(GetNode()->GetComponentTemplate()) != nullptr))
{
TSharedPtr<FSCSRowDragDropOp> DragRowOp = StaticCastSharedPtr<FSCSRowDragDropOp>( Operation );
check(DragRowOp.IsValid());
switch(DragRowOp->PendingDropAction)
{
case FSCSRowDragDropOp::DropAction_AttachTo:
OnAttachToDropAction(DragRowOp->SourceNodes);
break;
case FSCSRowDragDropOp::DropAction_DetachFrom:
OnDetachFromDropAction(DragRowOp->SourceNodes);
break;
case FSCSRowDragDropOp::DropAction_MakeNewRoot:
check(DragRowOp->SourceNodes.Num() == 1);
OnMakeNewRootDropAction(DragRowOp->SourceNodes[0]);
break;
case FSCSRowDragDropOp::DropAction_AttachToOrMakeNewRoot:
{
check(DragRowOp->SourceNodes.Num() == 1);
FWidgetPath WidgetPath = DragDropEvent.GetEventPath() != nullptr ? *DragDropEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(
SharedThis(this),
WidgetPath,
BuildSceneRootDropActionMenu(DragRowOp->SourceNodes[0]).ToSharedRef(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
);
}
break;
case FSCSRowDragDropOp::DropAction_None:
default:
break;
}
}
else if (Operation->IsOfType<FExternalDragOperation>() || Operation->IsOfType<FAssetDragDropOp>())
{
// defer to the tree widget's handler for this type of operation
TSharedPtr<SSCSEditor> PinnedEditor = SCSEditor.Pin();
if ( PinnedEditor.IsValid() && PinnedEditor->SCSTreeWidget.IsValid() )
{
// The widget geometry is irrelevant to the tree widget's OnDrop
PinnedEditor->SCSTreeWidget->OnDrop( FGeometry(), DragDropEvent );
}
}
return FReply::Handled();
}
void ConformTransformRelativeToParent(USceneComponent* SceneComponentTemplate, USceneComponent* ParentSceneComponent)
{
// If we find a match, calculate its new position relative to the scene root component instance in its current scene
FTransform ComponentToWorld(SceneComponentTemplate->GetRelativeRotation(), SceneComponentTemplate->GetRelativeLocation(), SceneComponentTemplate->GetRelativeScale3D());
FTransform ParentToWorld = (SceneComponentTemplate->GetAttachSocketName() != NAME_None) ? ParentSceneComponent->GetSocketTransform(SceneComponentTemplate->GetAttachSocketName(), RTS_World) : ParentSceneComponent->GetComponentToWorld();
FTransform RelativeTM = ComponentToWorld.GetRelativeTransform(ParentToWorld);
// Store new relative location value (if not set to absolute)
if (!SceneComponentTemplate->IsUsingAbsoluteLocation())
{
SceneComponentTemplate->SetRelativeLocation_Direct(RelativeTM.GetTranslation());
}
// Store new relative rotation value (if not set to absolute)
if (!SceneComponentTemplate->IsUsingAbsoluteRotation())
{
SceneComponentTemplate->SetRelativeRotation_Direct(RelativeTM.Rotator());
}
// Store new relative scale value (if not set to absolute)
if (!SceneComponentTemplate->IsUsingAbsoluteScale())
{
SceneComponentTemplate->SetRelativeScale3D_Direct(RelativeTM.GetScale3D());
}
}
void SSCS_RowWidget::OnAttachToDropAction(const TArray<FSCSEditorTreeNodePtrType>& DroppedNodePtrs)
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
check(NodePtr.IsValid());
check(DroppedNodePtrs.Num() > 0);
TSharedPtr<SSCSEditor> SCSEditorPtr = SCSEditor.Pin();
check(SCSEditorPtr.IsValid());
bool bRegenerateTreeNodes = false;
const FScopedTransaction TransactionContext(DroppedNodePtrs.Num() > 1 ? LOCTEXT("AttachComponents", "Attach Components") : LOCTEXT("AttachComponent", "Attach Component"));
if (SCSEditorPtr->GetEditorMode() == EComponentEditorMode::BlueprintSCS)
{
// Get the current Blueprint context
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint);
// Get the current "preview" Actor instance
AActor* PreviewActor = SCSEditorPtr->PreviewActor.Get();
check(PreviewActor);
for(const FSCSEditorTreeNodePtrType& DroppedNodePtr : DroppedNodePtrs)
{
// Clone the component if it's being dropped into a different SCS
if(DroppedNodePtr->GetBlueprint() != Blueprint)
{
bRegenerateTreeNodes = true;
check(DroppedNodePtr.IsValid());
UActorComponent* ComponentTemplate = DroppedNodePtr->GetComponentTemplate();
check(ComponentTemplate);
// Note: This will mark the Blueprint as structurally modified
UActorComponent* ClonedComponent = SCSEditorPtr->AddNewComponent(ComponentTemplate->GetClass(), nullptr);
check(ClonedComponent);
UEngine::CopyPropertiesForUnrelatedObjects(ComponentTemplate, ClonedComponent);
// Attach the copied node to the target node (this will also detach it from the root if necessary)
FSCSEditorTreeNodePtrType NewNodePtr = SCSEditorPtr->GetNodeFromActorComponent(ClonedComponent);
if(NewNodePtr.IsValid())
{
NodePtr->AddChild(NewNodePtr);
}
}
else
{
// Get the associated component template if it is a scene component, so we can adjust the transform
USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(DroppedNodePtr->GetComponentTemplate());
// Cache current default values for propagation
FVector OldRelativeLocation, OldRelativeScale3D;
FRotator OldRelativeRotation;
if(SceneComponentTemplate)
{
OldRelativeLocation = SceneComponentTemplate->GetRelativeLocation();
OldRelativeRotation = SceneComponentTemplate->GetRelativeRotation();
OldRelativeScale3D = SceneComponentTemplate->GetRelativeScale3D();
}
// Check for a valid parent node
FSCSEditorTreeNodePtrType ParentNodePtr = DroppedNodePtr->GetParent();
if(ParentNodePtr.IsValid())
{
// Detach the dropped node from its parent
ParentNodePtr->RemoveChild(DroppedNodePtr);
// If the associated component template is a scene component, maintain its preview world position
if(SceneComponentTemplate)
{
// Save current state
SceneComponentTemplate->Modify();
// Reset the attach socket name
SceneComponentTemplate->SetupAttachment(SceneComponentTemplate->GetAttachParent(), NAME_None);
USCS_Node* SCS_Node = DroppedNodePtr->GetSCSNode();
if(SCS_Node)
{
SCS_Node->Modify();
SCS_Node->AttachToName = NAME_None;
}
// Attempt to locate a matching registered instance of the component template in the Actor context that's being edited
USceneComponent* InstancedSceneComponent = Cast<USceneComponent>(DroppedNodePtr->FindComponentInstanceInActor(PreviewActor));
if(InstancedSceneComponent && InstancedSceneComponent->IsRegistered())
{
// If we find a match, save off the world position
const FTransform& ComponentToWorld = InstancedSceneComponent->GetComponentToWorld();
SceneComponentTemplate->SetRelativeTransform_Direct(ComponentToWorld);
}
}
}
// Attach the dropped node to the given node
NodePtr->AddChild(DroppedNodePtr);
// Attempt to locate a matching instance of the parent component template in the Actor context that's being edited
USceneComponent* ParentSceneComponent = Cast<USceneComponent>(NodePtr->FindComponentInstanceInActor(PreviewActor));
if(SceneComponentTemplate && ParentSceneComponent && ParentSceneComponent->IsRegistered())
{
ConformTransformRelativeToParent(SceneComponentTemplate, ParentSceneComponent);
}
// Propagate any default value changes out to all instances of the template. If we didn't do this, then instances could incorrectly override the new default value with the old default value when construction scripts are re-run.
if(SceneComponentTemplate)
{
TArray<UObject*> InstancedSceneComponents;
SceneComponentTemplate->GetArchetypeInstances(InstancedSceneComponents);
for(int32 InstanceIndex = 0; InstanceIndex < InstancedSceneComponents.Num(); ++InstanceIndex)
{
USceneComponent* InstancedSceneComponent = Cast<USceneComponent>(InstancedSceneComponents[InstanceIndex]);
if(InstancedSceneComponent != nullptr)
{
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SceneComponentTemplate->GetRelativeLocation());
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeRotation_DirectMutable(), OldRelativeRotation, SceneComponentTemplate->GetRelativeRotation());
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeScale3D_DirectMutable(), OldRelativeScale3D, SceneComponentTemplate->GetRelativeScale3D());
}
}
}
}
}
}
else // EComponentEditorMode::ActorInstance
{
for(const FSCSEditorTreeNodePtrType& DroppedNodePtr : DroppedNodePtrs)
{
// Check for a valid parent node
FSCSEditorTreeNodePtrType ParentNodePtr = DroppedNodePtr->GetParent();
if(ParentNodePtr.IsValid())
{
// Detach the dropped node from its parent
ParentNodePtr->RemoveChild(DroppedNodePtr);
}
// Attach the dropped node to the given node
NodePtr->AddChild(DroppedNodePtr);
}
}
check(SCSEditorPtr->SCSTreeWidget.IsValid());
SCSEditorPtr->SCSTreeWidget->SetItemExpansion(NodePtr, true);
PostDragDropAction(bRegenerateTreeNodes);
}
void SSCS_RowWidget::OnDetachFromDropAction(const TArray<FSCSEditorTreeNodePtrType>& DroppedNodePtrs)
{
check(DroppedNodePtrs.Num() > 0);
TSharedPtr<SSCSEditor> SCSEditorPtr = SCSEditor.Pin();
check(SCSEditorPtr.IsValid());
const FScopedTransaction TransactionContext(DroppedNodePtrs.Num() > 1 ? LOCTEXT("DetachComponents", "Detach Components") : LOCTEXT("DetachComponent", "Detach Component"));
if (SCSEditorPtr->GetEditorMode() == EComponentEditorMode::BlueprintSCS)
{
// Get the current "preview" Actor instance
AActor* PreviewActor = SCSEditorPtr->PreviewActor.Get();
check(PreviewActor);
for (const FSCSEditorTreeNodePtrType& DroppedNodePtr : DroppedNodePtrs)
{
FVector OldRelativeLocation, OldRelativeScale3D;
FRotator OldRelativeRotation;
check(DroppedNodePtr.IsValid());
// Detach the node from its parent
FSCSEditorTreeNodePtrType ParentNodePtr = DroppedNodePtr->GetParent();
check(ParentNodePtr.IsValid());
ParentNodePtr->RemoveChild(DroppedNodePtr);
// If the associated component template is a scene component, maintain its current world position
USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(DroppedNodePtr->GetComponentTemplate());
if(SceneComponentTemplate)
{
// Cache current default values for propagation
OldRelativeLocation = SceneComponentTemplate->GetRelativeLocation();
OldRelativeRotation = SceneComponentTemplate->GetRelativeRotation();
OldRelativeScale3D = SceneComponentTemplate->GetRelativeScale3D();
// Save current state
SceneComponentTemplate->Modify();
// Reset the attach socket name
SceneComponentTemplate->SetupAttachment(SceneComponentTemplate->GetAttachParent(), NAME_None);
USCS_Node* SCS_Node = DroppedNodePtr->GetSCSNode();
if(SCS_Node)
{
SCS_Node->Modify();
SCS_Node->AttachToName = NAME_None;
}
// Attempt to locate a matching instance of the component template in the Actor context that's being edited
USceneComponent* InstancedSceneComponent = Cast<USceneComponent>(DroppedNodePtr->FindComponentInstanceInActor(PreviewActor));
if(InstancedSceneComponent && InstancedSceneComponent->IsRegistered())
{
// If we find a match, save off the world position
const FTransform& ComponentToWorld = InstancedSceneComponent->GetComponentToWorld();
SceneComponentTemplate->SetRelativeTransform_Direct(ComponentToWorld);
}
}
// Attach the dropped node to the current scene root node
FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode();
check(SceneRootNodePtr.IsValid());
SceneRootNodePtr->AddChild(DroppedNodePtr);
// Attempt to locate a matching instance of the scene root component template in the Actor context that's being edited
USceneComponent* InstancedSceneRootComponent = Cast<USceneComponent>(SceneRootNodePtr->FindComponentInstanceInActor(PreviewActor));
if(SceneComponentTemplate && InstancedSceneRootComponent && InstancedSceneRootComponent->IsRegistered())
{
ConformTransformRelativeToParent(SceneComponentTemplate, InstancedSceneRootComponent);
}
// Propagate any default value changes out to all instances of the template. If we didn't do this, then instances could incorrectly override the new default value with the old default value when construction scripts are re-run.
if(SceneComponentTemplate)
{
TArray<UObject*> InstancedSceneComponents;
SceneComponentTemplate->GetArchetypeInstances(InstancedSceneComponents);
for(int32 InstanceIndex = 0; InstanceIndex < InstancedSceneComponents.Num(); ++InstanceIndex)
{
USceneComponent* InstancedSceneComponent = Cast<USceneComponent>(InstancedSceneComponents[InstanceIndex]);
if(InstancedSceneComponent != nullptr)
{
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SceneComponentTemplate->GetRelativeLocation());
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeRotation_DirectMutable(), OldRelativeRotation, SceneComponentTemplate->GetRelativeRotation());
FComponentEditorUtils::ApplyDefaultValueChange(InstancedSceneComponent, InstancedSceneComponent->GetRelativeScale3D_DirectMutable(), OldRelativeScale3D, SceneComponentTemplate->GetRelativeScale3D());
}
}
}
}
}
else // EComponentEditorMode::ActorInstance
{
for (const FSCSEditorTreeNodePtrType& DroppedNodePtr : DroppedNodePtrs)
{
check(DroppedNodePtr.IsValid());
// Detach the node from its parent
FSCSEditorTreeNodePtrType ParentNodePtr = DroppedNodePtr->GetParent();
check(ParentNodePtr.IsValid());
ParentNodePtr->RemoveChild(DroppedNodePtr);
// Attach the dropped node to the current scene root node
FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode();
check(SceneRootNodePtr.IsValid());
SceneRootNodePtr->AddChild(DroppedNodePtr);
}
}
PostDragDropAction(false);
}
void SSCS_RowWidget::OnMakeNewRootDropAction(FSCSEditorTreeNodePtrType DroppedNodePtr)
{
TSharedPtr<SSCSEditor> SCSEditorPtr = SCSEditor.Pin();
check(SCSEditorPtr.IsValid());
// Get the current scene root node
FSCSEditorTreeNodePtrType SceneRootNodePtr = SCSEditorPtr->GetSceneRootNode();
FSCSEditorTreeNodePtrType NodePtr = GetNode();
// We cannot handle the drop action if any of these conditions fail on entry.
if (!ensure(NodePtr.IsValid()) || !ensure(DroppedNodePtr.IsValid()) || !ensure(NodePtr == SceneRootNodePtr))
{
return;
}
// Create a transaction record
const FScopedTransaction TransactionContext(LOCTEXT("MakeNewSceneRoot", "Make New Scene Root"));
FSCSEditorTreeNodePtrType OldSceneRootNodePtr;
// Remember whether or not we're replacing the default scene root
bool bWasDefaultSceneRoot = SceneRootNodePtr.IsValid() && SceneRootNodePtr->IsDefaultSceneRoot();
if (SCSEditorPtr->GetEditorMode() == EComponentEditorMode::BlueprintSCS)
{
// Get the current Blueprint context
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint && Blueprint->SimpleConstructionScript);
// Clone the component if it's being dropped into a different SCS
if(DroppedNodePtr->GetBlueprint() != Blueprint)
{
UActorComponent* ComponentTemplate = DroppedNodePtr->GetComponentTemplate();
check(ComponentTemplate);
// Note: This will mark the Blueprint as structurally modified
UActorComponent* ClonedComponent = SCSEditorPtr->AddNewComponent(ComponentTemplate->GetClass(), nullptr);
check(ClonedComponent);
UEngine::CopyPropertiesForUnrelatedObjects(ComponentTemplate, ClonedComponent);
DroppedNodePtr = SCSEditorPtr->GetNodeFromActorComponent(ClonedComponent);
check(DroppedNodePtr.IsValid());
}
if(DroppedNodePtr->GetParent().IsValid()
&& DroppedNodePtr->GetBlueprint() == Blueprint)
{
// If the associated component template is a scene component, reset its transform since it will now become the root
USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(DroppedNodePtr->GetComponentTemplate());
if(SceneComponentTemplate)
{
// Save current state
SceneComponentTemplate->Modify();
// Reset the attach socket name
SceneComponentTemplate->SetupAttachment(SceneComponentTemplate->GetAttachParent(), NAME_None);
USCS_Node* SCS_Node = DroppedNodePtr->GetSCSNode();
if(SCS_Node)
{
SCS_Node->Modify();
SCS_Node->AttachToName = NAME_None;
}
// Cache the current relative location and rotation values (for propagation)
const FVector OldRelativeLocation = SceneComponentTemplate->GetRelativeLocation();
const FRotator OldRelativeRotation = SceneComponentTemplate->GetRelativeRotation();
// Reset the relative transform (location and rotation only; scale is preserved)
SceneComponentTemplate->SetRelativeLocation(FVector::ZeroVector);
SceneComponentTemplate->SetRelativeRotation(FRotator::ZeroRotator);
// Propagate the root change & detachment to any instances of the template (done within the context of the current transaction)
TArray<UObject*> ArchetypeInstances;
SceneComponentTemplate->GetArchetypeInstances(ArchetypeInstances);
FDetachmentTransformRules DetachmentTransformRules(EDetachmentRule::KeepWorld, EDetachmentRule::KeepWorld, EDetachmentRule::KeepRelative, true);
for (int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex)
{
USceneComponent* SceneComponentInstance = Cast<USceneComponent>(ArchetypeInstances[InstanceIndex]);
if (SceneComponentInstance != nullptr)
{
// Detach from root (keeping world transform, except for scale)
SceneComponentInstance->DetachFromComponent(DetachmentTransformRules);
// Propagate the default relative location & rotation reset from the template to the instance
FComponentEditorUtils::ApplyDefaultValueChange(SceneComponentInstance, SceneComponentInstance->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SceneComponentTemplate->GetRelativeLocation());
FComponentEditorUtils::ApplyDefaultValueChange(SceneComponentInstance, SceneComponentInstance->GetRelativeRotation_DirectMutable(), OldRelativeRotation, SceneComponentTemplate->GetRelativeRotation());
// Must also reset the root component here, so that RerunConstructionScripts() will cache the correct root component instance data
AActor* Owner = SceneComponentInstance->GetOwner();
if (Owner)
{
Owner->Modify();
Owner->SetRootComponent(SceneComponentInstance);
}
}
}
}
// Remove the dropped node from its existing parent
DroppedNodePtr->GetParent()->RemoveChild(DroppedNodePtr);
}
check(bWasDefaultSceneRoot || SceneRootNodePtr->CanReparent());
// Remove the current scene root node from the SCS context
Blueprint->SimpleConstructionScript->RemoveNode(SceneRootNodePtr->GetSCSNode(), /*bValidateSceneRootNodes=*/false);
// Save old root node
OldSceneRootNodePtr = SceneRootNodePtr;
// Set node we are dropping as new root
SceneRootNodePtr = DroppedNodePtr;
SCSEditorPtr->SetSceneRootNode(SceneRootNodePtr);
// Add dropped node to the SCS context
Blueprint->SimpleConstructionScript->AddNode(SceneRootNodePtr->GetSCSNode());
// Remove or re-parent the old root
if (OldSceneRootNodePtr.IsValid())
{
check(SceneRootNodePtr->CanReparent());
// Set old root as child of new root
SceneRootNodePtr->AddChild(OldSceneRootNodePtr);
// Expand the new scene root as we've just added a child to it
SCSEditorPtr->SetNodeExpansionState(SceneRootNodePtr, true);
if (bWasDefaultSceneRoot)
{
SCSEditorPtr->RemoveComponentNode(OldSceneRootNodePtr);
}
}
}
else // EComponentEditorMode::ActorInstance
{
if(DroppedNodePtr->GetParent().IsValid())
{
// Remove the dropped node from its existing parent
DroppedNodePtr->GetParent()->RemoveChild(DroppedNodePtr);
}
// Save old root node
OldSceneRootNodePtr = SceneRootNodePtr;
// Set node we are dropping as new root
SceneRootNodePtr = DroppedNodePtr;
SCSEditorPtr->SetSceneRootNode(SceneRootNodePtr);
// Remove or re-parent the old root
if (OldSceneRootNodePtr.IsValid())
{
if (bWasDefaultSceneRoot)
{
SCSEditorPtr->RemoveComponentNode(OldSceneRootNodePtr);
SCSEditorPtr->GetActorContext()->SetRootComponent(CastChecked<USceneComponent>(DroppedNodePtr->GetComponentTemplate()));
}
else
{
check(SceneRootNodePtr->CanReparent());
// Set old root as child of new root
SceneRootNodePtr->AddChild(OldSceneRootNodePtr);
// Expand the new scene root as we've just added a child to it
SCSEditorPtr->SetNodeExpansionState(SceneRootNodePtr, true);
}
}
}
PostDragDropAction(true);
}
void SSCS_RowWidget::PostDragDropAction(bool bRegenerateTreeNodes)
{
GUnrealEd->ComponentVisManager.ClearActiveComponentVis();
FSCSEditorTreeNodePtrType NodePtr = GetNode();
TSharedPtr<SSCSEditor> PinnedEditor = SCSEditor.Pin();
if(PinnedEditor.IsValid())
{
PinnedEditor->UpdateTree(bRegenerateTreeNodes);
PinnedEditor->RefreshSelectionDetails();
if (PinnedEditor->GetEditorMode() == EComponentEditorMode::BlueprintSCS)
{
if(NodePtr.IsValid())
{
UBlueprint* Blueprint = GetBlueprint();
if(Blueprint != nullptr)
{
FBlueprintEditorUtils::PostEditChangeBlueprintActors(Blueprint, true);
}
}
}
else
{
AActor* ActorInstance = PinnedEditor->GetActorContext();
if(ActorInstance)
{
ActorInstance->RerunConstructionScripts();
}
}
}
}
FText SSCS_RowWidget::GetNameLabel() const
{
if( InlineWidget.IsValid() && !InlineWidget->IsInEditMode() )
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if(NodePtr->IsInheritedComponent())
{
return FText::Format(LOCTEXT("NativeComponentFormatString","{0} (Inherited)"), FText::FromString(GetNode()->GetDisplayString()));
}
}
// NOTE: Whatever this returns also becomes the variable name
return FText::FromString(GetNode()->GetDisplayString());
}
FText SSCS_RowWidget::GetTooltipText() const
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if (NodePtr->IsDefaultSceneRoot())
{
if (NodePtr->IsInheritedComponent())
{
return LOCTEXT("InheritedDefaultSceneRootToolTip", "This is the default scene root component. It cannot be copied, renamed or deleted.\nIt has been inherited from the parent class, so its properties cannot be edited here.\nNew scene components will automatically be attached to it.");
}
else
{
return LOCTEXT("DefaultSceneRootToolTip", "This is the default scene root component. It cannot be copied, renamed or deleted.\nIt can be replaced by drag/dropping another scene component over it.");
}
}
else
{
UClass* Class = ( NodePtr->GetComponentTemplate() != nullptr ) ? NodePtr->GetComponentTemplate()->GetClass() : nullptr;
const FText ClassDisplayName = FBlueprintEditorUtils::GetFriendlyClassDisplayName(Class);
const FText ComponentDisplayName = NodePtr->GetDisplayName();
FFormatNamedArguments Args;
Args.Add(TEXT("ClassName"), ClassDisplayName);
Args.Add(TEXT("NodeName"), FText::FromString(NodePtr->GetDisplayString()));
return FText::Format(LOCTEXT("ComponentTooltip", "{NodeName} ({ClassName})"), Args);
}
}
FString SSCS_RowWidget::GetDocumentationLink() const
{
check(SCSEditor.IsValid());
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if ((NodePtr == SCSEditor.Pin()->GetSceneRootNode()) || NodePtr->IsInheritedComponent())
{
return TEXT("Shared/Editors/BlueprintEditor/ComponentsMode");
}
return TEXT("");
}
FString SSCS_RowWidget::GetDocumentationExcerptName() const
{
check(SCSEditor.IsValid());
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if (NodePtr == SCSEditor.Pin()->GetSceneRootNode())
{
return TEXT("RootComponent");
}
else if (NodePtr->IsNativeComponent())
{
return TEXT("NativeComponents");
}
else if (NodePtr->IsInheritedComponent())
{
return TEXT("InheritedComponents");
}
return TEXT("");
}
UBlueprint* SSCS_RowWidget::GetBlueprint() const
{
check(SCSEditor.IsValid());
return SCSEditor.Pin()->GetBlueprint();
}
ESelectionMode::Type SSCS_RowWidget::GetSelectionMode() const
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
if (NodePtr->GetNodeType() == FSCSEditorTreeNode::SeparatorNode)
{
return ESelectionMode::None;
}
return SMultiColumnTableRow<FSCSEditorTreeNodePtrType>::GetSelectionMode();
}
bool SSCS_RowWidget::OnNameTextVerifyChanged(const FText& InNewText, FText& OutErrorMessage)
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
UBlueprint* Blueprint = GetBlueprint();
const FString& NewTextStr = InNewText.ToString();
if (!NewTextStr.IsEmpty())
{
if (NodePtr->GetVariableName().ToString() == NewTextStr)
{
return true;
}
const UActorComponent* ComponentInstance = NodePtr->GetComponentTemplate();
if (ensure(ComponentInstance))
{
AActor* ExistingNameSearchScope = ComponentInstance->GetOwner();
if ((ExistingNameSearchScope == nullptr) && (Blueprint != nullptr))
{
ExistingNameSearchScope = Cast<AActor>(Blueprint->GeneratedClass->GetDefaultObject());
}
if (!FComponentEditorUtils::IsValidVariableNameString(ComponentInstance, NewTextStr))
{
OutErrorMessage = LOCTEXT("RenameFailed_EngineReservedName", "This name is reserved for engine use.");
return false;
}
else if (NewTextStr.Len() >= NAME_SIZE)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("CharCount"), NAME_SIZE);
OutErrorMessage = FText::Format(LOCTEXT("ComponentRenameFailed_TooLong", "Component name must be less than {CharCount} characters long."), Arguments);
return false;
}
else if (!FComponentEditorUtils::IsComponentNameAvailable(NewTextStr, ExistingNameSearchScope, ComponentInstance)
|| !FComponentEditorUtils::IsComponentNameAvailable(NewTextStr, ComponentInstance->GetOuter(), ComponentInstance ))
{
OutErrorMessage = LOCTEXT("RenameFailed_ExistingName", "Another component already has the same name.");
return false;
}
}
else
{
OutErrorMessage = LOCTEXT("RenameFailed_InvalidComponentInstance", "This node is referencing an invalid component instance and cannot be renamed. Perhaps it was destroyed?");
return false;
}
}
TSharedPtr<INameValidatorInterface> NameValidator;
if (Blueprint != nullptr)
{
NameValidator = MakeShareable(new FKismetNameValidator(GetBlueprint(), NodePtr->GetVariableName()));
}
else
{
NameValidator = MakeShareable(new FStringSetNameValidator(NodePtr->GetComponentTemplate()->GetName()));
}
EValidatorResult ValidatorResult = NameValidator->IsValid(NewTextStr);
if (ValidatorResult == EValidatorResult::AlreadyInUse)
{
OutErrorMessage = FText::Format(LOCTEXT("RenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText);
}
else if (ValidatorResult == EValidatorResult::EmptyName)
{
OutErrorMessage = LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank!");
}
else if (ValidatorResult == EValidatorResult::TooLong)
{
OutErrorMessage = LOCTEXT("RenameFailed_NameTooLong", "Names must have fewer than 100 characters!");
}
if (OutErrorMessage.IsEmpty())
{
return true;
}
return false;
}
void SSCS_RowWidget::OnNameTextCommit(const FText& InNewName, ETextCommit::Type InTextCommit)
{
GetNode()->OnCompleteRename(InNewName);
// No need to call UpdateTree() in SCS editor mode; it will already be called by MBASM internally
check(SCSEditor.IsValid());
TSharedPtr<SSCSEditor> PinnedEditor = SCSEditor.Pin();
if (PinnedEditor.IsValid() && PinnedEditor->GetEditorMode() == EComponentEditorMode::ActorInstance)
{
PinnedEditor->UpdateTree(false);
}
}
//////////////////////////////////////////////////////////////////////////
// SSCS_RowWidget_ActorRoot
FSCSEditorActorNodePtrType SSCS_RowWidget_ActorRoot::GetActorNode() const
{
return StaticCastSharedPtr<FSCSEditorTreeNodeActorBase>(GetNode());
}
TSharedRef<SWidget> SSCS_RowWidget_ActorRoot::GenerateWidgetForColumn(const FName& ColumnName)
{
FSCSEditorTreeNodePtrType NodePtr = GetNode();
// We've removed the other columns for now, implement them for the root actor if necessary
ensure(ColumnName == SCS_ColumnName_ComponentClass);
// Create the name field
TSharedPtr<SInlineEditableTextBlock> InlineEditableWidget =
SNew(SInlineEditableTextBlock)
.Text(this, &SSCS_RowWidget_ActorRoot::GetActorDisplayText)
.OnVerifyTextChanged(this, &SSCS_RowWidget_ActorRoot::OnVerifyActorLabelChanged)
.OnTextCommitted(this, &SSCS_RowWidget_ActorRoot::OnNameTextCommit)
.IsSelected(this, &SSCS_RowWidget_ActorRoot::IsSelectedExclusively)
.IsReadOnly(!NodePtr->CanRename() || (SCSEditor.IsValid() && !SCSEditor.Pin()->IsEditingAllowed()) || FChildActorComponentEditorUtils::IsChildActorNode(NodePtr));
NodePtr->SetRenameRequestedDelegate(FSCSEditorTreeNode::FOnRenameRequested::CreateSP(InlineEditableWidget.Get(), &SInlineEditableTextBlock::EnterEditingMode));
const bool IsRootActorNode = NodePtr->GetNodeType() == FSCSEditorTreeNode::ENodeType::RootActorNode;
return SNew(SHorizontalBox)
.ToolTip(CreateToolTipWidget())
+ SHorizontalBox::Slot()
.Padding(FMargin(IsRootActorNode ? 0.f : 4.f, 0.f, 0.f, 0.f))
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SExpanderArrow, SharedThis(this))
.Visibility(IsRootActorNode ? EVisibility::Collapsed : EVisibility::Visible)
]
+ SHorizontalBox::Slot()
.Padding(FMargin(IsRootActorNode ? 4.f : 0.f, 0.f, 0.f, 0.f))
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(GetIconBrush())
.ColorAndOpacity(FSlateColor::UseForeground())
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(6.f, 0.f, 0.f, 0.f)
[
InlineEditableWidget.ToSharedRef()
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(4.f, 0.f, 0.f, 0.f)
[
SNew(STextBlock)
.Text(this, &SSCS_RowWidget_ActorRoot::GetActorContextText)
.ColorAndOpacity(FSlateColor::UseForeground())
];
}
TSharedRef<SToolTip> SSCS_RowWidget_ActorRoot::CreateToolTipWidget() const
{
// Create a box to hold every line of info in the body of the tooltip
TSharedRef<SVerticalBox> InfoBox = SNew(SVerticalBox);
// Add class
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipClass", "Class"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget_ActorRoot::GetActorClassNameText)), false);
// Add super class
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipSuperClass", "Parent Class"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget_ActorRoot::GetActorSuperClassNameText)), false);
// Add mobility
AddToToolTipInfoBox(InfoBox, LOCTEXT("TooltipMobility", "Mobility"), SNullWidget::NullWidget, TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SSCS_RowWidget_ActorRoot::GetActorMobilityText)), false);
TSharedRef<SBorder> TooltipContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.Padding(0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4.0f)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "SCSEditor.ComponentTooltip.Title")
.Text(this, &SSCS_RowWidget_ActorRoot::GetActorDisplayText)
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("NoBorder"))
.Padding(4.0f)
[
InfoBox
]
]
];
return IDocumentation::Get()->CreateToolTip(TAttribute<FText>(this, &SSCS_RowWidget_ActorRoot::GetActorDisplayText), TooltipContent, InfoBox, TEXT(""), TEXT(""));
}
bool SSCS_RowWidget_ActorRoot::OnVerifyActorLabelChanged(const FText& InLabel, FText& OutErrorMessage)
{
return FActorEditorUtils::ValidateActorName(InLabel, OutErrorMessage);
}
const FSlateBrush* SSCS_RowWidget_ActorRoot::GetIconBrush() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (const AActor* Actor = NodePtr->GetObject<AActor>())
{
return FClassIconFinder::FindIconForActor(Actor);
}
}
return nullptr;
}
FText SSCS_RowWidget_ActorRoot::GetActorDisplayText() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (FChildActorComponentEditorUtils::IsChildActorNode(NodePtr))
{
if (const AActor* ChildActor = NodePtr->GetObject<AActor>())
{
return ChildActor->GetClass()->GetDisplayNameText();
}
}
else
{
if (const AActor* DefaultActor = NodePtr->GetObject<AActor>())
{
FString Name;
UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass());
if (Blueprint != nullptr && !NodePtr->IsInstanced())
{
Blueprint->GetName(Name);
}
else
{
Name = DefaultActor->GetActorLabel();
}
return FText::FromString(Name);
}
}
}
return FText::GetEmpty();
}
FText SSCS_RowWidget_ActorRoot::GetActorContextText() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (FChildActorComponentEditorUtils::IsChildActorNode(NodePtr))
{
return LOCTEXT("ActorContext_ChildActor", " (Child Actor)");
}
else
{
if (const AActor* DefaultActor = NodePtr->GetObject<AActor>())
{
if (UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass()))
{
return LOCTEXT("ActorContext_self", " (self)");
}
else
{
return LOCTEXT("ActorContext_Instance", " (Instance)");
}
}
}
}
return FText::GetEmpty();
}
FText SSCS_RowWidget_ActorRoot::GetActorClassNameText() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (const AActor* DefaultActor = NodePtr->GetObject<AActor>())
{
return FText::FromString(DefaultActor->GetClass()->GetName());
}
}
return FText::GetEmpty();
}
FText SSCS_RowWidget_ActorRoot::GetActorSuperClassNameText() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (const AActor* DefaultActor = NodePtr->GetObject<AActor>())
{
return FText::FromString(DefaultActor->GetClass()->GetSuperClass()->GetName());
}
}
return FText::GetEmpty();
}
FText SSCS_RowWidget_ActorRoot::GetActorMobilityText() const
{
FSCSEditorActorNodePtrType NodePtr = GetActorNode();
if (NodePtr.IsValid())
{
if (const AActor* DefaultActor = NodePtr->GetObject<AActor>())
{
USceneComponent* RootComponent = DefaultActor->GetRootComponent();
FSCSEditorTreeNodePtrType SceneRootNodePtr = NodePtr->GetSceneRootNode();
if ((RootComponent == nullptr) && SceneRootNodePtr.IsValid())
{
RootComponent = Cast<USceneComponent>(SceneRootNodePtr->GetComponentTemplate());
}
if (RootComponent != nullptr)
{
if (RootComponent->Mobility == EComponentMobility::Static)
{
return LOCTEXT("ComponentMobility_Static", "Static");
}
else if (RootComponent->Mobility == EComponentMobility::Stationary)
{
return LOCTEXT("ComponentMobility_Stationary", "Stationary");
}
else if (RootComponent->Mobility == EComponentMobility::Movable)
{
return LOCTEXT("ComponentMobility_Movable", "Movable");
}
}
else
{
return LOCTEXT("ComponentMobility_NoRoot", "No root component, unknown mobility");
}
}
}
return FText::GetEmpty();
}
//////////////////////////////////////////////////////////////////////////
// SSCS_RowWidget_Separator
TSharedRef<SWidget> SSCS_RowWidget_Separator::GenerateWidgetForColumn(const FName& ColumnName)
{
return SNullWidget::NullWidget;
/*return SNew(SBox)
.Padding(1.f)
[
SNew(SBorder)
.Padding(FAppStyle::GetMargin(TEXT("Menu.Separator.Padding")))
.BorderImage(FAppStyle::GetBrush(TEXT("Menu.Separator")))
];*/
}
//////////////////////////////////////////////////////////////////////////
// SSCSEditor
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSCSEditor::Construct( const FArguments& InArgs )
{
EditorMode = InArgs._EditorMode;
ActorContext = InArgs._ActorContext;
AllowEditing = InArgs._AllowEditing;
PreviewActor = InArgs._PreviewActor;
OnSelectionUpdated = InArgs._OnSelectionUpdated;
OnItemDoubleClicked = InArgs._OnItemDoubleClicked;
OnHighlightPropertyInDetailsView = InArgs._OnHighlightPropertyInDetailsView;
OnObjectReplaced = InArgs._OnObjectReplaced;
bUpdatingSelection = false;
bAllowTreeUpdates = true;
bIsDiffing = InArgs._IsDiffing;
CommandList = MakeShareable( new FUICommandList );
CommandList->MapAction( FGenericCommands::Get().Cut,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::CutSelectedNodes ),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanCutNodes ) )
);
CommandList->MapAction( FGenericCommands::Get().Copy,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::CopySelectedNodes ),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanCopyNodes ) )
);
CommandList->MapAction( FGenericCommands::Get().Paste,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::PasteNodes ),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanPasteNodes ) )
);
CommandList->MapAction( FGenericCommands::Get().Duplicate,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnDuplicateComponent ),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanDuplicateComponent ) )
);
CommandList->MapAction( FGenericCommands::Get().Delete,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnDeleteNodes ),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanDeleteNodes ) )
);
CommandList->MapAction( FGenericCommands::Get().Rename,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnRenameComponent),
FCanExecuteAction::CreateSP( this, &SSCSEditor::CanRenameComponent ) )
);
CommandList->MapAction( FGraphEditorCommands::Get().FindReferences,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnFindReferences, false, EGetFindReferenceSearchStringFlags::Legacy) )
);
CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByNameLocal,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnFindReferences, false, EGetFindReferenceSearchStringFlags::None ) )
);
CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByNameGlobal,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnFindReferences, true, EGetFindReferenceSearchStringFlags::None ) )
);
CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberLocal,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnFindReferences, false, EGetFindReferenceSearchStringFlags::UseSearchSyntax ) )
);
CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberGlobal,
FUIAction( FExecuteAction::CreateSP( this, &SSCSEditor::OnFindReferences, true, EGetFindReferenceSearchStringFlags::UseSearchSyntax ) )
);
FSlateBrush const* MobilityHeaderBrush = FAppStyle::GetBrush(TEXT("ClassIcon.ComponentMobilityHeaderIcon"));
TSharedPtr<SHeaderRow> HeaderRow = SNew(SHeaderRow)
+ SHeaderRow::Column(SCS_ColumnName_ComponentClass)
.DefaultLabel(LOCTEXT("Class", "Class"))
.FillWidth(4);
SCSTreeWidget = SNew(SSCSTreeType)
.ToolTipText(LOCTEXT("DropAssetToAddComponent", "Drop asset here to add a component."))
.SCSEditor(this)
.TreeItemsSource(&RootNodes)
.SelectionMode(ESelectionMode::Multi)
.OnGenerateRow(this, &SSCSEditor::MakeTableRowWidget)
.OnGetChildren(this, &SSCSEditor::OnGetChildrenForTree)
.OnSetExpansionRecursive(this, &SSCSEditor::SetItemExpansionRecursive)
.OnSelectionChanged(this, &SSCSEditor::OnTreeSelectionChanged)
.OnContextMenuOpening(this, &SSCSEditor::CreateContextMenu)
.OnItemScrolledIntoView(this, &SSCSEditor::OnItemScrolledIntoView)
.OnMouseButtonDoubleClick(this, &SSCSEditor::HandleItemDoubleClicked)
.ClearSelectionOnClick(InArgs._EditorMode == EComponentEditorMode::BlueprintSCS ? true : false)
.OnTableViewBadState(this, &SSCSEditor::DumpTree)
.ItemHeight(24)
.HeaderRow
(
HeaderRow
);
SCSTreeWidget->GetHeaderRow()->SetVisibility(EVisibility::Collapsed);
TSharedPtr<SWidget> Contents;
FMenuBuilder EditBlueprintMenuBuilder( true, NULL );
EditBlueprintMenuBuilder.BeginSection( NAME_None, LOCTEXT("EditBlueprintMenu_ExistingBlueprintHeader", "Existing Blueprint" ) );
EditBlueprintMenuBuilder.AddMenuEntry
(
LOCTEXT("OpenBlueprintEditor", "Open Blueprint Editor"),
LOCTEXT("OpenBlueprintEditor_ToolTip", "Opens the blueprint editor for this asset"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSCSEditor::OnOpenBlueprintEditor, /*bForceCodeEditing=*/ false))
);
EditBlueprintMenuBuilder.AddMenuEntry
(
LOCTEXT("OpenBlueprintEditorScriptMode", "Add or Edit Script"),
LOCTEXT("OpenBlueprintEditorScriptMode_ToolTip", "Opens the blueprint editor for this asset, showing the event graph"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSCSEditor::OnOpenBlueprintEditor, /*bForceCodeEditing=*/ true))
);
EditBlueprintMenuBuilder.BeginSection(NAME_None, LOCTEXT("EditBlueprintMenu_InstanceHeader", "Instance modifications"));
EditBlueprintMenuBuilder.AddMenuEntry
(
LOCTEXT("PushChangesToBlueprint", "Apply Instance Changes to Blueprint"),
TAttribute<FText>(this, &SSCSEditor::OnGetApplyChangesToBlueprintTooltip),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSCSEditor::OnApplyChangesToBlueprint))
);
EditBlueprintMenuBuilder.AddMenuEntry
(
LOCTEXT("ResetToDefault", "Reset Instance Changes to Blueprint Default"),
TAttribute<FText>(this, &SSCSEditor::OnGetResetToBlueprintDefaultsTooltip),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSCSEditor::OnResetToBlueprintDefaults))
);
EditBlueprintMenuBuilder.BeginSection( NAME_None, LOCTEXT("EditBlueprintMenu_NewHeader", "Create New" ) );
//EditBlueprintMenuBuilder.AddMenuSeparator();
EditBlueprintMenuBuilder.AddMenuEntry
(
LOCTEXT("CreateChildBlueprint", "Create Child Blueprint Class"),
LOCTEXT("CreateChildBlueprintTooltip", "Creates a Child Blueprint Class based on the current Blueprint, allowing you to create variants easily. This replaces the current actor instance with a new one based on the new Child Blueprint Class." ),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSCSEditor::PromoteToBlueprint))
);
TSharedPtr<SVerticalBox> HeaderBox;
TSharedPtr<SWidget> SearchBar = SAssignNew(FilterBox, SSearchBox)
.HintText(EditorMode == EComponentEditorMode::ActorInstance ? LOCTEXT("SearchComponentsHint", "Search Components") : LOCTEXT("SearchHint", "Search"))
.OnTextChanged(this, &SSCSEditor::OnFilterTextChanged)
.Visibility(this, &SSCSEditor::GetComponentsFilterBoxVisibility);
const bool bInlineSearchBarWithButtons = (EditorMode == EComponentEditorMode::BlueprintSCS);
HideComponentClassCombo = InArgs._HideComponentClassCombo;
ComponentTypeFilter = InArgs._ComponentTypeFilter;
USCSEditorExtensionContext* ExtensionContext = NewObject<USCSEditorExtensionContext>();
ExtensionContext->SCSEditor = SharedThis(this);
ExtensionContext->AddToRoot();
ButtonBox = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SNew(SComponentClassCombo)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("Actor.AddComponent")))
.Visibility(this, &SSCSEditor::GetComponentClassComboButtonVisibility)
.OnComponentClassSelected(this, &SSCSEditor::PerformComboAddClass)
.ToolTipText(LOCTEXT("AddComponent_Tooltip", "Adds a new component to this actor"))
.IsEnabled(AllowEditing)
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SAssignNew(ExtensionPanel, SExtensionPanel)
.ExtensionPanelID("SCSEditor.NextToAddComponentButton")
.ExtensionContext(ExtensionContext)
]
//
// horizontal slot index #2 => reserved for BP-editor search bar (see 'ButtonBox' and 'SearchBarHorizontalSlotIndex' usage below)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SNew(SPositiveActionButton)
.AddMetaData<FTagMetaData>( FTagMetaData(TEXT("Actor.ConvertToBlueprint")) )
.Visibility( this, &SSCSEditor::GetPromoteToBlueprintButtonVisibility )
.OnClicked( this, &SSCSEditor::OnPromoteToBlueprintClicked )
.Icon(FAppStyle::Get().GetBrush("Icons.Blueprints"))
.ToolTip(IDocumentation::Get()->CreateToolTip(
LOCTEXT("PromoteToBluerprintTooltip", "Converts this actor into a reusable Blueprint Class that can have script behavior" ),
NULL,
TEXT("Shared/LevelEditor"),
TEXT("ConvertToBlueprint")))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SPositiveActionButton)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("Actor.EditBlueprint")))
.Visibility(this, &SSCSEditor::GetEditBlueprintButtonVisibility)
.ToolTipText(LOCTEXT("EditActorBlueprint_Tooltip", "Edit the Blueprint for this Actor"))
.Icon(FAppStyle::Get().GetBrush("Icons.Blueprints"))
.MenuContent()
[
EditBlueprintMenuBuilder.MakeWidget()
]
];
Contents = SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.VAlign(VAlign_Top)
.Padding(4.f, 0, 4.f, 4.f)
[
SAssignNew(HeaderBox, SVerticalBox)
]
+ SVerticalBox::Slot()
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("SCSEditor.Background"))
.Padding(4.f)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ComponentsPanel")))
.Visibility(this, &SSCSEditor::GetComponentsTreeVisibility)
[
SCSTreeWidget.ToSharedRef()
]
];
// Only insert the buttons and search bar in the Blueprints version
if (bInlineSearchBarWithButtons) // Blueprints
{
ButtonBox->AddSlot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.Padding(3.0f, 3.0f)
[
SearchBar.ToSharedRef()
];
HeaderBox->AddSlot()
.VAlign(VAlign_Center)
.AutoHeight()
[
ButtonBox.ToSharedRef()
];
}
this->ChildSlot
[
Contents.ToSharedRef()
];
// Refresh the tree widget
UpdateTree();
if (EditorMode == EComponentEditorMode::ActorInstance)
{
GEngine->OnLevelComponentRequestRename().AddSP(this, &SSCSEditor::OnLevelComponentRequestRename);
FCoreUObjectDelegates::OnObjectsReplaced.AddSP(this, &SSCSEditor::OnObjectsReplaced);
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
SSCSEditor::~SSCSEditor()
{
if (UObject* ExtensionContext = ExtensionPanel->GetExtensionContext())
{
ExtensionContext->RemoveFromRoot();
}
}
TSharedPtr<SWidget> SSCSEditor::GetToolButtonsBox()
{
return ButtonBox;
}
void SSCSEditor::OnLevelComponentRequestRename(const UActorComponent* InComponent)
{
TArray< FSCSEditorTreeNodePtrType > SelectedItems = SCSTreeWidget->GetSelectedItems();
FSCSEditorTreeNodePtrType Node = GetNodeFromActorComponent(InComponent);
if (SelectedItems.Contains(Node) && CanRenameComponent())
{
OnRenameComponent();
}
}
void SSCSEditor::OnObjectsReplaced(const TMap<UObject*, UObject*>& OldToNewInstanceMap)
{
bool bHasChanges = false;
ReplaceComponentReferencesInTree(GetActorNode(), OldToNewInstanceMap, bHasChanges);
if (bHasChanges)
{
OnObjectReplaced.ExecuteIfBound();
}
}
void SSCSEditor::ReplaceComponentReferencesInTree(FSCSEditorActorNodePtrType InActorNode, const TMap<UObject*, UObject*>& OldToNewInstanceMap, bool& OutHasChanges)
{
if (InActorNode.IsValid())
{
ReplaceComponentReferencesInTree(InActorNode->GetComponentNodes(), OldToNewInstanceMap, OutHasChanges);
}
}
void SSCSEditor::ReplaceComponentReferencesInTree(const TArray<FSCSEditorTreeNodePtrType>& Nodes, const TMap<UObject*, UObject*>& OldToNewInstanceMap, bool& OutHasChanges)
{
for (const FSCSEditorTreeNodePtrType& Node : Nodes)
{
if (Node.IsValid())
{
// We need to get the actual pointer to the old object which will be marked for pending kill, as these are the references which need updating
const bool bEvenIfPendingKill = true;
const UObject* OldObject = Node->GetObject<UObject>(bEvenIfPendingKill);
if (OldObject)
{
UObject* const* NewObject = OldToNewInstanceMap.Find(OldObject);
if (NewObject)
{
Node->SetObject(*NewObject);
OutHasChanges = true;
}
}
ReplaceComponentReferencesInTree(Node->GetChildren(), OldToNewInstanceMap, OutHasChanges);
}
}
}
UBlueprint* SSCSEditor::GetBlueprint() const
{
if (AActor* Actor = GetActorContext())
{
const UClass* ActorClass = Actor->GetClass();
check(ActorClass != nullptr);
return UBlueprint::GetBlueprintFromClass(ActorClass);
}
return nullptr;
}
FReply SSCSEditor::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
if (CommandList->ProcessCommandBindings(InKeyEvent))
{
return FReply::Handled();
}
return FReply::Unhandled();
}
TSharedRef<ITableRow> SSCSEditor::MakeTableRowWidget( FSCSEditorTreeNodePtrType InNodePtr, const TSharedRef<STableViewBase>& OwnerTable )
{
// Setup a meta tag for this node
FGraphNodeMetaData TagMeta(TEXT("TableRow"));
if (InNodePtr.IsValid() && InNodePtr->GetComponentTemplate() != NULL )
{
TagMeta.FriendlyName = FString::Printf(TEXT("TableRow,%s,0"), *InNodePtr->GetComponentTemplate()->GetReadableName());
}
// Create the node of the appropriate type
if (InNodePtr->IsActorNode())
{
return SNew(SSCS_RowWidget_ActorRoot, SharedThis(this), InNodePtr, OwnerTable);
}
else if (InNodePtr->GetNodeType() == FSCSEditorTreeNode::SeparatorNode)
{
return SNew(SSCS_RowWidget_Separator, SharedThis(this), InNodePtr, OwnerTable);
}
return SNew(SSCS_RowWidget, SharedThis(this), InNodePtr, OwnerTable)
.AddMetaData<FTutorialMetaData>(TagMeta);
}
void SSCSEditor::GetSelectedItemsForContextMenu(TArray<FComponentEventConstructionData>& OutSelectedItems) const
{
TArray<FSCSEditorTreeNodePtrType> SelectedTreeItems = SCSTreeWidget->GetSelectedItems();
for ( auto NodeIter = SelectedTreeItems.CreateConstIterator(); NodeIter; ++NodeIter )
{
FComponentEventConstructionData NewItem;
const FSCSEditorTreeNodePtrType& TreeNode = *NodeIter;
NewItem.VariableName = TreeNode->GetVariableName();
NewItem.Component = TreeNode->GetComponentTemplate();
OutSelectedItems.Add(NewItem);
}
}
TArray<UObject*> SSCSEditor::GetSelectedEditableObjects() const
{
TArray<UObject*> SelectedObjects;
if (UBlueprint* BP = GetBlueprint())
{
TArray<FSCSEditorTreeNodePtrType> SelectedTreeItems = SCSTreeWidget->GetSelectedItems();
SelectedObjects.Reserve(SelectedTreeItems.Num());
for (const FSCSEditorTreeNodePtrType& TreeNode : SelectedTreeItems)
{
UObject* Obj = TreeNode->GetEditableObjectForBlueprint<UObject>(BP);
if (Obj)
{
SelectedObjects.Add(Obj);
}
}
}
return SelectedObjects;
}
void SSCSEditor::PopulateContextMenu(UToolMenu* Menu)
{
TArray<FSCSEditorTreeNodePtrType> SelectedItems = SCSTreeWidget->GetSelectedItems();
if (SelectedItems.Num() > 0 || CanPasteNodes())
{
bool bOnlyShowPasteOption = false;
if (SelectedItems.Num() > 0)
{
if (SelectedItems.Num() == 1 && SelectedItems[0]->IsActorNode())
{
if (FChildActorComponentEditorUtils::IsChildActorNode(SelectedItems[0]))
{
// Include specific context menu options for a single child actor node selection
FChildActorComponentEditorUtils::FillChildActorContextMenuOptions(Menu, SelectedItems[0]);
}
else
{
bOnlyShowPasteOption = true;
}
}
else
{
for (FSCSEditorTreeNodePtrType SelectedNode : SelectedItems)
{
if (!SelectedNode->IsComponentNode())
{
bOnlyShowPasteOption = true;
break;
}
}
if (!bOnlyShowPasteOption)
{
bool bIsChildActorSubtreeNodeSelected = false;
TArray<UActorComponent*> SelectedComponents;
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = GetSelectedNodes();
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
// Get the current selected node reference
FSCSEditorTreeNodePtrType SelectedNodePtr = SelectedNodes[i];
check(SelectedNodePtr.IsValid());
// Get the component template associated with the selected node
UActorComponent* ComponentTemplate = SelectedNodePtr->GetComponentTemplate();
if (ComponentTemplate)
{
SelectedComponents.Add(ComponentTemplate);
}
// Determine if any selected node belongs to a child actor template
if (!bIsChildActorSubtreeNodeSelected)
{
bIsChildActorSubtreeNodeSelected = FChildActorComponentEditorUtils::IsChildActorSubtreeNode(SelectedNodePtr);
}
}
// Don't include these commands if any component was found above to belong to a child actor template (not supported at this time)
if (EditorMode == EComponentEditorMode::BlueprintSCS && !bIsChildActorSubtreeNodeSelected)
{
FToolMenuSection& BlueprintSCSSection = Menu->AddSection("BlueprintSCS");
if (SelectedItems.Num() == 1)
{
// Expandable menu: insert sub-menu here
BlueprintSCSSection.AddSubMenu(
FName("FindReferenceSubMenu"),
LOCTEXT("FindReferences_Label", "Find References"),
LOCTEXT("FindReferences_Tooltip", "Options for finding references to class members"),
FNewToolMenuChoice(FNewMenuDelegate::CreateStatic(&FGraphEditorCommands::BuildFindReferencesMenu))
);
}
// Create an "Add Event" option in the context menu only if we can edit
// the currently selected objects
if (IsEditingAllowed())
{
// Collect the classes of all selected objects
TArray<UClass*> SelectionClasses;
for (auto NodeIter = SelectedNodes.CreateConstIterator(); NodeIter; ++NodeIter)
{
FSCSEditorTreeNodePtrType TreeNode = *NodeIter;
if (UActorComponent* ComponentTemplate = TreeNode->GetComponentTemplate())
{
// If the component is native then we need to ensure it can actually be edited before we display it
if (!TreeNode->IsNativeComponent() || FComponentEditorUtils::GetPropertyForEditableNativeComponent(ComponentTemplate))
{
SelectionClasses.Add(ComponentTemplate->GetClass());
}
}
}
if (SelectionClasses.Num())
{
// Find the common base class of all selected classes
UClass* SelectedClass = UClass::FindCommonBase(SelectionClasses);
// Build an event submenu if we can generate events
if (FBlueprintEditorUtils::CanClassGenerateEvents(SelectedClass))
{
BlueprintSCSSection.AddSubMenu(
"AddEventSubMenu",
LOCTEXT("AddEventSubMenu", "Add Event"),
LOCTEXT("ActtionsSubMenu_ToolTip", "Add Event"),
FNewMenuDelegate::CreateStatic(&SSCSEditor::BuildMenuEventsSection,
GetBlueprint(), SelectedClass, FCanExecuteAction::CreateSP(this, &SSCSEditor::IsEditingAllowed),
FGetSelectedObjectsDelegate::CreateSP(this, &SSCSEditor::GetSelectedItemsForContextMenu)));
}
}
}
}
// Common menu options added for all component types
FComponentEditorUtils::FillComponentContextMenuOptions(Menu, SelectedComponents);
// For a selection outside of a child actor subtree, we may choose to include additional options
if (SelectedComponents.Num() == 1 && !bIsChildActorSubtreeNodeSelected)
{
// Extra options for a child actor component
if (UChildActorComponent* SelectedChildActorComponent = Cast<UChildActorComponent>(SelectedComponents[0]))
{
// These options will get added only in SCS mode
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
FChildActorComponentEditorUtils::FillComponentContextMenuOptions(Menu, SelectedChildActorComponent);
}
}
}
}
}
}
else
{
bOnlyShowPasteOption = true;
}
if (bOnlyShowPasteOption)
{
FToolMenuSection& Section = Menu->AddSection("PasteComponent", LOCTEXT("EditComponentHeading", "Edit"));
{
Section.AddMenuEntry(FGenericCommands::Get().Paste);
}
}
}
}
void SSCSEditor::RegisterContextMenu()
{
UToolMenus* ToolMenus = UToolMenus::Get();
if (!ToolMenus->IsMenuRegistered(SCS_ContextMenuName))
{
UToolMenu* Menu = ToolMenus->RegisterMenu(SCS_ContextMenuName);
Menu->AddDynamicSection("SCSEditorDynamic", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
USSCSEditorMenuContext* ContextObject = InMenu->FindContext<USSCSEditorMenuContext>();
if (ContextObject && ContextObject->SCSEditor.IsValid())
{
ContextObject->SCSEditor.Pin()->PopulateContextMenu(InMenu);
}
}));
}
}
TSharedPtr< SWidget > SSCSEditor::CreateContextMenu()
{
TArray<FSCSEditorTreeNodePtrType> SelectedItems = SCSTreeWidget->GetSelectedItems();
if (SelectedItems.Num() > 0 || CanPasteNodes())
{
RegisterContextMenu();
USSCSEditorMenuContext* ContextObject = NewObject<USSCSEditorMenuContext>();
ContextObject->SCSEditor = SharedThis(this);
FToolMenuContext ToolMenuContext(CommandList, TSharedPtr<FExtender>(), ContextObject);
return UToolMenus::Get()->GenerateWidget(SCS_ContextMenuName, ToolMenuContext);
}
return TSharedPtr<SWidget>();
}
void SSCSEditor::BuildMenuEventsSection(FMenuBuilder& Menu, UBlueprint* Blueprint, UClass* SelectedClass, FCanExecuteAction CanExecuteActionDelegate, FGetSelectedObjectsDelegate GetSelectedObjectsDelegate)
{
// Get Selected Nodes
TArray<FComponentEventConstructionData> SelectedNodes;
GetSelectedObjectsDelegate.ExecuteIfBound( SelectedNodes );
struct FMenuEntry
{
FText Label;
FText ToolTip;
FUIAction UIAction;
};
TArray< FMenuEntry > Actions;
TArray< FMenuEntry > NodeActions;
// Build Events entries
for (TFieldIterator<FMulticastDelegateProperty> PropertyIt(SelectedClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
FMulticastDelegateProperty* Property = *PropertyIt;
// Check for multicast delegates that we can safely assign
if (!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable))
{
FName EventName = Property->GetFName();
int32 ComponentEventViewEntries = 0;
// Add View Event Per Component
for (auto NodeIter = SelectedNodes.CreateConstIterator(); NodeIter; ++NodeIter )
{
if( NodeIter->Component.IsValid() )
{
FName VariableName = NodeIter->VariableName;
FObjectProperty* VariableProperty = FindFProperty<FObjectProperty>( Blueprint->SkeletonGeneratedClass, VariableName );
if( VariableProperty && FKismetEditorUtilities::FindBoundEventForComponent( Blueprint, EventName, VariableProperty->GetFName() ))
{
FMenuEntry NewEntry;
NewEntry.Label = ( SelectedNodes.Num() > 1 ) ? FText::Format( LOCTEXT("ViewEvent_ToolTipFor", "{0} for {1}"), FText::FromName( EventName ), FText::FromName( VariableName )) :
FText::Format( LOCTEXT("ViewEvent_ToolTip", "{0}"), FText::FromName( EventName ));
NewEntry.UIAction = FUIAction(FExecuteAction::CreateStatic( &SSCSEditor::ViewEvent, Blueprint, EventName, *NodeIter ), CanExecuteActionDelegate);
NodeActions.Add( NewEntry );
ComponentEventViewEntries++;
}
}
}
if( ComponentEventViewEntries < SelectedNodes.Num() )
{
// Create menu Add entry
FMenuEntry NewEntry;
NewEntry.Label = FText::Format( LOCTEXT("AddEvent_ToolTip", "Add {0}" ), FText::FromName( EventName ));
NewEntry.UIAction = FUIAction(FExecuteAction::CreateStatic( &SSCSEditor::CreateEventsForSelection, Blueprint, EventName, GetSelectedObjectsDelegate), CanExecuteActionDelegate);
Actions.Add( NewEntry );
}
}
}
// Build Menu Sections
Menu.BeginSection("AddComponentActions", LOCTEXT("AddEventHeader", "Add Event"));
for (auto ItemIter = Actions.CreateConstIterator(); ItemIter; ++ItemIter )
{
Menu.AddMenuEntry( ItemIter->Label, ItemIter->ToolTip, FSlateIcon(), ItemIter->UIAction );
}
Menu.EndSection();
Menu.BeginSection("ViewComponentActions", LOCTEXT("ViewEventHeader", "View Existing Events"));
for (auto ItemIter = NodeActions.CreateConstIterator(); ItemIter; ++ItemIter )
{
Menu.AddMenuEntry( ItemIter->Label, ItemIter->ToolTip, FSlateIcon(), ItemIter->UIAction );
}
Menu.EndSection();
}
void SSCSEditor::CreateEventsForSelection(UBlueprint* Blueprint, FName EventName, FGetSelectedObjectsDelegate GetSelectedObjectsDelegate)
{
if (EventName != NAME_None)
{
TArray<FComponentEventConstructionData> SelectedNodes;
GetSelectedObjectsDelegate.ExecuteIfBound(SelectedNodes);
for (auto SelectionIter = SelectedNodes.CreateConstIterator(); SelectionIter; ++SelectionIter)
{
ConstructEvent( Blueprint, EventName, *SelectionIter );
}
}
}
void SSCSEditor::ConstructEvent(UBlueprint* Blueprint, const FName EventName, const FComponentEventConstructionData EventData)
{
// Find the corresponding variable property in the Blueprint
FObjectProperty* VariableProperty = FindFProperty<FObjectProperty>(Blueprint->SkeletonGeneratedClass, EventData.VariableName );
if( VariableProperty )
{
if (!FKismetEditorUtilities::FindBoundEventForComponent(Blueprint, EventName, VariableProperty->GetFName()))
{
FKismetEditorUtilities::CreateNewBoundEventForComponent(EventData.Component.Get(), EventName, Blueprint, VariableProperty);
}
}
}
void SSCSEditor::ViewEvent(UBlueprint* Blueprint, const FName EventName, const FComponentEventConstructionData EventData)
{
// Find the corresponding variable property in the Blueprint
FObjectProperty* VariableProperty = FindFProperty<FObjectProperty>(Blueprint->SkeletonGeneratedClass, EventData.VariableName );
if( VariableProperty )
{
const UK2Node_ComponentBoundEvent* ExistingNode = FKismetEditorUtilities::FindBoundEventForComponent(Blueprint, EventName, VariableProperty->GetFName());
if (ExistingNode)
{
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingNode);
}
}
}
void SSCSEditor::OnFindReferences(bool bSearchAllBlueprints, const EGetFindReferenceSearchStringFlags Flags)
{
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = SCSTreeWidget->GetSelectedItems();
if (SelectedNodes.Num() == 1)
{
TSharedPtr<IToolkit> FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(GetBlueprint());
if (FoundAssetEditor.IsValid())
{
const FString VariableName = SelectedNodes[0]->GetVariableName().ToString();
FMemberReference MemberReference;
MemberReference.SetSelfMember(*VariableName);
const FString SearchTerm = EnumHasAnyFlags(Flags, EGetFindReferenceSearchStringFlags::UseSearchSyntax) ? MemberReference.GetReferenceSearchString(GetBlueprint()->SkeletonGeneratedClass) : FString::Printf(TEXT("\"%s\""), *VariableName);;
TSharedRef<IBlueprintEditor> BlueprintEditor = StaticCastSharedRef<IBlueprintEditor>(FoundAssetEditor.ToSharedRef());
const bool bSetFindWithinBlueprint = !bSearchAllBlueprints;
BlueprintEditor->SummonSearchUI(true, SearchTerm);
}
}
}
bool SSCSEditor::CanDuplicateComponent() const
{
if(!IsEditingAllowed())
{
return false;
}
// @todo - Allow duplication of components that belong to a child actor template? For now, we don't support this.
return CanCopyNodes() && !FChildActorComponentEditorUtils::ContainsChildActorSubtreeNode(SCSTreeWidget->GetSelectedItems());
}
void SSCSEditor::OnDuplicateComponent()
{
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = SCSTreeWidget->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
// Force the text box being edited (if any) to commit its text. The duplicate operation may trigger a regeneration of the tree view,
// releasing all row widgets. If one row was in edit mode (rename/rename on create), it was released before losing the focus and
// this would prevent the completion of the 'rename' or 'create + give initial name' transaction (occurring on focus lost).
FSlateApplication::Get().ClearKeyboardFocus();
const FScopedTransaction Transaction(SelectedNodes.Num() > 1 ? LOCTEXT("DuplicateComponents", "Duplicate Components") : LOCTEXT("DuplicateComponent", "Duplicate Component"));
FAddNewComponentParams NewComponentParams;
NewComponentParams.bConformTransformToParent = false;
TMap<USceneComponent*, USceneComponent*> DuplicateSceneComponentMap;
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
if (UActorComponent* ComponentTemplate = SelectedNodes[i]->GetComponentTemplate())
{
USCS_Node* SCSNode = SelectedNodes[i]->GetSCSNode();
check(SCSNode == nullptr || SCSNode->ComponentTemplate == ComponentTemplate);
UActorComponent* CloneComponent = AddNewComponent(ComponentTemplate->GetClass(), (SCSNode ? (UObject*)SCSNode : ComponentTemplate), NewComponentParams);
if (USceneComponent* SceneClone = Cast<USceneComponent>(CloneComponent))
{
DuplicateSceneComponentMap.Add(CastChecked<USceneComponent>(ComponentTemplate), SceneClone);
}
}
}
for (const TPair<USceneComponent*,USceneComponent*>& DuplicatedPair : DuplicateSceneComponentMap)
{
USceneComponent* OriginalComponent = DuplicatedPair.Key;
USceneComponent* NewSceneComponent = DuplicatedPair.Value;
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
// Ensure that any native attachment relationship inherited from the original copy is removed (to prevent a GLEO assertion)
NewSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
}
// Attempt to locate the original node in the SCS tree
FSCSEditorTreeNodePtrType OriginalNodePtr = FindTreeNode(OriginalComponent);
if(OriginalNodePtr.IsValid())
{
// If we're duplicating the root then we're already a child of it so need to reparent, but we do need to reset the scale
// otherwise we'll end up with the square of the root's scale instead of being the same size.
if (OriginalNodePtr == GetSceneRootNode())
{
NewSceneComponent->SetRelativeScale3D_Direct(FVector(1.f));
}
else
{
// If the original node was parented, attempt to add the duplicate as a child of the same parent node if the parent is not
// part of the duplicate set, otherwise parent to the parent's duplicate
FSCSEditorTreeNodePtrType ParentNodePtr = OriginalNodePtr->GetParent();
if (ParentNodePtr.IsValid())
{
if (USceneComponent** ParentDuplicateComponent = DuplicateSceneComponentMap.Find(Cast<USceneComponent>(ParentNodePtr->GetComponentTemplate())))
{
FSCSEditorTreeNodePtrType DuplicateParentNodePtr = FindTreeNode(*ParentDuplicateComponent);
if (DuplicateParentNodePtr.IsValid())
{
ParentNodePtr = DuplicateParentNodePtr;
}
}
// Locate the duplicate node (as a child of the current scene root node), and switch it to be a child of the original node's parent
FSCSEditorTreeNodePtrType NewChildNodePtr = GetSceneRootNode()->FindChild(NewSceneComponent, true);
if (NewChildNodePtr.IsValid())
{
// Note: This method will handle removal from the scene root node as well
ParentNodePtr->AddChild(NewChildNodePtr);
}
}
}
}
}
}
}
void SSCSEditor::OnGetChildrenForTree( FSCSEditorTreeNodePtrType InNodePtr, TArray<FSCSEditorTreeNodePtrType>& OutChildren )
{
if (InNodePtr.IsValid())
{
const TArray<FSCSEditorTreeNodePtrType>& Children = InNodePtr->GetChildren();
OutChildren.Reserve(Children.Num());
if (GetComponentTypeFilterToApply() || !GetFilterText().IsEmpty())
{
for (FSCSEditorTreeNodePtrType Child : Children)
{
if (!Child->IsFlaggedForFiltration())
{
OutChildren.Add(Child);
}
}
}
else
{
OutChildren = Children;
}
}
else
{
OutChildren.Empty();
}
}
UActorComponent* SSCSEditor::PerformComboAddClass(TSubclassOf<UActorComponent> ComponentClass, EComponentCreateAction::Type ComponentCreateAction, UObject* AssetOverride)
{
UClass* NewClass = ComponentClass;
UActorComponent* NewComponent = nullptr;
if( ComponentCreateAction == EComponentCreateAction::CreateNewCPPClass )
{
NewClass = CreateNewCPPComponent( ComponentClass );
}
else if( ComponentCreateAction == EComponentCreateAction::CreateNewBlueprintClass )
{
NewClass = CreateNewBPComponent( ComponentClass );
}
if( NewClass != nullptr )
{
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
USelection* Selection = GEditor->GetSelectedObjects();
bool bAddedComponent = false;
// This adds components according to the type selected in the drop down. If the user
// has the appropriate objects selected in the content browser then those are added,
// else we go down the previous route of adding components by type.
//
// Furthermore don't try to match up assets for USceneComponent it will match lots of things and doesn't have any nice behavior for asset adds
if (Selection->Num() > 0 && !AssetOverride && NewClass != USceneComponent::StaticClass())
{
for(FSelectionIterator ObjectIter(*Selection); ObjectIter; ++ObjectIter)
{
UObject* Object = *ObjectIter;
UClass* Class = Object->GetClass();
TArray< TSubclassOf<UActorComponent> > ComponentClasses = FComponentAssetBrokerage::GetComponentsForAsset(Object);
// if the selected asset supports the selected component type then go ahead and add it
for(int32 ComponentIndex = 0; ComponentIndex < ComponentClasses.Num(); ComponentIndex++)
{
if(ComponentClasses[ComponentIndex]->IsChildOf(NewClass))
{
NewComponent = AddNewComponent(NewClass, Object);
bAddedComponent = true;
break;
}
}
}
}
if(!bAddedComponent)
{
// As the SCS splits up the scene and actor components, can now add directly
NewComponent = AddNewComponent(NewClass, AssetOverride);
}
UpdateTree();
}
return NewComponent;
}
TArray<FSCSEditorTreeNodePtrType> SSCSEditor::GetSelectedNodes() const
{
TArray<FSCSEditorTreeNodePtrType> SelectedTreeNodes = SCSTreeWidget->GetSelectedItems();
struct FCompareSelectedSCSEditorTreeNodes
{
FORCEINLINE bool operator()(const FSCSEditorTreeNodePtrType& A, const FSCSEditorTreeNodePtrType& B) const
{
return B.IsValid() && B->IsAttachedTo(A);
}
};
// Ensure that nodes are ordered from parent to child (otherwise they are sorted in the order that they were selected)
SelectedTreeNodes.Sort(FCompareSelectedSCSEditorTreeNodes());
return SelectedTreeNodes;
}
FSCSEditorTreeNodePtrType SSCSEditor::GetNodeFromActorComponent(const UActorComponent* ActorComponent, bool bIncludeAttachedComponents) const
{
FSCSEditorTreeNodePtrType NodePtr;
if(ActorComponent)
{
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
// If the given component instance is not already an archetype object
if (!ActorComponent->IsTemplate())
{
// Get the component owner's class object
check(ActorComponent->GetOwner() != NULL);
UClass* OwnerClass = ActorComponent->GetOwner()->GetClass();
// If the given component is one that's created during Blueprint construction
if (ActorComponent->IsCreatedByConstructionScript())
{
// Check the entire Class hierarchy for the node
TArray<UBlueprintGeneratedClass*> ParentBPStack;
UBlueprint::GetBlueprintHierarchyFromClass(OwnerClass, ParentBPStack);
for(int32 StackIndex = ParentBPStack.Num() - 1; StackIndex >= 0; --StackIndex)
{
USimpleConstructionScript* ParentSCS = ParentBPStack[StackIndex] ? ParentBPStack[StackIndex]->SimpleConstructionScript.Get() : nullptr;
if (ParentSCS)
{
// Attempt to locate an SCS node with a variable name that matches the name of the given component
for (USCS_Node* SCS_Node : ParentSCS->GetAllNodes())
{
check(SCS_Node != NULL);
if (SCS_Node->GetVariableName() == ActorComponent->GetFName())
{
// We found a match; redirect to the component archetype instance that may be associated with a tree node
ActorComponent = SCS_Node->ComponentTemplate;
break;
}
}
}
}
}
else
{
// Get the class default object
const AActor* CDO = Cast<AActor>(OwnerClass->GetDefaultObject());
if (CDO)
{
// Iterate over the Components array and attempt to find a component with a matching name
for (UActorComponent* ComponentTemplate : CDO->GetComponents())
{
if (ComponentTemplate && ComponentTemplate->GetFName() == ActorComponent->GetFName())
{
// We found a match; redirect to the component archetype instance that may be associated with a tree node
ActorComponent = ComponentTemplate;
break;
}
}
}
}
}
}
// If we have a valid component archetype instance, attempt to find a tree node that corresponds to it
const TArray<FSCSEditorTreeNodePtrType>& Nodes = GetRootNodes();
for (int32 i = 0; i < Nodes.Num() && !NodePtr.IsValid(); i++)
{
NodePtr = FindTreeNode(ActorComponent, Nodes[i]);
}
// If we didn't find it in the tree, step up the chain to the parent of the given component and recursively see if that is in the tree (unless the flag is false)
if(!NodePtr.IsValid() && bIncludeAttachedComponents)
{
const USceneComponent* SceneComponent = Cast<const USceneComponent>(ActorComponent);
if(SceneComponent && SceneComponent->GetAttachParent())
{
return GetNodeFromActorComponent(SceneComponent->GetAttachParent(), bIncludeAttachedComponents);
}
}
}
return NodePtr;
}
void SSCSEditor::SelectRoot()
{
const TArray<FSCSEditorTreeNodePtrType>& Nodes = GetRootNodes();
if (Nodes.Num() > 0)
{
SCSTreeWidget->SetSelection(Nodes[0]);
}
}
void SSCSEditor::SelectNode(FSCSEditorTreeNodePtrType InNodeToSelect, bool IsCntrlDown)
{
if(SCSTreeWidget.IsValid() && InNodeToSelect.IsValid())
{
if(!IsCntrlDown)
{
SCSTreeWidget->SetSelection(InNodeToSelect);
}
else
{
SCSTreeWidget->SetItemSelection(InNodeToSelect, !SCSTreeWidget->IsItemSelected(InNodeToSelect));
}
}
}
void SSCSEditor::SetNodeExpansionState(FSCSEditorTreeNodePtrType InNodeToChange, const bool bIsExpanded)
{
if(SCSTreeWidget.IsValid() && InNodeToChange.IsValid())
{
SCSTreeWidget->SetItemExpansion(InNodeToChange, bIsExpanded);
}
}
static FSCSEditorTreeNode* FindRecursive( FSCSEditorTreeNode* Node, FName Name )
{
if (Node->GetVariableName() == Name)
{
return Node;
}
else
{
for (const FSCSEditorTreeNodePtrType& Child : Node->GetChildren())
{
if (FSCSEditorTreeNode* Result = FindRecursive(Child.Get(), Name))
{
return Result;
}
}
}
return nullptr;
}
void SSCSEditor::HighlightTreeNode(FName TreeNodeName, const class FPropertyPath& Property)
{
for( const FSCSEditorTreeNodePtrType& Node : GetRootNodes() )
{
if( FSCSEditorTreeNode* FoundNode = FindRecursive( Node.Get(), TreeNodeName ) )
{
SelectNode(FoundNode->AsShared(), false);
if (Property != FPropertyPath())
{
// Invoke the delegate to highlight the property
OnHighlightPropertyInDetailsView.ExecuteIfBound(Property);
}
return;
}
}
ClearSelection();
}
void SSCSEditor::HighlightTreeNode(const USCS_Node* Node, FName Property)
{
check(Node);
FSCSEditorTreeNodePtrType TreeNode = FindTreeNode( Node );
check( TreeNode.IsValid() );
SelectNode( TreeNode, false );
if( Property != FName() )
{
UActorComponent* Component = TreeNode->GetComponentTemplate();
FProperty* CurrentProp = FindFProperty<FProperty>(Component->GetClass(), Property);
FPropertyPath Path;
if( CurrentProp )
{
FPropertyInfo NewInfo(CurrentProp, -1);
Path.ExtendPath(NewInfo);
}
// Invoke the delegate to highlight the property
OnHighlightPropertyInDetailsView.ExecuteIfBound( Path );
}
}
void SSCSEditor::BuildSubTreeForActorNode(FSCSEditorActorNodePtrType InActorNode)
{
if (!InActorNode.IsValid())
{
return;
}
// Get the actor instance that we're editing
const AActor* Actor = InActorNode->GetObject<AActor>();
if (!Actor)
{
return;
}
// Build the tree data source according to what mode we're in
if (!InActorNode->IsInstanced())
{
TInlineComponentArray<UActorComponent*> Components;
Actor->GetComponents(Components);
// Add the native root component
USceneComponent* RootComponent = Actor->GetRootComponent();
if (RootComponent != nullptr)
{
Components.Remove(RootComponent);
AddTreeNodeFromComponent(RootComponent, FindOrCreateParentForExistingComponent(RootComponent, InActorNode));
}
// Add the rest of the native base class SceneComponent hierarchy
for (UActorComponent* Component : Components)
{
AddTreeNodeFromComponent(Component, FindOrCreateParentForExistingComponent(Component, InActorNode));
}
// If it's a Blueprint-generated class, also get the inheritance stack
TArray<UBlueprintGeneratedClass*> ParentBPStack;
UBlueprint::GetBlueprintHierarchyFromClass(Actor->GetClass(), ParentBPStack);
UBlueprint* ActorBP = (ParentBPStack.Num() > 0 && ParentBPStack[0]) ? Cast<UBlueprint>(ParentBPStack[0]->ClassGeneratedBy) : nullptr;
ensure(ActorBP);
// Add the full SCS tree node hierarchy (including SCS nodes inherited from parent blueprints)
for (int32 StackIndex = ParentBPStack.Num() - 1; StackIndex >= 0; --StackIndex)
{
USimpleConstructionScript* ParentSCS = ParentBPStack[StackIndex] ? ParentBPStack[StackIndex]->SimpleConstructionScript.Get() : nullptr;
if (ParentSCS)
{
for (USCS_Node* SCS_Node : ParentSCS->GetRootNodes())
{
check(SCS_Node);
FSCSEditorTreeNodePtrType NewNodePtr;
if (SCS_Node->ParentComponentOrVariableName != NAME_None)
{
USceneComponent* ParentComponent = SCS_Node->GetParentComponentTemplate(ActorBP);
if (ParentComponent)
{
FSCSEditorTreeNodePtrType ParentNodePtr = FindTreeNode(ParentComponent, InActorNode);
if (ParentNodePtr.IsValid())
{
NewNodePtr = AddTreeNode(SCS_Node, ParentNodePtr, StackIndex > 0);
}
}
}
else
{
NewNodePtr = AddTreeNode(SCS_Node, InActorNode, StackIndex > 0);
}
// Only necessary to do the following for inherited nodes (StackIndex > 0).
if (NewNodePtr.IsValid() && StackIndex > 0)
{
// This call creates ICH override templates for the current Blueprint. Without this, the parent node
// search above can fail when attempting to match an inherited node in the tree via component template.
NewNodePtr->GetOrCreateEditableComponentTemplate(ActorBP);
for (FSCSEditorTreeNodePtrType ChildNodePtr : NewNodePtr->GetChildren())
{
if (ensure(ChildNodePtr.IsValid()))
{
ChildNodePtr->GetOrCreateEditableComponentTemplate(ActorBP);
}
}
}
}
}
}
}
else // InActorNode->IsInstanced()
{
// Get the full set of instanced components
TSet<UActorComponent*> ComponentsToAdd(Actor->GetComponents());
const bool bHideConstructionScriptComponentsInDetailsView = GetDefault<UBlueprintEditorSettings>()->bHideConstructionScriptComponentsInDetailsView;
auto ShouldAddInstancedActorComponent = [bHideConstructionScriptComponentsInDetailsView](UActorComponent* ActorComp, USceneComponent* ParentSceneComp)
{
// Exclude nested DSOs attached to BP-constructed instances, which are not mutable.
return (ActorComp != nullptr
&& (!ActorComp->IsVisualizationComponent())
&& (ActorComp->CreationMethod != EComponentCreationMethod::UserConstructionScript || !bHideConstructionScriptComponentsInDetailsView)
&& (ParentSceneComp == nullptr || !ParentSceneComp->IsCreatedByConstructionScript() || !ActorComp->HasAnyFlags(RF_DefaultSubObject)))
&& (ActorComp->CreationMethod != EComponentCreationMethod::Native || FComponentEditorUtils::GetPropertyForEditableNativeComponent(ActorComp));
};
for (TSet<UActorComponent*>::TIterator It(ComponentsToAdd.CreateIterator()); It; ++It)
{
UActorComponent* ActorComp = *It;
USceneComponent* SceneComp = Cast<USceneComponent>(ActorComp);
USceneComponent* ParentSceneComp = SceneComp != nullptr ? SceneComp->GetAttachParent() : nullptr;
if (!ShouldAddInstancedActorComponent(ActorComp, ParentSceneComp))
{
It.RemoveCurrent();
}
}
TFunction<void(USceneComponent*, FSCSEditorTreeNodePtrType)> AddInstancedTreeNodesRecursive = [&](USceneComponent* Component, FSCSEditorTreeNodePtrType TreeNode)
{
if (Component != nullptr)
{
TArray<USceneComponent*> Components = Component->GetAttachChildren();
for (USceneComponent* ChildComponent : Components)
{
if (ComponentsToAdd.Contains(ChildComponent)
&& ChildComponent->GetOwner() == Component->GetOwner())
{
ComponentsToAdd.Remove(ChildComponent);
FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(ChildComponent, TreeNode);
AddInstancedTreeNodesRecursive(ChildComponent, NewParentNode);
}
}
}
};
// Add the root component first (it may not be the first one)
USceneComponent* RootComponent = Actor->GetRootComponent();
if (RootComponent != nullptr)
{
ComponentsToAdd.Remove(RootComponent);
// Recursively add any instanced children that are already attached through the root, and keep track of added
// instances. This will be a faster path than the loop below, because we create new parent tree nodes as we go.
FSCSEditorTreeNodePtrType NewParentNode = AddTreeNodeFromComponent(RootComponent, FindOrCreateParentForExistingComponent(RootComponent, InActorNode));
AddInstancedTreeNodesRecursive(RootComponent, NewParentNode);
}
// Sort components by type (always put scene components first in the tree)
ComponentsToAdd.Sort([](const UActorComponent& A, const UActorComponent& /* B */)
{
return A.IsA<USceneComponent>();
});
// Now add any remaining instanced owned components not already added above. This will first add any
// unattached scene components followed by any instanced non-scene components owned by the Actor instance.
for (UActorComponent* ActorComp : ComponentsToAdd)
{
AddTreeNodeFromComponent(ActorComp, FindOrCreateParentForExistingComponent(ActorComp, InActorNode));
}
}
// Always expand actor nodes to reveal children
SCSTreeWidget->SetItemExpansion(InActorNode, true);
}
void SSCSEditor::UpdateTree(bool bRegenerateTreeNodes)
{
check(SCSTreeWidget.IsValid());
// Early exit if we're deferring tree updates
if(!bAllowTreeUpdates)
{
return;
}
if(bRegenerateTreeNodes)
{
// Obtain the set of expandable tree nodes that are currently collapsed
TSet<FSCSEditorTreeNodePtrType> CollapsedTreeNodes;
GetCollapsedNodes(GetSceneRootNode(), CollapsedTreeNodes);
// Obtain the list of selected items
TArray<FSCSEditorTreeNodePtrType> SelectedTreeNodes = SCSTreeWidget->GetSelectedItems();
// Clear the current tree
if (SelectedTreeNodes.Num() != 0)
{
SCSTreeWidget->ClearSelection();
}
RootNodes.Empty();
TSharedPtr<FSCSEditorTreeNodeRootActor> RootActorNode = MakeShareable(new FSCSEditorTreeNodeRootActor(GetActorContext(), EditorMode == EComponentEditorMode::ActorInstance));
RefreshFilteredState(RootActorNode, false);
SCSTreeWidget->SetItemExpansion(RootActorNode, true);
RootNodes.Add(RootActorNode);
BuildSubTreeForActorNode(RootActorNode);
AActor* PreviewActorInstance = PreviewActor.Get();
if (PreviewActorInstance != nullptr && !GetDefault<UBlueprintEditorSettings>()->bHideConstructionScriptComponentsInDetailsView)
{
TInlineComponentArray<UActorComponent*> Components;
PreviewActorInstance->GetComponents(Components);
for (UActorComponent* Component : Components)
{
if (Component->CreationMethod == EComponentCreationMethod::UserConstructionScript)
{
AddTreeNodeFromComponent(Component, FindOrCreateParentForExistingComponent(Component, RootActorNode));
}
}
}
// Restore the previous expansion state on the new tree nodes
TArray<FSCSEditorTreeNodePtrType> CollapsedTreeNodeArray = CollapsedTreeNodes.Array();
for(int32 i = 0; i < CollapsedTreeNodeArray.Num(); ++i)
{
// Look for a component match in the new hierarchy; if found, mark it as collapsed to match the previous setting
FSCSEditorTreeNodePtrType NodeToExpandPtr = FindTreeNode(CollapsedTreeNodeArray[i]->GetComponentTemplate());
if(NodeToExpandPtr.IsValid())
{
SCSTreeWidget->SetItemExpansion(NodeToExpandPtr, false);
}
}
if(SelectedTreeNodes.Num() > 0)
{
// If there is only one item selected, imitate user selection to preserve navigation
ESelectInfo::Type SelectInfo = SelectedTreeNodes.Num() == 1 ? ESelectInfo::OnMouseClick : ESelectInfo::Direct;
// Restore the previous selection state on the new tree nodes
for (int i = 0; i < SelectedTreeNodes.Num(); ++i)
{
if (SelectedTreeNodes[i]->GetNodeType() == FSCSEditorTreeNode::RootActorNode)
{
SCSTreeWidget->SetItemSelection(RootActorNode, true, SelectInfo);
}
else
{
FSCSEditorTreeNodePtrType NodeToSelectPtr = FindTreeNode(SelectedTreeNodes[i]->GetComponentTemplate());
if (NodeToSelectPtr.IsValid())
{
SCSTreeWidget->SetItemSelection(NodeToSelectPtr, true, SelectInfo);
}
}
}
if (GetEditorMode() != EComponentEditorMode::BlueprintSCS)
{
TArray<FSCSEditorTreeNodePtrType> NewSelectedTreeNodes = SCSTreeWidget->GetSelectedItems();
if (NewSelectedTreeNodes.Num() == 0)
{
SCSTreeWidget->SetItemSelection(GetRootNodes()[0], true);
}
}
}
// If we have a pending deferred rename request, redirect it to the new tree node
if(DeferredRenameRequest != NAME_None)
{
FSCSEditorTreeNodePtrType NodeToRenamePtr = FindTreeNode(DeferredRenameRequest);
if(NodeToRenamePtr.IsValid())
{
SCSTreeWidget->RequestScrollIntoView(NodeToRenamePtr);
}
}
}
// refresh widget
SCSTreeWidget->RequestTreeRefresh();
}
void SSCSEditor::DumpTree()
{
/* Example:
[ACTOR] MyBlueprint (self)
|
[SEPARATOR]
|
DefaultSceneRoot (Inherited)
|
+- StaticMesh (Inherited)
| |
| +- Scene4 (Inherited)
| |
| +- Scene (Inherited)
| |
| +- Scene1 (Inherited)
|
+- Scene2 (Inherited)
| |
| +- Scene3 (Inherited)
|
[SEPARATOR]
|
ProjectileMovement (Inherited)
*/
UE_LOG(LogSCSEditor, Log, TEXT("---------------------"));
UE_LOG(LogSCSEditor, Log, TEXT(" STreeView NODE DUMP"));
UE_LOG(LogSCSEditor, Log, TEXT("---------------------"));
const UBlueprint* BlueprintContext = nullptr;
const AActor* ActorInstance = GetActorContext();
if (ActorInstance)
{
BlueprintContext = UBlueprint::GetBlueprintFromClass(ActorInstance->GetClass());
}
TArray<TArray<FSCSEditorTreeNodePtrType>> NodeListStack;
NodeListStack.Push(RootNodes);
auto LineSpacingLambda = [&NodeListStack](const TArray<FSCSEditorTreeNodePtrType>& NodeList, int32 CurrentDepth, const FString& Prefix)
{
bool bAddLineSpacing = false;
for (int Depth = 0; Depth <= CurrentDepth && !bAddLineSpacing; ++Depth)
{
bAddLineSpacing = NodeListStack[Depth].Num() > 0;
}
if (bAddLineSpacing)
{
UE_LOG(LogSCSEditor, Log, TEXT(" %s%s"), *Prefix, NodeList.Num() > 0 ? TEXT("|") : TEXT(""));
}
};
while (NodeListStack.Num() > 0)
{
const int32 CurrentDepth = NodeListStack.Num() - 1;
TArray<FSCSEditorTreeNodePtrType>& NodeList = NodeListStack[CurrentDepth];
if (NodeList.Num() > 0)
{
FString Prefix;
for (int32 Depth = 1; Depth < CurrentDepth; ++Depth)
{
int32 NodeCount = NodeListStack[Depth].Num();
if (Depth == 1)
{
NodeCount += NodeListStack[0].Num();
}
Prefix += (NodeCount > 0) ? TEXT("| ") : TEXT(" ");
}
FString NodePrefix;
if (CurrentDepth > 0)
{
NodePrefix = TEXT("+- ");
}
FSCSEditorTreeNodePtrType Node = NodeList[0];
NodeList.RemoveAt(0);
if (Node.IsValid())
{
FString NodeLabel = TEXT("[UNKNOWN]");
switch (Node->GetNodeType())
{
case FSCSEditorTreeNode::ENodeType::RootActorNode:
switch (EditorMode)
{
case EComponentEditorMode::ActorInstance:
NodeLabel = TEXT("[ACTOR]");
break;
case EComponentEditorMode::BlueprintSCS:
NodeLabel = TEXT("[BLUEPRINT]");
break;
}
if (BlueprintContext)
{
NodeLabel += FString::Printf(TEXT(" %s (self)"), *BlueprintContext->GetName());
}
else if (ActorInstance)
{
NodeLabel += FString::Printf(TEXT(" %s (Instance)"), *ActorInstance->GetActorLabel());
}
break;
case FSCSEditorTreeNode::ENodeType::SeparatorNode:
NodeLabel = TEXT("[SEPARATOR]");
break;
case FSCSEditorTreeNode::ENodeType::ComponentNode:
NodeLabel = Node->GetDisplayString();
if (Node->IsInheritedComponent())
{
NodeLabel += TEXT(" (Inherited)");
}
break;
case FSCSEditorTreeNode::ENodeType::ChildActorNode:
NodeLabel = Node->GetDisplayString();
NodeLabel += TEXT(" [CHILD ACTOR]");
break;
}
UE_LOG(LogSCSEditor, Log, TEXT(" %s%s%s"), *Prefix, *NodePrefix, *NodeLabel);
const TArray<FSCSEditorTreeNodePtrType>& Children = Node->GetChildren();
if (Children.Num() > 0)
{
if (CurrentDepth > 1)
{
UE_LOG(LogSCSEditor, Log, TEXT(" %s%s|"), *Prefix, NodeListStack[CurrentDepth].Num() > 0 ? TEXT("| ") : TEXT(" "));
}
else if (CurrentDepth == 1)
{
UE_LOG(LogSCSEditor, Log, TEXT(" %s%s|"), *Prefix, NodeListStack[0].Num() > 0 ? TEXT("| ") : TEXT(" "));
}
else
{
UE_LOG(LogSCSEditor, Log, TEXT(" %s|"), *Prefix);
}
NodeListStack.Push(Children);
}
else
{
LineSpacingLambda(NodeList, CurrentDepth, Prefix);
}
}
else
{
UE_LOG(LogSCSEditor, Log, TEXT(" %s%s[INVALID]"), *Prefix, *NodePrefix);
LineSpacingLambda(NodeList, CurrentDepth, Prefix);
}
}
else
{
NodeListStack.Pop();
}
}
UE_LOG(LogSCSEditor, Log, TEXT("--------(end)--------"));
}
const TArray<FSCSEditorTreeNodePtrType>& SSCSEditor::GetRootNodes() const
{
return RootNodes;
}
FSCSEditorActorNodePtrType SSCSEditor::GetActorNode() const
{
if (RootNodes.Num() > 0)
{
return StaticCastSharedPtr<FSCSEditorTreeNodeRootActor>(RootNodes[0]);
}
return FSCSEditorActorNodePtrType();
}
FSCSEditorTreeNodePtrType SSCSEditor::GetSceneRootNode() const
{
FSCSEditorActorNodePtrType ActorNode = GetActorNode();
if (ActorNode.IsValid())
{
return ActorNode->GetSceneRootNode();
}
return FSCSEditorTreeNodePtrType();
}
void SSCSEditor::SetSceneRootNode(FSCSEditorTreeNodePtrType NewSceneRootNode)
{
GetActorNode()->SetSceneRootNode(NewSceneRootNode);
}
class FComponentClassParentFilter : public IClassViewerFilter
{
public:
FComponentClassParentFilter(const TSubclassOf<UActorComponent>& InComponentClass) : ComponentClass(InComponentClass) {}
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
return InClass->IsChildOf(ComponentClass);
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
return InUnloadedClassData->IsChildOf(ComponentClass);
}
TSubclassOf<UActorComponent> ComponentClass;
};
typedef FComponentClassParentFilter FNativeComponentClassParentFilter;
class FBlueprintComponentClassParentFilter : public FComponentClassParentFilter
{
public:
FBlueprintComponentClassParentFilter(const TSubclassOf<UActorComponent>& InComponentClass) : FComponentClassParentFilter(InComponentClass) {}
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
return FComponentClassParentFilter::IsClassAllowed(InInitOptions, InClass, InFilterFuncs) && FKismetEditorUtilities::CanCreateBlueprintOfClass(InClass);
}
};
UClass* SSCSEditor::CreateNewCPPComponent( TSubclassOf<UActorComponent> ComponentClass )
{
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(SharedThis(this));
FString AddedClassName;
auto OnCodeAddedToProject = [&AddedClassName](const FString& ClassName, const FString& ClassPath, const FString& ModuleName)
{
if(!ClassName.IsEmpty() && !ClassPath.IsEmpty())
{
AddedClassName = FString::Printf(TEXT("/Script/%s.%s"), *ModuleName, *ClassName);
}
};
FGameProjectGenerationModule::Get().OpenAddCodeToProjectDialog(
FAddToProjectConfig()
.WindowTitle(LOCTEXT("AddNewC++Component", "Add C++ Component"))
.ParentWindow(ParentWindow)
.Modal()
.OnAddedToProject(FOnAddedToProject::CreateLambda(OnCodeAddedToProject))
.FeatureComponentClasses()
.AllowableParents(MakeShareable( new FNativeComponentClassParentFilter(ComponentClass) ))
.DefaultClassPrefix(TEXT("New"))
);
return LoadClass<UActorComponent>(nullptr, *AddedClassName, nullptr, LOAD_None, nullptr);
}
UClass* SSCSEditor::CreateNewBPComponent(TSubclassOf<UActorComponent> ComponentClass)
{
UClass* NewClass = nullptr;
auto OnAddedToProject = [&](const FString& ClassName, const FString& PackagePath, const FString& ModuleName)
{
if(!ClassName.IsEmpty() && !PackagePath.IsEmpty())
{
if (UPackage* Package = FindPackage(nullptr, *PackagePath))
{
if (UBlueprint* NewBP = FindObjectFast<UBlueprint>(Package, *ClassName))
{
NewClass = NewBP->GeneratedClass;
TArray<UObject*> Objects;
Objects.Emplace(NewBP);
GEditor->SyncBrowserToObjects(Objects);
// Open the editor for the new blueprint
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(NewBP);
}
}
}
};
FGameProjectGenerationModule::Get().OpenAddBlueprintToProjectDialog(
FAddToProjectConfig()
.WindowTitle(LOCTEXT("AddNewBlueprintComponent", "Add Blueprint Component"))
.ParentWindow(FSlateApplication::Get().FindWidgetWindow(SharedThis(this)))
.Modal()
.AllowableParents(MakeShareable( new FBlueprintComponentClassParentFilter(ComponentClass) ))
.FeatureComponentClasses()
.OnAddedToProject(FOnAddedToProject::CreateLambda(OnAddedToProject))
.DefaultClassPrefix(TEXT("New"))
);
return NewClass;
}
void SSCSEditor::ClearSelection()
{
if ( bUpdatingSelection == false )
{
check(SCSTreeWidget.IsValid());
SCSTreeWidget->ClearSelection();
}
}
void SSCSEditor::SaveSCSCurrentState( USimpleConstructionScript* SCSObj )
{
if (SCSObj)
{
SCSObj->SaveToTransactionBuffer();
}
}
void SSCSEditor::SaveSCSNode( USCS_Node* Node )
{
if (Node)
{
Node->SaveToTransactionBuffer();
}
}
bool SSCSEditor::IsEditingAllowed() const
{
return AllowEditing.Get() && nullptr == GEditor->PlayWorld;
}
UActorComponent* SSCSEditor::AddNewComponent( UClass* NewComponentClass, UObject* Asset, const FAddNewComponentParams Params)
{
if (NewComponentClass->ClassWithin && NewComponentClass->ClassWithin != UObject::StaticClass())
{
FNotificationInfo Info(LOCTEXT("AddComponentFailed", "Cannot add components that have \"Within\" markup"));
Info.Image = FAppStyle::GetBrush(TEXT("Icons.Error"));
Info.bFireAndForget = true;
Info.bUseSuccessFailIcons = false;
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info);
return nullptr;
}
// If an 'add' transaction is ongoing, it is most likely because AddNewComponent() is being called in a tight loop inside a larger transaction (e.g. 'duplicate')
// and bSetFocusToNewItem was true for each element.
if (DeferredOngoingCreateTransaction.IsValid() && Params.bSetFocusToNewItem)
{
// Close the ongoing 'add' sub-transaction before staring another one. The user will not be able to edit the name of that component because the
// new component is going to still focus.
DeferredOngoingCreateTransaction.Reset();
}
// Begin a transaction. The transaction will end when the component name will be provided/confirmed by the user.
TUniquePtr<FScopedTransaction> AddTransaction = MakeUnique<FScopedTransaction>( LOCTEXT("AddComponent", "Add Component") );
UActorComponent* NewComponent = nullptr;
FName TemplateVariableName;
USCS_Node* SCSNode = Cast<USCS_Node>(Asset);
UActorComponent* ComponentTemplate = (SCSNode ? ToRawPtr(SCSNode->ComponentTemplate) : Cast<UActorComponent>(Asset));
if (SCSNode)
{
TemplateVariableName = SCSNode->GetVariableName();
Asset = nullptr;
}
else if (ComponentTemplate)
{
Asset = nullptr;
}
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr);
Blueprint->Modify();
SaveSCSCurrentState(Blueprint->SimpleConstructionScript);
// Defer Blueprint class regeneration and tree updates until after we copy any object properties from a source template.
const bool bMarkBlueprintModified = false;
bAllowTreeUpdates = false;
FName NewVariableName;
if (ComponentTemplate)
{
if (!TemplateVariableName.IsNone())
{
NewVariableName = TemplateVariableName;
}
else
{
FString TemplateName = ComponentTemplate->GetName();
NewVariableName = (TemplateName.EndsWith(USimpleConstructionScript::ComponentTemplateNameSuffix)
? FName(*TemplateName.LeftChop(USimpleConstructionScript::ComponentTemplateNameSuffix.Len()))
: ComponentTemplate->GetFName());
}
}
else if (Asset)
{
NewVariableName = *FComponentEditorUtils::GenerateValidVariableNameFromAsset(Asset, nullptr);
}
USCS_Node* NewSCSNode = Blueprint->SimpleConstructionScript->CreateNode(NewComponentClass, NewVariableName);
NewComponent = NewSCSNode->ComponentTemplate;
FAddedNodeDetails NewNodeDetails;
AddNewNode(NewNodeDetails, MoveTemp(AddTransaction), NewSCSNode, Asset, bMarkBlueprintModified, Params.bSetFocusToNewItem);
if (ComponentTemplate)
{
UEngine::CopyPropertiesForUnrelatedObjects(ComponentTemplate, NewComponent);
NewComponent->UpdateComponentToWorld();
}
if (Params.bConformTransformToParent)
{
if (USceneComponent* AsSceneComp = Cast<USceneComponent>(NewComponent))
{
if (USceneComponent* ParentSceneComp = CastChecked<USceneComponent>(NewNodeDetails.ParentNodePtr->GetComponentTemplate(), ECastCheckedType::NullAllowed))
{
ConformTransformRelativeToParent(AsSceneComp, ParentSceneComp);
}
}
}
// Wait until here to mark as structurally modified because we don't want any RerunConstructionScript() calls to happen until AFTER we've serialized properties from the source object.
if (!Params.bSkipMarkBlueprintModified)
{
bAllowTreeUpdates = true;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
else // EComponentEditorMode::ActorInstance
{
if (ComponentTemplate)
{
// Create a duplicate of the provided template
NewComponent = FComponentEditorUtils::DuplicateComponent(ComponentTemplate);
FSCSEditorTreeNodePtrType ParentNodePtr = FindParentForNewComponent(NewComponent);
AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), NewComponent, ParentNodePtr, nullptr, Params.bSetFocusToNewItem);
}
else if (AActor* ActorInstance = GetActorContext())
{
// No template, so create a wholly new component
ActorInstance->Modify();
// Create an appropriate name for the new component
FName NewComponentName = NAME_None;
if (Asset)
{
NewComponentName = *FComponentEditorUtils::GenerateValidVariableNameFromAsset(Asset, ActorInstance);
}
else
{
NewComponentName = *FComponentEditorUtils::GenerateValidVariableName(NewComponentClass, ActorInstance);
}
// Get the set of owned components that exists prior to instancing the new component.
TInlineComponentArray<UActorComponent*> PreInstanceComponents;
ActorInstance->GetComponents(PreInstanceComponents);
// Construct the new component and attach as needed
UActorComponent* NewInstanceComponent = NewObject<UActorComponent>(ActorInstance, NewComponentClass, NewComponentName, RF_Transactional);
FSCSEditorTreeNodePtrType ParentNodePtr = FindParentForNewComponent(NewInstanceComponent);
// Do Scene Attachment if this new Component is a USceneComponent
if (USceneComponent* NewSceneComponent = Cast<USceneComponent>(NewInstanceComponent))
{
if(ParentNodePtr->GetNodeType() == FSCSEditorTreeNode::RootActorNode)
{
ActorInstance->SetRootComponent(NewSceneComponent);
}
else
{
USceneComponent* AttachTo = Cast<USceneComponent>(ParentNodePtr->GetComponentTemplate());
if (AttachTo == nullptr)
{
AttachTo = ActorInstance->GetRootComponent();
}
check(AttachTo != nullptr);
// Make sure that the mobility of the new scene component is such that we can attach it
if (AttachTo->Mobility == EComponentMobility::Movable)
{
NewSceneComponent->Mobility = EComponentMobility::Movable;
}
else if (AttachTo->Mobility == EComponentMobility::Stationary && NewSceneComponent->Mobility == EComponentMobility::Static)
{
NewSceneComponent->Mobility = EComponentMobility::Stationary;
}
NewSceneComponent->AttachToComponent(AttachTo, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
}
}
// If the component was created from/for a particular asset, assign it now
if (Asset)
{
FComponentAssetBrokerage::AssignAssetToComponent(NewInstanceComponent, Asset);
}
// Add to SerializedComponents array so it gets saved
ActorInstance->AddInstanceComponent(NewInstanceComponent);
NewInstanceComponent->OnComponentCreated();
NewInstanceComponent->RegisterComponent();
// Register any new components that may have been created during construction of the instanced component, but were not explicitly registered.
TInlineComponentArray<UActorComponent*> PostInstanceComponents;
ActorInstance->GetComponents(PostInstanceComponents);
for (UActorComponent* ActorComponent : PostInstanceComponents)
{
if (!ActorComponent->IsRegistered() && ActorComponent->bAutoRegister && IsValid(ActorComponent) && !PreInstanceComponents.Contains(ActorComponent))
{
ActorComponent->RegisterComponent();
}
}
// Rerun construction scripts
ActorInstance->RerunConstructionScripts();
// If the running the construction script destroyed the new node, don't create an entry for it
if (IsValid(NewInstanceComponent))
{
AddNewNodeForInstancedComponent(MoveTemp(AddTransaction), NewInstanceComponent, ParentNodePtr, Asset, Params.bSetFocusToNewItem);
NewComponent = NewInstanceComponent;
}
}
}
return NewComponent;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindOrCreateParentForExistingComponent(UActorComponent* InActorComponent, FSCSEditorActorNodePtrType ActorRootNode)
{
check(InActorComponent != nullptr);
USceneComponent* SceneComponent = Cast<USceneComponent>(InActorComponent);
if (SceneComponent == nullptr)
{
check(ActorRootNode.IsValid());
check(ActorRootNode->IsActorNode());
return ActorRootNode;
}
FSCSEditorTreeNodePtrType ParentNodePtr;
if (SceneComponent->GetAttachParent() != nullptr
&& (EditorMode != EComponentEditorMode::ActorInstance || SceneComponent->GetAttachParent()->GetOwner() == ActorRootNode->GetObject<AActor>()))
{
// Attempt to find the parent node in the current tree
ParentNodePtr = FindTreeNode(SceneComponent->GetAttachParent(), ActorRootNode);
if (!ParentNodePtr.IsValid())
{
// If the actual attach parent wasn't found, attempt to find its archetype.
// This handles the BP editor case where we might add UCS component nodes taken
// from the preview actor instance, which are not themselves template objects.
ParentNodePtr = FindTreeNode(Cast<USceneComponent>(SceneComponent->GetAttachParent()->GetArchetype()), ActorRootNode);
if (!ParentNodePtr.IsValid())
{
// Recursively add the parent node to the tree if it does not exist yet
ParentNodePtr = AddTreeNodeFromComponent(SceneComponent->GetAttachParent(), FindOrCreateParentForExistingComponent(SceneComponent->GetAttachParent(), ActorRootNode));
}
}
}
if (!ParentNodePtr.IsValid())
{
ParentNodePtr = ActorRootNode->GetSceneRootNode();
}
// Actor doesn't have a root component yet
if (!ParentNodePtr.IsValid())
{
ParentNodePtr = ActorRootNode;
}
return ParentNodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindParentForNewComponent(UActorComponent* NewComponent) const
{
// Find Parent to attach to (depending on the new Node type).
FSCSEditorTreeNodePtrType TargetParentNode;
TArray<FSCSEditorTreeNodePtrType> SelectedTreeNodes;
if (SCSTreeWidget.IsValid() && SCSTreeWidget->GetSelectedItems(SelectedTreeNodes))
{
TargetParentNode = SelectedTreeNodes[0];
}
// If the current selection belongs to a child actor template, move the target to its outer component node.
while (TargetParentNode.IsValid() && FChildActorComponentEditorUtils::IsChildActorSubtreeNode(TargetParentNode))
{
TargetParentNode = FChildActorComponentEditorUtils::GetOuterChildActorComponentNode(TargetParentNode);
}
if (USceneComponent* NewSceneComponent = Cast<USceneComponent>(NewComponent))
{
if (TargetParentNode.IsValid())
{
if (TargetParentNode->IsActorNode())
{
FSCSEditorActorNodePtrType TargetActorNode = StaticCastSharedPtr<FSCSEditorTreeNodeActorBase>(TargetParentNode);
if (TargetActorNode.IsValid())
{
FSCSEditorTreeNodePtrType TargetSceneRootNode = TargetActorNode->GetSceneRootNode();
if (TargetSceneRootNode.IsValid())
{
TargetParentNode = TargetSceneRootNode;
USceneComponent* CastTargetToSceneComponent = Cast<USceneComponent>(TargetParentNode->GetComponentTemplate());
if (CastTargetToSceneComponent == nullptr || !NewSceneComponent->CanAttachAsChild(CastTargetToSceneComponent, NAME_None))
{
TargetParentNode = GetSceneRootNode(); // Default to SceneRoot
}
}
}
}
else if(TargetParentNode->IsComponentNode())
{
USceneComponent* CastTargetToSceneComponent = Cast<USceneComponent>(TargetParentNode->GetComponentTemplate());
if (CastTargetToSceneComponent == nullptr || !NewSceneComponent->CanAttachAsChild(CastTargetToSceneComponent, NAME_None))
{
TargetParentNode = GetSceneRootNode(); // Default to SceneRoot
}
}
}
else
{
TargetParentNode = GetSceneRootNode();
}
}
else
{
if (TargetParentNode.IsValid())
{
while (!TargetParentNode->IsActorNode())
{
TargetParentNode = TargetParentNode->GetParent();
}
}
else
{
TargetParentNode = GetActorNode();
}
check(TargetParentNode.IsValid() && TargetParentNode->IsActorNode());
}
return TargetParentNode;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindParentForNewNode(USCS_Node* NewNode) const
{
return FindParentForNewComponent(NewNode->ComponentTemplate);
}
UActorComponent* SSCSEditor::AddNewNode(TUniquePtr<FScopedTransaction> OngoingCreateTransaction, USCS_Node* NewNode, UObject* Asset, bool bMarkBlueprintModified, bool bSetFocusToNewItem)
{
FAddedNodeDetails AddedNodeDetails;
AddNewNode(AddedNodeDetails, MoveTemp(OngoingCreateTransaction), NewNode, Asset, bMarkBlueprintModified, bSetFocusToNewItem);
return NewNode->ComponentTemplate;
}
void SSCSEditor::AddNewNode(SSCSEditor::FAddedNodeDetails& OutAddedNodeDetails, TUniquePtr<FScopedTransaction> InOngoingCreateTransaction, USCS_Node* NewNode, UObject* Asset, bool bMarkBlueprintModified, bool bSetFocusToNewItem)
{
check(NewNode != nullptr);
if(Asset)
{
FComponentAssetBrokerage::AssignAssetToComponent(NewNode->ComponentTemplate, Asset);
}
FSCSEditorTreeNodePtrType& NewNodePtr = OutAddedNodeDetails.NewNodePtr;
FSCSEditorTreeNodePtrType& ParentNodePtr = OutAddedNodeDetails.ParentNodePtr;
ParentNodePtr = FindParentForNewNode(NewNode);
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr);
// Add the new node to the editor tree
NewNodePtr = AddTreeNode(NewNode, ParentNodePtr, /*bIsInheritedSCS=*/ false);
// Potentially adjust variable names for any child blueprints
const FName VariableName = NewNode->GetVariableName();
if(VariableName != NAME_None)
{
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, VariableName);
}
if(bSetFocusToNewItem)
{
// Select and request a rename on the new component
SCSTreeWidget->SetSelection(NewNodePtr);
OnRenameComponent(MoveTemp(InOngoingCreateTransaction));
}
// Will call UpdateTree as part of OnBlueprintChanged handling
if(bMarkBlueprintModified)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
else
{
UpdateTree();
}
}
void SSCSEditor::AddNewNodeForInstancedComponent(TUniquePtr<FScopedTransaction> InOngoingCreateTransaction, UActorComponent* NewInstanceComponent, FSCSEditorTreeNodePtrType InParentNodePtr, UObject* Asset, bool bSetFocusToNewItem)
{
check(NewInstanceComponent != nullptr);
FSCSEditorTreeNodePtrType NewNodePtr;
// Add the new node to the editor tree
NewNodePtr = AddTreeNodeFromComponent(NewInstanceComponent, InParentNodePtr);
if(bSetFocusToNewItem)
{
// Select and request a rename on the new component
SCSTreeWidget->SetSelection(NewNodePtr);
OnRenameComponent(MoveTemp(InOngoingCreateTransaction));
}
UpdateTree(false);
}
bool SSCSEditor::IsComponentSelected(const UPrimitiveComponent* PrimComponent) const
{
check(PrimComponent);
if (SCSTreeWidget.IsValid())
{
FSCSEditorTreeNodePtrType NodePtr = GetNodeFromActorComponent(PrimComponent, false);
if (NodePtr.IsValid())
{
return SCSTreeWidget->IsItemSelected(NodePtr);
}
else
{
UChildActorComponent* PossiblySelectedComponent = nullptr;
AActor* ComponentOwner = PrimComponent->GetOwner();
while (ComponentOwner->IsChildActor())
{
PossiblySelectedComponent = ComponentOwner->GetParentComponent();
ComponentOwner = ComponentOwner->GetParentActor();
}
if (PossiblySelectedComponent)
{
NodePtr = GetNodeFromActorComponent(PossiblySelectedComponent, false);
if (NodePtr.IsValid())
{
return SCSTreeWidget->IsItemSelected(NodePtr);
}
}
}
}
return false;
}
void SSCSEditor::SetSelectionOverride(UPrimitiveComponent* PrimComponent) const
{
PrimComponent->SelectionOverrideDelegate = UPrimitiveComponent::FSelectionOverride::CreateSP(this, &SSCSEditor::IsComponentSelected);
PrimComponent->PushSelectionToProxy();
}
bool SSCSEditor::CanCutNodes() const
{
return CanCopyNodes() && CanDeleteNodes();
}
void SSCSEditor::CutSelectedNodes()
{
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = GetSelectedNodes();
const FScopedTransaction Transaction( SelectedNodes.Num() > 1 ? LOCTEXT("CutComponents", "Cut Components") : LOCTEXT("CutComponent", "Cut Component") );
CopySelectedNodes();
OnDeleteNodes();
}
bool SSCSEditor::CanCopyNodes() const
{
TArray<UActorComponent*> ComponentsToCopy;
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = GetSelectedNodes();
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
// Get the current selected node reference
FSCSEditorTreeNodePtrType SelectedNodePtr = SelectedNodes[i];
check(SelectedNodePtr.IsValid());
// Get the component template associated with the selected node
UActorComponent* ComponentTemplate = SelectedNodePtr->GetComponentTemplate();
if (ComponentTemplate)
{
ComponentsToCopy.Add(ComponentTemplate);
}
}
// Verify that the components can be copied
return FComponentEditorUtils::CanCopyComponents(ComponentsToCopy);
}
void SSCSEditor::CopySelectedNodes()
{
// Distill the selected nodes into a list of components to copy
TArray<UActorComponent*> ComponentsToCopy;
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = GetSelectedNodes();
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
// Get the current selected node reference
FSCSEditorTreeNodePtrType SelectedNodePtr = SelectedNodes[i];
check(SelectedNodePtr.IsValid());
// Get the component template associated with the selected node
UActorComponent* ComponentTemplate = SelectedNodePtr->GetComponentTemplate();
if (ComponentTemplate)
{
ComponentsToCopy.Add(ComponentTemplate);
if (EditorMode == EComponentEditorMode::BlueprintSCS && ComponentTemplate->CreationMethod != EComponentCreationMethod::UserConstructionScript)
{
// CopyComponents uses component attachment to maintain hierarchy, but the SCS templates are not
// setup with a relationship to each other. Briefly setup the attachment between the templates being
// copied so that the hierarchy is retained upon pasting
if (USceneComponent* SceneTemplate = Cast<USceneComponent>(ComponentTemplate))
{
FSCSEditorTreeNodePtrType SelectedParentNodePtr = SelectedNodePtr->GetParent();
if (SelectedParentNodePtr.IsValid())
{
if (USceneComponent* ParentSceneTemplate = Cast<USceneComponent>(SelectedParentNodePtr->GetComponentTemplate()))
{
SceneTemplate->SetupAttachment(ParentSceneTemplate);
}
}
}
}
}
}
// Copy the components to the clipboard
FComponentEditorUtils::CopyComponents(ComponentsToCopy);
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
for (UActorComponent* ComponentTemplate : ComponentsToCopy)
{
if (ComponentTemplate->CreationMethod != EComponentCreationMethod::UserConstructionScript)
{
if (USceneComponent* SceneTemplate = Cast<USceneComponent>(ComponentTemplate))
{
// clear back out any temporary attachments we set up for the copy
SceneTemplate->SetupAttachment(nullptr);
}
}
}
}
}
bool SSCSEditor::CanPasteNodes() const
{
if(!IsEditingAllowed())
{
return false;
}
FSCSEditorTreeNodePtrType SceneRootNodePtr = GetSceneRootNode();
return SceneRootNodePtr.IsValid() && FComponentEditorUtils::CanPasteComponents(Cast<USceneComponent>(SceneRootNodePtr->GetComponentTemplate()), SceneRootNodePtr->IsDefaultSceneRoot(), true);
}
void SSCSEditor::PasteNodes()
{
const FScopedTransaction Transaction(LOCTEXT("PasteComponents", "Paste Component(s)"));
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
// Get the components to paste from the clipboard
TMap<FName, FName> ParentMap;
TMap<FName, UActorComponent*> NewObjectMap;
FComponentEditorUtils::GetComponentsFromClipboard(ParentMap, NewObjectMap, true);
// Clear the current selection
SCSTreeWidget->ClearSelection();
// Get the blueprint that's being edited
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr);
Blueprint->Modify();
SaveSCSCurrentState(Blueprint->SimpleConstructionScript);
// stop allowing tree updates
bool bRestoreAllowTreeUpdates = bAllowTreeUpdates;
bAllowTreeUpdates = false;
// Create a new tree node for each new (pasted) component
FSCSEditorTreeNodePtrType FirstNode;
TMap<FName, FSCSEditorTreeNodePtrType> NewNodeMap;
for (const TPair<FName, UActorComponent*>& NewObjectPair : NewObjectMap)
{
// Get the component object instance
UActorComponent* NewActorComponent = NewObjectPair.Value;
check(NewActorComponent);
// Create a new SCS node to contain the new component and add it to the tree
NewActorComponent = AddNewNode(TUniquePtr<FScopedTransaction>(), Blueprint->SimpleConstructionScript->CreateNodeAndRenameComponent(NewActorComponent), nullptr, false, false);
if (NewActorComponent)
{
// Locate the node that corresponds to the new component template or instance
FSCSEditorTreeNodePtrType NewNodePtr = FindTreeNode(NewActorComponent);
if (NewNodePtr.IsValid())
{
// Add the new node to the node map
NewNodeMap.Add(NewObjectPair.Key, NewNodePtr);
// Update the selection to include the new node
SCSTreeWidget->SetItemSelection(NewNodePtr, true);
if (!FirstNode.IsValid())
{
FirstNode = NewNodePtr;
}
}
}
}
// Restore the node hierarchy from the original copy
for (const TPair<FName, FSCSEditorTreeNodePtrType>& NewNodePair : NewNodeMap)
{
// If an entry exists in the set of known parent nodes for the current node
if (ParentMap.Contains(NewNodePair.Key))
{
// Get the parent node name
FName ParentName = ParentMap[NewNodePair.Key];
if (NewNodeMap.Contains(ParentName))
{
// Reattach the current node to the parent node (this will also handle detachment from the scene root node)
NewNodeMap[ParentName]->AddChild(NewNodePair.Value);
// Ensure that the new node is expanded to show the child node(s)
SCSTreeWidget->SetItemExpansion(NewNodeMap[ParentName], true);
}
}
}
// allow tree updates again
bAllowTreeUpdates = bRestoreAllowTreeUpdates;
// scroll the first node into view
if (FirstNode.IsValid())
{
SCSTreeWidget->RequestScrollIntoView(FirstNode);
}
// Modify the Blueprint generated class structure (this will also call UpdateTree() as a result)
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
else // EComponentEditorMode::ActorInstance
{
// Determine where in the hierarchy to paste (default to the root)
USceneComponent* TargetComponent = GetActorContext()->GetRootComponent();
for (FSCSEditorTreeNodePtrType SelectedNodePtr : GetSelectedNodes())
{
check(SelectedNodePtr.IsValid());
if (USceneComponent* SceneComponent = Cast<USceneComponent>(SelectedNodePtr->GetComponentTemplate()))
{
TargetComponent = SceneComponent;
break;
}
}
// Paste the components
TArray<UActorComponent*> PastedComponents;
FComponentEditorUtils::PasteComponents(PastedComponents, GetActorContext(), TargetComponent);
if (PastedComponents.Num() > 0)
{
// We only want the pasted node(s) to be selected
SCSTreeWidget->ClearSelection();
UpdateTree();
// Select the nodes that correspond to the pasted components
for (UActorComponent* PastedComponent : PastedComponents)
{
FSCSEditorTreeNodePtrType PastedNode = GetNodeFromActorComponent(PastedComponent);
if (PastedNode.IsValid())
{
SCSTreeWidget->SetItemSelection(PastedNode, true);
}
}
}
}
}
bool SSCSEditor::CanDeleteNodes() const
{
if(!IsEditingAllowed())
{
return false;
}
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = SCSTreeWidget->GetSelectedItems();
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
if (!SelectedNodes[i]->CanDelete())
{
return false;
}
else
{
// Don't allow nodes that belong to a child actor template to be deleted
const bool bIsChildActorSubtreeNode = FChildActorComponentEditorUtils::IsChildActorSubtreeNode(SelectedNodes[i]);
if (bIsChildActorSubtreeNode)
{
return false;
}
}
}
return SelectedNodes.Num() > 0;
}
void SSCSEditor::OnDeleteNodes()
{
const FScopedTransaction Transaction( LOCTEXT("RemoveComponents", "Remove Components") );
// Invalidate any active component in the visualizer
GUnrealEd->ComponentVisManager.ClearActiveComponentVis();
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
UBlueprint* Blueprint = GetBlueprint();
check(Blueprint != nullptr);
// Get the current render info for the blueprint. If this is NULL then the blueprint is not currently visualizable (no visible primitive components)
FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo( Blueprint );
// A lamda for displaying a confirm message to the user if there is a dynamic delegate bound to the
// component they are trying to delete
auto ConfirmDeleteLambda = [](USCS_Node* ScsNode) -> FSuppressableWarningDialog::EResult
{
if (ensure(ScsNode))
{
FText VarNam = FText::FromName(ScsNode->GetVariableName());
FText ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteDynamicDelegate", "Component \"{0}\" has bound events in use! If you delete it then those nodes will become invalid. Are you sure you want to delete it?"), VarNam);
// Warn the user that this may result in data loss
FSuppressableWarningDialog::FSetupInfo Info(ConfirmDelete, LOCTEXT("DeleteComponent", "Delete Component"), "DeleteComponentInUse_Warning");
Info.ConfirmText = LOCTEXT("ConfirmDeleteDynamicDelegate_Yes", "Yes");
Info.CancelText = LOCTEXT("ConfirmDeleteDynamicDelegate_No", "No");
FSuppressableWarningDialog DeleteVariableInUse(Info);
// If the user selects cancel then return false
return DeleteVariableInUse.ShowModal();
}
return FSuppressableWarningDialog::Cancel;
};
// Remove node(s) from SCS
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = SCSTreeWidget->GetSelectedItems();
// Confirm that the user wants to delete this node
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
FSCSEditorTreeNodePtrType Node = SelectedNodes[i];
USCS_Node* SCS_Node = Node->GetSCSNode();
if (SCS_Node != nullptr)
{
// If this node is in use by Dynamic delegates, then confirm before continuing
if (FKismetEditorUtilities::PropertyHasBoundEvents(Blueprint, SCS_Node->GetVariableName()))
{
// The user has decided not to delete the component, stop trying to delete this component
if (ConfirmDeleteLambda(SCS_Node) == FSuppressableWarningDialog::Cancel)
{
return;
}
}
}
}
//const FScopedTransaction Transaction(LOCTEXT("SetNodeEnabledState", "Set Node Enabled State"));
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
FSCSEditorTreeNodePtrType Node = SelectedNodes[i];
USCS_Node* SCS_Node = Node->GetSCSNode();
if(SCS_Node != nullptr)
{
USimpleConstructionScript* SCS = SCS_Node->GetSCS();
check(SCS != nullptr && Blueprint == SCS->GetBlueprint());
// Saving objects for restoring purpose.
Blueprint->Modify();
SaveSCSCurrentState( SCS );
}
RemoveComponentNode(Node);
}
// Will call UpdateTree as part of OnBlueprintChanged handling
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
// If we had a thumbnail before we deleted any components, check to see if we should clear it
// If we deleted the final visualizable primitive from the blueprint, GetRenderingInfo should return NULL
FThumbnailRenderingInfo* NewRenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo( Blueprint );
if ( RenderInfo && !NewRenderInfo )
{
// We removed the last visible primitive component, clear the thumbnail
const FString BPFullName = FString::Printf(TEXT("%s %s"), *Blueprint->GetClass()->GetName(), *Blueprint->GetPathName());
UPackage* BPPackage = Blueprint->GetOutermost();
ThumbnailTools::CacheEmptyThumbnail( BPFullName, BPPackage );
}
// Do this AFTER marking the Blueprint as modified
UpdateSelectionFromNodes(SCSTreeWidget->GetSelectedItems());
}
else // EComponentEditorMode::ActorInstance
{
// const FScopedTransaction Transaction(LOCTEXT("SetNodeEnabledState", "Set Node Enabled State"));
if (AActor* ActorInstance = GetActorContext())
{
ActorInstance->Modify();
}
TArray<UActorComponent*> ComponentsToDelete;
TArray<FSCSEditorTreeNodePtrType> SelectedNodes = GetSelectedNodes();
for (int32 i = 0; i < SelectedNodes.Num(); ++i)
{
// Get the current selected node reference
FSCSEditorTreeNodePtrType SelectedNodePtr = SelectedNodes[i];
check(SelectedNodePtr.IsValid());
// Get the component template associated with the selected node
UActorComponent* ComponentTemplate = SelectedNodePtr->GetComponentTemplate();
if (ComponentTemplate)
{
ComponentsToDelete.Add(ComponentTemplate);
}
}
UActorComponent* ComponentToSelect = nullptr;
int32 NumDeletedComponents = FComponentEditorUtils::DeleteComponents(ComponentsToDelete, ComponentToSelect);
if (NumDeletedComponents > 0)
{
if (ComponentToSelect)
{
FSCSEditorTreeNodePtrType NodeToSelect = GetNodeFromActorComponent(ComponentToSelect);
if (NodeToSelect.IsValid())
{
SCSTreeWidget->SetSelection(NodeToSelect);
}
}
// Rebuild the tree view to reflect the new component hierarchy
UpdateTree();
}
// Do this AFTER marking the Blueprint as modified
UpdateSelectionFromNodes(SCSTreeWidget->GetSelectedItems());
}
}
void SSCSEditor::RemoveComponentNode(FSCSEditorTreeNodePtrType InNodePtr)
{
check(InNodePtr.IsValid());
if (EditorMode == EComponentEditorMode::BlueprintSCS)
{
USCS_Node* SCS_Node = InNodePtr->GetSCSNode();
if(SCS_Node != nullptr)
{
// Clear selection if current
if (SCSTreeWidget->GetSelectedItems().Contains(InNodePtr))
{
SCSTreeWidget->ClearSelection();
}
USimpleConstructionScript* SCS = SCS_Node->GetSCS();
check(SCS != nullptr);
// Remove any instances of variable accessors from the blueprint graphs
UBlueprint* Blueprint = SCS->GetBlueprint();
if(Blueprint != nullptr)
{
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, InNodePtr->GetVariableName());
// If there are any Bound Component events for this property then give them compiler errors
TArray<UK2Node_ComponentBoundEvent*> EventNodes;
FKismetEditorUtilities::FindAllBoundEventsForComponent(Blueprint, SCS_Node->GetVariableName(), EventNodes);
if(EventNodes.Num() > 0)
{
// Find any dynamic delegate nodes and give a compiler error for each that is problematic
FCompilerResultsLog LogResults;
FMessageLog MessageLog("BlueprintLog");
// Add a compiler error for each bound event node
for (UK2Node_ComponentBoundEvent* Node : EventNodes)
{
LogResults.Error(*LOCTEXT("RemoveBoundEvent_Error", "The component that @@ was bound to has been deleted! This node is no longer valid").ToString(), Node);
}
// Notify the user that these nodes are no longer valid
MessageLog.NewPage(LOCTEXT("RemoveBoundEvent_Error_Label", "Removed Owner of Component Bound Event"));
MessageLog.AddMessages(LogResults.Messages);
MessageLog.Notify(LOCTEXT("RemoveBoundEvent_Error_Msg", "Removed Owner of Component Bound Event"));
// Focus on the first node that we found
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(EventNodes[0]);
}
}
// Remove node from SCS tree
SCS->RemoveNodeAndPromoteChildren(SCS_Node);
// Clear the delegate
SCS_Node->SetOnNameChanged(FSCSNodeNameChanged());
// on removal, since we don't move the template from the GeneratedClass (which we shouldn't, as it would create a
// discrepancy with existing instances), we rename it instead so that we can re-use the name without having to compile
// (we still have a problem if they attempt to name it to what ever we choose here, but that is unlikely)
// note: skip this for the default scene root; we don't actually destroy that node when it's removed, so we don't need the template to be renamed.
if (!InNodePtr->IsDefaultSceneRoot() && SCS_Node->ComponentTemplate != nullptr)
{
const FName TemplateName = SCS_Node->ComponentTemplate->GetFName();
const FString RemovedName = SCS_Node->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString();
SCS_Node->ComponentTemplate->Modify();
SCS_Node->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors);
TArray<UObject*> ArchetypeInstances;
auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate)
{
ComponentTemplate->GetArchetypeInstances(ArchetypeInstances);
for (UObject* ArchetypeInstance : ArchetypeInstances)
{
if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate))
{
CastChecked<UActorComponent>(ArchetypeInstance)->DestroyComponent();
ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors);
}
}
};
DestroyArchetypeInstances(SCS_Node->ComponentTemplate);
if (Blueprint)
{
// Children need to have their inherited component template instance of the component renamed out of the way as well
TArray<UClass*> ChildrenOfClass;
GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass);
for (UClass* ChildClass : ChildrenOfClass)
{
UBlueprintGeneratedClass* BPChildClass = CastChecked<UBlueprintGeneratedClass>(ChildClass);
if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName))
{
Component->Modify();
Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors);
DestroyArchetypeInstances(Component);
}
}
}
}
}
}
else // EComponentEditorMode::ActorInstance
{
AActor* ActorInstance = GetActorContext();
UActorComponent* ComponentInstance = InNodePtr->GetComponentTemplate();
if ((ActorInstance != nullptr) && (ComponentInstance != nullptr))
{
// Clear selection if current
if (SCSTreeWidget->GetSelectedItems().Contains(InNodePtr))
{
SCSTreeWidget->ClearSelection();
}
const bool bWasDefaultSceneRoot = InNodePtr.IsValid() && InNodePtr->IsDefaultSceneRoot();
// Destroy the component instance
ComponentInstance->Modify();
ComponentInstance->DestroyComponent(!bWasDefaultSceneRoot);
}
}
}
void SSCSEditor::UpdateSelectionFromNodes(const TArray<FSCSEditorTreeNodePtrType> &SelectedNodes)
{
bUpdatingSelection = true;
// Notify that the selection has updated
OnSelectionUpdated.ExecuteIfBound(SelectedNodes);
bUpdatingSelection = false;
}
void SSCSEditor::RefreshSelectionDetails()
{
UpdateSelectionFromNodes(SCSTreeWidget->GetSelectedItems());
}
void SSCSEditor::OnTreeSelectionChanged(FSCSEditorTreeNodePtrType, ESelectInfo::Type /*SelectInfo*/)
{
UpdateSelectionFromNodes(SCSTreeWidget->GetSelectedItems());
}
bool SSCSEditor::IsNodeInSimpleConstructionScript( USCS_Node* Node ) const
{
check(Node);
USimpleConstructionScript* NodeSCS = Node->GetSCS();
if(NodeSCS != NULL)
{
return NodeSCS->GetAllNodes().Contains(Node);
}
return false;
}
FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNode(USCS_Node* InSCSNode, FSCSEditorTreeNodePtrType InParentNodePtr, const bool bIsInheritedSCS)
{
FSCSEditorTreeNodePtrType NewNodePtr;
check(InSCSNode != nullptr && InParentNodePtr.IsValid());
// During diffs, ComponentTemplates can easily be null, so prevent these checks.
if (!bIsDiffing && InSCSNode->ComponentTemplate)
{
checkf(InSCSNode->ParentComponentOrVariableName == NAME_None
|| (!InSCSNode->bIsParentComponentNative && InParentNodePtr->GetSCSNode() != nullptr && InParentNodePtr->GetSCSNode()->GetVariableName() == InSCSNode->ParentComponentOrVariableName)
|| (InSCSNode->bIsParentComponentNative && InParentNodePtr->GetComponentTemplate() != nullptr && InParentNodePtr->GetComponentTemplate()->GetFName() == InSCSNode->ParentComponentOrVariableName),
TEXT("Failed to add SCS node %s to tree:\n- bIsParentComponentNative=%d\n- Stored ParentComponentOrVariableName=%s\n- Actual ParentComponentOrVariableName=%s"),
*InSCSNode->GetVariableName().ToString(),
!!InSCSNode->bIsParentComponentNative,
*InSCSNode->ParentComponentOrVariableName.ToString(),
!InSCSNode->bIsParentComponentNative
? (InParentNodePtr->GetSCSNode() != nullptr ? *InParentNodePtr->GetSCSNode()->GetVariableName().ToString() : TEXT("NULL"))
: (InParentNodePtr->GetComponentTemplate() != nullptr ? *InParentNodePtr->GetComponentTemplate()->GetFName().ToString() : TEXT("NULL")));
}
// Determine whether or not the given node is inherited from a parent Blueprint
USimpleConstructionScript* NodeSCS = InSCSNode->GetSCS();
// do this first, because we need a FSCSEditorTreeNodePtrType for the new node
NewNodePtr = InParentNodePtr->AddChild(InSCSNode, bIsInheritedSCS);
RefreshFilteredState(NewNodePtr, /*bRecursive =*/false);
if( InSCSNode->ComponentTemplate &&
InSCSNode->ComponentTemplate->IsA(USceneComponent::StaticClass()) &&
InParentNodePtr->GetNodeType() == FSCSEditorTreeNode::ComponentNode)
{
bool bParentIsEditorOnly = InParentNodePtr->GetComponentTemplate()->IsEditorOnly();
// if you can't nest this new node under the proposed parent (then swap the two)
if (bParentIsEditorOnly && !InSCSNode->ComponentTemplate->IsEditorOnly() && InParentNodePtr->CanReparent())
{
FSCSEditorTreeNodePtrType OldParentPtr = InParentNodePtr;
InParentNodePtr = OldParentPtr->GetParent();
OldParentPtr->RemoveChild(NewNodePtr);
NodeSCS->RemoveNode(OldParentPtr->GetSCSNode());
// if the grandparent node is invalid (assuming this means that the parent node was the scene-root)
if (!InParentNodePtr.IsValid())
{
check(OldParentPtr == GetSceneRootNode());
SetSceneRootNode(NewNodePtr);
NodeSCS->AddNode(NewNodePtr->GetSCSNode());
}
else
{
InParentNodePtr->AddChild(NewNodePtr);
}
// move the proposed parent in as a child to the new node
NewNodePtr->AddChild(OldParentPtr);
} // if bParentIsEditorOnly...
}
else
{
// If the SCS root node array does not already contain the given node, this will add it (this should only occur after node creation)
if(NodeSCS != nullptr)
{
NodeSCS->AddNode(InSCSNode);
}
}
// Expand parent nodes by default
SCSTreeWidget->SetItemExpansion(InParentNodePtr, true);
// Add this node's child actor node, if present
AddTreeNodeFromChildActor(NewNodePtr);
// Recursively add the given SCS node's child nodes
for (USCS_Node* ChildNode : InSCSNode->GetChildNodes())
{
AddTreeNode(ChildNode, NewNodePtr, bIsInheritedSCS);
}
return NewNodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromComponent(UActorComponent* InActorComponent, FSCSEditorTreeNodePtrType InParentTreeNode)
{
check(InActorComponent != NULL);
ensure(IsValid(InActorComponent));
FSCSEditorTreeNodePtrType NewNodePtr = InParentTreeNode->FindChild(InActorComponent);
if (!NewNodePtr.IsValid())
{
NewNodePtr = FSCSEditorTreeNode::FactoryNodeFromComponent(InActorComponent);
InParentTreeNode->AddChild(NewNodePtr);
RefreshFilteredState(NewNodePtr, false);
}
AddTreeNodeFromChildActor(NewNodePtr);
SCSTreeWidget->SetItemExpansion(NewNodePtr, true);
return NewNodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::AddTreeNodeFromChildActor(FSCSEditorTreeNodePtrType InNodePtr)
{
if (!InNodePtr.IsValid())
{
return nullptr;
}
const EChildActorComponentTreeViewVisualizationMode DefaultVisOverride = UICustomization.IsValid() ? UICustomization->GetChildActorVisualizationMode() : EChildActorComponentTreeViewVisualizationMode::UseDefault;
// Skip any expansion logic if the option is disabled
if (DefaultVisOverride == EChildActorComponentTreeViewVisualizationMode::UseDefault && !FChildActorComponentEditorUtils::IsChildActorTreeViewExpansionEnabled())
{
return nullptr;
}
FSCSEditorChildActorNodePtrType ChildActorNodePtr = InNodePtr->GetChildActorNode();
if (ChildActorNodePtr.IsValid())
{
// Get the child actor component that's associated with the child actor node
UChildActorComponent* ChildActorComponent = ChildActorNodePtr->GetChildActorComponent();
check(ChildActorComponent != nullptr);
// Check to see if we should expand the child actor node within the tree view
const bool bExpandChildActorInTreeView = FChildActorComponentEditorUtils::ShouldExpandChildActorInTreeView(ChildActorComponent, DefaultVisOverride);
if (bExpandChildActorInTreeView)
{
// Do the expansion as a normal actor subtree
BuildSubTreeForActorNode(ChildActorNodePtr);
// Check to see if we should include the child actor node within the tree view
const bool bShowChildActorNodeInTreeView = FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(ChildActorComponent, DefaultVisOverride);
if (bShowChildActorNodeInTreeView)
{
// Add the child actor node into the tree view
InNodePtr->AddChild(ChildActorNodePtr);
RefreshFilteredState(ChildActorNodePtr, false);
}
else
{
// Add the child actor's subtree into the tree view
TArray<FSCSEditorTreeNodePtrType> Children = ChildActorNodePtr->GetChildren();
for (const FSCSEditorTreeNodePtrType& ChildNode : Children)
{
// Remove the child actor node as the visible root in the tree view, and replace it with the given node
ChildActorNodePtr->RemoveChild(ChildNode);
InNodePtr->AddChild(ChildNode);
// Restore the subtree's actor root to indicate that it's still a child actor expansion in the tree view
ChildNode->SetActorRootNode(ChildActorNodePtr);
}
}
// Expand the given parent to reveal the child actor node and/or its subtree
SCSTreeWidget->SetItemExpansion(InNodePtr, true);
}
}
return ChildActorNodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const USCS_Node* InSCSNode, FSCSEditorTreeNodePtrType InStartNodePtr) const
{
FSCSEditorTreeNodePtrType NodePtr;
if(InSCSNode != NULL)
{
// Start at the scene root node if none was given
if(!InStartNodePtr.IsValid())
{
InStartNodePtr = GetSceneRootNode();
}
if(InStartNodePtr.IsValid())
{
// Check to see if the given SCS node matches the given tree node
if(InStartNodePtr->GetSCSNode() == InSCSNode)
{
NodePtr = InStartNodePtr;
}
else
{
// Recursively search for the node in our child set
NodePtr = InStartNodePtr->FindChild(InSCSNode);
if(!NodePtr.IsValid())
{
for(int32 i = 0; i < InStartNodePtr->GetChildren().Num() && !NodePtr.IsValid(); ++i)
{
NodePtr = FindTreeNode(InSCSNode, InStartNodePtr->GetChildren()[i]);
}
}
}
}
}
return NodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const UActorComponent* InComponent, FSCSEditorTreeNodePtrType InStartNodePtr) const
{
FSCSEditorTreeNodePtrType NodePtr;
if(InComponent != NULL)
{
// Start at the scene root node if none was given
if(!InStartNodePtr.IsValid())
{
InStartNodePtr = GetActorNode();
}
if(InStartNodePtr.IsValid())
{
// Check to see if the given component template matches the given tree node
//
// For certain node types, GetOrCreateEditableComponentTemplate() will handle retrieving
// the "OverridenComponentTemplate" which may be what we're looking for in some
// cases; if not, then we fall back to just checking GetComponentTemplate()
if (InStartNodePtr->GetOrCreateEditableComponentTemplate(GetBlueprint()) == InComponent)
{
NodePtr = InStartNodePtr;
}
else if (InStartNodePtr->GetComponentTemplate() == InComponent)
{
NodePtr = InStartNodePtr;
}
else
{
// Recursively search for the node in our child set
NodePtr = InStartNodePtr->FindChild(InComponent);
if(!NodePtr.IsValid())
{
for(int32 i = 0; i < InStartNodePtr->GetChildren().Num() && !NodePtr.IsValid(); ++i)
{
NodePtr = FindTreeNode(InComponent, InStartNodePtr->GetChildren()[i]);
}
}
}
}
}
return NodePtr;
}
FSCSEditorTreeNodePtrType SSCSEditor::FindTreeNode(const FName& InVariableOrInstanceName, FSCSEditorTreeNodePtrType InStartNodePtr) const
{
FSCSEditorTreeNodePtrType NodePtr;
if(InVariableOrInstanceName != NAME_None)
{
// Start at the root node if none was given
if(!InStartNodePtr.IsValid())
{
InStartNodePtr = GetActorNode();
}
if(InStartNodePtr.IsValid())
{
FName ItemName = InStartNodePtr->GetNodeID();
// Check to see if the given name matches the item name
if(InVariableOrInstanceName == ItemName)
{
NodePtr = InStartNodePtr;
}
else
{
// Recursively search for the node in our child set
NodePtr = InStartNodePtr->FindChild(InVariableOrInstanceName);
if(!NodePtr.IsValid())
{
for(int32 i = 0; i < InStartNodePtr->GetChildren().Num() && !NodePtr.IsValid(); ++i)
{
NodePtr = FindTreeNode(InVariableOrInstanceName, InStartNodePtr->GetChildren()[i]);
}
}
}
}
}
return NodePtr;
}
void SSCSEditor::OnItemScrolledIntoView( FSCSEditorTreeNodePtrType InItem, const TSharedPtr<ITableRow>& InWidget)
{
if(DeferredRenameRequest != NAME_None)
{
FName ItemName = InItem->GetNodeID();
if(DeferredRenameRequest == ItemName)
{
DeferredRenameRequest = NAME_None;
InItem->OnRequestRename(MoveTemp(DeferredOngoingCreateTransaction)); // Transfer responsibility to end the 'create + give initial name' transaction to the tree item if such transaction is ongoing.
}
}
}
void SSCSEditor::HandleItemDoubleClicked(FSCSEditorTreeNodePtrType InItem)
{
// Notify that the selection has updated
OnItemDoubleClicked.ExecuteIfBound(InItem);
}
void SSCSEditor::OnRenameComponent()
{
OnRenameComponent(nullptr); // null means that the rename is not part of the creation process (create + give initial name).
}
void SSCSEditor::OnRenameComponent(TUniquePtr<FScopedTransaction> InComponentCreateTransaction)
{
TArray< FSCSEditorTreeNodePtrType > SelectedItems = SCSTreeWidget->GetSelectedItems();
// Should already be prevented from making it here.
check(SelectedItems.Num() == 1);
DeferredRenameRequest = SelectedItems[0]->GetNodeID();
check(!DeferredOngoingCreateTransaction.IsValid()); // If this fails, something in the chain of responsibility failed to end the previous transaction.
DeferredOngoingCreateTransaction = MoveTemp(InComponentCreateTransaction); // If a 'create + give initial name' transaction is ongoing, take responsibility of ending it until the selected item is scrolled into view.
SCSTreeWidget->RequestScrollIntoView(SelectedItems[0]);
if (DeferredOngoingCreateTransaction.IsValid() && !PostTickHandle.IsValid())
{
// Ensure the item will be scrolled into view during the frame (See explanation in OnPostTick()).
PostTickHandle = FSlateApplication::Get().OnPostTick().AddSP(this, &SSCSEditor::OnPostTick);
}
}
void SSCSEditor::OnPostTick(float)
{
// If a 'create + give initial name' is ongoing and the transaction ownership was not transferred during the frame it was requested, it is most likely because the newly
// created item could not be scrolled into view (should say 'teleported', the scrolling is not animated). The tree view will not put the item in view if the there is
// no space left to display the item. (ex a splitter where all the display space is used by the other component). End the transaction before starting a new frame. The user
// will not be able to rename on creation, the widget is likely not in view and cannot be edited anyway.
DeferredOngoingCreateTransaction.Reset();
// The post tick event handler is not required anymore.
FSlateApplication::Get().OnPostTick().Remove(PostTickHandle);
PostTickHandle.Reset();
}
bool SSCSEditor::CanRenameComponent() const
{
if (!IsEditingAllowed())
{
return false;
}
// In addition to certain node types, don't allow nodes within a child actor template's hierarchy to be renamed
TArray<FSCSEditorTreeNodePtrType> SelectedItems = SCSTreeWidget->GetSelectedItems();
return SelectedItems.Num() == 1 && SelectedItems[0]->CanRename() && !FChildActorComponentEditorUtils::IsChildActorSubtreeNode(SelectedItems[0]);
}
void SSCSEditor::DepthFirstTraversal(const FSCSEditorTreeNodePtrType& InNodePtr, TSet<FSCSEditorTreeNodePtrType>& OutVisitedNodes, const TFunctionRef<void(const FSCSEditorTreeNodePtrType&)> InFunction) const
{
if (InNodePtr.IsValid()
&& ensureMsgf(!OutVisitedNodes.Contains(InNodePtr), TEXT("Already visited node: %s (Parent: %s)"), *InNodePtr->GetDisplayString(), InNodePtr->GetParent().IsValid() ? *InNodePtr->GetParent()->GetDisplayString() : TEXT("NULL")))
{
InFunction(InNodePtr);
OutVisitedNodes.Add(InNodePtr);
for (const FSCSEditorTreeNodePtrType& Child : InNodePtr->GetChildren())
{
DepthFirstTraversal(Child, OutVisitedNodes, InFunction);
}
}
}
void SSCSEditor::GetCollapsedNodes(const FSCSEditorTreeNodePtrType& InNodePtr, TSet<FSCSEditorTreeNodePtrType>& OutCollapsedNodes) const
{
TSet<FSCSEditorTreeNodePtrType> VisitedNodes;
DepthFirstTraversal(InNodePtr, VisitedNodes, [SCSTreeWidget = this->SCSTreeWidget, &OutCollapsedNodes](const FSCSEditorTreeNodePtrType& InNodePtr)
{
if (InNodePtr->GetChildren().Num() > 0 && !SCSTreeWidget->IsItemExpanded(InNodePtr))
{
OutCollapsedNodes.Add(InNodePtr);
}
});
}
EVisibility SSCSEditor::GetPromoteToBlueprintButtonVisibility() const
{
UObject* ActorContextPtr = GetActorContext();
return (UICustomization.IsValid() && UICustomization->HideBlueprintButtons(MakeArrayView(&ActorContextPtr, 1)))
|| (EditorMode != EComponentEditorMode::ActorInstance)
|| (GetBlueprint() != nullptr)
? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SSCSEditor::GetEditBlueprintButtonVisibility() const
{
UObject* ActorContextPtr = GetActorContext();
return (UICustomization.IsValid() && UICustomization->HideBlueprintButtons(MakeArrayView(&ActorContextPtr, 1)))
|| (EditorMode != EComponentEditorMode::ActorInstance)
|| (GetBlueprint() == nullptr)
? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SSCSEditor::GetComponentClassComboButtonVisibility() const
{
UObject* ActorContextPtr = GetActorContext();
return (HideComponentClassCombo.Get()
|| (UICustomization.IsValid() && UICustomization->HideAddComponentButton(MakeArrayView(&ActorContextPtr, 1))))
? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SSCSEditor::GetComponentsTreeVisibility() const
{
UObject* ActorContextPtr = GetActorContext();
return (UICustomization.IsValid() && UICustomization->HideComponentsTree(MakeArrayView(&ActorContextPtr, 1)))
? EVisibility::Collapsed : EVisibility::Visible;
}
EVisibility SSCSEditor::GetComponentsFilterBoxVisibility() const
{
UObject* ActorContextPtr = GetActorContext();
return (UICustomization.IsValid() && UICustomization->HideComponentsFilterBox(MakeArrayView(&ActorContextPtr, 1)))
? EVisibility::Collapsed : EVisibility::Visible;
}
FText SSCSEditor::OnGetApplyChangesToBlueprintTooltip() const
{
int32 NumChangedProperties = 0;
AActor* Actor = GetActorContext();
UBlueprint* Blueprint = (Actor != nullptr) ? Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy) : nullptr;
if(Actor != NULL && Blueprint != NULL && Actor->GetClass()->ClassGeneratedBy == Blueprint)
{
AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject<AActor>();
if(BlueprintCDO != NULL)
{
const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::PreviewOnly|EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties|EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties);
NumChangedProperties += EditorUtilities::CopyActorProperties(Actor, BlueprintCDO, CopyOptions);
}
NumChangedProperties += Actor->GetInstanceComponents().Num();
}
if(NumChangedProperties == 0)
{
return LOCTEXT("DisabledPushToBlueprintDefaults_ToolTip", "Replaces the Blueprint's defaults with any altered property values.");
}
else if(NumChangedProperties > 1)
{
return FText::Format(LOCTEXT("PushToBlueprintDefaults_ToolTip", "Click to apply {0} changed properties to the Blueprint."), FText::AsNumber(NumChangedProperties));
}
else
{
return LOCTEXT("PushOneToBlueprintDefaults_ToolTip", "Click to apply 1 changed property to the Blueprint.");
}
}
FText SSCSEditor::OnGetResetToBlueprintDefaultsTooltip() const
{
int32 NumChangedProperties = 0;
AActor* Actor = GetActorContext();
UBlueprint* Blueprint = (Actor != nullptr) ? Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy) : nullptr;
if(Actor != NULL && Blueprint != NULL && Actor->GetClass()->ClassGeneratedBy == Blueprint)
{
AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject<AActor>();
if(BlueprintCDO != NULL)
{
const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::PreviewOnly|EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties);
NumChangedProperties += EditorUtilities::CopyActorProperties(BlueprintCDO, Actor, CopyOptions);
}
NumChangedProperties += Actor->GetInstanceComponents().Num();
}
if(NumChangedProperties == 0)
{
return LOCTEXT("DisabledResetBlueprintDefaults_ToolTip", "Resets altered properties back to their Blueprint default values.");
}
else if(NumChangedProperties > 1)
{
return FText::Format(LOCTEXT("ResetToBlueprintDefaults_ToolTip", "Click to reset {0} changed properties to their Blueprint default values."), FText::AsNumber(NumChangedProperties));
}
else
{
return LOCTEXT("ResetOneToBlueprintDefaults_ToolTip", "Click to reset 1 changed property to its Blueprint default value.");
}
}
void SSCSEditor::OnOpenBlueprintEditor(bool bForceCodeEditing) const
{
if (AActor* ActorInstance = GetActorContext())
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(ActorInstance->GetClass()->ClassGeneratedBy))
{
if (bForceCodeEditing && (Blueprint->UbergraphPages.Num() > 0))
{
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(Blueprint->GetLastEditedUberGraph());
}
else
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Blueprint);
}
}
}
}
/**
This struct saves and deselects all selected instanced components (from given actor), then finds them (in recreated actor instance, after compilation) and selects them again.
*/
struct FRestoreSelectedInstanceComponent
{
TWeakObjectPtr<UClass> ActorClass;
FName ActorName;
TWeakObjectPtr<UObject> ActorOuter;
struct FComponentKey
{
FName Name;
TWeakObjectPtr<UClass> Class;
FComponentKey(FName InName, UClass* InClass) : Name(InName), Class(InClass) {}
};
TArray<FComponentKey> ComponentKeys;
FRestoreSelectedInstanceComponent()
: ActorClass(nullptr)
, ActorOuter(nullptr)
{ }
void Save(AActor* InActor)
{
check(InActor);
ActorClass = InActor->GetClass();
ActorName = InActor->GetFName();
ActorOuter = InActor->GetOuter();
check(GEditor);
TArray<UActorComponent*> ComponentsToSaveAndDelesect;
for (FSelectionIterator Iter = GEditor->GetSelectedComponentIterator(); Iter; ++Iter)
{
UActorComponent* Component = CastChecked<UActorComponent>(*Iter, ECastCheckedType::NullAllowed);
if (Component && InActor->GetInstanceComponents().Contains(Component))
{
ComponentsToSaveAndDelesect.Add(Component);
}
}
for (UActorComponent* Component : ComponentsToSaveAndDelesect)
{
USelection* SelectedComponents = GEditor->GetSelectedComponents();
if (ensure(SelectedComponents))
{
ComponentKeys.Add(FComponentKey(Component->GetFName(), Component->GetClass()));
SelectedComponents->Deselect(Component);
}
}
}
void Restore()
{
AActor* Actor = (ActorClass.IsValid() && ActorOuter.IsValid())
? Cast<AActor>((UObject*)FindObjectWithOuter(ActorOuter.Get(), ActorClass.Get(), ActorName))
: nullptr;
if (Actor)
{
for (const FComponentKey& IterKey : ComponentKeys)
{
UActorComponent* const* ComponentPtr = Algo::FindByPredicate(Actor->GetComponents(), [&](UActorComponent* InComp)
{
return InComp && (InComp->GetFName() == IterKey.Name) && (InComp->GetClass() == IterKey.Class.Get());
});
if (ComponentPtr && *ComponentPtr)
{
check(GEditor);
GEditor->SelectComponent(*ComponentPtr, true, false);
}
}
}
}
};
void SSCSEditor::OnApplyChangesToBlueprint() const
{
AActor* Actor = GetActorContext();
const UBlueprint* const Blueprint = (Actor != nullptr) ? Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy) : nullptr;
if (Actor != NULL && Blueprint != NULL && Actor->GetClass()->ClassGeneratedBy.Get() == Blueprint)
{
const FString ActorLabel = Actor->GetActorLabel();
int32 NumChangedProperties = FKismetEditorUtilities::ApplyInstanceChangesToBlueprint(Actor);
// Set up a notification record to indicate success/failure
FNotificationInfo NotificationInfo(FText::GetEmpty());
NotificationInfo.FadeInDuration = 1.0f;
NotificationInfo.FadeOutDuration = 2.0f;
NotificationInfo.bUseLargeFont = false;
SNotificationItem::ECompletionState CompletionState;
if (NumChangedProperties > 0)
{
if (NumChangedProperties > 1)
{
FFormatNamedArguments Args;
Args.Add(TEXT("BlueprintName"), FText::FromName(Blueprint->GetFName()));
Args.Add(TEXT("NumChangedProperties"), NumChangedProperties);
Args.Add(TEXT("ActorName"), FText::FromString(ActorLabel));
NotificationInfo.Text = FText::Format(LOCTEXT("PushToBlueprintDefaults_ApplySuccess", "Updated Blueprint {BlueprintName} ({NumChangedProperties} property changes applied from actor {ActorName})."), Args);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("BlueprintName"), FText::FromName(Blueprint->GetFName()));
Args.Add(TEXT("ActorName"), FText::FromString(ActorLabel));
NotificationInfo.Text = FText::Format(LOCTEXT("PushOneToBlueprintDefaults_ApplySuccess", "Updated Blueprint {BlueprintName} (1 property change applied from actor {ActorName})."), Args);
}
CompletionState = SNotificationItem::CS_Success;
}
else
{
NotificationInfo.Text = LOCTEXT("PushToBlueprintDefaults_ApplyFailed", "No properties were copied");
CompletionState = SNotificationItem::CS_Fail;
}
// Add the notification to the queue
const TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(NotificationInfo);
Notification->SetCompletionState(CompletionState);
}
}
void SSCSEditor::OnResetToBlueprintDefaults()
{
int32 NumChangedProperties = 0;
AActor* Actor = GetActorContext();
UBlueprint* Blueprint = (Actor != nullptr) ? Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy) : nullptr;
if ((Actor != NULL) && (Blueprint != NULL) && (Actor->GetClass()->ClassGeneratedBy == Blueprint))
{
const FScopedTransaction Transaction(LOCTEXT("ResetToBlueprintDefaults_Transaction", "Reset to Class Defaults"));
{
AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject<AActor>();
if (BlueprintCDO != NULL)
{
const EditorUtilities::ECopyOptions::Type CopyOptions = (EditorUtilities::ECopyOptions::Type)(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties);
NumChangedProperties = EditorUtilities::CopyActorProperties(BlueprintCDO, Actor, CopyOptions);
}
NumChangedProperties += Actor->GetInstanceComponents().Num();
Actor->ClearInstanceComponents(true);
}
// Set up a notification record to indicate success/failure
FNotificationInfo NotificationInfo(FText::GetEmpty());
NotificationInfo.FadeInDuration = 1.0f;
NotificationInfo.FadeOutDuration = 2.0f;
NotificationInfo.bUseLargeFont = false;
SNotificationItem::ECompletionState CompletionState;
if (NumChangedProperties > 0)
{
if (NumChangedProperties > 1)
{
FFormatNamedArguments Args;
Args.Add(TEXT("BlueprintName"), FText::FromName(Blueprint->GetFName()));
Args.Add(TEXT("NumChangedProperties"), NumChangedProperties);
Args.Add(TEXT("ActorName"), FText::FromString(Actor->GetActorLabel()));
NotificationInfo.Text = FText::Format(LOCTEXT("ResetToBlueprintDefaults_ApplySuccess", "Reset {ActorName} ({NumChangedProperties} property changes applied from Blueprint {BlueprintName})."), Args);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("BlueprintName"), FText::FromName(Blueprint->GetFName()));
Args.Add(TEXT("ActorName"), FText::FromString(Actor->GetActorLabel()));
NotificationInfo.Text = FText::Format(LOCTEXT("ResetOneToBlueprintDefaults_ApplySuccess", "Reset {ActorName} (1 property change applied from Blueprint {BlueprintName})."), Args);
}
CompletionState = SNotificationItem::CS_Success;
}
else
{
NotificationInfo.Text = LOCTEXT("ResetToBlueprintDefaults_Failed", "No properties were reset");
CompletionState = SNotificationItem::CS_Fail;
}
UpdateTree();
// Add the notification to the queue
const TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(NotificationInfo);
Notification->SetCompletionState(CompletionState);
}
}
void SSCSEditor::PromoteToBlueprint() const
{
FCreateBlueprintFromActorDialog::OpenDialog(ECreateBlueprintFromActorMode::Subclass, GetActorContext());
}
FReply SSCSEditor::OnPromoteToBlueprintClicked()
{
PromoteToBlueprint();
return FReply::Handled();
}
/** Returns the Actor context for which we are viewing/editing the SCS. Can return null. Should not be cached as it may change from frame to frame. */
AActor* SSCSEditor::GetActorContext() const
{
return ActorContext.Get(nullptr);
}
void SSCSEditor::SetItemExpansionRecursive(FSCSEditorTreeNodePtrType Model, bool bInExpansionState)
{
SetNodeExpansionState(Model, bInExpansionState);
for (const FSCSEditorTreeNodePtrType& Child : Model->GetChildren())
{
if (Child.IsValid())
{
SetItemExpansionRecursive(Child, bInExpansionState);
}
}
}
FText SSCSEditor::GetFilterText() const
{
return FilterBox->GetText();
}
void SSCSEditor::OnFilterTextChanged(const FText& /*InFilterText*/)
{
struct OnFilterTextChanged_Inner
{
static FSCSEditorTreeNodePtrType ExpandToFilteredChildren(SSCSEditor* SCSEditor, FSCSEditorTreeNodePtrType TreeNode)
{
FSCSEditorTreeNodePtrType NodeToFocus;
const TArray<FSCSEditorTreeNodePtrType>& Children = TreeNode->GetChildren();
// iterate backwards so we select from the top down
for (int32 ChildIndex = Children.Num() - 1; ChildIndex >= 0; --ChildIndex)
{
const FSCSEditorTreeNodePtrType& Child = Children[ChildIndex];
// Don't attempt to focus a separator or filtered node
if ((Child->GetNodeType() != FSCSEditorTreeNode::SeparatorNode) && !Child->IsFlaggedForFiltration())
{
SCSEditor->SetNodeExpansionState(TreeNode, /*bIsExpanded =*/true);
NodeToFocus = ExpandToFilteredChildren(SCSEditor, Child);
}
}
if (!NodeToFocus.IsValid() && !TreeNode->IsFlaggedForFiltration())
{
NodeToFocus = TreeNode;
}
return NodeToFocus;
}
};
FSCSEditorTreeNodePtrType NewSelection;
// iterate backwards so we select from the top down
for (int32 ComponentIndex = RootNodes.Num() - 1; ComponentIndex >= 0; --ComponentIndex)
{
FSCSEditorTreeNodePtrType Node = RootNodes[ComponentIndex];
bool bIsRootVisible = !RefreshFilteredState(Node, true);
SCSTreeWidget->SetItemExpansion(Node, bIsRootVisible);
if (bIsRootVisible)
{
if (!GetFilterText().IsEmpty())
{
NewSelection = OnFilterTextChanged_Inner::ExpandToFilteredChildren(this, Node);
}
}
}
if (!NewSelection.IsValid() && RootNodes.Num() > 0)
{
NewSelection = RootNodes[0];
}
if (NewSelection.IsValid() && !SCSTreeWidget->IsItemSelected(NewSelection))
{
SelectNode(NewSelection, /*IsCntrlDown =*/false);
}
UpdateTree(/*bRegenerateTreeNodes =*/false);
}
bool SSCSEditor::RefreshFilteredState(FSCSEditorTreeNodePtrType TreeNode, bool bRecursive)
{
const UClass* FilterType = GetComponentTypeFilterToApply();
FString FilterText = FText::TrimPrecedingAndTrailing( GetFilterText() ).ToString();
TArray<FString> FilterTerms;
FilterText.ParseIntoArray(FilterTerms, TEXT(" "), /*CullEmpty =*/true);
TreeNode->RefreshFilteredState(FilterType, FilterTerms, bRecursive);
return TreeNode->IsFlaggedForFiltration();
}
void SSCSEditor::SetUICustomization(TSharedPtr<ISCSEditorUICustomization> InUICustomization)
{
UICustomization = InUICustomization;
UpdateTree(true /*bRegenerateTreeNodes*/);
}
TSubclassOf<UActorComponent> SSCSEditor::GetComponentTypeFilterToApply() const
{
UObject* ActorContextPtr = GetActorContext();
TSubclassOf<UActorComponent> ComponentType = UICustomization.IsValid() ? UICustomization->GetComponentTypeFilter(MakeArrayView(&ActorContextPtr, 1)) : nullptr;
if (!ComponentType)
{
ComponentType = ComponentTypeFilter.Get();
}
return ComponentType;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#undef LOCTEXT_NAMESPACE