// 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( 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()->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& OwnerTab, const TSharedRef& OwnerWindow ) { // Bind the level editor tab's label to the currently loaded level name string in the main frame OwnerTab->SetLabel( TAttribute( this, &SLevelEditor::GetTabTitle) ); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked< FLevelEditorModule >(LevelEditorModuleName); LevelEditorModule.OnActorSelectionChanged().AddSP(this, &SLevelEditor::OnActorSelectionChanged); TSharedRef Widget2 = RestoreContentArea( OwnerTab, OwnerWindow ); TSharedRef Widget1 = FLevelEditorMenu::MakeLevelEditorMenu( LevelEditorCommands, SharedThis(this) ); ChildSlot [ SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ SNew( SOverlay ) +SOverlay::Slot() [ SNew( SBox ) .AddMetaData(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(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(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(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(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()->OnSettingChanged().RemoveAll( this ); GetMutableDefault()->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 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 SLevelEditor::GetActiveViewport() { // The first visible viewport TSharedPtr FirstVisibleViewport; // Search through all current viewport tabs for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex ) { TSharedPtr 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 SLevelEditor::GetActiveViewportTab() { // The first visible viewport TSharedPtr FirstVisibleViewportTab; // Search through all current viewport tabs for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex ) { TSharedPtr 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( LevelEditorModuleName ); TSharedPtr LevelEditorTab = LevelEditorModule.GetLevelEditorInstanceTab().Pin(); TSharedPtr 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().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 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 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 SLevelEditor::GetTabManager() const { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( LevelEditorModuleName ); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); return LevelEditorTabManager.ToSharedRef(); } TSharedRef SLevelEditor::SummonDetailsPanel( FName TabIdentifier ) { TSharedRef ActorDetails = SNew(SActorDetails, TabIdentifier, LevelEditorCommands); const FText Label = NSLOCTEXT( "LevelEditor", "DetailsTabTitle", "Details" ); TSharedRef DocTab = SNew(SDockTab) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Details" ) ) .Label( Label ) .ToolTip( IDocumentation::Get()->CreateToolTip( Label, nullptr, "Shared/LevelEditor", "DetailsTab" ) ) [ SNew( SBox ) .AddMetaData(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 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(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 DetailsPanel = SummonDetailsPanel( TabIdentifier ); GUnrealEd->UpdateFloatingPropertyWindows(); return DetailsPanel; } else if( TabIdentifier == TEXT("LevelEditorToolBox") ) { TSharedRef 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(TEXT("ToolsPanel"), TEXT("LevelEditorToolBox"))) [ NewToolBox ] ]; } else if( TabIdentifier == LevelEditorBuildAndSubmitTab ) { TSharedRef NewBuildAndSubmit = SNew( SLevelEditorBuildAndSubmit, SharedThis( this ) ); TSharedRef 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 WeakLevelEditor = SharedThis(this); InitOptions.DefaultMenuExtender = MakeShareable(new FExtender); InitOptions.DefaultMenuExtender->AddMenuExtension( "MainSection", EExtensionHook::Before, GetLevelEditorActions(), FMenuExtensionDelegate::CreateStatic([](FMenuBuilder& MenuBuilder, TWeakPtr InWeakLevelEditor){ // Only extend the menu if we have actors selected if (GEditor->GetSelectedActorCount() > 0) { FLevelEditorContextMenu::FillMenu(MenuBuilder, InWeakLevelEditor, LevelEditorMenuContext::NonViewport, TSharedPtr()); } }, WeakLevelEditor) ); } FText Label = NSLOCTEXT( "LevelEditor", "SceneOutlinerTabTitle", "World Outliner" ); FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::Get().LoadModuleChecked( "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(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( "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(TEXT("LayerBrowser"), TEXT("LevelEditorLayerBrowser"))) [ LayersModule.CreateLayerBrowser() ] ]; } else if( TabIdentifier == WorldBrowserHierarchyTab ) { FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked( "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( "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( "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( "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("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(TEXT("WorldSettings"), TEXT("WorldSettingsTab"))) [ WorldSettingsView.ToSharedRef() ]; } return SNew(SDockTab); } void SLevelEditor::InvokeTab( FName TabID ) { TSharedPtr LevelEditorTabManager = GetTabManager(); LevelEditorTabManager->InvokeTab(TabID); } void SLevelEditor::SyncDetailsToSelection() { static const FName DetailsTabIdentifiers[] = { "LevelEditorSelectionDetails", "LevelEditorSelectionDetails2", "LevelEditorSelectionDetails3", "LevelEditorSelectionDetails4" }; FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked("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 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 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 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 ClosedTab) { TWeakPtr* const ClosedTabContent = ViewportTabs.FindByPredicate([&ClosedTab](TWeakPtr& InPotentialElement) -> bool { TSharedPtr ViewportTabContent = InPotentialElement.Pin(); return ViewportTabContent.IsValid() && ViewportTabContent->BelongsToTab(ClosedTab); }); if(ClosedTabContent) { TSharedPtr ClosedTabContentPin = ClosedTabContent->Pin(); if(ClosedTabContentPin.IsValid()) { SaveViewportTabInfo(ClosedTabContentPin.ToSharedRef()); // Untrack the viewport ViewportTabs.Remove(ClosedTabContentPin); CleanupPointerArray(ViewportTabs); } } } void SLevelEditor::SaveViewportTabInfo(TSharedRef ViewportTabContent) { const TArray>* 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(LevelViewportClient.ViewportType)); TransientEditorViews.Add( Key, FLevelViewportInfo( LevelViewportClient.GetViewLocation(), LevelViewportClient.GetViewRotation(), LevelViewportClient.GetOrthoZoom() ) ); } } } void SLevelEditor::RestoreViewportTabInfo(TSharedRef ViewportTabContent) const { const TArray>* 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(ViewportType), TransientEditorView->CamPosition, TransientEditorView->CamRotation, TransientEditorView->CamOrthoZoom ); if (ViewportType != LVT_Perspective) { bInitializedOrthoViewport = true; } } } } } } } void SLevelEditor::ResetViewportTabInfo() { TransientEditorViews.Reset(); } TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef& OwnerTab, const TSharedRef& OwnerWindow ) { const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( LevelEditorModuleName ); LevelEditorModule.SetLevelEditorTabManager(OwnerTab); TSharedPtr 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(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(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails"), FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails", "Details 1")) .SetTooltipText(DetailsTooltip) .SetGroup( MenuStructure.GetLevelEditorDetailsCategory() ) .SetIcon( DetailsIcon ); LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails2", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails2"), FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails2", "Details 2")) .SetTooltipText(DetailsTooltip) .SetGroup( MenuStructure.GetLevelEditorDetailsCategory() ) .SetIcon( DetailsIcon ); LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails3", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorSelectionDetails3"), FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorSelectionDetails3", "Details 3")) .SetTooltipText(DetailsTooltip) .SetGroup( MenuStructure.GetLevelEditorDetailsCategory() ) .SetIcon( DetailsIcon ); LevelEditorTabManager->RegisterTabSpawner( "LevelEditorSelectionDetails4", FOnSpawnTab::CreateSP(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( 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(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(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(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(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(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(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(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(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 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("LevelEditor"); TSharedPtr 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 ActiveModes; GLevelEditorModeTools().GetActiveModes( ActiveModes ); for ( FEdMode* Mode : ActiveModes ) { if ( Mode->GetID() != ModeID && Mode->GetModeInfo().bVisible ) { GLevelEditorModeTools().DeactivateMode( Mode->GetID() ); } } FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( "LevelEditor" ); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); TSharedRef 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( "LevelEditor" ); // //TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); // //TSharedRef 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 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( "LevelEditor" ); const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); TSharedPtr 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 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 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 > OutViewports; for( int32 TabIndex = 0; TabIndex < ViewportTabs.Num(); ++TabIndex ) { TSharedPtr 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& 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& NewSelection, bool bForceRefresh) { for( auto It = AllActorDetailPanels.CreateIterator(); It; ++It ) { TSharedPtr ActorDetails = It->Pin(); if( ActorDetails.IsValid() ) { ActorDetails->SetObjects(NewSelection, bForceRefresh); } else { // remove stray entries here } } }