Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Toolkits/SStandaloneAssetEditorToolkitHost.cpp
2025-05-18 13:04:45 +08:00

493 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Toolkits/SStandaloneAssetEditorToolkitHost.h"
#include "Toolkits/AssetEditorToolkitMenuContext.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Widgets/Layout/SBorder.h"
#include "Misc/ConfigCacheIni.h"
#include "Modules/ModuleManager.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/AppStyle.h"
#include "Toolkits/ToolkitManager.h"
#include "Interfaces/IMainFrameModule.h"
#include "Widgets/Docking/SDockTab.h"
#include "UObject/Package.h"
#include "StatusBarSubsystem.h"
#include "ToolMenus.h"
#include "EditorModeManager.h"
#include "EditorModes.h"
#include "WidgetDrawerConfig.h"
#include "Elements/Framework/TypedElementCommonActions.h"
#define LOCTEXT_NAMESPACE "StandaloneAssetEditorToolkit"
static int32 StatusBarIdGenerator = 0;
void SStandaloneAssetEditorToolkitHost::Construct( const SStandaloneAssetEditorToolkitHost::FArguments& InArgs, const TSharedPtr<FTabManager>& InTabManager, const FName InitAppName )
{
ToolbarSlot = nullptr;
EditorCloseRequest = InArgs._OnRequestClose;
EditorClosing = InArgs._OnClose;
AppName = InitAppName;
// Asset editors have non-unique names. For example every material editor is just "MaterialEditor" so the base app name plus a unique number to generate uniqueness. This number is not used across sessions and should never be saved.
StatusBarName = FName(AppName, ++StatusBarIdGenerator);
MyTabManager = InTabManager;
CommonActions = NewObject<UTypedElementCommonActions>();
CommonActions->AddToRoot();
}
void SStandaloneAssetEditorToolkitHost::SetupInitialContent( const TSharedRef<FTabManager::FLayout>& DefaultLayout, const TSharedPtr<SDockTab>& InHostTab, const bool bCreateDefaultStandaloneMenu )
{
// @todo toolkit major: Expose common asset editing features here! (or require the asset editor's content to do this itself!)
// - Add a "toolkit menu"
// - Toolkits can access this and add menu items as needed
// - In world-centric, main frame menu becomes extendable
// - e.g., "Blueprint", "Debug" menus added
// - In standalone, toolkits get their own menu
// - Also, the core menu is just added as the first pull-down in the standalone menu
// - Multiple toolkits can be active and add their own menu items!
// - In world-centric, the core toolkit menu is available from the drop down
// - No longer need drop down next to toolkit display? Not sure... Probably still want this
// - Add a "toolkit toolbar"
// - In world-centric, draws next to the level editor tool bar (or on top of)
// - Could either extend existing tool bar or add additional tool bars
// - May need to change arrangement to allow for wider tool bars (maybe displace grid settings too)
// - In standalone, just draws under the toolkit's menu
const FName AssetEditorMenuName = GetMenuName();
if (!UToolMenus::Get()->IsMenuRegistered(AssetEditorMenuName))
{
UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(AssetEditorMenuName, "MainFrame.MainMenu");
if (bCreateDefaultStandaloneMenu)
{
CreateDefaultStandaloneMenuBar(Menu);
}
}
DefaultMenuWidget = SNullWidget::NullWidget;
HostTabPtr = InHostTab;
MenuOverlayWidgetContent = SNew(SBox);
RestoreFromLayout(DefaultLayout);
GenerateMenus(bCreateDefaultStandaloneMenu);
if (InHostTab)
{
InHostTab->SetRightContent(
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(8.0f, 0.0f, 8.0f, 0.0f)
[
MenuOverlayWidgetContent.ToSharedRef()
]
);
}
}
void SStandaloneAssetEditorToolkitHost::CreateDefaultStandaloneMenuBar(UToolMenu* MenuBar)
{
struct Local
{
static void ExtendFileMenu(UToolMenu* InMenuBar)
{
const FName MenuName = *(InMenuBar->GetMenuName().ToString() + TEXT(".") + TEXT("File"));
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu(MenuName);
FToolMenuSection& FileAssetSection = Menu->FindOrAddSection("FileAsset");
FileAssetSection.Label = LOCTEXT("FileAssetSectionHeading", "Open");
FileAssetSection.AddDynamicEntry(NAME_None, FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
if (UAssetEditorToolkitMenuContext* Context = InSection.FindContext<UAssetEditorToolkitMenuContext>())
{
Context->Toolkit.Pin()->FillDefaultFileMenuOpenCommands(InSection);
}
}));
FToolMenuSection& Section = Menu->FindOrAddSection("FileLoadAndSave");
Section.AddDynamicEntry(NAME_None, FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
if (UAssetEditorToolkitMenuContext* Context = InSection.FindContext<UAssetEditorToolkitMenuContext>())
{
Context->Toolkit.Pin()->FillDefaultFileMenuCommands(InSection);
}
}));
}
static void FillAssetMenu(UToolMenu* InMenu)
{
FToolMenuSection& Section = InMenu->AddSection("AssetEditorActions", LOCTEXT("ActionsHeading", "Actions"));
Section.AddDynamicEntry(NAME_None, FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
if (UAssetEditorToolkitMenuContext* Context = InSection.FindContext<UAssetEditorToolkitMenuContext>())
{
Context->Toolkit.Pin()->FillDefaultAssetMenuCommands(InSection);
}
}));
}
static void ExtendHelpMenu(UToolMenu* InMenuBar)
{
const FName MenuName = *(InMenuBar->GetMenuName().ToString() + TEXT(".") + TEXT("Help"));
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu(MenuName);
Menu->AddDynamicSection(NAME_None, FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu)
{
TSharedPtr<FAssetEditorToolkit> Toolkit = InMenu->FindContext<UAssetEditorToolkitMenuContext>()->Toolkit.Pin();
FFormatNamedArguments Args;
Args.Add(TEXT("Editor"), Toolkit->GetBaseToolkitName());
FToolMenuSection& Section = InMenu->AddSection("HelpResources", FText::Format(NSLOCTEXT("MainHelpMenu", "AssetEditorHelpResources", "{Editor} Resources"), Args));
Section.InsertPosition = FToolMenuInsert("Learn", EToolMenuInsertType::First);
Toolkit->FillDefaultHelpMenuCommands(Section);
}));
}
};
// Add asset-specific menu items to the top of the "File" menu
Local::ExtendFileMenu(MenuBar);
// Add the "Asset" menu, if we're editing an asset
MenuBar->FindOrAddSection(NAME_None).AddDynamicEntry("DynamicAssetEntry", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
UAssetEditorToolkitMenuContext* Context = InSection.FindContext<UAssetEditorToolkitMenuContext>();
if (Context && Context->Toolkit.IsValid() && Context->Toolkit.Pin()->IsActuallyAnAsset())
{
InSection.AddSubMenu(
"Asset",
LOCTEXT("AssetMenuLabel", "Asset"), // @todo toolkit major: Either use "Asset", "File", or the asset type name e.g. "Blueprint" (Also update custom pull-down menus)
LOCTEXT("AssetMenuLabel_ToolTip", "Opens a menu with commands for managing this asset"),
FNewToolMenuDelegate::CreateStatic(&Local::FillAssetMenu)
).InsertPosition = FToolMenuInsert("Edit", EToolMenuInsertType::After);
}
}));
// Add asset-specific menu items to the "Help" menu
Local::ExtendHelpMenu(MenuBar);
}
void SStandaloneAssetEditorToolkitHost::RestoreFromLayout( const TSharedRef<FTabManager::FLayout>& NewLayout )
{
BindEditorCloseRequestToHostTab();
const TSharedRef<SDockTab> HostTab = HostTabPtr.Pin().ToSharedRef();
HostTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateSP(this, &SStandaloneAssetEditorToolkitHost::OnTabClosed));
this->ChildSlot[SNullWidget::NullWidget];
MyTabManager->CloseAllAreas();
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow( HostTab );
TSharedPtr<SWidget> RestoredUI = MyTabManager->RestoreFrom( NewLayout, ParentWindow );
checkf(RestoredUI.IsValid(), TEXT("The layout must have a primary dock area"));
UE::Editor::Toolbars::ECreateStatusBarOptions StatusBarCreationOptions = UE::Editor::Toolbars::ECreateStatusBarOptions::Default;
if (HostedAssetEditorToolkit.IsValid())
{
StatusBarCreationOptions = HostedAssetEditorToolkit->GetStatusBarCreationOptions();
}
if (UStatusBarSubsystem* StatusBarSubsystem = GEditor ? GEditor->GetEditorSubsystem<UStatusBarSubsystem>() : nullptr)
{
StatusBarWidget = StatusBarSubsystem->MakeStatusBarWidget(StatusBarName, HostTab, StatusBarCreationOptions);
}
this->ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(FMargin(0.0f, 0.0f, 0.0f, 2.0f))
.AutoHeight()
.Expose(ToolbarSlot)
+ SVerticalBox::Slot()
.Padding(4.f, 2.f, 4.f, 2.f)
.FillHeight(1.0f)
[
RestoredUI.ToSharedRef()
]
+ SVerticalBox::Slot()
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
.AutoHeight()
[
StatusBarWidget.IsValid() ? StatusBarWidget.ToSharedRef() : SNullWidget::NullWidget
]
];
}
FName SStandaloneAssetEditorToolkitHost::GetMenuName() const
{
FName MenuAppName;
if (HostedAssetEditorToolkit.IsValid())
{
MenuAppName = HostedAssetEditorToolkit->GetToolMenuAppName();
}
else
{
MenuAppName = AppName;
}
return *(FString(TEXT("AssetEditor.")) + MenuAppName.ToString() + TEXT(".MainMenu"));
}
TSharedRef<SWidget> SStandaloneAssetEditorToolkitHost::CreateMenuBar(FToolMenuContext& ToolMenuContext) const
{
TSharedPtr<SWidget> ToolbarMenu;
const FName AssetEditorMenuName = GetMenuName();
if (HostedAssetEditorToolkit.IsValid())
{
// This might return nullptr if the implementation doesn't provide a custom toolbar menu.
ToolbarMenu = HostedAssetEditorToolkit->CreateMenuBar(MyTabManager, AssetEditorMenuName, ToolMenuContext);
}
if (!ToolbarMenu)
{
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>( "MainFrame" );
ToolbarMenu = MainFrameModule.MakeMainMenu(MyTabManager, AssetEditorMenuName, ToolMenuContext);
}
return ToolbarMenu.ToSharedRef();
}
void SStandaloneAssetEditorToolkitHost::GenerateMenus(bool bForceCreateMenu)
{
if( bForceCreateMenu || DefaultMenuWidget != SNullWidget::NullWidget )
{
UAssetEditorToolkitMenuContext* ContextObject = NewObject<UAssetEditorToolkitMenuContext>();
ContextObject->Toolkit = HostedAssetEditorToolkit;
FToolMenuContext ToolMenuContext(HostedAssetEditorToolkit->GetToolkitCommands(), FExtender::Combine(MenuExtenders), ContextObject);
HostedAssetEditorToolkit->InitToolMenuContext(ToolMenuContext);
DefaultMenuWidget = CreateMenuBar(ToolMenuContext);
}
}
void SStandaloneAssetEditorToolkitHost::SetMenuOverlay( TSharedRef<SWidget> NewOverlay )
{
MenuOverlayWidgetContent->SetContent(NewOverlay);
}
void SStandaloneAssetEditorToolkitHost::SetToolbar(TSharedPtr<SWidget> Toolbar)
{
if (Toolbar)
{
(*ToolbarSlot)
[
Toolbar.ToSharedRef()
];
}
else
{
(*ToolbarSlot)
[
SNullWidget::NullWidget
];
}
}
void SStandaloneAssetEditorToolkitHost::RegisterDrawer(FWidgetDrawerConfig&& Drawer, int32 SlotIndex)
{
if (StatusBarWidget.IsValid())
{
GEditor->GetEditorSubsystem<UStatusBarSubsystem>()->RegisterDrawer(StatusBarName, MoveTemp(Drawer), SlotIndex);
}
}
FEditorModeTools& SStandaloneAssetEditorToolkitHost::GetEditorModeManager() const
{
check(HostedAssetEditorToolkit.IsValid());
return HostedAssetEditorToolkit->GetEditorModeManager();
}
void SStandaloneAssetEditorToolkitHost::BindEditorCloseRequestToHostTab()
{
if (TSharedPtr<SDockTab> HostTab = HostTabPtr.Pin())
{
HostTab->SetCanCloseTab(EditorCloseRequest);
}
}
void SStandaloneAssetEditorToolkitHost::UnbindEditorCloseRequestFromHostTab()
{
if (TSharedPtr<SDockTab> HostTab = HostTabPtr.Pin())
{
HostTab->SetCanCloseTab(SDockTab::FCanCloseTab());
}
}
SStandaloneAssetEditorToolkitHost::~SStandaloneAssetEditorToolkitHost()
{
ShutdownToolkitHost();
CommonActions->RemoveFromRoot();
}
TSharedRef< SWidget > SStandaloneAssetEditorToolkitHost::GetParentWidget()
{
return AsShared();
}
void SStandaloneAssetEditorToolkitHost::BringToFront()
{
// If our host window is not active, force it to front to ensure the tab will be visible
// The tab manager won't activate a tab on an inactive window in all cases
const TSharedPtr<SDockTab> HostTab = HostTabPtr.Pin();
if (HostTab.IsValid())
{
TSharedPtr<SWindow> ParentWindow = HostTab->GetParentWindow();
if (ParentWindow.IsValid() && !ParentWindow->IsActive())
{
ParentWindow->BringToFront();
}
}
FGlobalTabmanager::Get()->DrawAttentionToTabManager( this->MyTabManager.ToSharedRef() );
}
void SStandaloneAssetEditorToolkitHost::OnToolkitHostingStarted( const TSharedRef< class IToolkit >& Toolkit )
{
// Keep track of the toolkit we're hosting
HostedToolkits.Add(Toolkit);
// The tab manager needs to know how to spawn tabs from this toolkit
Toolkit->RegisterTabSpawners(MyTabManager.ToSharedRef());
if (!HostedAssetEditorToolkit.IsValid())
{
HostedAssetEditorToolkit = StaticCastSharedRef<FAssetEditorToolkit>(Toolkit);
}
else
{
HostedAssetEditorToolkit->OnToolkitHostingStarted(Toolkit);
}
}
void SStandaloneAssetEditorToolkitHost::ShutdownToolkitHost()
{
const TSharedPtr<SDockTab> HostTab = HostTabPtr.Pin();
if (HostTab.IsValid())
{
HostTab->RequestCloseTab();
}
// Let the toolkit manager know that we're going away now
FToolkitManager::Get().OnToolkitHostDestroyed(this);
HostedToolkits.Reset();
HostedAssetEditorToolkit.Reset();
}
void SStandaloneAssetEditorToolkitHost::OnToolkitHostingFinished( const TSharedRef< class IToolkit >& Toolkit )
{
// The tab manager should forget how to spawn tabs from this toolkit
Toolkit->UnregisterTabSpawners(MyTabManager.ToSharedRef());
HostedToolkits.Remove(Toolkit);
// Standalone Asset Editors close by shutting down their major tab.
if (Toolkit == HostedAssetEditorToolkit)
{
ShutdownToolkitHost();
}
else if (HostedAssetEditorToolkit.IsValid())
{
HostedAssetEditorToolkit->OnToolkitHostingFinished(Toolkit);
}
}
UWorld* SStandaloneAssetEditorToolkitHost::GetWorld() const
{
// Currently, standalone asset editors never have a world
UE_LOG(LogInit, Warning, TEXT("IToolkitHost::GetWorld() doesn't make sense in SStandaloneAssetEditorToolkitHost currently"));
return NULL;
}
UTypedElementCommonActions* SStandaloneAssetEditorToolkitHost::GetCommonActions() const
{
return CommonActions.Get();
}
void SStandaloneAssetEditorToolkitHost::AddViewportOverlayWidget(TSharedRef<SWidget> InOverlaidWidget, int32 ZOrder, TSharedPtr<IAssetViewport> InViewport)
{
if (HostedAssetEditorToolkit.IsValid())
{
HostedAssetEditorToolkit->AddViewportOverlayWidget(InOverlaidWidget, ZOrder);
}
}
void SStandaloneAssetEditorToolkitHost::RemoveViewportOverlayWidget(TSharedRef<SWidget> InOverlaidWidget, TSharedPtr<IAssetViewport> InViewport)
{
if (HostedAssetEditorToolkit.IsValid())
{
HostedAssetEditorToolkit->RemoveViewportOverlayWidget(InOverlaidWidget);
}
}
FReply SStandaloneAssetEditorToolkitHost::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
// Check to see if any of the actions for the toolkits can be processed by the current event
// If we are in debug mode do not process commands
if (FSlateApplication::Get().IsNormalExecution())
{
for (TSharedPtr<IToolkit>& HostedToolkit : HostedToolkits)
{
if (HostedToolkit->ProcessCommandBindings(InKeyEvent))
{
return FReply::Handled();
}
}
}
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
void SStandaloneAssetEditorToolkitHost::OnTabClosed(TSharedRef<SDockTab> TabClosed) const
{
check(TabClosed == HostTabPtr.Pin());
EditorClosing.ExecuteIfBound();
MyTabManager->SetMenuMultiBox(nullptr, nullptr);
if(HostedAssetEditorToolkit.IsValid())
{
const TArray<UObject*>* const ObjectsBeingEdited = HostedAssetEditorToolkit->GetObjectsCurrentlyBeingEdited();
if(ObjectsBeingEdited)
{
const bool IsDockedAssetEditor = TabClosed->HasSiblingTab(FName("DockedToolkit"), false/*TreatIndexNoneAsWildcard*/);
const EAssetEditorToolkitTabLocation AssetEditorToolkitTabLocation = (IsDockedAssetEditor) ? EAssetEditorToolkitTabLocation::Docked : EAssetEditorToolkitTabLocation::Standalone;
for(const UObject* ObjectBeingEdited : *ObjectsBeingEdited)
{
// Only record assets that have a valid saved package
UPackage* const Package = ObjectBeingEdited->GetOutermost();
if(Package && Package->GetFileSize())
{
GConfig->SetInt(
TEXT("AssetEditorToolkitTabLocation"),
*ObjectBeingEdited->GetPathName(),
static_cast<int32>(AssetEditorToolkitTabLocation),
GEditorPerProjectIni
);
}
}
}
}
}
#undef LOCTEXT_NAMESPACE