Skip to content

Commit

Permalink
feat(dragdrop): Initial D&D events support on ListView
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Dec 1, 2020
1 parent c43f6d6 commit aaf4309
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public bool IsSwipeEnabled
// Skipping already declared property HeaderTemplate
// Skipping already declared property Header
// Skipping already declared property DataFetchSize
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public bool CanReorderItems
{
Expand All @@ -56,7 +56,7 @@ public bool CanReorderItems
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public bool CanDragItems
{
Expand Down Expand Up @@ -195,15 +195,15 @@ public bool IsActiveView
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.UI.Xaml.DependencyProperty CanDragItemsProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
nameof(CanDragItems), typeof(bool),
typeof(global::Windows.UI.Xaml.Controls.ListViewBase),
new FrameworkPropertyMetadata(default(bool)));
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public static global::Windows.UI.Xaml.DependencyProperty CanReorderItemsProperty { get; } =
Windows.UI.Xaml.DependencyProperty.Register(
Expand Down Expand Up @@ -498,7 +498,7 @@ public void CompleteViewChangeTo( global::Windows.UI.Xaml.Controls.SemanticZoom
// Forced skipping of method Windows.UI.Xaml.Controls.ListViewBase.HeaderProperty.get
// Forced skipping of method Windows.UI.Xaml.Controls.ListViewBase.HeaderTemplateProperty.get
// Forced skipping of method Windows.UI.Xaml.Controls.ListViewBase.HeaderTransitionsProperty.get
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public event global::Windows.UI.Xaml.Controls.DragItemsStartingEventHandler DragItemsStarting
{
Expand Down Expand Up @@ -563,7 +563,7 @@ public void CompleteViewChangeTo( global::Windows.UI.Xaml.Controls.SemanticZoom
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public event global::Windows.Foundation.TypedEventHandler<global::Windows.UI.Xaml.Controls.ListViewBase, global::Windows.UI.Xaml.Controls.DragItemsCompletedEventArgs> DragItemsCompleted
{
Expand Down
3 changes: 3 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,8 @@ protected virtual bool UpdateItems()

protected virtual void ClearContainerForItemOverride(DependencyObject element, object item) { }

internal virtual void ContainerClearedForItem(object item, SelectorItem itemContainer) { }

/// <summary>
/// Unset content of container. This should be called when the container is no longer going to be used.
/// </summary>
Expand All @@ -969,6 +971,7 @@ internal void CleanUpContainer(global::Windows.UI.Xaml.DependencyObject element)

}
ClearContainerForItemOverride(element, item);
ContainerClearedForItem(item, element as SelectorItem);

if (element is ContentPresenter presenter
&& (
Expand Down
76 changes: 76 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Uno.Client;
using System.Threading.Tasks;
using System.Threading;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Uno.UI;

Expand Down Expand Up @@ -655,13 +656,88 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
}
}

private const string DragItemsFormatId = DataPackage.UnoPrivateDataPrefix + "__dragged__items__";

internal override void ContainerPreparedForItem(object item, SelectorItem itemContainer, int itemIndex)
{
base.ContainerPreparedForItem(item, itemContainer, itemIndex);

if (CanDragItems)
{
itemContainer.CanDrag = true;
itemContainer.DragStarting += OnItemContainerDragStarting;
itemContainer.DropCompleted += OnItemContainerDragCompleted;
}

ContainerContentChanging?.Invoke(this, new ContainerContentChangingEventArgs(item, itemContainer, itemIndex));
}

internal override void ContainerClearedForItem(object item, SelectorItem itemContainer)
{
itemContainer.DragStarting -= OnItemContainerDragStarting;
itemContainer.DropCompleted -= OnItemContainerDragCompleted;

base.ContainerClearedForItem(item, itemContainer);
}

public event DragItemsStartingEventHandler DragItemsStarting;
public event TypedEventHandler<ListViewBase, DragItemsCompletedEventArgs> DragItemsCompleted;

public static DependencyProperty CanReorderItemsProperty { get; } = DependencyProperty.Register(
nameof(CanReorderItems),
typeof(bool),
typeof(ListViewBase),
new FrameworkPropertyMetadata(default(bool)));

public bool CanReorderItems
{
get => (bool)GetValue(CanReorderItemsProperty);
set => SetValue(CanReorderItemsProperty, value);
}

public static DependencyProperty CanDragItemsProperty { get; } = DependencyProperty.Register(
nameof(CanDragItems),
typeof(bool),
typeof(ListViewBase),
new FrameworkPropertyMetadata(default(bool)));

public bool CanDragItems
{
get => (bool)GetValue(CanDragItemsProperty);
set => SetValue(CanDragItemsProperty, value);
}

private static void OnItemContainerDragStarting(UIElement sender, DragStartingEventArgs innerArgs)
{
if (ItemsControlFromItemContainer(sender) is ListViewBase that && that.CanDragItems)
{
var items = that.SelectedItems.ToList();
if (that.ItemFromContainer(sender) is {} draggedItem && !items.Contains(draggedItem))
{
items.Add(draggedItem);
}
var args = new DragItemsStartingEventArgs(innerArgs, items);

that.DragItemsStarting?.Invoke(that, args);

// The application has the ability to add some items in the list, so make sure to freeze it only after event has been raised.
args.Data.SetData(DragItemsFormatId, args.Items.ToList());
}
}

private static void OnItemContainerDragCompleted(UIElement sender, DropCompletedEventArgs innerArgs)
{
// Note: It's not the responsibility of the ListView to remove item from the source, not matter the AcceptedOperation.

if (ItemsControlFromItemContainer(sender) is ListViewBase that && that.CanDragItems)
{
var items = innerArgs.Info.Data.FindRawData(DragItemsFormatId) as IReadOnlyList<object> ?? new List<object>(0);
var args = new DragItemsCompletedEventArgs(innerArgs, items);

that.DragItemsCompleted?.Invoke(that, args);
}
}

/// <summary>
/// Apply the multi-selection state to the provided item
/// </summary>
Expand Down
10 changes: 9 additions & 1 deletion src/Uno.UI/UI/Xaml/DragDrop/DropCompletedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
#nullable enable

using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.DataTransfer.DragDrop.Core;

namespace Windows.UI.Xaml
{
public partial class DropCompletedEventArgs : RoutedEventArgs
{
internal DropCompletedEventArgs(UIElement originalSource, DataPackageOperation result)
internal DropCompletedEventArgs(UIElement originalSource, CoreDragInfo info, DataPackageOperation result)
: base(originalSource)
{
Info = info;
DropResult = result;

CanBubbleNatively = false;
}

/// <summary>
/// Get access to the shared data, for internal usage only.
/// This has to be treated as readonly for drop completed.
/// </summary>
internal CoreDragInfo Info { get; }

public DataPackageOperation DropResult { get; }
}
}
39 changes: 17 additions & 22 deletions src/Uno.UI/UI/Xaml/UIElement.Pointers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,40 +588,35 @@ private Task<DataPackageOperation> StartDragAsyncCore(PointerPoint pointer, Poin
return Task.FromCanceled<DataPackageOperation>(CancellationToken.None);
}

var dragInfo = new CoreDragInfo(
source: ptArgs,
data: routedArgs.Data.GetView(),
routedArgs.AllowedOperations,
dragUI: routedArgs.DragUI);

if (!pointer.Properties.HasPressedButton)
{
// This is the UWP behavior: if no button is pressed, then the drag is completed immediately
var noneResult = Task.FromResult(DataPackageOperation.None);
OnDragCompleted(noneResult, this);
return noneResult;
OnDropCompleted(dragInfo, DataPackageOperation.None);
return Task.FromResult(DataPackageOperation.None);
}

var result = new TaskCompletionSource<DataPackageOperation>();
result.Task.ContinueWith(OnDragCompleted, this, TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.RunContinuationsAsynchronously);
var asyncResult = new TaskCompletionSource<DataPackageOperation>();

var dragInfo = new CoreDragInfo(
source: ptArgs,
data: routedArgs.Data.GetView(),
routedArgs.AllowedOperations,
dragUI: routedArgs.DragUI);
dragInfo.RegisterCompletedCallback(result.SetResult);
dragInfo.RegisterCompletedCallback(result =>
{
OnDropCompleted(dragInfo, result);
asyncResult.SetResult(result);
});

CoreDragDropManager.GetForCurrentView()!.DragStarted(dragInfo);

return result.Task;
return asyncResult.Task;
}

private static void OnDragCompleted(Task<DataPackageOperation> resultTask, object snd)
{
var that = (UIElement)snd;
var result = resultTask.IsFaulted
? DataPackageOperation.None
: resultTask.Result;
private void OnDropCompleted(CoreDragInfo info, DataPackageOperation result)
// Note: originalSource = this => DropCompleted is not actually a routed event, the original source is always the sender
var args = new DropCompletedEventArgs(that, result);

that.SafeRaiseEvent(DropCompletedEvent, args);
}
=> SafeRaiseEvent(DropCompletedEvent, new DropCompletedEventArgs(this, info, result));

/// <summary>
/// Provides ability to a control to fulfill the data that is going to be shared, by drag-and-drop for instance.
Expand Down
2 changes: 2 additions & 0 deletions src/Uno.UWP/ApplicationModel/DataTransfer/DataPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace Windows.ApplicationModel.DataTransfer
{
public partial class DataPackage
{
internal const string UnoPrivateDataPrefix = "__uno__private__data__";

public event TypedEventHandler<DataPackage, OperationCompletedEventArgs>? OperationCompleted;

private ImmutableDictionary<string, object> _data = ImmutableDictionary<string, object>.Empty;
Expand Down
6 changes: 5 additions & 1 deletion src/Uno.UWP/ApplicationModel/DataTransfer/DataPackageView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal DataPackageView(

public DataPackageOperation RequestedOperation { get; }

public IReadOnlyList<string> AvailableFormats => _data.Keys.ToArray();
public IReadOnlyList<string> AvailableFormats => _data.Keys.Where(k => !k.StartsWith(DataPackage.UnoPrivateDataPrefix)).ToArray();

public IAsyncOperation<IReadOnlyDictionary<string, RandomAccessStreamReference>> GetResourceMapAsync()
=> Task.FromResult(_resourceMap as IReadOnlyDictionary<string, RandomAccessStreamReference>).AsAsyncOperation();
Expand All @@ -50,7 +50,11 @@ public bool Contains(string formatId)
return _data.ContainsKey(formatId);
}

internal object? FindRawData(string formatId)
=> _data.TryGetValue(formatId, out var data) ? data : null;

public IAsyncOperation<object> GetDataAsync(string formatId)
// Note: Using this, application can gain access to the data prefixed with DataPackage.UnoPrivateDataKey ... which is acceptable
=> GetData<object>(formatId);

public IAsyncOperation<Uri> GetWebLinkAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@

namespace Windows.ApplicationModel.DataTransfer.DragDrop.Core
{
public partial class CoreDragInfo
public partial class CoreDragInfo
{
private ImmutableList<Action<DataPackageOperation>>? _completions = ImmutableList<Action<DataPackageOperation>>.Empty;
internal delegate void Completed(DataPackageOperation result);

private ImmutableList<Completed>? _completions = ImmutableList<Completed>.Empty;
private int _result = -1;
private IDragEventSource _source;

Expand Down Expand Up @@ -65,7 +67,7 @@ internal void UpdateSource(IDragEventSource src)
(Position, Modifiers) = src.GetState();
}

internal void RegisterCompletedCallback(Action<DataPackageOperation> onCompleted)
internal void RegisterCompletedCallback(Completed onCompleted)
{
if (_result > 0
// If the Update return false, it means that the _completions is null, which means that the _result is now ready!
Expand All @@ -76,7 +78,7 @@ internal void RegisterCompletedCallback(Action<DataPackageOperation> onCompleted
onCompleted((DataPackageOperation)_result);
}

ImmutableList<Action<DataPackageOperation>>? AddCompletion(ImmutableList<Action<DataPackageOperation>>? completions, Action<DataPackageOperation> callback)
ImmutableList<Completed>? AddCompletion(ImmutableList<Completed>? completions, Completed callback)
=> completions?.Add(callback);
}

Expand Down

0 comments on commit aaf4309

Please sign in to comment.