Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions src/Avalonia.Base/Input/PointerEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;
using Avalonia.Rendering;

namespace Avalonia.Input
{
public class PointerEventArgs : RoutedEventArgs, IKeyModifiersEventArgs
{
private readonly Visual? _rootVisual;
private readonly Point _rootVisualPosition;
private readonly IPresentationSource? _eventPresentationSource; // the original observer of the event
private readonly Point _presentationSourcePosition;
private readonly PointerPointProperties _properties;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;

Expand All @@ -24,8 +24,8 @@ public PointerEventArgs(RoutedEvent? routedEvent,
: base(routedEvent)
{
Source = source;
_rootVisual = rootVisual;
_rootVisualPosition = rootVisualPosition;
_eventPresentationSource = rootVisual?.PresentationSource;
_presentationSourcePosition = rootVisualPosition;
_properties = properties;
Pointer = pointer;
Timestamp = timestamp;
Expand Down Expand Up @@ -76,31 +76,33 @@ private set

private Point GetPosition(Point pt, Visual? relativeTo)
{
if (_rootVisual == null)
return default;
if (relativeTo == null)
return pt;
if (_eventPresentationSource == null || relativeTo?.PresentationSource?.RootVisual == null)
return default;

// If the visual the user passed in, is not connected to the same visual root
// (i.e. they called it for a control inside a popup.
if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is { })
if (relativeTo.PresentationSource != _eventPresentationSource)
{
// Convert to absolute screen coordinates.
var screenPt = _rootVisual.PointToScreen(pt);

// Convert to client co-ordinates of the visual inside the other visual root.
return relativeTo.PointToClient(screenPt);
if (_eventPresentationSource.PointToScreen(pt) is { } screenPt &&
relativeTo.PresentationSource.PointToClient(screenPt) is { } targetClientPt)
{
pt = targetClientPt;
}
else
{
return default;
}
}

return pt * _rootVisual.TransformToVisual(relativeTo) ?? default;
return relativeTo.PresentationSource.RootVisual.TranslatePoint(pt, relativeTo) ?? default;
}

/// <summary>
/// Gets the pointer position relative to a control.
/// </summary>
/// <param name="relativeTo">The visual whose coordinate system to use. Pass null for toplevel coordinate system</param>
/// <returns>The pointer position in the control's coordinates.</returns>
public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
public Point GetPosition(Visual? relativeTo) => GetPosition(_presentationSourcePosition, relativeTo);

/// <summary>
/// Returns the PointerPoint associated with the current event
Expand All @@ -117,7 +119,7 @@ public PointerPoint GetCurrentPoint(Visual? relativeTo)
/// <returns></returns>
public IReadOnlyList<PointerPoint> GetIntermediatePoints(Visual? relativeTo)
{
var previousPoints = _previousPoints?.Value;
var previousPoints = _previousPoints?.Value;
if (previousPoints == null || previousPoints.Count == 0)
return new[] { GetCurrentPoint(relativeTo) };
var points = new PointerPoint[previousPoints.Count + 1];
Expand Down Expand Up @@ -145,7 +147,7 @@ public void PreventGestureRecognition()
/// </summary>
public PointerPointProperties Properties => _properties;
}

public enum MouseButton
{
None,
Expand Down
4 changes: 2 additions & 2 deletions src/Avalonia.Base/Rendering/IPresentationSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ public interface IPresentationSource
/// </summary>
internal Size ClientSize { get; }

internal PixelPoint PointToScreen(Point point);
internal Point PointToClient(PixelPoint point);
internal PixelPoint? PointToScreen(Point point);
internal Point? PointToClient(PixelPoint point);
}
10 changes: 6 additions & 4 deletions src/Avalonia.Base/VisualExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ public static class VisualExtensions
/// <param name="visual">The visual.</param>
/// <param name="point">The point in screen coordinates.</param>
/// <returns>The point in client coordinates.</returns>
/// <exception cref="ArgumentException">Thown when <paramref name="visual"/> does not belong to a visual tree.</exception>
public static Point PointToClient(this Visual visual, PixelPoint point)
{
var source = visual.PresentationSource;
var root = source?.RootVisual ??
throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
var rootPoint = source.PointToClient(point);
throw new ArgumentException("Visual does not belong to a visual tree.", nameof(visual));
var rootPoint = source.PointToClient(point) ?? default;
return root.TranslatePoint(rootPoint, visual)!.Value;
}

Expand All @@ -29,13 +30,14 @@ public static Point PointToClient(this Visual visual, PixelPoint point)
/// <param name="visual">The visual.</param>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
/// <exception cref="ArgumentException">Thown when <paramref name="visual"/> does not belong to a visual tree.</exception>
public static PixelPoint PointToScreen(this Visual visual, Point point)
{
var source = visual.PresentationSource;
var root = source?.RootVisual ??
throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
throw new ArgumentException("Visual does not belong to a visual tree.", nameof(visual));
var p = visual.TranslatePoint(point, root);
return source.PointToScreen(p!.Value);
return source.PointToScreen(p!.Value) ?? default;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public void SceneInvalidated(object? sender, SceneInvalidatedEventArgs sceneInva
_pointerOverPreProcessor?.SceneInvalidated(sceneInvalidatedEventArgs.DirtyRect);
}

public PixelPoint PointToScreen(Point point) => PlatformImpl?.PointToScreen(point) ?? default;
public PixelPoint? PointToScreen(Point point) => PlatformImpl?.PointToScreen(point);

public Point PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point) ?? default;
public Point? PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point);

private void HandleScalingChanged(double scaling)
=> RenderScaling = LayoutHelper.ValidateScaling(scaling);
Expand Down
72 changes: 71 additions & 1 deletion tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Avalonia.Controls;
using System;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media;
Expand Down Expand Up @@ -128,6 +130,74 @@ public void GetPosition_Should_Respect_Control_RenderTransform()
Assert.Equal(new Point(1, 11), result);
}

private IDisposable SetupCrossTreePositionRequest(PixelPoint topLevelPosition, out PointerEventArgs pointerEvent, out Control elementA, out Control elementB)
{
var app = UnitTestApplication.Start(new TestServices(
inputManager: new InputManager(),
renderInterface: new HeadlessPlatformRenderInterface()));

var renderer = new Mock<IHitTester>();
var deviceMock = CreatePointerDeviceMock();
var impl1 = CreateTopLevelImplMock();
// Mocked position: topLevelPosition
impl1.Setup(w => w.PointToScreen(default)).Returns<Point>(p => (PixelPoint.FromPoint(p, 1) + topLevelPosition));

elementA = new Border();
PointerEventArgs? moveEventArgs = null;

elementA.PointerMoved += (s, e) => moveEventArgs = e;
var root1 = CreateInputRoot(impl1.Object, elementA, renderer.Object);

SetMove(deviceMock, root1.InputRoot, elementA);
impl1.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root1));

