Skip to content

Commit

Permalink
feat(dragdrop): Add ability to override the default UI
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Oct 16, 2020
1 parent fabb408 commit b3f7cb3
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 62 deletions.
52 changes: 49 additions & 3 deletions src/Uno.UI/UI/Xaml/DragDropManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public DragDropManager(Window window)
_window = window;
}

/// <inheritdoc />
public bool AreConcurrentOperationsEnabled { get; set; } = false;

/// <inheritdoc />
public void BeginDragAndDrop(CoreDragInfo info, ICoreDropOperationTarget? target = null)
{
Expand All @@ -28,6 +31,16 @@ public void BeginDragAndDrop(CoreDragInfo info, ICoreDropOperationTarget? target
return;
}

if (!AreConcurrentOperationsEnabled)
{
foreach (var pending in _dragOperations.ToArray())
{
pending.Abort();
}
}

RegisterHandlers();

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

info.RegisterCompletedCallback(_ => _dragOperations.Remove(op));
Expand All @@ -43,13 +56,46 @@ public void BeginDragAndDrop(CoreDragInfo info, ICoreDropOperationTarget? target
return op;
}

public void ProcessPointerEnter(PointerRoutedEventArgs args)
private bool _registered = false;
private void RegisterHandlers()
{
if (_registered)
{
return;
}

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.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;
}

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);

