Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp
Marc Audy af581ecffc Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 2972815)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2821607 on 2016/01/08 by Mieszko.Zielinski

	Added a way to limit amount of information logged by vlog by discarding logs from classes from outside of class whitelist #UE4

	This feature was followed by refactoring of functions taking FVisualLogEntry pointers to use references instead.

Change 2828384 on 2016/01/14 by Mieszko.Zielinski

	Back out of visual log refactor done as part of CL#2821607 #UE4

Change 2965743 on 2016/05/04 by Tom.Looman

	Added check to PostActorConstruction to avoid BeginPlay call on pendingkill actor. UE-27528 #rb MarcA

Change 2965744 on 2016/05/04 by Marc.Audy

	VS2015 Shadow Variable fixes

Change 2965813 on 2016/05/04 by Tom.Looman

	Moved UninitializeComponents outside (bActorInitialized) to always uninit components when actors gets destroyed early.

	UE-27529 #rb MarcA

Change 2966564 on 2016/05/04 by Marc.Audy

	VS2015 shadow variable fixes

Change 2967244 on 2016/05/05 by Jon.Nabozny

	Remove UPROPERTY from members that don't require serialization and aren't user editable.
	#JIRA UE-30155

Change 2967377 on 2016/05/05 by Lukasz.Furman

	fixed processing of AIMessages when new message appears during notify loop
	#ue4

Change 2967437 on 2016/05/05 by Marc.Audy

	Add a static One to TBigInt
	Remove numerous local statics and TEncryptionInt specific version in KeyGenerator.cpp
	Part of fixing shadow variables for VS2015

Change 2967465 on 2016/05/05 by Marc.Audy

	Fix VS2015 shadow variables fixes

Change 2967552 on 2016/05/05 by Marc.Audy

	Fix compile error in DocumentationCode

Change 2967556 on 2016/05/05 by Marc.Audy

	Enable shadow variable warnings in 2015

Change 2967836 on 2016/05/05 by Marc.Audy

	Another DocumentationCode project fix

Change 2967941 on 2016/05/05 by Marc.Audy

	Make bShowHUD not config
	Expose HUD properties to blueprints
	Cleanup stale entries in BaseGame.ini
	Deprecate unnecessary colors in AHUD in favor of using FColor statics
	#jira UE-30045

Change 2969008 on 2016/05/06 by Marc.Audy

	VS2015 Shadow Variable fixes found by CIS

Change 2969315 on 2016/05/06 by John.Abercrombie

	Duplicating CL 2969279 from //Fortnite/Main/

	Behavior tree auxilary nodes, parallel tasks, active tasks, and aborting tasks shouldn't be ticked while the behavior tree is paused

	--------
	Integrated using branch //Fortnite/Main/_to_//UE4/Dev-Framework of change#2969279 by John.Abercrombie on 2016/05/06 14:21:40.

Change 2969611 on 2016/05/06 by Marc.Audy

	Default bShowHUD to true

Change 2971041 on 2016/05/09 by Marc.Audy

	Add Get/Set Actor/Component TickInterval functions and expose to blueprints

Change 2971072 on 2016/05/09 by Marc.Audy

	Fix VS2015 shadow variables warnings

Change 2971629 on 2016/05/09 by Marc.Audy

	PR#1981 (contributed by EverNewJoy)
	CheatManager is blueprintable (though very basic exposure at this time) and can be set from PlayerController
	DebugCameraController is now visible and can be subclassed and specified via CheatManager blueprint
	#jira UE-25901

Change 2971632 on 2016/05/09 by Marc.Audy

	Missed file from CL# 2971629

[CL 2972828 by Marc Audy in Main branch]
2016-05-10 16:00:39 -04:00

