Skip to content

Commit

Permalink
Merge pull request #18795 from ramezgerges/disable_dragging_in_parent…
Browse files Browse the repository at this point in the history
…_recognizers

fix(drag&drop): when a child and a parent subscribe to dragging, only fire on the child
  • Loading branch information
jeromelaban authored Dec 9, 2024
2 parents 15cfdb5 + 9d1be14 commit 8359486
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 33 deletions.
56 changes: 56 additions & 0 deletions src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,62 @@ public async Task When_DragEnter_Fires_Along_DragStarting()
Assert.AreEqual(1, dragOverCount);
}

[TestMethod]
[RunsOnUIThread]
[DataRow(true)]
[DataRow(false)]
#if !HAS_INPUT_INJECTOR
[Ignore("InputInjector is not supported on this platform.")]
#endif
public async Task When_CanDrag_Child_And_Parent(bool childCanDrag)
{
var child = new Rectangle
{
Fill = new SolidColorBrush(Microsoft.UI.Colors.LightBlue),
Width = 100,
Height = 80,
CanDrag = childCanDrag
};
var parent = new Frame
{
Background = new SolidColorBrush(Microsoft.UI.Colors.LightCoral),
Width = 400,
Height = 300,
CanDrag = true,
Content = child
};

var parentDragStartingCount = 0;
var childDragStartingCount = 0;
parent.DragStarting += (_, _) => parentDragStartingCount++;
child.DragStarting += (_, _) => childDragStartingCount++;

await UITestHelper.Load(parent);

var injector = InputInjector.TryCreate() ?? throw new InvalidOperationException("Failed to init the InputInjector");
using var mouse = injector.GetMouse();

mouse.MoveTo(child.GetAbsoluteBoundsRect().GetCenter());
await UITestHelper.WaitForIdle();
mouse.Press();
await UITestHelper.WaitForIdle();
mouse.MoveTo(child.GetAbsoluteBoundsRect().GetCenter() + new Windows.Foundation.Point(20, 0), steps: 10);
await UITestHelper.WaitForIdle();
mouse.Release();
await UITestHelper.WaitForIdle();

if (childCanDrag)
{
Assert.AreEqual(1, childDragStartingCount);
Assert.AreEqual(0, parentDragStartingCount);
}
else
{
Assert.AreEqual(0, childDragStartingCount);
Assert.AreEqual(1, parentDragStartingCount);
}
}

