2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-05-15 17:34:14 -04:00
# include "GraphEditorCommon.h"
# include "SGraphNodeKnot.h"
2014-05-16 13:18:18 -04:00
# include "ScopedTransaction.h"
# include "BlueprintEditorUtils.h"
2014-10-14 22:50:06 -04:00
# include "GenericCommands.h"
# include "SInlineEditableTextBlock.h"
2014-05-16 13:18:18 -04:00
2015-04-28 04:58:34 -04:00
namespace SKnotNodeDefinitions
{
/** Offset from the left edge to display comment toggle button at */
static const float KnotCenterButtonAdjust = 3.f ;
/** Offset from the left edge to display comment bubbles at */
static const float KnotCenterBubbleAdjust = 20.f ;
/** Knot node spacer sizes */
static const FVector2D NodeSpacerSize ( 42.0f , 24.0f ) ;
}
2014-05-15 17:34:14 -04:00
class FAmbivalentDirectionDragConnection : public FDragConnection
{
public :
// FDragDropOperation interface
2014-06-13 06:14:46 -04:00
virtual void OnDragged ( const class FDragDropEvent & DragDropEvent ) override ;
2014-05-15 17:34:14 -04:00
// End of FDragDropOperation interface
UEdGraphPin * GetBestPin ( ) const ;
// FDragConnection interface
2014-06-13 06:14:46 -04:00
virtual void ValidateGraphPinList ( TArray < UEdGraphPin * > & OutValidPins ) override ;
2014-05-15 17:34:14 -04:00
// End of FDragConnection interface
2014-06-18 15:58:03 -04:00
static TSharedRef < FAmbivalentDirectionDragConnection > New ( UK2Node_Knot * InKnot , const TSharedRef < SGraphPanel > & InGraphPanel , const TArray < TSharedRef < SGraphPin > > & InStartingPins , bool bInShiftOperation )
{
TSharedRef < FAmbivalentDirectionDragConnection > Operation = MakeShareable ( new FAmbivalentDirectionDragConnection ( InKnot , InGraphPanel , InStartingPins , bInShiftOperation ) ) ;
Operation - > Construct ( ) ;
return Operation ;
}
protected :
FAmbivalentDirectionDragConnection ( UK2Node_Knot * InKnot , const TSharedRef < SGraphPanel > & InGraphPanel , const TArray < TSharedRef < SGraphPin > > & InStartingPins , bool bInShiftOperation )
: FDragConnection ( InGraphPanel , InStartingPins , bInShiftOperation )
, KnotPtr ( InKnot )
, StartScreenPos ( FVector2D : : ZeroVector )
, MostRecentScreenPos ( FVector2D : : ZeroVector )
, bLatchedStartScreenPos ( false )
{
}
2014-05-15 17:34:14 -04:00
protected :
TWeakObjectPtr < UK2Node_Knot > KnotPtr ;
FVector2D StartScreenPos ;
FVector2D MostRecentScreenPos ;
bool bLatchedStartScreenPos ;
} ;
UEdGraphPin * FAmbivalentDirectionDragConnection : : GetBestPin ( ) const
{
if ( bLatchedStartScreenPos )
{
if ( UK2Node_Knot * Knot = KnotPtr . Get ( ) )
{
const bool bIsRight = MostRecentScreenPos . X > = StartScreenPos . X ;
return bIsRight ? Knot - > GetOutputPin ( ) : Knot - > GetInputPin ( ) ;
}
}
return nullptr ;
}
void FAmbivalentDirectionDragConnection : : OnDragged ( const class FDragDropEvent & DragDropEvent )
{
if ( bLatchedStartScreenPos )
{
const FVector2D LastScreenPos = MostRecentScreenPos ;
MostRecentScreenPos = DragDropEvent . GetScreenSpacePosition ( ) ;
// Switch directions on the preview connector as we cross from left to right of the starting drag point (or vis versa)
const bool bWasRight = LastScreenPos . X > = StartScreenPos . X ;
const bool bIsRight = MostRecentScreenPos . X > = StartScreenPos . X ;
if ( bWasRight ^ bIsRight )
{
GraphPanel - > OnStopMakingConnection ( /*bForceStop=*/ true ) ;
GraphPanel - > OnBeginMakingConnection ( GetBestPin ( ) ) ;
}
}
else
{
StartScreenPos = DragDropEvent . GetScreenSpacePosition ( ) ;
MostRecentScreenPos = StartScreenPos ;
bLatchedStartScreenPos = true ;
}
FDragConnection : : OnDragged ( DragDropEvent ) ;
}
void FAmbivalentDirectionDragConnection : : ValidateGraphPinList ( TArray < UEdGraphPin * > & OutValidPins )
{
OutValidPins . Empty ( StartingPins . Num ( ) ) ;
if ( UK2Node_Knot * Knot = KnotPtr . Get ( ) )
{
bool bUseOutput = true ;
// Pick output or input based on if the drag op is currently to the left or to the right of the starting drag point
if ( bLatchedStartScreenPos )
{
bUseOutput = ( StartScreenPos . X < MostRecentScreenPos . X ) ;
}
if ( UEdGraphPin * TargetPinObj = GetHoveredPin ( ) )
{
// if (UK2Node_Knot* TargetKnot = Cast<UK2Node_Knot>(TargetPinObj->GetOwningNode()))
// {
// // The visible pin on a knot is always an output, so Rely on the direction matching; since the visible pin on another knot is always an output
// }
// else
{
// Dragging to another pin, pick the opposite direction as a source to maximize connection chances
if ( TargetPinObj - > Direction = = EGPD_Input )
{
bUseOutput = true ;
}
else
{
bUseOutput = false ;
}
}
}
// Switch the effective valid pin so it makes sense for the current drag context
if ( bUseOutput )
{
OutValidPins . Add ( Knot - > GetOutputPin ( ) ) ;
}
else
{
OutValidPins . Add ( Knot - > GetInputPin ( ) ) ;
}
}
else
{
// Fall back to the default behavior
FDragConnection : : ValidateGraphPinList ( OutValidPins ) ;
}
}
/////////////////////////////////////////////////////
// SGraphPinKnot
class SGraphPinKnot : public SGraphPin
{
public :
SLATE_BEGIN_ARGS ( SGraphPinKnot ) { }
SLATE_END_ARGS ( )
void Construct ( const FArguments & InArgs , UEdGraphPin * InPin ) ;
// SWidget interface
2014-06-13 06:14:46 -04:00
virtual void OnDragEnter ( const FGeometry & MyGeometry , const FDragDropEvent & DragDropEvent ) override ;
2014-05-15 17:34:14 -04:00
// End of SWidget interface
protected :
// Begin SGraphPin interface
2014-06-13 06:14:46 -04:00
virtual TSharedRef < SWidget > GetDefaultValueWidget ( ) override ;
virtual TSharedRef < FDragDropOperation > SpawnPinDragEvent ( const TSharedRef < SGraphPanel > & InGraphPanel , const TArray < TSharedRef < SGraphPin > > & InStartingPins , bool bInShiftOperation ) override ;
virtual FReply OnPinMouseDown ( const FGeometry & SenderGeometry , const FPointerEvent & MouseEvent ) override ;
virtual FSlateColor GetPinColor ( ) const override ;
2014-05-15 17:34:14 -04:00
// End SGraphPin interface
} ;
void SGraphPinKnot : : Construct ( const FArguments & InArgs , UEdGraphPin * InPin )
{
SGraphPin : : Construct ( SGraphPin : : FArguments ( ) . SideToSideMargin ( 0.0f ) , InPin ) ;
}
void SGraphPinKnot : : OnDragEnter ( const FGeometry & MyGeometry , const FDragDropEvent & DragDropEvent )
{
TSharedPtr < FDragDropOperation > Operation = DragDropEvent . GetOperation ( ) ;
if ( Operation . IsValid ( ) & & Operation - > IsOfType < FDragConnection > ( ) )
{
TSharedPtr < FDragConnection > DragConnectionOp = StaticCastSharedPtr < FDragConnection > ( Operation ) ;
TArray < UEdGraphPin * > ValidPins ;
DragConnectionOp - > ValidateGraphPinList ( /*out*/ ValidPins ) ;
if ( ValidPins . Num ( ) > 0 )
{
if ( UK2Node_Knot * Knot = Cast < UK2Node_Knot > ( GraphPinObj - > GetOwningNode ( ) ) )
{
// Dragging to another pin, pick the opposite direction as a source to maximize connection chances
UEdGraphPin * PinToHoverOver = ( ValidPins [ 0 ] - > Direction = = EGPD_Input ) ? Knot - > GetOutputPin ( ) : Knot - > GetInputPin ( ) ;
DragConnectionOp - > SetHoveredPin ( PinToHoverOver ) ;
// Pins treat being dragged over the same as being hovered outside of drag and drop if they know how to respond to the drag action.
SBorder : : OnMouseEnter ( MyGeometry , DragDropEvent ) ;
return ;
}
}
}
SGraphPin : : OnDragEnter ( MyGeometry , DragDropEvent ) ;
}
FSlateColor SGraphPinKnot : : GetPinColor ( ) const
{
// Make ourselves transparent if we're the input, since we are underneath the output pin and would double-blend looking ugly
return ( GetPinObj ( ) - > Direction = = EEdGraphPinDirection : : EGPD_Input ) ? FLinearColor : : Transparent : SGraphPin : : GetPinColor ( ) ;
}
TSharedRef < SWidget > SGraphPinKnot : : GetDefaultValueWidget ( )
{
return SNullWidget : : NullWidget ;
}
TSharedRef < FDragDropOperation > SGraphPinKnot : : SpawnPinDragEvent ( const TSharedRef < SGraphPanel > & InGraphPanel , const TArray < TSharedRef < SGraphPin > > & InStartingPins , bool bInShiftOperation )
{
2014-06-18 15:58:03 -04:00
TSharedRef < FAmbivalentDirectionDragConnection > Operation = FAmbivalentDirectionDragConnection : : New ( CastChecked < UK2Node_Knot > ( GetPinObj ( ) - > GetOwningNode ( ) ) , InGraphPanel , InStartingPins , bInShiftOperation ) ;
2014-05-15 17:34:14 -04:00
return Operation ;
}
FReply SGraphPinKnot : : OnPinMouseDown ( const FGeometry & SenderGeometry , const FPointerEvent & MouseEvent )
{
if ( MouseEvent . GetEffectingButton ( ) = = EKeys : : LeftMouseButton )
{
if ( ! GraphPinObj - > bNotConnectable & & IsEditable . Get ( ) )
{
if ( MouseEvent . IsAltDown ( ) )
{
// Normally break connections, but overloaded here to delete the node entirely
const FScopedTransaction Transaction ( FGenericCommands : : Get ( ) . Delete - > GetDescription ( ) ) ;
UK2Node_Knot * NodeToDelete = CastChecked < UK2Node_Knot > ( GetPinObj ( ) - > GetOwningNode ( ) ) ;
UBlueprint * OwnerBlueprint = NodeToDelete - > GetBlueprint ( ) ;
NodeToDelete - > GetGraph ( ) - > Modify ( ) ;
FBlueprintEditorUtils : : RemoveNode ( OwnerBlueprint , NodeToDelete , /*bDontRecompile=*/ true ) ;
FBlueprintEditorUtils : : MarkBlueprintAsModified ( OwnerBlueprint ) ;
return FReply : : Handled ( ) ;
}
else if ( MouseEvent . IsControlDown ( ) )
{
// Normally moves the connections from one pin to another, but moves the node instead since it's really representing a set of connections
// Returning unhandled will cause the node behind us to catch it and move us
return FReply : : Unhandled ( ) ;
}
}
}
return SGraphPin : : OnPinMouseDown ( SenderGeometry , MouseEvent ) ;
}
//////////////////////////////////////////////////////////////////////////
// SGraphNodeKnot
void SGraphNodeKnot : : Construct ( const FArguments & InArgs , UK2Node_Knot * InKnot )
{
SGraphNodeDefault : : Construct ( SGraphNodeDefault : : FArguments ( ) . GraphNodeObj ( InKnot ) ) ;
}
void SGraphNodeKnot : : UpdateGraphNode ( )
{
InputPins . Empty ( ) ;
OutputPins . Empty ( ) ;
// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
RightNodeBox . Reset ( ) ;
LeftNodeBox . Reset ( ) ;
//@TODO: Keyboard focus on edit doesn't work unless the node is visible, but the text is just the comment and it's already shown in a bubble, so Transparent black it is...
InlineEditableText = SNew ( SInlineEditableTextBlock )
. ColorAndOpacity ( FLinearColor : : Transparent )
. Style ( FEditorStyle : : Get ( ) , " Graph.Node.NodeTitleInlineEditableText " )
. Text ( this , & SGraphNodeKnot : : GetEditableNodeTitleAsText )
. OnVerifyTextChanged ( this , & SGraphNodeKnot : : OnVerifyNameTextChanged )
. OnTextCommitted ( this , & SGraphNodeKnot : : OnNameTextCommited )
. IsReadOnly ( this , & SGraphNodeKnot : : IsNameReadOnly )
. IsSelected ( this , & SGraphNodeKnot : : IsSelectedExclusively ) ;
this - > ContentScale . Bind ( this , & SGraphNode : : GetContentScale ) ;
2014-11-03 10:40:57 -05:00
this - > GetOrAddSlot ( ENodeZone : : Center )
2014-05-15 17:34:14 -04:00
. HAlign ( HAlign_Center )
. VAlign ( VAlign_Center )
[
SNew ( SOverlay )
+ SOverlay : : Slot ( )
[
// Grab handle to be able to move the node
SNew ( SSpacer )
2015-04-28 04:58:34 -04:00
. Size ( SKnotNodeDefinitions : : NodeSpacerSize )
2014-05-15 17:34:14 -04:00
. Visibility ( EVisibility : : Visible )
. Cursor ( EMouseCursor : : CardinalCross )
]
+ SOverlay : : Slot ( )
// .VAlign(VAlign_Center)
// .HAlign(HAlign_Center)
[
SNew ( SVerticalBox )
+ SVerticalBox : : Slot ( )
. VAlign ( VAlign_Top )
. HAlign ( HAlign_Center )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. AutoWidth ( )
[
SNew ( SOverlay )
+ SOverlay : : Slot ( )
[
// LEFT
SAssignNew ( LeftNodeBox , SVerticalBox )
]
+ SOverlay : : Slot ( )
[
// RIGHT
SAssignNew ( RightNodeBox , SVerticalBox )
]
]
]
]
] ;
2014-11-05 13:10:16 -05:00
// Create comment bubble
2015-04-28 04:58:34 -04:00
const FSlateColor CommentColor = GetDefault < UGraphEditorSettings > ( ) - > DefaultCommentNodeTitleColor ;
2014-05-15 17:34:14 -04:00
2015-04-28 04:58:34 -04:00
SAssignNew ( CommentBubble , SCommentBubble )
. GraphNode ( GraphNode )
. Text ( this , & SGraphNode : : GetNodeComment )
. OnTextCommitted ( this , & SGraphNode : : OnCommentTextCommitted )
. ToggleButtonCheck ( this , & SGraphNodeKnot : : GetBubbleCheckState )
. EnableTitleBarBubble ( true )
. EnableBubbleCtrls ( true )
. AllowPinning ( true )
. ColorAndOpacity ( CommentColor )
. GraphLOD ( this , & SGraphNode : : GetCurrentLOD )
. IsGraphNodeHovered ( this , & SGraphNodeKnot : : IsKnotHovered ) ;
bHoveredCommentVisibility = false ;
GetOrAddSlot ( ENodeZone : : TopCenter )
. SlotOffset ( TAttribute < FVector2D > ( this , & SGraphNodeKnot : : GetCommentOffset ) )
. SlotSize ( TAttribute < FVector2D > ( CommentBubble . Get ( ) , & SCommentBubble : : GetSize ) )
. AllowScaling ( TAttribute < bool > ( CommentBubble . Get ( ) , & SCommentBubble : : IsScalingAllowed ) )
. VAlign ( VAlign_Top )
2014-11-05 13:10:16 -05:00
[
CommentBubble . ToSharedRef ( )
] ;
2014-05-15 17:34:14 -04:00
CreatePinWidgets ( ) ;
}
const FSlateBrush * SGraphNodeKnot : : GetShadowBrush ( bool bSelected ) const
{
return bSelected ? FEditorStyle : : GetBrush ( TEXT ( " Graph.Node.ShadowSelected " ) ) : FEditorStyle : : GetNoBrush ( ) ;
}
TSharedPtr < SGraphPin > SGraphNodeKnot : : CreatePinWidget ( UEdGraphPin * Pin ) const
{
return SNew ( SGraphPinKnot , Pin ) ;
}
void SGraphNodeKnot : : AddPin ( const TSharedRef < SGraphPin > & PinToAdd )
{
PinToAdd - > SetOwner ( SharedThis ( this ) ) ;
const UEdGraphPin * PinObj = PinToAdd - > GetPinObj ( ) ;
if ( PinToAdd - > GetDirection ( ) = = EEdGraphPinDirection : : EGPD_Input )
{
LeftNodeBox - > AddSlot ( )
. AutoHeight ( )
. HAlign ( HAlign_Left )
. VAlign ( VAlign_Center )
// .Padding(10, 4)
[
PinToAdd
] ;
InputPins . Add ( PinToAdd ) ;
}
else
{
RightNodeBox - > AddSlot ( )
. AutoHeight ( )
. HAlign ( HAlign_Right )
. VAlign ( VAlign_Center )
// .Padding(10, 4)
[
PinToAdd
] ;
OutputPins . Add ( PinToAdd ) ;
}
}
2014-11-05 13:10:16 -05:00
FVector2D SGraphNodeKnot : : GetCommentOffset ( ) const
{
2015-04-28 04:58:34 -04:00
const bool bBubbleVisible = GraphNode - > bCommentBubbleVisible | | bHoveredCommentVisibility ;
const float ZoomAmount = GraphNode - > bCommentBubblePinned & & OwnerGraphPanelPtr . IsValid ( ) ? OwnerGraphPanelPtr . Pin ( ) - > GetZoomAmount ( ) : 1.f ;
const float NodeWidthOffset = bBubbleVisible ? SKnotNodeDefinitions : : KnotCenterBubbleAdjust * ZoomAmount :
SKnotNodeDefinitions : : KnotCenterButtonAdjust * ZoomAmount ;
return FVector2D ( NodeWidthOffset - CommentBubble - > GetArrowCenterOffset ( ) , - CommentBubble - > GetDesiredSize ( ) . Y ) ;
2014-11-05 13:10:16 -05:00
}
void SGraphNodeKnot : : OnMouseEnter ( const FGeometry & MyGeometry , const FPointerEvent & MouseEvent )
{
2015-04-28 04:58:34 -04:00
SGraphNode : : OnMouseEnter ( MyGeometry , MouseEvent ) ;
if ( ! GraphNode - > bCommentBubbleVisible & & ! GraphNode - > NodeComment . IsEmpty ( ) )
2014-11-05 13:10:16 -05:00
{
2015-04-28 04:58:34 -04:00
bHoveredCommentVisibility = true ;
// Create the bubble widget while hovered
CommentBubble - > OnCommentBubbleToggle ( ECheckBoxState : : Checked ) ;
2014-11-05 13:10:16 -05:00
}
}
2015-04-28 04:58:34 -04:00
void SGraphNodeKnot : : OnMouseLeave ( const FPointerEvent & MouseEvent )
2014-11-05 13:10:16 -05:00
{
2015-04-28 04:58:34 -04:00
SGraphNode : : OnMouseLeave ( MouseEvent ) ;
if ( bHoveredCommentVisibility )
2014-11-05 13:10:16 -05:00
{
2015-04-28 04:58:34 -04:00
bHoveredCommentVisibility = false ;
// Destroy the comment is visibility hasn't changed
CommentBubble - > OnCommentBubbleToggle ( ECheckBoxState : : Unchecked ) ;
2014-11-05 13:10:16 -05:00
}
}
2015-03-17 08:44:54 -04:00
2015-04-28 04:58:34 -04:00
bool SGraphNodeKnot : : IsKnotHovered ( ) const
2015-03-17 08:44:54 -04:00
{
2015-04-28 04:58:34 -04:00
const bool bIsVisible = bHoveredCommentVisibility | | GraphNode - > bCommentBubbleVisible ;
return bIsVisible ? false : IsHovered ( ) ;
}
ECheckBoxState SGraphNodeKnot : : GetBubbleCheckState ( ) const
{
const bool bIsChecked = GraphNode - > bCommentBubbleVisible & & ! bHoveredCommentVisibility ;
return bIsChecked ? ECheckBoxState : : Checked : ECheckBoxState : : Unchecked ;
}