ManagedPopupPositioner.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. namespace Avalonia.Controls.Primitives.PopupPositioning
  5. {
  6. public interface IManagedPopupPositionerPopup
  7. {
  8. IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens { get; }
  9. Rect ParentClientAreaScreenGeometry { get; }
  10. void MoveAndResize(Point devicePoint, Size virtualSize);
  11. Point TranslatePoint(Point pt);
  12. Size TranslateSize(Size size);
  13. }
  14. public class ManagedPopupPositionerScreenInfo
  15. {
  16. public Rect Bounds { get; }
  17. public Rect WorkingArea { get; }
  18. public ManagedPopupPositionerScreenInfo(Rect bounds, Rect workingArea)
  19. {
  20. Bounds = bounds;
  21. WorkingArea = workingArea;
  22. }
  23. }
  24. public class ManagedPopupPositioner : IPopupPositioner
  25. {
  26. private readonly IManagedPopupPositionerPopup _popup;
  27. public ManagedPopupPositioner(IManagedPopupPositionerPopup popup)
  28. {
  29. _popup = popup;
  30. }
  31. private static Point GetAnchorPoint(Rect anchorRect, PopupPositioningEdge edge)
  32. {
  33. double x, y;
  34. if ((edge & PopupPositioningEdge.Left) != 0)
  35. x = anchorRect.X;
  36. else if ((edge & PopupPositioningEdge.Right) != 0)
  37. x = anchorRect.Right;
  38. else
  39. x = anchorRect.X + anchorRect.Width / 2;
  40. if ((edge & PopupPositioningEdge.Top) != 0)
  41. y = anchorRect.Y;
  42. else if ((edge & PopupPositioningEdge.Bottom) != 0)
  43. y = anchorRect.Bottom;
  44. else
  45. y = anchorRect.Y + anchorRect.Height / 2;
  46. return new Point(x, y);
  47. }
  48. private static Point Gravitate(Point anchorPoint, Size size, PopupPositioningEdge gravity)
  49. {
  50. double x, y;
  51. if ((gravity & PopupPositioningEdge.Left) != 0)
  52. x = -size.Width;
  53. else if ((gravity & PopupPositioningEdge.Right) != 0)
  54. x = 0;
  55. else
  56. x = -size.Width / 2;
  57. if ((gravity & PopupPositioningEdge.Top) != 0)
  58. y = -size.Height;
  59. else if ((gravity & PopupPositioningEdge.Bottom) != 0)
  60. y = 0;
  61. else
  62. y = -size.Height / 2;
  63. return anchorPoint + new Point(x, y);
  64. }
  65. public void Update(PopupPositionerParameters parameters)
  66. {
  67. Update(_popup.TranslateSize(parameters.Size), parameters.Size,
  68. new Rect(_popup.TranslatePoint(parameters.AnchorRectangle.TopLeft),
  69. _popup.TranslateSize(parameters.AnchorRectangle.Size)),
  70. parameters.Anchor, parameters.Gravity, parameters.ConstraintAdjustment,
  71. _popup.TranslatePoint(parameters.Offset));
  72. }
  73. private void Update(Size translatedSize, Size originalSize,
  74. Rect anchorRect, PopupPositioningEdge anchor, PopupPositioningEdge gravity,
  75. PopupPositionerConstraintAdjustment constraintAdjustment, Point offset)
  76. {
  77. var parentGeometry = _popup.ParentClientAreaScreenGeometry;
  78. anchorRect = anchorRect.Translate(parentGeometry.TopLeft);
  79. Rect GetBounds()
  80. {
  81. var screens = _popup.Screens;
  82. var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft))
  83. ?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
  84. ?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
  85. ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
  86. ?? screens.FirstOrDefault();
  87. if (targetScreen != null && targetScreen.WorkingArea.IsEmpty)
  88. {
  89. return targetScreen.Bounds;
  90. }
  91. return targetScreen?.WorkingArea
  92. ?? new Rect(0, 0, double.MaxValue, double.MaxValue);
  93. }
  94. var bounds = GetBounds();
  95. bool FitsInBounds(Rect rc, PopupPositioningEdge edge = PopupPositioningEdge.AllMask)
  96. {
  97. if ((edge & PopupPositioningEdge.Left) != 0
  98. && rc.X < bounds.X)
  99. return false;
  100. if ((edge & PopupPositioningEdge.Top) != 0
  101. && rc.Y < bounds.Y)
  102. return false;
  103. if ((edge & PopupPositioningEdge.Right) != 0
  104. && rc.Right > bounds.Right)
  105. return false;
  106. if ((edge & PopupPositioningEdge.Bottom) != 0
  107. && rc.Bottom > bounds.Bottom)
  108. return false;
  109. return true;
  110. }
  111. Rect GetUnconstrained(PopupPositioningEdge a, PopupPositioningEdge g) =>
  112. new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize);
  113. var geo = GetUnconstrained(anchor, gravity);
  114. // If flipping geometry and anchor is allowed and helps, use the flipped one,
  115. // otherwise leave it as is
  116. if (!FitsInBounds(geo, PopupPositioningEdge.HorizontalMask)
  117. && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0)
  118. {
  119. var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
  120. if (FitsInBounds(flipped, PopupPositioningEdge.HorizontalMask))
  121. geo = geo.WithX(flipped.X);
  122. }
  123. // If sliding is allowed, try moving the rect into the bounds
  124. if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0)
  125. {
  126. geo = geo.WithX(Math.Max(geo.X, bounds.X));
  127. if (geo.Right > bounds.Right)
  128. geo = geo.WithX(bounds.Right - geo.Width);
  129. }
  130. // If flipping geometry and anchor is allowed and helps, use the flipped one,
  131. // otherwise leave it as is
  132. if (!FitsInBounds(geo, PopupPositioningEdge.VerticalMask)
  133. && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0)
  134. {
  135. var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
  136. if (FitsInBounds(flipped, PopupPositioningEdge.VerticalMask))
  137. geo = geo.WithY(flipped.Y);
  138. }
  139. // If sliding is allowed, try moving the rect into the bounds
  140. if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0)
  141. {
  142. geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
  143. if (geo.Bottom > bounds.Bottom)
  144. geo = geo.WithY(bounds.Bottom - geo.Height);
  145. }
  146. _popup.MoveAndResize(geo.TopLeft, originalSize);
  147. }
  148. }
  149. }