Skip to content

Commit

Permalink
fix: Prevent nested elements with GestureRecognizers from raising ove…
Browse files Browse the repository at this point in the history
…rlapping events

(cherry picked from commit a0cdd1c)
  • Loading branch information
ramezgerges authored and mergify[bot] committed Aug 7, 2023
1 parent b82b7fd commit e2b9066
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 30 deletions.
6 changes: 6 additions & 0 deletions src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ public IList<PointerPoint> GetIntermediatePoints(UIElement relativeTo)

public bool Handled { get; set; }

internal bool TapAlreadyRaised { get; set; }

internal bool RightTapAlreadyRaised { get; set; }

internal bool HoldAlreadyRaised { get; set; }

public VirtualKeyModifiers KeyModifiers { get; }

public Pointer Pointer { get; }
Expand Down
16 changes: 15 additions & 1 deletion src/Uno.UI/UI/Xaml/UIElement.Pointers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,8 @@ internal bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = def
GestureRecognizer.ProcessBeforeUpEvent(currentPoint, !ctx.IsInternal || isOverOrCaptured);
}

var (tapAlreadyRaised, rightTapAlreadyRaised, holdAlreadyRaised) = CheckTapEventsRaised(args, currentPoint);

handledInManaged |= SetPressed(args, false, ctx);

// Note: We process the UpEvent between Release and Exited as the gestures like "Tap"
Expand All @@ -1051,7 +1053,7 @@ internal bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = def
// if they are bubbling in managed it means that they where handled a child control,
// so we should not use them for gesture recognition.
var isDragging = GestureRecognizer.IsDragging;
GestureRecognizer.ProcessUpEvent(currentPoint, !ctx.IsInternal || isOverOrCaptured);
GestureRecognizer.ProcessUpEvent(currentPoint, !ctx.IsInternal || isOverOrCaptured, tapAlreadyRaised, rightTapAlreadyRaised, holdAlreadyRaised);
if (isDragging && !ctx.IsInternal)
{
global::Windows.UI.Xaml.Window.Current.DragDrop.ProcessDropped(args);
Expand All @@ -1071,6 +1073,18 @@ internal bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = def
return handledInManaged;
}

private (bool tapAlreadyRaised, bool rightTapAlreadyRaised, bool holdAlreadyRaised) CheckTapEventsRaised(PointerRoutedEventArgs args, PointerPoint currentPoint)
{

var (tapAlreadyRaised, rightTapAlreadyRaised, holdAlreadyRaised) = (args.TapAlreadyRaised, args.RightTapAlreadyRaised, args.HoldAlreadyRaised);
var (canRaiseTapped, canRaiseRightTapped, canRaiseHolding) = IsGestureRecognizerCreated ? GestureRecognizer.CanRaiseTapEvents(currentPoint) : (false, false, false);

args.TapAlreadyRaised |= canRaiseTapped;
args.RightTapAlreadyRaised |= canRaiseRightTapped;
args.HoldAlreadyRaised |= canRaiseHolding;
return (tapAlreadyRaised, rightTapAlreadyRaised, holdAlreadyRaised);
}

private bool OnNativePointerExited(PointerRoutedEventArgs args) => OnPointerExited(args);

internal bool OnPointerExited(PointerRoutedEventArgs args, BubblingContext ctx = default)
Expand Down
60 changes: 34 additions & 26 deletions src/Uno.UWP/UI/Input/GestureRecognizer.Gesture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ private class Gesture
{
private readonly GestureRecognizer _recognizer;
private DispatcherQueueTimer? _holdingTimer;
private GestureSettings _settings;
internal GestureSettings Settings
{
get;
private set;
}

private HoldingState? _holdingState;

public ulong PointerIdentifier { get; }
Expand All @@ -49,8 +54,8 @@ private class Gesture
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
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 @@ -84,7 +89,7 @@ public void ProcessMove(PointerPoint point)
TryUpdateHolding(point);
}

public void ProcessUp(PointerPoint up)
public void ProcessUp(PointerPoint up, bool tapAlreadyRaised, bool rightTapAlreadyRaised, bool holdAlreadyRaised)
{
if (IsCompleted)
{
Expand All @@ -96,8 +101,8 @@ public void ProcessUp(PointerPoint up)
Up = up;

// Process the pointer
TryEndHolding(HoldingState.Completed);
TryRecognize();
TryEndHolding(HoldingState.Completed, holdAlreadyRaised);
TryRecognize(tapAlreadyRaised, rightTapAlreadyRaised);
}

public void ProcessComplete()
Expand All @@ -111,32 +116,32 @@ public void ProcessComplete()
IsCompleted = true;

// Process the pointer
TryEndHolding(HoldingState.Canceled);
TryRecognize();
TryEndHolding(HoldingState.Canceled, false);
TryRecognize(false, false);
}

