// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SLevelEditor.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "Framework/Docking/LayoutService.h" #include "EditorModeRegistry.h" #include "EdMode.h" #include "Engine/Selection.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "Styling/SlateStyleRegistry.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "EditorStyleSet.h" #include "Editor/EditorPerProjectUserSettings.h" #include "Editor/UnrealEdEngine.h" #include "Settings/EditorExperimentalSettings.h" #include "LevelEditorViewport.h" #include "EditorModes.h" #include "UnrealEdGlobals.h" #include "LevelEditor.h" #include "LevelEditorMenu.h" #include "IDetailsView.h" #include "LevelEditorActions.h" #include "LevelEditorModesActions.h" #include "LevelEditorContextMenu.h" #include "LevelEditorToolBar.h" #include "SLevelEditorToolBox.h" #include "SLevelEditorModeContent.h" #include "SLevelEditorBuildAndSubmit.h" #include "Kismet2/DebuggerCommands.h" #include "SceneOutlinerPublicTypes.h" #include "SceneOutlinerModule.h" #include "Editor/Layers/Public/LayersModule.h" #include "Editor/WorldBrowser/Public/WorldBrowserModule.h" #include "Toolkits/ToolkitManager.h" #include "PropertyEditorModule.h" #include "Interfaces/IMainFrameModule.h" #include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructure.h" #include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h" #include "StatsViewerModule.h" #include "Widgets/SToolTip.h" #include "IDocumentation.h" #include "TutorialMetaData.h" #include "Widgets/Docking/SDockTab.h" #include "SActorDetails.h" #include "GameFramework/WorldSettings.h" #include "Framework/Docking/LayoutExtender.h" #include "HierarchicalLODOutlinerModule.h" static const FName LevelEditorBuildAndSubmitTab("LevelEditorBuildAndSubmit"); static const FName LevelEditorStatsViewerTab("LevelEditorStatsViewer"); static const FName MainFrameModuleName("MainFrame"); 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.ToggleVR, FExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ToggleVR ), FCanExecuteAction::CreateStatic( &FLevelEditorActionCallbacks::ToggleVR_CanExecute ), FIsActionChecked::CreateStatic( &FLevelEditorActionCallbacks::ToggleVR_IsChecked ), FIsActionButtonVisible::CreateStatic(&FLevelEditorActionCallbacks::ToggleVR_IsButtonActive)); 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::EditorModeCommandsChanged); // @todo This is a hack to get this working for now. This won't work with multiple worlds GEditor->GetEditorWorldContext(true).AddRef(World); // Set the initial preview feature level. UEditorEngine* Editor = (UEditorEngine*)GEngine; World->ChangeFeatureLevel(Editor->GetActiveFeatureLevelPreviewType()); // Patch into the OnPreviewFeatureLevelChanged() delegate to swap out the current feature level with a user selection. PreviewFeatureLevelChangedHandle = Editor->OnPreviewFeatureLevelChanged().AddLambda([this](ERHIFeatureLevel::Type NewFeatureLevel) { World->ChangeFeatureLevel(NewFeatureLevel); }); 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) ); OwnerTab->SetTabLabelSuffix(TAttribute(this, &SLevelEditor::GetTabSuffix)); 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 ) .VAlign( VAlign_Center ) [ 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 ) ) ]; // developer tools const IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked(MainFrameModuleName); const FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked< FLevelEditorModule >(LevelEditorModuleName); TArray Tools; for (const TTuple& Item : LevelEditorModule.GetStatusBarItems()) { Tools.Add({Item.Value.Visibility, Item.Value.Label, Item.Value.Value}); } NotificationBarBox->AddSlot() .AutoWidth() .Padding(5.0f, 0.0f, 0.0f, 0.0f) [ MainFrameModule.MakeDeveloperTools( Tools ) ]; } 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 ); if(UObjectInitialized()) { GetMutableDefault()->OnSettingChanged().RemoveAll(this); GetMutableDefault()->OnUserSettingChanged().RemoveAll(this); } FEditorModeRegistry::Get().OnRegisteredModesChanged().RemoveAll( this ); FEditorDelegates::MapChange.RemoveAll(this); if (GEngine) { CastChecked(GEngine)->OnPreviewFeatureLevelChanged().Remove(PreviewFeatureLevelChangedHandle); } if (GEditor) { GEditor->GetEditorWorldContext(true).RemoveRef(World); } } FText SLevelEditor::GetTabTitle() const { const IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked< IMainFrameModule >( MainFrameModuleName ); return FText::FromString(MainFrameModule.GetLoadedLevelName()); } FText SLevelEditor::GetTabSuffix() const { const bool bDirtyState = World && World->GetCurrentLevel()->GetOutermost()->IsDirty(); return bDirtyState ? FText::FromString(TEXT("*")) : FText::GetEmpty(); } 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 TMap< FName, TSharedPtr< IViewportLayoutEntity > >* LevelViewports = ViewportTab.Pin()->GetViewports(); if (LevelViewports != NULL) { // Search for a viewport with a pie session for (auto& Pair : *LevelViewports) { if (Pair.Value->IsPlayInEditorViewportActive()) { return true; } } } } } // Also check standalone viewports for( const TWeakPtr< SLevelViewport >& StandaloneViewportWeakPtr : StandaloneViewports ) { const TSharedPtr< SLevelViewport > Viewport = StandaloneViewportWeakPtr.Pin(); if( Viewport.IsValid() ) { 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 TMap< FName, TSharedPtr< IViewportLayoutEntity > >* LevelViewports = ViewportTab->GetViewports(); if (LevelViewports != NULL) { for(auto& Pair : *LevelViewports) { TSharedPtr Viewport = Pair.Value->AsLevelViewport(); if( Viewport.IsValid() && 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; } } } } } } } // Also check standalone viewports for( const TWeakPtr< SLevelViewport >& StandaloneViewportWeakPtr : StandaloneViewports ) { const TSharedPtr< SLevelViewport > Viewport = StandaloneViewportWeakPtr.Pin(); if( Viewport.IsValid() ) { 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; } 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. } TSharedPtr SLevelEditor::GetTabManager() const { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( LevelEditorModuleName ); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); return LevelEditorTabManager; } void SLevelEditor::AttachSequencer( TSharedPtr SequencerWidget, TSharedPtr NewSequencerAssetEditor ) { struct Local { static void OnSequencerClosed( TSharedRef DockTab, TWeakPtr InSequencerAssetEditor ) { TSharedPtr AssetEditorInstance = InSequencerAssetEditor.Pin(); if (AssetEditorInstance.IsValid()) { InSequencerAssetEditor.Pin()->CloseWindow(); } } }; static bool bIsReentrant = false; if( !bIsReentrant ) { TSharedRef Tab = SNew(SDockTab); Tab = InvokeTab("Sequencer"); // Close the sequence editor after invoking a sequencer tab instead of before so that the existing asset editor doesn't refer to a stale sequencer. if(SequencerAssetEditor.IsValid()) { // Closing the window will invoke this method again but we are handling reopening with a new movie scene ourselves TGuardValue ReentrantGuard(bIsReentrant, true); // Shutdown cleanly SequencerAssetEditor.Pin()->CloseWindow(); } if(!FGlobalTabmanager::Get()->OnOverrideDockableAreaRestore_Handler.IsBound()) { // Don't allow standard tab closing behavior when the override is active Tab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateStatic(&Local::OnSequencerClosed, TWeakPtr(NewSequencerAssetEditor))); } if(SequencerWidget.IsValid() && NewSequencerAssetEditor.IsValid()) { Tab->SetContent(SequencerWidget.ToSharedRef()); SequencerWidgetPtr = SequencerWidget; SequencerAssetEditor = NewSequencerAssetEditor; if (FGlobalTabmanager::Get()->OnOverrideDockableAreaRestore_Handler.IsBound()) { // @todo vreditor: more general vr editor tab manager should handle windows instead // Close the original tab so we just work with the override window Tab->RequestCloseTab(); } } else { Tab->SetContent(SNullWidget::NullWidget); SequencerAssetEditor.Reset(); } } } TSharedRef SLevelEditor::SummonDetailsPanel( FName TabIdentifier ) { TSharedRef ActorDetails = StaticCastSharedRef( CreateActorDetails( TabIdentifier ) ); 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 ] ]; 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 == FEditorModeTools::EditorModeToolbarTabName) { return GLevelEditorModeTools().MakeModeToolbarTab(); } 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 = StaticCastSharedRef( CreateToolBox() ); 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.bShowTransient = true; 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){ // Extend the menu even if no actors selected, as Edit menu should always exist for scene outliner FLevelEditorContextMenu::FillMenu(MenuBuilder, InWeakLevelEditor, LevelEditorMenuContext::SceneOutliner, TSharedPtr()); }, WeakLevelEditor) ); } FText Label = NSLOCTEXT( "LevelEditor", "SceneOutlinerTabTitle", "World Outliner" ); FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::Get().LoadModuleChecked("SceneOutliner"); TSharedRef SceneOutlinerRef = SceneOutlinerModule.CreateSceneOutliner( InitOptions, FOnActorPicked() /* Not used for outliner when in browsing mode */); SceneOutlinerPtr = SceneOutlinerRef; 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"))) [ SceneOutlinerRef ] ]; } 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 == TEXT("LevelEditorHierarchicalLODOutliner")) { FText Label = NSLOCTEXT("LevelEditor", "HLODOutlinerTabTitle", "Hierarchical LOD Outliner"); FHierarchicalLODOutlinerModule& HLODModule = FModuleManager::LoadModuleChecked("HierarchicalLODOutliner"); return SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.HLOD")) .Label(Label) .ToolTip(IDocumentation::Get()->CreateToolTip(Label, nullptr, "Shared/Editor/HLOD", "main")) [ HLODModule.CreateHLODOutlinerWidget() ]; } 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") ) { if (FSlateStyleRegistry::FindSlateStyle("LevelSequenceEditorStyle")) { // @todo sequencer: remove when world-centric mode is added return SNew(SDockTab) .Icon(FSlateStyleRegistry::FindSlateStyle("LevelSequenceEditorStyle")->GetBrush("LevelSequenceEditor.Tabs.Sequencer")) .Label(NSLOCTEXT("Sequencer", "SequencerMainTitle", "Sequencer")) [ SNullWidget::NullWidget ]; } } else if( TabIdentifier == TEXT("SequencerGraphEditor") ) { const FSlateIcon SequencerGraphIcon = FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCurveEditor.TabIcon"); // @todo sequencer: remove when world-centric mode is added return SNew(SDockTab) .Icon(SequencerGraphIcon.GetIcon()) .Label(NSLOCTEXT("Sequencer", "SequencerMainGraphEditorTitle", "Sequencer Curves")) [ SNullWidget::NullWidget ]; } 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); } bool SLevelEditor::CanSpawnEditorModeToolbarTab(const FSpawnTabArgs& Args) const { return GLevelEditorModeTools().ShouldShowModeToolbar(); } TSharedRef SLevelEditor::InvokeTab( FName TabID ) { TSharedPtr LevelEditorTabManager = GetTabManager(); return 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 TMap>* const Viewports = ViewportTabContent->GetViewports(); if(Viewports) { const FString& LayoutId = ViewportTabContent->GetLayoutString(); for (auto& Pair : *Viewports) { TSharedPtr Viewport = Pair.Value->AsLevelViewport(); if( !Viewport.IsValid() ) { continue; } //@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 TMap>* const Viewports = ViewportTabContent->GetViewports(); if(Viewports) { const FString& LayoutId = ViewportTabContent->GetLayoutString(); for (auto& Pair : *Viewports) { TSharedPtr Viewport = Pair.Value->AsLevelViewport(); if( !Viewport.IsValid() ) { continue; } 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 Panel")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes.")) .SetGroup(MenuStructure.GetLevelEditorModesCategory()) .SetIcon(ToolsIcon); FCanSpawnTab CanSpawnTabDelegate = FCanSpawnTab::CreateSP(this, &SLevelEditor::CanSpawnEditorModeToolbarTab); LevelEditorTabManager->RegisterTabSpawner(FEditorModeTools::EditorModeToolbarTabName, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FEditorModeTools::EditorModeToolbarTabName, FString()), CanSpawnTabDelegate) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTab", "Active Mode Tools")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTabTooltipText", "Open the modes toolbar which has the active mode's tools")) .SetGroup(MenuStructure.GetLevelEditorModesCategory()) .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 ); } { const FSlateIcon LayersIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.HLOD"); LevelEditorTabManager->RegisterTabSpawner("LevelEditorHierarchicalLODOutliner", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorHierarchicalLODOutliner"), FString())) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorHierarchicalLODOutliner", "Hierarchical LOD Outliner")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorHierarchicalLODOutlinerTooltipText", "Open the Hierarchical LOD Outliner.")) .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 const FSlateIcon SequencerIcon("LevelSequenceEditorStyle", "LevelSequenceEditor.Tabs.Sequencer" ); LevelEditorTabManager->RegisterTabSpawner( "Sequencer", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("Sequencer"), FString()) ) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "Sequencer", "Sequencer")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) .SetIcon( SequencerIcon ); } { // @todo remove when world-centric mode is added const FSlateIcon SequencerGraphIcon = FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCurveEditor.TabIcon"); LevelEditorTabManager->RegisterTabSpawner("SequencerGraphEditor", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("SequencerGraphEditor"), FString())) .SetMenuType(ETabSpawnerMenuType::Type::Hidden); } { 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 ); } FTabSpawnerEntry& BuildAndSubmitEntry = LevelEditorTabManager->RegisterTabSpawner(LevelEditorBuildAndSubmitTab, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, LevelEditorBuildAndSubmitTab, FString())); BuildAndSubmitEntry.SetAutoGenerateMenuEntry(false); LevelEditorModule.OnRegisterTabs().Broadcast(LevelEditorTabManager); } // 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 ) ->SetExtensionId( "TopLevelArea" ) ->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) ->AddTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab) ) ->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")) ) ) )); FLayoutExtender LayoutExtender; // Make a layout extension for the mode toolbar. This avoids bumping level editor layout version to add a mandatory tab LayoutExtender.ExtendLayout(FName("LevelEditorToolBar"), ELayoutExtensionPosition::Below, FTabManager::FTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab)); LevelEditorModule.OnRegisterLayoutExtensions().Broadcast(LayoutExtender); Layout->ProcessExtensions(LayoutExtender); 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 ) { // Prompt the user if Matinee must be closed before activating new mode if (ModeID != FBuiltinEditorModes::EM_InterpEdit) { FEdMode* MatineeMode = GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_InterpEdit); if (MatineeMode && !MatineeMode->IsCompatibleWith(ModeID)) { FEditorModeInfo MatineeModeInfo = FEditorModeRegistry::Get().GetModeInfo(ModeID); FFormatNamedArguments Args; Args.Add(TEXT("ModeName"), MatineeModeInfo.Name); FText Msg = FText::Format(NSLOCTEXT("LevelEditor", "ModeSwitchCloseMatineeQ", "Activating '{ModeName}' editor mode will close UnrealMatinee. Continue?"), Args); if (EAppReturnType::Yes != FMessageDialog::Open(EAppMsgType::YesNo, Msg)) { return; } } } // *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::EditorModeCommandsChanged() { if (FLevelEditorModesCommands::IsRegistered()) { FLevelEditorModesCommands::Unregister(); } RefreshEditorModeCommands(); } void SLevelEditor::RefreshEditorModeCommands() { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( "LevelEditor" ); if(!FLevelEditorModesCommands::IsRegistered()) { FLevelEditorModesCommands::Register(); } const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); // We need to remap all the actions to commands. const FLevelEditorModesCommands& Commands = FLevelEditorModesCommands::Get(); int32 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 ( EditorModeCommand.IsValid() && !LevelEditorCommands->IsActionMapped(Commands.EditorModeCommands[CommandIndex]) ) { 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()) { for (const auto& ActiveToolkit : HostedToolkits) { // A toolkit is active, so direct all command processing to it if (ActiveToolkit->ProcessCommandBindings(InKeyEvent)) { return FReply::Handled(); } } // No toolkit processed the key, 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 ); } void SLevelEditor::SummonLevelViewportViewOptionMenu(const ELevelViewportType ViewOption) { FLevelEditorContextMenu::SummonViewOptionMenu( SharedThis( this ), ViewOption); } 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 TMap< FName, TSharedPtr< IViewportLayoutEntity > >* LevelViewports = ViewportTab->GetViewports(); if (LevelViewports != NULL) { for (auto& Pair : *LevelViewports) { TSharedPtr Viewport = Pair.Value->AsLevelViewport(); if( Viewport.IsValid() ) { OutViewports.Add(Viewport); } } } } } // Also add any standalone viewports { for( const TWeakPtr< SLevelViewport >& StandaloneViewportWeakPtr : StandaloneViewports ) { const TSharedPtr< SLevelViewport > Viewport = StandaloneViewportWeakPtr.Pin(); if( Viewport.IsValid() ) { OutViewports.Add( Viewport ); } } } return OutViewports; } TSharedPtr SLevelEditor::GetActiveViewportInterface() { return GetActiveViewport(); } 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 } } } void SLevelEditor::AddStandaloneLevelViewport( const TSharedRef& LevelViewport ) { CleanupPointerArray( StandaloneViewports ); StandaloneViewports.Add( LevelViewport ); } TSharedRef SLevelEditor::CreateActorDetails( const FName TabIdentifier ) { TSharedRef ActorDetails = SNew( SActorDetails, TabIdentifier, LevelEditorCommands, GetTabManager() ); // Immediately update it (otherwise it will appear empty) { TArray SelectedActors; for( FSelectionIterator It( GEditor->GetSelectedActorIterator() ); It; ++It ) { AActor* Actor = static_cast( *It ); checkSlow( Actor->IsA( AActor::StaticClass() ) ); if( !Actor->IsPendingKill() ) { SelectedActors.Add( Actor ); } } const bool bForceRefresh = true; ActorDetails->SetObjects( SelectedActors, bForceRefresh ); } AllActorDetailPanels.Add( ActorDetails ); return ActorDetails; } TSharedRef SLevelEditor::CreateToolBox() { TSharedRef NewToolBox = SNew( SLevelEditorToolBox, SharedThis( this ) ) .IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() ); ToolBoxTabs.Add( NewToolBox ); return NewToolBox; }