Skip to content

Commit

Permalink
feat(dragdrop): Add ability to raise a routed event only for a portio…
Browse files Browse the repository at this point in the history
…n of the tree
  • Loading branch information
dr1rrb committed Dec 1, 2020
1 parent b79f6d8 commit e183f1e
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 32 deletions.
31 changes: 13 additions & 18 deletions src/Uno.UI/UI/Xaml/DragDrop/DropUITarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class DropUITarget : ICoreDropOperationTarget

// Note: As drag events are routed (so they may be received by multiple elements), we might not have an entry for each drop targets.
// We will instead have entry only for leaf (a.k.a. OriginalSource).
// This is valid as UWP does clear the UIOverride as soon as a DragLeave is raised.
// This is valid as UWP does clear the UIOverride as soon as a DragLeave is raised, no matter the number of drop target under pointer.
private readonly Dictionary<UIElement, (DragUIOverride uiOverride, DataPackageOperation acceptedOperation)> _pendingDropTargets
= new Dictionary<UIElement, (DragUIOverride uiOverride, DataPackageOperation acceptedOperation)>();

Expand Down Expand Up @@ -132,28 +132,23 @@ public IAsyncOperation<DataPackageOperation> DropAsync(CoreDragInfo dragInfo)
// First raise the drag leave event on stale branch if any.
if (target.stale is { } staleBranch)
{
var leftElements = staleBranch
// We need to find the actual leaf drop target to fulfill the leaveArgs
var leafTarget = staleBranch
.EnumerateLeafToRoot()
.Select(elt => (isDragOver: _pendingDropTargets.TryGetValue(staleBranch.Leaf, out var dragState), elt, dragState))
.Where(t => t.isDragOver)
.ToArray();
.FirstOrDefault(t => t.isDragOver);

Debug.Assert(leftElements.Length > 0);
var leaveArgs = leafTarget.elt is null // Might happen if the element has been unloaded
? new DragEventArgs(staleBranch.Leaf, dragInfo, new DragUIOverride(new CoreDragUIOverride()))
: new DragEventArgs(leafTarget.elt, dragInfo, leafTarget.dragState.uiOverride);

if (leftElements.Length > 0)
{
// TODO: We should raise the event only from the Leaf to the Root of the branch, not the whole tree like that
// 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);

staleBranch.Leaf.RaiseDragLeave(leaveArgs);
// We raise the event only up to the stale branch root.
// This is important for ListView reordering where a Leave would cancel the reordering.
staleBranch.Leaf.RaiseDragLeave(leaveArgs, upTo: staleBranch.Root);

if (leaveArgs.Deferral is { } deferral)
{
await deferral.Completed(ct);
}
if (leaveArgs.Deferral is { } deferral)
{
await deferral.Completed(ct);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/UIElement.Pointers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -638,11 +638,11 @@ internal void RaiseDragEnterOrOver(global::Windows.UI.Xaml.DragEventArgs args)
SafeRaiseEvent(evt, args);
}

internal void RaiseDragLeave(global::Windows.UI.Xaml.DragEventArgs args)
internal void RaiseDragLeave(global::Windows.UI.Xaml.DragEventArgs args, UIElement upTo = null)
{
if (_draggingOver?.Remove(args.SourceId) ?? false)
{
SafeRaiseEvent(DragLeaveEvent, args);
SafeRaiseEvent(DragLeaveEvent, args, BubblingContext.BubbleUpTo(upTo));
}
}

Expand Down
53 changes: 41 additions & 12 deletions src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,11 +564,11 @@ private void UpdateSubscribedToHandledEventsToo()
SubscribedToHandledEventsToo = subscribedToHandledEventsToo;
}

internal bool SafeRaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args)
internal bool SafeRaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingContext ctx = default)
{
try
{
return RaiseEvent(routedEvent, args);
return RaiseEvent(routedEvent, args, ctx);
}
catch (Exception e)
{
Expand All @@ -588,10 +588,7 @@ internal bool SafeRaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args)
/// <remarks>
/// Return true if event is handled in managed code (shouldn't bubble natively)
/// </remarks>
internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args)
=> RaiseEvent(routedEvent, args, BubblingMode.Bubble);

private bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingMode mode)
internal bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingContext ctx = default)
{
#if TRACE_ROUTED_EVENT_BUBBLING
Debug.Write(new string('\t', Depth) + $"[{routedEvent.Name.Trim().ToUpperInvariant()}] {this.GetDebugName()}\r\n");
Expand All @@ -604,7 +601,7 @@ private bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingM

// [3] Any local handlers?
var isHandled = IsHandled(args);
if (!mode.HasFlag(BubblingMode.IgnoreElement)
if (!ctx.Mode.HasFlag(BubblingMode.IgnoreElement)
&& _eventHandlerStore.TryGetValue(routedEvent, out var handlers)
&& handlers.Any())
{
Expand Down Expand Up @@ -638,7 +635,7 @@ private bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingM
}
}

if (mode.HasFlag(BubblingMode.IgnoreParents))
if (ctx.Mode.HasFlag(BubblingMode.IgnoreParents))
{
return isHandled;
}
Expand Down Expand Up @@ -670,15 +667,28 @@ private bool RaiseEvent(RoutedEvent routedEvent, RoutedEventArgs args, BubblingM
}

// [13] Raise on parent
return RaiseOnParent(routedEvent, args, parent);
return RaiseOnParent(routedEvent, args, parent, ctx);
}

// This method is a workaround for https://github.com/mono/mono/issues/12981
// It can be inlined in RaiseEvent when fixed.
private static bool RaiseOnParent(RoutedEvent routedEvent, RoutedEventArgs args, UIElement parent)
private static bool RaiseOnParent(RoutedEvent routedEvent, RoutedEventArgs args, UIElement parent, BubblingContext ctx)
{
var mode = parent.PrepareManagedEventBubbling(routedEvent, args, out args);
var handledByAnyParent = parent.RaiseEvent(routedEvent, args, mode);

// If we have reached the requested root element on which this event should bubble,
// we make sure to not allow bubbling on parents.
if (parent == ctx.Root)
{
mode |= BubblingMode.IgnoreParents;
}
ctx = new BubblingContext
{
Mode = mode,
Root = ctx.Root
};

var handledByAnyParent = parent.RaiseEvent(routedEvent, args, ctx);

return handledByAnyParent;
}
Expand Down Expand Up @@ -722,8 +732,27 @@ private BubblingMode PrepareManagedEventBubbling(RoutedEvent routedEvent, Routed
partial void PrepareManagedGestureEventBubbling(RoutedEvent routedEvent, ref RoutedEventArgs args, ref BubblingMode bubblingMode);
partial void PrepareManagedDragAndDropEventBubbling(RoutedEvent routedEvent, ref RoutedEventArgs args, ref BubblingMode bubblingMode);

internal struct BubblingContext
{
public static readonly BubblingContext Bubble = default;

public static BubblingContext BubbleUpTo(UIElement root)
=> new BubblingContext {Root = root};

/// <summary>
/// The mode to use for bubbling
/// </summary>
public BubblingMode Mode { get; set; }

/// <summary>
/// An optional root element on which the bubbling should stop.
/// </summary>
/// <remarks>It's expected that the event is raised on this Root element.</remarks>
public UIElement Root { get; set; }
}

[Flags]
private enum BubblingMode
internal enum BubblingMode
{
/// <summary>
/// The event should bubble normally in this element and its parent
Expand Down

0 comments on commit e183f1e

Please sign in to comment.