Browse Source

Capture source change notification (#20656)

* Fire CaptureChanging event when source changes

* Do not notfiy platform if only source changed

* Notify ancestors of element to be captured if none yet

* Pointer capture notify on source change tests

---------

Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
release/latest
Jan Kučera 1 month ago
committed by Julien Lebosquain
parent
commit
b08c7e9ebd
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 20
      src/Avalonia.Base/Input/Pointer.cs
  2. 56
      tests/Avalonia.Base.UnitTests/Input/PointerTests.cs

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

@ -54,23 +54,28 @@ namespace Avalonia.Input
internal void Capture(IInputElement? control, CaptureSource source)
{
var oldCapture = Captured;
if (oldCapture == control)
var oldSource = CaptureSource;
// 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)
return;
var oldVisual = oldCapture as Visual;
var newVisual = control as Visual;
IInputElement? commonParent = null;
if (oldVisual != null)
if (oldVisual != null || newVisual != null)
{
commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
var visual = oldVisual ?? newVisual!; // We want the capture to be cancellable even if there is no currently captured element.
foreach (var notifyTarget in visual.GetSelfAndVisualAncestors().OfType<IInputElement>())
{
if (notifyTarget == commonParent)
break;
var args = new PointerCaptureChangingEventArgs(notifyTarget, this, control, source);
notifyTarget.RaiseEvent(args);
if (args.Handled)
return;
if (notifyTarget == commonParent)
break;
}
}
@ -79,7 +84,8 @@ namespace Avalonia.Input
Captured = control;
CaptureSource = source;
if (source != CaptureSource.Platform)
// However, we still want to notify the platform only if the captured element actually changed.
if (oldCapture != control && source != CaptureSource.Platform)
PlatformCapture(control);
if (oldVisual != null)
@ -90,7 +96,7 @@ namespace Avalonia.Input
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this));
}
if (Captured is Visual newVisual)
if (newVisual != null)
newVisual.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)

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

@ -53,5 +53,61 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(2, pointer.PlatformCaptureCalled);
}
[Fact]
public void Capture_Explicit_ShouldNotify_After_Implicit()
{
var pointer = new TestPointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
Border capture = new Border();
List<CaptureSource> sources = new();
capture.PointerCaptureChanging += (sender, e) =>
{
sources.Add(e.CaptureSource);
};
pointer.Capture(capture, CaptureSource.Implicit);
pointer.Capture(capture, CaptureSource.Explicit);
Assert.True(sources.SequenceEqual([CaptureSource.Implicit, CaptureSource.Explicit]));
Assert.Equal(1, pointer.PlatformCaptureCalled);
pointer.Capture(null, CaptureSource.Implicit); // not ignored, so captured element will become null
pointer.Capture(null, CaptureSource.Explicit); // changing from null to null does not notify anything
Assert.True(sources.SequenceEqual([CaptureSource.Implicit, CaptureSource.Explicit, CaptureSource.Implicit]));
Assert.Equal(2, pointer.PlatformCaptureCalled);
}
[Fact]
public void Capture_Explicit_ShouldNotify_After_HandledImplicit()
{
var pointer = new TestPointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
Border capture = new Border();
List<CaptureSource> sources = new();
capture.PointerCaptureChanging += (sender, e) =>
{
sources.Add(e.CaptureSource);
e.Handled = e.CaptureSource == CaptureSource.Implicit;
};
pointer.Capture(capture, CaptureSource.Implicit);
pointer.Capture(capture, CaptureSource.Explicit);
Assert.True(sources.SequenceEqual([CaptureSource.Implicit, CaptureSource.Explicit]));
Assert.Equal(1, pointer.PlatformCaptureCalled);
pointer.Capture(null, CaptureSource.Implicit);
pointer.Capture(null, CaptureSource.Explicit);
Assert.True(sources.SequenceEqual([CaptureSource.Implicit, CaptureSource.Explicit, CaptureSource.Implicit, CaptureSource.Explicit]));
Assert.Equal(2, pointer.PlatformCaptureCalled);
}
}
}

Loading…
Cancel
Save