// Copyright 2022 Just2Devs. All Rights Reserved. #include "K2Node_WidgetConstructObject.h" #include "UObject/UnrealType.h" #include "EdGraphSchema_K2.h" #include "Kismet2/BlueprintEditorUtils.h" #include "BlueprintNodeSpawner.h" #include "BlueprintActionDatabaseRegistrar.h" #include "FindInBlueprintManager.h" #include "K2Node_CallFunction.h" #include "K2Node_WidgetNodeHelper.h" #include "KismetCompiler.h" #include "KismetCompilerMisc.h" #include "Components/Widget.h" #include "Kismet/GameplayStatics.h" #include "Runtime/Launch/Resources/Version.h" struct FK2Node_WidgetConstructObjectHelper { static FName WorldContextPinName; static FName ClassPinName; static FName OuterPinName; }; FName FK2Node_WidgetConstructObjectHelper::WorldContextPinName(TEXT("WorldContextObject")); FName FK2Node_WidgetConstructObjectHelper::ClassPinName(TEXT("Class")); FName FK2Node_WidgetConstructObjectHelper::OuterPinName(TEXT("Outer")); #define LOCTEXT_NAMESPACE "K2Node_WidgetConstructObject" UK2Node_WidgetConstructObject::UK2Node_WidgetConstructObject(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeTooltip = LOCTEXT("K2Node_WidgetConstructObject_NodeTooltip", "Attempts to spawn a new object"); } UClass* UK2Node_WidgetConstructObject::GetClassPinBaseClass() const { return UWidget::StaticClass(); } #if WITH_EDITOR void UK2Node_WidgetConstructObject::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if(PropertyChangedEvent.Property) { if(PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UK2Node_WidgetConstructObject, PinsEnabled)) { for(int32 i = 0; i < PinsNames.Num(); i++) { if(PinsEnabled.IsValidIndex(i) && PinsEnabled[i]) { for(UEdGraphPin* Pin : Pins) { if(Pin->GetFName().IsEqual(PinsNames[i])) { Pin->bHidden = false; } } } else { for(UEdGraphPin* Pin : Pins) { if(Pin->GetFName().IsEqual(PinsNames[i])) { Pin->bHidden = true; Pin->BreakAllPinLinks(); Pin->ResetDefaultValue(); } } } } GetGraph()->NotifyGraphChanged(); } } } #endif void UK2Node_WidgetConstructObject::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); UK2Node_CallFunction* CallCreateNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallCreateNode->FunctionReference.SetExternalMember(GET_FUNCTION_NAME_CHECKED(UGameplayStatics, SpawnObject), UGameplayStatics::StaticClass()); CallCreateNode->AllocateDefaultPins(); // store off the class to spawn before we mutate pin connections: UClass* ClassToSpawn = GetClassToSpawn(); bool bSucceeded = true; //connect exe { UEdGraphPin* SpawnExecPin = GetExecPin(); UEdGraphPin* CallExecPin = CallCreateNode->GetExecPin(); bSucceeded &= SpawnExecPin && CallExecPin && CompilerContext.MovePinLinksToIntermediate(*SpawnExecPin, *CallExecPin).CanSafeConnect(); } //connect class { UEdGraphPin* SpawnClassPin = GetClassPin(); UEdGraphPin* CallClassPin = CallCreateNode->FindPin(TEXT("ObjectClass")); bSucceeded &= SpawnClassPin && CallClassPin && CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallClassPin).CanSafeConnect(); } //connect outer { UEdGraphPin* SpawnOuterPin = GetOuterPin(); UEdGraphPin* CallOuterPin = CallCreateNode->FindPin(TEXT("Outer")); bSucceeded &= SpawnOuterPin && CallOuterPin && CompilerContext.MovePinLinksToIntermediate(*SpawnOuterPin, *CallOuterPin).CanSafeConnect(); } UEdGraphPin* CallResultPin = nullptr; //connect result { UEdGraphPin* SpawnResultPin = GetResultPin(); CallResultPin = CallCreateNode->GetReturnValuePin(); // cast HACK. It should be safe. The only problem is native code generation. if (SpawnResultPin && CallResultPin) { CallResultPin->PinType = SpawnResultPin->PinType; } bSucceeded &= SpawnResultPin && CallResultPin && CompilerContext.MovePinLinksToIntermediate(*SpawnResultPin, *CallResultPin).CanSafeConnect(); } //assign exposed values and connect then { UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallCreateNode, this, CallResultPin, ClassToSpawn); UEdGraphPin* SpawnNodeThen = GetThenPin(); bSucceeded &= SpawnNodeThen && LastThen && CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *LastThen).CanSafeConnect(); } BreakAllNodeLinks(); if (!bSucceeded) { CompilerContext.MessageLog.Error(*LOCTEXT("GenericCreateObject_Error", "ICE: GenericCreateObject error @@").ToString(), this); } } void UK2Node_WidgetConstructObject::AllocateDefaultPins() { // Add execution pins CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); // If required add the world context pin if (UseWorldContext()) { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UObject::StaticClass(), FK2Node_WidgetConstructObjectHelper::WorldContextPinName); } // Add blueprint pin UEdGraphPin* ClassPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Class, GetClassPinBaseClass(), FK2Node_WidgetConstructObjectHelper::ClassPinName); // Result pin UEdGraphPin* ResultPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, GetClassPinBaseClass(), UEdGraphSchema_K2::PN_ReturnValue); if (UseOuter()) { UEdGraphPin* OuterPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UObject::StaticClass(), FK2Node_WidgetConstructObjectHelper::OuterPinName); } Super::AllocateDefaultPins(); } UEdGraphPin* UK2Node_WidgetConstructObject::GetOuterPin() const { UEdGraphPin* Pin = FindPin(FK2Node_WidgetConstructObjectHelper::OuterPinName); ensure(nullptr == Pin || Pin->Direction == EGPD_Input); return Pin; } void UK2Node_WidgetConstructObject::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const { MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString(); UEdGraphSchema_K2 const* const K2Schema = Cast(GetSchema()); if (K2Schema != nullptr) { MutatablePin.PinToolTip += TEXT(" "); MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString(); } MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString(); } void UK2Node_WidgetConstructObject::CreatePinsForClass(UClass* InClass, TArray* OutClassPins) { check(InClass); const UEdGraphSchema_K2* K2Schema = GetDefault(); const UObject* const ClassDefaultObject = InClass->GetDefaultObject(false); PinsNames.Empty(); for (TFieldIterator PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; const bool bIsDelegate = Property->IsA(FMulticastDelegateProperty::StaticClass()); const bool bIsSettableExternally = !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance); if( !Property->HasAnyPropertyFlags(CPF_Parm) && bIsSettableExternally && Property->HasAllPropertyFlags(CPF_Edit) && !bIsDelegate && (nullptr == FindPin(Property->GetFName()) ) && FBlueprintEditorUtils::PropertyStillExists(Property)) { if (UEdGraphPin* Pin = CreatePin(EGPD_Input, NAME_None, Property->GetFName())) { K2Schema->ConvertPropertyToPinType(Property, Pin->PinType); Pin->bHidden = true; PinsNames.Add(Pin->GetFName()); if(PinsEnabled.IsValidIndex(PinsNames.Num()-1) && PinsEnabled[PinsNames.Num()-1]) Pin->bHidden = false; if (OutClassPins) { OutClassPins->Add(Pin); } if (ClassDefaultObject && K2Schema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueAsString; const bool bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(Property, reinterpret_cast(ClassDefaultObject), DefaultValueAsString, this); check(bDefaultValueSet); K2Schema->SetPinAutogeneratedDefaultValue(Pin, DefaultValueAsString); } // Copy tooltip from the property. K2Schema->ConstructBasicPinTooltip(*Pin, Property->GetToolTipText(), Pin->PinToolTip); } } } #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION > 4 PinsEnabled.SetNum(PinsNames.Num(), EAllowShrinking::Yes); #else PinsEnabled.SetNum(PinsNames.Num(), true); #endif // Change class of output pin UEdGraphPin* ResultPin = GetResultPin(); ResultPin->PinType.PinSubCategoryObject = InClass->GetAuthoritativeClass(); } UClass* UK2Node_WidgetConstructObject::GetClassToSpawn(const TArray* InPinsToSearch /*=NULL*/) const { UClass* UseSpawnClass = nullptr; const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; UEdGraphPin* ClassPin = GetClassPin(PinsToSearch); if (ClassPin && ClassPin->DefaultObject && ClassPin->LinkedTo.Num() == 0) { UseSpawnClass = CastChecked(ClassPin->DefaultObject); } else if (ClassPin && ClassPin->LinkedTo.Num()) { UEdGraphPin* ClassSource = ClassPin->LinkedTo[0]; UseSpawnClass = ClassSource ? Cast(ClassSource->PinType.PinSubCategoryObject.Get()) : nullptr; } return UseSpawnClass; } void UK2Node_WidgetConstructObject::ReallocatePinsDuringReconstruction(TArray& OldPins) { AllocateDefaultPins(); if (UClass* UseSpawnClass = GetClassToSpawn(&OldPins)) { CreatePinsForClass(UseSpawnClass); } RestoreSplitPins(OldPins); } void UK2Node_WidgetConstructObject::PostPlacedNewNode() { Super::PostPlacedNewNode(); if (UClass* UseSpawnClass = GetClassToSpawn()) { CreatePinsForClass(UseSpawnClass); } } void UK2Node_WidgetConstructObject::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const { Super::AddSearchMetaDataInfo(OutTaggedMetaData); OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_NativeName, CachedNodeTitle.GetCachedText())); } bool UK2Node_WidgetConstructObject::IsSpawnVarPin(UEdGraphPin* Pin) const { return( Pin->PinName != UEdGraphSchema_K2::PN_Execute && Pin->PinName != UEdGraphSchema_K2::PN_Then && Pin->PinName != UEdGraphSchema_K2::PN_ReturnValue && Pin->PinName != FK2Node_WidgetConstructObjectHelper::ClassPinName && Pin->PinName != FK2Node_WidgetConstructObjectHelper::WorldContextPinName && Pin->PinName != FK2Node_WidgetConstructObjectHelper::OuterPinName); } void UK2Node_WidgetConstructObject::OnClassPinChanged() { // Remove all pins related to archetype variables TArray OldPins = Pins; TArray OldClassPins; for (UEdGraphPin* OldPin : OldPins) { if (IsSpawnVarPin(OldPin)) { Pins.Remove(OldPin); OldClassPins.Add(OldPin); } } CachedNodeTitle.MarkDirty(); PinsEnabled.Empty(); PinsNames.Empty(); TArray NewClassPins; if (UClass* UseSpawnClass = GetClassToSpawn()) { CreatePinsForClass(UseSpawnClass, &NewClassPins); } RestoreSplitPins(OldPins); UEdGraphPin* ResultPin = GetResultPin(); // Cache all the pin connections to the ResultPin, we will attempt to recreate them TArray ResultPinConnectionList = ResultPin->LinkedTo; // Because the archetype has changed, we break the output link as the output pin type will change ResultPin->BreakAllPinLinks(true); const UEdGraphSchema_K2* K2Schema = GetDefault(); // Recreate any pin links to the Result pin that are still valid for (UEdGraphPin* Connections : ResultPinConnectionList) { K2Schema->TryCreateConnection(ResultPin, Connections); } // Rewire the old pins to the new pins so connections are maintained if possible RewireOldPinsToNewPins(OldClassPins, Pins, nullptr); // Refresh the UI for the graph so the pin changes show up GetGraph()->NotifyGraphChanged(); // Mark dirty FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); } void UK2Node_WidgetConstructObject::PinConnectionListChanged(UEdGraphPin* Pin) { Super::PinConnectionListChanged(Pin); if (Pin && (Pin->PinName == FK2Node_WidgetConstructObjectHelper::ClassPinName)) { OnClassPinChanged(); } } void UK2Node_WidgetConstructObject::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const { if (UEdGraphPin* ClassPin = GetClassPin()) { SetPinToolTip(*ClassPin, LOCTEXT("ClassPinDescription", "The object class you want to construct")); } if (UEdGraphPin* ResultPin = GetResultPin()) { SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "The constructed object")); } if (UEdGraphPin* OuterPin = (UseOuter() ? GetOuterPin() : nullptr)) { SetPinToolTip(*OuterPin, LOCTEXT("OuterPinDescription", "Owner of the constructed object")); } return Super::GetPinHoverText(Pin, HoverTextOut); } void UK2Node_WidgetConstructObject::PinDefaultValueChanged(UEdGraphPin* ChangedPin) { if (ChangedPin && (ChangedPin->PinName == FK2Node_WidgetConstructObjectHelper::ClassPinName)) { OnClassPinChanged(); } } FText UK2Node_WidgetConstructObject::GetTooltipText() const { return NodeTooltip; } UEdGraphPin* UK2Node_WidgetConstructObject::GetThenPin()const { UEdGraphPin* Pin = FindPinChecked(UEdGraphSchema_K2::PN_Then); check(Pin->Direction == EGPD_Output); return Pin; } UEdGraphPin* UK2Node_WidgetConstructObject::GetClassPin(const TArray* InPinsToSearch /*= NULL*/) const { const TArray* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins; UEdGraphPin* Pin = nullptr; for (UEdGraphPin* TestPin : *PinsToSearch) { if (TestPin && TestPin->PinName == FK2Node_WidgetConstructObjectHelper::ClassPinName) { Pin = TestPin; break; } } check(Pin == nullptr || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_WidgetConstructObject::GetWorldContextPin() const { UEdGraphPin* Pin = FindPin(FK2Node_WidgetConstructObjectHelper::WorldContextPinName); check(Pin == nullptr || Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UK2Node_WidgetConstructObject::GetResultPin() const { UEdGraphPin* Pin = FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue); check(Pin->Direction == EGPD_Output); return Pin; } FText UK2Node_WidgetConstructObject::GetBaseNodeTitle() const { return LOCTEXT("WidgetConstructObject_BaseTitle", "Widget Construct Object"); } FText UK2Node_WidgetConstructObject::GetNodeTitleFormat() const { return LOCTEXT("K2Node_WidgetConstruct", "Widget Construct {ClassName}"); } FText UK2Node_WidgetConstructObject::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) { return GetBaseNodeTitle(); } else if (UClass* ClassToSpawn = GetClassToSpawn()) { if (CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("ClassName"), ClassToSpawn->GetDisplayNameText()); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(GetNodeTitleFormat(), Args), this); } return CachedNodeTitle; } return LOCTEXT("WidgetConstructObject_Title_NONE", "Widget Construct NONE"); } bool UK2Node_WidgetConstructObject::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const { if(Super::IsCompatibleWithGraph(TargetGraph)) { return FK2Node_WidgetNodeHelper::IsCompatibleWidgetGraph(TargetGraph); } return false; } void UK2Node_WidgetConstructObject::GetNodeAttributes(TArray>& OutNodeAttributes) const { UClass* ClassToSpawn = GetClassToSpawn(); const FString ClassToSpawnStr = ClassToSpawn ? ClassToSpawn->GetName() : TEXT( "InvalidClass" ); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "WidgetConstructObject" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Name" ), GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "ObjectClass" ), ClassToSpawnStr )); } void UK2Node_WidgetConstructObject::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_WidgetConstructObject::GetMenuCategory() const { return LOCTEXT("WidgetConstructObjectK2Node_MenuCategory", "Widget Function Library"); } bool UK2Node_WidgetConstructObject::HasExternalDependencies(TArray* OptionalOutput) const { UClass* SourceClass = GetClassToSpawn(); const UBlueprint* SourceBlueprint = GetBlueprint(); const bool bResult = (SourceClass && (SourceClass->ClassGeneratedBy != SourceBlueprint)); if (bResult && OptionalOutput) { OptionalOutput->AddUnique(SourceClass); } const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); return bSuperResult || bResult; } FText UK2Node_WidgetConstructObject::GetKeywords() const { return LOCTEXT("WidgetConstructObjectKeywords", "Widget Construct Create New"); } #undef LOCTEXT_NAMESPACE