Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphNodeKnot.cpp
Ben Marsh 149375b14b Update copyright notices to 2015.
[CL 2379638 by Ben Marsh in Main branch]
2014-12-07 19:09:38 -05:00

408 lines
12 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "GraphEditorCommon.h"
#include "SGraphNodeKnot.h"
#include "ScopedTransaction.h"
#include "BlueprintEditorUtils.h"
#include "GenericCommands.h"
#include "SInlineEditableTextBlock.h"
class FAmbivalentDirectionDragConnection : public FDragConnection
{
public:
// FDragDropOperation interface
virtual void OnDragged(const class FDragDropEvent& DragDropEvent) override;
// End of FDragDropOperation interface
UEdGraphPin* GetBestPin() const;
// FDragConnection interface
virtual void ValidateGraphPinList(TArray<UEdGraphPin*>& OutValidPins) override;
// End of FDragConnection interface
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)
{
}
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
virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
// End of SWidget interface
protected:
// Begin SGraphPin interface
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;
// 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)
{
TSharedRef<FAmbivalentDirectionDragConnection> Operation = FAmbivalentDirectionDragConnection::New(CastChecked<UK2Node_Knot>(GetPinObj()->GetOwningNode()), InGraphPanel, InStartingPins, bInShiftOperation);
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()
{
const FVector2D NodeSpacerSize(42.0f, 24.0f);
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 );
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SOverlay)
+SOverlay::Slot()
[
// Grab handle to be able to move the node
SNew(SSpacer)
.Size(NodeSpacerSize)
.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)
]
]
]
]
];
// Create comment bubble
SAssignNew( CommentBubble, SCommentBubble )
.GraphNode( GraphNode )
.Text( this, &SGraphNode::GetNodeComment )
.ColorAndOpacity( FLinearColor::White )
.GraphLOD( this, &SGraphNode::GetCurrentLOD )
.IsGraphNodeHovered( this, &SGraphNode::IsHovered );
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 )
[
CommentBubble.ToSharedRef()
];
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);
}
}
FVector2D SGraphNodeKnot::GetCommentOffset() const
{
static const FVector2D CenterAdjust( 8.f, 0.f );
return CommentBubble->GetOffset() + CenterAdjust;
}
void SGraphNodeKnot::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if( !GraphNode->NodeComment.IsEmpty() )
{
GraphNode->bCommentBubbleVisible = true;
CommentBubble->UpdateBubble();
}
}
void SGraphNodeKnot::OnMouseLeave( const FPointerEvent& MouseEvent )
{
if( !GraphNode->NodeComment.IsEmpty() )
{
GraphNode->bCommentBubbleVisible = false;
CommentBubble->UpdateBubble();
}
}