Browse Source

Use _effective_ capturer (which can be recognizer) for PlatformCapture (#22076)

pull/22095/head
Nikita Tsukanov 1 month ago
committed by GitHub
parent
commit
73ab31fcce
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 46
      src/Avalonia.Base/Input/Pointer.cs
  2. 53
      tests/Avalonia.Base.UnitTests/Input/PointerTests.cs
  3. 3
      tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

46
src/Avalonia.Base/Input/Pointer.cs

@ -58,9 +58,7 @@ namespace Avalonia.Input
private void CaptureLostCore(CaptureSource source)
{
if (Captured != null)
CaptureCore(null, source);
CaptureGestureRecognizerCore(null);
CaptureCore(null, null, source);
IsGestureRecognitionSkipped = false;
}
@ -69,6 +67,8 @@ namespace Avalonia.Input
Capture(control, CaptureSource.Explicit);
}
private IInputElement? EffectiveCapturer => this.CapturedGestureRecognizer?.Target ?? Captured;
internal void Capture(IInputElement? control, CaptureSource source)
{
if (_disposed)
@ -77,16 +77,21 @@ namespace Avalonia.Input
return;
}
CaptureCore(control, source);
CaptureCore(control, null, source);
}
private void CaptureCore(IInputElement? control, CaptureSource source)
private void CaptureCore(
IInputElement? control,
GestureRecognizer? gestureRecognizer,
CaptureSource source)
{
var oldCapture = Captured;
var oldGestureRecognizer = CapturedGestureRecognizer;
var oldSource = CaptureSource;
var oldEffectiveCapturer = EffectiveCapturer;
// If a handler marks Implicit capture as handled, we still want them to have another chance if the element is captured explicitly.
if (oldCapture == control && oldSource == source)
if (oldCapture == control && oldGestureRecognizer == gestureRecognizer && oldSource == source)
return;
var oldVisual = oldCapture as Visual;
@ -110,12 +115,17 @@ namespace Avalonia.Input
if (oldVisual != null)
oldVisual.DetachedFromVisualTree -= OnCaptureDetached;
if (oldGestureRecognizer != gestureRecognizer)
oldGestureRecognizer?.PointerCaptureLostInternal(this);
Captured = control;
CapturedGestureRecognizer = gestureRecognizer;
CaptureSource = source;
// However, we still want to notify the platform only if the captured element actually changed.
if (oldCapture != control && source != CaptureSource.Platform)
PlatformCapture(control);
if (oldEffectiveCapturer != EffectiveCapturer && source != CaptureSource.Platform)
PlatformCapture(EffectiveCapturer);
if (oldVisual != null)
foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
@ -128,9 +138,6 @@ namespace Avalonia.Input
if (newVisual != null)
newVisual.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
CaptureGestureRecognizer(null);
if (Captured == null && CapturedGestureRecognizer == null)
{
IsGestureRecognitionSkipped = false;
@ -199,22 +206,7 @@ namespace Avalonia.Input
return;
}
CaptureGestureRecognizerCore(gestureRecognizer);
}
private void CaptureGestureRecognizerCore(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
{
CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
}
CapturedGestureRecognizer = gestureRecognizer;
if (gestureRecognizer != null)
{
Capture(null);
}
CaptureCore(null, gestureRecognizer, CaptureSource.Explicit);
}
}
}

53
tests/Avalonia.Base.UnitTests/Input/PointerTests.cs

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
@ -54,6 +55,37 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(2, pointer.PlatformCaptureCalled);
}
[Fact]
public void Gesture_Recognizer_Capture_Should_Keep_Platform_Capture_On_Same_Target()
{
var pointer = new TestPointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
var target = new Border();
var recognizer = new TestGestureRecognizer { Target = target };
pointer.Capture(target, CaptureSource.Implicit);
recognizer.CapturePointer(pointer);
Assert.Null(pointer.Captured);
Assert.Same(recognizer, pointer.CapturedGestureRecognizer);
Assert.Equal([target], pointer.PlatformCaptures);
}
[Fact]
public void Gesture_Recognizer_Capture_Should_Move_Platform_Capture_To_Target()
{
var pointer = new TestPointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
var initialCapture = new Border();
var target = new Border { Child = initialCapture };
var recognizer = new TestGestureRecognizer { Target = target };
pointer.Capture(initialCapture, CaptureSource.Implicit);
recognizer.CapturePointer(pointer);
Assert.Null(pointer.Captured);
Assert.Same(recognizer, pointer.CapturedGestureRecognizer);
Assert.Equal([initialCapture, target], pointer.PlatformCaptures);
}
[Fact]
public void Capture_Explicit_ShouldNotify_After_Implicit()
{
@ -109,5 +141,26 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(2, pointer.PlatformCaptureCalled);
}
private sealed class TestGestureRecognizer : GestureRecognizer
{
public void CapturePointer(IPointer pointer) => Capture(pointer);
protected override void PointerPressed(PointerPressedEventArgs e)
{
}
protected override void PointerReleased(PointerReleasedEventArgs e)
{
}
protected override void PointerMoved(PointerEventArgs e)
{
}
protected override void PointerCaptureLost(IPointer pointer)
{
}
}
}
}

3
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
@ -17,12 +18,14 @@ public abstract class PointerTestsBase : ScopedTestBase
protected class TestPointer : Pointer
{
internal int PlatformCaptureCalled = 0;
internal List<IInputElement?> PlatformCaptures { get; } = new();
internal TestPointer(int id, PointerType type, bool isPrimary) : base(id, type, isPrimary) { }
protected override void PlatformCapture(IInputElement? element)
{
PlatformCaptureCalled++;
PlatformCaptures.Add(element);
}
}

Loading…
Cancel
Save