// Copyright Epic Games, Inc. All Rights Reserved. #include "PCGEditorGraphSchema.h" #include "PCGComponent.h" #include "PCGDataAsset.h" #include "PCGEdge.h" #include "PCGGraph.h" #include "PCGPin.h" #include "Elements/PCGCollapseElement.h" #include "Elements/PCGCreateSurfaceFromSpline.h" #include "Elements/PCGExecuteBlueprint.h" #include "Elements/PCGFilterByType.h" #include "Elements/PCGMakeConcreteElement.h" #include "Elements/PCGReroute.h" #include "Elements/PCGUserParameterGet.h" #include "PCGEditor.h" #include "PCGEditorCommon.h" #include "PCGEditorGraph.h" #include "PCGEditorGraphSchemaActions.h" #include "PCGEditorSettings.h" #include "PCGEditorUtils.h" #include "Helpers/PCGPropertyHelpers.h" #include "Nodes/PCGEditorGraphNodeBase.h" #include "Nodes/PCGEditorGraphNodeReroute.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Blueprint/BlueprintSupport.h" #include "Engine/Blueprint.h" #include "Framework/Application/SlateApplication.h" #include "ScopedTransaction.h" #include "UObject/UObjectIterator.h" #include "SGraphPanel.h" #define LOCTEXT_NAMESPACE "PCGEditorGraphSchema" FPCGActionsFilter::FPCGActionsFilter(const UEdGraph* InEdGraph, EPCGElementType InElementFilterType) : FilterType(InElementFilterType) , Graph(Cast(InEdGraph)) { if (Graph && Graph->GetPCGGraph()) { Customization = Graph->GetPCGGraph()->GraphCustomization; } } bool FPCGActionsFilter::Accepts(const FText& InCategory) const { return Customization.Accepts(InCategory); } void UPCGEditorGraphSchema::GetPaletteActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { GetGraphActions(ActionMenuBuilder, ActionFilter, /*bIsContextual=*/false); } void UPCGEditorGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const { Super::GetGraphContextActions(ContextMenuBuilder); GetGraphActions(ContextMenuBuilder, FPCGActionsFilter(ContextMenuBuilder.CurrentGraph), /*bIsContextual=*/true); } void UPCGEditorGraphSchema::GetGraphActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter, bool bIsContextual) const { EPCGElementType ElementTypeFilter = ActionFilter.FilterType; if (!!(ElementTypeFilter & EPCGElementType::Native)) { GetNativeElementActions(ActionMenuBuilder, ActionFilter); } if (!!(ElementTypeFilter & EPCGElementType::Subgraph)) { GetSubgraphElementActions(ActionMenuBuilder, ActionFilter); } if (!!(ElementTypeFilter & EPCGElementType::Blueprint)) { GetBlueprintElementActions(ActionMenuBuilder, ActionFilter); } if (!!(ElementTypeFilter & EPCGElementType::Settings)) { GetSettingsElementActions(ActionMenuBuilder, ActionFilter, bIsContextual); } if (!!(ElementTypeFilter & EPCGElementType::Asset)) { GetDataAssetActions(ActionMenuBuilder, ActionFilter); } if (!!(ElementTypeFilter & EPCGElementType::Other)) { GetNamedRerouteUsageActions(ActionMenuBuilder, ActionFilter); GetExtraElementActions(ActionMenuBuilder, ActionFilter); } } FLinearColor UPCGEditorGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { return GetDefault()->GetPinColor(PinType); } FConnectionDrawingPolicy* UPCGEditorGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const { return new FPCGEditorConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj); } const FPinConnectionResponse UPCGEditorGraphSchema::CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const { check(A && B); const UEdGraphNode* NodeA = A->GetOwningNode(); const UEdGraphNode* NodeB = B->GetOwningNode(); if (NodeA == NodeB) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionSameNode", "Both pins are on same node")); } if (A->Direction == B->Direction) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionSameDirection", "Both pins are the same direction")); } const UPCGEditorGraphNodeBase* EditorNodeA = CastChecked(NodeA); const UPCGEditorGraphNodeBase* EditorNodeB = CastChecked(NodeB); const UPCGEditorGraphNodeBase* EditorNodeWithInput = nullptr; // Check type compatibility & whether we can connect more pins const UPCGPin* InputPin = nullptr; const UPCGPin* OutputPin = nullptr; if (A->Direction == EGPD_Output) { OutputPin = EditorNodeA->GetPCGNode()->GetOutputPin(A->PinName); InputPin = EditorNodeB->GetPCGNode()->GetInputPin(B->PinName); EditorNodeWithInput = EditorNodeB; } else { OutputPin = EditorNodeB->GetPCGNode()->GetOutputPin(B->PinName); InputPin = EditorNodeA->GetPCGNode()->GetInputPin(A->PinName); EditorNodeWithInput = EditorNodeA; } if (!InputPin || !OutputPin) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionFailed", "Unable to verify pins")); } if (!InputPin->IsCompatible(OutputPin)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionTypesIncompatible", "Pins are incompatible")); } const EPCGTypeConversion RequiredTypeConversion = InputPin->GetRequiredTypeConversion(OutputPin); if (RequiredTypeConversion == EPCGTypeConversion::CollapseToPoint) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, LOCTEXT("ConnectionConversionToPoint", "Convert to Point")); } else if (RequiredTypeConversion == EPCGTypeConversion::Filter) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, LOCTEXT("ConnectionUsingFilter", "Filter data to match type")); } else if (RequiredTypeConversion == EPCGTypeConversion::MakeConcrete) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, LOCTEXT("ConnectionUsingMakeConcrete", "Make data concrete")); } if (!InputPin->AllowsMultipleConnections() && InputPin->EdgeCount() > 0) { return FPinConnectionResponse((A->Direction == EGPD_Output) ? CONNECT_RESPONSE_BREAK_OTHERS_B : CONNECT_RESPONSE_BREAK_OTHERS_A, LOCTEXT("ConnectionBreakExisting", "Break existing connection?")); } FText Reason; if (!EditorNodeWithInput->IsCompatible(InputPin, OutputPin, Reason)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, Reason); } return FPinConnectionResponse(); } bool UPCGEditorGraphSchema::TryCreateConnection(UEdGraphPin* InA, UEdGraphPin* InB) const { return TryCreateConnectionInternal(InA, InB, /*bAddConversionNodeIfNeeded=*/true); } bool UPCGEditorGraphSchema::TryCreateConnectionInternal(UEdGraphPin* InA, UEdGraphPin* InB, bool bAddConversionNodeIfNeeded) const { check(InA && InB); if (InA->Direction == InB->Direction) { // Don't connect same polarity return false; } UEdGraphPin* A = (InA->Direction == EGPD_Output) ? InA : InB; UEdGraphPin* B = (InA->Direction == EGPD_Input) ? InA : InB; check(A->Direction == EGPD_Output && B->Direction == EGPD_Input); UEdGraphNode* NodeA = A->GetOwningNodeUnchecked(); UEdGraphNode* NodeB = B->GetOwningNodeUnchecked(); if (!ensure(NodeA && NodeB)) { // TODO: We've had crashes where one of these nodes was nullptr, we need to figure out why this can happen. return false; } UPCGEditorGraphNodeBase* PCGEdGraphNodeA = CastChecked(NodeA); UPCGEditorGraphNodeBase* PCGEdGraphNodeB = CastChecked(NodeB); UPCGNode* PCGNodeA = PCGEdGraphNodeA->GetPCGNode(); UPCGNode* PCGNodeB = PCGEdGraphNodeB->GetPCGNode(); check(PCGNodeA && PCGNodeB); const UPCGPin* PCGPinA = PCGNodeA->GetOutputPin(A->PinName); const UPCGPin* PCGPinB = PCGNodeB->GetInputPin(B->PinName); check(PCGPinA && PCGPinB); if (!PCGPinA->IsCompatible(PCGPinB)) { return false; } UPCGGraph* PCGGraph = PCGNodeA->GetGraph(); check(PCGGraph); // UPCGEditorGraphSchema::TryCreateConnectionInternal is called directly by FDragConnection::DroppedOnPin PCGGraph->PrimeGraphCompilationCache(); // Creates a connection via an intermediate conversion node. auto ConnectViaIntermediate = [this, NodeA, NodeB](UPCGNode* IntermediateNode, UEdGraphPin* A, UEdGraphPin* B) { UEdGraph* Graph = NodeA->GetGraph(); check(Graph); Graph->Modify(); FGraphNodeCreator NodeCreator(*Graph); UPCGEditorGraphNode* ConversionNode = NodeCreator.CreateUserInvokedNode(/*bSelectNewNode=*/false); ConversionNode->Construct(IntermediateNode); // Put the conversion node between A & B but make it stay within a radius of B to keep things tidy. { // Initially place at mid point ConversionNode->NodePosX = (NodeA->NodePosX + NodeB->NodePosX) / 2; ConversionNode->NodePosY = (NodeA->NodePosY + NodeB->NodePosY) / 2; // A hand tweaked distance that keeps it reasonably close. constexpr float MaxDistFromB = 200; const FVector2D OffsetFromB(ConversionNode->NodePosX - NodeB->NodePosX, ConversionNode->NodePosY - NodeB->NodePosY); const float Dist = OffsetFromB.Length(); if (Dist > MaxDistFromB) { const float Scale = MaxDistFromB / Dist; ConversionNode->NodePosX = NodeB->NodePosX + Scale * OffsetFromB.X; ConversionNode->NodePosY = NodeB->NodePosY + Scale * OffsetFromB.Y; } } NodeCreator.Finalize(); IntermediateNode->PositionX = ConversionNode->NodePosX; IntermediateNode->PositionY = ConversionNode->NodePosY; bool bModifiedA = false, bModifiedB = false; UEdGraphPin*const* ConversionInputPin = ConversionNode->GetAllPins().FindByPredicate([](const UEdGraphPin* InPin) { return InPin->Direction == EGPD_Input && InPin->GetFName() == PCGPinConstants::DefaultInputLabel; }); if (ensure(ConversionInputPin && *ConversionInputPin)) { // Last argument: don't allow recursively adding conversion nodes. bModifiedA = TryCreateConnectionInternal(A, *ConversionInputPin, /*bAddConversionNodeIfNeeded=*/false); } // Call GetAllPins() a second time. It's important that we wire up the pins one at a time. Wiring a pin can change dynamic pin types // which can refresh the node, so we must re-query the pins after each connection is made. UEdGraphPin*const* ConversionOutputPin = ConversionNode->GetAllPins().FindByPredicate([](const UEdGraphPin* InPin) { return InPin->Direction == EGPD_Output && (InPin->GetFName() == PCGPinConstants::DefaultOutputLabel || InPin->GetFName() == PCGPinConstants::DefaultInFilterLabel); }); if (ensure(ConversionOutputPin && *ConversionOutputPin)) { // Last argument: don't allow recursively adding conversion nodes. bModifiedB = TryCreateConnectionInternal(*ConversionOutputPin, B, /*bAddConversionNodeIfNeeded=*/false); } return bModifiedA || bModifiedB; }; const EPCGTypeConversion Conversion = bAddConversionNodeIfNeeded ? PCGPinA->GetRequiredTypeConversion(PCGPinB) : EPCGTypeConversion::NoConversionRequired; if (Conversion == EPCGTypeConversion::CollapseToPoint) { UPCGSettings* NodeSettings = nullptr; UPCGNode* ConversionPCGNode = PCGGraph->AddNodeOfType(UPCGCollapseSettings::StaticClass(), NodeSettings); return ConnectViaIntermediate(ConversionPCGNode, A, B); } else if (Conversion == EPCGTypeConversion::Filter) { UPCGSettings* NodeSettings = nullptr; UPCGNode* ConversionPCGNode = PCGGraph->AddNodeOfType(UPCGFilterByTypeSettings::StaticClass(), NodeSettings); FName PinAName = A->GetFName(); FName PinBName = B->GetFName(); // Setup the output pin based on the conversion target type, before new node is finalized. UPCGFilterByTypeSettings* Settings = CastChecked(NodeSettings); Settings->TargetType = PCGPinB->Properties.AllowedTypes; ConversionPCGNode->UpdateAfterSettingsChangeDuringCreation(); // Requery A & B because the update might have changed the pins. UEdGraphPin* const* NewA = NodeA->GetAllPins().FindByPredicate([&PinAName](const UEdGraphPin* InPin) { return InPin->Direction == EGPD_Output && InPin->GetName() == PinAName; }); if (NewA) { A = *NewA; } UEdGraphPin* const* NewB = NodeB->GetAllPins().FindByPredicate([&PinBName](const UEdGraphPin* InPin) { return InPin->Direction == EGPD_Input && InPin->GetName() == PinBName; }); if (NewB) { B = *NewB; } return ConnectViaIntermediate(ConversionPCGNode, A, B); } else if (Conversion == EPCGTypeConversion::MakeConcrete) { UPCGSettings* NodeSettings = nullptr; UPCGNode* ConversionPCGNode = PCGGraph->AddNodeOfType(UPCGMakeConcreteSettings::StaticClass(), NodeSettings); return ConnectViaIntermediate(ConversionPCGNode, A, B); } else if (Conversion == EPCGTypeConversion::SplineToSurface) { UPCGSettings* NodeSettings = nullptr; UPCGNode* ConversionPCGNode = PCGGraph->AddNodeOfType(UPCGCreateSurfaceFromSplineSettings::StaticClass(), NodeSettings); UPCGCreateSurfaceFromSplineSettings* Settings = CastChecked(NodeSettings); Settings->bShouldDrawNodeCompact = true; return ConnectViaIntermediate(ConversionPCGNode, A, B); } else { const bool bModified = Super::TryCreateConnection(InA, InB); if (bModified) { PCGGraph->AddLabeledEdge(PCGNodeA, A->PinName, PCGNodeB, B->PinName); } return bModified; } } void UPCGEditorGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const { const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorBreakPinLinks", "Break Pin Links"), nullptr); Super::BreakPinLinks(TargetPin, bSendsNodeNotification); UEdGraphNode* GraphNode = TargetPin.GetOwningNode(); UPCGEditorGraphNodeBase* PCGGraphNode = CastChecked(GraphNode); UPCGNode* PCGNode = PCGGraphNode->GetPCGNode(); check(PCGNode); UPCGGraph* PCGGraph = PCGNode->GetGraph(); check(PCGGraph); if (TargetPin.Direction == EEdGraphPinDirection::EGPD_Input) { PCGGraph->RemoveInboundEdges(PCGNode, TargetPin.PinName); } else if (TargetPin.Direction == EEdGraphPinDirection::EGPD_Output) { PCGGraph->RemoveOutboundEdges(PCGNode, TargetPin.PinName); } } void UPCGEditorGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const { const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGEditorBreakSinglePinLink", "Break Single Pin Link"), nullptr); Super::BreakSinglePinLink(SourcePin, TargetPin); UEdGraphNode* SourceGraphNode = SourcePin->GetOwningNode(); UEdGraphNode* TargetGraphNode = TargetPin->GetOwningNode(); UPCGEditorGraphNodeBase* SourcePCGGraphNode = CastChecked(SourceGraphNode); UPCGEditorGraphNodeBase* TargetPCGGraphNode = CastChecked(TargetGraphNode); UPCGNode* SourcePCGNode = SourcePCGGraphNode->GetPCGNode(); UPCGNode* TargetPCGNode = TargetPCGGraphNode->GetPCGNode(); check(SourcePCGNode && TargetPCGNode); UPCGGraph* PCGGraph = SourcePCGNode->GetGraph(); PCGGraph->RemoveEdge(SourcePCGNode, SourcePin->PinName, TargetPCGNode, TargetPin->PinName); } void UPCGEditorGraphSchema::GetNativeElementActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { TArray SettingsClasses; for (TObjectIterator It; It; ++It) { UClass* Class = *It; if (Class->IsChildOf(UPCGSettings::StaticClass()) && !Class->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_Hidden)) { SettingsClasses.Add(Class); } } for (UClass* SettingsClass : SettingsClasses) { if (const UPCGSettings* PCGSettings = SettingsClass->GetDefaultObject()) { if (PCGSettings->bExposeToLibrary) { const FText MenuDesc = PCGSettings->GetDefaultNodeTitle(); const FText Category = StaticEnum()->GetDisplayNameTextByValue(static_cast<__underlying_type(EPCGSettingsType)>(PCGSettings->GetType())); const FText Description = PCGSettings->GetNodeTooltipText(); if (!ActionFilter.Accepts(Category)) { continue; } TArray AllPreconfiguredInfo = PCGSettings->GetPreconfiguredInfo(); if (AllPreconfiguredInfo.IsEmpty() || !PCGSettings->OnlyExposePreconfiguredSettings()) { TSharedPtr NewAction(new FPCGEditorGraphSchemaAction_NewNativeElement(Category, MenuDesc, Description, 0)); NewAction->SettingsClass = SettingsClass; ActionMenuBuilder.AddAction(NewAction); // Also add all aliases for (const FText& Alias : PCGSettings->GetNodeTitleAliases()) { TSharedPtr NewAliasAction(new FPCGEditorGraphSchemaAction_NewNativeElement(Category, Alias, Description, 0)); NewAliasAction->SettingsClass = SettingsClass; ActionMenuBuilder.AddAction(NewAliasAction); } } // Also add preconfigured settings const FText NewCategory = PCGSettings->GroupPreconfiguredSettings() ? FText::Format(LOCTEXT("PreconfiguredSettingsCategory", "{0}|{1}"), Category, MenuDesc) : Category; if (!ActionFilter.Accepts(NewCategory)) { continue; } for (FPCGPreConfiguredSettingsInfo PreconfiguredInfo : AllPreconfiguredInfo) { TSharedPtr NewPreconfiguredAction(new FPCGEditorGraphSchemaAction_NewNativeElement(NewCategory, PreconfiguredInfo.Label, PreconfiguredInfo.Tooltip.IsEmpty() ? Description : PreconfiguredInfo.Tooltip,0, PreconfiguredInfo.SearchHints)); NewPreconfiguredAction->SettingsClass = SettingsClass; NewPreconfiguredAction->PreconfiguredInfo = std::move(PreconfiguredInfo); ActionMenuBuilder.AddAction(NewPreconfiguredAction); } } } } const FText UserParameterCategory = LOCTEXT("UserParametersCategoryName", "Graph Parameters"); if (ActionFilter.Accepts(UserParameterCategory)) { const UPCGEditorGraph* Graph = ActionFilter.Graph; const UPCGGraph* PCGGraph = Graph ? const_cast(Graph)->GetPCGGraph() : nullptr; const FInstancedPropertyBag* UserParameters = PCGGraph ? PCGGraph->GetUserParametersStruct() : nullptr; if (const UPropertyBag* BagStruct = UserParameters ? UserParameters->GetPropertyBagStruct() : nullptr) { for (const FPropertyBagPropertyDesc& PropertyDesc : BagStruct->GetPropertyDescs()) { const FText MenuDesc = FText::Format(LOCTEXT("GetterNodeName", "Get {0}"), FText::FromName(PropertyDesc.Name)); const FText Description = FText::Format(LOCTEXT("NodeTooltip", "Get the value from '{0}' parameter, can be overridden by the graph instance."), FText::FromName(PropertyDesc.Name)); TSharedPtr NewAction(new FPCGEditorGraphSchemaAction_NewGetParameterElement(UserParameterCategory, MenuDesc, Description, 0)); NewAction->SettingsClass = UPCGUserParameterGetSettings::StaticClass(); NewAction->PropertyDesc = PropertyDesc; ActionMenuBuilder.AddAction(NewAction); } } } } void UPCGEditorGraphSchema::GetBlueprintElementActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { PCGEditorUtils::ForEachPCGBlueprintAssetData([&ActionMenuBuilder, &ActionFilter](const FAssetData& AssetData) { const bool bExposeToLibrary = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGBlueprintElement, bExposeToLibrary)); const bool bOnlyExposePreconfiguredSettings = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGBlueprintElement, bOnlyExposePreconfiguredSettings)); if (bExposeToLibrary) { const FText MenuDesc = FText::FromString(FName::NameToDisplayString(AssetData.AssetName.ToString(), false)); const FText Category = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGBlueprintElement, Category)); const FText Description = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGBlueprintElement, Description)); if (!ActionFilter.Accepts(Category)) { return true; } const FSoftClassPath GeneratedClass = FSoftClassPath(AssetData.GetTagValueRef(FBlueprintTags::GeneratedClassPath)); // Only load the class if we have enabled preconfigured settings. TArray AllPreconfiguredInfo; if (AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGBlueprintElement, bEnablePreconfiguredSettings))) { TSubclassOf BlueprintClass = GeneratedClass.TryLoadClass(); const UPCGBlueprintElement* BlueprintElement = BlueprintClass ? Cast(BlueprintClass->GetDefaultObject()) : nullptr; AllPreconfiguredInfo = BlueprintElement ? BlueprintElement->PreconfiguredInfo : TArray{}; } if (AllPreconfiguredInfo.IsEmpty() || !bOnlyExposePreconfiguredSettings) { TSharedPtr NewBlueprintAction(new FPCGEditorGraphSchemaAction_NewBlueprintElement(Category, MenuDesc, Description, 0)); NewBlueprintAction->BlueprintClassPath = GeneratedClass; ActionMenuBuilder.AddAction(NewBlueprintAction); } // Also add preconfigured settings const FText NewCategory = FText::Format(LOCTEXT("PreconfiguredSettingsCategory", "{0}|{1}"), Category, MenuDesc); if (!ActionFilter.Accepts(NewCategory)) { return true; } for (FPCGPreConfiguredSettingsInfo PreconfiguredInfo : AllPreconfiguredInfo) { TSharedPtr NewPreconfiguredAction(new FPCGEditorGraphSchemaAction_NewBlueprintElement(NewCategory, PreconfiguredInfo.Label, PreconfiguredInfo.Tooltip.IsEmpty() ? Description : PreconfiguredInfo.Tooltip, 0)); NewPreconfiguredAction->BlueprintClassPath = GeneratedClass; NewPreconfiguredAction->PreconfiguredInfo = std::move(PreconfiguredInfo); ActionMenuBuilder.AddAction(NewPreconfiguredAction); } } return true; }); } void UPCGEditorGraphSchema::GetSettingsElementActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter, bool bIsContextual) const { PCGEditorUtils::ForEachPCGSettingsAssetData([&ActionMenuBuilder, &ActionFilter, bIsContextual](const FAssetData& AssetData) { const bool bExposeToLibrary = AssetData.GetTagValueRef(TEXT("bExposeToLibrary")); if (bExposeToLibrary) { const FText MenuDesc = FText::FromString(FName::NameToDisplayString(AssetData.AssetName.ToString(), false)); const FText Category = AssetData.GetTagValueRef(TEXT("Category")); const FText MenuAndSubCategory = FText::Join(LOCTEXT("MenuDelimiter", "|"), Category, MenuDesc); const FText Description = AssetData.GetTagValueRef(TEXT("Description")); if (!bIsContextual && ActionFilter.Accepts(Category)) { TSharedPtr NewSettingsAction(new FPCGEditorGraphSchemaAction_NewSettingsElement(Category, MenuDesc, Description, 0)); NewSettingsAction->SettingsObjectPath = AssetData.GetSoftObjectPath(); ActionMenuBuilder.AddAction(NewSettingsAction); } else if(bIsContextual && ActionFilter.Accepts(MenuAndSubCategory)) { TSharedPtr NewSettingsActionCopy(new FPCGEditorGraphSchemaAction_NewSettingsElement(MenuAndSubCategory, LOCTEXT("ContextMenuCopySettings", "Copy"), Description, 0)); NewSettingsActionCopy->SettingsObjectPath = AssetData.GetSoftObjectPath(); NewSettingsActionCopy->Behavior = EPCGEditorNewSettingsBehavior::ForceCopy; ActionMenuBuilder.AddAction(NewSettingsActionCopy); TSharedPtr NewSettingsActionInstance(new FPCGEditorGraphSchemaAction_NewSettingsElement(MenuAndSubCategory, LOCTEXT("ContextMenuInstanceSettings", "Instance"), Description, 0)); NewSettingsActionInstance->SettingsObjectPath = AssetData.GetSoftObjectPath(); NewSettingsActionInstance->Behavior = EPCGEditorNewSettingsBehavior::ForceInstance; ActionMenuBuilder.AddAction(NewSettingsActionInstance); } } return true; }); } void UPCGEditorGraphSchema::GetSubgraphElementActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); PCGEditorUtils::ForEachPCGGraphAssetData([&ActionMenuBuilder, &ActionFilter, &AssetRegistryModule](const FAssetData& AssetData) -> bool { const bool bExposeToLibrary = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInterface, bExposeToLibrary)); if (bExposeToLibrary) { // Only exposing the instance if it's parent graph is defined for instances. Otherwise, it is not interesting. // Also march up the hierarchy to find overrides for category and description. // We don't look for override in titles because if we have no override for an instance but the parent has an override we will have 2 times the same entry in the palette. auto GetRecursiveTextsAndIsValid = [&AssetRegistryModule](const FAssetData& InAssetData, FText& OutCategory, FText& OutDescription, auto&& Recurse) { if (InAssetData.IsInstanceOf()) { const FSoftObjectPath ParentGraph(InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInstance, Graph))); if (ParentGraph.IsNull()) { return false; } if (OutCategory.IsEmpty()) { OutCategory = InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInstance, bOverrideCategory)) ? InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInstance, Category)) : FText(); } if (OutDescription.IsEmpty()) { OutDescription = InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInstance, bOverrideDescription)) ? InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInstance, Description)) : FText(); } // Asset data are not big so that should not be that big of a deal, but they are copied all the time. // If we ever have performances issues, might be good to have a cache. const FAssetData ParentAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(ParentGraph); return ParentAssetData.IsValid() ? Recurse(ParentAssetData, OutCategory, OutDescription, Recurse) : false; } else { if (OutCategory.IsEmpty()) { OutCategory = InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraph, Category)); } if (OutDescription.IsEmpty()) { OutDescription = InAssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraph, Description)); } return true; } }; FText Category, Description; if (GetRecursiveTextsAndIsValid(AssetData, Category, Description, GetRecursiveTextsAndIsValid) && ActionFilter.Accepts(Category)) { // As stated above, we either have an override and we take it, or we use the asset name, to differentiate all possible instances of the same graph. const FText MenuDesc = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInterface, bOverrideTitle)) ? AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGGraphInterface, Title)) : FText::FromString(FName::NameToDisplayString(AssetData.AssetName.ToString(), false)); TSharedPtr NewSubgraphAction(new FPCGEditorGraphSchemaAction_NewSubgraphElement(Category, MenuDesc, Description, 0)); NewSubgraphAction->SubgraphObjectPath = AssetData.GetSoftObjectPath(); ActionMenuBuilder.AddAction(NewSubgraphAction); } } return true; }); } void UPCGEditorGraphSchema::GetExtraElementActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { const FText NoCategory; // Comment action const FText CommentMenuDesc = LOCTEXT("PCGAddComment", "Add Comment..."); const FText CommentDescription = LOCTEXT("PCGAddCommentTooltip", "Create a resizable comment box."); const TSharedPtr NewCommentAction(new FPCGEditorGraphSchemaAction_NewComment(NoCategory, CommentMenuDesc, CommentDescription, 0)); ActionMenuBuilder.AddAction(NewCommentAction); // Reroute action const FText RerouteMenuDesc = LOCTEXT("PCGAddRerouteNode", "Add Reroute Node"); const FText RerouteDescription = LOCTEXT("PCGAddRerouteNodeTooltip", "Add a reroute node, aka knot."); const TSharedPtr NewRerouteAction(new FPCGEditorGraphSchemaAction_NewReroute(NoCategory, RerouteMenuDesc, RerouteDescription, 0)); ActionMenuBuilder.AddAction(NewRerouteAction); // Named reroute declaration action const FText NamedRerouteMenuDesc = LOCTEXT("PCGAddNamedRerouteDeclarationNode", "Add Named Reroute Declaration Node..."); const FText NamedRerouteDescription = LOCTEXT("PCGAddNamedRerouteDeclarationNodeTooltip", "Creates a new Named Reroute Declaration from the input."); const TSharedPtr NewNamedRerouteDeclarationAction(new FPCGEditorGraphSchemaAction_NewNamedRerouteDeclaration(NoCategory, NamedRerouteMenuDesc, NamedRerouteDescription, 0)); ActionMenuBuilder.AddAction(NewNamedRerouteDeclarationAction); } void UPCGEditorGraphSchema::GetNamedRerouteUsageActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { const UEdGraph* CurrentGraph = ActionFilter.Graph; const UPCGEditorGraph* Graph = Cast(CurrentGraph); if (!Graph) { return; } const UPCGGraph* PCGGraph = const_cast(Graph)->GetPCGGraph(); if (!PCGGraph) { return; } for (const UPCGNode* Node : PCGGraph->GetNodes()) { if (const UPCGNamedRerouteDeclarationSettings* RerouteDeclaration = Cast(Node->GetSettings())) { static const FText Category = LOCTEXT("NamedRerouteCategory", "Named Reroutes"); const FText Name = FText::FromName(Node->NodeTitle); const FText Tooltip = FText::Format(LOCTEXT("NamedRerouteTooltip", "Add a usage of '{0}' here."), Name); TSharedPtr NewRerouteAction(new FPCGEditorGraphSchemaAction_NewNamedRerouteUsage(Category, Name, Tooltip, 1 /* We want named reroutes to be on top */)); NewRerouteAction->DeclarationNode = Node; ActionMenuBuilder.AddAction(NewRerouteAction); } } } void UPCGEditorGraphSchema::GetDataAssetActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FPCGActionsFilter& ActionFilter) const { PCGEditorUtils::ForEachPCGAssetData([&ActionMenuBuilder, &ActionFilter](const FAssetData& AssetData) { const bool bExposeToLibrary = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, bExposeToLibrary)); if (bExposeToLibrary) { const FText MenuDesc = FText::FromString(FName::NameToDisplayString(AssetData.AssetName.ToString(), false)); const FText Category = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, Category)); const FText Description = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, Description)); const FString AssetName = AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, Name)); if (!ActionFilter.Accepts(Category)) { return true; } const FSoftClassPath SettingsClassPath = FSoftClassPath(AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, SettingsClass))); TSubclassOf SettingsClass = SettingsClassPath.TryLoadClass(); TSharedPtr NewLoadDataAssetAction(new FPCGEditorGraphSchemaAction_NewLoadAssetElement(Category, AssetName.IsEmpty() ? MenuDesc : FText::FromString(AssetName), Description, 0)); NewLoadDataAssetAction->Asset = AssetData; NewLoadDataAssetAction->SettingsClass = SettingsClass; ActionMenuBuilder.AddAction(NewLoadDataAssetAction); } return true; }); } void UPCGEditorGraphSchema::DroppedAssetsOnGraph(const TArray& Assets, const FVector2f& GraphPosition, UEdGraph* Graph) const { FVector2f GraphPositionOffset = GraphPosition; constexpr float PositionOffsetIncrementY = 50.f; UEdGraphPin* NullFromPin = nullptr; TArray SubgraphPaths; TArray SubgraphPositions; TArray SettingsPaths; TArray SettingsGraphPositions; for (const FAssetData& AssetData : Assets) { if(const UClass* AssetClass = AssetData.GetClass()) { if(AssetClass->IsChildOf(UPCGGraphInterface::StaticClass())) { SubgraphPaths.Add(AssetData.GetSoftObjectPath()); SubgraphPositions.Add(FDeprecateSlateVector2D(GraphPositionOffset)); GraphPositionOffset.Y += PositionOffsetIncrementY; } else if (PCGEditorUtils::IsAssetPCGBlueprint(AssetData)) { const FString GeneratedClass = AssetData.GetTagValueRef(FBlueprintTags::GeneratedClassPath); FPCGEditorGraphSchemaAction_NewBlueprintElement NewBlueprintAction; NewBlueprintAction.BlueprintClassPath = FSoftClassPath(GeneratedClass); NewBlueprintAction.PerformAction(Graph, NullFromPin, GraphPositionOffset); GraphPositionOffset.Y += PositionOffsetIncrementY; } else if(AssetClass->IsChildOf(UPCGDataAsset::StaticClass())) { FPCGEditorGraphSchemaAction_NewLoadAssetElement NewLoadAssetAction; NewLoadAssetAction.Asset = AssetData; NewLoadAssetAction.SettingsClass = FSoftClassPath(AssetData.GetTagValueRef(GET_MEMBER_NAME_CHECKED(UPCGDataAsset, SettingsClass))).TryLoadClass(); NewLoadAssetAction.PerformAction(Graph, NullFromPin, GraphPositionOffset); GraphPositionOffset.Y += PositionOffsetIncrementY; } else if(AssetClass->IsChildOf(UPCGSettings::StaticClass())) { // Delay creation so we can open a menu, once, if needed. SettingsPaths.Add(AssetData.GetSoftObjectPath()); SettingsGraphPositions.Add(FDeprecateSlateVector2D(GraphPositionOffset)); GraphPositionOffset.Y += PositionOffsetIncrementY; } } } UPCGEditorGraph* EditorGraph = CastChecked(Graph); TSharedPtr GraphEditor = SGraphEditor::FindGraphEditorForGraph(EditorGraph); const FDeprecateSlateVector2D MouseCursorLocation = FSlateApplication::Get().GetCursorPos(); // If we've dragged settings assets or a graph, we might want to open a menu (ergo this call) if (!SettingsPaths.IsEmpty()) { check(SettingsPaths.Num() == SettingsGraphPositions.Num()); FPCGEditorGraphSchemaAction_NewSettingsElement::MakeSettingsNodesOrContextualMenu(GraphEditor->GetGraphPanel()->AsShared(), MouseCursorLocation, Graph, SettingsPaths, SettingsGraphPositions, /*bSelectNewNodes=*/true); } if (!SubgraphPaths.IsEmpty()) { check(SubgraphPaths.Num() == SubgraphPositions.Num()); FPCGEditorGraphSchemaAction_NewSubgraphElement::MakeGraphNodesOrContextualMenu(GraphEditor->GetGraphPanel()->AsShared(), MouseCursorLocation, Graph, SubgraphPaths, SubgraphPositions, /*bSelectNewNodes=*/true); } } void UPCGEditorGraphSchema::GetAssetsGraphHoverMessage(const TArray& Assets, const UEdGraph* /*HoverGraph*/, FString& OutTooltipText, bool& OutOkIcon) const { for (const FAssetData& AssetData : Assets) { // TODO: get class from asset data, shouldn't require loading if(const UClass* AssetClass = AssetData.GetClass()) { if(AssetClass->IsChildOf(UPCGGraphInterface::StaticClass()) || AssetClass->IsChildOf(UPCGSettings::StaticClass()) || AssetClass->IsChildOf(UPCGDataAsset::StaticClass()) || PCGEditorUtils::IsAssetPCGBlueprint(AssetData)) { OutOkIcon = true; return; } else if(AssetClass->IsChildOf(UBlueprint::StaticClass())) { OutTooltipText = LOCTEXT("PCGEditorDropAssetInvalidBP", "Blueprint does not derive from UPCGBlueprintElement").ToString(); OutOkIcon = false; return; } } } OutTooltipText = LOCTEXT("PCGEditorDropAssetInvalid", "Can't create a node for this asset").ToString(); OutOkIcon = false; } void UPCGEditorGraphSchema::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2f& GraphPosition) const { const FScopedTransaction Transaction(*FPCGEditorCommon::ContextIdentifier, LOCTEXT("PCGCreateRerouteNodeOnWire", "Create Reroute Node"), nullptr); const FVector2f NodeSpacerSize(42.0f, 24.0f); const FVector2f KnotTopLeft = GraphPosition - (NodeSpacerSize * 0.5f); UEdGraph* EditorGraph = PinA->GetOwningNode()->GetGraph(); EditorGraph->Modify(); FPCGEditorGraphSchemaAction_NewReroute Action; if (UPCGEditorGraphNodeReroute* RerouteNode = Cast(Action.PerformAction(EditorGraph, nullptr, KnotTopLeft, /*bSelectNewNode=*/true))) { UEdGraphNode* SourceGraphNode = PinA->GetOwningNode(); UEdGraphNode* TargetGraphNode = PinB->GetOwningNode(); UPCGEditorGraphNodeBase* SourcePCGGraphNode = CastChecked(SourceGraphNode); UPCGEditorGraphNodeBase* TargetPCGGraphNode = CastChecked(TargetGraphNode); // We need to disable full node reconstruction to make sure the pins are valid when creating the connections. SourcePCGGraphNode->EnableDeferredReconstruct(); TargetPCGGraphNode->EnableDeferredReconstruct(); BreakSinglePinLink(PinA, PinB); TryCreateConnection(PinA, (PinA->Direction == EGPD_Output) ? RerouteNode->GetInputPin() : RerouteNode->GetOutputPin()); TryCreateConnection(PinB, (PinB->Direction == EGPD_Output) ? RerouteNode->GetInputPin() : RerouteNode->GetOutputPin()); SourcePCGGraphNode->DisableDeferredReconstruct(); TargetPCGGraphNode->DisableDeferredReconstruct(); } } const FSlateBrush* UPCGEditorGraphSchema::GetMetadataTypeSlateBrush(const EPCGContainerType ContainerType) const { switch (ContainerType) { case EPCGContainerType::Array: return FAppStyle::GetBrush(TEXT("Kismet.VariableList.ArrayTypeIcon")); case EPCGContainerType::Set: return FAppStyle::GetBrush(TEXT("Kismet.VariableList.SetTypeIcon")); // @todo_pcg: Enable after Map support finalizes // case EPCGContainerType::Map: // return FAppStyle::GetBrush(TEXT("Kismet.VariableList.MapKeyTypeIcon")); default: // fall-through case EPCGContainerType::Element: return FAppStyle::GetBrush(TEXT("Kismet.VariableList.TypeIcon")); } } FLinearColor UPCGEditorGraphSchema::GetMetadataTypeColor(const EPCGMetadataTypes Type) const { const UGraphEditorSettings* GraphSettings = GetDefault(); check(GraphSettings) switch (Type) { case EPCGMetadataTypes::Float: return GraphSettings->FloatPinTypeColor; case EPCGMetadataTypes::Double: return GraphSettings->DoublePinTypeColor; case EPCGMetadataTypes::Integer32: return GraphSettings->IntPinTypeColor; case EPCGMetadataTypes::Integer64: return GraphSettings->Int64PinTypeColor; case EPCGMetadataTypes::Vector2: // fall-through case EPCGMetadataTypes::Vector: // fall-through case EPCGMetadataTypes::Vector4: // fall-through return GraphSettings->VectorPinTypeColor; case EPCGMetadataTypes::Quaternion: return GraphSettings->RotatorPinTypeColor; case EPCGMetadataTypes::Transform: return GraphSettings->TransformPinTypeColor; case EPCGMetadataTypes::String: return GraphSettings->StringPinTypeColor; case EPCGMetadataTypes::Boolean: return GraphSettings->BooleanPinTypeColor; case EPCGMetadataTypes::Rotator: return GraphSettings->RotatorPinTypeColor; case EPCGMetadataTypes::Name: return GraphSettings->NamePinTypeColor; case EPCGMetadataTypes::SoftObjectPath: return GraphSettings->SoftObjectPinTypeColor; case EPCGMetadataTypes::SoftClassPath: return GraphSettings->SoftClassPinTypeColor; default: // fall-through case EPCGMetadataTypes::Unknown: ensure(false); return FLinearColor::White; } } const FSlateBrush* UPCGEditorGraphSchema::GetPropertyBagTypeSlateBrush(EPropertyBagContainerType ContainerType) const { switch (ContainerType) { case EPropertyBagContainerType::Array: return FAppStyle::GetBrush(TEXT("Kismet.VariableList.ArrayTypeIcon")); case EPropertyBagContainerType::Set: return FAppStyle::GetBrush(TEXT("Kismet.VariableList.SetTypeIcon")); // @todo_pcg: Enable after Map support finalizes // case EPropertyBagContainerType::Map: // return FAppStyle::GetBrush(TEXT("Kismet.VariableList.MapKeyTypeIcon")); default: // fall-through return FAppStyle::GetBrush(TEXT("Kismet.VariableList.TypeIcon")); } } FLinearColor UPCGEditorGraphSchema::GetPropertyBagTypeColor(const FPropertyBagPropertyDesc& Desc) const { const UGraphEditorSettings* GraphSettings = GetDefault(); check(GraphSettings) // For struct, extract the common structs if (Desc.ValueType == EPropertyBagPropertyType::Struct && Desc.ValueTypeObject) { if (Desc.ValueTypeObject == TBaseStructure::Get() || Desc.ValueTypeObject == TBaseStructure::Get() || Desc.ValueTypeObject == TBaseStructure::Get()) { return GraphSettings->VectorPinTypeColor; } else if (Desc.ValueTypeObject == TBaseStructure::Get() || Desc.ValueTypeObject == TBaseStructure::Get()) { return GraphSettings->RotatorPinTypeColor; } else if (Desc.ValueTypeObject == TBaseStructure::Get()) { return GraphSettings->TransformPinTypeColor; } } switch (Desc.ValueType) { case EPropertyBagPropertyType::Bool: return GraphSettings->BooleanPinTypeColor; case EPropertyBagPropertyType::Byte: return GraphSettings->BytePinTypeColor; case EPropertyBagPropertyType::Int32: return GraphSettings->IntPinTypeColor; case EPropertyBagPropertyType::Int64: return GraphSettings->Int64PinTypeColor; case EPropertyBagPropertyType::Float: return GraphSettings->FloatPinTypeColor; case EPropertyBagPropertyType::Double: return GraphSettings->DoublePinTypeColor; case EPropertyBagPropertyType::Name: return GraphSettings->NamePinTypeColor; case EPropertyBagPropertyType::String: return GraphSettings->StringPinTypeColor; case EPropertyBagPropertyType::Text: return GraphSettings->TextPinTypeColor; case EPropertyBagPropertyType::Enum: return GraphSettings->ObjectPinTypeColor; case EPropertyBagPropertyType::Struct: return GraphSettings->StructPinTypeColor; case EPropertyBagPropertyType::Object: return GraphSettings->ObjectPinTypeColor; case EPropertyBagPropertyType::SoftObject: return GraphSettings->SoftObjectPinTypeColor; case EPropertyBagPropertyType::Class: return GraphSettings->ClassPinTypeColor; case EPropertyBagPropertyType::SoftClass: return GraphSettings->SoftClassPinTypeColor; default: break; } return FLinearColor::White; } FPCGEditorConnectionDrawingPolicy::FPCGEditorConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraph) : FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements) , Graph(CastChecked(InGraph)) { ArrowImage = nullptr; ArrowRadius = FVector2D::ZeroVector; } bool FPCGEditorConnectionDrawingPolicy::UpdateParamsIfDebugging(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, FConnectionParams& Params) { check(OutputPin && InputPin); // Early validation const UPCGEditorGraphNodeBase* UpstreamEditorNode = CastChecked(OutputPin->GetOwningNode()); if (!UpstreamEditorNode || !UpstreamEditorNode->GetPCGNode()) { return false; } const UPCGNode* NodeToInspect = nullptr; const UPCGPin* PinToInspect = nullptr; // Walk up the graph if the current node is a reroute node because there is no associated inspection data. PCGEditorGraphUtils::GetInspectablePin(UpstreamEditorNode->GetPCGNode(), UpstreamEditorNode->GetPCGNode()->GetOutputPin(OutputPin->GetFName()), NodeToInspect, PinToInspect); if (!NodeToInspect || !PinToInspect) { return false; } // Early out if we aren't in debug mode if (!Graph || !Graph->GetEditor().IsValid()) { return false; } const FPCGEditor* Editor = Graph->GetEditor().Pin().Get(); const FPCGStack* PCGStack = Editor ? Editor->GetStackBeingInspected() : nullptr; UPCGComponent* PCGComponent = Editor ? Editor->GetPCGComponentBeingInspected() : nullptr; if (!Editor || !PCGStack || !PCGComponent || !PCGComponent->GetExecutionState().GetInspection().IsInspecting()) { return false; } FPCGStack Stack = *PCGStack; TArray& StackFrames = Stack.GetStackFramesMutable(); StackFrames.Reserve(StackFrames.Num() + 2); StackFrames.Emplace(NodeToInspect); StackFrames.Emplace(PinToInspect); if (const FPCGDataCollection* DataCollection = PCGComponent->GetExecutionState().GetInspection().GetInspectionData(Stack)) { if (DataCollection->TaggedData.Num() > 1) { Params.WireThickness *= GetDefault()->MultiDataEdgeDebugEmphasis; } } else { Params.WireColor = Params.WireColor.Desaturate(GetDefault()->EmptyEdgeDebugDesaturateFactor); } return true; } void FPCGEditorConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) { FConnectionDrawingPolicy::DetermineWiringStyle(OutputPin, InputPin, Params); Params.WireThickness = GetDefault()->DefaultWireThickness; // Emphasize wire thickness on hovered pins if (HoveredPins.Contains(InputPin) && HoveredPins.Contains(OutputPin)) { Params.WireThickness *= GetDefault()->HoverEdgeEmphasis; } // Base the color of the wire on the color of the output pin if (OutputPin) { Params.WireColor = GetDefault()->GetPinColor(OutputPin->PinType); } // Desaturate connection if downstream node is disabled or if the data on this wire won't be used if (InputPin && OutputPin) { // Try to apply debugging/dynamic visualization - if it fails, fall back to static visualization if (!UpdateParamsIfDebugging(OutputPin, InputPin, Params)) { const UPCGEditorGraphNodeBase* EditorNode = CastChecked(InputPin->GetOwningNode()); const UPCGNode* PCGNode = EditorNode ? EditorNode->GetPCGNode() : nullptr; const UPCGPin* PCGPin = PCGNode ? PCGNode->GetInputPin(InputPin->GetFName()) : nullptr; const UPCGEditorGraphNodeBase* UpstreamEditorNode = CastChecked(OutputPin->GetOwningNode()); const UPCGEditorGraphNodeBase* DownstreamEditorNode = CastChecked(InputPin->GetOwningNode()); if (PCGPin && UpstreamEditorNode && DownstreamEditorNode) { const bool bDownstreamNodeForceDisabled = DownstreamEditorNode->IsDisplayAsDisabledForced(); // Look for the PCG edge that correlates with passed in (OutputPin, InputPin) edge const TObjectPtr* PCGEdge = PCGPin->Edges.FindByPredicate([UpstreamEditorNode, OutputPin](const UPCGEdge* ConnectedPCGEdge) { return UpstreamEditorNode->GetPCGNode() == ConnectedPCGEdge->InputPin->Node && ConnectedPCGEdge->InputPin->Properties.Label == OutputPin->GetFName(); }); const bool bDownstreamNodeDoesNotUseData = PCGEdge && !PCGNode->IsEdgeUsedByNodeExecution(*PCGEdge); // If edge found and is not used, gray it out if (bDownstreamNodeForceDisabled || bDownstreamNodeDoesNotUseData) { Params.WireColor = Params.WireColor.Desaturate(GetDefault()->EmptyEdgeDebugDesaturateFactor); } } } } } #undef LOCTEXT_NAMESPACE