Files
UnrealEngineUWP/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp
Dmitriy Dyomin ba1b145186 Additional clean-up of obsolte levels module
[CL 2520615 by Dmitriy Dyomin in Main branch]
2015-04-22 00:24:23 -04:00

1412 lines
53 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "LevelEditor.h"
#include "ISourceControlModule.h"
#include "LevelEditorMenu.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
#include "SLevelEditor.h"
#include "LevelEditorActions.h"
#include "LevelEditorModesActions.h"
#include "SLevelViewport.h"
#include "LevelViewportTabContent.h"
#include "AssetSelection.h"
#include "LevelEditorContextMenu.h"
#include "LevelEditorToolBar.h"
#include "SLevelEditorToolBox.h"
#include "SLevelEditorModeContent.h"
#include "SLevelEditorBuildAndSubmit.h"
#include "Editor/UnrealEd/Public/Kismet2/DebuggerCommands.h"
#include "Editor/SceneOutliner/Public/SceneOutliner.h"
#include "Editor/Layers/Public/LayersModule.h"
#include "Editor/WorldBrowser/Public/WorldBrowserModule.h"
#include "Editor/ClassViewer/Public/ClassViewerModule.h"
#include "Toolkits/IToolkit.h"
#include "Toolkits/ToolkitManager.h"
#include "Editor/PropertyEditor/Public/PropertyEditorModule.h"
#include "Editor/ContentBrowser/Public/ContentBrowserModule.h"
#include "Editor/MainFrame/Public/MainFrame.h"
#include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h"
#include "Editor/Sequencer/Public/ISequencerModule.h"
#include "Editor/StatsViewer/Public/StatsViewerModule.h"
#include "EditorModes.h"
#include "IDocumentation.h"
#include "NewsFeed.h"
#include "TutorialMetaData.h"
#include "SDockTab.h"
#include "SActorDetails.h"
#include "ScopedTransaction.h"
#include "GameFramework/WorldSettings.h"
static const FName LevelEditorBuildAndSubmitTab("LevelEditorBuildAndSubmit");
static const FName LevelEditorStatsViewerTab("LevelEditorStatsViewer");
static const FName MainFrameModuleName("MainFrame");
static const FName NewsFeedModuleName("NewsFeed");
static const FName LevelEditorModuleName("LevelEditor");
static const FName WorldBrowserHierarchyTab("WorldBrowserHierarchy");
static const FName WorldBrowserDetailsTab("WorldBrowserDetails");
static const FName WorldBrowserCompositionTab("WorldBrowserComposition");
namespace LevelEditorConstants
{
/** The size of the thumbnail pool */
const int32 ThumbnailPoolSize = 32;
}
SLevelEditor::SLevelEditor()
: World(NULL)
{
const bool bAreRealTimeThumbnailsAllowed = false;
ThumbnailPool = MakeShareable(new FAssetThumbnailPool(LevelEditorConstants::ThumbnailPoolSize, bAreRealTimeThumbnailsAllowed));
}
void SLevelEditor::BindCommands()
{
LevelEditorCommands = MakeShareable( new FUICommandList );
const FLevelEditorCommands& Actions = FLevelEditorCommands::Get();
// Map UI commands to delegates that are executed when the command is handled by a keybinding or menu
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorModuleName );
// Append the list of the level editor commands for this instance with the global list of commands for all instances.
LevelEditorCommands->Append( LevelEditorModule.GetGlobalLevelEditorActions() );
// Append the list of global PlayWorld commands
LevelEditorCommands->Append( FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef() );
LevelEditorCommands->MapAction(
Actions.EditAssetNoConfirmMultiple,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::EditAsset_Clicked, EToolkitMode::Standalone, TWeakPtr< SLevelEditor >( SharedThis( this ) ), false ) );
LevelEditorCommands->MapAction(
Actions.EditAsset,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::EditAsset_Clicked, EToolkitMode::Standalone, TWeakPtr< SLevelEditor >( SharedThis( this ) ), true ) );
LevelEditorCommands->MapAction(
Actions.CheckOutProjectSettingsConfig,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::CheckOutProjectSettingsConfig ) );
LevelEditorCommands->MapAction(
Actions.OpenLevelBlueprint,
FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenLevelBlueprint, SharedThis( this ) ) );
LevelEditorCommands->MapAction(
Actions.CreateBlankBlueprintClass,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::CreateBlankBlueprintClass ) );
LevelEditorCommands->MapAction(
Actions.ConvertSelectionToBlueprintViaHarvest,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::HarvestSelectedActorsIntoBlueprintClass ),
FCanExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::CanHarvestSelectedActorsIntoBlueprintClass ) );
LevelEditorCommands->MapAction(
Actions.ConvertSelectionToBlueprintViaSubclass,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::SubclassSelectedActorIntoBlueprintClass ),
FCanExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::CanSubclassSelectedActorIntoBlueprintClass ) );
LevelEditorCommands->MapAction(
Actions.OpenContentBrowser,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::OpenContentBrowser ) );
LevelEditorCommands->MapAction(
Actions.OpenMarketplace,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::OpenMarketplace ) );
LevelEditorCommands->MapAction(
Actions.WorldProperties,
FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OnShowWorldProperties, SharedThis( this ) ) );
LevelEditorCommands->MapAction(
Actions.FocusAllViewportsToSelection,
FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ExecuteExecCommand, FString( TEXT("CAMERA ALIGN") ) )
);
}
void SLevelEditor::Construct( const SLevelEditor::FArguments& InArgs)
{
// Important: We use raw bindings here because we are releasing our binding in our destructor (where a weak pointer would be invalid)
// It's imperative that our delegate is removed in the destructor for the level editor module to play nicely with reloading.
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked< FLevelEditorModule >( LevelEditorModuleName );
LevelEditorModule.OnNotificationBarChanged().AddRaw( this, &SLevelEditor::ConstructNotificationBar );
GetMutableDefault<UEditorExperimentalSettings>()->OnSettingChanged().AddRaw(this, &SLevelEditor::HandleExperimentalSettingChanged);
BindCommands();
// We need to register when modes list changes so that we can refresh the auto generated commands.
FEditorModeRegistry::Get().OnRegisteredModesChanged().AddRaw(this, &SLevelEditor::RefreshEditorModeCommands);
// @todo This is a hack to get this working for now. This won't work with multiple worlds
GEditor->GetEditorWorldContext(true).AddRef(World);
FEditorDelegates::MapChange.AddRaw(this, &SLevelEditor::HandleEditorMapChange);
HandleEditorMapChange(MapChangeEventFlags::NewMap);
}
void SLevelEditor::Initialize( const TSharedRef<SDockTab>& OwnerTab, const TSharedRef<SWindow>& OwnerWindow )
{
// Bind the level editor tab's label to the currently loaded level name string in the main frame
OwnerTab->SetLabel( TAttribute<FText>( this, &SLevelEditor::GetTabTitle) );
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked< FLevelEditorModule >(LevelEditorModuleName);
LevelEditorModule.OnActorSelectionChanged().AddSP(this, &SLevelEditor::OnActorSelectionChanged);
TSharedRef<SWidget> Widget2 = RestoreContentArea( OwnerTab, OwnerWindow );
TSharedRef<SWidget> Widget1 = FLevelEditorMenu::MakeLevelEditorMenu( LevelEditorCommands, SharedThis(this) );
ChildSlot
[
SNew( SVerticalBox )
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SOverlay )
+SOverlay::Slot()
[
SNew( SBox )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("MainMenu")))
[
Widget1
]
]
// For platforms without a global menu bar we can put the perf. tools in the editor window's menu bar
#if !PLATFORM_MAC
+SOverlay::Slot()
.HAlign( HAlign_Right )
[
SAssignNew( NotificationBarBox, SHorizontalBox )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("PerformanceTools")))
]
#endif
]
#if PLATFORM_MAC
// Without the in-window menu bar, we need some space between the tab bar and tab contents
+SVerticalBox::Slot()
.AutoHeight()
[
SNew( SBox )
.HeightOverride( 1.0f )
]
#endif
+SVerticalBox::Slot()
.FillHeight( 1.0f )
[
Widget2
]
];
// For OS X we need to put it into the window's title bar since there's no per-window menu bar
#if PLATFORM_MAC
OwnerTab->SetRightContent(
SAssignNew( NotificationBarBox, SHorizontalBox )
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("PerformanceTools")))
);
#endif
ConstructNotificationBar();
OnLayoutHasChanged();
}
void SLevelEditor::ConstructNotificationBar()
{
NotificationBarBox->ClearChildren();
// level editor commands
NotificationBarBox->AddSlot()
.AutoWidth()
.Padding(5.0f, 0.0f, 0.0f, 0.0f)
[
FLevelEditorMenu::MakeNotificationBar( LevelEditorCommands, SharedThis(this ) )
];
#define SHOW_NEWS_FEED 0
#if SHOW_NEWS_FEED
// news feed button
INewsFeedModule& NewsFeedModule = FModuleManager::LoadModuleChecked<INewsFeedModule>(NewsFeedModuleName);
NotificationBarBox->AddSlot()
.AutoWidth()
.Padding(5.0f, 0.0f, 0.0f, 0.0f)
.VAlign(VAlign_Bottom)
[
NewsFeedModule.CreateNewsFeedButton()
];
#endif
// developer tools
const IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked<IMainFrameModule>(MainFrameModuleName);
NotificationBarBox->AddSlot()
.AutoWidth()
.Padding(5.0f, 0.0f, 0.0f, 0.0f)
[
MainFrameModule.MakeDeveloperTools()
];
}
SLevelEditor::~SLevelEditor()
{
// We're going away now, so make sure all toolkits that are hosted within this level editor are shut down
FToolkitManager::Get().OnToolkitHostDestroyed( this );
HostedToolkits.Reset();
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked< FLevelEditorModule >( LevelEditorModuleName );
LevelEditorModule.OnNotificationBarChanged().RemoveAll( this );
GetMutableDefault<UEditorExperimentalSettings>()->OnSettingChanged().RemoveAll( this );
GetMutableDefault<UEditorPerProjectUserSettings>()->OnUserSettingChanged().RemoveAll( this );
FEditorModeRegistry::Get().OnRegisteredModesChanged().RemoveAll( this );
FEditorDelegates::MapChange.RemoveAll(this);
GEditor->GetEditorWorldContext(true).RemoveRef(World);
}
FText SLevelEditor::GetTabTitle() const
{
const IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked< IMainFrameModule >( MainFrameModuleName );
const bool bIncludeGameName = false;
const bool bDirtyState = World && World->GetCurrentLevel()->GetOutermost()->IsDirty();
FFormatNamedArguments Args;
Args.Add( TEXT("LevelName"), FText::FromString( MainFrameModule.GetLoadedLevelName() ) );
Args.Add( TEXT("DirtyState"), bDirtyState ? FText::FromString( TEXT( "*" ) ) : FText::GetEmpty() );
return FText::Format( NSLOCTEXT("LevelEditor", "TabTitleSpacer", "{LevelName}{DirtyState}"), Args );
}
bool SLevelEditor::HasActivePlayInEditorViewport() const
{
// Search through all current viewport layouts
for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex )
{
TWeakPtr<FLevelViewportTabContent> ViewportTab = ViewportTabs[ TabIndex ];
if (ViewportTab.IsValid())
{
// Get all the viewports in the layout
const TArray< TSharedPtr< SLevelViewport > >* LevelViewports = ViewportTab.Pin()->GetViewports();
if (LevelViewports != NULL)
{
// Search for a viewport with a pie session
for( int32 ViewportIndex = 0; ViewportIndex < LevelViewports->Num(); ++ViewportIndex )
{
const TSharedPtr< SLevelViewport >& Viewport = (*LevelViewports)[ ViewportIndex ];
if( Viewport->IsPlayInEditorViewportActive() )
{
return true;
}
}
}
}
}
return false;
}
TSharedPtr<SLevelViewport> SLevelEditor::GetActiveViewport()
{
// The first visible viewport
TSharedPtr<SLevelViewport> FirstVisibleViewport;
// Search through all current viewport tabs
for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex )
{
TSharedPtr<FLevelViewportTabContent> ViewportTab = ViewportTabs[ TabIndex ].Pin();
if (ViewportTab.IsValid())
{
// Only check the viewports in the tab if its visible
if( ViewportTab->IsVisible() )
{
const TArray< TSharedPtr< SLevelViewport > >* LevelViewports = ViewportTab->GetViewports();
if (LevelViewports != NULL)
{
for( int32 ViewportIndex = 0; ViewportIndex < LevelViewports->Num(); ++ViewportIndex )
{
const TSharedPtr< SLevelViewport >& Viewport = (*LevelViewports)[ ViewportIndex ];
if( Viewport->IsInForegroundTab() )
{
if( &Viewport->GetLevelViewportClient() == GCurrentLevelEditingViewportClient )
{
// If the viewport is visible and is also the current level editing viewport client
// return it as the active viewport
return Viewport;
}
else if( !FirstVisibleViewport.IsValid() )
{
// If there is no current first visible viewport set it now
// We will return this viewport if the current level editing viewport client is not visible
FirstVisibleViewport = Viewport;
}
}
}
}
}
}
}
// Return the first visible viewport if we found one. This can be null if we didn't find any visible viewports
return FirstVisibleViewport;
}
TSharedPtr<FLevelViewportTabContent> SLevelEditor::GetActiveViewportTab()
{
// The first visible viewport
TSharedPtr<FLevelViewportTabContent> FirstVisibleViewportTab;
// Search through all current viewport tabs
for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex )
{
TSharedPtr<FLevelViewportTabContent> ViewportTab = ViewportTabs[ TabIndex ].Pin();
if (ViewportTab.IsValid())
{
// Only check the viewports in the tab if its visible
if( ViewportTab->IsVisible() )
{
const TArray< TSharedPtr< SLevelViewport > >* LevelViewports = ViewportTab->GetViewports();
if (LevelViewports != NULL)
{
for( int32 ViewportIndex = 0; ViewportIndex < LevelViewports->Num(); ++ViewportIndex )
{
const TSharedPtr< SLevelViewport >& Viewport = (*LevelViewports)[ ViewportIndex ];
if( Viewport->IsVisible() )
{
if( &Viewport->GetLevelViewportClient() == GCurrentLevelEditingViewportClient )
{
// If the viewport is visible and is also the current level editing viewport client
// return it as the active viewport
return ViewportTab;
}
else if( !FirstVisibleViewportTab.IsValid() )
{
// If there is no current first visible viewport set it now
// We will return this viewport tab if the current level editing viewport client is not visible
FirstVisibleViewportTab = ViewportTab;
}
}
}
}
}
}
}
// Return the first visible viewport tab if we found one. This can be null if we didn't find any visible viewports
return FirstVisibleViewportTab;
}
TSharedRef< SWidget > SLevelEditor::GetParentWidget()
{
return AsShared();
}
void SLevelEditor::BringToFront()
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorModuleName );
TSharedPtr<SDockTab> LevelEditorTab = LevelEditorModule.GetLevelEditorInstanceTab().Pin();
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
if (LevelEditorTabManager.IsValid() && LevelEditorTab.IsValid())
{
LevelEditorTabManager->DrawAttention( LevelEditorTab.ToSharedRef() );
}
}
TSharedRef< SDockTabStack > SLevelEditor::GetTabSpot( const EToolkitTabSpot::Type TabSpot )
{
ensureMsgf(false, TEXT("Unimplemented"));
return TSharedPtr<SDockTabStack>().ToSharedRef();
}
void SLevelEditor::OnToolkitHostingStarted( const TSharedRef< class IToolkit >& Toolkit )
{
// @todo toolkit minor: We should consider only allowing a single toolkit for a specific asset editor type hosted
// at once. OR, we allow multiple to be hosted, but we only show tabs for one at a time (fast switching.)
// Otherwise, it's going to be a huge cluster trying to distinguish tabs for different assets of the same type
// of editor
TSharedPtr<FTabManager> LevelEditorTabManager = GetTabManager();
HostedToolkits.Add( Toolkit );
Toolkit->RegisterTabSpawners( LevelEditorTabManager.ToSharedRef() );
// @todo toolkit minor: We should clean out old invalid array entries from time to time
// Tell all of the toolkit area widgets about the new toolkit
for( auto ToolBoxIt = ToolBoxTabs.CreateIterator(); ToolBoxIt; ++ToolBoxIt )
{
if( ToolBoxIt->IsValid() )
{
ToolBoxIt->Pin()->OnToolkitHostingStarted( Toolkit );
}
}
// Tell all of the toolkit area widgets about the new toolkit
for( auto ToolBoxIt = ModesTabs.CreateIterator(); ToolBoxIt; ++ToolBoxIt )
{
if( ToolBoxIt->IsValid() )
{
ToolBoxIt->Pin()->OnToolkitHostingStarted( Toolkit );
}
}
}
void SLevelEditor::OnToolkitHostingFinished( const TSharedRef< class IToolkit >& Toolkit )
{
TSharedPtr<FTabManager> LevelEditorTabManager = GetTabManager();
Toolkit->UnregisterTabSpawners(LevelEditorTabManager.ToSharedRef());
// Tell all of the toolkit area widgets that our toolkit was removed
for( auto ToolBoxIt = ToolBoxTabs.CreateIterator(); ToolBoxIt; ++ToolBoxIt )
{
if( ToolBoxIt->IsValid() )
{
ToolBoxIt->Pin()->OnToolkitHostingFinished( Toolkit );
}
}
// Tell all of the toolkit area widgets that our toolkit was removed
for( auto ToolBoxIt = ModesTabs.CreateIterator(); ToolBoxIt; ++ToolBoxIt )
{
if( ToolBoxIt->IsValid() )
{
ToolBoxIt->Pin()->OnToolkitHostingFinished( Toolkit );
}
}
HostedToolkits.Remove( Toolkit );
// @todo toolkit minor: If user clicks X on all opened world-centric toolkit tabs, should we exit that toolkit automatically?
// Feel 50/50 about this. It's totally valid to use the "Save" menu even after closing tabs, etc. Plus, you can spawn the tabs back up using the tab area down-down menu.
}
TSharedRef<FTabManager> SLevelEditor::GetTabManager() const
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorModuleName );
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
return LevelEditorTabManager.ToSharedRef();
}
TSharedRef<SDockTab> SLevelEditor::SummonDetailsPanel( FName TabIdentifier )
{
TSharedRef<SActorDetails> ActorDetails = SNew(SActorDetails, TabIdentifier, LevelEditorCommands);
const FText Label = NSLOCTEXT( "LevelEditor", "DetailsTabTitle", "Details" );
TSharedRef<SDockTab> DocTab = SNew(SDockTab)
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Details" ) )
.Label( Label )
.ToolTip( IDocumentation::Get()->CreateToolTip( Label, nullptr, "Shared/LevelEditor", "DetailsTab" ) )
[
SNew( SBox )
.AddMetaData<FTutorialMetaData>(FTutorialMetaData(TEXT("ActorDetails"), TEXT("LevelEditorSelectionDetails")))
[
ActorDetails
]
];
AllActorDetailPanels.Add( ActorDetails );
return DocTab;
}
/** Method to call when a tab needs to be spawned by the FLayoutService */
TSharedRef<SDockTab> SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Args, FName TabIdentifier, FString InitializationPayload )
{
if( TabIdentifier == TEXT("LevelEditorViewport" ) )
{
return this->BuildViewportTab( NSLOCTEXT("LevelViewportTypes", "LevelEditorViewport", "Viewport 1"), TEXT("Viewport 1"), InitializationPayload );
}
else if( TabIdentifier == TEXT("LevelEditorViewport_Clone1" ) )
{
return this->BuildViewportTab( NSLOCTEXT("LevelViewportTypes", "LevelEditorViewport_Clone1", "Viewport 2"), TEXT("Viewport 2"), InitializationPayload );
}
else if( TabIdentifier == TEXT("LevelEditorViewport_Clone2" ) )
{
return this->BuildViewportTab( NSLOCTEXT("LevelViewportTypes", "LevelEditorViewport_Clone2", "Viewport 3"), TEXT("Viewport 3"), InitializationPayload );
}
else if( TabIdentifier == TEXT("LevelEditorViewport_Clone3" ) )
{
return this->BuildViewportTab( NSLOCTEXT("LevelViewportTypes", "LevelEditorViewport_Clone3", "Viewport 4"), TEXT("Viewport 4"), InitializationPayload );
}
else if( TabIdentifier == TEXT( "LevelEditorToolBar") )
{
return SNew( SDockTab )
.Label( NSLOCTEXT("LevelEditor", "ToolBarTabTitle", "Toolbar") )
.ShouldAutosize(true)
.Icon( FEditorStyle::GetBrush("ToolBar.Icon") )
[
SNew(SHorizontalBox)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("LevelEditorToolbar")))
+SHorizontalBox::Slot()
.FillWidth(1)
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Left)
[
FLevelEditorToolBar::MakeLevelEditorToolBar( LevelEditorCommands.ToSharedRef(), SharedThis(this) )
]
];
}
else if( TabIdentifier == TEXT("LevelEditorSelectionDetails") || TabIdentifier == TEXT("LevelEditorSelectionDetails2") || TabIdentifier == TEXT("LevelEditorSelectionDetails3") || TabIdentifier == TEXT("LevelEditorSelectionDetails4") )
{
TSharedRef<SDockTab> DetailsPanel = SummonDetailsPanel( TabIdentifier );
GUnrealEd->UpdateFloatingPropertyWindows();
return DetailsPanel;
}
else if( TabIdentifier == TEXT("LevelEditorToolBox") )
{
TSharedRef<SLevelEditorToolBox> NewToolBox =
SNew( SLevelEditorToolBox, SharedThis( this ) )
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() );
ToolBoxTabs.Add( NewToolBox );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Modes" ) )
.Label( NSLOCTEXT( "LevelEditor", "ToolsTabTitle", "Modes" ) )
[
SNew( SBox )
.AddMetaData<FTutorialMetaData>(FTutorialMetaData(TEXT("ToolsPanel"), TEXT("LevelEditorToolBox")))
[
NewToolBox
]
];
}
else if( TabIdentifier == LevelEditorBuildAndSubmitTab )
{
TSharedRef<SLevelEditorBuildAndSubmit> NewBuildAndSubmit = SNew( SLevelEditorBuildAndSubmit, SharedThis( this ) );
TSharedRef<SDockTab> NewTab = SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.BuildAndSubmit" ) )
.Label( NSLOCTEXT("LevelEditor", "BuildAndSubmitTabTitle", "Build and Submit") )
[
NewBuildAndSubmit
];
NewBuildAndSubmit->SetDockableTab(NewTab);
return NewTab;
}
else if( TabIdentifier == TEXT("LevelEditorSceneOutliner") )
{
SceneOutliner::FInitializationOptions InitOptions;
InitOptions.Mode = ESceneOutlinerMode::ActorBrowsing;
{
TWeakPtr<SLevelEditor> WeakLevelEditor = SharedThis(this);
InitOptions.DefaultMenuExtender = MakeShareable(new FExtender);
InitOptions.DefaultMenuExtender->AddMenuExtension(
"MainSection", EExtensionHook::Before, GetLevelEditorActions(),
FMenuExtensionDelegate::CreateStatic([](FMenuBuilder& MenuBuilder, TWeakPtr<SLevelEditor> InWeakLevelEditor){
// Only extend the menu if we have actors selected
if (GEditor->GetSelectedActorCount() > 0)
{
FLevelEditorContextMenu::FillMenu(MenuBuilder, InWeakLevelEditor, LevelEditorMenuContext::NonViewport, TSharedPtr<FExtender>());
}
}, WeakLevelEditor)
);
}
FText Label = NSLOCTEXT( "LevelEditor", "SceneOutlinerTabTitle", "World Outliner" );
FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::Get().LoadModuleChecked<FSceneOutlinerModule>( "SceneOutliner" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Outliner" ) )
.Label( Label )
.ToolTip( IDocumentation::Get()->CreateToolTip( Label, nullptr, "Shared/LevelEditor", "SceneOutlinerTab" ) )
[
SNew(SBorder)
.Padding(4)
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
.AddMetaData<FTutorialMetaData>(FTutorialMetaData(TEXT("SceneOutliner"), TEXT("LevelEditorSceneOutliner")))
[
SceneOutlinerModule.CreateSceneOutliner(
InitOptions,
FOnActorPicked() /* Not used for outliner when in browsing mode */ )
]
];
}
else if( TabIdentifier == TEXT("LevelEditorLayerBrowser") )
{
FLayersModule& LayersModule = FModuleManager::LoadModuleChecked<FLayersModule>( "Layers" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Layers" ) )
.Label( NSLOCTEXT("LevelEditor", "LayersTabTitle", "Layers") )
[
SNew(SBorder)
.Padding( 0 )
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
.AddMetaData<FTutorialMetaData>(FTutorialMetaData(TEXT("LayerBrowser"), TEXT("LevelEditorLayerBrowser")))
[
LayersModule.CreateLayerBrowser()
]
];
}
else if( TabIdentifier == WorldBrowserHierarchyTab )
{
FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked<FWorldBrowserModule>( "WorldBrowser" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowser" ) )
.Label( NSLOCTEXT("LevelEditor", "WorldBrowserHierarchyTabTitle", "Levels") )
[
WorldBrowserModule.CreateWorldBrowserHierarchy()
];
}
else if( TabIdentifier == WorldBrowserDetailsTab )
{
FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked<FWorldBrowserModule>( "WorldBrowser" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowserDetails" ) )
.Label( NSLOCTEXT("LevelEditor", "WorldBrowserDetailsTabTitle", "Level Details") )
[
WorldBrowserModule.CreateWorldBrowserDetails()
];
}
else if( TabIdentifier == WorldBrowserCompositionTab )
{
FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked<FWorldBrowserModule>( "WorldBrowser" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowserComposition" ) )
.Label( NSLOCTEXT("LevelEditor", "WorldBrowserCompositionTabTitle", "World Composition") )
[
WorldBrowserModule.CreateWorldBrowserComposition()
];
}
else if( TabIdentifier == TEXT("Sequencer") && FParse::Param(FCommandLine::Get(), TEXT("sequencer")) )
{
// @todo remove when world-centric mode is added
SequencerTab = SNew(SDockTab)
.Icon( FEditorStyle::GetBrush("Sequencer.Tabs.SequencerMain") )
.Label( NSLOCTEXT("Sequencer", "SequencerMainTitle", "Sequencer") )
[
SNullWidget::NullWidget
];
return SequencerTab.ToSharedRef();
}
else if( TabIdentifier == LevelEditorStatsViewerTab )
{
FStatsViewerModule& StatsViewerModule = FModuleManager::Get().LoadModuleChecked<FStatsViewerModule>( "StatsViewer" );
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.StatsViewer" ) )
.Label( NSLOCTEXT("LevelEditor", "StatsViewerTabTitle", "Statistics") )
[
StatsViewerModule.CreateStatsViewer()
];
}
else if ( TabIdentifier == "WorldSettingsTab" )
{
FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs( false, false, true, FDetailsViewArgs::HideNameArea, false, GUnrealEd );
DetailsViewArgs.bShowActorLabel = false;
WorldSettingsView = PropPlugin.CreateDetailView( DetailsViewArgs );
if (GetWorld() != NULL)
{
WorldSettingsView->SetObject(GetWorld()->GetWorldSettings());
}
return SNew( SDockTab )
.Icon( FEditorStyle::GetBrush( "LevelEditor.WorldProperties.Tab" ) )
.Label( NSLOCTEXT("LevelEditor", "WorldSettingsTabTitle", "World Settings") )
.AddMetaData<FTutorialMetaData>(FTutorialMetaData(TEXT("WorldSettings"), TEXT("WorldSettingsTab")))
[
WorldSettingsView.ToSharedRef()
];
}
return SNew(SDockTab);
}
void SLevelEditor::InvokeTab( FName TabID )
{
TSharedPtr<FTabManager> LevelEditorTabManager = GetTabManager();
LevelEditorTabManager->InvokeTab(TabID);
}
void SLevelEditor::SyncDetailsToSelection()
{
static const FName DetailsTabIdentifiers[] = { "LevelEditorSelectionDetails", "LevelEditorSelectionDetails2", "LevelEditorSelectionDetails3", "LevelEditorSelectionDetails4" };
FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FName FirstClosedDetailsTabIdentifier;
// First see if there is an already open details view that can handle the request
// For instance, if "Details 3" is open, we don't want to open "Details 2" to handle this
for(const FName& DetailsTabIdentifier : DetailsTabIdentifiers)
{
TSharedPtr<IDetailsView> DetailsView = PropPlugin.FindDetailView(DetailsTabIdentifier);
if(!DetailsView.IsValid())
{
// Track the first closed details view in case no currently open ones can handle our request
if(FirstClosedDetailsTabIdentifier.IsNone())
{
FirstClosedDetailsTabIdentifier = DetailsTabIdentifier;
}
continue;
}
if(DetailsView->IsUpdatable() && !DetailsView->IsLocked())
{
InvokeTab(DetailsTabIdentifier);
return;
}
}
// If we got this far then there were no open details views, so open the first available one
if(!FirstClosedDetailsTabIdentifier.IsNone())
{
InvokeTab(FirstClosedDetailsTabIdentifier);
}
}
/** Builds a viewport tab. */
TSharedRef<SDockTab> SLevelEditor::BuildViewportTab( const FText& Label, const FString LayoutId, const FString& InitializationPayload )
{
// The tab must be created before the viewport layout because the layout needs them
TSharedRef< SDockTab > DockableTab =
SNew(SDockTab)
.Label(Label)
.Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Viewports"))
.OnTabClosed(this, &SLevelEditor::OnViewportTabClosed);
// Create a new tab
TSharedRef<FLevelViewportTabContent> ViewportTabContent = MakeShareable(new FLevelViewportTabContent());
// Track the viewport
CleanupPointerArray(ViewportTabs);
ViewportTabs.Add(ViewportTabContent);
ViewportTabContent->Initialize(SharedThis(this), DockableTab, LayoutId);
// Restore transient camera position
RestoreViewportTabInfo(ViewportTabContent);
return DockableTab;
}
void SLevelEditor::OnViewportTabClosed(TSharedRef<SDockTab> ClosedTab)
{
TWeakPtr<FLevelViewportTabContent>* const ClosedTabContent = ViewportTabs.FindByPredicate([&ClosedTab](TWeakPtr<FLevelViewportTabContent>& InPotentialElement) -> bool
{
TSharedPtr<FLevelViewportTabContent> ViewportTabContent = InPotentialElement.Pin();
return ViewportTabContent.IsValid() && ViewportTabContent->BelongsToTab(ClosedTab);
});
if(ClosedTabContent)
{
TSharedPtr<FLevelViewportTabContent> ClosedTabContentPin = ClosedTabContent->Pin();
if(ClosedTabContentPin.IsValid())
{
SaveViewportTabInfo(ClosedTabContentPin.ToSharedRef());
// Untrack the viewport
ViewportTabs.Remove(ClosedTabContentPin);
CleanupPointerArray(ViewportTabs);
}
}
}
void SLevelEditor::SaveViewportTabInfo(TSharedRef<const FLevelViewportTabContent> ViewportTabContent)
{
const TArray<TSharedPtr<SLevelViewport>>* const Viewports = ViewportTabContent->GetViewports();
if(Viewports)
{
const FString& LayoutId = ViewportTabContent->GetLayoutString();
for(const auto& Viewport : *Viewports)
{
//@todo there could potentially be more than one of the same viewport type. This effectively takes the last one of a specific type
const FLevelEditorViewportClient& LevelViewportClient = Viewport->GetLevelViewportClient();
const FString Key = FString::Printf(TEXT("%s[%d]"), *LayoutId, static_cast<int32>(LevelViewportClient.ViewportType));
TransientEditorViews.Add(
Key, FLevelViewportInfo(
LevelViewportClient.GetViewLocation(),
LevelViewportClient.GetViewRotation(),
LevelViewportClient.GetOrthoZoom()
)
);
}
}
}
void SLevelEditor::RestoreViewportTabInfo(TSharedRef<FLevelViewportTabContent> ViewportTabContent) const
{
const TArray<TSharedPtr<SLevelViewport>>* const Viewports = ViewportTabContent->GetViewports();
if(Viewports)
{
const FString& LayoutId = ViewportTabContent->GetLayoutString();
for(const auto& Viewport : *Viewports)
{
FLevelEditorViewportClient& LevelViewportClient = Viewport->GetLevelViewportClient();
bool bInitializedOrthoViewport = false;
for (int32 ViewportType = 0; ViewportType < LVT_MAX; ViewportType++)
{
if (ViewportType == LVT_Perspective || !bInitializedOrthoViewport)
{
const FString Key = FString::Printf(TEXT("%s[%d]"), *LayoutId, ViewportType);
const FLevelViewportInfo* const TransientEditorView = TransientEditorViews.Find(Key);
if (TransientEditorView)
{
LevelViewportClient.SetInitialViewTransform(
static_cast<ELevelViewportType>(ViewportType),
TransientEditorView->CamPosition,
TransientEditorView->CamRotation,
TransientEditorView->CamOrthoZoom
);
if (ViewportType != LVT_Perspective)
{
bInitializedOrthoViewport = true;
}
}
}
}
}
}
}
void SLevelEditor::ResetViewportTabInfo()
{
TransientEditorViews.Reset();
}
TSharedRef<SWidget> SLevelEditor::RestoreContentArea( const TSharedRef<SDockTab>& OwnerTab, const TSharedRef<SWindow>& OwnerWindow )
{
const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure();
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( LevelEditorModuleName );
LevelEditorModule.SetLevelEditorTabManager(OwnerTab);
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
// Register Level Editor tab spawners
{
{
const FText ViewportTooltip = NSLOCTEXT("LevelEditorTabs", "LevelEditorViewportTooltip", "Open a Viewport tab. Use this to view and edit the current level.");
const FSlateIcon ViewportIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports");
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorViewport", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorViewport"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorViewport", "Viewport 1"))
.SetTooltipText(ViewportTooltip)
.SetGroup( MenuStructure.GetLevelEditorViewportsCategory() )
.SetIcon(ViewportIcon);
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorViewport_Clone1", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorViewport_Clone1"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorViewport_Clone1", "Viewport 2"))
.SetTooltipText(ViewportTooltip)
.SetGroup( MenuStructure.GetLevelEditorViewportsCategory() )
.SetIcon(ViewportIcon);
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorViewport_Clone2", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorViewport_Clone2"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorViewport_Clone2", "Viewport 3"))
.SetTooltipText(ViewportTooltip)
.SetGroup( MenuStructure.GetLevelEditorViewportsCategory() )
.SetIcon(ViewportIcon);
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorViewport_Clone3", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorViewport_Clone3"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorViewport_Clone3", "Viewport 4"))
.SetTooltipText(ViewportTooltip)
.SetGroup( MenuStructure.GetLevelEditorViewportsCategory() )
.SetIcon(ViewportIcon);
}
{
const FSlateIcon ToolbarIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Toolbar");
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBar", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBar"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBar", "Toolbar"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBarTooltipText", "Open the Toolbar tab, which provides access to the most common / important actions."))
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( ToolbarIcon );
}
{
const FText DetailsTooltip = NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetailsTooltip", "Open a Details tab. Use this to view and edit properties of the selected object(s).");
const FSlateIcon DetailsIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details");
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails", "Details 1"))
.SetTooltipText(DetailsTooltip)
.SetGroup( MenuStructure.GetLevelEditorDetailsCategory() )
.SetIcon( DetailsIcon );
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails2", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails2"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails2", "Details 2"))
.SetTooltipText(DetailsTooltip)
.SetGroup( MenuStructure.GetLevelEditorDetailsCategory() )
.SetIcon( DetailsIcon );
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails3", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails3"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails3", "Details 3"))
.SetTooltipText(DetailsTooltip)
.SetGroup( MenuStructure.GetLevelEditorDetailsCategory() )
.SetIcon( DetailsIcon );
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails4", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails4"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails4", "Details 4"))
.SetTooltipText(DetailsTooltip)
.SetGroup( MenuStructure.GetLevelEditorDetailsCategory() )
.SetIcon( DetailsIcon );
}
const FSlateIcon ToolsIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Modes" );
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBox", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>( this, &SLevelEditor::SpawnLevelEditorTab, FName( "LevelEditorToolBox" ), FString() ) )
.SetDisplayName( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBox", "Modes" ) )
.SetTooltipText( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes." ) )
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( ToolsIcon );
{
const FSlateIcon OutlinerIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Outliner");
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSceneOutliner", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSceneOutliner"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSceneOutliner", "World Outliner"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorSceneOutlinerTooltipText", "Open the World Outliner tab, which provides a searchable and filterable list of all actors in the world."))
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( OutlinerIcon );
}
{
const FSlateIcon LayersIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Layers");
LevelEditorTabManager->RegisterTabSpawner( "LevelEditorLayerBrowser", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorLayerBrowser"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorLayerBrowser", "Layers"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorLayerBrowserTooltipText", "Open the Layers tab. Use this to manage which actors in the world belong to which layers."))
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( LayersIcon );
}
{
LevelEditorTabManager->RegisterTabSpawner( WorldBrowserHierarchyTab, FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserHierarchyTab, FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserHierarchy", "Levels"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "WorldBrowserHierarchyTooltipText", "Open the Levels tab. Use this to manage the levels in the current project."))
.SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() )
.SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowser") );
LevelEditorTabManager->RegisterTabSpawner( WorldBrowserDetailsTab, FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserDetailsTab, FString()) )
.SetMenuType( ETabSpawnerMenuType::Hidden )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserDetails", "Level Details"))
.SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() )
.SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowserDetails") );
LevelEditorTabManager->RegisterTabSpawner( WorldBrowserCompositionTab, FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserCompositionTab, FString()) )
.SetMenuType( ETabSpawnerMenuType::Hidden )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserComposition", "World Composition"))
.SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() )
.SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowserComposition") );
}
{
const FSlateIcon StatsViewerIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.StatsViewer");
LevelEditorTabManager->RegisterTabSpawner( LevelEditorStatsViewerTab, FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, LevelEditorStatsViewerTab, FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorStatsViewer", "Statistics"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorStatsViewerTooltipText", "Open the Statistics tab, in order to see data pertaining to lighting, textures and primitives."))
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( StatsViewerIcon );
}
// @todo remove when world-centric mode is added
if (FParse::Param(FCommandLine::Get(), TEXT("sequencer")))
{
LevelEditorTabManager->RegisterTabSpawner( "Sequencer", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("Sequencer"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "Sequencer", "Sequencer"))
.SetGroup( MenuStructure.GetLevelEditorCategory() );
}
{
const FSlateIcon WorldPropertiesIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.WorldProperties.Tab");
LevelEditorTabManager->RegisterTabSpawner( "WorldSettingsTab", FOnSpawnTab::CreateSP<SLevelEditor, FName, FString>(this, &SLevelEditor::SpawnLevelEditorTab, FName("WorldSettingsTab"), FString()) )
.SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldSettings", "World Settings"))
.SetTooltipText(NSLOCTEXT("LevelEditorTabs", "WorldSettingsTooltipText", "Open the World Settings tab, in which global properties of the level can be viewed and edited."))
.SetGroup( MenuStructure.GetLevelEditorCategory() )
.SetIcon( WorldPropertiesIcon );
}
}
// Rebuild the editor mode commands and their tab spawners before we restore the layout,
// or there wont be any tab spawners for the modes.
RefreshEditorModeCommands();
const TSharedRef<FTabManager::FLayout> Layout = FLayoutSaveRestore::LoadFromConfig(GEditorLayoutIni,
FTabManager::NewLayout( "LevelEditor_Layout_v1.1" )
->AddArea
(
FTabManager::NewPrimaryArea()
->SetOrientation( Orient_Horizontal )
->Split
(
FTabManager::NewSplitter()
->SetOrientation( Orient_Vertical )
->SetSizeCoefficient( 1 )
->Split
(
FTabManager::NewSplitter()
->SetSizeCoefficient( .75f )
->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient( 0.3f )
->AddTab( "LevelEditorToolBox", ETabState::OpenedTab )
)
->Split
(
FTabManager::NewSplitter()
->SetOrientation(Orient_Vertical)
->SetSizeCoefficient( 1.15f )
->Split
(
FTabManager::NewStack()
->SetHideTabWell(true)
->AddTab("LevelEditorToolBar", ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetHideTabWell(true)
->SetSizeCoefficient( 1.0f )
->AddTab("LevelEditorViewport", ETabState::OpenedTab)
)
)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(.4)
->AddTab("ContentBrowserTab1", ETabState::OpenedTab)
->AddTab("OutputLog", ETabState::ClosedTab)
)
)
->Split
(
FTabManager::NewSplitter()
->SetSizeCoefficient(0.25f)
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab("LevelEditorSceneOutliner", ETabState::OpenedTab)
->AddTab("LevelEditorLayerBrowser", ETabState::ClosedTab)
)
->Split
(
FTabManager::NewStack()
->AddTab("LevelEditorSelectionDetails", ETabState::OpenedTab)
->AddTab("WorldSettingsTab", ETabState::ClosedTab)
->SetForegroundTab(FName("LevelEditorSelectionDetails"))
)
)
));
return LevelEditorTabManager->RestoreFrom( Layout, OwnerWindow ).ToSharedRef();
}
void SLevelEditor::HandleExperimentalSettingChanged(FName PropertyName)
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
LevelEditorTabManager->UpdateMainMenu(true);
}
FName SLevelEditor::GetEditorModeTabId( FEditorModeID ModeID )
{
return FName(*(FString("EditorMode.Tab.") + ModeID.ToString()));
}
void SLevelEditor::ToggleEditorMode( FEditorModeID ModeID )
{
// *Important* - activate the mode first since FEditorModeTools::DeactivateMode will
// activate the default mode when the stack becomes empty, resulting in multiple active visible modes.
GLevelEditorModeTools().ActivateMode( ModeID );
// Find and disable any other 'visible' modes since we only ever allow one of those active at a time.
TArray<FEdMode*> ActiveModes;
GLevelEditorModeTools().GetActiveModes( ActiveModes );
for ( FEdMode* Mode : ActiveModes )
{
if ( Mode->GetID() != ModeID && Mode->GetModeInfo().bVisible )
{
GLevelEditorModeTools().DeactivateMode( Mode->GetID() );
}
}
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( "LevelEditor" );
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
TSharedRef<SDockTab> ToolboxTab = LevelEditorTabManager->InvokeTab( FTabId("LevelEditorToolBox") );
//// If it's already active deactivate the mode
//if ( GLevelEditorModeTools().IsModeActive( ModeID ) )
//{
// //GLevelEditorModeTools().DeactivateAllModes();
// //GLevelEditorModeTools().DeactivateMode( ModeID );
//}
//else // Activate the mode and create the tab for it.
//{
// GLevelEditorModeTools().DeactivateAllModes();
// GLevelEditorModeTools().ActivateMode( ModeID );
// //FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( "LevelEditor" );
// //TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
// //TSharedRef<SDockTab> ToolboxTab = LevelEditorTabManager->InvokeTab( GetEditorModeTabId( ModeID ) );
//}
}
bool SLevelEditor::IsModeActive( FEditorModeID ModeID )
{
// The level editor changes the default mode to placement
if ( ModeID == FBuiltinEditorModes::EM_Placement )
{
// Only return true if this is the *only* active mode
TArray<FEdMode*> ActiveModes;
GLevelEditorModeTools().GetActiveModes(ActiveModes);
for( FEdMode* Mode : ActiveModes )
{
if( Mode->GetModeInfo().bVisible && Mode->GetID() != FBuiltinEditorModes::EM_Placement )
{
return false;
}
}
}
return GLevelEditorModeTools().IsModeActive( ModeID );
}
void SLevelEditor::RefreshEditorModeCommands()
{
FLevelEditorModesCommands::Unregister();
FLevelEditorModesCommands::Register();
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>( "LevelEditor" );
const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure();
TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
// We need to remap all the actions to commands.
const FLevelEditorModesCommands& Commands = FLevelEditorModesCommands::Get();
int commandIndex = 0;
for( const FEditorModeInfo& Mode : FEditorModeRegistry::Get().GetSortedModeInfo() )
{
// If the mode isn't visible don't create a menu option for it.
if( !Mode.bVisible )
{
continue;
}
FName EditorModeTabName = GetEditorModeTabId( Mode.ID );
FName EditorModeCommandName = FName(*(FString("EditorMode.") + Mode.ID.ToString()));
TSharedPtr<FUICommandInfo> EditorModeCommand =
FInputBindingManager::Get().FindCommandInContext(Commands.GetContextName(), EditorModeCommandName);
// If a command isn't yet registered for this mode, we need to register one.
if ( ensure(EditorModeCommand.IsValid()) )
{
LevelEditorCommands->MapAction(
Commands.EditorModeCommands[commandIndex],
FExecuteAction::CreateStatic( &SLevelEditor::ToggleEditorMode, Mode.ID ),
FCanExecuteAction(),
FIsActionChecked::CreateStatic( &SLevelEditor::IsModeActive, Mode.ID ));
}
commandIndex++;
}
for( const auto& ToolBoxTab : ToolBoxTabs )
{
auto Tab = ToolBoxTab.Pin();
if( Tab.IsValid() )
{
Tab->OnEditorModeCommandsChanged();
}
}
}
FReply SLevelEditor::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
// Check to see if any of the actions for the level editor can be processed by the current event
// If we are in debug mode do not process commands
if( FSlateApplication::Get().IsNormalExecution() )
{
// Figure out if any of our toolkit's tabs is the active tab. This is important because we want
// the toolkit to have it's own keybinds (which may overlap the level editor's keybinds or any
// other toolkit). When a toolkit tab is active, we give that toolkit a chance to process
// commands instead of the level editor.
TSharedPtr< IToolkit > ActiveToolkit;
{
const TSharedPtr<SDockableTab> CurrentActiveTab;// = FSlateApplication::xxxGetGlobalTabManager()->GetActiveTab();
for( auto HostedToolkitIt = HostedToolkits.CreateConstIterator(); HostedToolkitIt && !ActiveToolkit.IsValid(); ++HostedToolkitIt )
{
const auto& CurToolkit = *HostedToolkitIt;
if( CurToolkit.IsValid() )
{
// Iterate over this toolkits spawned tabs
const auto& ToolkitTabsInSpots = CurToolkit->GetToolkitTabsInSpots();
for( auto CurSpotIt( ToolkitTabsInSpots.CreateConstIterator() ); CurSpotIt && !ActiveToolkit.IsValid(); ++CurSpotIt )
{
const auto& TabsForSpot = CurSpotIt.Value();
for( auto CurTabIt( TabsForSpot.CreateConstIterator() ); CurTabIt; ++CurTabIt )
{
const auto& PinnedTab = CurTabIt->Pin();
if( PinnedTab.IsValid() )
{
if( PinnedTab == CurrentActiveTab )
{
ActiveToolkit = CurToolkit;
}
}
}
}
}
}
}
if( ActiveToolkit.IsValid() )
{
// A toolkit tab is active, so direct all command processing to it
if( ActiveToolkit->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
}
else
{
// No toolkit tab is active, so let the level editor have a chance at the keystroke
if( LevelEditorCommands->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
}
}
return FReply::Unhandled();
}
FReply SLevelEditor::OnKeyDownInViewport( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
// Check to see if any of the actions for the level editor can be processed by the current keyboard from a viewport
if( LevelEditorCommands->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
// NOTE: Currently, we don't bother allowing toolkits to get a chance at viewport keys
return FReply::Unhandled();
}
/** Callback for when the level editor layout has changed */
void SLevelEditor::OnLayoutHasChanged()
{
// ...
}
void SLevelEditor::SummonLevelViewportContextMenu()
{
FLevelEditorContextMenu::SummonMenu( SharedThis( this ), LevelEditorMenuContext::Viewport );
}
const TArray< TSharedPtr< IToolkit > >& SLevelEditor::GetHostedToolkits() const
{
return HostedToolkits;
}
TArray< TSharedPtr< ILevelViewport > > SLevelEditor::GetViewports() const
{
TArray< TSharedPtr<ILevelViewport> > OutViewports;
for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex )
{
TSharedPtr<FLevelViewportTabContent> ViewportTab = ViewportTabs[ TabIndex ].Pin();
if (ViewportTab.IsValid())
{
const TArray< TSharedPtr< SLevelViewport > >* LevelViewports = ViewportTab->GetViewports();
if (LevelViewports != NULL)
{
for( int32 ViewportIndex = 0; ViewportIndex < LevelViewports->Num(); ++ViewportIndex )
{
const TSharedPtr< SLevelViewport >& Viewport = (*LevelViewports)[ ViewportIndex ];
OutViewports.Add(Viewport);
}
}
}
}
return OutViewports;
}
TSharedPtr< class FAssetThumbnailPool > SLevelEditor::GetThumbnailPool() const
{
return ThumbnailPool;
}
void SLevelEditor::AppendCommands( const TSharedRef<FUICommandList>& InCommandsToAppend )
{
LevelEditorCommands->Append(InCommandsToAppend);
}
UWorld* SLevelEditor::GetWorld() const
{
return World;
}
void SLevelEditor::HandleEditorMapChange( uint32 MapChangeFlags )
{
ResetViewportTabInfo();
if (WorldSettingsView.IsValid())
{
WorldSettingsView->SetObject(GetWorld()->GetWorldSettings(), true);
}
}
void SLevelEditor::OnActorSelectionChanged(const TArray<UObject*>& NewSelection, bool bForceRefresh)
{
for( auto It = AllActorDetailPanels.CreateIterator(); It; ++It )
{
TSharedPtr<SActorDetails> ActorDetails = It->Pin();
if( ActorDetails.IsValid() )
{
ActorDetails->SetObjects(NewSelection, bForceRefresh);
}
else
{
// remove stray entries here
}
}
}