Files
UnrealEngineUWP/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp
robert manuszewski d1443992e1 Deprecating ANY_PACKAGE.
This change consists of multiple changes:

Core:
- Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject)
- Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter
- Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses
- Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names
- Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used  in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed.
- Added static UClass::TryConvertShortNameToPathName utility function
- Added static UClass::TryFixShortClassNameExportPath utility function
- Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass')
- All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath
- Added a new startup test that checks for short type names in UClass/FProperty MetaData values

AssetRegistry:
- Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath
- Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names
- This also applies to a few other modules' APIs to match AssetRegistry changes

Everything else:
- Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input)
- Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName()
- Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names

#jira UE-99463
#rb many.people
[FYI] Marcus.Wassmer
#preflight 629248ec2256738f75de9b32

#codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786

#ROBOMERGE-OWNER: robert.manuszewski
#ROBOMERGE-AUTHOR: robert.manuszewski
#ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246)

[CL 20448496 by robert manuszewski in ue5-main branch]
2022-06-01 03:46:59 -04:00

2337 lines
78 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 "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "Internationalization/TextPackageNamespaceUtil.h"
#include "UObject/PropertyPortFlags.h"
#include "Blueprint/WidgetTree.h"
#include "Misc/ConfigCacheIni.h"
#include "Modules/ModuleManager.h"
#include "MovieScene.h"
#include "WidgetBlueprint.h"
#include "HAL/PlatformApplicationMisc.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/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/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 "Widgets/SVirtualWindow.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;
}
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));
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();
// Rename Preview before renaming the template widget so the preview widget can be found
UWidget* WidgetPreview = BlueprintEditor->GetReferenceFromTemplate(Widget).GetPreview();
if (WidgetPreview)
{
WidgetPreview->SetDisplayLabel(NewNameStr);
WidgetPreview->Rename(*NewNameStr);
}
if (!WidgetPreview || WidgetPreview != Widget)
{
// Find and update all variable references in the graph
Widget->SetDisplayLabel(NewNameStr);
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
// Update Variable References and
// Update Event References to member variables
FBlueprintEditorUtils::ReplaceVariableReferences(Blueprint, 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);
}
});
// 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);
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.PushCommandList(BlueprintEditor->DesignerCommandList.ToSharedRef());
MenuBuilder.BeginSection("Edit", LOCTEXT("Edit", "Edit"));
{
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);
}
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 )
)
);
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();
}
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(UWidgetBlueprint* Blueprint, TSet<FWidgetReference> Widgets, bool bSilentDelete /*=false*/)
{
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 (!bSilentDelete && UsedVariables.Num()!= 0 && !ShouldContinueDeleteOperation(Blueprint, WidgetNames))
{
Transaction.Cancel();
return;
}
Blueprint->WidgetTree->SetFlags(RF_Transactional);
Blueprint->WidgetTree->Modify();
Blueprint->Modify();
bool bRemoved = false;
for ( FWidgetReference& Item : Widgets )
{
UWidget* WidgetTemplate = Item.GetTemplate();
WidgetTemplate->SetFlags(RF_Transactional);
// 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 removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name.
WidgetTemplate->Rename(nullptr, GetTransientPackage());
// 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 )
{
Widget->SetFlags(RF_Transactional);
Widget->Modify();
if (UsedVariables.Contains(Widget))
{
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, Widget->GetFName());
}
Widget->Rename(nullptr, GetTransientPackage());
}
}
//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<FWidgetReference>& Widgets, const UWidgetBlueprint* BP, TArray<UWidget*>& UsedVariables, TArray<FText>& WidgetNames, bool bIncludeVariablesOnChildren)
{
TSet<UWidget*> AllWidgets;
AllWidgets.Reserve(Widgets.Num());
for (const FWidgetReference& Item : Widgets)
{
AllWidgets.Add(Item.GetTemplate());
if (bIncludeVariablesOnChildren)
{
TArray<UWidget*> ChildWidgets;
UWidgetTree::GetChildWidgets(Item.GetTemplate(), 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)
{
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) )
{
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);
UWidget* CurrentSlot = FindNamedSlotHostWidgetForContent(Widget, BP->WidgetTree);
// If the widget doesn't currently have a slot or parent, and isn't the root, ignore it.
if (CurrentSlot == nullptr && CurrentParent == nullptr && Widget != BP->WidgetTree->RootWidget)
{
continue;
}
Widget->Modify();
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
if (CurrentSlot)
{
// If this is a named slot, we need to properly remove and reassign the slot content
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = TScriptInterface<INamedSlotInterface>(CurrentSlot))
{
CurrentSlot->SetFlags(RF_Transactional);
CurrentSlot->Modify();
UPanelWidget* NewSlotContents = CastChecked<UPanelWidget>(Template->Create(BP->WidgetTree));
NewSlotContents->SetDesignerFlags(BlueprintEditor->GetCurrentDesignerFlags());
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());
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->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.ObjectPath != NAME_None)
{
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) )
{
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, BlueprintEditor, BP, Widgets, ReplacementClass)
));
}
}
Menu.EndSection();
}
void FWidgetBlueprintEditorUtils::ReplaceWidgetWithSelectedTemplate(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget)
{
// @Todo: Needs to deal with bound object in animation tracks
const FScopedTransaction Transaction(LOCTEXT("ReplaceWidgets", "Replace Widgets"));
bool bIsUserWidget = false;
UWidget* ThisWidget = Widget.GetTemplate();
UWidget* NewReplacementWidget;
if (BlueprintEditor->GetSelectedTemplate().IsValid())
{
UClass* WidgetClass = BlueprintEditor->GetSelectedTemplate().Get();
TSharedPtr<FWidgetTemplateClass> Template = MakeShareable(new FWidgetTemplateClass(WidgetClass));
NewReplacementWidget = Template->Create(BP->WidgetTree);
}
else if (BlueprintEditor->GetSelectedUserWidget().ObjectPath != NAME_None)
{
bIsUserWidget = true;
FAssetData WidgetAssetData = BlueprintEditor->GetSelectedUserWidget();
TSharedPtr<FWidgetTemplateBlueprintClass> Template = MakeShareable(new FWidgetTemplateBlueprintClass(WidgetAssetData));
NewReplacementWidget = Template->Create(BP->WidgetTree);
}
else
{
return;
}
NewReplacementWidget->SetFlags(RF_Transactional);
NewReplacementWidget->Modify();
if (UPanelWidget* ExistingPanel = Cast<UPanelWidget>(ThisWidget))
{
// if they are both panel widgets then call the existing replace function
UPanelWidget* ReplacementPanelWidget = Cast<UPanelWidget>(NewReplacementWidget);
if (ReplacementPanelWidget)
{
TSet<FWidgetReference> WidgetToReplace;
WidgetToReplace.Add(Widget);
ReplaceWidgets(BlueprintEditor, BP, WidgetToReplace, ReplacementPanelWidget->GetClass());
return;
}
}
ThisWidget->SetFlags(RF_Transactional);
ThisWidget->Modify();
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
// Look if the Widget to replace is a NamedSlot.
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(ThisWidget, BP->WidgetTree))
{
ReplaceNamedSlotHostContent(ThisWidget, NamedSlotHost, NewReplacementWidget);
}
else if (UPanelWidget* CurrentParent = ThisWidget->GetParent())
{
CurrentParent->SetFlags(RF_Transactional);
CurrentParent->Modify();
CurrentParent->ReplaceChild(ThisWidget, NewReplacementWidget);
FString ReplaceName = ThisWidget->GetName();
bool bIsGeneratedName = ThisWidget->IsGeneratedName();
// Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name.
ThisWidget->Rename(nullptr, GetTransientPackage());
// Rename the new Widget to maintain the current name if it's not a generic name
if (!bIsGeneratedName)
{
ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName);
NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree);
}
}
else if (ThisWidget == BP->WidgetTree->RootWidget)
{
BP->WidgetTree->RootWidget = NewReplacementWidget;
}
else
{
return;
}
// Delete the widget that has been replaced
TSet<FWidgetReference> WidgetsToDelete;
WidgetsToDelete.Add(Widget);
DeleteWidgets(BP, WidgetsToDelete);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
bool FWidgetBlueprintEditorUtils::CanBeReplacedWithTemplate(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, FWidgetReference Widget)
{
FAssetData SelectedUserWidget = BlueprintEditor->GetSelectedUserWidget();
UWidget* ThisWidget = Widget.GetTemplate();
UPanelWidget* ExistingPanel = Cast<UPanelWidget>(ThisWidget);
// If selecting another widget blueprint
if (SelectedUserWidget.ObjectPath != NAME_None)
{
if (ExistingPanel)
{
if (ExistingPanel->GetChildrenCount() != 0)
{
return false;
}
}
UUserWidget* NewUserWidget = CastChecked<UUserWidget>(FWidgetTemplateBlueprintClass(SelectedUserWidget).Create(BP->WidgetTree));
const bool bFreeFromCircularRefs = BP->IsWidgetFreeFromCircularReferences(NewUserWidget);
NewUserWidget->Rename(nullptr, GetTransientPackage());
return bFreeFromCircularRefs;
}
UClass* WidgetClass = BlueprintEditor->GetSelectedTemplate().Get();
const bool bCanReplace = WidgetClass->IsChildOf(UPanelWidget::StaticClass());
if (!ExistingPanel && !bCanReplace)
{
return true;
}
else if (!ExistingPanel && bCanReplace)
{
return true;
}
else if (ExistingPanel && !bCanReplace)
{
if (ExistingPanel->GetChildrenCount() == 0)
{
return true;
}
else
{
return false;
}
}
else
{
if (ExistingPanel->GetClass()->GetDefaultObject<UPanelWidget>()->CanHaveMultipleChildren() && bCanReplace)
{
const bool bChildAllowed = WidgetClass->GetDefaultObject<UPanelWidget>()->CanHaveMultipleChildren() || ExistingPanel->GetChildrenCount() == 0;
return bChildAllowed;
}
else
{
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(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
const bool bForceDelete = true;
DeleteWidgets(BP, WidgetsToDelete, bForceDelete);
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});
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
}
void FWidgetBlueprintEditorUtils::ReplaceWidgets(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets, UClass* WidgetClass)
{
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));
for ( FWidgetReference& Item : Widgets )
{
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
UPanelWidget* NewReplacementWidget = CastChecked<UPanelWidget>(Template->Create(BP->WidgetTree));
UWidget* ThisWidget = Item.GetTemplate();
ThisWidget->SetFlags(RF_Transactional);
ThisWidget->Modify();
// Look if the Widget to replace is a NamedSlot.
if (TScriptInterface<INamedSlotInterface> NamedSlotHost = FindNamedSlotHostForContent(ThisWidget, BP->WidgetTree))
{
ReplaceNamedSlotHostContent(ThisWidget, NamedSlotHost, NewReplacementWidget);
}
else if (UPanelWidget* CurrentParent = ThisWidget->GetParent())
{
CurrentParent->SetFlags(RF_Transactional);
CurrentParent->Modify();
CurrentParent->ReplaceChild(ThisWidget, NewReplacementWidget);
}
else if (ThisWidget == BP->WidgetTree->RootWidget)
{
BP->WidgetTree->RootWidget = NewReplacementWidget;
}
else
{
continue;
}
NewReplacementWidget->SetFlags(RF_Transactional);
NewReplacementWidget->Modify();
if (UPanelWidget* ExistingPanel = Cast<UPanelWidget>(ThisWidget))
{
while (ExistingPanel->GetChildrenCount() > 0)
{
UWidget* Widget = ExistingPanel->GetChildAt(0);
Widget->SetFlags(RF_Transactional);
Widget->Modify();
NewReplacementWidget->AddChild(Widget);
}
}
FString ReplaceName = ThisWidget->GetName();
const bool bIsGeneratedName = ThisWidget->IsGeneratedName();
// Delete the widget that has been replaced
const bool bForceDelete = true;
DeleteWidgets(BP, {Item}, bForceDelete);
// Rename the new Widget to maintain the current name if it's not a generic name
if (!bIsGeneratedName)
{
ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName);
NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree);
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
void FWidgetBlueprintEditorUtils::CutWidgets(UWidgetBlueprint* BP, TSet<FWidgetReference> Widgets)
{
CopyWidgets(BP, Widgets);
DeleteWidgets(BP, Widgets);
}
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)
{
Transaction.Cancel();
}
}
return DuplicatedWidgets;
}
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.ObjectPath.ToString() : 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.ObjectPath.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;
}
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);
// 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);
}
}
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)
{
Transaction.Cancel();
}
return PastedWidgets;
}
TArray<UWidget*> FWidgetBlueprintEditorUtils::PasteWidgetsInternal(TSharedRef<FWidgetBlueprintEditor> BlueprintEditor, UWidgetBlueprint* BP, const FString& TextToImport, FWidgetReference ParentWidgetRef, FName SlotName, FVector2D PasteLocation, bool bForceSibling, bool& bTransactionSuccessful)
{
// Import the nodes
TSet<UWidget*> PastedWidgets;
TMap<FName, UWidgetSlotPair*> PastedExtraSlotData;
FWidgetBlueprintEditorUtils::ImportWidgetsFromText(BP, TextToImport, /*out*/ PastedWidgets, /*out*/ PastedExtraSlotData);
// Ignore an empty set of widget paste data.
if ( PastedWidgets.Num() == 0 )
{
bTransactionSuccessful = false;
return TArray<UWidget*>();
}
// If we're pasting into a content widget of the same type, treat it as a sibling duplication
UWidget* FirstPastedWidget = *PastedWidgets.CreateIterator();
if (FirstPastedWidget->IsA(UContentWidget::StaticClass()) &&
ParentWidgetRef.IsValid() &&
FirstPastedWidget->GetClass() == ParentWidgetRef.GetTemplate()->GetClass())
{
UPanelWidget* TargetParentWidget = ParentWidgetRef.GetTemplate()->GetParent();
if (TargetParentWidget && TargetParentWidget->CanAddMoreChildren())
{
bForceSibling = true;
}
}
TArray<UWidget*> RootPasteWidgets;
for ( UWidget* NewWidget : PastedWidgets )
{
// 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 ( 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 )
{
UWidget* WidgetTemplate = ParentWidgetRef.GetTemplate();
ParentWidget = WidgetTemplate->GetParent();
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 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;
}
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 (RootPasteWidgets.Num() > 0)
{
// Delete the singular child
ChildWidgetToDelete = ParentWidget->GetAllChildren()[0];
ChildWidgetToDelete->SetFlags(RF_Transactional);
ChildWidgetToDelete->Modify();
ParentWidget->SetFlags(RF_Transactional);
ParentWidget->Modify();
ParentWidget->RemoveChild(ChildWidgetToDelete);
}
}
// 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);
}
}
if (ChildWidgetToDelete)
{
DeleteWidgets(BP, { BlueprintEditor->GetReferenceFromTemplate(ChildWidgetToDelete) });
}
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();
for ( UWidget* NewWidget : RootPasteWidgets )
{
BP->WidgetTree->RootWidget = NewWidget;
break;
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
}
else
{
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;
}
void FWidgetBlueprintEditorUtils::ImportWidgetsFromText(UWidgetBlueprint* BP, const FString& TextToImport, /*out*/ TSet<UWidget*>& ImportedWidgetSet, /*out*/ TMap<FName, UWidgetSlotPair*>& PastedExtraSlotData)
{
// 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.
UPackage* TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/UMG/Editor/Transient"), RF_Transient);
TempPackage->AddToRoot();
// 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);
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);
}
}
// Remove the temp package from the root now that it has served its purpose.
TempPackage->RemoveFromRoot();
}
void FWidgetBlueprintEditorUtils::ExportPropertiesToText(UObject* Object, TMap<FName, FString>& ExportedProperties)
{
if ( Object )
{
for ( TFieldIterator<FProperty> PropertyIt(Object->GetClass(), EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt )
{
FProperty* Property = *PropertyIt;
// Don't serialize out object properties, we just want value data.
if ( !Property->IsA<FObjectProperty>() )
{
FString ValueText;
if ( Property->ExportText_InContainer(0, ValueText, Object, Object, Object, PPF_IncludeTransient) )
{
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, 0);
FPropertyChangedEvent ChangedEvent(Property);
Object->PostEditChangeProperty(ChangedEvent);
}
}
}
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(FProperty* InProperty)
{
bool bIsOptional;
return IsBindWidgetProperty(InProperty, bIsOptional);
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(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(FProperty* InProperty)
{
bool bIsOptional;
return IsBindWidgetAnimProperty(InProperty, bIsOptional);
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(FProperty* InProperty, bool& bIsOptional)
{
if (InProperty)
{
bool bIsBindWidgetAnim = InProperty->HasMetaData("BindWidgetAnim") || InProperty->HasMetaData("BindWidgetAnimOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetAnimOptional");
return bIsBindWidgetAnim;
}
return false;
}
bool FWidgetBlueprintEditorUtils::IsUsableWidgetClass(UClass* WidgetClass)
{
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(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 true;
}
return false;
}
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(NameLen, *OldName);
}
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;
}
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>(UUserInterfaceSettings::StaticClass())->GetDPIScaleBasedOnSize(FIntPoint(PreviewSize.X, 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);
}
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;
float XOffset = 0;
float YOffset = 0;
if (WidgetSize.X > WidgetSize.Y)
{
Scale = ThumbnailSize.X / WidgetSize.X;
WidgetSize *= Scale;
YOffset = (ThumbnailSize.Y - WidgetSize.Y) / 2.f;
}
else
{
Scale = 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;
}
}
#undef LOCTEXT_NAMESPACE