5251 lines
189 KiB
C++
5251 lines
189 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "SContentBrowser.h"
|
|
|
|
#include "Algo/AllOf.h"
|
|
#include "Algo/AnyOf.h"
|
|
#include "Algo/Sort.h"
|
|
#include "Algo/Unique.h"
|
|
#include "AssetContextMenu.h"
|
|
#include "AssetRegistry/ARFilter.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "AssetRegistry/AssetDataTagMap.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "AssetRegistry/IAssetRegistry.h"
|
|
#include "AssetTextFilter.h"
|
|
#include "AssetThumbnail.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "AssetViewUtils.h"
|
|
#include "CollectionManagerModule.h"
|
|
#include "CollectionManagerTypes.h"
|
|
#include "CollectionViewUtils.h"
|
|
#include "Containers/Map.h"
|
|
#include "Containers/Set.h"
|
|
#include "Containers/StringFwd.h"
|
|
#include "Containers/StringView.h"
|
|
#include "ContentBrowserCommands.h"
|
|
#include "ContentBrowserConfig.h"
|
|
#include "ContentBrowserDataFilter.h"
|
|
#include "ContentBrowserDataSource.h"
|
|
#include "ContentBrowserDataSubsystem.h"
|
|
#include "ContentBrowserDataUtils.h"
|
|
#include "ContentBrowserItem.h"
|
|
#include "ContentBrowserItemData.h"
|
|
#include "ContentBrowserItemPath.h"
|
|
#include "ContentBrowserLog.h"
|
|
#include "ContentBrowserMenuContexts.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "ContentBrowserSingleton.h"
|
|
#include "ContentBrowserStyle.h"
|
|
#include "ContentBrowserUtils.h"
|
|
#include "ContentBrowserVirtualPathTree.h"
|
|
#include "AssetViewContentSources.h"
|
|
#include "CoreGlobals.h"
|
|
#include "Delegates/Delegate.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "FileHelpers.h"
|
|
#include "Filters.h"
|
|
#include "Filters/FilterBase.h"
|
|
#include "Filters/SAssetFilterBar.h"
|
|
#include "Filters/SBasicFilterBar.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "Framework/Commands/InputBindingManager.h"
|
|
#include "Framework/Commands/InputChord.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Framework/Commands/UICommandInfo.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Framework/MultiBox/MultiBoxDefs.h"
|
|
#include "Framework/MultiBox/MultiBoxExtender.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "FrontendFilters.h"
|
|
#include "GenericPlatform/GenericApplication.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "IAddContentDialogModule.h"
|
|
#include "IAssetTools.h"
|
|
#include "ICollectionContainer.h"
|
|
#include "ICollectionSource.h"
|
|
#include "ICollectionManager.h"
|
|
#include "IContentBrowserDataModule.h"
|
|
#include "Input/Events.h"
|
|
#include "InputCoreTypes.h"
|
|
#include "Layout/BasicLayoutWidgetSlot.h"
|
|
#include "Layout/Children.h"
|
|
#include "Layout/ChildrenBase.h"
|
|
#include "Layout/Clipping.h"
|
|
#include "Layout/Margin.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Logging/LogCategory.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "Math/UnrealMathSSE.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "Misc/CString.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/ExpressionParserTypes.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Misc/FilterCollection.h"
|
|
#include "Misc/NamePermissionList.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "Misc/TextFilterExpressionEvaluator.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "NewAssetOrClassContextMenu.h"
|
|
#include "PathContextMenu.h"
|
|
#include "SActionButton.h"
|
|
#include "SAssetSearchBox.h"
|
|
#include "SAssetView.h"
|
|
#include "SCollectionView.h"
|
|
#include "SFilterList.h"
|
|
#include "SNavigationBar.h"
|
|
#include "SPathView.h"
|
|
#include "SPositiveActionButton.h"
|
|
#include "SSearchToggleButton.h"
|
|
#include "Selection.h"
|
|
#include "Settings/ContentBrowserSettings.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "SlotBase.h"
|
|
#include "SourcesSearch.h"
|
|
#include "StatusBarSubsystem.h"
|
|
#include "String/Find.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Styling/ISlateStyle.h"
|
|
#include "Styling/SlateColor.h"
|
|
#include "Styling/SlateTypes.h"
|
|
#include "Styling/StyleDefaults.h"
|
|
#include "Templates/Casts.h"
|
|
#include "Templates/Tuple.h"
|
|
#include "Templates/TypeHash.h"
|
|
#include "Textures/SlateIcon.h"
|
|
#include "ToolMenu.h"
|
|
#include "ToolMenuContext.h"
|
|
#include "ToolMenuDelegates.h"
|
|
#include "ToolMenuEntry.h"
|
|
#include "ToolMenuMisc.h"
|
|
#include "ToolMenuSection.h"
|
|
#include "ToolMenus.h"
|
|
#include "ContentSources/Widgets/SContentSourcesView.h"
|
|
#include "ContentSources/Widgets/SLegacyContentSource.h"
|
|
#include "Experimental/ContentBrowserExtensionUtils.h"
|
|
#include "Toolkits/GlobalEditorCommonCommands.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
#include "Types/ISlateMetaData.h"
|
|
#include "Types/SlateStructs.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/ObjectRedirector.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UnrealNames.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SComboButton.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SExpandableArea.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
#include "Widgets/Layout/SSplitter.h"
|
|
#include "Widgets/Layout/SWidgetSwitcher.h"
|
|
#include "Widgets/Navigation/SBreadcrumbTrail.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SContentBrowserSourceTree.h"
|
|
#include "Widgets/SNullWidget.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
class FTreeItem;
|
|
struct FGeometry;
|
|
struct FSlateBrush;
|
|
|
|
#define LOCTEXT_NAMESPACE "ContentBrowser"
|
|
|
|
namespace UE::Editor::ContentBrowser::Private
|
|
{
|
|
// Find and return the slot index containing a widget with the given tag. Will return INDEX_NONE if not found.
|
|
static int32 FindSlotByWidgetTag(const TSharedRef<SSplitter>& InSplitter, const FName InTag)
|
|
{
|
|
for (int32 SlotIndex = 0; SlotIndex < InSplitter->GetChildren()->Num(); ++SlotIndex)
|
|
{
|
|
TSharedRef<SWidget> WidgetInSlot = InSplitter->SlotAt(SlotIndex).GetWidget();
|
|
if (WidgetInSlot->GetTag() == InTag)
|
|
{
|
|
return SlotIndex;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
}
|
|
|
|
const FString SContentBrowser::SettingsIniSection = TEXT("ContentBrowser");
|
|
|
|
class SContentBrowser::FCollectionSource
|
|
{
|
|
public:
|
|
FCollectionSource(SContentBrowser& InContentBrowser, const TSharedRef<ICollectionContainer>& InCollectionContainer)
|
|
: ContentBrowser(InContentBrowser)
|
|
{
|
|
CollectionSearch = MakeShared<FSourcesSearch>();
|
|
CollectionSearch->Initialize();
|
|
CollectionSearch->SetHintText(LOCTEXT("CollectionsViewSearchBoxHint", "Search Collections"));
|
|
|
|
CollectionViewPtr = SNew(SCollectionView)
|
|
.OnCollectionSelected_Lambda([this](const FCollectionNameType& SelectedCollection)
|
|
{
|
|
ContentBrowser.CollectionSelected(GetCollectionContainer(), SelectedCollection);
|
|
})
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserCollections")))
|
|
.AllowCollectionDrag(true)
|
|
.AllowQuickAssetManagement(true)
|
|
.CollectionContainer(InCollectionContainer)
|
|
.IsDocked(true)
|
|
.ExternalSearch(CollectionSearch);
|
|
}
|
|
|
|
FCollectionSource(const FCollectionSource&) = delete;
|
|
|
|
FCollectionSource& operator=(const FCollectionSource&) = delete;
|
|
|
|
const TSharedPtr<ICollectionContainer>& GetCollectionContainer() const
|
|
{
|
|
return CollectionViewPtr->GetCollectionContainer();
|
|
}
|
|
|
|
bool IsProjectCollectionContainer() const
|
|
{
|
|
return GetCollectionContainer() == FCollectionManagerModule::GetModule().Get().GetProjectCollectionContainer();
|
|
}
|
|
|
|
void LoadSettings(const FName& InInstanceName)
|
|
{
|
|
const FString SettingsString = GetSettingsString(InInstanceName);
|
|
const FString EditorPerProjectIni = GetCollectionContainer()->GetCollectionSource()->GetEditorPerProjectIni();
|
|
|
|
CollectionViewPtr->LoadSettings(EditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
CollectionArea->LoadSettings(EditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
}
|
|
|
|
void SaveSettings(const FName& InInstanceName) const
|
|
{
|
|
const FString SettingsString = GetSettingsString(InInstanceName);
|
|
const FString EditorPerProjectIni = GetCollectionContainer()->GetCollectionSource()->GetEditorPerProjectIni();
|
|
|
|
CollectionViewPtr->SaveSettings(EditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
CollectionArea->SaveSettings(EditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
}
|
|
|
|
SSplitter::ESizeRule GetCollectionsAreaSizeRule() const
|
|
{
|
|
// Make sure the area is expanded
|
|
return CollectionArea->IsExpanded() ? SSplitter::ESizeRule::FractionOfParent : SSplitter::ESizeRule::SizeToContent;
|
|
}
|
|
|
|
/** Handler for clicking the add collection button */
|
|
FReply OnAddCollectionClicked()
|
|
{
|
|
CollectionArea->SetExpanded(true);
|
|
|
|
CollectionViewPtr->MakeAddCollectionMenu(ContentBrowser.AsShared());
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
private:
|
|
FString GetSettingsString(const FName& InInstanceName) const
|
|
{
|
|
if (IsProjectCollectionContainer())
|
|
{
|
|
// Maintain backwards compatibility with previous version of the SContentBrowser which had a single collection view.
|
|
return InInstanceName.ToString();
|
|
}
|
|
else
|
|
{
|
|
return InInstanceName.ToString() + TEXT(".") + GetCollectionContainer()->GetCollectionSource()->GetName().ToString();
|
|
}
|
|
}
|
|
|
|
public:
|
|
SContentBrowser& ContentBrowser;
|
|
|
|
/** The sources search for collections*/
|
|
TSharedPtr<FSourcesSearch> CollectionSearch;
|
|
|
|
/** The collection widget */
|
|
TSharedPtr<SCollectionView> CollectionViewPtr;
|
|
|
|
/** Collection area widget */
|
|
TSharedPtr<UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea> CollectionArea;
|
|
};
|
|
|
|
SContentBrowser::SContentBrowser() = default;
|
|
|
|
SContentBrowser::~SContentBrowser()
|
|
{
|
|
IConsoleManager::Get().UnregisterConsoleVariableSink_Handle(CVarSinkHandle);
|
|
|
|
// Remove the listener for when view settings are changed
|
|
UContentBrowserSettings::OnSettingChanged().RemoveAll( this );
|
|
|
|
// Remove listeners for when collections/paths are renamed/deleted
|
|
if (FCollectionManagerModule::IsModuleAvailable())
|
|
{
|
|
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
|
|
|
|
CollectionManagerModule.Get().OnCollectionContainerCreated().RemoveAll(this);
|
|
CollectionManagerModule.Get().OnCollectionContainerDestroyed().RemoveAll(this);
|
|
}
|
|
|
|
if (IContentBrowserDataModule* ContentBrowserDataModule = IContentBrowserDataModule::GetPtr())
|
|
{
|
|
if (UContentBrowserDataSubsystem* ContentBrowserData = ContentBrowserDataModule->GetSubsystem())
|
|
{
|
|
ContentBrowserData->OnItemDataUpdated().RemoveAll(this);
|
|
}
|
|
}
|
|
|
|
if (bIsPrimaryBrowser && GEditor)
|
|
{
|
|
if (USelection* EditorSelection = GEditor->GetSelectedObjects())
|
|
{
|
|
EditorSelection->DeselectAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::ShouldShowRedirectors() const
|
|
{
|
|
return LegacyContentSourceWidgets->FilterListPtr.IsValid() ? ContentBrowserUtils::ShouldShowRedirectors(LegacyContentSourceWidgets->FilterListPtr) : false;
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void SContentBrowser::Construct( const FArguments& InArgs, const FName& InInstanceName, const FContentBrowserConfig* Config )
|
|
{
|
|
using namespace UE::Editor::ContentBrowser::Private;
|
|
|
|
InstanceName = InInstanceName;
|
|
|
|
// Store a copy of the init config if specified so we can re-create the asset view widgets dynamically
|
|
if (Config)
|
|
{
|
|
// We have to disable deprecation warnings because the default assignment operator copies a deprecated member
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
InitConfig = *Config;
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
bHasInitConfig = true;
|
|
}
|
|
|
|
JumpMRU.MaxItems = 30;
|
|
|
|
UContentBrowserConfig::Initialize();
|
|
UContentBrowserConfig::Get()->LoadEditorConfig();
|
|
const FContentBrowserInstanceConfig* EditorConfig = CreateEditorConfigIfRequired();
|
|
|
|
if ( InArgs._ContainingTab.IsValid() )
|
|
{
|
|
// For content browsers that are placed in tabs, save settings when the tab is closing.
|
|
ContainingTab = InArgs._ContainingTab;
|
|
InArgs._ContainingTab->SetOnPersistVisualState( SDockTab::FOnPersistVisualState::CreateSP( this, &SContentBrowser::OnContainingTabSavingVisualState ) );
|
|
InArgs._ContainingTab->SetOnTabClosed( SDockTab::FOnTabClosedCallback::CreateSP( this, &SContentBrowser::OnContainingTabClosed ) );
|
|
InArgs._ContainingTab->SetOnTabActivated( SDockTab::FOnTabActivatedCallback::CreateSP( this, &SContentBrowser::OnContainingTabActivated ) );
|
|
}
|
|
|
|
LegacyContentSource = SNew(UE::Editor::ContentBrowser::SLegacyContentSource);
|
|
|
|
bIsLocked = InArgs._InitiallyLocked;
|
|
bCanSetAsPrimaryBrowser = Config != nullptr ? Config->bCanSetAsPrimaryBrowser : true;
|
|
bIsDrawer = InArgs._IsDrawer;
|
|
|
|
HistoryManager.SetOnApplyHistoryData(FOnApplyHistoryData::CreateSP(this, &SContentBrowser::OnApplyHistoryData));
|
|
HistoryManager.SetOnUpdateHistoryData(FOnUpdateHistoryData::CreateSP(this, &SContentBrowser::OnUpdateHistoryData));
|
|
|
|
FrontendFilters = MakeShareable(new FAssetFilterCollectionType());
|
|
TextFilter = MakeShared<FAssetTextFilter>();
|
|
|
|
PluginPathFilters = MakeShareable(new FPluginFilterCollectionType());
|
|
|
|
FavoritesSearch = MakeShared<FSourcesSearch>();
|
|
FavoritesSearch->Initialize();
|
|
FavoritesSearch->SetHintText(LOCTEXT("SearchFavoritesHint", "Search Favorites"));
|
|
|
|
SourcesSearch = MakeShared<FSourcesSearch>();
|
|
SourcesSearch->Initialize();
|
|
SourcesSearch->SetHintText(LOCTEXT("SearchPathsHint", "Search Paths"));
|
|
|
|
static const FName DefaultForegroundName("DefaultForeground");
|
|
|
|
UContentBrowserSettings::OnSettingChanged().AddSP(this, &SContentBrowser::OnContentBrowserSettingsChanged);
|
|
|
|
// Register console variable sink for private content setting changing
|
|
CVarSinkHandle = IConsoleManager::Get().RegisterConsoleVariableSink_Handle(FConsoleCommandDelegate::CreateSP(this, &SContentBrowser::OnConsoleVariableChanged));
|
|
UpdatePrivateContentFeatureEnabled(false /* bUpdateFilterIfChanged */);
|
|
|
|
ChildSlot
|
|
[
|
|
// The legacy content source will be activated by default, which will call OnLegacyContentSourceEnabled which ends up creating the asset view
|
|
// widgets and initiatializing all settings and so on
|
|
SAssignNew(ContentSourcesContainer, UE::Editor::ContentBrowser::SContentSourcesView)
|
|
.LegacyContentSource(LegacyContentSource)
|
|
.OnLegacyContentSourceEnabled(this, &SContentBrowser::OnLegacyContentSourceEnabled)
|
|
.OnLegacyContentSourceDisabled(this, &SContentBrowser::OnLegacyContentSourceDisabled)
|
|
|
|
];
|
|
|
|
ExtendViewOptionsMenu(Config);
|
|
|
|
// Set the initial history data
|
|
HistoryManager.AddHistoryData();
|
|
|
|
// We want to be able to search the feature packs in the super search so we need the module loaded
|
|
IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked<IAddContentDialogModule>("AddContentDialog");
|
|
|
|
// Update the breadcrumb trail path
|
|
OnContentBrowserSettingsChanged(NAME_None);
|
|
}
|
|
|
|
void SContentBrowser::OnDragLeave(const FDragDropEvent& DragDropEvent)
|
|
{
|
|
SCompoundWidget::OnDragLeave(DragDropEvent);
|
|
// Always dismiss the ContentDrawer if the drag leave the ContentBrowser
|
|
if (bIsDrawer)
|
|
{
|
|
GEditor->GetEditorSubsystem<UStatusBarSubsystem>()->DismissContentBrowserDrawer();
|
|
}
|
|
}
|
|
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
void SContentBrowser::BindCommands()
|
|
{
|
|
Commands = TSharedPtr< FUICommandList >(new FUICommandList);
|
|
|
|
Commands->MapAction(FGenericCommands::Get().Rename, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleRenameCommand),
|
|
FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleRenameCommandCanExecute)
|
|
));
|
|
|
|
Commands->MapAction(FGenericCommands::Get().Delete, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleDeleteCommandExecute),
|
|
FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleDeleteCommandCanExecute)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().OpenAssetsOrFolders, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleOpenAssetsOrFoldersCommandExecute)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().PreviewAssets, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandlePreviewAssetsCommandExecute)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().CreateNewFolder, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleCreateNewFolderCommandExecute)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().GoUpToParentFolder, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleGoUpToParentFolder),
|
|
FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleCanGoUpToParentFolder)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().SaveSelectedAsset, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAssetCommand),
|
|
FCanExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAssetCommandCanExecute)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().SaveAllCurrentFolder, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleSaveAllCurrentFolderCommand)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().ResaveAllCurrentFolder, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::HandleResaveAllCurrentFolderCommand)
|
|
));
|
|
|
|
Commands->MapAction(FContentBrowserCommands::Get().EditPath, FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::EditPathCommand)
|
|
));
|
|
|
|
// Allow extenders to add commands
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
TArray<FContentBrowserCommandExtender> CommmandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders();
|
|
|
|
for (int32 i = 0; i < CommmandExtenderDelegates.Num(); ++i)
|
|
{
|
|
if (CommmandExtenderDelegates[i].IsBound())
|
|
{
|
|
CommmandExtenderDelegates[i].Execute(Commands.ToSharedRef(), FOnContentBrowserGetSelection::CreateSP(this, &SContentBrowser::GetSelectionState));
|
|
}
|
|
}
|
|
|
|
FInputBindingManager::Get().RegisterCommandList(FContentBrowserCommands::Get().GetContextName(), Commands.ToSharedRef());
|
|
}
|
|
|
|
void SContentBrowser::UnbindCommands()
|
|
{
|
|
Commands.Reset();
|
|
}
|
|
|
|
EVisibility SContentBrowser::GetFavoriteFolderVisibility() const
|
|
{
|
|
if (const FContentBrowserInstanceConfig* Config = GetConstInstanceConfig())
|
|
{
|
|
return Config->bShowFavorites ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
return GetDefault<UContentBrowserSettings>()->GetDisplayFavorites() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SContentBrowser::GetLockButtonVisibility() const
|
|
{
|
|
return IsLocked() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SContentBrowser::AddFolderFavorite(const TArray<FString>& FolderPaths)
|
|
{
|
|
for (const FString& FolderPath : FolderPaths)
|
|
{
|
|
const FContentBrowserItemPath ItemPath(FolderPath, EContentBrowserPathType::Virtual);
|
|
if (!ContentBrowserUtils::IsFavoriteFolder(ItemPath))
|
|
{
|
|
ContentBrowserUtils::AddFavoriteFolder(ItemPath);
|
|
}
|
|
}
|
|
|
|
SaveAndShowNewFolderFavorites(FolderPaths);
|
|
}
|
|
|
|
void SContentBrowser::ToggleFolderFavorite(const TArray<FString>& FolderPaths)
|
|
{
|
|
TArray<FString> FolderPathsAdded;
|
|
for (const FString& FolderPath : FolderPaths)
|
|
{
|
|
const FContentBrowserItemPath ItemPath(FolderPath, EContentBrowserPathType::Virtual);
|
|
if (ContentBrowserUtils::IsFavoriteFolder(ItemPath))
|
|
{
|
|
ContentBrowserUtils::RemoveFavoriteFolder(ItemPath);
|
|
}
|
|
else
|
|
{
|
|
ContentBrowserUtils::AddFavoriteFolder(ItemPath);
|
|
FolderPathsAdded.Add(FolderPath);
|
|
}
|
|
}
|
|
|
|
SaveAndShowNewFolderFavorites(FolderPathsAdded);
|
|
}
|
|
|
|
void SContentBrowser::SetFilterLayout(const EFilterBarLayout InFilterBarLayout) const
|
|
{
|
|
if (const TSharedPtr<SFilterList>& FilterBar = LegacyContentSourceWidgets->FilterListPtr; FilterBar && LegacyContentSourceWidgets->AssetViewPtr)
|
|
{
|
|
FilterBar->SetFilterLayout(InFilterBarLayout);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetFilterBar(FilterBar);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogContentBrowser, Warning, TEXT("SetFilterLayout failed: %s is invalid. FilterListPtr: %s, AssetViewPtr: %s"),
|
|
TEXT("SContentBrowser::SetFilterLayout"),
|
|
LegacyContentSourceWidgets->FilterListPtr.IsValid() ? TEXT("Valid") : TEXT("Invalid"),
|
|
LegacyContentSourceWidgets->AssetViewPtr.IsValid() ? TEXT("Valid") : TEXT("Invalid"));
|
|
}
|
|
}
|
|
|
|
EFilterBarLayout SContentBrowser::GetFilterLayout() const
|
|
{
|
|
if (const TSharedPtr<SFilterList>& FilterBar = LegacyContentSourceWidgets->FilterListPtr)
|
|
{
|
|
return FilterBar->GetFilterLayout();
|
|
}
|
|
|
|
UE_LOG(LogContentBrowser, Warning, TEXT("GetFilterLayout: FilterListPtr is invalid, returning default layout."));
|
|
return EFilterBarLayout::Vertical;
|
|
}
|
|
|
|
TSharedPtr<SWidget> SContentBrowser::GetActiveFilterContainer() const
|
|
{
|
|
if (const TSharedPtr<SFilterList>& FilterBar = LegacyContentSourceWidgets->FilterListPtr)
|
|
{
|
|
return FilterBar->GetActiveFilterContainer();
|
|
}
|
|
|
|
UE_LOG(LogContentBrowser, Warning, TEXT("GetFilterLayout: FilterListPtr is invalid, returning nullptr."));
|
|
return nullptr;
|
|
}
|
|
|
|
void SContentBrowser::SaveAndShowNewFolderFavorites(const TArray<FString>& FolderPaths)
|
|
{
|
|
// If the legacy content source isn't active - the settings will get updated when it is made active
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, InstanceName.ToString() + TEXT(".Favorites"));
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->Populate();
|
|
|
|
if (!FolderPaths.IsEmpty())
|
|
{
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetSelectedPaths(FolderPaths);
|
|
if (GetFavoriteFolderVisibility() == EVisibility::Collapsed)
|
|
{
|
|
UContentBrowserSettings* Settings = GetMutableDefault<UContentBrowserSettings>();
|
|
Settings->SetDisplayFavorites(true);
|
|
Settings->SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::TogglePrivateContentEdit(const TArray<FString>& FolderPaths)
|
|
{
|
|
for (const FString& FolderPath : FolderPaths)
|
|
{
|
|
ensure(FContentBrowserSingleton::Get().IsFolderShowPrivateContentToggleable(FolderPath));
|
|
|
|
if (FContentBrowserSingleton::Get().IsShowingPrivateContent(FolderPath))
|
|
{
|
|
ContentBrowserUtils::RemoveShowPrivateContentFolder(FolderPath, TEXT("ContentBrowser"));
|
|
}
|
|
else
|
|
{
|
|
ContentBrowserUtils::AddShowPrivateContentFolder(FolderPath, TEXT("ContentBrowser"));
|
|
}
|
|
}
|
|
|
|
OnAssetViewRefreshRequested();
|
|
}
|
|
|
|
void SContentBrowser::HandleAssetViewSearchOptionsChanged()
|
|
{
|
|
bool bIncludeClassName = LegacyContentSourceWidgets->AssetViewPtr->IsIncludingClassNames();
|
|
bool bIncludeAssetPath = LegacyContentSourceWidgets->AssetViewPtr->IsIncludingAssetPaths();
|
|
bool bIncludeCollectionNames = LegacyContentSourceWidgets->AssetViewPtr->IsIncludingCollectionNames();
|
|
|
|
TextFilter->SetIncludeClassName(bIncludeClassName);
|
|
TextFilter->SetIncludeAssetPath(bIncludeAssetPath);
|
|
TextFilter->SetIncludeCollectionNames(bIncludeCollectionNames);
|
|
|
|
// Make sure all custom text filters get the updated Asset View Search Options
|
|
LegacyContentSourceWidgets->FilterListPtr->UpdateCustomTextFilterIncludes(bIncludeClassName, bIncludeAssetPath, bIncludeCollectionNames);
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateToolBar(const FContentBrowserConfig* Config)
|
|
{
|
|
FToolMenuContext MenuContext;
|
|
|
|
UContentBrowserToolbarMenuContext* CommonContextObject = NewObject<UContentBrowserToolbarMenuContext>();
|
|
CommonContextObject->ContentBrowser = SharedThis(this);
|
|
CommonContextObject->AssetView = LegacyContentSourceWidgets->AssetViewPtr;
|
|
CommonContextObject->ContentBrowserConfig = Config;
|
|
|
|
MenuContext.AddObject(CommonContextObject);
|
|
|
|
return UToolMenus::Get()->GenerateWidget("ContentBrowser.ToolBar", MenuContext);
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateNavigationToolBar(const FContentBrowserConfig* Config)
|
|
{
|
|
FToolMenuContext MenuContext;
|
|
|
|
UContentBrowserToolbarMenuContext* CommonContextObject = NewObject<UContentBrowserToolbarMenuContext>();
|
|
CommonContextObject->ContentBrowser = SharedThis(this);
|
|
CommonContextObject->AssetView = LegacyContentSourceWidgets->AssetViewPtr;
|
|
CommonContextObject->ContentBrowserConfig = Config;
|
|
|
|
MenuContext.AddObject(CommonContextObject);
|
|
|
|
return UToolMenus::Get()->GenerateWidget("ContentBrowser.NavigationBar", MenuContext);
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateLockButton(const FContentBrowserConfig* Config)
|
|
{
|
|
if(Config == nullptr || Config->bCanShowLockButton)
|
|
{
|
|
return
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ToolTipText(LOCTEXT("LockToggleTooltip", "Toggle lock. If locked, this browser will ignore Find in Content Browser requests."))
|
|
.OnClicked(this, &SContentBrowser::ToggleLockClicked)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserLock")))
|
|
.Visibility(this, &SContentBrowser::GetLockButtonVisibility)
|
|
[
|
|
SNew(SImage)
|
|
.Image(this, &SContentBrowser::GetLockIconBrush)
|
|
.ColorAndOpacity(FSlateColor::UseStyle())
|
|
];
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
void SContentBrowser::OnFilterBarLayoutChanging(EFilterBarLayout NewLayout)
|
|
{
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
using namespace UE::ContentBrowser::Private;
|
|
|
|
// Identify filter view locations by widget tag, so we don't assume slot index
|
|
static FName HorizontalFilterViewTagName = "HorizontalFilterView";
|
|
static FName VerticalFilterViewTagName = "VerticalFilterView";
|
|
|
|
if (NewLayout == EFilterBarLayout::Horizontal)
|
|
{
|
|
const int32 FoundVerticalFilterViewSlotIndex = UE::Editor::ContentBrowser::Private::FindSlotByWidgetTag(
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr.ToSharedRef(),
|
|
VerticalFilterViewTagName);
|
|
|
|
// Remove from Vertical layout (if needed)
|
|
if (FoundVerticalFilterViewSlotIndex != INDEX_NONE)
|
|
{
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr->RemoveAt(FoundVerticalFilterViewSlotIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int32 HorizontalFilterViewSlotIndex = UE::Editor::ContentBrowser::Private::FindSlotByWidgetTag(
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr.ToSharedRef(),
|
|
HorizontalFilterViewTagName);
|
|
|
|
// Remove from Horizontal layout (if needed)
|
|
if (HorizontalFilterViewSlotIndex != INDEX_NONE)
|
|
{
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr->RemoveAt(HorizontalFilterViewSlotIndex);
|
|
}
|
|
|
|
// This can differ to the desired index, depending on widget state
|
|
const int32 FoundVerticalFilterViewSlotIndex = UE::Editor::ContentBrowser::Private::FindSlotByWidgetTag(
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr.ToSharedRef(),
|
|
VerticalFilterViewTagName);
|
|
|
|
// Check for Existing
|
|
if (FoundVerticalFilterViewSlotIndex != INDEX_NONE)
|
|
{
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr->RemoveAt(FoundVerticalFilterViewSlotIndex);
|
|
}
|
|
|
|
constexpr int32 DesiredVerticalFilterViewSlotIndex = 1;
|
|
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr->AddSlot(DesiredVerticalFilterViewSlotIndex)
|
|
.MinSize(95.0f)
|
|
.Resizable(true)
|
|
.SizeRule(SSplitter::SizeToContent)
|
|
.OnSlotResized(this, &SContentBrowser::OnFilterBoxColumnResized)
|
|
[
|
|
// Vertical Filter View
|
|
SNew(SBox)
|
|
.Tag(VerticalFilterViewTagName)
|
|
.WidthOverride(this, &SContentBrowser::GetFilterViewBoxWidthOverride)
|
|
// Don't take up space when there are no filters
|
|
.Visibility_Lambda([this]
|
|
{
|
|
return LegacyContentSourceWidgets->FilterListPtr->HasAnyFilters() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
})
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Header
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.0f, 2.0f)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FContentBrowserStyle::Get().GetBrush("ContentBrowser.VerticalFilterViewHeaderBrush"))
|
|
.Padding(FContentBrowserStyle::Get().GetMargin("ContentBrowser.VerticalFilterViewHeaderPadding"))
|
|
.Content()
|
|
[
|
|
// Enforce widget height
|
|
SNew(SBox)
|
|
.HeightOverride(FContentBrowserStyle::Get().GetFloat("ContentBrowser.VerticalFilterViewHeaderTextHeight"))
|
|
.Padding(0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("FilterListVerticalHeader", "Filters"))
|
|
.TextStyle(FAppStyle::Get(), "ButtonText")
|
|
.Font(FAppStyle::Get().GetFontStyle("NormalFontBold"))
|
|
]
|
|
]
|
|
]
|
|
|
|
// Filter List
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.0f)
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FContentBrowserStyle::Get().GetBrush("ContentBrowser.VerticalFilterViewBodyBrush"))
|
|
.Padding(0.0f)
|
|
[
|
|
LegacyContentSourceWidgets->FilterListPtr.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FOptionalSize SearchBoxDesiredWidth = 500.0f;
|
|
constexpr float SearchBoxMaxWidth = 0.0f;
|
|
|
|
if (NewLayout == EFilterBarLayout::Horizontal)
|
|
{
|
|
TSharedPtr<SHorizontalBox> SearchBoxSlot =
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Top)
|
|
.Padding(6, 4, 2, 0)
|
|
[
|
|
FilterComboButton.ToSharedRef()
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0)
|
|
.VAlign(VAlign_Top)
|
|
.Padding(0, 4, 0, 0)
|
|
[
|
|
SNew(SBox)
|
|
.VAlign(VAlign_Center)
|
|
.WidthOverride(SearchBoxDesiredWidth)
|
|
[
|
|
LegacyContentSourceWidgets->SearchBoxPtr.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
/** We add the Combo Button and the Search Box to the FilterList itself, so that the filters wrap with them
|
|
* properly in the Horizontal Layout
|
|
*/
|
|
LegacyContentSourceWidgets->FilterListPtr->AddWidgetToCurrentLayout(SearchBoxSlot.ToSharedRef());
|
|
|
|
LegacyContentSourceWidgets->AssetViewBorder->SetContent(
|
|
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.0f)
|
|
.AutoHeight()
|
|
[
|
|
LegacyContentSourceWidgets->FilterListPtr.ToSharedRef()
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0, 0)
|
|
[
|
|
LegacyContentSourceWidgets->AssetViewPtr.ToSharedRef()
|
|
]
|
|
);
|
|
}
|
|
else
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewBorder->SetContent(
|
|
|
|
SNew(SSplitter)
|
|
.PhysicalSplitterHandleSize(2.0f)
|
|
|
|
/* Filters in an SScrollBox */
|
|
+ SSplitter::Slot()
|
|
.MinSize(95.f)
|
|
.Resizable(true)
|
|
.SizeRule(SSplitter::SizeToContent)
|
|
.OnSlotResized(this, &SContentBrowser::OnFilterBoxColumnResized)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(this, &SContentBrowser::GetFilterViewBoxWidthOverride)
|
|
// Don't take up space when there are no filters
|
|
.Visibility_Lambda([this]
|
|
{
|
|
return LegacyContentSourceWidgets->FilterListPtr->HasAnyFilters() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
})
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Header
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.0f, 2.0f)
|
|
.AutoHeight()
|
|
[
|
|
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Header"))
|
|
.Padding(FMargin(8.0f, 6.0f))
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("FilterListVerticalHeader", "Filters"))
|
|
.TextStyle(FAppStyle::Get(), "ButtonText")
|
|
.Font(FAppStyle::Get().GetFontStyle("NormalFontBold"))
|
|
]
|
|
]
|
|
|
|
// Filter List
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.0f)
|
|
.FillHeight(1.0f)
|
|
[
|
|
LegacyContentSourceWidgets->FilterListPtr.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SSplitter::Slot()
|
|
.Value(0.88f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(6, 4, 0, 0)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0, 0, 2, 0)
|
|
[
|
|
FilterComboButton.ToSharedRef()
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.MaxWidth(SearchBoxMaxWidth)
|
|
[
|
|
SNew(SBox)
|
|
.VAlign(VAlign_Center)
|
|
.WidthOverride(SearchBoxDesiredWidth)
|
|
[
|
|
LegacyContentSourceWidgets->SearchBoxPtr.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0, 0)
|
|
[
|
|
LegacyContentSourceWidgets->AssetViewPtr.ToSharedRef()
|
|
]
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateAssetView(const FContentBrowserConfig* Config)
|
|
{
|
|
// Create the Filter Bar Widget
|
|
LegacyContentSourceWidgets->FilterListPtr = SNew(SFilterList)
|
|
.OnFilterChanged(this, &SContentBrowser::OnFilterChanged)
|
|
.Visibility((Config != nullptr ? Config->bCanShowFilters : true) ? EVisibility::Visible : EVisibility::Collapsed)
|
|
.FrontendFilters(FrontendFilters)
|
|
.FilterBarIdentifier(InstanceName)
|
|
.FilterBarLayout(EFilterBarLayout::Vertical)
|
|
.CanChangeOrientation(true)
|
|
.OnFilterBarLayoutChanging(this, &SContentBrowser::OnFilterBarLayoutChanging)
|
|
.UseSharedSettings(true)
|
|
.CreateTextFilter(SFilterList::FCreateTextFilter::CreateLambda([this]
|
|
{
|
|
TSharedPtr<FFrontendFilter_CustomText> NewFilter = MakeShared<FFrontendFilter_CustomText>();
|
|
|
|
// Make sure the new filter has the right search options from the AssetView. We only have to set it once, SFilterList handles syncing it on change
|
|
NewFilter->UpdateCustomTextFilterIncludes(LegacyContentSourceWidgets->AssetViewPtr->IsIncludingClassNames(),
|
|
LegacyContentSourceWidgets->AssetViewPtr->IsIncludingAssetPaths(),
|
|
LegacyContentSourceWidgets->AssetViewPtr->IsIncludingCollectionNames());
|
|
|
|
return NewFilter;
|
|
}))
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserFilters")));
|
|
|
|
// Create the Filter Combo Button
|
|
FilterComboButton = SFilterList::MakeAddFilterButton(LegacyContentSourceWidgets->FilterListPtr.ToSharedRef());
|
|
TSharedPtr<ISlateMetaData> FilterComboButtonMetaData = MakeShared<FTagMetaData>(TEXT("ContentBrowserFiltersCombo"));
|
|
FilterComboButton->AddMetadata(FilterComboButtonMetaData.ToSharedRef());
|
|
FilterComboButton->SetVisibility((Config != nullptr ? Config->bCanShowFilters : true) ? EVisibility::Visible : EVisibility::Collapsed);
|
|
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetFilterBar(LegacyContentSourceWidgets->FilterListPtr);
|
|
|
|
LegacyContentSourceWidgets->SearchBoxPtr = SNew(SAssetSearchBox)
|
|
.HintText(this, &SContentBrowser::GetSearchAssetsHintText)
|
|
.ShowSearchHistory(true)
|
|
.OnTextChanged(this, &SContentBrowser::OnSearchBoxChanged)
|
|
.OnTextCommitted(this, &SContentBrowser::OnSearchBoxCommitted)
|
|
.OnKeyDownHandler(this, &SContentBrowser::OnSearchKeyDown)
|
|
.OnSaveSearchClicked(this, &SContentBrowser::OnSaveSearchButtonClicked)
|
|
.OnAssetSearchBoxSuggestionFilter(this, &SContentBrowser::OnAssetSearchSuggestionFilter)
|
|
.OnAssetSearchBoxSuggestionChosen(this, &SContentBrowser::OnAssetSearchSuggestionChosen)
|
|
.DelayChangeNotificationsWhileTyping(true)
|
|
.Visibility((Config != nullptr ? Config->bCanShowAssetSearch : true) ? EVisibility::Visible : EVisibility::Collapsed)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserSearchAssets")));
|
|
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
LegacyContentSourceWidgets->NavigationBar = SNew(SNavigationBar)
|
|
.BreadcrumbButtonContentPadding(FMargin(2, 0, 2, -1)) // This ensures proper vertical alignment of the text to fit the 24px height of the toolbar
|
|
.OnPathClicked(this, &SContentBrowser::OnPathClicked)
|
|
.GetPathMenuContent(this, &SContentBrowser::OnGetCrumbDelimiterContent)
|
|
.GetComboOptions(this, &SContentBrowser::GetRecentPaths)
|
|
.OnNavigateToPath(this, &SContentBrowser::OnNavigateToPath)
|
|
.OnCompletePrefix(this, &SContentBrowser::OnCompletePathPrefix)
|
|
.OnCanEditPathAsText(this, &SContentBrowser::OnCanEditPathAsText)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserPath")));
|
|
}
|
|
|
|
|
|
/* Create the Border that the Asset View will live in, the actual layout is populated in OnFilterBarLayoutChanging,
|
|
* which is called initially called through SAssetFilterBar::LoadSettings through SContentBrowser::LoadSettings()
|
|
*/
|
|
const FMargin AssetViewPadding =
|
|
UE::Editor::ContentBrowser::IsNewStyleEnabled()
|
|
? FMargin(2.0f, 0.0f, 2.0f, 0.0f)
|
|
: FMargin(2.0f, 2.0f, 2.0f, 0.0f);
|
|
|
|
LegacyContentSourceWidgets->AssetViewBorder = SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel"))
|
|
.Padding(AssetViewPadding)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0, 0)
|
|
[
|
|
LegacyContentSourceWidgets->AssetViewPtr.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
return LegacyContentSourceWidgets->AssetViewBorder.ToSharedRef();
|
|
}
|
|
|
|
SContentBrowser::FCollectionSource& SContentBrowser::AddSlotForCollectionContainer(int32 Index, const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = CollectionSources.Num();
|
|
}
|
|
|
|
const TUniquePtr<FCollectionSource>& CollectionSource = CollectionSources.EmplaceAt_GetRef(Index, new FCollectionSource(*this, CollectionContainer));
|
|
|
|
const int32 SlotIndex = LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots + Index;
|
|
SSplitter* Splitter = nullptr;
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
LegacyContentSourceWidgets->SourceTreePtr->AddSlot(SlotIndex)
|
|
.AreaWidget(CreateCollectionsView(*CollectionSource))
|
|
.Size(0.4f);
|
|
|
|
Splitter = LegacyContentSourceWidgets->SourceTreePtr->GetSplitter().Get();
|
|
}
|
|
else
|
|
{
|
|
const float SourceTreeHeaderHeightMin = 26.0f + 3.0f;
|
|
|
|
LegacyContentSourceWidgets->PathFavoriteSplitterPtr->AddSlot(SlotIndex)
|
|
.SizeRule_Raw(CollectionSource.Get(), &FCollectionSource::GetCollectionsAreaSizeRule)
|
|
.MinSize(SourceTreeHeaderHeightMin)
|
|
.Value(0.4f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Header"))
|
|
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
|
|
[
|
|
CreateCollectionsView(*CollectionSource)
|
|
]
|
|
];
|
|
|
|
Splitter = LegacyContentSourceWidgets->PathFavoriteSplitterPtr.Get();
|
|
}
|
|
|
|
FString Key, Filename;
|
|
GetSourceTreeSplitterSlotSizeSettingKeyAndFilename(SlotIndex, Key, Filename);
|
|
|
|
float SplitterSize = Splitter->SlotAt(SlotIndex).GetSizeValue();
|
|
GConfig->GetFloat(*SettingsIniSection, *Key, SplitterSize, Filename);
|
|
Splitter->SlotAt(SlotIndex).SetSizeValue(SplitterSize);
|
|
|
|
CollectionSource->LoadSettings(InstanceName);
|
|
|
|
return *CollectionSource;
|
|
}
|
|
|
|
void SContentBrowser::RemoveSlotForCollectionContainer(const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
int32 Index = CollectionSources.IndexOfByPredicate([&CollectionContainer](const TUniquePtr<FCollectionSource>& CollectionSource)
|
|
{
|
|
return CollectionSource->GetCollectionContainer() == CollectionContainer;
|
|
});
|
|
if (ensure(Index != INDEX_NONE))
|
|
{
|
|
CollectionSources[Index]->SaveSettings(InstanceName);
|
|
|
|
const int32 SlotIndex = LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots + Index;
|
|
SSplitter& Splitter = UE::Editor::ContentBrowser::IsNewStyleEnabled() ?
|
|
*LegacyContentSourceWidgets->SourceTreePtr->GetSplitter() : *LegacyContentSourceWidgets->PathFavoriteSplitterPtr;
|
|
|
|
FString Key, Filename;
|
|
GetSourceTreeSplitterSlotSizeSettingKeyAndFilename(SlotIndex, Key, Filename);
|
|
|
|
float SplitterSize = Splitter.SlotAt(SlotIndex).GetSizeValue();
|
|
GConfig->SetFloat(*SettingsIniSection, *Key, SplitterSize, Filename);
|
|
|
|
Splitter.RemoveAt(SlotIndex);
|
|
|
|
CollectionSources.RemoveAt(Index);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::SetFavoritesExpanded(bool bExpanded)
|
|
{
|
|
if (FContentBrowserInstanceConfig* EditorConfig = GetMutableInstanceConfig())
|
|
{
|
|
EditorConfig->bFavoritesExpanded = bExpanded;
|
|
UContentBrowserConfig::Get()->SaveEditorConfig();
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateFavoritesView(const FContentBrowserConfig* Config)
|
|
{
|
|
SAssignNew(LegacyContentSourceWidgets->FavoritePathViewPtr, SFavoritePathView)
|
|
.OnItemSelectionChanged(this, &SContentBrowser::OnItemSelectionChanged, EContentBrowserViewContext::FavoriteView)
|
|
.OnGetItemContextMenu(this, &SContentBrowser::GetItemContextMenu, EContentBrowserViewContext::FavoriteView)
|
|
.FocusSearchBoxWhenOpened(false)
|
|
.ShowTreeTitle(false)
|
|
.ShowSeparator(false)
|
|
.AllowClassesFolder(true)
|
|
.CanShowDevelopersFolder(true)
|
|
.OwningContentBrowserName(InstanceName)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserFavorites")))
|
|
.ExternalSearch(FavoritesSearch);
|
|
|
|
TSharedRef<UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea> ViewWidget =
|
|
SAssignNew(
|
|
FavoritesArea,
|
|
UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea,
|
|
"Favorites",
|
|
FavoritesSearch,
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr.ToSharedRef())
|
|
.Label(LOCTEXT("Favorites", "Favorites"))
|
|
.Visibility(this, &SContentBrowser::GetFavoriteFolderVisibility)
|
|
.OnExpansionChanged(this, &SContentBrowser::SetFavoritesExpanded)
|
|
.IsEmpty_Lambda([PathView = LegacyContentSourceWidgets->FavoritePathViewPtr]
|
|
{
|
|
return PathView->IsEmpty();
|
|
})
|
|
.EmptyBodyLabel(LOCTEXT("FavoritesEmpty", "Right click a folder to add it to your favorites."));
|
|
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetOnFolderFavoriteAdd(SFavoritePathView::FOnFolderFavoriteAdd::CreateSP(this, &SContentBrowser::AddFolderFavorite));
|
|
return ViewWidget;
|
|
}
|
|
|
|
void SContentBrowser::SetPathViewExpanded(bool bExpanded)
|
|
{
|
|
if (FContentBrowserInstanceConfig* EditorConfig = GetMutableInstanceConfig())
|
|
{
|
|
EditorConfig->PathView.bExpanded = bExpanded;
|
|
UContentBrowserConfig::Get()->SaveEditorConfig();
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreatePathView(const FContentBrowserConfig* Config)
|
|
{
|
|
SAssignNew(LegacyContentSourceWidgets->PathViewPtr, SPathView)
|
|
.OnItemSelectionChanged(this, &SContentBrowser::OnItemSelectionChanged, EContentBrowserViewContext::PathView)
|
|
.OnGetItemContextMenu(this, &SContentBrowser::GetItemContextMenu, EContentBrowserViewContext::PathView)
|
|
.FocusSearchBoxWhenOpened(false)
|
|
.ShowTreeTitle(false)
|
|
.ShowSeparator(false)
|
|
.ShowRedirectors_Lambda([this]() { return ContentBrowserUtils::ShouldShowRedirectors(LegacyContentSourceWidgets->FilterListPtr); })
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserSources")))
|
|
.ExternalSearch(SourcesSearch)
|
|
.PluginPathFilters(PluginPathFilters)
|
|
.OwningContentBrowserName(InstanceName)
|
|
.AllowClassesFolder(Config != nullptr ? Config->bCanShowClasses : true)
|
|
.CanShowDevelopersFolder(Config != nullptr ? Config->bCanShowDevelopersFolder : true);
|
|
|
|
TSharedRef<UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea> ViewWidget =
|
|
SAssignNew(
|
|
PathArea,
|
|
UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea,
|
|
"Path",
|
|
SourcesSearch,
|
|
LegacyContentSourceWidgets->PathViewPtr.ToSharedRef())
|
|
.Label(FText::FromString(FApp::GetProjectName()))
|
|
.ExpandedByDefault(true) // The path area, unlike the favorites and collection areas, are expanded by default (unless overridden)
|
|
.OnExpansionChanged(this, &SContentBrowser::SetPathViewExpanded);
|
|
|
|
return ViewWidget;
|
|
}
|
|
|
|
TSharedRef<UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea> SContentBrowser::CreateCollectionsView(FCollectionSource& CollectionSource)
|
|
{
|
|
return SAssignNew(
|
|
CollectionSource.CollectionArea,
|
|
UE::Editor::ContentBrowser::Private::SContentBrowserSourceTreeArea,
|
|
"Collection",
|
|
CollectionSource.CollectionSearch,
|
|
CollectionSource.CollectionViewPtr.ToSharedRef())
|
|
.Label(CollectionSource.GetCollectionContainer()->GetCollectionSource()->GetTitle())
|
|
.HeaderContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.AutoWidth()
|
|
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ToolTipText(LOCTEXT("AddCollectionButtonTooltip", "Add a collection."))
|
|
.OnClicked_Raw(&CollectionSource, &FCollectionSource::OnAddCollectionClicked)
|
|
.ContentPadding(FMargin(1, 0))
|
|
.Visibility_Lambda([CollectionContainer = CollectionSource.GetCollectionContainer()]() { return !CollectionContainer->IsReadOnly(ECollectionShareType::CST_All) ? EVisibility::Visible : EVisibility::Collapsed; })
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
]
|
|
]
|
|
.IsEmpty_Lambda([PathView = CollectionSource.CollectionViewPtr]
|
|
{
|
|
return PathView->IsEmpty();
|
|
})
|
|
.EmptyBodyLabel_Lambda([CollectionContainer = CollectionSource.GetCollectionContainer()]()
|
|
{
|
|
if (CollectionContainer->IsReadOnly(ECollectionShareType::CST_All))
|
|
{
|
|
return LOCTEXT("CollectionsEmptyAndReadOnly", "No collections found.");
|
|
}
|
|
else
|
|
{
|
|
return LOCTEXT("CollectionsEmpty", "Click the <img src=\"Icons.PlusCircle\"/> in the section header to create a collection.");
|
|
}
|
|
});
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateDrawerDockButton(const FContentBrowserConfig* Config)
|
|
{
|
|
if(bIsDrawer)
|
|
{
|
|
return
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ToolTipText(LOCTEXT("DockInLayout_Tooltip", "Docks this content browser in the current layout, copying all settings from the drawer.\nThe drawer will still be usable as a temporary browser."))
|
|
.OnClicked(this, &SContentBrowser::DockInLayoutClicked)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0, 0.0f)
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("Icons.Layout"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DockInLayout", "Dock in Layout"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
];
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
void SContentBrowser::ExtendViewOptionsMenu(const FContentBrowserConfig* Config)
|
|
{
|
|
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetViewOptions");
|
|
|
|
const bool bShowLockButton = Config == nullptr || Config->bCanShowLockButton;
|
|
const bool bShowSourcesView = Config == nullptr || Config->bUseSourcesView;
|
|
|
|
if (!bShowLockButton && !bShowSourcesView)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Menu->AddDynamicSection("ContentBrowserViewOptionsSection",
|
|
FNewToolMenuDelegate::CreateLambda(
|
|
[bShowLockButton, bShowSourcesView](UToolMenu* InMenu)
|
|
{
|
|
if (UContentBrowserAssetViewContextMenuContext* Context = InMenu->FindContext<UContentBrowserAssetViewContextMenuContext>())
|
|
{
|
|
if (TSharedPtr<SContentBrowser> ContentBrowser = Context->OwningContentBrowser.Pin())
|
|
{
|
|
if (bShowLockButton)
|
|
{
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
FToolMenuSection& Section = InMenu->FindOrAddSection("Manage");
|
|
Section.AddMenuEntry(
|
|
"ToggleLock",
|
|
TAttribute<FText>(ContentBrowser.ToSharedRef(), &SContentBrowser::GetLockMenuText),
|
|
LOCTEXT("LockToggleTooltip", "Toggle lock. If locked, this browser will ignore Find in Content Browser requests."),
|
|
TAttribute<FSlateIcon>(ContentBrowser.ToSharedRef(), &SContentBrowser::GetLockIcon),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda([ContentBrowser = Context->OwningContentBrowser]() { ContentBrowser.Pin()->ToggleLockClicked(); })
|
|
)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
FToolMenuSection& Section = InMenu->AddSection("Locking", LOCTEXT("LockingMenuHeader", "Locking"), FToolMenuInsert("AssetViewType", EToolMenuInsertType::After));
|
|
Section.AddMenuEntry(
|
|
"ToggleLock",
|
|
TAttribute<FText>(ContentBrowser.ToSharedRef(), &SContentBrowser::GetLockMenuText),
|
|
LOCTEXT("LockToggleTooltip", "Toggle lock. If locked, this browser will ignore Find in Content Browser requests."),
|
|
TAttribute<FSlateIcon>(ContentBrowser.ToSharedRef(), &SContentBrowser::GetLockIcon),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda([ContentBrowser = Context->OwningContentBrowser]() { ContentBrowser.Pin()->ToggleLockClicked(); })
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (bShowSourcesView)
|
|
{
|
|
FToolMenuSection& Section = InMenu->FindOrAddSection(UE::Editor::ContentBrowser::IsNewStyleEnabled() ? "Show" : "View");
|
|
Section.AddMenuEntry(
|
|
"ToggleSources",
|
|
UE::Editor::ContentBrowser::IsNewStyleEnabled()
|
|
? LOCTEXT("ToggleSourcesView_NewStyle", "Sources Panel")
|
|
: LOCTEXT("ToggleSourcesView", "Show Sources Panel"),
|
|
LOCTEXT("ToggleSourcesView_Tooltip", "Show or hide the sources panel"),
|
|
TAttribute<FSlateIcon>(),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda([ContentBrowser = Context->OwningContentBrowser]() { ContentBrowser.Pin()->SourcesViewExpandClicked(); }),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateLambda([ContentBrowser = Context->OwningContentBrowser]() { return ContentBrowser.Pin()->bSourcesViewExpanded; })),
|
|
EUserInterfaceActionType::ToggleButton
|
|
)
|
|
.InsertPosition =
|
|
UE::Editor::ContentBrowser::IsNewStyleEnabled()
|
|
? FToolMenuInsert(NAME_None, EToolMenuInsertType::First)
|
|
: FToolMenuInsert();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
));
|
|
}
|
|
|
|
SSplitter::ESizeRule SContentBrowser::GetFavoritesAreaSizeRule() const
|
|
{
|
|
// Make sure the area is expanded and visible
|
|
return FavoritesArea->IsExpanded() && GetFavoriteFolderVisibility() == EVisibility::Visible ? SSplitter::ESizeRule::FractionOfParent : SSplitter::ESizeRule::SizeToContent;
|
|
}
|
|
|
|
SSplitter::ESizeRule SContentBrowser::GetPathAreaSizeRule() const
|
|
{
|
|
return PathArea->IsExpanded() ? SSplitter::ESizeRule::FractionOfParent : SSplitter::ESizeRule::SizeToContent;
|
|
}
|
|
|
|
void SContentBrowser::OnPathViewBoxColumnResized(float InSize)
|
|
{
|
|
PathViewBoxWidth = InSize;
|
|
}
|
|
|
|
FOptionalSize SContentBrowser::GetPathViewBoxWidthOverride() const
|
|
{
|
|
return FOptionalSize(PathViewBoxWidth);
|
|
}
|
|
|
|
void SContentBrowser::OnFilterBoxColumnResized(float InSize)
|
|
{
|
|
FilterBoxWidth = InSize;
|
|
}
|
|
|
|
FOptionalSize SContentBrowser::GetFilterViewBoxWidthOverride() const
|
|
{
|
|
return FOptionalSize(FilterBoxWidth);
|
|
}
|
|
|
|
float SContentBrowser::GetFavoritesAreaMinSize() const
|
|
{
|
|
static const float SourceTreeHeaderHeightMin =
|
|
UE::Editor::ContentBrowser::IsNewStyleEnabled()
|
|
? 36.0f
|
|
: 26.0f + 3.0f;
|
|
|
|
return GetFavoriteFolderVisibility() == EVisibility::Visible ? SourceTreeHeaderHeightMin : 0.0f;
|
|
}
|
|
|
|
FText SContentBrowser::GetHighlightedText() const
|
|
{
|
|
return TextFilter->GetRawFilterText();
|
|
}
|
|
|
|
void SContentBrowser::CreateNewAsset(const FString& DefaultAssetName, const FString& PackagePath, UClass* AssetClass, UFactory* Factory)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a new asset creation is requested
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
LegacyContentSourceWidgets->AssetViewPtr->CreateNewAsset(DefaultAssetName, PackagePath, AssetClass, Factory);
|
|
}
|
|
|
|
void SContentBrowser::PrepareToSyncItems(TArrayView<const FContentBrowserItem> ItemsToSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
bool bRepopulate = false;
|
|
|
|
// Check to see if any of the assets require certain folders to be visible
|
|
const UContentBrowserSettings* ContentBrowserSettings = GetDefault<UContentBrowserSettings>();
|
|
bool bDisplayDev = ContentBrowserSettings->GetDisplayDevelopersFolder();
|
|
bool bDisplayEngine = ContentBrowserSettings->GetDisplayEngineFolder();
|
|
bool bDisplayPlugins = ContentBrowserSettings->GetDisplayPluginFolders();
|
|
bool bDisplayLocalized = ContentBrowserSettings->GetDisplayL10NFolder();
|
|
|
|
// check to see if we have an instance config that overrides the default in UContentBrowserSettings
|
|
if (const FContentBrowserInstanceConfig* EditorConfig = GetConstInstanceConfig())
|
|
{
|
|
bDisplayDev = EditorConfig->bShowDeveloperContent;
|
|
bDisplayEngine = EditorConfig->bShowEngineContent;
|
|
bDisplayPlugins = EditorConfig->bShowPluginContent;
|
|
bDisplayLocalized = EditorConfig->bShowLocalizedContent;
|
|
}
|
|
|
|
// Keep track of any of the settings changing so we can let the user know
|
|
bool bDisplayDevChanged = false;
|
|
bool bDisplayEngineChanged = false;
|
|
bool bDisplayPluginsChanged = false;
|
|
bool bDisplayLocalizedChanged = false;
|
|
|
|
if ( !bDisplayDev || !bDisplayEngine || !bDisplayPlugins || !bDisplayLocalized )
|
|
{
|
|
for (const FContentBrowserItem& ItemToSync : ItemsToSync)
|
|
{
|
|
if (!bDisplayDev && ContentBrowserUtils::IsItemDeveloperContent(ItemToSync))
|
|
{
|
|
bDisplayDev = true;
|
|
LegacyContentSourceWidgets->AssetViewPtr->OverrideShowDeveloperContent();
|
|
bRepopulate = true;
|
|
bDisplayDevChanged = true;
|
|
}
|
|
|
|
if (!bDisplayEngine && ContentBrowserUtils::IsItemEngineContent(ItemToSync))
|
|
{
|
|
bDisplayEngine = true;
|
|
LegacyContentSourceWidgets->AssetViewPtr->OverrideShowEngineContent();
|
|
bRepopulate = true;
|
|
bDisplayEngineChanged = true;
|
|
}
|
|
|
|
if (!bDisplayPlugins && ContentBrowserUtils::IsItemPluginContent(ItemToSync))
|
|
{
|
|
bDisplayPlugins = true;
|
|
LegacyContentSourceWidgets->AssetViewPtr->OverrideShowPluginContent();
|
|
bRepopulate = true;
|
|
bDisplayPluginsChanged = true;
|
|
}
|
|
|
|
if (!bDisplayLocalized && ContentBrowserUtils::IsItemLocalizedContent(ItemToSync))
|
|
{
|
|
bDisplayLocalized = true;
|
|
LegacyContentSourceWidgets->AssetViewPtr->OverrideShowLocalizedContent();
|
|
bRepopulate = true;
|
|
bDisplayLocalizedChanged = true;
|
|
}
|
|
|
|
if (bDisplayDev && bDisplayEngine && bDisplayPlugins && bDisplayLocalized)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Disable any plugin filters which hide the path we're navigating too in the path tree
|
|
bool bSomePluginPathFiltersChanged = false;
|
|
if (LegacyContentSourceWidgets->PathViewPtr->DisablePluginPathFiltersThatHideItems(ItemsToSync))
|
|
{
|
|
bSomePluginPathFiltersChanged = true;
|
|
bRepopulate = true;
|
|
}
|
|
|
|
// Check to see if any item paths don't exist (this can happen if we haven't ticked since the path was created)
|
|
if (!bRepopulate)
|
|
{
|
|
bRepopulate = Algo::AnyOf(ItemsToSync, [this](const FContentBrowserItem& Item) {
|
|
return Item.IsFolder() && !LegacyContentSourceWidgets->PathViewPtr->DoesItemExist(Item.GetVirtualPath());
|
|
});
|
|
}
|
|
|
|
if (bDisableFiltersThatHideAssets)
|
|
{
|
|
// Disable the filter categories
|
|
// Do this before repopulate because the redirectors filter can hide folders
|
|
LegacyContentSourceWidgets->FilterListPtr->DisableFiltersThatHideItems(ItemsToSync);
|
|
}
|
|
|
|
// If we have auto-enabled any flags or found a non-existant path, force a refresh
|
|
if (bRepopulate)
|
|
{
|
|
// let the user know if one of their settings is being changed to be able to show the sync targets
|
|
if (bDisplayDevChanged || bDisplayEngineChanged || bDisplayPluginsChanged || bDisplayLocalizedChanged || bSomePluginPathFiltersChanged)
|
|
{
|
|
TArray<FText> SettingsText;
|
|
if (bDisplayDevChanged)
|
|
{
|
|
SettingsText.Add(LOCTEXT("ShowDeveloperContent", "Show Developer Content"));
|
|
}
|
|
if (bDisplayEngineChanged)
|
|
{
|
|
SettingsText.Add(LOCTEXT("ShowEngineContent", "Show Engine Content"));
|
|
}
|
|
if (bDisplayPluginsChanged)
|
|
{
|
|
SettingsText.Add(LOCTEXT("ShowPluginContent", "Show Plugin Content"));
|
|
}
|
|
if (bDisplayLocalizedChanged)
|
|
{
|
|
SettingsText.Add(LOCTEXT("ShowLocalizedContent", "Show Localized Content"));
|
|
}
|
|
if (bSomePluginPathFiltersChanged)
|
|
{
|
|
SettingsText.Add(LOCTEXT("SomePluginPathFilters", "Some Plugin Filters"));
|
|
}
|
|
FTextBuilder NotificationBuilder;
|
|
const FText NotificationPrefix = FText::Format(
|
|
LOCTEXT("AssetRequiresFilterChanges", "To show {0}|plural(one=this asset,other=these assets), the following {1}|plural(one=setting has,other=settings have) been changed for the active Content Browser:\n"),
|
|
ItemsToSync.Num(),
|
|
SettingsText.Num());
|
|
NotificationBuilder.AppendLine(NotificationPrefix);
|
|
NotificationBuilder.Indent();
|
|
for ( const FText& SettingsTextEntry : SettingsText)
|
|
{
|
|
NotificationBuilder.AppendLine(SettingsTextEntry);
|
|
}
|
|
|
|
FNotificationInfo NotificationInfo(NotificationBuilder.ToText());
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
}
|
|
LegacyContentSourceWidgets->PathViewPtr->Populate();
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->Populate();
|
|
}
|
|
|
|
// Disable the filter search (reset the filter, then clear the search text)
|
|
// Note: we have to remove the filter immediately, we can't wait for OnSearchBoxChanged to hit
|
|
SetSearchBoxText(FText::GetEmpty());
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetText(FText::GetEmpty());
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetError(FText::GetEmpty());
|
|
}
|
|
|
|
void SContentBrowser::PrepareToSyncVirtualPaths(TArrayView<const FName> VirtualPathsToSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
// We need to try and resolve these paths back to items in order to query their attributes
|
|
// This will only work for items that have already been discovered
|
|
TArray<FContentBrowserItem> ItemsToSync;
|
|
{
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
|
|
for (const FName& VirtualPathToSync : VirtualPathsToSync)
|
|
{
|
|
FContentBrowserItem ItemToSync = ContentBrowserData->GetItemAtPath(VirtualPathToSync, EContentBrowserItemTypeFilter::IncludeAll);
|
|
if (ItemToSync.IsValid())
|
|
{
|
|
ItemsToSync.Add(MoveTemp(ItemToSync));
|
|
}
|
|
}
|
|
}
|
|
|
|
PrepareToSyncItems(ItemsToSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
|
|
void SContentBrowser::PrepareToSyncLegacy(TArrayView<const FAssetData> AssetDataList, TArrayView<const FString> FolderPaths, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
TArray<FName> VirtualPathsToSync;
|
|
ContentBrowserUtils::ConvertLegacySelectionToVirtualPaths(AssetDataList, FolderPaths, /*UseFolderPaths*/false, VirtualPathsToSync);
|
|
|
|
PrepareToSyncVirtualPaths(VirtualPathsToSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
|
|
void SContentBrowser::SyncToAssets(TArrayView<const FAssetData> AssetDataList, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
SyncToLegacy(AssetDataList, TArrayView<const FString>(), bAllowImplicitSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
|
|
void SContentBrowser::SyncToFolders(TArrayView<const FString> FolderList, const bool bAllowImplicitSync)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
SyncToLegacy(TArrayView<const FAssetData>(), FolderList, bAllowImplicitSync, /*bDisableFiltersThatHideAssets*/false);
|
|
}
|
|
|
|
void SContentBrowser::SyncToItems(TArrayView<const FContentBrowserItem> ItemsToSync, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
const TSharedRef<FPathPermissionList>& FolderPermissions = FAssetToolsModule::GetModule().Get().GetFolderPermissionList();
|
|
|
|
TArray<FContentBrowserItem> NewItemsToSync = ContentBrowserUtils::FilterOrAliasItems(ItemsToSync);
|
|
ItemsToSync = NewItemsToSync;
|
|
PrepareToSyncItems(ItemsToSync, bDisableFiltersThatHideAssets);
|
|
|
|
// Tell the sources view first so the asset view will be up to date by the time we request the sync
|
|
LegacyContentSourceWidgets->PathViewPtr->SyncToItems(ItemsToSync, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SyncToItems(ItemsToSync, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SyncToItems(ItemsToSync);
|
|
}
|
|
|
|
void SContentBrowser::SyncToVirtualPaths(TArrayView<const FName> VirtualPathsToSync, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
const TSharedRef<FPathPermissionList>& FolderPermissions = FAssetToolsModule::GetModule().Get().GetFolderPermissionList();
|
|
|
|
// If any of the items to sync don't pass the permission filter, try to map the item to a different one that might be visible
|
|
TArray<FName> NewItemsToSync;
|
|
for (const FName VirtualPath : VirtualPathsToSync)
|
|
{
|
|
FName InternalPath;
|
|
ContentBrowserData->TryConvertVirtualPath(VirtualPath, InternalPath);
|
|
if (FolderPermissions->PassesStartsWithFilter(InternalPath))
|
|
{
|
|
NewItemsToSync.Add(VirtualPath);
|
|
}
|
|
else
|
|
{
|
|
TArray<FContentBrowserItemPath> Aliases = ContentBrowserData->GetAliasesForPath(InternalPath);
|
|
for (const FContentBrowserItemPath& Alias : Aliases)
|
|
{
|
|
if (FolderPermissions->PassesStartsWithFilter(Alias.GetInternalPathName()))
|
|
{
|
|
NewItemsToSync.Add(Alias.GetVirtualPathName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
VirtualPathsToSync = NewItemsToSync;
|
|
PrepareToSyncVirtualPaths(VirtualPathsToSync, bDisableFiltersThatHideAssets);
|
|
|
|
// Tell the sources view first so the asset view will be up to date by the time we request the sync
|
|
LegacyContentSourceWidgets->PathViewPtr->SyncToVirtualPaths(VirtualPathsToSync, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SyncToVirtualPaths(VirtualPathsToSync, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SyncToVirtualPaths(VirtualPathsToSync);
|
|
}
|
|
|
|
void SContentBrowser::SyncToLegacy(TArrayView<const FAssetData> AssetDataList, TArrayView<const FString> FolderList, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
const TSharedRef<FPathPermissionList>& FolderPermissions = FAssetToolsModule::GetModule().Get().GetFolderPermissionList();
|
|
|
|
// If any of the items to sync don't pass the permission filter, try to map the item to a different one that might be visible
|
|
TArray<FAssetData> NewItemsToSync;
|
|
for (int32 i = 0; i < AssetDataList.Num(); ++i)
|
|
{
|
|
if (FolderPermissions->PassesStartsWithFilter(AssetDataList[i].GetObjectPathString()))
|
|
{
|
|
NewItemsToSync.Add(AssetDataList[i]);
|
|
}
|
|
else
|
|
{
|
|
TArray<FContentBrowserItemPath> Aliases = ContentBrowserData->GetAliasesForPath(*AssetDataList[i].GetObjectPathString());
|
|
for (const FContentBrowserItemPath& Alias : Aliases)
|
|
{
|
|
const FName InternalPath = Alias.GetInternalPathName();
|
|
if (FolderPermissions->PassesStartsWithFilter(InternalPath))
|
|
{
|
|
FAssetData AliasAssetData(InternalPath, *FPackageName::GetLongPackagePath(InternalPath.ToString()), FPackageName::GetShortFName(InternalPath), AssetDataList[i].AssetClassPath, AssetDataList[i].TagsAndValues.CopyMap());
|
|
NewItemsToSync.Add(MoveTemp(AliasAssetData));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AssetDataList = NewItemsToSync;
|
|
PrepareToSyncLegacy(AssetDataList, FolderList, bDisableFiltersThatHideAssets);
|
|
|
|
// Tell the sources view first so the asset view will be up to date by the time we request the sync
|
|
LegacyContentSourceWidgets->PathViewPtr->SyncToLegacy(AssetDataList, FolderList, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SyncToLegacy(AssetDataList, FolderList, bAllowImplicitSync);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SyncToLegacy(AssetDataList, FolderList);
|
|
}
|
|
|
|
void SContentBrowser::SyncTo( const FContentBrowserSelection& ItemSelection, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets )
|
|
{
|
|
// For now we just forcefully enable the legacy content source when a sync is requested so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
if (ItemSelection.IsLegacy())
|
|
{
|
|
SyncToLegacy(ItemSelection.SelectedAssets, ItemSelection.SelectedFolders, bAllowImplicitSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
else
|
|
{
|
|
SyncToItems(ItemSelection.SelectedItems, bAllowImplicitSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::SetIsPrimaryContentBrowser(bool NewIsPrimary)
|
|
{
|
|
if (!CanSetAsPrimaryContentBrowser())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bIsPrimaryBrowser = NewIsPrimary;
|
|
|
|
if ( bIsPrimaryBrowser )
|
|
{
|
|
SyncGlobalSelectionSet();
|
|
}
|
|
else
|
|
{
|
|
USelection* EditorSelection = GEditor->GetSelectedObjects();
|
|
if ( ensure( EditorSelection != NULL ) )
|
|
{
|
|
EditorSelection->DeselectAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::CanSetAsPrimaryContentBrowser() const
|
|
{
|
|
return bCanSetAsPrimaryBrowser;
|
|
}
|
|
|
|
TSharedPtr<FTabManager> SContentBrowser::GetTabManager() const
|
|
{
|
|
if (TSharedPtr<SDockTab> Tab = ContainingTab.Pin())
|
|
{
|
|
return Tab->GetTabManagerPtr();
|
|
}
|
|
|
|
return TSharedPtr<FTabManager>();
|
|
}
|
|
|
|
void SContentBrowser::LoadSelectedObjectsIfNeeded()
|
|
{
|
|
// Get the selected assets in the asset view
|
|
const TArray<FAssetData>& SelectedAssets = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedAssets();
|
|
|
|
// Load every asset that isn't already in memory
|
|
for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt )
|
|
{
|
|
const FAssetData& AssetData = *AssetIt;
|
|
const bool bShowProgressDialog = (!AssetData.IsAssetLoaded() && FEditorFileUtils::IsMapPackageAsset(AssetData.GetObjectPathString()));
|
|
GWarn->BeginSlowTask(LOCTEXT("LoadingObjects", "Loading Objects..."), bShowProgressDialog);
|
|
|
|
(*AssetIt).GetAsset();
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
// Sync the global selection set if we are the primary browser
|
|
if ( bIsPrimaryBrowser )
|
|
{
|
|
SyncGlobalSelectionSet();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::GetSelectedAssets(TArray<FAssetData>& SelectedAssets)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when this public function is called so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
SelectedAssets = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedAssets();
|
|
}
|
|
|
|
void SContentBrowser::GetSelectedFolders(TArray<FString>& SelectedFolders)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when this public function is called so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
SelectedFolders = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolders();
|
|
}
|
|
|
|
TArray<FString> SContentBrowser::GetSelectedPathViewFolders()
|
|
{
|
|
// For now we just forcefully enable the legacy content source when this public function is called so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
check(LegacyContentSourceWidgets->PathViewPtr.IsValid());
|
|
return LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
}
|
|
|
|
void SContentBrowser::SaveSettings() const
|
|
{
|
|
// Individual content sources will handle saving their own settings. If the legacy content source is active we save its settings, otherwise
|
|
// the settings were saved when the legacy content source was disabled
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FString& SettingsString = InstanceName.ToString();
|
|
|
|
GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni);
|
|
GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".IsLocked")), bIsLocked, GEditorPerProjectIni);
|
|
|
|
FavoritesArea->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
PathArea->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
|
|
for(int32 SlotIndex = 0; SlotIndex < LegacyContentSourceWidgets->PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++)
|
|
{
|
|
// First Slot containing the PathView is using SizeToContent so the SizeValue is not updated
|
|
// Adding another config line for older projects otherwise when they open for the first time after this update the SplitterSlot size will be based on the older one
|
|
// The older one is a normalized value, so it is too small and will make the SplitterSlot for the PathView seems like if it was collapsed the very first time you re-open it
|
|
const bool bIsFirstSlot = SlotIndex == 0;
|
|
float SplitterSize = bIsFirstSlot ? PathViewBoxWidth : LegacyContentSourceWidgets->PathAssetSplitterPtr->SlotAt(SlotIndex).GetSizeValue();
|
|
if (bIsFirstSlot)
|
|
{
|
|
GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.FixedSlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni);
|
|
}
|
|
else
|
|
{
|
|
GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni);
|
|
}
|
|
}
|
|
|
|
{
|
|
SSplitter& Splitter = UE::Editor::ContentBrowser::IsNewStyleEnabled() ?
|
|
*LegacyContentSourceWidgets->SourceTreePtr->GetSplitter() : *LegacyContentSourceWidgets->PathFavoriteSplitterPtr;
|
|
for (int32 SlotIndex = 0; SlotIndex < Splitter.GetChildren()->Num(); SlotIndex++)
|
|
{
|
|
FString Key, Filename;
|
|
GetSourceTreeSplitterSlotSizeSettingKeyAndFilename(SlotIndex, Key, Filename);
|
|
|
|
float SplitterSize = Splitter.SlotAt(SlotIndex).GetSizeValue();
|
|
GConfig->SetFloat(*SettingsIniSection, *Key, SplitterSize, Filename);
|
|
}
|
|
}
|
|
|
|
// Save all our data using the settings string as a key in the user settings ini
|
|
LegacyContentSourceWidgets->FilterListPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
LegacyContentSourceWidgets->PathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString + TEXT(".Favorites"));
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->SaveSettings(InstanceName);
|
|
}
|
|
LegacyContentSourceWidgets->AssetViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
|
|
GConfig->SetArray(*SettingsIniSection, *(SettingsString + TEXT(".JumpMRU")), JumpMRU, GEditorPerProjectIni);
|
|
}
|
|
|
|
const FName SContentBrowser::GetInstanceName() const
|
|
{
|
|
return InstanceName;
|
|
}
|
|
|
|
bool SContentBrowser::IsLocked() const
|
|
{
|
|
return bIsLocked;
|
|
}
|
|
|
|
void SContentBrowser::SetKeyboardFocusOnSearch() const
|
|
{
|
|
if (ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
// Focus on the search box
|
|
FSlateApplication::Get().SetKeyboardFocus( LegacyContentSourceWidgets->SearchBoxPtr, EFocusCause::SetDirectly );
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::CopySettingsFromBrowser(TSharedPtr<SContentBrowser> OtherBrowser)
|
|
{
|
|
if (ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
FName InstanceNameToCopyFrom = OtherBrowser->GetInstanceName();
|
|
|
|
// Clear out any existing settings that dont get reset on load
|
|
LegacyContentSourceWidgets->FilterListPtr->RemoveAllFilters();
|
|
|
|
LoadSettings(InstanceNameToCopyFrom);
|
|
}
|
|
|
|
}
|
|
|
|
FReply SContentBrowser::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if (ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
bool bIsRenamingAsset = LegacyContentSourceWidgets->AssetViewPtr && LegacyContentSourceWidgets->AssetViewPtr->IsRenamingAsset();
|
|
if(bIsRenamingAsset || Commands->ProcessCommandBindings( InKeyEvent ) )
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SContentBrowser::OnPreviewMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
// Clicking in a content browser will shift it to be the primary browser
|
|
FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this));
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SContentBrowser::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
// Mouse back and forward buttons traverse history
|
|
if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton)
|
|
{
|
|
HistoryManager.GoBack();
|
|
return FReply::Handled();
|
|
}
|
|
else if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2)
|
|
{
|
|
HistoryManager.GoForward();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SContentBrowser::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent )
|
|
{
|
|
// Mouse back and forward buttons traverse history
|
|
if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton)
|
|
{
|
|
HistoryManager.GoBack();
|
|
return FReply::Handled();
|
|
}
|
|
else if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2)
|
|
{
|
|
HistoryManager.GoForward();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SContentBrowser::OnContainingTabSavingVisualState() const
|
|
{
|
|
SaveSettings();
|
|
}
|
|
|
|
void SContentBrowser::OnContainingTabClosed(TSharedRef<SDockTab> DockTab)
|
|
{
|
|
FContentBrowserSingleton::Get().ContentBrowserClosed( SharedThis(this) );
|
|
}
|
|
|
|
void SContentBrowser::OnContainingTabActivated(TSharedRef<SDockTab> DockTab, ETabActivationCause InActivationCause)
|
|
{
|
|
if(InActivationCause == ETabActivationCause::UserClickedOnTab)
|
|
{
|
|
FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this));
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::GetSourceTreeSplitterSlotSizeSettingKeyAndFilename(int32 SlotIndex, FString& OutKey, FString& OutFilename) const
|
|
{
|
|
OutKey = InstanceName.ToString();
|
|
|
|
if (SlotIndex < LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots)
|
|
{
|
|
OutKey += FString::Printf(TEXT(".FavoriteSplitter.SlotSize%d"), SlotIndex);
|
|
OutFilename = GEditorPerProjectIni;
|
|
}
|
|
else
|
|
{
|
|
const TUniquePtr<FCollectionSource>& CollectionSource = CollectionSources[SlotIndex - LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots];
|
|
if (CollectionSource->IsProjectCollectionContainer())
|
|
{
|
|
// Reconsider the .FavoriteSplitter.SlotSize key naming scheme if you hit this check.
|
|
check(LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots == 2);
|
|
|
|
// Maintain backwards compatibility with previous version of the SContentBrowser which had a single collection view.
|
|
OutKey += FString::Printf(TEXT(".FavoriteSplitter.SlotSize%d"), LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots);
|
|
}
|
|
else
|
|
{
|
|
OutKey += FString::Printf(TEXT(".%s.FavoriteSplitter.SlotSize"), *CollectionSource->GetCollectionContainer()->GetCollectionSource()->GetName().ToString());
|
|
}
|
|
OutFilename = CollectionSource->GetCollectionContainer()->GetCollectionSource()->GetEditorPerProjectIni();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::LoadSettings(const FName& InInstanceName)
|
|
{
|
|
// Individual content sources will handle saving their own settings. If the legacy content source is active we load the settings, otherwise
|
|
// the settings will be loaded when the legacy content source is enabled
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString SettingsString = InInstanceName.ToString();
|
|
|
|
// Now that we have determined the appropriate settings string, actually load the settings
|
|
bSourcesViewExpanded = true;
|
|
GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni);
|
|
|
|
bIsLocked = false;
|
|
GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".IsLocked")), bIsLocked, GEditorPerProjectIni);
|
|
|
|
FavoritesArea->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
PathArea->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
|
|
for(int32 SlotIndex = 0; SlotIndex < LegacyContentSourceWidgets->PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++)
|
|
{
|
|
// First Slot containing the PathView is using SizeToContent so the SizeValue is not updated
|
|
// Adding another config line for older projects otherwise when they open for the first time after this update the SplitterSlot size will be based on the older one
|
|
// The older one is a normalized value, so it is too small and make will make the SplitterSlot for the PathView seems like if it was collapsed the very first time you re-open it
|
|
const bool bIsFirstSlot = SlotIndex == 0;
|
|
float SplitterSize = bIsFirstSlot ? PathViewBoxWidth : LegacyContentSourceWidgets->PathAssetSplitterPtr->SlotAt(SlotIndex).GetSizeValue();
|
|
if (bIsFirstSlot)
|
|
{
|
|
GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.FixedSlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni);
|
|
PathViewBoxWidth = SplitterSize;
|
|
}
|
|
else
|
|
{
|
|
GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni);
|
|
LegacyContentSourceWidgets->PathAssetSplitterPtr->SlotAt(SlotIndex).SetSizeValue(SplitterSize);
|
|
}
|
|
}
|
|
|
|
{
|
|
SSplitter& Splitter = UE::Editor::ContentBrowser::IsNewStyleEnabled() ?
|
|
*LegacyContentSourceWidgets->SourceTreePtr->GetSplitter() : *LegacyContentSourceWidgets->PathFavoriteSplitterPtr;
|
|
for (int32 SlotIndex = 0; SlotIndex < Splitter.GetChildren()->Num(); SlotIndex++)
|
|
{
|
|
FString Key, Filename;
|
|
GetSourceTreeSplitterSlotSizeSettingKeyAndFilename(SlotIndex, Key, Filename);
|
|
|
|
float SplitterSize = Splitter.SlotAt(SlotIndex).GetSizeValue();
|
|
GConfig->GetFloat(*SettingsIniSection, *Key, SplitterSize, Filename);
|
|
Splitter.SlotAt(SlotIndex).SetSizeValue(SplitterSize);
|
|
}
|
|
}
|
|
|
|
// Save all our data using the settings string as a key in the user settings ini
|
|
LegacyContentSourceWidgets->FilterListPtr->LoadSettings(InInstanceName, GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
LegacyContentSourceWidgets->PathViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString + TEXT(".Favorites"));
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->LoadSettings(InstanceName);
|
|
}
|
|
LegacyContentSourceWidgets->AssetViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString);
|
|
|
|
GConfig->GetArray(*SettingsIniSection, *(SettingsString + TEXT(".JumpMRU")), JumpMRU, GEditorPerProjectIni);
|
|
}
|
|
|
|
void SContentBrowser::SourcesChanged(const TArray<FString>& SelectedPaths, const TArray<FCollectionRef>& SelectedCollections)
|
|
{
|
|
FString NewSource = SelectedPaths.Num() > 0 ? SelectedPaths[0] : (SelectedCollections.Num() > 0 ? SelectedCollections[0].Name.ToString() : TEXT("None"));
|
|
UE_LOG(LogContentBrowser, VeryVerbose, TEXT("The content browser source was changed by the sources view to '%s'"), *NewSource);
|
|
|
|
FAssetViewContentSources ContentSources;
|
|
{
|
|
TArray<FName> SelectedPathNames;
|
|
SelectedPathNames.Reserve(SelectedPaths.Num());
|
|
for (const FString& SelectedPath : SelectedPaths)
|
|
{
|
|
SelectedPathNames.Add(FName(*SelectedPath));
|
|
}
|
|
ContentSources = FAssetViewContentSources(MoveTemp(SelectedPathNames), SelectedCollections);
|
|
}
|
|
|
|
// A dynamic collection should apply its search query to the CB search, so we need to stash the current search so that we can restore it again later
|
|
if (ContentSources.IsDynamicCollection())
|
|
{
|
|
// Only stash the user search term once in case we're switching between dynamic collections
|
|
if (!StashedSearchBoxText.IsSet())
|
|
{
|
|
StashedSearchBoxText = TextFilter->GetRawFilterText();
|
|
}
|
|
|
|
const FCollectionRef& DynamicCollection = ContentSources.GetCollections()[0];
|
|
|
|
FString DynamicQueryString;
|
|
DynamicCollection.Container->GetDynamicQueryText(DynamicCollection.Name, DynamicCollection.Type, DynamicQueryString);
|
|
|
|
const FText DynamicQueryText = FText::FromString(DynamicQueryString);
|
|
SetSearchBoxText(DynamicQueryText);
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetText(DynamicQueryText);
|
|
}
|
|
else if (StashedSearchBoxText.IsSet())
|
|
{
|
|
// Restore the stashed search term
|
|
const FText StashedText = StashedSearchBoxText.GetValue();
|
|
StashedSearchBoxText.Reset();
|
|
|
|
SetSearchBoxText(StashedText);
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetText(StashedText);
|
|
}
|
|
|
|
if (!LegacyContentSourceWidgets->AssetViewPtr->GetContentSources().IsEmpty())
|
|
{
|
|
// Update the current history data to preserve selection if there is a valid ContentSources
|
|
HistoryManager.UpdateHistoryData();
|
|
}
|
|
|
|
// Change the filter for the asset view
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(ContentSources);
|
|
|
|
// Add a new history data now that the source has changed
|
|
HistoryManager.AddHistoryData();
|
|
|
|
// Update the breadcrumb trail path
|
|
UpdatePath();
|
|
}
|
|
|
|
void SContentBrowser::FolderEntered(const FContentBrowserItem& Folder)
|
|
{
|
|
check(Folder.IsFolder());
|
|
|
|
// Have we entered a sub-collection folder?
|
|
const bool bCollectionFolder = EnumHasAnyFlags(Folder.GetItemCategory(), EContentBrowserItemFlags::Category_Collection);
|
|
if (bCollectionFolder)
|
|
{
|
|
TSharedPtr<ICollectionContainer> CollectionContainer;
|
|
FName CollectionName;
|
|
ECollectionShareType::Type CollectionFolderShareType = ECollectionShareType::CST_All;
|
|
if (ContentBrowserUtils::IsCollectionPath(Folder.GetVirtualPath().ToString(), &CollectionContainer, &CollectionName, &CollectionFolderShareType))
|
|
{
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
if (CollectionSource->GetCollectionContainer() == CollectionContainer)
|
|
{
|
|
const FCollectionNameType SelectedCollection(CollectionName, CollectionFolderShareType);
|
|
|
|
TArray<FCollectionNameType> Collections;
|
|
Collections.Add(SelectedCollection);
|
|
CollectionSource->CollectionViewPtr->SetSelectedCollections(Collections);
|
|
|
|
CollectionSelected(CollectionContainer, SelectedCollection);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// set the path view to the incoming path
|
|
TArray<FString> SelectedPaths;
|
|
SelectedPaths.Add(Folder.GetVirtualPath().ToString());
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
|
|
PathSelected(SelectedPaths[0]);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::PathSelected(const FString& FolderPath)
|
|
{
|
|
JumpMRU.AddUnique(FolderPath);
|
|
|
|
// You may not select both collections and paths
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->CollectionViewPtr->ClearSelection();
|
|
}
|
|
|
|
TArray<FString> SelectedPaths = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
// Selecting a folder shows it in the favorite list also
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
TArray<FCollectionRef> SelectedCollections;
|
|
SourcesChanged(SelectedPaths, SelectedCollections);
|
|
|
|
LegacyContentSourceWidgets->PathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, InstanceName.ToString());
|
|
|
|
// Notify 'asset path changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
|
|
FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged();
|
|
if(PathChangedDelegate.IsBound())
|
|
{
|
|
PathChangedDelegate.Broadcast(FolderPath);
|
|
}
|
|
|
|
// Update the context menu's selected paths list
|
|
LegacyContentSourceWidgets->PathContextMenu->SetSelectedFolders(LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems());
|
|
}
|
|
|
|
void SContentBrowser::FavoritePathSelected(const FString& FolderPath)
|
|
{
|
|
JumpMRU.AddUnique(FolderPath);
|
|
|
|
// You may not select both collections and paths
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->CollectionViewPtr->ClearSelection();
|
|
}
|
|
|
|
TArray<FString> SelectedPaths = LegacyContentSourceWidgets->FavoritePathViewPtr->GetSelectedPaths();
|
|
// Selecting a favorite shows it in the main list also
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
TArray<FCollectionRef> SelectedCollections;
|
|
SourcesChanged(SelectedPaths, SelectedCollections);
|
|
|
|
LegacyContentSourceWidgets->PathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, InstanceName.ToString());
|
|
|
|
// Notify 'asset path changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged();
|
|
if (PathChangedDelegate.IsBound())
|
|
{
|
|
PathChangedDelegate.Broadcast(FolderPath);
|
|
}
|
|
|
|
// Update the context menu's selected paths list
|
|
LegacyContentSourceWidgets->PathContextMenu->SetSelectedFolders(LegacyContentSourceWidgets->FavoritePathViewPtr->GetSelectedFolderItems());
|
|
}
|
|
|
|
TSharedRef<FExtender> SContentBrowser::GetPathContextMenuExtender(const TArray<FString>& InSelectedPaths) const
|
|
{
|
|
return LegacyContentSourceWidgets->PathContextMenu->MakePathViewContextMenuExtender(InSelectedPaths);
|
|
}
|
|
|
|
void SContentBrowser::CollectionSelected(const TSharedPtr<ICollectionContainer>& CollectionContainer, const FCollectionNameType& SelectedCollection)
|
|
{
|
|
// You may not select both collections and paths
|
|
LegacyContentSourceWidgets->PathViewPtr->ClearSelection();
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->ClearSelection();
|
|
|
|
TArray<FCollectionRef> SelectedCollections;
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
if (CollectionSource->GetCollectionContainer() == CollectionContainer)
|
|
{
|
|
Algo::Transform(
|
|
CollectionSource->CollectionViewPtr->GetSelectedCollections(),
|
|
SelectedCollections,
|
|
[&CollectionContainer](const FCollectionNameType& Collection) { return FCollectionRef(CollectionContainer, Collection); });
|
|
}
|
|
else
|
|
{
|
|
CollectionSource->CollectionViewPtr->ClearSelection();
|
|
}
|
|
}
|
|
|
|
TArray<FString> SelectedPaths;
|
|
|
|
if (SelectedCollections.Num() == 0)
|
|
{
|
|
// Select a dummy "None" collection to avoid the sources view switching to the paths view
|
|
SelectedCollections.Emplace(CollectionContainer, FCollectionNameType(NAME_None, ECollectionShareType::CST_System));
|
|
}
|
|
|
|
SourcesChanged(SelectedPaths, SelectedCollections);
|
|
}
|
|
|
|
void SContentBrowser::SetSelectedPaths(const TArray<FString>& FolderPaths, bool bNeedsRefresh/* = false */)
|
|
{
|
|
if (FolderPaths.Num() > 0)
|
|
{
|
|
if (bNeedsRefresh)
|
|
{
|
|
LegacyContentSourceWidgets->PathViewPtr->Populate();
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->Populate();
|
|
}
|
|
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(FolderPaths);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetSelectedPaths(FolderPaths);
|
|
PathSelected(FolderPaths[0]);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::ForceShowPluginContent(bool bEnginePlugin)
|
|
{
|
|
// For now we just forcefully enable the legacy content source when this public function is called so it succeeds
|
|
ContentSourcesContainer->ActivateLegacyContentSource();
|
|
|
|
if (LegacyContentSourceWidgets->AssetViewPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->ForceShowPluginFolder(bEnginePlugin);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnApplyHistoryData( const FHistoryData& History )
|
|
{
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LegacyContentSourceWidgets->PathViewPtr->ApplyHistoryData(History);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->ApplyHistoryData(History);
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->CollectionViewPtr->ApplyHistoryData(History);
|
|
}
|
|
LegacyContentSourceWidgets->AssetViewPtr->ApplyHistoryData(History);
|
|
|
|
// Update the breadcrumb trail path
|
|
UpdatePath();
|
|
|
|
if (History.ContentSources.HasVirtualPaths())
|
|
{
|
|
// Notify 'asset path changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged();
|
|
if (PathChangedDelegate.IsBound())
|
|
{
|
|
PathChangedDelegate.Broadcast(History.ContentSources.GetVirtualPaths()[0].ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnUpdateHistoryData(FHistoryData& HistoryData) const
|
|
{
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
const TArray<FContentBrowserItem> SelectedItems = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems();
|
|
|
|
const FText NewSource = ContentSources.HasVirtualPaths() ? FText::FromName(ContentSources.GetVirtualPaths()[0]) :
|
|
(ContentSources.HasCollections() ? FText::FromName(ContentSources.GetCollections()[0].Name) : LOCTEXT("AllAssets", "All Assets"));
|
|
|
|
HistoryData.HistoryDesc = NewSource;
|
|
HistoryData.ContentSources = ContentSources;
|
|
|
|
HistoryData.SelectionData.Reset();
|
|
for (const FContentBrowserItem& SelectedItem : SelectedItems)
|
|
{
|
|
HistoryData.SelectionData.SelectedVirtualPaths.Add(SelectedItem.GetVirtualPath());
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::NewFolderRequested(const FString& SelectedPath)
|
|
{
|
|
if( ensure(SelectedPath.Len() > 0) && LegacyContentSourceWidgets->AssetViewPtr.IsValid() )
|
|
{
|
|
CreateNewFolder(SelectedPath, FOnCreateNewFolder::CreateSP(LegacyContentSourceWidgets->AssetViewPtr.Get(), &SAssetView::NewFolderItemRequested));
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::NewFileItemRequested(const FContentBrowserItemDataTemporaryContext& NewItemContext)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->AssetViewPtr)
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->NewFileItemRequested(NewItemContext);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::SetSearchText(const FText& InSearchText)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->SearchBoxPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetText(InSearchText);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::SetSearchBoxText(const FText& InSearchText)
|
|
{
|
|
// Has anything changed? (need to test case as the operators are case-sensitive)
|
|
if (!InSearchText.ToString().Equals(TextFilter->GetRawFilterText().ToString(), ESearchCase::CaseSensitive))
|
|
{
|
|
TextFilter->SetRawFilterText(InSearchText);
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetError(TextFilter->GetFilterErrorText());
|
|
if (InSearchText.IsEmpty())
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetUserSearching(false);
|
|
}
|
|
else
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetUserSearching(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnSearchBoxChanged(const FText& InSearchText)
|
|
{
|
|
SetSearchBoxText(InSearchText);
|
|
|
|
// Broadcast 'search box changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
|
|
ContentBrowserModule.GetOnSearchBoxChanged().Broadcast(InSearchText, bIsPrimaryBrowser);
|
|
}
|
|
|
|
void SContentBrowser::OnSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type CommitInfo)
|
|
{
|
|
SetSearchBoxText(InSearchText);
|
|
}
|
|
|
|
FReply SContentBrowser::OnSearchKeyDown(const FGeometry& Geometry, const FKeyEvent& InKeyEvent)
|
|
{
|
|
FInputChord CheckChord(InKeyEvent.GetKey(), EModifierKey::FromBools(InKeyEvent.IsControlDown(), InKeyEvent.IsAltDown(), InKeyEvent.IsShiftDown(), InKeyEvent.IsCommandDown()));
|
|
|
|
// Clear focus if the content browser drawer key is clicked so it will close the opened content browser
|
|
if (FGlobalEditorCommonCommands::Get().OpenContentBrowserDrawer->HasActiveChord(CheckChord))
|
|
{
|
|
FReply Reply = FReply::Handled().ClearUserFocus(EFocusCause::SetDirectly);
|
|
|
|
if (bIsDrawer)
|
|
{
|
|
GEditor->GetEditorSubsystem<UStatusBarSubsystem>()->DismissContentBrowserDrawer();
|
|
}
|
|
return Reply;
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
bool SContentBrowser::IsSaveSearchButtonEnabled() const
|
|
{
|
|
return !TextFilter->GetRawFilterText().IsEmptyOrWhitespace();
|
|
}
|
|
|
|
void SContentBrowser::OnSaveSearchButtonClicked(const FText& InSearchText)
|
|
{
|
|
// Need to make sure we can see the collections view
|
|
if (!bSourcesViewExpanded)
|
|
{
|
|
SourcesViewExpandClicked();
|
|
}
|
|
|
|
// We want to add any currently selected paths to the final saved query so that you get back roughly the same list of objects as what you're currently seeing
|
|
FString SelectedPathsQuery;
|
|
{
|
|
const TArray<FName>& VirtualPaths = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources().GetVirtualPaths();
|
|
for (int32 SelectedPathIndex = 0; SelectedPathIndex < VirtualPaths.Num(); ++SelectedPathIndex)
|
|
{
|
|
SelectedPathsQuery.Append(TEXT("Path:'"));
|
|
SelectedPathsQuery.Append(VirtualPaths[SelectedPathIndex].ToString());
|
|
SelectedPathsQuery.Append(TEXT("'..."));
|
|
|
|
if (SelectedPathIndex + 1 < VirtualPaths.Num())
|
|
{
|
|
SelectedPathsQuery.Append(TEXT(" OR "));
|
|
}
|
|
}
|
|
}
|
|
|
|
// todo: should we automatically append any type filters too?
|
|
|
|
// Produce the final query
|
|
FText FinalQueryText;
|
|
if (SelectedPathsQuery.IsEmpty())
|
|
{
|
|
FinalQueryText = TextFilter->GetRawFilterText();
|
|
}
|
|
else
|
|
{
|
|
FinalQueryText = FText::FromString(FString::Printf(TEXT("(%s) AND (%s)"), *TextFilter->GetRawFilterText().ToString(), *SelectedPathsQuery));
|
|
}
|
|
|
|
// Get all menu extenders for this context menu from the content browser module
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
TArray<FContentBrowserMenuExtender> MenuExtenderDelegates = ContentBrowserModule.GetAllCollectionViewContextMenuExtenders();
|
|
|
|
TArray<TSharedPtr<FExtender>> Extenders;
|
|
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
|
|
{
|
|
if (MenuExtenderDelegates[i].IsBound())
|
|
{
|
|
Extenders.Add(MenuExtenderDelegates[i].Execute());
|
|
}
|
|
}
|
|
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
|
|
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL, MenuExtender, true);
|
|
|
|
/** Make the menu to save a search */
|
|
MenuBuilder.BeginSection("ContentBrowserSaveSearch", LOCTEXT("ContentBrowserCreateFilterMenuHeading", "Create Filter"));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ContentBrowserSaveAsCustomFilter", "Save as Custom Filter"),
|
|
LOCTEXT("ContentBrowserSaveAsCustomFilterTooltip", "Save the current search text as a custom filter in the filter bar"),
|
|
FSlateIcon(),
|
|
FUIAction(FSimpleDelegate::CreateSP(this, &SContentBrowser::SaveSearchAsFilter))
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
if (!CollectionSources.IsEmpty())
|
|
{
|
|
if (CollectionSources.Num() == 1)
|
|
{
|
|
CollectionSources[0]->CollectionViewPtr->MakeSaveDynamicCollectionMenu(MenuBuilder, FinalQueryText);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.BeginSection(NAME_None, LOCTEXT("ContentBrowserCollectionContainersMenuHeading", "Collection Containers"));
|
|
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
const TSharedPtr<ICollectionContainer>& CollectionContainer = CollectionSource->GetCollectionContainer();
|
|
MenuBuilder.AddSubMenu(
|
|
CollectionContainer->GetCollectionSource()->GetTitle(),
|
|
TAttribute<FText>(),
|
|
FNewMenuDelegate::CreateLambda([CollectionView = CollectionSource->CollectionViewPtr, FinalQueryText](FMenuBuilder& InSubMenuBuilder)
|
|
{
|
|
CollectionView->MakeSaveDynamicCollectionMenu(InSubMenuBuilder, FinalQueryText);
|
|
}));
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
}
|
|
|
|
FWidgetPath WidgetPath;
|
|
if (FSlateApplication::Get().GeneratePathToWidgetUnchecked(AsShared(), WidgetPath, EVisibility::All)) // since the collection window can be hidden, we need to manually search the path with a EVisibility::All instead of the default EVisibility::Visible
|
|
{
|
|
FSlateApplication::Get().PushMenu(
|
|
AsShared(),
|
|
WidgetPath,
|
|
MenuBuilder.MakeWidget(),
|
|
FSlateApplication::Get().GetCursorPos(),
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TopMenu)
|
|
);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::SaveSearchAsFilter()
|
|
{
|
|
LegacyContentSourceWidgets->FilterListPtr->CreateCustomFilterDialog(TextFilter->GetRawFilterText());
|
|
}
|
|
|
|
void SContentBrowser::EditPathCommand()
|
|
{
|
|
LegacyContentSourceWidgets->NavigationBar->StartEditingPath();
|
|
}
|
|
|
|
void SContentBrowser::OnNavigateToPath(const FString& NewPath)
|
|
{
|
|
FContentBrowserItem Item = ContentBrowserUtils::TryGetItemFromUserProvidedPath(NewPath);
|
|
if (Item.IsValid())
|
|
{
|
|
SyncToItems(MakeArrayView(&Item, 1));
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnPathClicked( const FString& CrumbData )
|
|
{
|
|
FAssetViewContentSources ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
if (ContentSources.HasCollections() )
|
|
{
|
|
// Collection crumb was clicked. See if we've clicked on a different collection in the hierarchy, and change the path if required.
|
|
FCollectionSource* CollectionSource = nullptr;
|
|
FCollectionNameType CollectionClicked(NAME_None, ECollectionShareType::CST_System);
|
|
if (ParseCollectionCrumbData(CrumbData, CollectionSource, CollectionClicked) &&
|
|
(ContentSources.GetCollections()[0].Container != CollectionSource->GetCollectionContainer() ||
|
|
ContentSources.GetCollections()[0].Name != CollectionClicked.Name ||
|
|
ContentSources.GetCollections()[0].Type != CollectionClicked.Type))
|
|
{
|
|
TArray<FCollectionNameType> Collections;
|
|
Collections.Add(CollectionClicked);
|
|
CollectionSource->CollectionViewPtr->SetSelectedCollections(Collections);
|
|
|
|
CollectionSelected(CollectionSource->GetCollectionContainer(), CollectionClicked);
|
|
}
|
|
}
|
|
else if ( !ContentSources.HasVirtualPaths() )
|
|
{
|
|
// No collections or paths are selected. This is "All Assets". Don't change the path when this is clicked.
|
|
}
|
|
else if (ContentSources.GetVirtualPaths().Num() > 1 || ContentSources.GetVirtualPaths()[0].ToString() != CrumbData )
|
|
{
|
|
// More than one path is selected or the crumb that was clicked is not the same path as the current one. Change the path.
|
|
TArray<FString> SelectedPaths;
|
|
SelectedPaths.Add(CrumbData);
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
PathSelected(SelectedPaths[0]);
|
|
}
|
|
}
|
|
|
|
TArray<FString> SContentBrowser::GetRecentPaths() const
|
|
{
|
|
return JumpMRU;
|
|
}
|
|
|
|
void SContentBrowser::OnPathMenuItemClicked(FString ClickedPath)
|
|
{
|
|
OnPathClicked(ClickedPath);
|
|
}
|
|
|
|
bool SContentBrowser::OnCanEditPathAsText(const FString& Text) const
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
if (ContentSources.HasCollections())
|
|
{
|
|
// Do not present collections as text because their names are not very user friendly right now.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TArray<FString> SContentBrowser::OnCompletePathPrefix(const FString& Prefix) const
|
|
{
|
|
FStringView PrefixView = Prefix;
|
|
|
|
// Strip to last path separator
|
|
FName Parent;
|
|
if (int32 Index = UE::String::FindLastChar(PrefixView, '/'); Index != INDEX_NONE)
|
|
{
|
|
PrefixView.LeftInline(Index);
|
|
Parent = FName(PrefixView);
|
|
}
|
|
|
|
// Find PrefixView in the available tree of data sources, get its direct children, and filter them by SuffixView
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
TArray<FContentBrowserItem> SubItems = ContentBrowserUtils::GetChildItemsFromVirtualPath(
|
|
Parent,
|
|
LegacyContentSourceWidgets->PathViewPtr->GetContentBrowserItemCategoryFilter(),
|
|
LegacyContentSourceWidgets->PathViewPtr->GetContentBrowserItemAttributeFilter(),
|
|
InstanceName,
|
|
*LegacyContentSourceWidgets->PathViewPtr);
|
|
TArray<FString> Results;
|
|
for (const FContentBrowserItem& Item : SubItems)
|
|
{
|
|
FName Path = Item.GetVirtualPath();
|
|
FNameBuilder PathBuilder(Path);
|
|
if (PathBuilder.ToView().StartsWith(Prefix))
|
|
{
|
|
Results.Add(Item.GetVirtualPath().ToString());
|
|
}
|
|
}
|
|
return Results;
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::OnGetCrumbDelimiterContent(const FString& CrumbData) const
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
TSharedPtr<SWidget> Widget = SNullWidget::NullWidget;
|
|
TSharedPtr<SWidget> MenuWidget;
|
|
|
|
if( ContentSources.HasCollections() )
|
|
{
|
|
FCollectionSource* CollectionSource = nullptr;
|
|
FCollectionNameType CollectionClicked(NAME_None, ECollectionShareType::CST_System);
|
|
if (ParseCollectionCrumbData(CrumbData, CollectionSource, CollectionClicked))
|
|
{
|
|
TArray<FCollectionNameType> ChildCollections;
|
|
CollectionSource->GetCollectionContainer()->GetChildCollections(CollectionClicked.Name, CollectionClicked.Type, ChildCollections);
|
|
|
|
if( ChildCollections.Num() > 0 )
|
|
{
|
|
FMenuBuilder MenuBuilder( true, nullptr );
|
|
|
|
for( const FCollectionNameType& ChildCollection : ChildCollections )
|
|
{
|
|
const FString ChildCollectionCrumbData = ContentBrowserUtils::FormatCollectionCrumbData(*CollectionSource->GetCollectionContainer(), ChildCollection);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromName(ChildCollection.Name),
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), ECollectionShareType::GetIconStyleName(ChildCollection.Type)),
|
|
FUIAction(FExecuteAction::CreateSP(const_cast<SContentBrowser*>(this), &SContentBrowser::OnPathMenuItemClicked, ChildCollectionCrumbData))
|
|
);
|
|
}
|
|
|
|
MenuWidget = MenuBuilder.MakeWidget();
|
|
}
|
|
}
|
|
}
|
|
else if( ContentSources.HasVirtualPaths() )
|
|
{
|
|
TArray<FContentBrowserItem> SubItems = ContentBrowserUtils::GetChildItemsFromVirtualPath(
|
|
*CrumbData,
|
|
LegacyContentSourceWidgets->PathViewPtr->GetContentBrowserItemCategoryFilter(),
|
|
LegacyContentSourceWidgets->PathViewPtr->GetContentBrowserItemAttributeFilter(),
|
|
InstanceName,
|
|
*LegacyContentSourceWidgets->PathViewPtr);
|
|
SubItems.Sort([](const FContentBrowserItem& ItemOne, const FContentBrowserItem& ItemTwo)
|
|
{
|
|
return ItemOne.GetDisplayName().CompareTo(ItemTwo.GetDisplayName()) < 0;
|
|
});
|
|
|
|
if(SubItems.Num() > 0)
|
|
{
|
|
FMenuBuilder MenuBuilder( true, nullptr );
|
|
|
|
for (const FContentBrowserItem& SubItem : SubItems)
|
|
{
|
|
FName FolderBrushName = NAME_None;
|
|
FName FolderShadowBrushName = NAME_None;
|
|
ContentBrowserUtils::TryGetFolderBrushAndShadowNameSmall(SubItem, FolderBrushName, FolderShadowBrushName);
|
|
|
|
FText EntryName = SubItem.GetDisplayName();
|
|
FUIAction EntryAction = FUIAction(FExecuteAction::CreateSP(const_cast<SContentBrowser*>(this), &SContentBrowser::OnPathMenuItemClicked, SubItem.GetVirtualPath().ToString()));
|
|
|
|
if (FolderBrushName != NAME_None)
|
|
{
|
|
FLinearColor FolderColor = UE::Editor::ContentBrowser::ExtensionUtils::GetFolderColor(SubItem).Get(ContentBrowserUtils::GetDefaultColor());
|
|
|
|
FMenuEntryParams Params;
|
|
Params.EntryWidget = ContentBrowserUtils::GetFolderWidgetForNavigationBar(EntryName, FolderBrushName, FolderColor);
|
|
Params.DirectActions = EntryAction;
|
|
MenuBuilder.AddMenuEntry(Params);
|
|
}
|
|
else
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
EntryName,
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), FolderBrushName),
|
|
EntryAction
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
MenuWidget = MenuBuilder.MakeWidget();
|
|
}
|
|
}
|
|
|
|
if( MenuWidget.IsValid() )
|
|
{
|
|
// Do not allow the menu to become too large if there are many directories
|
|
Widget =
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
.MaxHeight( 400.0f )
|
|
[
|
|
MenuWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
return Widget.ToSharedRef();
|
|
}
|
|
|
|
bool SContentBrowser::ParseCollectionCrumbData(const FString& CrumbData, FCollectionSource*& OutCollectionSource, FCollectionNameType& OutCollection) const
|
|
{
|
|
OutCollectionSource = nullptr;
|
|
OutCollection = FCollectionNameType(NAME_None, ECollectionShareType::CST_System);
|
|
|
|
TOptional<FCollectionNameType> Collection;
|
|
{
|
|
FString CollectionContainerName;
|
|
FString Temp;
|
|
if (CrumbData.Split(TEXT("?"), &CollectionContainerName, &Temp))
|
|
{
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
if (CollectionSource->GetCollectionContainer()->GetCollectionSource()->GetName() == CollectionContainerName)
|
|
{
|
|
FString CollectionName;
|
|
FString CollectionTypeString;
|
|
if (Temp.Split(TEXT("?"), &CollectionName, &CollectionTypeString))
|
|
{
|
|
const int32 CollectionType = FCString::Atoi(*CollectionTypeString);
|
|
if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All)
|
|
{
|
|
OutCollectionSource = CollectionSource.Get();
|
|
OutCollection = FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType));
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FString SContentBrowser::GetCurrentPath(const EContentBrowserPathType PathType) const
|
|
{
|
|
FString CurrentPath;
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
if ( ContentSources.HasVirtualPaths() && ContentSources.GetVirtualPaths()[0] != NAME_None )
|
|
{
|
|
if (PathType == EContentBrowserPathType::Virtual)
|
|
{
|
|
ContentSources.GetVirtualPaths()[0].ToString(CurrentPath);
|
|
}
|
|
else if (IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(FNameBuilder(ContentSources.GetVirtualPaths()[0]), CurrentPath) != PathType)
|
|
{
|
|
const EContentBrowserPathType ConvertedPathType = IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(FNameBuilder(ContentSources.GetVirtualPaths()[0]), CurrentPath);
|
|
if (ConvertedPathType != PathType)
|
|
{
|
|
CurrentPath.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
return CurrentPath;
|
|
}
|
|
|
|
void SContentBrowser::AppendNewMenuContextObjects(const EContentBrowserDataMenuContext_AddNewMenuDomain InDomain, const TArray<FName>& InSelectedPaths, FToolMenuContext& InOutMenuContext, UContentBrowserToolbarMenuContext* CommonContext, bool bCanBeModified)
|
|
{
|
|
if(!CommonContext)
|
|
{
|
|
UContentBrowserMenuContext* CommonContextObject = NewObject<UContentBrowserMenuContext>();
|
|
CommonContextObject->ContentBrowser = SharedThis(this);
|
|
InOutMenuContext.AddObject(CommonContextObject);
|
|
}
|
|
else
|
|
{
|
|
InOutMenuContext.AddObject(CommonContext);
|
|
}
|
|
|
|
{
|
|
bool bContainsValidPackagePath = false;
|
|
for (const FName SelectedPath : InSelectedPaths)
|
|
{
|
|
FString ConvertedPath;
|
|
if (IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(FNameBuilder(SelectedPath), ConvertedPath) == EContentBrowserPathType::Internal)
|
|
{
|
|
if (FPackageName::IsValidPath(ConvertedPath))
|
|
{
|
|
bContainsValidPackagePath = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UContentBrowserDataMenuContext_AddNewMenu* DataContextObject = NewObject<UContentBrowserDataMenuContext_AddNewMenu>();
|
|
DataContextObject->SelectedPaths = InSelectedPaths;
|
|
DataContextObject->OwnerDomain = InDomain;
|
|
DataContextObject->OnBeginItemCreation = UContentBrowserDataMenuContext_AddNewMenu::FOnBeginItemCreation::CreateSP(this, &SContentBrowser::NewFileItemRequested);
|
|
DataContextObject->bCanBeModified = bCanBeModified;
|
|
DataContextObject->bContainsValidPackagePath = bContainsValidPackagePath;
|
|
DataContextObject->OwningInstanceConfig = GetConstInstanceConfig();
|
|
InOutMenuContext.AddObject(DataContextObject);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::MakeAddNewContextMenu(const EContentBrowserDataMenuContext_AddNewMenuDomain InDomain, UContentBrowserToolbarMenuContext* CommonContext)
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
bool bCanBeModified = false;
|
|
|
|
// Get all menu extenders for this context menu from the content browser module
|
|
TSharedPtr<FExtender> MenuExtender;
|
|
{
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
TArray<FContentBrowserMenuExtender_SelectedPaths> MenuExtenderDelegates = ContentBrowserModule.GetAllAssetContextMenuExtenders();
|
|
|
|
// Delegate wants paths as FStrings
|
|
TArray<FString> SelectedPackagePaths;
|
|
{
|
|
// We need to try and resolve these paths back to items in order to query their attributes
|
|
// This will only work for items that have already been discovered
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
|
|
for (const FName& VirtualPathToSync : ContentSources.GetVirtualPaths())
|
|
{
|
|
const FContentBrowserItem ItemToSync = ContentBrowserData->GetItemAtPath(VirtualPathToSync, EContentBrowserItemTypeFilter::IncludeFolders);
|
|
if (ItemToSync.IsValid())
|
|
{
|
|
FName PackagePath;
|
|
if (ItemToSync.Legacy_TryGetPackagePath(PackagePath))
|
|
{
|
|
SelectedPackagePaths.Add(PackagePath.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SelectedPackagePaths.Num() > 0)
|
|
{
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
bCanBeModified = AssetToolsModule.Get().AllPassWritableFolderFilter(SelectedPackagePaths);
|
|
|
|
TArray<TSharedPtr<FExtender>> Extenders;
|
|
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
|
|
{
|
|
if (MenuExtenderDelegates[i].IsBound())
|
|
{
|
|
Extenders.Add(MenuExtenderDelegates[i].Execute(SelectedPackagePaths));
|
|
}
|
|
}
|
|
MenuExtender = FExtender::Combine(Extenders);
|
|
}
|
|
}
|
|
|
|
FToolMenuContext ToolMenuContext(nullptr, MenuExtender, nullptr);
|
|
AppendNewMenuContextObjects(InDomain, ContentSources.GetVirtualPaths(), ToolMenuContext, CommonContext, bCanBeModified);
|
|
|
|
TSharedRef<SWidget> GeneratedWidget = UToolMenus::Get()->GenerateWidget("ContentBrowser.AddNewContextMenu", ToolMenuContext);
|
|
GeneratedWidget->AddMetadata<FTagMetaData>(MakeShared<FTagMetaData>(TEXT("ContentBrowser.AddNewContextMenu")));
|
|
return GeneratedWidget;
|
|
}
|
|
|
|
void SContentBrowser::PopulateAddNewContextMenu(class UToolMenu* Menu)
|
|
{
|
|
const UContentBrowserDataMenuContext_AddNewMenu* ContextObject = Menu->FindContext<UContentBrowserDataMenuContext_AddNewMenu>();
|
|
checkf(ContextObject, TEXT("Required context UContentBrowserDataMenuContext_AddNewMenu was missing!"));
|
|
|
|
// Only add "New Folder" item if we do not have a collection selected
|
|
FNewAssetOrClassContextMenu::FOnNewFolderRequested OnNewFolderRequested;
|
|
if (ContextObject->OwnerDomain != EContentBrowserDataMenuContext_AddNewMenuDomain::PathView &&
|
|
Algo::AllOf(CollectionSources, [](const TUniquePtr<FCollectionSource>& CollectionSource) { return CollectionSource->CollectionViewPtr->GetSelectedCollections().Num() == 0; }))
|
|
{
|
|
OnNewFolderRequested = FNewAssetOrClassContextMenu::FOnNewFolderRequested::CreateSP(this, &SContentBrowser::NewFolderRequested);
|
|
}
|
|
|
|
|
|
// New feature packs don't depend on the current paths, so we always add this item if it was requested
|
|
FNewAssetOrClassContextMenu::FOnGetContentRequested OnGetContentRequested;
|
|
|
|
OnGetContentRequested = FNewAssetOrClassContextMenu::FOnGetContentRequested::CreateSP(this, &SContentBrowser::OnAddContentRequested);
|
|
|
|
FNewAssetOrClassContextMenu::MakeContextMenu(
|
|
Menu,
|
|
ContextObject->SelectedPaths,
|
|
OnNewFolderRequested,
|
|
OnGetContentRequested
|
|
);
|
|
}
|
|
|
|
bool SContentBrowser::CanWriteToCurrentPath() const
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->AssetViewPtr.IsValid())
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
if (ContentSources.GetVirtualPaths().Num() == 1)
|
|
{
|
|
FName CurrentPath = ContentSources.GetVirtualPaths()[0];
|
|
if (!CachedCanWriteToCurrentPath.IsSet() || CachedCanWriteToCurrentPath.GetValue() != CurrentPath)
|
|
{
|
|
CachedCanWriteToCurrentPath = CurrentPath;
|
|
bCachedCanWriteToCurrentPath = CanWriteToPath(FContentBrowserItemPath(CurrentPath, EContentBrowserPathType::Virtual));
|
|
}
|
|
|
|
return bCachedCanWriteToCurrentPath;
|
|
}
|
|
else
|
|
{
|
|
CachedCanWriteToCurrentPath.Reset();
|
|
bCachedCanWriteToCurrentPath = false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SContentBrowser::CanWriteToPath(const FContentBrowserItemPath InPath) const
|
|
{
|
|
// Reject if only virtual
|
|
if (!InPath.HasInternalPath())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Reject if path not inside a mount point
|
|
if (!FPackageName::IsValidPath(InPath.GetInternalPathString()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Reject if folder writes blocked to path
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
const TSharedRef<FPathPermissionList>& WritableFolderFilter = AssetToolsModule.Get().GetWritableFolderPermissionList();
|
|
if (!WritableFolderFilter->PassesStartsWithFilter(InPath.GetInternalPathName()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SContentBrowser::AddCustomTextFilter(const FCustomTextFilterData& FilterData, bool bApplyFilter)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->FilterListPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->FilterListPtr->OnCreateCustomTextFilter(FilterData, bApplyFilter);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::DeleteCustomTextFilterByLabel(const FText& FilterLabel)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->FilterListPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->FilterListPtr->DeleteCustomTextFilterByLabel(FilterLabel);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::ModifyCustomTextFilterByLabel(const FCustomTextFilterData& NewFilterData, const FText& FilterLabel)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->FilterListPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->FilterListPtr->ModifyCustomTextFilterByLabel(NewFilterData, FilterLabel);
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::IsAssetViewDoneFiltering()
|
|
{
|
|
bool isDoneFiltering = false;
|
|
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->AssetViewPtr.IsValid())
|
|
{
|
|
isDoneFiltering = !LegacyContentSourceWidgets->AssetViewPtr->HasItemsPendingFilter()
|
|
&& !LegacyContentSourceWidgets->AssetViewPtr->HasThumbnailsPendingUpdate();
|
|
}
|
|
|
|
return isDoneFiltering;
|
|
}
|
|
|
|
bool SContentBrowser::IsAddNewEnabled() const
|
|
{
|
|
return CanWriteToCurrentPath();
|
|
}
|
|
|
|
FText SContentBrowser::GetAddNewToolTipText() const
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
if ( ContentSources.GetVirtualPaths().Num() == 1 )
|
|
{
|
|
const FString CurrentPath = ContentSources.GetVirtualPaths()[0].ToString();
|
|
|
|
if (!CanWriteToCurrentPath())
|
|
{
|
|
return FText::Format(LOCTEXT("AddNewToolTip_CannotWrite", "Cannot write to path {0}..."), FText::FromString(CurrentPath));
|
|
}
|
|
|
|
return FText::Format( LOCTEXT("AddNewToolTip_AddNewContent", "Create new content in {0}...\nShortcut: Ctrl + RMB anywhere in the asset view"), FText::FromString(CurrentPath) );
|
|
}
|
|
else if ( ContentSources.GetVirtualPaths().Num() > 1 )
|
|
{
|
|
return LOCTEXT( "AddNewToolTip_MultiplePaths", "Cannot add content to multiple paths." );
|
|
}
|
|
|
|
return LOCTEXT( "AddNewToolTip_NoPath", "No path is selected as an add target." );
|
|
}
|
|
|
|
void SContentBrowser::PopulatePathViewFiltersMenu(UToolMenu* Menu)
|
|
{
|
|
if (LegacyContentSourceWidgets && LegacyContentSourceWidgets->PathViewPtr.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->PathViewPtr->PopulatePathViewFiltersMenu(Menu);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::ExtendAssetViewButtonMenuContext(FToolMenuContext& InMenuContext)
|
|
{
|
|
UContentBrowserMenuContext* ContextObject = NewObject<UContentBrowserMenuContext>();
|
|
ContextObject->ContentBrowser = SharedThis(this);
|
|
InMenuContext.AddObject(ContextObject);
|
|
}
|
|
|
|
FReply SContentBrowser::OnSaveClicked()
|
|
{
|
|
ContentBrowserUtils::SaveDirtyPackages();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SContentBrowser::OnAddContentRequested()
|
|
{
|
|
IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked<IAddContentDialogModule>("AddContentDialog");
|
|
FWidgetPath WidgetPath;
|
|
FSlateApplication::Get().GeneratePathToWidgetChecked(AsShared(), WidgetPath);
|
|
AddContentDialogModule.ShowDialog(WidgetPath.GetWindow());
|
|
}
|
|
|
|
void SContentBrowser::OnNewItemRequested(const FContentBrowserItem& NewItem)
|
|
{
|
|
// Make sure we are showing the location of the new file (we may have created it in a folder)
|
|
TArray<FString> SelectedPaths;
|
|
SelectedPaths.Add(FPaths::GetPath(NewItem.GetVirtualPath().ToString()));
|
|
|
|
const TArray<FString> CurrentlySelectedPath = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
|
|
// Only change the selected paths if needed. (To avoid adding an entry to navigation history when it is not needed)
|
|
if (SelectedPaths != CurrentlySelectedPath)
|
|
{
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(SelectedPaths);
|
|
PathSelected(SelectedPaths[0]);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnItemSelectionChanged(const FContentBrowserItem& SelectedItem, ESelectInfo::Type SelectInfo, EContentBrowserViewContext ViewContext)
|
|
{
|
|
if (ViewContext == EContentBrowserViewContext::AssetView)
|
|
{
|
|
if (bIsPrimaryBrowser)
|
|
{
|
|
SyncGlobalSelectionSet();
|
|
}
|
|
|
|
// Notify 'asset selection changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
FContentBrowserModule::FOnAssetSelectionChanged& AssetSelectionChangedDelegate = ContentBrowserModule.GetOnAssetSelectionChanged();
|
|
|
|
const TArray<FContentBrowserItem> SelectedItems = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems();
|
|
LegacyContentSourceWidgets->AssetContextMenu->SetSelectedItems(SelectedItems);
|
|
|
|
{
|
|
TArray<FSoftObjectPath> SelectedCollectionItems;
|
|
for (const FContentBrowserItem& SelectedAssetItem : SelectedItems)
|
|
{
|
|
FSoftObjectPath CollectionItemId;
|
|
if (SelectedAssetItem.TryGetCollectionId(CollectionItemId))
|
|
{
|
|
SelectedCollectionItems.Add(CollectionItemId);
|
|
}
|
|
}
|
|
|
|
for (const TUniquePtr<FCollectionSource>& CollectionSource : CollectionSources)
|
|
{
|
|
CollectionSource->CollectionViewPtr->SetSelectedAssetPaths(SelectedCollectionItems);
|
|
}
|
|
}
|
|
|
|
if (AssetSelectionChangedDelegate.IsBound())
|
|
{
|
|
TArray<FAssetData> SelectedAssets;
|
|
for (const FContentBrowserItem& SelectedAssetItem : SelectedItems)
|
|
{
|
|
FAssetData ItemAssetData;
|
|
if (SelectedAssetItem.Legacy_TryGetAssetData(ItemAssetData))
|
|
{
|
|
SelectedAssets.Add(MoveTemp(ItemAssetData));
|
|
}
|
|
}
|
|
|
|
AssetSelectionChangedDelegate.Broadcast(SelectedAssets, bIsPrimaryBrowser);
|
|
}
|
|
}
|
|
else if (ViewContext == EContentBrowserViewContext::FavoriteView)
|
|
{
|
|
checkf(!SelectedItem.IsValid() || SelectedItem.IsFolder(), TEXT("File item passed to path view selection!"));
|
|
FavoritePathSelected(SelectedItem.IsValid() ? SelectedItem.GetVirtualPath().ToString() : FString());
|
|
}
|
|
else
|
|
{
|
|
checkf(!SelectedItem.IsValid() || SelectedItem.IsFolder(), TEXT("File item passed to path view selection!"));
|
|
PathSelected(SelectedItem.IsValid() ? SelectedItem.GetVirtualPath().ToString() : FString());
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnItemsActivated(TArrayView<const FContentBrowserItem> ActivatedItems, EAssetTypeActivationMethod::Type ActivationMethod)
|
|
{
|
|
FContentBrowserItem FirstActivatedFolder;
|
|
|
|
// Batch these by their data sources
|
|
TMap<UContentBrowserDataSource*, TArray<FContentBrowserItemData>> SourcesAndItems;
|
|
for (const FContentBrowserItem& ActivatedItem : ActivatedItems)
|
|
{
|
|
if (ActivatedItem.IsFile())
|
|
{
|
|
FContentBrowserItem::FItemDataArrayView ItemDataArray = ActivatedItem.GetInternalItems();
|
|
for (const FContentBrowserItemData& ItemData : ItemDataArray)
|
|
{
|
|
if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource())
|
|
{
|
|
TArray<FContentBrowserItemData>& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource);
|
|
ItemsForSource.Add(ItemData);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ActivatedItem.IsFolder() && !FirstActivatedFolder.IsValid())
|
|
{
|
|
FirstActivatedFolder = ActivatedItem;
|
|
}
|
|
}
|
|
|
|
if (SourcesAndItems.Num() == 0 && FirstActivatedFolder.IsValid())
|
|
{
|
|
// Activate the selected folder
|
|
FolderEntered(FirstActivatedFolder);
|
|
return;
|
|
}
|
|
|
|
// Execute the operation now
|
|
for (const auto& SourceAndItemsPair : SourcesAndItems)
|
|
{
|
|
if (ActivationMethod == EAssetTypeActivationMethod::Previewed)
|
|
{
|
|
SourceAndItemsPair.Key->BulkPreviewItems(SourceAndItemsPair.Value);
|
|
}
|
|
else
|
|
{
|
|
for (const FContentBrowserItemData& ItemToEdit : SourceAndItemsPair.Value)
|
|
{
|
|
FText EditErrorMsg;
|
|
if (!SourceAndItemsPair.Key->CanEditItem(ItemToEdit, &EditErrorMsg) && !SourceAndItemsPair.Key->CanViewItem(ItemToEdit, &EditErrorMsg))
|
|
{
|
|
AssetViewUtils::ShowErrorNotifcation(EditErrorMsg);
|
|
}
|
|
}
|
|
|
|
if (!SourceAndItemsPair.Key->BulkEditItems(SourceAndItemsPair.Value))
|
|
{
|
|
static const FText ErrorMessage = LOCTEXT("EditItemsFailure", "Unable to edit assets");
|
|
|
|
FNotificationInfo WarningNotification(ErrorMessage);
|
|
WarningNotification.ExpireDuration = 5.0f;
|
|
WarningNotification.Hyperlink = FSimpleDelegate::CreateStatic([](){ FMessageLog("LoadErrors").Open(EMessageSeverity::Info, true); });
|
|
WarningNotification.HyperlinkText = LOCTEXT("LoadObjectHyperlink", "Show Message Log");
|
|
WarningNotification.bFireAndForget = true;
|
|
FSlateNotificationManager::Get().AddNotification(WarningNotification);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SContentBrowser::ToggleLockClicked()
|
|
{
|
|
bIsLocked = !bIsLocked;
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SContentBrowser::DockInLayoutClicked()
|
|
{
|
|
FContentBrowserSingleton::Get().DockContentBrowserDrawer();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FText SContentBrowser::GetLockMenuText() const
|
|
{
|
|
return IsLocked() ? LOCTEXT("ContentBrowserLockMenu_Unlock", "Unlock Content Browser") : LOCTEXT("ContentBrowserLockMenu_Lock", "Lock Content Browser");
|
|
}
|
|
|
|
FSlateIcon SContentBrowser::GetLockIcon() const
|
|
{
|
|
static const FSlateIcon Unlocked = FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Unlock");
|
|
static const FSlateIcon Locked = FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Lock");
|
|
return IsLocked() ? Locked : Unlocked;
|
|
}
|
|
|
|
const FSlateBrush* SContentBrowser::GetLockIconBrush() const
|
|
{
|
|
static const FName Unlock = "Icons.Unlock";
|
|
static const FName Lock = "Icons.Lock";
|
|
|
|
return FAppStyle::Get().GetBrush(IsLocked() ? Lock : Unlock);
|
|
}
|
|
|
|
EVisibility SContentBrowser::GetSourcesViewVisibility() const
|
|
{
|
|
return bSourcesViewExpanded ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SContentBrowser::SetSourcesViewExpanded(bool bExpanded)
|
|
{
|
|
bSourcesViewExpanded = bExpanded;
|
|
|
|
if (FContentBrowserInstanceConfig* EditorConfig = GetMutableInstanceConfig())
|
|
{
|
|
EditorConfig->bSourcesExpanded = bSourcesViewExpanded;
|
|
UContentBrowserConfig::Get()->SaveEditorConfig();
|
|
}
|
|
|
|
|
|
// Notify 'Sources View Expanded' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
|
|
FContentBrowserModule::FOnSourcesViewChanged& SourcesViewChangedDelegate = ContentBrowserModule.GetOnSourcesViewChanged();
|
|
if (SourcesViewChangedDelegate.IsBound())
|
|
{
|
|
SourcesViewChangedDelegate.Broadcast(bSourcesViewExpanded);
|
|
}
|
|
}
|
|
|
|
FReply SContentBrowser::SourcesViewExpandClicked()
|
|
{
|
|
SetSourcesViewExpanded(!bSourcesViewExpanded);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void SContentBrowser::OnContentBrowserSettingsChanged(FName PropertyName)
|
|
{
|
|
if (PropertyName.IsNone())
|
|
{
|
|
// Ensure the path is set to the correct view mode
|
|
UpdatePath();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnConsoleVariableChanged()
|
|
{
|
|
UpdatePrivateContentFeatureEnabled(true /* bUpdateFilterIfChanged */);
|
|
}
|
|
|
|
void SContentBrowser::UpdatePrivateContentFeatureEnabled(bool bUpdateFilterIfChanged)
|
|
{
|
|
}
|
|
|
|
void SContentBrowser::OnLegacyContentSourceEnabled()
|
|
{
|
|
// Re-bind our commands so they work properly
|
|
BindCommands();
|
|
|
|
// Create the content browser's default widgets and set them as the child widget contents
|
|
LegacyContentSource->SetContent(CreateLegacyAssetViewWidgets());
|
|
|
|
// Load our settings
|
|
LoadSettings(InstanceName);
|
|
|
|
// Sanity sync to make sure the global selection set is synced
|
|
SyncGlobalSelectionSet();
|
|
}
|
|
|
|
void SContentBrowser::OnLegacyContentSourceDisabled()
|
|
{
|
|
// Save our settings before destroying the widgets
|
|
SaveSettings();
|
|
|
|
// Unbind commands
|
|
UnbindCommands();
|
|
|
|
// Set the child widget contents to null and destroy all asset view widgets and they will be re-bound when the legacy content source is enabled
|
|
LegacyContentSource->SetContent(SNullWidget::NullWidget);
|
|
LegacyContentSourceWidgets.Reset();
|
|
|
|
// Unbind all delegates since we don't need them anymore, and
|
|
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
|
|
|
|
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
|
|
CollectionManagerModule.Get().GetCollectionContainers(CollectionContainers);
|
|
|
|
int32 InsertIndex = 0;
|
|
for (const TSharedPtr<ICollectionContainer>& CollectionContainer : CollectionContainers)
|
|
{
|
|
CollectionContainer->OnIsHiddenChanged().RemoveAll(this);
|
|
CollectionContainer->OnCollectionRenamed().RemoveAll(this);
|
|
CollectionContainer->OnCollectionDestroyed().RemoveAll(this);
|
|
CollectionContainer->OnCollectionUpdated().RemoveAll(this);
|
|
}
|
|
|
|
CollectionManagerModule.Get().OnCollectionContainerCreated().RemoveAll(this);
|
|
CollectionManagerModule.Get().OnCollectionContainerDestroyed().RemoveAll(this);
|
|
|
|
CollectionSources.Empty();
|
|
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
ContentBrowserData->OnItemDataUpdated().RemoveAll(this);
|
|
}
|
|
|
|
TSharedRef<SWidget> SContentBrowser::CreateLegacyAssetViewWidgets()
|
|
{
|
|
LegacyContentSourceWidgets = MakeShared<FLegacyContentSourceWidgets>();
|
|
|
|
using namespace UE::Editor::ContentBrowser::Private;
|
|
|
|
// The final widget that conatins all child widgets
|
|
TSharedRef<SWidget> FinalWidget = SNullWidget::NullWidget;
|
|
|
|
LegacyContentSourceWidgets->PathContextMenu = MakeShareable(new FPathContextMenu( AsShared() ));
|
|
LegacyContentSourceWidgets->PathContextMenu->SetOnRenameFolderRequested(FPathContextMenu::FOnRenameFolderRequested::CreateSP(this, &SContentBrowser::OnRenameRequested));
|
|
LegacyContentSourceWidgets->PathContextMenu->SetOnFolderDeleted(FPathContextMenu::FOnFolderDeleted::CreateSP(this, &SContentBrowser::OnOpenedFolderDeleted));
|
|
LegacyContentSourceWidgets->PathContextMenu->SetOnFolderFavoriteToggled(FPathContextMenu::FOnFolderFavoriteToggled::CreateSP(this, &SContentBrowser::ToggleFolderFavorite));
|
|
LegacyContentSourceWidgets->PathContextMenu->SetOnPrivateContentEditToggled(FPathContextMenu::FOnPrivateContentEditToggled::CreateSP(this, &SContentBrowser::TogglePrivateContentEdit));
|
|
|
|
// Currently this controls the asset count
|
|
const bool bShowBottomToolbar = InitConfig.bShowBottomToolbar;
|
|
|
|
LegacyContentSourceWidgets->AssetViewPtr = SNew(SAssetView)
|
|
.ThumbnailLabel(InitConfig.ThumbnailLabel)
|
|
//.ThumbnailScale(Config != nullptr ? Config->ThumbnailScale : 0.18f)
|
|
.InitialViewType(InitConfig.InitialAssetViewType)
|
|
.OnNewItemRequested(this, &SContentBrowser::OnNewItemRequested)
|
|
.OnItemSelectionChanged(this, &SContentBrowser::OnItemSelectionChanged, EContentBrowserViewContext::AssetView)
|
|
.OnItemsActivated(this, &SContentBrowser::OnItemsActivated)
|
|
.OnGetItemContextMenu(this, &SContentBrowser::GetItemContextMenu, EContentBrowserViewContext::AssetView)
|
|
.OnItemRenameCommitted(this, &SContentBrowser::OnItemRenameCommitted)
|
|
.FrontendFilters(FrontendFilters)
|
|
.TextFilter(TextFilter)
|
|
.ShowRedirectors(this, &SContentBrowser::ShouldShowRedirectors)
|
|
.HighlightedText(this, &SContentBrowser::GetHighlightedText)
|
|
.ShowBottomToolbar(bShowBottomToolbar)
|
|
.ShowViewOptions(false) // We control this for the main content browser
|
|
.AllowThumbnailEditMode(true)
|
|
.AllowThumbnailHintLabel(false)
|
|
.CanShowFolders(InitConfig.bCanShowFolders)
|
|
.CanShowClasses(InitConfig.bCanShowClasses)
|
|
.CanShowRealTimeThumbnails(InitConfig.bCanShowRealTimeThumbnails)
|
|
.CanShowDevelopersFolder(InitConfig.bCanShowDevelopersFolder)
|
|
.CanShowFavorites(true)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserAssets")))
|
|
.OwningContentBrowser(SharedThis(this))
|
|
.OnSearchOptionsChanged(this, &SContentBrowser::HandleAssetViewSearchOptionsChanged)
|
|
.bShowPathViewFilters(true)
|
|
.FillEmptySpaceInTileView(true)
|
|
.ShowDisallowedAssetClassAsUnsupportedItems(true)
|
|
.AllowCustomView(true);
|
|
|
|
TSharedRef<SWidget> ViewOptions = SNullWidget::NullWidget;
|
|
|
|
// Note, for backwards compatibility ShowBottomToolbar controls the visibility of view options so we respect that here
|
|
if (bShowBottomToolbar)
|
|
{
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
ViewOptions =
|
|
SNew(SActionButton)
|
|
.ActionButtonType(EActionButtonType::Simple)
|
|
.OnGetMenuContent(LegacyContentSourceWidgets->AssetViewPtr.ToSharedRef(), &SAssetView::GetViewButtonContent)
|
|
.Icon(FAppStyle::Get().GetBrush("Icons.Settings"));
|
|
}
|
|
else
|
|
{
|
|
ViewOptions =
|
|
SNew(SComboButton)
|
|
.ComboButtonStyle(&FAppStyle::Get().GetWidgetStyle<FComboButtonStyle>("SimpleComboButton"))
|
|
.OnGetMenuContent(LegacyContentSourceWidgets->AssetViewPtr.ToSharedRef(), &SAssetView::GetViewButtonContent)
|
|
.HasDownArrow(false)
|
|
.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0, 0.0f)
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("Icons.Settings"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("Settings", "Settings"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
|
|
{
|
|
constexpr float ToolBarVerticalPadding = 4.0f; // ToolBar total height should be 36.0f
|
|
constexpr float ToolBarButtonHeight = 24.0f; // Used for buttons that should appear to be part of the ToolBar, but aren't
|
|
constexpr float PanelInsetPadding = 2.0f;
|
|
constexpr float SourceTreeSectionPadding = 2.0f;
|
|
|
|
const TSharedRef<SWidget> AssetView = CreateAssetView(&InitConfig);
|
|
|
|
CreateFavoritesView(&InitConfig);
|
|
CreatePathView(&InitConfig);
|
|
|
|
{
|
|
SAssignNew(LegacyContentSourceWidgets->SourceTreePtr, SContentBrowserSourceTree)
|
|
|
|
+ SContentBrowserSourceTree::Slot()
|
|
.AreaWidget(FavoritesArea)
|
|
.Size(0.2f)
|
|
.Visibility(this, &SContentBrowser::GetFavoriteFolderVisibility)
|
|
|
|
+ SContentBrowserSourceTree::Slot()
|
|
.AreaWidget(PathArea)
|
|
.Size(0.8f);
|
|
}
|
|
|
|
LegacyContentSourceWidgets->SearchBoxSizeSwitcher = MakeShared<TWidgetDesiredSizeSwitcher<EAxis::X>>(
|
|
LegacyContentSourceWidgets->SearchBoxPtr,
|
|
nullptr,
|
|
FInt16Range(100)); // @note: this is overridden in menu registration
|
|
|
|
LegacyContentSourceWidgets->NavigationToolBarWidget = CreateNavigationToolBar(&InitConfig);
|
|
|
|
TSharedRef<SWidget> ToolBarWidget = CreateToolBar(&InitConfig);
|
|
|
|
LegacyContentSourceWidgets->SearchBoxSizeSwitcher->SetMaxSizeReferenceWidget(ToolBarWidget.ToSharedPtr());
|
|
|
|
TSharedPtr<SBox> AssetViewNavigationToolBarContainer = nullptr;
|
|
TSharedPtr<SBox> SourceTreeAndAssetViewNavigationToolBarContainer = nullptr;
|
|
|
|
FinalWidget =
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SSeparator)
|
|
.Thickness(2.0f)
|
|
]
|
|
|
|
// Source / Tree + Assets
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Tree + Assets + Navigation Bar
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.Padding(FMargin(0.0f))
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Assets/tree
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(FMargin(0.0f))
|
|
[
|
|
// The tree/assets splitter
|
|
SAssignNew(LegacyContentSourceWidgets->PathAssetSplitterPtr, SSplitter)
|
|
.PhysicalSplitterHandleSize(PanelInsetPadding)
|
|
|
|
// Sources View
|
|
+ SSplitter::Slot()
|
|
.Resizable(true)
|
|
.SizeRule(SSplitter::SizeToContent)
|
|
.OnSlotResized(this, &SContentBrowser::OnPathViewBoxColumnResized)
|
|
[
|
|
SNew(SBox)
|
|
.Padding(0.0f)
|
|
.Visibility(this, &SContentBrowser::GetSourcesViewVisibility)
|
|
.WidthOverride(this, &SContentBrowser::GetPathViewBoxWidthOverride)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(SourceTreeSectionPadding)
|
|
.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
|
|
[
|
|
// Panel background, seen between items
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("WhiteBrush"))
|
|
.BorderBackgroundColor(FStyleColors::Panel)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Fill)
|
|
.Padding(0.0f)
|
|
[
|
|
LegacyContentSourceWidgets->SourceTreePtr.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Asset View
|
|
+ SSplitter::Slot()
|
|
.Value(0.75f)
|
|
[
|
|
SNew(SBox)
|
|
.Padding(0.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FMargin(3, ToolBarVerticalPadding))
|
|
.BorderImage(bIsDrawer ? FStyleDefaults::GetNoBrush() : FAppStyle::Get().GetBrush("Brushes.Panel"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(5, 0, 0, 0)
|
|
[
|
|
ToolBarWidget
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Top)
|
|
.Padding(0.f, 2.f, 0.f, 0.f)
|
|
[
|
|
CreateDrawerDockButton(&InitConfig)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Top)
|
|
.Padding(5.0f, 2.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(SBox)
|
|
.HeightOverride(ToolBarButtonHeight)
|
|
[
|
|
ViewOptions
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
AssetView
|
|
]
|
|
|
|
// Navigation ToolBar
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Fill)
|
|
.Padding(FMargin(0, PanelInsetPadding, 0, 0))
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FMargin(3))
|
|
.BorderImage(bIsDrawer ? FStyleDefaults::GetNoBrush() : FAppStyle::Get().GetBrush("Brushes.Panel"))
|
|
[
|
|
SAssignNew(AssetViewNavigationToolBarContainer, SBox)
|
|
.HAlign(HAlign_Fill)
|
|
.Padding(0)
|
|
[
|
|
LegacyContentSourceWidgets->NavigationToolBarWidget.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0)
|
|
[
|
|
SAssignNew(SourceTreeAndAssetViewNavigationToolBarContainer, SBox)
|
|
.Padding(0)
|
|
]
|
|
]
|
|
];
|
|
|
|
LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots = LegacyContentSourceWidgets->SourceTreePtr->GetSplitter()->GetChildren()->NumSlot();
|
|
}
|
|
else
|
|
{
|
|
FinalWidget =
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 0, 0, 0, 0 )
|
|
[
|
|
SNew( SBorder )
|
|
.Padding( FMargin( 3 ) )
|
|
.BorderImage(bIsDrawer ? FStyleDefaults::GetNoBrush() : FAppStyle::Get().GetBrush("Brushes.Panel"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Left)
|
|
.Padding(5, 0, 0, 0)
|
|
[
|
|
CreateToolBar(&InitConfig)
|
|
]
|
|
// History Back Button
|
|
+SHorizontalBox::Slot()
|
|
.Padding(10, 0, 0, 0)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(EVerticalAlignment::VAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ToolTipText( this, &SContentBrowser::GetHistoryBackTooltip )
|
|
.ContentPadding( FMargin(1, 0) )
|
|
.OnClicked(this, &SContentBrowser::BackClicked)
|
|
.IsEnabled(this, &SContentBrowser::IsBackEnabled)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserHistoryBack")))
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::Get().GetBrush("Icons.CircleArrowLeft"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
]
|
|
// History Forward Button
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(2, 0, 0, 0)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(EVerticalAlignment::VAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ToolTipText( this, &SContentBrowser::GetHistoryForwardTooltip )
|
|
.ContentPadding( FMargin(1, 0) )
|
|
.OnClicked(this, &SContentBrowser::ForwardClicked)
|
|
.IsEnabled(this, &SContentBrowser::IsForwardEnabled)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserHistoryForward")))
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::Get().GetBrush("Icons.CircleArrowRight"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
]
|
|
|
|
// Path
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Fill)
|
|
.FillWidth(1.0f)
|
|
.Padding(2, 0, 0, 0)
|
|
[
|
|
SAssignNew(LegacyContentSourceWidgets->NavigationBar, SNavigationBar)
|
|
.OnPathClicked(this, &SContentBrowser::OnPathClicked)
|
|
.GetPathMenuContent(this, &SContentBrowser::OnGetCrumbDelimiterContent)
|
|
.GetComboOptions(this, &SContentBrowser::GetRecentPaths)
|
|
.OnNavigateToPath(this, &SContentBrowser::OnNavigateToPath)
|
|
.OnCompletePrefix(this, &SContentBrowser::OnCompletePathPrefix)
|
|
.OnCanEditPathAsText(this, &SContentBrowser::OnCanEditPathAsText)
|
|
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("ContentBrowserPath")))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
CreateLockButton(&InitConfig)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
CreateDrawerDockButton(&InitConfig)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(5.0f, 0.0f, 0.0f, 0.0f)
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
ViewOptions
|
|
]
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SSeparator)
|
|
.Thickness(2.0f)
|
|
]
|
|
|
|
// Assets/tree
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0.0f)
|
|
[
|
|
// The tree/assets splitter
|
|
SAssignNew(LegacyContentSourceWidgets->PathAssetSplitterPtr, SSplitter)
|
|
.PhysicalSplitterHandleSize(2.0f)
|
|
|
|
// Sources View
|
|
+ SSplitter::Slot()
|
|
.Resizable(true)
|
|
.SizeRule(SSplitter::SizeToContent)
|
|
.OnSlotResized(this, &SContentBrowser::OnPathViewBoxColumnResized)
|
|
[
|
|
SNew(SBox)
|
|
.Padding(FMargin(4.f))
|
|
.Visibility(this, &SContentBrowser::GetSourcesViewVisibility)
|
|
.WidthOverride(this, &SContentBrowser::GetPathViewBoxWidthOverride)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FMargin(0))
|
|
.BorderImage(FAppStyle::GetBrush("Brushes.Recessed"))
|
|
|
|
[
|
|
SAssignNew(LegacyContentSourceWidgets->PathFavoriteSplitterPtr, SSplitter)
|
|
.Clipping(EWidgetClipping::ClipToBounds)
|
|
.PhysicalSplitterHandleSize(2.0f)
|
|
.HitDetectionSplitterHandleSize(8.0f)
|
|
.Orientation(EOrientation::Orient_Vertical)
|
|
.MinimumSlotHeight(26.0f)
|
|
.Visibility( this, &SContentBrowser::GetSourcesViewVisibility )
|
|
+SSplitter::Slot()
|
|
.SizeRule(TAttribute<SSplitter::ESizeRule>(this, &SContentBrowser::GetFavoritesAreaSizeRule))
|
|
.MinSize(TAttribute<float>(this, &SContentBrowser::GetFavoritesAreaMinSize))
|
|
.Value(0.2f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Header"))
|
|
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
|
|
[
|
|
CreateFavoritesView(&InitConfig)
|
|
]
|
|
]
|
|
|
|
+SSplitter::Slot()
|
|
.SizeRule(TAttribute<SSplitter::ESizeRule>(this, &SContentBrowser::GetPathAreaSizeRule))
|
|
.MinSize(29.0f)
|
|
.Value(0.8f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Header"))
|
|
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
|
|
[
|
|
CreatePathView(&InitConfig)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Asset View
|
|
+ SSplitter::Slot()
|
|
.Value(0.75f)
|
|
[
|
|
CreateAssetView(&InitConfig)
|
|
]
|
|
];
|
|
|
|
LegacyContentSourceWidgets->SourceTreeSplitterNumFixedSlots = LegacyContentSourceWidgets->PathFavoriteSplitterPtr->GetChildren()->NumSlot();
|
|
}
|
|
|
|
LegacyContentSourceWidgets->AssetContextMenu = MakeShared<FAssetContextMenu>(LegacyContentSourceWidgets->AssetViewPtr);
|
|
LegacyContentSourceWidgets->AssetContextMenu->BindCommands(Commands);
|
|
LegacyContentSourceWidgets->AssetContextMenu->SetOnShowInPathsViewRequested( FAssetContextMenu::FOnShowInPathsViewRequested::CreateSP(this, &SContentBrowser::OnShowInPathsViewRequested) );
|
|
LegacyContentSourceWidgets->AssetContextMenu->SetOnRenameRequested( FAssetContextMenu::FOnRenameRequested::CreateSP(this, &SContentBrowser::OnRenameRequested) );
|
|
LegacyContentSourceWidgets->AssetContextMenu->SetOnDuplicateRequested( FAssetContextMenu::FOnDuplicateRequested::CreateSP(this, &SContentBrowser::OnDuplicateRequested) );
|
|
LegacyContentSourceWidgets->AssetContextMenu->SetOnAssetViewRefreshRequested( FAssetContextMenu::FOnAssetViewRefreshRequested::CreateSP( this, &SContentBrowser::OnAssetViewRefreshRequested) );
|
|
|
|
TOptional<FCollectionRef> SelectedCollection;
|
|
|
|
SelectedCollection = InitConfig.SelectedCollection;
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
// Check for someone using the deprecated SelectedCollectionName instead of SelectedCollection.
|
|
if (!SelectedCollection.GetValue().IsValid() && InitConfig.SelectedCollectionName.Name != NAME_None)
|
|
{
|
|
SelectedCollection = FCollectionRef(FCollectionManagerModule::GetModule().Get().GetProjectCollectionContainer(), InitConfig.SelectedCollectionName);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
if (SelectedCollection.IsSet() && SelectedCollection.GetValue().IsValid())
|
|
{
|
|
// Select the specified collection by default
|
|
FAssetViewContentSources DefaultContentSources(SelectedCollection.GetValue());
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(DefaultContentSources);
|
|
}
|
|
else
|
|
{
|
|
// Select /Game by default
|
|
const FName DefaultInvariantPath(TEXT("/Game"));
|
|
FName DefaultVirtualPath;
|
|
IContentBrowserDataModule::Get().GetSubsystem()->ConvertInternalPathToVirtual(DefaultInvariantPath, DefaultVirtualPath);
|
|
|
|
FAssetViewContentSources DefaultContentSources(DefaultVirtualPath);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(DefaultContentSources);
|
|
}
|
|
|
|
if (bHasInitConfig)
|
|
{
|
|
// Make sure the sources view is initially visible if we were asked to show it
|
|
SetSourcesViewExpanded(InitConfig.bExpandSourcesView && InitConfig.bUseSourcesView);
|
|
}
|
|
// else
|
|
{
|
|
// in case we do not have a config, see what the global default settings are for the Sources Panel
|
|
bool bSourcesExpanded = true;
|
|
if (const FContentBrowserInstanceConfig* EditorConfig = GetConstInstanceConfig())
|
|
{
|
|
bSourcesExpanded = EditorConfig->bSourcesExpanded;
|
|
}
|
|
|
|
SetSourcesViewExpanded(bSourcesExpanded);
|
|
}
|
|
|
|
// Bindings to manage history when items are deleted
|
|
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
|
|
|
|
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
|
|
CollectionManagerModule.Get().GetCollectionContainers(CollectionContainers);
|
|
|
|
int32 InsertIndex = 0;
|
|
for (const TSharedPtr<ICollectionContainer>& CollectionContainer : CollectionContainers)
|
|
{
|
|
if (!CollectionContainer->IsHidden())
|
|
{
|
|
AddSlotForCollectionContainer(InsertIndex++, CollectionContainer.ToSharedRef());
|
|
}
|
|
|
|
CollectionContainer->OnIsHiddenChanged().AddSP(this, &SContentBrowser::HandleIsHiddenChanged);
|
|
CollectionContainer->OnCollectionRenamed().AddSP(this, &SContentBrowser::HandleCollectionRenamed);
|
|
CollectionContainer->OnCollectionDestroyed().AddSP(this, &SContentBrowser::HandleCollectionRemoved);
|
|
CollectionContainer->OnCollectionUpdated().AddSP(this, &SContentBrowser::HandleCollectionUpdated);
|
|
}
|
|
|
|
CollectionManagerModule.Get().OnCollectionContainerCreated().AddSP(this, &SContentBrowser::HandleCollectionContainerAdded);
|
|
CollectionManagerModule.Get().OnCollectionContainerDestroyed().AddSP(this, &SContentBrowser::HandleCollectionContainerRemoved);
|
|
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
ContentBrowserData->OnItemDataUpdated().AddSP(this, &SContentBrowser::HandleItemDataUpdated);
|
|
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->SetTreeTitle(LOCTEXT("Favorites", "Favorites"));
|
|
|
|
// Initialize the search options
|
|
HandleAssetViewSearchOptionsChanged();
|
|
|
|
return FinalWidget;
|
|
}
|
|
|
|
FReply SContentBrowser::BackClicked()
|
|
{
|
|
HistoryManager.GoBack();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SContentBrowser::ForwardClicked()
|
|
{
|
|
HistoryManager.GoForward();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
bool SContentBrowser::HandleRenameCommandCanExecute() const
|
|
{
|
|
// The order of these conditions are carefully crafted to match the logic of the context menu summoning, as this callback
|
|
// is shared between the path and asset views, and is given zero context as to which one is making the request
|
|
// Change this logic at your peril, lest the the dominoes fall like a house of cards (checkmate)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the path view if it has focus, which may be the case when using the keyboard to invoke the action,
|
|
// but will be false when using the context menu (which isn't an issue, as the path view clears the asset view
|
|
// selection when invoking its context menu to avoid the selection ambiguity present when using the keyboard)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteRename();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the asset menu if the asset view has focus (which may be the case when using the keyboard to invoke
|
|
// the action), as it is the only thing that is updated with the correct selection context when no context menu
|
|
// has been invoked, and can work for both folders and files
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->AssetContextMenu->CanExecuteRename();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
// Folder selection takes precedence over file selection for the context menu used...
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteRename();
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0)
|
|
{
|
|
// ... but the asset view still takes precedence over an unfocused path view unless it has no selection
|
|
return LegacyContentSourceWidgets->AssetContextMenu->CanExecuteRename();
|
|
}
|
|
else if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteRename();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SContentBrowser::HandleRenameCommand()
|
|
{
|
|
// The order of these conditions are carefully crafted to match the logic of the context menu summoning, as this callback
|
|
// is shared between the path and asset views, and is given zero context as to which one is making the request
|
|
// Change this logic at your peril, lest the the dominoes fall like a house of cards (checkmate)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the path view if it has focus, which may be the case when using the keyboard to invoke the action,
|
|
// but will be false when using the context menu (which isn't an issue, as the path view clears the asset view
|
|
// selection when invoking its context menu to avoid the selection ambiguity present when using the keyboard)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteRename(EContentBrowserViewContext::PathView);
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the asset menu if the asset view has focus (which may be the case when using the keyboard to invoke
|
|
// the action), as it is the only thing that is updated with the correct selection context when no context menu
|
|
// has been invoked, and can work for both folders and files
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->AssetContextMenu->ExecuteRename(EContentBrowserViewContext::AssetView);
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
// Folder selection takes precedence over file selection for the context menu used...
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteRename(EContentBrowserViewContext::AssetView);
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0)
|
|
{
|
|
// ... but the asset view still takes precedence over an unfocused path view unless it has no selection
|
|
LegacyContentSourceWidgets->AssetContextMenu->ExecuteRename(EContentBrowserViewContext::AssetView);
|
|
}
|
|
else if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteRename(EContentBrowserViewContext::PathView);
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::HandleSaveAssetCommandCanExecute() const
|
|
{
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0 && !LegacyContentSourceWidgets->AssetViewPtr->IsRenamingAsset())
|
|
{
|
|
return LegacyContentSourceWidgets->AssetContextMenu->CanExecuteSaveAsset();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SContentBrowser::HandleSaveAssetCommand()
|
|
{
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->AssetContextMenu->ExecuteSaveAsset();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandleSaveAllCurrentFolderCommand() const
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteSaveFolder();
|
|
}
|
|
|
|
void SContentBrowser::HandleResaveAllCurrentFolderCommand() const
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteResaveFolder();
|
|
}
|
|
|
|
void SContentBrowser::CopySelectedAssetPathCommand() const
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->CopySelectedFolder();
|
|
}
|
|
|
|
bool SContentBrowser::HandleDeleteCommandCanExecute() const
|
|
{
|
|
// The order of these conditions are carefully crafted to match the logic of the context menu summoning, as this callback
|
|
// is shared between the path and asset views, and is given zero context as to which one is making the request
|
|
// Change this logic at your peril, lest the the dominoes fall like a house of cards (checkmate)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the path view if it has focus, which may be the case when using the keyboard to invoke the action,
|
|
// but will be false when using the context menu (which isn't an issue, as the path view clears the asset view
|
|
// selection when invoking its context menu to avoid the selection ambiguity present when using the keyboard)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteDelete();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the asset menu if the asset view has focus (which may be the case when using the keyboard to invoke
|
|
// the action), as it is the only thing that is updated with the correct selection context when no context menu
|
|
// has been invoked, and can work for both folders and files
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->AssetContextMenu->CanExecuteDelete();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
// Folder selection takes precedence over file selection for the context menu used...
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteDelete();
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0)
|
|
{
|
|
// ... but the asset view still takes precedence over an unfocused path view unless it has no selection
|
|
return LegacyContentSourceWidgets->AssetContextMenu->CanExecuteDelete();
|
|
}
|
|
else if (LegacyContentSourceWidgets->FavoritePathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
else if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
return LegacyContentSourceWidgets->PathContextMenu->CanExecuteDelete();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SContentBrowser::HandleDeleteCommandExecute()
|
|
{
|
|
// The order of these conditions are carefully crafted to match the logic of the context menu summoning, as this callback
|
|
// is shared between the path and asset views, and is given zero context as to which one is making the request
|
|
// Change this logic at your peril, lest the the dominoes fall like a house of cards (checkmate)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the path view if it has focus, which may be the case when using the keyboard to invoke the action,
|
|
// but will be false when using the context menu (which isn't an issue, as the path view clears the asset view
|
|
// selection when invoking its context menu to avoid the selection ambiguity present when using the keyboard)
|
|
if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteDelete();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->HasFocusedDescendants())
|
|
{
|
|
// Prefer the asset menu if the asset view has focus (which may be the case when using the keyboard to invoke
|
|
// the action), as it is the only thing that is updated with the correct selection context when no context menu
|
|
// has been invoked, and can work for both folders and files
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->AssetContextMenu->ExecuteDelete();
|
|
}
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
// Folder selection takes precedence over file selection for the context menu used...
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteDelete();
|
|
}
|
|
else if (LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFileItems().Num() > 0)
|
|
{
|
|
// ... but the asset view still takes precedence over an unfocused path view unless it has no selection
|
|
LegacyContentSourceWidgets->AssetContextMenu->ExecuteDelete();
|
|
}
|
|
else if (LegacyContentSourceWidgets->FavoritePathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
HandleDeleteFavorite(LegacyContentSourceWidgets->PathContextMenu->GetParentContent());
|
|
}
|
|
else if (LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems().Num() > 0)
|
|
{
|
|
LegacyContentSourceWidgets->PathContextMenu->ExecuteDelete();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandleDeleteFavorite(TSharedPtr<SWidget> ParentWidget)
|
|
{
|
|
TArray<FContentBrowserItem> SelectedFolders = LegacyContentSourceWidgets->FavoritePathViewPtr->GetSelectedFolderItems();
|
|
if (ParentWidget.IsValid() && SelectedFolders.Num() > 0)
|
|
{
|
|
FText Prompt;
|
|
if (SelectedFolders.Num() == 1)
|
|
{
|
|
Prompt = FText::Format(LOCTEXT("FavoriteDeleteConfirm_Single", "Remove favorite '{0}'?"), SelectedFolders[0].GetDisplayName());
|
|
}
|
|
else
|
|
{
|
|
Prompt = FText::Format(LOCTEXT("FavoriteDeleteConfirm_Multiple", "Remove {0} favorites?"), SelectedFolders.Num());
|
|
}
|
|
|
|
// Spawn a confirmation dialog since this is potentially a highly destructive operation
|
|
ContentBrowserUtils::DisplayConfirmationPopup(
|
|
Prompt,
|
|
LOCTEXT("FavoriteRemoveConfirm_Yes", "Remove"),
|
|
LOCTEXT("FavoriteRemoveConfirm_No", "Cancel"),
|
|
ParentWidget.ToSharedRef(),
|
|
FOnClicked::CreateLambda([this, SelectedFolders]() -> FReply
|
|
{
|
|
for (const FContentBrowserItem& Folder : SelectedFolders)
|
|
{
|
|
ContentBrowserUtils::RemoveFavoriteFolder(FContentBrowserItemPath(Folder.GetVirtualPath(), EContentBrowserPathType::Virtual));
|
|
}
|
|
|
|
GConfig->Flush(false, GEditorPerProjectIni);
|
|
LegacyContentSourceWidgets->FavoritePathViewPtr->Populate();
|
|
|
|
return FReply::Handled();
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandleOpenAssetsOrFoldersCommandExecute()
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->OnOpenAssetsOrFolders();
|
|
}
|
|
|
|
void SContentBrowser::HandlePreviewAssetsCommandExecute()
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->OnPreviewAssets();
|
|
}
|
|
|
|
void SContentBrowser::HandleCreateNewFolderCommandExecute()
|
|
{
|
|
TArray<FString> SelectedPaths = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
|
|
// only create folders when a single path is selected
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
const bool bCanCreateNewFolder = SelectedPaths.Num() == 1 && ContentBrowserData->CanCreateFolder(*SelectedPaths[0], nullptr);;
|
|
|
|
if (bCanCreateNewFolder)
|
|
{
|
|
CreateNewFolder(
|
|
SelectedPaths.Num() > 0
|
|
? SelectedPaths[0]
|
|
: FString(),
|
|
FOnCreateNewFolder::CreateSP(LegacyContentSourceWidgets->AssetViewPtr.Get(), &SAssetView::NewFolderItemRequested));
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandleGoUpToParentFolder()
|
|
{
|
|
const FString SelectedPath = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPath();
|
|
int32 LastSlashIdx = INDEX_NONE;
|
|
if (ensure(SelectedPath.FindLastChar('/', LastSlashIdx)))
|
|
{
|
|
const int32 ChopCount = SelectedPath.Len() - LastSlashIdx;
|
|
const FString NewPathSelection = SelectedPath.LeftChop(ChopCount);
|
|
SetSelectedPaths({NewPathSelection}, true);
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::HandleCanGoUpToParentFolder() const
|
|
{
|
|
// Allow going up if there's one non-root folder selected
|
|
TArray<FString> SelectedPaths = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
if (SelectedPaths.Num() == 1)
|
|
{
|
|
int32 LastSlashIdx = INDEX_NONE;
|
|
if (SelectedPaths[0].FindLastChar('/', LastSlashIdx))
|
|
{
|
|
return LastSlashIdx > 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SContentBrowser::GetSelectionState(TArray<FAssetData>& SelectedAssets, TArray<FString>& SelectedPaths)
|
|
{
|
|
SelectedAssets.Reset();
|
|
SelectedPaths.Reset();
|
|
if (LegacyContentSourceWidgets->AssetViewPtr->HasAnyUserFocusOrFocusedDescendants())
|
|
{
|
|
SelectedAssets = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedAssets();
|
|
SelectedPaths = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedFolders();
|
|
}
|
|
else if (LegacyContentSourceWidgets->PathViewPtr->HasAnyUserFocusOrFocusedDescendants())
|
|
{
|
|
SelectedPaths = LegacyContentSourceWidgets->PathViewPtr->GetSelectedPaths();
|
|
}
|
|
}
|
|
|
|
bool SContentBrowser::IsBackEnabled() const
|
|
{
|
|
return HistoryManager.CanGoBack();
|
|
}
|
|
|
|
bool SContentBrowser::IsForwardEnabled() const
|
|
{
|
|
return HistoryManager.CanGoForward();
|
|
}
|
|
|
|
FText SContentBrowser::GetHistoryBackTooltip() const
|
|
{
|
|
if ( HistoryManager.CanGoBack() )
|
|
{
|
|
return FText::Format( LOCTEXT("HistoryBackTooltipFmt", "Back to {0}"), HistoryManager.GetBackDesc() );
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FText SContentBrowser::GetHistoryForwardTooltip() const
|
|
{
|
|
if ( HistoryManager.CanGoForward() )
|
|
{
|
|
return FText::Format( LOCTEXT("HistoryForwardTooltipFmt", "Forward to {0}"), HistoryManager.GetForwardDesc() );
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
void SContentBrowser::SyncGlobalSelectionSet()
|
|
{
|
|
if (!ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
USelection* EditorSelection = GEditor->GetSelectedObjects();
|
|
if ( !ensure( EditorSelection != NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the selected assets in the asset view
|
|
const TArray<FAssetData>& SelectedAssets = LegacyContentSourceWidgets->AssetViewPtr->GetSelectedAssets();
|
|
|
|
EditorSelection->BeginBatchSelectOperation();
|
|
{
|
|
TSet< UObject* > SelectedObjects;
|
|
// Lets see what the user has selected and add any new selected objects to the global selection set
|
|
for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt )
|
|
{
|
|
// Grab the object if it is loaded
|
|
if ( (*AssetIt).IsAssetLoaded() )
|
|
{
|
|
UObject* FoundObject = (*AssetIt).GetAsset();
|
|
if( FoundObject != NULL && FoundObject->GetClass() != UObjectRedirector::StaticClass() )
|
|
{
|
|
SelectedObjects.Add( FoundObject );
|
|
|
|
// Select this object!
|
|
EditorSelection->Select( FoundObject );
|
|
}
|
|
}
|
|
}
|
|
|
|
// List of objects that need to be removed from the global selection set
|
|
TArray<UObject*> EditorSelectedObjects;
|
|
EditorSelection->GetSelectedObjects(EditorSelectedObjects);
|
|
for (UObject* CurEditorObject : EditorSelectedObjects)
|
|
{
|
|
if (CurEditorObject && !SelectedObjects.Contains(CurEditorObject))
|
|
{
|
|
EditorSelection->Deselect(CurEditorObject);
|
|
}
|
|
}
|
|
}
|
|
EditorSelection->EndBatchSelectOperation();
|
|
}
|
|
|
|
void SContentBrowser::UpdatePath()
|
|
{
|
|
if (ContentSourcesContainer->IsLegacyContentSourceActive())
|
|
{
|
|
ContentBrowserUtils::UpdateNavigationBar(LegacyContentSourceWidgets->NavigationBar, LegacyContentSourceWidgets->AssetViewPtr, LegacyContentSourceWidgets->PathViewPtr);
|
|
|
|
CachedCanWriteToCurrentPath.Reset();
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnFilterChanged()
|
|
{
|
|
TArray<TSharedRef<const FPathPermissionList>> CustomPermissionLists;
|
|
FARFilter Filter = LegacyContentSourceWidgets->FilterListPtr->GetCombinedBackendFilter(CustomPermissionLists);
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetBackendFilter(Filter, &CustomPermissionLists);
|
|
|
|
// Notify 'filter changed' delegate
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
|
|
ContentBrowserModule.GetOnFilterChanged().Broadcast(Filter, bIsPrimaryBrowser);
|
|
}
|
|
|
|
FText SContentBrowser::GetPathText() const
|
|
{
|
|
FText PathLabelText;
|
|
|
|
if ( IsFilteredBySource() )
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
// At least one source is selected
|
|
const int32 NumSources = ContentSources.GetVirtualPaths().Num() + ContentSources.GetCollections().Num();
|
|
|
|
if (NumSources > 0)
|
|
{
|
|
PathLabelText = FText::FromName(ContentSources.HasVirtualPaths() ? ContentSources.GetVirtualPaths()[0] : ContentSources.GetCollections()[0].Name);
|
|
|
|
if (NumSources > 1)
|
|
{
|
|
PathLabelText = FText::Format(LOCTEXT("PathTextFmt", "{0} and {1} {1}|plural(one=other,other=others)..."), PathLabelText, NumSources - 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PathLabelText = LOCTEXT("AllAssets", "All Assets");
|
|
}
|
|
|
|
return PathLabelText;
|
|
}
|
|
|
|
bool SContentBrowser::IsFilteredBySource() const
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
return !ContentSources.IsEmpty();
|
|
}
|
|
|
|
void SContentBrowser::OnItemRenameCommitted(TArrayView<const FContentBrowserItem> Items)
|
|
{
|
|
// After a rename is committed we allow an implicit sync so as not to
|
|
// disorientate the user if they are looking at a parent folder
|
|
|
|
const bool bAllowImplicitSync = true;
|
|
const bool bDisableFiltersThatHideAssets = false;
|
|
SyncToItems(Items, bAllowImplicitSync, bDisableFiltersThatHideAssets);
|
|
}
|
|
|
|
void SContentBrowser::OnShowInPathsViewRequested(TArrayView<const FContentBrowserItem> ItemsToFind)
|
|
{
|
|
SyncToItems(ItemsToFind);
|
|
}
|
|
|
|
void SContentBrowser::OnRenameRequested(const FContentBrowserItem& Item, EContentBrowserViewContext ViewContext)
|
|
{
|
|
FText RenameErrorMsg;
|
|
if (Item.CanRename(nullptr, &RenameErrorMsg))
|
|
{
|
|
if (ViewContext == EContentBrowserViewContext::AssetView)
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->RenameItem(Item);
|
|
}
|
|
else
|
|
{
|
|
LegacyContentSourceWidgets->PathViewPtr->RenameFolderItem(Item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AssetViewUtils::ShowErrorNotifcation(RenameErrorMsg);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnOpenedFolderDeleted()
|
|
{
|
|
// Since the contents of the asset view have just been deleted, set the selected path to the default "/Game"
|
|
TArray<FString> DefaultSelectedPaths;
|
|
DefaultSelectedPaths.Add(TEXT("/Game"));
|
|
LegacyContentSourceWidgets->PathViewPtr->SetSelectedPaths(DefaultSelectedPaths);
|
|
PathSelected(TEXT("/Game"));
|
|
}
|
|
|
|
void SContentBrowser::OnDuplicateRequested(TArrayView<const FContentBrowserItem> OriginalItems)
|
|
{
|
|
if (OriginalItems.Num() == 1)
|
|
{
|
|
// Asynchronous duplication of a single item
|
|
const FContentBrowserItem& OriginalItem = OriginalItems[0];
|
|
if (ensureAlwaysMsgf(OriginalItem.IsFile(), TEXT("Can only duplicate files!")))
|
|
{
|
|
FText DuplicateErrorMsg;
|
|
if (OriginalItem.CanDuplicate(&DuplicateErrorMsg))
|
|
{
|
|
const FContentBrowserItemDataTemporaryContext NewItemContext = OriginalItem.Duplicate();
|
|
if (NewItemContext.IsValid())
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->NewFileItemRequested(NewItemContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AssetViewUtils::ShowErrorNotifcation(DuplicateErrorMsg);
|
|
}
|
|
}
|
|
}
|
|
else if (OriginalItems.Num() > 1)
|
|
{
|
|
// Batch these by their data sources
|
|
TMap<UContentBrowserDataSource*, TArray<FContentBrowserItemData>> SourcesAndItems;
|
|
for (const FContentBrowserItem& OriginalItem : OriginalItems)
|
|
{
|
|
FContentBrowserItem::FItemDataArrayView ItemDataArray = OriginalItem.GetInternalItems();
|
|
for (const FContentBrowserItemData& ItemData : ItemDataArray)
|
|
{
|
|
if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource())
|
|
{
|
|
FText DuplicateErrorMsg;
|
|
if (ItemDataSource->CanDuplicateItem(ItemData, &DuplicateErrorMsg))
|
|
{
|
|
TArray<FContentBrowserItemData>& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource);
|
|
ItemsForSource.Add(ItemData);
|
|
}
|
|
else
|
|
{
|
|
AssetViewUtils::ShowErrorNotifcation(DuplicateErrorMsg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute the operation now
|
|
TArray<FContentBrowserItemData> NewItems;
|
|
for (const auto& SourceAndItemsPair : SourcesAndItems)
|
|
{
|
|
SourceAndItemsPair.Key->BulkDuplicateItems(SourceAndItemsPair.Value, NewItems);
|
|
}
|
|
|
|
// Sync the view to the new items
|
|
if (NewItems.Num() > 0)
|
|
{
|
|
TArray<FContentBrowserItem> ItemsToSync;
|
|
for (const FContentBrowserItemData& NewItem : NewItems)
|
|
{
|
|
ItemsToSync.Emplace(NewItem);
|
|
}
|
|
|
|
SyncToItems(ItemsToSync);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnAssetViewRefreshRequested()
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->RequestSlowFullListRefresh();
|
|
}
|
|
|
|
void SContentBrowser::HandleCollectionContainerAdded(const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
if (!CollectionContainer->IsHidden())
|
|
{
|
|
ShowCollectionContainer(CollectionContainer);
|
|
}
|
|
|
|
CollectionContainer->OnIsHiddenChanged().AddSP(this, &SContentBrowser::HandleIsHiddenChanged);
|
|
CollectionContainer->OnCollectionRenamed().AddSP(this, &SContentBrowser::HandleCollectionRenamed);
|
|
CollectionContainer->OnCollectionDestroyed().AddSP(this, &SContentBrowser::HandleCollectionRemoved);
|
|
CollectionContainer->OnCollectionUpdated().AddSP(this, &SContentBrowser::HandleCollectionUpdated);
|
|
}
|
|
|
|
void SContentBrowser::ShowCollectionContainer(const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
TArray<FSoftObjectPath> SelectedCollectionItems;
|
|
for (const FContentBrowserItem& SelectedAssetItem : LegacyContentSourceWidgets->AssetViewPtr->GetSelectedItems())
|
|
{
|
|
FSoftObjectPath CollectionItemId;
|
|
if (SelectedAssetItem.TryGetCollectionId(CollectionItemId))
|
|
{
|
|
SelectedCollectionItems.Add(CollectionItemId);
|
|
}
|
|
}
|
|
|
|
int32 InsertIndex = INDEX_NONE;
|
|
|
|
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
|
|
FCollectionManagerModule::GetModule().Get().GetVisibleCollectionContainers(CollectionContainers);
|
|
|
|
for (int32 Index = 0; Index < CollectionContainers.Num(); ++Index)
|
|
{
|
|
if (CollectionContainer == CollectionContainers[Index])
|
|
{
|
|
InsertIndex = Index;
|
|
break;
|
|
}
|
|
|
|
// Make sure FCollectionManagerModule and CollectionSources maintain the same order.
|
|
if (!ensure(Index < CollectionSources.Num() && CollectionContainers[Index] == CollectionSources[Index]->GetCollectionContainer()))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
AddSlotForCollectionContainer(InsertIndex, CollectionContainer).CollectionViewPtr->SetSelectedAssetPaths(SelectedCollectionItems);
|
|
}
|
|
|
|
void SContentBrowser::HandleCollectionContainerRemoved(const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
CollectionContainer->OnIsHiddenChanged().RemoveAll(this);
|
|
CollectionContainer->OnCollectionRenamed().RemoveAll(this);
|
|
CollectionContainer->OnCollectionDestroyed().RemoveAll(this);
|
|
CollectionContainer->OnCollectionUpdated().RemoveAll(this);
|
|
|
|
HideCollectionContainer(CollectionContainer);
|
|
}
|
|
|
|
void SContentBrowser::HideCollectionContainer(const TSharedRef<ICollectionContainer>& CollectionContainer)
|
|
{
|
|
RemoveSlotForCollectionContainer(CollectionContainer);
|
|
|
|
auto UpdateContentSources = [&CollectionContainer](const FAssetViewContentSources& ContentSources, FAssetViewContentSources& OutNewContentSources)
|
|
{
|
|
auto Predicate = [&CollectionContainer](const FCollectionRef& Collection)
|
|
{
|
|
return CollectionContainer == Collection.Container;
|
|
};
|
|
|
|
if (ContentSources.GetCollections().ContainsByPredicate(Predicate))
|
|
{
|
|
TArray<FCollectionRef> NewCollections = ContentSources.GetCollections();
|
|
NewCollections.RemoveAll(Predicate);
|
|
|
|
OutNewContentSources = ContentSources;
|
|
OutNewContentSources.SetCollections(NewCollections);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(LegacyContentSourceWidgets->AssetViewPtr->GetContentSources(), NewContentSources))
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(NewContentSources);
|
|
}
|
|
}
|
|
|
|
HistoryManager.RemoveHistoryData([&UpdateContentSources](FHistoryData& HistoryData)
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(HistoryData.ContentSources, NewContentSources))
|
|
{
|
|
if (!NewContentSources.HasCollections())
|
|
{
|
|
// Remove the history if we removed the last collection.
|
|
return true;
|
|
}
|
|
|
|
HistoryData.ContentSources = MoveTemp(NewContentSources);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void SContentBrowser::HandleIsHiddenChanged(ICollectionContainer& CollectionContainer, bool bIsHidden)
|
|
{
|
|
if (bIsHidden)
|
|
{
|
|
HideCollectionContainer(CollectionContainer.AsShared());
|
|
}
|
|
else
|
|
{
|
|
ShowCollectionContainer(CollectionContainer.AsShared());
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandleCollectionRemoved(ICollectionContainer& CollectionContainer, const FCollectionNameType& Collection)
|
|
{
|
|
// Remove Collection from ContentSources.
|
|
auto UpdateContentSources = [&CollectionContainer, &Collection](const FAssetViewContentSources& ContentSources, FAssetViewContentSources& OutNewContentSources)
|
|
{
|
|
const int32 FoundIndex = ContentSources.GetCollections().IndexOfByPredicate([&CollectionContainer, &Collection](const FCollectionRef& CollectionRef)
|
|
{
|
|
return &CollectionContainer == CollectionRef.Container.Get() &&
|
|
Collection.Name == CollectionRef.Name &&
|
|
Collection.Type == CollectionRef.Type;
|
|
});
|
|
if (FoundIndex != INDEX_NONE)
|
|
{
|
|
TArray<FCollectionRef> NewCollections = ContentSources.GetCollections();
|
|
NewCollections.RemoveAt(FoundIndex);
|
|
|
|
OutNewContentSources = ContentSources;
|
|
OutNewContentSources.SetCollections(NewCollections);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(LegacyContentSourceWidgets->AssetViewPtr->GetContentSources(), NewContentSources))
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(NewContentSources);
|
|
}
|
|
}
|
|
|
|
HistoryManager.RemoveHistoryData([&UpdateContentSources](FHistoryData& HistoryData)
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(HistoryData.ContentSources, NewContentSources))
|
|
{
|
|
if (!NewContentSources.HasCollections())
|
|
{
|
|
// Remove the history if we removed the last collection.
|
|
return true;
|
|
}
|
|
|
|
HistoryData.ContentSources = MoveTemp(NewContentSources);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void SContentBrowser::HandleCollectionRenamed(ICollectionContainer& CollectionContainer, const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection)
|
|
{
|
|
// Replaces OriginalCollection with NewCollection in ContentSources.
|
|
auto UpdateContentSources = [&CollectionContainer, &OriginalCollection, &NewCollection](const FAssetViewContentSources& ContentSources, FAssetViewContentSources& OutNewContentSources)
|
|
{
|
|
const int32 FoundIndex = ContentSources.GetCollections().IndexOfByPredicate([&CollectionContainer, &OriginalCollection](const FCollectionRef& Collection)
|
|
{
|
|
return &CollectionContainer == Collection.Container.Get() &&
|
|
OriginalCollection.Name == Collection.Name &&
|
|
OriginalCollection.Type == Collection.Type;
|
|
});
|
|
if (FoundIndex != INDEX_NONE)
|
|
{
|
|
TArray<FCollectionRef> NewCollections = ContentSources.GetCollections();
|
|
NewCollections[FoundIndex] = FCollectionRef(CollectionContainer.AsShared(), NewCollection);
|
|
|
|
OutNewContentSources = ContentSources;
|
|
OutNewContentSources.SetCollections(NewCollections);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(LegacyContentSourceWidgets->AssetViewPtr->GetContentSources(), NewContentSources))
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->SetContentSources(NewContentSources);
|
|
}
|
|
}
|
|
|
|
HistoryManager.RewriteHistoryData([&UpdateContentSources](FHistoryData& HistoryData)
|
|
{
|
|
FAssetViewContentSources NewContentSources;
|
|
if (UpdateContentSources(HistoryData.ContentSources, NewContentSources))
|
|
{
|
|
HistoryData.ContentSources = MoveTemp(NewContentSources);
|
|
}
|
|
});
|
|
}
|
|
|
|
void SContentBrowser::HandleCollectionUpdated(ICollectionContainer& CollectionContainer, const FCollectionNameType& Collection)
|
|
{
|
|
const FAssetViewContentSources& ContentSources = LegacyContentSourceWidgets->AssetViewPtr->GetContentSources();
|
|
|
|
// If we're currently viewing the dynamic collection that was updated, make sure our active filter text is up-to-date
|
|
if (ContentSources.IsDynamicCollection())
|
|
{
|
|
const FCollectionRef& DynamicCollection = ContentSources.GetCollections()[0];
|
|
if (DynamicCollection.Container.Get() == &CollectionContainer &&
|
|
DynamicCollection.Name == Collection.Name &&
|
|
DynamicCollection.Type == Collection.Type)
|
|
{
|
|
FString DynamicQueryString;
|
|
DynamicCollection.Container->GetDynamicQueryText(DynamicCollection.Name, DynamicCollection.Type, DynamicQueryString);
|
|
|
|
const FText DynamicQueryText = FText::FromString(DynamicQueryString);
|
|
SetSearchBoxText(DynamicQueryText);
|
|
LegacyContentSourceWidgets->SearchBoxPtr->SetText(DynamicQueryText);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::HandlePathRemoved(const FName Path)
|
|
{
|
|
HistoryManager.RemoveHistoryData([&Path](const FHistoryData& HistoryData)
|
|
{
|
|
return HistoryData.ContentSources.GetVirtualPaths().Num() == 1
|
|
&& HistoryData.ContentSources.GetVirtualPaths().Contains(Path);
|
|
});
|
|
}
|
|
|
|
void SContentBrowser::HandleItemDataUpdated(TArrayView<const FContentBrowserItemDataUpdate> InUpdatedItems)
|
|
{
|
|
for (const FContentBrowserItemDataUpdate& ItemDataUpdate : InUpdatedItems)
|
|
{
|
|
if (!ItemDataUpdate.GetItemData().IsFolder())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (ItemDataUpdate.GetUpdateType())
|
|
{
|
|
case EContentBrowserItemUpdateType::Moved:
|
|
HandlePathRemoved(ItemDataUpdate.GetPreviousVirtualPath());
|
|
break;
|
|
|
|
case EContentBrowserItemUpdateType::Removed:
|
|
HandlePathRemoved(ItemDataUpdate.GetItemData().GetVirtualPath());
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FText SContentBrowser::GetSearchAssetsHintText() const
|
|
{
|
|
if (LegacyContentSourceWidgets->PathViewPtr.IsValid())
|
|
{
|
|
TArray<FContentBrowserItem> Paths = LegacyContentSourceWidgets->PathViewPtr->GetSelectedFolderItems();
|
|
if (Paths.Num() > 0)
|
|
{
|
|
FString SearchHint = NSLOCTEXT( "ContentBrowser", "SearchBoxPartialHint", "Search" ).ToString();
|
|
SearchHint += TEXT(" ");
|
|
for(int32 i = 0; i < Paths.Num(); i++)
|
|
{
|
|
SearchHint += Paths[i].GetDisplayName().ToString();
|
|
|
|
if (i + 1 < Paths.Num())
|
|
{
|
|
SearchHint += ", ";
|
|
}
|
|
}
|
|
|
|
return FText::FromString(SearchHint);
|
|
}
|
|
}
|
|
|
|
return NSLOCTEXT( "ContentBrowser", "SearchBoxHint", "Search Assets" );
|
|
}
|
|
|
|
void ExtractAssetSearchFilterTerms(const FText& SearchText, FString* OutFilterKey, FString* OutFilterValue, int32* OutSuggestionInsertionIndex)
|
|
{
|
|
const FString SearchString = SearchText.ToString();
|
|
|
|
if (OutFilterKey)
|
|
{
|
|
OutFilterKey->Reset();
|
|
}
|
|
if (OutFilterValue)
|
|
{
|
|
OutFilterValue->Reset();
|
|
}
|
|
if (OutSuggestionInsertionIndex)
|
|
{
|
|
*OutSuggestionInsertionIndex = SearchString.Len();
|
|
}
|
|
|
|
// Build the search filter terms so that we can inspect the tokens
|
|
FTextFilterExpressionEvaluator LocalFilter(ETextFilterExpressionEvaluatorMode::Complex);
|
|
LocalFilter.SetFilterText(SearchText);
|
|
|
|
// Inspect the tokens to see what the last part of the search term was
|
|
// If it was a key->value pair then we'll use that to control what kinds of results we show
|
|
// For anything else we just use the text from the last token as our filter term to allow incremental auto-complete
|
|
const TArray<FExpressionToken>& FilterTokens = LocalFilter.GetFilterExpressionTokens();
|
|
if (FilterTokens.Num() > 0)
|
|
{
|
|
const FExpressionToken& LastToken = FilterTokens.Last();
|
|
|
|
// If the last token is a text token, then consider it as a value and walk back to see if we also have a key
|
|
if (LastToken.Node.Cast<TextFilterExpressionParser::FTextToken>())
|
|
{
|
|
if (OutFilterValue)
|
|
{
|
|
*OutFilterValue = LastToken.Context.GetString();
|
|
}
|
|
if (OutSuggestionInsertionIndex)
|
|
{
|
|
*OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, LastToken.Context.GetCharacterIndex());
|
|
}
|
|
|
|
if (FilterTokens.IsValidIndex(FilterTokens.Num() - 2))
|
|
{
|
|
const FExpressionToken& ComparisonToken = FilterTokens[FilterTokens.Num() - 2];
|
|
if (ComparisonToken.Node.Cast<TextFilterExpressionParser::FEqual>())
|
|
{
|
|
if (FilterTokens.IsValidIndex(FilterTokens.Num() - 3))
|
|
{
|
|
const FExpressionToken& KeyToken = FilterTokens[FilterTokens.Num() - 3];
|
|
if (KeyToken.Node.Cast<TextFilterExpressionParser::FTextToken>())
|
|
{
|
|
if (OutFilterKey)
|
|
{
|
|
*OutFilterKey = KeyToken.Context.GetString();
|
|
}
|
|
if (OutSuggestionInsertionIndex)
|
|
{
|
|
*OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, KeyToken.Context.GetCharacterIndex());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the last token is a comparison operator, then walk back and see if we have a key
|
|
else if (LastToken.Node.Cast<TextFilterExpressionParser::FEqual>())
|
|
{
|
|
if (FilterTokens.IsValidIndex(FilterTokens.Num() - 2))
|
|
{
|
|
const FExpressionToken& KeyToken = FilterTokens[FilterTokens.Num() - 2];
|
|
if (KeyToken.Node.Cast<TextFilterExpressionParser::FTextToken>())
|
|
{
|
|
if (OutFilterKey)
|
|
{
|
|
*OutFilterKey = KeyToken.Context.GetString();
|
|
}
|
|
if (OutSuggestionInsertionIndex)
|
|
{
|
|
*OutSuggestionInsertionIndex = FMath::Min(*OutSuggestionInsertionIndex, KeyToken.Context.GetCharacterIndex());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OnAssetSearchSuggestionFilter(const FText& SearchText, TArray<FAssetSearchBoxSuggestion>& PossibleSuggestions, FText& SuggestionHighlightText) const
|
|
{
|
|
// We don't bind the suggestion list, so this list should be empty as we populate it here based on the search term
|
|
check(PossibleSuggestions.Num() == 0);
|
|
|
|
FString FilterKey;
|
|
FString FilterValue;
|
|
ExtractAssetSearchFilterTerms(SearchText, &FilterKey, &FilterValue, nullptr);
|
|
|
|
auto PassesValueFilter = [&FilterValue](const FString& InOther)
|
|
{
|
|
return FilterValue.IsEmpty() || InOther.Contains(FilterValue);
|
|
};
|
|
|
|
auto SortPredicate = [](const FAssetSearchBoxSuggestion& A, const FAssetSearchBoxSuggestion& B)
|
|
{
|
|
return A.DisplayName.CompareTo(B.DisplayName) < 0;
|
|
};
|
|
|
|
if (FilterKey.IsEmpty() || (FilterKey == TEXT("Type") || FilterKey == TEXT("Class")))
|
|
{
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
TArray< TWeakPtr<IAssetTypeActions> > AssetTypeActionsList;
|
|
AssetToolsModule.Get().GetAssetTypeActionsList(AssetTypeActionsList);
|
|
|
|
const int32 StartIndex = PossibleSuggestions.Num();
|
|
const FText TypesCategoryName = NSLOCTEXT("ContentBrowser", "TypesCategoryName", "Types");
|
|
for (auto TypeActionsIt = AssetTypeActionsList.CreateConstIterator(); TypeActionsIt; ++TypeActionsIt)
|
|
{
|
|
if ((*TypeActionsIt).IsValid())
|
|
{
|
|
const TSharedPtr<IAssetTypeActions> TypeActions = (*TypeActionsIt).Pin();
|
|
if (TypeActions->GetSupportedClass())
|
|
{
|
|
const FString TypeName = TypeActions->GetSupportedClass()->GetName();
|
|
const FText TypeDisplayName = TypeActions->GetSupportedClass()->GetDisplayNameText();
|
|
FString TypeSuggestion = FString::Printf(TEXT("Type=%s"), *TypeName);
|
|
if (PassesValueFilter(TypeSuggestion))
|
|
{
|
|
PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ MoveTemp(TypeSuggestion), TypeDisplayName, TypesCategoryName });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Algo::Sort(TArrayView<FAssetSearchBoxSuggestion>(PossibleSuggestions.GetData() + StartIndex, PossibleSuggestions.Num() - StartIndex), SortPredicate);
|
|
}
|
|
|
|
if (FilterKey.IsEmpty() || (FilterKey == TEXT("Collection") || FilterKey == TEXT("Tag")))
|
|
{
|
|
ICollectionManager& CollectionManager = FCollectionManagerModule::GetModule().Get();
|
|
|
|
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
|
|
CollectionManager.GetVisibleCollectionContainers(CollectionContainers);
|
|
|
|
const int32 StartIndex = PossibleSuggestions.Num();
|
|
const FText CollectionsCategoryName = NSLOCTEXT("ContentBrowser", "CollectionsCategoryName", "Collections");
|
|
TArray<FCollectionNameType> AllCollections;
|
|
for (const TSharedPtr<ICollectionContainer>& CollectionContainer : CollectionContainers)
|
|
{
|
|
AllCollections.Reset();
|
|
CollectionContainer->GetCollections(AllCollections);
|
|
|
|
for (const FCollectionNameType& Collection : AllCollections)
|
|
{
|
|
FString CollectionName = Collection.Name.ToString();
|
|
FString CollectionSuggestion = FString::Printf(TEXT("Collection=%s"), *CollectionName);
|
|
if (PassesValueFilter(CollectionSuggestion))
|
|
{
|
|
PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ MoveTemp(CollectionSuggestion), FText::FromString(MoveTemp(CollectionName)), CollectionsCategoryName });
|
|
}
|
|
}
|
|
}
|
|
|
|
Algo::Sort(TArrayView<FAssetSearchBoxSuggestion>(PossibleSuggestions.GetData() + StartIndex, PossibleSuggestions.Num() - StartIndex), SortPredicate);
|
|
|
|
// Remove duplicate collection names (either from different containers or types).
|
|
PossibleSuggestions.SetNum(StartIndex + Algo::Unique(
|
|
TArrayView<FAssetSearchBoxSuggestion>(PossibleSuggestions.GetData() + StartIndex, PossibleSuggestions.Num() - StartIndex),
|
|
[](const FAssetSearchBoxSuggestion& A, const FAssetSearchBoxSuggestion& B)
|
|
{
|
|
return A.SuggestionString.Compare(B.SuggestionString) == 0;
|
|
}));
|
|
}
|
|
|
|
if (FilterKey.IsEmpty())
|
|
{
|
|
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
|
|
|
const int32 StartIndex = PossibleSuggestions.Num();
|
|
const FText MetaDataCategoryName = NSLOCTEXT("ContentBrowser", "MetaDataCategoryName", "Meta-Data");
|
|
FString TagNameStr;
|
|
AssetRegistry.ReadLockEnumerateAllTagToAssetDatas(
|
|
[&PassesValueFilter, &PossibleSuggestions, &MetaDataCategoryName, &TagNameStr](FName TagName, IAssetRegistry::FEnumerateAssetDatasFunc EnumerateAssets)
|
|
{
|
|
TagName.ToString(TagNameStr);
|
|
if (PassesValueFilter(TagNameStr))
|
|
{
|
|
PossibleSuggestions.Add(FAssetSearchBoxSuggestion{ TagNameStr, FText::FromString(TagNameStr), MetaDataCategoryName });
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
Algo::Sort(TArrayView<FAssetSearchBoxSuggestion>(PossibleSuggestions.GetData() + StartIndex, PossibleSuggestions.Num() - StartIndex), SortPredicate);
|
|
}
|
|
|
|
SuggestionHighlightText = FText::FromString(FilterValue);
|
|
}
|
|
|
|
FText SContentBrowser::OnAssetSearchSuggestionChosen(const FText& SearchText, const FString& Suggestion) const
|
|
{
|
|
int32 SuggestionInsertionIndex = 0;
|
|
ExtractAssetSearchFilterTerms(SearchText, nullptr, nullptr, &SuggestionInsertionIndex);
|
|
|
|
FString SearchString = SearchText.ToString();
|
|
SearchString.RemoveAt(SuggestionInsertionIndex, SearchString.Len() - SuggestionInsertionIndex, EAllowShrinking::No);
|
|
SearchString.Append(Suggestion);
|
|
|
|
return FText::FromString(SearchString);
|
|
}
|
|
|
|
TSharedPtr<SWidget> SContentBrowser::GetItemContextMenu(TArrayView<const FContentBrowserItem> SelectedItems, EContentBrowserViewContext ViewContext)
|
|
{
|
|
// We may only open the file or folder context menu (folder takes priority), so see whether we have any folders selected
|
|
TArray<FContentBrowserItem> SelectedFolders;
|
|
for (const FContentBrowserItem& SelectedItem : SelectedItems)
|
|
{
|
|
if (SelectedItem.IsFolder())
|
|
{
|
|
SelectedFolders.Add(SelectedItem);
|
|
}
|
|
}
|
|
|
|
const bool bIsControlDown = FSlateApplication::Get().GetModifierKeys().IsControlDown();
|
|
const bool bIsAssetViewContext = ViewContext == EContentBrowserViewContext::AssetView;
|
|
const bool bShouldForceAddMenu = bIsControlDown && bIsAssetViewContext;
|
|
|
|
if (SelectedFolders.Num() > 0 && !bShouldForceAddMenu)
|
|
{
|
|
// Folders selected - show the folder menu
|
|
|
|
// Clear any selection in the asset view, as it'll conflict with other view info
|
|
// This is important for determining which context menu may be open based on the asset selection for rename/delete operations
|
|
if (ViewContext != EContentBrowserViewContext::AssetView)
|
|
{
|
|
LegacyContentSourceWidgets->AssetViewPtr->ClearSelection();
|
|
}
|
|
|
|
// Ensure the path context menu has the up-to-date list of paths being worked on
|
|
LegacyContentSourceWidgets->PathContextMenu->SetSelectedFolders(SelectedFolders);
|
|
|
|
TArray<FString> SelectedPackagePaths;
|
|
bool bPhysicalPathExists = false;
|
|
for (const FContentBrowserItem& SelectedFolder : SelectedFolders)
|
|
{
|
|
FName PackagePath;
|
|
if (SelectedFolder.Legacy_TryGetPackagePath(PackagePath))
|
|
{
|
|
SelectedPackagePaths.Add(PackagePath.ToString());
|
|
|
|
if (!bPhysicalPathExists)
|
|
{
|
|
FString PhysicalPath;
|
|
if (SelectedFolder.GetItemPhysicalPath(PhysicalPath) && FPaths::DirectoryExists(PhysicalPath))
|
|
{
|
|
bPhysicalPathExists = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FExtender> Extender;
|
|
if (SelectedPackagePaths.Num() > 0)
|
|
{
|
|
Extender = GetPathContextMenuExtender(SelectedPackagePaths);
|
|
}
|
|
|
|
UContentBrowserFolderContext* Context = NewObject<UContentBrowserFolderContext>();
|
|
Context->ContentBrowser = SharedThis(this);
|
|
// Note: This always uses the path view to manage the temporary folder item, even if the context menu came from the favorites view, as the favorites view can't make folders correctly
|
|
Context->OnCreateNewFolder = ViewContext == EContentBrowserViewContext::AssetView
|
|
? FOnCreateNewFolder::CreateSP(LegacyContentSourceWidgets->AssetViewPtr.Get(), &SAssetView::NewFolderItemRequested)
|
|
: FOnCreateNewFolder::CreateSP(LegacyContentSourceWidgets->PathViewPtr.Get(), &SPathView::NewFolderItemRequested);
|
|
|
|
ContentBrowserUtils::CountPathTypes(SelectedPackagePaths, Context->NumAssetPaths, Context->NumClassPaths);
|
|
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
Context->bCanBeModified = AssetToolsModule.Get().AllPassWritableFolderFilter(SelectedPackagePaths);
|
|
|
|
if (SelectedPackagePaths.Num() == 0)
|
|
{
|
|
Context->bNoFolderOnDisk = true;
|
|
Context->bCanBeModified = false;
|
|
}
|
|
|
|
if (!bPhysicalPathExists)
|
|
{
|
|
Context->bNoFolderOnDisk = true;
|
|
}
|
|
|
|
FToolMenuContext MenuContext(Commands, Extender, Context);
|
|
|
|
{
|
|
UContentBrowserDataMenuContext_FolderMenu* DataContextObject = NewObject<UContentBrowserDataMenuContext_FolderMenu>();
|
|
// Include the items that are not folders to help the batch operations operate on these also.
|
|
DataContextObject->SelectedItems = SelectedItems;
|
|
DataContextObject->bCanBeModified = Context->bCanBeModified;
|
|
DataContextObject->ParentWidget = ViewContext == EContentBrowserViewContext::AssetView
|
|
? TSharedPtr<SWidget>(LegacyContentSourceWidgets->AssetViewPtr)
|
|
: ViewContext == EContentBrowserViewContext::FavoriteView
|
|
? TSharedPtr<SWidget>(LegacyContentSourceWidgets->FavoritePathViewPtr)
|
|
: TSharedPtr<SWidget>(LegacyContentSourceWidgets->PathViewPtr);
|
|
|
|
MenuContext.AddObject(DataContextObject);
|
|
}
|
|
|
|
{
|
|
TArray<FName> SelectedVirtualPaths;
|
|
for (const FContentBrowserItem& SelectedFolder : SelectedFolders)
|
|
{
|
|
SelectedVirtualPaths.Add(SelectedFolder.GetVirtualPath());
|
|
}
|
|
AppendNewMenuContextObjects(EContentBrowserDataMenuContext_AddNewMenuDomain::PathView, SelectedVirtualPaths, MenuContext, nullptr, Context->bCanBeModified);
|
|
}
|
|
|
|
Context->SelectedPackagePaths = MoveTemp(SelectedPackagePaths);
|
|
return UToolMenus::Get()->GenerateWidget("ContentBrowser.FolderContextMenu", MenuContext);
|
|
}
|
|
else if (SelectedItems.Num() > 0 && !bShouldForceAddMenu)
|
|
{
|
|
// Files selected - show the file menu
|
|
checkf(ViewContext == EContentBrowserViewContext::AssetView, TEXT("File items were passed from a path view!"));
|
|
return LegacyContentSourceWidgets->AssetContextMenu->MakeContextMenu(SelectedItems, LegacyContentSourceWidgets->AssetViewPtr->GetContentSources(), Commands);
|
|
}
|
|
else if (ViewContext == EContentBrowserViewContext::AssetView || bShouldForceAddMenu)
|
|
{
|
|
// Nothing selected - show the new asset menu
|
|
return MakeAddNewContextMenu(EContentBrowserDataMenuContext_AddNewMenuDomain::AssetView, nullptr);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SContentBrowser::PopulateFolderContextMenu(UToolMenu* Menu)
|
|
{
|
|
UContentBrowserFolderContext* Context = Menu->FindContext<UContentBrowserFolderContext>();
|
|
check(Context);
|
|
|
|
const TArray<FContentBrowserItem>& SelectedFolders = LegacyContentSourceWidgets->PathContextMenu->GetSelectedFolders();
|
|
|
|
// We can only create folders when we have a single path selected
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
const bool bCanCreateNewFolder = SelectedFolders.Num() == 1 && ContentBrowserData->CanCreateFolder(SelectedFolders[0].GetVirtualPath(), nullptr);
|
|
|
|
FText NewFolderToolTip;
|
|
if(SelectedFolders.Num() == 1)
|
|
{
|
|
if(bCanCreateNewFolder)
|
|
{
|
|
NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_CreateIn", "Create a new folder in {0}."), FText::FromName(SelectedFolders[0].GetVirtualPath()));
|
|
}
|
|
else
|
|
{
|
|
NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_InvalidPath", "Cannot create new folders in {0}."), FText::FromName(SelectedFolders[0].GetVirtualPath()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NewFolderToolTip = LOCTEXT("NewFolderTooltip_InvalidNumberOfPaths", "Can only create folders when there is a single path selected.");
|
|
}
|
|
|
|
{
|
|
FToolMenuSection& Section = Menu->AddSection("Section");
|
|
|
|
if (Context->bCanBeModified)
|
|
{
|
|
// New Folder
|
|
Section.AddMenuEntry(
|
|
"NewFolder",
|
|
LOCTEXT("NewFolder", "New Folder"),
|
|
NewFolderToolTip,
|
|
FSlateIcon(UE::ContentBrowser::Private::FContentBrowserStyle::Get().GetStyleSetName(), "ContentBrowser.NewFolderIcon"),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SContentBrowser::CreateNewFolder, SelectedFolders.Num() > 0 ? SelectedFolders[0].GetVirtualPath().ToString() : FString(), Context->OnCreateNewFolder),
|
|
FCanExecuteAction::CreateLambda([bCanCreateNewFolder] { return bCanCreateNewFolder; })
|
|
)
|
|
);
|
|
}
|
|
|
|
Section.AddMenuEntry(
|
|
"FolderContext",
|
|
LOCTEXT("ShowInNewContentBrowser", "Show in New Content Browser"),
|
|
LOCTEXT("ShowInNewContentBrowserTooltip", "Opens a new Content Browser at this folder location (at least 1 Content Browser window needs to be locked)"),
|
|
FSlateIcon(UE::ContentBrowser::Private::FContentBrowserStyle::Get().GetStyleSetName(), "ContentBrowser.TabIcon"),
|
|
FUIAction(FExecuteAction::CreateSP(this, &SContentBrowser::OpenNewContentBrowser))
|
|
);
|
|
}
|
|
|
|
LegacyContentSourceWidgets->PathContextMenu->MakePathViewContextMenu(Menu);
|
|
}
|
|
|
|
void SContentBrowser::CreateNewFolder(FString FolderPath, FOnCreateNewFolder InOnCreateNewFolder)
|
|
{
|
|
const FText DefaultFolderBaseName = LOCTEXT("DefaultFolderName", "NewFolder");
|
|
UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem();
|
|
|
|
// Create a valid base name for this folder
|
|
FString DefaultFolderName = DefaultFolderBaseName.ToString();
|
|
int32 NewFolderPostfix = 0;
|
|
FName CombinedPathName;
|
|
for (;;)
|
|
{
|
|
FString CombinedPathNameStr = FolderPath / DefaultFolderName;
|
|
if (NewFolderPostfix > 0)
|
|
{
|
|
CombinedPathNameStr.AppendInt(NewFolderPostfix);
|
|
}
|
|
++NewFolderPostfix;
|
|
|
|
CombinedPathName = *CombinedPathNameStr;
|
|
|
|
const FContentBrowserItem ExistingFolder = ContentBrowserData->GetItemAtPath(CombinedPathName, EContentBrowserItemTypeFilter::IncludeFolders);
|
|
if (!ExistingFolder.IsValid())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
const FContentBrowserItemTemporaryContext NewFolderItem = ContentBrowserData->CreateFolder(CombinedPathName);
|
|
if (NewFolderItem.IsValid())
|
|
{
|
|
InOnCreateNewFolder.ExecuteIfBound(NewFolderItem);
|
|
}
|
|
}
|
|
|
|
void SContentBrowser::OpenNewContentBrowser()
|
|
{
|
|
const TArray<FContentBrowserItem> SelectedFolders = LegacyContentSourceWidgets->PathContextMenu->GetSelectedFolders();
|
|
FContentBrowserSingleton::Get().SyncBrowserToItems(SelectedFolders, false, true, NAME_None, true);
|
|
}
|
|
|
|
const FContentBrowserInstanceConfig* SContentBrowser::GetConstInstanceConfig() const
|
|
{
|
|
return ContentBrowserUtils::GetConstInstanceConfig(InstanceName);
|
|
}
|
|
|
|
FContentBrowserInstanceConfig* SContentBrowser::GetMutableInstanceConfig()
|
|
{
|
|
if (InstanceName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UContentBrowserConfig* Config = UContentBrowserConfig::Get();
|
|
if (Config == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FContentBrowserInstanceConfig* InstanceConfig = Config->Instances.Find(InstanceName);
|
|
return InstanceConfig;
|
|
}
|
|
|
|
FContentBrowserInstanceConfig* SContentBrowser::CreateEditorConfigIfRequired()
|
|
{
|
|
UContentBrowserConfig* Config = UContentBrowserConfig::Get();
|
|
if (Config == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FContentBrowserInstanceConfig* InstanceConfig = Config->Instances.Find(InstanceName);
|
|
if (InstanceConfig != nullptr)
|
|
{
|
|
return InstanceConfig;
|
|
}
|
|
|
|
InstanceConfig = &Config->Instances.Add(InstanceName, FContentBrowserInstanceConfig());
|
|
|
|
const UContentBrowserSettings* Settings = GetDefault<UContentBrowserSettings>();
|
|
InstanceConfig->bShowEngineContent = Settings->GetDisplayEngineFolder();
|
|
InstanceConfig->bShowDeveloperContent = Settings->GetDisplayDevelopersFolder();
|
|
InstanceConfig->bShowLocalizedContent = Settings->GetDisplayL10NFolder();
|
|
InstanceConfig->bShowPluginContent = Settings->GetDisplayPluginFolders();
|
|
InstanceConfig->bShowFolders = Settings->DisplayFolders;
|
|
InstanceConfig->bShowEmptyFolders = Settings->DisplayEmptyFolders;
|
|
InstanceConfig->bShowCppFolders = Settings->GetDisplayCppFolders();
|
|
InstanceConfig->bFavoritesExpanded = Settings->GetDisplayFavorites();
|
|
InstanceConfig->bSearchAssetPaths = Settings->GetIncludeAssetPaths();
|
|
InstanceConfig->bSearchClasses = Settings->GetIncludeClassNames();
|
|
InstanceConfig->bSearchCollections = Settings->GetIncludeCollectionNames();
|
|
InstanceConfig->bFilterRecursively = Settings->FilterRecursively;
|
|
|
|
UContentBrowserConfig::Get()->SaveEditorConfig();
|
|
|
|
return InstanceConfig;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|