Browse Source

Merge pull request #11502 from AvaloniaUI/repro/popup-transparent-areas

Allow popup to route correct coordinates to Parent.
pull/11549/head
Dan Walmsley 3 years ago
committed by GitHub
parent
commit
3e7cc20d11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Base/Input/PointerEventArgs.cs
  2. 122
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  3. 24
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

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

@ -70,6 +70,17 @@ namespace Avalonia.Input
if (relativeTo == null)
return pt;
// 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 { })
{
// 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);
}
return pt * _rootVisual.TransformToVisual(relativeTo) ?? default;
}

122
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -19,6 +19,7 @@ using Avalonia.Rendering;
using System.Threading.Tasks;
using Avalonia.Threading;
using Avalonia.Interactivity;
using Avalonia.Media;
namespace Avalonia.Controls.UnitTests.Primitives
{
@ -1076,30 +1077,98 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
private IDisposable CreateServices()
[Fact]
public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null,
x =>
// This test only applies when using a PopupRoot host and not an overlay popup.
if (UsePopupHost)
return;
using (CreateServices())
{
var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red };
var popup = new Popup { Child = popupContent, HorizontalOffset = 40, VerticalOffset = 40, Placement = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.TopLeft, PlacementGravity = PopupGravity.BottomRight};
var popupParent = new Border { Child = popup };
var root = PreparedWindow(popupParent);
popup.Open();
// Verify that the popup is positioned at 40,40 as descibed by the Horizontal/
// VerticalOffset: 10,10 becomes 50,50 in screen coordinates.
Assert.Equal(new PixelPoint(50, 50), popupContent.PointToScreen(new Point(10, 10)));
// The popup parent is positioned at 0,0 in screen coordinates so client and
// screen coordinates are the same.
Assert.Equal(new PixelPoint(10, 10), popupParent.PointToScreen(new Point(10, 10)));
// The event will be raised on the popup content at 50,50 (90,90 in screen coordinates)
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
var ev = new PointerPressedEventArgs(
popupContent,
pointer,
popupContent.VisualRoot as PopupRoot,
new Point(50 , 50),
0,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
KeyModifiers.None);
var contentRaised = 0;
var parentRaised = 0;
// The event is raised on the popup content in popup coordinates.
popupContent.AddHandler(Button.PointerPressedEvent, (s, e) =>
{
++contentRaised;
Assert.Equal(new Point(50, 50), e.GetPosition(popupContent));
});
// The event is raised on the parent in root coordinates (which in this case are
// the same as screen coordinates).
popupParent.AddHandler(Button.PointerPressedEvent, (s, e) =>
{
++parentRaised;
Assert.Equal(new Point(90, 90), e.GetPosition(popupParent));
});
popupContent.RaiseEvent(ev);
Assert.Equal(1, contentRaised);
Assert.Equal(1, parentRaised);
}
}
private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null)
{
impl ??= popupParent.PlatformImpl.CreatePopup();
var result = new PopupRoot(popupParent, impl)
{
Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter
{
if(UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock(x).Object;
})));
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
}.RegisterInNameScope(scope)),
};
result.ApplyTemplate();
return result;
}
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(
windowingPlatform: CreateMockWindowingPlatform()));
}
private IDisposable CreateServicesWithFocus()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null,
x =>
{
if (UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock(x).Object;
}),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
return UnitTestApplication.Start(TestServices.StyledWindow.With(
windowingPlatform: CreateMockWindowingPlatform(),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
}
@ -1116,6 +1185,23 @@ namespace Avalonia.Controls.UnitTests.Primitives
KeyModifiers.None);
}
private MockWindowingPlatform CreateMockWindowingPlatform()
{
return new MockWindowingPlatform(() =>
{
var mock = MockWindowingPlatform.CreateWindowMock();
mock.Setup(x => x.CreatePopup()).Returns(() =>
{
if (UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock(mock.Object).Object;
});
return mock.Object;
}, null);
}
private static Window PreparedWindow(object content = null)
{
var w = new Window { Content = content };

24
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -24,7 +24,6 @@ namespace Avalonia.UnitTests
public static Mock<IWindowImpl> CreateWindowMock(double initialWidth = 800, double initialHeight = 600)
{
var windowImpl = new Mock<IWindowImpl>();
var position = new PixelPoint();
var clientSize = new Size(initialWidth, initialHeight);
windowImpl.SetupAllProperties();
@ -35,7 +34,6 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
windowImpl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
@ -51,10 +49,16 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback<PixelPoint>(x =>
{
position = x;
windowImpl.Setup(x => x.Position).Returns(x);
windowImpl.Object.PositionChanged?.Invoke(x);
});
windowImpl.Setup(x => x.PointToScreen(It.IsAny<Point>()))
.Returns<Point>((point) => PixelPoint.FromPoint(point, 1) + windowImpl.Object.Position);
windowImpl.Setup(x => x.PointToClient(It.IsAny<PixelPoint>()))
.Returns<PixelPoint>(point => (point - windowImpl.Object.Position).ToPoint(1));
windowImpl.Setup(x => x.Resize(It.IsAny<Size>(), It.IsAny<WindowResizeReason>()))
.Callback<Size, WindowResizeReason>((x, y) =>
{
@ -73,9 +77,6 @@ namespace Avalonia.UnitTests
windowImpl.Object.Activated?.Invoke();
});
windowImpl.Setup(x => x.PointToScreen(It.IsAny<Point>()))
.Returns((Point p) => PixelPoint.FromPoint(p, 1D) + position);
return windowImpl;
}
@ -83,10 +84,12 @@ namespace Avalonia.UnitTests
{
var popupImpl = new Mock<IPopupImpl>();
var clientSize = new Size();
var position = new PixelPoint();
var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) =>
{
clientSize = size.Constrain(s_screenSize);
position = pos;
popupImpl.Object.PositionChanged?.Invoke(pos);
popupImpl.Object.Resized?.Invoke(clientSize, WindowResizeReason.Unspecified);
});
@ -100,6 +103,15 @@ namespace Avalonia.UnitTests
popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
popupImpl.Setup(x => x.Position).Returns(()=>position);
popupImpl.Setup(x => x.PointToScreen(It.IsAny<Point>()))
.Returns<Point>((point) => PixelPoint.FromPoint(point, 1) + position);
popupImpl.Setup(x => x.PointToClient(It.IsAny<PixelPoint>()))
.Returns<PixelPoint>(point => (point - position).ToPoint(1));
popupImpl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
popupImpl.Setup(x => x.Dispose()).Callback(() =>

Loading…
Cancel
Save