2019-12-26 15:33:43 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-05-22 12:00:20 -04:00
# include "AnimGraphDetails.h"
# include "IAnimationBlueprintEditor.h"
# include "DetailLayoutBuilder.h"
# include "DetailCategoryBuilder.h"
# include "EdGraph/EdGraph.h"
2019-09-23 07:23:26 -04:00
# include "AnimGraphNode_LinkedInputPose.h"
2019-05-22 12:00:20 -04:00
# include "Algo/Transform.h"
# include "DetailWidgetRow.h"
# include "Widgets/Text/STextBlock.h"
# include "Widgets/Layout/SBox.h"
# include "EdGraphSchema_K2_Actions.h"
# include "Widgets/Input/SButton.h"
# include "Widgets/Images/SImage.h"
# include "AnimGraphNode_Root.h"
# include "ScopedTransaction.h"
2019-11-07 15:39:36 -05:00
# include "ObjectEditorUtils.h"
2019-05-22 12:00:20 -04:00
# include "SKismetInspector.h"
2019-10-16 14:04:36 -04:00
# include "AnimationGraph.h"
2019-05-22 12:00:20 -04:00
# include "AnimationGraphSchema.h"
2022-09-22 16:20:10 -04:00
# include "AnimGraphNode_LinkedAnimLayer.h"
2019-05-22 12:00:20 -04:00
# include "Kismet2/BlueprintEditorUtils.h"
# include "Widgets/Input/SComboButton.h"
# include "Widgets/Input/SEditableTextBox.h"
# define LOCTEXT_NAMESPACE "FAnimGraphDetails"
TSharedPtr < IDetailCustomization > FAnimGraphDetails : : MakeInstance ( TSharedPtr < IBlueprintEditor > InBlueprintEditor )
{
const TArray < UObject * > * Objects = ( InBlueprintEditor . IsValid ( ) ? InBlueprintEditor - > GetObjectsCurrentlyBeingEdited ( ) : nullptr ) ;
if ( Objects & & Objects - > Num ( ) = = 1 )
{
if ( UAnimBlueprint * AnimBlueprint = Cast < UAnimBlueprint > ( ( * Objects ) [ 0 ] ) )
{
return MakeShareable ( new FAnimGraphDetails ( StaticCastSharedPtr < IAnimationBlueprintEditor > ( InBlueprintEditor ) , AnimBlueprint ) ) ;
}
}
return nullptr ;
}
void FAnimGraphDetails : : CustomizeDetails ( IDetailLayoutBuilder & DetailLayout )
{
TArray < TWeakObjectPtr < UObject > > Objects ;
DetailLayout . GetObjectsBeingCustomized ( Objects ) ;
Graph = CastChecked < UEdGraph > ( Objects [ 0 ] . Get ( ) ) ;
2019-10-16 14:04:36 -04:00
if ( IsInterface ( ) )
{
DetailLayout . HideCategory ( " GraphBlending " ) ;
}
2019-05-22 12:00:20 -04:00
bool const bIsStateMachine = ! Graph - > GetOuter ( ) - > IsA ( UAnimBlueprint : : StaticClass ( ) ) ;
if ( Objects . Num ( ) > 1 | | bIsStateMachine )
{
2019-09-23 07:23:26 -04:00
IDetailCategoryBuilder & InputsCategory = DetailLayout . EditCategory ( " Inputs " , LOCTEXT ( " LinkedInputPoseInputsCategory " , " Inputs " ) ) ;
2019-05-22 12:00:20 -04:00
InputsCategory . SetCategoryVisibility ( false ) ;
return ;
}
const bool bIsDefaultGraph = Graph - > GetFName ( ) = = UEdGraphSchema_K2 : : GN_AnimGraph ;
if ( ! Graph - > bAllowDeletion & & ! bIsDefaultGraph )
{
FText ReadOnlyWarning = LOCTEXT ( " ReadOnlyWarning " , " This graph's inputs are read-only and cannot be edited " ) ;
2019-09-23 07:23:26 -04:00
IDetailCategoryBuilder & InputsCategory = DetailLayout . EditCategory ( " Inputs " , LOCTEXT ( " LinkedInputPoseInputsCategory " , " Inputs " ) ) ;
2019-05-22 12:00:20 -04:00
InputsCategory . SetCategoryVisibility ( false ) ;
IDetailCategoryBuilder & WarningCategoryBuilder = DetailLayout . EditCategory ( " GraphInputs " , LOCTEXT ( " GraphInputsCategory " , " Graph Inputs " ) ) ;
WarningCategoryBuilder . AddCustomRow ( ReadOnlyWarning )
. WholeRowContent ( )
[
SNew ( STextBlock )
. Text ( ReadOnlyWarning )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
] ;
return ;
}
if ( ! bIsDefaultGraph )
{
IDetailCategoryBuilder & LayerCategory = DetailLayout . EditCategory ( " Layer " , LOCTEXT ( " LayerCategory " , " Layer " ) ) ;
{
FText GroupLabel ( LOCTEXT ( " LayerGroup " , " Group " ) ) ;
FText GroupToolTip ( LOCTEXT ( " LayerGroupToolTip " , " The group of this layer. Grouped layers will run using the same underlying instance, so can share state. " ) ) ;
RefreshGroupSource ( ) ;
LayerCategory . AddCustomRow ( GroupLabel )
. NameContent ( )
[
SNew ( STextBlock )
. Text ( GroupLabel )
. ToolTipText ( GroupToolTip )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
. ValueContent ( )
[
SAssignNew ( GroupComboButton , SComboButton )
. ContentPadding ( FMargin ( 0 , 0 , 5 , 0 ) )
. ToolTipText ( GroupToolTip )
. ButtonContent ( )
[
SNew ( SBorder )
2022-05-09 13:12:28 -04:00
. BorderImage ( FAppStyle : : GetBrush ( " NoBorder " ) )
2019-05-22 12:00:20 -04:00
. Padding ( FMargin ( 0 , 0 , 5 , 0 ) )
[
SNew ( SEditableTextBox )
. Text ( this , & FAnimGraphDetails : : OnGetGroupText )
. OnTextCommitted ( this , & FAnimGraphDetails : : OnGroupTextCommitted )
. ToolTipText ( GroupToolTip )
. SelectAllTextWhenFocused ( true )
. RevertTextOnEscape ( true )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
]
. MenuContent ( )
[
SNew ( SVerticalBox )
+ SVerticalBox : : Slot ( )
. AutoHeight ( )
. MaxHeight ( 400.0f )
[
SAssignNew ( GroupListView , SListView < TSharedPtr < FText > > )
. ListItemsSource ( & GroupSource )
. OnGenerateRow ( this , & FAnimGraphDetails : : MakeGroupViewWidget )
. OnSelectionChanged ( this , & FAnimGraphDetails : : OnGroupSelectionChanged )
]
]
] ;
}
}
2019-09-23 07:23:26 -04:00
IDetailCategoryBuilder & InputsCategory = DetailLayout . EditCategory ( " Inputs " , LOCTEXT ( " LinkedInputPoseInputsCategory " , " Inputs " ) ) ;
2019-11-05 05:38:45 -05:00
InputsCategory . RestoreExpansionState ( true ) ;
2019-05-22 12:00:20 -04:00
DetailLayoutBuilder = & DetailLayout ;
// Gather inputs, if any
2019-09-23 07:23:26 -04:00
TArray < UAnimGraphNode_LinkedInputPose * > LinkedInputPoseInputs ;
Graph - > GetNodesOfClass < UAnimGraphNode_LinkedInputPose > ( LinkedInputPoseInputs ) ;
2019-05-22 12:00:20 -04:00
TSharedRef < SHorizontalBox > InputsHeaderContentWidget = SNew ( SHorizontalBox ) ;
TWeakPtr < SWidget > WeakInputsHeaderWidget = InputsHeaderContentWidget ;
InputsHeaderContentWidget - > AddSlot ( )
[
SNew ( SHorizontalBox )
] ;
InputsHeaderContentWidget - > AddSlot ( )
. AutoWidth ( )
2019-09-17 19:12:19 -04:00
. VAlign ( VAlign_Center )
2019-05-22 12:00:20 -04:00
[
SNew ( SButton )
2022-05-09 13:12:28 -04:00
. ButtonStyle ( FAppStyle : : Get ( ) , " RoundButton " )
. ForegroundColor ( FAppStyle : : GetSlateColor ( " DefaultForeground " ) )
2019-05-22 12:00:20 -04:00
. ContentPadding ( FMargin ( 2 , 0 ) )
. OnClicked ( this , & FAnimGraphDetails : : OnAddNewInputPoseClicked )
. HAlign ( HAlign_Right )
. ToolTipText ( LOCTEXT ( " NewInputPoseTooltip " , " Create a new input pose " ) )
. VAlign ( VAlign_Center )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. AutoWidth ( )
. Padding ( FMargin ( 0 , 1 ) )
[
SNew ( SImage )
2022-05-09 13:12:28 -04:00
. Image ( FAppStyle : : GetBrush ( " Plus " ) )
2019-05-22 12:00:20 -04:00
]
+ SHorizontalBox : : Slot ( )
. VAlign ( VAlign_Center )
. AutoWidth ( )
. Padding ( FMargin ( 2 , 0 , 0 , 0 ) )
[
SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFontBold ( ) )
. Text ( LOCTEXT ( " NewInputPoseButtonText " , " New Input Pose " ) )
. Visibility ( this , & FAnimGraphDetails : : OnGetNewInputPoseTextVisibility , WeakInputsHeaderWidget )
. ShadowOffset ( FVector2D ( 1 , 1 ) )
]
]
] ;
InputsCategory . HeaderContent ( InputsHeaderContentWidget ) ;
2019-09-23 07:23:26 -04:00
if ( LinkedInputPoseInputs . Num ( ) )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
for ( UAnimGraphNode_LinkedInputPose * LinkedInputPoseNode : LinkedInputPoseInputs )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
auto GetLinkedInputPoseLabel = [ WeakLinkedInputPoseNode = TWeakObjectPtr < UAnimGraphNode_LinkedInputPose > ( LinkedInputPoseNode ) ] ( )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
if ( WeakLinkedInputPoseNode . IsValid ( ) )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
return FText : : FromName ( WeakLinkedInputPoseNode - > Node . Name ) ;
2019-05-22 12:00:20 -04:00
}
return FText : : GetEmpty ( ) ;
} ;
TArray < UObject * > ExternalObjects ;
2019-09-23 07:23:26 -04:00
ExternalObjects . Add ( LinkedInputPoseNode ) ;
2019-11-05 05:38:45 -05:00
FAddPropertyParams AddPropertyParams ;
AddPropertyParams . UniqueId ( LinkedInputPoseNode - > GetFName ( ) ) ;
if ( IDetailPropertyRow * LinkedInputPoseRow = InputsCategory . AddExternalObjects ( ExternalObjects , EPropertyLocation : : Default , AddPropertyParams ) )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
LinkedInputPoseRow - > CustomWidget ( )
2019-05-22 12:00:20 -04:00
. NameContent ( )
[
SNew ( SBox )
. Padding ( 2.0f )
[
SNew ( STextBlock )
. Text ( LOCTEXT ( " InputPose " , " Input Pose " ) )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
]
. ValueContent ( )
2019-09-03 11:26:06 -04:00
. MaxDesiredWidth ( 250.0f )
2019-05-22 12:00:20 -04:00
[
SNew ( SBox )
. Padding ( 2.0f )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. VAlign ( VAlign_Center )
2019-09-03 11:26:06 -04:00
. FillWidth ( 1.0f )
2019-05-22 12:00:20 -04:00
[
SNew ( STextBlock )
2019-09-23 07:23:26 -04:00
. Text_Lambda ( GetLinkedInputPoseLabel )
2019-05-22 12:00:20 -04:00
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
+ SHorizontalBox : : Slot ( )
. Padding ( 4.0f , 0.0f , 0.0f , 0.0f )
. VAlign ( VAlign_Center )
. HAlign ( HAlign_Right )
2019-09-03 11:26:06 -04:00
. AutoWidth ( )
2019-05-22 12:00:20 -04:00
[
SNew ( SButton )
2022-05-09 13:12:28 -04:00
. ButtonStyle ( FAppStyle : : Get ( ) , " HoverHintOnly " )
. ForegroundColor ( FAppStyle : : GetSlateColor ( " DefaultForeground " ) )
2019-05-22 12:00:20 -04:00
. ContentPadding ( FMargin ( 2 , 2 ) )
2019-09-23 07:23:26 -04:00
. OnClicked ( this , & FAnimGraphDetails : : OnRemoveInputPoseClicked , LinkedInputPoseNode )
2019-05-22 12:00:20 -04:00
. ToolTipText ( LOCTEXT ( " RemoveInputPoseTooltip " , " Remove this input pose " ) )
[
SNew ( SImage )
2022-05-09 13:12:28 -04:00
. Image ( FAppStyle : : GetBrush ( " Cross " ) )
2019-05-22 12:00:20 -04:00
]
]
]
] ;
}
}
}
else
{
// Add a text widget to let the user know to hit the + icon to add parameters.
InputsCategory . AddCustomRow ( FText : : GetEmpty ( ) )
. WholeRowContent ( )
. MaxDesiredWidth ( 980.f )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. VAlign ( VAlign_Center )
. Padding ( 0.0f , 0.0f , 4.0f , 0.0f )
. AutoWidth ( )
[
SNew ( STextBlock )
. Text ( LOCTEXT ( " NoInputPosesAddedForAnimGraph " , " Please press the + icon above to add input poses " ) )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
] ;
}
2019-10-16 14:04:36 -04:00
if ( IsInterface ( ) )
2019-05-22 12:00:20 -04:00
{
UAnimationGraphSchema : : AutoArrangeInterfaceGraph ( * Graph ) ;
}
}
FReply FAnimGraphDetails : : OnAddNewInputPoseClicked ( )
{
EK2NewNodeFlags NewNodeOperation = EK2NewNodeFlags : : None ;
FVector2D NewNodePosition ( 0.0f , 0.0f ) ;
2019-10-16 14:04:36 -04:00
if ( ! IsInterface ( ) )
2019-05-22 12:00:20 -04:00
{
2019-09-23 07:23:26 -04:00
NewNodePosition = UAnimationGraphSchema : : GetPositionForNewLinkedInputPoseNode ( * Graph ) ;
2019-05-22 12:00:20 -04:00
}
2019-09-23 07:23:26 -04:00
FEdGraphSchemaAction_K2NewNode : : SpawnNode < UAnimGraphNode_LinkedInputPose > ( Graph , NewNodePosition , EK2NewNodeFlags : : None ) ;
2019-05-22 12:00:20 -04:00
2022-09-22 16:20:10 -04:00
TSharedPtr < IAnimationBlueprintEditor > AnimBlueprintEditor = AnimBlueprintEditorPtr . Pin ( ) ;
UBlueprint * Blueprint = AnimBlueprintEditor - > GetBlueprintObj ( ) ;
UAnimGraphNode_LinkedInputPose : : ReconstructLayerNodes ( Blueprint ) ;
2019-05-22 12:00:20 -04:00
DetailLayoutBuilder - > ForceRefreshDetails ( ) ;
return FReply : : Handled ( ) ;
}
EVisibility FAnimGraphDetails : : OnGetNewInputPoseTextVisibility ( TWeakPtr < SWidget > WeakInputsHeaderWidget ) const
{
return WeakInputsHeaderWidget . Pin ( ) - > IsHovered ( ) ? EVisibility : : Visible : EVisibility : : Collapsed ;
}
2019-09-23 07:23:26 -04:00
FReply FAnimGraphDetails : : OnRemoveInputPoseClicked ( UAnimGraphNode_LinkedInputPose * InLinkedInputPose )
2019-05-22 12:00:20 -04:00
{
{
2019-09-23 07:23:26 -04:00
FScopedTransaction Transaction ( LOCTEXT ( " RemoveInputPose " , " Remove Linked Input Pose " ) ) ;
Graph - > RemoveNode ( InLinkedInputPose ) ;
2019-05-22 12:00:20 -04:00
}
TSharedPtr < IAnimationBlueprintEditor > AnimBlueprintEditor = AnimBlueprintEditorPtr . Pin ( ) ;
2022-09-22 16:20:10 -04:00
UBlueprint * Blueprint = AnimBlueprintEditor - > GetBlueprintObj ( ) ;
FBlueprintEditorUtils : : MarkBlueprintAsStructurallyModified ( Blueprint ) ;
2019-05-22 12:00:20 -04:00
2022-09-22 16:20:10 -04:00
UAnimGraphNode_LinkedInputPose : : ReconstructLayerNodes ( Blueprint ) ;
2019-05-22 12:00:20 -04:00
DetailLayoutBuilder - > ForceRefreshDetails ( ) ;
return FReply : : Handled ( ) ;
}
FText FAnimGraphDetails : : OnGetGroupText ( ) const
{
UAnimGraphNode_Root * Root = FBlueprintEditorUtils : : GetAnimGraphRoot ( Graph ) ;
2023-12-12 03:41:26 -05:00
if ( Root - > Node . GetGroup ( ) = = FAnimNode_Root : : DefaultSharedGroup )
2019-05-22 12:00:20 -04:00
{
2023-12-12 03:41:26 -05:00
return LOCTEXT ( " DefaultGroupSharedGroup " , " Default Shared Group " ) ;
}
else if ( Root - > Node . GetGroup ( ) = = NAME_None )
{
return LOCTEXT ( " DefaultGroupUngrouped " , " Ungrouped " ) ;
2019-05-22 12:00:20 -04:00
}
2021-04-22 04:57:09 -04:00
return FText : : FromName ( Root - > Node . GetGroup ( ) ) ;
2019-05-22 12:00:20 -04:00
}
void FAnimGraphDetails : : OnGroupTextCommitted ( const FText & NewText , ETextCommit : : Type InTextCommit )
{
if ( InTextCommit = = ETextCommit : : OnEnter | | InTextCommit = = ETextCommit : : OnUserMovedFocus )
{
// Remove excess whitespace and prevent categories with just spaces
FText GroupName = FText : : TrimPrecedingAndTrailing ( NewText ) ;
2023-12-12 03:41:26 -05:00
if ( GroupName . ToString ( ) . Equals ( TEXT ( " Ungrouped " ) ) )
2019-05-22 12:00:20 -04:00
{
GroupName = FText : : GetEmpty ( ) ;
}
FBlueprintEditorUtils : : SetAnimationGraphLayerGroup ( Graph , GroupName ) ;
AnimBlueprintEditorPtr . Pin ( ) - > RefreshMyBlueprint ( ) ;
RefreshGroupSource ( ) ;
}
}
void FAnimGraphDetails : : OnGroupSelectionChanged ( TSharedPtr < FText > ProposedSelection , ESelectInfo : : Type SelectInfo )
{
if ( ProposedSelection . IsValid ( ) )
{
FText GroupName = * ProposedSelection . Get ( ) ;
2023-12-12 03:41:26 -05:00
if ( GroupName . ToString ( ) . Equals ( TEXT ( " Ungrouped " ) ) )
2019-05-22 12:00:20 -04:00
{
GroupName = FText : : GetEmpty ( ) ;
}
FBlueprintEditorUtils : : SetAnimationGraphLayerGroup ( Graph , GroupName ) ;
AnimBlueprintEditorPtr . Pin ( ) - > RefreshMyBlueprint ( ) ;
GroupListView . Pin ( ) - > ClearSelection ( ) ;
GroupComboButton . Pin ( ) - > SetIsOpen ( false ) ;
RefreshGroupSource ( ) ;
}
}
TSharedRef < ITableRow > FAnimGraphDetails : : MakeGroupViewWidget ( TSharedPtr < FText > Item , const TSharedRef < STableViewBase > & OwnerTable )
{
return
SNew ( STableRow < TSharedPtr < FString > > , OwnerTable )
[
SNew ( STextBlock )
. Text ( * Item . Get ( ) )
] ;
}
2019-10-16 14:04:36 -04:00
bool FAnimGraphDetails : : IsInterface ( ) const
{
TSharedPtr < IAnimationBlueprintEditor > AnimBlueprintEditor = AnimBlueprintEditorPtr . Pin ( ) ;
return AnimBlueprintEditor - > GetBlueprintObj ( ) - > BlueprintType = = BPTYPE_Interface ;
}
2019-05-22 12:00:20 -04:00
void FAnimGraphDetails : : RefreshGroupSource ( )
{
TSharedPtr < IAnimationBlueprintEditor > AnimBlueprintEditor = AnimBlueprintEditorPtr . Pin ( ) ;
UClass * Class = AnimBlueprintEditor - > GetBlueprintObj ( ) - > SkeletonGeneratedClass ;
GroupSource . Empty ( ) ;
UAnimGraphNode_Root * Root = FBlueprintEditorUtils : : GetAnimGraphRoot ( Graph ) ;
2021-04-22 04:57:09 -04:00
if ( Root - > Node . GetGroup ( ) ! = NAME_None )
2019-05-22 12:00:20 -04:00
{
2023-12-12 03:41:26 -05:00
GroupSource . Add ( MakeShared < FText > ( LOCTEXT ( " DefaultGroupUngrouped " , " Ungrouped " ) ) ) ;
2019-05-22 12:00:20 -04:00
}
// Pull groups from implemented functions
for ( TFieldIterator < UFunction > FunctionIt ( Class , EFieldIteratorFlags : : IncludeSuper ) ; FunctionIt ; + + FunctionIt )
{
const UFunction * Function = * FunctionIt ;
if ( Function - > HasMetaData ( FBlueprintMetadata : : MD_AnimBlueprintFunction ) )
{
2019-11-07 15:39:36 -05:00
FText Group = FObjectEditorUtils : : GetCategoryText ( Function ) ;
2019-05-22 12:00:20 -04:00
if ( ! Group . IsEmpty ( ) )
{
bool bNewCategory = true ;
for ( int32 GroupIndex = 0 ; GroupIndex < GroupSource . Num ( ) & & bNewCategory ; + + GroupIndex )
{
bNewCategory & = ! GroupSource [ GroupIndex ] . Get ( ) - > EqualTo ( Group ) ;
}
if ( bNewCategory )
{
GroupSource . Add ( MakeShared < FText > ( Group ) ) ;
}
}
}
}
if ( GroupListView . IsValid ( ) )
{
GroupListView . Pin ( ) - > RequestListRefresh ( ) ;
}
}
# undef LOCTEXT_NAMESPACE