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

1724 lines
63 KiB
C++

// 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<AActor*>& 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<AActor*>& 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<ULevelEditorContextMenuContext>();
if (!LevelEditorContext)
{
return;
}
TArray<UActorComponent*> SelectedComponents;
for (FSelectedEditableComponentIterator It(GEditor->GetSelectedEditableComponentIterator()); It; ++It)
{
SelectedComponents.Add(CastChecked<UActorComponent>(*It));
}
{
FToolMenuSection& Section = InMenu->AddSection("ComponentControl", LOCTEXT("ComponentControlHeading", "Component"));
AActor* OwnerActor = GEditor->GetSelectedActors()->GetTop<AActor>();
if(OwnerActor)
{
Section.AddMenuEntry(
FLevelEditorCommands::Get().SelectComponentOwnerActor,
FText::Format(LOCTEXT("SelectComponentOwner", "Select Owner [{0}]"), FText::FromString(OwnerActor->GetHumanReadableName())),
TAttribute<FText>(),
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<ULevelEditorContextMenuContext>();
if (!LevelEditorContext || !LevelEditorContext->LevelEditor.IsValid())
{
return;
}
TWeakPtr<ILevelEditor> LevelEditor = LevelEditorContext->LevelEditor;
// Generate information about our selection
TArray<AActor*> SelectedActors;
GEditor->GetSelectedActors()->GetSelectedObjects<AActor>(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<FText>(), // use command's label
TAttribute<FText>(), // use command's tooltip
AssetIcon
);
}
else if (ReferencedAssets.Num() == 1)
{
UObject* Asset = ReferencedAssets[0];
const FString AssetLabel = Cast<AActor>(Asset) ? Cast<AActor>(Asset)->GetActorNameOrLabel() : Asset->GetName();
Section.AddMenuEntry(
FLevelEditorCommands::Get().EditAsset,
FText::Format(LOCTEXT("EditAssociatedAsset", "Edit {0}"), FText::FromString(AssetLabel)),
TAttribute<FText>(), // use command's tooltip
AssetIcon
);
}
else if (ReferencedAssets.Num() > 1)
{
Section.AddMenuEntry(
FLevelEditorCommands::Get().EditAssetNoConfirmMultiple,
LOCTEXT("EditAssociatedAssetsMultiple", "Edit Multiple Assets"),
TAttribute<FText>(), // 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<FText>(), TAttribute<FText>(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Convert"), Action, EUserInterfaceActionType::None);
}
Section.AddMenuEntry(
FLevelEditorCommands::Get().CopyActorFilePathtoClipboard,
TAttribute<FText>(), // use command's label
TAttribute<FText>(), // use command's tooltip
FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Copy")
);
Section.AddMenuEntry(
FLevelEditorCommands::Get().OpenActorInReferenceViewer,
TAttribute<FText>(), // use command's label
TAttribute<FText>(), // use command's tooltip
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.ReferenceViewer")
);
Section.AddMenuEntry(
FLevelEditorCommands::Get().SaveActor,
TAttribute<FText>(), // use command's label
TAttribute<FText>(), // use command's tooltip
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Save")
);
LevelEditorCreateActorMenu::FillAddReplaceContextMenuSections(Section, LevelEditorContext);
}
{
if(FModuleManager::LoadModuleChecked<FPropertyEditorModule>( "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<FText>(), // use command's label
TAttribute<FText>(), // 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<FText>(),
TAttribute<FText>(),
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<FText>(),
TAttribute<FText>(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.GoHere")
);
}
Section.AddMenuEntry(
FLevelEditorCommands::Get().SnapCameraToObject,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Actors.SnapViewToObject")
);
Section.AddMenuEntry(
FLevelEditorCommands::Get().SnapObjectToCamera,
TAttribute<FText>(),
TAttribute<FText>(),
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<FText>(), // Use command title
TAttribute<FText>(), // 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<ULevelEditorContextMenuContext>())
{
TWeakPtr<ISceneOutliner> 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<ULevelEditorContextMenuContext>())
{
{
FToolMenuSection& Section = InMenu->AddSection("SelectActorGeneral", LOCTEXT("SelectAnyHeading", "General"));
Section.AddMenuEntry(FGenericCommands::Get().SelectAll, TAttribute<FText>(), 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<UActorComponent>())
{
return "LevelEditor.ComponentContextMenu";
}
if (InSelectionSet->HasSelectedObjects<AActor>())
{
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<UActorComponent>())
{
return LOCTEXT("ComponentContextMenuTitle", "Component");
}
if (InSelectionSet->HasSelectedObjects<AActor>())
{
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<UActorComponent>();
if (ComponentCount == 1)
{
UActorComponent* Component = InSelectionSet->GetTopSelectedObject<UActorComponent>();
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<AActor>();
if (ActorCount == 1)
{
AActor* Actor = InSelectionSet->GetTopSelectedObject<AActor>();
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<ILevelEditor> LevelEditor, ELevelEditorMenuContext ContextType, const FTypedElementHandle& HitProxyElement)
{
RegisterComponentContextMenu();
RegisterActorContextMenu();
RegisterElementContextMenu();
RegisterSceneOutlinerContextMenu();
RegisterMenuBarEmptyContextMenu();
RegisterEmptySelectionContextMenu();
TSharedPtr<ILevelEditor> LevelEditorPtr = LevelEditor.Pin();
check(LevelEditorPtr);
TSharedPtr<FUICommandList> LevelEditorActionsList = LevelEditorPtr->GetLevelEditorActions();
Context.AppendCommandList(LevelEditorActionsList);
ULevelEditorContextMenuContext* ContextObject = NewObject<ULevelEditorContextMenuContext>();
ContextObject->LevelEditor = LevelEditor;
ContextObject->ContextType = ContextType;
ContextObject->CurrentSelection = LevelEditorPtr->GetElementSelectionSet();
ContextObject->HitProxyElement = HitProxyElement;
{
TTypedElement<ITypedElementObjectInterface> HitProxyObjectElement = ContextObject->CurrentSelection->GetElementList()->GetElement<ITypedElementObjectInterface>(ContextObject->HitProxyElement);
ContextObject->HitProxyActor = HitProxyObjectElement ? Cast<AActor>(HitProxyObjectElement.GetObject()) : nullptr;
}
for (FSelectedEditableComponentIterator It(GEditor->GetSelectedEditableComponentIterator()); It; ++It)
{
ContextObject->SelectedComponents.Add(CastChecked<UActorComponent>(*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<ULevelEditorContextMenuContext>(InContext);
CastContext->CurrentSelection = nullptr;
CastContext->HitProxyElement.Release();
});
if (GEditor->GetSelectedComponentCount() == 0 && GEditor->GetSelectedActorCount() > 0)
{
TArray<AActor*> SelectedActors;
GEditor->GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);
// Get all menu extenders for this context menu from the level editor module
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
TArray<FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors> MenuExtenderDelegates = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
TArray<TSharedPtr<FExtender>> 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<ILevelEditor> LevelEditor, ELevelEditorMenuContext ContextType, TSharedPtr<FExtender> 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<FExtender> 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<SWidget> 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<SWidget> 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<SWidget> 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<ULevelEditorContextMenuContext>();
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<ULevelEditorContextMenuContext>())
{
if (!LevelEditorContext->CurrentSelection || LevelEditorContext->CurrentSelection->GetNumSelectedElements() == 0)
{
return;
}
{
FToolMenuSection& Section = Menu->AddSection("DeltaTransformToActors");
Section.AddMenuEntry(FLevelEditorCommands::Get().DeltaTransformToActors);
}
if (LevelEditorContext->CurrentSelection->HasSelectedObjects<AActor>())
{
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>(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<FSceneOutlinerModule>( "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<AActor>();
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<ULevelEditorContextMenuContext>())
{
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<ULevelEditorContextMenuContext>())
{
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<ULevelEditorContextMenuContext>();
if (!LevelEditorContext)
{
return;
}
TObjectPtr<const UTypedElementSelectionSet> Selection = LevelEditorContext->CurrentSelection;
if(!Selection)
{
return;
}
TArray<AActor*> SelectedActors = Selection->GetSelectedObjects<AActor>();
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<TSubclassOf<UActorComponent>, 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<USceneComponent>(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<AActor*>::TIterator ActorIt = SelectedActors.CreateIterator();
++ActorIt;
for(; ActorIt; ++ActorIt)
{
// Iterate through each common component
for(TMap<TSubclassOf<UActorComponent>, TArray< UObject* >>::TIterator ComponentIt = CommonComponents.CreateIterator(); ComponentIt; ++ComponentIt)
{
// Get all components of the current component type from this actor
TArray<UActorComponent*> 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<USceneComponent>(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<UObject*>& Components = it.Value();
FUIAction BulkEditAction(
FExecuteAction::CreateLambda([Components]()
{
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
PropertyEditorModule.CreatePropertyEditorToolkit(TSharedPtr<IToolkitHost>(), 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<FText> GoToCodeForActorLabel;
TAttribute<FText> 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<IMergeActorsTool*> 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<AActor*>& 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<AActor*> 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<AActor*>& SelectedActors)
{
AActor* SelectedActor = (1 == SelectedActors.Num()) ? SelectedActors[0] : NULL;
TWeakObjectPtr<AActor> 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