509 lines
14 KiB
C++
509 lines
14 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Toolkits/SGlobalTabSwitchingDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/MultiBox/MultiBox.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Layout/SScrollBorder.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "Framework/Docking/TabManager.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
|
|
#include "EngineGlobals.h"
|
|
#include "Editor.h"
|
|
|
|
#include "LevelEditor.h"
|
|
#include "WorkspaceMenuStructure.h"
|
|
#include "WorkspaceMenuStructureModule.h"
|
|
|
|
#if PLATFORM_MAC
|
|
#include "Mac/MacApplication.h"
|
|
#endif
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "ThumbnailRendering/ThumbnailManager.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SGlobalTabSwitchingDialog"
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FTabSwitchingListItemBase
|
|
|
|
class FTabSwitchingListItemBase
|
|
{
|
|
public:
|
|
FTabSwitchingListItemBase()
|
|
: LastAccessTime(0.0)
|
|
{
|
|
}
|
|
|
|
virtual ~FTabSwitchingListItemBase() {}
|
|
|
|
virtual TSharedRef<SWidget> CreateWidget(TSharedPtr<class FAssetThumbnailPool> AssetThumbnailPool)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
virtual FText GetTypeString() const
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
virtual FText GetPathString() const
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
virtual void ActivateTab() { }
|
|
|
|
virtual void ShowInContentBrowser()
|
|
{
|
|
}
|
|
|
|
virtual TSharedPtr<FTabManager> GetAssociatedTabManager()
|
|
{
|
|
return TSharedPtr<FTabManager>();
|
|
}
|
|
public:
|
|
double LastAccessTime;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FTabSwitchingListItem_Asset
|
|
|
|
class FTabSwitchingListItem_Asset : public FTabSwitchingListItemBase
|
|
{
|
|
public:
|
|
FTabSwitchingListItem_Asset(UObject* InAsset)
|
|
: MyAsset(InAsset)
|
|
{
|
|
if (IAssetEditorInstance* EditorInstance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/ false))
|
|
{
|
|
LastAccessTime = EditorInstance->GetLastActivationTime();
|
|
}
|
|
}
|
|
|
|
virtual TSharedRef<SWidget> CreateWidget(TSharedPtr<class FAssetThumbnailPool> AssetThumbnailPool) override
|
|
{
|
|
// Create a label for the asset name
|
|
const bool bDirtyState = MyAsset->GetOutermost()->IsDirty();
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("AssetName"), FText::AsCultureInvariant(MyAsset->GetName()));
|
|
Args.Add(TEXT("DirtyState"), bDirtyState ? LOCTEXT("AssetModified", " [Modified]") : FText::GetEmpty());
|
|
FText AssetText = FText::Format(LOCTEXT("AssetEntryLabel", "{AssetName}{DirtyState}"), Args);
|
|
|
|
// Create a thumbnail to represent the asset type
|
|
const int32 ThumbnailSize = 48;
|
|
|
|
FAssetData AssetData(MyAsset);
|
|
Thumbnail = MakeShareable(new FAssetThumbnail(MyAsset, ThumbnailSize, ThumbnailSize, AssetThumbnailPool));
|
|
|
|
FAssetThumbnailConfig ThumbnailConfig;
|
|
|
|
return SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0f, 4.0f)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(static_cast<float>(ThumbnailSize))
|
|
.HeightOverride(static_cast<float>(ThumbnailSize))
|
|
[
|
|
Thumbnail->MakeThumbnailWidget(ThumbnailConfig)
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(8.0f, 0.0f, 8.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FAppStyle::Get(), "ControlTabMenu.AssetNameStyle")
|
|
.Text(AssetText)
|
|
];
|
|
}
|
|
|
|
virtual void ShowInContentBrowser() override
|
|
{
|
|
TArray<UObject*> ObjectsToSync;
|
|
ObjectsToSync.Add(MyAsset);
|
|
GEditor->SyncBrowserToObjects(ObjectsToSync);
|
|
}
|
|
|
|
virtual FText GetTypeString() const override
|
|
{
|
|
return MyAsset->GetClass()->GetDisplayNameText();
|
|
}
|
|
|
|
virtual FText GetPathString() const override
|
|
{
|
|
return FText::AsCultureInvariant(MyAsset->GetOutermost()->GetName());
|
|
}
|
|
|
|
virtual void ActivateTab() override
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/ true);
|
|
}
|
|
|
|
virtual TSharedPtr<FTabManager> GetAssociatedTabManager() override
|
|
{
|
|
IAssetEditorInstance* Instance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/ false);
|
|
if (Instance)
|
|
{
|
|
return Instance->GetAssociatedTabManager();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
protected:
|
|
UObject* MyAsset;
|
|
TSharedPtr<class FAssetThumbnail> Thumbnail;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FTabSwitchingListItem_World
|
|
|
|
class FTabSwitchingListItem_World : public FTabSwitchingListItem_Asset
|
|
{
|
|
public:
|
|
static TSharedPtr<FTabSwitchingListItem_World> MakeWorldItem()
|
|
{
|
|
UWorld* MyWorld = nullptr;
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE)
|
|
{
|
|
MyWorld = Context.World();
|
|
break;
|
|
}
|
|
else if (Context.WorldType == EWorldType::Editor)
|
|
{
|
|
MyWorld = Context.World();
|
|
}
|
|
}
|
|
|
|
check(MyWorld != nullptr);
|
|
return MakeShareable(new FTabSwitchingListItem_World(MyWorld));
|
|
}
|
|
|
|
virtual void ActivateTab() override
|
|
{
|
|
const TSharedPtr<FTabManager> LevelEditorTabManager = GetAssociatedTabManager();
|
|
FGlobalTabmanager::Get()->DrawAttention(LevelEditorTabManager->GetOwnerTab().ToSharedRef());
|
|
}
|
|
|
|
virtual TSharedPtr<FTabManager> GetAssociatedTabManager() override
|
|
{
|
|
return FModuleManager::Get().GetModuleChecked<FLevelEditorModule>("LevelEditor").GetLevelEditorTabManager();
|
|
}
|
|
|
|
protected:
|
|
FTabSwitchingListItem_World(UWorld* InWorld)
|
|
: FTabSwitchingListItem_Asset(InWorld)
|
|
{
|
|
const TSharedPtr<SDockTab> LevelEditorTabPtr = FModuleManager::Get().GetModuleChecked<FLevelEditorModule>("LevelEditor").GetLevelEditorTab();
|
|
LastAccessTime = LevelEditorTabPtr->GetLastActivationTime();
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FTabSwitchingListItem_Asset
|
|
|
|
bool SGlobalTabSwitchingDialog::bIsAlreadyOpen = false;
|
|
|
|
SGlobalTabSwitchingDialog::~SGlobalTabSwitchingDialog()
|
|
{
|
|
bIsAlreadyOpen = false;
|
|
|
|
#if PLATFORM_MAC
|
|
MacApplication->SetIsRightClickEmulationEnabled(true);
|
|
#endif
|
|
}
|
|
|
|
SGlobalTabSwitchingDialog::FTabListItemPtr SGlobalTabSwitchingDialog::GetMainTabListSelectedItem() const
|
|
{
|
|
TArray<FTabListItemPtr> SelectedItems = MainTabsListWidget->GetSelectedItems();
|
|
if (SelectedItems.Num() > 0)
|
|
{
|
|
return SelectedItems[0];
|
|
}
|
|
else
|
|
{
|
|
return FTabListItemPtr();
|
|
}
|
|
}
|
|
|
|
FReply SGlobalTabSwitchingDialog::OnBrowseToSelectedAsset()
|
|
{
|
|
FTabListItemPtr SelectedItem = GetMainTabListSelectedItem();
|
|
if (SelectedItem.IsValid())
|
|
{
|
|
SelectedItem->ShowInContentBrowser();
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SGlobalTabSwitchingDialog::OnMainTabListSelectionChanged(FTabListItemPtr InItem, ESelectInfo::Type SelectInfo)
|
|
{
|
|
TSharedRef<SWidget> NewTopContents = SNullWidget::NullWidget;
|
|
TSharedRef<SWidget> NewBottomContents = SNullWidget::NullWidget;
|
|
TSharedRef<SWidget> NewToolTabsContent = SNullWidget::NullWidget;
|
|
|
|
TArray<FTabListItemPtr> SelectedItems = MainTabsListWidget->GetSelectedItems();
|
|
|
|
if (SelectedItems.Num() > 0)
|
|
{
|
|
FTabListItemPtr SelectedItem = SelectedItems[0];
|
|
|
|
NewTopContents = SelectedItem->CreateWidget(UThumbnailManager::Get().GetSharedThumbnailPool());
|
|
|
|
NewBottomContents =
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverOnlyHyperlinkButton")
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &SGlobalTabSwitchingDialog::OnBrowseToSelectedAsset)
|
|
.ToolTipText(LOCTEXT("BrowseButtonToolTipText", "Browse to Asset in Content Browser"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Search"))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FAppStyle::Get(), "ControlTabMenu.AssetPathStyle")
|
|
.Text(SelectedItem->GetPathString())
|
|
]
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNullWidget::NullWidget
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FAppStyle::Get(), "ControlTabMenu.AssetTypeStyle")
|
|
.Text(SelectedItem->GetTypeString())
|
|
];
|
|
}
|
|
|
|
NewTabItemToActivateDisplayBox->SetContent(NewTopContents);
|
|
NewTabItemToActivatePathBox->SetContent(NewBottomContents);
|
|
}
|
|
|
|
void SGlobalTabSwitchingDialog::OnMainTabListItemClicked(FTabListItemPtr InItem)
|
|
{
|
|
DismissDialog();
|
|
}
|
|
|
|
void SGlobalTabSwitchingDialog::CycleSelection(bool bForwards)
|
|
{
|
|
// This is done here each time in case someone clicks off of the selected item (and to prime the pump at startup),
|
|
// otherwise the code below wouldn't cycle back into an item if nothing was selected
|
|
if ((MainTabsListWidget->GetNumItemsSelected() == 0) && (MainTabsListDataSource.Num() > 0))
|
|
{
|
|
MainTabsListWidget->SetSelection(MainTabsListDataSource[0]);
|
|
}
|
|
|
|
// Move to the next/previous item
|
|
FTabListItemPtr OldSelectedItem = GetMainTabListSelectedItem();
|
|
if (OldSelectedItem.IsValid())
|
|
{
|
|
int32 OldSelectionIndex;
|
|
if (MainTabsListDataSource.Find(OldSelectedItem, /*out*/ OldSelectionIndex))
|
|
{
|
|
const int32 NewSelectionIndex = (OldSelectionIndex + MainTabsListDataSource.Num() + (bForwards ? 1 : -1)) % MainTabsListDataSource.Num();
|
|
if (NewSelectionIndex != OldSelectionIndex)
|
|
{
|
|
FTabListItemPtr NewSelectedItem = MainTabsListDataSource[NewSelectionIndex];
|
|
MainTabsListWidget->SetSelection(NewSelectedItem);
|
|
MainTabsListWidget->RequestScrollIntoView(NewSelectedItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SGlobalTabSwitchingDialog::DismissDialog()
|
|
{
|
|
FTabListItemPtr SelectedItem = GetMainTabListSelectedItem();
|
|
if (SelectedItem.IsValid())
|
|
{
|
|
SelectedItem->ActivateTab();
|
|
}
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
|
|
void SGlobalTabSwitchingDialog::Construct(const FArguments& InArgs, FVector2D InSize, FInputChord InTriggerChord)
|
|
{
|
|
check(!bIsAlreadyOpen);
|
|
bIsAlreadyOpen = true;
|
|
|
|
#if PLATFORM_MAC
|
|
// On Mac we emulate right click with ctrl+left click. This needs to be disabled for tab navigator, so that users can click on its widgets while they keep the ctrl key pressed
|
|
MacApplication->SetIsRightClickEmulationEnabled(false);
|
|
#endif
|
|
|
|
TriggerChord = InTriggerChord;
|
|
|
|
// Populate the list with open asset editors
|
|
TArray<UObject*> OpenAssetList = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->GetAllEditedAssets();
|
|
for (UObject* OpenAsset : OpenAssetList)
|
|
{
|
|
if (OpenAsset->GetOuter() != GetTransientPackage())
|
|
{
|
|
MainTabsListDataSource.Add(MakeShareable(new FTabSwitchingListItem_Asset(OpenAsset)));
|
|
}
|
|
}
|
|
|
|
MainTabsListDataSource.Add(FTabSwitchingListItem_World::MakeWorldItem());
|
|
|
|
// Sort the list by access time
|
|
MainTabsListDataSource.Sort([](const FTabListItemPtr& A, const FTabListItemPtr& B) { return (A->LastAccessTime > B->LastAccessTime); });
|
|
|
|
// Create the widgets
|
|
NewTabItemToActivateDisplayBox =
|
|
SNew(SBox)
|
|
.Padding(FMargin(0.0f, 0.0f, 10.0f, 0.0f))
|
|
.HeightOverride(70.0f)
|
|
.VAlign(VAlign_Top);
|
|
|
|
NewTabItemToActivatePathBox =
|
|
SNew(SBox)
|
|
.Padding(FMargin(0.0f, 10.0f, 10.0f, 10.0f))
|
|
.HeightOverride(40.0f)
|
|
.VAlign(VAlign_Center);
|
|
|
|
MainTabsListWidget = SNew(STabListWidget)
|
|
.ListItemsSource(&MainTabsListDataSource)
|
|
.OnGenerateRow(this, &SGlobalTabSwitchingDialog::OnGenerateTabSwitchListItemWidget)
|
|
.OnSelectionChanged(this, &SGlobalTabSwitchingDialog::OnMainTabListSelectionChanged)
|
|
.OnMouseButtonClick(this, &SGlobalTabSwitchingDialog::OnMainTabListItemClicked)
|
|
.SelectionMode(ESelectionMode::Single);
|
|
|
|
TSharedRef<SWidget> DocumentTabList = SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FAppStyle::Get(), "ControlTabMenu.HeadingStyle")
|
|
.Text(LOCTEXT("OpenAssetsHeading", "Active Files"))
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SScrollBorder, MainTabsListWidget.ToSharedRef())
|
|
[
|
|
MainTabsListWidget.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("ControlTabMenu.Background"))
|
|
.ForegroundColor(FCoreStyle::Get().GetSlateColor("DefaultForeground"))
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(static_cast<float>(InSize.X))
|
|
.HeightOverride(static_cast<float>(InSize.Y))
|
|
.Padding(FMargin(12.0f, 12.0f, 12.0f, 0.0f))
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
NewTabItemToActivateDisplayBox.ToSharedRef()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.Padding(12.f, 24.f)
|
|
.FillHeight(1.0f)
|
|
[
|
|
DocumentTabList
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
NewTabItemToActivatePathBox.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Pick the second most recent or least recent file based on whether Shift was held down when we were summoned
|
|
if (MainTabsListDataSource.Num() > 0)
|
|
{
|
|
CycleSelection(/*bForwards=*/ !FSlateApplication::Get().GetModifierKeys().IsShiftDown());
|
|
}
|
|
}
|
|
|
|
FReply SGlobalTabSwitchingDialog::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
// Check to see if the trigger modifier key was released, which should close the dialog
|
|
const bool bCloseViaControl = TriggerChord.NeedsControl() && ((InKeyEvent.GetKey() == EKeys::LeftControl) || (InKeyEvent.GetKey() == EKeys::RightControl));
|
|
const bool bCloseViaCommand = TriggerChord.NeedsCommand() && ((InKeyEvent.GetKey() == EKeys::LeftCommand) || (InKeyEvent.GetKey() == EKeys::RightCommand));
|
|
const bool bCloseViaAlt = TriggerChord.NeedsAlt() && ((InKeyEvent.GetKey() == EKeys::LeftAlt) || (InKeyEvent.GetKey() == EKeys::RightAlt));
|
|
|
|
if (bCloseViaControl || bCloseViaCommand || bCloseViaAlt)
|
|
{
|
|
DismissDialog();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SGlobalTabSwitchingDialog::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
if (TriggerChord.Key == InKeyEvent.GetKey())
|
|
{
|
|
const bool bCycleForward = !InKeyEvent.IsShiftDown();
|
|
CycleSelection(bCycleForward);
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SGlobalTabSwitchingDialog::OnPreviewKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
if (InKeyEvent.GetKey() == EKeys::Escape)
|
|
{
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
TSharedRef<ITableRow> SGlobalTabSwitchingDialog::OnGenerateTabSwitchListItemWidget(FTabListItemPtr InItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
return SNew(STableRow<FTabListItemPtr>, OwnerTable)[InItem->CreateWidget(UThumbnailManager::Get().GetSharedThumbnailPool())];
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#undef LOCTEXT_NAMESPACE
|