#endregion
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4654,7 +4654,6 @@ public async Task When_UpdateLayout_In_DragDropping()
Content = new TextBlock
{
AllowDrop = true,
CanDrag = true,
Height = 100,
Text = i.ToString()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void DoubleTapped()
[TestMethod]
public void DoubleTapped_Without_Tapped()
{
var sut = new GestureRecognizer { GestureSettings = GestureSettings.DoubleTap };
var sut = new GestureRecognizer { GestureSettings = GestureSettings.Tap | GestureSettings.DoubleTap };
var taps = new List<TappedEventArgs>();
sut.Tapped += (snd, e) => taps.Add(e);

Expand Down
13 changes: 6 additions & 7 deletions src/Uno.UI/UI/Input/GestureRecognizer.Gesture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ namespace Windows.UI.Input
{
public partial class GestureRecognizer
{
public static bool IsOutOfTapRange(Point p1, Point p2)
=> Math.Abs(p1.X - p2.X) > TapMaxXDelta
|| Math.Abs(p1.Y - p2.Y) > TapMaxYDelta;

/// <summary>
/// This is the state machine which handles the gesture ([Double|Right]Tapped and Holding gestures)
/// </summary>
internal class Gesture
private class Gesture
{
private readonly GestureRecognizer _recognizer;
private DispatcherQueueTimer? _holdingTimer;
Expand Down Expand Up @@ -57,7 +61,6 @@ public Gesture(GestureRecognizer recognizer, PointerPoint down)
{
_recognizer = recognizer;
Settings = recognizer._gestureSettings & GestureSettingsHelper.SupportedGestures; // Keep only flags of supported gestures, so we can more quickly disable us if possible
Settings |= GestureSettings.Tap; // On WinUI, Tap is always raised no matter the flag set on the recognizer

Down = down;
PointerIdentifier = GetPointerIdentifier(down);
Expand Down Expand Up @@ -130,7 +133,7 @@ public void PreventGestures(GestureSettings gestures)
}

Settings &= ~gestures;
if ((Settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
if (Settings == GestureSettings.None)
{
IsCompleted = true;
}
Expand Down Expand Up @@ -392,10 +395,6 @@ private static bool IsRightTapGesture(Gesture points, out bool isLongPress)

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
}
}
Expand Down
35 changes: 24 additions & 11 deletions src/Uno.UI/UI/Input/GestureRecognizer.Manipulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private enum ManipulationState
public bool IsTranslateYEnabled => _isTranslateYEnabled;
public bool IsRotateEnabled => _isRotateEnabled;
public bool IsScaleEnabled => _isScaleEnabled;
public bool IsDraggingEnabled => _isDraggingEnable;

internal static void AddPointer(GestureRecognizer recognizer, PointerPoint pointer)
{
Expand Down Expand Up @@ -576,7 +577,7 @@ private bool IsBeginningOfDragManipulation()
// those thresholds are lower than a Tap (and actually only 1px), which does not math the UWP behavior.
var down = _origins.Pointer1;
var current = _currents.Pointer1;
var isOutOfRange = Gesture.IsOutOfTapRange(down.Position, current.Position);
var isOutOfRange = IsOutOfTapRange(down.Position, current.Position);

switch (_deviceType)
{
Expand Down Expand Up @@ -607,6 +608,19 @@ private bool IsBeginningOfDragManipulation()
}
}

public void DisableDragging()
{
StopDragTimer();
if (_state is ManipulationState.Starting)
{
_isDraggingEnable = false;
}
else if (_state is ManipulationState.Started && IsDragManipulation)
{
Complete();
}
}

private bool ShouldStartInertia(ManipulationVelocities velocities)
=> _inertia is null
&& !IsDragManipulation
Expand Down Expand Up @@ -652,24 +666,23 @@ static bool isAngleNearYAxis(double slope)
=> Math.Abs(slope) >= Math.Tan(67.5 * Math.PI / 180);
}

internal struct Thresholds
{
public double TranslateX;
public double TranslateY;
public double Rotate; // Degrees
public double Expansion;
}
internal readonly record struct Thresholds(
double TranslateX,
double TranslateY,
double Rotate, // Degrees
double Expansion
);

// WARNING: This struct is ** MUTABLE **
private struct Points
{
public PointerPoint Pointer1;
private PointerPoint? _pointer2;

public ulong Timestamp;
public ulong Timestamp; // The timestamp of the latest pointer update to either pointer
public Point Center; // This is the center in ** absolute ** coordinates spaces (i.e. relative to the screen)
public float Distance;
public double Angle;
public float Distance; // The distance between the 2 points, or zero if !HasPointer2
public double Angle; // The angle between the horizontal axis and the line segment formed by the 2 points, or zero if !HasPointer2

public bool HasPointer2 => _pointer2 != null;

Expand Down
22 changes: 16 additions & 6 deletions src/Uno.UI/UI/Input/GestureRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public partial class GestureRecognizer
private readonly Logger _log;
private IDictionary<uint, Gesture> _gestures = new Dictionary<uint, Gesture>(_defaultGesturesSize);
private Manipulation _manipulation;
private GestureSettings _gestureSettings;
private GestureSettings _gestureSettings = GestureSettings.Tap; // On WinUI, Tap is always raised no matter the flag set on the recognizer
private bool _isManipulationOrDragEnabled;

public GestureSettings GestureSettings
Expand Down Expand Up @@ -194,16 +194,26 @@ public void CompleteGesture()
}

/// <returns>The set of events that can be raised by this recognizer for this pointer ID</returns>
internal GestureSettings PreventEvents(uint pointerId, GestureSettings events)
internal GestureSettings PreventEvents(PointerIdentifier pointerId, GestureSettings events)
{
if (_gestures.TryGetValue(pointerId, out var gesture))
if ((events & GestureSettings.Drag) != 0 && (_manipulation?.IsActive(pointerId) ?? false))
{
gesture.PreventGestures(events & GestureSettingsHelper.SupportedGestures);
_manipulation?.DisableDragging();
}

return gesture.Settings;
var ret = GestureSettings.None;
if (_gestures.TryGetValue(pointerId.Id, out var gesture))
{
gesture.PreventGestures(events);
ret |= gesture.Settings;
}

if (_manipulation is not null && _manipulation.IsActive(pointerId) && _manipulation.IsDraggingEnabled)
{
ret |= GestureSettings.Drag;
}

return GestureSettings.None;
return ret;
}

#region Manipulations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private static bool IsMultiTapGesture((ulong id, ulong ts, Point position) previ

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

partial void OnPointerPressedPartial(PointerRoutedEventArgs args)
Expand Down
29 changes: 23 additions & 6 deletions src/Uno.UI/UI/Xaml/UIElement.Pointers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Windows.ApplicationModel.DataTransfer.DragDrop;
using Windows.ApplicationModel.DataTransfer.DragDrop.Core;
using Windows.Devices.Haptics;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.UI.Core;
using Microsoft.UI.Xaml.Input;
Expand All @@ -22,12 +23,12 @@
using Uno.UI.Extensions;
using Uno.UI.Xaml;
using Uno.UI.Xaml.Core;
using PointerDeviceType = Windows.Devices.Input.PointerDeviceType;

#if HAS_UNO_WINUI
using Microsoft.UI.Input;
#else
using Windows.UI.Input;
using Windows.Devices.Input;
#endif

namespace Microsoft.UI.Xaml
Expand Down Expand Up @@ -558,19 +559,31 @@ partial void PrepareManagedGestureEventBubbling(RoutedEvent routedEvent, ref Rou
{
if (routedEvent == TappedEvent)
{
GestureRecognizer.PreventEvents(((TappedRoutedEventArgs)args).PointerId, GestureSettings.Tap);
var tappedArgs = (TappedRoutedEventArgs)args;
GestureRecognizer.PreventEvents(
new PointerIdentifier((PointerDeviceType)tappedArgs.PointerDeviceType, tappedArgs.PointerId),
GestureSettings.Tap);
}
else if (routedEvent == DoubleTappedEvent)
{
GestureRecognizer.PreventEvents(((DoubleTappedRoutedEventArgs)args).PointerId, GestureSettings.DoubleTap);
var doubleTappedArgs = (DoubleTappedRoutedEventArgs)args;
GestureRecognizer.PreventEvents(
new PointerIdentifier((PointerDeviceType)doubleTappedArgs.PointerDeviceType, doubleTappedArgs.PointerId),
GestureSettings.DoubleTap);
}
else if (routedEvent == RightTappedEvent)
{
GestureRecognizer.PreventEvents(((RightTappedRoutedEventArgs)args).PointerId, GestureSettings.RightTap);
var rightTappedArgs = (RightTappedRoutedEventArgs)args;
GestureRecognizer.PreventEvents(
new PointerIdentifier((PointerDeviceType)rightTappedArgs.PointerDeviceType, rightTappedArgs.PointerId),
GestureSettings.RightTap);
}
else if (routedEvent == HoldingEvent)
{
GestureRecognizer.PreventEvents(((HoldingRoutedEventArgs)args).PointerId, GestureSettings.Hold);
var holdingArgs = (HoldingRoutedEventArgs)args;
GestureRecognizer.PreventEvents(
new PointerIdentifier((PointerDeviceType)holdingArgs.PointerDeviceType, holdingArgs.PointerId),
GestureSettings.Hold);
}
}
}
Expand Down Expand Up @@ -606,7 +619,7 @@ private void UpdateRaisedGestureEventsFlag(PointerRoutedEventArgs args)
}

var pointerId = args.Pointer.PointerId;
args.GestureEventsAlreadyRaised |= GestureRecognizer.PreventEvents(pointerId, args.GestureEventsAlreadyRaised);
args.GestureEventsAlreadyRaised |= GestureRecognizer.PreventEvents(new PointerIdentifier((PointerDeviceType)args.Pointer.PointerDeviceType, args.Pointer.PointerId), args.GestureEventsAlreadyRaised);
}
#endregion

Expand Down Expand Up @@ -896,7 +909,11 @@ partial void PrepareManagedPointerEventBubbling(RoutedEvent routedEvent, ref Rou
// ptArgs.Pointer.IsInRange && ptArgs.IsPointCoordinatesOver(this) (and probably share it on all platforms).
var isOver = ptArgs.Pointer.IsInRange && (ptArgs.Pointer.PointerDeviceType, ptArgs.Pointer.IsInContact) switch
{
#if HAS_UNO_WINUI
(global::Microsoft.UI.Input.PointerDeviceType.Touch, false) => false,
#else
(PointerDeviceType.Touch, false) => false,
#endif
_ => ptArgs.IsPointCoordinatesOver(this),
};
#endif
Expand Down

0 comments on commit 8359486

Please sign in to comment.