EM_Task/UnrealEd/Private/Toolkits/AssetEditorToolkit.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

1202 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Toolkits/AssetEditorToolkit.h"
#include "Widgets/Layout/SBorder.h"
#include "Framework/MultiBox/MultiBoxDefs.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "GameFramework/Actor.h"
#include "Editor.h"
#include "Misc/ConfigCacheIni.h"
#include "Modules/ModuleManager.h"
#include "EditorStyleSet.h"
#include "Classes/EditorStyleSettings.h"
#include "EditorReimportHandler.h"
#include "FileHelpers.h"
#include "Toolkits/SStandaloneAssetEditorToolkitHost.h"
#include "Toolkits/ToolkitManager.h"
#include "Toolkits/AssetEditorCommonCommands.h"
#include "Toolkits/GlobalEditorCommonCommands.h"
#include "Styling/SlateIconFinder.h"
#include "CollectionManagerTypes.h"
#include "ICollectionManager.h"
#include "CollectionManagerModule.h"
#include "Widgets/SToolTip.h"
#include "IDocumentation.h"
#include "IIntroTutorials.h"
#include "Widgets/Docking/SDockTab.h"
#include "IAssetTools.h"
#include "IAssetTypeActions.h"
#include "AssetToolsModule.h"
#include "Toolkits/AssetEditorToolkitMenuContext.h"
#include "ToolMenus.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Logging/LogMacros.h"
#define LOCTEXT_NAMESPACE "AssetEditorToolkit"
DEFINE_LOG_CATEGORY_STATIC(LogAssetEditorToolkit, Log, All);
TWeakPtr<IToolkitHost> FAssetEditorToolkit::PreviousWorldCentricToolkitHostForNewAssetEditor;
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::SharedMenuExtensibilityManager;
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::SharedToolBarExtensibilityManager;
const FName FAssetEditorToolkit::ToolbarTabId(TEXT("AssetEditorToolkit_Toolbar"));
FAssetEditorToolkit::FAssetEditorToolkit()
: GCEditingObjects(*this), bCheckDirtyOnAssetSave(false), AssetEditorModeManager(nullptr), bIsToolbarFocusable(false), bIsToolbarUsingSmallIcons(false)
{
WorkspaceMenuCategory = FWorkspaceItem::NewGroup(LOCTEXT("WorkspaceMenu_BaseAssetEditor", "Asset Editor"));
}
void FAssetEditorToolkit::InitAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost>& InitToolkitHost, const FName AppIdentifier, const TSharedRef<FTabManager::FLayout>& StandaloneDefaultLayout, const bool bCreateDefaultStandaloneMenu, const bool bCreateDefaultToolbar, UObject* ObjectToEdit, const bool bInIsToolbarFocusable, const bool bInUseSmallToolbarIcons)
{
TArray<UObject*> ObjectsToEdit;
ObjectsToEdit.Add(ObjectToEdit);
InitAssetEditor(Mode, InitToolkitHost, AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, bInIsToolbarFocusable, bInUseSmallToolbarIcons);
}
void FAssetEditorToolkit::InitAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost>& InitToolkitHost, const FName AppIdentifier, const TSharedRef<FTabManager::FLayout>& StandaloneDefaultLayout, const bool bCreateDefaultStandaloneMenu, const bool bCreateDefaultToolbar, const TArray<UObject*>& ObjectsToEdit, const bool bInIsToolbarFocusable, const bool bInUseSmallToolbarIcons)
{
// Must not already be editing an object
check(ObjectsToEdit.Num() > 0);
check(EditingObjects.Num() == 0);
bIsToolbarFocusable = bInIsToolbarFocusable;
bIsToolbarUsingSmallIcons = bInUseSmallToolbarIcons;
// cache reference to ToolkitManager; also ensure it was initialized.
FToolkitManager& ToolkitManager = FToolkitManager::Get();
EditingObjects.Append(ObjectsToEdit);
// Store "previous" asset editing toolkit host, and clear it out
PreviousWorldCentricToolkitHost = PreviousWorldCentricToolkitHostForNewAssetEditor;
PreviousWorldCentricToolkitHostForNewAssetEditor.Reset();
ToolkitMode = Mode;
TSharedPtr<SWindow> ParentWindow;
TSharedPtr<SDockTab> NewMajorTab;
TSharedPtr<SStandaloneAssetEditorToolkitHost> NewStandaloneHost;
if (ToolkitMode == EToolkitMode::WorldCentric) // @todo toolkit major: Do we need to remember this setting on a per-asset editor basis? Probably.
{
// Keep track of the level editor we're attached to (if any)
ToolkitHost = InitToolkitHost;
}
else if (ensure(ToolkitMode == EToolkitMode::Standalone))
{
// Open a standalone app to edit this asset.
check(AppIdentifier != NAME_None);
// Create the label and the link for the toolkit documentation.
TAttribute<FText> Label = TAttribute<FText>(this, &FAssetEditorToolkit::GetToolkitName);
TAttribute<FText> LabelSuffix = TAttribute<FText>(this, &FAssetEditorToolkit::GetTabSuffix);
TAttribute<FText> ToolTipText = TAttribute<FText>(this, &FAssetEditorToolkit::GetToolkitToolTipText);
FString DocLink = GetDocumentationLink();
if (!DocLink.StartsWith("Shared/"))
{
DocLink = FString("Shared/") + DocLink;
}
// Create a new SlateToolkitHost
NewMajorTab = SNew(SDockTab)
.ContentPadding(0.0f)
.TabRole(ETabRole::MajorTab)
.ToolTip(IDocumentation::Get()->CreateToolTip(ToolTipText, nullptr, DocLink, GetToolkitFName().ToString()))
.Icon(this, &FAssetEditorToolkit::GetDefaultTabIcon)
.TabColorScale(this, &FAssetEditorToolkit::GetDefaultTabColor)
.Label(Label)
.LabelSuffix(LabelSuffix);
{
static_assert(sizeof(EAssetEditorToolkitTabLocation) == sizeof(int32), "EAssetEditorToolkitTabLocation is the incorrect size");
const UEditorStyleSettings* StyleSettings = GetDefault<UEditorStyleSettings>();
FName PlaceholderId(TEXT("StandaloneToolkit"));
TSharedPtr<FTabManager::FSearchPreference> SearchPreference = nullptr;
if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::Default)
{
// Work out where we should create this asset editor
EAssetEditorToolkitTabLocation SavedAssetEditorToolkitTabLocation = EAssetEditorToolkitTabLocation::Standalone;
GConfig->GetInt(
TEXT("AssetEditorToolkitTabLocation"),
*ObjectsToEdit[0]->GetPathName(),
reinterpret_cast<int32&>(SavedAssetEditorToolkitTabLocation),
GEditorPerProjectIni);
PlaceholderId = (SavedAssetEditorToolkitTabLocation == EAssetEditorToolkitTabLocation::Docked) ? TEXT("DockedToolkit") : TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch());
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::NewWindow)
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FRequireClosedTab());
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::MainWindow)
{
PlaceholderId = TEXT("DockedToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch(TEXT("LevelEditor")));
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::ContentBrowser)
{
PlaceholderId = TEXT("DockedToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch(TEXT("ContentBrowserTab1")));
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrNewWindow)
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(NAME_None));
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrMainWindow)
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(TEXT("LevelEditor")));
}
else if (StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrContentBrowser)
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(TEXT("ContentBrowserTab1")));
}
else
{
// Add more cases!
check(false);
}
FGlobalTabmanager::Get()->InsertNewDocumentTab(PlaceholderId, *SearchPreference, NewMajorTab.ToSharedRef());
// Bring the window to front. The tab manager will not do this for us to avoid intrusive stealing focus behavior
// However, here the expectation is that opening an new asset editor is something that should steal focus so the user can see their asset
TSharedPtr<SWindow> Window = NewMajorTab->GetParentWindow();
if (Window.IsValid())
{
Window->BringToFront();
}
}
IIntroTutorials& IntroTutorials = FModuleManager::LoadModuleChecked<IIntroTutorials>(TEXT("IntroTutorials"));
TSharedRef<SWidget> TutorialWidget = IntroTutorials.CreateTutorialsWidget(GetToolkitContextFName(), NewMajorTab->GetParentWindow());
NewMajorTab->SetRightContent(
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f, 8.0f, 0.0f)
.VAlign(VAlign_Center)
[TutorialWidget]);
const TSharedRef<FTabManager> NewTabManager = FGlobalTabmanager::Get()->NewTabManager(NewMajorTab.ToSharedRef());
NewTabManager->SetOnPersistLayout(FTabManager::FOnPersistLayout::CreateRaw(this, &FAssetEditorToolkit::HandleTabManagerPersistLayout));
this->TabManager = NewTabManager;
NewMajorTab->SetContent(
SAssignNew(NewStandaloneHost, SStandaloneAssetEditorToolkitHost, NewTabManager, AppIdentifier)
.OnRequestClose(this, &FAssetEditorToolkit::OnRequestClose)
.OnClose(this, &FAssetEditorToolkit::OnClose));
// Assign our toolkit host before we setup initial content. (Important: We must cache this pointer here as SetupInitialContent
// will callback into the toolkit host.)
ToolkitHost = NewStandaloneHost;
StandaloneHost = NewStandaloneHost;
}
check(ToolkitHost.IsValid());
ToolkitManager.RegisterNewToolkit(SharedThis(this));
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SaveAsset,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::SaveAsset_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanSaveAsset));
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SaveAssetAs,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::SaveAssetAs_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanSaveAssetAs));
ToolkitCommands->MapAction(
FGlobalEditorCommonCommands::Get().FindInContentBrowser,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::FindInContentBrowser_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanFindInContentBrowser));
ToolkitCommands->MapAction(
FGlobalEditorCommonCommands::Get().OpenDocumentation,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::BrowseDocumentation_Execute));
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().ReimportAsset,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::Reimport_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanReimport));
FGlobalEditorCommonCommands::MapActions(ToolkitCommands);
if (IsWorldCentricAssetEditor())
{
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SwitchToStandaloneEditor,
FExecuteAction::CreateStatic(&FAssetEditorToolkit::SwitchToStandaloneEditor_Execute, TWeakPtr<FAssetEditorToolkit>(AsShared())));
}
else
{
if (GetPreviousWorldCentricToolkitHost().IsValid())
{
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SwitchToWorldCentricEditor,
FExecuteAction::CreateStatic(&FAssetEditorToolkit::SwitchToWorldCentricEditor_Execute, TWeakPtr<FAssetEditorToolkit>(AsShared())));
}
}
// Create menus
if (ToolkitMode == EToolkitMode::Standalone)
{
AddMenuExtender(GetSharedMenuExtensibilityManager()->GetAllExtenders(ToolkitCommands, EditingObjects));
TSharedRef<FTabManager::FLayout> LayoutToUse = FLayoutSaveRestore::LoadFromConfig(GEditorLayoutIni, StandaloneDefaultLayout);
// Actually create the widget content
NewStandaloneHost->SetupInitialContent(LayoutToUse, NewMajorTab, bCreateDefaultStandaloneMenu);
}
// Create toolbars
AddToolbarExtender(GetSharedToolBarExtensibilityManager()->GetAllExtenders(ToolkitCommands, EditingObjects));
if (bCreateDefaultToolbar)
{
GenerateToolbar();
}
else
{
Toolbar = SNullWidget::NullWidget;
}
// NOTE: Currently, the AssetEditorManager will keep a hard reference to our object as we're editing it
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetsOpened(EditingObjects, this);
}
FAssetEditorToolkit::~FAssetEditorToolkit()
{
EditingObjects.Empty();
// We're no longer editing this object, so let the editor know
if (GEditor)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyEditorClosed(this);
}
}
void FAssetEditorToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
// Use the first child category of the local workspace root if there is one, otherwise use the root itself
const auto& LocalCategories = InTabManager->GetLocalWorkspaceMenuRoot()->GetChildItems();
AssetEditorTabsCategory = LocalCategories.Num() > 0 ? LocalCategories[0] : InTabManager->GetLocalWorkspaceMenuRoot();
InTabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FAssetEditorToolkit::SpawnTab_Toolbar))
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
.SetGroup(AssetEditorTabsCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Toolbar.Icon"));
}
void FAssetEditorToolkit::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
InTabManager->UnregisterTabSpawner(ToolbarTabId);
InTabManager->ClearLocalWorkspaceMenuCategories();
}
bool FAssetEditorToolkit::IsAssetEditor() const
{
return true;
}
FText FAssetEditorToolkit::GetToolkitName() const
{
const UObject* EditingObject = GetEditingObject();
check(EditingObject != NULL);
return GetLabelForObject(EditingObject);
}
FText FAssetEditorToolkit::GetTabSuffix() const
{
bool bDirtyState = false;
for (int32 x = 0; x < EditingObjects.Num(); ++x)
{
if (EditingObjects[x]->GetOutermost()->IsDirty())
{
bDirtyState = true;
break;
}
}
return bDirtyState ? LOCTEXT("TabSuffixAsterix", "*") : FText::GetEmpty();
}
FText FAssetEditorToolkit::GetToolkitToolTipText() const
{
const UObject* EditingObject = GetEditingObject();
check(EditingObject != NULL);
return GetToolTipTextForObject(EditingObject);
}
FText FAssetEditorToolkit::GetLabelForObject(const UObject* InObject)
{
FString NameString;
if (const AActor* ObjectAsActor = Cast<AActor>(InObject))
{
NameString = ObjectAsActor->GetActorLabel();
}
else
{
NameString = InObject->GetName();
}
return FText::FromString(NameString);
}
FText FAssetEditorToolkit::GetToolTipTextForObject(const UObject* InObject)
{
FString ToolTipString;
if (const AActor* ObjectAsActor = Cast<AActor>(InObject))
{
ToolTipString += LOCTEXT("ToolTipActorLabel", "Actor").ToString();
ToolTipString += TEXT(": ");
ToolTipString += ObjectAsActor->GetActorLabel();
}
else
{
ToolTipString += LOCTEXT("ToolTipAssetLabel", "Asset").ToString();
ToolTipString += TEXT(": ");
ToolTipString += InObject->GetName();
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
const FString CollectionNames = CollectionManagerModule.Get().GetCollectionsStringForObject(*InObject->GetPathName(), ECollectionShareType::CST_All);
if (!CollectionNames.IsEmpty())
{
ToolTipString += TEXT("\n");
ToolTipString += LOCTEXT("ToolTipCollectionsLabel", "Collections").ToString();
ToolTipString += TEXT(": ");
ToolTipString += CollectionNames;
}
}
return FText::FromString(ToolTipString);
}
class FEdMode* FAssetEditorToolkit::GetEditorMode() const
{
return nullptr;
}
class UEdMode* FAssetEditorToolkit::GetScriptableEditorMode() const
{
return nullptr;
}
FText FAssetEditorToolkit::GetEditorModeDisplayName() const
{
return FText::GetEmpty();
}
FSlateIcon FAssetEditorToolkit::GetEditorModeIcon() const
{
return FSlateIcon();
}
const TArray<UObject*>* FAssetEditorToolkit::GetObjectsCurrentlyBeingEdited() const
{
return &EditingObjects;
}
FName FAssetEditorToolkit::GetEditorName() const
{
return GetToolkitFName();
}
void FAssetEditorToolkit::FocusWindow(UObject* ObjectToFocusOn)
{
BringToolkitToFront();
}
bool FAssetEditorToolkit::CloseWindow()
{
if (OnRequestClose())
{
OnClose();
// Close this toolkit
FToolkitManager::Get().CloseToolkit(AsShared());
}
return true;
}
void FAssetEditorToolkit::InvokeTab(const FTabId& TabId)
{
GetTabManager()->TryInvokeTab(TabId);
}
TSharedPtr<class FTabManager> FAssetEditorToolkit::GetAssociatedTabManager()
{
return TabManager;
}
double FAssetEditorToolkit::GetLastActivationTime()
{
double MostRecentTime = 0.0;
if (TabManager.IsValid())
{
TSharedPtr<SDockTab> OwnerTab = TabManager->GetOwnerTab();
if (OwnerTab.IsValid())
{
MostRecentTime = OwnerTab->GetLastActivationTime();
}
}
return MostRecentTime;
}
TSharedPtr<IToolkitHost> FAssetEditorToolkit::GetPreviousWorldCentricToolkitHost()
{
return PreviousWorldCentricToolkitHost.Pin();
}
void FAssetEditorToolkit::SetPreviousWorldCentricToolkitHostForNewAssetEditor(TSharedRef<IToolkitHost> ToolkitHost)
{
PreviousWorldCentricToolkitHostForNewAssetEditor = ToolkitHost;
}
UObject* FAssetEditorToolkit::GetEditingObject() const
{
check(EditingObjects.Num() == 1);
return EditingObjects[0];
}
const TArray<UObject*>& FAssetEditorToolkit::GetEditingObjects() const
{
check(EditingObjects.Num() > 0);
return EditingObjects;
}
void FAssetEditorToolkit::GetSaveableObjects(TArray<UObject*>& OutObjects) const
{
for (const auto Object: EditingObjects)
{
// If we are editing a subobject of asset (e.g., a level script blueprint which is contained in a map asset), still provide the
// option to work with it but treat save operations/etc... as working on the top level asset itself
for (UObject* TestObject = Object; TestObject != nullptr; TestObject = TestObject->GetOuter())
{
if (TestObject->IsAsset())
{
OutObjects.Add(TestObject);
break;
}
}
}
}
void FAssetEditorToolkit::AddEditingObject(UObject* Object)
{
EditingObjects.Add(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetOpened(Object, this);
}
void FAssetEditorToolkit::RemoveEditingObject(UObject* Object)
{
EditingObjects.Remove(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(Object, this);
}
void FAssetEditorToolkit::SaveAsset_Execute()
{
if (EditingObjects.Num() == 0)
{
return;
}
TArray<UObject*> ObjectsToSave;
GetSaveableObjects(ObjectsToSave);
if (ObjectsToSave.Num() == 0)
{
return;
}
TArray<UPackage*> PackagesToSave;
for (UObject* Object: ObjectsToSave)
{
if ((Object == nullptr) || !Object->IsAsset())
{
// Log an invalid object but don't try to save it
UE_LOG(LogAssetEditorToolkit, Log, TEXT("Invalid object to save: %s"), (Object != nullptr) ? *Object->GetFullName() : TEXT("Null Object"));
}
else
{
PackagesToSave.Add(Object->GetOutermost());
}
}
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirtyOnAssetSave, /*bPromptToSave=*/false);
}
void FAssetEditorToolkit::SaveAssetAs_Execute()
{
if (EditingObjects.Num() == 0)
{
return;
}
TSharedPtr<IToolkitHost> MyToolkitHost = ToolkitHost.Pin();
if (!MyToolkitHost.IsValid())
{
return;
}
// get collection of objects to save
TArray<UObject*> ObjectsToSave;
GetSaveableObjects(ObjectsToSave);
if (ObjectsToSave.Num() == 0)
{
return;
}
// save assets under new name
TArray<UObject*> SavedObjects;
FEditorFileUtils::SaveAssetsAs(ObjectsToSave, SavedObjects);
if (SavedObjects.Num() == 0)
{
return;
}
// close existing asset editors for resaved assets
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
/* @todo editor: Persona does not behave well when closing specific objects
for (int32 Index = 0; Index < ObjectsToSave.Num(); ++Index)
{
if ((SavedObjects[Index] != ObjectsToSave[Index]) && (SavedObjects[Index] != nullptr))
{
AssetEditorManager.CloseAllEditorsForAsset(ObjectsToSave[Index]);
}
}
// reopen asset editor
AssetEditorManager.OpenEditorForAssets(TArrayBuilder<UObject*>().Add(SavedObjects[0]), ToolkitMode, MyToolkitHost.ToSharedRef());
*/
// hack
TArray<UObject*> ObjectsToReopen;
for (auto Object: EditingObjects)
{
if (Object->IsAsset() && !ObjectsToSave.Contains(Object))
{
ObjectsToReopen.Add(Object);
}
}
for (auto Object: SavedObjects)
{
ObjectsToReopen.AddUnique(Object);
}
for (auto Object: EditingObjects)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(Object, this);
}
AssetEditorSubsystem->OpenEditorForAssets_Advanced(ObjectsToReopen, ToolkitMode, MyToolkitHost.ToSharedRef());
// end hack
}
const FSlateBrush* FAssetEditorToolkit::GetDefaultTabIcon() const
{
if (EditingObjects.Num() == 0)
{
return nullptr;
}
const FSlateBrush* IconBrush = nullptr;
for (UObject* Object: EditingObjects)
{
if (Object)
{
UClass* IconClass = Object->GetClass();
if (IconClass->IsChildOf<UBlueprint>())
{
UBlueprint* Blueprint = Cast<UBlueprint>(Object);
IconClass = Blueprint->GeneratedClass;
}
// Find the first object that has a valid brush
const FSlateBrush* ThisAssetBrush = FSlateIconFinder::FindIconBrushForClass(IconClass);
if (ThisAssetBrush != nullptr)
{
IconBrush = ThisAssetBrush;
break;
}
}
}
if (!IconBrush)
{
IconBrush = FEditorStyle::GetBrush(TEXT("ClassIcon.Default"));
;
}
return IconBrush;
}
FLinearColor FAssetEditorToolkit::GetDefaultTabColor() const
{
FLinearColor TabColor = FLinearColor::Transparent;
if (EditingObjects.Num() == 0 || !GetDefault<UEditorStyleSettings>()->bEnableColorizedEditorTabs)
{
return TabColor;
}
FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule();
IAssetTools& AssetTools = AssetToolsModule.Get();
for (auto ObjectIt = EditingObjects.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetTools.GetAssetTypeActionsForClass((*ObjectIt)->GetClass());
if (AssetTypeActions.IsValid())
{
const FLinearColor ThisAssetColor = AssetTypeActions.Pin()->GetTypeColor();
if (ThisAssetColor != FLinearColor::Transparent)
{
return ThisAssetColor;
}
}
}
return TabColor;
}
FAssetEditorModeManager* FAssetEditorToolkit::GetAssetEditorModeManager() const
{
return AssetEditorModeManager;
}
void FAssetEditorToolkit::SetAssetEditorModeManager(FAssetEditorModeManager* InModeManager)
{
AssetEditorModeManager = InModeManager;
}
void FAssetEditorToolkit::RemoveEditingAsset(UObject* Asset)
{
// Just close the editor tab if it's the last element
if (EditingObjects.Num() == 1 && EditingObjects.Contains(Asset))
{
CloseWindow();
}
else
{
RemoveEditingObject(Asset);
}
}
void FAssetEditorToolkit::SwitchToStandaloneEditor_Execute(TWeakPtr<FAssetEditorToolkit> ThisToolkitWeakRef)
{
// NOTE: We're being very careful here with pointer handling because we need to make sure the tookit's
// destructor is called when we call CloseToolkit, as it needs to be fully unregistered before we go
// and try to open a new asset editor for the same asset
// First, close the world-centric toolkit
TArray<FWeakObjectPtr> ObjectsToEditStandaloneWeak;
TSharedPtr<IToolkitHost> PreviousWorldCentricToolkitHost;
{
TSharedRef<FAssetEditorToolkit> ThisToolkit = ThisToolkitWeakRef.Pin().ToSharedRef();
check(ThisToolkit->IsWorldCentricAssetEditor());
PreviousWorldCentricToolkitHost = ThisToolkit->GetToolkitHost();
const auto& EditingObjects = *ThisToolkit->GetObjectsCurrentlyBeingEdited();
for (auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter)
{
ObjectsToEditStandaloneWeak.Add(*ObjectIter);
}
FToolkitManager::Get().CloseToolkit(ThisToolkit);
// At this point, we should be the only referencer of the toolkit! It will be fully destroyed when
// as the code pointer exits this block.
ensure(ThisToolkit.IsUnique());
}
// Now, reopen the toolkit in "standalone" mode
TArray<UObject*> ObjectsToEdit;
for (auto ObjectPtrItr = ObjectsToEditStandaloneWeak.CreateIterator(); ObjectPtrItr; ++ObjectPtrItr)
{
const auto WeakObjectPtr = *ObjectPtrItr;
if (WeakObjectPtr.IsValid())
{
ObjectsToEdit.Add(WeakObjectPtr.Get());
}
}
if (ObjectsToEdit.Num() > 0)
{
ensure(GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets_Advanced(ObjectsToEdit, EToolkitMode::Standalone, PreviousWorldCentricToolkitHost.ToSharedRef()));
}
}
void FAssetEditorToolkit::SwitchToWorldCentricEditor_Execute(TWeakPtr<FAssetEditorToolkit> ThisToolkitWeakRef)
{
// @todo toolkit minor: Maybe also allow the user to drag and drop the standalone editor's tab into a specific level editor to switch to world-centric mode?
// NOTE: We're being very careful here with pointer handling because we need to make sure the tookit's
// destructor is called when we call CloseToolkit, as it needs to be fully unregistered before we go
// and try to open a new asset editor for the same asset
// First, close the standalone toolkit
TArray<FWeakObjectPtr> ObjectToEditWorldCentricWeak;
TSharedPtr<IToolkitHost> WorldCentricLevelEditor;
{
TSharedRef<FAssetEditorToolkit> ThisToolkit = ThisToolkitWeakRef.Pin().ToSharedRef();
const auto& EditingObjects = *ThisToolkit->GetObjectsCurrentlyBeingEdited();
for (auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter)
{
ObjectToEditWorldCentricWeak.Add(*ObjectIter);
}
check(!ThisToolkit->IsWorldCentricAssetEditor());
WorldCentricLevelEditor = ThisToolkit->GetPreviousWorldCentricToolkitHost();
FToolkitManager::Get().CloseToolkit(ThisToolkit);
// At this point, we should be the only referencer of the toolkit! It will be fully destroyed when
// as the code pointer exits this block.
ensure(ThisToolkit.IsUnique());
}
// Now, reopen the toolkit in "world-centric" mode
TArray<UObject*> ObjectsToEdit;
for (auto ObjectPtrItr = ObjectToEditWorldCentricWeak.CreateIterator(); ObjectPtrItr; ++ObjectPtrItr)
{
const auto WeakObjectPtr = *ObjectPtrItr;
if (WeakObjectPtr.IsValid())
{
ObjectsToEdit.Add(WeakObjectPtr.Get());
}
}
if (ObjectsToEdit.Num() > 0)
{
ensure(GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets_Advanced(ObjectsToEdit, EToolkitMode::WorldCentric, WorldCentricLevelEditor));
}
}
void FAssetEditorToolkit::FindInContentBrowser_Execute()
{
TArray<UObject*> ObjectsToSyncTo;
GetSaveableObjects(ObjectsToSyncTo);
if (ObjectsToSyncTo.Num() > 0)
{
GEditor->SyncBrowserToObjects(ObjectsToSyncTo);
}
}
void FAssetEditorToolkit::BrowseDocumentation_Execute() const
{
IDocumentation::Get()->Open(GetDocumentationLink(), FDocumentationSourceInfo(TEXT("help_menu_asset")));
}
FString FAssetEditorToolkit::GetDocumentationLink() const
{
return FString(TEXT("%ROOT%"));
}
bool FAssetEditorToolkit::CanReimport() const
{
for (auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter)
{
auto EditingObject = *ObjectIter;
if (CanReimport(EditingObject))
{
return true;
}
}
return false;
}
bool FAssetEditorToolkit::CanReimport(UObject* EditingObject) const
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
if (EditingObject != NULL && EditingObject->IsAsset())
{
if (FReimportManager::Instance()->CanReimport(EditingObject))
{
return true;
}
}
return false;
}
void FAssetEditorToolkit::Reimport_Execute()
{
if (ensure(EditingObjects.Num() > 0))
{
for (auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter)
{
const auto EditingObject = *ObjectIter;
Reimport_Execute(EditingObject);
}
}
}
void FAssetEditorToolkit::Reimport_Execute(UObject* EditingObject)
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
if (EditingObject != NULL && EditingObject->IsAsset())
{
// Reimport the asset
FReimportManager::Instance()->Reimport(EditingObject, ShouldPromptForNewFilesOnReload(*EditingObject));
}
}
bool FAssetEditorToolkit::ShouldPromptForNewFilesOnReload(const UObject& EditingObject) const
{
return true;
}
TSharedRef<SDockTab> FAssetEditorToolkit::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == ToolbarTabId);
TSharedRef<SDockTab> DockTab = SNew(SDockTab)
.Label(NSLOCTEXT("AssetEditorToolkit", "Toolbar_TabTitle", "Toolbar"))
.Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Toolbar"))
.ShouldAutosize(true)
[SAssignNew(ToolbarWidgetContent, SBorder)
.Padding(0)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))];
if (Toolbar.IsValid())
{
ToolbarWidgetContent->SetContent(Toolbar.ToSharedRef());
}
return DockTab;
}
void FAssetEditorToolkit::FillDefaultFileMenuCommands(FToolMenuSection& InSection)
{
const FToolMenuInsert InsertPosition(NAME_None, EToolMenuInsertType::First);
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SaveAsset, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetEditor.SaveAsset.Greyscale")).InsertPosition = InsertPosition;
if (IsActuallyAnAsset())
{
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SaveAssetAs, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetEditor.SaveAssetAs.Small")).InsertPosition = InsertPosition;
}
InSection.AddSeparator("DefaultFileMenuCommandsSeparator").InsertPosition = InsertPosition;
;
if (IsWorldCentricAssetEditor())
{
// @todo toolkit minor: It would be awesome if the user could just "tear off" the SToolkitDisplay to do SwitchToStandaloneEditor
// Would need to probably drop at mouseup location though instead of using saved layout pos.
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SwitchToStandaloneEditor).InsertPosition = InsertPosition;
;
}
else
{
if (GetPreviousWorldCentricToolkitHost().IsValid())
{
// @todo toolkit checkin: Disabled temporarily until we have world-centric "ready to use"!
if (0)
{
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SwitchToWorldCentricEditor).InsertPosition = InsertPosition;
;
}
}
}
}
void FAssetEditorToolkit::FillDefaultAssetMenuCommands(FToolMenuSection& InSection)
{
InSection.AddMenuEntry(FGlobalEditorCommonCommands::Get().FindInContentBrowser, LOCTEXT("FindInContentBrowser", "Find in Content Browser..."));
// Commands we only want to be accessible when editing an asset should go here
if (IsActuallyAnAsset())
{
// Add a reimport menu entry for each supported editable object
for (auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter)
{
const auto EditingObject = *ObjectIter;
if (EditingObject != NULL && EditingObject->IsAsset())
{
if (CanReimport(EditingObject))
{
FFormatNamedArguments LabelArguments;
LabelArguments.Add(TEXT("Name"), FText::FromString(EditingObject->GetName()));
const FText LabelText = FText::Format(LOCTEXT("Reimport_Label", "Reimport {Name}..."), LabelArguments);
FFormatNamedArguments ToolTipArguments;
ToolTipArguments.Add(TEXT("Type"), FText::FromString(EditingObject->GetClass()->GetName()));
const FText ToolTipText = FText::Format(LOCTEXT("Reimport_ToolTip", "Reimports this {Type}"), ToolTipArguments);
const FName IconName = TEXT("AssetEditor.Reimport");
FUIAction UIAction;
UIAction.ExecuteAction.BindRaw(this, &FAssetEditorToolkit::Reimport_Execute, EditingObject);
InSection.AddMenuEntry(NAME_None, LabelText, ToolTipText, FSlateIcon(FEditorStyle::GetStyleSetName(), IconName), UIAction);
}
}
}
}
}
void FAssetEditorToolkit::FillDefaultHelpMenuCommands(FToolMenuSection& InSection)
{
FFormatNamedArguments Args;
Args.Add(TEXT("Editor"), GetBaseToolkitName());
const FText ToolTip = FText::Format(LOCTEXT("BrowseDocumentationTooltip", "Browse {Editor} documentation..."), Args);
InSection.AddMenuEntry(FGlobalEditorCommonCommands::Get().OpenDocumentation, ToolTip);
}
FName FAssetEditorToolkit::GetToolMenuAppName() const
{
if (IsSimpleAssetEditor() && EditingObjects.Num() == 1 && EditingObjects[0])
{
return *(EditingObjects[0]->GetClass()->GetFName().ToString() + TEXT("Editor"));
}
return GetToolkitFName();
}
FName FAssetEditorToolkit::GetToolMenuName() const
{
return *(TEXT("AssetEditor.") + GetToolMenuAppName().ToString() + TEXT(".MainMenu"));
}
FName FAssetEditorToolkit::GetToolMenuToolbarName() const
{
FName ParentName;
return GetToolMenuToolbarName(ParentName);
}
FName FAssetEditorToolkit::GetToolMenuToolbarName(FName& OutParentName) const
{
static const FName DefaultToolbarName = "AssetEditor.DefaultToolBar";
OutParentName = DefaultToolbarName;
return *(TEXT("AssetEditor.") + GetToolMenuAppName().ToString() + TEXT(".ToolBar"));
}
void FAssetEditorToolkit::RegisterDefaultToolBar()
{
static const FName DefaultToolBarName("AssetEditor.DefaultToolBar");
UToolMenus* ToolMenus = UToolMenus::Get();
if (!ToolMenus->IsMenuRegistered(DefaultToolBarName))
{
UToolMenu* ToolbarBuilder = ToolMenus->RegisterMenu(DefaultToolBarName, NAME_None, EMultiBoxType::ToolBar);
{
FToolMenuSection& Section = ToolbarBuilder->AddSection("Asset");
Section.AddEntry(FToolMenuEntry::InitToolBarButton(FAssetEditorCommonCommands::Get().SaveAsset));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(FGlobalEditorCommonCommands::Get().FindInContentBrowser, LOCTEXT("FindInContentBrowserButton", "Browse")));
}
}
}
void FAssetEditorToolkit::InitToolMenuContext(FToolMenuContext& MenuContext)
{
}
void FAssetEditorToolkit::GenerateToolbar()
{
TSharedPtr<FExtender> Extender = FExtender::Combine(ToolbarExtenders);
RegisterDefaultToolBar();
FName ParentToolbarName;
const FName ToolBarName = GetToolMenuToolbarName(ParentToolbarName);
UToolMenus* ToolMenus = UToolMenus::Get();
UToolMenu* FoundMenu = ToolMenus->FindMenu(ToolBarName);
if (!FoundMenu || !FoundMenu->IsRegistered())
{
FoundMenu = ToolMenus->RegisterMenu(ToolBarName, ParentToolbarName, EMultiBoxType::ToolBar);
}
FToolMenuContext MenuContext(GetToolkitCommands(), Extender);
UAssetEditorToolkitMenuContext* ToolkitMenuContext = NewObject<UAssetEditorToolkitMenuContext>(FoundMenu);
ToolkitMenuContext->Toolkit = AsShared();
MenuContext.AddObject(ToolkitMenuContext);
InitToolMenuContext(MenuContext);
UToolMenu* GeneratedToolbar = ToolMenus->GenerateMenu(ToolBarName, MenuContext);
GeneratedToolbar->bToolBarIsFocusable = bIsToolbarFocusable;
GeneratedToolbar->bToolBarForceSmallIcons = bIsToolbarUsingSmallIcons;
TSharedRef<class SWidget> ToolBarWidget = ToolMenus->GenerateWidget(GeneratedToolbar);
TSharedRef<SHorizontalBox> MiscWidgets = SNew(SHorizontalBox);
for (int32 WidgetIdx = 0; WidgetIdx < ToolbarWidgets.Num(); ++WidgetIdx)
{
MiscWidgets->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0.0f, 2.0f, 0.0f, 2.0f)
[ToolbarWidgets[WidgetIdx]];
}
Toolbar =
SNew(SHorizontalBox) + SHorizontalBox::Slot().HAlign(HAlign_Left).VAlign(VAlign_Center)[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().VAlign(VAlign_Bottom)[ToolBarWidget]] + SHorizontalBox::Slot().HAlign(HAlign_Right).VAlign(VAlign_Center).AutoWidth()[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().VAlign(VAlign_Bottom)[SNew(SBorder).BorderImage(FEditorStyle::GetBrush(TEXT("Toolbar.Background"))).Visibility(ToolbarWidgets.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed)[MiscWidgets]]];
if (ToolbarWidgetContent.IsValid())
{
ToolbarWidgetContent->SetContent(Toolbar.ToSharedRef());
}
}
void FAssetEditorToolkit::RegenerateMenusAndToolbars()
{
RemoveAllToolbarWidgets();
TSharedPtr<class SStandaloneAssetEditorToolkitHost> HostWidget = StandaloneHost.Pin();
if (HostWidget)
{
HostWidget->GenerateMenus(false);
}
if (Toolbar != SNullWidget::NullWidget)
{
GenerateToolbar();
}
PostRegenerateMenusAndToolbars();
}
void FAssetEditorToolkit::RestoreFromLayout(const TSharedRef<FTabManager::FLayout>& NewLayout)
{
TSharedPtr<class SStandaloneAssetEditorToolkitHost> HostWidget = StandaloneHost.Pin();
if (HostWidget.Get() != NULL)
{
// Save the old layout
FLayoutSaveRestore::SaveToConfig(GEditorLayoutIni, TabManager->PersistLayout());
// Load the potentially previously saved new layout
TSharedRef<FTabManager::FLayout> UserConfiguredNewLayout = FLayoutSaveRestore::LoadFromConfig(GEditorLayoutIni, NewLayout);
for (TSharedPtr<FLayoutExtender> LayoutExtender: LayoutExtenders)
{
NewLayout->ProcessExtensions(*LayoutExtender);
}
// Apply the new layout
HostWidget->RestoreFromLayout(UserConfiguredNewLayout);
}
}
bool FAssetEditorToolkit::IsActuallyAnAsset() const
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
bool bIsActuallyAnAsset = false;
for (auto ObjectIter = GetObjectsCurrentlyBeingEdited()->CreateConstIterator(); !bIsActuallyAnAsset && ObjectIter; ++ObjectIter)
{
const auto ObjectBeingEdited = *ObjectIter;
bIsActuallyAnAsset |= ObjectBeingEdited != NULL && ObjectBeingEdited->IsAsset();
}
return bIsActuallyAnAsset;
}
void FAssetEditorToolkit::AddMenuExtender(TSharedPtr<FExtender> Extender)
{
StandaloneHost.Pin()->GetMenuExtenders().AddUnique(Extender);
}
void FAssetEditorToolkit::RemoveMenuExtender(TSharedPtr<FExtender> Extender)
{
StandaloneHost.Pin()->GetMenuExtenders().Remove(Extender);
}
void FAssetEditorToolkit::AddToolbarExtender(TSharedPtr<FExtender> Extender)
{
ToolbarExtenders.AddUnique(Extender);
}
void FAssetEditorToolkit::RemoveToolbarExtender(TSharedPtr<FExtender> Extender)
{
ToolbarExtenders.Remove(Extender);
}
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::GetSharedMenuExtensibilityManager()
{
if (!SharedMenuExtensibilityManager.IsValid())
{
SharedMenuExtensibilityManager = MakeShareable(new FExtensibilityManager);
}
return SharedMenuExtensibilityManager;
}
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::GetSharedToolBarExtensibilityManager()
{
if (!SharedToolBarExtensibilityManager.IsValid())
{
SharedToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager);
}
return SharedToolBarExtensibilityManager;
}
void FAssetEditorToolkit::SetMenuOverlay(TSharedRef<SWidget> Widget)
{
StandaloneHost.Pin()->SetMenuOverlay(Widget);
}
void FAssetEditorToolkit::AddToolbarWidget(TSharedRef<SWidget> Widget)
{
ToolbarWidgets.AddUnique(Widget);
}
void FAssetEditorToolkit::RemoveAllToolbarWidgets()
{
ToolbarWidgets.Empty();
}
void FAssetEditorToolkit::FGCEditingObjects::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObjects(OwnerToolkit.EditingObjects);
// Remove null objects as a safe guard against assets being forcibly GC'd
OwnerToolkit.EditingObjects.RemoveAllSwap([](UObject* Obj)
{
return Obj == nullptr;
});
}
FString FAssetEditorToolkit::FGCEditingObjects::GetReferencerName() const
{
return TEXT("FAssetEditorToolkit::FGCEditorObjects");
}
TSharedPtr<FExtender> FExtensibilityManager::GetAllExtenders()
{
return FExtender::Combine(Extenders);
}
TSharedPtr<FExtender> FExtensibilityManager::GetAllExtenders(const TSharedRef<FUICommandList>& CommandList, const TArray<UObject*>& ContextSensitiveObjects)
{
auto OutExtenders = Extenders;
for (int32 i = 0; i < ExtenderDelegates.Num(); ++i)
{
if (ExtenderDelegates[i].IsBound())
{
OutExtenders.Add(ExtenderDelegates[i].Execute(CommandList, ContextSensitiveObjects));
}
}
return FExtender::Combine(OutExtenders);
}
#undef LOCTEXT_NAMESPACE