diff --git a/src/AtomUI.Controls/Dialog/DialogPositionRequest.cs b/src/AtomUI.Controls/Dialog/DialogPositionRequest.cs
index 09c67c691..859c719a2 100644
--- a/src/AtomUI.Controls/Dialog/DialogPositionRequest.cs
+++ b/src/AtomUI.Controls/Dialog/DialogPositionRequest.cs
@@ -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; }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Dialog/DialogPositioning/CustomDialogPlacement.cs b/src/AtomUI.Controls/Dialog/DialogPositioning/CustomDialogPlacement.cs
index 578fa39fd..f63b7936c 100644
--- a/src/AtomUI.Controls/Dialog/DialogPositioning/CustomDialogPlacement.cs
+++ b/src/AtomUI.Controls/Dialog/DialogPositioning/CustomDialogPlacement.cs
@@ -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;
}
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Dialog/DialogPositioning/IDialogPositioner.cs b/src/AtomUI.Controls/Dialog/DialogPositioning/IDialogPositioner.cs
index 29d339ee5..1455193b9 100644
--- a/src/AtomUI.Controls/Dialog/DialogPositioning/IDialogPositioner.cs
+++ b/src/AtomUI.Controls/Dialog/DialogPositioning/IDialogPositioner.cs
@@ -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
+{
+ ///
+ /// Don't alter the surface position even if it is constrained on some
+ /// axis, for example partially outside the edge of an output.
+ ///
+ None = 0,
+
+ ///
+ /// 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.
+ ///
+ SlideX = 1,
+
+ ///
+ /// 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.
+ ///
+ SlideY = 2,
+
+ ///
+ /// Horizontally resize the surface
+ ///
+ ///
+ /// Resize the surface horizontally so that it is completely unconstrained.
+ ///
+ ResizeX = 16,
+
+ ///
+ /// Vertically resize the surface
+ ///
+ ///
+ /// Resize the surface vertically so that it is completely unconstrained.
+ ///
+ 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;
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositioner.cs b/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositioner.cs
index 6ae272abe..7e12d6086 100644
--- a/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositioner.cs
+++ b/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositioner.cs
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositionerDialogImplHelper.cs b/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositionerDialogImplHelper.cs
index b8e6d74ee..a160d444f 100644
--- a/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositionerDialogImplHelper.cs
+++ b/src/AtomUI.Controls/Dialog/DialogPositioning/ManagedDialogPositionerDialogImplHelper.cs
@@ -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);
-
}
}
diff --git a/src/AtomUI.Controls/Dialog/OverlayDialogHost.cs b/src/AtomUI.Controls/Dialog/OverlayDialogHost.cs
index e98a25cf0..48391b96c 100644
--- a/src/AtomUI.Controls/Dialog/OverlayDialogHost.cs
+++ b/src/AtomUI.Controls/Dialog/OverlayDialogHost.cs
@@ -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);
+ }
}
}
}