private static void OnPointerReleased(object snd, PointerRoutedEventArgs e)
=> Window.Current.DragDrop.ProcessPointerReleased(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 ProcessPointerExited(PointerRoutedEventArgs args)
public void ProcessPointerExitedWindow(PointerRoutedEventArgs args)
=> FindOperation(args)?.Exited(args);

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

public void ProcessPointerReleased(PointerRoutedEventArgs args)
Expand Down
163 changes: 136 additions & 27 deletions src/Uno.UI/UI/Xaml/DragOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Windows.ApplicationModel.DataTransfer.DragDrop.Core;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Input;
using Windows.UI.Xaml.Input;

namespace Windows.UI.Xaml
Expand All @@ -21,63 +22,120 @@ internal class DragOperation
private readonly ICoreDropOperationTarget _target;
private readonly DragView _view;
private readonly IDisposable _viewHandle;
private readonly CoreDragUIOverride _viewOverride;
private readonly LinkedList<TargetAsyncTask> _queue = new LinkedList<TargetAsyncTask>();

private bool _isRunning;
private bool _isCompleted;
private State _state = State.None;

private enum State
{
None,
Over,
Completing,
Completed
}

public DragOperation(Window window, CoreDragInfo info, ICoreDropOperationTarget? target = null)
{
Info = info;
Pointer = info.Pointer as Pointer;

_target = target ?? new DropUITarget(); // This must be re-created for each drag info! (Caching of the drag ui-override)
_target = target ?? new DropUITarget(window); // The DropUITarget must be re-created for each drag operation! (Caching of the drag ui-override)
_view = new DragView(info.DragUI as DragUI);
_viewHandle = window.OpenDragAndDrop(_view);
_viewOverride = new CoreDragUIOverride(); // UWP does re-use the same instance for each update on _target
}

public CoreDragInfo Info { get; }

public Pointer? Pointer { get; private set; }

internal void Entered(PointerRoutedEventArgs args)
{
UpdateState(args);
Enqueue(EnteredCore, isIgnorable: true);

Task EnteredCore(CancellationToken ct)
=> _target.EnterAsync(Info, new CoreDragUIOverride()).AsTask(ct);
}
=> EnteredOrMoved(args);

internal void Moved(PointerRoutedEventArgs args)
=> EnteredOrMoved(args);

private void EnteredOrMoved(PointerRoutedEventArgs args)
{
UpdateState(args); // It's required to do that as soon as possible in order to update the view's location
Enqueue(MovedCore, isIgnorable: true);
if (_state >= State.Completing)
{
return;
}

Update(args); // It's required to do that as soon as possible in order to update the view's location
Enqueue(Over, isIgnorable: _state == State.Over); // This is ignorable only if we already over

async Task Over(CancellationToken ct)
{
if (_state >= State.Completing)
{
return;
}

var isOver = _state == State.Over;
_state = State.Over;

var acceptedOperation = isOver
? await _target.OverAsync(Info, _viewOverride).AsTask(ct)
: await _target.EnterAsync(Info, _viewOverride).AsTask(ct);
acceptedOperation &= Info.AllowedOperations;

Task MovedCore(CancellationToken ct)
=> _target.OverAsync(Info, new CoreDragUIOverride()).AsTask(ct);
_view.Update(acceptedOperation, _viewOverride);
}
}

internal void Exited(PointerRoutedEventArgs args)
{
UpdateState(args);
Enqueue(ExitedCore);
if (_state >= State.Completing)
{
return;
}

Update(args);
Enqueue(Leave);

async Task Leave(CancellationToken ct)
{
if (_state != State.Over)
{
return;
}

_state = State.None;
await _target.LeaveAsync(Info).AsTask(ct);

Task ExitedCore(CancellationToken ct)
=> _target.LeaveAsync(Info).AsTask(ct);
// When the pointer goes out of the window, we hide our internal control and,
// if supported by the OS, we request a Drag and Drop operation with the native UI.
// TODO: Request native D&D
_view.Hide();
}
}

internal void Dropped(PointerRoutedEventArgs args)
{
UpdateState(args);
Enqueue(DroppedCore);
if (_state >= State.Completing)
{
return;
}

async Task DroppedCore(CancellationToken ct)
Update(args);
Enqueue(Drop);

async Task Drop(CancellationToken ct)
{
var result = DataPackageOperation.None;
try
{
if (_state != State.Over)
{
return;
}

_state = State.Completing;
result = await _target.DropAsync(Info).AsTask(ct);
result &= Info.AllowedOperations;
}
finally
{
Expand All @@ -88,13 +146,24 @@ async Task DroppedCore(CancellationToken ct)

internal void Aborted(PointerRoutedEventArgs args)
{
UpdateState(args);
Enqueue(AbortedCore);
if (_state >= State.Completing)
{
return;
}

async Task AbortedCore(CancellationToken ct)
Update(args);
Enqueue(Abort);

async Task Abort(CancellationToken ct)
{
try
{
if (_state != State.Over)
{
return;
}

_state = State.Completing;
await _target.LeaveAsync(Info).AsTask(ct);
}
finally
Expand All @@ -104,7 +173,41 @@ async Task AbortedCore(CancellationToken ct)
}
}

private void UpdateState(PointerRoutedEventArgs args)
/// <summary>
/// This is used by the manager to abort a pending D&D for any consideration without an event args for the given pointer
/// It ** MUST ** be invoked on the UI thread.
/// </summary>
internal void Abort()
{
if (Pointer == null || _state == State.None)
{
// The D&D didn't had time to be started (or has already left), we can just complete
Complete(DataPackageOperation.None);
return;
}

Enqueue(Abort);

async Task Abort(CancellationToken ct)
{
if (_state != State.Over)
{
return;
}

try
{
await _target.LeaveAsync(Info).AsTask(ct);
}
finally
{
_state = State.Completing;
Complete(DataPackageOperation.None);
}
}
}

private void Update(PointerRoutedEventArgs args)
{
var point = args.GetCurrentPoint(null);
var mods = DragDropModifiers.None;
Expand Down Expand Up @@ -152,6 +255,8 @@ private void UpdateState(PointerRoutedEventArgs args)

private void Enqueue(Func<CancellationToken, Task> action, bool isIgnorable = false)
{
// If possible we debounce multiple "over" update.
// This might happen when the app uses the DragEventsArgs.GetDeferral (or customized the _target).
if (_queue.Last?.Value.IsIgnorable ?? false)
{
_queue.RemoveLast();
Expand All @@ -166,15 +271,15 @@ private async void Run()
{
// This ** MUST ** be run on the UI thread

if (_isRunning || _isCompleted)
if (_isRunning || _state == State.Completed)
{
return;
}

try
{
_isRunning = true;
while (!_isCompleted && _queue.First is { } first)
while (_state != State.Completed && _queue.First is { } first)
{
_queue.RemoveFirst();

Expand Down Expand Up @@ -206,7 +311,11 @@ private async void Run()

private void Complete(DataPackageOperation result)
{
_isCompleted = true;
if (_state == State.Completed)
{
return;
}
_state = State.Completed;

_viewHandle.Dispose();
Info.Complete(result);
Expand Down
20 changes: 15 additions & 5 deletions src/Uno.UI/UI/Xaml/DragUI.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
#nullable enable

using Windows.Foundation;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace Windows.UI.Xaml
{
public partial class DragUI
public partial class DragUI
{
public void SetContentFromBitmapImage( global::Windows.UI.Xaml.Media.Imaging.BitmapImage bitmapImage)
internal ImageSource? Content { get; private set; }

internal Point? Anchor { get; private set; }

public void SetContentFromBitmapImage(BitmapImage bitmapImage)
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.DragUI", "void DragUI.SetContentFromBitmapImage(BitmapImage bitmapImage)");
Content = bitmapImage;
}
public void SetContentFromBitmapImage( global::Windows.UI.Xaml.Media.Imaging.BitmapImage bitmapImage, global::Windows.Foundation.Point anchorPoint)

public void SetContentFromBitmapImage(BitmapImage bitmapImage, Point anchorPoint)
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.DragUI", "void DragUI.SetContentFromBitmapImage(BitmapImage bitmapImage, Point anchorPoint)");
Content = bitmapImage;
Anchor = anchorPoint;
}
}
}
Loading

0 comments on commit b3f7cb3

Please sign in to comment.