Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphEditorImpl.cpp
robert manuszewski b7568cc694 Fix for UE-90683: You can no longer delete conflicting variables
Refactored FindField into FindUField and FindFProperty to avoid confusion caused by the fact that FindField<UField> will no longer return FProperties.

#jira UE-90683
#rb Steve.Robb
#tests Basic editor functionality test, cooked and ran PC client and server, bot soak tests for two hours

#ROBOMERGE-OWNER: robert.manuszewski
#ROBOMERGE-AUTHOR: robert.manuszewski
#ROBOMERGE-SOURCE: CL 12190998 in //UE4/Release-4.25/... via CL 12190999
#ROBOMERGE-BOT: RELEASE (Release-4.25Plus -> Main) (v661-12148976)

[CL 12191300 by robert manuszewski in Main branch]
2020-03-15 10:33:45 -04:00

1289 lines
37 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SGraphEditorImpl.h"
#include "GraphEditAction.h"
#include "EdGraph/EdGraph.h"
#include "Modules/ModuleManager.h"
#include "Widgets/SBoxPanel.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ToolMenus.h"
#include "EditorStyleSet.h"
#include "Editor.h"
#include "GraphEditorModule.h"
#include "SGraphPanel.h"
#include "GraphEditorActions.h"
#include "ScopedTransaction.h"
#include "SGraphEditorActionMenu.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/Input/SMultiLineEditableTextBox.h"
#include "EdGraphSchema_K2.h"
#define LOCTEXT_NAMESPACE "GraphEditorModule"
FVector2D GetNodeSize(const SGraphEditor& GraphEditor, const UEdGraphNode* Node)
{
FSlateRect Rect;
if (GraphEditor.GetBoundsForNode(Node, Rect, 0.f))
{
return FVector2D(Rect.Right - Rect.Left, Rect.Bottom - Rect.Top);
}
return FVector2D(Node->NodeWidth, Node->NodeHeight);
}
/////////////////////////////////////////////////////
// FAlignmentHelper
FAlignmentData FAlignmentHelper::GetAlignmentDataForNode(UEdGraphNode* Node)
{
float PropertyOffset = 0.f;
const float NodeSize = Orientation == Orient_Horizontal ? GetNodeSize(*GraphEditor, Node).X : GetNodeSize(*GraphEditor, Node).Y;
switch (AlignType)
{
case EAlignType::Minimum: PropertyOffset = 0.f; break;
case EAlignType::Middle: PropertyOffset = NodeSize * .5f; break;
case EAlignType::Maximum: PropertyOffset = NodeSize; break;
}
int32* Property = Orientation == Orient_Horizontal ? &Node->NodePosX : &Node->NodePosY;
return FAlignmentData(Node, *Property, PropertyOffset);
}
/////////////////////////////////////////////////////
// SGraphEditorImpl
FVector2D SGraphEditorImpl::GetPasteLocation() const
{
return GraphPanel->GetPastePosition();
}
bool SGraphEditorImpl::IsNodeTitleVisible( const UEdGraphNode* Node, bool bEnsureVisible )
{
return GraphPanel->IsNodeTitleVisible(Node, bEnsureVisible);
}
void SGraphEditorImpl::JumpToNode( const UEdGraphNode* JumpToMe, bool bRequestRename, bool bSelectNode )
{
GraphPanel->JumpToNode(JumpToMe, bRequestRename, bSelectNode);
FocusLockedEditorHere();
}
void SGraphEditorImpl::JumpToPin( const UEdGraphPin* JumpToMe )
{
GraphPanel->JumpToPin(JumpToMe);
FocusLockedEditorHere();
}
bool SGraphEditorImpl::SupportsKeyboardFocus() const
{
return true;
}
FReply SGraphEditorImpl::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent )
{
OnFocused.ExecuteIfBound(SharedThis(this));
return FReply::Handled();
}
FReply SGraphEditorImpl::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if(MouseEvent.IsMouseButtonDown(EKeys::ThumbMouseButton))
{
OnNavigateHistoryBack.ExecuteIfBound();
}
else if(MouseEvent.IsMouseButtonDown(EKeys::ThumbMouseButton2))
{
OnNavigateHistoryForward.ExecuteIfBound();
}
return FReply::Handled().SetUserFocus(SharedThis(this), EFocusCause::Mouse);
}
FReply SGraphEditorImpl::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
int32 NumNodes = GetCurrentGraph()->Nodes.Num();
if (Commands->ProcessCommandBindings( InKeyEvent ) )
{
bool bPasteOperation = InKeyEvent.IsControlDown() && InKeyEvent.GetKey() == EKeys::V;
if( !bPasteOperation && GetCurrentGraph()->Nodes.Num() > NumNodes )
{
OnNodeSpawnedByKeymap.ExecuteIfBound();
}
return FReply::Handled();
}
else
{
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
}
void SGraphEditorImpl::NotifyGraphChanged()
{
FEdGraphEditAction DefaultAction;
OnGraphChanged(DefaultAction);
}
void SGraphEditorImpl::OnGraphChanged(const FEdGraphEditAction& InAction)
{
if ( !bIsActiveTimerRegistered )
{
const UEdGraphSchema* Schema = EdGraphObj->GetSchema();
const bool bSchemaRequiresFullRefresh = Schema->ShouldAlwaysPurgeOnModification();
const bool bWasAddAction = (InAction.Action & GRAPHACTION_AddNode) != 0;
const bool bWasSelectAction = (InAction.Action & GRAPHACTION_SelectNode) != 0;
const bool bWasRemoveAction = (InAction.Action & GRAPHACTION_RemoveNode) != 0;
// If we did a 'default action' (or some other action not handled by SGraphPanel::OnGraphChanged
// or if we're using a schema that always needs a full refresh, then purge the current nodes
// and queue an update:
if (bSchemaRequiresFullRefresh ||
(!bWasAddAction && !bWasSelectAction && !bWasRemoveAction) )
{
GraphPanel->PurgeVisualRepresentation();
// Trigger the refresh
bIsActiveTimerRegistered = true;
RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SGraphEditorImpl::TriggerRefresh));
}
}
}
//void SGraphEditorImpl::GraphEd_OnPanelUpdated()
//{
//
//}
const TSet<class UObject*>& SGraphEditorImpl::GetSelectedNodes() const
{
return GraphPanel->SelectionManager.GetSelectedNodes();
}
void SGraphEditorImpl::ClearSelectionSet()
{
GraphPanel->SelectionManager.ClearSelectionSet();
}
void SGraphEditorImpl::SetNodeSelection(UEdGraphNode* Node, bool bSelect)
{
GraphPanel->SelectionManager.SetNodeSelection(Node, bSelect);
}
void SGraphEditorImpl::SelectAllNodes()
{
FGraphPanelSelectionSet NewSet;
for (int32 NodeIndex = 0; NodeIndex < EdGraphObj->Nodes.Num(); ++NodeIndex)
{
UEdGraphNode* Node = EdGraphObj->Nodes[NodeIndex];
if (Node)
{
ensureMsgf(Node->IsValidLowLevel(), TEXT("Node is invalid"));
NewSet.Add(Node);
}
}
GraphPanel->SelectionManager.SetSelectionSet(NewSet);
}
UEdGraphPin* SGraphEditorImpl::GetGraphPinForMenu()
{
return GraphPinForMenu.Get();
}
UEdGraphNode* SGraphEditorImpl::GetGraphNodeForMenu()
{
return GraphNodeForMenu.IsValid() ? GraphNodeForMenu.Get() : nullptr;
}
void SGraphEditorImpl::ZoomToFit(bool bOnlySelection)
{
GraphPanel->ZoomToFit(bOnlySelection);
}
bool SGraphEditorImpl::GetBoundsForSelectedNodes( class FSlateRect& Rect, float Padding )
{
return GraphPanel->GetBoundsForSelectedNodes(Rect, Padding);
}
bool SGraphEditorImpl::GetBoundsForNode( const UEdGraphNode* InNode, class FSlateRect& Rect, float Padding) const
{
FVector2D TopLeft, BottomRight;
if (GraphPanel->GetBoundsForNode(InNode, TopLeft, BottomRight, Padding))
{
Rect.Left = TopLeft.X;
Rect.Top = TopLeft.Y;
Rect.Bottom = BottomRight.Y;
Rect.Right = BottomRight.X;
return true;
}
return false;
}
void SGraphEditorImpl::StraightenConnections()
{
GraphPanel->StraightenConnections();
}
void SGraphEditorImpl::StraightenConnections(UEdGraphPin* SourcePin, UEdGraphPin* PinToAlign)
{
GraphPanel->StraightenConnections(SourcePin, PinToAlign);
}
void SGraphEditorImpl::RefreshNode(UEdGraphNode& Node)
{
GraphPanel->RefreshNode(Node);
}
void SGraphEditorImpl::Construct( const FArguments& InArgs )
{
Commands = MakeShareable( new FUICommandList() );
IsEditable = InArgs._IsEditable;
DisplayAsReadOnly = InArgs._DisplayAsReadOnly;
Appearance = InArgs._Appearance;
TitleBar = InArgs._TitleBar;
bAutoExpandActionMenu = InArgs._AutoExpandActionMenu;
ShowGraphStateOverlay = InArgs._ShowGraphStateOverlay;
OnNavigateHistoryBack = InArgs._OnNavigateHistoryBack;
OnNavigateHistoryForward = InArgs._OnNavigateHistoryForward;
OnNodeSpawnedByKeymap = InArgs._GraphEvents.OnNodeSpawnedByKeymap;
bIsActiveTimerRegistered = false;
// Make sure that the editor knows about what kinds
// of commands GraphEditor can do.
FGraphEditorCommands::Register();
// Tell GraphEditor how to handle all the known commands
{
Commands->MapAction( FGraphEditorCommands::Get().ReconstructNodes,
FExecuteAction::CreateSP( this, &SGraphEditorImpl::ReconstructNodes ),
FCanExecuteAction::CreateSP( this, &SGraphEditorImpl::CanReconstructNodes )
);
Commands->MapAction( FGraphEditorCommands::Get().BreakNodeLinks,
FExecuteAction::CreateSP( this, &SGraphEditorImpl::BreakNodeLinks ),
FCanExecuteAction::CreateSP( this, &SGraphEditorImpl::CanBreakNodeLinks )
);
Commands->MapAction( FGraphEditorCommands::Get().BreakPinLinks,
FExecuteAction::CreateSP( this, &SGraphEditorImpl::BreakPinLinks, true),
FCanExecuteAction::CreateSP( this, &SGraphEditorImpl::CanBreakPinLinks )
);
// Append any additional commands that a consumer of GraphEditor wants us to be aware of.
const TSharedPtr<FUICommandList>& AdditionalCommands = InArgs._AdditionalCommands;
if ( AdditionalCommands.IsValid() )
{
Commands->Append( AdditionalCommands.ToSharedRef() );
}
}
bResetMenuContext = false;
GraphPinForMenu.SetPin(nullptr);
EdGraphObj = InArgs._GraphToEdit;
OnFocused = InArgs._GraphEvents.OnFocused;
OnCreateActionMenu = InArgs._GraphEvents.OnCreateActionMenu;
OnCreateNodeOrPinMenu = InArgs._GraphEvents.OnCreateNodeOrPinMenu;
struct Local
{
static FText GetPIENotifyText(TAttribute<FGraphAppearanceInfo> Appearance, FText DefaultText)
{
FText OverrideText = Appearance.Get().PIENotifyText;
return !OverrideText.IsEmpty() ? OverrideText : DefaultText;
}
static FText GetReadOnlyText(TAttribute<FGraphAppearanceInfo>Appearance, FText DefaultText)
{
FText OverrideText = Appearance.Get().ReadOnlyText;
return !OverrideText.IsEmpty() ? OverrideText : DefaultText;
}
};
FText DefaultPIENotify(LOCTEXT("GraphSimulatingText", "SIMULATING"));
TAttribute<FText> PIENotifyText = Appearance.IsBound() ?
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateStatic(&Local::GetPIENotifyText, Appearance, DefaultPIENotify)) :
TAttribute<FText>(DefaultPIENotify);
FText DefaultReadOnlyText(LOCTEXT("GraphReadOnlyText", "READ-ONLY"));
TAttribute<FText> ReadOnlyText = Appearance.IsBound() ?
TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateStatic(&Local::GetReadOnlyText, Appearance, DefaultReadOnlyText)) :
TAttribute<FText>(DefaultReadOnlyText);
TSharedPtr<SOverlay> OverlayWidget;
this->ChildSlot
[
SAssignNew(OverlayWidget, SOverlay)
// The graph panel
+SOverlay::Slot()
.Expose(GraphPanelSlot)
[
SAssignNew(GraphPanel, SGraphPanel)
.GraphObj( EdGraphObj )
.GraphObjToDiff( InArgs._GraphToDiff)
.OnGetContextMenuFor( this, &SGraphEditorImpl::GraphEd_OnGetContextMenuFor )
.OnSelectionChanged( InArgs._GraphEvents.OnSelectionChanged )
.OnNodeDoubleClicked( InArgs._GraphEvents.OnNodeDoubleClicked )
.IsEditable( this, &SGraphEditorImpl::IsGraphEditable )
.DisplayAsReadOnly( this, &SGraphEditorImpl::DisplayGraphAsReadOnly )
.OnDropActor( InArgs._GraphEvents.OnDropActor )
.OnDropStreamingLevel( InArgs._GraphEvents.OnDropStreamingLevel )
.OnVerifyTextCommit( InArgs._GraphEvents.OnVerifyTextCommit )
.OnTextCommitted( InArgs._GraphEvents.OnTextCommitted )
.OnSpawnNodeByShortcut( InArgs._GraphEvents.OnSpawnNodeByShortcut )
//.OnUpdateGraphPanel( this, &SGraphEditorImpl::GraphEd_OnPanelUpdated )
.OnDisallowedPinConnection( InArgs._GraphEvents.OnDisallowedPinConnection )
.ShowGraphStateOverlay(InArgs._ShowGraphStateOverlay)
]
// Indicator of current zoom level
+SOverlay::Slot()
.Padding(5)
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.TextStyle( FEditorStyle::Get(), "Graph.ZoomText" )
.Text( this, &SGraphEditorImpl::GetZoomText )
.ColorAndOpacity( this, &SGraphEditorImpl::GetZoomTextColorAndOpacity )
]
// Title bar - optional
+SOverlay::Slot()
.VAlign(VAlign_Top)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
InArgs._TitleBar.IsValid() ? InArgs._TitleBar.ToSharedRef() : (TSharedRef<SWidget>)SNullWidget::NullWidget
]
+ SVerticalBox::Slot()
.Padding(20.f, 20.f, 20.f, 0.f)
.VAlign(VAlign_Top)
.HAlign(HAlign_Center)
.AutoHeight()
[
SNew(SBorder)
.Padding(FMargin(10.f, 4.f))
.BorderImage(FEditorStyle::GetBrush(TEXT("Graph.InstructionBackground")))
.BorderBackgroundColor(this, &SGraphEditorImpl::InstructionBorderColor)
.HAlign(HAlign_Center)
.ColorAndOpacity(this, &SGraphEditorImpl::InstructionTextTint)
.Visibility(this, &SGraphEditorImpl::InstructionTextVisibility)
[
SNew(STextBlock)
.TextStyle(FEditorStyle::Get(), "Graph.InstructionText")
.Text(this, &SGraphEditorImpl::GetInstructionText)
]
]
]
// Bottom-right corner text indicating the type of tool
+SOverlay::Slot()
.Padding(10)
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Visibility( EVisibility::HitTestInvisible )
.TextStyle( FEditorStyle::Get(), "Graph.CornerText" )
.Text(Appearance.Get().CornerText)
]
// Top-right corner text indicating PIE is active
+SOverlay::Slot()
.Padding(20)
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Visibility(this, &SGraphEditorImpl::PIENotification)
.TextStyle( FEditorStyle::Get(), "Graph.SimulatingText" )
.Text( PIENotifyText )
]
// Top-right corner text indicating read only when not simulating
+ SOverlay::Slot()
.Padding(20)
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SNew(STextBlock)
.Visibility(this, &SGraphEditorImpl::ReadOnlyVisibility)
.TextStyle(FEditorStyle::Get(), "Graph.CornerText")
.Text(ReadOnlyText)
]
// + SOverlay::Slot()
// .Padding(20)
// .VAlign(VAlign_Fill)
// .HAlign(HAlign_Fill)
// [
// SNew(SVerticalBox)
// + SVerticalBox::Slot()
// .FillHeight(0.5)
// .VAlign(VAlign_Bottom)
// .HAlign(HAlign_Center)
// [
//
// ]
// + SVerticalBox::Slot()
// .FillHeight(0.5)
// ]
// Bottom-right corner text for notification list position
+SOverlay::Slot()
.Padding(15.f)
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Right)
[
SAssignNew(NotificationListPtr, SNotificationList)
.Visibility(EVisibility::HitTestInvisible)
]
];
GraphPanel->RestoreViewSettings(FVector2D::ZeroVector, -1);
NotifyGraphChanged();
}
EVisibility SGraphEditorImpl::PIENotification( ) const
{
if(ShowGraphStateOverlay.Get() && (GEditor->bIsSimulatingInEditor || GEditor->PlayWorld != NULL))
{
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
SGraphEditorImpl::~SGraphEditorImpl()
{
}
void SGraphEditorImpl::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
if (bResetMenuContext)
{
GraphPinForMenu.SetPin(nullptr);
GraphNodeForMenu.Reset();
bResetMenuContext = false;
}
// If locked to another graph editor, and our panel has moved, synchronise the locked graph editor accordingly
if ((EdGraphObj != NULL) && GraphPanel.IsValid())
{
if(GraphPanel->HasMoved() && IsLocked())
{
FocusLockedEditorHere();
}
}
}
EActiveTimerReturnType SGraphEditorImpl::TriggerRefresh( double InCurrentTime, float InDeltaTime )
{
GraphPanel->Update();
bIsActiveTimerRegistered = false;
return EActiveTimerReturnType::Stop;
}
void SGraphEditorImpl::OnClosedActionMenu()
{
GraphPanel->OnStopMakingConnection(/*bForceStop=*/ true);
}
void SGraphEditorImpl::RegisterContextMenuFor_EdGraphSchema(const FName ParentMenuName)
{
static const FName MenuName = UEdGraphSchema::GetContextMenuName(UEdGraphSchema::StaticClass());
UToolMenus* ToolMenus = UToolMenus::Get();
if (ToolMenus->IsMenuRegistered(MenuName))
{
return;
}
UToolMenu* Menu = ToolMenus->RegisterMenu(MenuName, ParentMenuName);
Menu->AddDynamicSection("GetNodeContextMenuActions", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
UGraphNodeContextMenuContext* Context = InMenu->FindContext<UGraphNodeContextMenuContext>();
if (Context && Context->Node)
{
Context->Node->GetNodeContextMenuActions(InMenu, Context);
}
}));
Menu->AddDynamicSection("EdGraphSchema", FNewToolMenuDelegate::CreateStatic(&SGraphEditorImpl::AddContextMenuCommentSection));
}
void SGraphEditorImpl::AddContextMenuCommentSection(UToolMenu* InMenu)
{
UGraphNodeContextMenuContext* Context = InMenu->FindContext<UGraphNodeContextMenuContext>();
if (!Context)
{
return;
}
const UEdGraphSchema* GraphSchema = Context->Graph->GetSchema();
if (!GraphSchema)
{
return;
}
// Helper to do the node comment editing
struct Local
{
// Called by the EditableText widget to get the current comment for the node
static FString GetNodeComment(TWeakObjectPtr<UEdGraphNode> NodeWeakPtr)
{
if (UEdGraphNode* SelectedNode = NodeWeakPtr.Get())
{
return SelectedNode->NodeComment;
}
return FString();
}
// Called by the EditableText widget when the user types a new comment for the selected node
static void OnNodeCommentTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo, TWeakObjectPtr<UEdGraphNode> NodeWeakPtr)
{
// Apply the change to the selected actor
UEdGraphNode* SelectedNode = NodeWeakPtr.Get();
FString NewString = NewText.ToString();
if (SelectedNode && !SelectedNode->NodeComment.Equals(NewString, ESearchCase::CaseSensitive))
{
// send property changed events
const FScopedTransaction Transaction(LOCTEXT("EditNodeComment", "Change Node Comment"));
SelectedNode->Modify();
FProperty* NodeCommentProperty = FindFProperty<FProperty>(SelectedNode->GetClass(), "NodeComment");
if (NodeCommentProperty != nullptr)
{
SelectedNode->PreEditChange(NodeCommentProperty);
SelectedNode->NodeComment = NewString;
SelectedNode->SetMakeCommentBubbleVisible(true);
FPropertyChangedEvent NodeCommentPropertyChangedEvent(NodeCommentProperty);
SelectedNode->PostEditChangeProperty(NodeCommentPropertyChangedEvent);
}
}
// Only dismiss all menus if the text was committed via some user action.
// ETextCommit::Default implies that focus was switched by some other means. If this is because a submenu was opened, we don't want to close all the menus as a consequence.
if (CommitInfo != ETextCommit::Default)
{
FSlateApplication::Get().DismissAllMenus();
}
}
};
if (!Context->Pin)
{
int32 SelectionCount = GraphSchema->GetNodeSelectionCount(Context->Graph);
if (SelectionCount == 1)
{
// Node comment area
TSharedRef<SHorizontalBox> NodeCommentBox = SNew(SHorizontalBox);
{
FToolMenuSection& Section = InMenu->AddSection("GraphNodeComment", LOCTEXT("NodeCommentMenuHeader", "Node Comment"));
Section.AddEntry(FToolMenuEntry::InitWidget("NodeCommentBox", NodeCommentBox, FText::GetEmpty()));
}
TWeakObjectPtr<UEdGraphNode> SelectedNodeWeakPtr = MakeWeakObjectPtr(const_cast<UEdGraphNode*>(Context->Node));
FText NodeCommentText;
if (UEdGraphNode* SelectedNode = SelectedNodeWeakPtr.Get())
{
NodeCommentText = FText::FromString(SelectedNode->NodeComment);
}
const FSlateBrush* NodeIcon = FCoreStyle::Get().GetDefaultBrush();//@TODO: FActorIconFinder::FindIconForActor(SelectedActors(0).Get());
// Comment label
NodeCommentBox->AddSlot()
.VAlign(VAlign_Center)
.FillWidth(1.0f)
[
SNew(SMultiLineEditableTextBox)
.Text(NodeCommentText)
.ToolTipText(LOCTEXT("NodeComment_ToolTip", "Comment for this node"))
.OnTextCommitted_Static(&Local::OnNodeCommentTextCommitted, SelectedNodeWeakPtr)
.SelectAllTextWhenFocused(true)
.RevertTextOnEscape(true)
.ModiferKeyForNewLine(EModifierKey::Control)
];
}
else if (SelectionCount > 1)
{
struct SCommentUtility
{
static void CreateComment(const UEdGraphSchema* Schema, UEdGraph* Graph)
{
if (Schema && Graph)
{
TSharedPtr<FEdGraphSchemaAction> Action = Schema->GetCreateCommentAction();
if (Action.IsValid())
{
Action->PerformAction(Graph, nullptr, FVector2D());
}
}
}
};
FToolMenuSection& Section = InMenu->AddSection("SchemaActionComment", LOCTEXT("MultiCommentHeader", "Comment Group"));
Section.AddMenuEntry(
"MultiCommentDesc",
LOCTEXT("MultiCommentDesc", "Create Comment from Selection"),
LOCTEXT("CommentToolTip", "Create a resizable comment box around selection."),
FSlateIcon(),
FExecuteAction::CreateStatic(SCommentUtility::CreateComment, GraphSchema, const_cast<UEdGraph*>(Context->Graph)
));
}
}
}
FName SGraphEditorImpl::GetNodeParentContextMenuName(UClass* InClass)
{
if (InClass && InClass != UEdGraphNode::StaticClass())
{
if (InClass->GetDefaultObject<UEdGraphNode>()->IncludeParentNodeContextMenu())
{
if (UClass* SuperClass = InClass->GetSuperClass())
{
return GetNodeContextMenuName(SuperClass);
}
}
}
return NAME_None;
}
FName SGraphEditorImpl::GetNodeContextMenuName(UClass* InClass)
{
return FName(*(FString(TEXT("GraphEditor.GraphNodeContextMenu.")) + InClass->GetName()));
}
void SGraphEditorImpl::RegisterContextMenu(const UEdGraphSchema* Schema, FToolMenuContext& MenuContext) const
{
UToolMenus* ToolMenus = UToolMenus::Get();
const FName SchemaMenuName = Schema->GetContextMenuName();
UGraphNodeContextMenuContext* Context = MenuContext.FindContext<UGraphNodeContextMenuContext>();
// Root menu
// "GraphEditor.GraphContextMenu.Common"
// contains: GraphSchema->GetContextMenuActions(Menu, Context)
const FName CommonRootMenuName = "GraphEditor.GraphContextMenu.Common";
if (!ToolMenus->IsMenuRegistered(CommonRootMenuName))
{
UToolMenu* CommonRootMenu = ToolMenus->RegisterMenu(CommonRootMenuName);
CommonRootMenu->AddDynamicSection("GetContextMenuActions", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
if (UGraphNodeContextMenuContext* ContextObject = InMenu->FindContext<UGraphNodeContextMenuContext>())
{
if (const UEdGraphSchema* GraphSchema = ContextObject->Graph->GetSchema())
{
GraphSchema->GetContextMenuActions(InMenu, ContextObject);
}
}
}));
}
// Menu for EdGraphSchema, used most but not all schema subclasses
// contains: Node->GetNodeContextMenuActions()
// contains: Comment for node
RegisterContextMenuFor_EdGraphSchema(CommonRootMenuName);
// Menus for subclasses of EdGraphSchema
for (UClass* CurrentClass = Schema->GetClass(); CurrentClass && CurrentClass->IsChildOf(UEdGraphSchema::StaticClass()); CurrentClass = CurrentClass->GetSuperClass())
{
// EdGraphSchema class is registered instead by RegisterContextMenuFor_EdGraphSchema()
if (CurrentClass == UEdGraphSchema::StaticClass())
{
break;
}
const UEdGraphSchema* CurrentSchema = CurrentClass->GetDefaultObject<UEdGraphSchema>();
const FName CheckMenuName = CurrentSchema->GetContextMenuName();
// Some subclasses of UEdGraphSchema chose not to include UEdGraphSchema's menu
// Note: menu "GraphEditor.GraphContextMenu.Common" calls GraphSchema->GetContextMenuActions() and adds entry for node comment
const FName CheckParentName = CurrentSchema->GetParentContextMenuName();
if (!ToolMenus->IsMenuRegistered(CheckMenuName))
{
FName ParentNameToUse = CheckParentName;
// Connect final menu in chain to the common root
if (ParentNameToUse == NAME_None)
{
ParentNameToUse = CommonRootMenuName;
}
ToolMenus->RegisterMenu(CheckMenuName, ParentNameToUse);
}
if (CheckParentName == NAME_None)
{
break;
}
}
// Node menus
if (Context->Node)
{
for (UClass* CurrentClass = Context->Node->GetClass(); CurrentClass && CurrentClass->IsChildOf(UEdGraphNode::StaticClass()); CurrentClass = CurrentClass->GetSuperClass())
{
const FName CheckMenuName = GetNodeContextMenuName(CurrentClass);
const FName CheckParentName = GetNodeParentContextMenuName(CurrentClass);
if (!ToolMenus->IsMenuRegistered(CheckMenuName))
{
FName ParentNameToUse = CheckParentName;
// Connect final menu in chain to schema's chain of menus
if (CheckParentName == NAME_None)
{
ParentNameToUse = SchemaMenuName;
}
ToolMenus->RegisterMenu(CheckMenuName, ParentNameToUse);
}
if (CheckParentName == NAME_None)
{
break;
}
}
}
}
UToolMenu* SGraphEditorImpl::GenerateContextMenu(const UEdGraphSchema* Schema, FToolMenuContext& MenuContext) const
{
// Register all the menu's needed
RegisterContextMenu(Schema, MenuContext);
UToolMenus* ToolMenus = UToolMenus::Get();
UGraphNodeContextMenuContext* Context = MenuContext.FindContext<UGraphNodeContextMenuContext>();
FName MenuName = NAME_None;
if (Context->Node)
{
MenuName = GetNodeContextMenuName(Context->Node->GetClass());
}
else
{
MenuName = Schema->GetContextMenuName();
}
return ToolMenus->GenerateMenu(MenuName, MenuContext);
}
FActionMenuContent SGraphEditorImpl::GraphEd_OnGetContextMenuFor(const FGraphContextMenuArguments& SpawnInfo)
{
FActionMenuContent Result;
if (EdGraphObj != NULL)
{
Result = FActionMenuContent( SNew(STextBlock) .Text( NSLOCTEXT("GraphEditor", "NoNodes", "No Nodes") ) );
const UEdGraphSchema* Schema = EdGraphObj->GetSchema();
check(Schema);
// Cache the pin this menu is being brought up for
GraphPinForMenu.SetPin(SpawnInfo.GraphPin);
GraphNodeForMenu = SpawnInfo.GraphNode;
if ((SpawnInfo.GraphPin != NULL) || (SpawnInfo.GraphNode != NULL))
{
// Get all menu extenders for this context menu from the graph editor module
FGraphEditorModule& GraphEditorModule = FModuleManager::GetModuleChecked<FGraphEditorModule>( TEXT("GraphEditor") );
TArray<FGraphEditorModule::FGraphEditorMenuExtender_SelectedNode> MenuExtenderDelegates = GraphEditorModule.GetAllGraphEditorContextMenuExtender();
TArray<TSharedPtr<FExtender>> Extenders;
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
{
if (MenuExtenderDelegates[i].IsBound())
{
Extenders.Add(MenuExtenderDelegates[i].Execute(this->Commands.ToSharedRef(), EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, !IsEditable.Get()));
}
}
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
if (OnCreateNodeOrPinMenu.IsBound())
{
// Show the menu for the pin or node under the cursor
const bool bShouldCloseAfterAction = true;
FMenuBuilder MenuBuilder( bShouldCloseAfterAction, this->Commands, MenuExtender );
Result = OnCreateNodeOrPinMenu.Execute(EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, &MenuBuilder, !IsEditable.Get());
}
else
{
UGraphNodeContextMenuContext* ContextObject = NewObject<UGraphNodeContextMenuContext>();
ContextObject->Init(EdGraphObj, SpawnInfo.GraphNode, SpawnInfo.GraphPin, !IsEditable.Get());
FToolMenuContext Context(this->Commands, MenuExtender, ContextObject);
UToolMenus* ToolMenus = UToolMenus::Get();
UToolMenu* GeneratedMenu = GenerateContextMenu(Schema, Context);
Result = FActionMenuContent(ToolMenus->GenerateWidget(GeneratedMenu));
}
}
else if (IsEditable.Get())
{
if (EdGraphObj->GetSchema() != NULL )
{
if(OnCreateActionMenu.IsBound())
{
Result = OnCreateActionMenu.Execute(
EdGraphObj,
SpawnInfo.NodeAddPosition,
SpawnInfo.DragFromPins,
bAutoExpandActionMenu,
SGraphEditor::FActionMenuClosed::CreateSP(this, &SGraphEditorImpl::OnClosedActionMenu)
);
}
else
{
TSharedRef<SGraphEditorActionMenu> Menu =
SNew(SGraphEditorActionMenu)
.GraphObj( EdGraphObj )
.NewNodePosition(SpawnInfo.NodeAddPosition)
.DraggedFromPins(SpawnInfo.DragFromPins)
.AutoExpandActionMenu(bAutoExpandActionMenu)
.OnClosedCallback( SGraphEditor::FActionMenuClosed::CreateSP(this, &SGraphEditorImpl::OnClosedActionMenu)
);
Result = FActionMenuContent( Menu, Menu->GetFilterTextBox() );
}
if (SpawnInfo.DragFromPins.Num() > 0)
{
GraphPanel->PreservePinPreviewUntilForced();
}
}
}
else
{
Result = FActionMenuContent( SNew(STextBlock) .Text( NSLOCTEXT("GraphEditor", "CannotCreateWhileDebugging", "Cannot create new nodes in a read only graph") ) );
}
}
else
{
Result = FActionMenuContent( SNew(STextBlock) .Text( NSLOCTEXT("GraphEditor", "GraphObjectIsNull", "Graph Object is Null") ) );
}
Result.OnMenuDismissed.AddLambda([this]()
{
bResetMenuContext = true;
});
return Result;
}
bool SGraphEditorImpl::CanReconstructNodes() const
{
return IsGraphEditable() && (GraphPanel->SelectionManager.AreAnyNodesSelected());
}
bool SGraphEditorImpl::CanBreakNodeLinks() const
{
return IsGraphEditable() && (GraphPanel->SelectionManager.AreAnyNodesSelected());
}
bool SGraphEditorImpl::CanBreakPinLinks() const
{
return IsGraphEditable() && (GraphPinForMenu.Get() != nullptr);
}
void SGraphEditorImpl::ReconstructNodes()
{
const UEdGraphSchema* Schema = this->EdGraphObj->GetSchema();
{
FScopedTransaction const Transaction(LOCTEXT("ReconstructNodeTransaction", "Refresh Node(s)"));
for (FGraphPanelSelectionSet::TConstIterator NodeIt( GraphPanel->SelectionManager.GetSelectedNodes() ); NodeIt; ++NodeIt)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
{
const bool bCurDisableOrphanSaving = Node->bDisableOrphanPinSaving;
Node->bDisableOrphanPinSaving = true;
Schema->ReconstructNode(*Node);
Node->ClearCompilerMessage();
Node->bDisableOrphanPinSaving = bCurDisableOrphanSaving;
}
}
}
NotifyGraphChanged();
}
void SGraphEditorImpl::BreakNodeLinks()
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakNodeLinks", "Break Node Links") );
for (FGraphPanelSelectionSet::TConstIterator NodeIt( GraphPanel->SelectionManager.GetSelectedNodes() ); NodeIt; ++NodeIt)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
{
const UEdGraphSchema* Schema = Node->GetSchema();
Schema->BreakNodeLinks(*Node);
}
}
}
void SGraphEditorImpl::BreakPinLinks(bool bSendNodeNotification)
{
if (UEdGraphPin* PinContext = GraphPinForMenu.Get())
{
const UEdGraphSchema* Schema = PinContext->GetSchema();
Schema->BreakPinLinks(*PinContext, bSendNodeNotification);
}
}
FText SGraphEditorImpl::GetZoomText() const
{
return GraphPanel->GetZoomText();
}
FSlateColor SGraphEditorImpl::GetZoomTextColorAndOpacity() const
{
return GraphPanel->GetZoomTextColorAndOpacity();
}
bool SGraphEditorImpl::IsGraphEditable() const
{
return (EdGraphObj != NULL) && IsEditable.Get();
}
bool SGraphEditorImpl::DisplayGraphAsReadOnly() const
{
return (EdGraphObj != NULL) && DisplayAsReadOnly.Get();
}
bool SGraphEditorImpl::IsLocked() const
{
for( auto LockedGraph : LockedGraphs )
{
if( LockedGraph.IsValid() )
{
return true;
}
}
return false;
}
TSharedPtr<SWidget> SGraphEditorImpl::GetTitleBar() const
{
return TitleBar;
}
void SGraphEditorImpl::SetViewLocation( const FVector2D& Location, float ZoomAmount, const FGuid& BookmarkId )
{
if( GraphPanel.IsValid() && EdGraphObj && (!IsLocked() || !GraphPanel->HasDeferredObjectFocus()))
{
GraphPanel->RestoreViewSettings(Location, ZoomAmount, BookmarkId);
}
}
void SGraphEditorImpl::GetViewLocation( FVector2D& Location, float& ZoomAmount )
{
if( GraphPanel.IsValid() && EdGraphObj && (!IsLocked() || !GraphPanel->HasDeferredObjectFocus()))
{
Location = GraphPanel->GetViewOffset();
ZoomAmount = GraphPanel->GetZoomAmount();
}
}
void SGraphEditorImpl::GetViewBookmark( FGuid& BookmarkId )
{
if (GraphPanel.IsValid())
{
BookmarkId = GraphPanel->GetViewBookmarkId();
}
}
void SGraphEditorImpl::LockToGraphEditor( TWeakPtr<SGraphEditor> Other )
{
if( !LockedGraphs.Contains(Other) )
{
LockedGraphs.Push(Other);
}
if (GraphPanel.IsValid())
{
FocusLockedEditorHere();
}
}
void SGraphEditorImpl::UnlockFromGraphEditor( TWeakPtr<SGraphEditor> Other )
{
check(Other.IsValid());
int idx = LockedGraphs.Find(Other);
if (idx != INDEX_NONE)
{
LockedGraphs.RemoveAtSwap(idx);
}
}
void SGraphEditorImpl::AddNotification( FNotificationInfo& Info, bool bSuccess )
{
// set up common notification properties
Info.bUseLargeFont = true;
TSharedPtr<SNotificationItem> Notification = NotificationListPtr->AddNotification(Info);
if ( Notification.IsValid() )
{
Notification->SetCompletionState( bSuccess ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail );
}
}
void SGraphEditorImpl::FocusLockedEditorHere()
{
for( int i = 0; i < LockedGraphs.Num(); ++i )
{
TSharedPtr<SGraphEditor> LockedGraph = LockedGraphs[i].Pin();
if (LockedGraph != TSharedPtr<SGraphEditor>())
{
LockedGraph->SetViewLocation(GraphPanel->GetViewOffset(), GraphPanel->GetZoomAmount());
}
else
{
LockedGraphs.RemoveAtSwap(i--);
}
}
}
void SGraphEditorImpl::SetPinVisibility( SGraphEditor::EPinVisibility InVisibility )
{
if( GraphPanel.IsValid())
{
SGraphEditor::EPinVisibility CachedVisibility = GraphPanel->GetPinVisibility();
GraphPanel->SetPinVisibility(InVisibility);
if(CachedVisibility != InVisibility)
{
NotifyGraphChanged();
}
}
}
TSharedRef<FActiveTimerHandle> SGraphEditorImpl::RegisterActiveTimer(float TickPeriod, FWidgetActiveTimerDelegate TickFunction)
{
return SWidget::RegisterActiveTimer(TickPeriod, TickFunction);
}
EVisibility SGraphEditorImpl::ReadOnlyVisibility() const
{
if(ShowGraphStateOverlay.Get() && PIENotification() == EVisibility::Hidden && !IsEditable.Get())
{
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
FText SGraphEditorImpl::GetInstructionText() const
{
if (Appearance.IsBound())
{
return Appearance.Get().InstructionText;
}
return FText::GetEmpty();
}
EVisibility SGraphEditorImpl::InstructionTextVisibility() const
{
if (!GetInstructionText().IsEmpty() && (GetInstructionTextFade() > 0.0f))
{
return EVisibility::HitTestInvisible;
}
return EVisibility::Hidden;
}
float SGraphEditorImpl::GetInstructionTextFade() const
{
float InstructionOpacity = 1.0f;
if (Appearance.IsBound())
{
InstructionOpacity = Appearance.Get().InstructionFade.Get();
}
return InstructionOpacity;
}
FLinearColor SGraphEditorImpl::InstructionTextTint() const
{
return FLinearColor(1.f, 1.f, 1.f, GetInstructionTextFade());
}
FSlateColor SGraphEditorImpl::InstructionBorderColor() const
{
FLinearColor BorderColor(0.1f, 0.1f, 0.1f, 0.7f);
BorderColor.A *= GetInstructionTextFade();
return BorderColor;
}
void SGraphEditorImpl::CaptureKeyboard()
{
FSlateApplication::Get().SetKeyboardFocus(GraphPanel);
}
void SGraphEditorImpl::SetNodeFactory(const TSharedRef<class FGraphNodeFactory>& NewNodeFactory)
{
GraphPanel->SetNodeFactory(NewNodeFactory);
}
void SGraphEditorImpl::OnAlignTop()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesTop->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Vertical, EAlignType::Minimum);
Helper.Align();
}
void SGraphEditorImpl::OnAlignMiddle()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesMiddle->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Vertical, EAlignType::Middle);
Helper.Align();
}
void SGraphEditorImpl::OnAlignBottom()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesBottom->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Vertical, EAlignType::Maximum);
Helper.Align();
}
void SGraphEditorImpl::OnAlignLeft()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesLeft->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Horizontal, EAlignType::Minimum);
Helper.Align();
}
void SGraphEditorImpl::OnAlignCenter()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesCenter->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Horizontal, EAlignType::Middle);
Helper.Align();
}
void SGraphEditorImpl::OnAlignRight()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().AlignNodesRight->GetLabel());
FAlignmentHelper Helper(SharedThis(this), Orient_Horizontal, EAlignType::Maximum);
Helper.Align();
}
void SGraphEditorImpl::OnStraightenConnections()
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().StraightenConnections->GetLabel());
if (UEdGraphPin* Pin = GetGraphPinForMenu())
{
StraightenConnections(Pin, UEdGraphSchema_K2::GetAndResetStraightenDestinationPin());
}
else
{
StraightenConnections();
}
}
/** Distribute the specified array of node data evenly */
void DistributeNodes(TArray<FAlignmentData>& InData)
{
// Sort the data
InData.Sort([](const FAlignmentData& A, const FAlignmentData& B) {
return A.TargetProperty + A.TargetOffset / 2 < B.TargetProperty + B.TargetOffset / 2;
});
// Measure the available space
float TotalWidthOfNodes = 0.f;
for (int32 Index = 1; Index < InData.Num() - 1; ++Index)
{
TotalWidthOfNodes += InData[Index].TargetOffset;
}
const float SpaceToDistributeIn = InData.Last().TargetProperty - InData[0].GetTarget();
const float PaddingAmount = ((SpaceToDistributeIn - TotalWidthOfNodes) / (InData.Num() - 1));
float TargetPosition = InData[0].GetTarget() + PaddingAmount;
// Now set all the properties on the target
for (int32 Index = 1; Index < InData.Num() - 1; ++Index)
{
FAlignmentData& Entry = InData[Index];
Entry.Node->Modify();
Entry.TargetProperty = TargetPosition;
TargetPosition = Entry.GetTarget() + PaddingAmount;
}
}
void SGraphEditorImpl::OnDistributeNodesH()
{
TArray<FAlignmentData> AlignData;
for (UObject* It : GetSelectedNodes())
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(It))
{
AlignData.Add(FAlignmentData(Node, Node->NodePosX, GetNodeSize(*this, Node).X));
}
}
if (AlignData.Num() > 2)
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().DistributeNodesHorizontally->GetLabel());
DistributeNodes(AlignData);
}
}
void SGraphEditorImpl::OnDistributeNodesV()
{
TArray<FAlignmentData> AlignData;
for (UObject* It : GetSelectedNodes())
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(It))
{
AlignData.Add(FAlignmentData(Node, Node->NodePosY, GetNodeSize(*this, Node).Y));
}
}
if (AlignData.Num() > 2)
{
const FScopedTransaction Transaction(FGraphEditorCommands::Get().DistributeNodesVertically->GetLabel());
DistributeNodes(AlignData);
}
}
int32 SGraphEditorImpl::GetNumberOfSelectedNodes() const
{
return GetSelectedNodes().Num();
}
UEdGraphNode* SGraphEditorImpl::GetSingleSelectedNode() const
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
return (SelectedNodes.Num() == 1) ? Cast<UEdGraphNode>(*SelectedNodes.CreateConstIterator()) : nullptr;
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE