// 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 CollectionContainer; FName CollectionName; ECollectionShareType::Type CollectionFolderShareType = ECollectionShareType::CST_All; ContentBrowserUtils::IsCollectionPath(InFolder.GetVirtualPath().ToString(), &CollectionContainer, &CollectionName, &CollectionFolderShareType); if (TOptional Color = CollectionViewUtils::GetCustomColor(CollectionContainer.Get(), CollectionName, CollectionFolderShareType)) { OutFolderOverrideColor = Color.GetValue(); } } else { if (TOptional Color = ContentBrowserUtils::GetPathColor(InFolder.GetInvariantPath().ToString())) { OutFolderOverrideColor = Color.GetValue(); } } } } } 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 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 ContentBrowserDragAndDrop = FContentBrowserDataDragDropOp::New(InItems, FolderThumbnailOverrideParams); return ContentBrowserDragAndDrop; } 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; 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 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 (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& 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