// Copyright Epic Games, Inc. All Rights Reserved. #include "LevelEditorContextMenu.h" #include "Misc/Attribute.h" #include "Styling/SlateColor.h" #include "Input/Reply.h" #include "Widgets/SWidget.h" #include "Misc/Paths.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SBoxPanel.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ToolMenus.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "Kismet2/ComponentEditorUtils.h" #include "Engine/HitResult.h" #include "Engine/Selection.h" #include "HAL/FileManager.h" #include "Modules/ModuleManager.h" #include "SLevelEditor.h" #include "Layout/WidgetPath.h" #include "Framework/Application/MenuStack.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Styling/AppStyle.h" #include "Editor/GroupActor.h" #include "LevelEditorViewport.h" #include "EditorModes.h" #include "LevelEditor.h" #include "Kismet2/KismetEditorUtilities.h" #include "AssetSelection.h" #include "LevelEditorActions.h" #include "SceneOutlinerPublicTypes.h" #include "SceneOutlinerModule.h" #include "ActorTreeItem.h" #include "Kismet2/DebuggerCommands.h" #include "Styling/SlateIconFinder.h" #include "EditorViewportCommands.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "LevelEditorCreateActorMenu.h" #include "Elements/Interfaces/TypedElementObjectInterface.h" #include "SourceCodeNavigation.h" #include "EditorClassUtils.h" #include "Framework/Commands/GenericCommands.h" #include "LevelViewportActions.h" #include "ActorGroupingUtils.h" #include "IMergeActorsModule.h" #include "IMergeActorsTool.h" #include "SLevelEditor.h" #include "SLevelViewport.h" #include "PropertyEditorModule.h" #define LOCTEXT_NAMESPACE "LevelViewportContextMenu" DEFINE_LOG_CATEGORY_STATIC(LogViewportMenu, Log, All); class FLevelEditorContextMenuImpl { public: static FSelectedActorInfo SelectionInfo; public: /** * Fills in menu options for the actor visibility menu * * @param MenuBuilder The menu to add items to */ static void FillActorVisibilityMenu(UToolMenu* Menu); /** * Fills in menu options for the actor level menu * * @param SharedLevel The level shared between all selected actors. If any actors are in a different level, this is NULL * @param bAllInCurrentLevel true if all selected actors are in the current level * @param MenuBuilder The menu to add items to */ static void FillActorLevelMenu(UToolMenu* Menu); /** * Fills in menu options for the transform menu * * @param MenuBuilder The menu to add items to */ static void FillTransformMenu(UToolMenu* Menu); /** * Fills in menu options for the Fill Actor menu * * @param MenuBuilder The menu to add items to */ static void FillActorMenu(UToolMenu* Menu); /** * Fills in menu options for the snap menu * * @param MenuBuilder The menu to add items to */ static void FillSnapAlignMenu(UToolMenu* Menu); /** * Fills in menu options for the pivot menu * * @param MenuBuilder The menu to add items to */ static void FillPivotMenu(UToolMenu* Menu); /** * Fills in menu options for the group menu * * @param MenuBuilder The menu to add items to */ static void FillGroupMenu( UToolMenu* Menu ); /** * Fills in menu options for the edit menu * * @param MenuBuilder The menu to add items to * @param ContextType The context for this editor menu */ static void FillEditMenu(UToolMenu* Menu) { FillEditMenu(Menu, nullptr); } static void FillEditMenu(UToolMenu* Menu, FToolMenuSection* InSection); static void FillBulkEditComponentsMenu(UToolMenu* Menu); /** * Fills in the menu options for the Asset Tools submenu * * @param Menu The menu to add items to */ static void FillAssetToolsMenu(UToolMenu* Menu); /** * Fills in menu options for the merge actors menu * * @param MenuBuilder The menu to add items to */ static void FillMergeActorsMenu(UToolMenu* Menu); /** * Adds the Source Control SubMenu to the provided Section. * * @param Section The menu to add items to */ static void AddSourceControlMenu(FToolMenuSection& Section); /** * Fills in menu options for the source control menu * * @param MenuBuilder The menu to add items to */ static void FillSourceControlMenu(UToolMenu* Menu); /** * Adds the entry for Select Immediate Children depending on the current selection * * @param Section The section to add items to * @param SelectedActors Current list of selected actors * @param Context Menu context, used to determine whether entry is needed */ static void AddSelectChildrenEntry(FToolMenuSection& Section, const TArray& SelectedActors, ULevelEditorContextMenuContext* Context); }; FSelectedActorInfo FLevelEditorContextMenuImpl::SelectionInfo; struct FLevelScriptEventMenuHelper { /** * Fills in menu options for events that can be associated with that actors's blueprint in the level script blueprint * * @param MenuBuilder The menu to add items to */ static void FillLevelBlueprintEventsMenu(FToolMenuSection& Section, const TArray& SelectedActors); }; void FLevelEditorContextMenu::RegisterComponentContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.ComponentContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.ComponentContextMenu"); Menu->AddDynamicSection("ComponentControlDynamic", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { ULevelEditorContextMenuContext* LevelEditorContext = InMenu->FindContext(); if (!LevelEditorContext) { return; } TArray SelectedComponents; for (FSelectedEditableComponentIterator It(GEditor->GetSelectedEditableComponentIterator()); It; ++It) { SelectedComponents.Add(CastChecked(*It)); } { FToolMenuSection& Section = InMenu->AddSection("ComponentControl", LOCTEXT("ComponentControlHeading", "Component")); AActor* OwnerActor = GEditor->GetSelectedActors()->GetTop(); if(OwnerActor) { Section.AddMenuEntry( FLevelEditorCommands::Get().SelectComponentOwnerActor, FText::Format(LOCTEXT("SelectComponentOwner", "Select Owner [{0}]"), FText::FromString(OwnerActor->GetHumanReadableName())), TAttribute(), FSlateIconFinder::FindIconForClass(OwnerActor->GetClass()) ); } Section.AddMenuEntry(FEditorViewportCommands::Get().FocusViewportToSelection); const FVector* ClickLocation = &GEditor->ClickLocation; FUIAction GoHereAction; GoHereAction.ExecuteAction = FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::GoHere_Clicked, ClickLocation); Section.AddMenuEntry(FLevelEditorCommands::Get().GoHere); Section.AddMenuEntry(FLevelEditorCommands::Get().SnapCameraToObject); Section.AddMenuEntry(FLevelEditorCommands::Get().SnapObjectToCamera); AddPlayFromHereSubMenu(Section); Section.AddMenuEntry(FLevelEditorCommands::Get().CopyActorFilePathtoClipboard); FLevelEditorContextMenuImpl::AddSourceControlMenu(Section); } FComponentEditorUtils::FillComponentContextMenuOptions(InMenu, SelectedComponents); })); } void FLevelEditorContextMenu::AddPlayFromHereSubMenu(FToolMenuSection& Section) { if(FLevelEditorActionCallbacks::PlayFromHere_IsVisible()) { Section.AddSubMenu("PlayFromHere", LOCTEXT("PlayFromHere", "Play From Here"), FText(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { FToolMenuSection& NewSection = InMenu->AddSection("Section"); FUIAction PlayFromHere(FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::PlayFromHere_Clicked, false)); FUIAction PlayFromHereFloating(FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::PlayFromHere_Clicked, true)); NewSection.AddMenuEntry("PlayFromHereActiveViewport", LOCTEXT("PlayFromHereActiveViewport", "Selected Viewport"), LOCTEXT("PlayFromHereActiveViewportTooltip", "Play from this actor in the active level editor viewport"), FSlateIcon("EditorSTyle", "PlayWorld.PlayInViewport"), PlayFromHere); NewSection.AddMenuEntry("PlayFromHereFloatingWindow", LOCTEXT("PlayFromHereFloatingWindow", "New Editor Window (PIE)"), LOCTEXT("PlayFromHereFloatingWindowTooltip", "Play from this actor in a new editor window"), FSlateIcon("EditorSTyle", "PlayWorld.PlayInEditorFloating"), PlayFromHereFloating); })); } } void FLevelEditorContextMenu::RegisterActorContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.ActorContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.ActorContextMenu"); Menu->AddDynamicSection("ActorContextMenuDynamic", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { ULevelEditorContextMenuContext* LevelEditorContext = InMenu->FindContext(); if (!LevelEditorContext || !LevelEditorContext->LevelEditor.IsValid()) { return; } TWeakPtr LevelEditor = LevelEditorContext->LevelEditor; // Generate information about our selection TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(SelectedActors); FSelectedActorInfo& SelectionInfo = FLevelEditorContextMenuImpl::SelectionInfo; SelectionInfo = AssetSelectionUtils::BuildSelectedActorInfo(SelectedActors); int32 NumSelectedActors = SelectedActors.Num(); { // General actions that apply to most actors and their underlying assets // In most cases, you DO NOT want to extend this section; look at ActorUETools or ActorTypeTools below FToolMenuSection& Section = InMenu->AddSection("ActorGeneral", LOCTEXT("AssetOptionsHeading", "Asset Options")); // Check if current selection has any referenced assets that can be edited TArray< UObject* > ReferencedAssets; GEditor->GetReferencedAssetsForEditorSelection(ReferencedAssets); TArray< FSoftObjectPath> SoftReferencedAssets; GEditor->GetSoftReferencedAssetsForEditorSelection(SoftReferencedAssets); // Asset type icon is used in multiple places below FSlateIcon AssetIcon = ReferencedAssets.Num() == 1 ? FSlateIconFinder::FindIconForClass(ReferencedAssets[0]->GetClass()) : FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Default"); // Edit and Find entries (a) always appear in main menu, and (b) appear in right-click menu if referenced asset is available if (LevelEditorContext->ContextType == ELevelEditorMenuContext::MainMenu || ReferencedAssets.Num() > 0 || SoftReferencedAssets.Num() > 0 || SelectionInfo.bHaveBrowseOverride) { Section.AddMenuEntry(FGlobalEditorCommonCommands::Get().FindInContentBrowser); if (ReferencedAssets.Num() == 0) { Section.AddMenuEntry( FLevelEditorCommands::Get().EditAsset, TAttribute(), // use command's label TAttribute(), // use command's tooltip AssetIcon ); } else if (ReferencedAssets.Num() == 1) { UObject* Asset = ReferencedAssets[0]; const FString AssetLabel = Cast(Asset) ? Cast(Asset)->GetActorNameOrLabel() : Asset->GetName(); Section.AddMenuEntry( FLevelEditorCommands::Get().EditAsset, FText::Format(LOCTEXT("EditAssociatedAsset", "Edit {0}"), FText::FromString(AssetLabel)), TAttribute(), // use command's tooltip AssetIcon ); } else if (ReferencedAssets.Num() > 1) { Section.AddMenuEntry( FLevelEditorCommands::Get().EditAssetNoConfirmMultiple, LOCTEXT("EditAssociatedAssetsMultiple", "Edit Multiple Assets"), TAttribute(), // use command's tooltip AssetIcon ); } } if (LevelEditorContext->ContextType == ELevelEditorMenuContext::MainMenu) { Section.AddSubMenu( "AssetToolsSubMenu", LOCTEXT("AssetToolsSubMenu", "Asset Tools"), LOCTEXT("AssetToolsSubMenuToolTip", "Tools for the asset associated with the selected actor"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillAssetToolsMenu), /*bInOpenSubMenuOnClick*/ false, FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Adjust")); // This is an invisible entry used as an extension point for "Convert SomeActor To SomeType" entries FUIAction Action; Action.IsActionVisibleDelegate = FIsActionButtonVisible::CreateLambda([]() { return false; }); Section.AddMenuEntry("ActorConvert", TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Convert"), Action, EUserInterfaceActionType::None); } Section.AddMenuEntry( FLevelEditorCommands::Get().CopyActorFilePathtoClipboard, TAttribute(), // use command's label TAttribute(), // use command's tooltip FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Copy") ); Section.AddMenuEntry( FLevelEditorCommands::Get().OpenActorInReferenceViewer, TAttribute(), // use command's label TAttribute(), // use command's tooltip FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.ReferenceViewer") ); Section.AddMenuEntry( FLevelEditorCommands::Get().SaveActor, TAttribute(), // use command's label TAttribute(), // use command's tooltip FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Save") ); LevelEditorCreateActorMenu::FillAddReplaceContextMenuSections(Section, LevelEditorContext); } { if(FModuleManager::LoadModuleChecked( "PropertyEditor" ).GetCanUsePropertyMatrix() && NumSelectedActors >= 2) { // Options that relate to bulk editing assets // In most cases, you DO NOT want to extend this section; look at ActorUETools or ActorTypeTools below FToolMenuSection& Section = InMenu->AddSection("ActorBulkEdit", LOCTEXT("ActorBulkEditHeading", "Bulk Editing")); Section.AddMenuEntry( FLevelEditorCommands::Get().OpenSelectionInPropertyMatrix, TAttribute(), // use command's label TAttribute(), // use command's tooltip FSlateIcon(FAppStyle::GetAppStyleSetName(), "DetailsView.EditRawProperties") ); Section.AddSubMenu( "BulkEditComponentsSubmenu", LOCTEXT("BulkEditComponentsSubmenuName", "Edit Components in the Property Matrix"), LOCTEXT("BulkEditComponentsSubmenuTooltip", "Bulk Edit any editable components that are common between the selected actors in the Property Matrix"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillBulkEditComponentsMenu), FUIAction(), EUserInterfaceActionType::Button, false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "DetailsView.EditRawProperties") ); } } { // Options that affect the current viewport. // In most cases, you DO NOT want to extend this section; look at ActorUETools or ActorTypeTools below FToolMenuSection& Section = InMenu->AddSection("ActorViewOptions", LOCTEXT("ViewOptionsHeading", "View Options")); const FVector* ClickLocation = &GEditor->ClickLocation; Section.AddMenuEntry( FEditorViewportCommands::Get().FocusViewportToSelection, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.FrameActor") ); // This keys off the mouse position so can only appear in the viewport if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport) { FUIAction GoHereAction; GoHereAction.ExecuteAction = FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::GoHere_Clicked, ClickLocation); Section.AddMenuEntry( FLevelEditorCommands::Get().GoHere, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.GoHere") ); } Section.AddMenuEntry( FLevelEditorCommands::Get().SnapCameraToObject, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.SnapViewToObject") ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapObjectToCamera, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.SnapObjectToView") ); if (SelectedActors.Num() == 1) { const FLevelViewportCommands& Actions = FLevelViewportCommands::Get(); auto Viewport = LevelEditor.Pin()->GetActiveViewportInterface(); if (Viewport.IsValid()) { auto& ViewportClient = Viewport->GetLevelViewportClient(); if (ViewportClient.IsPerspective() && !ViewportClient.IsLockedToCinematic()) { if (Viewport->IsSelectedActorLocked()) { Section.AddMenuEntry( Actions.EjectActorPilot, FText::Format(LOCTEXT("PilotActor_Stop", "Stop piloting '{0}'"), FText::FromString(SelectedActors[0]->GetActorLabel())) ); } else { Section.AddMenuEntry( Actions.PilotSelectedActor, FText::Format(LOCTEXT("PilotActor", "Pilot '{0}'"), FText::FromString(SelectedActors[0]->GetActorLabel())) ); } } } } } { // Options for editing, transforming, and manipulating this actor // In most cases, you DO NOT want to extend this section; look at ActorUETools or ActorTypeTools below FToolMenuSection& Section = InMenu->AddSection("ActorOptions", LOCTEXT("ActorOptionsHeading", "Actor Options")); FLevelEditorContextMenuImpl::AddSelectChildrenEntry(Section, SelectedActors, LevelEditorContext); Section.AddSubMenu( "EditSubMenu", LOCTEXT("EditSubMenu", "Edit"), FText::GetEmpty(), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillEditMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Edit")); Section.AddSubMenu( "VisibilitySubMenu", LOCTEXT("VisibilitySubMenu", "Visibility"), LOCTEXT("VisibilitySubMenu_ToolTip", "Selected actor visibility options"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillActorVisibilityMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Visibility")); Section.AddSubMenu( "TransformSubMenu", LOCTEXT("TransformSubMenu", "Transform"), LOCTEXT("TransformSubMenu_ToolTip", "Actor transform utils"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillTransformMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Transform")); Section.AddSubMenu( "SnapAlignSubMenu", LOCTEXT("SnapAlignSubMenu", "Snapping"), LOCTEXT("SnapAlignSubMenu_ToolTip", "Actor snap/align utils"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillSnapAlignMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Snap")); Section.AddSubMenu( "PivotSubMenu", LOCTEXT("PivotSubMenu", "Pivot"), LOCTEXT("PivotSubMenu_ToolTip", "Actor pivoting utils"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillPivotMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.SetShowPivot")); // Build the menu for grouping actors - this is either the Group item or Groups submenu BuildGroupMenu(Section, SelectionInfo); // Attach and detach Section.AddSubMenu( "ActorAttachToSubMenu", LOCTEXT("ActorAttachToSubMenu", "Attach To"), LOCTEXT("ActorAttachToSubMenu_ToolTip", "Attach Actor as child"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillActorMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.Attach")); Section.AddMenuEntry( FLevelEditorCommands::Get().DetachFromParent, TAttribute(), // Use command title TAttribute(), // Use command tooltip FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.Detach")); // Add/jump to event should go in main menu only if (LevelEditorContext->ContextType == ELevelEditorMenuContext::MainMenu) { FLevelScriptEventMenuHelper::FillLevelBlueprintEventsMenu(Section, SelectedActors); } } // General-purpose extension point for tools that apply to many types of actors // These should appear in the main menu context only, by design // For type-specific actions, consider adding them to "ActorTypeTools" below if (LevelEditorContext->ContextType == ELevelEditorMenuContext::MainMenu) { FToolMenuSection& Section = InMenu->AddSection("ActorUETools", LOCTEXT("UEToolsHeading", "UE Tools")); Section.AddSubMenu( "MergeActorsSubMenu", FText::Format(LOCTEXT("MergeActorsSubMenu", "Merge Actors ({0})"), FText::AsNumber(SelectedActors.Num())), LOCTEXT("MergeActorsSubMenu_ToolTip", "Merge actors utils"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillMergeActorsMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Merge")); } // General-purpose extension point for tools that only appear for certain types of actors // Generally, you should only use this for type-specific actions since this section appears in all contexts // For tools that apply to many types of actors, add them to "ActorUETools" above FToolMenuSection& ActorTypeToolsSection = InMenu->AddSection("ActorTypeTools"); ActorTypeToolsSection.AddSubMenu( "LevelSubMenu", LOCTEXT("LevelSubMenu", "Level"), LOCTEXT("LevelSubMenu_ToolTip", "Options for interacting with this actor's level"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillActorLevelMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Level")); // DEPRECATED SECTION NAMES -- DO NOT ADD NEW EXTENSIONS TO THESE POINTS -- THEY MAY BE REMOVED IN THE FUTURE // These sections are included here because they used to exist and may have been used as extension points // For new context menu entries, use "ActorUETools" or "ActorTypeTools" above InMenu->AddSection("ActorAsset"); InMenu->AddSection("ActorControl"); InMenu->AddSection("ActorSelectVisibilityLevels"); InMenu->AddSection("ActorType"); InMenu->AddSection("LevelViewportAttach"); { // Play from here, keep simulation changes FToolMenuSection& Section = InMenu->AddSection("ActorPreview", LOCTEXT("PreviewHeading", "Preview")); if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport) { AddPlayFromHereSubMenu(Section); // Only extend if above PlayFromHere option isn't available if (!FLevelEditorActionCallbacks::PlayFromHere_IsVisible()) { // Note: not using a command for play from here since it requires a mouse click FUIAction PlayFromHereAction( FExecuteAction::CreateStatic(&FPlayWorldCommandCallbacks::StartPlayFromHere)); const FText PlayFromHereLabel = GEditor->OnlyLoadEditorVisibleLevelsInPIE() ? LOCTEXT("PlayFromHereVisible", "Play From Here (visible levels)") : LOCTEXT("PlayFromHere", "Play From Here"); Section.AddMenuEntry(NAME_None, PlayFromHereLabel, LOCTEXT("PlayFromHere_ToolTip", "Starts a game preview from the clicked location"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Play"), PlayFromHereAction); } } if (GEditor->PlayWorld != NULL) { if (SelectionInfo.NumSelected > 0) { Section.AddMenuEntry(FLevelEditorCommands::Get().KeepSimulationChanges); } } } })); } void FLevelEditorContextMenu::RegisterElementContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.ElementContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.ElementContextMenu"); Menu->AddDynamicSection("ElementContextMenuDynamic", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { { FToolMenuSection& Section = InMenu->AddSection("ElementEditActions", LOCTEXT("ElementEditActions", "Edit")); FLevelEditorContextMenuImpl::FillEditMenu(InMenu, &Section); } { FToolMenuSection& Section = InMenu->AddSection("ElementLevelActions", LOCTEXT("ElementLevelActions", "Level")); Section.AddSubMenu( "TransformSubMenu", LOCTEXT("TransformSubMenu", "Transform"), LOCTEXT("TransformSubMenu_ToolTip_Element", "Element transform utils"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillTransformMenu)); } })); } void FLevelEditorContextMenu::RegisterSceneOutlinerContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.SceneOutlinerContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.SceneOutlinerContextMenu"); Menu->AddDynamicSection("SelectVisibilityLevels", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { if (ULevelEditorContextMenuContext* LevelEditorContext = InMenu->FindContext()) { TWeakPtr SceneOutlinerPtr = LevelEditorContext->LevelEditor.Pin()->GetMostRecentlyUsedSceneOutliner(); if (SceneOutlinerPtr.IsValid()) { FToolMenuSection& Section = InMenu->AddSection("SelectVisibilityLevels"); Section.AddSubMenu( "EditSubMenu", LOCTEXT("EditSubMenu", "Edit"), FText::GetEmpty(), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillEditMenu) ); } } })); } void FLevelEditorContextMenu::RegisterMenuBarEmptyContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.MenuBarEmptyContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.MenuBarEmptyContextMenu"); FToolMenuSection& Section = Menu->AddSection("MenuBarEmpty"); const FText EmptySelectionInformationalMessage = LOCTEXT("EmptySelectionInformationalMessage", "Select an object to view actions."); Section.AddEntry(FToolMenuEntry::InitWidget( NAME_None, SNew(SBox) .Padding(FMargin(80.f, 8.f)) [ SNew(STextBlock) .Text(EmptySelectionInformationalMessage) .TextStyle(FAppStyle::Get(), "HintText") ], FText::GetEmpty(), /*bNoIndent*/ true, /*bSearchable*/ false)); } void FLevelEditorContextMenu::RegisterEmptySelectionContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (ToolMenus->IsMenuRegistered("LevelEditor.EmptySelectionContextMenu")) { return; } UToolMenu* Menu = ToolMenus->RegisterMenu("LevelEditor.EmptySelectionContextMenu"); Menu->AddDynamicSection("PlaceActors", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { if (ULevelEditorContextMenuContext* LevelEditorContext = InMenu->FindContext()) { { FToolMenuSection& Section = InMenu->AddSection("SelectActorGeneral", LOCTEXT("SelectAnyHeading", "General")); Section.AddMenuEntry(FGenericCommands::Get().SelectAll, TAttribute(), LOCTEXT("SelectAll_ToolTip", "Selects all actors")); } if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport) { FToolMenuSection& Section = InMenu->AddSection("ActorType"); LevelEditorCreateActorMenu::FillAddReplaceContextMenuSections(Section, LevelEditorContext); } } })); } FName FLevelEditorContextMenu::GetContextMenuName(ELevelEditorMenuContext ContextType, const UTypedElementSelectionSet* InSelectionSet) { if (InSelectionSet) { if (InSelectionSet->HasSelectedObjects()) { return "LevelEditor.ComponentContextMenu"; } if (InSelectionSet->HasSelectedObjects()) { return "LevelEditor.ActorContextMenu"; } if (InSelectionSet->GetNumSelectedElements()) { return "LevelEditor.ElementContextMenu"; } } if (ContextType == ELevelEditorMenuContext::SceneOutliner) { return "LevelEditor.SceneOutlinerContextMenu"; } if (ContextType == ELevelEditorMenuContext::MainMenu) { return "LevelEditor.MenuBarEmptyContextMenu"; } return "LevelEditor.EmptySelectionContextMenu"; } FText FLevelEditorContextMenu::GetContextMenuTitle(ELevelEditorMenuContext ContextType, const UTypedElementSelectionSet* InSelectionSet) { if (InSelectionSet) { if (InSelectionSet->HasSelectedObjects()) { return LOCTEXT("ComponentContextMenuTitle", "Component"); } if (InSelectionSet->HasSelectedObjects()) { return LOCTEXT("ActorContextMenuTitle", "Actor"); } if (InSelectionSet->GetNumSelectedElements()) { return LOCTEXT("ElementContextMenuTitle", "Element"); } } // Show "Actor" label by default as title when nothing selected since most selections (currently) will be actors anyways return LOCTEXT("ActorContextMenuTitle", "Actor"); } FText FLevelEditorContextMenu::GetContextMenuToolTip(ELevelEditorMenuContext ContextType, const UTypedElementSelectionSet* InSelectionSet) { if (InSelectionSet) { const int32 ComponentCount = InSelectionSet->CountSelectedObjects(); if (ComponentCount == 1) { UActorComponent* Component = InSelectionSet->GetTopSelectedObject(); check(Component); return FText::Format(LOCTEXT("ComponentContextMenuToolTipSingle", "Show actions for component \"{0}\""), FText::FromString(Component->GetName())); } else if (ComponentCount > 1) { return FText::Format(LOCTEXT("ComponentContextMenuToolTipOther", "Show actions for {0} components"), FText::AsNumber(ComponentCount)); } const int32 ActorCount = InSelectionSet->CountSelectedObjects(); if (ActorCount == 1) { AActor* Actor = InSelectionSet->GetTopSelectedObject(); check(Actor); return FText::Format(LOCTEXT("ActorContextMenuToolTipSingle", "Show actions for actor \"{0}\""), FText::FromString(Actor->GetActorLabel())); } else if (ActorCount > 1) { return FText::Format(LOCTEXT("ActorContextMenuToolTipOther", "Show actions for {0} actors"), FText::AsNumber(ActorCount)); } const int32 ElementCount = InSelectionSet->GetNumSelectedElements(); if (ElementCount) { return FText::Format(LOCTEXT("ElementContextMenuToolTip", "Show actions for {0} {0}|plural(one=element,other=elements)"), FText::AsNumber(ElementCount)); } } return LOCTEXT("NothingSelectedToolTip", "Select an object to show actions"); } FName FLevelEditorContextMenu::InitMenuContext(FToolMenuContext& Context, TWeakPtr LevelEditor, ELevelEditorMenuContext ContextType, const FTypedElementHandle& HitProxyElement) { RegisterComponentContextMenu(); RegisterActorContextMenu(); RegisterElementContextMenu(); RegisterSceneOutlinerContextMenu(); RegisterMenuBarEmptyContextMenu(); RegisterEmptySelectionContextMenu(); TSharedPtr LevelEditorPtr = LevelEditor.Pin(); check(LevelEditorPtr); TSharedPtr LevelEditorActionsList = LevelEditorPtr->GetLevelEditorActions(); Context.AppendCommandList(LevelEditorActionsList); ULevelEditorContextMenuContext* ContextObject = NewObject(); ContextObject->LevelEditor = LevelEditor; ContextObject->ContextType = ContextType; ContextObject->CurrentSelection = LevelEditorPtr->GetElementSelectionSet(); ContextObject->HitProxyElement = HitProxyElement; { TTypedElement HitProxyObjectElement = ContextObject->CurrentSelection->GetElementList()->GetElement(ContextObject->HitProxyElement); ContextObject->HitProxyActor = HitProxyObjectElement ? Cast(HitProxyObjectElement.GetObject()) : nullptr; } for (FSelectedEditableComponentIterator It(GEditor->GetSelectedEditableComponentIterator()); It; ++It) { ContextObject->SelectedComponents.Add(CastChecked(*It)); } // obtain the world location of the cursor if (GCurrentLevelEditingViewportClient) { FHitResult HitResult; FViewportCursorLocation CursorLocation = GCurrentLevelEditingViewportClient->GetCursorWorldLocationFromMousePos(); FCollisionQueryParams LineParams(SCENE_QUERY_STAT(FocusOnPoint), true); if (GCurrentLevelEditingViewportClient->GetWorld()->LineTraceSingleByObjectType( HitResult, CursorLocation.GetOrigin(), CursorLocation.GetOrigin() + CursorLocation.GetDirection() * HALF_WORLD_MAX, FCollisionObjectQueryParams(FCollisionObjectQueryParams::InitType::AllObjects), LineParams)) { ContextObject->CursorWorldLocation = HitResult.ImpactPoint; } } Context.AddObject(ContextObject, [](UObject* InContext) { ULevelEditorContextMenuContext* CastContext = CastChecked(InContext); CastContext->CurrentSelection = nullptr; CastContext->HitProxyElement.Release(); }); if (GEditor->GetSelectedComponentCount() == 0 && GEditor->GetSelectedActorCount() > 0) { TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(SelectedActors); // Get all menu extenders for this context menu from the level editor module FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TArray MenuExtenderDelegates = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(LevelEditorActionsList.ToSharedRef(), SelectedActors)); } } if (Extenders.Num() > 0) { Context.AddExtender(FExtender::Combine(Extenders)); } } return GetContextMenuName(ContextType, ContextObject->CurrentSelection); } UToolMenu* FLevelEditorContextMenu::GenerateMenu(TWeakPtr LevelEditor, ELevelEditorMenuContext ContextType, TSharedPtr Extender, const FTypedElementHandle& HitProxyElement) { FToolMenuContext Context; if (Extender.IsValid()) { Context.AddExtender(Extender); } FName ContextMenuName = InitMenuContext(Context, LevelEditor, ContextType, HitProxyElement); return UToolMenus::Get()->GenerateMenu(ContextMenuName, Context); } // NOTE: We intentionally receive a WEAK pointer here because we want to be callable by a delegate whose // payload contains a weak reference to a level editor instance TSharedRef< SWidget > FLevelEditorContextMenu::BuildMenuWidget(TWeakPtr< ILevelEditor > LevelEditor, ELevelEditorMenuContext ContextType, TSharedPtr Extender, const FTypedElementHandle& HitProxyElement) { UToolMenu* Menu = GenerateMenu(LevelEditor, ContextType, Extender, HitProxyElement); return UToolMenus::Get()->GenerateWidget(Menu); } namespace EViewOptionType { enum Type { Top, Bottom, Left, Right, Front, Back, Perspective }; } TSharedPtr MakeViewOptionWidget(const TSharedRef< SLevelEditor >& LevelEditor, bool bShouldCloseWindowAfterMenuSelection, EViewOptionType::Type ViewOptionType) { FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, LevelEditor->GetActiveViewport()->GetCommandList()); if (ViewOptionType == EViewOptionType::Top) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Top); } else if (ViewOptionType == EViewOptionType::Bottom) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Bottom); } else if (ViewOptionType == EViewOptionType::Left) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Left); } else if (ViewOptionType == EViewOptionType::Right) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Right); } else if (ViewOptionType == EViewOptionType::Front) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Front); } else if (ViewOptionType == EViewOptionType::Back) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Back); } else if (ViewOptionType == EViewOptionType::Perspective) { MenuBuilder.AddMenuEntry(FEditorViewportCommands::Get().Perspective); } else { return nullptr; } return MenuBuilder.MakeWidget(); } void BuildViewOptionMenu(const TSharedRef< SLevelEditor >& LevelEditor, TSharedPtr InWidget, const FVector2D WidgetPosition) { if (InWidget.IsValid()) { FSlateApplication::Get().PushMenu( LevelEditor->GetActiveViewport().ToSharedRef(), FWidgetPath(), InWidget.ToSharedRef(), WidgetPosition, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)); } } void FLevelEditorContextMenu::SummonViewOptionMenu( const TSharedRef< SLevelEditor >& LevelEditor, const ELevelViewportType ViewOption ) { const FVector2D MouseCursorLocation = FSlateApplication::Get().GetCursorPos(); bool bShouldCloseWindowAfterMenuSelection = true; EViewOptionType::Type ViewOptionType = EViewOptionType::Perspective; switch (ViewOption) { case LVT_OrthoBottom: ViewOptionType = EViewOptionType::Bottom; break; case LVT_OrthoBack: ViewOptionType = EViewOptionType::Back; break; case LVT_OrthoRight: ViewOptionType = EViewOptionType::Right; break; case LVT_OrthoTop: ViewOptionType = EViewOptionType::Top; break; case LVT_OrthoFront: ViewOptionType = EViewOptionType::Front; break; case LVT_OrthoLeft: ViewOptionType = EViewOptionType::Left; break; case LVT_Perspective: ViewOptionType = EViewOptionType::Perspective; break; }; // Build up menu BuildViewOptionMenu(LevelEditor, MakeViewOptionWidget(LevelEditor, bShouldCloseWindowAfterMenuSelection, ViewOptionType), MouseCursorLocation); } void FLevelEditorContextMenu::SummonMenu(const TSharedRef< SLevelEditor >& LevelEditor, ELevelEditorMenuContext ContextType, const FTypedElementHandle& HitProxyElement) { // Create the context menu! TSharedPtr MenuWidget = BuildMenuWidget( LevelEditor, ContextType, nullptr, HitProxyElement ); if ( MenuWidget.IsValid() ) { // @todo: Should actually use the location from a click event instead! const FVector2D MouseCursorLocation = FSlateApplication::Get().GetCursorPos(); FSlateApplication::Get().PushMenu( LevelEditor->GetActiveViewport().ToSharedRef(), FWidgetPath(), MenuWidget.ToSharedRef(), MouseCursorLocation, FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu ) ); } } FSlateColor InvertOnHover( const TWeakPtr< SWidget > WidgetPtr ) { TSharedPtr< SWidget > Widget = WidgetPtr.Pin(); if ( Widget.IsValid() && Widget->IsHovered() ) { static const FName InvertedForegroundName("InvertedForeground"); return FAppStyle::GetSlateColor(InvertedForegroundName); } return FSlateColor::UseForeground(); } void FLevelEditorContextMenu::BuildGroupMenu(FToolMenuSection& Section, const FSelectedActorInfo& SelectedActorInfo) { if( UActorGroupingUtils::IsGroupingActive() ) { // Whether or not we added a grouping sub-menu bool bNeedGroupSubMenu = SelectedActorInfo.bHaveSelectedLockedGroup || SelectedActorInfo.bHaveSelectedUnlockedGroup; if( bNeedGroupSubMenu ) { Section.AddSubMenu( "GroupMenu", LOCTEXT("GroupMenu", "Groups"), LOCTEXT("GroupMenu_ToolTip", "Opens the actor grouping menu"), FNewToolMenuDelegate::CreateStatic( &FLevelEditorContextMenuImpl::FillGroupMenu), false, // default FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.GroupActors")); } else { Section.AddMenuEntry( FLevelEditorCommands::Get().RegroupActors, FLevelEditorCommands::Get().GroupActors->GetLabel(), FLevelEditorCommands::Get().GroupActors->GetDescription(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.GroupActors")); } } } void FLevelEditorContextMenuImpl::FillActorVisibilityMenu(UToolMenu* Menu) { { FToolMenuSection& Section = Menu->AddSection("VisibilitySelected"); // Show 'Show Selected' only if the selection has any hidden actors if ( SelectionInfo.bHaveHidden ) { Section.AddMenuEntry(FLevelEditorCommands::Get().ShowSelected); } Section.AddMenuEntry(FLevelEditorCommands::Get().HideSelected); } { FToolMenuSection& Section = Menu->AddSection("VisibilityAll"); Section.AddMenuEntry(FLevelEditorCommands::Get().ShowSelectedOnly); Section.AddMenuEntry(FLevelEditorCommands::Get().ShowAll); } { FToolMenuSection& Section = Menu->AddSection("VisibilityStartup"); Section.AddMenuEntry(FLevelEditorCommands::Get().ShowAllStartup); Section.AddMenuEntry(FLevelEditorCommands::Get().ShowSelectedStartup); Section.AddMenuEntry(FLevelEditorCommands::Get().HideSelectedStartup); } } void FLevelEditorContextMenuImpl::FillActorLevelMenu(UToolMenu* Menu) { ULevelEditorContextMenuContext* LevelEditorContext = Menu->FindContext(); if (!LevelEditorContext || !LevelEditorContext->LevelEditor.IsValid()) { return; } if (LevelEditorContext->ContextType == ELevelEditorMenuContext::MainMenu) { { FToolMenuSection& Section = Menu->AddSection("ActorLevel", LOCTEXT("ActorLevel", "Actor Level")); if (SelectionInfo.SharedLevel && !SelectionInfo.SharedLevel->IsInstancedLevel() && SelectionInfo.SharedWorld && SelectionInfo.SharedWorld->GetCurrentLevel() != SelectionInfo.SharedLevel) { // All actors are in the same level and that level is not the current level // so add a menu entry to make the shared level current FText MakeCurrentLevelText = FText::Format(LOCTEXT("MakeCurrentLevelMenu", "Make Current Level: {0}"), FText::FromString(SelectionInfo.SharedLevel->GetOutermost()->GetName())); Section.AddMenuEntry(FLevelEditorCommands::Get().MakeActorLevelCurrent, MakeCurrentLevelText); } if (!SelectionInfo.bAllSelectedActorsBelongToCurrentLevel) { // Only show this menu entry if any actors are not in the current level Section.AddMenuEntry(FLevelEditorCommands::Get().MoveSelectedToCurrentLevel); } Section.AddMenuEntry(FLevelEditorCommands::Get().FindActorLevelInContentBrowser); } { FToolMenuSection& Section = Menu->AddSection("LevelBlueprint", LOCTEXT("LevelBlueprint", "Level Blueprint")); Section.AddMenuEntry(FLevelEditorCommands::Get().FindActorInLevelScript); } { FToolMenuSection& Section = Menu->AddSection("LevelBrowser", LOCTEXT("LevelBrowser", "Level Browser")); Section.AddMenuEntry(FLevelEditorCommands::Get().FindLevelsInLevelBrowser); Section.AddMenuEntry(FLevelEditorCommands::Get().AddLevelsToSelection); Section.AddMenuEntry(FLevelEditorCommands::Get().RemoveLevelsFromSelection); } } } void FLevelEditorContextMenuImpl::FillTransformMenu(UToolMenu* Menu) { if (ULevelEditorContextMenuContext* LevelEditorContext = Menu->FindContext()) { if (!LevelEditorContext->CurrentSelection || LevelEditorContext->CurrentSelection->GetNumSelectedElements() == 0) { return; } { FToolMenuSection& Section = Menu->AddSection("DeltaTransformToActors"); Section.AddMenuEntry(FLevelEditorCommands::Get().DeltaTransformToActors); } if (LevelEditorContext->CurrentSelection->HasSelectedObjects()) { FToolMenuSection& Section = Menu->AddSection("MirrorLock"); Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorX); Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorY); Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorZ); Section.AddMenuEntry(FLevelEditorCommands::Get().LockActorMovement); } } } // A box that will expand to match its content's desired size, but will never shrink. // A helper class for the AttachToActor menu so that it does not constantly resize all the time, // but also ensure that you're never in a state where you can't read the full actor name. class SOnlyExpandsBox : public SBox { protected: virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override { UE::Slate::FDeprecateVector2DResult RequestedDesiredSize = UE::Slate::CastToVector2f(SBox::ComputeDesiredSize(LayoutScaleMultiplier)); if (RequestedDesiredSize.X > MaxPreviousWidth) { MaxPreviousWidth = RequestedDesiredSize.X; return RequestedDesiredSize; } else { return FVector2D(MaxPreviousWidth, RequestedDesiredSize.Y); } } private: mutable float MaxPreviousWidth = 400.0f; }; void FLevelEditorContextMenuImpl::FillActorMenu(UToolMenu* Menu) { // The contents of this menu are added as a custom widget with its own search field so we // disable searching in this parent menu to avoid displaying two search fields to the user. Menu->bSearchable = false; struct Local { static FReply OnInteractiveActorPickerClicked() { FSlateApplication::Get().DismissAllMenus(); FLevelEditorActionCallbacks::AttachActorIteractive(); return FReply::Handled(); } }; FSceneOutlinerInitializationOptions InitOptions; { InitOptions.bShowHeaderRow = false; InitOptions.bFocusSearchBoxWhenOpened = true; // Only display Actors that we can attach too InitOptions.Filters->AddFilterPredicate(FActorTreeItem::FFilterPredicate::CreateStatic(&FLevelEditorActionCallbacks::IsAttachableActor)); } FToolMenuSection& Section = Menu->AddSection("Actor"); if(SelectionInfo.bHaveAttachedActor) { Section.AddMenuEntry(FLevelEditorCommands::Get().DetachFromParent, LOCTEXT("None", "None")); } // Actor selector to allow the user to choose a parent actor FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked( "SceneOutliner" ); TSharedRef< SWidget > MenuWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) +SVerticalBox::Slot() .MaxHeight(400.0f) [ SNew(SOnlyExpandsBox) [ SceneOutlinerModule.CreateActorPicker( InitOptions, FOnActorPicked::CreateStatic( &FLevelEditorActionCallbacks::AttachToActor ) ) ] ] ] +SHorizontalBox::Slot() .VAlign(VAlign_Top) .AutoWidth() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(4.0f, 0.0f, 0.0f, 0.0f) [ SNew(SButton) .ToolTipText( LOCTEXT( "PickButtonLabel", "Pick a parent actor to attach to") ) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .OnClicked(FOnClicked::CreateStatic(&Local::OnInteractiveActorPickerClicked)) .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.EyeDropper")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ]; Section.AddEntry(FToolMenuEntry::InitWidget("PickParentActor", MenuWidget, FText::GetEmpty(), false)); } void FLevelEditorContextMenuImpl::FillSnapAlignMenu(UToolMenu* Menu) { FToolMenuSection& Section = Menu->AddSection("SnapAlign"); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapOriginToGrid ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapOriginToGridPerActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignOriginToGrid ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapTo2DLayer ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapToFloor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignToFloor ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapPivotToFloor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignPivotToFloor ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapBottomCenterBoundsToFloor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignBottomCenterBoundsToFloor ); /* Section.AddSeparator(); AActor* Actor = GEditor->GetSelectedActors()->GetBottom(); if( Actor && FLevelEditorActionCallbacks::ActorsSelected_CanExecute()) { const FString Label = Actor->GetActorLabel(); // Update the options to show the actors label TSharedPtr< FUICommandInfo > SnapOriginToActor = FLevelEditorCommands::Get().SnapOriginToActor; TSharedPtr< FUICommandInfo > AlignOriginToActor = FLevelEditorCommands::Get().AlignOriginToActor; TSharedPtr< FUICommandInfo > SnapToActor = FLevelEditorCommands::Get().SnapToActor; TSharedPtr< FUICommandInfo > AlignToActor = FLevelEditorCommands::Get().AlignToActor; TSharedPtr< FUICommandInfo > SnapPivotToActor = FLevelEditorCommands::Get().SnapPivotToActor; TSharedPtr< FUICommandInfo > AlignPivotToActor = FLevelEditorCommands::Get().AlignPivotToActor; TSharedPtr< FUICommandInfo > SnapBottomCenterBoundsToActor = FLevelEditorCommands::Get().SnapBottomCenterBoundsToActor; TSharedPtr< FUICommandInfo > AlignBottomCenterBoundsToActor = FLevelEditorCommands::Get().AlignBottomCenterBoundsToActor; SnapOriginToActor->Label = FString::Printf( *LOCTEXT("Snap Origin To", "Snap Origin to %s"), *Label); AlignOriginToActor->Label = FString::Printf( *LOCTEXT("Align Origin To", "Align Origin to %s"), *Label); SnapToActor->Label = FString::Printf( *LOCTEXT("Snap To", "Snap to %s"), *Label); AlignToActor->Label = FString::Printf( *LOCTEXT("Align To", "Align to %s"), *Label); SnapPivotToActor->Label = FString::Printf( *LOCTEXT("Snap Pivot To", "Snap Pivot to %s"), *Label); AlignPivotToActor->Label = FString::Printf( *LOCTEXT("Align Pivot To", "Align Pivot to %s"), *Label); SnapBottomCenterBoundsToActor->Label = FString::Printf( *LOCTEXT("Snap Bottom Center Bounds To", "Snap Bottom Center Bounds to %s"), *Label); AlignBottomCenterBoundsToActor->Label = FString::Printf( *LOCTEXT("Align Bottom Center Bounds To", "Align Bottom Center Bounds to %s"), *Label); Section.AddMenuEntry( SnapOriginToActor ); Section.AddMenuEntry( AlignOriginToActor ); Section.AddMenuEntry( SnapToActor ); Section.AddMenuEntry( AlignToActor ); Section.AddMenuEntry( SnapPivotToActor ); Section.AddMenuEntry( AlignPivotToActor ); Section.AddMenuEntry( SnapBottomCenterBoundsToActor ); Section.AddMenuEntry( AlignBottomCenterBoundsToActor ); } else { Section.AddMenuEntry( FLevelEditorCommands::Get().SnapOriginToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignOriginToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapPivotToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignPivotToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().SnapBottomCenterBoundsToActor ); Section.AddMenuEntry( FLevelEditorCommands::Get().AlignBottomCenterBoundsToActor ); } */ } void FLevelEditorContextMenuImpl::FillPivotMenu( UToolMenu* Menu ) { { FToolMenuSection& Section = Menu->AddSection("SaveResetPivot"); Section.AddMenuEntry(FLevelEditorCommands::Get().SavePivotToPrePivot); Section.AddMenuEntry(FLevelEditorCommands::Get().ResetPrePivot); if (ULevelEditorContextMenuContext* LevelEditorContext = Menu->FindContext()) { if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport) { Section.AddMenuEntry(FLevelEditorCommands::Get().MovePivotHere); Section.AddMenuEntry(FLevelEditorCommands::Get().MovePivotHereSnapped); } } } { FToolMenuSection& Section = Menu->AddSection("MovePivot"); Section.AddMenuEntry(FLevelEditorCommands::Get().MovePivotToCenter); } } void FLevelEditorContextMenuImpl::FillGroupMenu( UToolMenu* Menu ) { FToolMenuSection& Section = Menu->AddSection("Group"); if( SelectionInfo.NumSelectedUngroupedActors > 1 ) { // Only show this menu item if we have more than one actor. Section.AddMenuEntry( FLevelEditorCommands::Get().GroupActors ); } if( SelectionInfo.bHaveSelectedLockedGroup || SelectionInfo.bHaveSelectedUnlockedGroup ) { const int32 NumActiveGroups = AGroupActor::NumActiveGroups(true); // Regroup will clear any existing groups and create a new one from the selection // Only allow regrouping if multiple groups are selected, or a group and ungrouped actors are selected if( NumActiveGroups > 1 || (NumActiveGroups && SelectionInfo.NumSelectedUngroupedActors) ) { Section.AddMenuEntry( FLevelEditorCommands::Get().RegroupActors ); } Section.AddMenuEntry( FLevelEditorCommands::Get().UngroupActors ); if( SelectionInfo.bHaveSelectedUnlockedGroup ) { // Only allow removal of loose actors or locked subgroups if( !SelectionInfo.bHaveSelectedLockedGroup || ( SelectionInfo.bHaveSelectedLockedGroup && SelectionInfo.bHaveSelectedSubGroup ) ) { Section.AddMenuEntry( FLevelEditorCommands::Get().RemoveActorsFromGroup ); } Section.AddMenuEntry( FLevelEditorCommands::Get().LockGroup ); } if( SelectionInfo.bHaveSelectedLockedGroup ) { Section.AddMenuEntry( FLevelEditorCommands::Get().UnlockGroup ); } // Only allow group adds if a single group is selected in addition to ungrouped actors if( AGroupActor::NumActiveGroups(true, false) == 1 && SelectionInfo.NumSelectedUngroupedActors ) { Section.AddMenuEntry( FLevelEditorCommands::Get().AddActorsToGroup ); } if (AGroupActor::SelectedGroupNeedsFixup()) { Section.AddMenuEntry(FLevelEditorCommands::Get().FixupGroupActor); } } } void FLevelEditorContextMenuImpl::FillEditMenu( UToolMenu* Menu, FToolMenuSection* InSection ) { FToolMenuSection& Section = InSection ? *InSection : Menu->AddSection("Section"); Section.AddMenuEntry( FGenericCommands::Get().Cut ); Section.AddMenuEntry( FGenericCommands::Get().Copy ); Section.AddMenuEntry( FGenericCommands::Get().Paste ); if (ULevelEditorContextMenuContext* LevelEditorContext = Menu->FindContext()) { if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport) { Section.AddMenuEntry(FLevelEditorCommands::Get().PasteHere); } } Section.AddMenuEntry( FGenericCommands::Get().Duplicate ); Section.AddMenuEntry( FGenericCommands::Get().Delete ); Section.AddMenuEntry( FGenericCommands::Get().Rename ); } void FLevelEditorContextMenuImpl::FillBulkEditComponentsMenu( UToolMenu* Menu ) { /** This is how the logic for selecting components to bulk edit works at a high level: * We try to find the common types of components that are shared between all the actors in the selection, and then * add a menu entry for each of the common component types which opens them in the Property Matrix * To achieve this, we pick the components of the first actor in the selection as a base, and then remove any * that are not shared with every subsequent actor in the selection, early exiting if there are no common components. */ FToolMenuSection& Section = Menu->AddSection("Section"); ULevelEditorContextMenuContext* LevelEditorContext = Menu->FindContext(); if (!LevelEditorContext) { return; } TObjectPtr Selection = LevelEditorContext->CurrentSelection; if(!Selection) { return; } TArray SelectedActors = Selection->GetSelectedObjects(); if(SelectedActors.Num() == 0) { return; } /* Map to keep track of components that are common between the current selection * The key represents the type of the component, while the value contains the actual components from each object in the selection */ TMap, TArray< UObject* >> CommonComponents; // We start by filling in CommonComponents with all the components of the first actor in the selection const AActor* FirstActor = SelectedActors[0]; for( UActorComponent* Component : FirstActor->GetComponents()) { // Make sure the component can be edited if(FComponentEditorUtils::CanEditComponentInstance(Component, Cast(Component), false)) { // If this type of component already exists, simply add it to the array if(TArray< UObject* >* FoundCommonComponent = CommonComponents.Find(Component->GetClass())) { FoundCommonComponent->Add(Component); } // Otherwise add a new entry for this component type else { TArray< UObject* > CurrentActorComponents; CurrentActorComponents.Add(Component); CommonComponents.Add(Component->GetClass(), CurrentActorComponents); } } } // We start iterating from the second object since we've already used the first as a base TArray::TIterator ActorIt = SelectedActors.CreateIterator(); ++ActorIt; for(; ActorIt; ++ActorIt) { // Iterate through each common component for(TMap, TArray< UObject* >>::TIterator ComponentIt = CommonComponents.CreateIterator(); ComponentIt; ++ComponentIt) { // Get all components of the current component type from this actor TArray ComponentsOfType; (*ActorIt)->GetComponents(ComponentIt.Key(), ComponentsOfType); bool bHasComponentOfCurrentType = false; /* For each component the current actor has, we make sure it is EXACTLY the same type as the common component * because GetComponents also checks SubClasses. i.e if CommonComponent is SceneComponent and CurrentComponent * is StaticMeshComponent, we do not want to multi edit them currently */ for(UActorComponent* CurrentComponent : ComponentsOfType) { if(ComponentIt.Key() == CurrentComponent->GetClass() && FComponentEditorUtils::CanEditComponentInstance(CurrentComponent, Cast(CurrentComponent), false)) { ComponentIt.Value().Add(CurrentComponent); bHasComponentOfCurrentType = true; } } // If this actor has no components of this type, discard this type if(!bHasComponentOfCurrentType) { ComponentIt.RemoveCurrent(); } } // Early out if there are no common components between all actors in the selection if(CommonComponents.IsEmpty()) { break; } } // If we have no common components, show some text indicating so if(CommonComponents.IsEmpty()) { Section.AddEntry(FToolMenuEntry::InitWidget( NAME_None, SNew(SBox) .Padding(FMargin(6.f, 0.f)) [ SNew(STextBlock) .Text(LOCTEXT("NoCommonComponents", "The selected actors have no common editable components.")) .TextStyle(FAppStyle::Get(), "HintText") ], FText::GetEmpty(), /*bNoIndent*/ true, /*bSearchable*/ false, false, LOCTEXT("NoCommonComponentsTooltip", "There are no editable components of the same type between the currently selected actors."))); return; } // Add a menu entry to open each set of components in the property matrix for(auto it = CommonComponents.CreateIterator(); it; ++it) { TArray& Components = it.Value(); FUIAction BulkEditAction( FExecuteAction::CreateLambda([Components]() { FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( "PropertyEditor" ); PropertyEditorModule.CreatePropertyEditorToolkit(TSharedPtr(), Components ); })); Section.AddMenuEntry( NAME_None, FText::FromName(it.Key()->GetFName()), FText::GetEmpty(), FSlateIcon(), BulkEditAction); } } void FLevelEditorContextMenuImpl::FillAssetToolsMenu(UToolMenu* Menu) { { FToolMenuSection& Section = Menu->AddSection("AssetTools"); // Go to C++ Code // Create custom label and tooltip with the header's filename if possible, // otherwise the unset TAttribute's will cause the menu item to use the command's label/tooltip. TAttribute GoToCodeForActorLabel; TAttribute GoToCodeForActorToolTip; if (SelectionInfo.SelectionClass != NULL) { if (FSourceCodeNavigation::IsCompilerAvailable()) { FString ClassHeaderPath; if (FSourceCodeNavigation::FindClassHeaderPath(SelectionInfo.SelectionClass, ClassHeaderPath) && IFileManager::Get().FileSize(*ClassHeaderPath) != INDEX_NONE) { const FString CodeFileName = FPaths::GetCleanFilename(*ClassHeaderPath); GoToCodeForActorLabel = FText::Format(LOCTEXT("GoToCodeForActor", "Open {0}"), FText::FromString(CodeFileName)); GoToCodeForActorToolTip = FText::Format(LOCTEXT("GoToCodeForActor_ToolTip", "Opens the header file for this actor ({0}) in a code editing program"), FText::FromString(CodeFileName)); } } } Section.AddMenuEntry(FLevelEditorCommands::Get().GoToCodeForActor, GoToCodeForActorLabel, GoToCodeForActorToolTip, FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.C++")); } { FToolMenuSection& Section = Menu->AddSection("SourceControl"); FLevelEditorContextMenuImpl::AddSourceControlMenu(Section); } } void FLevelEditorContextMenuImpl::FillMergeActorsMenu(UToolMenu* Menu) { FToolMenuSection& Section = Menu->AddSection("OpenPanel"); IMergeActorsModule& MergeActorsModule = IMergeActorsModule::Get(); TArray MergeActorTools; MergeActorsModule.Get().GetRegisteredMergeActorsTools(MergeActorTools); for (IMergeActorsTool* Tool : MergeActorTools) { FUIAction MergeActorToolAction( FExecuteAction::CreateLambda([Tool]() { Tool->RunMergeFromSelection(); }), FCanExecuteAction::CreateLambda([Tool]() { return Tool->CanMergeFromSelection(); })); Section.AddMenuEntry( NAME_None, Tool->GetToolNameText(), Tool->GetTooltipText(), FSlateIcon(FAppStyle::GetAppStyleSetName(), Tool->GetIconName()), MergeActorToolAction); } if (MergeActorTools.Num() > 0) { Section.AddSeparator(NAME_None); } Section.AddMenuEntry( FLevelEditorCommands::Get().OpenMergeActor, LOCTEXT("OpenMergeActor", "Merge Actors Settings..."), LOCTEXT("OpenMergeActor_ToolTip", "Click to open the Merge Actor panel"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.GameSettings")); } void FLevelEditorContextMenuImpl::AddSourceControlMenu(FToolMenuSection& Section) { Section.AddSubMenu(TEXT("SourceControlSubMenu"), LOCTEXT("SourceControlSubMenu", "Revision Control"), LOCTEXT("SourceControlSubMenu_ToolTip", "Opens the Revision Control sub menu"), FNewToolMenuDelegate::CreateStatic(&FLevelEditorContextMenuImpl::FillSourceControlMenu), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "MainFrame.ConnectToSourceControl")); } void FLevelEditorContextMenuImpl::FillSourceControlMenu(UToolMenu* Menu) { FToolMenuSection& Section = Menu->AddSection(TEXT("Revision Control")); Section.AddMenuEntry(FLevelEditorCommands::Get().ShowActorHistory); } void FLevelEditorContextMenuImpl::AddSelectChildrenEntry(FToolMenuSection& Section, const TArray& SelectedActors, ULevelEditorContextMenuContext* Context) { // Don't show in main Actor menu because it's already available from the Select pulldown menu. if (Context->ContextType == ELevelEditorMenuContext::MainMenu) { return; } bool bHasAttachedChildren = false; for (AActor* Actor : SelectedActors) { TArray AttachedActors; Actor->GetAttachedActors(AttachedActors); if (!AttachedActors.IsEmpty()) { bHasAttachedChildren = true; break; } } // Only show if there are attached children to prevent crowding the context menu all the time. if (bHasAttachedChildren) { Section.AddMenuEntry(FLevelEditorCommands::Get().SelectImmediateChildren); } } void FLevelScriptEventMenuHelper::FillLevelBlueprintEventsMenu(FToolMenuSection& Section, const TArray& SelectedActors) { AActor* SelectedActor = (1 == SelectedActors.Num()) ? SelectedActors[0] : NULL; TWeakObjectPtr ActorPtr(SelectedActor); const bool bAnyEventExists = FKismetEditorUtilities::AnyBoundLevelScriptEventForActor(SelectedActor, false); const bool bAnyEventCanBeAdded = FKismetEditorUtilities::AnyBoundLevelScriptEventForActor(SelectedActor, true); Section.AddSubMenu( "AddEventSubMenu", LOCTEXT("AddEventSubMenu", "Add Event"), FText::GetEmpty(), FNewToolMenuDelegate::CreateStatic(&FKismetEditorUtilities::AddLevelScriptEventOptionsForActor, ActorPtr, false, true, true), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([bAnyEventCanBeAdded]() { return bAnyEventCanBeAdded; })), EUserInterfaceActionType::Button, false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Event") ); Section.AddSubMenu( "JumpEventSubMenu", LOCTEXT("JumpEventSubMenu", "Jump to Event"), FText::GetEmpty(), FNewToolMenuDelegate::CreateStatic(&FKismetEditorUtilities::AddLevelScriptEventOptionsForActor, ActorPtr, true, false, true), FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([bAnyEventExists]() { return bAnyEventExists; })), EUserInterfaceActionType::Button, false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.JumpToEvent") ); } #undef LOCTEXT_NAMESPACE