// Copyright 2022 Just2Devs. All Rights Reserved. #include "K2Node_WidgetTimeline.h" #include "BaseWidgetBlueprint.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintEditor.h" #include "BlueprintNodeSpawner.h" #include "EdGraphSchema_K2.h" #include "K2Node_CallFunction.h" #include "K2Node_Composite.h" #include "K2Node_IfThenElse.h" #include "K2Node_TemporaryVariable.h" #include "K2Node_VariableGet.h" #include "K2Node_WidgetNodeHelper.h" #include "KismetCompiler.h" #include "WidgetFL.h" #include "WidgetTimelineAsync.h" #include "Blueprint/UserWidget.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #define LOCTEXT_NAMESPACE "K2Node_WidgetTimeline" FName UK2Node_WidgetTimeline::PlayPinName("Play"); FName UK2Node_WidgetTimeline::PlayFromStartPinName("Play From Start"); FName UK2Node_WidgetTimeline::ReverseFromEndPinName("Reverse From End"); FName UK2Node_WidgetTimeline::StopPinName("Stop"); FName UK2Node_WidgetTimeline::ResumePinName("Resume"); FName UK2Node_WidgetTimeline::FloatValuePinName("Value"); FName UK2Node_WidgetTimeline::LoopPinName("bLoop"); FName UK2Node_WidgetTimeline::WidgetTimelineObjectPinName("WidgetTimelineObject"); UK2Node_WidgetTimeline::UK2Node_WidgetTimeline(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UWidgetFL, CreateWidgetTimelineObjectProxy); ProxyFactoryClass = UWidgetFL::StaticClass(); ProxyClass = UWidgetTimelineAsyncAction::StaticClass(); } void UK2Node_WidgetTimeline::AllocateDefaultPins() { const UEdGraphSchema_K2* K2Schema = GetDefault(); UEdGraphPin* PlayPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); PlayPin->PinFriendlyName = FText::FromName(PlayPinName); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, PlayFromStartPinName); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, ReverseFromEndPinName); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, StopPinName); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, ResumePinName); bool bExposeProxy = false; bool bHideThen = true; FText ExposeProxyDisplayName; for (const UStruct* TestStruct = ProxyClass; TestStruct; TestStruct = TestStruct->GetSuperStruct()) { bExposeProxy |= TestStruct->HasMetaData(TEXT("ExposedAsyncProxy")); bHideThen |= TestStruct->HasMetaData(TEXT("HideThen")); if (ExposeProxyDisplayName.IsEmpty()) { ExposeProxyDisplayName = TestStruct->GetMetaDataText(TEXT("ExposedAsyncProxy")); } } if (!bHideThen) { CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); } if (bExposeProxy) { UEdGraphPin* ProxyPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, ProxyClass, FBaseAsyncTaskHelper::GetAsyncTaskProxyName()); if (!ExposeProxyDisplayName.IsEmpty()) { ProxyPin->PinFriendlyName = ExposeProxyDisplayName; } } UFunction* Function = GetFactoryFunction(); if (!bHideThen && Function) { for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; // invert the check for function inputs (below) and exclude the factory func's return param - the assumption is // that the factory method will be returning the proxy object, and that other outputs should be forwarded along // with the 'then' pin const bool bIsFunctionOutput = Param->HasAnyPropertyFlags(CPF_OutParm) && !Param->HasAnyPropertyFlags(CPF_ReferenceParm) && !Param->HasAnyPropertyFlags(CPF_ReturnParm); if (bIsFunctionOutput) { UEdGraphPin* Pin = CreatePin(EGPD_Output, NAME_None, Param->GetFName()); K2Schema->ConvertPropertyToPinType(Param, /*out*/ Pin->PinType); } } } UFunction* DelegateSignatureFunction = nullptr; for (TFieldIterator PropertyIt(ProxyClass); PropertyIt; ++PropertyIt) { if (FMulticastDelegateProperty* Property = CastField(*PropertyIt)) { UEdGraphPin* ExecPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, Property->GetFName()); ExecPin->PinToolTip = Property->GetToolTipText().ToString(); ExecPin->PinFriendlyName = Property->GetDisplayNameText(); if (!DelegateSignatureFunction) { DelegateSignatureFunction = Property->SignatureFunction; if (DelegateSignatureFunction) { for (TFieldIterator PropIt(DelegateSignatureFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm); if (bIsFunctionInput) { UEdGraphPin* Pin = CreatePin(EGPD_Output, NAME_None, Param->GetFName()); K2Schema->ConvertPropertyToPinType(Param, /*out*/ Pin->PinType); Pin->PinToolTip = Param->GetToolTipText().ToString(); } } } } } } bool bAllPinsGood = true; if (Function) { TSet PinsToHide; FBlueprintEditorUtils::GetHiddenPinsForFunction(GetGraph(), Function, PinsToHide); for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm); if (!bIsFunctionInput) { // skip function output, it's internal node data continue; } UEdGraphNode::FCreatePinParams PinParams; PinParams.bIsReference = Param->HasAnyPropertyFlags(CPF_ReferenceParm) && bIsFunctionInput; UEdGraphPin* Pin = CreatePin(EGPD_Input, NAME_None, Param->GetFName(), PinParams); const bool bPinGood = (Pin && K2Schema->ConvertPropertyToPinType(Param, /*out*/ Pin->PinType)); if (bPinGood) { //Flag pin as read only for const reference property Pin->bDefaultValueIsIgnored = Param->HasAllPropertyFlags(CPF_ConstParm | CPF_ReferenceParm) && (!Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm) || Pin->PinType.IsContainer()); const bool bAdvancedPin = Param->HasAllPropertyFlags(CPF_AdvancedDisplay); Pin->bAdvancedView = bAdvancedPin; if(bAdvancedPin && (ENodeAdvancedPins::NoPins == AdvancedPinDisplay)) { AdvancedPinDisplay = ENodeAdvancedPins::Hidden; } FString ParamValue; if (K2Schema->FindFunctionParameterDefaultValue(Function, Param, ParamValue)) { K2Schema->SetPinAutogeneratedDefaultValue(Pin, ParamValue); } else { K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); } if (PinsToHide.Contains(Pin->PinName)) { Pin->bHidden = true; } } bAllPinsGood = bAllPinsGood && bPinGood; } } } FText UK2Node_WidgetTimeline::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("WidgetTimelineK2Node_Title", "Widget Timeline"); } FText UK2Node_WidgetTimeline::GetTooltipText() const { return LOCTEXT("WidgetTimelineK2Node_Tooltip", "Timeline compatible with UMGs."); } void UK2Node_WidgetTimeline::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const { Super::GetPinHoverText(Pin, HoverTextOut); if(Pin.PinName == FloatValuePinName) { HoverTextOut = "The value updated while the timeline runs. The value is always in range of 0-1."; } if(Pin.PinFriendlyName.EqualTo(FText::FromName(PlayPinName))) { HoverTextOut = "This is the execution pin for this node and it must always be called the first time you run the timeline. \n \ After you run it the first time you can make use of the other pins to control the timeline. E.g. stop it and resume it."; } if(Pin.PinName == PlayFromStartPinName) { HoverTextOut = "If the timeline has already played from the Play pin and you want to simply restart it from 0 you can fire this pin."; } if(Pin.PinName == ReverseFromEndPinName) { HoverTextOut = "If the timeline has already been played from the Play pin and you want to restart it from 1 you can fire this pin."; } if(Pin.PinName == StopPinName) { HoverTextOut = "You can fire this pin while the timeline is playing to stop it."; } if(Pin.PinName == ResumePinName) { HoverTextOut = "You can fire this pin if you have played and then stopped the timeline to resume playing. It will resume the timeline playing in the same direction it was last playing."; } } bool UK2Node_WidgetTimeline::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const { if(Super::IsCompatibleWithGraph(TargetGraph)) { return FK2Node_WidgetNodeHelper::IsCompatibleWidgetGraph(TargetGraph, true); } return false; } FText UK2Node_WidgetTimeline::GetMenuCategory() const { return LOCTEXT("WidgetTimelineK2Node_MenuCategory", "Widget Function Library"); } void UK2Node_WidgetTimeline::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { ExpandSplitPins(CompilerContext, SourceGraph); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); check(SourceGraph && Schema); bool bIsErrorFree = true; // Create a call to factory the proxy object UK2Node_CallFunction* const CallCreateProxyObjectNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallCreateProxyObjectNode->FunctionReference.SetExternalMember(ProxyFactoryFunctionName, ProxyFactoryClass); CallCreateProxyObjectNode->AllocateDefaultPins(); if (CallCreateProxyObjectNode->GetTargetFunction() == nullptr) { const FText ClassName = ProxyFactoryClass ? FText::FromString(ProxyFactoryClass->GetName()) : LOCTEXT("MissingClassString", "Unknown Class"); const FString FormattedMessage = FText::Format( LOCTEXT("AsyncTaskErrorFmt", "BaseAsyncTask: Missing function {0} from class {1} for async task @@"), FText::FromString(ProxyFactoryFunctionName.GetPlainNameString()), ClassName ).ToString(); CompilerContext.MessageLog.Error(*FormattedMessage, this); return; } bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(UEdGraphSchema_K2::PN_Execute), *CallCreateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute)).CanSafeConnect(); for (UEdGraphPin* CurrentPin : Pins) { if (FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Input)) { UEdGraphPin* DestPin = CallCreateProxyObjectNode->FindPin(CurrentPin->PinName); // match function inputs, to pass data to function from CallFunction node bIsErrorFree &= DestPin && CompilerContext.MovePinLinksToIntermediate(*CurrentPin, *DestPin).CanSafeConnect(); } } UEdGraphPin* const ProxyObjectPin = CallCreateProxyObjectNode->GetReturnValuePin(); check(ProxyObjectPin); UEdGraphPin* OutputAsyncTaskProxy = FindPin(FBaseAsyncTaskHelper::GetAsyncTaskProxyName()); bIsErrorFree &= !OutputAsyncTaskProxy || CompilerContext.MovePinLinksToIntermediate(*OutputAsyncTaskProxy, *ProxyObjectPin).CanSafeConnect(); // GATHER OUTPUT PARAMETERS AND PAIR THEM WITH LOCAL VARIABLES TArray VariableOutputs; bool bPassedFactoryOutputs = false; for (UEdGraphPin* CurrentPin : Pins) { if ((OutputAsyncTaskProxy != CurrentPin) && FBaseAsyncTaskHelper::ValidDataPin(CurrentPin, EGPD_Output)) { if (!bPassedFactoryOutputs) { UEdGraphPin* DestPin = CallCreateProxyObjectNode->FindPin(CurrentPin->PinName); bIsErrorFree &= DestPin && CompilerContext.MovePinLinksToIntermediate(*CurrentPin, *DestPin).CanSafeConnect(); } else { const FEdGraphPinType& PinType = CurrentPin->PinType; UK2Node_TemporaryVariable* TempVarOutput = CompilerContext.SpawnInternalVariable( this, PinType.PinCategory, PinType.PinSubCategory, PinType.PinSubCategoryObject.Get(), PinType.ContainerType, PinType.PinValueType); bIsErrorFree &= TempVarOutput->GetVariablePin() && CompilerContext.MovePinLinksToIntermediate(*CurrentPin, *TempVarOutput->GetVariablePin()).CanSafeConnect(); VariableOutputs.Add(FBaseAsyncTaskHelper::FOutputPinAndLocalVariable(CurrentPin, TempVarOutput)); } } else if (!bPassedFactoryOutputs && CurrentPin && CurrentPin->Direction == EGPD_Output) { // the first exec that isn't the node's then pin is the start of the asyc delegate pins // once we hit this point, we've iterated beyond all outputs for the factory function bPassedFactoryOutputs = (CurrentPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && (CurrentPin->PinName != UEdGraphSchema_K2::PN_Then); } } // FOR EACH DELEGATE DEFINE EVENT, CONNECT IT TO DELEGATE AND IMPLEMENT A CHAIN OF ASSIGMENTS UEdGraphPin* LastThenPin = CallCreateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Then); UK2Node_CallFunction* IsValidFuncNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); const FName IsValidFuncName = GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, IsValid); IsValidFuncNode->FunctionReference.SetExternalMember(IsValidFuncName, UKismetSystemLibrary::StaticClass()); IsValidFuncNode->AllocateDefaultPins(); UEdGraphPin* IsValidInputPin = IsValidFuncNode->FindPinChecked(TEXT("Object")); bIsErrorFree &= Schema->TryCreateConnection(ProxyObjectPin, IsValidInputPin); UK2Node_IfThenElse* ValidateProxyNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ValidateProxyNode->AllocateDefaultPins(); bIsErrorFree &= Schema->TryCreateConnection(IsValidFuncNode->GetReturnValuePin(), ValidateProxyNode->GetConditionPin()); bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, ValidateProxyNode->GetExecPin()); LastThenPin = ValidateProxyNode->GetThenPin(); for (TFieldIterator PropertyIt(ProxyClass); PropertyIt && bIsErrorFree; ++PropertyIt) { #if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION == 27) || (ENGINE_MAJOR_VERSION == 5) UEdGraphPin* LastActivatedThenPin = nullptr; bIsErrorFree &= FBaseAsyncTaskHelper::HandleDelegateImplementation(*PropertyIt, VariableOutputs, ProxyObjectPin, LastThenPin, LastActivatedThenPin, this, SourceGraph, CompilerContext); #else bIsErrorFree &= FBaseAsyncTaskHelper::HandleDelegateImplementation(*PropertyIt, VariableOutputs, ProxyObjectPin, LastThenPin, this, SourceGraph, CompilerContext); #endif } if (CallCreateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Then) == LastThenPin) { CompilerContext.MessageLog.Error(*LOCTEXT("MissingDelegateProperties", "BaseAsyncTask: Proxy has no delegates defined. @@").ToString(), this); return; } // Create a call to activate the proxy object if necessary if (ProxyActivateFunctionName != NAME_None) { UK2Node_CallFunction* const CallActivateProxyObjectNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallActivateProxyObjectNode->FunctionReference.SetExternalMember(ProxyActivateFunctionName, ProxyClass); CallActivateProxyObjectNode->AllocateDefaultPins(); // Hook up the self connection UEdGraphPin* ActivateCallSelfPin = Schema->FindSelfPin(*CallActivateProxyObjectNode, EGPD_Input); check(ActivateCallSelfPin); bIsErrorFree &= Schema->TryCreateConnection(ProxyObjectPin, ActivateCallSelfPin); // Hook the activate node up in the exec chain UEdGraphPin* ActivateExecPin = CallActivateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute); UEdGraphPin* ActivateThenPin = CallActivateProxyObjectNode->FindPinChecked(UEdGraphSchema_K2::PN_Then); bIsErrorFree &= Schema->TryCreateConnection(LastThenPin, ActivateExecPin); LastThenPin = ActivateThenPin; } // Move the connections from the original node then pin to the last internal then pin UEdGraphPin* OriginalThenPin = FindPin(UEdGraphSchema_K2::PN_Then); if (OriginalThenPin) { bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*OriginalThenPin, *LastThenPin).CanSafeConnect(); } bIsErrorFree &= CompilerContext.CopyPinLinksToIntermediate(*LastThenPin, *ValidateProxyNode->GetElsePin()).CanSafeConnect(); if (!bIsErrorFree) { CompilerContext.MessageLog.Error(*LOCTEXT("InternalConnectionError", "BaseAsyncTask: Internal connection error. @@").ToString(), this); } UEdGraphPin* ReturnValuePin = CallCreateProxyObjectNode->GetReturnValuePin(); // Create stop function const UK2Node_CallFunction* StopCallFunction = GetCallFunctionFromName(CompilerContext, SourceGraph, GET_MEMBER_NAME_CHECKED(UWidgetFL, WidgetTimelineStopInternal)); CompilerContext.MovePinLinksToIntermediate(*FindPin(StopPinName, EGPD_Input), *StopCallFunction->GetExecPin()); ReturnValuePin->MakeLinkTo(StopCallFunction->FindPinChecked(WidgetTimelineObjectPinName)); //Create stop function // Get loop bool pin UEdGraphPin* LoopValuePin = CallCreateProxyObjectNode->FindPinChecked(LoopPinName, EGPD_Input); //Create play from start function const UK2Node_CallFunction* PlayFromStartCallFunction = GetCallFunctionFromName(CompilerContext, SourceGraph, GET_MEMBER_NAME_CHECKED(UWidgetFL, WidgetTimelinePlayFromStartInternal)); CompilerContext.MovePinLinksToIntermediate(*FindPin(PlayFromStartPinName, EGPD_Input), *PlayFromStartCallFunction->GetExecPin()); ReturnValuePin->MakeLinkTo(PlayFromStartCallFunction->FindPinChecked(WidgetTimelineObjectPinName)); CompilerContext.CopyPinLinksToIntermediate(*LoopValuePin, *PlayFromStartCallFunction->FindPinChecked(LoopPinName)); //Create play from start function // Create resume play function const UK2Node_CallFunction* ResumePlayCallFunction = GetCallFunctionFromName(CompilerContext, SourceGraph, GET_MEMBER_NAME_CHECKED(UWidgetFL, WidgetTimelineResumeInternal)); CompilerContext.MovePinLinksToIntermediate(*FindPin(ResumePinName, EGPD_Input), *ResumePlayCallFunction->GetExecPin()); ReturnValuePin->MakeLinkTo(ResumePlayCallFunction->FindPinChecked(WidgetTimelineObjectPinName)); // Create resume play function // Create play from end function const UK2Node_CallFunction* ReverseFromEndCallFunction = GetCallFunctionFromName(CompilerContext, SourceGraph, GET_MEMBER_NAME_CHECKED(UWidgetFL, WidgetTimelineReverseFromEndInternal)); CompilerContext.MovePinLinksToIntermediate(*FindPin(ReverseFromEndPinName, EGPD_Input), *ReverseFromEndCallFunction->GetExecPin()); ReturnValuePin->MakeLinkTo(ReverseFromEndCallFunction->FindPinChecked(WidgetTimelineObjectPinName)); // Create play from end function // Make sure we caught everything BreakAllNodeLinks(); } UK2Node_CallFunction* UK2Node_WidgetTimeline::GetCallFunctionFromName(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph, FName FunctionName) { const UClass* FunctionLibrary = UWidgetFL::StaticClass(); UFunction* BlueprintFunction = FunctionLibrary->FindFunctionByName(FunctionName); if(!BlueprintFunction) { CompilerContext.MessageLog.Error(*LOCTEXT("InvalidFunctionName", "The function has not been found.").ToString(), this); return nullptr; } UK2Node_CallFunction* CallFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallFunction->SetFromFunction(BlueprintFunction); CallFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFunction, this); return CallFunction; } FSlateIcon UK2Node_WidgetTimeline::GetIconAndTint(FLinearColor& OutColor) const { static FSlateIcon Icon("EditorStyle", "GraphEditor.Timeline_16x"); return Icon; } FLinearColor UK2Node_WidgetTimeline::GetNodeTitleColor() const { return FLinearColor(1.0f, 0.51f, 0.0f); } #undef LOCTEXT_NAMESPACE