1486 lines
43 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "GraphEditorCommon.h"
#include "NodeFactory.h"
#include "TokenizedMessage.h"
#include "Editor/UnrealEd/Public/DragAndDrop/ActorDragDropGraphEdOp.h"
#include "Editor/UnrealEd/Public/DragAndDrop/AssetDragDropOp.h"
#include "Editor/Persona/Public/BoneDragDropOp.h"
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h"
#include "SLevelOfDetailBranchNode.h"
#include "IDocumentation.h"
#include "TutorialMetaData.h"
#include "SGraphPanel.h"
#include "SInlineEditableTextBlock.h"
/////////////////////////////////////////////////////
// SNodeTitle
void SNodeTitle::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
GraphNode = InNode;
ExtraLineStyle = InArgs._ExtraLineStyle;
CachedSize = FVector2D::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);
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))
{
NodeTitleCache.SetCachedText(TitleText.Get(), GraphNode);
RebuildWidget();
}
}
FText SNodeTitle::GetNodeTitle() const
{
return (GraphNode != NULL)
? GraphNode->GetNodeTitle(ENodeTitleType::FullTitle)
: NSLOCTEXT("GraphEditor", "NullNode", "Null Node");
}
FText SNodeTitle::GetHeadTitle() const
{
return (GraphNode && GraphNode->bCanRenameNode) ? GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle) : CachedHeadTitle;
}
FVector2D SNodeTitle::GetTitleSize() const
{
return CachedSize;
}
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) * 13;
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( FEditorStyle::Get(), 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;
GraphNode->GetSchema()->GetAssetsNodeHoverMessage(AssetOp->AssetData, GraphNode, TooltipText, bOkIcon);
bool bReadOnly = OwnerGraphPanelPtr.IsValid() ? !OwnerGraphPanelPtr.Pin()->IsGraphEditable() : false;
bOkIcon = bReadOnly ? false : bOkIcon;
const FSlateBrush* TooltipIcon = bOkIcon ? FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")) : FEditorStyle::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 */
FVector2D SGraphNode::NodeCoordToGraphCoord( const FVector2D& NodeSpaceCoordinate ) const
{
TSharedPtr<SGraphPanel> OwnerCanvas = OwnerGraphPanelPtr.Pin();
if (OwnerCanvas.IsValid())
{
//@TODO: NodeSpaceCoordinate != PanelCoordinate
FVector2D PanelSpaceCoordinate = NodeSpaceCoordinate;
return OwnerCanvas->PanelCoordToGraphCoord( PanelSpaceCoordinate );
}
else
{
return FVector2D::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 FVector2D 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);
Node->GetSchema()->DroppedAssetsOnNode(AssetOp->AssetData, 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(NULL);
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);
}
bool SGraphNode::IsSelectedExclusively() const
{
TSharedPtr<SGraphPanel> OwnerPanel = OwnerGraphPanelPtr.Pin();
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())
{
this->LeftNodeBox->ClearChildren();
this->RightNodeBox->ClearChildren();
CreatePinWidgets();
}
}
/** @param NewPosition The Node should be relocated to this position in the graph panel */
void SGraphNode::MoveTo( const FVector2D& NewPosition, FNodeSet& NodeFilter )
{
if ( !NodeFilter.Find( SharedThis( this )))
{
if (GraphNode && !RequiresSecondPassLayout())
{
NodeFilter.Add( SharedThis( this ) );
GraphNode->Modify();
GraphNode->NodePosX = NewPosition.X;
GraphNode->NodePosY = NewPosition.Y;
}
}
}
/** @return the Node's position within the graph */
FVector2D SGraphNode::GetPosition() const
{
return FVector2D( GraphNode->NodePosX, GraphNode->NodePosY );
}
FString SGraphNode::GetEditableNodeTitle() const
{
if (GraphNode != NULL)
{
// Trying to catch a non-reproducible crash in this function
check(GraphNode->IsValidLowLevel());
}
if(GraphNode)
{
return GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle).ToString();
}
return NSLOCTEXT("GraphEditor", "NullNode", "Null Node").ToString();
// Get the portion of the node that is actually editable text (may be a subsection of the title, or something else entirely)
return (GraphNode != NULL)
? GraphNode->GetNodeTitle(ENodeTitleType::EditableTitle).ToString()
: 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())
{
ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f);
}
else
{
ReturnTitleColor.A = FadeCurve.GetLerp();
}
return ReturnTitleColor;
}
FSlateColor SGraphNode::GetNodeBodyColor() const
{
FLinearColor ReturnBodyColor = FLinearColor::White;
if(!GraphNode->IsNodeEnabled())
{
ReturnBodyColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.5f);
}
return ReturnBodyColor;
}
FSlateColor SGraphNode::GetNodeTitleIconColor() const
{
FLinearColor ReturnIconColor = IconColor;
if(!GraphNode->IsNodeEnabled())
{
ReturnIconColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f);
}
return ReturnIconColor;
}
FLinearColor SGraphNode::GetNodeTitleTextColor() const
{
FLinearColor ReturnTextColor = FLinearColor::White;
if(!GraphNode->IsNodeEnabled())
{
ReturnTextColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f);
}
return ReturnTextColor;
}
FSlateColor SGraphNode::GetNodeCommentColor() const
{
return GetNodeObj()->GetNodeCommentColor();
}
/** @return the tooltip to display when over the node */
FText SGraphNode::GetNodeTooltip() const
{
if (GraphNode != NULL)
{
// 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 = FEditorStyle::GetColor("ErrorReporting.BackgroundColor");
}
else if (GraphNode->ErrorType <= EMessageSeverity::Warning)
{
ErrorMsg = FString( TEXT("WARNING!") );
ErrorColor = FEditorStyle::GetColor("ErrorReporting.WarningBackgroundColor");
}
else
{
ErrorMsg = FString( TEXT("NOTE") );
ErrorColor = FEditorStyle::GetColor("InfoReporting.BackgroundColor");
}
}
else if (!GraphNode->NodeUpgradeMessage.IsEmpty())
{
ErrorMsg = FString(TEXT("UPGRADE NOTE"));
ErrorColor = FEditorStyle::GetColor("InfoReporting.BackgroundColor");
}
else
{
ErrorColor = FLinearColor(0,0,0);
ErrorMsg.Empty();
}
}
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);
}
TSharedRef<SWidget> SGraphNode::CreateTitleWidget(TSharedPtr<SNodeTitle> NodeTitle)
{
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style(FEditorStyle::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);
InlineEditableText->SetColorAndOpacity(TAttribute<FLinearColor>::Create(TAttribute<FLinearColor>::FGetter::CreateSP(this, &SGraphNode::GetNodeTitleTextColor)));
return InlineEditableText.ToSharedRef();
}
/**
* 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 = NULL;
if (GraphNode != NULL && GraphNode->ShowPaletteIconOnNode())
{
IconBrush = FEditorStyle::GetBrush(GraphNode->GetPaletteIcon(IconColor));
}
TSharedRef<SOverlay> DefaultTitleAreaWidget =
SNew(SOverlay)
+SOverlay::Slot()
[
SNew(SImage)
.Image( FEditorStyle::GetBrush("Graph.Node.TitleGloss") )
.ColorAndOpacity( this, &SGraphNode::GetNodeTitleIconColor )
]
+SOverlay::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("Graph.Node.ColorSpill") )
// The extra margin on the right
// is for making the color spill stretch well past the node title
.Padding( FMargin(10,5,30,3) )
.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()
]
]
]
]
+SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SBorder)
.Visibility(EVisibility::HitTestInvisible)
.BorderImage( FEditorStyle::GetBrush( "Graph.Node.TitleHighlight" ) )
.BorderBackgroundColor( this, &SGraphNode::GetNodeTitleIconColor )
[
SNew(SSpacer)
.Size(FVector2D(20,20))
]
];
SetDefaultTitleAreaWidget(DefaultTitleAreaWidget);
TSharedRef<SWidget> TitleAreaWidget =
SNew(SLevelOfDetailBranchNode)
.UseLowDetailSlot(this, &SGraphNode::UseLowDetailNodeTitles)
.LowDetail()
[
SNew(SBorder)
.BorderImage( FEditorStyle::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 );
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(FEditorStyle::GetBrush("Graph.Node.Body"))
.ColorAndOpacity(this, &SGraphNode::GetNodeBodyColor)
]
+SOverlay::Slot()
[
SAssignNew(InnerVerticalBox, SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
.Padding(Settings->GetNonPinNodeBodyPadding())
[
TitleAreaWidget
]
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Top)
[
SNew(SOverlay)
+SOverlay::Slot()
.VAlign(VAlign_Fill)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Graph.Node.IndicatorOverlay"))
.Visibility(this, &SGraphNode::GetNodeIndicatorOverlayVisibility)
.ColorAndOpacity(this, &SGraphNode::GetNodeIndicatorOverlayColor)
]
+SOverlay::Slot()
[
CreateNodeContentArea()
]
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(Settings->GetNonPinNodeBodyPadding())
[
ErrorReporting->AsWidget()
]
]
]
];
// 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 )
.SlotOffset( TAttribute<FVector2D>( CommentBubble.Get(), &SCommentBubble::GetOffset ))
.SlotSize( TAttribute<FVector2D>( CommentBubble.Get(), &SCommentBubble::GetSize ))
.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
TSharedRef<SWidget> SGraphNode::CreateNodeContentArea()
{
// NODE CONTENT AREA
return SNew(SBorder)
.BorderImage( FEditorStyle::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 FEditorStyle::GetBrush(bAdvancedPinsHidden ? TEXT("Kismet.TitleBarEditor.ArrowDown") : TEXT("Kismet.TitleBarEditor.ArrowUp"));
}
/** 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(FEditorStyle::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
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GraphNode->GetSchema());
bool bHideNoConnectionPins = false;
bool bHideNoConnectionNoDefaultPins = false;
// Not allowed to hide exec pins
const bool bCanHidePin = (K2Schema && (InPin->PinType.PinCategory != K2Schema->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 = K2Schema && (InPin->PinType.PinCategory == K2Schema->PC_Object) && (InPin->PinName == K2Schema->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 : *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() );
}
FVector2D SGraphNode::GetContentScale() const
{
const float CurZoomValue = ZoomCurve.GetLerp();
return FVector2D( 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( FVector2D::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
FVector2D PrevPos(0.0f, 0.0f);
for (auto NodeIt = PreviousNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* PreviousNode = *NodeIt;
const FVector2D CornerPos(PreviousNode->NodePosX, PreviousNode->NodePosY);
PrevPos += CornerPos + NodeToWidgetLookup.FindChecked(PreviousNode)->GetDesiredSize() * 0.5f;
}
// Find the next position centroid
FVector2D NextPos(0.0f, 0.0f);
for (auto NodeIt = NextNodes.CreateConstIterator(); NodeIt; ++NodeIt)
{
UEdGraphNode* NextNode = *NodeIt;
const FVector2D CornerPos(NextNode->NodePosX, NextNode->NodePosY);
NextPos += CornerPos + NodeToWidgetLookup.FindChecked(NextNode)->GetDesiredSize() * 0.5f;
}
PositionThisNodeBetweenOtherNodes(PrevPos, NextPos, HeightAboveWire);
}
void SGraphNode::PositionThisNodeBetweenOtherNodes(const FVector2D& PrevPos, const FVector2D& NextPos, float HeightAboveWire) const
{
const FVector2D DesiredNodeSize = GetDesiredSize();
FVector2D DeltaPos(NextPos - PrevPos);
if (DeltaPos.IsNearlyZero())
{
DeltaPos = FVector2D(10.0f, 0.0f);
}
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
const FVector2D SlidingCapsuleBias = FVector2D::ZeroVector;//(0.5f * FMath::Sin(Normal.X * (float)HALF_PI) * DesiredNodeSize.X, 0.0f);
const FVector2D NewCenter = PrevPos + (0.5f * DeltaPos) + (HeightAboveWire * Normal) + SlidingCapsuleBias;
// Now we need to adjust the new center by the node size and zoom factor
const FVector2D NewCorner = NewCenter - (0.5f * DesiredNodeSize);
GraphNode->NodePosX = NewCorner.X;
GraphNode->NodePosY = NewCorner.Y;
}
FText SGraphNode::GetErrorMsgToolTip( ) const
{
FText Result;
// 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);
}
}
else
{
Result = FText::FromString(GraphNode->ErrorMsg);
}
return Result;
}
bool SGraphNode::IsNameReadOnly() const
{
return !GraphNode->bCanRenameNode;
}
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 != NULL) && GraphNode->bCanRenameNode)
{
bRenameIsPending = true;
}
}
void SGraphNode::ApplyRename()
{
if (bRenameIsPending)
{
bRenameIsPending = false;
InlineEditableText->EnterEditingMode();
}
}
FSlateRect SGraphNode::GetTitleRect() const
{
const FVector2D NodePosition = GetPosition();
const FVector2D 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 (const SGraphPanel* MyOwnerPanel = GetOwnerPanel().Get())
{
return (MyOwnerPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowestDetail) && !InlineEditableText->IsInEditMode();
}
else
{
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(FEditorStyle::GetBrush(TEXT("PropertyWindow.Button_AddToArray")))
];
}
else
{
SAssignNew(ButtonContent, SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
. VAlign(VAlign_Center)
. Padding( 0,0,7,0 )
[
SNew(SImage)
.Image(FEditorStyle::GetBrush(TEXT("PropertyWindow.Button_AddToArray")))
]
+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( FEditorStyle::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 != nullptr)
{
// We want the name of the blueprint as our name - we can find the node from the GUID
UObject* Package = GraphNode->GetOutermost();
UObject* LastOuter = GraphNode->GetOuter();
while (LastOuter->GetOuter() != Package)
{
LastOuter = LastOuter->GetOuter();
}
TagMeta->Tag = FName(*FString::Printf(TEXT("GraphNode_%s_%s"), *LastOuter->GetFullName(), *GraphNode->NodeGuid.ToString()));
TagMeta->OuterName = LastOuter->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();
}