Skip to content

Commit

Permalink
feat(dragdrop): Add support of Dragging event on the GestureRecognizer
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Oct 16, 2020
1 parent bbff5ab commit 97c476c
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.UI.Input
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented]
#endif
public partial class DraggingEventArgs
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public global::Windows.UI.Input.DraggingState DraggingState
{
Expand All @@ -17,7 +17,7 @@ public partial class DraggingEventArgs
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public global::Windows.Devices.Input.PointerDeviceType PointerDeviceType
{
Expand All @@ -27,7 +27,7 @@ public partial class DraggingEventArgs
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public global::Windows.Foundation.Point Position
{
Expand All @@ -37,7 +37,7 @@ public partial class DraggingEventArgs
}
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")]
public uint ContactCount
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.UI.Input
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented]
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ public void ProcessInertia()
}
}
#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.Input.GestureRecognizer, global::Windows.UI.Input.DraggingEventArgs> Dragging
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.UI.Input
{
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
#if false
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__
[global::Uno.NotImplemented]
#endif
Expand Down
31 changes: 31 additions & 0 deletions src/Uno.UWP/UI/Input/DraggingEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Windows.Devices.Input;
using Windows.Foundation;

namespace Windows.UI.Input
{
public partial class DraggingEventArgs
{
internal DraggingEventArgs(DraggingState state, PointerDeviceType type, Point position)
{
DraggingState = state;
PointerDeviceType = type;
Position = position;
}

public DraggingState DraggingState { get; }

public PointerDeviceType PointerDeviceType { get; }

public Point Position { get; }

[global::Uno.NotImplemented]
public uint ContactCount
{
get
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Input.RightTappedEventArgs", "uint RightTappedEventArgs.ContactCount");
return 0;
}
}
}
}
9 changes: 9 additions & 0 deletions src/Uno.UWP/UI/Input/DraggingState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Windows.UI.Input
{
public enum DraggingState
{
Started,
Continuing,
Completed,
}
}
149 changes: 143 additions & 6 deletions src/Uno.UWP/UI/Input/GestureRecognizer.Gesture.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.System;
using Microsoft.Extensions.Logging;
using Uno.Logging;
Expand All @@ -18,7 +21,7 @@ public partial class GestureRecognizer
private class Gesture
{
private readonly GestureRecognizer _recognizer;
private DispatcherQueueTimer _holdingTimer;
private DispatcherQueueTimer? _holdingTimer;
private GestureSettings _settings;
private HoldingState? _holdingState;

Expand All @@ -28,7 +31,7 @@ private class Gesture

public PointerPoint Down { get; }

public PointerPoint Up { get; private set; }
public PointerPoint? Up { get; private set; }

public bool IsCompleted { get; private set; }

Expand Down Expand Up @@ -161,7 +164,7 @@ private bool TryRecognizeTap()
{
// Note: Up cannot be 'null' here!

_recognizer._lastSingleTap = (PointerIdentifier, Up.Timestamp, Up.Position);
_recognizer._lastSingleTap = (PointerIdentifier, Up!.Timestamp, Up.Position);
_recognizer.Tapped?.Invoke(_recognizer, new TappedEventArgs(PointerType, Down.Position, tapCount: 1));

return true;
Expand Down Expand Up @@ -201,7 +204,7 @@ private bool TryRecognizeRightTap()
}
}

