// Copyright Epic Games, Inc. All Rights Reserved. #include "WidgetBlueprint.h" #include "Components/NamedSlot.h" #include "Components/Widget.h" #include "Blueprint/UserWidget.h" #include "MovieScene.h" #include "StructUtils/UserDefinedStruct.h" #include "EdGraph/EdGraph.h" #include "Blueprint/WidgetTree.h" #include "Animation/WidgetAnimation.h" #include "Animation/WidgetAnimationHandle.h" #include "Animation/UMGSequencePlayer.h" #include "Kismet2/StructureEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Binding/PropertyBinding.h" #include "Blueprint/WidgetBlueprintGeneratedClass.h" #include "UObject/AssetRegistryTagsContext.h" #include "UObject/PropertyTag.h" #include "WidgetBlueprintCompiler.h" #include "UObject/EditorObjectVersion.h" #include "UObject/FortniteMainBranchObjectVersion.h" #include "UObject/UE5ReleaseStreamObjectVersion.h" #include "UObject/ObjectSaveContext.h" #include "WidgetGraphSchema.h" #include "UMGEditorProjectSettings.h" #if WITH_EDITOR #include "Interfaces/ITargetPlatform.h" #include "Modules/ModuleManager.h" #include "DiffResults.h" #include "Misc/DataValidation.h" #endif #include "Kismet2/BlueprintEditorUtils.h" #include "K2Node_CallFunction.h" #include "K2Node_MacroInstance.h" #include "K2Node_Composite.h" #include "K2Node_FunctionResult.h" #include "Blueprint/WidgetNavigation.h" #include "WidgetBlueprintEditorUtils.h" #include "WidgetBlueprintExtension.h" #include "WidgetEditingProjectSettings.h" #define LOCTEXT_NAMESPACE "UMG" PRAGMA_DISABLE_DEPRECATION_WARNINGS; FWidgetBlueprintDelegates::FGetAssetTags FWidgetBlueprintDelegates::GetAssetTags; PRAGMA_ENABLE_DEPRECATION_WARNINGS; FWidgetBlueprintDelegates::FGetAssetTagsWithContext FWidgetBlueprintDelegates::GetAssetTagsWithContext; FEditorPropertyPathSegment::FEditorPropertyPathSegment() : Struct(nullptr) , MemberName(NAME_None) , MemberGuid() , IsProperty(true) { } FEditorPropertyPathSegment::FEditorPropertyPathSegment(const FProperty* InProperty) { IsProperty = true; MemberName = InProperty->GetFName(); if ( InProperty->GetOwnerStruct() ) { Struct = InProperty->GetOwnerStruct(); MemberGuid = FStructureEditorUtils::GetGuidForProperty(InProperty); } else if ( InProperty->GetOwnerClass() ) { Struct = InProperty->GetOwnerClass(); UBlueprint::GetGuidFromClassByFieldName(InProperty->GetOwnerClass(), InProperty->GetFName(), MemberGuid); } else { // Should not be possible to hit. check(false); } } FEditorPropertyPathSegment::FEditorPropertyPathSegment(const UFunction* InFunction) { IsProperty = false; MemberName = InFunction->GetFName(); if ( InFunction->GetOwnerClass() ) { Struct = InFunction->GetOwnerClass(); UBlueprint::GetGuidFromClassByFieldName(InFunction->GetOwnerClass(), InFunction->GetFName(), MemberGuid); } else { // Should not be possible to hit. check(false); } } FEditorPropertyPathSegment::FEditorPropertyPathSegment(const UEdGraph* InFunctionGraph) { IsProperty = false; MemberName = InFunctionGraph->GetFName(); UBlueprint* Blueprint = CastChecked(InFunctionGraph->GetOuter()); Struct = Blueprint->GeneratedClass; check(Struct); MemberGuid = InFunctionGraph->GraphGuid; } void FEditorPropertyPathSegment::Rebase(UBlueprint* SegmentBase) { Struct = SegmentBase->GeneratedClass; } bool FEditorPropertyPathSegment::ValidateMember(FDelegateProperty* DelegateProperty, FText& OutError) const { // We may be binding to a function that doesn't have a explicit binder system that can handle it. In that case // check to see if the function signatures are compatible, if it is, even if we don't have a binder we can just // directly bind the function to the delegate. if ( UFunction* Function = GetMember().Get() ) { // Check the signatures to ensure these functions match. if ( Function->IsSignatureCompatibleWith(DelegateProperty->SignatureFunction, UFunction::GetDefaultIgnoredSignatureCompatibilityFlags() | CPF_ReturnParm) ) { return true; } } // Next check to see if we have a binder suitable for handling this case. if ( DelegateProperty->SignatureFunction->NumParms == 1 ) { if ( FProperty* ReturnProperty = DelegateProperty->SignatureFunction->GetReturnProperty() ) { // TODO I don't like having the path segment system needing to have knowledge of the binding layer. // think about divorcing the two. // Find the binder that can handle the delegate return type. TSubclassOf Binder = UWidget::FindBinderClassForDestination(ReturnProperty); if ( Binder == nullptr ) { OutError = FText::Format(LOCTEXT("Binding_Binder_NotFound", "Member:{0}: No binding exists for {1}."), GetMemberDisplayText(), ReturnProperty->GetClass()->GetDisplayNameText()); return false; } FFieldVariant Field = GetMember(); if (Field.IsValid()) { if ( FProperty* Property = Field.Get() ) { // Ensure that the binder also can handle binding from the property we care about. if ( Binder->GetDefaultObject()->IsSupportedSource(Property) ) { return true; } else { OutError = FText::Format(LOCTEXT("Binding_UnsupportedType_Property", "Member:{0} Unable to bind {1}, unsupported type."), GetMemberDisplayText(), Property->GetClass()->GetDisplayNameText()); return false; } } else if ( UFunction* Function = Field.Get() ) { if ( Function->NumParms == 1 ) { if ( Function->HasAnyFunctionFlags(FUNC_Const | FUNC_BlueprintPure) ) { if ( FProperty* MemberReturn = Function->GetReturnProperty() ) { // Ensure that the binder also can handle binding from the property we care about. if ( Binder->GetDefaultObject()->IsSupportedSource(MemberReturn) ) { return true; } else { OutError = FText::Format(LOCTEXT("Binding_UnsupportedType_Function", "Member:{0} Unable to bind {1}, unsupported type."), GetMemberDisplayText(), MemberReturn->GetClass()->GetDisplayNameText()); return false; } } else { OutError = FText::Format(LOCTEXT("Binding_NoReturn", "Member:{0} Has no return value, unable to bind."), GetMemberDisplayText()); return false; } } else { OutError = FText::Format(LOCTEXT("Binding_Pure", "Member:{0} Unable to bind, the function is not marked as pure."), GetMemberDisplayText()); return false; } } else { OutError = FText::Format(LOCTEXT("Binding_NumArgs", "Member:{0} Has the wrong number of arguments, it needs to return 1 value and take no parameters."), GetMemberDisplayText()); return false; } } } } } OutError = LOCTEXT("Binding_UnknownError", "Unknown Error"); return false; } FFieldVariant FEditorPropertyPathSegment::GetMember() const { FName FieldName = GetMemberName(); if ( FieldName != NAME_None ) { FFieldVariant Field = FindUFieldOrFProperty(Struct, FieldName); //if ( Field == nullptr ) //{ // if ( UClass* Class = Cast(Struct) ) // { // if ( UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy) ) // { // if ( UClass* SkeletonClass = Blueprint->SkeletonGeneratedClass ) // { // Field = FindUField(SkeletonClass, FieldName); // } // } // } //} return Field; } return FFieldVariant(); } FName FEditorPropertyPathSegment::GetMemberName() const { if ( MemberGuid.IsValid() ) { FName NameFromGuid = NAME_None; if ( UClass* Class = Cast(Struct) ) { if ( UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy) ) { if ( IsProperty ) { NameFromGuid = UBlueprint::GetFieldNameFromClassByGuid(Class, MemberGuid); } else { NameFromGuid = UBlueprint::GetFieldNameFromClassByGuid(Class, MemberGuid); } } } else if ( UUserDefinedStruct* UserStruct = Cast(Struct) ) { if ( FProperty* Property = FStructureEditorUtils::GetPropertyByGuid(UserStruct, MemberGuid) ) { NameFromGuid = Property->GetFName(); } } //check(NameFromGuid != NAME_None); return NameFromGuid; } //check(MemberName != NAME_None); return MemberName; } FText FEditorPropertyPathSegment::GetMemberDisplayText() const { if ( MemberGuid.IsValid() ) { if ( UClass* Class = Cast(Struct) ) { if ( UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy) ) { if ( IsProperty ) { return FText::FromName(UBlueprint::GetFieldNameFromClassByGuid(Class, MemberGuid)); } else { return FText::FromName(UBlueprint::GetFieldNameFromClassByGuid(Class, MemberGuid)); } } } else if ( UUserDefinedStruct* UserStruct = Cast(Struct) ) { if ( FProperty* Property = FStructureEditorUtils::GetPropertyByGuid(UserStruct, MemberGuid) ) { return Property->GetDisplayNameText(); } } } return FText::FromName(MemberName); } FGuid FEditorPropertyPathSegment::GetMemberGuid() const { return MemberGuid; } FEditorPropertyPath::FEditorPropertyPath() { } FEditorPropertyPath::FEditorPropertyPath(const TArray& BindingChain) { for ( FFieldVariant Field : BindingChain ) { if ( const FProperty* Property = Field.Get()) { Segments.Add(FEditorPropertyPathSegment(Property)); } else if ( const UFunction* Function = Field.Get()) { Segments.Add(FEditorPropertyPathSegment(Function)); } else { // Should never happen check(false); } } } bool FEditorPropertyPath::Rebase(UBlueprint* SegmentBase) { if ( !IsEmpty() ) { Segments[0].Rebase(SegmentBase); return true; } return false; } bool FEditorPropertyPath::Validate(FDelegateProperty* Destination, FText& OutError) const { if ( IsEmpty() ) { OutError = LOCTEXT("Binding_Empty", "The binding is empty."); return false; } for ( int32 SegmentIndex = 0; SegmentIndex < Segments.Num(); SegmentIndex++ ) { const FEditorPropertyPathSegment& Segment = Segments[SegmentIndex]; if ( UStruct* OwnerStruct = Segment.GetStruct() ) { if ( Segment.GetMember() == nullptr ) { OutError = FText::Format(LOCTEXT("Binding_MemberNotFound", "Binding: '{0}' : '{1}' was not found on '{2}'."), GetDisplayText(), Segment.GetMemberDisplayText(), OwnerStruct->GetDisplayNameText()); return false; } } else { OutError = FText::Format(LOCTEXT("Binding_StructNotFound", "Binding: '{0}' : Unable to locate owner class or struct for '{1}'"), GetDisplayText(), Segment.GetMemberDisplayText()); return false; } } // Validate the last member in the segment const FEditorPropertyPathSegment& LastSegment = Segments[Segments.Num() - 1]; return LastSegment.ValidateMember(Destination, OutError); } FText FEditorPropertyPath::GetDisplayText() const { FString DisplayText; for ( int32 SegmentIndex = 0; SegmentIndex < Segments.Num(); SegmentIndex++ ) { const FEditorPropertyPathSegment& Segment = Segments[SegmentIndex]; DisplayText.Append(Segment.GetMemberDisplayText().ToString()); if ( SegmentIndex < ( Segments.Num() - 1 ) ) { DisplayText.Append(TEXT(".")); } } return FText::FromString(DisplayText); } FDynamicPropertyPath FEditorPropertyPath::ToPropertyPath() const { TArray PropertyChain; for (const FEditorPropertyPathSegment& Segment : Segments) { FName SegmentName = Segment.GetMemberName(); if (SegmentName != NAME_None) { PropertyChain.Add(SegmentName.ToString()); } else { return FDynamicPropertyPath(); } } return FDynamicPropertyPath(PropertyChain); } bool FDelegateEditorBinding::IsAttributePropertyBinding(UWidgetBlueprint* Blueprint) const { // First find the target widget we'll be attaching the binding to. if (UWidget* TargetWidget = Blueprint->WidgetTree->FindWidget(FName(*ObjectName))) { // Next find the underlying delegate we're actually binding to, if it's an event the name will be the same, // for properties we need to lookup the delegate property we're actually going to be binding to. FDelegateProperty* BindableProperty = FindFProperty(TargetWidget->GetClass(), FName(*(PropertyName.ToString() + TEXT("Delegate")))); return BindableProperty != nullptr; } return false; } bool FDelegateEditorBinding::DoesBindingTargetExist(UWidgetBlueprint* Blueprint) const { // First find the target widget we'll be attaching the binding to. if (UWidget* TargetWidget = Blueprint->WidgetTree->FindWidget(FName(*ObjectName))) { return true; } return false; } bool FDelegateEditorBinding::IsBindingValid(UClass* BlueprintGeneratedClass, UWidgetBlueprint* Blueprint, FCompilerResultsLog& MessageLog) const { FDelegateRuntimeBinding RuntimeBinding = ToRuntimeBinding(Blueprint); // First find the target widget we'll be attaching the binding to. if ( UWidget* TargetWidget = Blueprint->WidgetTree->FindWidget(FName(*ObjectName)) ) { // Next find the underlying delegate we're actually binding to, if it's an event the name will be the same, // for properties we need to lookup the delegate property we're actually going to be binding to. FDelegateProperty* BindableProperty = FindFProperty(TargetWidget->GetClass(), FName(*( PropertyName.ToString() + TEXT("Delegate") ))); FDelegateProperty* EventProperty = FindFProperty(TargetWidget->GetClass(), PropertyName); bool bNeedsToBePure = BindableProperty ? true : false; FDelegateProperty* DelegateProperty = BindableProperty ? BindableProperty : EventProperty; // Locate the delegate property on the widget that's a delegate for a property we want to bind. if ( DelegateProperty ) { if ( !SourcePath.IsEmpty() ) { FText ValidationError; if ( SourcePath.Validate(DelegateProperty, ValidationError) == false ) { MessageLog.Error( *FText::Format( LOCTEXT("BindingErrorFmt", "Binding: Property '@@' on Widget '@@': {0}"), ValidationError ).ToString(), DelegateProperty, TargetWidget ); return false; } // We allow for widget delegates to have deprecated metadata without fully deprecating. // Since full deprecation breaks existing widgets, checking as below allows for slow deprecation. FString DeprecationWarning = DelegateProperty->GetMetaData("DeprecationMessage"); if (!DeprecationWarning.IsEmpty()) { MessageLog.Warning( *FText::Format( LOCTEXT("BindingWarningDeprecated", "Binding: Deprecated property '@@' on Widget '@@': {0}"), FText::FromString(DeprecationWarning) ).ToString(), DelegateProperty, TargetWidget ); } return true; } else { // On our incoming blueprint generated class, try and find the function we claim exists that users // are binding their property too. if ( UFunction* Function = BlueprintGeneratedClass->FindFunctionByName(RuntimeBinding.FunctionName, EIncludeSuperFlag::IncludeSuper) ) { // Check the signatures to ensure these functions match. if ( Function->IsSignatureCompatibleWith(DelegateProperty->SignatureFunction, UFunction::GetDefaultIgnoredSignatureCompatibilityFlags() | CPF_ReturnParm) ) { // Only allow binding pure functions to property bindings. if ( bNeedsToBePure && !Function->HasAnyFunctionFlags(FUNC_Const | FUNC_BlueprintPure) ) { FText const ErrorFormat = LOCTEXT("BindingNotBoundToPure", "Binding: property '@@' on widget '@@' needs to be bound to a pure function, '@@' is not pure."); MessageLog.Error(*ErrorFormat.ToString(), DelegateProperty, TargetWidget, Function); return false; } return true; } else { FText const ErrorFormat = LOCTEXT("BindingFunctionSigDontMatch", "Binding: property '@@' on widget '@@' bound to function '@@', but the sigatnures don't match. The function must return the same type as the property and have no parameters."); MessageLog.Error(*ErrorFormat.ToString(), DelegateProperty, TargetWidget, Function); } } else { // Bindable property removed. } } } else { // Bindable Property Removed } } else { // Ignore missing widgets } return false; } FDelegateRuntimeBinding FDelegateEditorBinding::ToRuntimeBinding(UWidgetBlueprint* Blueprint) const { FDelegateRuntimeBinding Binding; Binding.ObjectName = ObjectName; Binding.PropertyName = PropertyName; if ( Kind == EBindingKind::Function ) { Binding.FunctionName = ( MemberGuid.IsValid() ) ? Blueprint->GetFieldNameFromClassByGuid(Blueprint->SkeletonGeneratedClass, MemberGuid) : FunctionName; } else { Binding.FunctionName = FunctionName; } Binding.Kind = Kind; Binding.SourcePath = SourcePath.ToPropertyPath(); return Binding; } ///////////////////////////////////////////////////// // UWidgetBlueprint UWidgetBlueprint::UWidgetBlueprint(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bCanCallInitializedWithoutPlayerContext(false) , TickFrequency(EWidgetTickFrequency::Auto) { // Widget GUIDs must be cooked to support fixups at runtime, so enable by default ShouldCookPropertyGuidsValue = EShouldCookBlueprintPropertyGuids::Yes; } void UWidgetBlueprint::ReplaceDeprecatedNodes() { if (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::WidgetStopDuplicatingAnimations) { // Update old graphs to all use the widget graph schema. TArray Graphs; GetAllGraphs(Graphs); for (UEdGraph* Graph : Graphs) { Graph->Schema = UWidgetGraphSchema::StaticClass(); } } #if WITH_EDITORONLY_DATA if (GetLinkerCustomVersion(FUE5ReleaseStreamObjectVersion::GUID) < FUE5ReleaseStreamObjectVersion::BlueprintPinsUseRealNumbers) { // Revert any overzealous PC_Float to PC_Real/PC_Double conversions. // The Blueprint real number changes will automatically convert pin types to doubles if used in a non-native context. // However, UMG property bindings are a special case: the BP functions that bind to the native delegate must agree on their underlying types. // Specifically, bindings used with float properties *must* use the PC_Float type as the return value in a BP function. // In order to correct this behavior, we need to: // * Iterate through the property bindings. // * Find the corresponding delegate signature. // * Find the function graph that matches the binding. // * Find the result node. // * Change the pin type back to float if that's what the delegate signature expects. TArray Graphs; GetAllGraphs(Graphs); for (const FDelegateEditorBinding& Binding : Bindings) { if (Binding.IsAttributePropertyBinding(this)) { check(WidgetTree); if (UWidget* TargetWidget = WidgetTree->FindWidget(FName(*Binding.ObjectName))) { const FDelegateProperty* BindableProperty = FindFProperty(TargetWidget->GetClass(), FName(*(Binding.PropertyName.ToString() + TEXT("Delegate")))); if (BindableProperty) { FName FunctionName = Binding.FunctionName; if (!Binding.SourcePath.IsEmpty()) { check(Binding.SourcePath.Segments.Num() > 0); const FEditorPropertyPathSegment& LastSegment = Binding.SourcePath.Segments[Binding.SourcePath.Segments.Num() - 1]; FunctionName = LastSegment.GetMemberName(); } auto GraphMatchesBindingPredicate = [FunctionName](const UEdGraph* Graph) { check(Graph); return (FunctionName == Graph->GetFName()); }; if (UEdGraph** GraphEntry = Graphs.FindByPredicate(GraphMatchesBindingPredicate)) { UEdGraph* CurrentGraph = *GraphEntry; check(CurrentGraph); for (UEdGraphNode* Node : CurrentGraph->Nodes) { check(Node); if (Node->IsA()) { for (UEdGraphPin* Pin : Node->Pins) { check(Pin); if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real) { FName PinName = Pin->GetFName(); const UFunction* DelegateFunction = BindableProperty->SignatureFunction; check(DelegateFunction); auto OutputParameterMatchesPin = [PinName](FFloatProperty* FloatParam) { check(FloatParam); bool bHasMatch = (FloatParam->PropertyFlags & CPF_OutParm) && (FloatParam->GetFName() == PinName); return bHasMatch; }; bool bFoundMatchingParam = false; for (TFieldIterator It(DelegateFunction); It; ++It) { if (OutputParameterMatchesPin(*It)) { Pin->PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; bFoundMatchingParam = true; break; } } if (bFoundMatchingParam) { UK2Node_FunctionResult* FunctionResultNode = CastChecked(Node); for (TSharedPtr& UserPin : FunctionResultNode->UserDefinedPins) { check(UserPin); if (UserPin->PinName == PinName) { check(UserPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real); UserPin->PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; break; } } } break; } } } } } } } } } } #endif Super::ReplaceDeprecatedNodes(); } #if WITH_EDITORONLY_DATA void UWidgetBlueprint::PreSave(FObjectPreSaveContext ObjectSaveContext) { Super::PreSave(ObjectSaveContext); } void UWidgetBlueprint::GetAssetRegistryTags(TArray& OutTags) const { PRAGMA_DISABLE_DEPRECATION_WARNINGS; Super::GetAssetRegistryTags(OutTags); PRAGMA_ENABLE_DEPRECATION_WARNINGS; } void UWidgetBlueprint::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const { Super::GetAssetRegistryTags(Context); PRAGMA_DISABLE_DEPRECATION_WARNINGS; TArray DeprecatedFunctionTags; FWidgetBlueprintDelegates::GetAssetTags.Broadcast(this, DeprecatedFunctionTags); for (UObject::FAssetRegistryTag& Tag : DeprecatedFunctionTags) { Context.AddTag(MoveTemp(Tag)); } PRAGMA_ENABLE_DEPRECATION_WARNINGS; // Add AvailableNamedSlots, also available on generated class, to the WidgetBlueprint if (const UWidgetBlueprintGeneratedClass* WidgetBPGeneratedClass = Cast(GeneratedClass)) { TStringBuilder<512> Builder; for (FName NamedSlot : WidgetBPGeneratedClass->AvailableNamedSlots) { if (!NamedSlot.IsNone()) { if (Builder.Len() > 0) { Builder << TEXT(','); } Builder << NamedSlot; } } Context.AddTag(FAssetRegistryTag(FName("AvailableNamedSlots"), Builder.ToString(), FAssetRegistryTag::TT_Hidden)); } FWidgetBlueprintDelegates::GetAssetTagsWithContext.Broadcast(this, Context); } void UWidgetBlueprint::NotifyGraphRenamed(class UEdGraph* Graph, FName OldName, FName NewName) { Super::NotifyGraphRenamed(Graph, OldName, NewName); // Update any explicit widget bindings. WidgetTree->ForEachWidget([OldName, NewName](UWidget* Widget) { if (Widget->Navigation) { Widget->Navigation->SetFlags(RF_Transactional); Widget->Navigation->Modify(); Widget->Navigation->TryToRenameBinding(OldName, NewName); } }); } EDataValidationResult UWidgetBlueprint::IsDataValid(FDataValidationContext& Context) const { EDataValidationResult Result = UBlueprint::IsDataValid(Context); const bool bFoundLeak = DetectSlateWidgetLeaks(Context); return bFoundLeak ? EDataValidationResult::Invalid : Result; } bool UWidgetBlueprint::DetectSlateWidgetLeaks(FDataValidationContext& Context) const { // We can't safely run this in anything but a running editor, since widgets // rely on a functioning slate application. if (IsRunningCommandlet()) { return false; } // The detection relies on instantiation of the class: don't try to create an abstract class. // The validation will have to be run on the WBP inheriting from abstract ones. if (GeneratedClass == nullptr || GeneratedClass->HasAnyClassFlags(CLASS_Abstract)) { return false; } UWorld* DummyWorld = NewObject(); UUserWidget* TempUserWidget = NewObject(DummyWorld, GeneratedClass); TempUserWidget->ClearFlags(RF_Transactional); TempUserWidget->SetDesignerFlags(EWidgetDesignFlags::Designing); // If there's no widget tree, there's no test to be performed. if (WidgetTree == nullptr) { return false; } // Update the widget tree directly to match the blueprint tree. That way the preview can update // without needing to do a full recompile. TempUserWidget->DuplicateAndInitializeFromWidgetTree(WidgetTree, TMap()); // We don't want this widget doing all the normal startup and acting like it's the real deal // trying to do gameplay stuff, so make sure it's in design mode. TempUserWidget->SetDesignerFlags(EWidgetDesignFlags::Designing); // Force construction of the slate widgets, and immediately let it go. TWeakPtr PreviewSlateWidgetWeak = TempUserWidget->TakeWidget(); bool bFoundLeak = false; // NOTE: This doesn't explore sub UUserWidget trees, searching for leaks there on purpose, // those widgets will be handled by their own validation steps. // Verify everything is going to be garbage collected. TempUserWidget->WidgetTree->ForEachWidget([&Context, &bFoundLeak](UWidget* Widget) { if (!bFoundLeak) { TWeakPtr PreviewChildWidget = Widget->GetCachedWidget(); if (PreviewChildWidget.IsValid()) { bFoundLeak = true; if (const UPanelWidget* ParentWidget = Widget->GetParent()) { Context.AddError( FText::Format( LOCTEXT("LeakingWidgetsWithParent_WarningFmt", "Leak Detected! {0} ({1}) still has living Slate widgets, it or the parent {2} ({3}) is keeping them in memory. Make sure all Slate resources (TSharedPtr's) are being released in the UWidget's ReleaseSlateResources(). Also check the USlot's ReleaseSlateResources()."), FText::FromString(Widget->GetName()), FText::FromString(Widget->GetClass()->GetName()), FText::FromString(ParentWidget->GetName()), FText::FromString(ParentWidget->GetClass()->GetName()) ) ); } else { Context.AddError( FText::Format( LOCTEXT("LeakingWidgetsWithoutParent_WarningFmt", "Leak Detected! {0} ({1}) still has living Slate widgets, it or the parent widget is keeping them in memory. Make sure all Slate resources (TSharedPtr's) are being released in the UWidget's ReleaseSlateResources(). Also check the USlot's ReleaseSlateResources()."), FText::FromString(Widget->GetName()), FText::FromString(Widget->GetClass()->GetName()) ) ); } } } }); DummyWorld->MarkObjectsPendingKill(); return bFoundLeak; } bool UWidgetBlueprint::FindDiffs(const UBlueprint* OtherBlueprint, FDiffResults& Results) const { const UWidgetBlueprint* OtherWidgetBP = Cast(OtherBlueprint); if (!OtherWidgetBP) { return false; } // Look for all widget instances in both, add shared ones to ObjectsToDiff and add notes for add/remove TMap WidgetMap; TMap OtherWidgetMap; WidgetTree->ForEachWidget([&](UWidget* Widget) { FString WidgetPath = Widget->GetPathName(this); WidgetMap.Add(WidgetPath, Widget); }); OtherWidgetBP->WidgetTree->ForEachWidget([&](UWidget* Widget) { FString WidgetPath = Widget->GetPathName(OtherWidgetBP); OtherWidgetMap.Add(WidgetPath, Widget); }); for (TPair Pair : WidgetMap) { UWidget** FoundOtherWidget = OtherWidgetMap.Find(Pair.Key); UWidget* Widget = Pair.Value; if (FoundOtherWidget) { if (Results.CanStoreResults()) { // Add to general object diff map FDiffSingleResult Diff; Diff.Diff = EDiffType::OBJECT_REQUEST_DIFF; Diff.Object1 = Widget; Diff.Object2 = *FoundOtherWidget; Diff.OwningObjectPath = Pair.Key; FFormatNamedArguments Args; Args.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); Diff.ToolTip = FText::Format(LOCTEXT("DIF_RequestWidgetTooltip", "Widget {WidgetTitle}\nPath: {WidgetPath}"), Args); Diff.DisplayString = FText::Format(LOCTEXT("DIF_RequestWidgetLabel", "Widget {WidgetTitle}"), Args); Diff.Category = EDiffType::CONTROL; Results.Add(Diff); UPanelSlot* Slot = Widget->Slot; UPanelSlot* OtherSlot = (*FoundOtherWidget)->Slot; if (Slot && OtherSlot) { FDiffSingleResult SlotDiff; SlotDiff.Diff = EDiffType::OBJECT_REQUEST_DIFF; SlotDiff.Object1 = Slot; SlotDiff.Object2 = OtherSlot; SlotDiff.OwningObjectPath = Pair.Key; FFormatNamedArguments SlotArgs; SlotArgs.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); SlotArgs.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); SlotDiff.ToolTip = FText::Format(LOCTEXT("DIF_RequestSlotTooltip", "Slot for {WidgetTitle}\nPath: {WidgetPath}"), SlotArgs); SlotDiff.DisplayString = FText::Format(LOCTEXT("DIF_RequestSlotLabel", "Slot for {WidgetTitle}"), SlotArgs); Diff.Category = EDiffType::CONTROL; Results.Add(SlotDiff); } } } else { // This is newly added FDiffSingleResult Diff; Diff.Diff = EDiffType::OBJECT_ADDED; Diff.Object1 = Widget; Diff.OwningObjectPath = Pair.Key; if (Results.CanStoreResults()) { FFormatNamedArguments Args; Args.Add(TEXT("WidgetTitle"), Widget->GetLabelTextWithMetadata()); Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); Diff.ToolTip = FText::Format(LOCTEXT("DIF_AddedWidgetTooltip", "Added Widget {WidgetTitle}\nPath: {WidgetPath}"), Args); Diff.DisplayString = FText::Format(LOCTEXT("DIF_AddedWidgetLabel", "Added Widget {WidgetTitle}"), Args); Diff.Category = EDiffType::ADDITION; } Results.Add(Diff); } } for (TPair Pair : OtherWidgetMap) { UWidget** FoundMyWidget = WidgetMap.Find(Pair.Key); UWidget* OtherWidget = Pair.Value; if (!FoundMyWidget) { // This is newly added FDiffSingleResult Diff; Diff.Diff = EDiffType::OBJECT_REMOVED; Diff.Object1 = OtherWidget; Diff.OwningObjectPath = Pair.Key; if (Results.CanStoreResults()) { FFormatNamedArguments Args; Args.Add(TEXT("WidgetTitle"), OtherWidget->GetLabelTextWithMetadata()); Args.Add(TEXT("WidgetPath"), FText::FromString(Pair.Key)); Diff.ToolTip = FText::Format(LOCTEXT("DIF_RemovedWidgetTooltip", "Removed Widget {WidgetTitle}\nPath:{WidgetPath}"), Args); Diff.DisplayString = FText::Format(LOCTEXT("DIF_RemovedWidgetLabel", "Removed Widget {WidgetTitle}"), Args); Diff.Category = EDiffType::SUBTRACTION; } Results.Add(Diff); } } // Add info warning if (Results.CanStoreResults()) { FDiffSingleResult Diff; Diff.Diff = EDiffType::INFO_MESSAGE; Diff.Category = EDiffType::CONTROL; Diff.ToolTip = LOCTEXT("DIF_WidgetWarningMessage", "Warning: This may be missing changes to Animations and Bindings"); Diff.DisplayString = Diff.ToolTip; Results.Add(Diff); } return true; } void UWidgetBlueprint::OnVariableAdded(const FName& VariableName) { ensure(!WidgetVariableNameToGuidMap.Contains(VariableName)); Modify(); WidgetVariableNameToGuidMap.Emplace(VariableName, FGuid::NewGuid()); } void UWidgetBlueprint::OnVariableRenamed(const FName& OldName, const FName& NewName) { if (OldName == NewName) { return; } // We should never have an entry for the new name and always have an entry for the current name ensure(!WidgetVariableNameToGuidMap.Contains(NewName)); FGuid VariableGuid; if (ensure(WidgetVariableNameToGuidMap.RemoveAndCopyValue(OldName, VariableGuid))) { Modify(); WidgetVariableNameToGuidMap.Emplace(NewName, VariableGuid); } else { OnVariableAdded(NewName); } } void UWidgetBlueprint::OnVariableRemoved(const FName& VariableName) { Modify(); WidgetVariableNameToGuidMap.Remove(VariableName); } #endif // WITH_EDITORONLY_DATA void UWidgetBlueprint::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FEditorObjectVersion::GUID); Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); Ar.UsingCustomVersion(FUE5ReleaseStreamObjectVersion::GUID); } void UWidgetBlueprint::PostLoad() { Super::PostLoad(); WidgetTree->ClearFlags(RF_ArchetypeObject); WidgetTree->ForEachWidget([&] (UWidget* Widget) { Widget->ConnectEditorData(); }); if ( GetLinkerUEVersion() < VER_UE4_RENAME_WIDGET_VISIBILITY ) { static const FName Visiblity(TEXT("Visiblity")); static const FName Visibility(TEXT("Visibility")); for ( FDelegateEditorBinding& Binding : Bindings ) { if ( Binding.PropertyName == Visiblity ) { Binding.PropertyName = Visibility; } } } if ( GetLinkerCustomVersion(FEditorObjectVersion::GUID) < FEditorObjectVersion::WidgetGraphSchema ) { // Update old graphs to all use the widget graph schema. TArray Graphs; GetAllGraphs(Graphs); for ( UEdGraph* Graph : Graphs ) { Graph->Schema = UWidgetGraphSchema::StaticClass(); } } if ( GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::UpgradeWidgetBlueprintLegacySequencePlayer ) { FixupLegacySequencePlayerCalls(); } } void UWidgetBlueprint::FixupLegacySequencePlayerCalls() { TArray Graphs; GetAllGraphs(Graphs); for (UEdGraph* Graph : Graphs) { TArray CallFunctionNodes; Graph->GetNodesOfClass(CallFunctionNodes); for (UK2Node_CallFunction* CallFunctionNode : CallFunctionNodes) { // Old calls to GetUserTag and SetUserTag were done on the UUMGSequencePlayer object returned by // calls to PlayAnimation and its variants. These would be hooked up on the "self" pin. // // The new calls are done one the animation handle structure, via functions on // UWidgetAnimationHandleFunctionLibrary. In this case, since these functions are static, the // "self" pin is hidden, and we want calls to be done on the "Target" pin which didn't exist // before, and doesn't exist yet as we are being called in PostLoad. // // Core redirectors can't handle this case so we fix them up here. We rename "self" to Target // so that connections are preserved. // // TODO: this wouldn't fix calls done _outside_ of the widget, those will be broken. // const FName FunctionName = CallFunctionNode->GetFunctionName(); const bool bMatchesAnimationFunctionName = ( FunctionName == GET_FUNCTION_NAME_CHECKED(UUMGSequencePlayer, GetUserTag) || FunctionName == GET_FUNCTION_NAME_CHECKED(UUMGSequencePlayer, SetUserTag)); if (!bMatchesAnimationFunctionName) { continue; } UEdGraphPin* SelfPin = GetDefault()->FindSelfPin(*CallFunctionNode, EGPD_Input); const bool bMatchesAnimationClass = ( SelfPin && SelfPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object && SelfPin->PinType.PinSubCategoryObject == UUMGSequencePlayer::StaticClass()); if (!bMatchesAnimationClass) { continue; } // Redirect from UUMGSequencePlayer::XxxUserTag to UWidgetAnimationHandleFunctionLibrary::XxxUserTag. CallFunctionNode->FunctionReference.SetExternalMember( FunctionName, UWidgetAnimationHandleFunctionLibrary::StaticClass()); // IMPORTANT: the name "Target" must match the parameter name in the GetUserTag and // SetUserTag methods! UEdGraphPin* TargetPin = CallFunctionNode->FindPin(TEXT("Target")); if (SelfPin && !SelfPin->LinkedTo.IsEmpty() && !TargetPin) { SelfPin->PinName = TEXT("Target"); #if WITH_EDITORONLY_DATA SelfPin->PinFriendlyName = FText(); #endif } } } } void UWidgetBlueprint::PostDuplicate(bool bDuplicateForPIE) { Super::PostDuplicate(bDuplicateForPIE); if ( !bDuplicatingReadOnly ) { // We need to update all the bindings and change each bindings first segment in the path // to be the new class this blueprint generates, as all bindings must first originate on // the widget blueprint, the first segment is always a reference to 'self'. for ( FDelegateEditorBinding& Binding : Bindings ) { Binding.SourcePath.Rebase(this); } } } UClass* UWidgetBlueprint::GetBlueprintClass() const { return UWidgetBlueprintGeneratedClass::StaticClass(); } bool UWidgetBlueprint::AllowsDynamicBinding() const { return true; } bool UWidgetBlueprint::SupportsInputEvents() const { return true; } void UWidgetBlueprint::GatherDependencies(TSet>& InDependencies) const { Super::GatherDependencies(InDependencies); if ( WidgetTree ) { WidgetTree->ForEachWidget([&] (UWidget* Widget) { if ( UBlueprint* WidgetBlueprint = UBlueprint::GetBlueprintFromClass(Widget->GetClass()) ) { bool bWasAlreadyInSet; InDependencies.Add(WidgetBlueprint, &bWasAlreadyInSet); if ( !bWasAlreadyInSet ) { WidgetBlueprint->GatherDependencies(InDependencies); } } }); } } bool UWidgetBlueprint::ValidateGeneratedClass(const UClass* InClass) { const UWidgetBlueprintGeneratedClass* GeneratedClass = Cast(InClass); if ( !ensure(GeneratedClass) ) { return false; } const UWidgetBlueprint* Blueprint = Cast(GetBlueprintFromClass(GeneratedClass)); if ( !ensure(Blueprint) ) { return false; } if ( !ensure(Blueprint->WidgetTree && ( Blueprint->WidgetTree->GetOuter() == Blueprint )) ) { return false; } else { TArray < UWidget* > AllWidgets; Blueprint->WidgetTree->GetAllWidgets(AllWidgets); for ( UWidget* Widget : AllWidgets ) { if ( !ensure(Widget->GetOuter() == Blueprint->WidgetTree) ) { return false; } } } UWidgetTree* WidgetTree = GeneratedClass->GetWidgetTreeArchetype(); if ( !ensure(WidgetTree && (WidgetTree->GetOuter() == GeneratedClass )) ) { return false; } else { TArray AllWidgets; WidgetTree->GetAllWidgets(AllWidgets); for ( UWidget* Widget : AllWidgets ) { if ( !ensure(Widget->GetOuter() == WidgetTree) ) { return false; } } } return true; } TSharedPtr UWidgetBlueprint::GetCompilerForWidgetBP(UBlueprint* BP, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions) { return TSharedPtr(new FWidgetBlueprintCompilerContext(CastChecked(BP), InMessageLog, InCompileOptions)); } void UWidgetBlueprint::GetReparentingRules(TSet< const UClass* >& AllowedChildrenOfClasses, TSet< const UClass* >& DisallowedChildrenOfClasses) const { AllowedChildrenOfClasses.Add( UUserWidget::StaticClass() ); } bool UWidgetBlueprint::IsWidgetFreeFromCircularReferences(UUserWidget* UserWidget) const { if (UserWidget != nullptr) { if (UserWidget->GetClass() == GeneratedClass) { // If this user widget is the same as the blueprint's generated class, we should reject it because it // will cause a circular reference within the blueprint. return false; } else if (UWidgetBlueprint* GeneratedByBlueprint = Cast(UserWidget->WidgetGeneratedBy)) { // Check the generated by blueprints - this will catch even cases where one has the other in the widget tree but hasn't compiled yet if (GeneratedByBlueprint->WidgetTree && GeneratedByBlueprint->WidgetTree->RootWidget) { TArray ChildWidgets; GeneratedByBlueprint->WidgetTree->GetAllWidgets(ChildWidgets); for (UWidget* ChildWidget : ChildWidgets) { if (UWidgetBlueprint* ChildGeneratedBlueprint = Cast(ChildWidget->WidgetGeneratedBy)) { if (this == ChildGeneratedBlueprint) { return false; } } } } } else if (UserWidget->WidgetTree) { // This loop checks for references that existed in the compiled blueprint, in case it's changed since then TArray ChildWidgets; UserWidget->WidgetTree->GetAllWidgets(ChildWidgets); for (UWidget* Widget : ChildWidgets) { if (Cast(Widget) != nullptr) { if ( !IsWidgetFreeFromCircularReferences(Cast(Widget)) ) { return false; } } } } } return true; } namespace UE::UMG::Private { bool HasCircularReferences(const UClass* CurrentClass, TArray> DiscoveredBlueprint, UWidget*& OutResult) { if (DiscoveredBlueprint.ContainsByPredicate([CurrentClass](const UClass* Other) { return CurrentClass->IsChildOf(Other); })) { return true; } DiscoveredBlueprint.Add(CurrentClass); if (const UWidgetBlueprintGeneratedClass* CurrentWidgetClass = Cast(CurrentClass)) { TArray AllWidgets; if (const UWidgetBlueprint* WidgetBP = Cast(CurrentWidgetClass->ClassGeneratedBy)) { if (WidgetBP->WidgetTree) { WidgetBP->WidgetTree->GetAllWidgets(AllWidgets); } } else if (UWidgetTree* CurrentWidgetTree = CurrentWidgetClass->GetWidgetTreeArchetype()) { CurrentWidgetTree->GetAllWidgets(AllWidgets); } for (UWidget* Widget : AllWidgets) { if (UUserWidget* UserWidget = Cast(Widget)) { if (HasCircularReferences(UserWidget->GetClass(), DiscoveredBlueprint, OutResult)) { OutResult = Widget; return true; } } } } return false; } const UWidgetBlueprint* GetGeneratedWidgetBlueprintFromClass(const UClass* CurrentClass) { if (const UWidgetBlueprintGeneratedClass* GeneratedClass = Cast(CurrentClass)) { return Cast(GeneratedClass->ClassGeneratedBy); } return nullptr; } bool TryBuildSetNameForWidgets(const UClass* CurrentClass, TSet& InOutWidgetNames, TSet& InOutConflictingWidgets) { if (const UWidgetBlueprint* WidgetBP = GetGeneratedWidgetBlueprintFromClass(CurrentClass)) { // only search in parent class if this class isn't overwriting the root if (WidgetBP->WidgetTree->RootWidget == nullptr) { if (!TryBuildSetNameForWidgets(WidgetBP->ParentClass, InOutWidgetNames, InOutConflictingWidgets)) { return false; } } if (WidgetBP->WidgetTree) { WidgetBP->WidgetTree->ForEachWidget([&InOutWidgetNames,&InOutConflictingWidgets](UWidget* Widget) { FName WidgetName = Widget->GetFName(); if (InOutWidgetNames.Contains(WidgetName)) { InOutConflictingWidgets.Add(Widget); } else { InOutWidgetNames.Add(Widget->GetFName()); } }); } } return InOutConflictingWidgets.IsEmpty(); } } TValueOrError UWidgetBlueprint::HasCircularReferences() const { if (GeneratedClass) { TArray> DiscoveredBlueprint; UWidget* Result = nullptr; if (UE::UMG::Private::HasCircularReferences(GeneratedClass, DiscoveredBlueprint, Result)) { return MakeError(Result); } } return MakeValue(); } TValueOrError> UWidgetBlueprint::HasConflictingWidgetNamesFromInheritance() const { if (GeneratedClass) { // we search for conflicting widget names when the parent is also a generated widgetblueprint // this allows bailing out early on most compilations if (const UWidgetBlueprint* WidgetBP = UE::UMG::Private::GetGeneratedWidgetBlueprintFromClass(GeneratedClass) ) { if (Cast(WidgetBP->ParentClass) != nullptr) { TSet WidgetNames; TSet Result; if (!UE::UMG::Private::TryBuildSetNameForWidgets(GeneratedClass, WidgetNames, Result)) { return MakeError(Result); } } } } return MakeValue(); } UPackage* UWidgetBlueprint::GetWidgetTemplatePackage() const { return GetOutermost(); } static bool HasLatentActions(UEdGraph* Graph) { if (!Graph) { return false; } for (const UEdGraphNode* Node : Graph->Nodes) { if (const UK2Node_CallFunction* CallFunctionNode = Cast(Node)) { // Check any function call nodes to see if they are latent. UFunction* TargetFunction = CallFunctionNode->GetTargetFunction(); if (TargetFunction && TargetFunction->HasMetaData(FBlueprintMetadata::MD_Latent)) { return true; } } else if (const UK2Node_MacroInstance* MacroInstanceNode = Cast(Node)) { // Any macro graphs that haven't already been checked need to be checked for latent function calls //if (InspectedGraphList.Find(MacroInstanceNode->GetMacroGraph()) == INDEX_NONE) { if (HasLatentActions(MacroInstanceNode->GetMacroGraph())) { return true; } } } else if (const UK2Node_Composite* CompositeNode = Cast(Node)) { // Any collapsed graphs that haven't already been checked need to be checked for latent function calls //if (InspectedGraphList.Find(CompositeNode->BoundGraph) == INDEX_NONE) { if (HasLatentActions(CompositeNode->BoundGraph)) { return true; } } } } return false; } void UWidgetBlueprint::UpdateTickabilityStats(bool& OutHasLatentActions, bool& OutHasAnimations, bool& OutClassRequiresNativeTick) { if (GeneratedClass && GeneratedClass->ClassConstructor) { UWidgetBlueprintGeneratedClass* WidgetBPGeneratedClass = CastChecked(GeneratedClass); UUserWidget* DefaultWidget = WidgetBPGeneratedClass->GetDefaultObject(); TArray BlueprintParents; UBlueprint::GetBlueprintHierarchyFromClass(WidgetBPGeneratedClass, BlueprintParents); bool bHasLatentActions = false; bool bHasAnimations = false; const bool bHasScriptImplementedTick = DefaultWidget->bHasScriptImplementedTick; for (UBlueprint* Blueprint : BlueprintParents) { UWidgetBlueprint* WidgetBP = Cast(Blueprint); if (WidgetBP) { bHasAnimations |= WidgetBP->Animations.Num() > 0; if (!bHasLatentActions) { TArray AllGraphs; WidgetBP->GetAllGraphs(AllGraphs); for (UEdGraph* Graph : AllGraphs) { if (HasLatentActions(Graph)) { bHasLatentActions = true; break; } } } } } UClass* NativeParent = FBlueprintEditorUtils::GetNativeParent(this); static const FName DisableNativeTickMetaTag("DisableNativeTick"); const bool bClassRequiresNativeTick = !NativeParent->HasMetaData(DisableNativeTickMetaTag); TickFrequency = DefaultWidget->GetDesiredTickFrequency(); TickPredictionReason = TEXT(""); TickPrediction = EWidgetCompileTimeTickPrediction::WontTick; switch (TickFrequency) { case EWidgetTickFrequency::Never: TickPrediction = EWidgetCompileTimeTickPrediction::WontTick; break; case EWidgetTickFrequency::Auto: { TArray Reasons; if (bHasScriptImplementedTick) { Reasons.Add(TEXT("Script")); } if (bClassRequiresNativeTick) { Reasons.Add(TEXT("Native")); } if (bHasAnimations) { Reasons.Add(TEXT("Anim")); } if (bHasLatentActions) { Reasons.Add(TEXT("Latent")); } for (int32 ReasonIdx = 0; ReasonIdx < Reasons.Num(); ++ReasonIdx) { TickPredictionReason += Reasons[ReasonIdx]; if (ReasonIdx != Reasons.Num() - 1) { TickPredictionReason.AppendChar('|'); } } if (bHasScriptImplementedTick || bClassRequiresNativeTick) { // Widget has an implemented tick or the generated class is not a direct child of UUserWidget (means it could have a native tick) then it will definitely tick TickPrediction = EWidgetCompileTimeTickPrediction::WillTick; } else if (bHasAnimations || bHasLatentActions) { // Widget has latent actions or animations and will tick if these are triggered TickPrediction = EWidgetCompileTimeTickPrediction::OnDemand; } } break; } OutHasLatentActions = bHasLatentActions; OutHasAnimations = bHasAnimations; OutClassRequiresNativeTick = bClassRequiresNativeTick; } } bool UWidgetBlueprint::ArePropertyBindingsAllowed() const { return GetRelevantSettings()->CompilerOption_PropertyBindingRule(this) == EPropertyBindingPermissionLevel::Allow; } TArray UWidgetBlueprint::GetInheritedAvailableNamedSlots() const { if (const UWidgetBlueprintGeneratedClass* GeneratedBPClass = Cast(GeneratedClass->GetSuperClass())) { return GeneratedBPClass->AvailableNamedSlots; } return TArray(); } TSet UWidgetBlueprint::GetInheritedNamedSlotsWithContentInSameTree() const { if (const UWidgetBlueprintGeneratedClass* GeneratedBPClass = Cast(GeneratedClass->GetSuperClass())) { return GeneratedBPClass->NamedSlotsWithContentInSameTree; } return TSet(); } UWidgetEditingProjectSettings* UWidgetBlueprint::GetRelevantSettings() { return GetMutableDefault(); } const UWidgetEditingProjectSettings* UWidgetBlueprint::GetRelevantSettings() const { return GetDefault(); } #if WITH_EDITOR void UWidgetBlueprint::LoadModulesRequiredForCompilation() { static const FName ModuleName(TEXT("UMGEditor")); FModuleManager::Get().LoadModule(ModuleName); } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE