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) if (relativeTo == null)
return pt; 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; 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 System.Threading.Tasks;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
namespace Avalonia.Controls.UnitTests.Primitives 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: // This test only applies when using a PopupRoot host and not an overlay popup.
new MockWindowingPlatform(null, if (UsePopupHost)
x => 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) Name = "PART_ContentPresenter",
return null; [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
return MockWindowingPlatform.CreatePopupMock(x).Object; }.RegisterInNameScope(scope)),
}))); };
result.ApplyTemplate();
return result;
}
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(
windowingPlatform: CreateMockWindowingPlatform()));
} }
private IDisposable CreateServicesWithFocus() private IDisposable CreateServicesWithFocus()
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: return UnitTestApplication.Start(TestServices.StyledWindow.With(
new MockWindowingPlatform(null, windowingPlatform: CreateMockWindowingPlatform(),
x => focusManager: new FocusManager(),
{ keyboardDevice: () => new KeyboardDevice()));
if (UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock(x).Object;
}),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
} }
@ -1116,6 +1185,23 @@ namespace Avalonia.Controls.UnitTests.Primitives
KeyModifiers.None); 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) private static Window PreparedWindow(object content = null)
{ {
var w = new Window { Content = content }; 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) public static Mock<IWindowImpl> CreateWindowMock(double initialWidth = 800, double initialHeight = 600)
{ {
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
var position = new PixelPoint();
var clientSize = new Size(initialWidth, initialHeight); var clientSize = new Size(initialWidth, initialHeight);
windowImpl.SetupAllProperties(); windowImpl.SetupAllProperties();
@ -35,7 +34,6 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); 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); 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 => 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.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>())) windowImpl.Setup(x => x.Resize(It.IsAny<Size>(), It.IsAny<WindowResizeReason>()))
.Callback<Size, WindowResizeReason>((x, y) => .Callback<Size, WindowResizeReason>((x, y) =>
{ {
@ -73,9 +77,6 @@ namespace Avalonia.UnitTests
windowImpl.Object.Activated?.Invoke(); windowImpl.Object.Activated?.Invoke();
}); });
windowImpl.Setup(x => x.PointToScreen(It.IsAny<Point>()))
.Returns((Point p) => PixelPoint.FromPoint(p, 1D) + position);
return windowImpl; return windowImpl;
} }
@ -83,10 +84,12 @@ namespace Avalonia.UnitTests
{ {
var popupImpl = new Mock<IPopupImpl>(); var popupImpl = new Mock<IPopupImpl>();
var clientSize = new Size(); var clientSize = new Size();
var position = new PixelPoint();
var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) => var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) =>
{ {
clientSize = size.Constrain(s_screenSize); clientSize = size.Constrain(s_screenSize);
position = pos;
popupImpl.Object.PositionChanged?.Invoke(pos); popupImpl.Object.PositionChanged?.Invoke(pos);
popupImpl.Object.Resized?.Invoke(clientSize, WindowResizeReason.Unspecified); 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.MaxAutoSizeHint).Returns(s_screenSize);
popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); 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(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
popupImpl.Setup(x => x.Dispose()).Callback(() => popupImpl.Setup(x => x.Dispose()).Callback(() =>

Loading…
Cancel
Save