diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index a6d32fb47..a3bc8384e 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -13,6 +13,7 @@ #include "Nodes/Graph/FlowNode_CustomOutput.h" #include "Nodes/Graph/FlowNode_Start.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "Nodes/Route/FlowNode_NamedReroute.h" #include "Types/FlowAutoDataPinsWorkingData.h" #include "Types/FlowDataPinValue.h" #include "Types/FlowStructUtils.h" @@ -573,6 +574,15 @@ void UFlowAsset::HarvestNodeConnections(UFlowNode* TargetNode) } } + // Process the Named Reroute Usage node + if (auto* NamedRerouteUsage = Cast(FlowNode)) + { + if (NamedRerouteUsage->IsDeclarationValid()) + { + FoundConnections.Add(UFlowNode::DefaultOutputPin.PinName, FConnectedPin(NamedRerouteUsage->Declaration->GetGuid(), UFlowNode::DefaultInputPin.PinName)); + } + } + // This check exists to ensure that we don't mark graph dirty, if none of connections changed { const TMap& OldConnections = FlowNode->Connections; diff --git a/Source/Flow/Private/Nodes/Route/FlowNode_NamedReroute.cpp b/Source/Flow/Private/Nodes/Route/FlowNode_NamedReroute.cpp new file mode 100644 index 000000000..0ee333dad --- /dev/null +++ b/Source/Flow/Private/Nodes/Route/FlowNode_NamedReroute.cpp @@ -0,0 +1,255 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Nodes/Route/FlowNode_NamedReroute.h" + +#include "FlowAsset.h" + +UFlowNode_NamedRerouteBase::UFlowNode_NamedRerouteBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITOR + Category = TEXT("Route"); +#endif + + AllowedSignalModes = {EFlowSignalMode::Enabled, EFlowSignalMode::Disabled}; +} + +class UFlowNode_NamedRerouteDeclaration* UFlowNode_NamedRerouteBase::FindDeclarationInGraph( + const FGuid& VariableGuid) const +{ + const auto FlowAsset = GetFlowAsset(); + check(FlowAsset); + return FindDeclarationInArray(VariableGuid, FlowAsset->GetAllNodes()); +} + +void UFlowNode_NamedRerouteDeclaration::ExecuteInput(const FName& PinName) +{ + TriggerFirstOutput(true); +} + +void UFlowNode_NamedRerouteDeclaration::PostInitProperties() +{ + Super::PostInitProperties(); + // Init the GUID + UpdateVariableGuid(false, false); +} + +void UFlowNode_NamedRerouteDeclaration::PostLoad() +{ + Super::PostLoad(); + // Init the GUID + UpdateVariableGuid(false, false); +} + +void UFlowNode_NamedRerouteDeclaration::PostDuplicate(bool bDuplicateForPIE) +{ + Super::PostDuplicate(bDuplicateForPIE); + + UpdateVariableGuid(false, true); +} + +#if WITH_EDITOR +void UFlowNode_NamedRerouteDeclaration::SetEditableName(const FString& NewName) +{ + VariableName = *NewName; + MakeNameUnique(); +} + +void UFlowNode_NamedRerouteDeclaration::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(ThisClass, VariableName)) + { + MakeNameUnique(); + } +} + +void UFlowNode_NamedRerouteDeclaration::PostPasteNode(const TArray& PastedNodes) +{ + Super::PostPasteNode(PastedNodes); + + // Only force regeneration of Guid if there's already a variable with the same one + if (FindDeclarationInGraph(VariableGuid)) + { + // Update Guid, and update the copied usages accordingly + FGuid OldGuid = VariableGuid; + UpdateVariableGuid(true, true); + for (UFlowNode* PastedNode : PastedNodes) + { + auto* Usage = Cast(PastedNode); + if (Usage && Usage->DeclarationVariableGuid == OldGuid) + { + Usage->Declaration = this; + Usage->DeclarationVariableGuid = VariableGuid; + } + } + + // Find a new name + MakeNameUnique(); + } + else + { + // If there's no existing variable with this GUID, only create it if needed + UpdateVariableGuid(false, true); + } +} + +FText UFlowNode_NamedRerouteDeclaration::GetNodeToolTip() const +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return Super::GetNodeTitle(); + } + return FText::FromString(FString::Printf(TEXT("Declaration of [ %s ]"), *GetVariableName().ToString())); +} + +FText UFlowNode_NamedRerouteDeclaration::GetNodeTitle() const +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return Super::GetNodeTitle(); + } + return FText::FromString(GetVariableName().ToString()); +} + +void UFlowNode_NamedRerouteDeclaration::MakeNameUnique() +{ + if (const auto* FlowAsset = GetFlowAsset()) + { + auto Nodes = FlowAsset->GetAllNodes(); + + int32 NameIndex = 1; + bool bResultNameIndexValid = true; + FName PotentialName; + + // Find an available unique name + do + { + PotentialName = VariableName; + if (NameIndex != 1) + { + PotentialName.SetNumber(NameIndex); + } + + bResultNameIndexValid = true; + for (UFlowNode* Node : Nodes) + { + auto* OtherDeclaration = Cast(Node); + if (OtherDeclaration && OtherDeclaration != this && OtherDeclaration->VariableName == PotentialName) + { + bResultNameIndexValid = false; + break; + } + } + + NameIndex++; + } while (!bResultNameIndexValid); + + VariableName = PotentialName; + } +} +#endif + +void UFlowNode_NamedRerouteDeclaration::UpdateVariableGuid(bool bForceGeneration, bool bAllowMarkingPackageDirty) +{ + // If we are in the editor, and we don't have a valid GUID yet, generate one. + if (GIsEditor && !FApp::IsGame()) + { + if (bForceGeneration || !VariableGuid.IsValid()) + { + VariableGuid = FGuid::NewGuid(); + + if (bAllowMarkingPackageDirty) + { + MarkPackageDirty(); + } + } + } +} + +UFlowNode_NamedRerouteUsage::UFlowNode_NamedRerouteUsage(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + OutputPins.Empty(); +} + +bool UFlowNode_NamedRerouteUsage::IsDeclarationValid() const +{ + // Deleted nodes are marked as pending kill (see SFlowGraphEditor::DeleteSelectedNodes()) + return IsValid(Declaration); +} + +void UFlowNode_NamedRerouteUsage::ExecuteInput(const FName& PinName) +{ + Finish(); + if (IsDeclarationValid()) + { + GetFlowAsset()->TriggerInput(Declaration->GetGuid(), UFlowNode::DefaultInputPin.PinName, FConnectedPin(GetGuid(), UFlowNode::DefaultOutputPin.PinName)); + } +} + +#if WITH_EDITOR +void UFlowNode_NamedRerouteUsage::PostPasteNode(const TArray& PastedNodes) +{ + Super::PostPasteNode(PastedNodes); + + // First try to find the declaration in the copied nodes + Declaration = FindDeclarationInArray(DeclarationVariableGuid, PastedNodes); + if (!IsValid(Declaration)) + { + // If unsuccessful, try to find it in the whole graph + Declaration = FindDeclarationInGraph(DeclarationVariableGuid); + } + + // Keep GUID in sync. In case this is pasted by itself into another graph, we don't want this node to connect up to a previously connected declaration. + if (IsValid(Declaration)) + { + DeclarationVariableGuid = Declaration->GetVariableGuid(); + } + + // Save that Declaration change + MarkPackageDirty(); +} + +FText UFlowNode_NamedRerouteUsage::GetNodeToolTip() const +{ + FString NodeTitle = IsDeclarationValid() ? Declaration->GetVariableName().ToString() : TEXT("Invalid named reroute"); + return FText::FromString(FString::Printf(TEXT("Usage of [ %s ]"), *NodeTitle)); +} + +FText UFlowNode_NamedRerouteUsage::GetNodeTitle() const +{ + FString NodeTitle = IsDeclarationValid() ? Declaration->GetVariableName().ToString() : TEXT("Invalid named reroute"); + return FText::FromString(NodeTitle); +} + +EDataValidationResult UFlowNode_NamedRerouteUsage::ValidateNode() +{ + if (Super::ValidateNode() == EDataValidationResult::Invalid) + { + return EDataValidationResult::Invalid; + } + + if (!IsDeclarationValid()) + { + ValidationLog.Error(TEXT("Invalid named reroute usage"), this); + return EDataValidationResult::Invalid; + } + + if (Declaration->GetFlowAsset() != GetFlowAsset()) + { + ValidationLog.Error(TEXT("Named reroute usage has a different flow asset than the declaration"), this); + return EDataValidationResult::Invalid; + } + + if (Declaration->GatherConnectedNodes().Contains(this)) + { + ValidationLog.Error(TEXT("Loop detected in named reroute"), this); + return EDataValidationResult::Invalid; + } + + return EDataValidationResult::Valid; +} +#endif diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index ee470558d..93e4d8e62 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -41,6 +41,7 @@ class FLOW_API UFlowAsset : public UObject friend class UFlowNode; friend class UFlowNode_CustomOutput; friend class UFlowNode_SubGraph; + friend class UFlowNode_NamedRerouteUsage; friend class UFlowSubsystem; friend class FFlowAssetDetails; diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index ef8fb5004..011269da1 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -371,6 +371,8 @@ class FLOW_API UFlowNodeBase // used when import graph from another asset virtual void PostImport() {} + + virtual void PostPasteNode(const TArray& PastedNodes) {} #endif public: diff --git a/Source/Flow/Public/Nodes/Route/FlowNode_NamedReroute.h b/Source/Flow/Public/Nodes/Route/FlowNode_NamedReroute.h new file mode 100644 index 000000000..1cecd637f --- /dev/null +++ b/Source/Flow/Public/Nodes/Route/FlowNode_NamedReroute.h @@ -0,0 +1,124 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Nodes/FlowNode.h" +#include "FlowNode_NamedReroute.generated.h" + +UCLASS(Abstract) +class FLOW_API UFlowNode_NamedRerouteBase : public UFlowNode +{ + GENERATED_BODY() + +public: + UFlowNode_NamedRerouteBase(const FObjectInitializer& ObjectInitializer); + +protected: + template + class UFlowNode_NamedRerouteDeclaration* FindDeclarationInArray(const FGuid& VariableGuid, const NodesArrayType& Nodes) const; + + class UFlowNode_NamedRerouteDeclaration* FindDeclarationInGraph(const FGuid& VariableGuid) const; +}; + +UCLASS(NotBlueprintable, meta = (DisplayName = "NamedReroute")) +class FLOW_API UFlowNode_NamedRerouteDeclaration final : public UFlowNode_NamedRerouteBase +{ + GENERATED_BODY() + +public: + const FName& GetVariableName() const { return VariableName; } + const FGuid& GetVariableGuid() const { return VariableGuid; } + +#if WITH_EDITOR + // virtual bool CanRenameNode() const override; + // virtual FString GetEditableName() const override; + void SetEditableName(const FString& NewName); +#endif + +protected: + virtual void ExecuteInput(const FName& PinName) override; + + //~ Begin UObject Interface + virtual void PostInitProperties() override; + virtual void PostLoad() override; + virtual void PostDuplicate(bool bDuplicateForPIE) override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + //~ End UObject Interface + +#if WITH_EDITOR + virtual void PostPasteNode(const TArray& PastedNodes) override; +#endif + +#if WITH_EDITOR + virtual FText GetNodeToolTip() const override; + virtual FText GetNodeTitle() const override; + virtual bool CanUserAddInput() const override { return false; } + virtual bool CanUserAddOutput() const override { return false; } +#endif + +private: + UPROPERTY(EditAnywhere, Category = NamedRerouteDeclaration) + FName VariableName = TEXT("Name"); + + // The variable GUID, to support copy across graphs + UPROPERTY() + FGuid VariableGuid; + +#if WITH_EDITOR + void MakeNameUnique(); +#endif + void UpdateVariableGuid(bool bForceGeneration, bool bAllowMarkingPackageDirty); +}; + +// This node is not placeable manually, only created from context popup menu +UCLASS(NotBlueprintable, NotPlaceable, meta = (DisplayName = "NamedRerouteUsage")) +class FLOW_API UFlowNode_NamedRerouteUsage final : public UFlowNode_NamedRerouteBase +{ + GENERATED_BODY() + +public: + UFlowNode_NamedRerouteUsage(const FObjectInitializer& ObjectInitializer); + + // The declaration this node is linked to + UPROPERTY() + TObjectPtr Declaration; + + // The variable GUID, to support copy across graphs + UPROPERTY() + FGuid DeclarationVariableGuid; + + // Check that the declaration isn't deleted + bool IsDeclarationValid() const; + +protected: + virtual void ExecuteInput(const FName& PinName) override; + +#if WITH_EDITOR + virtual void PostPasteNode(const TArray& PastedNodes) override; +#endif + +#if WITH_EDITOR + virtual FText GetNodeToolTip() const override; + virtual FText GetNodeTitle() const override; + virtual bool CanUserAddInput() const override { return false; } + virtual bool CanUserAddOutput() const override { return false; } + virtual EDataValidationResult ValidateNode() override; +#endif +}; + +template +inline UFlowNode_NamedRerouteDeclaration* UFlowNode_NamedRerouteBase::FindDeclarationInArray(const FGuid& VariableGuid, const NodesArrayType& Nodes) const +{ + for (UFlowNode* Node : Nodes) + { + auto* Declaration = Cast(Node); + if (Declaration && this != Declaration && Declaration->GetVariableGuid() == VariableGuid) + { + return Declaration; + } + } + return nullptr; +} diff --git a/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp b/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp index 86023789f..86c795ea7 100644 --- a/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp +++ b/Source/FlowEditor/Private/Asset/FlowAssetEditor.cpp @@ -77,6 +77,10 @@ void FFlowAssetEditor::HandleUndoTransaction() void FFlowAssetEditor::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged) { + if( PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive ) + { + GetDefault()->ForceVisualizationCacheClear(); + } } FName FFlowAssetEditor::GetToolkitFName() const diff --git a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp index 4d14f5b57..3af4ef3a2 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphEditor.cpp @@ -543,7 +543,7 @@ void SFlowGraphEditor::DeleteSelectedNodes() if (const UFlowGraphNode* FlowGraphNode = Cast(Node)) { - if (const UFlowNode* FlowNode = Cast(FlowGraphNode->GetFlowNodeBase())) + if (UFlowNode* FlowNode = Cast(FlowGraphNode->GetFlowNodeBase())) { // If the user is pressing shift then try and reconnect the pins if (FSlateApplication::Get().GetModifierKeys().IsShiftDown()) @@ -555,6 +555,9 @@ void SFlowGraphEditor::DeleteSelectedNodes() Node->DestroyNode(); FlowAsset->UnregisterNode(FlowNode->GetGuid()); + FlowNode->Modify(); + // Make sure the deleted node is caught by gc + FlowNode->MarkAsGarbage(); continue; } } @@ -769,6 +772,8 @@ void SFlowGraphEditor::PasteNodesHere(const FVector2D& Location) AvgNodePosition.Y *= InvNumNodes; } + TArray NewFlowNodes; + NewFlowNodes.Reset(NodesToPaste.Num()); TMap EdNodeCopyIndexMap; for (TSet::TConstIterator It(NodesToPaste); It; ++It) { @@ -802,6 +807,7 @@ void SFlowGraphEditor::PasteNodesHere(const FVector2D& Location) // Only full FlowNodes are registered with the Asset // (for now? perhaps we register AddOns in the future?) FlowAsset->RegisterNode(PastedNode->NodeGuid, FlowNode); + NewFlowNodes.Add(FlowNode); } PastedFlowGraphNode->RemoveAllSubNodes(); @@ -834,6 +840,11 @@ void SFlowGraphEditor::PasteNodesHere(const FVector2D& Location) } } + for (auto* NewNode : NewFlowNodes) + { + NewNode->PostPasteNode(NewFlowNodes); + } + if (FlowGraph) { FlowGraph->UpdateClassData(); diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp index 68b6ce845..7a53d6e93 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema.cpp @@ -30,6 +30,7 @@ #include "Engine/MemberReference.h" #include "Kismet2/KismetEditorUtilities.h" #include "ScopedTransaction.h" +#include "Nodes/Route/FlowNode_NamedReroute.h" #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 #include "Kismet/BlueprintTypeConversions.h" @@ -243,6 +244,7 @@ void UFlowGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextM { GetFlowNodeActions(ContextMenuBuilder, GetEditedAssetOrClassDefault(ContextMenuBuilder.CurrentGraph), FString()); GetCommentAction(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph); + GetNamedRerouteActions(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph); if (!ContextMenuBuilder.FromPin && FFlowGraphUtils::GetFlowGraphEditor(ContextMenuBuilder.CurrentGraph)->CanPasteNodes()) { @@ -1269,6 +1271,28 @@ void UFlowGraphSchema::GetCommentAction(FGraphActionMenuBuilder& ActionMenuBuild } } +void UFlowGraphSchema::GetNamedRerouteActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) +{ + if (CurrentGraph) + { + for (UEdGraphNode* GraphNode : CurrentGraph->Nodes) + { + if (auto* FlowGraphNode = Cast(GraphNode)) + { + if (auto Declaration = Cast(FlowGraphNode->GetFlowNodeBase())) + { + static const FText Category = LOCTEXT("NamedRerouteCategory", "Named Reroutes"); + const FText Name = FText::FromString(Declaration->GetVariableName().ToString()); + const FText Tooltip = FText::Format(LOCTEXT("NamedRerouteTooltip", "Add a usage of {0} here"), Name); + TSharedPtr NewAction(new FFlowGraphSchemaAction_NewNamedRerouteUsage(Category, Name, Tooltip, 1 /* We want named reroutes to be on top */)); + NewAction->Declaration = Declaration; + ActionMenuBuilder.AddAction(NewAction); + } + } + } + } +} + bool UFlowGraphSchema::IsFlowNodeOrAddOnPlaceable(const UClass* Class) { if (Class == nullptr || Class->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable | CLASS_Deprecated)) diff --git a/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp b/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp index 42b404e38..ce4cc98f3 100644 --- a/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp +++ b/Source/FlowEditor/Private/Graph/FlowGraphSchema_Actions.cpp @@ -17,6 +17,7 @@ #include "EdGraphNode_Comment.h" #include "Editor.h" #include "ScopedTransaction.h" +#include "Nodes/Route/FlowNode_NamedReroute.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGraphSchema_Actions) @@ -63,7 +64,7 @@ UFlowGraphNode* FFlowGraphSchemaAction_NewNode::CreateNode(UEdGraph* ParentGraph // register to the graph NewGraphNode->CreateNewGuid(); - ParentGraph->AddNode(NewGraphNode, false, bSelectNewNode); + ParentGraph->AddNode(NewGraphNode, bSelectNewNode, bSelectNewNode); // link editor and runtime nodes together UFlowNode* FlowNode = FlowAsset->CreateNode(NodeClass, NewGraphNode); @@ -270,6 +271,28 @@ TSharedPtr FFlowSchemaAction_NewSubNode::AddNewSub ///////////////////////////////////////////////////// // Paste +UEdGraphNode* FFlowGraphSchemaAction_NewNamedRerouteUsage::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode) +{ + // prevent adding new nodes while playing + if (GEditor->PlayWorld != nullptr) + { + return nullptr; + } + + check(Declaration); + + const FScopedTransaction Transaction(LOCTEXT("FlowEditorNewNamedRerouteUsage", "Flow Editor: New Named Reroute Usage")); + + auto* NewGraphNode = FFlowGraphSchemaAction_NewNode::CreateNode(ParentGraph, FromPin, UFlowNode_NamedRerouteUsage::StaticClass(), FDeprecateSlateVector2D(Location), bSelectNewNode); + if (NewGraphNode) + { + auto* Usage = CastChecked(NewGraphNode->GetFlowNodeBase()); + Usage->Declaration = Declaration; + Usage->DeclarationVariableGuid = Declaration->GetVariableGuid(); + } + return NewGraphNode; +} + UEdGraphNode* FFlowGraphSchemaAction_Paste::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, const bool bSelectNewNode/* = true*/) { // prevent adding new nodes while playing diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 9864a54e4..9d6647028 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -1315,6 +1315,11 @@ void UFlowGraphNode::NodeConnectionListChanged() Graph->GetFlowAsset()->HarvestNodeConnections(Cast(GetFlowNodeBase())); Graph->NotifyNodeChanged(this); } + + if (const UEdGraphSchema* Schema = GetSchema()) + { + Schema->ForceVisualizationCacheClear(); + } } FString UFlowGraphNode::GetPropertyNameAndValueForDiff(const FProperty* Prop, const uint8* PropertyAddr) const diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_NamedReroute.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_NamedReroute.cpp new file mode 100644 index 000000000..9986bfacd --- /dev/null +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_NamedReroute.cpp @@ -0,0 +1,252 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Graph/Nodes/FlowGraphNode_NamedReroute.h" + +#include "FlowEditorCommands.h" +#include "Graph/FlowGraphEditor.h" +#include "Graph/FlowGraphSchema_Actions.h" +#include "Graph/FlowGraphUtils.h" +#include "Nodes/Route/FlowNode_NamedReroute.h" + +#define LOCTEXT_NAMESPACE "FlowGraphEditor" + +UFlowGraphNode_NamedRerouteDeclaration::UFlowGraphNode_NamedRerouteDeclaration(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AssignedNodeClasses = {UFlowNode_NamedRerouteDeclaration::StaticClass()}; + bCanRenameNode = true; +} + +void UFlowGraphNode_NamedRerouteDeclaration::GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const +{ + Super::GetNodeContextMenuActions(Menu, Context); + + const FFlowGraphCommands& FlowGraphCommands = FFlowGraphCommands::Get(); + if (Context->Node) + { + FToolMenuSection& Section = Menu->FindOrAddSection("FlowGraphNodeActions"); + + { + const TAttribute Label = LOCTEXT("FlowGraphNodeActions_CreateRerouteUsageFromDeclaration_Label", "Create Reroute Usage From Declaration"); + const TAttribute ToolTip = LOCTEXT("FlowGraphNodeActions_CreateRerouteUsageFromDeclaration_Tooltip", "Create a reroute usage from the declaration."); + const FSlateIcon Icon = FSlateIcon(); + + FToolUIAction UIAction; + UIAction.ExecuteAction = FToolMenuExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){OnCreateRerouteUsageFromDeclaration();}); + UIAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){return CanCreateRerouteUsageFromDeclaration();}); + Section.AddMenuEntry(TEXT("CreateRerouteUsageFromDeclaration"), Label, ToolTip, Icon, UIAction); + } + + { + const TAttribute Label = LOCTEXT("FlowGraphNodeActions_SelectNamedRerouteUsages_Label", "Select Named Reroute Usages"); + const TAttribute ToolTip = LOCTEXT("FlowGraphNodeActions_SelectNamedRerouteUsages_Tooltip", "Select the named reroute usages."); + const FSlateIcon Icon = FSlateIcon(); + + FToolUIAction UIAction; + UIAction.ExecuteAction = FToolMenuExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){OnSelectNamedRerouteUsages();}); + UIAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){return CanSelectNamedRerouteUsages();}); + Section.AddMenuEntry(TEXT("SelectNamedRerouteUsages"), Label, ToolTip, Icon, UIAction); + } + } +} + +void UFlowGraphNode_NamedRerouteDeclaration::OnNodeDoubleClicked() const +{ + OnSelectNamedRerouteUsages(); +} + +void UFlowGraphNode_NamedRerouteDeclaration::OnRenameNode(const FString& NewName) +{ + Super::OnRenameNode(NewName); + + if (auto* Declaration = Cast(GetFlowNodeBase())) + { + Declaration->Modify(); + Declaration->SetEditableName(NewName); + Declaration->MarkPackageDirty(); + + // Refresh usage names + for(UFlowNode* FlowNode : GetFlowAsset()->GetAllNodes()) + { + auto* Usage = Cast(FlowNode); + if (Usage && Usage->Declaration == Declaration) + { + if (UEdGraphNode* UsageGraphNode = Usage->GetGraphNode()) + { + UsageGraphNode->ReconstructNode(); + } + } + } + } +} + +void UFlowGraphNode_NamedRerouteDeclaration::OnCreateRerouteUsageFromDeclaration() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return; + } + + FlowGraphEditor->ClearSelectionSet(); + if (auto* Declaration = Cast(GetFlowNodeBase())) + { + FFlowGraphSchemaAction_NewNamedRerouteUsage UsageAction; + UsageAction.Declaration = Declaration; +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6 + UsageAction.PerformAction(GetGraph(), nullptr, FVector2f(NodePosX + 150.f, NodePosY), true); +#else + UsageAction.PerformAction(GetGraph(), nullptr, FVector2f(NodePosX + 150.f, NodePosY), true); +#endif + } + FlowGraphEditor->ZoomToFit(true); +} + +bool UFlowGraphNode_NamedRerouteDeclaration::CanCreateRerouteUsageFromDeclaration() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return false; + } + + if (FlowGraphEditor->GetSelectedFlowNodes().Num() == 1) + { + for (const UFlowGraphNode* SelectedNode : FlowGraphEditor->GetSelectedFlowNodes()) + { + return SelectedNode == this; + } + } + return false; +} + +void UFlowGraphNode_NamedRerouteDeclaration::OnSelectNamedRerouteUsages() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return; + } + + FlowGraphEditor->ClearSelectionSet(); + if (auto* Declaration = Cast(GetFlowNodeBase())) + { + //TArray FoundGraphNodes; + for(UFlowNode* FlowNode : GetFlowAsset()->GetAllNodes()) + { + auto* Usage = Cast(FlowNode); + if (Usage && Usage->Declaration == Declaration) + { + if (UEdGraphNode* UsageGraphNode = Usage->GetGraphNode()) + { + //FoundGraphNodes.Add(UsageGraphNode); + FlowGraphEditor->SetNodeSelection(UsageGraphNode, true); + } + } + } + + //Add usage results into SearchTab + // if (FoundGraphNodes.Num()) + // { + // // Spawn the tab in case the user doesn't have it open + // TabManager->TryInvokeTab(FFlowEditorTabs::FindTabId); + // + // FindResults->PopulateSearchItems(FoundGraphNodes); + // FindResults->FocusForUse(); + // } + } + FlowGraphEditor->ZoomToFit(true); +} + +bool UFlowGraphNode_NamedRerouteDeclaration::CanSelectNamedRerouteUsages() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return false; + } + + if (FlowGraphEditor->GetSelectedFlowNodes().Num() == 1) + { + for (const UFlowGraphNode* SelectedNode : FlowGraphEditor->GetSelectedFlowNodes()) + { + return SelectedNode == this; + } + } + return false; +} + +UFlowGraphNode_NamedRerouteUsage::UFlowGraphNode_NamedRerouteUsage(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AssignedNodeClasses = {UFlowNode_NamedRerouteUsage::StaticClass()}; +} + +void UFlowGraphNode_NamedRerouteUsage::GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const +{ + Super::GetNodeContextMenuActions(Menu, Context); + + const FFlowGraphCommands& FlowGraphCommands = FFlowGraphCommands::Get(); + if (Context->Node) + { + FToolMenuSection& Section = Menu->FindOrAddSection("FlowGraphNodeActions"); + { + const TAttribute Label = LOCTEXT("FlowGraphNodeActions_SelectNamedRerouteDeclaration_Label", "Select Named Reroute Declaration"); + const TAttribute ToolTip = LOCTEXT("FlowGraphNodeActions_SelectNamedRerouteDeclaration_Tooltip", "Select the named reroute declaration."); + const FSlateIcon Icon = FSlateIcon(); + + FToolUIAction UIAction; + UIAction.ExecuteAction = FToolMenuExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){OnSelectNamedRerouteDeclaration();}); + UIAction.CanExecuteAction = FToolMenuCanExecuteAction::CreateWeakLambda(this, [this](const FToolMenuContext&){return CanSelectNamedRerouteDeclaration();}); + Section.AddMenuEntry(TEXT("SelectNamedRerouteDeclaration"), Label, ToolTip, Icon, UIAction); + } + } +} + +void UFlowGraphNode_NamedRerouteUsage::OnNodeDoubleClicked() const +{ + OnSelectNamedRerouteDeclaration(); +} + +void UFlowGraphNode_NamedRerouteUsage::OnSelectNamedRerouteDeclaration() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return; + } + + FlowGraphEditor->ClearSelectionSet(); + if (auto* Usage = Cast(GetFlowNodeBase())) + { + if (IsValid(Usage->Declaration)) + { + if (UEdGraphNode* DeclarationGraphNode = Usage->Declaration->GetGraphNode()) + { + FlowGraphEditor->SetNodeSelection(DeclarationGraphNode, true); + } + } + } + FlowGraphEditor->ZoomToFit(true); +} + +bool UFlowGraphNode_NamedRerouteUsage::CanSelectNamedRerouteDeclaration() const +{ + TSharedPtr FlowGraphEditor = FFlowGraphUtils::GetFlowGraphEditor(GetGraph()); + if (!FlowGraphEditor.IsValid()) + { + return false; + } + + if (FlowGraphEditor->GetSelectedFlowNodes().Num() == 1) + { + for (const UFlowGraphNode* SelectedNode : FlowGraphEditor->GetSelectedFlowNodes()) + { + return SelectedNode == this; + } + } + return false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index 78918f786..9c9241b70 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h @@ -144,6 +144,7 @@ class FLOWEDITOR_API UFlowGraphSchema : public UEdGraphSchema static TArray GetFilteredPlaceableNodesOrAddOns(const UFlowAsset* EditedFlowAsset, const TArray& InNativeNodesOrAddOns, const TMap& InBlueprintNodesOrAddOns); static void GetCommentAction(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph = nullptr); + static void GetNamedRerouteActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph); static bool IsFlowNodeOrAddOnPlaceable(const UClass* Class); diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema_Actions.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema_Actions.h index f57a715dc..ab910b4eb 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema_Actions.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema_Actions.h @@ -94,6 +94,34 @@ struct FLOWEDITOR_API FFlowSchemaAction_NewSubNode : public FEdGraphSchemaAction static TSharedPtr AddNewSubNodeAction(FGraphActionListBuilderBase& ContextMenuBuilder, const FText& Category, const FText& MenuDesc, const FText& Tooltip); }; +/** Action to add a local variable usage to the graph */ +USTRUCT() +struct FFlowGraphSchemaAction_NewNamedRerouteUsage : public FEdGraphSchemaAction +{ + GENERATED_USTRUCT_BODY(); + + // Declaration that we want to add an usage of + UPROPERTY() + TObjectPtr Declaration = nullptr; + + // Simple type info + static FName StaticGetTypeId() {static FName Type("FFlowSchemaAction_NewNamedRerouteUsage"); return Type;} + virtual FName GetTypeId() const override { return StaticGetTypeId(); } + + FFlowGraphSchemaAction_NewNamedRerouteUsage() + : FEdGraphSchemaAction() + {} + + FFlowGraphSchemaAction_NewNamedRerouteUsage(FText InNodeCategory, FText InMenuDesc, FText InToolTip, const int32 InGrouping) + : FEdGraphSchemaAction(MoveTemp(InNodeCategory), MoveTemp(InMenuDesc), MoveTemp(InToolTip), InGrouping) + {} + + //~ Begin FEdGraphSchemaAction Interface + using FEdGraphSchemaAction::PerformAction; // Prevent hiding of deprecated base class function with FVector2D + virtual UEdGraphNode* PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode = true) override; + //~ End FEdGraphSchemaAction Interface +}; + /** Action to paste clipboard contents into the graph */ USTRUCT() struct FLOWEDITOR_API FFlowGraphSchemaAction_Paste : public FEdGraphSchemaAction diff --git a/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode_NamedReroute.h b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode_NamedReroute.h new file mode 100644 index 000000000..c224b9e39 --- /dev/null +++ b/Source/FlowEditor/Public/Graph/Nodes/FlowGraphNode_NamedReroute.h @@ -0,0 +1,51 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "FlowGraphNode.h" +#include "FlowGraphNode_NamedReroute.generated.h" + +UCLASS() +class FLOWEDITOR_API UFlowGraphNode_NamedRerouteDeclaration : public UFlowGraphNode +{ + GENERATED_BODY() + +public: + UFlowGraphNode_NamedRerouteDeclaration(const FObjectInitializer& ObjectInitializer); + +protected: + // UEdGraphNode Begin + virtual void GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override; + // UEdGraphNode End + virtual void OnNodeDoubleClicked() const override; + virtual void OnRenameNode(const FString& NewName) override; + +private: + void OnCreateRerouteUsageFromDeclaration() const; + bool CanCreateRerouteUsageFromDeclaration() const; + + void OnSelectNamedRerouteUsages() const; + bool CanSelectNamedRerouteUsages() const; +}; + +UCLASS() +class FLOWEDITOR_API UFlowGraphNode_NamedRerouteUsage : public UFlowGraphNode +{ + GENERATED_BODY() + +public: + UFlowGraphNode_NamedRerouteUsage(const FObjectInitializer& ObjectInitializer); + +protected: + // UEdGraphNode Begin + virtual void GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override; + // UEdGraphNode End + virtual void OnNodeDoubleClicked() const override; + +private: + void OnSelectNamedRerouteDeclaration() const; + bool CanSelectNamedRerouteDeclaration() const; +}; + +