Browse Source

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

pull/8918/head
Max Katz 4 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) if (relativeTo == null)
return pt; return pt;
// If relativeTo visual is from another visual tree and possibly window, translate position first.
var pointRootVisual = _rootVisual; var pointRootVisual = _rootVisual;
if (relativeTo.VisualRoot is { } root if (relativeTo.VisualRoot is { } root
&& _rootVisual != root) && _rootVisual != root)

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

@ -68,27 +68,29 @@ namespace Avalonia.Input
if (dirtyRect.Contains(clientPoint)) 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)) 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() 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; _lastPointer = null;
_lastActivePointerDevice = null; _lastActivePointerDevice = null;
} }
private void ClearPointerOver(IPointer pointer, IInputRoot root, 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; var element = root.PointerOverElement;
if (element is null) if (element is null)
@ -96,11 +98,10 @@ namespace Avalonia.Input
return; 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. // so GetPosition won't return invalid values.
var hasPosition = position.X >= 0 && position.Y >= 0;
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer, 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); timestamp, properties, inputModifiers);
if (element != null && !element.IsAttachedToVisualTree) 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 impl = CreateTopLevelImplMock(renderer.Object);
var invalidateRect = new Rect(0, 0, 15, 15); 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; Canvas canvas;
var root = CreateInputRoot(impl.Object, new Panel var root = (Window)CreateInputRoot(impl.Object, new Panel
{ {
Children = Children =
{ {
(canvas = new Canvas()) (canvas = new Canvas())
} }
}); });
AddEnteredExitedHandlers(HandleEvent, root, canvas);
// Let input know about latest device. // Let input know about latest device.
SetHit(renderer, canvas); SetHit(renderer, canvas);
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, lastClientPosition));
Assert.True(canvas.IsPointerOver); Assert.True(canvas.IsPointerOver);
SetHit(renderer, canvas); SetHit(renderer, canvas);
@ -380,6 +388,52 @@ namespace Avalonia.Base.UnitTests.Input
SetHit(renderer, null); SetHit(renderer, null);
renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect)); renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect));
Assert.False(canvas.IsPointerOver); 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 // 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 namespace Avalonia.Base.UnitTests.Input
{ {
public class PointerTests public class PointerTests : PointerTestsBase
{ {
[Fact] [Fact]
public void On_Capture_Transfer_PointerCaptureLost_Should_Propagate_Up_To_The_Common_Parent() public void On_Capture_Transfer_PointerCaptureLost_Should_Propagate_Up_To_The_Common_Parent()

Loading…
Cancel
Save