// Copyright Epic Games, Inc. All Rights Reserved. #include "AIGraphSchema.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ToolMenus.h" #include "Settings/EditorStyleSettings.h" #include "EdGraph/EdGraph.h" #include "AIGraphNode.h" #include "GraphEditorActions.h" #include "AIGraphConnectionDrawingPolicy.h" #include "ScopedTransaction.h" #include "Framework/Commands/GenericCommands.h" #include "EdGraphNode_Comment.h" #define LOCTEXT_NAMESPACE "AIGraph" namespace { // Maximum distance a drag can be off a node edge to require 'push off' from node const int32 NodeDistance = 60; } ////////////////////////////////////////////////////////////////////////// UEdGraphNode* FAISchemaAction_AddComment::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode) { UEdGraphNode_Comment* const CommentTemplate = NewObject(); FVector2f SpawnLocation = Location; FSlateRect Bounds; TSharedPtr GraphEditorPtr = SGraphEditor::FindGraphEditorForGraph(ParentGraph); if (GraphEditorPtr.IsValid()) { // If they have a selection, build a bounding box around the selection if (GraphEditorPtr->GetBoundsForSelectedNodes(/*out*/ Bounds, 50.0f)) { CommentTemplate->SetBounds(Bounds); SpawnLocation.X = CommentTemplate->NodePosX; SpawnLocation.Y = CommentTemplate->NodePosY; } else { // Otherwise initialize a default comment at the user's cursor location. SpawnLocation = GraphEditorPtr->GetPasteLocation2f(); } } UEdGraphNode* const NewNode = FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate(ParentGraph, CommentTemplate, SpawnLocation, bSelectNewNode); return NewNode; } ////////////////////////////////////////////////////////////////////////// UEdGraphNode* FAISchemaAction_NewNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode) { UEdGraphNode* ResultNode = NULL; // If there is a template, we actually use it if (NodeTemplate != NULL) { const FScopedTransaction Transaction(LOCTEXT("AddNode", "Add Node")); ParentGraph->Modify(); if (FromPin) { FromPin->Modify(); } NodeTemplate->SetFlags(RF_Transactional); // set outer to be the graph so it doesn't go away NodeTemplate->Rename(NULL, ParentGraph, REN_NonTransactional); ParentGraph->AddNode(NodeTemplate, true); NodeTemplate->CreateNewGuid(); NodeTemplate->PostPlacedNewNode(); // For input pins, new node will generally overlap node being dragged off // Work out if we want to visually push away from connected node int32 XLocation = static_cast(Location.X); if (FromPin && FromPin->Direction == EGPD_Input) { UEdGraphNode* PinNode = FromPin->GetOwningNode(); const float XDelta = FMath::Abs(PinNode->NodePosX - Location.X); if (XDelta < NodeDistance) { // Set location to edge of current node minus the max move distance // to force node to push off from connect node enough to give selection handle XLocation = PinNode->NodePosX - NodeDistance; } } NodeTemplate->NodePosX = XLocation; NodeTemplate->NodePosY = static_cast(Location.Y); NodeTemplate->SnapToGrid(GetDefault()->GridSnapSize); // setup pins after placing node in correct spot, since pin sorting will happen as soon as link connection change occurs NodeTemplate->AllocateDefaultPins(); NodeTemplate->AutowireNewNode(FromPin); ResultNode = NodeTemplate; } return ResultNode; } UEdGraphNode* FAISchemaAction_NewNode::PerformAction(class UEdGraph* ParentGraph, TArray& FromPins, const FVector2f& Location, bool bSelectNewNode) { UEdGraphNode* ResultNode = NULL; if (FromPins.Num() > 0) { ResultNode = PerformAction(ParentGraph, FromPins[0], Location); // Try autowiring the rest of the pins for (int32 Index = 1; Index < FromPins.Num(); ++Index) { ResultNode->AutowireNewNode(FromPins[Index]); } } else { ResultNode = PerformAction(ParentGraph, NULL, Location, bSelectNewNode); } return ResultNode; } void FAISchemaAction_NewNode::AddReferencedObjects(FReferenceCollector& Collector) { FEdGraphSchemaAction::AddReferencedObjects(Collector); // These don't get saved to disk, but we want to make sure the objects don't get GC'd while the action array is around Collector.AddReferencedObject(NodeTemplate); } UEdGraphNode* FAISchemaAction_NewSubNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode) { ParentNode->AddSubNode(NodeTemplate, ParentGraph); return NULL; } UEdGraphNode* FAISchemaAction_NewSubNode::PerformAction(class UEdGraph* ParentGraph, TArray& FromPins, const FVector2f& Location, bool bSelectNewNode) { return PerformAction(ParentGraph, NULL, Location, bSelectNewNode); } void FAISchemaAction_NewSubNode::AddReferencedObjects(FReferenceCollector& Collector) { FEdGraphSchemaAction::AddReferencedObjects(Collector); // These don't get saved to disk, but we want to make sure the objects don't get GC'd while the action array is around Collector.AddReferencedObject(NodeTemplate); Collector.AddReferencedObject(ParentNode); } ////////////////////////////////////////////////////////////////////////// UAIGraphSchema::UAIGraphSchema(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } TSharedPtr UAIGraphSchema::AddNewNodeAction(FGraphActionListBuilderBase& ContextMenuBuilder, const FText& Category, const FText& MenuDesc, const FText& Tooltip) { TSharedPtr NewAction = TSharedPtr(new FAISchemaAction_NewNode(Category, MenuDesc, Tooltip, 0)); ContextMenuBuilder.AddAction(NewAction); return NewAction; } TSharedPtr UAIGraphSchema::AddNewSubNodeAction(FGraphActionListBuilderBase& ContextMenuBuilder, const FText& Category, const FText& MenuDesc, const FText& Tooltip) { TSharedPtr NewAction = TSharedPtr(new FAISchemaAction_NewSubNode(Category, MenuDesc, Tooltip, 0)); ContextMenuBuilder.AddAction(NewAction); return NewAction; } void UAIGraphSchema::GetSubNodeClasses(int32 SubNodeFlags, TArray& ClassData, UClass*& GraphNodeClass) const { // empty in base class } void UAIGraphSchema::GetGraphNodeContextActions(FGraphContextMenuBuilder& ContextMenuBuilder, int32 SubNodeFlags) const { UEdGraph* Graph = (UEdGraph*)ContextMenuBuilder.CurrentGraph; UClass* GraphNodeClass = nullptr; TArray NodeClasses; GetSubNodeClasses(SubNodeFlags, NodeClasses, GraphNodeClass); if (GraphNodeClass) { for (const auto& NodeClass : NodeClasses) { const FText NodeTypeName = FText::FromString(FName::NameToDisplayString(NodeClass.ToString(), false)); UAIGraphNode* OpNode = NewObject(Graph, GraphNodeClass); OpNode->ClassData = NodeClass; TSharedPtr AddOpAction = UAIGraphSchema::AddNewSubNodeAction(ContextMenuBuilder, NodeClass.GetCategory(), NodeTypeName, NodeClass.GetTooltip()); AddOpAction->ParentNode = Cast(ContextMenuBuilder.SelectedObjects[0]); AddOpAction->NodeTemplate = OpNode; } } } void UAIGraphSchema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const { if (Context->Node) { { FToolMenuSection& Section = Menu->AddSection("BehaviorTreeGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions")); Section.AddMenuEntry(FGenericCommands::Get().Delete); Section.AddMenuEntry(FGenericCommands::Get().Cut); Section.AddMenuEntry(FGenericCommands::Get().Copy); Section.AddMenuEntry(FGenericCommands::Get().Duplicate); Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks); } } Super::GetContextMenuActions(Menu, Context); } void UAIGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakNodeLinks", "Break Node Links")); Super::BreakNodeLinks(TargetNode); } void UAIGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links")); Super::BreakPinLinks(TargetPin, bSendsNodeNotification); } void UAIGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link")); Super::BreakSinglePinLink(SourcePin, TargetPin); } FLinearColor UAIGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { return FColor::White; } bool UAIGraphSchema::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const { check(Pin != NULL); return Pin->bDefaultValueIsIgnored; } class FConnectionDrawingPolicy* UAIGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const { return new FAIGraphConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj); } TSharedPtr UAIGraphSchema::GetCreateCommentAction() const { return TSharedPtr(static_cast(new FAISchemaAction_AddComment)); } int32 UAIGraphSchema::GetNodeSelectionCount(const UEdGraph* Graph) const { if (Graph) { TSharedPtr GraphEditorPtr = SGraphEditor::FindGraphEditorForGraph(Graph); if (GraphEditorPtr.IsValid()) { return GraphEditorPtr->GetNumberOfSelectedNodes(); } } return 0; } #undef LOCTEXT_NAMESPACE