Assert.NotNull(moveEventArgs);
pointerEvent = moveEventArgs;

var impl2 = CreateTopLevelImplMock();
// Mocked position: topLevelPosition * 2
impl2.Setup(w => w.PointToClient(default)).Returns<PixelPoint>(p => (p - topLevelPosition - topLevelPosition).ToPoint(1));

elementB = new Border();
var root2 = CreateInputRoot(impl2.Object, elementB, renderer.Object);

return app;
}

[Fact]
public void GetPosition_Should_Support_Cross_Tree_Requests()
{
var topLevelOffset = new PixelPoint(5, 0);
using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out _, out var elementB))
{
Assert.Equal(topLevelOffset.ToPoint(1), pointerEvent.GetPosition(elementB));
}
}

[Fact]
public void GetPosition_Should_Return_Default_When_Cross_Tree_Source_Closed()
{
var topLevelOffset = new PixelPoint(5, 0);
using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out var elementA, out var elementB))
{
((PresentationSource)elementA.PresentationSource!).Dispose();

Assert.Equal(default, pointerEvent.GetPosition(elementB));
}
}

[Fact]
public void GetPosition_Should_Return_Default_When_Cross_Tree_Target_Closed()
{
var topLevelOffset = new PixelPoint(5, 0);
using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out _, out var elementB))
{
((PresentationSource)elementB.PresentationSource!).Dispose();

Assert.Equal(default, pointerEvent.GetPosition(elementB));
}
}

[Fact]
public void Mouse_Pointer_Should_Set_Focus_On_Pointer_Pressed()
{
Expand Down
4 changes: 2 additions & 2 deletions tests/Avalonia.RenderTests/TestRenderRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public void Invalidate(Rect rect)
{
}

public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);
public Point? PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);

public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
public PixelPoint? PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);

public IFocusManager? FocusManager { get; }
public IPlatformSettings? PlatformSettings { get; }
Expand Down
4 changes: 2 additions & 2 deletions tests/Avalonia.UnitTests/TestRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ public void Invalidate(Rect rect)
{
}

public Point PointToClient(PixelPoint p) => p.ToPoint(1);
public Point? PointToClient(PixelPoint p) => p.ToPoint(1);

public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public PixelPoint? PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);


public void RegisterChildrenNames()
Expand Down