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

1735 lines
63 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SCollectionView.h"
#include "Algo/AllOf.h"
#include "Algo/Transform.h"
#include "AssetRegistry/AssetData.h"
#include "CollectionAssetManagement.h"
#include "CollectionContextMenu.h"
#include "CollectionViewTypes.h"
#include "CollectionViewUtils.h"
#include "ContentBrowserConfig.h"
#include "ContentBrowserDelegates.h"
#include "ContentBrowserModule.h"
#include "ContentBrowserPluginFilters.h"
#include "ContentBrowserStyle.h"
#include "ContentBrowserUtils.h"
#include "CoreGlobals.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/CollectionDragDropOp.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/SlateDelegates.h"
#include "Framework/Views/ITypedTableView.h"
#include "GenericPlatform/ICursor.h"
#include "HistoryManager.h"
#include "ICollectionContainer.h"
#include "ICollectionManager.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "ISourceControlState.h"
#include "Input/DragAndDrop.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Layout/Children.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "Layout/SlateRect.h"
#include "Layout/WidgetPath.h"
#include "Math/Color.h"
#include "Math/Vector2D.h"
#include "Misc/CString.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/TextFilterUtils.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "SAssetTagItem.h"
#include "SlotBase.h"
#include "SourcesSearch.h"
#include "SourcesViewWidgets.h"
#include "TelemetryRouter.h"
#include "Settings/ContentBrowserSettings.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateColor.h"
#include "Templates/Tuple.h"
#include "Textures/SlateIcon.h"
#include "UObject/NameTypes.h"
#include "UObject/UnrealNames.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Views/ITableRow.h"
#include "Widgets/Views/STableRow.h"
class SWidget;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "ContentBrowser"
namespace CollectionViewFilter
{
void GetBasicStrings(const FCollectionItem& InCollection, TArray<FString>& OutBasicStrings)
{
OutBasicStrings.Add(InCollection.CollectionName.ToString());
}
bool TestComplexExpression(const FCollectionItem& InCollection, const FName& InKey, const FTextFilterString& InValue, ETextFilterComparisonOperation InComparisonOperation, ETextFilterTextComparisonMode InTextComparisonMode)
{
static const FName NameKeyName = "Name";
static const FName TypeKeyName = "Type";
// Handle the collection name
if (InKey == NameKeyName)
{
// Names can only work with Equal or NotEqual type tests
if (InComparisonOperation != ETextFilterComparisonOperation::Equal && InComparisonOperation != ETextFilterComparisonOperation::NotEqual)
{
return false;
}
const bool bIsMatch = TextFilterUtils::TestBasicStringExpression(InCollection.CollectionName.ToString(), InValue, InTextComparisonMode);
return (InComparisonOperation == ETextFilterComparisonOperation::Equal) ? bIsMatch : !bIsMatch;
}
// Handle the collection type
if (InKey == TypeKeyName)
{
// Types can only work with Equal or NotEqual type tests
if (InComparisonOperation != ETextFilterComparisonOperation::Equal && InComparisonOperation != ETextFilterComparisonOperation::NotEqual)
{
return false;
}
const bool bIsMatch = TextFilterUtils::TestBasicStringExpression(ECollectionShareType::ToString(InCollection.CollectionType), InValue, InTextComparisonMode);
return (InComparisonOperation == ETextFilterComparisonOperation::Equal) ? bIsMatch : !bIsMatch;
}
return false;
}
} // namespace CollectionViewFilter
void SCollectionView::Construct( const FArguments& InArgs )
{
OnCollectionSelected = InArgs._OnCollectionSelected;
bAllowCollectionButtons = InArgs._AllowCollectionButtons;
bAllowRightClickMenu = InArgs._AllowRightClickMenu;
bAllowCollectionDrag = InArgs._AllowCollectionDrag;
bAllowExternalSearch = false;
bDraggedOver = false;
bQueueCollectionItemsUpdate = false;
bQueueSCCRefresh = true;
IsDocked = InArgs._IsDocked;
CollectionContainer = InArgs._CollectionContainer;
if (CollectionContainer)
{
CollectionContainer->OnCollectionCreated().AddSP(this, &SCollectionView::HandleCollectionCreated);
CollectionContainer->OnCollectionRenamed().AddSP(this, &SCollectionView::HandleCollectionRenamed);
CollectionContainer->OnCollectionReparented().AddSP(this, &SCollectionView::HandleCollectionReparented);
CollectionContainer->OnCollectionDestroyed().AddSP(this, &SCollectionView::HandleCollectionDestroyed);
CollectionContainer->OnCollectionUpdated().AddSP(this, &SCollectionView::HandleCollectionUpdated);
CollectionContainer->OnAssetsAddedToCollection().AddSP(this, &SCollectionView::HandleAssetsAddedToCollection);
CollectionContainer->OnAssetsRemovedFromCollection().AddSP(this, &SCollectionView::HandleAssetsRemovedFromCollection);
}
ISourceControlModule::Get().RegisterProviderChanged(FSourceControlProviderChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlProviderChanged));
SourceControlStateChangedDelegateHandle = ISourceControlModule::Get().GetProvider().RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlStateChanged));
Commands = TSharedPtr< FUICommandList >(new FUICommandList);
CollectionContextMenu = MakeShareable(new FCollectionContextMenu( SharedThis(this) ));
CollectionContextMenu->BindCommands(Commands);
CollectionItemTextFilter = MakeShareable(new FCollectionItemTextFilter(
FCollectionItemTextFilter::FItemToStringArray::CreateStatic(&CollectionViewFilter::GetBasicStrings),
FCollectionItemTextFilter::FItemTestComplexExpression::CreateStatic(&CollectionViewFilter::TestComplexExpression)
));
CollectionItemTextFilter->OnChanged().AddSP(this, &SCollectionView::UpdateFilteredCollectionItems);
if ( CollectionContainer && InArgs._AllowQuickAssetManagement )
{
QuickAssetManagement = MakeShareable(new FCollectionAssetManagement(CollectionContainer.ToSharedRef()));
}
FOnContextMenuOpening CollectionListContextMenuOpening;
if ( InArgs._AllowContextMenu )
{
CollectionListContextMenuOpening = FOnContextMenuOpening::CreateSP( this, &SCollectionView::MakeCollectionTreeContextMenu );
}
SearchPtr = InArgs._ExternalSearch;
if (SearchPtr)
{
SearchPtr->OnSearchChanged().AddSP(this, &SCollectionView::SetCollectionsSearchFilterText);
}
ExternalSearchPtr = InArgs._ExternalSearch;
TitleContent = SNew(SHorizontalBox);
UContentBrowserCollectionProjectSettings* CollectionProjectSettings = GetMutableDefault<UContentBrowserCollectionProjectSettings>();
if (CollectionProjectSettings)
{
CollectionProjectSettings->OnSettingChanged().AddSPLambda(this, [this](UObject* CollectionProjectSettings_, struct FPropertyChangedEvent&)
{
UpdateFilteredCollectionItems();
});
}
PreventSelectionChangedDelegateCount = 0;
TSharedRef< SWidget > HeaderContent = SNew(SHorizontalBox)
.Visibility(this, &SCollectionView::GetHeaderVisibility)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(0.0f)
[
TitleContent.ToSharedRef()
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ToolTipText(LOCTEXT("AddCollectionButtonTooltip", "Add a collection."))
.OnClicked(this, &SCollectionView::OnAddCollectionClicked)
.ContentPadding( FMargin(2, 2) )
.Visibility(this, &SCollectionView::GetAddCollectionButtonVisibility)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
TSharedRef< SWidget > BodyContent = SNew(SVerticalBox)
// Collections tree
+SVerticalBox::Slot()
.FillHeight(1.f)
[
SAssignNew(CollectionTreePtr, STreeView< TSharedPtr<FCollectionItem> >)
.TreeItemsSource(&VisibleRootCollectionItems)
.OnGenerateRow(this, &SCollectionView::GenerateCollectionRow)
.OnGetChildren(this, &SCollectionView::GetCollectionItemChildren)
.SelectionMode(ESelectionMode::Multi)
.OnSelectionChanged(this, &SCollectionView::CollectionSelectionChanged)
.OnContextMenuOpening(CollectionListContextMenuOpening)
.OnItemScrolledIntoView(this, &SCollectionView::CollectionItemScrolledIntoView)
.ClearSelectionOnClick(false)
.Visibility(this, &SCollectionView::GetCollectionTreeVisibility)
];
ChildSlot
[
SNew(SOverlay)
// Main content
+SOverlay::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(12.0f, 0.0f, 0.0f, 0.0f))
[
HeaderContent
]
+ SVerticalBox::Slot()
[
BodyContent
]
]
// Drop target overlay
+SOverlay::Slot()
[
SNew(SBorder)
.Padding(0)
.Visibility(EVisibility::HitTestInvisible)
.BorderImage(this, &SCollectionView::GetCollectionViewDropTargetBorder)
.BorderBackgroundColor(FLinearColor::Yellow)
[
SNullWidget::NullWidget
]
]
];
UpdateCollectionItems();
}
bool SCollectionView::IsEmpty() const
{
return AvailableCollections.IsEmpty();
}
void SCollectionView::HandleCollectionCreated( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionRenamed( ICollectionContainer& InCollectionContainer, const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection )
{
bQueueCollectionItemsUpdate = true;
// Rename the item in-place so we can maintain its expansion and selection states correctly once the view is refreshed on the next Tick
TSharedPtr<FCollectionItem> CollectionItem = AvailableCollections.FindRef(OriginalCollection);
if (CollectionItem.IsValid())
{
CollectionItem->CollectionName = NewCollection.Name;
CollectionItem->CollectionType = NewCollection.Type;
AvailableCollections.Remove(OriginalCollection);
AvailableCollections.Add(NewCollection, CollectionItem);
}
}
void SCollectionView::HandleCollectionReparented( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection, const TOptional<FCollectionNameType>& OldParent, const TOptional<FCollectionNameType>& NewParent )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionDestroyed( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionUpdated( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection )
{
TSharedPtr<FCollectionItem> CollectionItemToUpdate = AvailableCollections.FindRef(Collection);
if (CollectionItemToUpdate.IsValid())
{
bQueueSCCRefresh = true;
CollectionItemToUpdate->CollectionColor = CollectionViewUtils::ResolveColor(InCollectionContainer, Collection.Name, Collection.Type);
UpdateCollectionItemStatus(CollectionItemToUpdate.ToSharedRef());
}
}
void SCollectionView::HandleAssetsAddedToCollection( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection, TConstArrayView<FSoftObjectPath> AssetsAdded )
{
HandleCollectionUpdated(InCollectionContainer, Collection);
}
void SCollectionView::HandleAssetsRemovedFromCollection( ICollectionContainer& InCollectionContainer, const FCollectionNameType& Collection, TConstArrayView<FSoftObjectPath> AssetsRemoved )
{
HandleCollectionUpdated(InCollectionContainer, Collection);
}
void SCollectionView::HandleSourceControlProviderChanged(ISourceControlProvider& OldProvider, ISourceControlProvider& NewProvider)
{
OldProvider.UnregisterSourceControlStateChanged_Handle(SourceControlStateChangedDelegateHandle);
SourceControlStateChangedDelegateHandle = NewProvider.RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlStateChanged));
bQueueSCCRefresh = true;
HandleSourceControlStateChanged();
}
void SCollectionView::HandleSourceControlStateChanged()
{
bQueueItemStatusUpdate = true;
}
void SCollectionView::UpdateCollectionItemStatus( const TSharedRef<FCollectionItem>& CollectionItem )
{
TRACE_CPUPROFILER_EVENT_SCOPE(SCollectionView::UpdateCollectionItemStatus);
int32 NewObjectCount = 0;
TOptional<ECollectionItemStatus> NewStatus;
// Check IsModuleAvailable as we might be in the process of shutting down, and were notified due to the SCC provider being nulled out...
if (ensure(CollectionItem->CollectionContainer))
{
FCollectionStatusInfo StatusInfo;
if (CollectionItem->CollectionContainer->GetCollectionStatusInfo(CollectionItem->CollectionName, CollectionItem->CollectionType, StatusInfo))
{
NewObjectCount = StatusInfo.NumObjects;
// Test the SCC state first as this should take priority when reporting the status back to the user
if (StatusInfo.bUseSCC)
{
if (StatusInfo.SCCState.IsValid() && StatusInfo.SCCState->IsSourceControlled())
{
if (StatusInfo.SCCState->IsCheckedOutOther())
{
NewStatus = ECollectionItemStatus::IsCheckedOutByAnotherUser;
}
else if (StatusInfo.SCCState->IsConflicted())
{
NewStatus = ECollectionItemStatus::IsConflicted;
}
else if (!StatusInfo.SCCState->IsCurrent())
{
NewStatus = ECollectionItemStatus::IsOutOfDate;
}
else if (StatusInfo.SCCState->IsModified())
{
NewStatus = ECollectionItemStatus::HasLocalChanges;
}
}
else
{
NewStatus = ECollectionItemStatus::IsMissingSCCProvider;
}
}
// Not set by the SCC status, so check just use the local state
if (!NewStatus.IsSet())
{
if (StatusInfo.bIsDirty)
{
NewStatus = ECollectionItemStatus::HasLocalChanges;
}
else if (StatusInfo.bIsEmpty)
{
NewStatus = ECollectionItemStatus::IsUpToDateAndEmpty;
}
else
{
NewStatus = ECollectionItemStatus::IsUpToDateAndPopulated;
}
}
}
}
CollectionItem->NumObjects = NewObjectCount;
CollectionItem->CurrentStatus = NewStatus.Get(ECollectionItemStatus::IsUpToDateAndEmpty);
}
void SCollectionView::UpdateCollectionItems()
{
struct FGatherCollectionItems
{
static void GatherCollectionItems(const TSharedRef<ICollectionContainer>& InCollectionContainer, FAvailableCollectionsMap& OutAvailableCollections)
{
TArray<FCollectionNameType> RootCollections;
InCollectionContainer->GetRootCollections(RootCollections);
ProcessGatheredCollectionsAndRecurse(InCollectionContainer, RootCollections, nullptr, OutAvailableCollections);
}
private:
static void GatherChildCollectionItems(const TSharedRef<ICollectionContainer>& InCollectionContainer, const TSharedPtr<FCollectionItem>& InParentCollectionItem, FAvailableCollectionsMap& OutAvailableCollections)
{
TArray<FCollectionNameType> ChildCollections;
InCollectionContainer->GetChildCollections(InParentCollectionItem->CollectionName, InParentCollectionItem->CollectionType, ChildCollections);
ProcessGatheredCollectionsAndRecurse(InCollectionContainer, ChildCollections, InParentCollectionItem, OutAvailableCollections);
}
static void ProcessGatheredCollectionsAndRecurse(const TSharedRef<ICollectionContainer>& InCollectionContainer, const TArray<FCollectionNameType>& InCollections, const TSharedPtr<FCollectionItem>& InParentCollectionItem, FAvailableCollectionsMap& OutAvailableCollections)
{
for (const FCollectionNameType& Collection : InCollections)
{
// Never display system collections
if (Collection.Type == ECollectionShareType::CST_System)
{
continue;
}
TSharedRef<FCollectionItem> CollectionItem = MakeShareable(new FCollectionItem(InCollectionContainer, Collection.Name, Collection.Type));
OutAvailableCollections.Add(Collection, CollectionItem);
InCollectionContainer->GetCollectionStorageMode(Collection.Name, Collection.Type, CollectionItem->StorageMode);
CollectionItem->CollectionColor = CollectionViewUtils::ResolveColor(*InCollectionContainer, Collection.Name, Collection.Type);
SCollectionView::UpdateCollectionItemStatus(CollectionItem);
if (InParentCollectionItem.IsValid())
{
// Fixup the parent and child pointers
InParentCollectionItem->ChildCollections.Add(CollectionItem);
CollectionItem->ParentCollection = InParentCollectionItem;
}
// Recurse
GatherChildCollectionItems(InCollectionContainer, CollectionItem, OutAvailableCollections);
}
}
};
// Backup the current selection and expansion state of our collections
// We're about to re-create the tree, so we'll need to re-apply this again afterwards
TArray<FCollectionNameType> SelectedCollections;
TArray<FCollectionNameType> ExpandedCollections;
{
const auto SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
SelectedCollections.Reserve(SelectedCollectionItems.Num());
for (const TSharedPtr<FCollectionItem>& SelectedCollectionItem : SelectedCollectionItems)
{
SelectedCollections.Add(FCollectionNameType(SelectedCollectionItem->CollectionName, SelectedCollectionItem->CollectionType));
}
}
{
TSet<TSharedPtr<FCollectionItem>> ExpandedCollectionItems;
CollectionTreePtr->GetExpandedItems(ExpandedCollectionItems);
ExpandedCollections.Reserve(ExpandedCollectionItems.Num());
for (const TSharedPtr<FCollectionItem>& ExpandedCollectionItem : ExpandedCollectionItems)
{
ExpandedCollections.Add(FCollectionNameType(ExpandedCollectionItem->CollectionName, ExpandedCollectionItem->CollectionType));
}
}
AvailableCollections.Reset();
if (CollectionContainer)
{
FGatherCollectionItems::GatherCollectionItems(CollectionContainer.ToSharedRef(), AvailableCollections);
}
UpdateFilteredCollectionItems();
// Restore selection and expansion
SetSelectedCollections(SelectedCollections, false);
SetExpandedCollections(ExpandedCollections);
bQueueSCCRefresh = true;
}
void SCollectionView::UpdateFilteredCollectionItems()
{
VisibleCollections.Reset();
VisibleRootCollectionItems.Reset();
const UContentBrowserCollectionProjectSettings* CollectionProjectSettings = GetDefault<UContentBrowserCollectionProjectSettings>();
const UContentBrowserSettings* ContentBrowserSettings = GetDefault<UContentBrowserSettings>();
auto AddVisibleCollection = [&](const FCollectionNameType& NameTypePair, const TSharedPtr<FCollectionItem>& InCollectionItem)
{
if (!ContentBrowserSettings->bDisplayExcludedCollections)
{
const int32 FoundIndex = CollectionProjectSettings->ExcludedCollectionsFromView.Find(NameTypePair.Name);
if (FoundIndex != INDEX_NONE)
{
return;
}
}
VisibleCollections.Add(FCollectionNameType(InCollectionItem->CollectionName, InCollectionItem->CollectionType));
if (!InCollectionItem->ParentCollection.IsValid())
{
VisibleRootCollectionItems.AddUnique(InCollectionItem);
}
};
auto AddVisibleCollectionRecursive = [&](const FCollectionNameType& NameTypePair, const TSharedPtr<FCollectionItem>& InCollectionItem)
{
TSharedPtr<FCollectionItem> CollectionItemToAdd = InCollectionItem;
do
{
AddVisibleCollection(NameTypePair, CollectionItemToAdd);
CollectionItemToAdd = CollectionItemToAdd->ParentCollection.Pin();
}
while(CollectionItemToAdd.IsValid());
};
// Do we have an active filter to test against?
if (CollectionItemTextFilter->GetRawFilterText().IsEmpty())
{
// No filter, just mark everything as visible
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
AddVisibleCollection(AvailableCollectionInfo.Key, AvailableCollectionInfo.Value);
}
}
else
{
TArray<TSharedRef<FCollectionItem>> CollectionsToExpandTo;
// Test everything against the filter - a visible child needs to make sure its parents are also marked as visible
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
const TSharedPtr<FCollectionItem>& CollectionItem = AvailableCollectionInfo.Value;
if (CollectionItemTextFilter->PassesFilter(*CollectionItem))
{
AddVisibleCollectionRecursive(AvailableCollectionInfo.Key, CollectionItem);
CollectionsToExpandTo.Add(CollectionItem.ToSharedRef());
}
}
// Make sure all matching items have their parents expanded so they can be seen
for (const TSharedRef<FCollectionItem>& CollectionItem : CollectionsToExpandTo)
{
ExpandParentItems(CollectionItem);
}
}
UContentBrowserSettings::OnSettingChanged().AddSP(this, &SCollectionView::HandleSettingChanged);
VisibleRootCollectionItems.Sort(FCollectionItem::FCompareFCollectionItemByName());
CollectionTreePtr->RequestTreeRefresh();
}
void SCollectionView::SetCollectionsSearchFilterText( const FText& InSearchText, TArray<FText>& OutErrors )
{
CollectionItemTextFilter->SetRawFilterText( InSearchText );
const FText ErrorText = CollectionItemTextFilter->GetFilterErrorText();
if (!ErrorText.IsEmpty())
{
OutErrors.Add(ErrorText);
}
}
FText SCollectionView::GetCollectionsSearchFilterText() const
{
return CollectionItemTextFilter->GetRawFilterText();
}
void SCollectionView::SetSelectedCollections(const TArray<FCollectionNameType>& CollectionsToSelect, const bool bEnsureVisible)
{
// Prevent the selection changed delegate since the invoking code requested it
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
// Clear the selection to start, then add the selected items as they are found
CollectionTreePtr->ClearSelection();
for (const FCollectionNameType& CollectionToSelect : CollectionsToSelect)
{
TSharedPtr<FCollectionItem> CollectionItemToSelect = AvailableCollections.FindRef(CollectionToSelect);
if (CollectionItemToSelect.IsValid())
{
if (bEnsureVisible)
{
ExpandParentItems(CollectionItemToSelect.ToSharedRef());
CollectionTreePtr->RequestScrollIntoView(CollectionItemToSelect);
}
CollectionTreePtr->SetItemSelection(CollectionItemToSelect, true);
// If the selected collection doesn't pass our current filter, we need to clear it
if (bEnsureVisible && !CollectionItemTextFilter->PassesFilter(*CollectionItemToSelect))
{
SearchPtr->ClearSearch();
}
}
}
}
void SCollectionView::SetExpandedCollections(const TArray<FCollectionNameType>& CollectionsToExpand)
{
// Clear the expansion to start, then add the expanded items as they are found
CollectionTreePtr->ClearExpandedItems();
for (const FCollectionNameType& CollectionToExpand : CollectionsToExpand)
{
TSharedPtr<FCollectionItem> CollectionItemToExpand = AvailableCollections.FindRef(CollectionToExpand);
if (CollectionItemToExpand.IsValid())
{
CollectionTreePtr->SetItemExpansion(CollectionItemToExpand, true);
}
}
}
void SCollectionView::ClearSelection()
{
// Prevent the selection changed delegate since the invoking code requested it
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
// Clear the selection to start, then add the selected paths as they are found
CollectionTreePtr->ClearSelection();
}
const TSharedPtr<ICollectionContainer>& SCollectionView::GetCollectionContainer() const
{
return CollectionContainer;
}
TArray<FCollectionNameType> SCollectionView::GetSelectedCollections() const
{
TArray<FCollectionNameType> RetArray;
TArray<TSharedPtr<FCollectionItem>> Items = CollectionTreePtr->GetSelectedItems();
for ( int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx )
{
const TSharedPtr<FCollectionItem>& Item = Items[ItemIdx];
RetArray.Add(FCollectionNameType(Item->CollectionName, Item->CollectionType));
}
return RetArray;
}
void SCollectionView::SetSelectedAssetPaths(const TArray<FSoftObjectPath>& SelectedAssets)
{
if ( QuickAssetManagement.IsValid() )
{
QuickAssetManagement->SetCurrentAssetPaths(SelectedAssets);
}
}
void SCollectionView::ApplyHistoryData( const FHistoryData& History )
{
// Prevent the selection changed delegate because it would add more history when we are just setting a state
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
CollectionTreePtr->ClearSelection();
for (const FCollectionRef& HistoryCollection : History.ContentSources.GetCollections())
{
if (CollectionContainer == HistoryCollection.Container)
{
TSharedPtr<FCollectionItem> CollectionHistoryItem = AvailableCollections.FindRef(FCollectionNameType(HistoryCollection.Name, HistoryCollection.Type));
if (CollectionHistoryItem.IsValid())
{
ExpandParentItems(CollectionHistoryItem.ToSharedRef());
CollectionTreePtr->RequestScrollIntoView(CollectionHistoryItem);
CollectionTreePtr->SetItemSelection(CollectionHistoryItem, true);
}
}
}
}
void SCollectionView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const
{
auto SaveCollectionsArrayToIni = [&](const FString& InSubKey, const TArray<TSharedPtr<FCollectionItem>>& InCollectionItems)
{
FString CollectionsString;
for (const TSharedPtr<FCollectionItem>& CollectionItem : InCollectionItems)
{
if (CollectionsString.Len() > 0)
{
CollectionsString += TEXT(",");
}
CollectionsString += CollectionItem->CollectionName.ToString();
CollectionsString += TEXT("?");
CollectionsString += FString::FromInt(CollectionItem->CollectionType);
}
GConfig->SetString(*IniSection, *(SettingsString + InSubKey), *CollectionsString, IniFilename);
};
SaveCollectionsArrayToIni(TEXT(".SelectedCollections"), CollectionTreePtr->GetSelectedItems());
{
TSet<TSharedPtr<FCollectionItem>> ExpandedCollectionItems;
CollectionTreePtr->GetExpandedItems(ExpandedCollectionItems);
SaveCollectionsArrayToIni(TEXT(".ExpandedCollections"), ExpandedCollectionItems.Array());
}
}
void SCollectionView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString)
{
auto LoadCollectionsArrayFromIni = [&](const FString& InSubKey) -> TArray<FCollectionNameType>
{
TArray<FCollectionNameType> RetCollectionsArray;
FString CollectionsArrayString;
if (GConfig->GetString(*IniSection, *(SettingsString + InSubKey), CollectionsArrayString, IniFilename))
{
TArray<FString> CollectionStrings;
CollectionsArrayString.ParseIntoArray(CollectionStrings, TEXT(","), /*bCullEmpty*/true);
for (const FString& CollectionString : CollectionStrings)
{
FString CollectionName;
FString CollectionTypeString;
if (CollectionString.Split(TEXT("?"), &CollectionName, &CollectionTypeString))
{
const int32 CollectionType = FCString::Atoi(*CollectionTypeString);
if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All)
{
RetCollectionsArray.Add(FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType)));
}
}
}
}
return RetCollectionsArray;
};
// Selected Collections
TArray<FCollectionNameType> NewSelectedCollections = LoadCollectionsArrayFromIni(TEXT(".SelectedCollections"));
if (NewSelectedCollections.Num() > 0)
{
SetSelectedCollections(NewSelectedCollections);
const TArray<TSharedPtr<FCollectionItem>> SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
if (SelectedCollectionItems.Num() > 0)
{
CollectionSelectionChanged(SelectedCollectionItems[0], ESelectInfo::Direct);
}
}
// Expanded Collections
TArray<FCollectionNameType> NewExpandedCollections = LoadCollectionsArrayFromIni(TEXT(".ExpandedCollections"));
if (NewExpandedCollections.Num() > 0)
{
SetExpandedCollections(NewExpandedCollections);
}
}
void SCollectionView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (bQueueCollectionItemsUpdate)
{
bQueueCollectionItemsUpdate = false;
UpdateCollectionItems();
}
if (bQueueSCCRefresh && CollectionContainer && ISourceControlModule::Get().IsEnabled())
{
bQueueSCCRefresh = false;
TArray<FString> CollectionFilesToRefresh;
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
FCollectionStatusInfo StatusInfo;
if (CollectionContainer->GetCollectionStatusInfo(AvailableCollectionInfo.Value->CollectionName, AvailableCollectionInfo.Value->CollectionType, StatusInfo))
{
if (StatusInfo.bUseSCC && StatusInfo.SCCState.IsValid() && StatusInfo.SCCState->IsSourceControlled())
{
CollectionFilesToRefresh.Add(StatusInfo.SCCState->GetFilename());
}
}
}
if (CollectionFilesToRefresh.Num() > 0)
{
ISourceControlModule::Get().QueueStatusUpdate(CollectionFilesToRefresh);
}
}
if (bQueueItemStatusUpdate)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("CollectionView Update Collection Items Status");
bQueueItemStatusUpdate = false;
// Update the status of each collection
for (const TPair<FCollectionNameType, TSharedPtr<FCollectionItem>>& AvailableCollectionInfo : AvailableCollections)
{
UpdateCollectionItemStatus(AvailableCollectionInfo.Value.ToSharedRef());
}
}
}
FReply SCollectionView::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
if( Commands->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver); // updates bDraggedOver
}
void SCollectionView::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
bDraggedOver = false;
}
FReply SCollectionView::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver); // updates bDraggedOver
return (bDraggedOver) ? FReply::Handled() : FReply::Unhandled();
}
FReply SCollectionView::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
if (ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver)) // updates bDraggedOver
{
bDraggedOver = false;
return HandleDragDropOnCollectionTree(MyGeometry, DragDropEvent);
}
if (bDraggedOver)
{
// We were able to handle this operation, but could not due to another error - still report this drop as handled so it doesn't fall through to other widgets
bDraggedOver = false;
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::MakeSaveDynamicCollectionMenu(FMenuBuilder& InMenuBuilder, FText InQueryString)
{
CollectionContextMenu->UpdateProjectSourceControl();
CollectionContextMenu->MakeSaveDynamicCollectionSubMenu(InMenuBuilder, InQueryString);
}
FReply SCollectionView::OnAddCollectionClicked()
{
MakeAddCollectionMenu(AsShared());
return FReply::Handled();
}
bool SCollectionView::ShouldAllowSelectionChangedDelegate() const
{
return PreventSelectionChangedDelegateCount == 0;
}
void SCollectionView::MakeAddCollectionMenu(TSharedRef<SWidget> MenuParent)
{
// 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);
CollectionContextMenu->UpdateProjectSourceControl();
CollectionContextMenu->MakeNewCollectionSubMenu(MenuBuilder, ECollectionStorageMode::Static, SCollectionView::FCreateCollectionPayload());
FSlateApplication::Get().PushMenu(
MenuParent,
FWidgetPath(),
MenuBuilder.MakeWidget(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect( FPopupTransitionEffect::TopMenu )
);
}
FVector2D SCollectionView::GetScrollDistance()
{
if (!CollectionTreePtr.IsValid())
{
return FVector2D::ZeroVector;
}
return CollectionTreePtr->GetScrollDistance();
}
FVector2D SCollectionView::GetScrollDistanceRemaining()
{
if (!CollectionTreePtr.IsValid())
{
return FVector2D::ZeroVector;
}
return CollectionTreePtr->GetScrollDistanceRemaining();
}
TSharedRef<SWidget> SCollectionView::GetScrollWidget()
{
return SharedThis(this);
}
void SCollectionView::CreateCollectionItem( ECollectionShareType::Type CollectionType, ECollectionStorageMode::Type StorageMode, const FCreateCollectionPayload& InCreationPayload )
{
if ( ensure(CollectionContainer && CollectionType != ECollectionShareType::CST_All) )
{
const FName BaseCollectionName = *LOCTEXT("NewCollectionName", "NewCollection").ToString();
FName CollectionName;
CollectionContainer->CreateUniqueCollectionName(BaseCollectionName, ECollectionShareType::CST_All, CollectionName);
TSharedPtr<FCollectionItem> NewItem = MakeShareable(new FCollectionItem(CollectionContainer.ToSharedRef(), CollectionName, CollectionType));
NewItem->StorageMode = StorageMode;
// Adding a new collection now, so clear any filter we may have applied
SearchPtr->ClearSearch();
if ( InCreationPayload.ParentCollection.IsSet() )
{
TSharedPtr<FCollectionItem> ParentCollectionItem = AvailableCollections.FindRef(InCreationPayload.ParentCollection.GetValue());
if ( ParentCollectionItem.IsValid() )
{
ParentCollectionItem->ChildCollections.Add(NewItem);
NewItem->ParentCollection = ParentCollectionItem;
// Make sure the parent is expanded so we can see its newly added child item
CollectionTreePtr->SetItemExpansion(ParentCollectionItem, true);
}
}
// Mark the new collection for rename and that it is new so it will be created upon successful rename
NewItem->bRenaming = true;
NewItem->bNewCollection = true;
NewItem->OnCollectionCreatedEvent = InCreationPayload.OnCollectionCreatedEvent;
AvailableCollections.Add( FCollectionNameType(NewItem->CollectionName, NewItem->CollectionType), NewItem );
UpdateFilteredCollectionItems();
CollectionTreePtr->RequestScrollIntoView(NewItem);
CollectionTreePtr->SetSelection( NewItem );
}
}
void SCollectionView::RenameCollectionItem( const TSharedPtr<FCollectionItem>& ItemToRename )
{
if ( ensure(ItemToRename.IsValid()) )
{
ItemToRename->bRenaming = true;
CollectionTreePtr->RequestScrollIntoView(ItemToRename);
}
}
void SCollectionView::DeleteCollectionItems( const TArray<TSharedPtr<FCollectionItem>>& ItemsToDelete )
{
if (ItemsToDelete.Num() == 0)
{
return;
}
// Before we delete anything (as this will trigger a tree update) we need to work out what our new selection should be in the case that
// all of the selected items are removed
const TArray<TSharedPtr<FCollectionItem>> PreviouslySelectedItems = CollectionTreePtr->GetSelectedItems();
// Get the first selected item that will be deleted so we can find a suitable new selection
TSharedPtr<FCollectionItem> FirstSelectedItemDeleted;
for (const auto& ItemToDelete : ItemsToDelete)
{
if (PreviouslySelectedItems.Contains(ItemToDelete))
{
FirstSelectedItemDeleted = ItemToDelete;
break;
}
}
// Build up an array of potential new selections (in the case that we're deleting everything that's selected)
// Earlier items should be considered first, we base this list on the first selected item that will be deleted, and include previous siblings, and then all parents and roots
TArray<FCollectionNameType> PotentialNewSelections;
if (FirstSelectedItemDeleted.IsValid())
{
TSharedPtr<FCollectionItem> RootSelectedItemDeleted = FirstSelectedItemDeleted;
TSharedPtr<FCollectionItem> ParentCollectionItem = FirstSelectedItemDeleted->ParentCollection.Pin();
if (ParentCollectionItem.IsValid())
{
// Add all the siblings until we find the item that will be deleted
for (const auto& ChildItemWeakPtr : ParentCollectionItem->ChildCollections)
{
TSharedPtr<FCollectionItem> ChildItem = ChildItemWeakPtr.Pin();
if (ChildItem.IsValid())
{
if (ChildItem == FirstSelectedItemDeleted)
{
break;
}
// We add siblings as the start, as the closest sibling should be the first match
PotentialNewSelections.Insert(FCollectionNameType(ChildItem->CollectionName, ChildItem->CollectionType), 0);
}
}
// Now add this parent, and all other parents too
do
{
PotentialNewSelections.Add(FCollectionNameType(ParentCollectionItem->CollectionName, ParentCollectionItem->CollectionType));
RootSelectedItemDeleted = ParentCollectionItem;
ParentCollectionItem = ParentCollectionItem->ParentCollection.Pin();
}
while (ParentCollectionItem.IsValid());
}
if (RootSelectedItemDeleted.IsValid())
{
// Add all the root level items before this one
const int32 InsertionPoint = PotentialNewSelections.Num();
for (const auto& RootItem : VisibleRootCollectionItems)
{
if (RootItem == RootSelectedItemDeleted)
{
break;
}
// Add each root item at the insertion point, as the closest item should be a better match
PotentialNewSelections.Insert(FCollectionNameType(RootItem->CollectionName, RootItem->CollectionType), InsertionPoint);
}
}
}
// Delete all given collections
int32 NumSelectedItemsDeleted = 0;
for (const TSharedPtr<FCollectionItem>& ItemToDelete : ItemsToDelete)
{
if (!ensure(ItemToDelete->CollectionContainer))
{
continue;
}
FText Error;
if (ItemToDelete->CollectionContainer->DestroyCollection(ItemToDelete->CollectionName, ItemToDelete->CollectionType, &Error))
{
if (PreviouslySelectedItems.Contains(ItemToDelete))
{
++NumSelectedItemsDeleted;
}
}
else
{
// Display a warning
const FVector2f& CursorPos = FSlateApplication::Get().GetCursorPos();
FSlateRect MessageAnchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y);
ContentBrowserUtils::DisplayMessage(
FText::Format( LOCTEXT("CollectionDestroyFailed", "Failed to destroy collection. {0}"), Error),
MessageAnchor,
CollectionTreePtr.ToSharedRef(),
ContentBrowserUtils::EDisplayMessageType::Error
);
}
}
// DestroyCollection will have triggered a notification that will have updated the tree, we now need to apply a suitable selection...
// Did this delete change the list of selected items?
if (NumSelectedItemsDeleted > 0 || PreviouslySelectedItems.Num() == 0)
{
// If we removed everything that was selected, we need to try and find a suitable replacement...
if (NumSelectedItemsDeleted >= PreviouslySelectedItems.Num() && VisibleCollections.Num() > 1)
{
// Include the first visible item as an absolute last resort should everything else suitable have been removed from the tree
PotentialNewSelections.Add(*VisibleCollections.CreateConstIterator());
// Check the potential new selections array and try and select the first one that's still visible in the tree
TArray<FCollectionNameType> NewItemSelection;
for (const FCollectionNameType& PotentialNewSelection : PotentialNewSelections)
{
if (VisibleCollections.Contains(PotentialNewSelection))
{
NewItemSelection.Add(PotentialNewSelection);
break;
}
}
SetSelectedCollections(NewItemSelection, true);
}
// Broadcast the new selection
const TArray<TSharedPtr<FCollectionItem>> UpdatedSelectedItems = CollectionTreePtr->GetSelectedItems();
CollectionSelectionChanged((UpdatedSelectedItems.Num() > 0) ? UpdatedSelectedItems[0] : nullptr, ESelectInfo::Direct);
}
}
EVisibility SCollectionView::GetCollectionTreeVisibility() const
{
return AvailableCollections.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SCollectionView::GetHeaderVisibility() const
{
return IsDocked.Get() ? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible;
}
EVisibility SCollectionView::GetAddCollectionButtonVisibility() const
{
return bAllowCollectionButtons && CollectionContainer.IsValid() && !CollectionContainer->IsReadOnly(ECollectionShareType::CST_All) ? EVisibility::Visible : EVisibility::Collapsed;
}
const FSlateBrush* SCollectionView::GetCollectionViewDropTargetBorder() const
{
return bDraggedOver ? UE::ContentBrowser::Private::FContentBrowserStyle::Get().GetBrush("ContentBrowser.CollectionTreeDragDropBorder") : FAppStyle::GetBrush("NoBorder");
}
TSharedRef<ITableRow> SCollectionView::GenerateCollectionRow( TSharedPtr<FCollectionItem> CollectionItem, const TSharedRef<STableViewBase>& OwnerTable )
{
check(CollectionItem.IsValid());
// Only bind the check box callbacks if we're allowed to show check boxes
TAttribute<bool> IsCollectionCheckBoxEnabledAttribute;
TAttribute<ECheckBoxState> IsCollectionCheckedAttribute;
FOnCheckStateChanged OnCollectionCheckStateChangedDelegate;
if ( QuickAssetManagement.IsValid() )
{
// Can only manage assets for static collections
if (CollectionItem->StorageMode == ECollectionStorageMode::Static)
{
IsCollectionCheckBoxEnabledAttribute.Bind(TAttribute<bool>::FGetter::CreateSP(this, &SCollectionView::IsCollectionCheckBoxEnabled, CollectionItem));
IsCollectionCheckedAttribute.Bind(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &SCollectionView::IsCollectionChecked, CollectionItem));
OnCollectionCheckStateChangedDelegate.BindSP(this, &SCollectionView::OnCollectionCheckStateChanged, CollectionItem);
}
else
{
IsCollectionCheckBoxEnabledAttribute.Bind(TAttribute<bool>::FGetter::CreateLambda([]{ return false; }));
IsCollectionCheckedAttribute.Bind(TAttribute<ECheckBoxState>::FGetter::CreateLambda([]{ return ECheckBoxState::Unchecked; }));
}
}
TSharedPtr< SAssetTagItemTableRow< TSharedPtr<FCollectionItem> > > TableRow = SNew( SAssetTagItemTableRow< TSharedPtr<FCollectionItem> >, OwnerTable )
.OnDragDetected(this, &SCollectionView::OnCollectionDragDetected);
TSharedPtr<SCollectionTreeItem> CollectionTreeItem;
TableRow->SetContent
(
SAssignNew(CollectionTreeItem, SCollectionTreeItem)
.ParentWidget(SharedThis(this))
.CollectionItem(CollectionItem)
.OnNameChangeCommit(this, &SCollectionView::CollectionNameChangeCommit)
.OnVerifyRenameCommit(this, &SCollectionView::CollectionVerifyRenameCommit)
.OnValidateDragDrop(this, &SCollectionView::ValidateDragDropOnCollectionItem)
.OnHandleDragDrop(this, &SCollectionView::HandleDragDropOnCollectionItem)
.IsSelected(TableRow.Get(), &STableRow< TSharedPtr<FCollectionItem> >::IsSelectedExclusively)
.IsReadOnly(this, &SCollectionView::IsCollectionNameReadOnly)
.HighlightText(this, &SCollectionView::GetCollectionsSearchFilterText)
.IsCheckBoxEnabled(IsCollectionCheckBoxEnabledAttribute)
.IsCollectionChecked(IsCollectionCheckedAttribute)
.OnCollectionCheckStateChanged(OnCollectionCheckStateChangedDelegate)
);
TableRow->SetIsDropTarget(MakeAttributeSP(CollectionTreeItem.Get(), &SCollectionTreeItem::IsDraggedOver));
return TableRow.ToSharedRef();
}
void SCollectionView::GetCollectionItemChildren( TSharedPtr<FCollectionItem> InParentItem, TArray< TSharedPtr<FCollectionItem> >& OutChildItems ) const
{
for (const auto& ChildItemWeakPtr : InParentItem->ChildCollections)
{
TSharedPtr<FCollectionItem> ChildItem = ChildItemWeakPtr.Pin();
if (ChildItem.IsValid() && VisibleCollections.Contains(FCollectionNameType(ChildItem->CollectionName, ChildItem->CollectionType)))
{
OutChildItems.Add(ChildItem);
}
}
OutChildItems.Sort(FCollectionItem::FCompareFCollectionItemByName());
}
FReply SCollectionView::OnCollectionDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if (bAllowCollectionDrag && MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
const TArray<FCollectionNameType> SelectedCollections = GetSelectedCollections();
if (SelectedCollections.Num() > 0)
{
TArray<FCollectionRef> CollectionRefs;
CollectionRefs.Reserve(SelectedCollections.Num());
Algo::Transform(
SelectedCollections,
CollectionRefs,
[this](const FCollectionNameType& Collection) { return FCollectionRef(CollectionContainer, Collection); });
TSharedRef<FCollectionDragDropOp> DragDropOp = FCollectionDragDropOp::New(CollectionRefs);
CurrentCollectionDragDropOp = DragDropOp;
return FReply::Handled().BeginDragDrop(DragDropOp);
}
}
return FReply::Unhandled();
}
bool SCollectionView::ValidateDragDropOnCollectionTree(const FGeometry& Geometry, const FDragDropEvent& DragDropEvent, bool& OutIsKnownDragOperation)
{
OutIsKnownDragOperation = false;
if (!CollectionContainer.IsValid())
{
return false;
}
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
OutIsKnownDragOperation = true;
if (Algo::AllOf(DragDropOp->CollectionRefs, [this](const FCollectionRef& Collection)
{
return CollectionContainer == Collection.Container;
}))
{
return true;
}
else
{
DragDropOp->SetToolTip(
LOCTEXT("InvalidParentCollectionContainer", "A collection cannot be parented to a different collection container"),
FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
Operation->SetCursorOverride(EMouseCursor::SlashedCircle);
}
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
if (!DragDropOp->HasAssets())
{
DragDropOp->SetCursorOverride(EMouseCursor::SlashedCircle);
DragDropOp->SetToolTip(LOCTEXT("CollectionView_DragDrop_NoAsset", "There is no asset being dragged"), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
return false;
}
const int32 NumDraggedItems = DragDropOp->GetAssets().Num();
const FText FirstItemText = FText::FromName(DragDropOp->GetAssets()[0].AssetName);
const FText AddToCollectionText = (NumDraggedItems > 1)
? FText::Format(LOCTEXT("CollectionView_DragDrop_MultipleItems", "Add '{0}' and {1} {1}|plural(one=other,other=others)"), FirstItemText, NumDraggedItems - 1)
: FText::Format(LOCTEXT("CollectionView_DragDrop_SingularItems", "Add '{0}'"), FirstItemText);
DragDropOp->SetToolTip(AddToCollectionText, FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")));
return true;
}
return false;
}
FReply SCollectionView::HandleDragDropOnCollectionTree(const FGeometry& Geometry, const FDragDropEvent& DragDropEvent)
{
// Should have already called ValidateDragDropOnCollectionTree prior to calling this...
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
check(Operation.IsValid());
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
// Reparent all of the collections in the drag drop so that they are root level items
for (const FCollectionRef& NewChildCollection : DragDropOp->CollectionRefs)
{
check(CollectionContainer == NewChildCollection.Container);
FText Error;
if (!CollectionContainer->ReparentCollection(
NewChildCollection.Name, NewChildCollection.Type,
NAME_None, ECollectionShareType::CST_All, &Error
))
{
ContentBrowserUtils::DisplayMessage(Error, Geometry.GetLayoutBoundingRect(), SharedThis(this), ContentBrowserUtils::EDisplayMessageType::Error);
}
}
return FReply::Handled();
}
return FReply::Unhandled();
}
bool SCollectionView::ValidateDragDropOnCollectionItem(TSharedRef<FCollectionItem> CollectionItem, const FGeometry& Geometry, const FDragDropEvent& DragDropEvent, bool& OutIsKnownDragOperation)
{
OutIsKnownDragOperation = false;
if (!ensure(CollectionItem->CollectionContainer))
{
return false;
}
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
bool bIsValidDrag = false;
TOptional<EMouseCursor::Type> NewDragCursor;
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
OutIsKnownDragOperation = true;
bIsValidDrag = true;
for (const FCollectionRef& PotentialChildCollection : DragDropOp->CollectionRefs)
{
FText Error;
if (CollectionItem->CollectionContainer == PotentialChildCollection.Container)
{
bIsValidDrag = CollectionItem->CollectionContainer->IsValidParentCollection(
PotentialChildCollection.Name, PotentialChildCollection.Type,
CollectionItem->CollectionName, CollectionItem->CollectionType, &Error);
}
else
{
bIsValidDrag = false;
Error = LOCTEXT("InvalidParentCollectionContainer", "A collection cannot be parented to a different collection container");
}
if (!bIsValidDrag)
{
DragDropOp->SetToolTip(Error, FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
break;
}
}
// If we are dragging over a child collection item, then this view as a whole should not be marked as dragged over
bDraggedOver = false;
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
OutIsKnownDragOperation = true;
bIsValidDrag = DragDropOp->HasAssets();
if (!bIsValidDrag)
{
DragDropOp->SetToolTip(LOCTEXT("CollectionViewItem_DragDrop_NoAsset", "There is no asset being dragged"), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
}
else
{
const int32 NumDraggedItems = DragDropOp->GetAssets().Num();
const FText FirstItemText = FText::FromName(DragDropOp->GetAssets()[0].AssetName);
const FText AddToCollectionText = (NumDraggedItems > 1)
? FText::Format(LOCTEXT("CollectionViewItem_DragDrop_MultipleItems", "Add '{0}' and {1} {1}|plural(one=other,other=others)"), FirstItemText, NumDraggedItems - 1)
: FText::Format(LOCTEXT("CollectionViewItem_DragDrop_SingularItems", "Add '{0}'"), FirstItemText);
DragDropOp->SetToolTip(AddToCollectionText, FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")));
}
}
// Set the default slashed circle if this drag is invalid and a drag operation hasn't set NewDragCursor to something custom
if (!bIsValidDrag && !NewDragCursor.IsSet())
{
NewDragCursor = EMouseCursor::SlashedCircle;
}
Operation->SetCursorOverride(NewDragCursor);
return bIsValidDrag;
}
FReply SCollectionView::HandleDragDropOnCollectionItem(TSharedRef<FCollectionItem> CollectionItem, const FGeometry& Geometry, const FDragDropEvent& DragDropEvent)
{
// Should have already called ValidateDragDropOnCollectionItem prior to calling this...
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
check(Operation.IsValid());
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
// Make sure our drop item is marked as expanded so that we'll be able to see the newly added children
CollectionTreePtr->SetItemExpansion(CollectionItem, true);
// Reparent all of the collections in the drag drop so that they are our immediate children
for (const FCollectionRef& NewChildCollection : DragDropOp->CollectionRefs)
{
check(CollectionItem->CollectionContainer == NewChildCollection.Container);
FText Error;
if (!CollectionItem->CollectionContainer->ReparentCollection(
NewChildCollection.Name, NewChildCollection.Type,
CollectionItem->CollectionName, CollectionItem->CollectionType, &Error
))
{
ContentBrowserUtils::DisplayMessage(Error, Geometry.GetLayoutBoundingRect(), SharedThis(this), ContentBrowserUtils::EDisplayMessageType::Error);
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
const TArray<FAssetData>& DroppedAssets = DragDropOp->GetAssets();
TArray<FSoftObjectPath> ObjectPaths;
ObjectPaths.Reserve(DroppedAssets.Num());
for (const FAssetData& AssetData : DroppedAssets)
{
ObjectPaths.Add(AssetData.GetSoftObjectPath());
}
const double BeginTimeSec = FPlatformTime::Seconds();
int32 NumAdded = 0;
FText Message;
ContentBrowserUtils::EDisplayMessageType MessageType = ContentBrowserUtils::EDisplayMessageType::Info;
if (CollectionItem->CollectionContainer->AddToCollection(
CollectionItem->CollectionName, CollectionItem->CollectionType, ObjectPaths, &NumAdded, &Message))
{
if (UE::Editor::ContentBrowser::IsNewStyleEnabled())
{
Message = FText::Format(LOCTEXT("AddingToCollection", "{0} {0}|plural(one=item,other=items) added to '{1}'"), NumAdded, FText::FromName(CollectionItem->CollectionName));
MessageType = ContentBrowserUtils::EDisplayMessageType::Successful;
}
else
{
if (DroppedAssets.Num() == 1)
{
FFormatNamedArguments Args;
Args.Add(TEXT("AssetName"), FText::FromName(DroppedAssets[0].AssetName));
Args.Add(TEXT("CollectionName"), FText::FromName(CollectionItem->CollectionName));
Message = FText::Format(LOCTEXT("CollectionAssetAdded", "Added {AssetName} to {CollectionName}"), Args);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("Number"), NumAdded);
Args.Add(TEXT("CollectionName"), FText::FromName(CollectionItem->CollectionName));
Message = FText::Format(LOCTEXT("CollectionAssetsAdded", "Added {Number} asset(s) to {CollectionName}"), Args);
}
MessageType = ContentBrowserUtils::EDisplayMessageType::Info;
}
const double DurationSec = FPlatformTime::Seconds() - BeginTimeSec;
{
FAssetAddedToCollectionTelemetryEvent AssetAdded;
AssetAdded.DurationSec = DurationSec;
AssetAdded.NumAdded = NumAdded;
AssetAdded.CollectionShareType = CollectionItem->CollectionType;
AssetAdded.Workflow = ECollectionTelemetryAssetAddedWorkflow::DragAndDrop;
FTelemetryRouter::Get().ProvideTelemetry(AssetAdded);
}
}
// Added items to the collection or failed. Either way, display the message.
ContentBrowserUtils::DisplayMessage(Message, Geometry.GetLayoutBoundingRect(), SharedThis(this), MessageType);
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::HandleSettingChanged(FName PropertyName)
{
if (PropertyName == GET_MEMBER_NAME_CHECKED(UContentBrowserSettings, bDisplayExcludedCollections))
{
UpdateFilteredCollectionItems();
}
}
void SCollectionView::ExpandParentItems(const TSharedRef<FCollectionItem>& InCollectionItem)
{
for (TSharedPtr<FCollectionItem> CollectionItemToExpand = InCollectionItem->ParentCollection.Pin();
CollectionItemToExpand.IsValid();
CollectionItemToExpand = CollectionItemToExpand->ParentCollection.Pin()
)
{
CollectionTreePtr->SetItemExpansion(CollectionItemToExpand, true);
}
}
TSharedPtr<SWidget> SCollectionView::MakeCollectionTreeContextMenu()
{
if ( !bAllowRightClickMenu )
{
return NULL;
}
return CollectionContextMenu->MakeCollectionTreeContextMenu(Commands);
}
bool SCollectionView::IsCollectionCheckBoxEnabled( TSharedPtr<FCollectionItem> CollectionItem ) const
{
return QuickAssetManagement.IsValid() && QuickAssetManagement->IsCollectionEnabled(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
ECheckBoxState SCollectionView::IsCollectionChecked( TSharedPtr<FCollectionItem> CollectionItem ) const
{
if ( QuickAssetManagement.IsValid() )
{
return QuickAssetManagement->GetCollectionCheckState(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
return ECheckBoxState::Unchecked;
}
void SCollectionView::OnCollectionCheckStateChanged( ECheckBoxState NewState, TSharedPtr<FCollectionItem> CollectionItem )
{
if ( QuickAssetManagement.IsValid() )
{
FCollectionNameType CollectionKey(CollectionItem->CollectionName, CollectionItem->CollectionType);
if (QuickAssetManagement->GetCollectionCheckState(CollectionKey) == ECheckBoxState::Checked)
{
QuickAssetManagement->RemoveCurrentAssetsFromCollection(CollectionKey);
}
else
{
QuickAssetManagement->AddCurrentAssetsToCollection(CollectionKey);
}
}
}
void SCollectionView::CollectionSelectionChanged( TSharedPtr< FCollectionItem > CollectionItem, ESelectInfo::Type /*SelectInfo*/ )
{
if ( ShouldAllowSelectionChangedDelegate() && OnCollectionSelected.IsBound() )
{
if ( CollectionItem.IsValid() )
{
OnCollectionSelected.Execute(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
else
{
OnCollectionSelected.Execute(FCollectionNameType(NAME_None, ECollectionShareType::CST_All));
}
}
}
void SCollectionView::CollectionItemScrolledIntoView( TSharedPtr<FCollectionItem> CollectionItem, const TSharedPtr<ITableRow>& Widget )
{
if ( CollectionItem->bRenaming && Widget.IsValid() && Widget->GetContent().IsValid() )
{
CollectionItem->OnRenamedRequestEvent.Broadcast();
}
}
bool SCollectionView::IsCollectionNameReadOnly() const
{
// We can't rename collections while they're being dragged
TSharedPtr<FCollectionDragDropOp> DragDropOp = CurrentCollectionDragDropOp.Pin();
if (DragDropOp.IsValid())
{
TArray<TSharedPtr<FCollectionItem>> SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
for (const auto& SelectedCollectionItem : SelectedCollectionItems)
{
if (DragDropOp->CollectionRefs.ContainsByPredicate([&SelectedCollectionItem](const FCollectionRef& Collection)
{
return SelectedCollectionItem->CollectionContainer == Collection.Container &&
SelectedCollectionItem->CollectionName == Collection.Name &&
SelectedCollectionItem->CollectionType == Collection.Type;
}))
{
return true;
}
}
}
CollectionContextMenu->UpdateProjectSourceControl();
return !CollectionContextMenu->CanRenameSelectedCollections();
}
bool SCollectionView::CollectionNameChangeCommit( const TSharedPtr< FCollectionItem >& CollectionItem, const FString& NewName, bool bChangeConfirmed, FText& OutWarningMessage )
{
if (!ensure(CollectionItem->CollectionContainer))
{
return false;
}
// If new name is empty, set it back to the original name
const FName NewNameFinal( NewName.IsEmpty() ? CollectionItem->CollectionName : FName(*NewName) );
if ( CollectionItem->bNewCollection )
{
CollectionItem->bNewCollection = false;
// Cache this here as CreateCollection will invalidate the current parent pointer
TOptional<FCollectionNameType> NewCollectionParentKey;
TSharedPtr<FCollectionItem> ParentCollectionItem = CollectionItem->ParentCollection.Pin();
if ( ParentCollectionItem.IsValid() )
{
NewCollectionParentKey = FCollectionNameType(ParentCollectionItem->CollectionName, ParentCollectionItem->CollectionType);
}
double BeginTimeSec = FPlatformTime::Seconds();
// If we canceled the name change when creating a new asset, we want to silently remove it
if ( !bChangeConfirmed )
{
AvailableCollections.Remove(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
UpdateFilteredCollectionItems();
return false;
}
FText Error;
if ( !CollectionItem->CollectionContainer->CreateCollection(NewNameFinal, CollectionItem->CollectionType, CollectionItem->StorageMode, &Error) )
{
// Failed to add the collection, remove it from the list
AvailableCollections.Remove(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
UpdateFilteredCollectionItems();
OutWarningMessage = FText::Format( LOCTEXT("CreateCollectionFailed", "Failed to create the collection. {0}"), Error);
return false;
}
// Since we're really adding a new collection (as our placeholder item is currently transient), we don't get a rename event from the collections manager
// We'll spoof one here that so that our placeholder tree item is updated with the final name - this will preserve its expansion and selection state
HandleCollectionRenamed(
*CollectionItem->CollectionContainer,
FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType),
FCollectionNameType(NewNameFinal, CollectionItem->CollectionType)
);
if ( NewCollectionParentKey.IsSet() )
{
// Try and set the parent correctly (if this fails for any reason, the collection will still be added, but will just appear at the root)
CollectionItem->CollectionContainer->ReparentCollection(NewNameFinal, CollectionItem->CollectionType, NewCollectionParentKey->Name, NewCollectionParentKey->Type);
}
// Notify anything that cares that this collection has been created now
if ( CollectionItem->OnCollectionCreatedEvent.IsBound())
{
CollectionItem->OnCollectionCreatedEvent.Execute(FCollectionNameType(NewNameFinal, CollectionItem->CollectionType));
CollectionItem->OnCollectionCreatedEvent.Unbind();
}
{
FCollectionCreatedTelemetryEvent CollectionCreatedEvent;
CollectionCreatedEvent.DurationSec = FPlatformTime::Seconds() - BeginTimeSec;
CollectionCreatedEvent.CollectionShareType = CollectionItem->CollectionType;
FTelemetryRouter::Get().ProvideTelemetry(CollectionCreatedEvent);
}
}
else
{
// If the old name is the same as the new name, just early exit here.
if ( CollectionItem->CollectionName == NewNameFinal )
{
return true;
}
// If the new name doesn't pass our current filter, we need to clear it
if ( !CollectionItemTextFilter->PassesFilter( FCollectionItem(CollectionItem->CollectionContainer.ToSharedRef(), NewNameFinal, CollectionItem->CollectionType) ) )
{
SearchPtr->ClearSearch();
}
// Otherwise perform the rename
FText Error;
if ( !CollectionItem->CollectionContainer->RenameCollection(CollectionItem->CollectionName, CollectionItem->CollectionType, NewNameFinal, CollectionItem->CollectionType, &Error) )
{
// Failed to rename the collection
OutWarningMessage = FText::Format( LOCTEXT("RenameCollectionFailed", "Failed to rename the collection. {0}"), Error);
return false;
}
}
// At this point CollectionItem is no longer a member of the CollectionItems list (as the list is repopulated by
// UpdateCollectionItems, which is called by a broadcast from CollectionManagerModule::RenameCollection, above).
// So search again for the item by name and type.
auto NewCollectionItemPtr = AvailableCollections.Find( FCollectionNameType(NewNameFinal, CollectionItem->CollectionType) );
// Reselect the path to notify that the selection has changed
{
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
CollectionTreePtr->ClearSelection();
}
// Set the selection
if (NewCollectionItemPtr)
{
const auto& NewCollectionItem = *NewCollectionItemPtr;
CollectionTreePtr->RequestScrollIntoView(NewCollectionItem);
CollectionTreePtr->SetItemSelection(NewCollectionItem, true);
}
return true;
}
bool SCollectionView::CollectionVerifyRenameCommit(const TSharedPtr< FCollectionItem >& CollectionItem, const FString& NewName, const FSlateRect& MessageAnchor, FText& OutErrorMessage)
{
// If the new name is the same as the old name, consider this to be unchanged, and accept it.
if (CollectionItem->CollectionName.ToString() == NewName)
{
return true;
}
if (!ensure(CollectionItem->CollectionContainer) || !CollectionItem->CollectionContainer->IsValidCollectionName(NewName, ECollectionShareType::CST_All, &OutErrorMessage))
{
return false;
}
return true;
}
#undef LOCTEXT_NAMESPACE