Browse Source

Use captured element if available as source for tap gestures (#19222)

* update mouse test to better simulate clicks on captured controls

* add tap failing test

* use captured element if available as source for tap gestures
release/11.3.3
Emmanuel Hansen 1 year ago
committed by Julien Lebosquain
parent
commit
ad34029c1b
  1. 34
      src/Avalonia.Base/Input/Gestures.cs
  2. 34
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  3. 6
      tests/Avalonia.UnitTests/MouseTestHelper.cs

34
src/Avalonia.Base/Input/Gestures.cs

@ -218,9 +218,16 @@ namespace Avalonia.Input
public static void RemoveScrollGestureInertiaStartingHandler(Interactive element, EventHandler<ScrollGestureInertiaStartingEventArgs> handler) => public static void RemoveScrollGestureInertiaStartingHandler(Interactive element, EventHandler<ScrollGestureInertiaStartingEventArgs> handler) =>
element.RemoveHandler(ScrollGestureInertiaStartingEvent, handler); element.RemoveHandler(ScrollGestureInertiaStartingEvent, handler);
private static object? GetCaptured(RoutedEventArgs? args)
{
if (args is not PointerEventArgs pointerEventArgs)
return null;
return pointerEventArgs.Pointer?.Captured ?? pointerEventArgs.Source;
}
private static void PointerPressed(RoutedEventArgs ev) private static void PointerPressed(RoutedEventArgs ev)
{ {
if (ev.Source is null) if (GetCaptured(ev) is not { } source)
{ {
return; return;
} }
@ -228,11 +235,11 @@ namespace Avalonia.Input
if (ev.Route == RoutingStrategies.Bubble) if (ev.Route == RoutingStrategies.Bubble)
{ {
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source; var visual = (Visual)source;
if(s_gestureState != null) if(s_gestureState != null)
{ {
if(s_gestureState.Value.Type == GestureStateType.Holding && ev.Source is Interactive i) if(s_gestureState.Value.Type == GestureStateType.Holding && source is Interactive i)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
} }
@ -246,8 +253,8 @@ namespace Avalonia.Input
if (e.ClickCount % 2 == 1) if (e.ClickCount % 2 == 1)
{ {
s_gestureState = new GestureState(GestureStateType.Pending, e.Pointer); s_gestureState = new GestureState(GestureStateType.Pending, e.Pointer);
s_lastPress.SetTarget(ev.Source); s_lastPress.SetTarget(source);
s_lastPressPoint = e.GetPosition((Visual)ev.Source); s_lastPressPoint = e.GetPosition((Visual)source);
s_holdCancellationToken = new CancellationTokenSource(); s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token; var token = s_holdCancellationToken.Token;
var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
@ -256,7 +263,7 @@ namespace Avalonia.Input
{ {
DispatcherTimer.RunOnce(() => DispatcherTimer.RunOnce(() =>
{ {
if (s_gestureState != null && !token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) if (s_gestureState != null && !token.IsCancellationRequested && source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
{ {
s_gestureState = new GestureState(GestureStateType.Holding, s_gestureState.Value.Pointer); s_gestureState = new GestureState(GestureStateType.Holding, s_gestureState.Value.Pointer);
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
@ -267,8 +274,8 @@ namespace Avalonia.Input
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
if (s_lastPress.TryGetTarget(out var target) && if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source && target == source &&
e.Source is Interactive i) source is Interactive i)
{ {
s_gestureState = new GestureState(GestureStateType.DoubleTapped, e.Pointer); s_gestureState = new GestureState(GestureStateType.DoubleTapped, e.Pointer);
i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
@ -283,10 +290,12 @@ namespace Avalonia.Input
{ {
var e = (PointerReleasedEventArgs)ev; var e = (PointerReleasedEventArgs)ev;
var source = GetCaptured(ev);
if (s_lastPress.TryGetTarget(out var target) && if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source && target == source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right && e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right &&
e.Source is Interactive i) source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
@ -325,9 +334,10 @@ namespace Avalonia.Input
if (ev.Route == RoutingStrategies.Bubble) if (ev.Route == RoutingStrategies.Bubble)
{ {
var e = (PointerEventArgs)ev; var e = (PointerEventArgs)ev;
var source = GetCaptured(e);
if (s_lastPress.TryGetTarget(out var target)) if (s_lastPress.TryGetTarget(out var target))
{ {
if (e.Pointer == s_gestureState?.Pointer && ev.Source is Interactive i) if (e.Pointer == s_gestureState?.Pointer && source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;

34
tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs

@ -88,6 +88,40 @@ namespace Avalonia.Base.UnitTests.Input
Assert.False(raised); Assert.False(raised);
} }
[Fact]
public void Tapped_Should_Be_Raised_From_Captured_Control()
{
Border inner = new Border()
{
Focusable = true,
Name = "Inner"
};
Border border = new Border()
{
Focusable = true,
Child = inner,
Name = "Parent"
};
var root = new TestRoot
{
Child = border
};
var raised = false;
border.PointerPressed += (s, e) =>
{
e.Pointer.Capture(inner);
};
_mouse.Click(border, MouseButton.Left);
root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Left);
Assert.True(raised);
}
[Fact] [Fact]
public void RightTapped_Should_Be_Raised_For_Right_Button() public void RightTapped_Should_Be_Raised_For_Right_Button()
{ {

6
tests/Avalonia.UnitTests/MouseTestHelper.cs

@ -104,7 +104,8 @@ namespace Avalonia.UnitTests
Point position = default, KeyModifiers modifiers = default) Point position = default, KeyModifiers modifiers = default)
{ {
Down(target, source, button, position, modifiers); Down(target, source, button, position, modifiers);
Up(target, source, button, position, modifiers); var captured = (_pointer.Captured as Interactive) ?? source;
Up(captured, captured, button, position, modifiers);
} }
public void DoubleClick(Interactive target, MouseButton button = MouseButton.Left, Point position = default, public void DoubleClick(Interactive target, MouseButton button = MouseButton.Left, Point position = default,
@ -115,7 +116,8 @@ namespace Avalonia.UnitTests
Point position = default, KeyModifiers modifiers = default) Point position = default, KeyModifiers modifiers = default)
{ {
Down(target, source, button, position, modifiers, clickCount: 1); Down(target, source, button, position, modifiers, clickCount: 1);
Up(target, source, button, position, modifiers); var captured = (_pointer.Captured as Interactive) ?? source;
Up(captured, captured, button, position, modifiers);
Down(target, source, button, position, modifiers, clickCount: 2); Down(target, source, button, position, modifiers, clickCount: 2);
} }

Loading…
Cancel
Save