// Copyright Epic Games, Inc. All Rights Reserved. #include "WidgetBlueprint.h" #include "Components/Widget.h" #include "Blueprint/UserWidget.h" #include "MovieScene.h" #include "Engine/UserDefinedStruct.h" #include "EdGraph/EdGraph.h" #include "Blueprint/WidgetTree.h" #include "Animation/WidgetAnimation.h" #include "Kismet2/StructureEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Binding/PropertyBinding.h" #include "Blueprint/WidgetBlueprintGeneratedClass.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" #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" #define LOCTEXT_NAMESPACE "UMG" FWidgetBlueprintDelegates::FGetAssetTags FWidgetBlueprintDelegates::GetAssetTags; 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; } bool FWidgetAnimation_DEPRECATED::SerializeFromMismatchedTag(struct FPropertyTag const& Tag, FStructuredArchive::FSlot Slot) { static FName AnimationDataName("AnimationData"); if(Tag.Type == NAME_StructProperty && Tag.Name == AnimationDataName) { FStructuredArchive::FRecord Record = Slot.EnterRecord(); Record << SA_VALUE(TEXT("MovieScene"), MovieScene); Record << SA_VALUE(TEXT("AnimationBindings"), AnimationBindings); return true; } return false; } ///////////////////////////////////////////////////// // UWidgetBlueprint UWidgetBlueprint::UWidgetBlueprint(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , TickFrequency(EWidgetTickFrequency::Auto) { } 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; }; for (TFieldIterator It(DelegateFunction); It; ++It) { if (OutputParameterMatchesPin(*It)) { Pin->PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; break; } } break; } } } } } } } } } } #endif Super::ReplaceDeprecatedNodes(); } #if WITH_EDITORONLY_DATA void UWidgetBlueprint::PreSave(const class ITargetPlatform* TargetPlatform) { PRAGMA_DISABLE_DEPRECATION_WARNINGS; Super::PreSave(TargetPlatform); PRAGMA_ENABLE_DEPRECATION_WARNINGS; } void UWidgetBlueprint::PreSave(FObjectPreSaveContext ObjectSaveContext) { Super::PreSave(ObjectSaveContext); } #endif // WITH_EDITORONLY_DATA #if WITH_EDITORONLY_DATA void UWidgetBlueprint::GetAssetRegistryTags(TArray& OutTags) const { Super::GetAssetRegistryTags(OutTags); FWidgetBlueprintDelegates::GetAssetTags.Broadcast(this, OutTags); } 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(TArray& ValidationErrors) { EDataValidationResult Result = UBlueprint::IsDataValid(ValidationErrors); const bool bFoundLeak = DetectSlateWidgetLeaks(ValidationErrors); return bFoundLeak ? EDataValidationResult::Invalid : Result; } bool UWidgetBlueprint::DetectSlateWidgetLeaks(TArray& ValidationErrors) { // We can't safely run this in anything but a running editor, since widgets // rely on a functioning slate application. if (IsRunningCommandlet()) { 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, nullptr); // 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([&ValidationErrors, &bFoundLeak](UWidget* Widget) { if (!bFoundLeak) { TWeakPtr PreviewChildWidget = Widget->GetCachedWidget(); if (PreviewChildWidget.IsValid()) { bFoundLeak = true; if (UPanelWidget* ParentWidget = Widget->GetParent()) { ValidationErrors.Add( 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 { ValidationErrors.Add( 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.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); 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); SlotDiff.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); 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.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); } 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.DisplayColor = FLinearColor(1.f, 0.4f, 0.4f); } Results.Add(Diff); } } // Add info warning if (Results.CanStoreResults()) { FDiffSingleResult Diff; Diff.Diff = EDiffType::INFO_MESSAGE; Diff.DisplayColor = FLinearColor(.7f, .7f, .7f); Diff.ToolTip = LOCTEXT("DIF_WidgetWarningMessage", "Warning: This may be missing changes to Animations and Bindings"); Diff.DisplayString = Diff.ToolTip; Results.Add(Diff); } return true; } #endif 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_FIXUP_WIDGET_ANIMATION_CLASS ) { // Fixup widget animations. for( auto& OldAnim : AnimationData_DEPRECATED ) { FName AnimName = OldAnim.MovieScene->GetFName(); // Rename the old movie scene so we can reuse the name OldAnim.MovieScene->Rename( *MakeUniqueObjectName( this, UMovieScene::StaticClass(), "MovieScene").ToString(), nullptr, REN_ForceNoResetLoaders | REN_DontCreateRedirectors | REN_DoNotDirty | REN_NonTransactional); UWidgetAnimation* NewAnimation = NewObject(this, AnimName, RF_Transactional); OldAnim.MovieScene->Rename(*AnimName.ToString(), NewAnimation, REN_ForceNoResetLoaders | REN_DontCreateRedirectors | REN_DoNotDirty | REN_NonTransactional ); NewAnimation->MovieScene = OldAnim.MovieScene; NewAnimation->AnimationBindings = OldAnim.AnimationBindings; Animations.Add( NewAnimation ); } AnimationData_DEPRECATED.Empty(); } 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(); } } } 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->GetChildWidgets(GeneratedByBlueprint->WidgetTree->RootWidget, 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; } 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 GetDefault()->CompilerOption_PropertyBindingRule(this) == EPropertyBindingPermissionLevel::Allow; } TArray UWidgetBlueprint::GetInheritedNamedSlots() const { TArray SlotNames; if ( UWidget* SuperClassCDO = GeneratedClass->GetSuperClass()->GetDefaultObject() ) { if ( const INamedSlotInterface* NamedSlotHost = Cast(SuperClassCDO) ) { NamedSlotHost->GetSlotNames(SlotNames); } } return SlotNames; } #if WITH_EDITOR void UWidgetBlueprint::LoadModulesRequiredForCompilation() { static const FName ModuleName(TEXT("UMGEditor")); FModuleManager::Get().LoadModule(ModuleName); } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE