mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 06:07:28 +08:00
Add ManagedDialogPositionerDialogImplHelper
This commit is contained in:
@@ -8,7 +8,7 @@ public class DialogPositionRequest
|
||||
internal DialogPositionRequest(Visual target,
|
||||
DialogHorizontalPlacement horizontalPlacement,
|
||||
DialogVerticalPlacement verticalPlacement,
|
||||
Point? offset = null,
|
||||
Point offset,
|
||||
Rect? anchorRect = null,
|
||||
CustomDialogPlacementCallback? placementCallback = null)
|
||||
{
|
||||
@@ -21,7 +21,8 @@ public class DialogPositionRequest
|
||||
public Visual Target { get; }
|
||||
public DialogHorizontalPlacement HorizontalPlacement { get; set; }
|
||||
public DialogVerticalPlacement VerticalPlacement { get; set; }
|
||||
public Point? Offset { get; }
|
||||
public Point Offset { get; }
|
||||
public Rect? AnchorRect { get; }
|
||||
public DialogPositionerConstraintAdjustment ConstraintAdjustment {get;}
|
||||
public CustomDialogPlacementCallback? PlacementCallback { get; }
|
||||
}
|
||||
@@ -5,13 +5,17 @@ namespace AtomUI.Controls.DialogPositioning;
|
||||
public record CustomDialogPlacement
|
||||
{
|
||||
public DialogHorizontalPlacement HorizontalPlacement { get; set; } = DialogHorizontalPlacement.Center;
|
||||
public DialogVerticalPlacement PlacementAnchor { get; set; } = DialogVerticalPlacement.Center;
|
||||
public DialogVerticalPlacement VerticalPlacement { get; set; } = DialogVerticalPlacement.Center;
|
||||
|
||||
public Size DialogSize { get; }
|
||||
public Visual Target { get; }
|
||||
public Point Offset { get; set; }
|
||||
public DialogPositionerConstraintAdjustment ConstraintAdjustment { get; set; }
|
||||
public Rect AnchorRectangle { get; set; }
|
||||
|
||||
internal CustomDialogPlacement(Size size)
|
||||
internal CustomDialogPlacement(Size size, Visual target)
|
||||
{
|
||||
DialogSize = size;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,125 @@ using Avalonia;
|
||||
|
||||
namespace AtomUI.Controls.DialogPositioning;
|
||||
|
||||
public record struct PopupPositionerParameters
|
||||
{
|
||||
public Size Size { get; set; }
|
||||
public Point Offset { get; set; }
|
||||
}
|
||||
public record struct DialogPositionerParameters
|
||||
{
|
||||
public Rect AnchorRectangle { get; set; }
|
||||
public DialogHorizontalPlacement HorizontalPlacement { get; set; }
|
||||
public DialogVerticalPlacement VerticalPlacement { get; set; }
|
||||
public Size Size { get; set; }
|
||||
public DialogPositionerConstraintAdjustment ConstraintAdjustment { get; set; }
|
||||
public Point Offset { get; set; }
|
||||
}
|
||||
|
||||
public interface IDialogPositioner
|
||||
{
|
||||
void Update(PopupPositionerParameters parameters);
|
||||
void Update(DialogPositionerParameters parameters);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DialogPositionerConstraintAdjustment
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't alter the surface position even if it is constrained on some
|
||||
/// axis, for example partially outside the edge of an output.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Slide the surface along the x axis until it is no longer 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.
|
||||
/// </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>
|
||||
/// 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 = 32,
|
||||
|
||||
All = SlideX|SlideY|ResizeX|ResizeY
|
||||
}
|
||||
|
||||
internal static class DialogPositionerExtensions
|
||||
{
|
||||
public static void Update(
|
||||
this IDialogPositioner positioner,
|
||||
DialogPositionRequest positionRequest,
|
||||
Size popupSize)
|
||||
{
|
||||
if (popupSize == default)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = BuildParameters(positionRequest, popupSize);
|
||||
positioner.Update(parameters);
|
||||
}
|
||||
|
||||
private static DialogPositionerParameters BuildParameters(
|
||||
DialogPositionRequest positionRequest,
|
||||
Size dialogSize)
|
||||
{
|
||||
DialogPositionerParameters positionerParameters = default;
|
||||
positionerParameters.Offset = positionRequest.Offset;
|
||||
positionerParameters.Size = dialogSize;
|
||||
positionerParameters.ConstraintAdjustment = positionRequest.ConstraintAdjustment;
|
||||
if (positionRequest.HorizontalPlacement == DialogHorizontalPlacement.Custom ||
|
||||
positionRequest.VerticalPlacement == DialogVerticalPlacement.Custom)
|
||||
{
|
||||
if (positionRequest.PlacementCallback is null)
|
||||
throw new InvalidOperationException(
|
||||
"CustomDialogPlacementCallback property must be set, when Placement=PlacementMode.Custom");
|
||||
|
||||
var customPlacementParameters = new CustomDialogPlacement(
|
||||
dialogSize,
|
||||
positionRequest.Target)
|
||||
{
|
||||
HorizontalPlacement = positionerParameters.HorizontalPlacement,
|
||||
VerticalPlacement = positionerParameters.VerticalPlacement,
|
||||
ConstraintAdjustment = positionerParameters.ConstraintAdjustment,
|
||||
Offset = positionerParameters.Offset
|
||||
};
|
||||
|
||||
positionRequest.PlacementCallback.Invoke(customPlacementParameters);
|
||||
|
||||
positionerParameters.HorizontalPlacement = customPlacementParameters.HorizontalPlacement;
|
||||
positionerParameters.VerticalPlacement = customPlacementParameters.VerticalPlacement;
|
||||
positionerParameters.ConstraintAdjustment = customPlacementParameters.ConstraintAdjustment;
|
||||
positionerParameters.Offset = customPlacementParameters.Offset;
|
||||
}
|
||||
return positionerParameters;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
|
||||
namespace AtomUI.Controls.DialogPositioning;
|
||||
@@ -31,7 +32,171 @@ public class ManagedDialogPositioner : IDialogPositioner
|
||||
_dialog = dialog;
|
||||
}
|
||||
|
||||
public void Update(PopupPositionerParameters parameters)
|
||||
public void Update(DialogPositionerParameters parameters)
|
||||
{
|
||||
var rect = Calculate(
|
||||
parameters.Size * _dialog.Scaling,
|
||||
new Rect(
|
||||
parameters.AnchorRectangle.TopLeft * _dialog.Scaling,
|
||||
parameters.AnchorRectangle.Size * _dialog.Scaling),
|
||||
parameters.HorizontalPlacement,
|
||||
parameters.VerticalPlacement,
|
||||
parameters.ConstraintAdjustment,
|
||||
parameters.Offset * _dialog.Scaling);
|
||||
|
||||
_dialog.MoveAndResize(
|
||||
rect.Position,
|
||||
rect.Size / _dialog.Scaling);
|
||||
}
|
||||
|
||||
private Rect Calculate(Size translatedSize,
|
||||
Rect anchorRect,
|
||||
DialogHorizontalPlacement horizontalPlacement,
|
||||
DialogVerticalPlacement verticalPlacement,
|
||||
DialogPositionerConstraintAdjustment constraintAdjustment,
|
||||
Point offset)
|
||||
{
|
||||
var parentGeometry = _dialog.ParentClientAreaScreenGeometry;
|
||||
anchorRect = anchorRect.Translate(parentGeometry.TopLeft);
|
||||
|
||||
Rect GetBounds()
|
||||
{
|
||||
var screens = _dialog.Screens;
|
||||
|
||||
var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft))
|
||||
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
|
||||
?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft))
|
||||
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
|
||||
?? screens.FirstOrDefault();
|
||||
|
||||
if (targetScreen != null &&
|
||||
(targetScreen.WorkingArea.Width == 0 && targetScreen.WorkingArea.Height == 0))
|
||||
{
|
||||
return targetScreen.Bounds;
|
||||
}
|
||||
|
||||
return targetScreen?.WorkingArea
|
||||
?? new Rect(0, 0, double.MaxValue, double.MaxValue);
|
||||
}
|
||||
|
||||
var bounds = GetBounds();
|
||||
|
||||
bool FitsInBounds(Rect rc, Direction direction)
|
||||
{
|
||||
if (direction == Direction.Left && rc.X < bounds.X ||
|
||||
direction == Direction.Top && rc.Y < bounds.Y ||
|
||||
direction == Direction.Right && rc.Right > bounds.Right ||
|
||||
direction == Direction.Bottom && rc.Bottom > bounds.Bottom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValid(in Rect rc) => rc.Width > 0 && rc.Height > 0;
|
||||
|
||||
var offsetX = 0.0d;
|
||||
var offsetY = 0.0d;
|
||||
|
||||
if (horizontalPlacement == DialogHorizontalPlacement.Left)
|
||||
{
|
||||
offsetX = 0;
|
||||
}
|
||||
else if (horizontalPlacement == DialogHorizontalPlacement.Right)
|
||||
{
|
||||
offsetX = bounds.Right - translatedSize.Width;
|
||||
}
|
||||
else if (horizontalPlacement == DialogHorizontalPlacement.Center)
|
||||
{
|
||||
offsetX = (bounds.Width - translatedSize.Width) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetX = offset.X;
|
||||
}
|
||||
|
||||
if (verticalPlacement == DialogVerticalPlacement.Top)
|
||||
{
|
||||
offsetY = 0;
|
||||
}
|
||||
else if (verticalPlacement == DialogVerticalPlacement.Bottom)
|
||||
{
|
||||
offsetY = bounds.Bottom - translatedSize.Height;
|
||||
}
|
||||
else if (verticalPlacement == DialogVerticalPlacement.Center)
|
||||
{
|
||||
offsetY = (bounds.Height - translatedSize.Height) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetY = offset.Y;
|
||||
}
|
||||
|
||||
Rect geo = new Rect(new Point(offsetX, offsetY), translatedSize);
|
||||
|
||||
// If sliding is allowed, try moving the rect into the bounds
|
||||
if (constraintAdjustment.HasAllFlags(DialogPositionerConstraintAdjustment.SlideX))
|
||||
{
|
||||
geo = geo.WithX(Math.Max(geo.X, bounds.X));
|
||||
if (geo.Right > bounds.Right)
|
||||
{
|
||||
geo = geo.WithX(bounds.Right - geo.Width);
|
||||
}
|
||||
}
|
||||
|
||||
// Resize the rect horizontally if allowed.
|
||||
if (constraintAdjustment.HasAllFlags(DialogPositionerConstraintAdjustment.ResizeX))
|
||||
{
|
||||
var unconstrainedRect = geo;
|
||||
|
||||
if (!FitsInBounds(unconstrainedRect, Direction.Left))
|
||||
{
|
||||
unconstrainedRect = unconstrainedRect.WithX(bounds.X);
|
||||
}
|
||||
|
||||
if (!FitsInBounds(unconstrainedRect, Direction.Right))
|
||||
{
|
||||
unconstrainedRect = unconstrainedRect.WithWidth(bounds.Width - unconstrainedRect.X);
|
||||
}
|
||||
|
||||
if (IsValid(unconstrainedRect))
|
||||
{
|
||||
geo = unconstrainedRect;
|
||||
}
|
||||
}
|
||||
|
||||
// If sliding is allowed, try moving the rect into the bounds
|
||||
if (constraintAdjustment.HasAllFlags(DialogPositionerConstraintAdjustment.SlideY))
|
||||
{
|
||||
geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
|
||||
if (geo.Bottom > bounds.Bottom)
|
||||
{
|
||||
geo = geo.WithY(bounds.Bottom - geo.Height);
|
||||
}
|
||||
}
|
||||
|
||||
// Resize the rect vertically if allowed.
|
||||
if (constraintAdjustment.HasAllFlags(DialogPositionerConstraintAdjustment.ResizeY))
|
||||
{
|
||||
var unconstrainedRect = geo;
|
||||
|
||||
if (!FitsInBounds(unconstrainedRect, Direction.Top))
|
||||
{
|
||||
unconstrainedRect = unconstrainedRect.WithY(bounds.Y);
|
||||
}
|
||||
|
||||
if (!FitsInBounds(unconstrainedRect, Direction.Bottom))
|
||||
{
|
||||
unconstrainedRect = unconstrainedRect.WithHeight(bounds.Bottom - unconstrainedRect.Y);
|
||||
}
|
||||
|
||||
if (IsValid(unconstrainedRect))
|
||||
{
|
||||
geo = unconstrainedRect;
|
||||
}
|
||||
}
|
||||
|
||||
return geo;
|
||||
}
|
||||
}
|
||||
-1
@@ -38,7 +38,6 @@ public class ManagedDialogPositionerDialogImplHelper : IManagedDialogPositionerD
|
||||
var point = _parent.PointToScreen(default);
|
||||
var size = _parent.ClientSize * Scaling;
|
||||
return new Rect(point.X, point.Y, size.Width, size.Height);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,20 +244,30 @@ public sealed class OverlayDialogHost : ContentControl,
|
||||
if (_needsUpdate && _dialogPositionRequest is not null)
|
||||
{
|
||||
_needsUpdate = false;
|
||||
// _positioner.Update(TopLevel.GetTopLevel(_overlayLayer)!, _popupPositionRequest, _popupSize, FlowDirection);
|
||||
_positioner.Update(_dialogPositionRequest, _dialogSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == IsDragMovableProperty)
|
||||
if (change.Property == IsModalProperty)
|
||||
{
|
||||
if (!IsHidden)
|
||||
{
|
||||
// TODO 需要重构,这样不行
|
||||
Hide();
|
||||
Show();
|
||||
if (change.OldValue is bool oldValue && oldValue && _dialogMask != null)
|
||||
{
|
||||
_dialogLayer.Children.Remove(_dialogMask);
|
||||
}
|
||||
|
||||
if (change.NewValue is bool newValue && newValue)
|
||||
{
|
||||
// 找到自己
|
||||
var index = _dialogLayer.Children.IndexOf(this);
|
||||
_dialogMask ??= new OverlayDialogMask();
|
||||
_dialogLayer.Children.Insert(index, _dialogMask);
|
||||
ConfigureMaskSize(_dialogLayer.Bounds.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user