diff --git a/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs b/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs index 7d545c5c76df..aa57afe8c16d 100644 --- a/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs +++ b/src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs @@ -9,10 +9,13 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; using System.IO; +using Vanara.PInvoke; +using Windows.ApplicationModel.DataTransfer; using Windows.System; using Windows.UI.Core; using Windows.Win32; using Windows.Win32.UI.WindowsAndMessaging; +using WinRT; namespace Files.App.Helpers { @@ -416,5 +419,20 @@ public static void AddItemsToOverflowMenu(AppBarButton? overflowItem, ContextMen } } } + + public static void InvokeRightButtonDropMenu(string folderPath, DataPackageView dataView, DataPackageOperation acceptedOperation) + { + using var sf = new Vanara.Windows.Shell.ShellItem(folderPath); + if (!sf.IsFolder) + return; + + PInvoke.GetCursorPos(out var dropPoint); + + var dataObjectProvider = dataView.As(); + var iddo = dataObjectProvider.GetDataObject(); + var dropTarget = sf.GetHandler(Shell32.BHID.BHID_SFViewObject); + dropTarget.DragEnter(iddo, MouseButtonState.MK_RBUTTON, new() { X = dropPoint.X, Y = dropPoint.Y }, (Ole32.DROPEFFECT)acceptedOperation); + dropTarget.Drop(iddo, MouseButtonState.MK_RBUTTON, new() { X = dropPoint.X, Y = dropPoint.Y }, (Ole32.DROPEFFECT)acceptedOperation); + } } } diff --git a/src/Files.App/Helpers/AutomaticDragHelper.cs b/src/Files.App/Helpers/AutomaticDragHelper.cs new file mode 100644 index 000000000000..13c17d0b3824 --- /dev/null +++ b/src/Files.App/Helpers/AutomaticDragHelper.cs @@ -0,0 +1,333 @@ +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Input; +using Windows.ApplicationModel.DataTransfer; +using Windows.Foundation; +using Windows.Win32; + +namespace Files.App.Helpers +{ + // https://github.com/microsoft/microsoft-ui-xaml/blob/winui3/release/1.6.3/src/dxaml/xcp/dxaml/lib/AutomaticDragHelper.cpp + public class AutomaticDragHelper : DependencyObject + { + // Added attached dependency property to unregister event handlers + public static AutomaticDragHelper GetDragHelper(DependencyObject obj) + { + return (AutomaticDragHelper)obj.GetValue(DragHelperProperty); + } + + public static void SetDragHelper(DependencyObject obj, AutomaticDragHelper value) + { + obj.SetValue(DragHelperProperty, value); + } + + public static readonly DependencyProperty DragHelperProperty = + DependencyProperty.RegisterAttached("DragHelper", typeof(AutomaticDragHelper), typeof(AutomaticDragHelper), new PropertyMetadata(null, OnDragHelperChanged)); + + private static void OnDragHelperChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.OldValue is AutomaticDragHelper old) + old.StopDetectingDrag(); + } + + // The standard Windows mouse drag box size is defined by SM_CXDRAG and SM_CYDRAG. + // UIElement uses the standard box size with dimensions multiplied by this constant. + // This arrangement is in place as accidentally triggering a drag was deemed too easy while + // selecting several items with the mouse in quick succession. + private const double UIELEMENT_MOUSE_DRAG_THRESHOLD_MULTIPLIER = 2.0; + private readonly UIElement m_pOwnerNoRef; + private readonly bool m_shouldAddInputHandlers; + private bool m_isCheckingForMouseDrag; + private Point m_lastMouseRightButtonDownPosition; + private bool m_dragDropPointerPressedToken, m_dragDropPointerMovedToken, m_dragDropPointerReleasedToken, m_dragDropPointerCaptureLostToken/*, m_dragDropHoldingToken*/; + //private PointerPoint m_spPointerPoint; + //private Pointer m_spPointer; + //private bool m_isHoldingCompleted; + private bool m_isRightButtonPressed; + + public AutomaticDragHelper(UIElement pUIElement, bool shouldAddInputHandlers) + { + m_pOwnerNoRef = pUIElement; + m_shouldAddInputHandlers = shouldAddInputHandlers; + } + + // Begin tracking the mouse cursor in order to fire a drag start if the pointer + // moves a certain distance away from m_lastMouseRightButtonDownPosition. + public void BeginCheckingForMouseDrag(Pointer pPointer) + { + bool captured = m_pOwnerNoRef.CapturePointer(pPointer); + + m_isCheckingForMouseDrag = !!captured; + } + + // Stop tracking the mouse cursor. + public void StopCheckingForMouseDrag(Pointer pPointer) + { + // Do not call ReleasePointerCapture() more times than we called CapturePointer() + if (m_isCheckingForMouseDrag) + { + m_isCheckingForMouseDrag = false; + + m_pOwnerNoRef.ReleasePointerCapture(pPointer); + } + } + + // Return true if we're tracking the mouse and newMousePosition is outside the drag + // rectangle centered at m_lastMouseRightButtonDownPosition (see IsOutsideDragRectangle). + bool ShouldStartMouseDrag(Point newMousePosition) + { + return m_isCheckingForMouseDrag && IsOutsideDragRectangle(newMousePosition, m_lastMouseRightButtonDownPosition); + } + + // Returns true if testPoint is outside of the rectangle + // defined by the SM_CXDRAG and SM_CYDRAG system metrics and + // dragRectangleCenter. + bool IsOutsideDragRectangle(Point testPoint, Point dragRectangleCenter) + { + double dx = Math.Abs(testPoint.X - dragRectangleCenter.X); + double dy = Math.Abs(testPoint.Y - dragRectangleCenter.Y); + + double maxDx = PInvoke.GetSystemMetrics(Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX.SM_CXDRAG); + double maxDy = PInvoke.GetSystemMetrics(Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX.SM_CYDRAG); + + maxDx *= UIELEMENT_MOUSE_DRAG_THRESHOLD_MULTIPLIER; + maxDy *= UIELEMENT_MOUSE_DRAG_THRESHOLD_MULTIPLIER; + + return (dx > maxDx || dy > maxDy); + } + + public void StartDetectingDrag() + { + if (m_shouldAddInputHandlers && !m_dragDropPointerPressedToken) + { + m_pOwnerNoRef.PointerPressed += HandlePointerPressedEventArgs; + m_dragDropPointerPressedToken = true; + } + } + + public void StopDetectingDrag() + { + if (m_dragDropPointerPressedToken) + { + m_pOwnerNoRef.PointerPressed -= HandlePointerPressedEventArgs; + // zero out the token; + m_dragDropPointerPressedToken = false; + } + } + + public void RegisterDragPointerEvents() + { + if (m_shouldAddInputHandlers) + { + // Hookup pointer events so we can catch and handle it for drag and drop. + if (!m_dragDropPointerMovedToken) + { + m_pOwnerNoRef.PointerMoved += HandlePointerMovedEventArgs; + m_dragDropPointerMovedToken = true; + } + + if (!m_dragDropPointerReleasedToken) + { + m_pOwnerNoRef.PointerReleased += HandlePointerReleasedEventArgs; + m_dragDropPointerReleasedToken = true; + } + + if (!m_dragDropPointerCaptureLostToken) + { + m_pOwnerNoRef.PointerCaptureLost += HandlePointerCaptureLostEventArgs; + m_dragDropPointerCaptureLostToken = true; + } + } + } + + public void HandlePointerPressedEventArgs(object sender, PointerRoutedEventArgs pArgs) + { + Pointer spPointer; + PointerDeviceType pointerDeviceType = PointerDeviceType.Touch; + PointerPoint spPointerPoint; + + //m_spPointerPoint = null; + //m_spPointer = null; + //m_isHoldingCompleted = false; + + spPointer = pArgs.Pointer; + pointerDeviceType = spPointer.PointerDeviceType; + + spPointerPoint = pArgs.GetCurrentPoint(m_pOwnerNoRef); + + // Check if this is a mouse button down. + if (pointerDeviceType == PointerDeviceType.Mouse || pointerDeviceType == PointerDeviceType.Pen) + { + // Mouse button down. + PointerPointProperties spPointerProperties; + bool isRightButtonPressed = false; + + spPointerProperties = spPointerPoint.Properties; + isRightButtonPressed = spPointerProperties.IsRightButtonPressed; + + // If the right mouse button was the one pressed... + if (!m_isRightButtonPressed && isRightButtonPressed) + { + m_isRightButtonPressed = true; + // Start listening for a mouse drag gesture + m_lastMouseRightButtonDownPosition = spPointerPoint.Position; + BeginCheckingForMouseDrag(spPointer); + + RegisterDragPointerEvents(); + } + } + /*else + { + m_spPointerPoint = spPointerPoint; + m_spPointer = spPointer; + + if (m_shouldAddInputHandlers && !m_dragDropHoldingToken) + { + // Touch input occurs, subscribe to holding + m_pOwnerNoRef.Holding += HandleHoldingEventArgs; + } + + RegisterDragPointerEvents(); + }*/ + } + + public void HandlePointerMovedEventArgs(object sender, PointerRoutedEventArgs pArgs) + { + Pointer spPointer; + PointerDeviceType pointerDeviceType = PointerDeviceType.Touch; + + spPointer = pArgs.Pointer; + pointerDeviceType = spPointer.PointerDeviceType; + + // Our behavior is different between mouse and touch. + // It's up to us to detect mouse drag gestures - if we + // detect one here, start a drag drop. + if (pointerDeviceType == PointerDeviceType.Mouse || pointerDeviceType == PointerDeviceType.Pen) + { + PointerPoint spPointerPoint; + Point newMousePosition; + + spPointerPoint = pArgs.GetCurrentPoint(m_pOwnerNoRef); + + newMousePosition = spPointerPoint.Position; + if (ShouldStartMouseDrag(newMousePosition)) + { + IAsyncOperation spAsyncOperation; + StopCheckingForMouseDrag(spPointer); + + spAsyncOperation = m_pOwnerNoRef.StartDragAsync(spPointerPoint); + } + } + } + + public void HandlePointerReleasedEventArgs(object sender, PointerRoutedEventArgs pArgs) + { + Pointer spPointer; + PointerDeviceType pointerDeviceType = PointerDeviceType.Touch; + + spPointer = pArgs.Pointer; + pointerDeviceType = spPointer.PointerDeviceType; + + // Check if this is a mouse button up + if (pointerDeviceType == PointerDeviceType.Mouse || pointerDeviceType == PointerDeviceType.Pen) + { + bool isRightButtonPressed = false; + PointerPoint spPointerPoint; + PointerPointProperties spPointerProperties; + + spPointerPoint = pArgs.GetCurrentPoint(m_pOwnerNoRef); + spPointerProperties = spPointerPoint.Properties; + isRightButtonPressed = spPointerProperties.IsRightButtonPressed; + + // if the mouse right button was the one released... + if (m_isRightButtonPressed && !isRightButtonPressed) + { + m_isRightButtonPressed = false; + UnregisterEvents(); + // Terminate any mouse drag gesture tracking. + StopCheckingForMouseDrag(spPointer); + } + } + else + { + UnregisterEvents(); + } + } + + public void HandlePointerCaptureLostEventArgs(object sender, PointerRoutedEventArgs pArgs) + { + Pointer spPointer; + PointerDeviceType pointerDeviceType = PointerDeviceType.Touch; + + spPointer = pArgs.Pointer; + + pointerDeviceType = spPointer.PointerDeviceType; + if (pointerDeviceType == PointerDeviceType.Mouse || pointerDeviceType == PointerDeviceType.Pen) + { + // We're not necessarily going to get a PointerReleased on capture lost, so reset this flag here. + m_isRightButtonPressed = false; + } + + UnregisterEvents(); + } + + public void UnregisterEvents() + { + // Unregister events handlers + if (m_dragDropPointerMovedToken) + { + m_pOwnerNoRef.PointerMoved -= HandlePointerMovedEventArgs; + m_dragDropPointerMovedToken = false; + } + + if (m_dragDropPointerReleasedToken) + { + m_pOwnerNoRef.PointerReleased -= HandlePointerReleasedEventArgs; + m_dragDropPointerReleasedToken = false; + } + + if (m_dragDropPointerCaptureLostToken) + { + m_pOwnerNoRef.PointerCaptureLost -= HandlePointerCaptureLostEventArgs; + m_dragDropPointerCaptureLostToken = false; + } + + /*if (m_dragDropHoldingToken) + { + m_pOwnerNoRef.Holding -= HandleHoldingEventArgs; + m_dragDropHoldingToken = false; + }*/ + } + + /*public void HandleHoldingEventArgs(object sender, HoldingRoutedEventArgs pArgs) + { + PointerDeviceType pointerDeviceType = PointerDeviceType.Touch; + + pointerDeviceType = pArgs.PointerDeviceType; + + if (pointerDeviceType == PointerDeviceType.Touch) + { + HoldingState holdingState = HoldingState.Started; + holdingState = pArgs.HoldingState; + + if (holdingState == HoldingState.Started) + { + m_isHoldingCompleted = true; + } + } + }*/ + + /*public void HandleDirectManipulationDraggingStarted() + { + // Release cross-slide viewport now + m_pOwnerNoRef.DirectManipulationCrossSlideContainerCompleted(); + if (m_isHoldingCompleted) + { + m_pOwnerNoRef.OnTouchDragStarted(m_spPointerPoint, m_spPointer); + } + + m_spPointerPoint = null; + m_spPointer = null; + }*/ + } +} diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs index ec787ddd73f6..dcd856c6ce9b 100644 --- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs +++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs @@ -7,10 +7,12 @@ using System.IO; using System.Runtime.InteropServices; using System.Windows.Input; +using Vanara.PInvoke; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer.DragDrop; using Windows.Storage; using Windows.System; +using WinRT; namespace Files.App.ViewModels.Layouts { @@ -100,6 +102,10 @@ public async Task DragOverAsync(DragEventArgs e) return; } + // Check if this is a right-button drag operation and store into the dataobject + if (e.Modifiers.HasFlag(DragDropModifiers.RightButton)) + e.DataView.As().GetDataObject().SetData("dragRightButton", true); + if (FilesystemHelpers.HasDraggedStorageItems(e.DataView)) { e.Handled = true; @@ -187,7 +193,16 @@ public async Task DropAsync(DragEventArgs e) try { - await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true); + e.DataView.As().GetDataObject().TryGetData(User32.RegisterClipboardFormat("dragRightButton"), out var isRightButtonDrag); + + if (isRightButtonDrag) + { + SafetyExtensions.IgnoreExceptions(() => ShellContextFlyoutFactory.InvokeRightButtonDropMenu(_associatedInstance.ShellViewModel.WorkingDirectory, e.DataView, e.AcceptedOperation)); + } + else + { + await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true); + } await _associatedInstance.RefreshIfNoWatcherExistsAsync(); } finally diff --git a/src/Files.App/ViewModels/UserControls/AddressToolbarViewModel.cs b/src/Files.App/ViewModels/UserControls/AddressToolbarViewModel.cs index 07e096b3da2c..f731279ff002 100644 --- a/src/Files.App/ViewModels/UserControls/AddressToolbarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/AddressToolbarViewModel.cs @@ -10,8 +10,11 @@ using Microsoft.UI.Xaml.Input; using System.IO; using System.Windows.Input; +using Vanara.PInvoke; using Windows.ApplicationModel.DataTransfer; +using Windows.ApplicationModel.DataTransfer.DragDrop; using Windows.UI.Text; +using WinRT; using FocusManager = Microsoft.UI.Xaml.Input.FocusManager; namespace Files.App.ViewModels.UserControls @@ -354,6 +357,10 @@ public async Task PathBoxItem_DragOver(object sender, DragEventArgs e) } } + // Check if this is a right-button drag operation and store into the dataobject + if (e.Modifiers.HasFlag(DragDropModifiers.RightButton)) + e.DataView.As().GetDataObject().SetData("dragRightButton", true); + // In search page if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView) || string.IsNullOrEmpty(pathBoxItem.Path)) { diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index b59fb8ac3172..8ffbb26df175 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -11,11 +11,13 @@ using System.Collections.Specialized; using System.IO; using System.Windows.Input; +using Vanara.PInvoke; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer.DragDrop; using Windows.Storage; using Windows.System; using Windows.UI.Core; +using WinRT; namespace Files.App.ViewModels.UserControls { @@ -1073,6 +1075,10 @@ private async Task HandleLocationItemDragOverAsync(LocationItem locationItem, It { var rawEvent = args.RawEvent; + // Check if this is a right-button drag operation and store into the dataobject + if (args.RawEvent.Modifiers.HasFlag(DragDropModifiers.RightButton)) + args.DroppedItem.As().GetDataObject().SetData("dragRightButton", true); + if (Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) { args.RawEvent.Handled = true; @@ -1158,6 +1164,10 @@ private async Task HandleDriveItemDragOverAsync(DriveItem driveItem, ItemDragOve if (!Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) return; + // Check if this is a right-button drag operation and store into the dataobject + if (args.RawEvent.Modifiers.HasFlag(DragDropModifiers.RightButton)) + args.DroppedItem.As().GetDataObject().SetData("dragRightButton", true); + args.RawEvent.Handled = true; var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); @@ -1253,14 +1263,33 @@ private async Task HandleLocationItemDroppedAsync(LocationItem locationItem, Ite } else { - await FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.DroppedItem, locationItem.Path, false, true); + args.DroppedItem.As().GetDataObject().TryGetData(User32.RegisterClipboardFormat("dragRightButton"), out var isRightButtonDrag); + + if (isRightButtonDrag) + { + SafetyExtensions.IgnoreExceptions(() => ShellContextFlyoutFactory.InvokeRightButtonDropMenu(locationItem.Path, args.DroppedItem, args.RawEvent.AcceptedOperation)); + } + else + { + await FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.DroppedItem, locationItem.Path, false, true); + } } } } private Task HandleDriveItemDroppedAsync(DriveItem driveItem, ItemDroppedEventArgs args) { - return FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.RawEvent.DataView, driveItem.Path, false, true); + args.DroppedItem.As().GetDataObject().TryGetData(User32.RegisterClipboardFormat("dragRightButton"), out var isRightButtonDrag); + + if (isRightButtonDrag) + { + SafetyExtensions.IgnoreExceptions(() => ShellContextFlyoutFactory.InvokeRightButtonDropMenu(driveItem.Path, args.DroppedItem, args.RawEvent.AcceptedOperation)); + return Task.FromResult(ReturnResult.Success); + } + else + { + return FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.RawEvent.DataView, driveItem.Path, false, true); + } } private async Task HandleTagItemDroppedAsync(FileTagItem fileTagItem, ItemDroppedEventArgs args) diff --git a/src/Files.App/Views/Layouts/BaseLayoutPage.cs b/src/Files.App/Views/Layouts/BaseLayoutPage.cs index 7dd162f74ba6..0dbdea2db337 100644 --- a/src/Files.App/Views/Layouts/BaseLayoutPage.cs +++ b/src/Files.App/Views/Layouts/BaseLayoutPage.cs @@ -6,6 +6,7 @@ using Files.App.Helpers.ContextFlyouts; using Files.App.UserControls.Menus; using Files.App.ViewModels.Layouts; +using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; @@ -22,6 +23,7 @@ using Windows.Foundation.Collections; using Windows.Storage; using Windows.System; +using Windows.UI.Core; using WinRT; using static Files.App.Helpers.PathNormalization; using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; @@ -921,7 +923,7 @@ private async Task AddShellMenuItemsAsync(List s // Filter mainShellMenuItems that have a non-null LoadSubMenuAction var mainItemsWithSubMenu = mainShellMenuItems.Where(x => x.LoadSubMenuAction is not null); - + var mainSubMenuTasks = mainItemsWithSubMenu.Select(async item => { await item.LoadSubMenuAction(); @@ -936,7 +938,7 @@ private async Task AddShellMenuItemsAsync(List s await item.LoadSubMenuAction(); ShellContextFlyoutFactory.AddItemsToOverflowMenu(overflowItem, item); }); - + itemsControl?.Items.OfType().ForEach(item => { // Enable CharacterEllipsis text trimming for menu items @@ -962,7 +964,7 @@ private async Task AddShellMenuItemsAsync(List s clickAction(flyout.Items); } }); - + await Task.WhenAll(mainSubMenuTasks.Concat(overflowSubMenuTasks)); } @@ -987,10 +989,23 @@ protected virtual void Page_CharacterReceived(UIElement sender, CharacterReceive } protected virtual void FileList_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + => e.Cancel = DragItemsStarting(sender, e.Data, e.Items); + + private void Item_DragStarting(UIElement sender, DragStartingEventArgs args) + { + if (GetItemFromElement(sender) is not ListedItem item) + return; + + var selectedItems = SelectedItems?.ToList() ?? new(); + selectedItems.AddIfNotPresent(item); + args.Cancel = DragItemsStarting(ItemsControl, args.Data, selectedItems); + } + + protected bool DragItemsStarting(object sender, DataPackage data, IList items) { try { - var itemList = e.Items.OfType().ToList(); + var itemList = items.OfType().ToList(); var firstItem = itemList.FirstOrDefault(); var sortedItems = SortingHelper.OrderFileList(itemList, FolderSettings.DirectorySortOption, FolderSettings.DirectorySortDirection, FolderSettings.SortDirectoriesAlongsideFiles, FolderSettings.SortFilesFirst).ToList(); var orderedItems = sortedItems.SkipWhile(x => x != firstItem).Concat(sortedItems.TakeWhile(x => x != firstItem)).ToList(); @@ -1000,14 +1015,14 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti { var iddo = shellItemList[0].Parent.GetChildrenUIObjects(HWND.NULL, shellItemList); shellItemList.ForEach(x => x.Dispose()); - var dataObjectProvider = e.Data.As(); + var dataObjectProvider = data.As(); dataObjectProvider.SetDataObject(iddo); } else { // Only support IStorageItem capable paths var storageItemList = orderedItems.Where(x => !(x.IsHiddenItem && x.IsLinkItem && x.IsRecycleBinItem && x.IsShortcut)).Select(x => VirtualStorageItem.FromListedItem(x)); - e.Data.SetStorageItems(storageItemList, false); + data.SetStorageItems(storageItemList, false); } // Set can window to front (#13255) @@ -1016,8 +1031,9 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti } catch (Exception) { - e.Cancel = true; + return true; } + return false; } protected virtual void FileList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) @@ -1044,6 +1060,10 @@ private async void Item_DragOver(object sender, DragEventArgs e) DragOperationDeferral? deferral = null; + // Check if this is a right-button drag operation and store into the dataobject + if (e.Modifiers.HasFlag(DragDropModifiers.RightButton)) + e.DataView.As().GetDataObject().SetData("dragRightButton", true); + try { deferral = e.GetDeferral(); @@ -1146,8 +1166,18 @@ protected virtual async void Item_Drop(object sender, DragEventArgs e) var item = GetItemFromElement(sender); if (item is not null) - await ParentShellPageInstance!.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, (item as ShortcutItem)?.TargetPath ?? item.ItemPath, false, true, item.IsExecutable, item.IsScriptFile); + { + e.DataView.As().GetDataObject().TryGetData(User32.RegisterClipboardFormat("dragRightButton"), out var isRightButtonDrag); + if (isRightButtonDrag) + { + SafetyExtensions.IgnoreExceptions(() => ShellContextFlyoutFactory.InvokeRightButtonDropMenu(item.ItemPath, e.DataView, e.AcceptedOperation)); + } + else + { + await ParentShellPageInstance!.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, (item as ShortcutItem)?.TargetPath ?? item.ItemPath, false, true, item.IsExecutable, item.IsScriptFile); + } + } deferral.Complete(); } @@ -1344,6 +1374,11 @@ protected void InitializeDrag(UIElement container, ListedItem item) container.DragLeave += Item_DragLeave; container.Drop += Item_Drop; } + + var dragHelper = new AutomaticDragHelper(container, true); + dragHelper.StartDetectingDrag(); + AutomaticDragHelper.SetDragHelper(container, dragHelper); + container.DragStarting += Item_DragStarting; } protected void UninitializeDrag(UIElement element) @@ -1352,6 +1387,9 @@ protected void UninitializeDrag(UIElement element) element.RemoveHandler(UIElement.DragOverEvent, Item_DragOverEventHandler); element.DragLeave -= Item_DragLeave; element.Drop -= Item_Drop; + + AutomaticDragHelper.SetDragHelper(element, null!); + element.DragStarting -= Item_DragStarting; } public virtual void Dispose() diff --git a/src/Files.App/Views/Shells/BaseShellPage.cs b/src/Files.App/Views/Shells/BaseShellPage.cs index 3c9e57ffd48d..239a6e28bb03 100644 --- a/src/Files.App/Views/Shells/BaseShellPage.cs +++ b/src/Files.App/Views/Shells/BaseShellPage.cs @@ -9,9 +9,11 @@ using Microsoft.UI.Xaml.Navigation; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Vanara.PInvoke; using Windows.Foundation.Metadata; using Windows.System; using Windows.UI.Core; +using WinRT; using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; namespace Files.App.Views.Shells @@ -417,7 +419,16 @@ protected void CoreWindow_PointerPressed(object sender, PointerRoutedEventArgs a protected async void ShellPage_PathBoxItemDropped(object sender, PathBoxItemDroppedEventArgs e) { - await FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.Package, e.Path, false, true); + e.Package.As().GetDataObject().TryGetData(User32.RegisterClipboardFormat("dragRightButton"), out var isRightButtonDrag); + + if (isRightButtonDrag) + { + SafetyExtensions.IgnoreExceptions(() => ShellContextFlyoutFactory.InvokeRightButtonDropMenu(e.Path, e.Package, e.AcceptedOperation)); + } + else + { + await FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.Package, e.Path, false, true); + } e.SignalEvent?.Set(); }