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

1098 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SLevelViewportToolBar.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBoxDefs.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "SceneView.h"
#include "Subsystems/PanelExtensionSubsystem.h"
#include "ToolMenus.h"
#include "LevelEditorMenuContext.h"
#include "ViewportToolBarContext.h"
#include "Modules/ModuleManager.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SSpinBox.h"
#include "Styling/AppStyle.h"
#include "Camera/CameraActor.h"
#include "Misc/ConfigCacheIni.h"
#include "GameFramework/ActorPrimitiveColorHandler.h"
#include "GameFramework/WorldSettings.h"
#include "EngineUtils.h"
#include "LevelEditor.h"
#include "STransformViewportToolbar.h"
#include "EditorShowFlags.h"
#include "LevelViewportActions.h"
#include "LevelEditorViewport.h"
#include "Layers/LayersSubsystem.h"
#include "DeviceProfiles/DeviceProfile.h"
#include "IDeviceProfileServicesModule.h"
#include "EditorViewportCommands.h"
#include "SEditorViewportToolBarMenu.h"
#include "SEditorViewportToolBarButton.h"
#include "SEditorViewportViewMenu.h"
#include "Stats/StatsData.h"
#include "BufferVisualizationData.h"
#include "NaniteVisualizationData.h"
#include "LumenVisualizationData.h"
#include "SubstrateVisualizationData.h"
#include "GroomVisualizationData.h"
#include "VirtualShadowMapVisualizationData.h"
#include "FoliageType.h"
#include "ShowFlagMenuCommands.h"
#include "Bookmarks/BookmarkUI.h"
#include "Scalability.h"
#include "SScalabilitySettings.h"
#include "Editor/EditorPerformanceSettings.h"
#include "SEditorViewportViewMenuContext.h"
#include "Bookmarks/IBookmarkTypeTools.h"
#include "ToolMenu.h"
#include "WorldPartition/IWorldPartitionEditorModule.h"
#include "WorldPartition/WorldPartitionSubsystem.h"
#include "WorldPartition/WorldPartition.h"
#include "SLevelViewport.h"
#include "SortHelper.h"
#include "Interfaces/IMainFrameModule.h"
#include "SCommonEditorViewportToolbarBase.h"
#include "Engine/SceneCapture.h"
#include "ViewportToolbar/LevelEditorSubmenus.h"
#include "ViewportToolbar/LevelViewportContext.h"
#include "ViewportToolbar/UnrealEdViewportToolbar.h"
#define LOCTEXT_NAMESPACE "LevelViewportToolBar"
/** Override the view menu, just so we can specify the level viewport as active when the button is clicked */
class SLevelEditorViewportViewMenu : public SEditorViewportViewMenu
{
public:
void Construct(const FArguments& InArgs, TSharedRef<SEditorViewport> InViewport, TSharedRef<class SViewportToolBar> InParentToolBar)
{
SEditorViewportViewMenu::Construct(InArgs, InViewport, InParentToolBar);
MenuName = FName("LevelEditor.LevelViewportToolBar.View");
}
virtual void RegisterMenus() const override
{
SEditorViewportViewMenu::RegisterMenus();
// Use a static bool to track whether or not this menu is registered. Bool instead of checking the registered
// state with ToolMenus because we want the new viewport toolbar to be able to create this menu without breaking
// this code. Static because this code can be called multiple times using different instances of this class.
static bool bDidRegisterMenu = false;
if (!bDidRegisterMenu)
{
bDidRegisterMenu = true;
// Don't warn here to avoid warnings if the new viewport toolbar already has created an empty version
// of this menu.
UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(
"LevelEditor.LevelViewportToolBar.View", "UnrealEd.ViewportToolbar.View", EMultiBoxType::Menu, false
);
Menu->AddDynamicSection("LevelSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) {
if (UEditorViewportViewMenuContext* Context = InMenu->FindContext<UEditorViewportViewMenuContext>())
{
if (TSharedPtr<const SEditorViewportToolbarMenu> Menu = Context->EditorViewportViewMenu.Pin())
{
if (TSharedPtr<SLevelViewportToolBar> LevelViewportToolBar = StaticCastSharedPtr<SLevelViewportToolBar>(Menu->GetParentToolBar().Pin()))
{
LevelViewportToolBar->FillViewMenu(InMenu);
}
}
}
}));
}
}
virtual TSharedRef<SWidget> GenerateViewMenuContent() const override
{
SLevelViewport* LevelViewport = static_cast<SLevelViewport*>(Viewport.Pin().Get());
LevelViewport->OnFloatingButtonClicked();
return SEditorViewportViewMenu::GenerateViewMenuContent();
}
};
void SLevelViewportToolBar::Construct( const FArguments& InArgs )
{
Viewport = InArgs._Viewport;
TSharedRef<SLevelViewport> ViewportRef = Viewport.Pin().ToSharedRef();
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
UViewportToolBarContext* ExtensionContextObject = NewObject<UViewportToolBarContext>();
ExtensionContextObject->ViewportToolBar = SharedThis(this);
ExtensionContextObject->Viewport = Viewport;
const FMargin ToolbarSlotPadding(4.0f, 1.0f);
// clang-format off
ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("EditorViewportToolBar.Background"))
.Visibility(EVisibility::SelfHitTestInvisible)
[
SNew( SVerticalBox )
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(ToolbarSlotPadding)
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(ToolbarSlotPadding)
[
SNew( SEditorViewportToolbarMenu )
.ParentToolBar( SharedThis( this ) )
.Visibility(Viewport.Pin().Get(), &SLevelViewport::GetOptionsMenuVisibility)
.Image("EditorViewportToolBar.OptionsDropdown")
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("EditorViewportToolBar.MenuDropdown")))
.OnGetMenuContent( this, &SLevelViewportToolBar::GenerateOptionsMenu )
]
+ SHorizontalBox::Slot()
[
SNew( SHorizontalBox )
.Visibility(Viewport.Pin().Get(), &SLevelViewport::GetFullToolbarVisibility)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( ToolbarSlotPadding )
[
SNew( SEditorViewportToolbarMenu )
.ParentToolBar( SharedThis( this ) )
.Label( this, &SLevelViewportToolBar::GetCameraMenuLabel )
.LabelIcon( this, &SLevelViewportToolBar::GetCameraMenuLabelIcon )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("EditorViewportToolBar.CameraMenu")))
.OnGetMenuContent( this, &SLevelViewportToolBar::GenerateCameraMenu )
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( ToolbarSlotPadding )
[
SNew( SLevelEditorViewportViewMenu, ViewportRef, SharedThis(this) )
.MenuExtenders(UE::LevelEditor::GetViewModesLegacyExtenders())
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ViewMenuButton")))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( ToolbarSlotPadding )
[
SNew( SEditorViewportToolbarMenu )
.Label( LOCTEXT("ShowMenuTitle", "Show") )
.ParentToolBar( SharedThis( this ) )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("EditorViewportToolBar.ShowMenu")))
.OnGetMenuContent( this, &SLevelViewportToolBar::GenerateShowMenu )
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( ToolbarSlotPadding )
[
SNew( SEditorViewportToolbarMenu )
.Label( this, &SLevelViewportToolBar::GetViewModeOptionsMenuLabel )
.ParentToolBar( SharedThis( this ) )
.Visibility( this, &SLevelViewportToolBar::GetViewModeOptionsVisibility )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("EditorViewportToolBar.ViewModeOptions")))
.OnGetMenuContent( this, &SLevelViewportToolBar::GenerateViewModeOptionsMenu )
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding( ToolbarSlotPadding )
[
SNew( SEditorViewportToolbarMenu )
.ParentToolBar( SharedThis( this ) )
.Label( this, &SLevelViewportToolBar::GetDevicePreviewMenuLabel )
.LabelIcon( this, &SLevelViewportToolBar::GetDevicePreviewMenuLabelIcon )
.OnGetMenuContent( this, &SLevelViewportToolBar::GenerateDevicePreviewMenu )
//@todo rendering: mobile preview in view port is not functional yet - remove this once it is.
.Visibility(EVisibility::Collapsed)
]
+ SHorizontalBox::Slot()
.Padding(ToolbarSlotPadding)
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Fill)
[
SNew(SExtensionPanel)
.ExtensionPanelID("LevelViewportToolBar.LeftExtension")
.ExtensionContext(ExtensionContextObject)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(ToolbarSlotPadding)
[
// Button to show that realtime is off
SNew(SEditorViewportToolBarButton)
.ButtonType(EUserInterfaceActionType::Button)
.ButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("EditorViewportToolBar.WarningButton"))
.OnClicked(this, &SLevelViewportToolBar::OnRealtimeWarningClicked)
.Visibility(this, &SLevelViewportToolBar::GetRealtimeWarningVisibility)
.ToolTipText(LOCTEXT("RealtimeOff_ToolTip", "This viewport is not updating in realtime. Click to turn on realtime mode."))
.Content()
[
SNew(STextBlock)
.TextStyle(&FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("SmallText"))
.Text(LOCTEXT("RealtimeOff", "Realtime Off"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(ToolbarSlotPadding)
[
// Button to show scalability warnings
SNew(SEditorViewportToolbarMenu)
.ParentToolBar(SharedThis(this))
.Label_Static(&UE::UnrealEd::GetScalabilityWarningLabel)
.MenuStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("EditorViewportToolBar.WarningButton"))
.OnGetMenuContent(this, &SLevelViewportToolBar::GetScalabilityWarningMenuContent)
.Visibility(this, &SLevelViewportToolBar::GetScalabilityWarningVisibility)
.ToolTipText_Static(&UE::UnrealEd::GetScalabilityWarningTooltip)
]
+ SHorizontalBox::Slot()
.Padding(ToolbarSlotPadding)
.HAlign(HAlign_Center)
.VAlign(VAlign_Fill)
[
SNew(SExtensionPanel)
.ExtensionPanelID("LevelViewportToolBar.MiddleExtension")
.ExtensionContext(ExtensionContextObject)
]
+ SHorizontalBox::Slot()
.Padding(ToolbarSlotPadding)
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Fill)
[
SNew(SExtensionPanel)
.ExtensionPanelID("LevelViewportToolBar.RightExtension")
.ExtensionContext(ExtensionContextObject)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.MaxWidth(TAttribute<float>::CreateSP(this, &SLevelViewportToolBar::GetTransformToolbarWidth))
.Padding(ToolbarSlotPadding)
.HAlign(HAlign_Right)
[
SAssignNew(TransformToolbar, STransformViewportToolBar)
.Viewport(ViewportRef)
.CommandList(ViewportRef->GetCommandList())
.Extenders(LevelEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders())
.Visibility(ViewportRef, &SLevelViewport::GetTransformToolbarVisibility)
]
+ SHorizontalBox::Slot()
.Padding(ToolbarSlotPadding)
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Fill)
[
SNew(SExtensionPanel)
.ExtensionPanelID("LevelViewportToolBar.RightmostExtension")
.ExtensionContext(ExtensionContextObject)
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.AutoWidth()
.Padding(ToolbarSlotPadding)
[
//The Maximize/Minimize button is only displayed when not in Immersive mode.
SNew(SEditorViewportToolBarButton)
.ButtonType(EUserInterfaceActionType::ToggleButton)
.CheckBoxStyle(&FAppStyle::Get().GetWidgetStyle<FCheckBoxStyle>("EditorViewportToolBar.MaximizeRestoreButton"))
.IsChecked(ViewportRef, &SLevelViewport::IsMaximized)
.OnClicked(ViewportRef, &SLevelViewport::OnToggleMaximize)
.Visibility(ViewportRef, &SLevelViewport::GetMaximizeToggleVisibility)
.Image("EditorViewportToolBar.Maximize")
.ToolTipText(LOCTEXT("Maximize_ToolTip", "Maximizes or restores this viewport"))
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.AutoWidth()
.Padding(ToolbarSlotPadding)
[
//The Restore from Immersive' button is only displayed when the editor is in Immersive mode.
SNew(SEditorViewportToolBarButton)
.ButtonType(EUserInterfaceActionType::Button)
.OnClicked(ViewportRef, &SLevelViewport::OnToggleMaximize)
.Visibility(ViewportRef, &SLevelViewport::GetCloseImmersiveButtonVisibility)
.Image("EditorViewportToolBar.RestoreFromImmersive.Normal")
.ToolTipText(LOCTEXT("RestoreFromImmersive_ToolTip", "Restore from Immersive"))
]
]
]
]
];
// clang-format on
SViewportToolBar::Construct(SViewportToolBar::FArguments());
}
bool SLevelViewportToolBar::IsViewModeSupported(EViewModeIndex ViewModeIndex) const
{
return true;
}
FLevelEditorViewportClient* SLevelViewportToolBar::GetLevelViewportClient() const
{
if (TSharedPtr<SLevelViewport> PinnedViewport = Viewport.Pin())
{
return &PinnedViewport->GetLevelViewportClient();
}
return nullptr;
}
FText SLevelViewportToolBar::GetCameraMenuLabel() const
{
TSharedPtr< SLevelViewport > PinnedViewport( Viewport.Pin() );
if( PinnedViewport.IsValid() )
{
return UE::UnrealEd::GetCameraSubmenuLabelFromViewportType(PinnedViewport->GetLevelViewportClient().ViewportType);
}
return LOCTEXT("CameraMenuTitle_Default", "Camera");
}
const FSlateBrush* SLevelViewportToolBar::GetCameraMenuLabelIcon() const
{
TSharedPtr< SLevelViewport > PinnedViewport( Viewport.Pin() );
if( PinnedViewport.IsValid() )
{
return GetCameraMenuLabelIconFromViewportType(PinnedViewport->GetLevelViewportClient().ViewportType );
}
return FStyleDefaults::GetNoBrush();
}
FText SLevelViewportToolBar::GetDevicePreviewMenuLabel() const
{
FText Label = LOCTEXT("DevicePreviewMenuTitle_Default", "Preview");
TSharedPtr< SLevelViewport > PinnedViewport( Viewport.Pin() );
if( PinnedViewport.IsValid() )
{
if ( PinnedViewport->GetDeviceProfileString() != "Default" )
{
Label = FText::FromString( PinnedViewport->GetDeviceProfileString() );
}
}
return Label;
}
const FSlateBrush* SLevelViewportToolBar::GetDevicePreviewMenuLabelIcon() const
{
TSharedRef<SLevelViewport> ViewportRef = Viewport.Pin().ToSharedRef();
FString DeviceProfileName = ViewportRef->GetDeviceProfileString();
FName PlatformIcon = NAME_None;
if( !DeviceProfileName.IsEmpty() && DeviceProfileName != "Default" )
{
static FName DeviceProfileServices( "DeviceProfileServices" );
IDeviceProfileServicesModule& ScreenDeviceProfileUIServices = FModuleManager::LoadModuleChecked<IDeviceProfileServicesModule>(TEXT( "DeviceProfileServices"));
IDeviceProfileServicesUIManagerPtr UIManager = ScreenDeviceProfileUIServices.GetProfileServicesManager();
PlatformIcon = UIManager->GetDeviceIconName( DeviceProfileName );
return FAppStyle::GetOptionalBrush( PlatformIcon );
}
return nullptr;
}
bool SLevelViewportToolBar::IsCurrentLevelViewport() const
{
TSharedPtr< SLevelViewport > PinnedViewport( Viewport.Pin() );
if( PinnedViewport.IsValid() )
{
if( &( PinnedViewport->GetLevelViewportClient() ) == GCurrentLevelEditingViewportClient )
{
return true;
}
}
return false;
}
bool SLevelViewportToolBar::IsPerspectiveViewport() const
{
TSharedPtr< SLevelViewport > PinnedViewport( Viewport.Pin() );
if( PinnedViewport.IsValid() )
{
if( PinnedViewport->GetLevelViewportClient().IsPerspective() )
{
return true;
}
}
return false;
}
TSharedRef<SWidget> SLevelViewportToolBar::GenerateOptionsMenu()
{
static const FName MenuName("LevelEditor.LevelViewportToolBar.Options");
if (!UToolMenus::Get()->IsMenuRegistered(MenuName))
{
UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName);
Menu->AddDynamicSection("DynamicSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
if (ULevelViewportToolBarContext* Context = InMenu->FindContext<ULevelViewportToolBarContext>())
{
if (TSharedPtr<SLevelViewportToolBar> ToolBar = Context->LevelViewportToolBarWidget.Pin())
{
ToolBar->FillOptionsMenu(InMenu);
}
}
}));
}
Viewport.Pin()->OnFloatingButtonClicked();
const FLevelViewportCommands& LevelViewportActions = FLevelViewportCommands::Get();
TSharedRef<FUICommandList> CommandList = Viewport.Pin()->GetCommandList().ToSharedRef();
// Get all menu extenders for this context menu from the level editor module
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( TEXT("LevelEditor") );
TSharedPtr<FExtender> MenuExtender = LevelEditorModule.AssembleExtenders(CommandList, LevelEditorModule.GetAllLevelViewportOptionsMenuExtenders());
FToolMenuContext MenuContext(CommandList, MenuExtender);
{
{
ULevelViewportToolBarContext* ToolbarContextObject = NewObject<ULevelViewportToolBarContext>();
ToolbarContextObject->LevelViewportToolBarWidget = SharedThis(this);
MenuContext.AddObject(ToolbarContextObject);
}
{
ULevelViewportContext* const LevelContextObject = NewObject<ULevelViewportContext>();
LevelContextObject->LevelViewport = Viewport;
MenuContext.AddObject(LevelContextObject);
}
{
UUnrealEdViewportToolbarContext* EdViewportToolbarContext = NewObject<UUnrealEdViewportToolbarContext>();
EdViewportToolbarContext->Viewport = Viewport;
MenuContext.AddObject(EdViewportToolbarContext);
}
}
return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext);
}
void SLevelViewportToolBar::FillOptionsMenu(UToolMenu* Menu)
{
const FLevelViewportCommands& LevelViewportActions = FLevelViewportCommands::Get();
const bool bIsPerspective = Viewport.Pin()->GetLevelViewportClient().IsPerspective();
{
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportViewportOptions", LOCTEXT("OptionsMenuHeader", "Viewport Options"));
Section.AddMenuEntry(FEditorViewportCommands::Get().ToggleRealTime);
// Add an option to disable the temporary override if there is one
{
Section.AddEntry(UE::UnrealEd::CreateRemoveRealtimeOverrideEntry(Viewport));
Section.AddSeparator("DisableRealtimeOverrideSeparator");
}
Section.AddMenuEntry(FEditorViewportCommands::Get().ToggleFPS);
#if STATS
{
Section.AddMenuEntry(FEditorViewportCommands::Get().ToggleStats);
Section.AddEntry(UE::LevelEditor::CreateShowStatsSubmenu());
}
#endif
Section.AddMenuEntry(LevelViewportActions.ToggleAllowConstrainedAspectRatioInPreview);
Section.AddMenuEntry(LevelViewportActions.ToggleViewportToolbar);
if( bIsPerspective )
{
Section.AddEntry(UE::LevelEditor::CreateFOVMenu(Viewport));
Section.AddEntry(UE::LevelEditor::CreateFarViewPlaneMenu(Viewport));
}
Section.AddEntry(UE::UnrealEd::CreateScreenPercentageSubmenu());
}
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportViewportOptions2");
if( bIsPerspective )
{
// Cinematic preview only applies to perspective
Section.AddMenuEntry( LevelViewportActions.ToggleCinematicPreview );
}
Section.AddMenuEntry( LevelViewportActions.ToggleGameView );
Section.AddMenuEntry( LevelViewportActions.ToggleImmersive );
}
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportBookmarks");
if( bIsPerspective )
{
// Bookmarks only work in perspective viewports so only show the menu option if this toolbar is in one
Section.AddSubMenu(
"Bookmark",
LOCTEXT("BookmarkSubMenu", "Bookmarks"),
LOCTEXT("BookmarkSubMenu_ToolTip", "Viewport location bookmarking"),
FNewToolMenuDelegate::CreateStatic(&UE::LevelEditor::CreateBookmarksMenu),
false,
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "EditorViewport.SubMenu.Bookmarks")
);
Section.AddSubMenu(
"Camera",
LOCTEXT("CameraSubMeun", "Create Camera Here"),
LOCTEXT("CameraSubMenu_ToolTip", "Select a camera type to create at current viewport's location"),
FNewToolMenuDelegate::CreateStatic(&UE::LevelEditor::CreateCameraSpawnMenu),
false,
FSlateIcon(FAppStyle::Get().GetStyleSetName(), "EditorViewport.SubMenu.CreateCamera")
);
}
Section.AddMenuEntry(LevelViewportActions.HighResScreenshot);
}
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportLayouts");
Section.AddSubMenu("Configs", LOCTEXT("ConfigsSubMenu", "Layouts"), FText::GetEmpty(),
FNewToolMenuDelegate::CreateLambda(
[](UToolMenu* InMenu)
{
UE::LevelEditor::GenerateViewportLayoutsMenu(InMenu);
}),
false, FSlateIcon(FAppStyle::Get().GetStyleSetName(), "Icons.Layout"));
}
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportSettings");
Section.AddMenuEntry(LevelViewportActions.AdvancedSettings);
}
}
}
TSharedRef<SWidget> SLevelViewportToolBar::GenerateDevicePreviewMenu() const
{
static const FName MenuName("LevelEditor.LevelViewportToolBar.DevicePreview");
if (!UToolMenus::Get()->IsMenuRegistered(MenuName))
{
UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName);
Menu->AddDynamicSection("DynamicSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
if (ULevelViewportToolBarContext* Context = InMenu->FindContext<ULevelViewportToolBarContext>())
{
if (TSharedPtr<SLevelViewportToolBar> ToolBar = Context->LevelViewportToolBarWidget.Pin())
{
ToolBar->FillDevicePreviewMenu(InMenu);
}
}
}));
}
ULevelViewportToolBarContext* ContextObject = NewObject<ULevelViewportToolBarContext>();
ContextObject->LevelViewportToolBarWidget = ConstCastSharedRef<SLevelViewportToolBar>(SharedThis(this));
FToolMenuContext MenuContext(Viewport.Pin()->GetCommandList(), TSharedPtr<FExtender>(), ContextObject);
return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext);
}
void SLevelViewportToolBar::FillDevicePreviewMenu(UToolMenu* Menu) const
{
IDeviceProfileServicesModule& ScreenDeviceProfileUIServices = FModuleManager::LoadModuleChecked<IDeviceProfileServicesModule>(TEXT( "DeviceProfileServices"));
IDeviceProfileServicesUIManagerPtr UIManager = ScreenDeviceProfileUIServices.GetProfileServicesManager();
TSharedRef<SLevelViewport> ViewportRef = Viewport.Pin().ToSharedRef();
// Default menu - clear all settings
{
FToolMenuSection& Section = Menu->AddSection("DevicePreview", LOCTEXT("DevicePreviewMenuTitle", "Device Preview"));
FUIAction Action( FExecuteAction::CreateSP( const_cast<SLevelViewportToolBar*>(this), &SLevelViewportToolBar::SetLevelProfile, FString( "Default" ) ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( ViewportRef, &SLevelViewport::IsDeviceProfileStringSet, FString( "Default" ) ) );
Section.AddMenuEntry("DevicePreviewMenuClear", LOCTEXT("DevicePreviewMenuClear", "Off"), FText::GetEmpty(), FSlateIcon(), Action, EUserInterfaceActionType::Button);
}
// Recent Device Profiles
{
FToolMenuSection& Section = Menu->AddSection("Recent", LOCTEXT("RecentMenuHeading", "Recent"));
const FString INISection = "SelectedProfile";
const FString INIKeyBase = "ProfileItem";
const int32 MaxItems = 4; // Move this into a config file
FString CurItem;
for( int32 ItemIdx = 0 ; ItemIdx < MaxItems; ++ItemIdx )
{
// Build the menu from the contents of the game ini
//@todo This should probably be using GConfig->GetText [10/21/2013 justin.sargent]
if ( GConfig->GetString( *INISection, *FString::Printf( TEXT("%s%d"), *INIKeyBase, ItemIdx ), CurItem, GEditorPerProjectIni ) )
{
const FName PlatformIcon = UIManager->GetDeviceIconName( CurItem );
FUIAction Action( FExecuteAction::CreateSP( const_cast<SLevelViewportToolBar*>(this), &SLevelViewportToolBar::SetLevelProfile, CurItem ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( ViewportRef, &SLevelViewport::IsDeviceProfileStringSet, CurItem ) );
Section.AddMenuEntry(NAME_None, FText::FromString(CurItem), FText(), FSlateIcon(FAppStyle::GetAppStyleSetName(), PlatformIcon), Action, EUserInterfaceActionType::Button );
}
}
}
// Device List
{
FToolMenuSection& Section = Menu->AddSection("Devices", LOCTEXT("DevicesMenuHeading", "Devices"));
const TArray<TSharedPtr<FString> > PlatformList = UIManager->GetPlatformList();
for ( int32 Index = 0; Index < PlatformList.Num(); Index++ )
{
TArray< UDeviceProfile* > DeviceProfiles;
UIManager->GetProfilesByType( DeviceProfiles, *PlatformList[Index] );
if ( DeviceProfiles.Num() > 0 )
{
const FString PlatformNameStr = DeviceProfiles[0]->DeviceType;
const FName PlatformIcon = UIManager->GetPlatformIconName( PlatformNameStr );
Section.AddSubMenu(
NAME_None,
FText::FromString( PlatformNameStr ),
FText::GetEmpty(),
FNewToolMenuDelegate::CreateRaw( const_cast<SLevelViewportToolBar*>(this), &SLevelViewportToolBar::MakeDevicePreviewSubMenu, DeviceProfiles ),
false,
FSlateIcon(FAppStyle::GetAppStyleSetName(), PlatformIcon)
);
}
}
}
}
void SLevelViewportToolBar::MakeDevicePreviewSubMenu(UToolMenu* Menu, TArray< class UDeviceProfile* > InProfiles)
{
TSharedRef<SLevelViewport> ViewportRef = Viewport.Pin().ToSharedRef();
FToolMenuSection& Section = Menu->AddSection("Section");
for ( int32 Index = 0; Index < InProfiles.Num(); Index++ )
{
FUIAction Action( FExecuteAction::CreateSP( this, &SLevelViewportToolBar::SetLevelProfile, InProfiles[ Index ]->GetName() ),
FCanExecuteAction(),
FIsActionChecked::CreateSP( ViewportRef, &SLevelViewport::IsDeviceProfileStringSet, InProfiles[ Index ]->GetName() ) );
Section.AddMenuEntry(NAME_None, FText::FromString( InProfiles[ Index ]->GetName() ), FText(), FSlateIcon(), Action, EUserInterfaceActionType::RadioButton );
}
}
void SLevelViewportToolBar::SetLevelProfile( FString DeviceProfileName )
{
TSharedRef<SLevelViewport> ViewportRef = Viewport.Pin().ToSharedRef();
ViewportRef->SetDeviceProfileString( DeviceProfileName );
IDeviceProfileServicesModule& ScreenDeviceProfileUIServices = FModuleManager::LoadModuleChecked<IDeviceProfileServicesModule>(TEXT( "DeviceProfileServices"));
IDeviceProfileServicesUIManagerPtr UIManager = ScreenDeviceProfileUIServices.GetProfileServicesManager();
UIManager->SetProfile( DeviceProfileName );
}
void SLevelViewportToolBar::GeneratePlacedCameraMenuEntries(FToolMenuSection& Section, TArray<AActor*> LookThroughActors) const
{
FSlateIcon CameraIcon( FAppStyle::GetAppStyleSetName(), "ClassIcon.CameraComponent" );
// Sort the cameras to make the ordering predictable for users.
LookThroughActors.StableSort([](const AActor& Left, const AActor& Right)
{
// Do "natural sorting" via SceneOutliner::FNumericStringWrapper to make more sense to humans (also matches the Scene Outliner). This sorts "Camera2" before "Camera10" which a normal lexicographical sort wouldn't.
SceneOutliner::FNumericStringWrapper LeftWrapper(FString(Left.GetActorLabel()));
SceneOutliner::FNumericStringWrapper RightWrapper(FString(Right.GetActorLabel()));
return LeftWrapper < RightWrapper;
});
for( AActor* LookThroughActor : LookThroughActors)
{
// Needed for the delegate hookup to work below
AActor* GenericActor = LookThroughActor;
FText ActorDisplayName = FText::FromString(LookThroughActor->GetActorLabel());
FUIAction LookThroughCameraAction(
FExecuteAction::CreateSP(Viewport.Pin().ToSharedRef(), &SLevelViewport::OnActorLockToggleFromMenu, GenericActor),
FCanExecuteAction(),
FIsActionChecked::CreateSP(Viewport.Pin().ToSharedRef(), &SLevelViewport::IsActorLocked, MakeWeakObjectPtr(GenericActor))
);
Section.AddMenuEntry( NAME_None, ActorDisplayName, FText::Format(LOCTEXT("LookThroughCameraActor_ToolTip", "Look through and pilot {0}"), ActorDisplayName), CameraIcon, LookThroughCameraAction, EUserInterfaceActionType::RadioButton );
}
}
void SLevelViewportToolBar::GeneratePlacedCameraMenuEntries(UToolMenu* Menu, TArray<AActor*> LookThroughActors) const
{
FToolMenuSection& Section = Menu->AddSection("Section");
GeneratePlacedCameraMenuEntries(Section, LookThroughActors);
}
void SLevelViewportToolBar::GenerateViewportTypeMenu(FToolMenuSection& Section) const
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
LevelEditorModule.IterateViewportTypes([&](FName ViewportTypeName, const FViewportTypeDefinition& InDefinition)
{
if (InDefinition.ActivationCommand.IsValid())
{
Section.AddMenuEntry(*FString::Printf(TEXT("ViewportType_%s"), *ViewportTypeName.ToString()), InDefinition.ActivationCommand);
}
});
}
void SLevelViewportToolBar::GenerateViewportTypeMenu(UToolMenu* Menu) const
{
FToolMenuSection& Section = Menu->AddSection("Section");
GenerateViewportTypeMenu(Section);
}
void SLevelViewportToolBar::GenerateCameraSpawnMenu(UToolMenu* Menu) const
{
FToolMenuSection& Section = Menu->AddSection("Section");
const FLevelViewportCommands& Actions = FLevelViewportCommands::Get();
for (TSharedPtr<FUICommandInfo> Camera : Actions.CreateCameras)
{
Section.AddMenuEntry(NAME_None, Camera);
}
}
TSharedRef<SWidget> SLevelViewportToolBar::GenerateCameraMenu() const
{
static const FName MenuName("LevelEditor.LevelViewportToolBar.Camera");
// Use a static bool to track whether or not this menu is registered. Bool instead of checking the registered
// state with ToolMenus because we want the new viewport toolbar to be able to create this menu without breaking
// this code. Static because this code can be called multiple times using different instances of this class.
static bool bDidRegisterMenu = false;
if (!bDidRegisterMenu)
{
bDidRegisterMenu = true;
constexpr bool bWarnIfAlreadyRegistered = false;
UToolMenu* Menu =
UToolMenus::Get()->RegisterMenu(MenuName, NAME_None, EMultiBoxType::Menu, bWarnIfAlreadyRegistered);
Menu->AddDynamicSection(
"DynamicSection",
FNewToolMenuDelegate::CreateLambda(
[](UToolMenu* InMenu)
{
if (ULevelViewportToolBarContext* Context = InMenu->FindContext<ULevelViewportToolBarContext>())
{
if (TSharedPtr<SLevelViewportToolBar> ViewportToolbar = Context->LevelViewportToolBarWidget.Pin())
{
ViewportToolbar->FillCameraMenu(InMenu);
}
}
}
)
);
}
Viewport.Pin()->OnFloatingButtonClicked();
ULevelViewportToolBarContext* ContextObject = NewObject<ULevelViewportToolBarContext>();
ContextObject->LevelViewportToolBarWidget = ConstCastSharedRef<SLevelViewportToolBar>(SharedThis(this));
FToolMenuContext MenuContext(Viewport.Pin()->GetCommandList(), TSharedPtr<FExtender>(), ContextObject);
return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext);
}
void SLevelViewportToolBar::FillCameraMenu(UToolMenu* Menu) const
{
// Camera types
{
FToolMenuSection& Section = Menu->AddSection("CameraTypes");
Section.AddMenuEntry(FEditorViewportCommands::Get().Perspective);
}
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportCameraType_Ortho", LOCTEXT("CameraTypeHeader_Ortho", "Orthographic"));
Section.AddMenuEntry(FEditorViewportCommands::Get().Top);
Section.AddMenuEntry(FEditorViewportCommands::Get().Bottom);
Section.AddMenuEntry(FEditorViewportCommands::Get().Left);
Section.AddMenuEntry(FEditorViewportCommands::Get().Right);
Section.AddMenuEntry(FEditorViewportCommands::Get().Front);
Section.AddMenuEntry(FEditorViewportCommands::Get().Back);
}
TArray<AActor*> LookThroughActors;
for( TActorIterator<ACameraActor> It(GetWorld().Get()); It; ++It )
{
LookThroughActors.Add( Cast<AActor>(*It) );
}
for (TActorIterator<ASceneCapture> It(GetWorld().Get()); It; ++It)
{
LookThroughActors.Add(Cast<AActor>(*It));
}
FText CameraActorsHeading = LOCTEXT("CameraActorsHeading", "Placed Cameras and Scene Capture Actors");
// Don't add too many cameras to the top level menu or else it becomes too large
const uint32 MaxCamerasInTopLevelMenu = 10;
if(LookThroughActors.Num() > MaxCamerasInTopLevelMenu )
{
FToolMenuSection& Section = Menu->AddSection("CameraActors");
Section.AddSubMenu("CameraActors", CameraActorsHeading, LOCTEXT("LookThroughPlacedCameras_ToolTip", "Look through and pilot placed cameras"), FNewToolMenuDelegate::CreateSP(this, &SLevelViewportToolBar::GeneratePlacedCameraMenuEntries, LookThroughActors ) );
}
else
{
FToolMenuSection& Section = Menu->AddSection("CameraActors", CameraActorsHeading);
GeneratePlacedCameraMenuEntries(Section, LookThroughActors);
}
UE::UnrealEd::GenerateViewportTypeMenu(Menu);
}
TSharedRef<SWidget> SLevelViewportToolBar::GenerateShowMenu() const
{
static const FName MenuName("LevelEditor.LevelViewportToolbar.Show");
// Use a static bool to track whether or not this menu is registered. Bool instead of checking the registered state
// with ToolMenus because we want the new viewport toolbar to be able to create this menu without breaking this
// code. Static because this code can be called multiple times using different instances of this class.
static bool bDidRegisterMenu = false;
if (!bDidRegisterMenu)
{
bDidRegisterMenu = true;
// Don't warn here to avoid warnings if the new viewport toolbar already has created an empty version
// of this menu.
UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName, NAME_None, EMultiBoxType::Menu, false);
Menu->AddDynamicSection(
"LevelDynamicSection",
FNewToolMenuDelegate::CreateLambda(
[](UToolMenu* InMenu)
{
if (ULevelViewportToolBarContext* Context = InMenu->FindContext<ULevelViewportToolBarContext>())
{
if (TSharedPtr<SLevelViewportToolBar> ToolBar = Context->LevelViewportToolBarWidget.Pin())
{
ToolBar->FillShowMenu(InMenu);
}
}
}
)
);
}
Viewport.Pin()->OnFloatingButtonClicked();
TSharedRef<FUICommandList> CommandList = Viewport.Pin()->GetCommandList().ToSharedRef();
// Get all menu extenders for this context menu from the level editor module
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
TSharedPtr<FExtender> MenuExtender = LevelEditorModule.AssembleExtenders(CommandList, LevelEditorModule.GetAllLevelViewportShowMenuExtenders());
FToolMenuContext MenuContext;
{
MenuContext.AppendCommandList(CommandList);
MenuContext.AddExtender(MenuExtender);
{
ULevelViewportToolBarContext* ContextObject = NewObject<ULevelViewportToolBarContext>();
ContextObject->LevelViewportToolBarWidget = ConstCastSharedRef<SLevelViewportToolBar>(SharedThis(this));
MenuContext.AddObject(ContextObject);
}
{
ULevelViewportContext* const ContextObject = NewObject<ULevelViewportContext>();
ContextObject->LevelViewport = Viewport;
MenuContext.AddObject(ContextObject);
}
}
return UToolMenus::Get()->GenerateWidget(MenuName, MenuContext);
}
void SLevelViewportToolBar::FillShowMenu(UToolMenu* Menu) const
{
const FLevelViewportCommands& Actions = FLevelViewportCommands::Get();
{
{
FToolMenuSection& Section = Menu->AddSection("UseDefaultShowFlags");
Section.AddMenuEntry(Actions.UseDefaultShowFlags);
}
FShowFlagMenuCommands::Get().BuildShowFlagsMenu(Menu);
FText ShowAllLabel = LOCTEXT("ShowAllLabel", "Show All");
FText HideAllLabel = LOCTEXT("HideAllLabel", "Hide All");
FLevelEditorViewportClient& ViewClient = Viewport.Pin()->GetLevelViewportClient();
const UWorld* World = ViewClient.GetWorld();
{
FToolMenuSection& Section = Menu->AddSection("LevelViewportEditorShow", LOCTEXT("EditorShowHeader", "Editor"));
Section.AddEntry(UE::LevelEditor::CreateShowVolumesSubmenu());
Section.AddEntry(UE::LevelEditor::CreateShowLayersSubmenu());
Section.AddEntry(UE::LevelEditor::CreateShowSpritesSubmenu());
Section.AddEntry(UE::LevelEditor::CreateShowFoliageSubmenu());
// Show 'HLODs' sub-menu is dynamically generated when the user enters 'show' menu
if (World->IsPartitionedWorld())
{
Section.AddEntry(UE::LevelEditor::CreateShowHLODsSubmenu());
}
}
}
}
EVisibility SLevelViewportToolBar::GetViewModeOptionsVisibility() const
{
const FLevelEditorViewportClient& ViewClient = Viewport.Pin()->GetLevelViewportClient();
if (ViewClient.GetViewMode() == VMI_MeshUVDensityAccuracy || ViewClient.GetViewMode() == VMI_MaterialTextureScaleAccuracy || ViewClient.GetViewMode() == VMI_RequiredTextureResolution)
{
return EVisibility::SelfHitTestInvisible;
}
else
{
return EVisibility::Collapsed;
}
}
FText SLevelViewportToolBar::GetViewModeOptionsMenuLabel() const
{
Viewport.Pin()->OnFloatingButtonClicked();
const FLevelEditorViewportClient& ViewClient = Viewport.Pin()->GetLevelViewportClient();
return ::GetViewModeOptionsMenuLabel(ViewClient.GetViewMode());
}
TSharedRef<SWidget> SLevelViewportToolBar::GenerateViewModeOptionsMenu() const
{
Viewport.Pin()->OnFloatingButtonClicked();
FLevelEditorViewportClient& ViewClient = Viewport.Pin()->GetLevelViewportClient();
const UWorld* World = ViewClient.GetWorld();
return BuildViewModeOptionsMenu(Viewport.Pin()->GetCommandList(), ViewClient.GetViewMode(), World ? World->GetFeatureLevel() : GMaxRHIFeatureLevel, ViewClient.GetViewModeParamNameMap());
}
double SLevelViewportToolBar::OnGetHLODInEditorMaxDrawDistanceValue() const
{
IWorldPartitionEditorModule* WorldPartitionEditorModule = FModuleManager::GetModulePtr<IWorldPartitionEditorModule>("WorldPartitionEditor");
return WorldPartitionEditorModule ? WorldPartitionEditorModule->GetHLODInEditorMaxDrawDistance() : 0;
}
void SLevelViewportToolBar::OnHLODInEditorMaxDrawDistanceValueChanged(double NewValue) const
{
IWorldPartitionEditorModule* WorldPartitionEditorModule = FModuleManager::GetModulePtr<IWorldPartitionEditorModule>("WorldPartitionEditor");
if (WorldPartitionEditorModule)
{
WorldPartitionEditorModule->SetHLODInEditorMaxDrawDistance(NewValue);
GEditor->RedrawLevelEditingViewports(true);
}
}
TWeakObjectPtr<UWorld> SLevelViewportToolBar::GetWorld() const
{
if (Viewport.IsValid())
{
return Viewport.Pin()->GetWorld();
}
return NULL;
}
void SLevelViewportToolBar::FillViewMenu(UToolMenu* Menu)
{
if (!Menu)
{
return;
}
ULevelViewportContext* const ContextObject = NewObject<ULevelViewportContext>();
ContextObject->LevelViewport = Viewport;
Menu->Context.AddObject(ContextObject);
UE::LevelEditor::PopulateViewModesMenu(Menu);
}
float SLevelViewportToolBar::GetTransformToolbarWidth() const
{
if (TransformToolbar)
{
const float TransformToolbarWidth = TransformToolbar->GetDesiredSize().X;
if (TransformToolbar_CachedMaxWidth == 0.0f)
{
TransformToolbar_CachedMaxWidth = TransformToolbarWidth;
}
const float ToolbarWidthMinusPreviousTransformToolbar = GetDesiredSize().X - TransformToolbar_CachedMaxWidth;
const float ToolbarWidthEstimate = ToolbarWidthMinusPreviousTransformToolbar + TransformToolbarWidth;
const float ViewportToolBarWidth = static_cast<float>(GetCachedGeometry().GetLocalSize().X);
const float OverflowWidth = ToolbarWidthEstimate - ViewportToolBarWidth;
if (OverflowWidth > 0.0f)
{
// There isn't enough space in the viewport to show the toolbar!
// Try and shrink the transform toolbar (which has an overflow area) to make things fit
TransformToolbar_CachedMaxWidth = FMath::Max(FMath::Min(4.0f, TransformToolbarWidth), TransformToolbarWidth - OverflowWidth);
}
else
{
TransformToolbar_CachedMaxWidth = TransformToolbarWidth;
}
return TransformToolbar_CachedMaxWidth;
}
return 0.0f;
}
FReply SLevelViewportToolBar::OnRealtimeWarningClicked()
{
FLevelEditorViewportClient& ViewportClient = Viewport.Pin()->GetLevelViewportClient();
ViewportClient.SetRealtime(true);
return FReply::Handled();
}
EVisibility SLevelViewportToolBar::GetRealtimeWarningVisibility() const
{
const FEditorViewportClient& ViewportClient = Viewport.Pin()->GetLevelViewportClient();
const bool bWarn = UE::UnrealEd::ShouldShowViewportRealtimeWarning(ViewportClient);
return bWarn ? EVisibility::Visible : EVisibility::Collapsed;
}
FText SLevelViewportToolBar::GetScalabilityWarningLabel() const
{
const int32 QualityLevel = Scalability::GetQualityLevels().GetMinQualityLevel();
if (QualityLevel >= 0)
{
return FText::Format(LOCTEXT("ScalabilityWarning", "Scalability: {0}"), Scalability::GetScalabilityNameFromQualityLevel(QualityLevel));
}
return FText::GetEmpty();
}
EVisibility SLevelViewportToolBar::GetScalabilityWarningVisibility() const
{
return UE::UnrealEd::IsScalabilityWarningVisible() ? EVisibility::Visible : EVisibility::Collapsed;
}
TSharedRef<SWidget> SLevelViewportToolBar::GetScalabilityWarningMenuContent() const
{
return
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Menu.Background"))
[
SNew(SScalabilitySettings)
];
}
FLevelEditorViewportClient* ULevelViewportToolBarContext::GetLevelViewportClient() const
{
if (TSharedPtr<SLevelViewportToolBar> Toolbar = LevelViewportToolBarWidget.Pin())
{
return Toolbar->GetLevelViewportClient();
}
return nullptr;
}
#undef LOCTEXT_NAMESPACE