-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Feature: Enable drag and drop reordering for Pinned Sidebar items #18320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fff0cd5
d96746e
8bb1de2
4ca2152
189b662
254b52e
c9908d1
3b1d37e
89fc12a
512e81c
5c5e1f1
1cc3475
1a16f57
6284a1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,15 @@ | |
| using CommunityToolkit.WinUI; | ||
| using Microsoft.UI.Dispatching; | ||
| 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; | ||
|
|
||
|
|
@@ -31,6 +36,7 @@ public sealed partial class SidebarItem : Control | |
| private bool isTemplateWired; | ||
| private DispatcherQueueTimer? dragOverTimer; | ||
| private DispatcherQueueTimer? dragOverExpandTimer; | ||
| private SidebarItemDropPosition lastDropPosition = SidebarItemDropPosition.Center; | ||
|
|
||
| public SidebarItem() | ||
| { | ||
|
|
@@ -126,7 +132,17 @@ public void HandleItemChange() | |
| HookupItemChangeListener(null, Item); | ||
| UpdateExpansionState(); | ||
| ReevaluateSelection(); | ||
| CanDrag = Item?.Path is string path && Path.IsPathRooted(path); | ||
|
|
||
| if (Item is not null) | ||
| { | ||
| CanDrag = IsValidDropPath(Item.Path); | ||
| UseReorderDrop = !IsGroupHeader && CanDrag && Item.IsReorderDropItem; | ||
| } | ||
| else | ||
| { | ||
| CanDrag = false; | ||
| UseReorderDrop = false; | ||
| } | ||
| } | ||
|
|
||
| private void HookupOwners() | ||
|
|
@@ -190,32 +206,36 @@ private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e) | |
| } | ||
| } | ||
|
|
||
| private static bool IsValidDropPath(string? path) | ||
| => path is not null && (System.IO.Path.IsPathRooted(path) || path.StartsWith("Shell:", StringComparison.OrdinalIgnoreCase)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?.Path 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) | ||
|
|
@@ -464,58 +484,92 @@ private void Item_PointerReleased(object sender, Microsoft.UI.Xaml.Input.Pointer | |
|
|
||
| private async void ItemBorder_DragOver(object sender, DragEventArgs e) | ||
| { | ||
| var insertsAbove = DetermineDropTargetPosition(e); | ||
| if (insertsAbove == SidebarItemDropPosition.Center) | ||
| { | ||
| VisualStateManager.GoToState(this, "DragOnTop", true); | ||
| } | ||
| else if (insertsAbove == SidebarItemDropPosition.Top) | ||
| { | ||
| VisualStateManager.GoToState(this, "DragInsertAbove", true); | ||
| } | ||
| else if (insertsAbove == SidebarItemDropPosition.Bottom) | ||
| // 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, "DragInsertBelow", true); | ||
| } | ||
| var dropPosition = DetermineDropTargetPosition(e); | ||
|
|
||
| Owner?.RaiseItemDragOver(this, insertsAbove, e); | ||
| if (Owner is not null) | ||
| Owner.RaiseItemDragOver(this, dropPosition, e); | ||
|
|
||
| var openDelay = Owner?.HoverToOpenDelay ?? TimeSpan.Zero; | ||
| var expandDelay = Owner?.HoverToExpandDelay ?? TimeSpan.Zero; | ||
| var isCenter = insertsAbove == SidebarItemDropPosition.Center; | ||
| var canHoverOpen = openDelay > TimeSpan.Zero && isCenter && Item is not null && (!IsGroupHeader || Item.IsLeafWithChildren); | ||
| var canHoverExpand = expandDelay > TimeSpan.Zero && isCenter && HasChildren && CollapseEnabled; | ||
| if (canHoverExpand) | ||
| { | ||
| dragOverExpandTimer ??= DispatcherQueue.CreateTimer(); | ||
| dragOverExpandTimer.Debounce( | ||
| () => | ||
| { | ||
| dragOverExpandTimer!.Stop(); | ||
| IsExpanded = true; | ||
| }, | ||
| expandDelay, | ||
| false); | ||
| } | ||
| else | ||
| { | ||
| dragOverExpandTimer?.Stop(); | ||
| } | ||
| if (canHoverOpen) | ||
| { | ||
| dragOverTimer ??= DispatcherQueue.CreateTimer(); | ||
| dragOverTimer.Debounce( | ||
| () => | ||
| { | ||
| dragOverTimer!.Stop(); | ||
| RaiseItemInvoked(PointerUpdateKind.Other); | ||
| }, | ||
| openDelay, | ||
| false); | ||
| bool isHandled = false; | ||
| DataPackageOperation acceptedOperation = DataPackageOperation.None; | ||
|
|
||
| var propertiesRead = SafetyExtensions.IgnoreExceptions(() => | ||
| { | ||
| isHandled = e.Handled; | ||
| acceptedOperation = e.AcceptedOperation; | ||
| }, null, typeof(COMException)); | ||
|
|
||
| if (dropPosition != lastDropPosition) | ||
| { | ||
| acceptedOperation = DataPackageOperation.None; | ||
| } | ||
| lastDropPosition = dropPosition; | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| var openDelay = Owner?.HoverToOpenDelay ?? TimeSpan.Zero; | ||
| var expandDelay = Owner?.HoverToExpandDelay ?? TimeSpan.Zero; | ||
| var isCenter = dropPosition == SidebarItemDropPosition.Center; | ||
| var canHoverOpen = openDelay > TimeSpan.Zero && isCenter && Item is not null && (!IsGroupHeader || Item.IsLeafWithChildren); | ||
| var canHoverExpand = expandDelay > TimeSpan.Zero && isCenter && HasChildren && CollapseEnabled; | ||
| if (canHoverExpand) | ||
| { | ||
| dragOverExpandTimer ??= DispatcherQueue.CreateTimer(); | ||
| dragOverExpandTimer.Debounce( | ||
| () => | ||
| { | ||
| dragOverExpandTimer!.Stop(); | ||
| IsExpanded = true; | ||
| }, | ||
| expandDelay, | ||
| false); | ||
| } | ||
| else | ||
| { | ||
| dragOverExpandTimer?.Stop(); | ||
| } | ||
| if (canHoverOpen) | ||
| { | ||
| dragOverTimer ??= DispatcherQueue.CreateTimer(); | ||
| dragOverTimer.Debounce( | ||
| () => | ||
| { | ||
| dragOverTimer!.Stop(); | ||
| RaiseItemInvoked(PointerUpdateKind.Other); | ||
| }, | ||
| openDelay, | ||
| false); | ||
| } | ||
| else | ||
| { | ||
| dragOverTimer?.Stop(); | ||
| } | ||
| } | ||
| else | ||
| finally | ||
| { | ||
| dragOverTimer?.Stop(); | ||
| if (deferral is not null) | ||
| SafetyExtensions.IgnoreExceptions(() => deferral.Complete(), null, typeof(COMException)); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -529,13 +583,15 @@ private void ItemBorder_DragLeave(object sender, DragEventArgs e) | |
| { | ||
| dragOverTimer?.Stop(); | ||
| dragOverExpandTimer?.Stop(); | ||
| lastDropPosition = SidebarItemDropPosition.Center; | ||
| UpdatePointerState(); | ||
| } | ||
|
|
||
| private void ItemBorder_Drop(object sender, DragEventArgs e) | ||
| { | ||
| dragOverTimer?.Stop(); | ||
| dragOverExpandTimer?.Stop(); | ||
| lastDropPosition = SidebarItemDropPosition.Center; | ||
| UpdatePointerState(); | ||
| Owner?.RaiseItemDropped(this, DetermineDropTargetPosition(e), e); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CanBeReordered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yair100 This is the kind of behavior I was trying to avoid with Center Drop => Create Shortcut.