Skip to content

Commit

Permalink
feat(dragdrop): Abstract the drag event source so they can be built f…
Browse files Browse the repository at this point in the history
…rom another source than pointer
  • Loading branch information
dr1rrb committed Oct 16, 2020
1 parent 4f16eed commit c80db88
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 115 deletions.
111 changes: 68 additions & 43 deletions src/Uno.UI/UI/Xaml/DragDropManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.DataTransfer.DragDrop;
using Windows.ApplicationModel.DataTransfer.DragDrop.Core;
using Windows.UI.Core;
using Windows.UI.Xaml.Input;
using Uno.Foundation.Extensibility;

namespace Windows.UI.Xaml
{
internal class DragDropManager : CoreDragDropManager.IDragDropManager
internal sealed class DragDropManager : CoreDragDropManager.IDragDropManager
{
private readonly Window _window;
private readonly List<DragOperation> _dragOperations = new List<DragOperation>();
private readonly IDragDropExtension? _hostExtension;

private bool _areWindowEventsRegistered;

public DragDropManager(Window window)
{
_window = window;
if (ApiExtensibility.CreateInstance<IDragDropExtension>(this, out var extension))
{
_hostExtension = extension;
}
}

/// <inheritdoc />
Expand All @@ -43,69 +53,84 @@ public void BeginDragAndDrop(CoreDragInfo info, ICoreDropOperationTarget? target
}
}

RegisterHandlers();
RegisterWindowHandlers();

var op = new DragOperation(_window, info, target);
var op = new DragOperation(_window, _hostExtension, info, target);

info.RegisterCompletedCallback(_ => _dragOperations.Remove(op));
_dragOperations.Add(op);
info.RegisterCompletedCallback(_ => _dragOperations.Remove(op));
}

private DragOperation? FindOperation(PointerRoutedEventArgs args)
{
var pointer = args.Pointer!;
var op = _dragOperations.FirstOrDefault(drag => pointer.Equals(drag.Pointer))
?? _dragOperations.FirstOrDefault(drag => drag.Pointer == null);

return op;
}

private bool _registered = false;
private void RegisterHandlers()
/// <summary>
/// This method is expected to be invoked each time a pointer involved in a drag operation is moved,
/// no matter if the drag operation has been initiated from this app or from an external app.
/// </summary>
/// <returns>
/// The last accepted operation.
/// Be aware that due to the async processing of dragging in UWP, this might not be the up to date.
/// </returns>
public DataPackageOperation ProcessMoved(IDragEventSource src)
=> FindOperation(src)?.Moved(src) ?? DataPackageOperation.None;

/// <summary>
/// This method is expected to be invoked when pointer involved in a drag operation is released,
/// no matter if the drag operation has been initiated from this app or from an external app.
/// </summary>
/// <returns>
/// The last accepted operation.
/// Be aware that due to the async processing of dragging in UWP, this might not be the up to date.
/// </returns>
public DataPackageOperation ProcessDropped(IDragEventSource src)
=> FindOperation(src)?.Dropped(src) ?? DataPackageOperation.None;

/// <summary>
/// This method is expected to be invoked when pointer involved in a drag operation
/// is lost for operation initiated by the current app,
/// or left the window (a.k.a. the "virtual pointer" is lost) for operation initiated by an other app.
/// </summary>
/// <returns>
/// The last accepted operation.
/// Be aware that due to the async processing of dragging in UWP, this might not be the up to date.
/// </returns>
public DataPackageOperation ProcessAborted(IDragEventSource src)
=> FindOperation(src)?.Aborted(src) ?? DataPackageOperation.None;

private DragOperation? FindOperation(IDragEventSource src)
=> _dragOperations.FirstOrDefault(drag => drag.Info.SourceId == src.Id);

private void RegisterWindowHandlers()
{
if (_registered)
if (_areWindowEventsRegistered)
{
return;
}

// Those events are subscribed for safety, but they are usually useless as:
//
// # for internally initiated drag operation:
// the pointer is (implicitly) captured by the GestureRecognizer when a Drag manipulation is detected;
//
// # for externally initiated drag operation:
// the current app does not receive any pointer event, but instead receive platform specific drag events,
// that are expected to be interpreted by the IDragDropExtension and forwarded to this manager using the Process* methods.

var root = _window.RootElement;
root.AddHandler(UIElement.PointerEnteredEvent, new PointerEventHandler(OnPointerEntered), handledEventsToo: true);
root.AddHandler(UIElement.PointerExitedEvent, new PointerEventHandler(OnPointerExited), handledEventsToo: true);
root.AddHandler(UIElement.PointerEnteredEvent, new PointerEventHandler(OnPointerMoved), handledEventsToo: true);
root.AddHandler(UIElement.PointerExitedEvent, new PointerEventHandler(OnPointerMoved), handledEventsToo: true);
root.AddHandler(UIElement.PointerMovedEvent, new PointerEventHandler(OnPointerMoved), handledEventsToo: true);
root.AddHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(OnPointerReleased), handledEventsToo: true);
root.AddHandler(UIElement.PointerCanceledEvent, new PointerEventHandler(OnPointerCanceled), handledEventsToo: true);

