// Copyright Epic Games, Inc. All Rights Reserved. #include "AIGraphEditor.h" #include "Framework/Application/SlateApplication.h" #include "Editor/EditorEngine.h" #include "EngineGlobals.h" #include "AIGraph.h" #include "AIGraphTypes.h" #include "AIGraphNode.h" #include "ScopedTransaction.h" #include "EdGraphUtilities.h" #include "Framework/Commands/GenericCommands.h" #include "GraphEditorActions.h" #include "HAL/PlatformApplicationMisc.h" #include "EdGraph/EdGraphSchema.h" #define LOCTEXT_NAMESPACE "AIGraph" FAIGraphEditor::FAIGraphEditor() { UEditorEngine* Editor = (UEditorEngine*)GEngine; if (Editor != NULL) { Editor->RegisterForUndo(this); } OnClassListUpdatedDelegateHandle = FGraphNodeClassHelper::OnPackageListUpdated.AddRaw(this, &FAIGraphEditor::OnClassListUpdated); } FAIGraphEditor::~FAIGraphEditor() { UEditorEngine* Editor = (UEditorEngine*)GEngine; if (Editor) { Editor->UnregisterForUndo(this); } FGraphNodeClassHelper::OnPackageListUpdated.Remove(OnClassListUpdatedDelegateHandle); } void FAIGraphEditor::CreateCommandList() { if (GraphEditorCommands.IsValid()) { return; } GraphEditorCommands = MakeShareable(new FUICommandList); // Can't use CreateSP here because derived editor are already implementing TSharedFromThis // however it should be safe, since commands are being used only within this editor // if it ever crashes, this function will have to go away and be reimplemented in each derived class GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll, FExecuteAction::CreateRaw(this, &FAIGraphEditor::SelectAllNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanSelectAllNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Delete, FExecuteAction::CreateRaw(this, &FAIGraphEditor::DeleteSelectedNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanDeleteNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateRaw(this, &FAIGraphEditor::CopySelectedNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanCopyNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Cut, FExecuteAction::CreateRaw(this, &FAIGraphEditor::CutSelectedNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanCutNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateRaw(this, &FAIGraphEditor::PasteNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanPasteNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateRaw(this, &FAIGraphEditor::DuplicateNodes), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanDuplicateNodes) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment, FExecuteAction::CreateRaw(this, &FAIGraphEditor::OnCreateComment), FCanExecuteAction::CreateRaw(this, &FAIGraphEditor::CanCreateComment) ); } FGraphPanelSelectionSet FAIGraphEditor::GetSelectedNodes() const { FGraphPanelSelectionSet CurrentSelection; if (TSharedPtr FocusedGraphEd = UpdateGraphEdPtr.Pin()) { CurrentSelection = FocusedGraphEd->GetSelectedNodes(); } return CurrentSelection; } void FAIGraphEditor::OnSelectedNodesChanged(const TSet& NewSelection) { // empty in base class } void FAIGraphEditor::PostUndo(bool bSuccess) { if (bSuccess) { // Clear selection, to avoid holding refs to nodes that go away if (TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin()) { CurrentGraphEditor->ClearSelectionSet(); CurrentGraphEditor->NotifyGraphChanged(); } FSlateApplication::Get().DismissAllMenus(); } } void FAIGraphEditor::PostRedo(bool bSuccess) { if (bSuccess) { // Clear selection, to avoid holding refs to nodes that go away if (TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin()) { CurrentGraphEditor->ClearSelectionSet(); CurrentGraphEditor->NotifyGraphChanged(); } FSlateApplication::Get().DismissAllMenus(); } } void FAIGraphEditor::SelectAllNodes() { if (TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin()) { CurrentGraphEditor->SelectAllNodes(); } } bool FAIGraphEditor::CanSelectAllNodes() const { return true; } void FAIGraphEditor::DeleteSelectedNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } const FScopedTransaction Transaction(FGenericCommands::Get().Delete->GetDescription()); CurrentGraphEditor->GetCurrentGraph()->Modify(); const FGraphPanelSelectionSet SelectedNodes = CurrentGraphEditor->GetSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UEdGraphNode* Node = Cast(*NodeIt)) { if (Node->CanUserDeleteNode()) { Node->Modify(); Node->DestroyNode(); } } } } bool FAIGraphEditor::CanDeleteNodes() const { // If any of the nodes can be deleted then we should allow deleting const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if (Node && Node->CanUserDeleteNode()) { return true; } } return false; } void FAIGraphEditor::DeleteSelectedDuplicatableNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } const FGraphPanelSelectionSet OldSelectedNodes = CurrentGraphEditor->GetSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if (Node && Node->CanDuplicateNode()) { CurrentGraphEditor->SetNodeSelection(Node, true); } } // Delete the duplicatable nodes DeleteSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { if (UEdGraphNode* Node = Cast(*SelectedIter)) { CurrentGraphEditor->SetNodeSelection(Node, true); } } } void FAIGraphEditor::CutSelectedNodes() { CopySelectedNodes(); DeleteSelectedDuplicatableNodes(); } bool FAIGraphEditor::CanCutNodes() const { return CanCopyNodes() && CanDeleteNodes(); } void FAIGraphEditor::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); TArray SubNodes; FString ExportedText; int32 CopySubNodeIndex = 0; for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); UAIGraphNode* AINode = Cast(Node); if (Node == nullptr) { SelectedIter.RemoveCurrent(); continue; } Node->PrepareForCopying(); if (AINode) { AINode->CopySubNodeIndex = CopySubNodeIndex; // append all subnodes for selection for (int32 Idx = 0; Idx < AINode->SubNodes.Num(); Idx++) { AINode->SubNodes[Idx]->CopySubNodeIndex = CopySubNodeIndex; SubNodes.Add(AINode->SubNodes[Idx]); } CopySubNodeIndex++; } } for (int32 Idx = 0; Idx < SubNodes.Num(); Idx++) { SelectedNodes.Add(SubNodes[Idx]); SubNodes[Idx]->PrepareForCopying(); } FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText); FPlatformApplicationMisc::ClipboardCopy(*ExportedText); for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UAIGraphNode* Node = Cast(*SelectedIter); if (Node) { Node->PostCopyNode(); } } } bool FAIGraphEditor::CanCopyNodes() const { // If any of the nodes can be duplicated then we should allow copying const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if (Node && Node->CanDuplicateNode()) { return true; } } return false; } void FAIGraphEditor::PasteNodes() { if (TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin()) { PasteNodesHere(CurrentGraphEditor->GetPasteLocation2f()); } } void FAIGraphEditor::PasteNodesHere(const FVector2D& Location) { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } // Undo/Redo support const FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription()); UEdGraph* EdGraph = CurrentGraphEditor->GetCurrentGraph(); UAIGraph* AIGraph = Cast(EdGraph); EdGraph->Modify(); if (AIGraph) { AIGraph->LockUpdates(); } UAIGraphNode* SelectedParent = NULL; bool bHasMultipleNodesSelected = false; const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UAIGraphNode* Node = Cast(*SelectedIter); if (Node && Node->IsSubNode()) { Node = Node->ParentNode; } if (Node) { if (SelectedParent == nullptr) { SelectedParent = Node; } else { bHasMultipleNodesSelected = true; break; } } } // Clear the selection set (newly pasted stuff will be selected) CurrentGraphEditor->ClearSelectionSet(); // Grab the text to paste from the clipboard. FString TextToImport; FPlatformApplicationMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(EdGraph, TextToImport, /*out*/ PastedNodes); //Average position of nodes so we can move them while still maintaining relative distances to each other FVector2D AvgNodePosition(0.0f, 0.0f); // Number of nodes used to calculate AvgNodePosition int32 AvgCount = 0; for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* EdNode = *It; UAIGraphNode* AINode = Cast(EdNode); if (EdNode && (AINode == nullptr || !AINode->IsSubNode())) { AvgNodePosition.X += EdNode->NodePosX; AvgNodePosition.Y += EdNode->NodePosY; ++AvgCount; } } if (AvgCount > 0) { float InvNumNodes = 1.0f / float(AvgCount); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; } bool bPastedParentNode = false; TMap NewToOldNodeMapping; TMap ParentMap; for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* PasteNode = *It; UAIGraphNode* PasteAINode = Cast(PasteNode); if (PasteNode && (PasteAINode == nullptr || !PasteAINode->IsSubNode())) { bPastedParentNode = true; // Select the newly pasted stuff CurrentGraphEditor->SetNodeSelection(PasteNode, true); const FVector::FReal NodePosX = (PasteNode->NodePosX - AvgNodePosition.X) + Location.X; const FVector::FReal NodePosY = (PasteNode->NodePosY - AvgNodePosition.Y) + Location.Y; PasteNode->NodePosX = static_cast(NodePosX); PasteNode->NodePosY = static_cast(NodePosY); PasteNode->SnapToGrid(16); const FGuid OldGuid = PasteNode->NodeGuid; // Give new node a different Guid from the old one PasteNode->CreateNewGuid(); const FGuid NewGuid = PasteNode->NodeGuid; NewToOldNodeMapping.Add(NewGuid, OldGuid); if (PasteAINode) { PasteAINode->RemoveAllSubNodes(); ParentMap.Add(PasteAINode->CopySubNodeIndex, PasteAINode); } } } for (TSet::TIterator It(PastedNodes); It; ++It) { UAIGraphNode* PasteNode = Cast(*It); if (PasteNode && PasteNode->IsSubNode()) { PasteNode->NodePosX = 0; PasteNode->NodePosY = 0; // remove subnode from graph, it will be referenced from parent node PasteNode->DestroyNode(); PasteNode->ParentNode = ParentMap.FindRef(PasteNode->CopySubNodeIndex); if (PasteNode->ParentNode) { PasteNode->ParentNode->AddSubNode(PasteNode, EdGraph); } else if (!bHasMultipleNodesSelected && !bPastedParentNode && SelectedParent) { PasteNode->ParentNode = SelectedParent; SelectedParent->AddSubNode(PasteNode, EdGraph); } } } FixupPastedNodes(PastedNodes, NewToOldNodeMapping); if (AIGraph) { AIGraph->UpdateClassData(); AIGraph->OnNodesPasted(TextToImport); AIGraph->UnlockUpdates(); } // Update UI CurrentGraphEditor->NotifyGraphChanged(); UObject* GraphOwner = EdGraph->GetOuter(); if (GraphOwner) { GraphOwner->PostEditChange(); GraphOwner->MarkPackageDirty(); } } void FAIGraphEditor::FixupPastedNodes(const TSet& PastedGraphNodes, const TMap& NewToOldNodeMapping) { } bool FAIGraphEditor::CanPasteNodes() const { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return false; } FString ClipboardContent; FPlatformApplicationMisc::ClipboardPaste(ClipboardContent); return FEdGraphUtilities::CanImportNodesFromText(CurrentGraphEditor->GetCurrentGraph(), ClipboardContent); } void FAIGraphEditor::DuplicateNodes() { CopySelectedNodes(); PasteNodes(); } bool FAIGraphEditor::CanDuplicateNodes() const { return CanCopyNodes(); } bool FAIGraphEditor::CanCreateComment() const { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); return CurrentGraphEditor.IsValid(); } void FAIGraphEditor::OnCreateComment() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (UEdGraph* EdGraph = CurrentGraphEditor.IsValid() ? CurrentGraphEditor->GetCurrentGraph() : nullptr) { TSharedPtr Action = EdGraph->GetSchema()->GetCreateCommentAction(); if (Action.IsValid()) { Action->PerformAction(EdGraph, nullptr, FVector2f()); } } } void FAIGraphEditor::OnClassListUpdated() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } UAIGraph* MyGraph = Cast(CurrentGraphEditor->GetCurrentGraph()); if (MyGraph) { const bool bUpdated = MyGraph->UpdateUnknownNodeClasses(); if (bUpdated) { FGraphPanelSelectionSet CurrentSelection = GetSelectedNodes(); OnSelectedNodesChanged(CurrentSelection); MyGraph->UpdateAsset(); } } } #undef LOCTEXT_NAMESPACE