Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/Files.App.Controls/Sidebar/ISidebarItemModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,17 @@ public interface ISidebarItemModel : INotifyPropertyChanged
/// </summary>
string? Path { get; }
}

public interface IDraggableSidebarItemModel : ISidebarItemModel
{
/// <summary>
/// The file path used for drag and drop operations
/// </summary>
string? DropPath { get; }

/// <summary>
/// Indicates whether the item supports reorder dropping
/// </summary>
bool IsReorderDropItem { get; }
}
}
107 changes: 77 additions & 30 deletions src/Files.App.Controls/Sidebar/SidebarItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@

using CommunityToolkit.WinUI;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.InteropServices;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;

Expand Down Expand Up @@ -92,7 +97,17 @@ public void HandleItemChange()
HookupItemChangeListener(null, Item);
UpdateExpansionState();
ReevaluateSelection();
CanDrag = Item?.Path is string path && Path.IsPathRooted(path);

if (Item is IDraggableSidebarItemModel draggableItem)
{
CanDrag = IsValidDropPath(draggableItem.DropPath);
UseReorderDrop = !IsGroupHeader && CanDrag && draggableItem.IsReorderDropItem;
}
else
{
CanDrag = false;
UseReorderDrop = false;
}
}

private void HookupOwners()
Expand Down Expand Up @@ -138,32 +153,36 @@ private void HookupItemChangeListener(ISidebarItemModel? oldItem, ISidebarItemMo
}
}

private static bool IsValidDropPath(string? path)
=> path is not null && (System.IO.Path.IsPathRooted(path) || path.StartsWith("Shell:", StringComparison.OrdinalIgnoreCase));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not put system specific code inside Files.App.Controls. It should be done within Files.App. Sidebar reordering should work in any other scenario.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0x5bfa Thanks for the review, I will work on it this week.


private void SidebarItem_DragStarting(UIElement sender, DragStartingEventArgs args)
{
if (Item?.Path is not string dragPath || !Path.IsPathRooted(dragPath))
if (Item is not IDraggableSidebarItemModel draggableItem || draggableItem.DropPath is not string dragPath || !IsValidDropPath(dragPath))
return;

args.Data.SetData(StandardDataFormats.Text, dragPath);
args.Data.RequestedOperation = DataPackageOperation.Move | DataPackageOperation.Copy | DataPackageOperation.Link;
args.Data.SetDataProvider(StandardDataFormats.StorageItems, async request =>
SafetyExtensions.IgnoreExceptions(() =>
{
var deferral = request.GetDeferral();
try
args.Data.SetData(StandardDataFormats.Text, dragPath);
args.Data.RequestedOperation = DataPackageOperation.Move | DataPackageOperation.Copy | DataPackageOperation.Link;
args.Data.SetDataProvider(StandardDataFormats.StorageItems, async request =>
{
if (Directory.Exists(dragPath))
var deferral = SafetyExtensions.IgnoreExceptions(() => request.GetDeferral(), null, typeof(COMException));
try
{
var folder = await StorageFolder.GetFolderFromPathAsync(dragPath);
request.SetData(new IStorageItem[] { folder });
if (Directory.Exists(dragPath))
{
var folder = await StorageFolder.GetFolderFromPathAsync(dragPath);
request.SetData(new IStorageItem[] { folder });
}
}
}
catch
{
}
finally
{
deferral.Complete();
}
});
finally
{
if (deferral is not null)
SafetyExtensions.IgnoreExceptions(() => deferral.Complete(), null, typeof(COMException));
}
});
}, null, typeof(COMException));
}

private void SetFlyoutOpen(bool isOpen = true)
Expand Down Expand Up @@ -394,21 +413,49 @@ private async void ItemBorder_DragOver(object sender, DragEventArgs e)
IsExpanded = true;
}

var insertsAbove = DetermineDropTargetPosition(e);
if (insertsAbove == SidebarItemDropPosition.Center)
{
VisualStateManager.GoToState(this, "DragOnTop", true);
}
else if (insertsAbove == SidebarItemDropPosition.Top)
// Expected to fail with COMException if the OLE drag payload is stale
var deferral = SafetyExtensions.IgnoreExceptions(() => e.GetDeferral(), null, typeof(COMException));

