// Copyright Epic Games, Inc. All Rights Reserved. #include "DragDropHandler.h" #include "Layout/WidgetPath.h" #include "Framework/Application/MenuStack.h" #include "Framework/Application/SlateApplication.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ToolMenus.h" #include "Styling/AppStyle.h" #include "AssetToolsModule.h" #include "Misc/NamePermissionList.h" #include "DragAndDrop/AssetDragDropOp.h" #include "ContentBrowserUtils.h" #include "ContentBrowserItem.h" #include "ContentBrowserDataSource.h" #include "ContentBrowserDataDragDropOp.h" #include "ContentBrowserDataMenuContexts.h" #define LOCTEXT_NAMESPACE "ContentBrowser" namespace DragDropHandler { TSharedPtr CreateDragOperation(TArrayView InItems) { if (InItems.Num() == 0) { return nullptr; } // Batch these by their data sources TMap> SourcesAndItems; for (const FContentBrowserItem& Item : InItems) { FContentBrowserItem::FItemDataArrayView ItemDataArray = Item.GetInternalItems(); for (const FContentBrowserItemData& ItemData : ItemDataArray) { if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource()) { TArray& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource); ItemsForSource.Add(ItemData); } } } // Custom handling via a data source? for (const auto& SourceAndItemsPair : SourcesAndItems) { if (TSharedPtr CustomDragOp = SourceAndItemsPair.Key->CreateCustomDragOperation(SourceAndItemsPair.Value)) { return CustomDragOp; } } // Generic handling return FContentBrowserDataDragDropOp::New(InItems); } template 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 ContentDragDropOp = InDragDropEvent.GetOperationAs()) { 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; for (const FContentBrowserItem& DraggedItem : ContentDragDropOp->GetDraggedItems()) { const bool bCanMoveOrCopy = DraggedItem.CanMove(InItem.GetVirtualPath()) || DraggedItem.CanCopy(InItem.GetVirtualPath()); if (bCanMoveOrCopy) { ++NumCanMoveOrCopy; } } if (NumCanMoveOrCopy == 0) { ContentDragDropOp->SetToolTip(LOCTEXT("OnDragFoldersOverFolder_CannotDrop", "Cannot move or copy to this folder"), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"))); } else { const FText FirstItemText = ContentDragDropOp->GetDraggedItems()[0].GetDisplayName(); const FText MoveOrCopyText = (NumCanMoveOrCopy > 1) ? FText::Format(LOCTEXT("OnDragAssetsOverFolder_MultipleItems", "Move or copy '{0}' and {1} {1}|plural(one=other,other=others)"), FirstItemText, NumDraggedItems - 1) : FText::Format(LOCTEXT("OnDragAssetsOverFolder_SingularItems", "Move or copy '{0}'"), 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 ContentDragDropOp = InDragDropEvent.GetOperationAs()) { ContentDragDropOp->ResetToDefaultToolTip(); return true; } return false; } template void HandleDragDropMoveOrCopy(const FContentBrowserItem& InDropTargetItem, const TArray& InDraggedItems, const TSharedPtr& InParentWidget, const FText InMoveOrCopyMsg, CanMoveOrCopyFuncType InCanMoveOrCopyFunc, BulkMoveOrCopyFuncType InBulkMoveOrCopyFunc) { const FName InDropTargetPath = InDropTargetItem.GetVirtualPath(); // Batch these by their data sources TMap> 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& 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 (NumMovedOrCopiedItems > 0 && InParentWidget) { const FText Message = FText::Format(InMoveOrCopyMsg, NumMovedOrCopiedItems, FText::FromName(InDropTargetPath)); const FVector2D& CursorPos = FSlateApplication::Get().GetCursorPos(); FSlateRect MessageAnchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y); ContentBrowserUtils::DisplayMessage(Message, MessageAnchor, InParentWidget.ToSharedRef()); } } void HandleDragDropMove(const FContentBrowserItem& InDropTargetItem, const TArray& InDraggedItems, const TSharedPtr& 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& InDraggedItems, const TSharedPtr& 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& InParentWidget) { // Custom handling via a data source? if (HandleDragEventOverride(InItem, InDragDropEvent, &UContentBrowserDataSource::HandleDragDropOnItem)) { return true; } if (!InItem.IsFolder()) { return false; } // Generic handling if (TSharedPtr ContentDragDropOp = InDragDropEvent.GetOperationAs()) { 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(); 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(); 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 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