1314 lines
42 KiB
C++
1314 lines
42 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ContentBrowserDataSubsystem.h"
|
|
|
|
#include "Containers/StringView.h"
|
|
#include "Containers/Ticker.h"
|
|
#include "ContentBrowserDataSource.h"
|
|
#include "ContentBrowserItemPath.h"
|
|
#include "Editor.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "GetOrEnumerateSink.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "IContentBrowserDataModule.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
#include "Internationalization/Text.h"
|
|
#include "Logging/LogCategory.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/EnumClassFlags.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/PathViews.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "PluginDescriptor.h"
|
|
#include "Settings/ContentBrowserSettings.h"
|
|
#include "Stats/Stats.h"
|
|
#include "String/RemoveFrom.h"
|
|
#include "Templates/Function.h"
|
|
#include "Templates/Less.h"
|
|
#include "Templates/SharedPointer.h"
|
|
#include "Templates/Tuple.h"
|
|
#include "Templates/UnrealTemplate.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/GarbageCollection.h"
|
|
#include "UObject/UObjectThreadContext.h"
|
|
|
|
class FSubsystemCollectionBase;
|
|
class UObject;
|
|
struct FAssetData;
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogContentBrowserDataSubsystem, Log, All);
|
|
|
|
namespace ContentBrowserDataSubsystem
|
|
{
|
|
FAutoConsoleCommand CVarContentBrowserDebug_TryConvertVirtualPath = FAutoConsoleCommand(
|
|
TEXT("ContentBrowser.Debug.TryConvertVirtualPath"),
|
|
TEXT("Try to convert virtual path"),
|
|
FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args)
|
|
{
|
|
if (Args.Num() > 0)
|
|
{
|
|
const FString& VirtualPath = Args[0];
|
|
FNameBuilder ConvertedPath;
|
|
EContentBrowserPathType PathType = IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(VirtualPath, ConvertedPath);
|
|
UE_LOG(LogContentBrowserDataSubsystem, Log, TEXT("InputVirtualPath: %s, ConvertedPath: %s, ConvertedPathType: %s"), *VirtualPath, *ConvertedPath, *UEnum::GetValueAsString(PathType));
|
|
}
|
|
}
|
|
));
|
|
|
|
FAutoConsoleCommand CVarContentBrowserDebug_ConvertInternalPathToVirtual = FAutoConsoleCommand(
|
|
TEXT("ContentBrowser.Debug.ConvertInternalPathToVirtual"),
|
|
TEXT("Convert internal path"),
|
|
FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args)
|
|
{
|
|
if (Args.Num() > 0)
|
|
{
|
|
const FString& InternalPath = Args[0];
|
|
FNameBuilder ConvertedPath;
|
|
IContentBrowserDataModule::Get().GetSubsystem()->ConvertInternalPathToVirtual(InternalPath, ConvertedPath);
|
|
UE_LOG(LogContentBrowserDataSubsystem, Log, TEXT("InputInternalPath: %s, ConvertedVirtualPath: %s"), *InternalPath, *ConvertedPath);
|
|
}
|
|
}
|
|
));
|
|
|
|
class FDefaultHideFolderIfEmptyFilter : public IContentBrowserHideFolderIfEmptyFilter
|
|
{
|
|
public:
|
|
FDefaultHideFolderIfEmptyFilter()
|
|
: GameDevelopersPath(FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()))
|
|
{
|
|
}
|
|
|
|
bool HideFolderIfEmpty(FName Path, FStringView PathString) const override
|
|
{
|
|
static const FName PublicCollectionsPath("/Game/Collections");
|
|
|
|
// Hide public collection folder.
|
|
if (Path == PublicCollectionsPath)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Hide private collection folders.
|
|
if (PathString.StartsWith(GameDevelopersPath))
|
|
{
|
|
for (int32 Index = GameDevelopersPath.Len(); Index < PathString.Len(); ++Index)
|
|
{
|
|
// Scan past the developer name in /Game/Developers/<developer>/Collections.
|
|
if (PathString[Index] == TEXT('/'))
|
|
{
|
|
return PathString.RightChop(Index + 1).Equals(TEXT("Collections"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
const FString GameDevelopersPath;
|
|
};
|
|
|
|
class FMergedHideFolderIfEmptyFilter : public IContentBrowserHideFolderIfEmptyFilter
|
|
{
|
|
public:
|
|
FMergedHideFolderIfEmptyFilter(TArray<TSharedPtr<IContentBrowserHideFolderIfEmptyFilter>>&& InHideFolderIfEmptyFilters)
|
|
: HideFolderIfEmptyFilters(MoveTemp(InHideFolderIfEmptyFilters))
|
|
{
|
|
}
|
|
|
|
bool HideFolderIfEmpty(FName Path, FStringView PathString) const override
|
|
{
|
|
for (const TSharedPtr<IContentBrowserHideFolderIfEmptyFilter>& HideFolderIfEmptyFilter : HideFolderIfEmptyFilters)
|
|
{
|
|
if (HideFolderIfEmptyFilter->HideFolderIfEmpty(Path, PathString))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
const TArray<TSharedPtr<IContentBrowserHideFolderIfEmptyFilter>> HideFolderIfEmptyFilters;
|
|
};
|
|
}
|
|
|
|
UContentBrowserDataSubsystem::UContentBrowserDataSubsystem()
|
|
: EditableFolderPermissionList(MakeShared<FPathPermissionList>())
|
|
{
|
|
|
|
}
|
|
void UContentBrowserDataSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
AllFolderPrefix = TEXT("/All");
|
|
|
|
DefaultPathViewSpecialSortFolders.Reset();
|
|
DefaultPathViewSpecialSortFolders.Add(TEXT("/Game"));
|
|
DefaultPathViewSpecialSortFolders.Add(TEXT("/Plugins"));
|
|
DefaultPathViewSpecialSortFolders.Add(TEXT("/Engine"));
|
|
DefaultPathViewSpecialSortFolders.Add(TEXT("/EngineData"));
|
|
SetPathViewSpecialSortFolders(GetDefaultPathViewSpecialSortFolders());
|
|
|
|
IModularFeatures& ModularFeatures = IModularFeatures::Get();
|
|
|
|
{
|
|
const FName DataSourceFeatureName = UContentBrowserDataSource::GetModularFeatureTypeName();
|
|
|
|
const int32 AvailableDataSourcesCount = ModularFeatures.GetModularFeatureImplementationCount(DataSourceFeatureName);
|
|
for (int32 AvailableDataSourcesIndex = 0; AvailableDataSourcesIndex < AvailableDataSourcesCount; ++AvailableDataSourcesIndex)
|
|
{
|
|
HandleDataSourceRegistered(DataSourceFeatureName, ModularFeatures.GetModularFeatureImplementation(DataSourceFeatureName, AvailableDataSourcesIndex));
|
|
}
|
|
|
|
/**
|
|
* If any view already exist refresh them now instead of waiting.
|
|
* This avoid asking for the view that where just created to refresh their data next frame during the editor initialization.
|
|
*/
|
|
bPendingItemDataRefreshedNotification = false;
|
|
ItemDataRefreshedDelegate.Broadcast();
|
|
}
|
|
|
|
ModularFeatures.OnModularFeatureRegistered().AddUObject(this, &UContentBrowserDataSubsystem::HandleDataSourceRegistered);
|
|
ModularFeatures.OnModularFeatureUnregistered().AddUObject(this, &UContentBrowserDataSubsystem::HandleDataSourceUnregistered);
|
|
|
|
DefaultHideFolderIfEmptyFilter = MakeShared<ContentBrowserDataSubsystem::FDefaultHideFolderIfEmptyFilter>();
|
|
|
|
FEditorDelegates::BeginPIE.AddUObject(this, &UContentBrowserDataSubsystem::OnBeginPIE);
|
|
FEditorDelegates::EndPIE.AddUObject(this, &UContentBrowserDataSubsystem::OnEndPIE);
|
|
|
|
FPackageName::OnContentPathMounted().AddUObject(this, &UContentBrowserDataSubsystem::OnContentPathMounted);
|
|
|
|
// Tick during normal operation
|
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(TEXT("ContentBrowserData"), 0.1f, [this](const float InDeltaTime)
|
|
{
|
|
Tick(InDeltaTime);
|
|
return true;
|
|
});
|
|
|
|
// Tick during modal dialog operation
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
FSlateApplication::Get().GetOnModalLoopTickEvent().AddUObject(this, &UContentBrowserDataSubsystem::Tick);
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::Deinitialize()
|
|
{
|
|
IModularFeatures& ModularFeatures = IModularFeatures::Get();
|
|
ModularFeatures.OnModularFeatureRegistered().RemoveAll(this);
|
|
ModularFeatures.OnModularFeatureUnregistered().RemoveAll(this);
|
|
|
|
FEditorDelegates::BeginPIE.RemoveAll(this);
|
|
FEditorDelegates::EndPIE.RemoveAll(this);
|
|
|
|
FPackageName::OnContentPathMounted().RemoveAll(this);
|
|
|
|
DeactivateAllDataSources();
|
|
ActiveDataSources.Reset();
|
|
AvailableDataSources.Reset();
|
|
ActiveDataSourcesDiscoveringContent.Reset();
|
|
DefaultHideFolderIfEmptyFilter.Reset();
|
|
|
|
if (TickHandle.IsValid())
|
|
{
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
|
TickHandle.Reset();
|
|
}
|
|
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
FSlateApplication::Get().GetOnModalLoopTickEvent().RemoveAll(this);
|
|
}
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::ActivateDataSource(const FName Name)
|
|
{
|
|
EnabledDataSources.AddUnique(Name);
|
|
|
|
if (!ActiveDataSources.Contains(Name))
|
|
{
|
|
if (UContentBrowserDataSource* DataSource = AvailableDataSources.FindRef(Name))
|
|
{
|
|
DataSource->SetDataSink(this);
|
|
ActiveDataSources.Add(Name, DataSource);
|
|
ActiveDataSourcesDiscoveringContent.Add(Name);
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
NotifyItemDataRefreshed();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// TODO: Log warning
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::DeactivateDataSource(const FName Name)
|
|
{
|
|
EnabledDataSources.Remove(Name);
|
|
|
|
if (UContentBrowserDataSource* DataSource = ActiveDataSources.FindRef(Name))
|
|
{
|
|
DataSource->SetDataSink(nullptr);
|
|
ActiveDataSources.Remove(Name);
|
|
ActiveDataSourcesDiscoveringContent.Remove(Name);
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
NotifyItemDataRefreshed();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::ActivateAllDataSources()
|
|
{
|
|
if (ActiveDataSources.Num() == AvailableDataSources.Num())
|
|
{
|
|
// Everything is already active - nothing to do
|
|
return;
|
|
}
|
|
|
|
ActiveDataSources = AvailableDataSources;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
ActiveDataSourcePair.Value->SetDataSink(this);
|
|
ActiveDataSourcesDiscoveringContent.Add(ActiveDataSourcePair.Key);
|
|
|
|
// Merge this array as it may contain sources that we've not yet discovered, so can't activate yet
|
|
EnabledDataSources.AddUnique(ActiveDataSourcePair.Key);
|
|
}
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
NotifyItemDataRefreshed();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::DeactivateAllDataSources()
|
|
{
|
|
if (ActiveDataSources.Num() == 0)
|
|
{
|
|
// Everything is already deactivated - nothing to do
|
|
return;
|
|
}
|
|
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
ActiveDataSourcePair.Value->SetDataSink(nullptr);
|
|
}
|
|
ActiveDataSources.Reset();
|
|
EnabledDataSources.Reset();
|
|
ActiveDataSourcesDiscoveringContent.Reset();
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
NotifyItemDataRefreshed();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
TArray<FName> UContentBrowserDataSubsystem::GetAvailableDataSources() const
|
|
{
|
|
TArray<FName> AvailableDataSourceNames;
|
|
AvailableDataSources.GenerateKeyArray(AvailableDataSourceNames);
|
|
return AvailableDataSourceNames;
|
|
}
|
|
|
|
TArray<FName> UContentBrowserDataSubsystem::GetActiveDataSources() const
|
|
{
|
|
TArray<FName> ActiveDataSourceNames;
|
|
ActiveDataSources.GenerateKeyArray(ActiveDataSourceNames);
|
|
return ActiveDataSourceNames;
|
|
}
|
|
|
|
FOnContentBrowserItemDataUpdated& UContentBrowserDataSubsystem::OnItemDataUpdated()
|
|
{
|
|
return ItemDataUpdatedDelegate;
|
|
}
|
|
|
|
FOnContentBrowserItemDataRefreshed& UContentBrowserDataSubsystem::OnItemDataRefreshed()
|
|
{
|
|
return ItemDataRefreshedDelegate;
|
|
}
|
|
|
|
FOnContentBrowserItemDataDiscoveryComplete& UContentBrowserDataSubsystem::OnItemDataDiscoveryComplete()
|
|
{
|
|
return ItemDataDiscoveryCompleteDelegate;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::CompileFilter(const FName InPath, const FContentBrowserDataFilter& InFilter, FContentBrowserDataCompiledFilter& OutCompiledFilter) const
|
|
{
|
|
OutCompiledFilter.ItemTypeFilter = InFilter.ItemTypeFilter;
|
|
OutCompiledFilter.ItemCategoryFilter = InFilter.ItemCategoryFilter;
|
|
OutCompiledFilter.ItemAttributeFilter = InFilter.ItemAttributeFilter;
|
|
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
FName ConvertedPath;
|
|
const EContentBrowserPathType ConvertedPathType = DataSource->TryConvertVirtualPath(InPath, ConvertedPath);
|
|
if (ConvertedPathType != EContentBrowserPathType::None)
|
|
{
|
|
// The requested path is managed by this data source, so compile the filter for it
|
|
DataSource->CompileFilter(InPath, InFilter, OutCompiledFilter);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsMatchingFilter(const FContentBrowserDataCompiledFilter& InFilter, TFunctionRef<bool(FContentBrowserItem&&)> InCallback) const
|
|
{
|
|
EnumerateItemsMatchingFilter(InFilter, [&InCallback](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
return InCallback(FContentBrowserItem(MoveTemp(InItemData)));
|
|
});
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsMatchingFilter(const FContentBrowserDataCompiledFilter& InFilter, TFunctionRef<bool(FContentBrowserItemData&&)> InCallback) const
|
|
{
|
|
EnumerateItemsMatchingFilter(InFilter, TGetOrEnumerateSink<FContentBrowserItemData>(InCallback));
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsMatchingFilter(const FContentBrowserDataCompiledFilter& InFilter, const TGetOrEnumerateSink<FContentBrowserItemData>& InSink) const
|
|
{
|
|
for (const TPair<FName, UContentBrowserDataSource*>& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
|
|
if (const FContentBrowserDataFilterList* FilterList = InFilter.CompiledFilters.Find(DataSource))
|
|
{
|
|
// Does data source have dummy paths down to its mount root that we also have to emit callbacks for?
|
|
if (const FContentBrowserCompiledSubsystemFilter* SubsystemFilter = FilterList->FindFilter<FContentBrowserCompiledSubsystemFilter>())
|
|
{
|
|
for (const FName& MountRootPart : SubsystemFilter->MountRootsToEnumerate)
|
|
{
|
|
check(EnumHasAnyFlags(InFilter.ItemTypeFilter, EContentBrowserItemTypeFilter::IncludeFolders));
|
|
|
|
const FString MountLeafName = FPackageName::GetShortName(MountRootPart);
|
|
FName InternalPath; // Virtual folders have no internal path
|
|
InSink.ProduceItem(FContentBrowserItemData(DataSource, EContentBrowserItemFlags::Type_Folder, MountRootPart, *MountLeafName, FText(), nullptr, InternalPath));
|
|
}
|
|
}
|
|
|
|
// Fully virtual folders are ones used purely for display purposes such as /All or /All/Plugins
|
|
if (const FContentBrowserCompiledVirtualFolderFilter* VirtualFolderFilter = FilterList->FindFilter<FContentBrowserCompiledVirtualFolderFilter>())
|
|
{
|
|
if (EnumHasAnyFlags(InFilter.ItemTypeFilter, EContentBrowserItemTypeFilter::IncludeFolders))
|
|
{
|
|
for (const auto& It : VirtualFolderFilter->CachedSubPaths)
|
|
{
|
|
// how do we skip over this item if not included (Engine Content, Engine Plugins, C++ Classes, etc..)
|
|
InSink.ProduceItem(FContentBrowserItemData(It.Value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DataSource->EnumerateItemsMatchingFilter(InFilter, InSink);
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsUnderPath(const FName InPath, const FContentBrowserDataFilter& InFilter, TFunctionRef<bool(FContentBrowserItem&&)> InCallback) const
|
|
{
|
|
EnumerateItemsUnderPath(InPath, InFilter, [&InCallback](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
return InCallback(FContentBrowserItem(MoveTemp(InItemData)));
|
|
});
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsUnderPath(const FName InPath, const FContentBrowserDataFilter& InFilter, TFunctionRef<bool(FContentBrowserItemData&&)> InCallback) const
|
|
{
|
|
FContentBrowserDataCompiledFilter CompiledFilter;
|
|
CompileFilter(InPath, InFilter, CompiledFilter);
|
|
EnumerateItemsMatchingFilter(CompiledFilter, MoveTemp(InCallback));
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsUnderPath(const FName InPath, const FContentBrowserDataFilter& InFilter, const TGetOrEnumerateSink<FContentBrowserItemData>& InSink) const
|
|
{
|
|
FContentBrowserDataCompiledFilter CompiledFilter;
|
|
CompileFilter(InPath, InFilter, CompiledFilter);
|
|
|
|
EnumerateItemsMatchingFilter(CompiledFilter, InSink);
|
|
}
|
|
|
|
TArray<FContentBrowserItem> UContentBrowserDataSubsystem::GetItemsUnderPath(const FName InPath, const FContentBrowserDataFilter& InFilter) const
|
|
{
|
|
TMap<FContentBrowserItemKey, FContentBrowserItem> FoundItems;
|
|
EnumerateItemsUnderPath(InPath, InFilter, [&FoundItems](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
|
|
const FContentBrowserItemKey ItemKey(InItemData);
|
|
if (FContentBrowserItem* FoundItem = FoundItems.Find(ItemKey))
|
|
{
|
|
FoundItem->Append(InItemData);
|
|
}
|
|
else
|
|
{
|
|
FoundItems.Add(ItemKey, FContentBrowserItem(MoveTemp(InItemData)));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
TArray<FContentBrowserItem> FoundItemsArray;
|
|
FoundItems.GenerateValueArray(FoundItemsArray);
|
|
FoundItemsArray.Sort([](const FContentBrowserItem& ItemOne, const FContentBrowserItem& ItemTwo)
|
|
{
|
|
return ItemOne.GetPrimaryInternalItem()->GetVirtualPath().Compare(ItemTwo.GetPrimaryInternalItem()->GetVirtualPath()) < 0;
|
|
});
|
|
return FoundItemsArray;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsAtPath(const FName InPath, const EContentBrowserItemTypeFilter InItemTypeFilter, TFunctionRef<bool(FContentBrowserItem&&)> InCallback) const
|
|
{
|
|
EnumerateItemsAtPath(InPath, InItemTypeFilter, [&InCallback](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
return InCallback(FContentBrowserItem(MoveTemp(InItemData)));
|
|
});
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::EnumerateItemsAtPath(const FName InPath, const EContentBrowserItemTypeFilter InItemTypeFilter, TFunctionRef<bool(FContentBrowserItemData&&)> InCallback) const
|
|
{
|
|
bool bHandledVirtualFolder = false;
|
|
for (const TPair<FName, UContentBrowserDataSource*>& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
FName InternalPath;
|
|
const EContentBrowserPathType ConvertedPathType = DataSource->TryConvertVirtualPath(InPath, InternalPath);
|
|
if (ConvertedPathType == EContentBrowserPathType::Internal)
|
|
{
|
|
DataSource->EnumerateItemsAtPath(InPath, InItemTypeFilter, InCallback);
|
|
}
|
|
else if (ConvertedPathType == EContentBrowserPathType::Virtual)
|
|
{
|
|
if (!bHandledVirtualFolder && EnumHasAnyFlags(InItemTypeFilter, EContentBrowserItemTypeFilter::IncludeFolders))
|
|
{
|
|
InCallback(DataSource->CreateVirtualFolderItem(InPath));
|
|
bHandledVirtualFolder = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::EnumerateItemsAtPaths(const TArrayView<struct FContentBrowserItemPath> InItemPaths, const EContentBrowserItemTypeFilter InItemTypeFilter, TFunctionRef<bool(FContentBrowserItemData&&)> InCallback) const
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (!DataSource->EnumerateItemsAtPaths(InItemPaths, InItemTypeFilter, InCallback))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::EnumerateItemsForObjects(TArrayView<UObject*> InObjects, TFunctionRef<bool(FContentBrowserItemData&&)> InCallback) const
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (!DataSource->EnumerateItemsForObjects(InObjects, InCallback))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TArray<FContentBrowserItem> UContentBrowserDataSubsystem::GetItemsAtPath(const FName InPath, const EContentBrowserItemTypeFilter InItemTypeFilter) const
|
|
{
|
|
TMap<FContentBrowserItemKey, FContentBrowserItem> FoundItems;
|
|
EnumerateItemsAtPath(InPath, InItemTypeFilter, [&FoundItems](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
|
|
FContentBrowserItem& FoundItem = FoundItems.FindOrAdd(FContentBrowserItemKey(InItemData));
|
|
FoundItem.Append(InItemData);
|
|
|
|
return true;
|
|
});
|
|
|
|
TArray<FContentBrowserItem> FoundItemsArray;
|
|
FoundItems.GenerateValueArray(FoundItemsArray);
|
|
return FoundItemsArray;
|
|
}
|
|
|
|
FContentBrowserItem UContentBrowserDataSubsystem::GetItemAtPath(const FName InPath, const EContentBrowserItemTypeFilter InItemTypeFilter) const
|
|
{
|
|
FContentBrowserItem FoundItem;
|
|
EnumerateItemsAtPath(InPath, InItemTypeFilter, [&FoundItem](FContentBrowserItemData&& InItemData)
|
|
{
|
|
checkf(InItemData.IsValid(), TEXT("Enumerated items must be valid!"));
|
|
|
|
if (FoundItem.IsValid())
|
|
{
|
|
if (FContentBrowserItemKey(FoundItem) == FContentBrowserItemKey(InItemData))
|
|
{
|
|
FoundItem.Append(InItemData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FoundItem = FContentBrowserItem(MoveTemp(InItemData));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return FoundItem;
|
|
}
|
|
|
|
TArray<FContentBrowserItemPath> UContentBrowserDataSubsystem::GetAliasesForPath(const FContentBrowserItemPath InPath) const
|
|
{
|
|
return GetAliasesForPath(InPath.GetInternalPathName());
|
|
}
|
|
|
|
TArray<FContentBrowserItemPath> UContentBrowserDataSubsystem::GetAliasesForPath(const FSoftObjectPath& InInternalPath) const
|
|
{
|
|
TArray<FContentBrowserItemPath> Aliases;
|
|
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
Aliases.Append(DataSource->GetAliasesForPath(InInternalPath));
|
|
}
|
|
|
|
return Aliases;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
TArray<FContentBrowserItemPath> UContentBrowserDataSubsystem::GetAliasesForPath(const FName InInternalPath) const
|
|
{
|
|
TArray<FContentBrowserItemPath> Aliases;
|
|
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
Aliases.Append(DataSource->GetAliasesForPath(InInternalPath));
|
|
}
|
|
|
|
return Aliases;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
bool UContentBrowserDataSubsystem::IsDiscoveringItems(TArray<FText>* OutStatus) const
|
|
{
|
|
bool bIsDiscoveringItems = false;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
FText DataSourceStatus;
|
|
if (ActiveDataSourcePair.Value->IsDiscoveringItems(&DataSourceStatus))
|
|
{
|
|
bIsDiscoveringItems = true;
|
|
if (OutStatus && !DataSourceStatus.IsEmpty())
|
|
{
|
|
OutStatus->Emplace(MoveTemp(DataSourceStatus));
|
|
}
|
|
}
|
|
}
|
|
return bIsDiscoveringItems;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::PrioritizeSearchPath(const FName InPath)
|
|
{
|
|
bool bDidPrioritize = false;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (DataSource->IsVirtualPathUnderMountRoot(InPath))
|
|
{
|
|
bDidPrioritize |= DataSource->PrioritizeSearchPath(InPath);
|
|
}
|
|
}
|
|
return bDidPrioritize;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::IsFolderVisible(const FName InPath, const EContentBrowserIsFolderVisibleFlags InFlags) const
|
|
{
|
|
FContentBrowserFolderContentsFilter ContentsFilter;
|
|
ContentsFilter.HideFolderIfEmptyFilter = CreateHideFolderIfEmptyFilter();
|
|
|
|
return IsFolderVisible(InPath, InFlags, ContentsFilter);
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::IsFolderVisible(const FName InPath, const EContentBrowserIsFolderVisibleFlags InFlags, const FContentBrowserFolderContentsFilter& InContentsFilter) const
|
|
{
|
|
bool bIsKnownPath = false;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (DataSource->IsVirtualPathUnderMountRoot(InPath))
|
|
{
|
|
bIsKnownPath = true;
|
|
if (DataSource->IsFolderVisible(InPath, InFlags, InContentsFilter))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return true if this is visible for any sources, or this path isn't handled by any of the sources
|
|
return !bIsKnownPath;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::IsFolderVisibleIfHidingEmpty(const FName InPath) const
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
return IsFolderVisible(InPath, EContentBrowserIsFolderVisibleFlags::Default | EContentBrowserIsFolderVisibleFlags::HideEmptyFolders);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::CanCreateFolder(const FName InPath, FText* OutErrorMsg) const
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (DataSource->IsVirtualPathUnderMountRoot(InPath))
|
|
{
|
|
if (DataSource->CanCreateFolder(InPath, OutErrorMsg))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FContentBrowserItemTemporaryContext UContentBrowserDataSubsystem::CreateFolder(const FName InPath) const
|
|
{
|
|
return CreateFolder(InPath, CreateHideFolderIfEmptyFilter());
|
|
}
|
|
|
|
FContentBrowserItemTemporaryContext UContentBrowserDataSubsystem::CreateFolder(const FName InPath, const TSharedPtr<IContentBrowserHideFolderIfEmptyFilter>& HideFolderIfEmptyFilter) const
|
|
{
|
|
FContentBrowserItemTemporaryContext NewItem;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
if (DataSource->IsVirtualPathUnderMountRoot(InPath))
|
|
{
|
|
FContentBrowserItemDataTemporaryContext NewItemData;
|
|
if (DataSource->CreateFolder(InPath, HideFolderIfEmptyFilter, NewItemData))
|
|
{
|
|
NewItem.AppendContext(MoveTemp(NewItemData));
|
|
}
|
|
}
|
|
}
|
|
return NewItem;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::Legacy_TryConvertPackagePathToVirtualPaths(const FName InPackagePath, TFunctionRef<bool(FName)> InCallback)
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
|
|
FName VirtualPath;
|
|
if (DataSource->Legacy_TryConvertPackagePathToVirtualPath(InPackagePath, VirtualPath))
|
|
{
|
|
if (!InCallback(VirtualPath))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::Legacy_TryConvertAssetDataToVirtualPaths(const FAssetData& InAssetData, const bool InUseFolderPaths, TFunctionRef<bool(FName)> InCallback)
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
|
|
FName VirtualPath;
|
|
if (DataSource->Legacy_TryConvertAssetDataToVirtualPath(InAssetData, InUseFolderPaths, VirtualPath))
|
|
{
|
|
if (!InCallback(VirtualPath))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::RefreshVirtualPathTreeIfNeeded()
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
DataSource->RefreshVirtualPathTreeIfNeeded();
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::SetVirtualPathTreeNeedsRebuild()
|
|
{
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
DataSource->SetVirtualPathTreeNeedsRebuild();
|
|
}
|
|
}
|
|
|
|
|
|
void UContentBrowserDataSubsystem::FContentBrowserFilterCacheApi::InitializeCacheIDOwner(UContentBrowserDataSubsystem& Subsystem, FContentBrowserDataFilterCacheIDOwner& IDOwner)
|
|
{
|
|
Subsystem.InitializeCacheIDOwner(IDOwner);
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::FContentBrowserFilterCacheApi::RemoveUnusedCachedData(const UContentBrowserDataSubsystem& Subsystem, const FContentBrowserDataFilterCacheIDOwner& IDOwner, TArrayView<const FName> InVirtualPathsInUse, const FContentBrowserDataFilter& DataFilter)
|
|
{
|
|
Subsystem.RemoveUnusedCachedFilterData(IDOwner, InVirtualPathsInUse, DataFilter);
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::FContentBrowserFilterCacheApi::ClearCachedData(const UContentBrowserDataSubsystem& Subsystem, const FContentBrowserDataFilterCacheIDOwner& IDOwner)
|
|
{
|
|
Subsystem.ClearCachedFilterData(IDOwner);
|
|
}
|
|
|
|
|
|
void UContentBrowserDataSubsystem::InitializeCacheIDOwner(FContentBrowserDataFilterCacheIDOwner& IDOwner)
|
|
{
|
|
++LastCacheIDForFilter;
|
|
if (LastCacheIDForFilter == INDEX_NONE)
|
|
{
|
|
++LastCacheIDForFilter;
|
|
}
|
|
|
|
IDOwner.ID = LastCacheIDForFilter;
|
|
IDOwner.DataSource = this;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::RemoveUnusedCachedFilterData(const FContentBrowserDataFilterCacheIDOwner& IDOwner, TArrayView<const FName> InVirtualPathsInUse, const FContentBrowserDataFilter& DataFilter) const
|
|
{
|
|
for (const TPair<FName, UContentBrowserDataSource*>& DataSource : AvailableDataSources)
|
|
{
|
|
DataSource.Value->RemoveUnusedCachedFilterData(IDOwner, InVirtualPathsInUse, DataFilter);
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::ClearCachedFilterData(const FContentBrowserDataFilterCacheIDOwner& IDOwner) const
|
|
{
|
|
for (const TPair<FName, UContentBrowserDataSource*>& DataSource : AvailableDataSources)
|
|
{
|
|
DataSource.Value->ClearCachedFilterData(IDOwner);
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::HandleDataSourceRegistered(const FName& Type, IModularFeature* Feature)
|
|
{
|
|
if (Type == UContentBrowserDataSource::GetModularFeatureTypeName())
|
|
{
|
|
UContentBrowserDataSource* DataSource = static_cast<UContentBrowserDataSource*>(Feature);
|
|
|
|
checkf(DataSource->IsInitialized(), TEXT("Data source '%s' was uninitialized! Did you forget to call Initialize?"), *DataSource->GetName());
|
|
|
|
AvailableDataSources.Add(DataSource->GetFName(), DataSource);
|
|
|
|
if (EnabledDataSources.Contains(DataSource->GetFName()))
|
|
{
|
|
ActivateDataSource(DataSource->GetFName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::HandleDataSourceUnregistered(const FName& Type, IModularFeature* Feature)
|
|
{
|
|
if (Type == UContentBrowserDataSource::GetModularFeatureTypeName())
|
|
{
|
|
UContentBrowserDataSource* DataSource = static_cast<UContentBrowserDataSource*>(Feature);
|
|
|
|
if (AvailableDataSources.Contains(DataSource->GetFName()))
|
|
{
|
|
DeactivateDataSource(DataSource->GetFName());
|
|
}
|
|
|
|
AvailableDataSources.Remove(DataSource->GetFName());
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::Tick(const float InDeltaTime)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UContentBrowserDataSubsystem_Tick);
|
|
|
|
if (GIsSavingPackage || IsGarbageCollecting() || FUObjectThreadContext::Get().IsRoutingPostLoad)
|
|
{
|
|
// Not to safe to Tick right now, as the below code may try and find objects
|
|
return;
|
|
}
|
|
|
|
if (TickSuppressionCount > 0)
|
|
{
|
|
// Not safe to Tick right now, as we've been asked not to
|
|
return;
|
|
}
|
|
|
|
if (bContentMountedThisFrame)
|
|
{
|
|
// Content just added, defer tick for a frame or we risk slowing down content load
|
|
bContentMountedThisFrame = false;
|
|
return;
|
|
}
|
|
|
|
for (const auto& AvailableDataSourcePair : AvailableDataSources)
|
|
{
|
|
AvailableDataSourcePair.Value->Tick(InDeltaTime);
|
|
}
|
|
|
|
if (bPendingItemDataRefreshedNotification)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UContentBrowserDataSubsystem::BroadcastItemDataRefreshed);
|
|
|
|
bPendingItemDataRefreshedNotification = false;
|
|
DelayedPendingUpdates.Empty();
|
|
PendingUpdates.Empty();
|
|
ItemDataRefreshedDelegate.Broadcast();
|
|
}
|
|
|
|
if (PendingUpdates.Num() > 0)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UContentBrowserDataSubsystem::BroadCastItemDataUpdate);
|
|
|
|
TArray<FContentBrowserItemDataUpdate> LocalPendingUpdates = MoveTemp(PendingUpdates);
|
|
PendingUpdates.Empty();
|
|
ItemDataUpdatedDelegate.Broadcast(MakeArrayView(LocalPendingUpdates));
|
|
}
|
|
|
|
if (ActiveDataSourcesDiscoveringContent.Num() > 0)
|
|
{
|
|
for (auto It = ActiveDataSourcesDiscoveringContent.CreateIterator(); It; ++It)
|
|
{
|
|
if (UContentBrowserDataSource* DataSource = ActiveDataSources.FindRef(*It))
|
|
{
|
|
// Has this source finished its content discovery?
|
|
if (!DataSource->IsDiscoveringItems())
|
|
{
|
|
It.RemoveCurrent();
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Source no longer active - just remove this entry
|
|
It.RemoveCurrent();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ActiveDataSourcesDiscoveringContent.Num() == 0)
|
|
{
|
|
ItemDataDiscoveryCompleteDelegate.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::OnContentPathMounted(const FString& AssetPath, const FString& ContentPath)
|
|
{
|
|
bContentMountedThisFrame = true;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::QueueItemDataUpdate(FContentBrowserItemDataUpdate&& InUpdate)
|
|
{
|
|
if (!AllowModifiedItemDataUpdates())
|
|
{
|
|
const EContentBrowserItemUpdateType UpdateType = InUpdate.GetUpdateType();
|
|
|
|
// Ignore modified during PIE to reduce hitches, they will be queue and then added to the pending updates when PIE stops
|
|
if (UpdateType == EContentBrowserItemUpdateType::Modified)
|
|
{
|
|
FContentBrowserItemKey ItemKey(InUpdate.GetItemData());
|
|
DelayedPendingUpdates.Add(MoveTemp(ItemKey), MoveTemp(InUpdate));
|
|
}
|
|
else
|
|
{
|
|
// Clear the delayed update for the item if there was one
|
|
if (UpdateType == EContentBrowserItemUpdateType::Moved)
|
|
{
|
|
const FContentBrowserItemData& ItemData = InUpdate.GetItemData();
|
|
FContentBrowserItemKey ItemKey(ItemData.GetItemType(), InUpdate.GetPreviousVirtualPath(), ItemData.GetOwnerDataSource());
|
|
DelayedPendingUpdates.Remove(ItemKey);
|
|
}
|
|
else
|
|
{
|
|
FContentBrowserItemKey ItemKey(InUpdate.GetItemData());
|
|
DelayedPendingUpdates.Remove(ItemKey);
|
|
}
|
|
|
|
// TODO: Merge multiple Modified updates for a single item?
|
|
PendingUpdates.Emplace(MoveTemp(InUpdate));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: Merge multiple Modified updates for a single item?
|
|
PendingUpdates.Emplace(MoveTemp(InUpdate));
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::NotifyItemDataRefreshed()
|
|
{
|
|
bPendingItemDataRefreshedNotification = true;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::AllowModifiedItemDataUpdates() const
|
|
{
|
|
return !bIsPIEActive;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::OnBeginPIE(const bool bIsSimulating)
|
|
{
|
|
bIsPIEActive = true;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::OnEndPIE(const bool bIsSimulating)
|
|
{
|
|
bIsPIEActive = false;
|
|
|
|
if (!DelayedPendingUpdates.IsEmpty())
|
|
{
|
|
// Move the DelayedPendingUpdates into the PendingUpdates.
|
|
PendingUpdates.Reserve(DelayedPendingUpdates.Num() + PendingUpdates.Num());
|
|
|
|
for (TPair<FContentBrowserItemKey, FContentBrowserItemDataUpdate>& Pair : DelayedPendingUpdates)
|
|
{
|
|
PendingUpdates.Add(MoveTemp(Pair.Value));
|
|
}
|
|
|
|
DelayedPendingUpdates.Empty();
|
|
}
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::SetPathViewSpecialSortFolders(const TArray<FName>& InSpecialSortFolders)
|
|
{
|
|
PathViewSpecialSortFolders = InSpecialSortFolders;
|
|
}
|
|
|
|
const TArray<FName>& UContentBrowserDataSubsystem::GetDefaultPathViewSpecialSortFolders() const
|
|
{
|
|
return DefaultPathViewSpecialSortFolders;
|
|
}
|
|
|
|
const TArray<FName>& UContentBrowserDataSubsystem::GetPathViewSpecialSortFolders() const
|
|
{
|
|
return PathViewSpecialSortFolders;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::ConvertInternalPathToVirtual(const FStringView InPath, FStringBuilderBase& OutPath)
|
|
{
|
|
OutPath.Reset();
|
|
|
|
if (GetDefault<UContentBrowserSettings>()->bShowAllFolder)
|
|
{
|
|
OutPath.Append(AllFolderPrefix);
|
|
if (InPath.Len() == 1 && InPath.Equals(TEXT("/")))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
TOptional<FStringView> MountPointOptional;
|
|
auto GetMountPoint = [&MountPointOptional, InPath]()
|
|
{
|
|
if (!MountPointOptional.IsSet())
|
|
{
|
|
MountPointOptional = FPathViews::GetMountPointNameFromPath(InPath);
|
|
}
|
|
return MountPointOptional.GetValue();
|
|
};
|
|
|
|
TOptional<TSharedPtr<IPlugin>> PluginOptional;
|
|
auto GetPlugin = [&PluginOptional, GetMountPoint]()
|
|
{
|
|
if (!PluginOptional.IsSet())
|
|
{
|
|
PluginOptional = IPluginManager::Get().FindPlugin(GetMountPoint());
|
|
}
|
|
return PluginOptional.GetValue();
|
|
};
|
|
|
|
if (UsePluginVersePathDelegate.IsBound())
|
|
{
|
|
if (const TSharedPtr<IPlugin> Plugin = GetPlugin())
|
|
{
|
|
if (UsePluginVersePath(Plugin.ToSharedRef()))
|
|
{
|
|
#if 0
|
|
// @fixme The semantically correct thing to do would be this:
|
|
OutPath.Append(FPathViews::GetPath(UE::String::RemoveFromEnd(FStringView(Plugin->GetVersePath()), TEXTVIEW("/"))));
|
|
#else
|
|
// However we have an issue with multi-plugin projects to solve first
|
|
// The root module/plugin uses the project Verse path
|
|
// Non-root modules are "faking" their Verse path this way: /owner@domain.com/project/module
|
|
// It's semantically invalid in Verse because that module isn't actually a sub-module of the root module
|
|
// This creates problems in the content browser: the root plugin being an actual folder, the virtual path
|
|
// of other plugins cannot be start with it (i.e. we can't mix real folder hierarchies with virtual path hierarchies)
|
|
// Another reason to fix this is to support "namespaces" in a project Verse paths such as:
|
|
// /owner@domain/purely/organizational/structure/project
|
|
OutPath.AppendChar(TCHAR('/'));
|
|
OutPath.Append(FPathViews::GetMountPointNameFromPath(FStringView(Plugin->GetVersePath())));
|
|
#endif
|
|
OutPath.Append(InPath.GetData(), InPath.Len());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetDefault<UContentBrowserSettings>()->bOrganizeFolders && InPath.Len() > 1)
|
|
{
|
|
if (GenerateVirtualPathPrefixDelegate.IsBound())
|
|
{
|
|
GenerateVirtualPathPrefixDelegate.Execute(InPath, OutPath);
|
|
}
|
|
else
|
|
{
|
|
if (TSharedPtr<IPlugin> Plugin = GetPlugin())
|
|
{
|
|
if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Engine)
|
|
{
|
|
OutPath.Append(TEXT("/EngineData/Plugins"));
|
|
}
|
|
else
|
|
{
|
|
OutPath.Append(TEXT("/Plugins"));
|
|
}
|
|
|
|
const FPluginDescriptor& PluginDescriptor = Plugin->GetDescriptor();
|
|
if (!PluginDescriptor.EditorCustomVirtualPath.IsEmpty())
|
|
{
|
|
int32 NumChars = PluginDescriptor.EditorCustomVirtualPath.Len();
|
|
if (PluginDescriptor.EditorCustomVirtualPath.EndsWith(TEXT("/")))
|
|
{
|
|
--NumChars;
|
|
}
|
|
|
|
if (NumChars > 0)
|
|
{
|
|
if (!PluginDescriptor.EditorCustomVirtualPath.StartsWith(TEXT("/")))
|
|
{
|
|
OutPath.Append(TEXT("/"));
|
|
}
|
|
|
|
OutPath.Append(*PluginDescriptor.EditorCustomVirtualPath, NumChars);
|
|
}
|
|
}
|
|
}
|
|
else if (GetMountPoint().Equals(TEXT("Engine")))
|
|
{
|
|
OutPath.Append(TEXT("/EngineData"));
|
|
}
|
|
}
|
|
}
|
|
|
|
OutPath.Append(InPath.GetData(), InPath.Len());
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::ConvertInternalPathToVirtual(const FStringView InPath, FName& OutPath)
|
|
{
|
|
FNameBuilder PathBuffer;
|
|
ConvertInternalPathToVirtual(InPath, PathBuffer);
|
|
OutPath = FName(PathBuffer);
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::ConvertInternalPathToVirtual(FName InPath, FName& OutPath)
|
|
{
|
|
ConvertInternalPathToVirtual(FNameBuilder(InPath), OutPath);
|
|
}
|
|
|
|
FName UContentBrowserDataSubsystem::ConvertInternalPathToVirtual(FName InPath)
|
|
{
|
|
FName OutPath;
|
|
ConvertInternalPathToVirtual(InPath, OutPath);
|
|
return OutPath;
|
|
}
|
|
|
|
TArray<FString> UContentBrowserDataSubsystem::ConvertInternalPathsToVirtual(const TArray<FString>& InPaths)
|
|
{
|
|
TArray<FString> VirtualPaths;
|
|
VirtualPaths.Reserve(InPaths.Num());
|
|
|
|
FNameBuilder Builder;
|
|
for (const FString& It : InPaths)
|
|
{
|
|
ConvertInternalPathToVirtual(It, Builder);
|
|
VirtualPaths.Add(FString(Builder));
|
|
}
|
|
|
|
return VirtualPaths;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::SetGenerateVirtualPathPrefixDelegate(const FContentBrowserGenerateVirtualPathDelegate& InDelegate)
|
|
{
|
|
GenerateVirtualPathPrefixDelegate = InDelegate;
|
|
SetVirtualPathTreeNeedsRebuild();
|
|
RefreshVirtualPathTreeIfNeeded();
|
|
}
|
|
|
|
FContentBrowserGenerateVirtualPathDelegate& UContentBrowserDataSubsystem::OnGenerateVirtualPathPrefix()
|
|
{
|
|
return GenerateVirtualPathPrefixDelegate;
|
|
}
|
|
|
|
bool UContentBrowserDataSubsystem::UsePluginVersePath(const TSharedRef<IPlugin>& Plugin)
|
|
{
|
|
if (UsePluginVersePathDelegate.IsBound() && !Plugin->GetVersePath().IsEmpty())
|
|
{
|
|
return UsePluginVersePathDelegate.Execute(Plugin);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::SetUsePluginVersePathDelegate(FContentBrowserUsePluginVersePathDelegate InDelegate)
|
|
{
|
|
UsePluginVersePathDelegate = MoveTemp(InDelegate);
|
|
SetVirtualPathTreeNeedsRebuild();
|
|
RefreshVirtualPathTreeIfNeeded();
|
|
}
|
|
|
|
FContentBrowserUsePluginVersePathDelegate& UContentBrowserDataSubsystem::GetUsePluginVersePathDelegate()
|
|
{
|
|
return UsePluginVersePathDelegate;
|
|
}
|
|
|
|
TSharedPtr<IContentBrowserHideFolderIfEmptyFilter> UContentBrowserDataSubsystem::CreateHideFolderIfEmptyFilter() const
|
|
{
|
|
if (CreateHideFolderIfEmptyFilterDelegates.IsEmpty())
|
|
{
|
|
return DefaultHideFolderIfEmptyFilter;
|
|
}
|
|
|
|
TArray<TSharedPtr<IContentBrowserHideFolderIfEmptyFilter>> HideFolderIfEmptyFilters;
|
|
HideFolderIfEmptyFilters.Reserve(1 + CreateHideFolderIfEmptyFilterDelegates.Num());
|
|
|
|
if (DefaultHideFolderIfEmptyFilter)
|
|
{
|
|
HideFolderIfEmptyFilters.Add(DefaultHideFolderIfEmptyFilter);
|
|
}
|
|
|
|
for (const FContentBrowserCreateHideFolderIfEmptyFilter& CreateHideFolderIfEmptyFilter : CreateHideFolderIfEmptyFilterDelegates)
|
|
{
|
|
TSharedPtr<IContentBrowserHideFolderIfEmptyFilter> HideFolderIfEmptyFilter = CreateHideFolderIfEmptyFilter.Execute();
|
|
if (HideFolderIfEmptyFilter.IsValid())
|
|
{
|
|
HideFolderIfEmptyFilters.Add(MoveTemp(HideFolderIfEmptyFilter));
|
|
}
|
|
}
|
|
|
|
if (HideFolderIfEmptyFilters.Num() == 1)
|
|
{
|
|
return HideFolderIfEmptyFilters[0];
|
|
}
|
|
|
|
return MakeShared<ContentBrowserDataSubsystem::FMergedHideFolderIfEmptyFilter>(MoveTemp(HideFolderIfEmptyFilters));
|
|
}
|
|
|
|
FDelegateHandle UContentBrowserDataSubsystem::RegisterCreateHideFolderIfEmptyFilter(FContentBrowserCreateHideFolderIfEmptyFilter Delegate)
|
|
{
|
|
return CreateHideFolderIfEmptyFilterDelegates.Add_GetRef(MoveTemp(Delegate)).GetHandle();
|
|
}
|
|
|
|
void UContentBrowserDataSubsystem::UnregisterCreateHideFolderIfEmptyFilter(FDelegateHandle DelegateHandle)
|
|
{
|
|
int32 Index = CreateHideFolderIfEmptyFilterDelegates.IndexOfByPredicate([DelegateHandle](const FContentBrowserCreateHideFolderIfEmptyFilter& Delegate)
|
|
{
|
|
return DelegateHandle == Delegate.GetHandle();
|
|
});
|
|
if (Index != INDEX_NONE)
|
|
{
|
|
CreateHideFolderIfEmptyFilterDelegates.RemoveAtSwap(Index);
|
|
}
|
|
}
|
|
|
|
const FString& UContentBrowserDataSubsystem::GetAllFolderPrefix() const
|
|
{
|
|
return AllFolderPrefix;
|
|
}
|
|
|
|
TSharedRef<FPathPermissionList>& UContentBrowserDataSubsystem::GetEditableFolderPermissionList()
|
|
{
|
|
return EditableFolderPermissionList;
|
|
}
|
|
|
|
EContentBrowserPathType UContentBrowserDataSubsystem::TryConvertVirtualPath(const FStringView InPath, FStringBuilderBase& OutPath) const
|
|
{
|
|
FNameBuilder FoundVirtualPath;
|
|
for (const auto& ActiveDataSourcePair : ActiveDataSources)
|
|
{
|
|
UContentBrowserDataSource* DataSource = ActiveDataSourcePair.Value;
|
|
const EContentBrowserPathType PathType = DataSource->TryConvertVirtualPath(InPath, OutPath);
|
|
if (PathType != EContentBrowserPathType::None)
|
|
{
|
|
if (PathType == EContentBrowserPathType::Internal)
|
|
{
|
|
return PathType;
|
|
}
|
|
else if (PathType == EContentBrowserPathType::Virtual)
|
|
{
|
|
// Another data source may be able to convert this to internal so keep checking
|
|
// Only after all data sources had a chance to claim ownership (internal) do we return
|
|
// Example: /Classes_Game is known to classes data source but not to asset data source
|
|
FoundVirtualPath.Reset();
|
|
FoundVirtualPath.Append(OutPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FoundVirtualPath.Len() > 0)
|
|
{
|
|
OutPath.Reset();
|
|
OutPath.Append(FoundVirtualPath);
|
|
return EContentBrowserPathType::Virtual;
|
|
}
|
|
else
|
|
{
|
|
return EContentBrowserPathType::None;
|
|
}
|
|
}
|
|
|
|
EContentBrowserPathType UContentBrowserDataSubsystem::TryConvertVirtualPath(FStringView InPath, FString& OutPath) const
|
|
{
|
|
FNameBuilder OutPathBuilder;
|
|
const EContentBrowserPathType ConvertedType = TryConvertVirtualPath(InPath, OutPathBuilder);
|
|
OutPath = FString(FStringView(OutPathBuilder));
|
|
return ConvertedType;
|
|
}
|
|
|
|
EContentBrowserPathType UContentBrowserDataSubsystem::TryConvertVirtualPath(FStringView InPath, FName& OutPath) const
|
|
{
|
|
FNameBuilder OutPathBuilder;
|
|
const EContentBrowserPathType ConvertedType = TryConvertVirtualPath(InPath, OutPathBuilder);
|
|
OutPath = FName(FStringView(OutPathBuilder));
|
|
return ConvertedType;
|
|
}
|
|
|
|
EContentBrowserPathType UContentBrowserDataSubsystem::TryConvertVirtualPath(FName InPath, FName& OutPath) const
|
|
{
|
|
FNameBuilder OutPathBuilder;
|
|
const EContentBrowserPathType ConvertedType = TryConvertVirtualPath(FNameBuilder(InPath), OutPathBuilder);
|
|
OutPath = FName(FStringView(OutPathBuilder));
|
|
return ConvertedType;
|
|
}
|
|
|
|
TArray<FString> UContentBrowserDataSubsystem::TryConvertVirtualPathsToInternal(const TArray<FString>& InPaths) const
|
|
{
|
|
TArray<FString> InternalPaths;
|
|
InternalPaths.Reserve(InPaths.Num());
|
|
|
|
for (const FString& VirtualPath : InPaths)
|
|
{
|
|
FString ConvertedPath;
|
|
if (TryConvertVirtualPath(VirtualPath, ConvertedPath) == EContentBrowserPathType::Internal)
|
|
{
|
|
InternalPaths.Add(MoveTemp(ConvertedPath));
|
|
}
|
|
}
|
|
|
|
return InternalPaths;
|
|
}
|