// Copyright Epic Games, Inc. All Rights Reserved. #include "MVVMWidgetBlueprintExtension_View.h" #include "Blueprint/WidgetTree.h" #include "Extensions/MVVMBlueprintViewExtension.h" #include "FindInBlueprintManager.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "MVVMBlueprintInstancedViewModel.h" #include "MVVMBlueprintView.h" #include "MVVMBlueprintViewModel.h" #include "MVVMBlueprintViewConversionFunction.h" #include "MVVMViewBlueprintCompiler.h" #include "ScopedTransaction.h" #include "View/MVVMViewClass.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MVVMWidgetBlueprintExtension_View) #define LOCTEXT_NAMESPACE "MVVMBlueprintExtensionView" namespace UE::MVVM::Private { bool GAllowViewClass = true; static FAutoConsoleVariableRef CVarAllowViewClass( TEXT("MVVM.AllowViewClass"), GAllowViewClass, TEXT("Is the model view viewmodel view is allowed to be added to the generated Widget GeneratedClass."), ECVF_ReadOnly ); bool GAutogeneratedFunctionsAreTransient = false; static FAutoConsoleVariableRef CVarAutogeneratedFunctionsAreTransient( TEXT("MVVM.AutogeneratedFunctionsAreTransient"), GAutogeneratedFunctionsAreTransient, TEXT("Is the autogenerated function should be mark as transient."), ECVF_ReadOnly ); } void UMVVMWidgetBlueprintExtension_View::CreateBlueprintViewInstance() { BlueprintView = NewObject(this, FName(), RF_Transactional); BlueprintViewChangedDelegate.Broadcast(); FBlueprintEditorUtils::MarkBlueprintAsModified(GetWidgetBlueprint()); } void UMVVMWidgetBlueprintExtension_View::DestroyBlueprintViewInstance() { BlueprintView = nullptr; BlueprintViewChangedDelegate.Broadcast(); } void UMVVMWidgetBlueprintExtension_View::PostLoad() { Super::PostLoad(); if (!HasAnyFlags(RF_Transactional)) { SetFlags(RF_Transactional); } } UMVVMBlueprintViewExtension* UMVVMWidgetBlueprintExtension_View::CreateBlueprintWidgetExtension(TSubclassOf ExtensionClass, FName WidgetName) { if (ensure(ExtensionClass.Get())) { UObject* ExtensionObj = NewObject(this, ExtensionClass.Get(), NAME_None, RF_Transactional); UMVVMBlueprintViewExtension* NewExtension = CastChecked(ExtensionObj); const FScopedTransaction Transaction(LOCTEXT("AddViewModelExtension", "Add viewmodel extension")); NewExtension->Modify(); Modify(); FMVVMExtensionItem ExtensionToAdd; ExtensionToAdd.WidgetName = WidgetName; ExtensionToAdd.ExtensionObj = NewExtension; BlueprintExtensions.Add(ExtensionToAdd); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetWidgetBlueprint()); return NewExtension; } return nullptr; } void UMVVMWidgetBlueprintExtension_View::RemoveBlueprintWidgetExtension(UMVVMBlueprintViewExtension* ExtensionToRemove, FName WidgetName) { FMVVMExtensionItem Extension; Extension.WidgetName = WidgetName; Extension.ExtensionObj = ExtensionToRemove; const FScopedTransaction Transaction(LOCTEXT("RemoveViewModelExtension", "Remove viewmodel extension")); Modify(); BlueprintExtensions.RemoveSingle(Extension); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetWidgetBlueprint()); } TArray UMVVMWidgetBlueprintExtension_View::GetBlueprintExtensionsForWidget(FName WidgetName) const { TArray ThisWidgetExtensions; for (const FMVVMExtensionItem& Extension : BlueprintExtensions) { if (Extension.WidgetName == WidgetName && Extension.ExtensionObj) { ThisWidgetExtensions.Add(Extension.ExtensionObj); } } return ThisWidgetExtensions; } TArray UMVVMWidgetBlueprintExtension_View::GetAllBlueprintExtensions() const { TArray AllExtensions; AllExtensions.Reset(BlueprintExtensions.Num()); for (const FMVVMExtensionItem& Extension : BlueprintExtensions) { if (Extension.ExtensionObj) { AllExtensions.Add(Extension.ExtensionObj); } } AllExtensions.Shrink(); return AllExtensions; } void UMVVMWidgetBlueprintExtension_View::VerifyWidgetExtensions() { if (const UWidgetBlueprint* WidgetBlueprint = GetWidgetBlueprint()) { if (const UWidgetTree* WidgetTree = WidgetBlueprint->WidgetTree) { bool bModified = false; auto UpdateModify = [&bModified, Self=this]() { if (!bModified) { bModified = true; Self->Modify(); } }; TArray > WidgetNamesToRemove; for (int32 Index = BlueprintExtensions.Num() - 1; Index >= 0; --Index) { if (BlueprintExtensions[Index].ExtensionObj == nullptr) { UpdateModify(); BlueprintExtensions.RemoveAtSwap(Index); } else if (!BlueprintExtensions[Index].WidgetName.IsNone()) { WidgetNamesToRemove.Add(BlueprintExtensions[Index].WidgetName); } } // Find widgets that are no longer in the tree and delete their extensions. if (WidgetNamesToRemove.Num() > 0) { WidgetTree->ForEachWidget([&WidgetNamesToRemove, this](TObjectPtr Widget) { if (Widget) { for (int32 Index = WidgetNamesToRemove.Num() - 1; Index >= 0; Index--) { const FName WidgetName = WidgetNamesToRemove[Index]; if (WidgetName == Widget->GetFName()) { WidgetNamesToRemove.RemoveSingleSwap(WidgetName); } } } }); } if (WidgetNamesToRemove.Num() > 0) { UpdateModify(); for (int32 Index = BlueprintExtensions.Num() - 1; Index >= 0; --Index) { if (WidgetNamesToRemove.Contains(BlueprintExtensions[Index].WidgetName)) { BlueprintExtensions.RemoveAtSwap(Index); } } } } } } void UMVVMWidgetBlueprintExtension_View::OnFieldRenamed(UClass* FieldOwnerClass, FName OldName, FName NewName) { const UWidgetBlueprint* WidgetBlueprint = GetWidgetBlueprint(); if (WidgetBlueprint != nullptr && WidgetBlueprint->GeneratedClass == FieldOwnerClass) { RenameWidgetExtensions(OldName, NewName); } } void UMVVMWidgetBlueprintExtension_View::RenameWidgetExtensions(FName OldWidgetName, FName NewWidgetName) { for (FMVVMExtensionItem& Extension : BlueprintExtensions) { if (Extension.WidgetName == OldWidgetName) { Modify(); Extension.WidgetName = NewWidgetName; if (Extension.ExtensionObj) { Extension.ExtensionObj->WidgetRenamed(OldWidgetName, NewWidgetName); } } } } void UMVVMWidgetBlueprintExtension_View::HandlePreloadObjectsForCompilation(UBlueprint* OwningBlueprint) { if (IsInGameThread() && BlueprintView) { BlueprintView->ConditionalPostLoad(); for (const FMVVMBlueprintViewModelContext& AvailableViewModel : BlueprintView->GetViewModels()) { if (AvailableViewModel.InstancedViewModel) { UBlueprint::ForceLoad(AvailableViewModel.InstancedViewModel); AvailableViewModel.InstancedViewModel->GenerateClass(true); } if (AvailableViewModel.GetViewModelClass()) { UBlueprint::ForceLoad(AvailableViewModel.GetViewModelClass()); } } for (FMVVMBlueprintViewBinding& Binding : BlueprintView->GetBindings()) { if (Binding.Conversion.DestinationToSourceConversion) { UBlueprint::ForceLoad(Binding.Conversion.DestinationToSourceConversion); } if (Binding.Conversion.SourceToDestinationConversion) { UBlueprint::ForceLoad(Binding.Conversion.SourceToDestinationConversion); } } } } void UMVVMWidgetBlueprintExtension_View::HandleBeginCompilation(FWidgetBlueprintCompilerContext& InCreationContext) { VerifyWidgetExtensions(); for (const FMVVMBlueprintViewModelContext& AvailableViewModel : BlueprintView->GetViewModels()) { #if WITH_EDITORONLY_DATA UWidgetBlueprint* WidgetBlueprint = GetWidgetBlueprint(); UClass* ViewModelClass = AvailableViewModel.GetViewModelClass(); if (WidgetBlueprint && ViewModelClass) { if (UBlueprint* ViewModelBP = Cast(ViewModelClass->ClassGeneratedBy)) { ViewModelBP->CachedDependents.Add(WidgetBlueprint); WidgetBlueprint->CachedDependencies.Add(ViewModelBP); } } #endif // WITH_EDITORONLY_DATA if (AvailableViewModel.InstancedViewModel) { AvailableViewModel.InstancedViewModel->GenerateClass(false); } } CurrentCompilerContext.Reset(); GeneratedFunctions.Reset(); if (BlueprintView) { BlueprintView->ResetBindingMessages(); for (UMVVMBlueprintViewEvent* ViewEvent : BlueprintView->GetEvents()) { ViewEvent->ResetCompilationMessages(); } CurrentCompilerContext = MakePimpl(InCreationContext, GetBlueprintView()); } } void UMVVMWidgetBlueprintExtension_View::HandleEndCompilation() { CurrentCompilerContext.Reset(); } void UMVVMWidgetBlueprintExtension_View::HandleCleanAndSanitizeClass(UWidgetBlueprintGeneratedClass* ClassToClean, UObject* OldCDO) { Super::HandleCleanAndSanitizeClass(ClassToClean, OldCDO); if (CurrentCompilerContext) { CurrentCompilerContext->CleanOldData(ClassToClean, OldCDO); } } void UMVVMWidgetBlueprintExtension_View::HandlePopulateGeneratedVariables(const FWidgetBlueprintCompilerContext::FPopulateGeneratedVariablesContext& Context) { Super::HandlePopulateGeneratedVariables(Context); CurrentCompilerContext->GatherGeneratedVariables(Context); } void UMVVMWidgetBlueprintExtension_View::HandleCreateClassVariablesFromBlueprint(const FWidgetBlueprintCompilerContext::FCreateVariableContext& Context) { Super::HandleCreateClassVariablesFromBlueprint(Context); CurrentCompilerContext->CreateVariables(Context); } void UMVVMWidgetBlueprintExtension_View::HandleCreateFunctionList(const FWidgetBlueprintCompilerContext::FCreateFunctionContext& Context) { Super::HandleCreateFunctionList(Context); if (CurrentCompilerContext) { CurrentCompilerContext->CreateFunctions(Context); } } void UMVVMWidgetBlueprintExtension_View::HandleFinishCompilingClass(UWidgetBlueprintGeneratedClass* Class) { Super::HandleFinishCompilingClass(Class); check(CurrentCompilerContext); if (CurrentCompilerContext->GetCompilerContext().bIsFullCompile) { UMVVMViewClass* ViewExtension = nullptr; bool bCompiled = false; if (CurrentCompilerContext->PreCompile(Class)) { FName ClassName = "ViewClass"; if (UObject* PreviousObj = StaticFindObjectFastInternal(nullptr, Class, ClassName, true)) { // Remove previous object. ERenameFlags RenameFlags = REN_NonTransactional | REN_DoNotDirty | REN_DontCreateRedirectors; FName TrashName = MakeUniqueObjectName(GetTransientPackage(), PreviousObj->GetClass(), *FString::Printf(TEXT("TRASH_%s"), *PreviousObj->GetName())); PreviousObj->Rename(*TrashName.ToString(), GetTransientPackage(), RenameFlags); } ViewExtension = NewObject(Class); bCompiled = CurrentCompilerContext->Compile(Class, ViewExtension); } if (bCompiled) { check(ViewExtension); // Does it have any bindings if (ViewExtension->GetBindings().Num() > 0 || ViewExtension->GetEvents().Num() > 0 || ViewExtension->GetConditions().Num() > 0 || (GetBlueprintView()->GetViewModels().Num() > 0 && ViewExtension->IsCreatedWithoutBindings())) { // Test if parent also has a view if (Class->GetExtension(true)) { CurrentCompilerContext->GetCompilerContext().MessageLog.Warning(*LOCTEXT("MoreThanOneViewWarning", "There is more than one view. This can happen when inheriting from a widget blueprint that also contains a view. Please be aware that the parent view model is not available to the child widget.").ToString()); } // If we are not allowed to add the view class if (UE::MVVM::Private::GAllowViewClass) { CurrentCompilerContext->AddExtension(Class, ViewExtension); } } } } GeneratedFunctions = CurrentCompilerContext->GetGeneratedFunctions(); // If we can't auto-generate function add the transient flags if (UE::MVVM::Private::GAutogeneratedFunctionsAreTransient) { // If we are not allowed to add the view class, add the transient flags on added conversion graphs and event graphs. for (TFieldIterator FunctionIter(Class, EFieldIteratorFlags::ExcludeSuper); FunctionIter; ++FunctionIter) { UFunction* Function = *FunctionIter; Function->SetFlags(RF_Transient); } } } UMVVMWidgetBlueprintExtension_View::FSearchData UMVVMWidgetBlueprintExtension_View::HandleGatherSearchData(const UBlueprint* OwningBlueprint) const { UMVVMWidgetBlueprintExtension_View::FSearchData SearchData; if (GetBlueprintView()) { { TUniquePtr ViewModelContextSearchData = MakeUnique(); ViewModelContextSearchData->Identifier = LOCTEXT("ViewmodelSearchTag", "Viewmodels"); for (const FMVVMBlueprintViewModelContext& ViewModelContext : GetBlueprintView()->GetViewModels()) { FSearchData& ViewModelSearchData = ViewModelContextSearchData->SearchSubList.AddDefaulted_GetRef(); ViewModelSearchData.Datas.Emplace(LOCTEXT("ViewmodelGuidSearchTag", "Guid"), FText::FromString(ViewModelContext.GetViewModelId().ToString(EGuidFormats::Digits))); ViewModelSearchData.Datas.Emplace(FFindInBlueprintSearchTags::FiB_Name, ViewModelContext.GetDisplayName()); ViewModelSearchData.Datas.Emplace(FFindInBlueprintSearchTags::FiB_ClassName, ViewModelContext.GetViewModelClass() ? ViewModelContext.GetViewModelClass()->GetDisplayNameText() : FText::GetEmpty()); ViewModelSearchData.Datas.Emplace(LOCTEXT("ViewmodelCreationTypeSearchTag", "CreationType"), StaticEnum()->GetDisplayNameTextByValue((int64)ViewModelContext.CreationType)); } SearchData.SearchArrayDatas.Add(MoveTemp(ViewModelContextSearchData)); } { TUniquePtr BindingContextSearchData = MakeUnique(); BindingContextSearchData->Identifier = LOCTEXT("BindingSearchTag", "Bindings"); for (const FMVVMBlueprintViewBinding& Binding : GetBlueprintView()->GetBindings()) { FSearchData& BindingSearchData = BindingContextSearchData->SearchSubList.AddDefaulted_GetRef(); BindingSearchData.Datas.Emplace(LOCTEXT("ViewBindingSearchTag", "Binding"), FText::FromString(Binding.GetDisplayNameString(GetWidgetBlueprint()))); } SearchData.SearchArrayDatas.Add(MoveTemp(BindingContextSearchData)); } } return SearchData; } void UMVVMWidgetBlueprintExtension_View::SetFilterSettings(FMVVMViewBindingFilterSettings InFilterSettings) { FilterSettings = InFilterSettings; } #if WITH_EDITORONLY_DATA void UMVVMWidgetBlueprintExtension_View::PostInitProperties() { Super::PostInitProperties(); if (!IsTemplate()) { SetFilterSettings(GetDefault()->FilterSettings); } } #endif #undef LOCTEXT_NAMESPACE