Files
UnrealEngineUWP/Engine/Source/Editor/LevelEditor/Private/LevelEditorContextMenu.cpp
aditya ravichandran 632ef3784e Outliner:
Port ActorBrowsingMode over to the new Editor Config system to have separate settings for each outliner
Enable Stacked Hierarchy Headers by default
Add the ability to disable framing selection
Add the ability to get the most recently used outliner from the level editor
Fix a bug where renaming an item on one outliner would sometimes execute the rename on a different outliner

#rb richard.malo
#jira UETOOL-5014
#preflight 627aa9811e74993343331221

[CL 20126651 by aditya ravichandran in ue5-main branch]
2022-05-10 14:20:42 -04:00

1523 lines
56 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/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"
#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);
/**
* 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);
{
// 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);
// 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)
{
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)
{
auto Asset = ReferencedAssets[0];
Section.AddMenuEntry(
FLevelEditorCommands::Get().EditAsset,
FText::Format(LOCTEXT("EditAssociatedAsset", "Edit {0}"), FText::FromString(Asset->GetName())),
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")
);
LevelEditorCreateActorMenu::FillAddReplaceContextMenuSections(Section, LevelEditorContext);
}
if (LevelEditorContext->ContextType == ELevelEditorMenuContext::Viewport || LevelEditorContext->ContextType == ELevelEditorMenuContext::SceneOutliner)
{
// Options that only appear in the viewport context menu or scene outliner (will 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;
// 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.");
#if PLATFORM_MAC
// Can't include arbitrary widgets in a main menu on Mac, so display the informational message using a disabled entry.
Section.AddMenuEntry(
NAME_None,
EmptySelectionInformationalMessage,
TAttribute<FText>(),
TAttribute<FSlateIcon>(),
FUIAction(FExecuteAction(), FCanExecuteAction::CreateLambda([]() { return false; })));
#else
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));
#endif
}
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_OrthoNegativeXY:
ViewOptionType = EViewOptionType::Bottom;
break;
case LVT_OrthoNegativeXZ:
ViewOptionType = EViewOptionType::Back;
break;
case LVT_OrthoNegativeYZ:
ViewOptionType = EViewOptionType::Right;
break;
case LVT_OrthoXY:
ViewOptionType = EViewOptionType::Top;
break;
case LVT_OrthoXZ:
ViewOptionType = EViewOptionType::Front;
break;
case LVT_OrthoYZ:
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 ( FLevelEditorActionCallbacks::ElementSelected_CanExecute() )
{
FToolMenuSection& Section = Menu->AddSection("DeltaTransformToActors");
Section.AddMenuEntry(FLevelEditorCommands::Get().DeltaTransformToActors);
}
{
FToolMenuSection& Section = Menu->AddSection("MirrorLock");
// TODO: Need an element API to allow the mirror actions
if ( FLevelEditorActionCallbacks::ActorSelected_CanExecute() )
{
Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorX);
Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorY);
Section.AddMenuEntry(FLevelEditorCommands::Get().MirrorActorZ);
}
if ( FLevelEditorActionCallbacks::ActorSelected_CanExecute() )
{
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
{
FVector2D RequestedDesiredSize = 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)
{
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 );
}
}
}
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::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", "Source Control"),
LOCTEXT("SourceControlSubMenu_ToolTip", "Opens the Source 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("Source 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