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

429 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DragDropHandler.h"
#include "AssetViewUtils.h"
#include "CollectionViewUtils.h"
#include "Containers/Array.h"
#include "Containers/Map.h"
#include "ContentBrowserDataDragDropOp.h"
#include "ContentBrowserDataMenuContexts.h"
#include "ContentBrowserDataSource.h"
#include "ContentBrowserItem.h"
#include "ContentBrowserItemData.h"
#include "ContentBrowserUtils.h"
#include "Delegates/Delegate.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UIAction.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "Input/DragAndDrop.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/SlateRect.h"
#include "Layout/WidgetPath.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/EnumClassFlags.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Templates/Invoke.h"
#include "Templates/Tuple.h"
#include "Textures/SlateIcon.h"
#include "ToolMenu.h"
#include "ToolMenuContext.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuSection.h"
#include "ToolMenus.h"
#include "UObject/NameTypes.h"
#include "UObject/UObjectGlobals.h"
#define LOCTEXT_NAMESPACE "ContentBrowser"
namespace DragDropHandlerUtilsPrivate
{
void RetrieveFolderInformation(const FContentBrowserItem& InFolder, FName& OutFolderBrushName, FName& OutFolderShadowName, FLinearColor& OutFolderOverrideColor)
{
// We are just dragging folders so add information on the FolderBrush to use and the color
if (InFolder.IsValid() && InFolder.IsFolder())
{
ContentBrowserUtils::TryGetFolderBrushAndShadowName(InFolder, OutFolderBrushName, OutFolderShadowName);
const bool bCollectionFolder = EnumHasAnyFlags(InFolder.GetItemCategory(), EContentBrowserItemFlags::Category_Collection);
if (bCollectionFolder)
{
TSharedPtr<ICollectionContainer> CollectionContainer;
FName CollectionName;
ECollectionShareType::Type CollectionFolderShareType = ECollectionShareType::CST_All;
ContentBrowserUtils::IsCollectionPath(InFolder.GetVirtualPath().ToString(), &CollectionContainer, &CollectionName, &CollectionFolderShareType);
if (TOptional<FLinearColor> Color = CollectionViewUtils::GetCustomColor(CollectionContainer.Get(), CollectionName, CollectionFolderShareType))
{
OutFolderOverrideColor = Color.GetValue();
}
}
else
{
if (TOptional<FLinearColor> Color = ContentBrowserUtils::GetPathColor(InFolder.GetInvariantPath().ToString()))
{
OutFolderOverrideColor = Color.GetValue();
}
}
}
}
}
namespace DragDropHandler
{
TSharedPtr<FDragDropOperation> CreateDragOperation(TArrayView<const FContentBrowserItem> InItems)
{
if (InItems.Num() == 0)
{
return nullptr;
}
// Batch these by their data sources
TMap<UContentBrowserDataSource*, TArray<FContentBrowserItemData>> SourcesAndItems;
for (const FContentBrowserItem& Item : InItems)
{
FContentBrowserItem::FItemDataArrayView ItemDataArray = Item.GetInternalItems();
for (const FContentBrowserItemData& ItemData : ItemDataArray)
{
if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource())
{
TArray<FContentBrowserItemData>& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource);
ItemsForSource.Add(ItemData);
}
}
}
// Custom handling via a data source?
for (const auto& SourceAndItemsPair : SourcesAndItems)
{
if (TSharedPtr<FDragDropOperation> CustomDragOp = SourceAndItemsPair.Key->CreateCustomDragOperation(SourceAndItemsPair.Value))
{
return CustomDragOp;
}
}
// Generic handling
FName FolderBrushName = NAME_None;
FName FolderShadowBrushName = NAME_None;
FLinearColor FolderColor = ContentBrowserUtils::GetDefaultColor();
// This information will be later used by the ContentBrowserDragAnDropOp in case you are dragging only folders
if (InItems.Num() > 0)
{
DragDropHandlerUtilsPrivate::RetrieveFolderInformation(InItems[0], FolderBrushName, FolderShadowBrushName, FolderColor);
}
FThumbnailOverrideParams FolderThumbnailOverrideParams;
FolderThumbnailOverrideParams.FolderBrushName = FolderBrushName;
FolderThumbnailOverrideParams.FolderShadowBrushName = FolderShadowBrushName;
FolderThumbnailOverrideParams.FolderColorOverride = FolderColor;
TSharedRef<FContentBrowserDataDragDropOp> ContentBrowserDragAndDrop = FContentBrowserDataDragDropOp::New(InItems, FolderThumbnailOverrideParams);
return ContentBrowserDragAndDrop;
}
template <typename FuncType>
bool HandleDragEventOverride(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent, FuncType Func)
{
FContentBrowserItem::FItemDataArrayView ItemDataArray = InItem.GetInternalItems();
for (const FContentBrowserItemData& ItemData : ItemDataArray)
{
if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource())
{
if (Invoke(Func, ItemDataSource, ItemData, InDragDropEvent))
{
return true;
}
}
}
return false;
}
bool ValidateGenericDragEvent(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent)
{
if (!InItem.IsFolder())
{
return false;
}
if (TSharedPtr<FContentBrowserDataDragDropOp> ContentDragDropOp = InDragDropEvent.GetOperationAs<FContentBrowserDataDragDropOp>())
{
if (EnumHasAnyFlags(InItem.GetItemCategory(), EContentBrowserItemFlags::Category_Collection))
{
ContentDragDropOp->SetToolTip(LOCTEXT("OnDragFoldersOverFolder_CannotDropOnCollectionFolder", "Cannot drop onto a collection folder. Drop onto the collection in the collection view instead."), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
}
else if (ContentDragDropOp->GetDraggedItems().Num() == 1 && ContentDragDropOp->GetDraggedItems()[0].GetVirtualPath() == InItem.GetVirtualPath())
{
ContentDragDropOp->SetToolTip(LOCTEXT("OnDragFoldersOverFolder_CannotSelfDrop", "Cannot move or copy a folder onto itself"), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
}
else
{
int32 NumDraggedItems = ContentDragDropOp->GetDraggedItems().Num();
int32 NumCanMoveOrCopy = 0;
bool bCanCopyOnce = false;
bool bCanMoveOnce = false;
for (const FContentBrowserItem& DraggedItem : ContentDragDropOp->GetDraggedItems())
{
const bool bCanMove = DraggedItem.CanMove(InItem.GetVirtualPath());
const bool bCanCopy = DraggedItem.CanCopy(InItem.GetVirtualPath());
bCanCopyOnce |= bCanCopy;
bCanMoveOnce |= bCanMove;
if (bCanMove || bCanCopy)
{
++NumCanMoveOrCopy;
}
}
if (NumCanMoveOrCopy == 0)
{
ContentDragDropOp->SetToolTip(LOCTEXT("OnDragFoldersOverFolder_CannotDrop", "Cannot move or copy to this folder"), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
}
else
{
FText MoveOrCopyString = FText::GetEmpty();
if (bCanCopyOnce && bCanMoveOnce)
{
MoveOrCopyString = LOCTEXT("DragAndDropMoveOrCopy", "Move or copy");
}
else if (bCanCopyOnce)
{
MoveOrCopyString = LOCTEXT("DragAndDropCopy", "Copy");
}
else
{
MoveOrCopyString = LOCTEXT("DragAndDropMove", "Move");
}
const FText FirstItemText = ContentDragDropOp->GetDraggedItems()[0].GetDisplayName();
const FText MoveOrCopyText = (NumCanMoveOrCopy > 1)
? FText::Format(LOCTEXT("OnDragAssetsOverFolder_MultipleItems", "{0} '{1}' and {2} {2}|plural(one=other,other=others)"), MoveOrCopyString, FirstItemText, NumDraggedItems - 1)
: FText::Format(LOCTEXT("OnDragAssetsOverFolder_SingularItems", "{0} '{1}'"), MoveOrCopyString, FirstItemText);
if (NumCanMoveOrCopy < NumDraggedItems)
{
ContentDragDropOp->SetToolTip(FText::Format(LOCTEXT("OnDragAssetsOverFolder_SomeInvalidItems", "{0}\n\n{1} {1}|plural(one=item,other=items) will be ignored as they cannot be moved or copied"), MoveOrCopyText, NumDraggedItems - NumCanMoveOrCopy), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OKWarn")));
}
else
{
ContentDragDropOp->SetToolTip(MoveOrCopyText, FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")));
}
}
}
return true;
}
return false;
}
bool HandleDragEnterItem(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent)
{
// Custom handling via a data source?
if (HandleDragEventOverride(InItem, InDragDropEvent, &UContentBrowserDataSource::HandleDragEnterItem))
{
return true;
}
// Generic handling
return ValidateGenericDragEvent(InItem, InDragDropEvent);
}
bool HandleDragOverItem(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent)
{
// Custom handling via a data source?
if (HandleDragEventOverride(InItem, InDragDropEvent, &UContentBrowserDataSource::HandleDragOverItem))
{
return true;
}
// Generic handling
return ValidateGenericDragEvent(InItem, InDragDropEvent);
}
bool HandleDragLeaveItem(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent)
{
// Custom handling via a data source?
if (HandleDragEventOverride(InItem, InDragDropEvent, &UContentBrowserDataSource::HandleDragLeaveItem))
{
return true;
}
if (!InItem.IsFolder())
{
return false;
}
// Generic handling
if (TSharedPtr<FContentBrowserDataDragDropOp> ContentDragDropOp = InDragDropEvent.GetOperationAs<FContentBrowserDataDragDropOp>())
{
ContentDragDropOp->ResetToDefaultToolTip();
return true;
}
return false;
}
template <typename CanMoveOrCopyFuncType, typename BulkMoveOrCopyFuncType>
void HandleDragDropMoveOrCopy(const FContentBrowserItem& InDropTargetItem, const TArray<FContentBrowserItem>& InDraggedItems, const TSharedPtr<SWidget>& InParentWidget, const FText InMoveOrCopyMsg, CanMoveOrCopyFuncType InCanMoveOrCopyFunc, BulkMoveOrCopyFuncType InBulkMoveOrCopyFunc)
{
const FName InDropTargetPath = InDropTargetItem.GetVirtualPath();
// Batch these by their data sources
TMap<UContentBrowserDataSource*, TArray<FContentBrowserItemData>> SourcesAndItems;
for (const FContentBrowserItem& DraggedItem : InDraggedItems)
{
FContentBrowserItem::FItemDataArrayView ItemDataArray = DraggedItem.GetInternalItems();
for (const FContentBrowserItemData& ItemData : ItemDataArray)
{
if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource())
{
FText MoveOrCopyErrorMsg;
if (Invoke(InCanMoveOrCopyFunc, *ItemDataSource, ItemData, InDropTargetPath, &MoveOrCopyErrorMsg))
{
TArray<FContentBrowserItemData>& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource);
ItemsForSource.Add(ItemData);
}
else
{
AssetViewUtils::ShowErrorNotifcation(MoveOrCopyErrorMsg);
}
}
}
}
// Execute the operation now
int32 NumMovedOrCopiedItems = 0;
for (const auto& SourceAndItemsPair : SourcesAndItems)
{
if (Invoke(InBulkMoveOrCopyFunc, *SourceAndItemsPair.Key, SourceAndItemsPair.Value, InDropTargetPath))
{
// This assumes that everything passed is moved or copied, which may not be true, but we've validated as best we can when building this array
NumMovedOrCopiedItems += SourceAndItemsPair.Value.Num();
}
}
// Show a message if the move or copy was successful
if (InParentWidget)
{
const bool bWasSuccessful = NumMovedOrCopiedItems > 0;
const ContentBrowserUtils::EDisplayMessageType MessageType = bWasSuccessful ? ContentBrowserUtils::EDisplayMessageType::Successful : ContentBrowserUtils::EDisplayMessageType::Info;
const FText Message = FText::Format(InMoveOrCopyMsg, NumMovedOrCopiedItems, InDropTargetItem.GetDisplayName());
const FVector2f& CursorPos = FSlateApplication::Get().GetCursorPos();
FSlateRect MessageAnchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y);
ContentBrowserUtils::DisplayMessage(Message, MessageAnchor, InParentWidget.ToSharedRef(), MessageType);
}
}
void HandleDragDropMove(const FContentBrowserItem& InDropTargetItem, const TArray<FContentBrowserItem>& InDraggedItems, const TSharedPtr<SWidget>& InParentWidget)
{
return HandleDragDropMoveOrCopy(InDropTargetItem, InDraggedItems, InParentWidget, LOCTEXT("ItemsDroppedMove", "{0} {0}|plural(one=item,other=items) moved to '.../{1}'"), &UContentBrowserDataSource::CanMoveItem, &UContentBrowserDataSource::BulkMoveItems);
}
void HandleDragDropCopy(const FContentBrowserItem& InDropTargetItem, const TArray<FContentBrowserItem>& InDraggedItems, const TSharedPtr<SWidget>& InParentWidget)
{
return HandleDragDropMoveOrCopy(InDropTargetItem, InDraggedItems, InParentWidget, LOCTEXT("ItemsDroppedCopy", "{0} {0}|plural(one=item,other=items) copied to '.../{1}'"), &UContentBrowserDataSource::CanCopyItem, &UContentBrowserDataSource::BulkCopyItems);
}
bool HandleDragDropOnItem(const FContentBrowserItem& InItem, const FDragDropEvent& InDragDropEvent, const TSharedRef<SWidget>& InParentWidget)
{
// Custom handling via a data source?
if (HandleDragEventOverride(InItem, InDragDropEvent, &UContentBrowserDataSource::HandleDragDropOnItem))
{
return true;
}
if (!InItem.IsFolder())
{
return false;
}
// Generic handling
if (TSharedPtr<FContentBrowserDataDragDropOp> ContentDragDropOp = InDragDropEvent.GetOperationAs<FContentBrowserDataDragDropOp>())
{
static const FName MenuName = "ContentBrowser.DragDropContextMenu";
UToolMenus* ToolMenus = UToolMenus::Get();
if (!ToolMenus->IsMenuRegistered(MenuName))
{
UToolMenu* Menu = ToolMenus->RegisterMenu(MenuName);
FToolMenuSection& Section = Menu->AddSection("MoveCopy", LOCTEXT("MoveCopyMenuHeading_Generic", "Move/Copy..."));
Section.AddDynamicEntry("DragDropMoveCopy_Dynamic", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
const UContentBrowserDataMenuContext_DragDropMenu* ContextObject = InSection.FindContext<UContentBrowserDataMenuContext_DragDropMenu>();
checkf(ContextObject, TEXT("Required context UContentBrowserDataMenuContext_DragDropMenu was missing!"));
if (ContextObject->bCanMove)
{
InSection.AddMenuEntry(
"DragDropMove",
LOCTEXT("DragDropMove", "Move Here"),
LOCTEXT("DragDropMoveTooltip", "Move the dragged items to this folder, preserving the structure of any copied folders."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([DropTargetItem = ContextObject->DropTargetItem, DraggedItems = ContextObject->DraggedItems, ParentWidget = ContextObject->ParentWidget]() { HandleDragDropMove(DropTargetItem, DraggedItems, ParentWidget.Pin()); }))
);
}
if (ContextObject->bCanCopy)
{
InSection.AddMenuEntry(
"DragDropCopy",
LOCTEXT("DragDropCopy", "Copy Here"),
LOCTEXT("DragDropCopyTooltip", "Copy the dragged items to this folder, preserving the structure of any copied folders."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([DropTargetItem = ContextObject->DropTargetItem, DraggedItems = ContextObject->DraggedItems, ParentWidget = ContextObject->ParentWidget]() { HandleDragDropCopy(DropTargetItem, DraggedItems, ParentWidget.Pin()); }))
);
}
}));
}
if (UToolMenu* Menu = ToolMenus->ExtendMenu(MenuName))
{
// Update the section display name for the current drop target
Menu->AddSection("MoveCopy", FText::Format(LOCTEXT("MoveCopyMenuHeading_Fmt", "Move/Copy to {0}"), InItem.GetDisplayName()));
}
UContentBrowserDataMenuContext_DragDropMenu* ContextObject = NewObject<UContentBrowserDataMenuContext_DragDropMenu>();
ContextObject->DropTargetItem = InItem;
ContextObject->DraggedItems = ContentDragDropOp->GetDraggedItems();
ContextObject->bCanMove = false;
ContextObject->bCanCopy = false;
for (const FContentBrowserItem& DraggedItem : ContextObject->DraggedItems)
{
ContextObject->bCanMove |= DraggedItem.CanMove(ContextObject->DropTargetItem.GetVirtualPath());
ContextObject->bCanCopy |= DraggedItem.CanCopy(ContextObject->DropTargetItem.GetVirtualPath());
if (ContextObject->bCanMove && ContextObject->bCanCopy)
{
break;
}
}
ContextObject->ParentWidget = InParentWidget;
FToolMenuContext MenuContext(ContextObject);
TSharedRef<SWidget> MenuWidget = ToolMenus->GenerateWidget(MenuName, MenuContext);
FSlateApplication::Get().PushMenu(
InParentWidget,
FWidgetPath(),
MenuWidget,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
);
return true;
}
return false;
}
} // namespace DragDropHandler
#undef LOCTEXT_NAMESPACE