_registered = true;
_areWindowEventsRegistered = true;
}

private static void OnPointerEntered(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerEnteredWindow(e);

private static void OnPointerExited(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerExitedWindow(e);

private static void OnPointerMoved(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerMovedOverWindow(e);
=> Window.Current.DragDrop.ProcessMoved(e);

private static void OnPointerReleased(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerReleased(e);
=> Window.Current.DragDrop.ProcessDropped(e);

private static void OnPointerCanceled(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerCanceled(e);

public void ProcessPointerEnteredWindow(PointerRoutedEventArgs args)
=> FindOperation(args)?.Entered(args);

public void ProcessPointerExitedWindow(PointerRoutedEventArgs args)
=> FindOperation(args)?.Exited(args);

public void ProcessPointerMovedOverWindow(PointerRoutedEventArgs args)
=> FindOperation(args)?.Moved(args);

public void ProcessPointerReleased(PointerRoutedEventArgs args)
=> FindOperation(args)?.Dropped(args);

public void ProcessPointerCanceled(PointerRoutedEventArgs args)
=> FindOperation(args)?.Aborted(args);
=> Window.Current.DragDrop.ProcessAborted(e);
}
}
9 changes: 3 additions & 6 deletions src/Uno.UI/UI/Xaml/DragEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,16 @@ namespace Windows.UI.Xaml
public partial class DragEventArgs : RoutedEventArgs, ICancellableRoutedEventArgs
{
private readonly CoreDragInfo _info;
private readonly PointerRoutedEventArgs _pointer;

internal DragEventArgs(
UIElement originalSource,
CoreDragInfo info,
DragUIOverride uiOverride,
PointerRoutedEventArgs pointer)
DragUIOverride uiOverride)
: base(originalSource)
{
CanBubbleNatively = false;

_info = info;
_pointer = pointer;

DragUIOverride = uiOverride;
}
Expand All @@ -43,7 +40,7 @@ internal DragEventArgs(

public DragDropModifiers Modifiers => _info.Modifiers;

internal Pointer Pointer => _pointer.Pointer;
internal long SourceId => _info.SourceId;
#endregion

#region Properties that are expected to be updated by the handler
Expand All @@ -55,7 +52,7 @@ internal DragEventArgs(
#endregion

public Point GetPosition(UIElement relativeTo)
=> _pointer.GetCurrentPoint(relativeTo).Position;
=> _info.GetPosition(relativeTo);

public DragOperationDeferral GetDeferral()
=> Deferral ??= new DragOperationDeferral();
Expand Down
33 changes: 7 additions & 26 deletions src/Uno.UI/UI/Xaml/DropUITarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ public IAsyncOperation<DataPackageOperation> OverAsync(CoreDragInfo dragInfo, Co
private IAsyncOperation<DataPackageOperation> EnterOrOverAsync(CoreDragInfo dragInfo, CoreDragUIOverride dragUIOverride)
=> AsyncOperation.FromTask(async ct =>
{
if (!(dragInfo.PointerRoutedEventArgs is PointerRoutedEventArgs ptArgs))
{
this.Log().Error("The pointer event args was not set. Impossible to raise the drag args on the view.");
return DataPackageOperation.None;
}

var target = await UpdateTarget(dragInfo, dragUIOverride, ptArgs, ct);
var target = await UpdateTarget(dragInfo, dragUIOverride, ct);
if (!target.HasValue)
{
return DataPackageOperation.None;
Expand All @@ -85,19 +79,13 @@ private IAsyncOperation<DataPackageOperation> EnterOrOverAsync(CoreDragInfo drag
public IAsyncAction LeaveAsync(CoreDragInfo dragInfo)
=> AsyncAction.FromTask(async ct =>
{
if (!(dragInfo.PointerRoutedEventArgs is PointerRoutedEventArgs ptArgs))
{
this.Log().Error("The pointer event args was not set. Impossible to raise the drag args on the view.");
return;
}

var leaveTasks = _pendingDropTargets.ToArray().Select(RaiseLeave);
_pendingDropTargets.Clear();
Task.WhenAll(leaveTasks);

async Task RaiseLeave(KeyValuePair<UIElement, (DragUIOverride uiOverride, DataPackageOperation acceptedOperation)> target)
{
var args = new DragEventArgs(target.Key, dragInfo, target.Value.uiOverride, ptArgs);
var args = new DragEventArgs(target.Key, dragInfo, target.Value.uiOverride);

target.Key.RaiseDragLeave(args);

Expand All @@ -112,13 +100,7 @@ async Task RaiseLeave(KeyValuePair<UIElement, (DragUIOverride uiOverride, DataPa
public IAsyncOperation<DataPackageOperation> DropAsync(CoreDragInfo dragInfo)
=> AsyncOperation.FromTask(async ct =>
{
if (!(dragInfo.PointerRoutedEventArgs is PointerRoutedEventArgs ptArgs))
{
this.Log().Error("The pointer event args was not set. Impossible to raise the drag args on the view.");
return DataPackageOperation.None;
}

var target = await UpdateTarget(dragInfo, null, ptArgs, ct);
var target = await UpdateTarget(dragInfo, null, ct);
if (!target.HasValue)
{
return DataPackageOperation.None;
Expand All @@ -138,13 +120,12 @@ public IAsyncOperation<DataPackageOperation> DropAsync(CoreDragInfo dragInfo)
private async Task<(UIElement element, global::Windows.UI.Xaml.DragEventArgs args)?> UpdateTarget(
CoreDragInfo dragInfo,
CoreDragUIOverride? dragUIOverride,
PointerRoutedEventArgs ptArgs,
CancellationToken ct)
{
var target = VisualTreeHelper.HitTest(
dragInfo.Position,
getTestability: _getDropHitTestability,
isStale: elt => elt.IsDragOver(ptArgs.Pointer));
isStale: elt => elt.IsDragOver(dragInfo.SourceId));

// First raise the drag leave event on stale branch if any.
if (target.stale is { } staleBranch)
Expand All @@ -163,7 +144,7 @@ public IAsyncOperation<DataPackageOperation> DropAsync(CoreDragInfo dragInfo)
// This is acceptable as a MVP as we usually have only one Drop target par app.
// Anyway if we Leave a bit too much, we will Enter again below
var leaf = leftElements.First();
var leaveArgs = new DragEventArgs(leaf.elt, dragInfo, leaf.dragState.uiOverride, ptArgs);
var leaveArgs = new DragEventArgs(leaf.elt, dragInfo, leaf.dragState.uiOverride);

staleBranch.Leaf.RaiseDragLeave(leaveArgs);

Expand All @@ -182,14 +163,14 @@ public IAsyncOperation<DataPackageOperation> DropAsync(CoreDragInfo dragInfo)
DragEventArgs args;
if (target.element is {} && _pendingDropTargets.TryGetValue(target.element, out var state))
{
args = new DragEventArgs(target.element!, dragInfo, state.uiOverride, ptArgs)
args = new DragEventArgs(target.element!, dragInfo, state.uiOverride)
{
AcceptedOperation = state.acceptedOperation
};
}
else
{
args = new DragEventArgs(target.element!, dragInfo, new DragUIOverride(dragUIOverride ?? new CoreDragUIOverride()), ptArgs);
args = new DragEventArgs(target.element!, dragInfo, new DragUIOverride(dragUIOverride ?? new CoreDragUIOverride()));
}

return (target.element!, args);
Expand Down
16 changes: 14 additions & 2 deletions src/Uno.UI/UI/Xaml/Input/Pointer.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Uno;
using Windows.Devices.Input;

namespace Windows.UI.Xaml.Input
{
public sealed partial class Pointer : IEquatable<Pointer>
{
private static int _unknownId;
internal static long CreateUniqueIdForUnknownPointer()
=> (long)1 << 63 | (long)Interlocked.Increment(ref _unknownId);

public Pointer(uint id, PointerDeviceType type, bool isInContact, bool isInRange)
{
PointerId = id;
PointerDeviceType = type;
IsInContact = isInContact;
IsInRange = isInRange;

UniqueId = (long)PointerDeviceType << 32 | PointerId;
}


Expand All @@ -25,6 +32,11 @@ internal Pointer(uint id, PointerDeviceType type)
}
#endif

/// <summary>
/// A unique identifier which contains <see cref="PointerDeviceType"/> and <see cref="PointerId"/>.
/// </summary>
internal long UniqueId { get; }

public uint PointerId { get; }

public PointerDeviceType PointerDeviceType { get;}
Expand All @@ -43,7 +55,7 @@ public bool Equals(Pointer other)
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;

return PointerDeviceType == other.PointerDeviceType && PointerId == other.PointerId;
return UniqueId == other.UniqueId;
}

public override bool Equals(object obj)
Expand All @@ -52,7 +64,7 @@ public override bool Equals(object obj)
if (ReferenceEquals(this, obj)) return true;
if (!(obj is Pointer other)) return false;

return PointerDeviceType == other.PointerDeviceType && PointerId == other.PointerId;
return UniqueId == other.UniqueId;
}

public override int GetHashCode()
Expand Down
Loading

0 comments on commit c80db88

Please sign in to comment.