2021-05-04 16:26:56 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "SSubobjectInstanceEditor.h"
# include "ScopedTransaction.h"
# include "IDocumentation.h"
2021-05-06 20:51:27 -04:00
# include "Widgets/SToolTip.h"
2021-05-04 16:26:56 -04:00
# include "GraphEditorActions.h"
# include "Editor/EditorEngine.h"
# include "ISCSEditorUICustomization.h" // #TODO_BH Rename this to subobject
# include "SCSEditorExtensionContext.h" // #TODO_BH Rename this to subobject
# include "Styling/SlateIconFinder.h"
# include "SlateOptMacros.h"
# include "Widgets/Input/SSearchBox.h"
# include "Framework/MultiBox/MultiBoxBuilder.h"
# include "SComponentClassCombo.h"
# include "SEditorHeaderButton.h"
# include "Editor/UnrealEdEngine.h"
# include "Subsystems/PanelExtensionSubsystem.h" // SExtensionPanel
# include "Kismet2/ComponentEditorUtils.h"
# define LOCTEXT_NAMESPACE "SSubobjectInstanceEditor"
extern UNREALED_API UUnrealEdEngine * GUnrealEd ;
////////////////////////////////////////////////
// SSubobjectInstanceEditor
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSubobjectInstanceEditor : : Construct ( const FArguments & InArgs )
{
ObjectContext = InArgs . _ObjectContext ;
OnSelectionUpdated = InArgs . _OnSelectionUpdated ;
OnItemDoubleClicked = InArgs . _OnItemDoubleClicked ;
AllowEditing = InArgs . _AllowEditing ;
OnObjectReplaced = InArgs . _OnObjectReplaced ;
bAllowTreeUpdates = true ;
CreateCommandList ( ) ;
// Build the tree widget
FSlateBrush const * MobilityHeaderBrush = FEditorStyle : : GetBrush ( TEXT ( " ClassIcon.ComponentMobilityHeaderIcon " ) ) ;
ConstructTreeWidget ( ) ;
// Should only be true when used in the blueprints details panel
const bool bInlineSearchBarWithButtons = ShowInlineSearchWithButtons ( ) ;
TSharedPtr < SWidget > Contents ;
TSharedPtr < SVerticalBox > HeaderBox ;
TSharedPtr < SWidget > SearchBar = SAssignNew ( FilterBox , SSearchBox )
. HintText ( ! bInlineSearchBarWithButtons ? LOCTEXT ( " SearchComponentsHint " , " Search Components " ) : LOCTEXT ( " SearchHint " , " Search " ) )
. OnTextChanged ( this , & SSubobjectInstanceEditor : : OnFilterTextChanged )
. Visibility ( this , & SSubobjectInstanceEditor : : GetComponentsFilterBoxVisibility ) ;
FMenuBuilder EditBlueprintMenuBuilder = CreateMenuBuilder ( ) ;
// Extension context for new boi
USCSEditorExtensionContext * ExtensionContext = NewObject < USCSEditorExtensionContext > ( ) ;
ExtensionContext - > SubobjectEditor = 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 ( HideComponentClassCombo . Get ( ) ? EVisibility : : Hidden : EVisibility : : Visible )
. OnSubobjectClassSelected ( this , & SSubobjectInstanceEditor : : PerformComboAddClass )
. ToolTipText ( LOCTEXT ( " AddComponent_Tooltip " , " Adds a new component to this actor " ) )
. IsEnabled ( true )
]
+ SHorizontalBox : : Slot ( )
. VAlign ( VAlign_Center )
2021-05-06 20:51:27 -04:00
. Padding ( 0.0f , 0.0f , 4.0f , 0.0f )
2021-05-04 16:26:56 -04:00
. 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 )
2021-05-06 20:51:27 -04:00
. Padding ( 0.0f , 0.0f , 4.0f , 0.0f )
2021-05-04 16:26:56 -04:00
. AutoWidth ( )
[
SNew ( SEditorHeaderButton )
. AddMetaData < FTagMetaData > ( FTagMetaData ( TEXT ( " Actor.ConvertToBlueprint " ) ) )
. Visibility ( this , & SSubobjectInstanceEditor : : GetPromoteToBlueprintButtonVisibility )
. OnClicked ( this , & SSubobjectInstanceEditor : : 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 " ) ,
nullptr ,
TEXT ( " Shared/LevelEditor " ) ,
TEXT ( " ConvertToBlueprint " ) ) )
]
+ SHorizontalBox : : Slot ( )
. VAlign ( VAlign_Center )
. AutoWidth ( )
[
SNew ( SEditorHeaderButton )
. AddMetaData < FTagMetaData > ( FTagMetaData ( TEXT ( " Actor.EditBlueprint " ) ) )
. Visibility ( this , & SSubobjectInstanceEditor : : 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 , & SSubobjectInstanceEditor : : GetComponentsTreeVisibility )
[
TreeWidget . ToSharedRef ( )
]
] ;
// Only insert the buttons and search bar in the Blueprints version
if ( bInlineSearchBarWithButtons )
{
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 ( )
] ;
// Populate the tree with subobject data
UpdateTree ( ) ;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSubobjectInstanceEditor : : OnDeleteNodes ( )
{
const FScopedTransaction Transaction ( LOCTEXT ( " RemoveComponents " , " Remove Components " ) ) ;
// Invalidate any active component in the visualizer
GUnrealEd - > ComponentVisManager . ClearActiveComponentVis ( ) ;
// Gather the handles of the components that we want to delete
TArray < FSubobjectDataHandle > HandlesToDelete ;
TArray < FSubobjectEditorTreeNodePtrType > SelectedNodes = GetSelectedNodes ( ) ;
for ( const FSubobjectEditorTreeNodePtrType & Node : SelectedNodes )
{
check ( Node - > IsValid ( ) ) ;
const FSubobjectData * Data = Node - > GetDataSource ( ) ;
if ( Data & & Data - > IsComponent ( ) )
{
HandlesToDelete . Add ( Data - > GetHandle ( ) ) ;
}
}
if ( USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) )
{
FSubobjectDataHandle HandleToSelect ;
int32 NumDeleted = System - > DeleteSubobjects ( RootNodes [ 0 ] - > GetDataHandle ( ) , HandlesToDelete , HandleToSelect ) ;
if ( NumDeleted > 0 )
2021-06-08 20:01:03 -04:00
{
FSubobjectEditorTreeNodePtrType NodeToSelect = HandleToSelect . IsValid ( ) ? FindSlateNodeForHandle ( HandleToSelect ) : GetSceneRootNode ( ) ;
if ( NodeToSelect . IsValid ( ) )
2021-05-04 16:26:56 -04:00
{
2021-06-08 20:01:03 -04:00
TreeWidget - > SetSelection ( NodeToSelect ) ;
2021-05-04 16:26:56 -04:00
}
UpdateTree ( ) ;
// Do this AFTER marking the Blueprint as modified
UpdateSelectionFromNodes ( TreeWidget - > GetSelectedItems ( ) ) ;
}
}
}
void SSubobjectInstanceEditor : : CopySelectedNodes ( )
{
TArray < FSubobjectDataHandle > SelectedHandles = GetSelectedHandles ( ) ;
if ( USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) )
{
return System - > CopySubobjects ( SelectedHandles , /* BpContext = */ nullptr ) ;
}
}
void SSubobjectInstanceEditor : : OnDuplicateComponent ( )
{
TArray < FSubobjectDataHandle > SelectedNodes = GetSelectedHandles ( ) ;
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 " ) ) ;
if ( USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) )
{
System - > DuplicateSubobjects ( GetObjectContextHandle ( ) , SelectedNodes , /* BpContext = */ nullptr ) ;
UpdateTree ( ) ;
}
}
}
void SSubobjectInstanceEditor : : PasteNodes ( )
{
if ( USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) )
{
TArray < FSubobjectDataHandle > OutHandles ;
System - > PasteSubobjects ( GetObjectContextHandle ( ) , GetSelectedHandles ( ) , nullptr , OutHandles ) ;
if ( OutHandles . Num ( ) > 0 )
{
// We only want the pasted node(s) to be selected
TreeWidget - > ClearSelection ( ) ;
UpdateTree ( ) ;
for ( const FSubobjectDataHandle & Handle : OutHandles )
{
if ( FSubobjectEditorTreeNodePtrType SlateNode = FindSlateNodeForHandle ( Handle ) )
{
TreeWidget - > SetItemSelection ( SlateNode , true ) ;
}
}
}
}
}
void SSubobjectInstanceEditor : : OnAttachToDropAction ( FSubobjectEditorTreeNodePtrType DroppedOn , const TArray < FSubobjectEditorTreeNodePtrType > & DroppedNodePtrs )
{
// Ask the subsystem to attach the dropped nodes onto the dropped on node
check ( DroppedOn . IsValid ( ) ) ;
check ( DroppedNodePtrs . Num ( ) > 0 ) ;
USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) ;
check ( System ) ;
const FScopedTransaction TransactionContext ( DroppedNodePtrs . Num ( ) > 1 ? LOCTEXT ( " AttachComponents " , " Attach Components " ) : LOCTEXT ( " AttachComponent " , " Attach Component " ) ) ;
TArray < FSubobjectDataHandle > HandlesToMove ;
Utils : : PopulateHandlesArray ( DroppedNodePtrs , HandlesToMove ) ;
FReparentSubobjectParams Params ;
Params . NewParentHandle = DroppedOn - > GetDataHandle ( ) ;
System - > ReparentSubobjects ( Params , HandlesToMove ) ;
check ( TreeWidget . IsValid ( ) ) ;
TreeWidget - > SetItemExpansion ( DroppedOn , true ) ;
PostDragDropAction ( true ) ;
}
void SSubobjectInstanceEditor : : OnDetachFromDropAction ( const TArray < FSubobjectEditorTreeNodePtrType > & DroppedNodePtrs )
{
check ( DroppedNodePtrs . Num ( ) > 0 ) ;
const FScopedTransaction TransactionContext ( DroppedNodePtrs . Num ( ) > 1 ? LOCTEXT ( " DetachComponents " , " Detach Components " ) : LOCTEXT ( " DetachComponent " , " Detach Component " ) ) ;
TArray < FSubobjectDataHandle > HandlesToMove ;
Utils : : PopulateHandlesArray ( DroppedNodePtrs , HandlesToMove ) ;
// Attach the dropped node to the current scene root node
FSubobjectEditorTreeNodePtrType SceneRootNodePtr = GetSceneRootNode ( ) ;
check ( SceneRootNodePtr . IsValid ( ) ) ;
// Ask the subsystem to reparent this object to the scene root
if ( USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) )
{
FReparentSubobjectParams Params ;
Params . NewParentHandle = SceneRootNodePtr - > GetDataHandle ( ) ;
System - > ReparentSubobjects ( Params , HandlesToMove ) ;
}
2021-05-14 16:06:25 -04:00
PostDragDropAction ( true ) ;
2021-05-04 16:26:56 -04:00
}
void SSubobjectInstanceEditor : : OnMakeNewRootDropAction ( FSubobjectEditorTreeNodePtrType DroppedNodePtr )
{
// Get the current scene root node
FSubobjectEditorTreeNodePtrType SceneRootNodePtr = GetSceneRootNode ( ) ;
// Create a transaction record
const FScopedTransaction TransactionContext ( LOCTEXT ( " MakeNewSceneRoot " , " Make New Scene Root " ) ) ;
USubobjectDataSubsystem * Subsystem = USubobjectDataSubsystem : : Get ( ) ;
const bool bSuccess = Subsystem - > MakeNewSceneRoot (
GetObjectContextHandle ( ) ,
DroppedNodePtr - > GetDataHandle ( ) ,
nullptr ) ;
PostDragDropAction ( true ) ;
}
void SSubobjectInstanceEditor : : PostDragDropAction ( bool bRegenerateTreeNodes )
{
GUnrealEd - > ComponentVisManager . ClearActiveComponentVis ( ) ;
UpdateTree ( bRegenerateTreeNodes ) ;
if ( AActor * ActorInstance = Cast < AActor > ( GetObjectContext ( ) ) )
{
ActorInstance - > RerunConstructionScripts ( ) ;
}
}
TSharedPtr < SWidget > SSubobjectInstanceEditor : : BuildSceneRootDropActionMenu ( FSubobjectEditorTreeNodePtrType DroppedOntoNodePtr , FSubobjectEditorTreeNodePtrType DroppedNodePtr )
{
FMenuBuilder MenuBuilder ( true , CommandList ) ;
const FSubobjectData * DroppedNodeData = DroppedNodePtr - > GetDataSource ( ) ;
const FSubobjectData * DroppedOntoNodeData = DroppedOntoNodePtr - > GetDataSource ( ) ;
check ( DroppedNodeData ) ;
MenuBuilder . BeginSection ( " SceneRootNodeDropActions " , LOCTEXT ( " SceneRootNodeDropActionContextMenu " , " Drop Actions " ) ) ;
{
const FText DroppedVariableNameText = FText : : FromName ( DroppedNodeData - > GetVariableName ( ) ) ;
const FText NodeVariableNameText = FText : : FromName ( DroppedOntoNodeData - > GetVariableName ( ) ) ;
bool bDroppedInSameBlueprint = true ;
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 , & SSubobjectEditor : : OnAttachToDropAction , DroppedOntoNodePtr , DroppedNodePtr ) ,
FCanExecuteAction ( ) ) ) ;
const bool bIsDefaultSceneRoot = DroppedNodeData - > 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 , & SSubobjectEditor : : OnMakeNewRootDropAction , DroppedNodePtr ) ,
FCanExecuteAction ( ) ) ) ;
}
MenuBuilder . EndSection ( ) ;
return MenuBuilder . MakeWidget ( ) ;
}
FSubobjectDataHandle SSubobjectInstanceEditor : : AddNewSubobject ( const FSubobjectDataHandle & ParentHandle , UClass * NewClass , UObject * AssetOverride , FText & OutFailReason , TUniquePtr < FScopedTransaction > InOngoingTransaction )
{
FAddNewSubobjectParams Params ;
Params . ParentHandle = ParentHandle ;
Params . NewClass = NewClass ;
Params . AssetOverride = AssetOverride ;
// This is an instance, so the blueprint context is null!
Params . BlueprintContext = nullptr ;
USubobjectDataSubsystem * System = USubobjectDataSubsystem : : Get ( ) ;
check ( System ) ;
return System - > AddNewSubobject ( Params , OutFailReason ) ;
}
void SSubobjectInstanceEditor : : PopulateContextMenuImpl ( UToolMenu * InMenu , TArray < FSubobjectEditorTreeNodePtrType > & InSelectedItems , bool bIsChildActorSubtreeNodeSelected )
{
TArray < UActorComponent * > SelectedComponents ;
for ( const FSubobjectEditorTreeNodePtrType & SelectedNodePtr : InSelectedItems )
{
check ( SelectedNodePtr - > IsValid ( ) ) ;
// Get the component template associated with the selected node
const UActorComponent * ComponentTemplate = SelectedNodePtr - > GetComponentTemplate ( ) ;
if ( ComponentTemplate )
{
// #TODO_BH Remove this const cast
SelectedComponents . Add ( const_cast < UActorComponent * > ( ComponentTemplate ) ) ;
}
}
// Common menu options added for all component types
FComponentEditorUtils : : FillComponentContextMenuOptions ( InMenu , SelectedComponents ) ;
}
FSlateColor SSubobjectInstanceEditor : : GetColorTintForIcon ( FSubobjectEditorTreeNodePtrType Node ) const
{
static const FLinearColor IntroducedHereColor ( FLinearColor : : White ) ;
// A blue-ish tint
static const FLinearColor InheritedBlueprintComponentColor ( 0.08f , 0.35f , 0.6f ) ;
static const FLinearColor InstancedInheritedBlueprintComponentColor ( 0.08f , 0.35f , 0.6f ) ;
// A green-ish tint
static const FLinearColor InheritedNativeComponentColor ( 0.7f , 0.9f , 0.7f ) ;
const FSubobjectData * Data = Node ? Node - > GetDataSource ( ) : nullptr ;
if ( ! Data )
{
return IntroducedHereColor ;
}
if ( Data - > IsInheritedComponent ( ) )
{
// Native C++ components will be tinted green
if ( Data - > IsNativeComponent ( ) )
{
return InheritedNativeComponentColor ;
}
else if ( Data - > IsInstancedComponent ( ) )
{
return InstancedInheritedBlueprintComponentColor ;
}
else
{
return InheritedBlueprintComponentColor ;
}
}
// If its not an actor, instanced, or native inherited component then it must be inherited from a BP
else if ( ! Data - > IsActor ( ) & & ! Data - > IsInstancedComponent ( ) )
{
return InheritedBlueprintComponentColor ;
}
else if ( Data - > IsActor ( ) )
{
return FSlateColor : : UseForeground ( ) ;
}
return IntroducedHereColor ;
}
# undef LOCTEXT_NAMESPACE