try
{
VisualStateManager.GoToState(this, "DragInsertAbove", true);
var dropPosition = DetermineDropTargetPosition(e);

if (Owner is not null)
Owner.RaiseItemDragOver(this, dropPosition, e);

bool isHandled = false;
DataPackageOperation acceptedOperation = DataPackageOperation.None;

var propertiesRead = SafetyExtensions.IgnoreExceptions(() =>
{
isHandled = e.Handled;
acceptedOperation = e.AcceptedOperation;
}, null, typeof(COMException));

if (!propertiesRead || !isHandled || acceptedOperation == DataPackageOperation.None)
{
VisualStateManager.GoToState(this, "Normal", true);
return;
}

if (dropPosition == SidebarItemDropPosition.Center)
{
VisualStateManager.GoToState(this, "DragOnTop", true);
}
else if (dropPosition == SidebarItemDropPosition.Top)
{
VisualStateManager.GoToState(this, "DragInsertAbove", true);
}
else if (dropPosition == SidebarItemDropPosition.Bottom)
{
VisualStateManager.GoToState(this, "DragInsertBelow", true);
}
}
else if (insertsAbove == SidebarItemDropPosition.Bottom)
finally
{
VisualStateManager.GoToState(this, "DragInsertBelow", true);
if (deferral is not null)
SafetyExtensions.IgnoreExceptions(() => deferral.Complete(), null, typeof(COMException));
}

Owner?.RaiseItemDragOver(this, insertsAbove, e);
}

private void ItemBorder_ContextRequested(UIElement sender, Microsoft.UI.Xaml.Input.ContextRequestedEventArgs args)
Expand Down
17 changes: 15 additions & 2 deletions src/Files.App.Controls/Sidebar/SidebarView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Markup;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.System;
using System.Runtime.InteropServices;
using Windows.UI.Core;

namespace Files.App.Controls
Expand Down Expand Up @@ -51,13 +53,24 @@ internal void RaiseContextRequested(SidebarItem item, Point e)
internal void RaiseItemDropped(SidebarItem sideBarItem, SidebarItemDropPosition dropPosition, DragEventArgs rawEvent)
{
if (sideBarItem.Item is null) return;
ItemDropped?.Invoke(this, new(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent));

// Expected to fail with COMException if the OLE drag payload is stale
var dataView = SafetyExtensions.IgnoreExceptions(() => rawEvent.DataView, null, typeof(COMException));
if (dataView is null) return;

ItemDropped?.Invoke(this, new(sideBarItem.Item, dataView, dropPosition, rawEvent));
}

internal void RaiseItemDragOver(SidebarItem sideBarItem, SidebarItemDropPosition dropPosition, DragEventArgs rawEvent)
{
if (sideBarItem.Item is null) return;
ItemDragOver?.Invoke(this, new(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent));

// Expected to fail with COMException if the OLE drag payload is stale
var dataView = SafetyExtensions.IgnoreExceptions(() => rawEvent.DataView, null, typeof(COMException));
if (dataView is null) return;

var args = new ItemDragOverEventArgs(sideBarItem.Item, dataView, dropPosition, rawEvent);
ItemDragOver?.Invoke(this, args);
}

private void UpdateMinimalMode()
Expand Down
6 changes: 5 additions & 1 deletion src/Files.App/Data/Contracts/INavigationControlItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

namespace Files.App.Data.Contracts
{
public interface INavigationControlItem : IComparable<INavigationControlItem>, INotifyPropertyChanged, ISidebarItemModel
public interface INavigationControlItem : IComparable<INavigationControlItem>, INotifyPropertyChanged, IDraggableSidebarItemModel
{
public new string Text { get; }

public string Path { get; }

string? IDraggableSidebarItemModel.DropPath => Path;

bool IDraggableSidebarItemModel.IsReorderDropItem => Section == SectionType.Pinned;

public SectionType Section { get; }

public NavigationControlItemType ItemType { get; }
Expand Down
2 changes: 2 additions & 0 deletions src/Files.App/Data/Items/LocationItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public string Path
ToolTip = string.IsNullOrEmpty(Path) ||
Path.Contains('?', StringComparison.Ordinal) ||
Path.StartsWith("shell:", StringComparison.OrdinalIgnoreCase) ||
Path.StartsWith("::{", StringComparison.Ordinal) ||
Path.StartsWith(@"\\SHELL\", StringComparison.OrdinalIgnoreCase) ||
Path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase) ||
Path == "Home" ||
Path == "ReleaseNotes" ||
Expand Down
Loading
Loading