// Copyright 1998-2014 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 "ScopedTransaction.h" #include "SLevelEditorToolBox.h" #include "SLevelEditorModeContent.h" #include "SLevelEditorBuildAndSubmit.h" #include "Editor/UnrealEd/Public/Kismet2/DebuggerCommands.h" #include "Editor/SceneOutliner/Public/SceneOutlinerModule.h" #include "Editor/Layers/Public/LayersModule.h" #include "Editor/Levels/Public/LevelsModule.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 "LevelEditorGenericDetails.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 "Editor/UMGEditor/Public/UMGEditorModule.h" #include "EditorModes.h" #include "STutorialWrapper.h" #include "IDocumentation.h" #include "NewsFeed.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.OpenLevelBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenLevelBlueprint, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.OpenGameModeBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenGameModeBlueprint, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.OpenGameStateBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenGameStateBlueprint, SharedThis( this ) ), FCanExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ), FIsActionChecked(), FIsActionButtonVisible::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.OpenDefaultPawnBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenDefaultPawnBlueprint, SharedThis( this ) ), FCanExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ), FIsActionChecked(), FIsActionButtonVisible::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.OpenHUDBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenHUDBlueprint, SharedThis( this ) ), FCanExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ), FIsActionChecked(), FIsActionButtonVisible::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.OpenPlayerControllerBlueprint, FExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::OpenPlayerControllerBlueprint, SharedThis( this ) ), FCanExecuteAction::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ), FIsActionChecked(), FIsActionButtonVisible::CreateStatic< TWeakPtr< SLevelEditor > >( &FLevelEditorActionCallbacks::CanEditGameInfoBlueprints, SharedThis( this ) ) ); LevelEditorCommands->MapAction( Actions.CreateClassBlueprint, FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::CreateClassBlueprint ) ); 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. GEditorModeTools().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) ); TSharedRef Widget2 = RestoreContentArea( OwnerTab, OwnerWindow ); TSharedRef Widget1 = FLevelEditorMenu::MakeLevelEditorMenu( LevelEditorCommands, SharedThis(this) ); ChildSlot [ SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ SNew( SOverlay ) +SOverlay::Slot() [ SNew( STutorialWrapper, TEXT("MainMenu") ) [ Widget1 ] ] +SOverlay::Slot() .HAlign( HAlign_Right ) [ SNew( STutorialWrapper, TEXT("PerformanceTools") ) [ SAssignNew( NotificationBarBox, SHorizontalBox ) ] ] ] +SVerticalBox::Slot() .FillHeight( 1.0f ) [ Widget2 ] ]; 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 ) ) ]; // 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() ]; // 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 ); GEditor->AccessEditorUserSettings().OnUserSettingChanged().RemoveAll( this ); GEditorModeTools().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->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->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 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(); } // @todo Slate TEMP to support both detail views static TSharedRef SummonDetailsPanel( FName TabIdentifier ) { struct Local { static bool IsPropertyVisible( const UProperty* InProperty ) { // For details views in the level editor all properties are the instanced versions if ((InProperty != NULL) && InProperty->HasAllPropertyFlags(CPF_DisableEditOnInstance)) { return false; } return true; } }; FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked("PropertyEditor"); const FDetailsViewArgs DetailsViewArgs( true, true, true, false, false, GUnrealEd, false, TabIdentifier ); TSharedRef DetailsView = PropPlugin.CreateDetailView( DetailsViewArgs ); DetailsView->SetIsPropertyVisibleDelegate( FIsPropertyVisible::CreateStatic( &Local::IsPropertyVisible ) ); // Set up a delegate to call to add generic details to the view DetailsView->SetGenericLayoutDetailsDelegate( FOnGetDetailCustomizationInstance::CreateStatic( &FLevelEditorGenericDetails::MakeInstance ) ); FText Label = NSLOCTEXT( "LevelEditor", "DetailsTabTitle", "Details" ); return SNew( SDockTab ) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Details" ) ) .Label( Label ) .ToolTip( IDocumentation::Get()->CreateToolTip( Label, nullptr, "Shared/LevelEditor", "DetailsTab" ) ) [ SNew( STutorialWrapper, TEXT("ActorDetails") ) [ DetailsView ] ]; } /** 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( STutorialWrapper, TEXT("LevelToolbar") ) [ SNew(SHorizontalBox) + 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( STutorialWrapper, TEXT("ToolsPanel") ) [ 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") ) { FSceneOutlinerInitializationOptions InitOptions; InitOptions.Mode = ESceneOutlinerMode::ActorBrowsing; { TWeakPtr WeakLevelEditor = SharedThis(this); InitOptions.DefaultMenuExtender = MakeShareable(new FExtender); InitOptions.DefaultMenuExtender->AddMenuExtension( "FolderSection", EExtensionHook::Before, GetLevelEditorActions(), FMenuExtensionDelegate::CreateStatic([](FMenuBuilder& MenuBuilder, TWeakPtr InWeakLevelEditor){ // Only extend the menu if we have actors selected if (GEditor->GetSelectedActors()->Num()) { FLevelEditorContextMenu::FillMenu(MenuBuilder, InWeakLevelEditor, LevelEditorMenuContext::NonViewport, TSharedPtr()); } }, WeakLevelEditor) ); } FText Label = NSLOCTEXT( "LevelEditor", "SceneOutlinerTabTitle", "Scene 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" ) ) .ContentPadding( 5 ) [ SNew( STutorialWrapper, TEXT("SceneOutliner") ) [ SNew(SBorder) .Padding(4) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ 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") ) [ LayersModule.CreateLayerBrowser() ] ]; } else if( TabIdentifier == TEXT("LevelEditorLevelBrowser") ) { FLevelsModule& LevelsModule = FModuleManager::LoadModuleChecked( "Levels" ); return SNew( SDockTab ) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.Levels" ) ) .Label( NSLOCTEXT("LevelEditor", "LevelsTabTitle", "Levels") ) [ SNew(SBorder) .Padding( 0 ) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ LevelsModule.CreateLevelBrowser() ] ]; } else if( GetDefault()->bWorldBrowser && TabIdentifier == WorldBrowserHierarchyTab) { FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked( "WorldBrowser" ); return SNew( SDockTab ) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowser" ) ) .Label( NSLOCTEXT("LevelEditor", "WorldBrowserHierarchyTabTitle", "Levels Hierarchy") ) [ WorldBrowserModule.CreateWorldBrowserHierarchy() ]; } else if( GetDefault()->bWorldBrowser && TabIdentifier == WorldBrowserDetailsTab) { FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked( "WorldBrowser" ); return SNew( SDockTab ) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowser" ) ) .Label( NSLOCTEXT("LevelEditor", "WorldBrowserDetailsTabTitle", "Levels Details") ) [ WorldBrowserModule.CreateWorldBrowserDetails() ]; } else if( GetDefault()->bWorldBrowser && TabIdentifier == WorldBrowserCompositionTab) { FWorldBrowserModule& WorldBrowserModule = FModuleManager::LoadModuleChecked( "WorldBrowser" ); return SNew( SDockTab ) .Icon( FEditorStyle::GetBrush( "LevelEditor.Tabs.WorldBrowser" ) ) .Label( NSLOCTEXT("LevelEditor", "WorldBrowserCompositionTabTitle", "Levels 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") ) .ContentPadding( 5 ) [ StatsViewerModule.CreateStatsViewer() ]; } else if ( TabIdentifier == "WorldSettingsTab" ) { FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs( false, false, true, false, 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") ) [ 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(); const FString Key = FString::Printf(TEXT("%s[%d]"), *LayoutId, static_cast(LevelViewportClient.ViewportType)); const FLevelViewportInfo* const TransientEditorView = TransientEditorViews.Find(Key); if(TransientEditorView) { LevelViewportClient.SetInitialViewTransform( TransientEditorView->CamPosition, TransientEditorView->CamRotation, TransientEditorView->CamOrthoZoom ); } } } } 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", "Scene Outliner")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorSceneOutlinerTooltipText", "Open the Scene Outliner tab, which provides a searchable and filterable list of all actors in the scene.")) .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 scene belong to which layers.")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) .SetIcon( LayersIcon ); } { const FSlateIcon LevelsIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Levels"); LevelEditorTabManager->RegisterTabSpawner( "LevelEditorLevelBrowser", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorLevelBrowser"), FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorLevelBrowser", "Levels")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorLevelBrowserTooltipText", "Open the Levels tab. Use this to manage the levels in the current project.")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) .SetIcon( LevelsIcon ); } if (GetDefault()->bWorldBrowser) { RegisterWorldBrowserTabSpawner(); } { 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 BuildAndSubmitIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.BuildAndSubmit"); LevelEditorTabManager->RegisterTabSpawner( LevelEditorBuildAndSubmitTab, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, LevelEditorBuildAndSubmitTab, FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "BuildAndSubmit", "Build And Submit")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "BuildAndSubmitTooltip", "Opens a tab allowing the user to build all levels and optionally submit the result to source control.")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) .SetIcon( BuildAndSubmitIcon ); } { const FSlateIcon WorldPropertiesIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.WorldProperties.Small"); 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_Vertical ) ->Split ( FTabManager::NewSplitter() ->SetOrientation( Orient_Horizontal ) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient( 0.2f ) ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient( 0.45f ) ->AddTab( "LevelEditorToolBox", ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack()->AddTab("ContentBrowserTab1", ETabState::OpenedTab) ) ) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient( 0.60f ) ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetHideTabWell(true) ->AddTab( "LevelEditorToolBar", ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetHideTabWell(true) ->SetSizeCoefficient(0.75f) ->AddTab( "LevelEditorViewport", ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.25f) ->AddTab( "OutputLog", ETabState::ClosedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient( 0.2f ) ->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) { if (PropertyName == TEXT("bWorldBrowser")) { if (GetDefault()->bWorldBrowser ) { RegisterWorldBrowserTabSpawner(); } else { UnregisterWorldBrowserTabSpawner(); } } } void SLevelEditor::RegisterWorldBrowserTabSpawner() { TSharedPtr LevelEditorTabManager = GetTabManager(); LevelEditorTabManager->RegisterTabSpawner( WorldBrowserHierarchyTab, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserHierarchyTab, FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserHierarchy", "World Browser")) .SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() ) .SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowser") ); LevelEditorTabManager->RegisterTabSpawner( WorldBrowserDetailsTab, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserDetailsTab, FString()) ) .SetMenuType( ETabSpawnerMenuType::Hide ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserDetails", "Levels Details")) .SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() ) .SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowser") ); LevelEditorTabManager->RegisterTabSpawner( WorldBrowserCompositionTab, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, WorldBrowserCompositionTab, FString()) ) .SetMenuType( ETabSpawnerMenuType::Hide ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "WorldBrowserComposition", "Levels Composition")) .SetGroup( WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory() ) .SetIcon( FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.WorldBrowser") ); } void SLevelEditor::UnregisterWorldBrowserTabSpawner() { TSharedPtr LevelEditorTabManager = GetTabManager(); LevelEditorTabManager->UnregisterTabSpawner( WorldBrowserHierarchyTab ); LevelEditorTabManager->UnregisterTabSpawner( WorldBrowserDetailsTab ); LevelEditorTabManager->UnregisterTabSpawner( WorldBrowserCompositionTab ); } 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 EM_Default when the stack becomes empty, resulting in multiple active visible modes. GEditorModeTools().ActivateMode( ModeID ); // Find and disable any other 'visible' modes since we only ever allow one of those active at a time. TArray ActiveModes; GEditorModeTools().GetActiveModes( ActiveModes ); for ( FEdMode* Mode : ActiveModes ) { if ( Mode->IsVisible() && Mode->GetID() != ModeID ) { GEditorModeTools().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 ( GEditorModeTools().IsModeActive( ModeID ) ) //{ // //GEditorModeTools().DeactivateAllModes(); // //GEditorModeTools().DeactivateMode( ModeID ); //} //else // Activate the mode and create the tab for it. //{ // GEditorModeTools().DeactivateAllModes(); // GEditorModeTools().ActivateMode( ModeID ); // //FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( "LevelEditor" ); // //TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); // //TSharedRef ToolboxTab = LevelEditorTabManager->InvokeTab( GetEditorModeTabId( ModeID ) ); //} } static bool IsDefaultModeActive() { TArray ActiveModes; GEditorModeTools().GetActiveModes( ActiveModes ); for ( FEdMode* Mode : ActiveModes ) { if ( Mode->IsVisible() ) { if ( !( Mode->GetID() == FBuiltinEditorModes::EM_Placement || Mode->GetID() == FBuiltinEditorModes::EM_Default ) ) { return false; } } } return GEditorModeTools().IsModeActive( FBuiltinEditorModes::EM_Placement ); } bool SLevelEditor::IsModeActive( FEditorModeID ModeID ) { if ( ModeID == FBuiltinEditorModes::EM_Placement || ModeID == FBuiltinEditorModes::EM_Default ) { return IsDefaultModeActive(); } return GEditorModeTools().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(); TArray Modes; GEditorModeTools().GetModes(Modes); int editorMode = 0; struct FCompareEdModeByPriority { FORCEINLINE bool operator()(const FEdMode& A, const FEdMode& B) const { return A.GetPriorityOrder() < B.GetPriorityOrder(); } }; Modes.Sort(FCompareEdModeByPriority()); int commandIndex = 0; for ( FEdMode* Mode : Modes ) { // If the mode isn't visible don't create a menu option for it. if ( !Mode->IsVisible() ) { continue; } FName EditorModeTabName = GetEditorModeTabId( Mode->GetID() ); FName EditorModeCommandName = FName(*(FString("EditorMode.") + Mode->GetID().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->GetID() ), FCanExecuteAction(), FIsActionChecked::CreateStatic( &SLevelEditor::IsModeActive, Mode->GetID() )); } // Register the new tab spawner, and unregister the old one if it exists //if ( LevelEditorTabManager.IsValid() ) //{ // LevelEditorTabManager->UnregisterTabSpawner( EditorModeTabName ); // LevelEditorTabManager->RegisterTabSpawner( EditorModeTabName, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorModeTab, Mode) ) // .SetDisplayName( Mode->GetName() ) // .SetGroup( MenuStructure.GetLevelEditorModesCategory() ) // .SetIcon( Mode->GetIcon() ); //} commandIndex++; } } TSharedRef SLevelEditor::SpawnLevelEditorModeTab( const FSpawnTabArgs& Args, FEdMode* EditorMode ) { if (!GEditorModeTools().IsModeActive(EditorMode->GetID())) { GEditorModeTools().ActivateMode(EditorMode->GetID()); } TSharedPtr NewEditorModeTab; TSharedPtr NewToolBox; SAssignNew( NewEditorModeTab, SDockTab ) .Icon( EditorMode->GetIcon().GetSmallIcon() ) .Label( EditorMode->GetName() ); NewEditorModeTab->SetContent( SNew( STutorialWrapper, TEXT("ToolsPanel") ) [ SAssignNew( NewToolBox, SLevelEditorModeContent, SharedThis( this ), NewEditorModeTab.ToSharedRef(), EditorMode ) .IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() ) ] ); ModesTabs.Add( NewToolBox ); return NewEditorModeTab.ToSharedRef(); } FReply SLevelEditor::OnKeyDown( const FGeometry& MyGeometry, const FKeyboardEvent& InKeyboardEvent ) { // Check to see if any of the actions for the level editor can be processed by the current keyboard 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( InKeyboardEvent ) ) { return FReply::Handled(); } } else { // No toolkit tab is active, so let the level editor have a chance at the keystroke if( LevelEditorCommands->ProcessCommandBindings( InKeyboardEvent ) ) { return FReply::Handled(); } } } return FReply::Unhandled(); } FReply SLevelEditor::OnKeyDownInViewport( const FGeometry& MyGeometry, const FKeyboardEvent& InKeyboardEvent ) { // Check to see if any of the actions for the level editor can be processed by the current keyboard event from a viewport if( LevelEditorCommands->ProcessCommandBindings( InKeyboardEvent ) ) { 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::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { if(ThumbnailPool.IsValid()) { ThumbnailPool->Tick( InDeltaTime ); } } void SLevelEditor::HandleEditorMapChange( uint32 MapChangeFlags ) { ResetViewportTabInfo(); if (WorldSettingsView.IsValid()) { WorldSettingsView->SetObject(GetWorld()->GetWorldSettings(), true); } }