private void TryUpdateHolding(PointerPoint current = null, bool timeElapsed = false)
private void TryUpdateHolding(PointerPoint? current = null, bool timeElapsed = false)
{
Debug.Assert(timeElapsed || current != null);

Expand All @@ -218,8 +221,8 @@ private void TryUpdateHolding(PointerPoint current = null, bool timeElapsed = fa
}
}
else if (SupportsHolding()
&& !_holdingState.HasValue
&& (timeElapsed || IsLongPress(this, current))
&& _holdingState is null
&& (timeElapsed || IsLongPress(Down, current!))
&& IsBeginningOfTapGesture(LeftButton, this))
{
StopHoldingTimer();
Expand Down Expand Up @@ -256,6 +259,7 @@ private bool NeedsHoldingTimer()
{
// When possible we don't start a timer for the Holding event, instead we rely on the fact that
// we get a lot of small moves due to the lack of precision of the capture device (pen and touch).
// Note: We rely on the same side effect for Drag detection (cf. Manipulation.IsBeginningOfDragManipulation).

switch (PointerType)
{
Expand Down Expand Up @@ -288,6 +292,139 @@ private static void OnHoldingTimerTick(DispatcherQueueTimer timer, object _)
((Gesture)timer.State).TryUpdateHolding(timeElapsed: true);
}
#endregion

#region Gestures recognition (static helpers that defines the actual gestures behavior)

// The beginning of a Tap gesture is: 1 down -> * moves close to the down with same buttons pressed
private static bool IsBeginningOfTapGesture(CheckButton isExpectedButton, Gesture points)
{
if (!isExpectedButton(points.Down)) // We validate only the start as for other points we validate the full pointer identifier
{
return false;
}

// Validate tap gesture
// Note: There is no limit for the duration of the tap!
if (points.HasMovedOutOfTapRange || points.HasChangedPointerIdentifier)
{
return false;
}

return true;
}

// A Tap gesture is: 1 down -> * moves close to the down with same buttons pressed -> 1 up
private static bool IsTapGesture(CheckButton isExpectedButton, Gesture points)
{
if (points.Up == null) // no tap if no up!
{
return false;
}

// Validate that all the intermediates points are valid
if (!IsBeginningOfTapGesture(isExpectedButton, points))
{
return false;
}

// For the pointer up, we check only the distance, as it's expected that the pressed button changed!
if (IsOutOfTapRange(points.Down.Position, points.Up.Position))
{
return false;
}

return true;
}

public static bool IsMultiTapGesture((ulong id, ulong ts, Point position) previousTap, PointerPoint down)
{
if (previousTap.ts == 0) // i.s. no previous tap to compare with
{
return false;
}

var currentId = GetPointerIdentifier(down);
var currentTs = down.Timestamp;
var currentPosition = down.Position;

return previousTap.id == currentId
&& currentTs - previousTap.ts <= MultiTapMaxDelayTicks
&& !IsOutOfTapRange(previousTap.position, currentPosition);
}

private static bool IsRightTapGesture(Gesture points, out bool isLongPress)
{
switch (points.PointerType)
{
case PointerDeviceType.Touch:
var isLeftTap = IsTapGesture(LeftButton, points);
if (isLeftTap && IsLongPress(points.Down, points.Up!))
{
isLongPress = true;
return true;
}
#if __IOS__
if (Uno.WinRTFeatureConfiguration.GestureRecognizer.InterpretForceTouchAsRightTap
&& isLeftTap
&& points.HasExceedMinHoldPressure)
{
isLongPress = true; // We handle the pressure exactly like a long press
return true;
}
#endif
isLongPress = false;
return false;

case PointerDeviceType.Pen:
if (IsTapGesture(BarrelButton, points))
{
isLongPress = false;
return true;
}

// Some pens does not have a barrel button, so we also allow long press (and anyway it's the UWP behavior)
if (IsTapGesture(LeftButton, points) && IsLongPress(points.Down, points.Up!))
{
isLongPress = true;
return true;
}

isLongPress = false;
return false;

case PointerDeviceType.Mouse:
if (IsTapGesture(RightButton, points))
{
isLongPress = false;
return true;
}
#if __ANDROID__
// On Android, usually the right button is mapped to back navigation. So, unlike UWP,
// we also allow a long press with the left button to be more user friendly.
if (Uno.WinRTFeatureConfiguration.GestureRecognizer.InterpretMouseLeftLongPressAsRightTap
&& IsTapGesture(LeftButton, points)
&& IsLongPress(points))
{
isLongPress = true;
return true;
}
#endif
isLongPress = false;
return false;

default:
isLongPress = false;
return false;
}
}

private static bool IsLongPress(PointerPoint down, PointerPoint current)
=> current.Timestamp - down.Timestamp > HoldMinDelayTicks;

public static bool IsOutOfTapRange(Point p1, Point p2)
=> Math.Abs(p1.X - p2.X) > TapMaxXDelta
|| Math.Abs(p1.Y - p2.Y) > TapMaxYDelta;
#endregion
}
}
}
Loading

0 comments on commit 97c476c

Please sign in to comment.