修复 Popup 和 动画指针坐标不正确的问题

This commit is contained in:
polarboy 2024-07-08 09:59:40 +08:00
parent f8741a4c90
commit 569b4b18ed
5 changed files with 113 additions and 133 deletions

View File

@ -92,7 +92,9 @@ public partial class ArrowDecoratedBox : StyledControl, IShadowMaskInfoProvider
Border.CornerRadiusProperty.AddOwner<ArrowDecoratedBox>();
// 指针最顶点位置
internal (double, double) ArrowVertexPoint { get; private set; }
// 相对坐标
private (double, double) _arrowVertexPoint;
internal (double, double) ArrowVertexPoint => GetArrowVertexPoint();
/// <summary>
/// 是否显示指示箭头

View File

@ -21,6 +21,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
private Rect _arrowRect;
private Border? _container;
private CompositeDisposable? _compositeDisposable;
private bool _needGenerateArrowVertexPoint = true;
// 组件的 Token 绑定属性
private double _arrowSize;
@ -69,17 +70,30 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
NotifyApplyFixedStyleConfig();
}
private (double, double) GetArrowVertexPoint()
{
if (_needGenerateArrowVertexPoint) {
BuildGeometry(true);
_arrowRect = GetArrowRect(DesiredSize);
_needGenerateArrowVertexPoint = false;
}
return _arrowVertexPoint;
}
void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IsShowArrowProperty ||
e.Property == ArrowPositionProperty ||
e.Property == ArrowSizeTokenProperty ||
e.Property == VisualParentProperty) {
if (_initialized) {
if (IsShowArrow) {
BuildGeometry(true);
GetArrowRect(DesiredSize);
if (e.Property == IsShowArrowProperty && VisualRoot is null) {
// 当开启的时候,但是还没有加入的渲染树,这个时候我们取不到 Token 需要在取值的时候重新生成一下
_needGenerateArrowVertexPoint = true;
}
if (_initialized && VisualRoot is not null) {
BuildGeometry(true);
_arrowRect = GetArrowRect(DesiredSize);
}
}
}
@ -200,6 +214,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
var position = ArrowPosition;
if (IsShowArrow) {
var size = _arrowGeometry!.Bounds.Size;
var minValue = Math.Min(size.Width, size.Height);
var maxValue = Math.Max(size.Width, size.Height);
if (position == ArrowPosition.Left ||
@ -273,14 +288,14 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
var targetRect = new Rect(offsetX, offsetY, targetWidth, targetHeight);
var center = targetRect.Center;
// 计算中点
var direction = GetDirection(position);
if (direction == Direction.Left || direction == Direction.Right) {
ArrowVertexPoint = (center.Y, finalSize.Height - center.Y);
_arrowVertexPoint = (center.Y, finalSize.Height - center.Y);
} else if (direction == Direction.Top || direction == Direction.Bottom) {
ArrowVertexPoint = (center.X, finalSize.Width - center.X);
_arrowVertexPoint = (center.X, finalSize.Width - center.X);
}
return targetRect;
}
}

View File

@ -142,22 +142,17 @@ public class Flyout : PopupFlyoutBase
}
}
if (flyoutPresenter is not null) {
var arrowPosition = PopupUtils.CalculateArrowPosition(popup.Placement, popup.PlacementAnchor, popup.PlacementGravity);
if (arrowPosition.HasValue) {
flyoutPresenter.ArrowPosition = arrowPosition.Value;
}
}
}
var placement = popup.Placement;
var anchor = popup.PlacementAnchor;
var gravity = popup.PlacementGravity;
private void SetupArrowPosition(FlyoutPresenter flyoutPresenter, PlacementMode placement, PopupAnchor? anchor,
PopupGravity? gravity)
{
if (flyoutPresenter is not null) {
var arrowPosition = PopupUtils.CalculateArrowPosition(placement, anchor, gravity);
if (arrowPosition.HasValue) {
flyoutPresenter.ArrowPosition = arrowPosition.Value;
}
}
}
protected override Control CreatePresenter()
{
@ -176,9 +171,7 @@ public class Flyout : PopupFlyoutBase
BindUtils.RelayBind(this, PlacementProperty, popup);
BindUtils.RelayBind(this, PlacementAnchorProperty, popup);
BindUtils.RelayBind(this, PlacementGravityProperty, popup);
BindUtils.RelayBind(this, HorizontalOffsetProperty, popup);
BindUtils.RelayBind(this, VerticalOffsetProperty, popup);
popup.MaskShadows = MaskShadows;
BindUtils.RelayBind(this, MaskShadowsProperty, popup);
SetupArrowPosition(popup);
}
@ -207,15 +200,19 @@ public class Flyout : PopupFlyoutBase
_compositeDisposable?.Dispose();
}
private Point CalculatePopupPositionDelta(Control anchorTarget, PlacementMode placement, PopupAnchor? anchor = null,
private Point CalculatePopupPositionDelta(Control anchorTarget,
Control? flyoutPresenter,
PlacementMode placement,
PopupAnchor? anchor = null,
PopupGravity? gravity = null)
{
var offsetX = 0d;
var offsetY = 0d;
if (IsShowArrow && IsPointAtCenter) {
if (PopupUtils.CanEnabledArrow(placement, anchor, gravity)) {
if (Popup.Child is ArrowDecoratedBox arrowDecoratedBox) {
if (flyoutPresenter is ArrowDecoratedBox arrowDecoratedBox) {
var arrowVertexPoint = arrowDecoratedBox.ArrowVertexPoint;
var anchorSize = anchorTarget.Bounds.Size;
var centerX = anchorSize.Width / 2;
var centerY = anchorSize.Height / 2;
@ -262,13 +259,9 @@ public class Flyout : PopupFlyoutBase
protected internal override void NotifyPositionPopup(bool showAtPointer)
{
Size sz;
// Popup.Child can't be null here, it was set in ShowAtCore.
if (Popup.Child!.DesiredSize == default) {
// Popup may not have been shown yet. Measure content
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
} else {
sz = Popup.Child.DesiredSize;
LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
}
Popup.PlacementAnchor = PlacementAnchor;
@ -281,12 +274,15 @@ public class Flyout : PopupFlyoutBase
Popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment;
}
var pointAtCenterOffset = CalculatePopupPositionDelta(Target!, Popup.Child, Popup.Placement, Popup.PlacementAnchor, Popup.PlacementGravity);
var offsetX = HorizontalOffset;
var offsetY = VerticalOffset;
var offset = CalculatePopupPositionDelta(Target!, Placement, PlacementAnchor, PlacementGravity);
offsetX += offset.X;
offsetY += offset.Y;
if (IsPointAtCenter) {
offsetX += pointAtCenterOffset.X;
offsetY += pointAtCenterOffset.Y;
}
// 更新弹出信息是否指向中点
Popup.HorizontalOffset = offsetX;
Popup.VerticalOffset = offsetY;
}
@ -307,13 +303,32 @@ public class Flyout : PopupFlyoutBase
UiStructureUtils.ClearLogicalParentRecursive(flyoutPresenter, null);
UiStructureUtils.ClearVisualParentRecursive(flyoutPresenter, null);
UiStructureUtils.SetLogicalParent(flyoutPresenter, placementToplevel);
_popupPositionInfo = AtomPopup.CalculatePositionInfo(placementTarget, presenter);
_popupPositionInfo = AtomPopup.CalculatePositionInfo(placementTarget,
presenter,
new Point(HorizontalOffset, VerticalOffset),
Placement);
// 重新设置箭头位置
SetupArrowPosition(flyoutPresenter,
// 因为可能有 flip 的情况
var arrowPosition = PopupUtils.CalculateArrowPosition(_popupPositionInfo.EffectivePlacement,
_popupPositionInfo.EffectivePlacementAnchor,
_popupPositionInfo.EffectivePlacementGravity);
if (arrowPosition.HasValue) {
flyoutPresenter.ArrowPosition = arrowPosition.Value;
}
// 获取是否在指向中点
var pointAtCenterOffset = CalculatePopupPositionDelta(placementTarget,
presenter,
_popupPositionInfo.EffectivePlacement,
_popupPositionInfo.EffectivePlacementAnchor,
_popupPositionInfo.EffectivePlacementGravity);
if (IsPointAtCenter) {
_popupPositionInfo.Offset = new Point(_popupPositionInfo.Offset.X + pointAtCenterOffset.X * _popupPositionInfo.Scaling,
_popupPositionInfo.Offset.Y + pointAtCenterOffset.Y * _popupPositionInfo.Scaling);
}
PlayShowMotion(_popupPositionInfo, placementTarget, flyoutPresenter, showAtPointer);
}
result = true;
@ -355,7 +370,8 @@ public class Flyout : PopupFlyoutBase
motion.ConfigureOpacity(_motionDuration);
motion.ConfigureRenderTransform(_motionDuration);
var topLevel = TopLevel.GetTopLevel(placementTarget);
var motionActor = new PopupMotionActor(MaskShadows, positionInfo, flyoutPresenter, motion);
var motionActor = new PopupMotionActor(MaskShadows, positionInfo.Offset, positionInfo.Scaling, flyoutPresenter, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = topLevel;
motionActor.Completed += (sender, args) =>
@ -401,7 +417,7 @@ public class Flyout : PopupFlyoutBase
UiStructureUtils.SetVisualParent(popup.Child, null);
UiStructureUtils.SetVisualParent(popup.Child, null);
var motionActor = new PopupMotionActor(MaskShadows, _popupPositionInfo, popup.Child, motion);
var motionActor = new PopupMotionActor(MaskShadows, _popupPositionInfo.Offset, _popupPositionInfo.Scaling, popup.Child, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = placementToplevel;

View File

@ -51,12 +51,8 @@ public class Popup : AbstractPopup
private CompositeDisposable? _compositeDisposable;
private bool _initialized;
private PlacementMode? _originPlacementMode;
private PopupAnchor? _originPlacementAnchor;
private PopupGravity? _originPlacementGravity;
private double _originOffsetX;
private double _originOffsetY;
private bool _ignoreSyncOriginValues = false;
private Point _pointAtCenterOffset;
static Popup()
{
@ -71,28 +67,6 @@ public class Popup : AbstractPopup
{
IsLightDismissEnabled = false;
_globalTokenBinder = new GlobalTokenBinder();
_originOffsetX = HorizontalOffset;
_originOffsetY = VerticalOffset;
}
internal void UpdateOriginOffset(double offsetX, double offsetY)
{
_originOffsetX = offsetX;
_originOffsetY = offsetY;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (!_ignoreSyncOriginValues) {
if (e.Property == PlacementProperty) {
_originPlacementMode = e.GetNewValue<PlacementMode>();
} else if (e.Property == PlacementAnchorProperty) {
_originPlacementAnchor = e.GetNewValue<PopupAnchor>();
} else if (e.Property == PlacementGravityProperty) {
_originPlacementGravity = e.GetNewValue<PopupGravity>();
}
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -303,15 +277,16 @@ public class Popup : AbstractPopup
protected internal override void NotifyPopupRootAboutToShow(PopupRoot popupRoot)
{
base.NotifyPopupRootAboutToShow(popupRoot);
using var ignoreSyncOriginHandling = IgnoreSyncOriginValueHandling();
var offsetX = _originOffsetX;
var offsetY = _originOffsetY;
var offsetX = HorizontalOffset;
var offsetY = VerticalOffset;
var marginToAnchorOffset = CalculateMarginToAnchorOffset(Placement);
offsetX += marginToAnchorOffset.X;
offsetY += marginToAnchorOffset.Y;
HorizontalOffset = offsetX;
VerticalOffset = offsetY;
var direction = GetDirection(Placement);
if (Placement != PlacementMode.Center &&
Placement != PlacementMode.Pointer) {
// 计算是否 flip
@ -342,7 +317,6 @@ public class Popup : AbstractPopup
parameters.AnchorRectangle.Size * scaling);
anchorRect = anchorRect.Translate(_managedPopupPositioner.ParentClientAreaScreenGeometry.TopLeft);
var flipInfo = CalculateFlipInfo(popupSize * scaling,
anchorRect,
parameters.Anchor,
@ -353,50 +327,37 @@ public class Popup : AbstractPopup
var flipAnchorAndGravity = GetAnchorAndGravity(flipPlacement);
var flipOffset = CalculateMarginToAnchorOffset(flipPlacement);
_originPlacementMode = Placement;
_originPlacementAnchor = PlacementAnchor;
_originPlacementGravity = PlacementGravity;
Placement = flipPlacement;
PlacementAnchor = flipAnchorAndGravity.Item1;
PlacementGravity = flipAnchorAndGravity.Item2;
HorizontalOffset = flipOffset.X;
// 这里有个问题,目前需要重新看看,就是 X 轴 和 Y 轴会不会同时被反转呢?
if (direction == Direction.Top || direction == Direction.Bottom) {
VerticalOffset = flipOffset.Y;
} else {
HorizontalOffset = flipOffset.X;
}
IsFlipped = true;
} else {
IsFlipped = false;
if (_originPlacementMode.HasValue) {
Placement = _originPlacementMode.Value;
}
if (_originPlacementAnchor.HasValue) {
PlacementAnchor = _originPlacementAnchor.Value;
}
if (_originPlacementGravity.HasValue) {
PlacementGravity = _originPlacementGravity.Value;
}
_originPlacementMode = null;
_originPlacementAnchor = null;
_originPlacementGravity = null;
}
}
}
internal PopupPositionInfo CalculatePositionInfo(Control placementTarget, Control popupContent)
internal PopupPositionInfo CalculatePositionInfo(Control placementTarget, Control popupContent, Point offset,
PlacementMode placement)
{
using var ignoreSyncOriginHandling = IgnoreSyncOriginValueHandling();
var offsetX = _originOffsetX;
var offsetY = _originOffsetY + 0.5; // TODO 不知道为什么会出现 0.5 的误差
var placement = _originPlacementMode ?? Placement;
var offsetX = offset.X;
var offsetY = offset.Y + 0.5; // TODO 不知道为什么会出现 0.5 的误差
var marginToAnchorOffset = CalculateMarginToAnchorOffset(placement);
offsetX += marginToAnchorOffset.X;
offsetY += marginToAnchorOffset.Y;
Point offset = default;
var direction = GetDirection(placement);
PopupPositionerParameters parameters = new PopupPositionerParameters();
var parentTopLevel = TopLevel.GetTopLevel(placementTarget)!;
@ -423,8 +384,6 @@ public class Popup : AbstractPopup
PlacementRect ?? new Rect(default, placementTarget.Bounds.Size),
FlowDirection);
var positionInfo = new PopupPositionInfo();
positionInfo.EffectivePlacement = placement;
positionInfo.EffectivePlacementAnchor = PlacementAnchor;
@ -461,7 +420,13 @@ public class Popup : AbstractPopup
positionInfo.EffectivePlacement = flipPlacement;
positionInfo.EffectivePlacementAnchor = flipAnchorAndGravity.Item1;
positionInfo.EffectivePlacementGravity = flipAnchorAndGravity.Item2;
positionInfo.Offset = flipOffset;
if (direction == Direction.Top || direction == Direction.Bottom) {
positionInfo.Offset = positionInfo.Offset.WithY(flipOffset.Y);
} else {
positionInfo.Offset = positionInfo.Offset.WithX(flipOffset.X);
}
positionInfo.IsFlipped = true;
} else {
positionInfo.IsFlipped = false;
@ -531,27 +496,6 @@ public class Popup : AbstractPopup
};
}
private IDisposable IgnoreSyncOriginValueHandling()
{
return new IgnoreSyncOriginValueDisposable(this);
}
private readonly struct IgnoreSyncOriginValueDisposable : IDisposable
{
private readonly Popup _popup;
public IgnoreSyncOriginValueDisposable(Popup popup)
{
_popup = popup;
_popup._ignoreSyncOriginValues = true;
}
public void Dispose()
{
_popup._ignoreSyncOriginValues = false;
}
}
public void HideShadowLayer()
{
if (_shadowLayer is not null) {

View File

@ -10,23 +10,26 @@ namespace AtomUI.Controls;
internal class PopupMotionActor : MotionActor
{
private BoxShadows _boxShadows;
private PopupPositionInfo _popupPositionInfo;
private Point _offset;
private double _scaling;
public PopupMotionActor(BoxShadows boxShadows,
PopupPositionInfo popupPositionInfo,
Point offset,
double scaling,
Control motionTarget,
AbstractMotion motion)
: base(motionTarget, motion)
{
_popupPositionInfo = popupPositionInfo;
_offset = offset;
_scaling = scaling;
_boxShadows = boxShadows;
}
protected override Point CalculateTopLevelGhostPosition()
{
var boxShadowsThickness = _boxShadows.Thickness();
var winPos = _popupPositionInfo.Offset; // TODO 可能需要乘以 scaling
var scaledThickness = boxShadowsThickness * _popupPositionInfo.Scaling;
var winPos = _offset; // TODO 可能需要乘以 scaling
var scaledThickness = boxShadowsThickness * _scaling;
return new Point(winPos.X - scaledThickness.Left, winPos.Y - scaledThickness.Top);
}