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

1741 lines
50 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SGraphNode.h"
#include "BlueprintEditorSettings.h"
#include "BoneDragDropOp.h"
#include "Containers/EnumAsByte.h"
#include "Delegates/Delegate.h"
#include "DragAndDrop/ActorDragDropGraphEdOp.h"
#include "DragAndDrop/ActorDragDropOp.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Text/TextLayout.h"
#include "GameFramework/Actor.h"
#include "GenericPlatform/GenericApplication.h"
#include "GenericPlatform/ICursor.h"
#include "GraphEditorDragDropAction.h"
#include "GraphEditorSettings.h"
#include "HAL/PlatformCrt.h"
#include "IDocumentation.h"
#include "Input/DragAndDrop.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_Literal.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Layout/ArrangedChildren.h"
#include "Layout/ArrangedWidget.h"
#include "Layout/Children.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "Logging/TokenizedMessage.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Guid.h"
#include "Misc/Optional.h"
#include "NodeFactory.h"
#include "SCommentBubble.h"
#include "SGraphPanel.h"
#include "SGraphPin.h"
#include "SLevelOfDetailBranchNode.h"
#include "SlateOptMacros.h"
#include "SlotBase.h"
#include "Templates/Casts.h"
#include "Templates/TypeHash.h"
#include "Textures/SlateIcon.h"
#include "TutorialMetaData.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 "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Notifications/SErrorText.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SToolTip.h"
#include "Widgets/SWidget.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Text/STextBlock.h"
class UBlueprint;
struct FSlateBrush;
/////////////////////////////////////////////////////
// SNodeTitle
void SNodeTitle::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
GraphNode = InNode;
ExtraLineStyle = InArgs._ExtraLineStyle;
StyleSet = InArgs._StyleSet;
CachedSize = FVector2f::ZeroVector;
// If the user set the text, use it, otherwise use the node title by default
if(InArgs._Text.IsSet())
{
TitleText = InArgs._Text;
}
else
{
TitleText = TAttribute<FText>(this, &SNodeTitle::GetNodeTitle);
}
NodeTitleCache.SetCachedText(TitleText.Get(), GraphNode.Get());
RebuildWidget();
}
void SNodeTitle::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
CachedSize = AllottedGeometry.GetLocalSize();
// Checks to see if the cached string is valid, and if not, updates it.
if (NodeTitleCache.IsOutOfDate(GraphNode.Get()))
{
NodeTitleCache.SetCachedText(TitleText.Get(), GraphNode.Get());
RebuildWidget();
}
}
FText SNodeTitle::GetNodeTitle() const
{
if (GetDefault<UBlueprintEditorSettings>()->bBlueprintNodeUniqueNames && GraphNode.IsValid())
{
return FText::FromName(GraphNode->GetFName());
}
else
{
return GraphNode.IsValid()
? GraphNode->GetNodeTitle(ENodeTitleType::FullTitle)
: NSLOCTEXT("GraphEditor", "NullNode", "Null Node");
}
}
FText SNodeTitle::GetHeadTitle() const
{
return (GraphNode.IsValid() && GraphNode->GetCanRenameNode()) ? GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle) : CachedHeadTitle;
}
UE::Slate::FDeprecateVector2DResult SNodeTitle::GetTitleSize() const
{
return CachedSize;
}
void SNodeTitle::MarkDirty()
{
NodeTitleCache.MarkDirty();
}
void SNodeTitle::RebuildWidget()
{
// Create the box to contain the lines
TSharedPtr<SVerticalBox> VerticalBox;
this->ChildSlot
[
SAssignNew(VerticalBox, SVerticalBox)
];
// Break the title into lines
TArray<FString> Lines;
const FString CachedTitleString = NodeTitleCache.GetCachedText().ToString().Replace(TEXT("\r"), TEXT(""));
CachedTitleString.ParseIntoArray(Lines, TEXT("\n"), false);
if (Lines.Num())
{
CachedHeadTitle = FText::FromString(Lines[0]);
}
// Pad the height of multi-line node titles to be a multiple of the graph snap grid taller than
// single-line nodes, so the pins will still line up if you place the node N cell snaps above
if (Lines.Num() > 1)
{
// Note: This code a little fragile, and will need to be updated if the font or padding of titles
// changes in the future, but the failure mode is just a slight misalignment.
const int32 EstimatedExtraHeight = (Lines.Num() - 1) * 14;
const int32 SnapSize = (int32)SNodePanel::GetSnapGridSize();
const int32 PadSize = SnapSize - (EstimatedExtraHeight % SnapSize);
if (PadSize < SnapSize)
{
VerticalBox->AddSlot()
[
SNew(SSpacer)
.Size(FVector2D(1.0f, PadSize))
];
}
}
// Make a separate widget for each line, using a less obvious style for subsequent lines
for (int32 Index = 1; Index < Lines.Num(); ++Index)
{
VerticalBox->AddSlot()
.AutoHeight()
[
SNew(STextBlock)
.TextStyle( StyleSet, ExtraLineStyle )
.Text(FText::FromString(Lines[Index]))
];
}
}
/////////////////////////////////////////////////////
// SGraphNode
// Check whether drag and drop functionality is permitted on the given node
bool SGraphNode::CanAllowInteractionUsingDragDropOp( const UEdGraphNode* GraphNodePtr, const TSharedPtr<FActorDragDropOp>& DragDropOp )
{
bool bReturn = false;
//Allow interaction only if this node is a literal type object.
//Only change actor reference if a single actor reference is dragged from the outliner.
if( GraphNodePtr->IsA( UK2Node_Literal::StaticClass() ) && DragDropOp->Actors.Num() == 1 )
{
bReturn = true;
}
return bReturn;
}
void SGraphNode::SetIsEditable(TAttribute<bool> InIsEditable)
{
IsEditable = InIsEditable;
}
bool SGraphNode::IsNodeEditable() const
{
bool bIsEditable = OwnerGraphPanelPtr.IsValid() ? OwnerGraphPanelPtr.Pin()->IsGraphEditable() : true;
return IsEditable.Get() && bIsEditable;
}
/** Set event when node is double clicked */
void SGraphNode::SetDoubleClickEvent(FSingleNodeEvent InDoubleClickEvent)
{
OnDoubleClick = InDoubleClickEvent;
}
void SGraphNode::SetVerifyTextCommitEvent(FOnNodeVerifyTextCommit InOnVerifyTextCommit)
{
OnVerifyTextCommit = InOnVerifyTextCommit;
}
void SGraphNode::SetTextCommittedEvent(FOnNodeTextCommitted InOnTextCommitted)
{
OnTextCommitted = InOnTextCommitted;
}
void SGraphNode::OnCommentTextCommitted(const FText& NewComment, ETextCommit::Type CommitInfo)
{
GetNodeObj()->OnUpdateCommentText(NewComment.ToString());
}
void SGraphNode::OnCommentBubbleToggled(bool bInCommentBubbleVisible)
{
GetNodeObj()->OnCommentBubbleToggled(bInCommentBubbleVisible);
}
void SGraphNode::SetDisallowedPinConnectionEvent(SGraphEditor::FOnDisallowedPinConnection InOnDisallowedPinConnection)
{
OnDisallowedPinConnection = InOnDisallowedPinConnection;
}
void SGraphNode::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return;
}
// Is someone dragging a connection?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
// Inform the Drag and Drop operation that we are hovering over this pin.
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
DragConnectionOp->SetHoveredNode( SharedThis(this) );
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
if( GraphNode->IsA( UK2Node_Literal::StaticClass() ) )
{
//Show tool tip only if a single actor is dragged
if( DragConnectionOp->Actors.Num() == 1 )
{
UK2Node_Literal* LiteralNode = CastChecked< UK2Node_Literal > ( GraphNode );
//Check whether this node is already referencing the same actor dragged from outliner
if( LiteralNode->GetObjectRef() != DragConnectionOp->Actors[0].Get() )
{
DragConnectionOp->SetToolTip(FActorDragDropGraphEdOp::ToolTip_Compatible);
}
}
else
{
//For more that one actor dragged on to a literal node, show tooltip as incompatible
DragConnectionOp->SetToolTip(FActorDragDropGraphEdOp::ToolTip_MultipleSelection_Incompatible);
}
}
else
{
DragConnectionOp->SetToolTip( (DragConnectionOp->Actors.Num() == 1) ? FActorDragDropGraphEdOp::ToolTip_Incompatible : FActorDragDropGraphEdOp::ToolTip_MultipleSelection_Incompatible);
}
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
}
void SGraphNode::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return;
}
// Is someone dragging a connection?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
// Inform the Drag and Drop operation that we are not hovering any pins
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
DragConnectionOp->SetHoveredNode( TSharedPtr<SGraphNode>(NULL) );
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
//Default tool tip
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
DragConnectionOp->ResetToDefaultToolTip();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> AssetOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
AssetOp->ResetToDefaultToolTip();
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
}
FReply SGraphNode::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
TSharedPtr<FAssetDragDropOp> AssetOp = DragDropEvent.GetOperationAs<FAssetDragDropOp>();
if (AssetOp.IsValid())
{
if(GraphNode != NULL && GraphNode->GetSchema() != NULL)
{
bool bOkIcon = false;
FString TooltipText;
if (AssetOp->HasAssets())
{
GraphNode->GetSchema()->GetAssetsNodeHoverMessage(AssetOp->GetAssets(), GraphNode, TooltipText, bOkIcon);
}
bool bReadOnly = OwnerGraphPanelPtr.IsValid() ? !OwnerGraphPanelPtr.Pin()->IsGraphEditable() : false;
bOkIcon = bReadOnly ? false : bOkIcon;
const FSlateBrush* TooltipIcon = bOkIcon ? FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")) : FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));;
AssetOp->SetToolTip(FText::FromString(TooltipText), TooltipIcon);
}
return FReply::Handled();
}
return FReply::Unhandled();
}
/** Given a coordinate in SGraphPanel space (i.e. panel widget space), return the same coordinate in graph space while taking zoom and panning into account */
UE::Slate::FDeprecateVector2DResult SGraphNode::NodeCoordToGraphCoord( const UE::Slate::FDeprecateVector2DParameter& NodeSpaceCoordinate ) const
{
TSharedPtr<SGraphPanel> OwnerCanvas = OwnerGraphPanelPtr.Pin();
if (OwnerCanvas.IsValid())
{
//@TODO: NodeSpaceCoordinate != PanelCoordinate
FVector2f PanelSpaceCoordinate = NodeSpaceCoordinate;
return OwnerCanvas->PanelCoordToGraphCoord( PanelSpaceCoordinate );
}
else
{
return FVector2f::ZeroVector;
}
}
FReply SGraphNode::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
bool bReadOnly = OwnerGraphPanelPtr.IsValid() ? !OwnerGraphPanelPtr.Pin()->IsGraphEditable() : false;
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid() || bReadOnly)
{
return FReply::Unhandled();
}
// Is someone dropping a connection onto this node?
if (Operation->IsOfType<FGraphEditorDragDropAction>())
{
TSharedPtr<FGraphEditorDragDropAction> DragConnectionOp = StaticCastSharedPtr<FGraphEditorDragDropAction>(Operation);
const FVector2f NodeAddPosition = NodeCoordToGraphCoord( MyGeometry.AbsoluteToLocal( DragDropEvent.GetScreenSpacePosition() ) );
FReply Result = DragConnectionOp->DroppedOnNode(DragDropEvent.GetScreenSpacePosition(), NodeAddPosition);
if (Result.IsEventHandled() && (GraphNode != NULL))
{
GraphNode->GetGraph()->NotifyGraphChanged();
}
return Result;
}
else if (Operation->IsOfType<FActorDragDropGraphEdOp>())
{
TSharedPtr<FActorDragDropGraphEdOp> DragConnectionOp = StaticCastSharedPtr<FActorDragDropGraphEdOp>(Operation);
if( CanAllowInteractionUsingDragDropOp( GraphNode, DragConnectionOp ) )
{
UK2Node_Literal* LiteralNode = CastChecked< UK2Node_Literal > ( GraphNode );
//Check whether this node is already referencing the same actor
if( LiteralNode->GetObjectRef() != DragConnectionOp->Actors[0].Get() )
{
//Replace literal node's object reference
LiteralNode->SetObjectRef( DragConnectionOp->Actors[ 0 ].Get() );
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(CastChecked<UEdGraph>(GraphNode->GetOuter()));
if (Blueprint != nullptr)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
UEdGraphNode* Node = GetNodeObj();
if(Node != NULL && Node->GetSchema() != NULL)
{
TSharedPtr<FAssetDragDropOp> AssetOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
if (AssetOp->HasAssets())
{
Node->GetSchema()->DroppedAssetsOnNode(AssetOp->GetAssets(), DragDropEvent.GetScreenSpacePosition(), Node);
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FBoneDragDropOp>())
{
//@TODO: A2REMOVAL: No support for A3 nodes handling this drag-drop op yet!
}
return FReply::Unhandled();
}
/**
* The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
*
* @return Whether the event was handled along with possible requests for the system to take action.
*/
FReply SGraphNode::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
return FReply::Unhandled();
}
// The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled.
FReply SGraphNode::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
return FReply::Unhandled();
}
// Called when a mouse button is double clicked. Override this in derived classes
FReply SGraphNode::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
{
if(InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
OnDoubleClick.ExecuteIfBound(GraphNode);
return FReply::Handled();
}
return FReply::Unhandled();
}
TSharedPtr<IToolTip> SGraphNode::GetToolTip()
{
TSharedPtr<IToolTip> CurrentTooltip = SWidget::GetToolTip();
if (!CurrentTooltip.IsValid())
{
TSharedPtr<SToolTip> ComplexTooltip = GetComplexTooltip();
if (ComplexTooltip.IsValid())
{
SetToolTip(ComplexTooltip);
bProvidedComplexTooltip = true;
}
}
return SWidget::GetToolTip();
}
void SGraphNode::OnToolTipClosing()
{
if (bProvidedComplexTooltip)
{
SetToolTip(nullptr);
bProvidedComplexTooltip = false;
}
}
void SGraphNode::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
CachedUnscaledPosition = AllottedGeometry.AbsolutePosition/AllottedGeometry.Scale;
SNodePanel::SNode::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
const bool bNeedToUpdateCommentBubble = GetNodeObj()->ShouldMakeCommentBubbleVisible();
if (IsHovered() || bNeedToUpdateCommentBubble)
{
if (FNodeSlot* CommentSlot = GetSlot(ENodeZone::TopCenter))
{
TSharedPtr<SCommentBubble> CommentBubble = StaticCastSharedRef<SCommentBubble>(CommentSlot->GetWidget());
if (CommentBubble.IsValid())
{
if (bNeedToUpdateCommentBubble)
{
CommentBubble->SetCommentBubbleVisibility(true);
GetNodeObj()->SetMakeCommentBubbleVisible(false);
}
else
{
CommentBubble->TickVisibility(InCurrentTime, InDeltaTime);
}
}
}
}
}
bool SGraphNode::IsSelectedExclusively() const
{
TSharedPtr<SGraphPanel> OwnerPanel = OwnerGraphPanelPtr.Pin();
if (!OwnerPanel.IsValid())
{
return false;
}
if (!OwnerPanel->HasKeyboardFocus() || OwnerPanel->SelectionManager.GetSelectedNodes().Num() > 1)
{
return false;
}
return OwnerPanel->SelectionManager.IsNodeSelected(GraphNode);
}
/** @param OwnerPanel The GraphPanel that this node belongs to */
void SGraphNode::SetOwner( const TSharedRef<SGraphPanel>& OwnerPanel )
{
check( !OwnerGraphPanelPtr.IsValid() );
SetParentPanel(OwnerPanel);
OwnerGraphPanelPtr = OwnerPanel;
GraphNode->DEPRECATED_NodeWidget = SharedThis(this);
/*Once we have an owner, and if hide Unused pins is enabled, we need to remake our pins to drop the hidden ones*/
if(OwnerGraphPanelPtr.Pin()->GetPinVisibility() != SGraphEditor::Pin_Show
&& LeftNodeBox.IsValid()
&& RightNodeBox.IsValid())
{
if (this->LeftNodeBox.IsValid())
{
this->LeftNodeBox->ClearChildren();
}
if (this->RightNodeBox.IsValid())
{
this->RightNodeBox->ClearChildren();
}
CreatePinWidgets();
}
if (TitleLODBranchNode.IsValid())
{
TitleLODBranchNode->RefreshLODSlotContent();
}
for (TSharedRef<SGraphPin> Pin : InputPins)
{
Pin->RefreshLOD();
}
for (TSharedRef<SGraphPin> Pin : OutputPins)
{
Pin->RefreshLOD();
}
}
/** @param NewPosition The Node should be relocated to this position in the graph panel */
void SGraphNode::MoveTo( const FVector2f& NewPosition, FNodeSet& NodeFilter, bool bMarkDirty )
{
if ( !NodeFilter.Find( SharedThis( this )))
{
if (GraphNode && !RequiresSecondPassLayout())
{
NodeFilter.Add( SharedThis( this ) );
GraphNode->Modify(bMarkDirty);
GraphNode->SetPosition(NewPosition);
}
}
}
/** @return the Node's position within the graph */
FVector2f SGraphNode::GetPosition2f() const
{
return GraphNode->GetPosition();
}
FString SGraphNode::GetEditableNodeTitle() const
{
if (GraphNode != nullptr)
{
// Trying to catch a non-reproducible crash in this function
check(GraphNode->IsValidLowLevel());
return GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle).ToString();
}
return NSLOCTEXT("GraphEditor", "NullNode", "Null Node").ToString();
}
FText SGraphNode::GetEditableNodeTitleAsText() const
{
FString NewString = GetEditableNodeTitle();
return FText::FromString(NewString);
}
FString SGraphNode::GetNodeComment() const
{
return GetNodeObj()->NodeComment;
}
UObject* SGraphNode::GetObjectBeingDisplayed() const
{
return GetNodeObj();
}
FSlateColor SGraphNode::GetNodeTitleColor() const
{
FLinearColor ReturnTitleColor = GraphNode->IsDeprecated() ? FLinearColor::Red : GetNodeObj()->GetNodeTitleColor();
if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated())
{
ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f);
}
else
{
ReturnTitleColor.A = FadeCurve.GetLerp();
}
return ReturnTitleColor;
}
FSlateColor SGraphNode::GetNodeBodyColor() const
{
FLinearColor ReturnBodyColor = GraphNode->GetNodeBodyTintColor();
if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated())
{
ReturnBodyColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.5f);
}
return ReturnBodyColor;
}
const FSlateBrush * SGraphNode::GetNodeBodyBrush() const
{
return FAppStyle::GetBrush("Graph.Node.Body");
}
FSlateColor SGraphNode::GetNodeTitleIconColor() const
{
FLinearColor ReturnIconColor = IconColor;
if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated())
{
ReturnIconColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f);
}
return ReturnIconColor;
}
FLinearColor SGraphNode::GetNodeTitleTextColor() const
{
return GetNodeObj()->GetNodeTitleTextColor();
}
FSlateColor SGraphNode::GetNodeCommentColor() const
{
return GetNodeObj()->GetNodeCommentColor();
}
/** @return the tooltip to display when over the node */
FText SGraphNode::GetNodeTooltip() const
{
if (GraphNode != NULL)
{
// If any of our child pins have an interactive tooltip, we shouldn't have a tooltip
for (UEdGraphPin* Pin : GraphNode->GetAllPins())
{
TSharedPtr<SGraphPin> PinWidget = FindWidgetForPin(Pin);
if (PinWidget.IsValid() && PinWidget->HasInteractiveTooltip())
{
return FText::GetEmpty();
}
}
// Display the native title of the node when alt is held
if(FSlateApplication::Get().GetModifierKeys().IsAltDown())
{
return FText::FromString(GraphNode->GetNodeTitle(ENodeTitleType::ListView).BuildSourceString());
}
FText TooltipText = GraphNode->GetTooltipText();
if (UEdGraph* Graph = GraphNode->GetGraph())
{
// If the node resides in an intermediate graph, show the UObject name for debug purposes
if (Graph->HasAnyFlags(RF_Transient))
{
FFormatNamedArguments Args;
Args.Add(TEXT("NodeName"), FText::FromString(GraphNode->GetName()));
Args.Add(TEXT("TooltipText"), TooltipText);
TooltipText = FText::Format(NSLOCTEXT("GraphEditor", "GraphNodeTooltip", "{NodeName}\n\n{TooltipText}"), Args);
}
}
if (TooltipText.IsEmpty())
{
TooltipText = GraphNode->GetNodeTitle(ENodeTitleType::FullTitle);
}
return TooltipText;
}
else
{
return NSLOCTEXT("GraphEditor", "InvalidGraphNode", "<Invalid graph node>");
}
}
/** @return the node being observed by this widget*/
UEdGraphNode* SGraphNode::GetNodeObj() const
{
return GraphNode;
}
TSharedRef<SGraphNode> SGraphNode::GetNodeUnderMouse(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return StaticCastSharedRef<SGraphNode>(AsShared());
}
TSharedPtr<SGraphPanel> SGraphNode::GetOwnerPanel() const
{
return OwnerGraphPanelPtr.Pin();
}
void SGraphNode::UpdateErrorInfo()
{
//Check for node errors/warnings
if (GraphNode->bHasCompilerMessage)
{
if (GraphNode->ErrorType <= EMessageSeverity::Error)
{
ErrorMsg = FString( TEXT("ERROR!") );
ErrorColor = FAppStyle::GetColor("ErrorReporting.BackgroundColor");
}
else if (GraphNode->ErrorType <= EMessageSeverity::Warning)
{
ErrorMsg = FString( TEXT("WARNING!") );
ErrorColor = FAppStyle::GetColor("ErrorReporting.WarningBackgroundColor");
}
else
{
ErrorMsg = FString( TEXT("NOTE") );
ErrorColor = FAppStyle::GetColor("InfoReporting.BackgroundColor");
}
}
else if (!GraphNode->NodeUpgradeMessage.IsEmpty())
{
ErrorMsg = FString(TEXT("UPGRADE NOTE"));
ErrorColor = FAppStyle::GetColor("InfoReporting.BackgroundColor");
}
else
{
ErrorColor = FLinearColor(0,0,0);
ErrorMsg.Empty();
}
VisualWarningMsg = FString(TEXT("WARNING!"));
VisualWarningColor = FAppStyle::GetColor("ErrorReporting.WarningBackgroundColor");
}
void SGraphNode::SetupErrorReporting()
{
UpdateErrorInfo();
if( !ErrorReporting.IsValid() )
{
TSharedPtr<SErrorText> ErrorTextWidget;
// generate widget
SAssignNew(ErrorTextWidget, SErrorText)
.BackgroundColor( this, &SGraphNode::GetErrorColor )
.ToolTipText( this, &SGraphNode::GetErrorMsgToolTip );
ErrorReporting = ErrorTextWidget;
}
ErrorReporting->SetError(ErrorMsg);
if (!VisualWarningReporting.IsValid())
{
TSharedPtr<SErrorText> ErrorTextWidget;
// generate widget
SAssignNew(ErrorTextWidget, SErrorText)
.Visibility(this, &SGraphNode::VisualWarningVisibility)
.BackgroundColor(this, &SGraphNode::GetVisualWarningColor)
.ToolTipText(this, &SGraphNode::GetVisualWarningMsgToolTip);
VisualWarningReporting = ErrorTextWidget;
}
VisualWarningReporting->SetError(VisualWarningMsg);
}
TSharedRef<SWidget> SGraphNode::CreateTitleWidget(TSharedPtr<SNodeTitle> NodeTitle)
{
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style(FAppStyle::Get(), "Graph.Node.NodeTitleInlineEditableText")
.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
.OnVerifyTextChanged(this, &SGraphNode::OnVerifyNameTextChanged)
.OnTextCommitted(this, &SGraphNode::OnNameTextCommited)
.IsReadOnly(this, &SGraphNode::IsNameReadOnly)
.IsSelected(this, &SGraphNode::IsSelectedExclusively)
.OverflowPolicy(GetNameOverflowPolicy());
InlineEditableText->SetColorAndOpacity(TAttribute<FLinearColor>::Create(TAttribute<FLinearColor>::FGetter::CreateSP(this, &SGraphNode::GetNodeTitleTextColor)));
return InlineEditableText.ToSharedRef();
}
TSharedRef<SWidget> SGraphNode::CreateTitleRightWidget()
{
return SNullWidget::NullWidget;
}
/**
* Update this GraphNode to match the data that it is observing
*/
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SGraphNode::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();
//
// ______________________
// | TITLE AREA |
// +-------+------+-------+
// | (>) L | | R (>) |
// | (>) E | | I (>) |
// | (>) F | | G (>) |
// | (>) T | | H (>) |
// | | | T (>) |
// |_______|______|_______|
//
TSharedPtr<SVerticalBox> MainVerticalBox;
SetupErrorReporting();
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
// Get node icon
IconColor = FLinearColor::White;
const FSlateBrush* IconBrush = nullptr;
if (GraphNode != NULL && GraphNode->ShowPaletteIconOnNode())
{
IconBrush = GraphNode->GetIconAndTint(IconColor).GetOptionalIcon();
}
TSharedRef<SOverlay> DefaultTitleAreaWidget =
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SImage)
.Image( FAppStyle::GetBrush("Graph.Node.TitleGloss") )
.ColorAndOpacity( this, &SGraphNode::GetNodeTitleIconColor )
]
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
[
SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("Graph.Node.ColorSpill") )
.Padding(TitleBorderMargin)
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleColor )
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.Padding(FMargin(0.f, 0.f, 4.f, 0.f))
.AutoWidth()
[
SNew(SImage)
.Image(IconBrush)
.ColorAndOpacity(this, &SGraphNode::GetNodeTitleIconColor)
]
+ SHorizontalBox::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
CreateTitleWidget(NodeTitle)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(0, 0, 5, 0)
.AutoWidth()
[
CreateTitleRightWidget()
]
]
+SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBorder)
.Visibility(EVisibility::HitTestInvisible)
.BorderImage( FAppStyle::GetBrush( "Graph.Node.TitleHighlight" ) )
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleIconColor )
[
SNew(SSpacer)
.Size(FVector2D(20,20))
]
];
SetDefaultTitleAreaWidget(DefaultTitleAreaWidget);
SAssignNew(TitleLODBranchNode, SLevelOfDetailBranchNode)
.UseLowDetailSlot(this, &SGraphNode::UseLowDetailNodeTitles)
.LowDetail()
[
SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("Graph.Node.ColorSpill") )
.Padding( FMargin(75.0f, 22.0f) ) // Saving enough space for a 'typical' title so the transition isn't quite so abrupt
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleColor )
]
.HighDetail()
[
DefaultTitleAreaWidget
];
if (!SWidget::GetToolTip().IsValid())
{
TSharedRef<SToolTip> DefaultToolTip = IDocumentation::Get()->CreateToolTip( TAttribute< FText >( this, &SGraphNode::GetNodeTooltip ), NULL, GraphNode->GetDocumentationLink(), GraphNode->GetDocumentationExcerptName() );
SetToolTip(DefaultToolTip);
}
// Setup a meta tag for this node
FGraphNodeMetaData TagMeta(TEXT("Graphnode"));
PopulateMetaTag(&TagMeta);
TSharedPtr<SVerticalBox> InnerVerticalBox;
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
InnerVerticalBox = SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(Settings->GetNonPinNodeBodyPadding())
[
TitleLODBranchNode.ToSharedRef()
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
CreateNodeContentArea()
];
TSharedPtr<SWidget> EnabledStateWidget = GetEnabledStateWidget();
if (EnabledStateWidget.IsValid())
{
InnerVerticalBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(FMargin(2, 0))
[
EnabledStateWidget.ToSharedRef()
];
}
InnerVerticalBox->AddSlot()
.AutoHeight()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
ErrorReporting->AsWidget()
];
InnerVerticalBox->AddSlot()
.AutoHeight()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
VisualWarningReporting->AsWidget()
];
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew(MainVerticalBox, SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SOverlay)
.AddMetaData<FGraphNodeMetaData>(TagMeta)
+SOverlay::Slot()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
SNew(SImage)
.Image(GetNodeBodyBrush())
.ColorAndOpacity(this, &SGraphNode::GetNodeBodyColor)
]
+SOverlay::Slot()
[
InnerVerticalBox.ToSharedRef()
]
]
];
bool SupportsBubble = true;
if (GraphNode != nullptr)
{
SupportsBubble = GraphNode->SupportsCommentBubble();
}
if (SupportsBubble)
{
// Create comment bubble
TSharedPtr<SCommentBubble> CommentBubble;
const FSlateColor CommentColor = GetDefault<UGraphEditorSettings>()->DefaultCommentNodeTitleColor;
SAssignNew(CommentBubble, SCommentBubble)
.GraphNode(GraphNode)
.Text(this, &SGraphNode::GetNodeComment)
.OnTextCommitted(this, &SGraphNode::OnCommentTextCommitted)
.OnToggled(this, &SGraphNode::OnCommentBubbleToggled)
.ColorAndOpacity(CommentColor)
.AllowPinning(true)
.EnableTitleBarBubble(true)
.EnableBubbleCtrls(true)
.GraphLOD(this, &SGraphNode::GetCurrentLOD)
.IsGraphNodeHovered(this, &SGraphNode::IsHovered);
GetOrAddSlot(ENodeZone::TopCenter)
.SlotOffset2f(TAttribute<FVector2f>(CommentBubble.Get(), &SCommentBubble::GetOffset2f))
.SlotSize2f(TAttribute<FVector2f>(CommentBubble.Get(), &SCommentBubble::GetSize2f))
.AllowScaling(TAttribute<bool>(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed))
.VAlign(VAlign_Top)
[
CommentBubble.ToSharedRef()
];
}
CreateBelowWidgetControls(MainVerticalBox);
CreatePinWidgets();
CreateInputSideAddButton(LeftNodeBox);
CreateOutputSideAddButton(RightNodeBox);
CreateBelowPinControls(InnerVerticalBox);
CreateAdvancedViewArrow(InnerVerticalBox);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedPtr<SWidget> SGraphNode::GetEnabledStateWidget()
{
if ((GraphNode->GetDesiredEnabledState() != ENodeEnabledState::Enabled) && !GraphNode->IsAutomaticallyPlacedGhostNode())
{
const bool bDevelopmentOnly = GraphNode->GetDesiredEnabledState() == ENodeEnabledState::DevelopmentOnly;
const FText StatusMessage = bDevelopmentOnly ? NSLOCTEXT("SGraphNode", "DevelopmentOnly", "Development Only") : NSLOCTEXT("SGraphNode", "DisabledNode", "Disabled");
const FText StatusMessageTooltip = bDevelopmentOnly ?
NSLOCTEXT("SGraphNode", "DevelopmentOnlyTooltip", "This node will only be executed in the editor and in Development builds in a packaged game (it will be treated as disabled in Shipping or Test builds cooked from a commandlet)") :
NSLOCTEXT("SGraphNode", "DisabledNodeTooltip", "This node is currently disabled and will not be executed");
return SNew(SBorder)
.BorderImage(FAppStyle::GetBrush(bDevelopmentOnly ? "Graph.Node.DevelopmentBanner" : "Graph.Node.DisabledBanner"))
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(STextBlock)
.Text(StatusMessage)
.ToolTipText(StatusMessageTooltip)
.Justification(ETextJustify::Center)
.ColorAndOpacity(FLinearColor::White)
.ShadowOffset(FVector2D::UnitVector)
.Visibility(EVisibility::Visible)
];
}
return TSharedPtr<SWidget>();
}
TSharedRef<SWidget> SGraphNode::CreateNodeContentArea()
{
// NODE CONTENT AREA
return SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding( FMargin(0,3) )
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.FillWidth(1.0f)
[
// LEFT
SAssignNew(LeftNodeBox, SVerticalBox)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
[
// RIGHT
SAssignNew(RightNodeBox, SVerticalBox)
]
];
}
/** Returns visibility of AdvancedViewButton */
EVisibility SGraphNode::AdvancedViewArrowVisibility() const
{
const bool bShowAdvancedViewArrow = GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay);
return bShowAdvancedViewArrow ? EVisibility::Visible : EVisibility::Collapsed;
}
void SGraphNode::OnAdvancedViewChanged( const ECheckBoxState NewCheckedState )
{
if(GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay))
{
const bool bAdvancedPinsHidden = (NewCheckedState != ECheckBoxState::Checked);
GraphNode->AdvancedPinDisplay = bAdvancedPinsHidden ? ENodeAdvancedPins::Hidden : ENodeAdvancedPins::Shown;
}
}
ECheckBoxState SGraphNode::IsAdvancedViewChecked() const
{
const bool bAdvancedPinsHidden = GraphNode && (ENodeAdvancedPins::Hidden == GraphNode->AdvancedPinDisplay);
return bAdvancedPinsHidden ? ECheckBoxState::Unchecked : ECheckBoxState::Checked;
}
const FSlateBrush* SGraphNode::GetAdvancedViewArrow() const
{
const bool bAdvancedPinsHidden = GraphNode && (ENodeAdvancedPins::Hidden == GraphNode->AdvancedPinDisplay);
return FAppStyle::GetBrush(bAdvancedPinsHidden ? TEXT("Icons.ChevronDown") : TEXT("Icons.ChevronUp"));
}
/** Create widget to show/hide advanced pins */
void SGraphNode::CreateAdvancedViewArrow(TSharedPtr<SVerticalBox> MainBox)
{
const bool bHidePins = OwnerGraphPanelPtr.IsValid() && (OwnerGraphPanelPtr.Pin()->GetPinVisibility() != SGraphEditor::Pin_Show);
const bool bAnyAdvancedPin = GraphNode && (ENodeAdvancedPins::NoPins != GraphNode->AdvancedPinDisplay);
if(!bHidePins && GraphNode && MainBox.IsValid())
{
MainBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(3, 0, 3, 3)
[
SNew(SCheckBox)
.Visibility(this, &SGraphNode::AdvancedViewArrowVisibility)
.OnCheckStateChanged( this, &SGraphNode::OnAdvancedViewChanged )
.IsChecked( this, &SGraphNode::IsAdvancedViewChecked )
.Cursor(EMouseCursor::Default)
.Style(FAppStyle::Get(), "Graph.Node.AdvancedView")
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SImage)
. Image(this, &SGraphNode::GetAdvancedViewArrow)
]
]
];
}
}
bool SGraphNode::ShouldPinBeHidden(const UEdGraphPin* InPin) const
{
bool bHideNoConnectionPins = false;
bool bHideNoConnectionNoDefaultPins = false;
// Not allowed to hide exec pins
const bool bCanHidePin = (InPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec);
if (OwnerGraphPanelPtr.IsValid() && bCanHidePin)
{
bHideNoConnectionPins = OwnerGraphPanelPtr.Pin()->GetPinVisibility() == SGraphEditor::Pin_HideNoConnection;
bHideNoConnectionNoDefaultPins = OwnerGraphPanelPtr.Pin()->GetPinVisibility() == SGraphEditor::Pin_HideNoConnectionNoDefault;
}
const bool bIsOutputPin = InPin->Direction == EGPD_Output;
const bool bPinHasDefaultValue = !InPin->DefaultValue.IsEmpty() || (InPin->DefaultObject != NULL);
const bool bIsSelfTarget = (InPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) && (InPin->PinName == UEdGraphSchema_K2::PN_Self);
const bool bPinHasValidDefault = !bIsOutputPin && (bPinHasDefaultValue || bIsSelfTarget);
const bool bPinHasConections = InPin->LinkedTo.Num() > 0;
const bool bPinDesiresToBeHidden = InPin->bHidden || (bHideNoConnectionPins && !bPinHasConections) || (bHideNoConnectionNoDefaultPins && !bPinHasConections && !bPinHasValidDefault);
// No matter how strong the desire, a pin with connections can never be hidden!
const bool bShowPin = !bPinDesiresToBeHidden || bPinHasConections;
return bShowPin;
}
void SGraphNode::CreateStandardPinWidget(UEdGraphPin* CurPin)
{
const bool bShowPin = ShouldPinBeHidden(CurPin);
if (bShowPin)
{
TSharedPtr<SGraphPin> NewPin = CreatePinWidget(CurPin);
check(NewPin.IsValid());
this->AddPin(NewPin.ToSharedRef());
}
}
void SGraphNode::CreatePinWidgets()
{
// Create Pin widgets for each of the pins.
for (int32 PinIndex = 0; PinIndex < GraphNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* CurPin = GraphNode->Pins[PinIndex];
if ( !ensureMsgf(CurPin->GetOuter() == GraphNode
, TEXT("Graph node ('%s' - %s) has an invalid %s pin: '%s'; (with a bad %s outer: '%s'); skiping creation of a widget for this pin.")
, *GraphNode->GetNodeTitle(ENodeTitleType::ListView).ToString()
, *GraphNode->GetPathName()
, (CurPin->Direction == EEdGraphPinDirection::EGPD_Input) ? TEXT("input") : TEXT("output")
, CurPin->PinFriendlyName.IsEmpty() ? *CurPin->PinName.ToString() : *CurPin->PinFriendlyName.ToString()
, CurPin->GetOuter() ? *CurPin->GetOuter()->GetClass()->GetName() : TEXT("UNKNOWN")
, CurPin->GetOuter() ? *CurPin->GetOuter()->GetPathName() : TEXT("NULL")) )
{
continue;
}
CreateStandardPinWidget(CurPin);
}
}
TSharedPtr<SGraphPin> SGraphNode::CreatePinWidget(UEdGraphPin* Pin) const
{
return FNodeFactory::CreatePinWidget(Pin);
}
void SGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = (PinObj != nullptr) && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility( TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced) );
}
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(Settings->GetInputPinPadding())
[
PinToAdd
];
InputPins.Add(PinToAdd);
}
else // Direction == EEdGraphPinDirection::EGPD_Output
{
RightNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(Settings->GetOutputPinPadding())
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
/**
* Get all the pins found on this node.
*
* @param AllPins The set of pins found on this node.
*/
void SGraphNode::GetPins( TSet< TSharedRef<SWidget> >& AllPins ) const
{
for( int32 PinIndex=0; PinIndex < this->InputPins.Num(); ++PinIndex )
{
AllPins.Add(InputPins[PinIndex]);
}
for( int32 PinIndex=0; PinIndex < this->OutputPins.Num(); ++PinIndex )
{
AllPins.Add(OutputPins[PinIndex]);
}
}
void SGraphNode::GetPins( TArray< TSharedRef<SWidget> >& AllPins ) const
{
for( int32 PinIndex=0; PinIndex < this->InputPins.Num(); ++PinIndex )
{
AllPins.Add(InputPins[PinIndex]);
}
for( int32 PinIndex=0; PinIndex < this->OutputPins.Num(); ++PinIndex )
{
AllPins.Add(OutputPins[PinIndex]);
}
}
TSharedPtr<SGraphPin> SGraphNode::GetHoveredPin( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) const
{
// We just need to find the one WidgetToFind among our descendants.
TSet< TSharedRef<SWidget> > MyPins;
{
GetPins( MyPins );
}
TMap<TSharedRef<SWidget>, FArrangedWidget> Result;
FindChildGeometries(MyGeometry, MyPins, Result);
if ( Result.Num() > 0 )
{
FArrangedChildren ArrangedPins(EVisibility::Visible);
Result.GenerateValueArray( ArrangedPins.GetInternalArray() );
int32 HoveredPinIndex = SWidget::FindChildUnderMouse( ArrangedPins, MouseEvent );
if ( HoveredPinIndex != INDEX_NONE )
{
return StaticCastSharedRef<SGraphPin>(ArrangedPins[HoveredPinIndex].Widget);
}
}
return TSharedPtr<SGraphPin>();
}
TSharedPtr<SGraphPin> SGraphNode::FindWidgetForPin( UEdGraphPin* ThePin ) const
{
// Search input or output pins?
const TArray< TSharedRef<SGraphPin> > &PinsToSearch = (ThePin->Direction == EGPD_Input) ? InputPins : OutputPins;
// Actually search for the widget
for( int32 PinIndex=0; PinIndex < PinsToSearch.Num(); ++PinIndex )
{
if ( PinsToSearch[PinIndex]->GetPinObj() == ThePin )
{
return PinsToSearch[PinIndex];
}
}
return TSharedPtr<SGraphPin>(NULL);
}
void SGraphNode::PlaySpawnEffect()
{
SpawnAnim.Play( this->AsShared() );
}
UE::Slate::FDeprecateVector2DResult SGraphNode::GetContentScale() const
{
const float CurZoomValue = ZoomCurve.GetLerp();
return FVector2f( CurZoomValue, CurZoomValue );
}
FLinearColor SGraphNode::GetColorAndOpacity() const
{
return FLinearColor(1,1,1,FadeCurve.GetLerp());
}
FLinearColor SGraphNode::GetPinLabelColorAndOpacity() const
{
return FLinearColor(0,0,0,FadeCurve.GetLerp());
}
SGraphNode::SGraphNode()
: IsEditable(true)
, bProvidedComplexTooltip(false)
, bRenameIsPending( false )
, ErrorColor( FLinearColor::White )
, CachedUnscaledPosition( FVector2f::ZeroVector )
, Settings( GetDefault<UGraphEditorSettings>() )
{
// Set up animation
{
ZoomCurve = SpawnAnim.AddCurve(0, 0.1f);
FadeCurve = SpawnAnim.AddCurve(0.15f, 0.15f);
SpawnAnim.JumpToEnd();
}
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup, UEdGraphNode* PreviousNode, UEdGraphNode* NextNode, float HeightAboveWire) const
{
if ((PreviousNode != NULL) && (NextNode != NULL))
{
TSet<UEdGraphNode*> PrevNodes;
PrevNodes.Add(PreviousNode);
TSet<UEdGraphNode*> NextNodes;
NextNodes.Add(NextNode);
PositionThisNodeBetweenOtherNodes(NodeToWidgetLookup, PrevNodes, NextNodes, HeightAboveWire);
}
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const TMap< UObject*, TSharedRef<SNode> >& NodeToWidgetLookup, TSet<UEdGraphNode*>& PreviousNodes, TSet<UEdGraphNode*>& NextNodes, float HeightAboveWire) const
{
// Find the previous position centroid
FVector2f PrevPos(0.0f, 0.0f);
for (auto NodeIt = PreviousNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* PreviousNode = *NodeIt;
const FVector2f CornerPos = PreviousNode->GetPosition();
PrevPos += CornerPos + NodeToWidgetLookup.FindChecked(PreviousNode)->GetDesiredSize() * 0.5f;
}
// Find the next position centroid
FVector2f NextPos(0.0f, 0.0f);
for (auto NodeIt = NextNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* NextNode = *NodeIt;
const FVector2f CornerPos = NextNode->GetPosition();
NextPos += CornerPos + NodeToWidgetLookup.FindChecked(NextNode)->GetDesiredSize() * 0.5f;
}
PositionThisNodeBetweenOtherNodes(PrevPos, NextPos, HeightAboveWire);
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const FVector2f& PrevPos, const FVector2f& NextPos, float HeightAboveWire) const
{
const FVector2f DesiredNodeSize = GetDesiredSize();
FVector2f DeltaPos(NextPos - PrevPos);
if (DeltaPos.IsNearlyZero())
{
DeltaPos = FVector2f(10.0f, 0.0f);
}
const FVector2f Normal = FVector2f(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
const FVector2f SlidingCapsuleBias = FVector2f::ZeroVector;//(0.5f * FMath::Sin(Normal.X * (float)HALF_PI) * DesiredNodeSize.X, 0.0f);
const FVector2f NewCenter = PrevPos + (0.5f * DeltaPos) + (HeightAboveWire * Normal) + SlidingCapsuleBias;
// Now we need to adjust the new center by the node size and zoom factor
const FVector2f NewCorner = NewCenter - (0.5f * DesiredNodeSize);
GraphNode->SetPosition(NewCorner);
}
FText SGraphNode::GetErrorMsgToolTip() const
{
FText Result = FText::GetEmpty();
if (GraphNode != nullptr)
{
Result = FText::FromString(GraphNode->ErrorMsg);
// Append the node's upgrade message, if any.
if (!GraphNode->NodeUpgradeMessage.IsEmpty())
{
if (Result.IsEmpty())
{
Result = GraphNode->NodeUpgradeMessage;
}
else
{
Result = FText::Format(FText::FromString(TEXT("{0}\n\n{1}")), Result, GraphNode->NodeUpgradeMessage);
}
}
}
return Result;
}
EVisibility SGraphNode::VisualWarningVisibility() const
{
const bool bShowVisualWarning = GraphNode && GraphNode->ShowVisualWarning();
return bShowVisualWarning ? EVisibility::Visible : EVisibility::Collapsed;
}
/* Helper function to set the error color for the node */
FSlateColor SGraphNode::GetVisualWarningColor() const
{
return VisualWarningColor;
}
FText SGraphNode::GetVisualWarningMsgToolTip() const
{
FText Result = FText::GetEmpty();
if (GraphNode != nullptr)
{
Result = GraphNode->GetVisualWarningTooltipText();
}
return Result;
}
bool SGraphNode::IsNameReadOnly() const
{
return (!GraphNode->GetCanRenameNode() || !IsNodeEditable());
}
bool SGraphNode::OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage)
{
bool bValid(true);
if ((GetEditableNodeTitle() != InText.ToString()) && OnVerifyTextCommit.IsBound())
{
bValid = OnVerifyTextCommit.Execute(InText, GraphNode, OutErrorMessage);
}
if( OutErrorMessage.IsEmpty() )
{
OutErrorMessage = FText::FromString(TEXT("Error"));
}
//UpdateErrorInfo();
//ErrorReporting->SetError(ErrorMsg);
return bValid;
}
void SGraphNode::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo)
{
OnTextCommitted.ExecuteIfBound(InText, CommitInfo, GraphNode);
UpdateErrorInfo();
if (ErrorReporting.IsValid())
{
ErrorReporting->SetError(ErrorMsg);
}
}
void SGraphNode::RequestRename()
{
if ((GraphNode != nullptr) && GraphNode->GetCanRenameNode())
{
bRenameIsPending = true;
}
}
void SGraphNode::ApplyRename()
{
if (bRenameIsPending)
{
bRenameIsPending = false;
InlineEditableText->EnterEditingMode();
}
}
FSlateRect SGraphNode::GetTitleRect() const
{
const FVector2f NodePosition = GetPosition2f();
const FVector2f NodeSize = GraphNode ? InlineEditableText->GetDesiredSize() : GetDesiredSize();
return FSlateRect( NodePosition.X, NodePosition.Y + NodeSize.Y, NodePosition.X + NodeSize.X, NodePosition.Y );
}
void SGraphNode::NotifyDisallowedPinConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
OnDisallowedPinConnection.ExecuteIfBound(PinA, PinB);
}
bool SGraphNode::UseLowDetailNodeTitles() const
{
if (InlineEditableText.IsValid())
{
if (const SGraphPanel* MyOwnerPanel = GetOwnerPanel().Get())
{
return (MyOwnerPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowestDetail) && !InlineEditableText->IsInEditMode();
}
}
return false;
}
TSharedRef<SWidget> SGraphNode::AddPinButtonContent(FText PinText, FText PinTooltipText, bool bRightSide, FString DocumentationExcerpt, TSharedPtr<SToolTip> CustomTooltip)
{
TSharedPtr<SWidget> ButtonContent;
if(bRightSide)
{
SAssignNew(ButtonContent, SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(PinText)
.ColorAndOpacity(FLinearColor::White)
]
+SHorizontalBox::Slot()
.AutoWidth()
. VAlign(VAlign_Center)
. Padding( 7,0,0,0 )
[
SNew(SImage)
.Image(FAppStyle::GetBrush(TEXT("Icons.PlusCircle")))
];
}
else
{
SAssignNew(ButtonContent, SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
. VAlign(VAlign_Center)
. Padding( 0,0,7,0 )
[
SNew(SImage)
.Image(FAppStyle::GetBrush(TEXT("Icons.PlusCircle")))
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(PinText)
.ColorAndOpacity(FLinearColor::White)
];
}
TSharedPtr<SToolTip> Tooltip;
if (CustomTooltip.IsValid())
{
Tooltip = CustomTooltip;
}
else if (!DocumentationExcerpt.IsEmpty())
{
Tooltip = IDocumentation::Get()->CreateToolTip( PinTooltipText, NULL, GraphNode->GetDocumentationLink(), DocumentationExcerpt );
}
TSharedRef<SButton> AddPinButton = SNew(SButton)
.ContentPadding(0.0f)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.OnClicked( this, &SGraphNode::OnAddPin )
.IsEnabled( this, &SGraphNode::IsNodeEditable )
.ToolTipText(PinTooltipText)
.ToolTip(Tooltip)
.Visibility(this, &SGraphNode::IsAddPinButtonVisible)
[
ButtonContent.ToSharedRef()
];
AddPinButton->SetCursor( EMouseCursor::Hand );
return AddPinButton;
}
EVisibility SGraphNode::IsAddPinButtonVisible() const
{
bool bIsHidden = false;
auto OwnerGraphPanel = OwnerGraphPanelPtr.Pin();
if(OwnerGraphPanel.IsValid())
{
bIsHidden |= (SGraphEditor::EPinVisibility::Pin_Show != OwnerGraphPanel->GetPinVisibility());
bIsHidden |= (OwnerGraphPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowDetail);
}
return bIsHidden ? EVisibility::Collapsed : EVisibility::Visible;
}
void SGraphNode::PopulateMetaTag(FGraphNodeMetaData* TagMeta) const
{
if (GraphNode && TagMeta)
{
// We want the name of the blueprint/world as our name - we can find the node from the GUID
UObject* OutermostObject = GraphNode->GetOutermostObject();
check(OutermostObject);
TagMeta->Tag = FName(*FString::Printf(TEXT("GraphNode_%s_%s"), *OutermostObject->GetFullName(), *GraphNode->NodeGuid.ToString()));
TagMeta->OuterName = OutermostObject->GetFullName();
TagMeta->GUID = GraphNode->NodeGuid;
TagMeta->FriendlyName = FString::Printf(TEXT("%s in %s"), *GraphNode->GetNodeTitle(ENodeTitleType::ListView).ToString(), *TagMeta->OuterName);
}
}
EGraphRenderingLOD::Type SGraphNode::GetCurrentLOD() const
{
return OwnerGraphPanelPtr.IsValid() ? OwnerGraphPanelPtr.Pin()->GetCurrentLOD() : EGraphRenderingLOD::DefaultDetail;
}
void SGraphNode::RefreshErrorInfo()
{
SetupErrorReporting();
}
void SGraphNode::InvalidateGraphData()
{
TSharedPtr<SGraphPanel> OwnerGraphPanel = OwnerGraphPanelPtr.Pin();
auto InvalidateGraphPinDataLambda = [&OwnerGraphPanel](TSharedRef<SGraphPin>& Pin)
{
Pin->InvalidateGraphData();
// Ensure that the graph panel's current marked pin reference is cleared if it equates to one of the pins being invalidated.
if (OwnerGraphPanel.IsValid() && OwnerGraphPanel->MarkedPin.IsValid() && OwnerGraphPanel->MarkedPin.HasSameObject(&Pin.Get()))
{
OwnerGraphPanel->MarkedPin.Reset();
}
};
for (TSharedRef<SGraphPin>& Pin : InputPins)
{
InvalidateGraphPinDataLambda(Pin);
}
for (TSharedRef<SGraphPin>& Pin : OutputPins)
{
InvalidateGraphPinDataLambda(Pin);
}
}