// Copyright Epic Games, Inc. All Rights Reserved. #include "WidgetBlueprintEditor.h" #include "MovieSceneBinding.h" #include "MovieSceneFolder.h" #include "MovieScene.h" #include "Animation/WidgetAnimation.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Docking/SDockTab.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/FileManager.h" #include "Engine/SimpleConstructionScript.h" #include "Blueprint/WidgetBlueprintGeneratedClass.h" #include "Kismet2/BlueprintEditorUtils.h" #include "WidgetBlueprint.h" #include "StatusBarSubsystem.h" #include "Editor.h" #include "WidgetBlueprintToolMenuContext.h" #if WITH_EDITOR #include "Styling/AppStyle.h" #endif // WITH_EDITOR #include "Algo/AllOf.h" #include "Components/PanelSlot.h" #include "Components/PanelWidget.h" #include "Settings/WidgetDesignerSettings.h" #include "Tracks/MovieScenePropertyTrack.h" #include "ISequencerModule.h" #include "SequencerSettings.h" #include "ObjectEditorUtils.h" #include "PropertyCustomizationHelpers.h" #include "BlueprintModes/WidgetBlueprintApplicationModes.h" #include "Blueprint/WidgetTree.h" #include "WidgetBlueprintEditorUtils.h" #include "WorkflowOrientedApp/ApplicationMode.h" #include "BlueprintModes/WidgetDesignerApplicationMode.h" #include "BlueprintModes/WidgetGraphApplicationMode.h" #include "BlueprintModes/WidgetPreviewApplicationMode.h" #include "WidgetModeManager.h" #include "WidgetBlueprintEditorToolbar.h" #include "Components/CanvasPanel.h" #include "Framework/Commands/GenericCommands.h" #include "Kismet2/CompilerResultsLog.h" #include "HAL/FileManager.h" #include "IMessageLogListing.h" #include "WidgetGraphSchema.h" #include "Animation/MovieSceneWidgetMaterialTrack.h" #include "Animation/WidgetMaterialTrackUtilities.h" #include "MVVM/ObjectBindingModelStorageExtension.h" #include "MVVM/ViewModels/ObjectBindingModel.h" #include "MVVM/ViewModels/SequencerEditorViewModel.h" #include "ScopedTransaction.h" #include "Designer/SDesignerView.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "UMGEditorActions.h" #include "UMGEditorModule.h" #include "GameProjectGenerationModule.h" #include "Tools/ToolCompatible.h" #include "Preview/PreviewMode.h" #include "Palette/SPaletteViewModel.h" #include "Library/SLibraryViewModel.h" #include "DesktopPlatformModule.h" #include "Engine/MemberReference.h" #include "Engine/TextureRenderTarget2D.h" #include "IDesktopPlatform.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "ImageUtils.h" #include "Serialization/BufferArchive.h" #include "Widgets/SVirtualWindow.h" #include "TabFactory/AnimationTabSummoner.h" #include "TabFactory/DesignerTabSummoner.h" #include "ToolPalette/WidgetEditorModeUILayer.h" #include "BlueprintEditorTabs.h" #include "Editor/UnrealEdEngine.h" #include "Preferences/UnrealEdOptions.h" #include "UnrealEdGlobals.h" #include "GraphEditorActions.h" #include "MovieSceneDynamicBindingCustomization.h" #include "UIComponentWidgetBlueprintExtension.h" #include "Extensions/UIComponent.h" #include "Extensions/UIComponentUserWidgetExtension.h" #define LOCTEXT_NAMESPACE "UMG" namespace UE::UMG::Editor { bool bDisplayWidgetVariableGuids = false; FAutoConsoleVariableRef CVarUMGEditorDisplayWidgetVariableGuids( TEXT("UMG.Editor.DisplayWidgetVariableGuids"), bDisplayWidgetVariableGuids, TEXT("Toggles displaying guids for variables generated by this blueprint (Widgets and Animations).")); } FWidgetBlueprintEditor::FWidgetBlueprintEditor() : PreviewScene(FPreviewScene::ConstructionValues().AllowAudioPlayback(true).ShouldSimulatePhysics(true)) , PreviewBlueprint(nullptr) , bIsSimulateEnabled(false) , bIsRealTime(true) , bIsSequencerDrawerOpen(false) , bRefreshGeneratedClassAnimations(false) , bUpdatingSequencerSelection(false) , bUpdatingExternalSelection(false) , bIsPreviewWidgetSelected(false) { PreviewScene.GetWorld()->SetBegunPlay(false); // Register sequencer menu extenders. ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked( "Sequencer" ); { int32 NewIndex = SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates().Add( FAssetEditorExtender::CreateRaw(this, &FWidgetBlueprintEditor::GetAddTrackSequencerExtender)); SequencerAddTrackExtenderHandle = SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates()[NewIndex].GetHandle(); } } FWidgetBlueprintEditor::~FWidgetBlueprintEditor() { UWidgetBlueprint* Blueprint = GetWidgetBlueprintObj(); if ( Blueprint ) { Blueprint->OnChanged().RemoveAll(this); Blueprint->OnCompiled().RemoveAll(this); } FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this); for (TWeakPtr SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { Sequencer->OnMovieSceneDataChanged().RemoveAll(this); Sequencer->OnMovieSceneBindingsPasted().RemoveAll(this); Sequencer->Close(); Sequencer.Reset(); } } // Un-Register sequencer menu extenders. ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked("Sequencer"); SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates().RemoveAll([this](const FAssetEditorExtender& Extender) { return SequencerAddTrackExtenderHandle == Extender.GetHandle(); }); } void FWidgetBlueprintEditor::InitWidgetBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray& InBlueprints, bool bShouldOpenInDefaultsMode) { bShowDashedOutlines = GetDefault()->bShowOutlines; bRespectLocks = GetDefault()->bRespectLocks; TSharedPtr ThisPtr(SharedThis(this)); PaletteViewModel = MakeShared(ThisPtr); PaletteViewModel->RegisterToEvents(); LibraryViewModel = MakeShared(ThisPtr); LibraryViewModel->RegisterToEvents(); WidgetToolbar = MakeShared(ThisPtr); BindToolkitCommands(); InitBlueprintEditor(Mode, InitToolkitHost, InBlueprints, bShouldOpenInDefaultsMode); // We only show compile tab results on error TSharedPtr CompileResultsTab = GetToolkitHost()->GetTabManager()->FindExistingLiveTab(FBlueprintEditorTabs::CompilerResultsID); if (CompileResultsTab) { CompileResultsTab->RequestCloseTab(); } // register for any objects replaced FCoreUObjectDelegates::OnObjectsReplaced.AddSP(this, &FWidgetBlueprintEditor::OnObjectsReplaced); // for change selected widgets on sequencer tree view UWidgetBlueprint* Blueprint = GetWidgetBlueprintObj(); UpdatePreview(GetWidgetBlueprintObj(), true); DesignerCommandList = MakeShared(); DesignerCommandList->MapAction(FGenericCommands::Get().Delete, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::DeleteSelectedWidgets), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanDeleteSelectedWidgets) ); DesignerCommandList->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CopySelectedWidgets), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanCopySelectedWidgets) ); DesignerCommandList->MapAction(FGenericCommands::Get().Cut, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CutSelectedWidgets), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanCutSelectedWidgets) ); DesignerCommandList->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::PasteWidgets), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanPasteWidgets) ); DesignerCommandList->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::DuplicateSelectedWidgets), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanDuplicateSelectedWidgets) ); DesignerCommandList->MapAction(FGraphEditorCommands::Get().FindReferences, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OnFindWidgetReferences, false, EGetFindReferenceSearchStringFlags::Legacy), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanFindWidgetReferences)); DesignerCommandList->MapAction(FGraphEditorCommands::Get().FindReferencesByNameLocal, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OnFindWidgetReferences, false, EGetFindReferenceSearchStringFlags::None), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanFindWidgetReferences)); DesignerCommandList->MapAction(FGraphEditorCommands::Get().FindReferencesByNameGlobal, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OnFindWidgetReferences, true, EGetFindReferenceSearchStringFlags::None), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanFindWidgetReferences)); DesignerCommandList->MapAction(FGraphEditorCommands::Get().FindReferencesByClassMemberLocal, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OnFindWidgetReferences, false, EGetFindReferenceSearchStringFlags::UseSearchSyntax), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanFindWidgetReferences)); DesignerCommandList->MapAction(FGraphEditorCommands::Get().FindReferencesByClassMemberGlobal, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OnFindWidgetReferences, true, EGetFindReferenceSearchStringFlags::UseSearchSyntax), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanFindWidgetReferences)); TSharedPtr PinnedToolkitHost = ToolkitHost.Pin(); check(PinnedToolkitHost.IsValid()); ModeUILayer = MakeShared(PinnedToolkitHost.Get()); } void FWidgetBlueprintEditor::InitalizeExtenders() { Super::InitalizeExtenders(); IUMGEditorModule& UMGEditorModule = FModuleManager::LoadModuleChecked("UMGEditor"); AddMenuExtender(UMGEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); AddMenuExtender(CreateMenuExtender()); TArrayView ToolbarExtenderDelegates = UMGEditorModule.GetAllWidgetEditorToolbarExtenders(); for (auto& ToolbarExtenderDelegate : ToolbarExtenderDelegates) { if (ToolbarExtenderDelegate.IsBound()) { AddToolbarExtender(ToolbarExtenderDelegate.Execute(GetToolkitCommands(), SharedThis(this))); } } } TSharedPtr FWidgetBlueprintEditor::CreateMenuExtender() { TSharedPtr MenuExtender = MakeShareable(new FExtender); // Extend the File menu with asset actions MenuExtender->AddMenuExtension( "FileLoadAndSave", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateSP(this, &FWidgetBlueprintEditor::FillFileMenu)); MenuExtender->AddMenuExtension( "AssetEditorActions", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateSP(this, &FWidgetBlueprintEditor::FillAssetMenu)); MenuExtender->AddMenuExtension( "FileLoadAndSave", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateSP(this, &FWidgetBlueprintEditor::CustomizeWidgetCompileOptions)); return MenuExtender; } void FWidgetBlueprintEditor::FillFileMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection(TEXT("Import/Export"), LOCTEXT("Import/Export", "Import/Export")); MenuBuilder.AddMenuEntry(FUMGEditorCommands::Get().ExportAsPNG); MenuBuilder.EndSection(); MenuBuilder.BeginSection(TEXT("WidgetBlueprint"), LOCTEXT("WidgetBlueprint", "Widget Blueprint")); MenuBuilder.AddMenuEntry(FUMGEditorCommands::Get().CreateNativeBaseClass); MenuBuilder.EndSection(); } void FWidgetBlueprintEditor::FillAssetMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection(TEXT("Thumbnail"), LOCTEXT("Thumbnail", "Thumbnail")); MenuBuilder.AddMenuEntry(FUMGEditorCommands::Get().SetImageAsThumbnail); MenuBuilder.AddMenuEntry(FUMGEditorCommands::Get().ClearCustomThumbnail); MenuBuilder.EndSection(); } void FWidgetBlueprintEditor::CustomizeWidgetCompileOptions(FMenuBuilder& InMenuBuilder) { InMenuBuilder.AddSubMenu( LOCTEXT("CreateCompileTab", "Create Compile Tab"), LOCTEXT("CreateCompileTab_ToolTip", "Displays Compile tab when hidden based on compilation results."), FNewMenuDelegate::CreateStatic(&FWidgetBlueprintEditor::AddCreateCompileTabSubMenu)); InMenuBuilder.AddSubMenu( LOCTEXT("DismissCompileTab", "Dismiss Compile Tab"), LOCTEXT("DismissCompileTab_ToolTip", "Dismisses compile tab based on compilation results."), FNewMenuDelegate::CreateStatic(&FWidgetBlueprintEditor::AddDismissCompileTabSubMenu)); } void FWidgetBlueprintEditor::AddCreateCompileTabSubMenu(FMenuBuilder& InMenuBuilder) { const FUMGEditorCommands& Commands = FUMGEditorCommands::Get(); InMenuBuilder.AddMenuEntry(Commands.CreateOnCompile_ErrorsAndWarnings); InMenuBuilder.AddMenuEntry(Commands.CreateOnCompile_Errors); InMenuBuilder.AddMenuEntry(Commands.CreateOnCompile_Warnings); InMenuBuilder.AddMenuEntry(Commands.CreateOnCompile_Never); } void FWidgetBlueprintEditor::AddDismissCompileTabSubMenu(FMenuBuilder& InMenuBuilder) { const FUMGEditorCommands& Commands = FUMGEditorCommands::Get(); InMenuBuilder.AddMenuEntry(Commands.DismissOnCompile_ErrorsAndWarnings); InMenuBuilder.AddMenuEntry(Commands.DismissOnCompile_Errors); InMenuBuilder.AddMenuEntry(Commands.DismissOnCompile_Warnings); InMenuBuilder.AddMenuEntry(Commands.DismissOnCompile_Never); } void FWidgetBlueprintEditor::BindToolkitCommands() { FUMGEditorCommands::Register(); GetToolkitCommands()->MapAction(FUMGEditorCommands::Get().CreateNativeBaseClass, FUIAction( FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::OpenCreateNativeBaseClassDialog), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CanCreateNativeBaseClass), FGetActionCheckState(), FIsActionButtonVisible::CreateSP(this, &FWidgetBlueprintEditor::IsCreateNativeBaseClassVisible) ) ); GetToolkitCommands()->MapAction(FUMGEditorCommands::Get().ExportAsPNG, FUIAction( FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::TakeSnapshot), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::IsPreviewWidgetInitialized) ) ); GetToolkitCommands()->MapAction(FUMGEditorCommands::Get().SetImageAsThumbnail, FUIAction( FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::CaptureThumbnail) ) ); GetToolkitCommands()->MapAction(FUMGEditorCommands::Get().ClearCustomThumbnail, FUIAction( FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::ClearThumbnail), FCanExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::IsImageUsedAsThumbnail) ) ); GetToolkitCommands()->MapAction(FUMGEditorCommands::Get().OpenAnimDrawer, FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::ToggleAnimDrawer) ); auto MapCreateOnCompileAction = [&](const TSharedPtr& InUICommand, EDisplayOnCompile InCreateOnCompile) { ToolkitCommands->MapAction( InUICommand, FExecuteAction::CreateStatic(&FWidgetBlueprintEditor::SetCreateOnCompileSetting, InCreateOnCompile), FCanExecuteAction(), FIsActionChecked::CreateStatic(&FWidgetBlueprintEditor::IsCreateOnCompileSet, InCreateOnCompile) ); }; MapCreateOnCompileAction(FUMGEditorCommands::Get().CreateOnCompile_ErrorsAndWarnings, EDisplayOnCompile::DoC_ErrorsOrWarnings); MapCreateOnCompileAction(FUMGEditorCommands::Get().CreateOnCompile_Errors, EDisplayOnCompile::DoC_ErrorsOnly); MapCreateOnCompileAction(FUMGEditorCommands::Get().CreateOnCompile_Warnings, EDisplayOnCompile::DoC_WarningsOnly); MapCreateOnCompileAction(FUMGEditorCommands::Get().CreateOnCompile_Never, EDisplayOnCompile::DoC_Never); auto MapDismissOnCompileAction = [&](const TSharedPtr& InUICommand, EDisplayOnCompile InDismissOnCompile) { ToolkitCommands->MapAction( InUICommand, FExecuteAction::CreateStatic(&FWidgetBlueprintEditor::SetDismissOnCompileSetting, InDismissOnCompile), FCanExecuteAction(), FIsActionChecked::CreateStatic(&FWidgetBlueprintEditor::IsDismissOnCompileSet, InDismissOnCompile) ); }; MapDismissOnCompileAction(FUMGEditorCommands::Get().DismissOnCompile_ErrorsAndWarnings, EDisplayOnCompile::DoC_ErrorsOrWarnings); MapDismissOnCompileAction(FUMGEditorCommands::Get().DismissOnCompile_Errors, EDisplayOnCompile::DoC_ErrorsOnly); MapDismissOnCompileAction(FUMGEditorCommands::Get().DismissOnCompile_Warnings, EDisplayOnCompile::DoC_WarningsOnly); MapDismissOnCompileAction(FUMGEditorCommands::Get().DismissOnCompile_Never, EDisplayOnCompile::DoC_Never); } void FWidgetBlueprintEditor::TakeSnapshot() { IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); if (DesktopPlatform) { TSharedPtr ParentWindow = FGlobalTabmanager::Get()->GetRootWindow(); const void* ParentWindowWindowHandle = FSlateApplication::Get().FindBestParentWindowHandleForDialogs(ParentWindow); TArray SaveFilenames; const bool bOpened = DesktopPlatform->SaveFileDialog( ParentWindowWindowHandle, LOCTEXT("ExportWidgetBlueprintDialogTitle", "Save Widget Blueprint Screenshot").ToString(), FPaths::GameAgnosticSavedDir(), TEXT(""), TEXT("PNG (*.png)|*.png"), EFileDialogFlags::None, SaveFilenames ); if (SaveFilenames.Num() > 0) { TUniquePtr Ar(IFileManager::Get().CreateFileWriter(*SaveFilenames[0])); if (Ar) { TSharedPtr WindowContent; UUserWidget* PreviewWidget = GetPreview(); UTextureRenderTarget2D* RenderTarget2D = NewObject(); TOptional ScaleAndOffset = FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTarget(PreviewWidget, RenderTarget2D); if (!ScaleAndOffset.IsSet()) { FMessageLog("Blueprint").Warning(LOCTEXT("ExportWidgetBlueprint_ImageSourceFailedToCreate", "ExportWidgetBlueprint: Failed to create image source.")); return; } FBufferArchive Buffer; bool bSuccess = FImageUtils::ExportRenderTarget2DAsPNG(RenderTarget2D, Buffer); if (bSuccess) { Ar->Serialize(const_cast(Buffer.GetData()), Buffer.Num()); } } } } } void FWidgetBlueprintEditor::CaptureThumbnail() { TSharedPtr WindowContent; UUserWidget* PreviewWidget = GetPreview(); if (!PreviewWidget) { return; } UTextureRenderTarget2D* RenderTarget2D = NewObject(); TOptional ScaleAndOffset = FWidgetBlueprintEditorUtils::DrawSWidgetInRenderTargetForThumbnail(PreviewWidget, RenderTarget2D, FVector2D(256.f, 256.f), TOptional(), EThumbnailPreviewSizeMode::MatchDesignerMode); if (!ScaleAndOffset.IsSet()) { return; } FImage Image; if ( !FImageUtils::GetRenderTargetImage(RenderTarget2D,Image) ) { return; } UTexture2D* ThumbnailTexture = FImageUtils::CreateTexture2DFromImage(Image); FWidgetBlueprintEditorUtils::SetTextureAsAssetThumbnail(GetWidgetBlueprintObj(), ThumbnailTexture); } void FWidgetBlueprintEditor::ClearThumbnail() { GetWidgetBlueprintObj()->ThumbnailImage = nullptr; } bool FWidgetBlueprintEditor::IsImageUsedAsThumbnail() { return GetWidgetBlueprintObj()->ThumbnailImage != nullptr; } bool FWidgetBlueprintEditor::IsPreviewWidgetInitialized() { return GetPreview() != nullptr; } FName FWidgetBlueprintEditor::GetToolkitContextFName() const { return GetToolkitFName(); } FName FWidgetBlueprintEditor::GetToolkitFName() const { return FName("WidgetBlueprintEditor"); } FText FWidgetBlueprintEditor::GetBaseToolkitName() const { return LOCTEXT("AppLabel", "Widget Editor"); } FString FWidgetBlueprintEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Widget Editor ").ToString(); } FLinearColor FWidgetBlueprintEditor::GetWorldCentricTabColorScale() const { return FLinearColor(0.3f, 0.25f, 0.35f, 0.5f); } void FWidgetBlueprintEditor::InitToolMenuContext(FToolMenuContext& MenuContext) { Super::InitToolMenuContext(MenuContext); UWidgetBlueprintToolMenuContext* Context = NewObject(); Context->WidgetBlueprintEditor = SharedThis(this); MenuContext.AddObject(Context); } void FWidgetBlueprintEditor::SetCreateOnCompileSetting(EDisplayOnCompile InCreateOnCompile) { UWidgetDesignerSettings* Settings = GetMutableDefault(); Settings->CreateOnCompile = InCreateOnCompile; Settings->SaveConfig(); } void FWidgetBlueprintEditor::SetDismissOnCompileSetting(EDisplayOnCompile InDismissOnCompile) { UWidgetDesignerSettings* Settings = GetMutableDefault(); Settings->DismissOnCompile = InDismissOnCompile; Settings->SaveConfig(); } bool FWidgetBlueprintEditor::IsCreateOnCompileSet(EDisplayOnCompile InCreateOnCompile) { const UWidgetDesignerSettings* Settings = GetDefault(); return Settings->CreateOnCompile == InCreateOnCompile; } bool FWidgetBlueprintEditor::IsDismissOnCompileSet(EDisplayOnCompile InDismissOnCompile) { const UWidgetDesignerSettings* Settings = GetDefault(); return Settings->DismissOnCompile == InDismissOnCompile; } void FWidgetBlueprintEditor::OpenCreateNativeBaseClassDialog() { FGameProjectGenerationModule::Get().OpenAddCodeToProjectDialog( FAddToProjectConfig() .DefaultClassPrefix(TEXT("")) .DefaultClassName(GetWidgetBlueprintObj()->GetName() + TEXT("Base")) .ParentClass(GetWidgetBlueprintObj()->ParentClass) .ParentWindow(FGlobalTabmanager::Get()->GetRootWindow()) .OnAddedToProject(FOnAddedToProject::CreateSP(this, &FWidgetBlueprintEditor::OnCreateNativeBaseClassSuccessfully)) ); } void FWidgetBlueprintEditor::OnCreateNativeBaseClassSuccessfully(const FString& InClassName, const FString& InClassPath, const FString& InModuleName) { UClass* NewNativeClass = FindObject(FTopLevelAssetPath(*InClassPath, *InClassName)); if (NewNativeClass) { ReparentBlueprint_NewParentChosen(NewNativeClass); } } void FWidgetBlueprintEditor::RegisterApplicationModes(const TArray& InBlueprints, bool bShouldOpenInDefaultsMode, bool bNewlyCreated/* = false*/) { //Super::RegisterApplicationModes(InBlueprints, bShouldOpenInDefaultsMode); if (InBlueprints.Num() == 1) { TSharedPtr ThisPtr(SharedThis(this)); // Create the modes and activate one (which will populate with a real layout) TArray< TSharedRef > TempModeList; TempModeList.Add(MakeShared(ThisPtr)); TempModeList.Add(MakeShared(ThisPtr)); if (FWidgetBlueprintApplicationModes::IsPreviewModeEnabled()) { TempModeList.Add(MakeShared(ThisPtr)); PreviewMode = MakeShared(); } for (TSharedRef& AppMode : TempModeList) { AddApplicationMode(AppMode->GetModeName(), AppMode); } SetCurrentMode(FWidgetBlueprintApplicationModes::DesignerMode); } else { //// We either have no blueprints or many, open in the defaults mode for multi-editing //AddApplicationMode( // FBlueprintEditorApplicationModes::BlueprintDefaultsMode, // MakeShareable(new FBlueprintDefaultsApplicationMode(SharedThis(this)))); //SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintDefaultsMode); } } void FWidgetBlueprintEditor::SelectWidgets(const TSet& Widgets, bool bAppendOrToggle) { TSet TempSelection; for ( const FWidgetReference& Widget : Widgets ) { if ( Widget.IsValid() ) { TempSelection.Add(Widget); } } OnSelectedWidgetsChanging.Broadcast(); // Finally change the selected widgets after we've updated the details panel // to ensure values that are pending are committed on focus loss, and migrated properly // to the old selected widgets. if ( !bAppendOrToggle ) { SelectedWidgets.Empty(); } SelectedObjects.Empty(); SelectedNamedSlot.Reset(); bIsPreviewWidgetSelected = false; for ( const FWidgetReference& Widget : TempSelection ) { if ( bAppendOrToggle && SelectedWidgets.Contains(Widget) ) { SelectedWidgets.Remove(Widget); } else { SelectedWidgets.Add(Widget); } } OnSelectedWidgetsChanged.Broadcast(); } void FWidgetBlueprintEditor::SelectObjects(const TSet& Objects) { OnSelectedWidgetsChanging.Broadcast(); SelectedWidgets.Empty(); SelectedObjects.Empty(); SelectedNamedSlot.Reset(); bIsPreviewWidgetSelected = false; for ( UObject* Obj : Objects ) { SelectedObjects.Add(Obj); if (Obj == GetPreview()) { bIsPreviewWidgetSelected = true; } } OnSelectedWidgetsChanged.Broadcast(); } bool FWidgetBlueprintEditor::IsBindingSelected(const FMovieSceneBinding& InBinding) { TSet Widgets = GetSelectedWidgets(); if (Widgets.Num() == 0) { return true; } TSharedPtr& ActiveSequencer = GetSequencer(); UMovieSceneSequence* AnimationSequence = ActiveSequencer->GetFocusedMovieSceneSequence(); UObject* BindingContext = GetAnimationPlaybackContext(); TArray> BoundObjects; AnimationSequence->LocateBoundObjects(InBinding.GetObjectGuid(), UE::UniversalObjectLocator::FResolveParams(BindingContext), ActiveSequencer->GetSharedPlaybackState(), BoundObjects); if (BoundObjects.Num() == 0) { return false; } else if (Cast(BoundObjects[0])) { return Widgets.Contains(GetReferenceFromPreview(Cast(BoundObjects[0])->Content)); } else { return Widgets.Contains(GetReferenceFromPreview(Cast(BoundObjects[0]))); } } void FWidgetBlueprintEditor::SetSelectedNamedSlot(TOptional InSelectedNamedSlot) { OnSelectedWidgetsChanging.Broadcast(); SelectedWidgets.Empty(); SelectedObjects.Empty(); SelectedNamedSlot.Reset(); bIsPreviewWidgetSelected = false; SelectedNamedSlot = InSelectedNamedSlot; if (InSelectedNamedSlot.IsSet()) { if (InSelectedNamedSlot->NamedSlotHostWidget.IsValid()) { SelectedWidgets.Add(InSelectedNamedSlot->NamedSlotHostWidget); } } OnSelectedWidgetsChanged.Broadcast(); } void FWidgetBlueprintEditor::CleanSelection() { TSet TempSelection; TArray WidgetsInTree; GetWidgetBlueprintObj()->WidgetTree->GetAllWidgets(WidgetsInTree); TSet TreeWidgetSet(WidgetsInTree); for ( FWidgetReference& WidgetRef : SelectedWidgets ) { if ( WidgetRef.IsValid() ) { if ( TreeWidgetSet.Contains(WidgetRef.GetTemplate()) ) { TempSelection.Add(WidgetRef); } } } if ( TempSelection.Num() != SelectedWidgets.Num() ) { SelectWidgets(TempSelection, false); } else if ( bIsPreviewWidgetSelected ) { TSet ValidObjects = { GetPreview() }; for ( const TWeakObjectPtr& SelectedObject : SelectedObjects ) { if ( SelectedObject.IsValid() ) { ValidObjects.Add(SelectedObject.Get()); } } SelectObjects(ValidObjects); } } const TSet& FWidgetBlueprintEditor::GetSelectedWidgets() const { return SelectedWidgets; } const TSet< TWeakObjectPtr >& FWidgetBlueprintEditor::GetSelectedObjects() const { return SelectedObjects; } TOptional FWidgetBlueprintEditor::GetSelectedNamedSlot() const { return SelectedNamedSlot; } void FWidgetBlueprintEditor::InvalidatePreview(bool bViewOnly) { if ( bViewOnly ) { OnWidgetPreviewUpdated.Broadcast(); } else { bPreviewInvalidated = true; } } void FWidgetBlueprintEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled ) { DestroyPreview(); Super::OnBlueprintChangedImpl(InBlueprint, bIsJustBeingCompiled); if ( InBlueprint ) { RefreshPreview(); } } void FWidgetBlueprintEditor::OnObjectsReplaced(const TMap& ReplacementMap) { // Remove dead references and update references for ( int32 HandleIndex = WidgetHandlePool.Num() - 1; HandleIndex >= 0; HandleIndex-- ) { TSharedPtr Ref = WidgetHandlePool[HandleIndex].Pin(); if ( Ref.IsValid() ) { UObject* const* NewObject = ReplacementMap.Find(Ref->Widget.Get()); if ( NewObject ) { Ref->Widget = Cast(*NewObject); } } else { WidgetHandlePool.RemoveAtSwap(HandleIndex); } } } bool FWidgetBlueprintEditor::CanDeleteSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); return Widgets.Num() > 0 && !FWidgetBlueprintEditorUtils::IsAnySelectedWidgetLocked(Widgets); } void FWidgetBlueprintEditor::DeleteSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); FWidgetBlueprintEditorUtils::DeleteWidgets(GetWidgetBlueprintObj(), FWidgetBlueprintEditorUtils::ResolveWidgetTemplates(Widgets), FWidgetBlueprintEditorUtils::EDeleteWidgetWarningType::WarnAndAskUser); // Clear the selection now that the widget has been deleted. TSet Empty; SelectWidgets(Empty, false); } bool FWidgetBlueprintEditor::CanCopySelectedWidgets() { TSet Widgets = GetSelectedWidgets(); return Widgets.Num() > 0; } void FWidgetBlueprintEditor::CopySelectedWidgets() { TSet Widgets = GetSelectedWidgets(); FWidgetBlueprintEditorUtils::CopyWidgets(GetWidgetBlueprintObj(), Widgets); } bool FWidgetBlueprintEditor::CanCutSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); return Widgets.Num() > 0 && !FWidgetBlueprintEditorUtils::IsAnySelectedWidgetLocked(Widgets); } void FWidgetBlueprintEditor::CutSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); FWidgetBlueprintEditorUtils::CutWidgets(SharedThis(this), GetWidgetBlueprintObj(), Widgets); } const UWidgetAnimation* FWidgetBlueprintEditor::RefreshCurrentAnimation() { return CurrentAnimation.Get(); } bool FWidgetBlueprintEditor::CanPasteWidgets() { TSet Widgets = GetSelectedWidgets(); if (FWidgetBlueprintEditorUtils::IsAnySelectedWidgetLocked(Widgets)) { return false; } if (!FWidgetBlueprintEditorUtils::DoesClipboardTextContainWidget(GetWidgetBlueprintObj())) { return false; } if (!FWidgetBlueprintEditorUtils::CanPasteWidgetsExtension(Widgets)) { return false; } if ( Widgets.Num() == 1 ) { // Always return true here now since we want to support pasting widgets as siblings return true; } else if ( GetWidgetBlueprintObj()->WidgetTree->RootWidget == nullptr ) { return true; } else { TOptional NamedSlotSelection = GetSelectedNamedSlot(); if ( NamedSlotSelection.IsSet() ) { INamedSlotInterface* NamedSlotHost = Cast(NamedSlotSelection->NamedSlotHostWidget.GetTemplate()); if ( NamedSlotHost == nullptr ) { return false; } else if ( NamedSlotHost->GetContentForSlot(NamedSlotSelection->SlotName) != nullptr ) { return false; } return true; } } return false; } void FWidgetBlueprintEditor::PasteWidgets() { TSet Widgets = GetSelectedWidgets(); FWidgetReference Target = Widgets.Num() > 0 ? *Widgets.CreateIterator() : FWidgetReference(); FName SlotName = NAME_None; TOptional NamedSlotSelection = GetSelectedNamedSlot(); if ( NamedSlotSelection.IsSet() ) { Target = NamedSlotSelection->NamedSlotHostWidget; SlotName = NamedSlotSelection->SlotName; } TArray PastedWidgets = FWidgetBlueprintEditorUtils::PasteWidgets(SharedThis(this), GetWidgetBlueprintObj(), Target, SlotName, PasteDropLocation); PasteDropLocation = PasteDropLocation + FVector2D(25, 25); TSet PastedWidgetRefs; for (UWidget* Widget : PastedWidgets) { PastedWidgetRefs.Add(GetReferenceFromPreview(Widget)); } SelectWidgets(PastedWidgetRefs, false); } bool FWidgetBlueprintEditor::CanDuplicateSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); if (Widgets.Num() == 1) { FWidgetReference Target = *Widgets.CreateIterator(); UPanelWidget* ParentWidget = Target.GetTemplate()->GetParent(); return ParentWidget && ParentWidget->CanAddMoreChildren(); } return false; } void FWidgetBlueprintEditor::DuplicateSelectedWidgets() { TSet Widgets = GetSelectedWidgets(); TArray DuplicatedWidgets = FWidgetBlueprintEditorUtils::DuplicateWidgets(SharedThis(this), GetWidgetBlueprintObj(), Widgets); TSet DuplicatedWidgetRefs; for (UWidget* Widget : DuplicatedWidgets) { DuplicatedWidgetRefs.Add(GetReferenceFromPreview(Widget)); } SelectWidgets(DuplicatedWidgetRefs, false); } void FWidgetBlueprintEditor::OnFindWidgetReferences(bool bSearchAllBlueprints, const EGetFindReferenceSearchStringFlags Flags) { FWidgetReference WidgetReference = *GetSelectedWidgets().CreateConstIterator(); const FString VariableName = WidgetReference.GetTemplate()->GetName(); FMemberReference MemberReference; MemberReference.SetSelfMember(*VariableName); const FString SearchTerm = EnumHasAnyFlags(Flags, EGetFindReferenceSearchStringFlags::UseSearchSyntax) ? MemberReference.GetReferenceSearchString(GetBlueprintObj()->SkeletonGeneratedClass) : FString::Printf(TEXT("\"%s\""), *VariableName); SetCurrentMode(FWidgetBlueprintApplicationModes::GraphMode); const bool bSetFindWithinBlueprint = !bSearchAllBlueprints; SummonSearchUI(bSetFindWithinBlueprint, SearchTerm); } bool FWidgetBlueprintEditor::CanFindWidgetReferences() const { return GetSelectedWidgets().Num() == 1 && GetSelectedWidgets().CreateConstIterator()->GetTemplate()->bIsVariable; } bool FWidgetBlueprintEditor::CanCreateNativeBaseClass() const { return ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed() && IsParentClassNative(); } bool FWidgetBlueprintEditor::IsCreateNativeBaseClassVisible() const { return ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed(); } void FWidgetBlueprintEditor::Tick(float DeltaTime) { Super::Tick(DeltaTime); // Tick the preview scene world. // Allow full tick only if preview simulation is enabled and we're not currently in an active SIE or PIE session if (bIsSimulateEnabled && GEditor->PlayWorld == nullptr && !GEditor->bIsSimulatingInEditor) { PreviewScene.GetWorld()->Tick(bIsRealTime ? LEVELTICK_All : LEVELTICK_TimeOnly, DeltaTime); } else { PreviewScene.GetWorld()->Tick(bIsRealTime ? LEVELTICK_ViewportsOnly : LEVELTICK_TimeOnly, DeltaTime); } // Whenever animations change the generated class animations need to be updated since they are copied on compile. This // update is deferred to tick since some edit operations (e.g. drag/drop) cause large numbers of changes to the data. if ( bRefreshGeneratedClassAnimations ) { TArray>& PreviewAnimations = Cast( PreviewBlueprint->GeneratedClass )->Animations; PreviewAnimations.Empty(); for ( UWidgetAnimation* WidgetAnimation : PreviewBlueprint->Animations ) { PreviewAnimations.Add( DuplicateObject( WidgetAnimation, PreviewBlueprint->GeneratedClass ) ); } bRefreshGeneratedClassAnimations = false; } // Note: The weak ptr can become stale if the actor is reinstanced due to a Blueprint change, etc. In that case we // look to see if we can find the new instance in the preview world and then update the weak ptr. if ( PreviewWidgetPtr.IsStale(true) || bPreviewInvalidated ) { bPreviewInvalidated = false; RefreshPreview(); } // Update the palette view model. if (PaletteViewModel->NeedUpdate()) { PaletteViewModel->Update(); } if (LibraryViewModel->NeedUpdate()) { LibraryViewModel->Update(); } } static bool MigratePropertyValue(UObject* SourceObject, UObject* DestinationObject, FEditPropertyChain::TDoubleLinkedListNode* PropertyChainNode, FProperty* MemberProperty, bool bIsModify) { FProperty* CurrentProperty = PropertyChainNode->GetValue(); FEditPropertyChain::TDoubleLinkedListNode* NextNode = PropertyChainNode->GetNextNode(); if ( !ensure(SourceObject && DestinationObject) ) { return false; } ensure(SourceObject->GetClass() == DestinationObject->GetClass()); // If the current property is an array, map or set, short-circuit current progress so that we copy the whole container. if ( CastField(CurrentProperty) || CastField(CurrentProperty) || CastField(CurrentProperty) || CastField(CurrentProperty)) { NextNode = nullptr; } if ( FObjectProperty* CurrentObjectProperty = CastField(CurrentProperty) ) { UObject* NewSourceObject = CurrentObjectProperty->GetObjectPropertyValue_InContainer(SourceObject); UObject* NewDestionationObject = CurrentObjectProperty->GetObjectPropertyValue_InContainer(DestinationObject); if ( NewSourceObject == nullptr || NewDestionationObject == nullptr ) { NextNode = nullptr; } } if ( NextNode == nullptr ) { if (bIsModify) { if (DestinationObject) { DestinationObject->SetFlags(RF_Transactional); DestinationObject->Modify(); } return true; } else { // Check to see if there's an edit condition property we also need to migrate. bool bDummyNegate = false; FBoolProperty* EditConditionProperty = PropertyCustomizationHelpers::GetEditConditionProperty(MemberProperty, bDummyNegate); if ( EditConditionProperty != nullptr ) { FObjectEditorUtils::MigratePropertyValue(SourceObject, EditConditionProperty, DestinationObject, EditConditionProperty); } return FObjectEditorUtils::MigratePropertyValue(SourceObject, MemberProperty, DestinationObject, MemberProperty); } } if ( FObjectProperty* CurrentObjectProperty = CastField(CurrentProperty) ) { UObject* NewSourceObject = CurrentObjectProperty->GetObjectPropertyValue_InContainer(SourceObject); UObject* NewDestionationObject = CurrentObjectProperty->GetObjectPropertyValue_InContainer(DestinationObject); return MigratePropertyValue(NewSourceObject, NewDestionationObject, NextNode, NextNode->GetValue(), bIsModify); } // ExportText/ImportText works on all property types return MigratePropertyValue(SourceObject, DestinationObject, NextNode, MemberProperty, bIsModify); } void FWidgetBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) { Super::AddReferencedObjects( Collector ); Collector.AddReferencedObject(PreviewWidgetPtr); } void FWidgetBlueprintEditor::MigrateFromChain(FEditPropertyChain* PropertyThatChanged, bool bIsModify) { UWidgetBlueprint* Blueprint = GetWidgetBlueprintObj(); if (const UUserWidget* PreviewUserWidget = GetPreview()) { FEditPropertyChain::TDoubleLinkedListNode* PropertyChainNode = PropertyThatChanged->GetHead(); ensure(PropertyChainNode); const UClass* OwnerClass = PropertyChainNode->GetValue()->GetOwnerClass(); if(!OwnerClass) { return; } // Here we handle migrating values on the Root Widget. So we need to migrate the property to the // Widget CDO. for (TWeakObjectPtr ObjectRef : SelectedObjects) { UObject* ObjectPtr = ObjectRef.Get(); check(ObjectPtr); if (!ObjectPtr) { continue; } // dealing with root widget here if (UObject* WidgetCDO = ObjectPtr->GetClass()->GetDefaultObject(true)) { // Only do the migration if the property on the head of the linked list lives in the CDO // We want to skip the migration if the property isn't leading to this CDO, such as when we call // FDetailCategoryImpl::AddExternalObjects to inject external objects in the details panel of the CDO. if (ObjectPtr->IsA(OwnerClass) && WidgetCDO->IsA(OwnerClass)) { MigratePropertyValue(ObjectPtr, WidgetCDO, PropertyChainNode, PropertyChainNode->GetValue(), bIsModify); } } } // Here we handle migrating values on Widgets in the WidgetTree. So we need to migrate the property to the // WidgetBlueprint WidgetTree and instead of the Preview Widget for ( FWidgetReference& WidgetRef : SelectedWidgets ) { if ( UWidget* PreviewWidget = WidgetRef.GetPreview()) { FName PreviewWidgetName = PreviewWidget->GetFName(); if (UWidget* TemplateWidget = Blueprint->WidgetTree->FindWidget(PreviewWidgetName)) { ensure(TemplateWidget == WidgetRef.GetTemplate()); // Only do the migration if the property on the head of the linked list lives in this UWidget // We want to skip the migration if the property isn't leading to this UWidget, such as when we call // FDetailCategoryImpl::AddExternalObjects to inject external objects in the details panel of this UWidget. if (PreviewWidget->IsA(OwnerClass) && TemplateWidget->IsA(OwnerClass)) { MigratePropertyValue(PreviewWidget, TemplateWidget, PropertyChainNode, PropertyChainNode->GetValue(), bIsModify); } // If the Object being modified is a UUIComponent, we want to migrate to the Extension else if(OwnerClass->IsChildOf()) { // We migrate the value to the Widget Blueprint Extension component UUIComponentUserWidgetExtension* ComponentExtension = PreviewUserWidget->GetExtension(); UUIComponentWidgetBlueprintExtension* ComponentWidgetBlueprintExtension = UWidgetBlueprintExtension::GetExtension(Blueprint); if (ComponentExtension && ComponentWidgetBlueprintExtension) { UUIComponent* Component = ComponentExtension->GetComponent(OwnerClass, TemplateWidget->GetFName()); UUIComponent* WPComponent = ComponentWidgetBlueprintExtension->GetComponent(OwnerClass, TemplateWidget->GetFName()); if(WPComponent && Component) { MigratePropertyValue(Component, WPComponent, PropertyChainNode, PropertyChainNode->GetValue(), bIsModify); } } } } } } } } void FWidgetBlueprintEditor::PostUndo(bool bSuccessful) { Super::PostUndo(bSuccessful); InvalidatePreview(); OnWidgetBlueprintTransaction.Broadcast(); } void FWidgetBlueprintEditor::PostRedo(bool bSuccessful) { Super::PostRedo(bSuccessful); InvalidatePreview(); OnWidgetBlueprintTransaction.Broadcast(); } TSharedRef FWidgetBlueprintEditor::CreateSequencerTabWidget() { TSharedRef SequencerOverlayRef = SNew(SOverlay) .AddMetaData(FTagMetaData(TEXT("Sequencer"))); TabSequencerOverlay = SequencerOverlayRef; TSharedPtr NoAnimationTextBlockPtr; if (!NoAnimationTextBlockTab.IsValid()) { NoAnimationTextBlockPtr = SNew(STextBlock) .TextStyle(FAppStyle::Get(), "UMGEditor.NoAnimationFont") .Text(LOCTEXT("NoAnimationSelected", "No Animation Selected")); NoAnimationTextBlockTab = NoAnimationTextBlockPtr; } SequencerOverlayRef->AddSlot(0) [ GetTabSequencer()->GetSequencerWidget() ]; SequencerOverlayRef->AddSlot(1) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ NoAnimationTextBlockTab.Pin().ToSharedRef() ]; return SequencerOverlayRef; } TSharedRef FWidgetBlueprintEditor::CreateSequencerDrawerWidget() { TSharedRef SequencerOverlayRef = SNew(SOverlay) .AddMetaData(FTagMetaData(TEXT("Sequencer"))); DrawerSequencerOverlay = SequencerOverlayRef; TSharedPtr NoAnimationTextBlockPtr; if (!NoAnimationTextBlockDrawer.IsValid()) { NoAnimationTextBlockPtr = SNew(STextBlock) .TextStyle(FAppStyle::Get(), "UMGEditor.NoAnimationFont") .Text(LOCTEXT("NoAnimationSelected", "No Animation Selected")); NoAnimationTextBlockDrawer = NoAnimationTextBlockPtr; } SequencerOverlayRef->AddSlot(0) [ GetDrawerSequencer()->GetSequencerWidget() ]; SequencerOverlayRef->AddSlot(1) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ NoAnimationTextBlockDrawer.Pin().ToSharedRef() ]; return SequencerOverlayRef; } TSharedRef FWidgetBlueprintEditor::OnGetWidgetAnimSequencer() { if (!AnimDrawerWidget.IsValid()) { FAnimationTabSummoner AnimDrawerSummoner(SharedThis(this), true); FWorkflowTabSpawnInfo SpawnInfo; AnimDrawerWidget = AnimDrawerSummoner.CreateTabBody(SpawnInfo); } return AnimDrawerWidget.ToSharedRef(); } void FWidgetBlueprintEditor::AddExternalEditorWidget(FName ID, TSharedRef InExternalWidget) { if (!ExternalEditorWidgets.Contains(ID)) { ExternalEditorWidgets.Add(ID, InExternalWidget); } } int32 FWidgetBlueprintEditor::RemoveExternalEditorWidget(FName ID) { return ExternalEditorWidgets.Remove(ID); } TSharedPtr FWidgetBlueprintEditor::GetExternalEditorWidget(FName ID) { TSharedPtr* ExternalWidget = ExternalEditorWidgets.Find(ID); if (ExternalWidget) { return *ExternalWidget; } return nullptr; } void FWidgetBlueprintEditor::ToggleAnimDrawer() { GEditor->GetEditorSubsystem()->TryToggleDrawer(FAnimationTabSummoner::WidgetAnimSequencerDrawerID); } void FWidgetBlueprintEditor::NotifyWidgetAnimListChanged() { OnWidgetAnimationsUpdated.Broadcast(); // Check if any animations viewed are invalid, if so select null animation // This can happen when a secondardary sequencer deletes our animatio for (TWeakPtr& SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { UWidgetAnimation* WidgetAnimation = Cast(Sequencer->GetFocusedMovieSceneSequence()); if (!GetWidgetBlueprintObj()->Animations.Contains(WidgetAnimation)) { Sequencer->ResetToNewRootSequence(*UWidgetAnimation::GetNullAnimation()); Sequencer->GetSequencerWidget()->SetEnabled(false); Sequencer->SetAutoChangeMode(EAutoChangeMode::None); } } } } void FWidgetBlueprintEditor::OnWidgetAnimSequencerOpened(FName StatusBarWithDrawerName) { OnWidgetAnimDrawerSequencerOpened(StatusBarWithDrawerName); } void FWidgetBlueprintEditor::OnWidgetAnimSequencerDismissed(const TSharedPtr& NewlyFocusedWidget) { OnWidgetAnimDrawerSequencerDismissed(NewlyFocusedWidget); } void FWidgetBlueprintEditor::OnWidgetAnimDrawerSequencerOpened(FName StatusBarWithDrawerName) { bIsSequencerDrawerOpen = true; if (TSharedPtr& ActiveSequencer = GetSequencer()) { UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); if (WidgetAnimation) { ChangeViewedAnimation(*WidgetAnimation); } } for (TWeakPtr SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { Sequencer->RefreshTree(); } } if (DrawerSequencer) { FSlateApplication::Get().SetUserFocus(FSlateApplication::Get().GetUserIndexForKeyboard(), DrawerSequencer->GetSequencerWidget()); } } void FWidgetBlueprintEditor::OnWidgetAnimDrawerSequencerDismissed(const TSharedPtr& NewlyFocusedWidget) { if (TSharedPtr& ActiveSequencer = GetSequencer()) { UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); if (WidgetAnimation) { ChangeViewedAnimation(*WidgetAnimation); } ActiveSequencer->GetSequencerWidget()->SetEnabled(false); ActiveSequencer->SetAutoChangeMode(EAutoChangeMode::None); } bIsSequencerDrawerOpen = false; for (TWeakPtr SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { Sequencer->RefreshTree(); } } SetKeyboardFocus(); } void FWidgetBlueprintEditor::OnWidgetAnimTabSequencerClosed(TSharedRef ClosedTab) { // Deselected any animation when closing the tab ChangeViewedAnimation(*UWidgetAnimation::GetNullAnimation()); if (TSharedPtr& ActiveSequencer = GetSequencer()) { ActiveSequencer->GetSequencerWidget()->SetEnabled(false); ActiveSequencer->SetAutoChangeMode(EAutoChangeMode::None); } } void FWidgetBlueprintEditor::OnWidgetAnimTabSequencerOpened() { if (TSharedPtr& ActiveSequencer = GetSequencer()) { if (UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence())) { ChangeViewedAnimation(*WidgetAnimation); } } } UWidgetBlueprint* FWidgetBlueprintEditor::GetWidgetBlueprintObj() const { return Cast(GetBlueprintObj()); } UUserWidget* FWidgetBlueprintEditor::GetPreview() const { if ( PreviewWidgetPtr.IsStale(true) ) { return nullptr; } return PreviewWidgetPtr.Get(); } FPreviewScene* FWidgetBlueprintEditor::GetPreviewScene() { return &PreviewScene; } bool FWidgetBlueprintEditor::IsSimulating() const { return bIsSimulateEnabled; } void FWidgetBlueprintEditor::SetIsSimulating(bool bSimulating) { bIsSimulateEnabled = bSimulating; } FWidgetReference FWidgetBlueprintEditor::GetReferenceFromTemplate(UWidget* TemplateWidget) { TSharedRef Reference = MakeShareable(new FWidgetHandle(TemplateWidget)); WidgetHandlePool.Add(Reference); return FWidgetReference(SharedThis(this), Reference); } FWidgetReference FWidgetBlueprintEditor::GetReferenceFromPreview(UWidget* PreviewWidget) { UUserWidget* PreviewRoot = GetPreview(); if ( PreviewRoot ) { UWidgetBlueprint* Blueprint = GetWidgetBlueprintObj(); if ( PreviewWidget ) { FName Name = PreviewWidget->GetFName(); return GetReferenceFromTemplate(Blueprint->WidgetTree->FindWidget(Name)); } } return FWidgetReference(SharedThis(this), TSharedPtr()); } TSharedPtr& FWidgetBlueprintEditor::GetSequencer() { return bIsSequencerDrawerOpen ? GetDrawerSequencer() : GetTabSequencer(); } TSharedPtr FWidgetBlueprintEditor::CreateSequencerWidgetInternal() { const float InTime = 0.f; const float OutTime = 5.0f; FSequencerViewParams ViewParams(TEXT("UMGSequencerSettings")); { ViewParams.OnGetAddMenuContent = FOnGetAddMenuContent::CreateSP(this, &FWidgetBlueprintEditor::OnGetAnimationAddMenuContent); ViewParams.OnBuildCustomContextMenuForGuid = FOnBuildCustomContextMenuForGuid::CreateSP(this, &FWidgetBlueprintEditor::OnBuildCustomContextMenuForGuid); } FSequencerInitParams SequencerInitParams; { UWidgetAnimation* NullAnimation = UWidgetAnimation::GetNullAnimation(); FFrameRate TickResolution = NullAnimation->MovieScene->GetTickResolution(); FFrameNumber StartFrame = (InTime * TickResolution).FloorToFrame(); FFrameNumber EndFrame = (OutTime * TickResolution).CeilToFrame(); NullAnimation->MovieScene->SetPlaybackRange(StartFrame, (EndFrame - StartFrame).Value); FMovieSceneEditorData& EditorData = NullAnimation->MovieScene->GetEditorData(); EditorData.WorkStart = InTime; EditorData.WorkEnd = OutTime; SequencerInitParams.ViewParams = ViewParams; SequencerInitParams.RootSequence = NullAnimation; SequencerInitParams.bEditWithinLevelEditor = false; SequencerInitParams.ToolkitHost = GetToolkitHost(); SequencerInitParams.PlaybackContext = TAttribute(this, &FWidgetBlueprintEditor::GetAnimationPlaybackContext); SequencerInitParams.EventContexts = TAttribute>(this, &FWidgetBlueprintEditor::GetAnimationEventContexts); SequencerInitParams.HostCapabilities.bSupportsCurveEditor = true; SequencerInitParams.HostCapabilities.bSupportsAddFromContentBrowser = true; SequencerInitParams.HostCapabilities.bSupportsSidebar = true; SequencerInitParams.HostCapabilities.bSupportsViewportSelectability = true; }; TSharedPtr Sequencer = FModuleManager::LoadModuleChecked("Sequencer").CreateSequencer(SequencerInitParams); // Never recompile the blueprint on evaluate as this can create an insidious loop Sequencer->GetSequencerSettings()->SetCompileDirectorOnEvaluate(false); Sequencer->OnMovieSceneDataChanged().AddSP(this, &FWidgetBlueprintEditor::OnMovieSceneDataChanged); Sequencer->OnMovieSceneBindingsPasted().AddSP(this, &FWidgetBlueprintEditor::OnMovieSceneBindingsPasted); // Change selected widgets in the sequencer tree view Sequencer->GetSelectionChangedObjectGuids().AddSP(this, &FWidgetBlueprintEditor::SyncSelectedWidgetsWithSequencerSelection); OnSelectedWidgetsChanged.AddSP(this, &FWidgetBlueprintEditor::SyncSequencerSelectionToSelectedWidgets); // Allow sequencer to test which bindings are selected Sequencer->OnGetIsBindingVisible().BindRaw(this, &FWidgetBlueprintEditor::IsBindingSelected); Sequencers.AddUnique(Sequencer); return Sequencer; } TSharedPtr& FWidgetBlueprintEditor::GetTabSequencer() { if(!TabSequencer.IsValid()) { TabSequencer = CreateSequencerWidgetInternal(); bIsSequencerDrawerOpen = false; ChangeViewedAnimation(*UWidgetAnimation::GetNullAnimation()); } return TabSequencer; } TSharedPtr& FWidgetBlueprintEditor::GetDrawerSequencer() { if(!DrawerSequencer.IsValid()) { DrawerSequencer = CreateSequencerWidgetInternal(); bIsSequencerDrawerOpen = true; ChangeViewedAnimation(*UWidgetAnimation::GetNullAnimation()); } return DrawerSequencer; } void FWidgetBlueprintEditor::DockInLayoutClicked() { GEditor->GetEditorSubsystem()->ForceDismissDrawer(); const FName AnimationsTabName = FName(TEXT("Animations")); if (TSharedPtr ExistingTab = GetToolkitHost()->GetTabManager()->TryInvokeTab(AnimationsTabName)) { ExistingTab->ActivateInParent(ETabActivationCause::SetDirectly); } } void FWidgetBlueprintEditor::ChangeViewedAnimation( UWidgetAnimation& InAnimationToView ) { CurrentAnimation = &InAnimationToView; for (TWeakPtr SequencerPtr : Sequencers) { if (SequencerPtr.IsValid()) { TSharedPtr Sequencer = SequencerPtr.Pin(); Sequencer->ResetToNewRootSequence(InAnimationToView); if (&InAnimationToView == UWidgetAnimation::GetNullAnimation()) { Sequencer->GetSequencerWidget()->SetEnabled(false); Sequencer->SetAutoChangeMode(EAutoChangeMode::None); } else { Sequencer->GetSequencerWidget()->SetEnabled(true); } } } auto ToggleSequencerInteraction = [this](TWeakPtr SequencerOverlay, TWeakPtr NoAnimationTextBlock, UWidgetAnimation& InAnimationToView) { if (SequencerOverlay.IsValid() && NoAnimationTextBlock.IsValid()) { TSharedPtr SequencerOverlayPin = SequencerOverlay.Pin(); TSharedPtr NoAnimationTextBlockPin = NoAnimationTextBlock.Pin(); if (&InAnimationToView == UWidgetAnimation::GetNullAnimation()) { const FName CurveEditorTabName = FName(TEXT("SequencerGraphEditor")); TSharedPtr ExistingTab = GetToolkitHost()->GetTabManager()->FindExistingLiveTab(CurveEditorTabName); if (ExistingTab) { ExistingTab->RequestCloseTab(); } // Disable sequencer from interaction NoAnimationTextBlockPin->SetVisibility(EVisibility::Visible); SequencerOverlayPin->SetVisibility(EVisibility::HitTestInvisible); } else { // Allow sequencer to be interacted with NoAnimationTextBlockPin->SetVisibility(EVisibility::Collapsed); SequencerOverlayPin->SetVisibility(EVisibility::SelfHitTestInvisible); } } }; ToggleSequencerInteraction(TabSequencerOverlay, NoAnimationTextBlockTab, InAnimationToView); ToggleSequencerInteraction(DrawerSequencerOverlay, NoAnimationTextBlockDrawer, InAnimationToView); InvalidatePreview(); OnSelectedAnimationChanged.Broadcast(); } void FWidgetBlueprintEditor::RefreshPreview() { // Rebuilding the preview can force objects to be recreated, so the selection may need to be updated. OnSelectedWidgetsChanging.Broadcast(); UpdatePreview(GetWidgetBlueprintObj(), true); CleanSelection(); // Fire the selection updated event to ensure everyone is watching the same widgets. OnSelectedWidgetsChanged.Broadcast(); } void FWidgetBlueprintEditor::Compile() { DestroyPreview(); FBlueprintEditor::Compile(); if (const UWidgetDesignerSettings* Settings = GetDefault()) { // Check if we should create the compile tab bool bShouldCreateCompileTab = false; switch (Settings->CreateOnCompile) { case EDisplayOnCompile::DoC_ErrorsOrWarnings: bShouldCreateCompileTab = CachedNumErrors > 0 || CachedNumWarnings > 0; break; case EDisplayOnCompile::DoC_ErrorsOnly: bShouldCreateCompileTab = CachedNumErrors > 0; break; case EDisplayOnCompile::DoC_WarningsOnly: bShouldCreateCompileTab = CachedNumWarnings > 0; break; case EDisplayOnCompile::DoC_Never: default: break; } if (bShouldCreateCompileTab) { GetToolkitHost()->GetTabManager()->TryInvokeTab(FBlueprintEditorTabs::CompilerResultsID); } // Check if we should dismiss the compile tab bool bShouldDismissCompileTab = false; switch (Settings->DismissOnCompile) { case EDisplayOnCompile::DoC_ErrorsOrWarnings: bShouldDismissCompileTab = CachedNumErrors == 0 && CachedNumWarnings == 0; break; case EDisplayOnCompile::DoC_ErrorsOnly: bShouldDismissCompileTab = CachedNumErrors == 0; break; case EDisplayOnCompile::DoC_WarningsOnly: bShouldDismissCompileTab = CachedNumWarnings == 0; break; case EDisplayOnCompile::DoC_Never: default: break; } if (bShouldDismissCompileTab) { TSharedPtr CompileResultsTab = GetToolkitHost()->GetTabManager()->FindExistingLiveTab(FBlueprintEditorTabs::CompilerResultsID); if (CompileResultsTab) { CompileResultsTab->RequestCloseTab(); } } } } bool FWidgetBlueprintEditor::OnRequestClose(EAssetEditorCloseReason InCloseReason) { bool bAllowClose = Super::OnRequestClose(InCloseReason); // Give any active modes a chance to shutdown while the toolkit host is still alive // Note: This along side with the default tool palette extension tab being closed // is what prevents an unrecognized tab from spawning on layout restore if (bAllowClose) { GetEditorModeManager().ActivateDefaultMode(); } return bAllowClose; } void FWidgetBlueprintEditor::OnToolkitHostingStarted(const TSharedRef& Toolkit) { ModeUILayer->OnToolkitHostingStarted(Toolkit); } void FWidgetBlueprintEditor::OnToolkitHostingFinished(const TSharedRef& Toolkit) { ModeUILayer->OnToolkitHostingFinished(Toolkit); } void FWidgetBlueprintEditor::DestroyPreview() { UUserWidget* PreviewUserWidget = GetPreview(); if ( PreviewUserWidget != nullptr ) { check(PreviewScene.GetWorld()); // Establish the widget as being in design time before destroying it PreviewUserWidget->SetDesignerFlags(GetCurrentDesignerFlags()); // Immediately release the preview ptr to let people know it's gone. PreviewWidgetPtr.Reset(); // Immediately notify anyone with a preview out there they need to dispose of it right now, // otherwise the leak detection can't be trusted. OnWidgetPreviewUpdated.Broadcast(); FWidgetBlueprintEditorUtils::DestroyUserWidget(PreviewUserWidget); } } void FWidgetBlueprintEditor::UpdatePreview(UBlueprint* InBlueprint, bool bInForceFullUpdate) { UUserWidget* PreviewUserWidget = GetPreview(); // Signal that we're going to be constructing editor components if ( InBlueprint != nullptr && InBlueprint->SimpleConstructionScript != nullptr ) { InBlueprint->SimpleConstructionScript->BeginEditorComponentConstruction(); } // If the Blueprint is changing if ( InBlueprint != PreviewBlueprint || bInForceFullUpdate ) { // Destroy the previous actor instance DestroyPreview(); // Save the Blueprint we're creating a preview for PreviewBlueprint = Cast(InBlueprint); PreviewUserWidget = FWidgetBlueprintEditorUtils::CreateUserWidgetFromBlueprint( PreviewScene.GetWorld(), PreviewBlueprint, FWidgetBlueprintEditorUtils::FCreateWidgetFromBlueprintParams{ GetCurrentDesignerFlags(), PreviewScene.GetWorld()->GetFirstLocalPlayerFromController() }); // Store a reference to the preview actor. PreviewWidgetPtr = PreviewUserWidget; } OnWidgetPreviewUpdated.Broadcast(); // We've changed the binding context so drastically that we should just clear all knowledge of our previous cached bindings for (TWeakPtr& SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { Sequencer->GetEvaluationState()->ClearObjectCaches(*Sequencer); Sequencer->ForceEvaluate(); } } } FGraphAppearanceInfo FWidgetBlueprintEditor::GetGraphAppearance(UEdGraph* InGraph) const { FGraphAppearanceInfo AppearanceInfo = Super::GetGraphAppearance(InGraph); if (FBlueprintEditorUtils::IsEditorUtilityBlueprint(GetBlueprintObj())) { AppearanceInfo.CornerText = LOCTEXT("EditorUtilityWidgetAppearanceCornerText", "EDITOR UTILITY WIDGET"); } else if ( GetBlueprintObj()->IsA(UWidgetBlueprint::StaticClass()) ) { AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText", "WIDGET BLUEPRINT"); } return AppearanceInfo; } TSubclassOf FWidgetBlueprintEditor::GetDefaultSchemaClass() const { return UWidgetGraphSchema::StaticClass(); } void FWidgetBlueprintEditor::ClearHoveredWidget() { HoveredWidget = FWidgetReference(); OnHoveredWidgetCleared.Broadcast(); } void FWidgetBlueprintEditor::SetHoveredWidget(FWidgetReference& InHoveredWidget) { if (InHoveredWidget != HoveredWidget) { HoveredWidget = InHoveredWidget; OnHoveredWidgetSet.Broadcast(InHoveredWidget); } } const FWidgetReference& FWidgetBlueprintEditor::GetHoveredWidget() const { return HoveredWidget; } void FWidgetBlueprintEditor::AddPostDesignerLayoutAction(TFunction Action) { QueuedDesignerActions.Add(MoveTemp(Action)); } void FWidgetBlueprintEditor::OnEnteringDesigner() { OnEnterWidgetDesigner.Broadcast(); } TArray< TFunction >& FWidgetBlueprintEditor::GetQueuedDesignerActions() { return QueuedDesignerActions; } EWidgetDesignFlags FWidgetBlueprintEditor::GetCurrentDesignerFlags() const { EWidgetDesignFlags Flags = EWidgetDesignFlags::Designing; if ( bShowDashedOutlines ) { Flags |= EWidgetDesignFlags::ShowOutline; } if ( const UWidgetDesignerSettings* Designer = GetDefault() ) { if ( Designer->bExecutePreConstructEvent ) { Flags |= EWidgetDesignFlags::ExecutePreConstruct; } } return Flags; } bool FWidgetBlueprintEditor::GetShowDashedOutlines() const { return bShowDashedOutlines; } void FWidgetBlueprintEditor::SetShowDashedOutlines(bool Value) { bShowDashedOutlines = Value; } bool FWidgetBlueprintEditor::GetIsRespectingLocks() const { return bRespectLocks; } void FWidgetBlueprintEditor::SetIsRespectingLocks(bool Value) { bRespectLocks = Value; } void FWidgetBlueprintEditor::CreateEditorModeManager() { TSharedPtr WidgetModeManager = MakeShared(); WidgetModeManager->OwningToolkit = SharedThis(this); EditorModeManager = WidgetModeManager; } class FObjectAndDisplayName { public: FObjectAndDisplayName(FText InDisplayName, UObject* InObject) { DisplayName = InDisplayName; Object = InObject; } bool operator<(FObjectAndDisplayName const& Other) const { return DisplayName.CompareTo(Other.DisplayName) < 0; } FText DisplayName; UObject* Object; }; void GetBindableObjects(UWidgetTree* WidgetTree, TArray& BindableObjects) { // Add the 'this' widget so you can animate it. BindableObjects.Add(FObjectAndDisplayName(LOCTEXT("RootWidgetThis", "[[This]]"), WidgetTree->GetOuter())); WidgetTree->ForEachWidget([&BindableObjects] (UWidget* Widget) { // if the widget has a generated name this is just some unimportant widget, don't show it in the list? if (Widget->IsGeneratedName() && !Widget->bIsVariable) { return; } BindableObjects.Add(FObjectAndDisplayName(Widget->GetLabelText(), Widget)); if (Widget->Slot && Widget->Slot->Parent) { FText SlotDisplayName = FText::Format(LOCTEXT("AddMenuSlotFormat", "{0} ({1})"), Widget->GetLabelText(), Widget->Slot->GetClass()->GetDisplayNameText()); BindableObjects.Add(FObjectAndDisplayName(SlotDisplayName, Widget->Slot)); } }); } void FWidgetBlueprintEditor::OnGetAnimationAddMenuContent(FMenuBuilder& MenuBuilder, TSharedRef InSequencer) { if (CurrentAnimation.IsValid()) { const TSet& Selection = GetSelectedWidgets(); for (const FWidgetReference& SelectedWidget : Selection) { if (UWidget* Widget = SelectedWidget.GetPreview()) { FUIAction AddWidgetTrackAction(FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::AddObjectToAnimation, (UObject*)Widget)); MenuBuilder.AddMenuEntry(Widget->GetLabelText(), FText(), FSlateIcon(), AddWidgetTrackAction); if (Widget->Slot && Widget->Slot->Parent) { FText SlotDisplayName = FText::Format(LOCTEXT("AddMenuSlotFormat", "{0} ({1})"), Widget->GetLabelText(), Widget->Slot->GetClass()->GetDisplayNameText()); FUIAction AddSlotTrackAction(FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::AddObjectToAnimation, (UObject*)Widget->Slot)); MenuBuilder.AddMenuEntry(SlotDisplayName, FText(), FSlateIcon(), AddSlotTrackAction); } } } MenuBuilder.AddSubMenu( LOCTEXT("AllNamedWidgets", "All Named Widgets"), LOCTEXT("AllNamedWidgetsTooltip", "Select a widget or slot to create an animation track for"), FNewMenuDelegate::CreateRaw(this, &FWidgetBlueprintEditor::OnGetAnimationAddMenuContentAllWidgets), false, FSlateIcon() ); } } void FWidgetBlueprintEditor::OnGetAnimationAddMenuContentAllWidgets(FMenuBuilder& MenuBuilder) { TArray BindableObjects; { GetBindableObjects(GetPreview()->WidgetTree, BindableObjects); BindableObjects.Sort(); } TSharedPtr& ActiveSequencer = GetSequencer(); for (FObjectAndDisplayName& BindableObject : BindableObjects) { FGuid BoundObjectGuid = ActiveSequencer->FindObjectId(*BindableObject.Object, ActiveSequencer->GetFocusedTemplateID()); if (BoundObjectGuid.IsValid() == false) { FUIAction AddMenuAction(FExecuteAction::CreateSP(this, &FWidgetBlueprintEditor::AddObjectToAnimation, BindableObject.Object)); MenuBuilder.AddMenuEntry(BindableObject.DisplayName, FText(), FSlateIcon(), AddMenuAction); } } } void FWidgetBlueprintEditor::AddObjectToAnimation(UObject* ObjectToAnimate) { TSharedPtr& ActiveSequencer = GetSequencer(); UMovieScene* MovieScene = ActiveSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); if (MovieScene->IsReadOnly()) { return; } const FScopedTransaction Transaction( LOCTEXT( "AddWidgetToAnimation", "Add widget to animation" ) ); ActiveSequencer->GetFocusedMovieSceneSequence()->Modify(); // @todo Sequencer - Make this kind of adding more explicit, this current setup seem a bit brittle. FGuid NewGuid = ActiveSequencer->GetHandleToObject(ObjectToAnimate); TArray SelectedParentFolders; ActiveSequencer->GetSelectedFolders(SelectedParentFolders); if (SelectedParentFolders.Num() > 0) { SelectedParentFolders[0]->AddChildObjectBinding(NewGuid); } } TSharedRef FWidgetBlueprintEditor::GetAddTrackSequencerExtender( const TSharedRef CommandList, const TArray ContextSensitiveObjects ) { TSharedRef AddTrackMenuExtender( new FExtender() ); AddTrackMenuExtender->AddMenuExtension( SequencerMenuExtensionPoints::AddTrackMenu_PropertiesSection, EExtensionHook::Before, CommandList, FMenuExtensionDelegate::CreateRaw( this, &FWidgetBlueprintEditor::ExtendSequencerAddTrackMenu, ContextSensitiveObjects ) ); return AddTrackMenuExtender; } void FWidgetBlueprintEditor::OnBuildCustomContextMenuForGuid(FMenuBuilder& MenuBuilder, FGuid ObjectBinding) { if (CurrentAnimation.IsValid()) { TArray ValidSelectedWidgets; for (FWidgetReference SelectedWidget : SelectedWidgets) { if (SelectedWidget.IsValid()) { //need to make sure it's a widget, if not bound assume it is. UWidget* BoundWidget = nullptr; bool bNotBound = true; TSharedPtr& ActiveSequencer = GetSequencer(); if (auto ObjectsView = ActiveSequencer->FindObjectsInCurrentSequence(ObjectBinding); ObjectsView.Num() > 0) { TWeakObjectPtr<> WeakObjectPtr = ObjectsView[0]; BoundWidget = Cast(WeakObjectPtr.Get()); bNotBound = false; } if (bNotBound || (BoundWidget && SelectedWidget.GetPreview()->GetTypedOuter() == BoundWidget->GetTypedOuter())) { ValidSelectedWidgets.Add(SelectedWidget); } } } if (ValidSelectedWidgets.Num() > 0) { MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry( LOCTEXT("AddSelectedToBinding", "Add Selected"), LOCTEXT("AddSelectedToBindingToolTip", "Add selected objects to this track"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::AddWidgetsToTrack, ValidSelectedWidgets, ObjectBinding) ); if (ValidSelectedWidgets.Num() > 1) { MenuBuilder.AddMenuEntry( LOCTEXT("ReplaceBindingWithSelected", "Replace with Selected"), LOCTEXT("ReplaceBindingWithSelectedToolTip", "Replace the object binding with selected objects"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::ReplaceTrackWithWidgets, ValidSelectedWidgets, ObjectBinding) ); } else { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("ReplaceObject", "Replace with {0}"), FText::FromString(ValidSelectedWidgets[0].GetPreview()->GetName())), FText::Format(LOCTEXT("ReplaceObjectToolTip", "Replace the bound widget in this animation with {0}"), FText::FromString(ValidSelectedWidgets[0].GetPreview()->GetName())), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::ReplaceTrackWithWidgets, ValidSelectedWidgets, ObjectBinding) ); } MenuBuilder.AddMenuEntry( LOCTEXT("RemoveSelectedFromBinding", "Remove Selected"), LOCTEXT("RemoveSelectedFromBindingToolTip", "Remove selected objects from this track"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::RemoveWidgetsFromTrack, ValidSelectedWidgets, ObjectBinding) ); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveAllBindings", "Remove All"), LOCTEXT("RemoveAllBindingsToolTip", "Remove all bound objects from this track"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::RemoveAllWidgetsFromTrack, ObjectBinding) ); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveMissing", "Remove Missing"), LOCTEXT("RemoveMissingToolTip", "Remove missing objects bound to this track"), FSlateIcon(), FExecuteAction::CreateRaw(this, &FWidgetBlueprintEditor::RemoveMissingWidgetsFromTrack, ObjectBinding) ); MenuBuilder.AddSubMenu( LOCTEXT("DynamicPossession", "Dynamic Possession"), LOCTEXT("DynamicPossessionToolTip", "Specify a Blueprint method that will find a compatible widget for this binding"), FNewMenuDelegate::CreateRaw(this, &FWidgetBlueprintEditor::AddDynamicPossessionMenu, ObjectBinding)); } } } void FWidgetBlueprintEditor::ExtendSequencerAddTrackMenu( FMenuBuilder& AddTrackMenuBuilder, const TArray ContextObjects ) { if ( ContextObjects.Num() == 1 ) { UWidget* Widget = Cast( ContextObjects[0] ); if ( Widget != nullptr && Widget->GetTypedOuter() == GetPreview() ) { if( Widget->GetParent() != nullptr && Widget->Slot != nullptr ) { AddTrackMenuBuilder.BeginSection( "Slot", LOCTEXT( "SlotSection", "Slot" ) ); { FUIAction AddSlotAction( FExecuteAction::CreateRaw( this, &FWidgetBlueprintEditor::AddSlotTrack, Widget->Slot ) ); FText AddSlotLabel = FText::Format(LOCTEXT("SlotLabelFormat", "{0} Slot"), FText::FromString(Widget->GetParent()->GetName())); FText AddSlotToolTip = FText::Format(LOCTEXT("SlotToolTipFormat", "Add {0} slot"), FText::FromString( Widget->GetParent()->GetName())); AddTrackMenuBuilder.AddMenuEntry(AddSlotLabel, AddSlotToolTip, FSlateIcon(), AddSlotAction); } AddTrackMenuBuilder.EndSection(); } TArray MaterialBrushPropertyPaths; WidgetMaterialTrackUtilities::GetMaterialBrushPropertyPaths( Widget, MaterialBrushPropertyPaths ); if ( MaterialBrushPropertyPaths.Num() > 0 ) { AddTrackMenuBuilder.BeginSection( "Materials", LOCTEXT( "MaterialsSection", "Materials" ) ); { for (FWidgetMaterialPropertyPath& MaterialBrushPropertyPath : MaterialBrushPropertyPaths ) { FString DisplayName = MaterialBrushPropertyPath.PropertyPath[0]->GetDisplayNameText().ToString(); for ( int32 i = 1; i < MaterialBrushPropertyPath.PropertyPath.Num(); i++) { DisplayName.AppendChar( '.' ); DisplayName.Append( MaterialBrushPropertyPath.PropertyPath[i]->GetDisplayNameText().ToString() ); } DisplayName.AppendChar('.'); DisplayName.Append(MaterialBrushPropertyPath.DisplayName); FText DisplayNameText = FText::FromString( DisplayName ); FUIAction AddMaterialAction( FExecuteAction::CreateRaw( this, &FWidgetBlueprintEditor::AddMaterialTrack, Widget, MaterialBrushPropertyPath.PropertyPath, DisplayNameText ) ); FText AddMaterialLabel = DisplayNameText; FText AddMaterialToolTip = FText::Format( LOCTEXT( "MaterialToolTipFormat", "Add a material track for the {0} property." ), DisplayNameText ); AddTrackMenuBuilder.AddMenuEntry( AddMaterialLabel, AddMaterialToolTip, FSlateIcon(), AddMaterialAction ); } } AddTrackMenuBuilder.EndSection(); } } } } void FWidgetBlueprintEditor::AddWidgetsToTrack(const TArray Widgets, FGuid ObjectId) { const FScopedTransaction Transaction(LOCTEXT("AddSelectedWidgetsToTrack", "Add Widgets to Track")); TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); FText ExistingBindingName; TArray WidgetsToAdd; for (const FWidgetReference& Widget : Widgets) { UWidget* PreviewWidget = Widget.GetPreview(); // If this widget is already bound to the animation we cannot add it to 2 separate bindings FGuid SelectedWidgetId = ActiveSequencer->FindObjectId(*PreviewWidget, MovieSceneSequenceID::Root); if (!SelectedWidgetId.IsValid()) { WidgetsToAdd.Add(Widget); } else if (ExistingBindingName.IsEmpty()) { ExistingBindingName = MovieScene->GetObjectDisplayName(SelectedWidgetId); } } if (WidgetsToAdd.Num() == 0) { FNotificationInfo Info(FText::Format(LOCTEXT("WidgetAlreadyBound", "Widget already bound to {0}"), ExistingBindingName)); Info.FadeInDuration = 0.1f; Info.FadeOutDuration = 0.5f; Info.ExpireDuration = 2.5f; auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); NotificationItem->SetCompletionState(SNotificationItem::CS_Success); NotificationItem->ExpireAndFadeout(); } else { MovieScene->Modify(); WidgetAnimation->Modify(); for (const FWidgetReference& Widget : WidgetsToAdd) { UWidget* PreviewWidget = Widget.GetPreview(); WidgetAnimation->BindPossessableObject(ObjectId, *PreviewWidget, GetAnimationPlaybackContext()); } UpdateTrackName(ObjectId); SyncSequencersMovieSceneData(); } } void FWidgetBlueprintEditor::RemoveWidgetsFromTrack(const TArray Widgets, FGuid ObjectId) { const FScopedTransaction Transaction(LOCTEXT("RemoveWidgetsFromTrack", "Remove Widgets from Track")); TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); TArray WidgetsToRemove; for (const FWidgetReference& Widget : Widgets) { UWidget* PreviewWidget = Widget.GetPreview(); FGuid WidgetId = ActiveSequencer->FindObjectId(*PreviewWidget, MovieSceneSequenceID::Root); if (WidgetId.IsValid() && WidgetId == ObjectId) { WidgetsToRemove.Add(Widget); } } if (WidgetsToRemove.Num() == 0) { FNotificationInfo Info(LOCTEXT("SelectedWidgetNotBound", "Selected Widget not Bound to Track")); Info.FadeInDuration = 0.1f; Info.FadeOutDuration = 0.5f; Info.ExpireDuration = 2.5f; auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); NotificationItem->SetCompletionState(SNotificationItem::CS_Success); NotificationItem->ExpireAndFadeout(); } else { MovieScene->Modify(); WidgetAnimation->Modify(); for (const FWidgetReference& Widget : WidgetsToRemove) { UWidget* PreviewWidget = Widget.GetPreview(); WidgetAnimation->RemoveBinding(*PreviewWidget); ActiveSequencer->PreAnimatedState.RestorePreAnimatedState(*PreviewWidget); } UpdateTrackName(ObjectId); SyncSequencersMovieSceneData(); } } void FWidgetBlueprintEditor::RemoveAllWidgetsFromTrack(FGuid ObjectId) { const FScopedTransaction Transaction(LOCTEXT("RemoveAllWidgetsFromTrack", "Remove All Widgets from Track")); TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); UUserWidget* PreviewRoot = GetPreview(); check(PreviewRoot); WidgetAnimation->Modify(); MovieScene->Modify(); // Restore object animation state for (TWeakObjectPtr<> WeakObject : ActiveSequencer->FindBoundObjects(ObjectId, MovieSceneSequenceID::Root)) { if (UObject* Obj = WeakObject.Get()) { ActiveSequencer->PreAnimatedState.RestorePreAnimatedState(*Obj); } } // Remove bindings for (int32 Index = WidgetAnimation->AnimationBindings.Num() - 1; Index >= 0; --Index) { if (WidgetAnimation->AnimationBindings[Index].AnimationGuid == ObjectId) { WidgetAnimation->AnimationBindings.RemoveAt(Index, EAllowShrinking::No); } } SyncSequencersMovieSceneData(); } void FWidgetBlueprintEditor::RemoveMissingWidgetsFromTrack(FGuid ObjectId) { const FScopedTransaction Transaction(LOCTEXT("RemoveMissingWidgetsFromTrack", "Remove Missing Widgets from Track")); TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); UUserWidget* PreviewRoot = GetPreview(); check(PreviewRoot); WidgetAnimation->Modify(); MovieScene->Modify(); for (int32 Index = WidgetAnimation->AnimationBindings.Num() - 1; Index >= 0; --Index) { const FWidgetAnimationBinding& Binding = WidgetAnimation->AnimationBindings[Index]; if (Binding.AnimationGuid == ObjectId && Binding.FindRuntimeObject(*PreviewRoot->WidgetTree, *PreviewRoot, WidgetAnimation, ActiveSequencer->GetSharedPlaybackState()) == nullptr) { WidgetAnimation->AnimationBindings.RemoveAt(Index, EAllowShrinking::No); } } UpdateTrackName(ObjectId); } void FWidgetBlueprintEditor::ReplaceTrackWithWidgets(TArray Widgets, FGuid ObjectId) { TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); // Filter out anything in the input array that is currently bound to another object in the animation FText ExistingBindingName; for (int32 Index = Widgets.Num()-1; Index >= 0; --Index) { UWidget* PreviewWidget = Widgets[Index].GetPreview(); FGuid WidgetId = ActiveSequencer->FindObjectId(*PreviewWidget, MovieSceneSequenceID::Root); if (WidgetId.IsValid() && WidgetId != ObjectId) { Widgets.RemoveAt(Index, EAllowShrinking::No); if (ExistingBindingName.IsEmpty()) { ExistingBindingName = MovieScene->GetObjectDisplayName(WidgetId); } } } if (Widgets.Num() == 0) { FNotificationInfo Info(FText::Format(LOCTEXT("WidgetAlreadyBound", "Widget already bound to {0}"), ExistingBindingName)); Info.FadeInDuration = 0.1f; Info.FadeOutDuration = 0.5f; Info.ExpireDuration = 2.5f; auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); NotificationItem->SetCompletionState(SNotificationItem::CS_Success); NotificationItem->ExpireAndFadeout(); return; } const FScopedTransaction Transaction( LOCTEXT( "ReplaceTrackWithSelectedWidgets", "Replace Track with Selected Widgets" ) ); WidgetAnimation->Modify(); MovieScene->Modify(); // Remove everything from the track RemoveAllWidgetsFromTrack(ObjectId); // Create a new guid for the first object FGuid NewGuid = ActiveSequencer->GetHandleToObject(Widgets[0].GetPreview()); // Move binding contents and remove possessable MovieScene->MoveBindingContents(ObjectId, NewGuid); MovieScene->RemovePossessable(ObjectId); // Add all the remaining widgets to the new binding AddWidgetsToTrack(Widgets, NewGuid); UpdateTrackName(NewGuid); SyncSequencersMovieSceneData(); } void FWidgetBlueprintEditor::AddDynamicPossessionMenu(FMenuBuilder& MenuBuilder, FGuid ObjectId) { using namespace UE::Sequencer; TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectId); if (!Possessable) { return; } FWidgetAnimationBinding* WidgetBinding = nullptr; for (int32 Index = 0; Index < WidgetAnimation->AnimationBindings.Num(); ++Index) { FWidgetAnimationBinding& Binding = WidgetAnimation->AnimationBindings[Index]; if (Binding.AnimationGuid == ObjectId) { WidgetBinding = &Binding; break; } } if (WidgetBinding) { FDetailsViewArgs DetailsViewArgs; { DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bCustomFilterAreaLocation = true; DetailsViewArgs.bCustomNameAreaLocation = true; DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.bLockable = false; DetailsViewArgs.bSearchInitialKeyFocus = true; DetailsViewArgs.bUpdatesFromSelection = false; DetailsViewArgs.bShowOptions = false; DetailsViewArgs.bShowModifiedPropertiesOption = false; DetailsViewArgs.bShowScrollBar = false; } FStructureDetailsViewArgs StructureViewArgs; { StructureViewArgs.bShowObjects = false; StructureViewArgs.bShowAssets = true; StructureViewArgs.bShowClasses = true; StructureViewArgs.bShowInterfaces = false; } TSharedRef StructureDetailsView = FModuleManager::GetModuleChecked("PropertyEditor") .CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); // Register details customizations for this instance StructureDetailsView->GetDetailsView()->RegisterInstancedCustomPropertyTypeLayout( FMovieSceneDynamicBinding::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMovieSceneDynamicBindingCustomization::MakeInstance, MovieScene, ObjectId, 0)); // We can't just show the FMovieSceneDynamicBinding struct in the details view, because Slate only uses // the above details view customization for *properties* (not for the root object). So here we put a copy of // our dynamic binding struct inside a container, and when the details view is done setting values on it, // we copy these values back to the original dynamic binding. TSharedPtr StructOnScope = MakeShared(FMovieSceneDynamicBindingContainer::StaticStruct()); FMovieSceneDynamicBindingContainer* BufferContainer = (FMovieSceneDynamicBindingContainer*)StructOnScope->GetStructMemory(); BufferContainer->DynamicBinding = WidgetBinding->DynamicBinding; StructureDetailsView->SetStructureData(StructOnScope); StructureDetailsView->GetOnFinishedChangingPropertiesDelegate().AddSP(this, &FWidgetBlueprintEditor::OnFinishedChangingDynamicBindingProperties, StructOnScope, ObjectId); MenuBuilder.BeginSection(NAME_None, LOCTEXT("DynamicBindingHeader", "Dynamic Binding")); { TSharedRef Widget = StructureDetailsView->GetWidget().ToSharedRef(); MenuBuilder.AddWidget(Widget, FText()); } MenuBuilder.EndSection(); } } void FWidgetBlueprintEditor::OnFinishedChangingDynamicBindingProperties(const FPropertyChangedEvent& ChangeEvent, TSharedPtr ValueStruct, FGuid ObjectId) { auto* Container = (FMovieSceneDynamicBindingContainer*)ValueStruct->GetStructMemory(); using namespace UE::Sequencer; TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectId); if (!Possessable) { return; } FWidgetAnimationBinding* WidgetBinding = nullptr; for (int32 Index = 0; Index < WidgetAnimation->AnimationBindings.Num(); ++Index) { FWidgetAnimationBinding& Binding = WidgetAnimation->AnimationBindings[Index]; if (Binding.AnimationGuid == ObjectId) { WidgetBinding = &Binding; break; } } if (WidgetBinding) { WidgetBinding->DynamicBinding = Container->DynamicBinding; } // Force refresh the binding if (FMovieSceneEvaluationState* EvaluationState = ActiveSequencer->GetSharedPlaybackState()->FindCapability()) { EvaluationState->Invalidate(ObjectId, ActiveSequencer->GetFocusedTemplateID()); } } void FWidgetBlueprintEditor::AddSlotTrack( UPanelSlot* Slot ) { TSharedPtr& ActiveSequencer = GetSequencer(); ActiveSequencer->GetHandleToObject( Slot ); } void FWidgetBlueprintEditor::AddMaterialTrack( UWidget* Widget, TArray MaterialPropertyPath, FText MaterialPropertyDisplayName ) { TSharedPtr& ActiveSequencer = GetSequencer(); FGuid WidgetHandle = ActiveSequencer->GetHandleToObject( Widget ); if ( WidgetHandle.IsValid() ) { UMovieScene* MovieScene = ActiveSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); if (MovieScene->IsReadOnly()) { return; } TArray MaterialPropertyNamePath; for ( FProperty* Property : MaterialPropertyPath ) { MaterialPropertyNamePath.Add( Property->GetFName() ); } if( MovieScene->FindTrack( UMovieSceneWidgetMaterialTrack::StaticClass(), WidgetHandle, WidgetMaterialTrackUtilities::GetTrackNameFromPropertyNamePath( MaterialPropertyNamePath ) ) == nullptr) { const FScopedTransaction Transaction( LOCTEXT( "AddWidgetMaterialTrack", "Add widget material track" ) ); MovieScene->Modify(); UMovieSceneWidgetMaterialTrack* NewTrack = Cast( MovieScene->AddTrack( UMovieSceneWidgetMaterialTrack::StaticClass(), WidgetHandle ) ); NewTrack->Modify(); NewTrack->SetBrushPropertyNamePath( MaterialPropertyNamePath ); NewTrack->SetDisplayName( FText::Format( LOCTEXT( "TrackDisplayNameFormat", "{0}"), MaterialPropertyDisplayName ) ); SyncSequencersMovieSceneData(); } } } void FWidgetBlueprintEditor::OnMovieSceneDataChanged(EMovieSceneDataChangeType DataChangeType) { bRefreshGeneratedClassAnimations = true; } void FWidgetBlueprintEditor::OnMovieSceneBindingsPasted(const TArray& BindingsPasted) { TArray BindableObjects; { GetBindableObjects(GetPreview()->WidgetTree, BindableObjects); } TSharedPtr& ActiveSequencer = GetSequencer(); UMovieSceneSequence* AnimationSequence = ActiveSequencer->GetFocusedMovieSceneSequence(); UObject* BindingContext = GetAnimationPlaybackContext(); // First, rebind top level possessables (without parents) - match binding pasted's name with the bindable object name for (const FMovieSceneBinding& BindingPasted : BindingsPasted) { FMovieScenePossessable* Possessable = AnimationSequence->GetMovieScene()->FindPossessable(BindingPasted.GetObjectGuid()); if (Possessable && !Possessable->GetParent().IsValid()) { for (FObjectAndDisplayName& BindableObject : BindableObjects) { if (BindableObject.DisplayName.ToString() == BindingPasted.GetName()) { AnimationSequence->BindPossessableObject(BindingPasted.GetObjectGuid(), *BindableObject.Object, BindingContext); break; } } } } // Second, bind child possessables - match the binding pasted's parent guid with the bindable slot's content guid for (const FMovieSceneBinding& BindingPasted : BindingsPasted) { FMovieScenePossessable* Possessable = AnimationSequence->GetMovieScene()->FindPossessable(BindingPasted.GetObjectGuid()); if (Possessable && Possessable->GetParent().IsValid()) { for (FObjectAndDisplayName& BindableObject : BindableObjects) { UPanelSlot* PanelSlot = Cast(BindableObject.Object); if (PanelSlot && PanelSlot->Content) { FGuid ParentGuid = AnimationSequence->FindPossessableObjectId(*PanelSlot->Content, BindingContext); if (ParentGuid == Possessable->GetParent()) { AnimationSequence->BindPossessableObject(BindingPasted.GetObjectGuid(), *BindableObject.Object, BindingContext); break; } // Special case for canvas slots, they need to be added again if (BindableObject.Object->GetFName().ToString() == BindingPasted.GetName()) { // Create handle, to rebind correctly ActiveSequencer->GetHandleToObject(BindableObject.Object); // Remove the existing binding, as it is now replaced by the that was just added by getting the handle AnimationSequence->GetMovieScene()->RemovePossessable(BindingPasted.GetObjectGuid()); break; } } } } } } void FWidgetBlueprintEditor::SyncSelectedWidgetsWithSequencerSelection(TArray ObjectGuids) { if (bUpdatingSequencerSelection) { return; } TGuardValue Guard(bUpdatingExternalSelection, true); TSharedPtr& ActiveSequencer = GetSequencer(); UMovieSceneSequence* AnimationSequence = ActiveSequencer->GetFocusedMovieSceneSequence(); UObject* BindingContext = GetAnimationPlaybackContext(); TSet SequencerSelectedWidgets; for (FGuid Guid : ObjectGuids) { TArray> BoundObjects; AnimationSequence->LocateBoundObjects(Guid, UE::UniversalObjectLocator::FResolveParams(BindingContext), ActiveSequencer->GetSharedPlaybackState(), BoundObjects); if (BoundObjects.Num() == 0) { continue; } else if (Cast(BoundObjects[0])) { SequencerSelectedWidgets.Add(GetReferenceFromPreview(Cast(BoundObjects[0])->Content)); } else { UWidget* BoundWidget = Cast(BoundObjects[0]); SequencerSelectedWidgets.Add(GetReferenceFromPreview(BoundWidget)); } } if (SequencerSelectedWidgets.Num() != 0) { SelectWidgets(SequencerSelectedWidgets, false); } } void FWidgetBlueprintEditor::SyncSequencerSelectionToSelectedWidgets() { if (bUpdatingExternalSelection) { return; } TGuardValue Guard(bUpdatingSequencerSelection, true); for (TWeakPtr SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { if (Sequencer->GetSequencerSettings()->GetShowSelectedNodesOnly()) { Sequencer->RefreshTree(); } Sequencer->ExternalSelectionHasChanged(); } } } void FWidgetBlueprintEditor::SyncSequencersMovieSceneData() { for (TWeakPtr SequencerPtr : Sequencers) { if (TSharedPtr Sequencer = SequencerPtr.Pin()) { Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); } } } void FWidgetBlueprintEditor::UpdateTrackName(FGuid ObjectId) { UUserWidget* PreviewRoot = GetPreview(); UObject* BindingContext = GetAnimationPlaybackContext(); TSharedPtr& ActiveSequencer = GetSequencer(); UWidgetAnimation* WidgetAnimation = Cast(ActiveSequencer->GetFocusedMovieSceneSequence()); UMovieScene* MovieScene = WidgetAnimation->GetMovieScene(); const TArray& WidgetBindings = WidgetAnimation->GetBindings(); for (FWidgetAnimationBinding& Binding : WidgetAnimation->AnimationBindings) { if (Binding.AnimationGuid != ObjectId) { continue; } TArray> BoundObjects; WidgetAnimation->LocateBoundObjects(ObjectId, UE::UniversalObjectLocator::FResolveParams(BindingContext), ActiveSequencer->GetSharedPlaybackState(), BoundObjects); if (BoundObjects.Num() > 0) { FString NewLabel = Binding.WidgetName.ToString(); if (BoundObjects.Num() > 1) { NewLabel.Append(FString::Printf(TEXT(" (%d)"), BoundObjects.Num())); } MovieScene->SetObjectDisplayName(ObjectId, FText::FromString(NewLabel)); break; } } } #undef LOCTEXT_NAMESPACE