Files
UnrealEngine/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp
2025-05-18 13:04:45 +08:00

3193 lines
109 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WidgetBlueprintEditorUtils.h"
#include "Components/PanelSlot.h"
#include "Components/PanelWidget.h"
#include "Components/ContentWidget.h"
#include "Engine/Texture2D.h"
#include "Interfaces/IPluginManager.h"
#include "UObject/GCObjectScopeGuard.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "Internationalization/TextPackageNamespaceUtil.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/TopLevelAssetPath.h"
#include "Blueprint/WidgetTree.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/PathViews.h"
#include "Modules/ModuleManager.h"
#include "MovieScene.h"
#include "UMGEditorProjectSettings.h"
#include "WidgetBlueprint.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Settings/ContentBrowserSettings.h"
#include "ClassViewerFilter.h"
#include "EditorClassUtils.h"
#include "Dialogs/Dialogs.h"
#include "DragAndDrop/DecoratedDragDropOp.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/ClassDragDropOp.h"
#include "DragDrop/WidgetTemplateDragDropOp.h"
#include "Exporters/Exporter.h"
#include "ObjectEditorUtils.h"
#include "Components/CanvasPanelSlot.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Animation/WidgetAnimation.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Templates/WidgetTemplateClass.h"
#include "Templates/WidgetTemplateImageClass.h"
#include "Templates/WidgetTemplateBlueprintClass.h"
#include "Factories.h"
#include "UnrealExporter.h"
#include "Framework/Commands/GenericCommands.h"
#include "ScopedTransaction.h"
#include "Components/CanvasPanel.h"
#include "Utility/WidgetSlotPair.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Components/Widget.h"
#include "Blueprint/WidgetNavigation.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "UObject/CoreRedirects.h"
#include "UObject/ScriptInterface.h"
#include "Components/NamedSlotInterface.h"
#include "K2Node_Variable.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/UserInterfaceSettings.h"
#include "Input/HittestGrid.h"
#include "Interfaces/ISlateRHIRendererModule.h"
#include "Interfaces/ISlate3DRenderer.h"
#include "Rendering/SlateDrawBuffer.h"
#include "Slate/WidgetRenderer.h"
#include "UMGEditorModule.h"
#include "Widgets/SVirtualWindow.h"
#include "GraphEditorActions.h"
#include "WidgetEditingProjectSettings.h"
#include "UIComponentUtils.h"
#define LOCTEXT_NAMESPACE "UMG"
class FWidgetObjectTextFactory : public FCustomizableTextObjectFactory
{
public:
FWidgetObjectTextFactory()
: FCustomizableTextObjectFactory(GWarn)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* ObjectClass, bool& bOmitSubObjs) const override
{
const bool bIsWidget = ObjectClass->IsChildOf(UWidget::StaticClass());
const bool bIsSlot = ObjectClass->IsChildOf(UPanelSlot::StaticClass());
const bool bIsSlotMetaData = ObjectClass->IsChildOf(UWidgetSlotPair::StaticClass());
return bIsWidget || bIsSlot || bIsSlotMetaData;
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
if ( UWidget* Widget = Cast<UWidget>(NewObject) )
{
NewWidgetMap.Add(Widget->GetFName(), Widget);
}
else if ( UWidgetSlotPair* SlotMetaData = Cast<UWidgetSlotPair>(NewObject) )
{
MissingSlotData.Add(SlotMetaData->GetWidgetName(), SlotMetaData);
}
}
// FCustomizableTextObjectFactory (end)
public:
// Name->Instance object mapping
TMap<FName, UWidget*> NewWidgetMap;
// Instance->OldSlotMetaData that didn't survive the journey because it wasn't copied.
TMap<FName, UWidgetSlotPair*> MissingSlotData;
};
FName SanitizeWidgetName(const FString& NewName, const FName CurrentName)
{
FString GeneratedName = SlugStringForValidName(NewName);
// If the new name is empty (for example, because it was composed entirely of invalid characters).
// then we'll use the current name
if (GeneratedName.IsEmpty())
{
return CurrentName;
}
const FName GeneratedFName(*GeneratedName);
check(GeneratedFName.IsValidXName(INVALID_OBJECTNAME_CHARACTERS));
return GeneratedFName;
}
bool FWidgetBlueprintEditorUtils::VerifyWidgetRename(TSharedRef<class FWidgetBlueprintEditor> BlueprintEditor, FWidgetReference Widget, const FText& NewName, FText& OutErrorMessage)
{
if (NewName.IsEmptyOrWhitespace())
{
OutErrorMessage = LOCTEXT("EmptyWidgetName", "Empty Widget Name");
return false;
}
const FString& NewNameString = NewName.ToString();
if (NewNameString.Len() >= NAME_SIZE)
{
OutErrorMessage = LOCTEXT("WidgetNameTooLong", "Widget Name is Too Long");
return false;
}
UWidget* RenamedTemplateWidget = Widget.GetTemplate();
if ( !RenamedTemplateWidget )
{
// In certain situations, the template might be lost due to mid recompile with focus lost on the rename box at
// during a strange moment.
return false;
}
// Slug the new name down to a valid object name
const FName NewNameSlug = SanitizeWidgetName(NewNameString, RenamedTemplateWidget->GetFName());
UWidgetBlueprint* Blueprint = BlueprintEditor->GetWidgetBlueprintObj();
UWidget* ExistingTemplate = Blueprint->WidgetTree->FindWidget(NewNameSlug);
bool bIsSameWidget = false;
if (ExistingTemplate != nullptr)
{
if ( RenamedTemplateWidget != ExistingTemplate )
{
OutErrorMessage = LOCTEXT("ExistingWidgetName", "Existing Widget Name");
return false;
}
else
{
bIsSameWidget = true;
}
}
else
{
// Not an existing widget in the tree BUT it still mustn't create a UObject name clash
UWidget* WidgetPreview = Widget.GetPreview();
if (WidgetPreview)
{
// Dummy rename with flag REN_Test returns if rename is possible
if (!WidgetPreview->Rename(*NewNameSlug.ToString(), nullptr, REN_Test))
{
OutErrorMessage = LOCTEXT("ExistingObjectName", "Existing Object Name");
return false;
}
}
UWidget* WidgetTemplate = RenamedTemplateWidget;
// Dummy rename with flag REN_Test returns if rename is possible
if (!WidgetTemplate->Rename(*NewNameSlug.ToString(), nullptr, REN_Test))
{
OutErrorMessage = LOCTEXT("ExistingObjectName", "Existing Object Name");
return false;
}
}
FObjectPropertyBase* Property = CastField<FObjectPropertyBase>(Blueprint->ParentClass->FindPropertyByName( NewNameSlug ));
if ( Property && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(Property))
{
if (!RenamedTemplateWidget->IsA(Property->PropertyClass))
{
OutErrorMessage = FText::Format(LOCTEXT("WidgetBindingOfWrongType", "Widget Binding is not type {0}"), Property->PropertyClass->GetDisplayNameText());
return false;
}
return true;
}
FKismetNameValidator Validator(Blueprint);
// For variable comparison, use the slug
EValidatorResult ValidatorResult = Validator.IsValid(NewNameSlug);
if (ValidatorResult != EValidatorResult::Ok)
{
if (bIsSameWidget && (ValidatorResult == EValidatorResult::AlreadyInUse || ValidatorResult == EValidatorResult::ExistingName))
{
// Continue successfully
}
else
{
OutErrorMessage = INameValidatorInterface::GetErrorText(NewNameString, ValidatorResult);
return false;
}
}
return true;
}
void FWidgetBlueprintEditorUtils::SetDesiredFocus(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, const FName DesiredFocusWidgetName)
{
SetDesiredFocus(BlueprintEditor->GetWidgetBlueprintObj(), DesiredFocusWidgetName);
}
void FWidgetBlueprintEditorUtils::SetDesiredFocus(UWidgetBlueprint* Blueprint, const FName& DesiredFocusWidgetName)
{
if (Blueprint)
{
if (Blueprint->GeneratedClass)
{
if (UUserWidget* WidgetCDO = Blueprint->GeneratedClass->GetDefaultObject<UUserWidget>())
{
WidgetCDO->SetFlags(RF_Transactional);
WidgetCDO->Modify();
WidgetCDO->SetDesiredFocusWidget(DesiredFocusWidgetName);
}
}
constexpr bool bFocusIfOpen = false;
FWidgetBlueprintEditor* BlueprintEditor = static_cast<FWidgetBlueprintEditor*>(GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Blueprint, bFocusIfOpen));
if (BlueprintEditor)
{
if (UUserWidget* PreviewWidget = BlueprintEditor->GetPreview())
{
// We need to change the PreviewWidget to make sure the DetailPanel show the right value.
PreviewWidget->SetFlags(RF_Transactional);
PreviewWidget->Modify();
PreviewWidget->SetDesiredFocusWidget(DesiredFocusWidgetName);
}
}
}
}
void FWidgetBlueprintEditorUtils::ReplaceDesiredFocus(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, const FName& OldName, const FName& NewName)
{
ReplaceDesiredFocus(BlueprintEditor->GetWidgetBlueprintObj(), OldName, NewName);
}
void FWidgetBlueprintEditorUtils::ReplaceDesiredFocus(UWidgetBlueprint* Blueprint, const FName& OldName, const FName& NewName)
{
if (Blueprint && Blueprint->GeneratedClass)
{
if (UUserWidget* WidgetCDO = Blueprint->GeneratedClass->GetDefaultObject<UUserWidget>())
{
// Verify if the Name changed is the Desired focus Widget name.
if (WidgetCDO->GetDesiredFocusWidgetName() == OldName)
{
WidgetCDO->SetFlags(RF_Transactional);
WidgetCDO->Modify();
WidgetCDO->SetDesiredFocusWidget(NewName);
constexpr bool bFocusIfOpen = false;
FWidgetBlueprintEditor* BlueprintEditor = static_cast<FWidgetBlueprintEditor*>(GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Blueprint, bFocusIfOpen));
if (BlueprintEditor)
{
if (UUserWidget* PreviewWidget = BlueprintEditor->GetPreview())
{
ensure(PreviewWidget->GetDesiredFocusWidgetName() == OldName);
// We need to change the PreviewWidget to make sure the DetailPanel show the right value.
PreviewWidget->SetFlags(RF_Transactional);
PreviewWidget->Modify();
PreviewWidget->SetDesiredFocusWidget(NewName);
}
}
}
}
}
}
bool FWidgetBlueprintEditorUtils::RenameWidget(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, const FName& OldObjectName, const FString& NewDisplayName)
{
UWidgetBlueprint* Blueprint = BlueprintEditor->GetWidgetBlueprintObj();
check(Blueprint);
UWidget* Widget = Blueprint->WidgetTree->FindWidget(OldObjectName);
check(Widget);
UClass* ParentClass = Blueprint->ParentClass;
check( ParentClass );
bool bRenamed = false;
TSharedPtr<INameValidatorInterface> NameValidator = MakeShareable(new FKismetNameValidator(Blueprint, OldObjectName));
const FName NewFName = SanitizeWidgetName(NewDisplayName, Widget->GetFName());
FObjectPropertyBase* ExistingProperty = CastField<FObjectPropertyBase>(ParentClass->FindPropertyByName(NewFName));
const bool bBindWidget = ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && Widget->IsA(ExistingProperty->PropertyClass);
// NewName should be already validated. But one must make sure that NewTemplateName is also unique.
const bool bUniqueNameForTemplate = ( EValidatorResult::Ok == NameValidator->IsValid( NewFName ) || bBindWidget );
if ( bUniqueNameForTemplate )
{
// Stringify the FNames
const FString NewNameStr = NewFName.ToString();
const FString OldNameStr = OldObjectName.ToString();
const FScopedTransaction Transaction(LOCTEXT("RenameWidget", "Rename Widget"));
// Rename Template
Blueprint->Modify();
Widget->Modify();
Blueprint->OnVariableRenamed(OldObjectName, NewFName);
// Rename Preview before renaming the template widget so the preview widget can be found
UWidget* WidgetPreview = BlueprintEditor->GetReferenceFromTemplate(Widget).GetPreview();
if (WidgetPreview)
{
WidgetPreview->SetDisplayLabel(NewDisplayName);
WidgetPreview->Rename(*NewNameStr);
}
if (!WidgetPreview || WidgetPreview != Widget)
{
// Find and update all variable references in the graph
Widget->SetDisplayLabel(NewDisplayName);
Widget->Rename(*NewNameStr);
}
#if UE_HAS_WIDGET_GENERATED_BY_CLASS
// When a widget gets renamed we need to check any existing blueprint getters that may be placed
// in the graphs to fix up their state
if(Widget->bIsVariable)
{
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for (const UEdGraph* CurrentGraph : AllGraphs)
{
TArray<UK2Node_Variable*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
for (UK2Node_Variable* CurrentNode : GraphNodes)
{
UClass* SelfClass = Blueprint->GeneratedClass;
UClass* VariableParent = CurrentNode->VariableReference.GetMemberParentClass(SelfClass);
if (SelfClass == VariableParent)
{
// Reconstruct this node in order to give it orphan pins and invalidate any
// connections that will no longer be valid
if (NewFName == CurrentNode->GetVarName())
{
UEdGraphPin* ValuePin = CurrentNode->GetValuePin();
ValuePin->Modify();
CurrentNode->Modify();
// Make the old pin an orphan and add a new pin of the proper type
UEdGraphPin* NewPin = CurrentNode->CreatePin(
ValuePin->Direction,
ValuePin->PinType.PinCategory,
ValuePin->PinType.PinSubCategory,
Widget->WidgetGeneratedByClass.Get(), // This generated object is what needs to patched up
NewFName
);
ValuePin->bOrphanedPin = true;
}
}
}
}
}
#endif
// Replace the Desired focus Widget name if it match the renamed widget
ReplaceDesiredFocus(BlueprintEditor, OldObjectName, NewFName);
// Find and update all binding references in the widget blueprint
for ( FDelegateEditorBinding& Binding : Blueprint->Bindings )
{
if ( Binding.ObjectName == OldNameStr )
{
Binding.ObjectName = NewNameStr;
}
}
// Update widget blueprint names
for( UWidgetAnimation* WidgetAnimation : Blueprint->Animations )
{
for( FWidgetAnimationBinding& AnimBinding : WidgetAnimation->AnimationBindings )
{
if( AnimBinding.WidgetName == OldObjectName )
{
AnimBinding.WidgetName = NewFName;
WidgetAnimation->MovieScene->Modify();
if (AnimBinding.SlotWidgetName == NAME_None)
{
FMovieScenePossessable* Possessable = WidgetAnimation->MovieScene->FindPossessable(AnimBinding.AnimationGuid);
if (Possessable)
{
Possessable->SetName(NewFName.ToString());
}
}
else
{
break;
}
}
}
}
// Update any explicit widget bindings.
Blueprint->WidgetTree->ForEachWidget([OldObjectName, NewFName](UWidget* Widget) {
if (Widget->Navigation)
{
Widget->Navigation->SetFlags(RF_Transactional);
Widget->Navigation->Modify();
Widget->Navigation->TryToRenameBinding(OldObjectName, NewFName);
}
});
// If we use Components, make sure to remane the target.
FUIComponentUtils::OnWidgetRenamed(BlueprintEditor, Blueprint, OldObjectName, NewFName);
// Validate child blueprints and adjust variable names to avoid a potential name collision
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewFName);
// Refresh references and flush editors
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
// Update Variable References and
// Update Event References to member variables
FBlueprintEditorUtils::ReplaceVariableReferences(Blueprint, OldObjectName, NewFName);
bRenamed = true;
}
return bRenamed;
}
void FWidgetBlueprintEditorUtils::CreateWidgetContextMenu(FMenuBuilder& MenuBuilder, TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, FVector2D TargetLocation)
{
BlueprintEditor->PasteDropLocation = TargetLocation;
TSet<FWidgetReference> Widgets = BlueprintEditor->GetSelectedWidgets();
UWidgetBlueprint* BP = BlueprintEditor->GetWidgetBlueprintObj();
MenuBuilder.BeginSection("Edit", LOCTEXT("Edit", "Edit"));
{
MenuBuilder.PushCommandList(BlueprintEditor->DesignerCommandList.ToSharedRef());
{
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Cut);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Paste);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete);
// Insert "Find References" sub-menu here
MenuBuilder.AddSubMenu(
LOCTEXT("FindReferences_Label", "Find References"),
LOCTEXT("FindReferences_Tooltip", "Options for finding references to class members"),
FNewMenuDelegate::CreateStatic(&FGraphEditorCommands::BuildFindReferencesMenu),
false,
FSlateIcon()
);
}
MenuBuilder.PopCommandList();
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Actions");
{
MenuBuilder.AddMenuEntry(
LOCTEXT( "EditBlueprint_Label", "Edit Widget Blueprint..." ),
LOCTEXT( "EditBlueprint_Tooltip", "Open the selected Widget Blueprint(s) for edit." ),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic( &FWidgetBlueprintEditorUtils::ExecuteOpenSelectedWidgetsForEdit, Widgets ),
FCanExecuteAction(),
FIsActionChecked(),
FIsActionButtonVisible::CreateStatic( &FWidgetBlueprintEditorUtils::CanOpenSelectedWidgetsForEdit, Widgets )
)
);
if (!FWidgetBlueprintEditorUtils::IsAnySelectedWidgetLocked(Widgets))
{
MenuBuilder.AddSubMenu(
LOCTEXT("WidgetTree_WrapWith", "Wrap With..."),
LOCTEXT("WidgetTree_WrapWithToolTip", "Wraps the currently selected widgets inside of another container widget"),
FNewMenuDelegate::CreateStatic(&FWidgetBlueprintEditorUtils::BuildWrapWithMenu, BlueprintEditor, BP, Widgets)
);
if (Widgets.Num() == 1)
{
MenuBuilder.AddSubMenu(
LOCTEXT("WidgetTree_ReplaceWith", "Replace With..."),
LOCTEXT("WidgetTree_ReplaceWithToolTip", "Replaces the currently selected widget, with another widget"),
FNewMenuDelegate::CreateStatic(&FWidgetBlueprintEditorUtils::BuildReplaceWithMenu, BlueprintEditor, BP, Widgets)
);
}
}
}
MenuBuilder.EndSection();
IUMGEditorModule& EditorModule = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor");
const TArrayView<const TSharedPtr<IWidgetContextMenuExtension>> ContextMenuExtensions = EditorModule.GetWidgetContextMenuExtensibilityManager()->GetExtensions();
for (const TSharedPtr<IWidgetContextMenuExtension>& ContextMenuExtension : ContextMenuExtensions)
{
ContextMenuExtension->ExtendContextMenu(MenuBuilder, BlueprintEditor, TargetLocation);
}
}
void FWidgetBlueprintEditorUtils::ExecuteOpenSelectedWidgetsForEdit( TSet<FWidgetReference> SelectedWidgets )
{
for ( auto& Widget : SelectedWidgets )
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset( Widget.GetTemplate()->GetClass()->ClassGeneratedBy );
}
}
bool FWidgetBlueprintEditorUtils::CanOpenSelectedWidgetsForEdit( TSet<FWidgetReference> SelectedWidgets )
{
bool bCanOpenAllForEdit = SelectedWidgets.Num() > 0;
for ( auto& Widget : SelectedWidgets )
{
auto Blueprint = Widget.GetTemplate()->GetClass()->ClassGeneratedBy;
if ( !Blueprint || !Blueprint->IsA( UWidgetBlueprint::StaticClass() ) )
{
bCanOpenAllForEdit = false;
break;
}
}
return bCanOpenAllForEdit;
}
void FWidgetBlueprintEditorUtils::DeleteWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* Blueprint, TSet<FWidgetReference> Widgets, bool bSilentDelete /*=false*/)
{
DeleteWidgets(Blueprint, ResolveWidgetTemplates(Widgets), bSilentDelete ? EDeleteWidgetWarningType::DeleteSilently : EDeleteWidgetWarningType::WarnAndAskUser);
}
void FWidgetBlueprintEditorUtils::DeleteWidgets(UWidgetBlueprint* Blueprint, TSet<UWidget*> Widgets, EDeleteWidgetWarningType WarningType)
{
if ( Widgets.Num() > 0 )
{
// Check if the widgets are used in the graph
FScopedTransaction Transaction(LOCTEXT("RemoveWidget", "Remove Widget"));
TArray<UWidget*> UsedVariables;
TArray<FText> WidgetNames;
const bool bIncludeChildrenVariables = true;
FindUsedVariablesForWidgets(Widgets, Blueprint, UsedVariables, WidgetNames, bIncludeChildrenVariables);
if (WarningType == EDeleteWidgetWarningType::WarnAndAskUser && UsedVariables.Num()!= 0 && !ShouldContinueDeleteOperation(Blueprint, WidgetNames))
{
Transaction.Cancel();
return;
}
Blueprint->WidgetTree->SetFlags(RF_Transactional);
Blueprint->WidgetTree->Modify();
Blueprint->Modify();
bool bRemoved = false;
for (UWidget* Item : Widgets)
{
UWidget* WidgetTemplate = Item;
WidgetTemplate->SetFlags(RF_Transactional);
const FName WidgetName = WidgetTemplate->GetFName();
// Find and update all binding references in the widget blueprint
for (int32 BindingIndex = Blueprint->Bindings.Num() - 1; BindingIndex >= 0; BindingIndex--)
{
FDelegateEditorBinding& Binding = Blueprint->Bindings[BindingIndex];
if (Binding.ObjectName == WidgetTemplate->GetName())
{
Blueprint->Bindings.RemoveAt(BindingIndex);
}
}
// Modify the widget's parent
UPanelWidget* Parent = WidgetTemplate->GetParent();
if ( Parent )
{
Parent->SetFlags(RF_Transactional);
Parent->Modify();
}
// Modify the widget being removed.
WidgetTemplate->Modify();
bRemoved |= Blueprint->WidgetTree->RemoveWidget(WidgetTemplate);
// If the widget we're removing doesn't have a parent it may be rooted in a named slot,
// so check there as well.
if ( WidgetTemplate->GetParent() == nullptr )
{
bRemoved |= FindAndRemoveNamedSlotContent(WidgetTemplate, Blueprint->WidgetTree);
}
if (UsedVariables.Contains(WidgetTemplate))
{
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, WidgetTemplate->GetFName());
}
// Rename the Desired Focus that fit the Widget Deleted
ReplaceDesiredFocus(Blueprint, WidgetTemplate->GetFName(), FName());
// Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name.
WidgetTemplate->Rename(nullptr, GetTransientPackage());
// Deletion can sometimes happen from replacing a widget with another one with the same name, so only delete the variable data if we no longer have a widget with the same name
const bool bHasWidgetWithSameName = Blueprint->GetAllSourceWidgets().ContainsByPredicate([WidgetName](const UWidget* Widget)
{
return WidgetName == Widget->GetFName();
});
if (!bHasWidgetWithSameName)
{
Blueprint->OnVariableRemoved(WidgetName);
}
// Rename all child widgets as well, to the transient package so that they don't conflict with future widgets sharing the same name.
TArray<UWidget*> ChildWidgets;
UWidgetTree::GetChildWidgets(WidgetTemplate, ChildWidgets);
for ( UWidget* Widget : ChildWidgets )
{
const FName ChildWidgetName = Widget->GetFName();
Widget->SetFlags(RF_Transactional);
Widget->Modify();
if (UsedVariables.Contains(Widget))
{
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, Widget->GetFName());
}
Widget->Rename(nullptr, GetTransientPackage());
// Deletion can sometimes happen from replacing a widget with another one with the same name, so only delete the variable data if we no longer have a widget with the same name
const bool bHasChildWidgetWithSameName = Blueprint->GetAllSourceWidgets().ContainsByPredicate([ChildWidgetName](const UWidget* Widget)
{
return ChildWidgetName == Widget->GetFName();
});
if (!bHasChildWidgetWithSameName)
{
Blueprint->OnVariableRemoved(ChildWidgetName);
}
}
}
//TODO UMG There needs to be an event for widget removal so that caches can be updated, and selection
if ( bRemoved )
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
}
void FWidgetBlueprintEditorUtils::FindUsedVariablesForWidgets(const TSet<UWidget*>& Widgets, const UWidgetBlueprint* BP, TArray<UWidget*>& UsedVariables, TArray<FText>& WidgetNames, bool bIncludeVariablesOnChildren)
{
TSet<UWidget*> AllWidgets;
AllWidgets.Reserve(Widgets.Num());
for (UWidget* Item : Widgets)
{
AllWidgets.Add(Item);
if (bIncludeVariablesOnChildren)
{
TArray<UWidget*> ChildWidgets;
UWidgetTree::GetChildWidgets(Item, ChildWidgets);
AllWidgets.Append(ChildWidgets);
}
}
for (UWidget* Widget : AllWidgets)
{
if (FBlueprintEditorUtils::IsVariableUsed(BP, Widget->GetFName()))
{
WidgetNames.Add(FText::FromName(Widget->GetFName()));
UsedVariables.Add(Widget);
}
}
}
bool FWidgetBlueprintEditorUtils::ShouldContinueDeleteOperation(UWidgetBlueprint* BP, const TArray<FText>& WidgetNames)
{
// If the Widget is used in the graph ask the user before we continue.
if (WidgetNames.Num())
{
FText ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteVariableInUse", "One or more widgets are in use in the graph! Do you really want to delete them? \n\n {0}"),
FText::Join(LOCTEXT("ConfirmDeleteVariableInUsedDelimiter", " \n "), WidgetNames));
// Warn the user that this may result in data loss
FSuppressableWarningDialog::FSetupInfo Info(ConfirmDelete, LOCTEXT("DeleteVar", "Delete widgets"), "DeleteWidgetsInUse_Warning");
Info.ConfirmText = LOCTEXT("DeleteVariable_Yes", "Yes");
Info.CancelText = LOCTEXT("DeleteVariable_No", "No");
FSuppressableWarningDialog DeleteVariableInUse(Info);
if (DeleteVariableInUse.ShowModal() == FSuppressableWarningDialog::Cancel)
{
return false;
}
}
return true;
}
bool FWidgetBlueprintEditorUtils::ShouldContinueReplaceOperation(UWidgetBlueprint* BP, const TArray<FText>& WidgetNames)
{
// If the Widget is used in the graph ask the user before we continue.
if (WidgetNames.Num())
{
FText ConfirmDelete = FText::Format(LOCTEXT("ConfirmReplaceWidgetWithVariableInUse", "One or more widgets you want to replace are in use in the graph! Do you really want to replace them? \n\n {0}"),
FText::Join(LOCTEXT("ConfirmDeleteVariableInUsedDelimiter", " \n "), WidgetNames));
// Warn the user that this may result in data loss
FSuppressableWarningDialog::FSetupInfo Info(ConfirmDelete, LOCTEXT("ReplaceWidgetVar", "Replace widgets"), "ReaplaceWidgetsInUse_Warning");
Info.ConfirmText = LOCTEXT("ReplaceWidget_Yes", "Yes");
Info.CancelText = LOCTEXT("ReplaceWidget_No", "No");
FSuppressableWarningDialog DeleteVariableInUse(Info);
if (DeleteVariableInUse.ShowModal() == FSuppressableWarningDialog::Cancel)
{
return false;
}
}
return true;
}
TScriptInterface<INamedSlotInterface> FWidgetBlueprintEditorUtils::FindNamedSlotHostForContent(UWidget* WidgetTemplate, UWidgetTree* WidgetTree)
{
// If the named slot comes from a parent widget class, the WidgetTree will be the SlotHost
TArray<FName> SlotNames;
WidgetTree->GetSlotNames(SlotNames);
for (FName SlotName : SlotNames)
{
if (UWidget* SlotContent = WidgetTree->GetContentForSlot(SlotName))
{
if (SlotContent == WidgetTemplate)
{
return WidgetTree;
}
}
}
return TScriptInterface<INamedSlotInterface>(FindNamedSlotHostWidgetForContent(WidgetTemplate, WidgetTree));
}
UWidget* FWidgetBlueprintEditorUtils::FindNamedSlotHostWidgetForContent(UWidget* WidgetTemplate, UWidgetTree* WidgetTree)
{
UWidget* HostWidget = nullptr;
WidgetTree->ForEachWidget([&](UWidget* Widget) {
if (HostWidget != nullptr)
{
return;
}
if (INamedSlotInterface* NamedSlotHost = Cast<INamedSlotInterface>(Widget))
{
TArray<FName> SlotNames;
NamedSlotHost->GetSlotNames(SlotNames);
for (FName SlotName : SlotNames)
{
if (UWidget* SlotContent = NamedSlotHost->GetContentForSlot(SlotName))
{
if (SlotContent == WidgetTemplate)
{
HostWidget = Widget;
}
}
}
}
});
return HostWidget;
}
void FWidgetBlueprintEditorUtils::FindAllAncestorNamedSlotHostWidgetsForContent(TArray<FWidgetReference>& OutSlotHostWidgets, UWidget* WidgetTemplate, TSharedRef<FWidgetBlueprintEditor> BlueprintEditor)
{
OutSlotHostWidgets.Empty();
UUserWidget* Preview = BlueprintEditor->GetPreview();
UWidgetBlueprint* WidgetBP = BlueprintEditor->GetWidgetBlueprintObj();
UWidgetTree* WidgetTree = (WidgetBP != nullptr) ? ToRawPtr(WidgetBP->WidgetTree) : nullptr;
if (Preview != nullptr && WidgetTree != nullptr)
{
// Find the first widget up the chain with a null parent, they're the only candidates for this approach.
while (WidgetTemplate && WidgetTemplate->GetParent())
{
WidgetTemplate = WidgetTemplate->GetParent();
}
UWidget* SlotHostWidget = FindNamedSlotHostWidgetForContent(WidgetTemplate, WidgetTree);
while (SlotHostWidget != nullptr)
{
UWidget* SlotWidget = Preview->GetWidgetFromName(SlotHostWidget->GetFName());
FWidgetReference WidgetRef;
if (SlotWidget != nullptr)
{
WidgetRef = BlueprintEditor->GetReferenceFromPreview(SlotWidget);
if (WidgetRef.IsValid())
{
OutSlotHostWidgets.Add(WidgetRef);
}
}
WidgetTemplate = WidgetRef.GetTemplate();
SlotHostWidget = nullptr;
if (WidgetTemplate != nullptr)
{
// Find the first widget up the chain with a null parent, they're the only candidates for this approach.
while (WidgetTemplate->GetParent())
{
WidgetTemplate = WidgetTemplate->GetParent();
}
SlotHostWidget = FindNamedSlotHostWidgetForContent(WidgetRef.GetTemplate(), WidgetTree);
}
}
}
}
bool FWidgetBlueprintEditorUtils::RemoveNamedSlotHostContent(UWidget* WidgetTemplate, TScriptInterface<INamedSlotInterface> NamedSlotHost)
{
return ReplaceNamedSlotHostContent(WidgetTemplate, NamedSlotHost, nullptr);
}
bool FWidgetBlueprintEditorUtils::ReplaceNamedSlotHostContent(UWidget* WidgetTemplate, TScriptInterface<INamedSlotInterface> NamedSlotHost, UWidget* NewContentWidget)
{
TArray<FName> SlotNames;
NamedSlotHost->GetSlotNames(SlotNames);
for (FName SlotName : SlotNames)
{
if (UWidget* SlotContent = NamedSlotHost->GetContentForSlot(SlotName))
{
if (SlotContent == WidgetTemplate)
{
NamedSlotHost.GetObject()->Modify();
if (UPanelWidget* NamedSlot = WidgetTemplate->GetParent())
{
// Make sure we also mark the named slot as modified to properly track changes in it.
NamedSlot->Modify();
}
if (NewContentWidget)
{
NewContentWidget->Modify();
if (UPanelWidget* Parent = NewContentWidget->GetParent())
{
Parent->Modify();
NewContentWidget->RemoveFromParent();
}
}
NamedSlotHost->SetContentForSlot(SlotName, NewContentWidget);
return true;
}
}
}
return false;
}
bool FWidgetBlueprintEditorUtils::FindAndRemoveNamedSlotContent(UWidget* WidgetTemplate, UWidgetTree* WidgetTree)
{
UWidget* NamedSlotHostWidget = FindNamedSlotHostWidgetForContent(WidgetTemplate, WidgetTree);
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = TScriptInterface<INamedSlotInterface>(NamedSlotHostWidget) )
{
NamedSlotHostWidget->Modify();
return RemoveNamedSlotHostContent(WidgetTemplate, NamedSlotHost);
}
return false;
}
void FWidgetBlueprintEditorUtils::BuildWrapWithMenu(FMenuBuilder& Menu, TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
TArray<UClass*> WrapperClasses;
for ( TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt )
{
UClass* WidgetClass = *ClassIt;
if ( FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass, BlueprintEditor) )
{
if ( WidgetClass->IsChildOf(UPanelWidget::StaticClass()) && !WidgetClass->HasAnyClassFlags(CLASS_HideDropDown) )
{
WrapperClasses.Add(WidgetClass);
}
}
}
WrapperClasses.Sort([] (UClass& Lhs, UClass& Rhs) { return Lhs.GetDisplayNameText().CompareTo(Rhs.GetDisplayNameText()) < 0; });
Menu.BeginSection("WrapWith", LOCTEXT("WidgetTree_WrapWith", "Wrap With..."));
{
for ( UClass* WrapperClass : WrapperClasses )
{
Menu.AddMenuEntry(
WrapperClass->GetDisplayNameText(),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::WrapWidgets, BlueprintEditor, BP, Widgets, WrapperClass),
FCanExecuteAction()
));
}
}
Menu.EndSection();
}
void FWidgetBlueprintEditorUtils::WrapWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets, UClass* WidgetClass)
{
const FScopedTransaction Transaction(LOCTEXT("WrapWidgets", "Wrap Widgets"));
TSharedPtr<FWidgetTemplateClass> Template = MakeShareable(new FWidgetTemplateClass(WidgetClass));
// When selecting multiple widgets, we only want to create a new wrapping widget around the root-most set of widgets
// So find any that children of other selected widgets, and skip them (because their parents will be wrapped)
TSet<FWidgetReference> WidgetsToRemove;
for (FWidgetReference& Item : Widgets)
{
int32 OutIndex;
UPanelWidget* CurrentParent = BP->WidgetTree->FindWidgetParent(Item.GetTemplate(), OutIndex);
for (FWidgetReference& OtherItem : Widgets)
{
if (OtherItem.GetTemplate() == CurrentParent)
{
WidgetsToRemove.Add(Item);
break;
}
}
}
for (FWidgetReference& Item : WidgetsToRemove)
{
Widgets.Remove(Item);
}
WidgetsToRemove.Empty();
// Old Parent -> New Parent Map
TMap<UPanelWidget*, UPanelWidget*> OldParentToNewParent;
for (FWidgetReference& Item : Widgets)
{
int32 OutIndex;
UWidget* Widget = Item.GetTemplate();
UPanelWidget* CurrentParent = BP->WidgetTree->FindWidgetParent(Widget, OutIndex);
TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(Widget, BP->WidgetTree);
// If the widget doesn't currently have a slot or parent, and isn't the root, ignore it.
if (NamedSlotHost == nullptr && CurrentParent == nullptr && Widget != BP->WidgetTree->RootWidget)
{
continue;
}
Widget->Modify();
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
if (NamedSlotHost)
{
// If this is a named slot, we need to properly remove and reassign the slot content
if (UObject* NamedSlotObject = NamedSlotHost.GetObject())
{
NamedSlotObject->SetFlags(RF_Transactional);
NamedSlotObject->Modify();
UPanelWidget* NewSlotContents = CastChecked<UPanelWidget>(Template->Create(BP->WidgetTree));
NewSlotContents->SetDesignerFlags(BlueprintEditor->GetCurrentDesignerFlags());
BP->OnVariableAdded(NewSlotContents->GetFName());
FWidgetBlueprintEditorUtils::ReplaceNamedSlotHostContent(Widget, NamedSlotHost, NewSlotContents);
NewSlotContents->AddChild(Widget);
}
}
else if (CurrentParent)
{
UPanelWidget*& NewWrapperWidget = OldParentToNewParent.FindOrAdd(CurrentParent);
if (NewWrapperWidget == nullptr || !NewWrapperWidget->CanAddMoreChildren())
{
NewWrapperWidget = CastChecked<UPanelWidget>(Template->Create(BP->WidgetTree));
NewWrapperWidget->SetDesignerFlags(BlueprintEditor->GetCurrentDesignerFlags());
BP->OnVariableAdded(NewWrapperWidget->GetFName());
CurrentParent->SetFlags(RF_Transactional);
CurrentParent->Modify();
CurrentParent->ReplaceChildAt(OutIndex, NewWrapperWidget);
}
if (NewWrapperWidget != nullptr && NewWrapperWidget->CanAddMoreChildren())
{
NewWrapperWidget->Modify();
NewWrapperWidget->AddChild(Widget);
}
}
else
{
UPanelWidget* NewRootContents = CastChecked<UPanelWidget>(Template->Create(BP->WidgetTree));
NewRootContents->SetDesignerFlags(BlueprintEditor->GetCurrentDesignerFlags());
BP->OnVariableAdded(NewRootContents->GetFName());
BP->WidgetTree->RootWidget = NewRootContents;
NewRootContents->AddChild(Widget);
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
void FWidgetBlueprintEditorUtils::BuildReplaceWithMenu(FMenuBuilder& Menu, TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
Menu.BeginSection("ReplaceWith", LOCTEXT("WidgetTree_ReplaceWith", "Replace With..."));
{
if ( Widgets.Num() == 1 )
{
FWidgetReference Widget = *Widgets.CreateIterator();
UClass* WidgetClass = Widget.GetTemplate()->GetClass();
TWeakObjectPtr<UClass> TemplateWidget = BlueprintEditor->GetSelectedTemplate();
FAssetData SelectedUserWidget = BlueprintEditor->GetSelectedUserWidget();
if (TemplateWidget.IsValid() || SelectedUserWidget.GetSoftObjectPath().IsValid() )
{
Menu.AddMenuEntry(
FText::Format(LOCTEXT("WidgetTree_ReplaceWithSelection", "Replace With {0}"), FText::FromString(TemplateWidget.IsValid() ? TemplateWidget->GetName() : SelectedUserWidget.AssetName.ToString())),
FText::Format(LOCTEXT("WidgetTree_ReplaceWithSelectionToolTip", "Replace this widget with a {0}"), FText::FromString(TemplateWidget.IsValid() ? TemplateWidget->GetName() : SelectedUserWidget.AssetName.ToString())),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::ReplaceWidgetWithSelectedTemplate, BlueprintEditor, BP, Widget),
FCanExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::CanBeReplacedWithTemplate, BlueprintEditor, BP, Widget)
));
Menu.AddMenuSeparator();
}
if ( WidgetClass->IsChildOf(UPanelWidget::StaticClass()) && Cast<UPanelWidget>(Widget.GetTemplate())->GetChildrenCount() == 1 )
{
Menu.AddMenuEntry(
LOCTEXT("ReplaceWithChild", "Replace With Child"),
LOCTEXT("ReplaceWithChildTooltip", "Remove this widget and insert the children of this widget into the parent."),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::ReplaceWidgetWithChildren, BlueprintEditor, BP, Widget),
FCanExecuteAction()
));
Menu.AddMenuSeparator();
}
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = TScriptInterface<INamedSlotInterface>(Widget.GetTemplate()))
{
TArray<FName> SlotNames;
NamedSlotHost->GetSlotNames(SlotNames);
for (const FName& SlotName : SlotNames)
{
const FText SlotNameTxt = FText::FromString(SlotName.ToString());
if (UWidget* Content = NamedSlotHost->GetContentForSlot(SlotName))
{
Menu.AddMenuEntry(
FText::Format(LOCTEXT("ReplaceWithNamedSlot", "Replace With '{0}'"), SlotNameTxt),
FText::Format(LOCTEXT("ReplaceWithNamedSlotTooltip", "Remove this widget and insert '{0}' content into the parent."), SlotNameTxt),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::ReplaceWidgetWithNamedSlot, BlueprintEditor, BP, Widget, SlotName),
FCanExecuteAction()
));
}
}
Menu.AddMenuSeparator();
}
}
TArray<UClass*> ReplacementClasses;
for ( TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt )
{
UClass* WidgetClass = *ClassIt;
if ( FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass, BlueprintEditor) )
{
if ( WidgetClass->IsChildOf(UPanelWidget::StaticClass()) && !WidgetClass->HasAnyClassFlags(CLASS_HideDropDown) )
{
// Only allow replacement with panels that accept multiple children
if ( WidgetClass->GetDefaultObject<UPanelWidget>()->CanHaveMultipleChildren() )
{
ReplacementClasses.Add(WidgetClass);
}
}
}
}
ReplacementClasses.Sort([] (UClass& Lhs, UClass& Rhs) { return Lhs.GetDisplayNameText().CompareTo(Rhs.GetDisplayNameText()) < 0; });
for ( UClass* ReplacementClass : ReplacementClasses )
{
Menu.AddMenuEntry(
ReplacementClass->GetDisplayNameText(),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FWidgetBlueprintEditorUtils::ReplaceWidgets, BP, ResolveWidgetTemplates(Widgets), ReplacementClass, EReplaceWidgetNamingMethod::MaintainNameAndReferences)
));
}
}
Menu.EndSection();
}
bool FWidgetBlueprintEditorUtils::IsDesiredFocusWidget(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidget* Widget)
{
return IsDesiredFocusWidget(BlueprintEditor->GetWidgetBlueprintObj(), Widget);
}
bool FWidgetBlueprintEditorUtils::IsDesiredFocusWidget(UWidgetBlueprint* Blueprint, UWidget* Widget)
{
if (Blueprint && Blueprint->GeneratedClass && Widget)
{
if (UUserWidget* WidgetCDO = Blueprint->GeneratedClass->GetDefaultObject<UUserWidget>())
{
return (WidgetCDO && WidgetCDO->GetDesiredFocusWidgetName() == Widget->GetFName());
}
}
return false;
}
void FWidgetBlueprintEditorUtils::ReplaceWidgetWithSelectedTemplate(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget)
{
// @Todo: Needs to deal with bound object in animation tracks
UWidget* WidgetToReplace = Widget.GetTemplate();
if (!WidgetToReplace)
{
return;
}
UClass* ReplacementWidgetClass = BlueprintEditor->GetSelectedTemplate().Get();
if (!ReplacementWidgetClass)
{
ReplacementWidgetClass = BlueprintEditor->GetSelectedUserWidget().GetClass(EResolveClass::Yes);
}
if (!ReplacementWidgetClass)
{
return;
}
ReplaceWidgets(BP, {WidgetToReplace}, ReplacementWidgetClass, EReplaceWidgetNamingMethod::MaintainNameAndReferences);
}
bool FWidgetBlueprintEditorUtils::CanBeReplacedWithTemplate(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget)
{
FAssetData SelectedUserWidget = BlueprintEditor->GetSelectedUserWidget();
UWidget* ThisWidget = Widget.GetTemplate();
UPanelWidget* ExistingPanel = Cast<UPanelWidget>(ThisWidget);
UClass* WidgetClass = nullptr;
// If selecting another widget blueprint
if (SelectedUserWidget.GetSoftObjectPath().IsValid())
{
if (ExistingPanel && ExistingPanel->GetChildrenCount() != 0)
{
return false;
}
if (UWidget* NewWidget = FWidgetTemplateBlueprintClass(SelectedUserWidget).Create(BP->WidgetTree))
{
// If we are creating a UserWidget, check for Circular references
if (UUserWidget* NewUserWidget = Cast<UUserWidget>(NewWidget))
{
const bool bFreeFromCircularRefs = BP->IsWidgetFreeFromCircularReferences(NewUserWidget);
NewWidget->Rename(nullptr, GetTransientPackage());
return bFreeFromCircularRefs;
}
WidgetClass = NewWidget->GetClass();
NewWidget->Rename(nullptr, GetTransientPackage());
}
}
// If we get here, the Widget selected is not a UserWidget and it's not a Blueprint.
if (!WidgetClass)
{
WidgetClass = BlueprintEditor->GetSelectedTemplate().Get();
}
// If the Widget to replace is not a Panel we can replace it with anything
if (!ExistingPanel)
{
return true;
}
const bool bNewWidgetClassIsAPanel = WidgetClass->IsChildOf(UPanelWidget::StaticClass());
// If the Widget to replace is a Panel and the new widget is not, we allow to replace it only if it's empty;
if (!bNewWidgetClassIsAPanel)
{
return ExistingPanel->GetChildrenCount() == 0;
}
// If the Widget to replace is a Panel that can have multiple children, we allow to replace it with a Panel that can support multiple children only.
if (ExistingPanel->GetClass()->GetDefaultObject<UPanelWidget>()->CanHaveMultipleChildren() && bNewWidgetClassIsAPanel)
{
const bool bChildAllowed = WidgetClass->GetDefaultObject<UPanelWidget>()->CanHaveMultipleChildren() || ExistingPanel->GetChildrenCount() == 0;
return bChildAllowed;
}
return true;
}
void FWidgetBlueprintEditorUtils::ReplaceWidgetWithChildren(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget)
{
FScopedTransaction Transaction(LOCTEXT("ReplaceWidgets", "Replace Widgets"));
TSet<FWidgetReference> WidgetsToDelete;
WidgetsToDelete.Add(Widget);
TArray<UWidget*> UsedVariables;
TArray<FText> WidgetNames;
const bool bIncludeChildrenVariables = false;
FindUsedVariablesForWidgets(ResolveWidgetTemplates(WidgetsToDelete), BP, UsedVariables, WidgetNames, bIncludeChildrenVariables);
if (UsedVariables.Num() != 0 && !ShouldContinueReplaceOperation(BP, WidgetNames))
{
Transaction.Cancel();
return;
}
if ( UPanelWidget* ExistingPanelTemplate = Cast<UPanelWidget>(Widget.GetTemplate()) )
{
UWidget* FirstChildTemplate = ExistingPanelTemplate->GetChildAt(0);
ExistingPanelTemplate->SetFlags(RF_Transactional);
ExistingPanelTemplate->Modify();
FirstChildTemplate->SetFlags(RF_Transactional);
FirstChildTemplate->Modify();
// Look if the Widget to replace is a NamedSlot.
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(ExistingPanelTemplate, BP->WidgetTree))
{
ReplaceNamedSlotHostContent(ExistingPanelTemplate, NamedSlotHost, FirstChildTemplate);
}
else if (UPanelWidget* PanelParentTemplate = ExistingPanelTemplate->GetParent())
{
PanelParentTemplate->Modify();
FirstChildTemplate->RemoveFromParent();
PanelParentTemplate->ReplaceChild(ExistingPanelTemplate, FirstChildTemplate);
}
else if ( ExistingPanelTemplate == BP->WidgetTree->RootWidget )
{
FirstChildTemplate->RemoveFromParent();
BP->WidgetTree->Modify();
BP->WidgetTree->RootWidget = FirstChildTemplate;
}
else
{
Transaction.Cancel();
return;
}
// Delete the widget that has been replaced
DeleteWidgets(BP, ResolveWidgetTemplates(WidgetsToDelete), EDeleteWidgetWarningType::DeleteSilently);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
}
void FWidgetBlueprintEditorUtils::ReplaceWidgetWithNamedSlot(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget, FName NamedSlot)
{
UWidget* WidgetTemplate = Widget.GetTemplate();
if (INamedSlotInterface* ExistingNamedSlotContainerTemplate = Cast<INamedSlotInterface>(WidgetTemplate))
{
UWidget* NamedSlotContentTemplate = ExistingNamedSlotContainerTemplate->GetContentForSlot(NamedSlot);
FScopedTransaction Transaction(LOCTEXT("ReplaceWidgets", "Replace Widgets"));
WidgetTemplate->SetFlags(RF_Transactional);
WidgetTemplate->Modify();
NamedSlotContentTemplate->SetFlags(RF_Transactional);
NamedSlotContentTemplate->Modify();
// Look if the Widget to replace is a NamedSlot.
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(WidgetTemplate, BP->WidgetTree))
{
ReplaceNamedSlotHostContent(WidgetTemplate, NamedSlotHost, NamedSlotContentTemplate);
}
else if (UPanelWidget* PanelParentTemplate = WidgetTemplate->GetParent())
{
PanelParentTemplate->Modify();
if (TScriptInterface<INamedSlotInterface> ContentNamedSlotHost = FindNamedSlotHostForContent(NamedSlotContentTemplate, BP->WidgetTree))
{
FWidgetBlueprintEditorUtils::RemoveNamedSlotHostContent(NamedSlotContentTemplate, ContentNamedSlotHost);
}
PanelParentTemplate->ReplaceChild(WidgetTemplate, NamedSlotContentTemplate);
}
else if (WidgetTemplate == BP->WidgetTree->RootWidget)
{
if (UPanelWidget* Parent = NamedSlotContentTemplate->GetParent())
{
Parent->Modify();
NamedSlotContentTemplate->RemoveFromParent();
}
BP->WidgetTree->Modify();
BP->WidgetTree->RootWidget = NamedSlotContentTemplate;
}
else
{
Transaction.Cancel();
return;
}
// Remove the widget replaced
DeleteWidgets(BP, {Widget.GetTemplate()}, EDeleteWidgetWarningType::WarnAndAskUser);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
}
void FWidgetBlueprintEditorUtils::ReplaceWidgets(UWidgetBlueprint* BP, TSet<UWidget*> Widgets, UClass* WidgetClass, EReplaceWidgetNamingMethod NewWidgetNamingMethod)
{
FScopedTransaction Transaction(LOCTEXT("ReplaceWidgets", "Replace Widgets"));
TArray<UWidget*> UsedVariables;
TArray<FText> WidgetNames;
const bool bIncludeChildrenVariables = false;
FindUsedVariablesForWidgets(Widgets, BP, UsedVariables, WidgetNames, bIncludeChildrenVariables);
if (UsedVariables.Num() != 0 && !ShouldContinueReplaceOperation(BP, WidgetNames))
{
Transaction.Cancel();
return;
}
TSharedPtr<FWidgetTemplateClass> Template = MakeShareable(new FWidgetTemplateClass(WidgetClass));
TMap<FName, FName> ReplacedWidgetMap;
for (UWidget* Item : Widgets)
{
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
UWidget* NewReplacementWidget = Template->Create(BP->WidgetTree);
UWidget* WidgetToReplace = Item;
// If replacing a panel widget, then it must not have children or the replacement must also be a panel widget
if (UPanelWidget* ExistingPanel = Cast<UPanelWidget>(WidgetToReplace))
{
if (ExistingPanel->GetChildrenCount() > 0 && !NewReplacementWidget->IsA<UPanelWidget>())
{
continue;
}
}
TMap<FName, FString> ExportedProperties;
ExportPropertiesToText(WidgetToReplace, ExportedProperties);
ImportPropertiesFromText(NewReplacementWidget, ExportedProperties);
WidgetToReplace->SetFlags(RF_Transactional);
WidgetToReplace->Modify();
const FName OriginalWidgetName = WidgetToReplace->GetFName();
// Look if the Widget to replace is a NamedSlot.
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(WidgetToReplace, BP->WidgetTree))
{
const bool bDidReplace = ReplaceNamedSlotHostContent(WidgetToReplace, NamedSlotHost, NewReplacementWidget);
if (!bDidReplace)
{
continue;
}
}
else if (UPanelWidget* CurrentParent = WidgetToReplace->GetParent())
{
CurrentParent->SetFlags(RF_Transactional);
CurrentParent->Modify();
const bool bDidReplace = CurrentParent->ReplaceChild(WidgetToReplace, NewReplacementWidget);
if (!bDidReplace)
{
continue;
}
}
else if (WidgetToReplace == BP->WidgetTree->RootWidget)
{
BP->WidgetTree->RootWidget = NewReplacementWidget;
}
else
{
continue;
}
NewReplacementWidget->SetFlags(RF_Transactional);
NewReplacementWidget->Modify();
if (UPanelWidget* ExistingPanel = Cast<UPanelWidget>(WidgetToReplace))
{
if (UPanelWidget* NewReplacementPanelWidget = Cast<UPanelWidget>(NewReplacementWidget))
{
while (ExistingPanel->GetChildrenCount() > 0)
{
UWidget* Widget = ExistingPanel->GetChildAt(0);
Widget->SetFlags(RF_Transactional);
Widget->Modify();
NewReplacementPanelWidget->AddChild(Widget);
}
}
}
// We need to check before replacing because the Widget might be deleted, reseting the DesiredFocus
const bool bReplacingDesiredFocus = IsDesiredFocusWidget(BP, WidgetToReplace);
FString ReplaceName = WidgetToReplace->GetName();
const bool bCanKeepName = (NewWidgetNamingMethod == EReplaceWidgetNamingMethod::MaintainNameAndReferencesForUnmatchingClass) ||
(!WidgetToReplace->IsGeneratedName() && NewWidgetNamingMethod == EReplaceWidgetNamingMethod::MaintainNameAndReferences
&& ((WidgetToReplace->IsA<UPanelWidget>() && NewReplacementWidget->IsA<UPanelWidget>())
|| WidgetToReplace->IsA(NewReplacementWidget->GetClass())
|| NewReplacementWidget->IsA(WidgetToReplace->GetClass())));
// Rename the removed widget to the transient package so that it doesn't conflict with the new widget if we try to keep the same name.
FName TrashName = MakeUniqueObjectName(GetTransientPackage(), WidgetToReplace->GetClass(), *FString::Printf(TEXT("TRASH_%s"), *WidgetToReplace->GetName()));
WidgetToReplace->Rename(*TrashName.ToString(), GetTransientPackage());
// Rename the new Widget to maintain the current name if it's not a generic name
if (NewWidgetNamingMethod == EReplaceWidgetNamingMethod::MaintainNameAndReferences || NewWidgetNamingMethod == EReplaceWidgetNamingMethod::MaintainNameAndReferencesForUnmatchingClass)
{
if (bCanKeepName)
{
ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName);
NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree);
}
// Preserve references to the widget if we haven't kept the same name
if (OriginalWidgetName != NewReplacementWidget->GetFName())
{
BP->OnVariableRenamed(OriginalWidgetName, NewReplacementWidget->GetFName());
}
// Even if the name hasn't changed, we need to replace references since the type might have changed
ReplacedWidgetMap.Add(OriginalWidgetName, NewReplacementWidget->GetFName());
}
else if (NewReplacementWidget->GetFName() != OriginalWidgetName)
{
BP->OnVariableRemoved(OriginalWidgetName);
BP->OnVariableAdded(NewReplacementWidget->GetFName());
}
// Delete the widget that has been replaced
DeleteWidgets(BP, {Item}, EDeleteWidgetWarningType::DeleteSilently);
if (bReplacingDesiredFocus)
{
SetDesiredFocus(BP, NewReplacementWidget->GetFName());
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
for (const TPair<FName, FName>& RenamedWidgets : ReplacedWidgetMap)
{
FBlueprintEditorUtils::ReplaceVariableReferences(BP, RenamedWidgets.Key, RenamedWidgets.Value);
}
}
void FWidgetBlueprintEditorUtils::CutWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
CopyWidgets(BP, Widgets);
DeleteWidgets(BP, ResolveWidgetTemplates(Widgets), EDeleteWidgetWarningType::WarnAndAskUser);
}
void FWidgetBlueprintEditorUtils::CopyWidgets(UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
FString ExportedText = CopyWidgetsInternal(BP, Widgets);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
FString FWidgetBlueprintEditorUtils::CopyWidgetsInternal(UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
TSet<UWidget*> TemplateWidgets;
// Convert the set of widget references into the list of widget templates we're going to copy.
for ( const FWidgetReference& Widget : Widgets )
{
UWidget* TemplateWidget = Widget.GetTemplate();
TemplateWidgets.Add(TemplateWidget);
}
TArray<UWidget*> FinalWidgets;
// Pair down copied widgets to the legitimate root widgets, if they're parent is not already
// in the set we're planning to copy, then keep them in the list, otherwise remove widgets that
// will already be handled when their parent copies into the array.
for ( UWidget* TemplateWidget : TemplateWidgets )
{
bool bFoundParent = false;
// See if the widget already has a parent in the set we're copying.
for ( UWidget* PossibleParent : TemplateWidgets )
{
if ( PossibleParent != TemplateWidget )
{
if ( TemplateWidget->IsChildOf(PossibleParent) )
{
bFoundParent = true;
break;
}
}
}
if ( !bFoundParent )
{
FinalWidgets.Add(TemplateWidget);
UWidgetTree::GetChildWidgets(TemplateWidget, FinalWidgets);
}
}
FString ExportedText;
FWidgetBlueprintEditorUtils::ExportWidgetsToText(FinalWidgets, /*out*/ ExportedText);
return ExportedText;
}
TArray<UWidget*> FWidgetBlueprintEditorUtils::DuplicateWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
TArray<UWidget*> DuplicatedWidgets;
FWidgetReference ParentWidgetRef = Widgets.Num() > 0 ? *Widgets.CreateIterator() : FWidgetReference();
FName SlotName = NAME_None;
TOptional<FNamedSlotSelection> NamedSlotSelection = BlueprintEditor->GetSelectedNamedSlot();
if (NamedSlotSelection.IsSet())
{
ParentWidgetRef = NamedSlotSelection->NamedSlotHostWidget;
SlotName = NamedSlotSelection->SlotName;
}
if (ParentWidgetRef.IsValid())
{
FString ExportedText = CopyWidgetsInternal(BP, Widgets);
FScopedTransaction Transaction(FGenericCommands::Get().Duplicate->GetDescription());
bool TransactionSuccesful = true;
DuplicatedWidgets = PasteWidgetsInternal(BlueprintEditor, BP, ExportedText, ParentWidgetRef, SlotName, FVector2D::ZeroVector, true, TransactionSuccesful);
if (!TransactionSuccesful)
{
BlueprintEditor->LogSimpleMessage(LOCTEXT("PasteWidgetsCancel", "Paste operation on widget cancelled."));
Transaction.Cancel();
}
}
return DuplicatedWidgets;
}
UUserWidget* FWidgetBlueprintEditorUtils::CreateUserWidgetFromBlueprint(UObject* Outer, UWidgetBlueprint* BP, const FCreateWidgetFromBlueprintParams& Params)
{
check(Outer);
check(BP);
UUserWidget* CreatedUserWidget = nullptr;
// Create the Widget, we have to do special swapping out of the widget tree.
{
// Assign the outer to the game instance if it exists, otherwise use the world
{
FMakeClassSpawnableOnScope TemporarilySpawnable(BP->GeneratedClass);
CreatedUserWidget = NewObject<UUserWidget>(Outer, BP->GeneratedClass);
}
// The preview widget should not be transactional.
CreatedUserWidget->ClearFlags(RF_Transactional);
// Establish the widget as being in design time before initializing and before duplication
// (so that IsDesignTime is reliable within both calls to Initialize)
// The preview widget is also the outer widget that will update all child flags
CreatedUserWidget->SetDesignerFlags(Params.FlagsToApply);
if (ULocalPlayer* Player = Params.LocalPlayer)
{
CreatedUserWidget->SetPlayerContext(FLocalPlayerContext(Player));
}
UWidgetTree* LatestWidgetTree = FWidgetBlueprintEditorUtils::FindLatestWidgetTree(BP, CreatedUserWidget);
TMap<FName, UWidget*> SortedNamedSlotContentToMerge;
UWidgetBlueprint* WidgetBlueprintIterator = BP;
TArray<TTuple<FName, UWidget*>> NamedSlotContentToMergeArray;
while (WidgetBlueprintIterator)
{
TArray<FName> SlotNames;
WidgetBlueprintIterator->WidgetTree->GetSlotNames(SlotNames);
// We iterate widget blueprints from child to parent, but we need the final namedslot array to be sorted from parent to child.
// Here, we iterate the slot names in reverse to maintain the order of namedslots per widget blueprint once the final array is reversed.
for (int32 Index = SlotNames.Num() - 1; Index >= 0; Index--)
{
FName SlotName = SlotNames[Index];
if (UWidget* Content = WidgetBlueprintIterator->WidgetTree->GetContentForSlot(SlotName))
{
NamedSlotContentToMergeArray.Add(TTuple<FName, UWidget*>(SlotName, Content));
}
}
WidgetBlueprintIterator = Cast<UWidgetBlueprint>(WidgetBlueprintIterator->GeneratedClass->GetSuperClass()->ClassGeneratedBy);
}
// We iterate the array in reverse so that the final SortedNamedSlotContentToMerge map ends up sorted from outermost namedslot to innermost.
for (int32 Index = NamedSlotContentToMergeArray.Num() - 1; Index >= 0; Index--)
{
TTuple<FName, UWidget*>& Element = NamedSlotContentToMergeArray[Index];
SortedNamedSlotContentToMerge.Add(Element.Key, Element.Value);
}
// Update the widget tree directly to match the blueprint tree. That way the preview can update
// without needing to do a full recompile.
CreatedUserWidget->DuplicateAndInitializeFromWidgetTree(LatestWidgetTree, SortedNamedSlotContentToMerge);
// Establish the widget as being in design time before initializing (so that IsDesignTime is reliable within Initialize)
// We have to call it to make sure that all the WidgetTree had the DesignerFlags set correctly
CreatedUserWidget->SetDesignerFlags(Params.FlagsToApply);
}
return CreatedUserWidget;
}
void FWidgetBlueprintEditorUtils::DestroyUserWidget(UUserWidget* UserWidget)
{
check(UserWidget);
TWeakPtr<SWidget> SlateWidgetWeak = UserWidget->GetCachedWidget();
UserWidget->MarkAsGarbage();
UserWidget->ReleaseSlateResources(true);
ensure(!SlateWidgetWeak.IsValid());
}
bool FWidgetBlueprintEditorUtils::IsAnySelectedWidgetLocked(TSet<FWidgetReference> SelectedWidgets)
{
for (const FWidgetReference& Widget : SelectedWidgets)
{
if (Widget.GetPreview()->IsLockedInDesigner())
{
return true;
}
}
return false;
}
bool FWidgetBlueprintEditorUtils::CanPasteWidgetsExtension(TSet<FWidgetReference> SelectedWidgets)
{
if (!SelectedWidgets.IsEmpty())
{
IUMGEditorModule& EditorModule = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor");
const TArrayView<const TSharedPtr<IClipboardExtension>> ClipboardExtensions = EditorModule.GetClipboardExtensibilityManager()->GetExtensions();
for (const TSharedPtr<IClipboardExtension>& ClipboardExtension : ClipboardExtensions)
{
if (ensure(ClipboardExtension.IsValid()))
{
for (const FWidgetReference& SelectedWidget : SelectedWidgets)
{
if (UWidget* TemplateWidget = SelectedWidget.GetTemplate())
{
if (!ClipboardExtension->CanWidgetAcceptPaste(TemplateWidget))
{
return false;
}
}
}
}
}
}
return true;
}
UWidget* FWidgetBlueprintEditorUtils::GetWidgetTemplateFromDragDrop(UWidgetBlueprint* Blueprint, UWidgetTree* RootWidgetTree, TSharedPtr<FDragDropOperation>& DragDropOp)
{
UWidget* Widget = nullptr;
if (!DragDropOp.IsValid())
{
return nullptr;
}
if (DragDropOp->IsOfType<FWidgetTemplateDragDropOp>())
{
TSharedPtr<FWidgetTemplateDragDropOp> TemplateDragDropOp = StaticCastSharedPtr<FWidgetTemplateDragDropOp>(DragDropOp);
Widget = TemplateDragDropOp->Template->Create(RootWidgetTree);
}
else if (DragDropOp->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> AssetDragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(DragDropOp);
if (AssetDragDropOp->GetAssets().Num() > 0)
{
// Only handle first valid dragged widget, multi widget drag drop is not practically useful
const FAssetData& AssetData = AssetDragDropOp->GetAssets()[0];
bool CodeClass = AssetData.AssetClassPath == FTopLevelAssetPath(TEXT("/Script/CoreUObject"), TEXT("Class"));
FString ClassName = CodeClass ? AssetData.GetObjectPathString() : AssetData.AssetClassPath.ToString();
UClass* AssetClass = FindObjectChecked<UClass>(nullptr, *ClassName);
if (FWidgetTemplateBlueprintClass::Supports(AssetClass))
{
// Allows a UMG Widget Blueprint to be dragged from the Content Browser to another Widget Blueprint...as long as we're not trying to place a
// blueprint inside itself.
FString BlueprintPath = Blueprint->GetPathName();
if (BlueprintPath != AssetData.GetSoftObjectPath().ToString())
{
Widget = FWidgetTemplateBlueprintClass(AssetData).Create(RootWidgetTree);
}
}
else if (CodeClass && AssetClass && AssetClass->IsChildOf(UWidget::StaticClass()))
{
Widget = FWidgetTemplateClass(AssetClass).Create(RootWidgetTree);
}
else if (FWidgetTemplateImageClass::Supports(AssetClass))
{
Widget = FWidgetTemplateImageClass(AssetData).Create(RootWidgetTree);
}
}
}
// Check to make sure that this widget can be added to the current blueprint
if (Cast<UUserWidget>(Widget) && !Blueprint->IsWidgetFreeFromCircularReferences(Cast<UUserWidget>(Widget)))
{
Widget = nullptr;
}
return Widget;
}
bool FWidgetBlueprintEditorUtils::ShouldPreventDropOnTargetExtensions(const UWidget* Target, const TSharedPtr<FDragDropOperation>& DragDropOp, FText& OutFailureText)
{
if (Target)
{
IUMGEditorModule& EditorModule = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor");
const TArrayView<const TSharedPtr<IWidgetDragDropExtension>> DragDropExtensions = EditorModule.GetWidgetDragDropExtensibilityManager()->GetExtensions();
for (const TSharedPtr<IWidgetDragDropExtension>& DragDropExtension : DragDropExtensions)
{
if (ensure(DragDropExtension.IsValid()) && DragDropExtension->ShouldPreventDropOnTarget(Target, DragDropOp))
{
OutFailureText = DragDropExtension->GetDropFailureText(Target, DragDropOp);
return true;
}
}
}
return false;
}
void FWidgetBlueprintEditorUtils::ExportWidgetsToText(TArray<UWidget*> WidgetsToExport, /*out*/ FString& ExportedText)
{
// Clear the mark state for saving.
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
FStringOutputDevice Archive;
// Validate all nodes are from the same scope and set all UUserWidget::WidgetTrees (and things outered to it) to be ignored
TArray<UObject*> WidgetsToIgnore;
UObject* LastOuter = nullptr;
for ( UWidget* Widget : WidgetsToExport )
{
// The nodes should all be from the same scope
UObject* ThisOuter = Widget->GetOuter();
check((LastOuter == ThisOuter) || (LastOuter == nullptr));
LastOuter = ThisOuter;
if ( UUserWidget* UserWidget = Cast<UUserWidget>(Widget) )
{
if ( UserWidget->WidgetTree )
{
WidgetsToIgnore.Add(UserWidget->WidgetTree);
// FExportObjectInnerContext does not automatically ignore UObjects if their outer is ignored
GetObjectsWithOuter(UserWidget->WidgetTree, WidgetsToIgnore);
}
}
}
const FExportObjectInnerContext Context(WidgetsToIgnore);
IUMGEditorModule& EditorModule = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor");
const TArrayView<const TSharedPtr<IClipboardExtension>> ClipboardExtensions = EditorModule.GetClipboardExtensibilityManager()->GetExtensions();
// Get the widget blueprint containing the exported widgets
UWidgetBlueprint* WidgetBlueprint = nullptr;
if (WidgetsToExport.Num() > 0)
{
WidgetBlueprint = FWidgetBlueprintEditorUtils::GetWidgetBlueprintFromWidget(WidgetsToExport[0]);
}
// Export each of the selected nodes
for ( UWidget* Widget : WidgetsToExport )
{
UExporter::ExportToOutputDevice(&Context, Widget, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, LastOuter);
// Check to see if this widget was content of another widget holding it in a named slot.
if ( Widget->GetParent() == nullptr )
{
for ( UWidget* ExportableWidget : WidgetsToExport )
{
if ( INamedSlotInterface* NamedSlotContainer = Cast<INamedSlotInterface>(ExportableWidget) )
{
if ( NamedSlotContainer->ContainsContent(Widget) )
{
continue;
}
}
}
}
if ( Widget->GetParent() == nullptr || !WidgetsToExport.Contains(Widget->GetParent()) )
{
auto SlotMetaData = NewObject<UWidgetSlotPair>();
SlotMetaData->SetWidget(Widget);
UExporter::ExportToOutputDevice(&Context, SlotMetaData, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, nullptr);
}
if (WidgetBlueprint)
{
for (const TSharedPtr<IClipboardExtension>& ClipboardExtension : ClipboardExtensions)
{
if (ClipboardExtension->CanAppendToClipboard(Widget))
{
IClipboardExtension::FExportArgs ExportArgs;
ExportArgs.Context = &Context;
ExportArgs.Exporter = nullptr;
ExportArgs.FileType = TEXT("copy");
ExportArgs.Indent = 0;
ExportArgs.PortFlags = PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited;
ExportArgs.bSelectedOnly = false;
ExportArgs.ExportRootScope = nullptr;
ExportArgs.Out = &Archive;
ClipboardExtension->AppendToClipboard(Widget, ExportArgs);
}
}
}
}
ExportedText = Archive;
}
TArray<UWidget*> FWidgetBlueprintEditorUtils::PasteWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference ParentWidgetRef, FName SlotName, FVector2D PasteLocation)
{
FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
// Grab the text to paste from the clipboard.
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
bool bTransactionSuccessful = true;
TArray<UWidget*> PastedWidgets = PasteWidgetsInternal(BlueprintEditor, BP, TextToImport, ParentWidgetRef, SlotName, PasteLocation, false, bTransactionSuccessful);
if (!bTransactionSuccessful)
{
BlueprintEditor->LogSimpleMessage(LOCTEXT("PasteWidgetsCancel", "Paste operation on widget cancelled."));
Transaction.Cancel();
}
return PastedWidgets;
}
bool FWidgetBlueprintEditorUtils::DisplayPasteWarningAndEarlyExit()
{
const FText DeleteConfirmationPrompt = LOCTEXT("DeleteConfirmationPrompt", "Pasting in a single-slot widget will erase its content. Do you wish to proceed?");
const FText DeleteConfirmationTitle = LOCTEXT("DeleteConfirmationTitle", "Delete widget");
// Warn the user that this may result in data loss
FSuppressableWarningDialog::FSetupInfo Info(DeleteConfirmationPrompt, DeleteConfirmationTitle, TEXT("Paste_Warning"));
Info.ConfirmText = LOCTEXT("DeleteConfirmation_Yes", "Yes");
Info.CancelText = LOCTEXT("DeleteConfirmation_No", "No");
FSuppressableWarningDialog DeleteChildWidgetWarningDialog(Info);
return DeleteChildWidgetWarningDialog.ShowModal() == FSuppressableWarningDialog::Cancel;
}
TArray<UWidget*> FWidgetBlueprintEditorUtils::PasteWidgetsInternal(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, const FString& TextToImport, FWidgetReference ParentWidgetRef, FName SlotName, FVector2D PasteLocation, bool bForceSibling, bool& bTransactionSuccessful)
{
// Do an intial text processing to make sure we have any widgets to paste
UPackage* TempPackage = nullptr;
FWidgetObjectTextFactory Factory = ProcessImportedText(BP, TextToImport, TempPackage);
TGCObjectScopeGuard<UPackage> TempPackageGCGuard(TempPackage);
const bool bHasPastedWidget = Factory.NewWidgetMap.Num() > 0;
// Ignore an empty set of widget paste data.
if (!bHasPastedWidget)
{
bTransactionSuccessful = false;
return TArray<UWidget*>();
}
TArray<UWidget*> RootPasteWidgets;
TMap<FName, UWidgetSlotPair*> PastedExtraSlotData;
TSet<UWidget*> PastedWidgets;
auto ImportWidgets = [&]()
{
FWidgetBlueprintEditorUtils::ImportWidgetsFromText(BP, TextToImport, /*out*/ PastedWidgets, /*out*/ PastedExtraSlotData);
for (UWidget* NewWidget : PastedWidgets)
{
BP->OnVariableAdded(NewWidget->GetFName());
// Widgets with a null parent mean that they were the root most widget of their selection set when
// they were copied and thus we need to paste only the root most widgets. All their children will be added
// automatically.
if (NewWidget->GetParent() == nullptr)
{
// Check to see if this widget is content of another widget holding it in a named slot.
bool bIsNamedSlot = false;
for (UWidget* ContainerWidget : PastedWidgets)
{
if (INamedSlotInterface* NamedSlotContainer = Cast<INamedSlotInterface>(ContainerWidget))
{
if (NamedSlotContainer->ContainsContent(NewWidget))
{
bIsNamedSlot = true;
break;
}
}
}
// It's a Root widget only if it's not not in a named slot.
if (!bIsNamedSlot)
{
RootPasteWidgets.Add(NewWidget);
}
}
}
};
// If we're pasting into a content widget of the same type, treat it as a sibling duplication
UWidget* FirstPastedWidget = Factory.NewWidgetMap.CreateIterator()->Value;
if (FirstPastedWidget->IsA(UContentWidget::StaticClass()) &&
ParentWidgetRef.IsValid() &&
FirstPastedWidget->GetClass() == ParentWidgetRef.GetTemplate()->GetClass())
{
UPanelWidget* TargetParentWidget = ParentWidgetRef.GetTemplate()->GetParent();
if (TargetParentWidget && TargetParentWidget->CanAddMoreChildren())
{
bForceSibling = true;
}
}
if ( SlotName == NAME_None )
{
UPanelWidget* ParentWidget = nullptr;
int32 IndexToInsert = INDEX_NONE;
if ( ParentWidgetRef.IsValid() )
{
ParentWidget = Cast<UPanelWidget>(ParentWidgetRef.GetTemplate());
// If the widget isn't a panel or we just really want it to be a sibling (ie. when duplicating), we'll try it's parent to see if the pasted widget can be a sibling (and get its index to insert at)
if ( bForceSibling || !ParentWidget )
{
if (UWidget* WidgetTemplate = ParentWidgetRef.GetTemplate())
{
ParentWidget = WidgetTemplate->GetParent();
if (ParentWidget && ParentWidget->CanHaveMultipleChildren())
{
IndexToInsert = ParentWidget->GetChildIndex(WidgetTemplate) + 1;
}
}
}
}
if ( !ParentWidget )
{
// If we already have a root widget, then we can't replace the root.
if ( BP->WidgetTree->RootWidget )
{
bTransactionSuccessful = false;
return TArray<UWidget*>();
}
}
if ( ParentWidget )
{
// If parent widget can only have one child and that slot is already occupied, we will remove its contents so the pasted widgets can be inserted in their place
UWidget* ChildWidgetToDelete = nullptr;
if (!ParentWidget->CanHaveMultipleChildren() && ParentWidget->GetChildrenCount() > 0)
{
// We do not Remove child if there is nothing to paste.
if ( bHasPastedWidget )
{
if (FWidgetBlueprintEditorUtils::DisplayPasteWarningAndEarlyExit())
{
bTransactionSuccessful = false;
return TArray<UWidget*>();
}
// Delete the singular child
ChildWidgetToDelete = ParentWidget->GetAllChildren()[0];
ChildWidgetToDelete->SetFlags(RF_Transactional);
ChildWidgetToDelete->Modify();
ParentWidget->SetFlags(RF_Transactional);
ParentWidget->Modify();
ParentWidget->RemoveChild(ChildWidgetToDelete);
}
}
if (ChildWidgetToDelete)
{
DeleteWidgets(BP, { ChildWidgetToDelete }, EDeleteWidgetWarningType::DeleteSilently);
}
}
ImportWidgets();
// If there isn't a root widget and we're copying multiple root widgets, then we need to add a container root
// to hold the pasted data since multiple root widgets isn't permitted.
if (!ParentWidget && RootPasteWidgets.Num() > 1)
{
ParentWidget = BP->WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass());
BP->WidgetTree->Modify();
BP->WidgetTree->RootWidget = ParentWidget;
BP->OnVariableAdded(ParentWidget->GetFName());
}
if (ParentWidget)
{
// A bit of a hack, but we can look at the widget's slot properties to determine if it is a canvas slot. If so, we'll try and maintain the relative positions
bool bShouldReproduceOffsets = true;
static const FName LayoutDataLabel = FName(TEXT("LayoutData"));
for (TPair<FName, UWidgetSlotPair*> SlotData : PastedExtraSlotData)
{
UWidgetSlotPair* SlotDataPair = SlotData.Value;
TMap<FName, FString> SlotProperties;
SlotDataPair->GetSlotProperties(SlotProperties);
if (!SlotProperties.Contains(LayoutDataLabel))
{
bShouldReproduceOffsets = false;
break;
}
}
FVector2D FirstWidgetPosition;
ParentWidget->Modify();
for ( UWidget* NewWidget : RootPasteWidgets )
{
UPanelSlot* Slot;
if ( IndexToInsert == INDEX_NONE )
{
Slot = ParentWidget->AddChild(NewWidget);
}
else
{
Slot = ParentWidget->InsertChildAt(IndexToInsert, NewWidget);
}
if ( Slot )
{
if ( UWidgetSlotPair* OldSlotData = PastedExtraSlotData.FindRef(NewWidget->GetFName()) )
{
TMap<FName, FString> OldSlotProperties;
OldSlotData->GetSlotProperties(OldSlotProperties);
FWidgetBlueprintEditorUtils::ImportPropertiesFromText(Slot, OldSlotProperties);
// Cache the initial position of the first widget so we can calculate offsets for additional widgets
if (NewWidget == RootPasteWidgets[0])
{
if (UCanvasPanelSlot* FirstCanvasSlot = Cast<UCanvasPanelSlot>(Slot))
{
FirstWidgetPosition = FirstCanvasSlot->GetPosition();
}
}
}
BlueprintEditor->RefreshPreview();
FWidgetReference WidgetRef = BlueprintEditor->GetReferenceFromTemplate(NewWidget);
UPanelSlot* PreviewSlot = WidgetRef.GetPreview()->Slot;
UPanelSlot* TemplateSlot = WidgetRef.GetTemplate()->Slot;
if ( UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(PreviewSlot) )
{
FVector2D PasteOffset = FVector2D(0, 0);
if (bShouldReproduceOffsets)
{
PasteOffset = CanvasSlot->GetPosition()- FirstWidgetPosition;
}
if (UCanvasPanel* Canvas = Cast<UCanvasPanel>(CanvasSlot->Parent))
{
Canvas->TakeWidget(); // Generate the underlying widget so redoing the layout below works.
}
CanvasSlot->SaveBaseLayout();
CanvasSlot->SetDesiredPosition(PasteLocation + PasteOffset);
CanvasSlot->RebaseLayout();
}
TMap<FName, FString> SlotProperties;
FWidgetBlueprintEditorUtils::ExportPropertiesToText(PreviewSlot, SlotProperties);
FWidgetBlueprintEditorUtils::ImportPropertiesFromText(TemplateSlot, SlotProperties);
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
else
{
check(RootPasteWidgets.Num() == 1)
// If we've arrived here, we must be creating the root widget from paste data, and there can only be
// one item in the paste data by now.
BP->WidgetTree->Modify();
if (RootPasteWidgets.Num() > 0)
{
BP->WidgetTree->RootWidget = RootPasteWidgets[0];
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
}
else
{
ImportWidgets();
if ( RootPasteWidgets.Num() > 1 )
{
FNotificationInfo Info(LOCTEXT("NamedSlotsOnlyHoldOneWidget", "Can't paste content, a slot can only hold one widget at the root."));
FSlateNotificationManager::Get().AddNotification(Info);
bTransactionSuccessful = false;
return TArray<UWidget*>();
}
BP->WidgetTree->Modify();
// If there's a ParentWidgetRef, then we're pasting into a named slot of a widget in the tree.
if (UWidget* NamedSlotHostWidget = ParentWidgetRef.GetTemplate())
{
NamedSlotHostWidget->SetFlags(RF_Transactional);
NamedSlotHostWidget->Modify();
INamedSlotInterface* NamedSlotInterface = Cast<INamedSlotInterface>(NamedSlotHostWidget);
NamedSlotInterface->SetContentForSlot(SlotName, RootPasteWidgets[0]);
}
else
{
// If there's no ParentWidgetRef then we're pasting into the exposed named slots of the widget tree.
// these are the slots that our parent class is exposing for use externally, but we can also override
// them as a subclass.
BP->WidgetTree->Modify();
BP->WidgetTree->SetContentForSlot(SlotName, RootPasteWidgets[0]);
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
return RootPasteWidgets;
}
FWidgetObjectTextFactory FWidgetBlueprintEditorUtils::ProcessImportedText(UWidgetBlueprint* BP, const FString& TextToImport, /*out*/ UPackage*& TempPackage)
{
// We create our own transient package here so that we can deserialize the data in isolation and ensure unreferenced
// objects not part of the deserialization set are unresolved.
TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/UMG/Editor/Transient"), RF_Transient);
// Force the transient package to have the same namespace as the final widget blueprint package.
// This ensures any text properties serialized from the buffer will be keyed correctly for the target package.
#if USE_STABLE_LOCALIZATION_KEYS
{
const FString PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(BP);
if (!PackageNamespace.IsEmpty())
{
TextNamespaceUtil::ForcePackageNamespace(TempPackage, PackageNamespace);
}
}
#endif // USE_STABLE_LOCALIZATION_KEYS
// Turn the text buffer into objects
FWidgetObjectTextFactory Factory;
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
return Factory;
}
void FWidgetBlueprintEditorUtils::ImportWidgetsFromText(UWidgetBlueprint* BP, const FString& TextToImport, /*out*/ TSet<UWidget*>& ImportedWidgetSet, /*out*/ TMap<FName, UWidgetSlotPair*>& PastedExtraSlotData)
{
UPackage* TempPackage = nullptr;
FWidgetObjectTextFactory Factory = ProcessImportedText(BP, TextToImport, TempPackage);
TGCObjectScopeGuard<UPackage> TempPackageGCGuard(TempPackage);
IUMGEditorModule& EditorModule = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor");
const TArrayView<const TSharedPtr<IClipboardExtension>> ClipboardExtensions = EditorModule.GetClipboardExtensibilityManager()->GetExtensions();
for (const TSharedPtr<IClipboardExtension>& ClipboardExtension : ClipboardExtensions)
{
ClipboardExtension->ProcessImportedText(BP, TextToImport, TempPackage);
}
PastedExtraSlotData = Factory.MissingSlotData;
for ( auto& Entry : Factory.NewWidgetMap )
{
UWidget* Widget = Entry.Value;
ImportedWidgetSet.Add(Widget);
Widget->SetFlags(RF_Transactional);
// We don't export parent slot pointers, so each panel will need to point it's children back to itself
UPanelWidget* PanelWidget = Cast<UPanelWidget>(Widget);
if (PanelWidget)
{
TArray<UPanelSlot*> PanelSlots = PanelWidget->GetSlots();
for (int32 i = 0; i < PanelWidget->GetChildrenCount(); i++)
{
UWidget* PanelChild = PanelWidget->GetChildAt(i);
if (ensure(PanelChild))
{
PanelChild->Slot = PanelSlots[i];
}
}
}
// If there is an existing widget with the same name, rename the newly placed widget.
FString WidgetOldName = Widget->GetName();
FString NewName = FindNextValidName(BP->WidgetTree, WidgetOldName);
if (NewName != WidgetOldName)
{
UWidgetSlotPair* SlotData = PastedExtraSlotData.FindRef(Widget->GetFName());
if ( SlotData )
{
PastedExtraSlotData.Remove(Widget->GetFName());
}
Widget->Rename(*NewName, BP->WidgetTree);
if (Widget->GetDisplayLabel().Equals(WidgetOldName))
{
Widget->SetDisplayLabel(Widget->GetName());
}
if ( SlotData )
{
SlotData->SetWidgetName(Widget->GetFName());
PastedExtraSlotData.Add(Widget->GetFName(), SlotData);
}
}
else
{
Widget->Rename(*WidgetOldName, BP->WidgetTree);
}
for (const TSharedPtr<IClipboardExtension>& Extension : ClipboardExtensions)
{
if (Extension->CanImportFromClipboard(Widget))
{
Extension->ImportDataToWidget(Widget, FName(WidgetOldName));
}
}
}
}
void FWidgetBlueprintEditorUtils::ExportPropertiesToText(UObject* Object, TMap<FName, FString>& ExportedProperties)
{
if ( Object )
{
static const TSet<FName> SpecialCaseProperties = { TEXT("bIsVariable") };
for ( TFieldIterator<FProperty> PropertyIt(Object->GetClass()); PropertyIt && PropertyIt.GetStruct() != UVisual::StaticClass(); ++PropertyIt )
{
FProperty* Property = *PropertyIt;
// Skip EditDefaultOnly, transient, and instanced properties, only include properties that the user can directly edit and some special cases
if (!Property->HasAnyPropertyFlags(CPF_TextExportTransient | CPF_Transient | CPF_DuplicateTransient | CPF_DisableEditOnInstance) && (
Property->HasAllPropertyFlags(CPF_Edit) ||
Property->IsA<FMulticastDelegateProperty>() ||
SpecialCaseProperties.Contains(Property->GetFName())))
{
FString ValueText;
if (Property->ExportText_InContainer(0, ValueText, Object, Object, Object, PPF_Copy))
{
ExportedProperties.Add(Property->GetFName(), ValueText);
}
}
}
}
}
void FWidgetBlueprintEditorUtils::ImportPropertiesFromText(UObject* Object, const TMap<FName, FString>& ExportedProperties)
{
if ( Object )
{
for ( const auto& Entry : ExportedProperties )
{
if ( FProperty* Property = FindFProperty<FProperty>(Object->GetClass(), Entry.Key) )
{
FEditPropertyChain PropertyChain;
PropertyChain.AddHead(Property);
Object->PreEditChange(PropertyChain);
Property->ImportText_InContainer(*Entry.Value, Object, Object, PPF_Copy);
FPropertyChangedEvent ChangedEvent(Property);
Object->PostEditChangeProperty(ChangedEvent);
}
}
}
}
bool FWidgetBlueprintEditorUtils::DoesClipboardTextContainWidget(UWidgetBlueprint* BP)
{
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
UPackage* TempPackage = nullptr;
FWidgetObjectTextFactory Factory = ProcessImportedText(BP, TextToImport, TempPackage);
return Factory.NewWidgetMap.Num() > 0;
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(const FProperty* InProperty)
{
bool bIsOptional;
return IsBindWidgetProperty(InProperty, bIsOptional);
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(const FProperty* InProperty, bool& bIsOptional)
{
if ( InProperty )
{
bool bIsBindWidget = InProperty->HasMetaData("BindWidget") || InProperty->HasMetaData("BindWidgetOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetOptional") || ( InProperty->HasMetaData("OptionalWidget") || InProperty->GetBoolMetaData("OptionalWidget") );
return bIsBindWidget;
}
return false;
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(const FProperty* InProperty)
{
bool bIsOptional;
return IsBindWidgetAnimProperty(InProperty, bIsOptional);
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(const FProperty* InProperty, bool& bIsOptional)
{
if (InProperty)
{
bool bIsBindWidgetAnim = InProperty->HasMetaData("BindWidgetAnim") || InProperty->HasMetaData("BindWidgetAnimOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetAnimOptional");
return bIsBindWidgetAnim;
}
return false;
}
namespace UE::UMG::Private
{
/** Helper class to perform path based filtering for unloaded BP's */
class FUnloadedBlueprintData : public IUnloadedBlueprintData
{
public:
FUnloadedBlueprintData(const FAssetData& InAssetData)
:ClassPath()
, ClassFlags(CLASS_None)
, bIsNormalBlueprintType(false)
{
ClassName = MakeShared<FString>(InAssetData.AssetName.ToString());
FString GeneratedClassPath;
const UClass* AssetClass = InAssetData.GetClass();
if (AssetClass && AssetClass->IsChildOf(UBlueprintGeneratedClass::StaticClass()))
{
ClassPath = InAssetData.ToSoftObjectPath().GetAssetPathString();
}
else if (InAssetData.GetTagValue(FBlueprintTags::GeneratedClassPath, GeneratedClassPath))
{
ClassPath = FTopLevelAssetPath(*FPackageName::ExportTextPathToObjectPath(GeneratedClassPath));
}
FEditorClassUtils::GetImplementedInterfaceClassPathsFromAsset(InAssetData, ImplementedInterfaces);
}
virtual ~FUnloadedBlueprintData()
{
}
// Begin IUnloadedBlueprintData interface
virtual bool HasAnyClassFlags(uint32 InFlagsToCheck) const
{
return (ClassFlags & InFlagsToCheck) != 0;
}
virtual bool HasAllClassFlags(uint32 InFlagsToCheck) const
{
return ((ClassFlags & InFlagsToCheck) == InFlagsToCheck);
}
virtual void SetClassFlags(uint32 InFlags)
{
ClassFlags = InFlags;
}
virtual bool ImplementsInterface(const UClass* InInterface) const
{
FString InterfacePath = InInterface->GetPathName();
for (const FString& ImplementedInterface : ImplementedInterfaces)
{
if (ImplementedInterface == InterfacePath)
{
return true;
}
}
return false;
}
virtual bool IsChildOf(const UClass* InClass) const
{
return false;
}
virtual bool IsA(const UClass* InClass) const
{
// Unloaded blueprint classes should always be a BPGC, so this just checks against the expected type.
return UBlueprintGeneratedClass::StaticClass()->UObject::IsA(InClass);
}
virtual const UClass* GetClassWithin() const
{
return nullptr;
}
virtual const UClass* GetNativeParent() const
{
return nullptr;
}
virtual void SetNormalBlueprintType(bool bInNormalBPType)
{
bIsNormalBlueprintType = bInNormalBPType;
}
virtual bool IsNormalBlueprintType() const
{
return bIsNormalBlueprintType;
}
virtual TSharedPtr<FString> GetClassName() const
{
return ClassName;
}
virtual FName GetClassPath() const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return ClassPath.ToFName();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
virtual FTopLevelAssetPath GetClassPathName() const
{
return ClassPath;
}
// End IUnloadedBlueprintData interface
private:
TSharedPtr<FString> ClassName;
FTopLevelAssetPath ClassPath;
uint32 ClassFlags;
TArray<FString> ImplementedInterfaces;
bool bIsNormalBlueprintType;
};
bool IsUsableWidgetClass(const FString& WidgetPathName, const FAssetData& WidgetAssetData, FName Category, const UClass* WidgetClass, TSharedRef<FWidgetBlueprintEditor> InCurrentActiveBlueprintEditor)
{
const UWidgetEditingProjectSettings* UMGEditorProjectSettings = FWidgetBlueprintEditorUtils::GetRelevantSettings(InCurrentActiveBlueprintEditor);
// Excludes engine content if user sets it to false
if (!UMGEditorProjectSettings->bShowWidgetsFromEngineContent)
{
if (WidgetPathName.StartsWith(TEXT("/Engine")))
{
return false;
}
}
// Excludes developer content if user sets it to false
if (!UMGEditorProjectSettings->bShowWidgetsFromDeveloperContent)
{
if (WidgetPathName.StartsWith(TEXT("/Game/Developers")))
{
return false;
}
}
UWidgetBlueprint* WidgetBP = InCurrentActiveBlueprintEditor->GetWidgetBlueprintObj();
const bool bAllowEditorWidget = WidgetBP ? WidgetBP->AllowEditorWidget() : false;
if (!bAllowEditorWidget)
{
if (WidgetClass && IsEditorOnlyObject(WidgetClass))
{
return false;
}
else if (WidgetAssetData.IsValid())
{
// should not load since the default for GetClass is EResolveClass::No
const UClass* AssetClass = WidgetAssetData.GetClass();
if (AssetClass && IsEditorOnlyObject(AssetClass))
{
return false;
}
}
}
if (UMGEditorProjectSettings->bUseEditorConfigPaletteFiltering)
{
FClassViewerModule* ClassViewerModule = FModuleManager::GetModulePtr<FClassViewerModule>("ClassViewer");
const TSharedPtr<IClassViewerFilter> GlobalClassFilter = ClassViewerModule ? ClassViewerModule->GetGlobalClassViewerFilter() : TSharedPtr<IClassViewerFilter>();
if (UMGEditorProjectSettings->GetAllowedPaletteCategories().PassesFilter(Category) && GlobalClassFilter.IsValid())
{
if (WidgetClass)
{
return GlobalClassFilter->IsClassAllowed(FClassViewerInitializationOptions(), WidgetClass, ClassViewerModule->CreateFilterFuncs());
}
else if (WidgetAssetData.IsValid())
{
TSharedRef<FUnloadedBlueprintData> UnloadedBlueprint = MakeShared<FUnloadedBlueprintData>(WidgetAssetData);
return GlobalClassFilter->IsUnloadedClassAllowed(FClassViewerInitializationOptions(), UnloadedBlueprint, ClassViewerModule->CreateFilterFuncs());
}
}
auto IsPathUnderMountPoints = [](FStringView Path)
{
static const FString EnginePath = TEXT("Engine");
static const FString GamePath = TEXT("Game");
const TSet<FString>& MountPoints = IPluginManager::Get().GetBuiltInPluginNames();
if (MountPoints.Num() > 0)
{
const FStringView MountPoint = FPathViews::GetMountPointNameFromPath(Path);
return MountPoints.ContainsByHash(GetTypeHash(MountPoint), MountPoint)
|| MountPoint.Equals(EnginePath, ESearchCase::IgnoreCase)
|| MountPoint.Equals(GamePath, ESearchCase::IgnoreCase);
}
return false;
};
const bool bPassesAllowedPalletteFilter = UMGEditorProjectSettings->GetAllowedPaletteWidgets().PassesFilter(WidgetPathName);
if (FPackageName::IsScriptPackage(WidgetPathName))
{
return bPassesAllowedPalletteFilter;
}
const bool bPathUnderMountPoints = IsPathUnderMountPoints(WidgetPathName);
if (bPathUnderMountPoints && !bPassesAllowedPalletteFilter)
{
return false;
}
return true;
}
else
{
// Excludes this widget if it is on the hide list
for (const FSoftClassPath& WidgetClassToHide : UMGEditorProjectSettings->WidgetClassesToHide)
{
if (WidgetPathName.Find(WidgetClassToHide.ToString()) == 0)
{
return false;
}
}
}
return true;
}
}
bool FWidgetBlueprintEditorUtils::IsUsableWidgetClass(const UClass* WidgetClass)
{
return false;
}
TValueOrError<FWidgetBlueprintEditorUtils::FUsableWidgetClassResult, void> FWidgetBlueprintEditorUtils::IsUsableWidgetClass(const FAssetData& WidgetAsset)
{
return MakeError();
}
bool FWidgetBlueprintEditorUtils::IsUsableWidgetClass(const UClass* WidgetClass, TSharedRef<FWidgetBlueprintEditor> BlueprintEditor)
{
if (WidgetClass->IsChildOf(UWidget::StaticClass()))
{
// We aren't interested in classes that are experimental or cannot be instantiated
bool bIsExperimental, bIsEarlyAccess;
FString MostDerivedDevelopmentClassName;
FObjectEditorUtils::GetClassDevelopmentStatus(const_cast<UClass*>(WidgetClass), bIsExperimental, bIsEarlyAccess, MostDerivedDevelopmentClassName);
const bool bIsInvalid = WidgetClass->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists);
if (bIsExperimental || bIsEarlyAccess || bIsInvalid)
{
return false;
}
// Don't include skeleton classes or the same class as the widget being edited
const bool bIsSkeletonClass = WidgetClass->HasAnyFlags(RF_Transient) && WidgetClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint);
// Check that the asset that generated this class is valid (necessary b/c of a larger issue wherein force delete does not wipe the generated class object)
if (bIsSkeletonClass)
{
return false;
}
return UE::UMG::Private::IsUsableWidgetClass(WidgetClass->GetPathName(), FAssetData(), *WidgetClass->GetDefaultObject<UWidget>()->GetPaletteCategory().ToString(), WidgetClass, BlueprintEditor);
}
return false;
}
TValueOrError<FWidgetBlueprintEditorUtils::FUsableWidgetClassResult, void> FWidgetBlueprintEditorUtils::IsUsableWidgetClass(const FAssetData& WidgetAsset, TSharedRef<FWidgetBlueprintEditor> InCurrentActiveBlueprintEditor)
{
if (const UClass* WidgetAssetClass = WidgetAsset.GetClass(EResolveClass::No))
{
if (IsUsableWidgetClass(WidgetAssetClass, InCurrentActiveBlueprintEditor))
{
FWidgetBlueprintEditorUtils::FUsableWidgetClassResult Result;
Result.NativeParentClass = WidgetAssetClass;
Result.AssetClassFlags = WidgetAssetClass->GetClassFlags();
return MakeValue(Result);
}
}
// Blueprints get the class type actions for their parent native class - this avoids us having to load the blueprint
UClass* NativeParentClass = nullptr;
FString NativeParentClassName;
WidgetAsset.GetTagValue(FBlueprintTags::NativeParentClassPath, NativeParentClassName);
if (NativeParentClassName.IsEmpty())
{
return MakeError();
}
else
{
const FString NativeParentClassPath = FPackageName::ExportTextPathToObjectPath(NativeParentClassName);
if (NativeParentClassPath.StartsWith(TEXT("/")))
{
// Metadata may be pointing to classes that no longer exist, so check for redirectors first
const FString RedirectedClassPath = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Class, FCoreRedirectObjectName(NativeParentClassPath)).ToString();
NativeParentClass = UClass::TryFindTypeSlow<UClass>(RedirectedClassPath);
}
if (NativeParentClass == nullptr)
{
return MakeError();
}
if (!NativeParentClass->IsChildOf(UWidget::StaticClass()))
{
return MakeError();
}
}
EClassFlags BPFlags = static_cast<EClassFlags>(WidgetAsset.GetTagValueRef<uint32>(FBlueprintTags::ClassFlags));
const bool bIsInvalid = (BPFlags & (CLASS_Deprecated | CLASS_Abstract | CLASS_NewerVersionExists)) != 0;
if (bIsInvalid)
{
return MakeError();
}
const FName CategoryName = *GetPaletteCategory(WidgetAsset, TSubclassOf<UWidget>(NativeParentClass)).ToString();
if (UE::UMG::Private::IsUsableWidgetClass(WidgetAsset.GetObjectPathString(), WidgetAsset, CategoryName, nullptr, InCurrentActiveBlueprintEditor))
{
FWidgetBlueprintEditorUtils::FUsableWidgetClassResult Result;
Result.NativeParentClass = NativeParentClass;
Result.AssetClassFlags = BPFlags;
return MakeValue(Result);
}
return MakeError();
}
FString RemoveSuffixFromName(const FString OldName)
{
int NameLen = OldName.Len();
int SuffixIndex = 0;
if (OldName.FindLastChar('_', SuffixIndex))
{
NameLen = SuffixIndex;
for (int32 i = SuffixIndex + 1; i < OldName.Len(); ++i)
{
const TCHAR& C = OldName[i];
const bool bGoodChar = ((C >= '0') && (C <= '9'));
if (!bGoodChar)
{
return OldName;
}
}
}
return FString::ConstructFromPtrSize(*OldName, NameLen);
}
FString FWidgetBlueprintEditorUtils::FindNextValidName(UWidgetTree* WidgetTree, const FString& Name)
{
// If the name of the widget is not already used, we use it.
if (FindObject<UObject>(WidgetTree, *Name))
{
// If the name is already used, we will suffix it with '_X'
FString NameWithoutSuffix = RemoveSuffixFromName(Name);
FString NewName = NameWithoutSuffix;
int32 Postfix = 0;
while (FindObject<UObject>(WidgetTree, *NewName))
{
++Postfix;
NewName = FString::Printf(TEXT("%s_%d"), *NameWithoutSuffix, Postfix);
}
return NewName;
}
return Name;
}
UWidgetTree* FWidgetBlueprintEditorUtils::FindLatestWidgetTree(UWidgetBlueprint* Blueprint, UUserWidget* UserWidget)
{
UWidgetTree* LatestWidgetTree = Blueprint->WidgetTree;
// If there is no RootWidget, we look for a WidgetTree in the parents classes until we find one.
if (LatestWidgetTree->RootWidget == nullptr)
{
UWidgetBlueprintGeneratedClass* BGClass = UserWidget->GetWidgetTreeOwningClass();
// If we find a class that owns the widget tree, just make sure it's not our current class, that would imply we've removed all the widgets
// from this current tree, and if we use this classes compiled tree it's going to be the outdated old version.
if (BGClass && BGClass != Blueprint->GeneratedClass)
{
LatestWidgetTree = BGClass->GetWidgetTreeArchetype();
}
}
return LatestWidgetTree;
}
int32 FWidgetBlueprintEditorUtils::UpdateHittestGrid(FHittestGrid& HitTestGrid, TSharedRef<SWindow> Window, float Scale, FVector2D DrawSize, float DeltaTime)
{
FSlateApplication::Get().InvalidateAllWidgets(false);
const FGeometry WindowGeometry = FGeometry::MakeRoot(DrawSize * (1.f / Scale), FSlateLayoutTransform(Scale));
const FSlateRect WindowClipRect = WindowGeometry.GetLayoutBoundingRect();
FPaintArgs PaintArgs(nullptr, HitTestGrid, FVector2D::ZeroVector, FApp::GetCurrentTime(), DeltaTime);
FSlateRenderer* MainSlateRenderer = FSlateApplication::Get().GetRenderer();
FScopeLock ScopeLock(MainSlateRenderer->GetResourceCriticalSection());
Window->SlatePrepass(WindowGeometry.Scale);
PaintArgs.GetHittestGrid().SetHittestArea(WindowClipRect.GetTopLeft(), WindowClipRect.GetSize());
PaintArgs.GetHittestGrid().Clear();
// Get the free buffer & add our virtual window
bool bUseGammaSpace = false;
TSharedPtr<ISlate3DRenderer, ESPMode::ThreadSafe> Renderer = FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer")
.CreateSlate3DRenderer(bUseGammaSpace);
int32 MaxLayerId = 0;
{
ISlate3DRenderer::FScopedAcquireDrawBuffer ScopedDrawBuffer{ *Renderer };
FSlateWindowElementList& WindowElementList = ScopedDrawBuffer.GetDrawBuffer().AddWindowElementList(Window);
MaxLayerId = Window->Paint(
PaintArgs,
WindowGeometry, WindowClipRect,
WindowElementList,
0,
FWidgetStyle(),
Window->IsEnabled());
}
FSlateApplication::Get().InvalidateAllWidgets(false);
return MaxLayerId;
}
TTuple<FVector2D, FVector2D> FWidgetBlueprintEditorUtils::GetWidgetPreviewAreaAndSize(UUserWidget* UserWidget, FVector2D DesiredSize, FVector2D PreviewSize, EDesignPreviewSizeMode SizeMode, TOptional<FVector2D> ThumbnailCustomSize)
{
FVector2D Size(PreviewSize.X, PreviewSize.Y);
FVector2D Area(PreviewSize.X, PreviewSize.Y);
if (UserWidget)
{
switch (SizeMode)
{
case EDesignPreviewSizeMode::Custom:
Area = ThumbnailCustomSize.IsSet()? ThumbnailCustomSize.GetValue() : UserWidget->DesignTimeSize;
// If the custom size is 0 in some dimension, use the desired size instead.
if (Area.X == 0)
{
Area.X = DesiredSize.X;
}
if (Area.Y == 0)
{
Area.Y = DesiredSize.Y;
}
Size = Area;
break;
case EDesignPreviewSizeMode::CustomOnScreen:
Size = ThumbnailCustomSize.IsSet() ? ThumbnailCustomSize.GetValue() : UserWidget->DesignTimeSize;
// If the custom size is 0 in some dimension, use the desired size instead.
if (Size.X == 0)
{
Size.X = DesiredSize.X;
}
if (Size.Y == 0)
{
Size.Y = DesiredSize.Y;
}
return TTuple<FVector2D, FVector2D>(Area, Size);
case EDesignPreviewSizeMode::Desired:
Area = DesiredSize;
// Fall through to DesiredOnScreen
case EDesignPreviewSizeMode::DesiredOnScreen:
Size = DesiredSize;
return TTuple<FVector2D, FVector2D>(Area, Size);
case EDesignPreviewSizeMode::FillScreen:
break;
}
}
return TTuple<FVector2D, FVector2D>(Area, Size);
}
float FWidgetBlueprintEditorUtils::GetWidgetPreviewDPIScale(UUserWidget* UserWidget, FVector2D PreviewSize)
{
// If the user is using a custom size then we disable the DPI scaling logic.
if (UserWidget)
{
if (UserWidget->DesignSizeMode == EDesignPreviewSizeMode::Custom ||
UserWidget->DesignSizeMode == EDesignPreviewSizeMode::Desired)
{
return 1.0f;
}
}
return GetDefault<UUserInterfaceSettings>()->GetDPIScaleBasedOnSize(FIntPoint(FMath::TruncToInt32(PreviewSize.X), FMath::TruncToInt32(PreviewSize.Y)));
}
FVector2D FWidgetBlueprintEditorUtils::GetWidgetPreviewUnScaledCustomSize(FVector2D DesiredSize, UUserWidget* UserWidget, TOptional<FVector2D> ThumbnailCustomSize, EThumbnailPreviewSizeMode ThumbnailSizeMode)
{
checkf(DesiredSize.X > 0.f && DesiredSize.Y > 0.f, TEXT("The size should have been previously checked to be > 0."));
FVector2D FinalSize(0.f,0.f);
int32 PreviewWidth;
const TCHAR* ConfigSectionName = TEXT("UMGEditor.Designer");
GConfig->GetInt(ConfigSectionName, TEXT("PreviewWidth"), PreviewWidth, GEditorPerProjectIni);
int32 PreviewHeight;
GConfig->GetInt(ConfigSectionName, TEXT("PreviewHeight"), PreviewHeight, GEditorPerProjectIni);
FVector2D PreviewSize(PreviewWidth, PreviewHeight);
TTuple<FVector2D, FVector2D> AreaAndSize = GetWidgetPreviewAreaAndSize(UserWidget, DesiredSize, PreviewSize, ConvertThumbnailSizeModeToDesignerSizeMode(ThumbnailSizeMode, UserWidget), ThumbnailCustomSize.IsSet() ? ThumbnailCustomSize.GetValue() : TOptional<FVector2D>());
float DPIScale;
if (ThumbnailCustomSize.IsSet())
{
DPIScale = 1.0f;
}
else
{
DPIScale = GetWidgetPreviewDPIScale(UserWidget, PreviewSize);
}
if (ensure(DPIScale > 0.f))
{
FinalSize = AreaAndSize.Get<1>() / DPIScale;
}
return FinalSize;
}
EDesignPreviewSizeMode FWidgetBlueprintEditorUtils::ConvertThumbnailSizeModeToDesignerSizeMode(EThumbnailPreviewSizeMode ThumbnailSizeMode, UUserWidget* WidgetInstance)
{
switch (ThumbnailSizeMode)
{
case EThumbnailPreviewSizeMode::MatchDesignerMode:
return WidgetInstance->DesignSizeMode;
case EThumbnailPreviewSizeMode::FillScreen:
return EDesignPreviewSizeMode::FillScreen;
case EThumbnailPreviewSizeMode::Custom:
return EDesignPreviewSizeMode::Custom;
case EThumbnailPreviewSizeMode::Desired:
return EDesignPreviewSizeMode::Desired;
default:
return EDesignPreviewSizeMode::Desired;
}
}
TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties> FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTarget(UUserWidget* WidgetInstance, UTextureRenderTarget2D* RenderTarget2D)
{
return DrawSWidgetInRenderTargetInternal(WidgetInstance, nullptr, RenderTarget2D, FVector2D(256.f,256.f), false, TOptional<FVector2D>(), EThumbnailPreviewSizeMode::MatchDesignerMode);
}
UWidgetEditingProjectSettings* FWidgetBlueprintEditorUtils::GetRelevantMutableSettings(TWeakPtr<FWidgetBlueprintEditor> CurrentEditor)
{
if (TSharedPtr<FWidgetBlueprintEditor> PinnedEditor = CurrentEditor.Pin())
{
if (UWidgetBlueprint* WidgetBP = PinnedEditor->GetWidgetBlueprintObj())
{
return WidgetBP->GetRelevantSettings();
}
}
// Fall back to the UMG Editor settings as default
return GetMutableDefault<UUMGEditorProjectSettings>();
}
const UWidgetEditingProjectSettings* FWidgetBlueprintEditorUtils::GetRelevantSettings(TWeakPtr<FWidgetBlueprintEditor> CurrentEditor)
{
if (TSharedPtr<FWidgetBlueprintEditor> PinnedEditor = CurrentEditor.Pin())
{
if (UWidgetBlueprint* WidgetBP = PinnedEditor->GetWidgetBlueprintObj())
{
return WidgetBP->GetRelevantSettings();
}
}
// Fall back to the UMG Editor settings as default
return GetDefault<UUMGEditorProjectSettings>();
}
TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties> FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTargetForThumbnail(UUserWidget* WidgetInstance, FRenderTarget* RenderTarget2D, FVector2D ThumbnailSize, TOptional<FVector2D> ThumbnailCustomSize, EThumbnailPreviewSizeMode ThumbnailSizeMode)
{
return DrawSWidgetInRenderTargetInternal(WidgetInstance, RenderTarget2D, nullptr,ThumbnailSize, true, ThumbnailCustomSize, ThumbnailSizeMode);
}
TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties> FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTargetForThumbnail(UUserWidget* WidgetInstance, UTextureRenderTarget2D* RenderTarget2D, FVector2D ThumbnailSize, TOptional<FVector2D> ThumbnailCustomSize, EThumbnailPreviewSizeMode ThumbnailSizeMode)
{
return DrawSWidgetInRenderTargetInternal(WidgetInstance, nullptr, RenderTarget2D,ThumbnailSize, true, ThumbnailCustomSize, ThumbnailSizeMode);
}
TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties> FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTargetInternal(UUserWidget* WidgetInstance, FRenderTarget* RenderTarget2D, UTextureRenderTarget2D* TextureRenderTarget,FVector2D ThumbnailSize, bool bIsForThumbnail, TOptional<FVector2D> ThumbnailCustomSize, EThumbnailPreviewSizeMode ThumbnailSizeMode)
{
if (TextureRenderTarget == nullptr && RenderTarget2D == nullptr)
{
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>();
}
//Create Window
FVector2D Offset(0.f, 0.f);
FVector2D ScaledSize(0.f, 0.f);
TSharedPtr<SWidget> WindowContent = WidgetInstance->TakeWidget();
if (!WindowContent)
{
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>();
}
TSharedRef<SVirtualWindow> Window = SNew(SVirtualWindow);
TUniquePtr<FHittestGrid> HitTestGrid = MakeUnique<FHittestGrid>();
Window->SetContent(WindowContent.ToSharedRef());
Window->Resize(ThumbnailSize);
// Store the desired size to maintain the aspect ratio later
FGeometry WindowGeometry = FGeometry::MakeRoot(ThumbnailSize, FSlateLayoutTransform(1.0f));
Window->SlatePrepass(1.0f);
FVector2D DesiredSizeWindow = Window->GetDesiredSize();
if (DesiredSizeWindow.X < SMALL_NUMBER || DesiredSizeWindow.Y < SMALL_NUMBER)
{
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>();
}
FVector2D UnscaledSize = FWidgetBlueprintEditorUtils::GetWidgetPreviewUnScaledCustomSize(DesiredSizeWindow, WidgetInstance, ThumbnailCustomSize, ThumbnailSizeMode);
if (UnscaledSize.X < SMALL_NUMBER || UnscaledSize.Y < SMALL_NUMBER)
{
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>();
}
float Scale = 1.f;
// Change some configuration if it is for thumbnail creation
if (bIsForThumbnail)
{
TTuple<float, FVector2D> ScaleAndOffset = FWidgetBlueprintEditorUtils::GetThumbnailImageScaleAndOffset(UnscaledSize, ThumbnailSize);
Scale = ScaleAndOffset.Get<0>();
Offset = ScaleAndOffset.Get<1>();
}
ScaledSize = UnscaledSize * Scale;
if (ScaledSize.X < 1.f || ScaledSize.Y < 1.f)
{
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>();
}
// Create Renderer Target and WidgetRenderer
bool bApplyGammaCorrection = RenderTarget2D? true : false;
FWidgetRenderer* WidgetRenderer = new FWidgetRenderer(bApplyGammaCorrection);
if (!bIsForThumbnail)
{
WidgetRenderer->SetIsPrepassNeeded(false);
}
if (TextureRenderTarget)
{
TextureRenderTarget->Filter = TF_Bilinear;
TextureRenderTarget->ClearColor = FLinearColor::Transparent;
TextureRenderTarget->SRGB = true;
TextureRenderTarget->RenderTargetFormat = RTF_RGBA8;
uint32 ScaledSizeX = static_cast<uint32>(ScaledSize.X);
uint32 ScaledSizeY = static_cast<uint32>(ScaledSize.Y);
const bool bForceLinearGamma = false;
const EPixelFormat RequestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat();
TextureRenderTarget->InitCustomFormat(ScaledSizeX, ScaledSizeY, RequestedFormat, bForceLinearGamma);
WidgetRenderer->DrawWindow(TextureRenderTarget, *HitTestGrid, Window, Scale, ScaledSize, 0.1f);
}
else
{
ensure(RenderTarget2D != nullptr);
WidgetRenderer->SetShouldClearTarget(false);
WidgetRenderer->ViewOffset = Offset;
WidgetRenderer->DrawWindow(RenderTarget2D, *HitTestGrid, Window, Scale, ScaledSize, 0.1f);
}
if (WidgetRenderer)
{
BeginCleanup(WidgetRenderer);
WidgetRenderer = nullptr;
}
return TOptional<FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties>(FWidgetBlueprintEditorUtils::FWidgetThumbnailProperties{ ScaledSize,Offset });
}
TTuple<float, FVector2D> FWidgetBlueprintEditorUtils::GetThumbnailImageScaleAndOffset(FVector2D WidgetSize, FVector2D ThumbnailSize)
{
// Scale the widget blueprint image to fit in the thumbnail
checkf(WidgetSize.X > 0.f && WidgetSize.Y > 0.f, TEXT("The size should have been previously checked to be > 0."));
float Scale;
double XOffset = 0;
double YOffset = 0;
if (WidgetSize.X > WidgetSize.Y)
{
Scale = static_cast<float>(ThumbnailSize.X / WidgetSize.X);
WidgetSize *= Scale;
YOffset = (ThumbnailSize.Y - WidgetSize.Y) / 2.f;
}
else
{
Scale = static_cast<float>(ThumbnailSize.Y / WidgetSize.Y);
WidgetSize *= Scale;
XOffset = (ThumbnailSize.X - WidgetSize.X) / 2.f;
}
return TTuple<float, FVector2D>(Scale, FVector2D(XOffset, YOffset));
}
void FWidgetBlueprintEditorUtils::SetTextureAsAssetThumbnail(UWidgetBlueprint* WidgetBlueprint, UTexture2D* ThumbnailTexture)
{
const TCHAR* ThumbnailName = TEXT("Thumbnail");
UTexture2D* ExistingThumbnail = FindObject<UTexture2D>(WidgetBlueprint, ThumbnailName, false);
if (ExistingThumbnail)
{
ExistingThumbnail->Rename(nullptr, GetTransientPackage());
}
if (!ThumbnailTexture)
{
WidgetBlueprint->ThumbnailImage = nullptr;
return;
}
FVector2D TextureSize(ThumbnailTexture->GetSizeX(), ThumbnailTexture->GetSizeY());
if (TextureSize.X < SMALL_NUMBER || TextureSize.Y < SMALL_NUMBER)
{
WidgetBlueprint->ThumbnailImage = nullptr;
}
else
{
ThumbnailTexture->Rename(ThumbnailName, WidgetBlueprint, REN_NonTransactional | REN_DontCreateRedirectors);
WidgetBlueprint->ThumbnailImage = ThumbnailTexture;
}
}
FText FWidgetBlueprintEditorUtils::GetPaletteCategory(const TSubclassOf<UWidget> WidgetClass)
{
if (WidgetClass.Get())
{
return WidgetClass.GetDefaultObject()->GetPaletteCategory();
}
return GetMutableDefault<UWidget>()->GetPaletteCategory();
}
FText FWidgetBlueprintEditorUtils::GetPaletteCategory(const FAssetData& WidgetAsset, const TSubclassOf<UWidget> NativeClass)
{
//The asset can be a UBlueprint, UBlueprintGeneratedClass, a UWidgetBlueprint or a UWidgetBlueprintGeneratedClass
if (UClass* WidgetAssetClass = WidgetAsset.GetClass(EResolveClass::No))
{
if (WidgetAssetClass->IsChildOf(UWidget::StaticClass()))
{
return GetPaletteCategory(TSubclassOf<UWidget>(WidgetAssetClass));
}
}
//If the blueprint is unloaded we need to extract it from the asset metadata.
FText FoundPaletteCategoryText = WidgetAsset.GetTagValueRef<FText>(GET_MEMBER_NAME_CHECKED(UWidgetBlueprint, PaletteCategory));
if (!FoundPaletteCategoryText.IsEmpty())
{
return FoundPaletteCategoryText;
}
else if (NativeClass.Get() != nullptr && NativeClass->IsChildOf(UWidget::StaticClass()) && !NativeClass->IsChildOf(UUserWidget::StaticClass()))
{
return NativeClass.GetDefaultObject()->GetPaletteCategory();
}
static const FTopLevelAssetPath BlueprintGeneratedClassAssetPath = UWidgetBlueprintGeneratedClass::StaticClass()->GetClassPathName();
static const FTopLevelAssetPath WidgetBlueprintAssetPath = UWidgetBlueprint::StaticClass()->GetClassPathName();
if (WidgetAsset.AssetClassPath == BlueprintGeneratedClassAssetPath || WidgetAsset.AssetClassPath == WidgetBlueprintAssetPath)
{
return GetMutableDefault<UUserWidget>()->GetPaletteCategory();
}
else
{
return GetMutableDefault<UWidget>()->GetPaletteCategory();
}
}
UWidgetBlueprint* FWidgetBlueprintEditorUtils::GetWidgetBlueprintFromWidget(const UWidget* Widget)
{
if (Widget)
{
if (UObject* WidgetTree = Widget->GetOuter())
{
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(WidgetTree->GetOuter());
if (WidgetBlueprint)
{
return WidgetBlueprint;
}
else if (WidgetTree->GetOuter())
{
WidgetBlueprint = Cast<UWidgetBlueprint>(WidgetTree->GetOuter()->GetClass()->ClassGeneratedBy);
if (WidgetBlueprint)
{
return WidgetBlueprint;
}
}
}
}
return nullptr;
}
TSet<UWidget*> FWidgetBlueprintEditorUtils::ResolveWidgetTemplates(const TSet<FWidgetReference>& Widgets)
{
TSet<UWidget*> Templates;
Algo::TransformIf(Widgets, Templates,
[&](const FWidgetReference& Widget)
{
return Widget.GetTemplate() != nullptr;
},
[&](const FWidgetReference& Widget)
{
return Widget.GetTemplate();
});
return Templates;
}
#undef LOCTEXT_NAMESPACE