|
|
|
@ -50,46 +50,48 @@ using Avalonia.VisualTree; |
|
|
|
namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
///
|
|
|
|
/// The IPopupPositioner provides a collection of rules for the placement of a
|
|
|
|
/// a popup relative to its parent. Rules can be defined to ensure
|
|
|
|
/// the popup remains within the visible area's borders, and to
|
|
|
|
/// specify how the popup changes its position, such as sliding along
|
|
|
|
/// an axis, or flipping around a rectangle. These positioner-created rules are
|
|
|
|
/// constrained by the requirement that a popup must intersect with or
|
|
|
|
/// be at least partially adjacent to its parent surface.
|
|
|
|
/// Provides positioning parameters to <see cref="IPopupPositioner"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// The IPopupPositioner provides a collection of rules for the placement of a a popup relative
|
|
|
|
/// to its parent. Rules can be defined to ensure the popup remains within the visible area's
|
|
|
|
/// borders, and to specify how the popup changes its position, such as sliding along an axis,
|
|
|
|
/// or flipping around a rectangle. These positioner-created rules are constrained by the
|
|
|
|
/// requirement that a popup must intersect with or be at least partially adjacent to its parent
|
|
|
|
/// surface.
|
|
|
|
/// </remarks>
|
|
|
|
public struct PopupPositionerParameters |
|
|
|
{ |
|
|
|
private PopupPositioningEdge _gravity; |
|
|
|
private PopupPositioningEdge _anchor; |
|
|
|
private PopupGravity _gravity; |
|
|
|
private PopupAnchor _anchor; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Set the size of the popup that is to be positioned with the positioner
|
|
|
|
/// object. The size is in scaled coordinates.
|
|
|
|
/// Set the size of the popup that is to be positioned with the positioner object, in device-
|
|
|
|
/// independent pixels.
|
|
|
|
/// </summary>
|
|
|
|
public Size Size { get; set; } |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Specify the anchor rectangle within the parent that the popup
|
|
|
|
/// will be placed relative to. The rectangle is relative to the
|
|
|
|
/// parent geometry
|
|
|
|
///
|
|
|
|
/// The anchor rectangle may not extend outside the window geometry of the
|
|
|
|
/// popup's parent. The anchor rectangle is in scaled coordinates
|
|
|
|
/// Specifies the anchor rectangle within the parent that the popup will be placed relative
|
|
|
|
/// to, in device-independent pixels.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// The rectangle is relative to the parent geometry and may not extend outside the window
|
|
|
|
/// geometry of the popup's parent.
|
|
|
|
/// </remarks>
|
|
|
|
public Rect AnchorRectangle { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the anchor point for the anchor rectangle. The specified anchor
|
|
|
|
/// is used derive an anchor point that the popup will be
|
|
|
|
/// positioned relative to. If a corner anchor is set (e.g. 'TopLeft' or
|
|
|
|
/// 'BottomRight'), the anchor point will be at the specified corner;
|
|
|
|
/// otherwise, the derived anchor point will be centered on the specified
|
|
|
|
/// edge, or in the center of the anchor rectangle if no edge is specified.
|
|
|
|
/// Defines the anchor point for the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
public PopupPositioningEdge Anchor |
|
|
|
/// <remarks>
|
|
|
|
/// The specified anchor is used derive an anchor point that the popup will be positioned
|
|
|
|
/// relative to. If a corner anchor is set (e.g. 'TopLeft' or 'BottomRight'), the anchor
|
|
|
|
/// point will be at the specified corner; otherwise, the derived anchor point will be
|
|
|
|
/// centered on the specified edge, or in the center of the anchor rectangle if no edge is
|
|
|
|
/// specified.
|
|
|
|
/// </remarks>
|
|
|
|
public PopupAnchor Anchor |
|
|
|
{ |
|
|
|
get => _anchor; |
|
|
|
set |
|
|
|
@ -100,66 +102,70 @@ namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines in what direction a popup should be positioned, relative to
|
|
|
|
/// the anchor point of the parent. If a corner gravity is
|
|
|
|
/// specified (e.g. 'BottomRight' or 'TopLeft'), then the popup
|
|
|
|
/// will be placed towards the specified gravity; otherwise, the popup
|
|
|
|
/// will be centered over the anchor point on any axis that had no
|
|
|
|
/// gravity specified.
|
|
|
|
/// Defines in what direction a popup should be positioned, relative to the anchor point of
|
|
|
|
/// the parent.
|
|
|
|
/// </summary>
|
|
|
|
public PopupPositioningEdge Gravity |
|
|
|
/// <remarks>
|
|
|
|
/// If a corner gravity is specified (e.g. 'BottomRight' or 'TopLeft'), then the popup will
|
|
|
|
/// be placed towards the specified gravity; otherwise, the popup will be centered over the
|
|
|
|
/// anchor point on any axis that had no gravity specified.
|
|
|
|
/// </remarks>
|
|
|
|
public PopupGravity Gravity |
|
|
|
{ |
|
|
|
get => _gravity; |
|
|
|
set |
|
|
|
{ |
|
|
|
PopupPositioningEdgeHelper.ValidateEdge(value); |
|
|
|
PopupPositioningEdgeHelper.ValidateGravity(value); |
|
|
|
_gravity = value; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Specify how the popup should be positioned if the originally intended
|
|
|
|
/// position caused the popup to be constrained, meaning at least
|
|
|
|
/// partially outside positioning boundaries set by the positioner. The
|
|
|
|
/// adjustment is set by constructing a bitmask describing the adjustment to
|
|
|
|
/// be made when the popup is constrained on that axis.
|
|
|
|
/// Specify how the popup should be positioned if the originally intended position caused
|
|
|
|
/// the popup to be constrained.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// Adjusts the popup position if the intended position caused the popup to be constrained;
|
|
|
|
/// meaning at least partially outside positioning boundaries set by the positioner. The
|
|
|
|
/// adjustment is set by constructing a bitmask describing the adjustment to be made when
|
|
|
|
/// the popup is constrained on that axis.
|
|
|
|
///
|
|
|
|
/// If no bit for one axis is set, the positioner will assume that the child
|
|
|
|
/// surface should not change its position on that axis when constrained.
|
|
|
|
/// If no bit for one axis is set, the positioner will assume that the child surface should
|
|
|
|
/// not change its position on that axis when constrained.
|
|
|
|
///
|
|
|
|
/// If more than one bit for one axis is set, the order of how adjustments
|
|
|
|
/// are applied is specified in the corresponding adjustment descriptions.
|
|
|
|
/// If more than one bit for one axis is set, the order of how adjustments are applied is
|
|
|
|
/// specified in the corresponding adjustment descriptions.
|
|
|
|
///
|
|
|
|
/// The default adjustment is none.
|
|
|
|
/// </summary>
|
|
|
|
/// </remarks>
|
|
|
|
public PopupPositionerConstraintAdjustment ConstraintAdjustment { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Specify the popup position offset relative to the position of the
|
|
|
|
/// anchor on the anchor rectangle and the anchor on the popup. For
|
|
|
|
/// example if the anchor of the anchor rectangle is at (x, y), the popup
|
|
|
|
/// has the gravity bottom|right, and the offset is (ox, oy), the calculated
|
|
|
|
/// surface position will be (x + ox, y + oy). The offset position of the
|
|
|
|
/// surface is the one used for constraint testing. See
|
|
|
|
/// set_constraint_adjustment.
|
|
|
|
///
|
|
|
|
/// An example use case is placing a popup menu on top of a user interface
|
|
|
|
/// element, while aligning the user interface element of the parent surface
|
|
|
|
/// with some user interface element placed somewhere in the popup.
|
|
|
|
/// anchor on the anchor rectangle and the anchor on the popup.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// For example if the anchor of the anchor rectangle is at (x, y), the popup has the
|
|
|
|
/// gravity bottom|right, and the offset is (ox, oy), the calculated surface position will
|
|
|
|
/// be (x + ox, y + oy). The offset position of the surface is the one used for constraint
|
|
|
|
/// testing. See set_constraint_adjustment.
|
|
|
|
///
|
|
|
|
/// An example use case is placing a popup menu on top of a user interface element, while
|
|
|
|
/// aligning the user interface element of the parent surface with some user interface
|
|
|
|
/// element placed somewhere in the popup.
|
|
|
|
/// </remarks>
|
|
|
|
public Point Offset { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The constraint adjustment value define ways how popup position will
|
|
|
|
/// be adjusted if the unadjusted position would result in the popup
|
|
|
|
/// being partly constrained.
|
|
|
|
///
|
|
|
|
/// Whether a popup is considered 'constrained' is left to the positioner
|
|
|
|
/// to determine. For example, the popup may be partly outside the
|
|
|
|
/// target platform defined 'work area', thus necessitating the popup's
|
|
|
|
/// position be adjusted until it is entirely inside the work area.
|
|
|
|
/// Defines how a popup position will be adjusted if the unadjusted position would result in
|
|
|
|
/// the popup being partly constrained.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// Whether a popup is considered 'constrained' is left to the positioner to determine. For
|
|
|
|
/// example, the popup may be partly outside the target platform defined 'work area', thus
|
|
|
|
/// necessitating the popup's position be adjusted until it is entirely inside the work area.
|
|
|
|
/// </remarks>
|
|
|
|
[Flags] |
|
|
|
public enum PopupPositionerConstraintAdjustment |
|
|
|
{ |
|
|
|
@ -171,79 +177,97 @@ namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Slide the surface along the x axis until it is no longer constrained.
|
|
|
|
/// First try to slide towards the direction of the gravity on the x axis
|
|
|
|
/// until either the edge in the opposite direction of the gravity is
|
|
|
|
/// unconstrained or the edge in the direction of the gravity is
|
|
|
|
/// constrained.
|
|
|
|
///
|
|
|
|
/// Then try to slide towards the opposite direction of the gravity on the
|
|
|
|
/// x axis until either the edge in the direction of the gravity is
|
|
|
|
/// unconstrained or the edge in the opposite direction of the gravity is
|
|
|
|
/// constrained.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// First try to slide towards the direction of the gravity on the x axis until either the
|
|
|
|
/// edge in the opposite direction of the gravity is unconstrained or the edge in the
|
|
|
|
/// direction of the gravity is constrained.
|
|
|
|
///
|
|
|
|
/// Then try to slide towards the opposite direction of the gravity on the x axis until
|
|
|
|
/// either the edge in the direction of the gravity is unconstrained or the edge in the
|
|
|
|
/// opposite direction of the gravity is constrained.
|
|
|
|
/// </remarks>
|
|
|
|
SlideX = 1, |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Slide the surface along the y axis until it is no longer constrained.
|
|
|
|
///
|
|
|
|
/// First try to slide towards the direction of the gravity on the y axis
|
|
|
|
/// until either the edge in the opposite direction of the gravity is
|
|
|
|
/// unconstrained or the edge in the direction of the gravity is
|
|
|
|
/// constrained.
|
|
|
|
///
|
|
|
|
/// Then try to slide towards the opposite direction of the gravity on the
|
|
|
|
/// y axis until either the edge in the direction of the gravity is
|
|
|
|
/// unconstrained or the edge in the opposite direction of the gravity is
|
|
|
|
/// constrained.
|
|
|
|
/// */
|
|
|
|
/// Slide the surface along the y axis until it is no longer constrained.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// First try to slide towards the direction of the gravity on the y axis until either the
|
|
|
|
/// edge in the opposite direction of the gravity is unconstrained or the edge in the
|
|
|
|
/// direction of the gravity is constrained.
|
|
|
|
///
|
|
|
|
/// Then try to slide towards the opposite direction of the gravity on the y axis until
|
|
|
|
/// either the edge in the direction of the gravity is unconstrained or the edge in the
|
|
|
|
/// opposite direction of the gravity is constrained.
|
|
|
|
/// </remarks>
|
|
|
|
SlideY = 2, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Invert the anchor and gravity on the x axis if the surface is
|
|
|
|
/// constrained on the x axis. For example, if the left edge of the
|
|
|
|
/// surface is constrained, the gravity is 'left' and the anchor is
|
|
|
|
/// 'left', change the gravity to 'right' and the anchor to 'right'.
|
|
|
|
///
|
|
|
|
/// If the adjusted position also ends up being constrained, the resulting
|
|
|
|
/// position of the flip_x adjustment will be the one before the
|
|
|
|
/// adjustment.
|
|
|
|
/// Invert the anchor and gravity on the x axis if the surface is constrained on the x axis.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// For example, if the left edge of the surface is constrained, the gravity is 'left' and
|
|
|
|
/// the anchor is 'left', change the gravity to 'right' and the anchor to 'right'.
|
|
|
|
///
|
|
|
|
/// If the adjusted position also ends up being constrained, the resulting position of the
|
|
|
|
/// FlipX adjustment will be the one before the adjustment.
|
|
|
|
/// /// </remarks>
|
|
|
|
FlipX = 4, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Invert the anchor and gravity on the y axis if the surface is
|
|
|
|
/// constrained on the y axis. For example, if the bottom edge of the
|
|
|
|
/// surface is constrained, the gravity is 'bottom' and the anchor is
|
|
|
|
/// 'bottom', change the gravity to 'top' and the anchor to 'top'.
|
|
|
|
/// Invert the anchor and gravity on the y axis if the surface is constrained on the y axis.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// For example, if the bottom edge of the surface is constrained, the gravity is 'bottom'
|
|
|
|
/// and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'.
|
|
|
|
///
|
|
|
|
/// The adjusted position is calculated given the original anchor
|
|
|
|
/// rectangle and offset, but with the new flipped anchor and gravity
|
|
|
|
/// values.
|
|
|
|
/// The adjusted position is calculated given the original anchor rectangle and offset, but
|
|
|
|
/// with the new flipped anchor and gravity values.
|
|
|
|
///
|
|
|
|
/// If the adjusted position also ends up being constrained, the resulting
|
|
|
|
/// position of the flip_y adjustment will be the one before the
|
|
|
|
/// adjustment.
|
|
|
|
/// </summary>
|
|
|
|
/// If the adjusted position also ends up being constrained, the resulting position of the
|
|
|
|
/// FlipY adjustment will be the one before the adjustment.
|
|
|
|
/// </remarks>
|
|
|
|
FlipY = 8, |
|
|
|
All = SlideX|SlideY|FlipX|FlipY |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Horizontally resize the surface
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// Resize the surface horizontally so that it is completely unconstrained.
|
|
|
|
/// </remarks>
|
|
|
|
ResizeX = 16, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Vertically resize the surface
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// Resize the surface vertically so that it is completely unconstrained.
|
|
|
|
/// </remarks>
|
|
|
|
ResizeY = 16, |
|
|
|
|
|
|
|
All = SlideX|SlideY|FlipX|FlipY|ResizeX|ResizeY |
|
|
|
} |
|
|
|
|
|
|
|
static class PopupPositioningEdgeHelper |
|
|
|
{ |
|
|
|
public static void ValidateEdge(this PopupPositioningEdge edge) |
|
|
|
public static void ValidateEdge(this PopupAnchor edge) |
|
|
|
{ |
|
|
|
if (((edge & PopupPositioningEdge.Left) != 0 && (edge & PopupPositioningEdge.Right) != 0) |
|
|
|
if (((edge & PopupAnchor.Left) != 0 && (edge & PopupAnchor.Right) != 0) |
|
|
|
|| |
|
|
|
((edge & PopupPositioningEdge.Top) != 0 && (edge & PopupPositioningEdge.Bottom) != 0)) |
|
|
|
((edge & PopupAnchor.Top) != 0 && (edge & PopupAnchor.Bottom) != 0)) |
|
|
|
throw new ArgumentException("Opposite edges specified"); |
|
|
|
} |
|
|
|
|
|
|
|
public static PopupPositioningEdge Flip(this PopupPositioningEdge edge) |
|
|
|
public static void ValidateGravity(this PopupGravity gravity) |
|
|
|
{ |
|
|
|
ValidateEdge((PopupAnchor)gravity); |
|
|
|
} |
|
|
|
|
|
|
|
public static PopupAnchor Flip(this PopupAnchor edge) |
|
|
|
{ |
|
|
|
var hmask = PopupPositioningEdge.Left | PopupPositioningEdge.Right; |
|
|
|
var vmask = PopupPositioningEdge.Top | PopupPositioningEdge.Bottom; |
|
|
|
var hmask = PopupAnchor.Left | PopupAnchor.Right; |
|
|
|
var vmask = PopupAnchor.Top | PopupAnchor.Bottom; |
|
|
|
if ((edge & hmask) != 0) |
|
|
|
edge ^= hmask; |
|
|
|
if ((edge & vmask) != 0) |
|
|
|
@ -251,43 +275,167 @@ namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
return edge; |
|
|
|
} |
|
|
|
|
|
|
|
public static PopupPositioningEdge FlipX(this PopupPositioningEdge edge) |
|
|
|
public static PopupAnchor FlipX(this PopupAnchor edge) |
|
|
|
{ |
|
|
|
if ((edge & PopupPositioningEdge.HorizontalMask) != 0) |
|
|
|
edge ^= PopupPositioningEdge.HorizontalMask; |
|
|
|
if ((edge & PopupAnchor.HorizontalMask) != 0) |
|
|
|
edge ^= PopupAnchor.HorizontalMask; |
|
|
|
return edge; |
|
|
|
} |
|
|
|
|
|
|
|
public static PopupPositioningEdge FlipY(this PopupPositioningEdge edge) |
|
|
|
public static PopupAnchor FlipY(this PopupAnchor edge) |
|
|
|
{ |
|
|
|
if ((edge & PopupPositioningEdge.VerticalMask) != 0) |
|
|
|
edge ^= PopupPositioningEdge.VerticalMask; |
|
|
|
if ((edge & PopupAnchor.VerticalMask) != 0) |
|
|
|
edge ^= PopupAnchor.VerticalMask; |
|
|
|
return edge; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static PopupGravity FlipX(this PopupGravity gravity) |
|
|
|
{ |
|
|
|
return (PopupGravity)FlipX((PopupAnchor)gravity); |
|
|
|
} |
|
|
|
|
|
|
|
public static PopupGravity FlipY(this PopupGravity gravity) |
|
|
|
{ |
|
|
|
return (PopupGravity)FlipY((PopupAnchor)gravity); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the edges around an anchor rectangle on which a popup will open.
|
|
|
|
/// </summary>
|
|
|
|
[Flags] |
|
|
|
public enum PopupPositioningEdge |
|
|
|
public enum PopupAnchor |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// The center of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
None, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The top edge of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
Top = 1, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The bottom edge of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
Bottom = 2, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The left edge of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
Left = 4, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The right edge of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
Right = 8, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The top-left corner of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
TopLeft = Top | Left, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The top-right corner of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
TopRight = Top | Right, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The bottom-left corner of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
BottomLeft = Bottom | Left, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The bottom-right corner of the anchor rectangle.
|
|
|
|
/// </summary>
|
|
|
|
BottomRight = Bottom | Right, |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A mask for the vertical component flags.
|
|
|
|
/// </summary>
|
|
|
|
VerticalMask = Top | Bottom, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A mask for the horizontal component flags.
|
|
|
|
/// </summary>
|
|
|
|
HorizontalMask = Left | Right, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// A mask for all flags.
|
|
|
|
/// </summary>
|
|
|
|
AllMask = VerticalMask|HorizontalMask |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the direction in which a popup will open.
|
|
|
|
/// </summary>
|
|
|
|
[Flags] |
|
|
|
public enum PopupGravity |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// The popup will be centered over the anchor edge.
|
|
|
|
/// </summary>
|
|
|
|
None, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned above the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
Top = 1, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned below the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
Bottom = 2, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the left of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
Left = 4, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the right of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
Right = 8, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the top-left of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
TopLeft = Top | Left, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the top-right of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
TopRight = Top | Right, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the bottom-left of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
BottomLeft = Bottom | Left, |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The popup will be positioned to the bottom-right of the anchor edge
|
|
|
|
/// </summary>
|
|
|
|
BottomRight = Bottom | Right, |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Positions an <see cref="IPopupHost"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// <see cref="IPopupPositioner"/> is an abstraction of the wayland xdg_positioner spec.
|
|
|
|
///
|
|
|
|
/// The popup positioner implementation is determined by the platform implementation. A default
|
|
|
|
/// managed implementation is provided in <see cref="ManagedPopupPositioner"/> for platforms
|
|
|
|
/// on which popups can be arbitrarily positioned.
|
|
|
|
/// </remarks>
|
|
|
|
public interface IPopupPositioner |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// Updates the position of the associated <see cref="IPopupHost"/> according to the
|
|
|
|
/// specified parameters.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="parameters">The positioning parameters.</param>
|
|
|
|
void Update(PopupPositionerParameters parameters); |
|
|
|
} |
|
|
|
|
|
|
|
@ -296,18 +444,19 @@ namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, |
|
|
|
TopLevel topLevel, |
|
|
|
IVisual target, PlacementMode placement, Point offset, |
|
|
|
PopupPositioningEdge anchor, PopupPositioningEdge gravity) |
|
|
|
PopupAnchor anchor, PopupGravity gravity, |
|
|
|
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect) |
|
|
|
{ |
|
|
|
// We need a better way for tracking the last pointer position
|
|
|
|
var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position); |
|
|
|
|
|
|
|
positionerParameters.Offset = offset; |
|
|
|
positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All; |
|
|
|
positionerParameters.ConstraintAdjustment = constraintAdjustment; |
|
|
|
if (placement == PlacementMode.Pointer) |
|
|
|
{ |
|
|
|
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1)); |
|
|
|
positionerParameters.Anchor = PopupPositioningEdge.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
|
|
|
positionerParameters.Anchor = PopupAnchor.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupGravity.BottomRight; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
@ -317,32 +466,33 @@ namespace Avalonia.Controls.Primitives.PopupPositioning |
|
|
|
if (matrix == null) |
|
|
|
{ |
|
|
|
if (target.GetVisualRoot() == null) |
|
|
|
throw new InvalidCastException("Target control is not attached to the visual tree"); |
|
|
|
throw new InvalidCastException("Target control is not in the same tree as the popup parent"); |
|
|
|
throw new InvalidOperationException("Target control is not attached to the visual tree"); |
|
|
|
throw new InvalidOperationException("Target control is not in the same tree as the popup parent"); |
|
|
|
} |
|
|
|
|
|
|
|
positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size) |
|
|
|
.TransformToAABB(matrix.Value); |
|
|
|
var bounds = new Rect(default, target.Bounds.Size); |
|
|
|
var anchorRect = rect ?? bounds; |
|
|
|
positionerParameters.AnchorRectangle = anchorRect.Intersect(bounds).TransformToAABB(matrix.Value); |
|
|
|
|
|
|
|
if (placement == PlacementMode.Right) |
|
|
|
{ |
|
|
|
positionerParameters.Anchor = PopupPositioningEdge.TopRight; |
|
|
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
|
|
|
positionerParameters.Anchor = PopupAnchor.TopRight; |
|
|
|
positionerParameters.Gravity = PopupGravity.BottomRight; |
|
|
|
} |
|
|
|
else if (placement == PlacementMode.Bottom) |
|
|
|
{ |
|
|
|
positionerParameters.Anchor = PopupPositioningEdge.BottomLeft; |
|
|
|
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; |
|
|
|
positionerParameters.Anchor = PopupAnchor.BottomLeft; |
|
|
|
positionerParameters.Gravity = PopupGravity.BottomRight; |
|
|
|
} |
|
|
|
else if (placement == PlacementMode.Left) |
|
|
|
{ |
|
|
|
positionerParameters.Anchor = PopupPositioningEdge.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupPositioningEdge.BottomLeft; |
|
|
|
positionerParameters.Anchor = PopupAnchor.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupGravity.BottomLeft; |
|
|
|
} |
|
|
|
else if (placement == PlacementMode.Top) |
|
|
|
{ |
|
|
|
positionerParameters.Anchor = PopupPositioningEdge.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupPositioningEdge.TopRight; |
|
|
|
positionerParameters.Anchor = PopupAnchor.TopLeft; |
|
|
|
positionerParameters.Gravity = PopupGravity.TopRight; |
|
|
|
} |
|
|
|
else if (placement == PlacementMode.AnchorAndGravity) |
|
|
|
{ |
|
|
|
|