public void PreventTap()
{
_settings &= ~GestureSettings.Tap;
if ((_settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
Settings &= ~GestureSettings.Tap;
if ((Settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
{
IsCompleted = true;
}
}

public void PreventDoubleTap()
{
_settings &= ~GestureSettings.DoubleTap;
if ((_settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
Settings &= ~GestureSettings.DoubleTap;
if ((Settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
{
IsCompleted = true;
}
}

public void PreventRightTap()
{
_settings &= ~GestureSettings.RightTap;
if ((_settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
Settings &= ~GestureSettings.RightTap;
if ((Settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
{
IsCompleted = true;
}
Expand All @@ -145,17 +150,17 @@ public void PreventRightTap()
public void PreventHolding()
{
StopHoldingTimer();
_settings &= ~(GestureSettings.Hold | GestureSettings.HoldWithMouse);
if ((_settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
Settings &= ~(GestureSettings.Hold | GestureSettings.HoldWithMouse);
if ((Settings & GestureSettingsHelper.SupportedGestures) == GestureSettings.None)
{
IsCompleted = true;
}
}

private void TryRecognize()
private void TryRecognize(bool tapAlreadyRaised, bool rightTapAlreadyRaised)
{
var recognized = TryRecognizeRightTap() // We check right tap first as for touch a right tap is a press and hold of the finger :)
|| TryRecognizeTap();
var recognized = (!rightTapAlreadyRaised && TryRecognizeRightTap()) // We check right tap first as for touch a right tap is a press and hold of the finger :)
|| (!tapAlreadyRaised && TryRecognizeTap());

if (!recognized && _recognizer._log.IsEnabled(LogLevel.Debug))
{
Expand All @@ -165,7 +170,7 @@ private void TryRecognize()

private bool TryRecognizeTap()
{
if (_settings.HasFlag(GestureSettings.Tap) && IsTapGesture(LeftButton, this))
if (Settings.HasFlag(GestureSettings.Tap) && IsTapGesture(LeftButton, this))
{
// Note: Up cannot be 'null' here!

Expand All @@ -182,7 +187,7 @@ private bool TryRecognizeTap()

private bool TryRecognizeMultiTap()
{
if (_settings.HasFlag(GestureSettings.DoubleTap) && IsMultiTapGesture(_recognizer._lastSingleTap, Down))
if (Settings.HasFlag(GestureSettings.DoubleTap) && IsMultiTapGesture(_recognizer._lastSingleTap, Down))
{
_recognizer._lastSingleTap = default; // The Recognizer supports only double tap, even on UWP
_recognizer.Tapped?.Invoke(_recognizer, new TappedEventArgs(PointerType, Down.Position, tapCount: 2));
Expand All @@ -197,7 +202,7 @@ private bool TryRecognizeMultiTap()

private bool TryRecognizeRightTap()
{
if (_settings.HasFlag(GestureSettings.RightTap) && IsRightTapGesture(this, out var isLongPress))
if (Settings.HasFlag(GestureSettings.RightTap) && IsRightTapGesture(this, out var isLongPress))
{
_recognizer.RightTapped?.Invoke(_recognizer, new RightTappedEventArgs(PointerType, Down.Position));

Expand Down Expand Up @@ -237,7 +242,7 @@ private void TryUpdateHolding(PointerPoint? current = null, bool timeElapsed = f
}
}

private void TryEndHolding(HoldingState state)
private void TryEndHolding(HoldingState state, bool holdAlreadyRaised)
{
Debug.Assert(state != HoldingState.Started);

Expand All @@ -246,16 +251,19 @@ private void TryEndHolding(HoldingState state)
if (_holdingState == HoldingState.Started)
{
_holdingState = state;
_recognizer.Holding?.Invoke(_recognizer, new HoldingEventArgs(Down.PointerId, PointerType, Down.Position, state));
if (!holdAlreadyRaised)
{
_recognizer.Holding?.Invoke(_recognizer, new HoldingEventArgs(Down.PointerId, PointerType, Down.Position, state));
}
}
}

#region Holding timer
private bool SupportsHolding()
=> PointerType switch
{
PointerDeviceType.Mouse => _settings.HasFlag(GestureSettings.HoldWithMouse),
_ => _settings.HasFlag(GestureSettings.Hold)
PointerDeviceType.Mouse => Settings.HasFlag(GestureSettings.HoldWithMouse),
_ => Settings.HasFlag(GestureSettings.Hold)
};

private void StartHoldingTimer()
Expand Down
18 changes: 15 additions & 3 deletions src/Uno.UWP/UI/Input/GestureRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ internal void ProcessBeforeUpEvent(PointerPoint value, bool isRelevant)
_manipulation?.Remove(value);
}

public void ProcessUpEvent(PointerPoint value) => ProcessUpEvent(value, true);
public void ProcessUpEvent(PointerPoint value) => ProcessUpEvent(value, true, false, false, false);

internal void ProcessUpEvent(PointerPoint value, bool isRelevant)
internal void ProcessUpEvent(PointerPoint value, bool isRelevant, bool tapAlreadyRaised, bool rightTapAlreadyRaised, bool holdAlreadyRaised)
{
#if NET461 || UNO_REFERENCE_API
if (_gestures.TryGetValue(value.PointerId, out var gesture))
Expand All @@ -146,7 +146,7 @@ internal void ProcessUpEvent(PointerPoint value, bool isRelevant)
// Note: At this point we MAY be IsActive == false, which is the expected behavior (same as UWP)
// even if we will fire some events now.

gesture.ProcessUp(value);
gesture.ProcessUp(value, tapAlreadyRaised, rightTapAlreadyRaised, holdAlreadyRaised);
}
else if (_log.IsEnabled(LogLevel.Debug))
{
Expand Down Expand Up @@ -277,5 +277,17 @@ private static ulong GetPointerIdentifier(PointerPoint point)

return identifier;
}

internal (bool, bool, bool) CanRaiseTapEvents(PointerPoint value)
{
if (_gestures.TryGetValue(value.PointerId, out var gesture))
{
return (Tapped is { } && gesture.Settings.HasFlag(GestureSettings.Tap),
RightTapped is { } && gesture.Settings.HasFlag(GestureSettings.RightTap),
Holding is { } && gesture.Settings.HasFlag(GestureSettings.Hold));
}

return (false, false, false);
}
}
}

0 comments on commit e2b9066

Please sign in to comment.