Skip to content

Commit b6bed7f

Browse files
committed
Position popup using the screen containing the anchor point (#21750)
1 parent c407e1b commit b6bed7f

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ private Rect Calculate(Size translatedSize,
109109
Rect GetBounds()
110110
{
111111
var screens = _popup.Screens;
112+
var anchorPoint = GetAnchorPoint(anchorRect, anchor);
113+
var parentGeometryPoint = GetAnchorPoint(parentGeometry, anchor);
112114

113-
var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft))
115+
var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorPoint))
114116
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
115-
?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft))
117+
?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometryPoint))
116118
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
117119
?? screens.FirstOrDefault();
118120

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Collections.Generic;
2+
using Avalonia.Controls.Primitives.PopupPositioning;
3+
using Avalonia.UnitTests;
4+
using Xunit;
5+
6+
namespace Avalonia.Controls.UnitTests.Primitives;
7+
8+
public class ManagedPopupPositionerTests : ScopedTestBase
9+
{
10+
// The anchor rectangle overlaps the 4 screens, so each corner lands on a different screen.
11+
// With no gravity the popup is centered on the anchor point and is slid back into the screen containing that point.
12+
[Theory]
13+
[InlineData(PopupAnchor.TopLeft, 600, 600)]
14+
[InlineData(PopupAnchor.TopRight, 1000, 600)]
15+
[InlineData(PopupAnchor.BottomLeft, 600, 1000)]
16+
[InlineData(PopupAnchor.BottomRight, 1000, 1000)]
17+
public void Uses_Screen_Containing_The_Anchor_Point(PopupAnchor anchor, double expectedX, double expectedY)
18+
{
19+
var popup = new MockManagedPopupPositionerPopup();
20+
var positioner = new ManagedPopupPositioner(popup);
21+
22+
positioner.Update(new PopupPositionerParameters
23+
{
24+
Size = new Size(400, 400),
25+
AnchorRectangle = new Rect(900, 900, 200, 200),
26+
Anchor = anchor,
27+
Gravity = PopupGravity.None,
28+
ConstraintAdjustment = PopupPositionerConstraintAdjustment.All
29+
});
30+
31+
Assert.Equal(new Point(expectedX, expectedY), popup.LastPosition);
32+
}
33+
34+
private sealed class MockManagedPopupPositionerPopup : IManagedPopupPositionerPopup
35+
{
36+
// Four screens arranged in a 2x2 grid, meeting at (1000, 1000).
37+
public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens { get; } =
38+
[
39+
new(new Rect(0, 0, 1000, 1000), new Rect(0, 0, 1000, 1000)),
40+
new(new Rect(1000, 0, 1000, 1000), new Rect(1000, 0, 1000, 1000)),
41+
new(new Rect(0, 1000, 1000, 1000), new Rect(0, 1000, 1000, 1000)),
42+
new(new Rect(1000, 1000, 1000, 1000), new Rect(1000, 1000, 1000, 1000))
43+
];
44+
45+
public Rect ParentClientAreaScreenGeometry => new(0, 0, 1000, 1000);
46+
47+
public double Scaling => 1.0;
48+
49+
public Point LastPosition { get; private set; }
50+
51+
public Size LastSize { get; private set; }
52+
53+
public void MoveAndResize(Point devicePoint, Size virtualSize)
54+
{
55+
LastPosition = devicePoint;
56+
LastSize = virtualSize;
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)