// Copyright Epic Games, Inc. All Rights Reserved. #include "WorldBrowserModule.h" #include "Widgets/SWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Modules/ModuleManager.h" #include "EditorModeRegistry.h" #include "EditorModes.h" #include "LevelCollectionCommands.h" #include "LevelFolders.h" #include "Engine/WorldComposition.h" #include "StreamingLevels/StreamingLevelEdMode.h" #include "Tiles/WorldTileCollectionModel.h" #include "StreamingLevels/StreamingLevelCollectionModel.h" #include "SWorldHierarchy.h" #include "SWorldDetails.h" #include "Tiles/SWorldComposition.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "LevelEditor.h" #include "EditorLevelUtils.h" #include "ThumbnailRendering/ThumbnailManager.h" IMPLEMENT_MODULE( FWorldBrowserModule, WorldBrowser ); #define LOCTEXT_NAMESPACE "WorldBrowser" /** * Fills the level menu with extra options */ TSharedRef FWorldBrowserModule::BindLevelMenu(const TSharedRef CommandList) { TSharedRef Extender(new FExtender()); Extender->AddMenuExtension("LevelListing", EExtensionHook::Before, CommandList, FMenuExtensionDelegate::CreateRaw(this, &FWorldBrowserModule::BuildLevelMenu)); return Extender; } void FWorldBrowserModule::BuildLevelMenu(FMenuBuilder& MenuBuilder) { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); TSharedPtr Model = SharedWorldModel(EditorWorld); if (Model.IsValid()) { FLevelModelList ModelList = Model->GetFilteredLevels(); for (TSharedPtr LevelModel : ModelList) { FUIAction Action(FExecuteAction::CreateRaw(this, &FWorldBrowserModule::SetCurrentSublevel, LevelModel), FCanExecuteAction(), FIsActionChecked::CreateRaw(this, &FWorldBrowserModule::IsCurrentSublevel, LevelModel)); MenuBuilder.AddMenuEntry(FText::FromString(LevelModel->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button); } } } bool FWorldBrowserModule::IsCurrentSublevel(TSharedPtr InLevelModel) { return InLevelModel->IsCurrent(); } void FWorldBrowserModule::SetCurrentSublevel(TSharedPtr InLevelModel) { InLevelModel->MakeLevelCurrent(); } void FWorldBrowserModule::StartupModule() { FLevelCollectionCommands::Register(); // register the editor mode FEditorModeRegistry::Get().RegisterMode( FBuiltinEditorModes::EM_StreamingLevel, NSLOCTEXT("WorldBrowser", "StreamingLevelMode", "Level Transform Editing")); if (ensure(GEngine)) { GEngine->OnWorldAdded().AddRaw(this, &FWorldBrowserModule::OnWorldCreated); GEngine->OnWorldDestroyed().AddRaw(this, &FWorldBrowserModule::OnWorldDestroyed); } UWorldComposition::WorldCompositionChangedEvent.AddRaw(this, &FWorldBrowserModule::OnWorldCompositionChanged); // Have to check GIsEditor because right now editor modules can be loaded by the game // Once LoadModule is guaranteed to return NULL for editor modules in game, this can be removed // Without this check, loading the level editor in the game will crash if (GIsEditor) { // Extend the level viewport levels menu FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); LevelMenuExtender = FLevelEditorModule::FLevelEditorMenuExtender::CreateRaw(this, &FWorldBrowserModule::BindLevelMenu); auto& MenuExtenders = LevelEditorModule.GetAllLevelEditorLevelMenuExtenders(); MenuExtenders.Add(LevelMenuExtender); LevelMenuExtenderHandle = MenuExtenders.Last().GetHandle(); } FLevelFolders::Init(); } void FWorldBrowserModule::ShutdownModule() { ReleaseWorldModel(); ShutdownDelegate.Broadcast(); FLevelFolders::Cleanup(); if (GEngine) { GEngine->OnWorldAdded().RemoveAll(this); GEngine->OnWorldDestroyed().RemoveAll(this); } UWorldComposition::WorldCompositionChangedEvent.RemoveAll(this); FLevelCollectionCommands::Unregister(); // unregister the editor mode FEditorModeRegistry::Get().UnregisterMode(FBuiltinEditorModes::EM_StreamingLevel); } void FWorldBrowserModule::ReleaseWorldModel() { if (WorldModel) { // So we have to be the last owner of this model check(WorldModel.IsUnique()); WorldModel.Reset(); } } TSharedRef FWorldBrowserModule::CreateWorldBrowserHierarchy() { return CreateWorldBrowserHierarchyWidget()->GetWidget(); } TSharedRef FWorldBrowserModule::CreateWorldBrowserHierarchyWidget() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); return SNew(SWorldHierarchy).InWorld(EditorWorld); } TSharedRef FWorldBrowserModule::CreateWorldBrowserDetails() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); return SNew(SWorldDetails).InWorld(EditorWorld); } TSharedRef FWorldBrowserModule::CreateWorldBrowserComposition() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); return SNew(SWorldComposition).InWorld(EditorWorld); } void FWorldBrowserModule::OnWorldCreated(UWorld* InWorld) { if (InWorld && InWorld->WorldType == EWorldType::Editor) { OnBrowseWorld.Broadcast(InWorld); } } void FWorldBrowserModule::OnWorldCompositionChanged(UWorld* InWorld) { if (InWorld && InWorld->WorldType == EWorldType::Editor) { OnBrowseWorld.Broadcast(NULL); // Release World Model here so that a new one gets created in ShareWorldModel even if the World is the same. ReleaseWorldModel(); OnBrowseWorld.Broadcast(InWorld); } } void FWorldBrowserModule::OnWorldDestroyed(UWorld* InWorld) { // Is there any editors alive? if (WorldModel.IsValid()) { UWorld* ManagedWorld = WorldModel->GetWorld(/*bEvenIfPendingKill*/true); // Is it our world gets cleaned up? if (ManagedWorld == InWorld) { // Will reset all references to a shared world model OnBrowseWorld.Broadcast(NULL); ReleaseWorldModel(); } } } TSharedPtr FWorldBrowserModule::SharedWorldModel(UWorld* InWorld) { if (!WorldModel.IsValid() || WorldModel->GetWorld() != InWorld) { if (InWorld) { if (InWorld->WorldComposition) { WorldModel = FWorldTileCollectionModel::Create(InWorld); } else { WorldModel = FStreamingLevelCollectionModel::Create(InWorld); } } } return WorldModel; } #undef LOCTEXT_NAMESPACE