Browse Source

Fix issue with 0,0 position when pointerover was cleaned up

pull/8918/head
Max Katz 3 years ago
parent
commit
46bf9e2602
  1. 1
      src/Avalonia.Base/Input/PointerEventArgs.cs
  2. 17
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  3. 58
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  4. 2
      tests/Avalonia.Base.UnitTests/Input/PointerTests.cs

1
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -66,6 +66,7 @@ namespace Avalonia.Input
if (relativeTo == null)
return pt;
// If relativeTo visual is from another visual tree and possibly window, translate position first.
var pointRootVisual = _rootVisual;
if (relativeTo.VisualRoot is { } root
&& _rootVisual != root)

17
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@ -68,27 +68,29 @@ namespace Avalonia.Input
if (dirtyRect.Contains(clientPoint))
{
SetPointerOver(pointer, _inputRoot, _inputRoot.InputHitTest(clientPoint), 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint);
SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
else if (!_inputRoot.Bounds.Contains(clientPoint))
{
ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None);
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
}
}
private void ClearPointerOver()
{
if (_lastPointer is (var pointer, var _))
if (_lastPointer is (var pointer, var position))
{
ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None);
var clientPoint = _inputRoot.PointToClient(position);
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
_lastPointer = null;
_lastActivePointerDevice = null;
}
private void ClearPointerOver(IPointer pointer, IInputRoot root,
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
ulong timestamp, Point? position, PointerPointProperties properties, KeyModifiers inputModifiers)
{
var element = root.PointerOverElement;
if (element is null)
@ -96,11 +98,10 @@ namespace Avalonia.Input
return;
}
// Do not pass rootVisual, when we have unknown (negative) position,
// Do not pass rootVisual, when we have unknown position,
// so GetPosition won't return invalid values.
var hasPosition = position.X >= 0 && position.Y >= 0;
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer,
hasPosition ? root : null, hasPosition ? position : default,
position.HasValue ? root : null, position.HasValue ? position.Value : default,
timestamp, properties, inputModifiers);
if (element != null && !element.IsAttachedToVisualTree)

58
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -356,20 +356,28 @@ namespace Avalonia.Base.UnitTests.Input
var impl = CreateTopLevelImplMock(renderer.Object);
var invalidateRect = new Rect(0, 0, 15, 15);
var lastClientPosition = new Point(1, 5);
var result = new List<(object?, string, Point)>();
void HandleEvent(object? sender, PointerEventArgs e)
{
result.Add((sender, e.RoutedEvent!.Name, e.GetPosition(null)));
}
Canvas canvas;
var root = CreateInputRoot(impl.Object, new Panel
var root = (Window)CreateInputRoot(impl.Object, new Panel
{
Children =
{
(canvas = new Canvas())
}
});
AddEnteredExitedHandlers(HandleEvent, root, canvas);
// Let input know about latest device.
SetHit(renderer, canvas);
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, lastClientPosition));
Assert.True(canvas.IsPointerOver);
SetHit(renderer, canvas);
@ -380,6 +388,52 @@ namespace Avalonia.Base.UnitTests.Input
SetHit(renderer, null);
renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect));
Assert.False(canvas.IsPointerOver);
Assert.Equal(
new[]
{
((object?)canvas, nameof(InputElement.PointerEntered), lastClientPosition),
(root, nameof(InputElement.PointerEntered), lastClientPosition),
(canvas, nameof(InputElement.PointerExited), lastClientPosition),
(root, nameof(InputElement.PointerExited), lastClientPosition),
},
result);
}
[Fact]
public void PointerOver_Invalidation_Should_Use_Previously_Captured_Element()
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
var renderer = new Mock<IRenderer>();
var deviceMock = CreatePointerDeviceMock();
var impl = CreateTopLevelImplMock(renderer.Object);
var invalidateRect = new Rect(0, 0, 15, 15);
Canvas canvas1, canvas2;
var root = CreateInputRoot(impl.Object, new Panel
{
Children =
{
(canvas1 = new Canvas()),
(canvas2 = new Canvas())
}
});
canvas1.PointerMoved += (s, a) => a.Pointer.Capture(canvas1);
// Let input know about latest device.
SetHit(renderer, canvas1);
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
Assert.True(canvas1.IsPointerOver);
Assert.False(canvas2.IsPointerOver);
SetHit(renderer, canvas2);
renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect));
Assert.False(canvas1.IsPointerOver);
Assert.True(canvas2.IsPointerOver);
}
// https://github.com/AvaloniaUI/Avalonia/issues/7748

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

@ -8,7 +8,7 @@ using Xunit;
namespace Avalonia.Base.UnitTests.Input
{
public class PointerTests
public class PointerTests : PointerTestsBase
{
[Fact]
public void On_Capture_Transfer_PointerCaptureLost_Should_Propagate_Up_To_The_Common_Parent()

Loading…
Cancel
Save