// Copyright Epic Games, Inc. All Rights Reserved. #include "KismetNodes/SGraphNodeK2CreateDelegate.h" #include "BlueprintEventNodeSpawner.h" #include "BlueprintNodeBinder.h" #include "Containers/UnrealString.h" #include "CoreTypes.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "K2Node_CustomEvent.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "KismetCompilerMisc.h" #include "K2Node.h" #include "K2Node_CreateDelegate.h" #include "Layout/Margin.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "ScopedTransaction.h" #include "SSearchableComboBox.h" #include "SlotBase.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" class SWidget; namespace UE::SGraphNodeK2::Private { bool MatchEventGraphPredicate(const TObjectPtr& Entry) { check(Entry); return Entry->GetFName() == UEdGraphSchema_K2::GN_EventGraph; }; } FText SGraphNodeK2CreateDelegate::FunctionDescription(const UFunction* Function, const bool bOnlyDescribeSignature /*= false*/, const int32 CharacterLimit /*= 32*/) { if (!Function || !Function->GetOuter()) { return NSLOCTEXT("GraphNodeK2Create", "Error", "Error"); } const UEdGraphSchema_K2* K2Schema = GetDefault(); FString Result; // Show function name. if (!bOnlyDescribeSignature) { Result = Function->GetName(); } Result += TEXT("("); // Describe input parameters. { bool bFirst = true; for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* const Param = *PropIt; const bool bIsFunctionInput = Param && (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)); if (bIsFunctionInput) { if (!bFirst) { Result += TEXT(", "); } if (CharacterLimit > INDEX_NONE && Result.Len() > CharacterLimit) { Result += TEXT("..."); break; } Result += bOnlyDescribeSignature ? UEdGraphSchema_K2::TypeToText(Param).ToString() : Param->GetName(); bFirst = false; } } } Result += TEXT(")"); // Describe outputs. { TArray Outputs; FProperty* const FunctionReturnProperty = Function->GetReturnProperty(); if (FunctionReturnProperty) { Outputs.Add(UEdGraphSchema_K2::TypeToText(FunctionReturnProperty).ToString()); } for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* const Param = *PropIt; const bool bIsFunctionOutput = Param && Param->HasAnyPropertyFlags(CPF_OutParm); if (bIsFunctionOutput) { Outputs.Add(bOnlyDescribeSignature ? UEdGraphSchema_K2::TypeToText(Param).ToString() : Param->GetName()); } } if (Outputs.Num() > 0) { Result += TEXT(" -> "); } if (Outputs.Num() > 1) { Result += TEXT("["); } bool bFirst = true; for (const FString& Output : Outputs) { if (!bFirst) { Result += TEXT(", "); } if (CharacterLimit > INDEX_NONE && Result.Len() > CharacterLimit) { Result += TEXT("..."); break; } Result += Output; bFirst = false; } if (Outputs.Num() > 1) { Result += TEXT("]"); } } return FText::FromString(Result); } void SGraphNodeK2CreateDelegate::Construct(const FArguments& InArgs, UK2Node* InNode) { GraphNode = InNode; UpdateGraphNode(); } FText SGraphNodeK2CreateDelegate::GetCurrentFunctionDescription() const { UK2Node_CreateDelegate* Node = Cast(GraphNode); UFunction* FunctionSignature = Node ? Node->GetDelegateSignature() : nullptr; UClass* ScopeClass = Node ? Node->GetScopeClass() : nullptr; if (!FunctionSignature || !ScopeClass) { return FText::GetEmpty(); } if (const UFunction* Func = FindUField(ScopeClass, Node->GetFunctionName())) { return FunctionDescription(Func); } if (Node->GetFunctionName() != NAME_None) { return FText::Format(NSLOCTEXT("GraphNodeK2Create", "ErrorLabelFmt", "Error? {0}"), FText::FromName(Node->GetFunctionName())); } return NSLOCTEXT("GraphNodeK2Create", "SelectFunctionLabel", "Select Function..."); } void SGraphNodeK2CreateDelegate::OnFunctionSelected(TSharedPtr FunctionItemData, ESelectInfo::Type SelectInfo) { const FScopedTransaction Transaction(NSLOCTEXT("GraphNodeK2Create", "CreateMatchingSignature", "Create matching signature")); if (FunctionItemData.IsValid()) { if (UK2Node_CreateDelegate* Node = Cast(GraphNode)) { UBlueprint* NodeBP = Node->GetBlueprint(); UEdGraph* const SourceGraph = Node->GetGraph(); check(NodeBP && SourceGraph); SourceGraph->Modify(); NodeBP->Modify(); Node->Modify(); if (FunctionItemData == CreateMatchingFunctionData) { // Get a valid name for the function graph FString ProposedFuncName = NodeBP->GetName() + "_AutoGenFunc"; FName NewFuncName = FBlueprintEditorUtils::GenerateUniqueGraphName(NodeBP, ProposedFuncName); UEdGraph* NewGraph = nullptr; NewGraph = FBlueprintEditorUtils::CreateNewGraph(NodeBP, NewFuncName, SourceGraph->GetClass(), SourceGraph->GetSchema() ? SourceGraph->GetSchema()->GetClass() : GetDefault()->GetClass()); if (NewGraph != nullptr) { FBlueprintEditorUtils::AddFunctionGraph(NodeBP, NewGraph, true, Node->GetDelegateSignature()); FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NewGraph); } Node->SetFunction(NewFuncName); } else if (FunctionItemData == CreateMatchingEventData) { TObjectPtr* EventGraphPtr = NodeBP->UbergraphPages.FindByPredicate(UE::SGraphNodeK2::Private::MatchEventGraphPredicate); check(EventGraphPtr); UEdGraph* EventGraph = *EventGraphPtr; check(EventGraph); FName NewEventName = FBlueprintEditorUtils::FindUniqueCustomEventName(NodeBP); UBlueprintEventNodeSpawner* Spawner = UBlueprintEventNodeSpawner::Create(UK2Node_CustomEvent::StaticClass(), NewEventName); FVector2f SpawnPos = EventGraph->GetGoodPlaceForNewNode(); UEdGraphNode* NewNode = Spawner->Invoke(EventGraph, IBlueprintNodeBinder::FBindingSet(), FDeprecateSlateVector2D(SpawnPos)); UK2Node_CustomEvent* NewEventNode = CastChecked(NewNode); NewEventNode->SetDelegateSignature(Node->GetDelegateSignature()); // Reconstruct to get the new parameters to show in the editor NewEventNode->ReconstructNode(); NewEventNode->bIsEditable = true; FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NewEventNode); Node->SetFunction(NewEventName); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(NodeBP); } else { FName FuncName( **FunctionItemData.Get()); Node->SetFunction(FuncName); } Node->HandleAnyChange(true); TSharedPtr SelectFunctionWidgetPtr = FunctionOptionComboBox.Pin(); if (SelectFunctionWidgetPtr.IsValid()) { SelectFunctionWidgetPtr->SetIsOpen(false); } } } } TSharedPtr SGraphNodeK2CreateDelegate::AddDefaultFunctionDataOption(const FText& DisplayName) { TSharedPtr Res = MakeShareable(new FString(DisplayName.ToString())); FunctionOptionList.Add(Res); return Res; } void SGraphNodeK2CreateDelegate::CreateBelowPinControls(TSharedPtr MainBox) { if (UK2Node_CreateDelegate* Node = Cast(GraphNode)) { const UBlueprint* NodeBP = Node->GetBlueprint(); check(NodeBP); UFunction* FunctionSignature = Node->GetDelegateSignature(); UClass* ScopeClass = Node->GetScopeClass(); if (FunctionSignature && ScopeClass) { FText FunctionSignaturePrompt; { FFormatNamedArguments FormatArguments; FormatArguments.Add(TEXT("FunctionSignature"), FunctionDescription(FunctionSignature, true)); FunctionSignaturePrompt = FText::Format(NSLOCTEXT("GraphNodeK2Create", "FunctionSignaturePrompt", "Signature: {FunctionSignature}"), FormatArguments); } FText FunctionSignatureToolTipText; { FFormatNamedArguments FormatArguments; FormatArguments.Add(TEXT("FullFunctionSignature"), FunctionDescription(FunctionSignature, true, INDEX_NONE)); FunctionSignatureToolTipText = FText::Format(NSLOCTEXT("GraphNodeK2Create", "FunctionSignatureToolTip", "Signature Syntax: (Inputs) -> [Outputs]\nFull Signature:{FullFunctionSignature}"), FormatArguments); } MainBox->AddSlot() .AutoHeight() .VAlign(VAlign_Fill) .Padding(4.0f) [ SNew(STextBlock) .Text(FunctionSignaturePrompt) .ToolTipText(FunctionSignatureToolTipText) ]; FunctionOptionList.Empty(); // add an empty row, so the user can clear the selection if they want AddDefaultFunctionDataOption(NSLOCTEXT("GraphNodeK2Create", "EmptyFunctionOption", "[None]")); // Option to create a function based on the event parameters CreateMatchingFunctionData = AddDefaultFunctionDataOption(NSLOCTEXT("GraphNodeK2Create", "CreateMatchingFunctionOption", "[Create a matching function]")); // Only signatures with no output parameters can be events // There must also be an event graph to add the event to const bool bCanCreateEvent = !UEdGraphSchema_K2::HasFunctionAnyOutputParameter(FunctionSignature) && NodeBP->UbergraphPages.ContainsByPredicate(UE::SGraphNodeK2::Private::MatchEventGraphPredicate) ; if (bCanCreateEvent) { CreateMatchingEventData = AddDefaultFunctionDataOption(NSLOCTEXT("GraphNodeK2Create", "CreateMatchingEventOption", "[Create a matching event]")); } struct FFunctionItemData { FName Name; FText Description; }; TArray ClassFunctions; for (TFieldIterator It(ScopeClass); It; ++It) { UFunction* Func = *It; if (Func && (FKismetCompilerUtilities::DoSignaturesHaveConvertibleFloatTypes(Func, FunctionSignature) != ConvertibleSignatureMatchResult::Different) && UEdGraphSchema_K2::FunctionCanBeUsedInDelegate(Func)) { FFunctionItemData ItemData; ItemData.Name = Func->GetFName(); ItemData.Description = FunctionDescription(Func); ClassFunctions.Emplace(MoveTemp(ItemData)); } } ClassFunctions.Sort([](const FFunctionItemData& A, const FFunctionItemData& B) { return A.Description.CompareTo(B.Description) < 0; }); for (const FFunctionItemData& ItemData : ClassFunctions) { // Add this to the searchable text box as an FString so users can type and find it FunctionOptionList.Add(MakeShareable(new FString(ItemData.Name.ToString()))); } TSharedRef SelectFunctionWidgetRef = SNew(SSearchableComboBox) .OptionsSource(&FunctionOptionList) .OnGenerateWidget(this, &SGraphNodeK2CreateDelegate::MakeFunctionOptionComboWidget) .OnSelectionChanged(this, &SGraphNodeK2CreateDelegate::OnFunctionSelected) .ContentPadding(2.0f) .MaxListHeight(200.0f) .Content() [ SNew(STextBlock) .Text(GetCurrentFunctionDescription()) ]; MainBox->AddSlot() .AutoHeight() .VAlign(VAlign_Fill) .Padding(4.0f) [ SelectFunctionWidgetRef ]; FunctionOptionComboBox = SelectFunctionWidgetRef; } } } TSharedRef SGraphNodeK2CreateDelegate::MakeFunctionOptionComboWidget(TSharedPtr InItem) { return SNew(STextBlock).Text(FText::FromString(*InItem)); } SGraphNodeK2CreateDelegate::~SGraphNodeK2CreateDelegate() { TSharedPtr SelectFunctionWidgetPtr = FunctionOptionComboBox.Pin(); if (SelectFunctionWidgetPtr.IsValid()) { SelectFunctionWidgetPtr->SetIsOpen(false); } }