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

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

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
@ -54,6 +55,37 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(2, pointer.PlatformCaptureCalled); 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] [Fact]
public void Capture_Explicit_ShouldNotify_After_Implicit() public void Capture_Explicit_ShouldNotify_After_Implicit()
{ {
@ -109,5 +141,26 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(2, pointer.PlatformCaptureCalled); 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 #nullable enable
using System; using System;
using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -17,12 +18,14 @@ public abstract class PointerTestsBase : ScopedTestBase
protected class TestPointer : Pointer protected class TestPointer : Pointer
{ {
internal int PlatformCaptureCalled = 0; internal int PlatformCaptureCalled = 0;
internal List<IInputElement?> PlatformCaptures { get; } = new();
internal TestPointer(int id, PointerType type, bool isPrimary) : base(id, type, isPrimary) { } internal TestPointer(int id, PointerType type, bool isPrimary) : base(id, type, isPrimary) { }
protected override void PlatformCapture(IInputElement? element) protected override void PlatformCapture(IInputElement? element)
{ {
PlatformCaptureCalled++; PlatformCaptureCalled++;
PlatformCaptures.Add(element);
} }
} }

Loading…
Cancel
Save