修复 offset 计算错误

This commit is contained in:
polarboy 2024-07-07 11:43:13 +08:00
parent 396c37f6a8
commit b37517deb6
8 changed files with 155 additions and 79 deletions

View File

@ -171,14 +171,8 @@ public partial class ArrowDecoratedBox : StyledControl, IShadowMaskInfoProvider
base.OnAttachedToLogicalTree(e);
if (!_initialized) {
_customStyle.SetupUi();
SetupRelayProperties();
}
SetupRelayProperties();
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_compositeDisposable?.Dispose();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

View File

@ -1,4 +1,5 @@
using System.Reactive.Disposables;
using AtomUI.Controls.Utils;
using AtomUI.Data;
using AtomUI.Media;
using AtomUI.Styling;
@ -38,9 +39,10 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
if (IsShowArrow) {
BuildGeometry(true);
}
LogicalChildren.Add(_container);
VisualChildren.Add(_container);
_controlTokenBinder.AddControlBinding(BackgroundProperty, GlobalResourceKey.ColorBgContainer);
_initialized = true;
}
@ -50,10 +52,14 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
_compositeDisposable = new CompositeDisposable();
// 生命周期一样,可以不用管理
if (_container is not null) {
if (Child?.Parent is not null) {
UiStructureUtils.SetLogicalParent(Child, null);
UiStructureUtils.SetVisualParent(Child, null);
}
_compositeDisposable.Add(BindUtils.RelayBind(this, BackgroundSizingProperty, _container));
_compositeDisposable.Add(BindUtils.RelayBind(this, BackgroundProperty, _container));
_compositeDisposable.Add(BindUtils.RelayBind(this, CornerRadiusProperty, _container));
_compositeDisposable.Add(BindUtils.RelayBind(this, ChildProperty, _container));
_compositeDisposable.Add(BindUtils.RelayBind(this, ChildProperty, _container));
_compositeDisposable.Add(BindUtils.RelayBind(this, PaddingProperty, _container));
}
}
@ -75,7 +81,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
GetArrowRect(DesiredSize);
}
}
}
}
}
private void BuildGeometry(bool force = false)
@ -98,7 +104,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
if (IsShowArrow) {
var direction = GetDirection(ArrowPosition);
var matrix = Matrix.CreateTranslation(-_arrowSize / 2, -_arrowSize / 2);
if (direction == Direction.Right) {
matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(90));
matrix *= Matrix.CreateTranslation(_arrowSize / 2, _arrowSize / 2);
@ -124,7 +130,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
var targetWidth = size.Width;
var targetHeight = size.Height;
targetHeight = Math.Max(MinHeight, targetHeight);
if (IsShowArrow) {
BuildGeometry();
var realArrowSize = Math.Min(_arrowGeometry!.Bounds.Size.Height, _arrowGeometry!.Bounds.Size.Width);

View File

@ -278,6 +278,7 @@ public class Flyout : PopupFlyoutBase
offsetY += offset.Y;
Popup.HorizontalOffset = offsetX;
Popup.VerticalOffset = offsetY;
Console.WriteLine($"NotifyPositionPopup-{offsetY}");
}
protected override bool ShowAtCore(Control placementTarget, bool showAtPointer = false)
@ -295,6 +296,7 @@ public class Flyout : PopupFlyoutBase
UiStructureUtils.SetLogicalParent(flyoutPresenter, placementTarget);
}
var positionInfo = AtomPopup.CalculatePositionInfo(placementTarget, presenter);
Console.WriteLine(positionInfo.Offset);
PlayShowUpMotion(positionInfo, placementTarget, flyoutPresenter, showAtPointer);
result = true;
} else {
@ -331,7 +333,7 @@ public class Flyout : PopupFlyoutBase
UiStructureUtils.ClearVisualParentRecursive(child, null);
}
_animating = false;
// base.ShowAtCore(placementTarget, showAtPointer);
base.ShowAtCore(placementTarget, showAtPointer);
};
director?.Schedule(motionActor);

View File

@ -54,6 +54,9 @@ public class Popup : AbstractPopup
private PlacementMode? _originPlacementMode;
private PopupAnchor? _originPlacementAnchor;
private PopupGravity? _originPlacementGravity;
private double _originOffsetX;
private double _originOffsetY;
private bool _ignoreSyncOriginValues = false;
static Popup()
{
@ -68,8 +71,28 @@ public class Popup : AbstractPopup
{
IsLightDismissEnabled = false;
_globalTokenBinder = new GlobalTokenBinder();
_originOffsetX = HorizontalOffset;
_originOffsetY = VerticalOffset;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (!_ignoreSyncOriginValues) {
if (e.Property == HorizontalOffsetProperty) {
_originOffsetX = e.GetNewValue<double>();
} else if (e.Property == VerticalOffsetProperty) {
_originOffsetY = e.GetNewValue<double>();
} else 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)
{
base.OnAttachedToLogicalTree(e);
@ -278,8 +301,9 @@ public class Popup : AbstractPopup
protected internal override void NotifyPopupRootAboutToShow(PopupRoot popupRoot)
{
base.NotifyPopupRootAboutToShow(popupRoot);
var offsetX = HorizontalOffset;
var offsetY = VerticalOffset;
using var ignoreSyncOriginHandling = IgnoreSyncOriginValueHandling();
var offsetX = _originOffsetX;
var offsetY = _originOffsetY;
var marginToAnchorOffset = CalculateMarginToAnchorOffset(Placement);
offsetX += marginToAnchorOffset.X;
offsetY += marginToAnchorOffset.Y;
@ -362,9 +386,14 @@ public class Popup : AbstractPopup
internal PopupPositionInfo CalculatePositionInfo(Control placementTarget, Control popupContent)
{
var offsetX = HorizontalOffset;
var offsetY = VerticalOffset;
using var ignoreSyncOriginHandling = IgnoreSyncOriginValueHandling();
var offsetX = _originOffsetX;
var offsetY = _originOffsetY;
Console.WriteLine(offsetY);
var marginToAnchorOffset = CalculateMarginToAnchorOffset(Placement);
Console.WriteLine(marginToAnchorOffset);
offsetX += marginToAnchorOffset.X;
offsetY += marginToAnchorOffset.Y;
Point offset = default;
@ -438,7 +467,7 @@ public class Popup : AbstractPopup
positionInfo.IsFlipped = false;
}
}
var rect = PopupUtils.Calculate(
parameters.Size * scaling,
new Rect(
@ -501,6 +530,27 @@ public class Popup : AbstractPopup
_ => throw new ArgumentOutOfRangeException(nameof(placement), placement, "Invalid value for PlacementMode")
};
}
private protected 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;
}
}
}
internal class PopupPositionInfo

View File

@ -1,17 +1,16 @@
using AtomUI.ColorSystem;
using AtomUI.Controls.Utils;
using AtomUI.Media;
using AtomUI.Media;
using AtomUI.MotionScene;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
namespace AtomUI.Controls.Primitives;
// TODO 这个类的实例只在动画过程中存在,所以是否需要处理属性变化对重绘的需求需要再评估,暂时先不处理
internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
{
public static readonly StyledProperty<BoxShadows> ShadowsProperty =
@ -21,7 +20,7 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
Border.CornerRadiusProperty.AddOwner<MotionGhostControl>();
/// <summary>
/// 渲染的阴影值
/// 渲染的阴影值,一般在探测失败的时候使用
/// </summary>
public BoxShadows Shadows
{
@ -30,21 +29,21 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
}
/// <summary>
/// mask 的圆角大小
/// mask 的圆角大小,一般在探测失败的时候使用
/// </summary>
public CornerRadius MaskCornerRadius
{
get => GetValue(MaskCornerRadiusProperty);
set => SetValue(MaskCornerRadiusProperty, value);
}
protected Border? _maskRenderer;
protected Border? _contentRenderer;
protected bool _initialized = false;
protected Canvas? _layout;
protected Control _motionTarget;
protected RenderTargetBitmap? _ghostBitmap;
protected Size _motionTargetSize;
protected Point _maskOffset;
protected Size _maskSize;
static MotionGhostControl()
{
@ -52,7 +51,7 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
AffectsRender<ShadowRenderer>(MaskCornerRadiusProperty);
}
public MotionGhostControl(Control motionTarget, BoxShadows shadows)
public MotionGhostControl(Control motionTarget, BoxShadows fallbackShadows)
{
_motionTarget = motionTarget;
if (_motionTarget.DesiredSize == default) {
@ -61,35 +60,31 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
_motionTargetSize = _motionTarget.DesiredSize;
}
foreach (var boxShadow in shadows) {
_maskOffset = default;
_maskSize = _motionTargetSize;
Shadows = fallbackShadows;
DetectMotionTargetInfo();
}
private void DetectMotionTargetInfo()
{
CornerRadius cornerRadius = default;
if (_motionTarget is IShadowMaskInfoProvider shadowMaskInfoProvider) {
cornerRadius = shadowMaskInfoProvider.GetMaskCornerRadius();
var maskBounds = shadowMaskInfoProvider.GetMaskBounds();
_maskOffset = maskBounds.Position;
_maskSize = maskBounds.Size;
} else if (_motionTarget is BorderedStyleControl bordered) {
cornerRadius = bordered.CornerRadius;
} else if (_motionTarget is TemplatedControl templatedControl) {
cornerRadius = templatedControl.CornerRadius;
}
// 探测出来的是最准确的,优先级高
if (cornerRadius != default) {
MaskCornerRadius = cornerRadius;
}
Shadows = new BoxShadows(new BoxShadow
{
OffsetX = 0,
OffsetY = 6 * 2,
Blur = 16 * 2,
Spread = 0,
Color = ColorUtils.FromRgbF(0.10, 0, 0, 0)
}, new []
{
new BoxShadow
{
OffsetX = 0,
OffsetY = 3 * 2,
Blur = 6 * 2,
Spread = -4 * 2,
Color = ColorUtils.FromRgbF(0.12, 0, 0, 0)
},
new BoxShadow
{
OffsetX = 0,
OffsetY = 9 * 2,
Blur = 28 * 2,
Spread = 8 * 2,
Color = ColorUtils.FromRgbF(0.07, 0, 0, 0)
}
});;
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -100,27 +95,20 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
_layout = new Canvas();
VisualChildren.Add(_layout);
LogicalChildren.Add(_layout);
LogicalChildren.Add(_layout);
var shadowThickness = Shadows.Thickness();
var offsetX = shadowThickness.Left;
var offsetY = shadowThickness.Top;
var shadowRenderers = BuildShadowRenderers(Shadows);
foreach (var renderer in shadowRenderers) {
_layout.Children.Add(renderer);
}
_maskRenderer = new Border
{
BorderThickness = new Thickness(0),
BoxShadow = Shadows,
CornerRadius = MaskCornerRadius,
Width = _motionTargetSize.Width,
Height = _motionTargetSize.Height
};
_layout.Children.Add(_maskRenderer);
_layout.Children.Add(_motionTarget);
Canvas.SetLeft(_maskRenderer, offsetX);
Canvas.SetTop(_maskRenderer, offsetY);
Canvas.SetLeft(_motionTarget, offsetX);
Canvas.SetTop(_motionTarget, offsetY);
@ -128,6 +116,37 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
}
}
/// <summary>
/// 目前的 Avalonia 版本中,当控件渲染到 RenderTargetBitmap 的时候,如果 BoxShadows 的 Count > 1 的时候,如果不是主阴影,后面的阴影如果
/// 指定 offset再 RenderScaling > 1 的情况下是错的。
/// </summary>
/// <returns></returns>
private List<Control> BuildShadowRenderers(in BoxShadows shadows)
{
var thickness = Shadows.Thickness();
var offsetX = thickness.Left;
var offsetY = thickness.Top;
offsetX += _maskOffset.X;
offsetY += _maskOffset.Y;
var renderers = new List<Control>();
for (var i = 0; i < shadows.Count; ++i) {
var renderer = new Border
{
BorderThickness = new Thickness(0),
BoxShadow = new BoxShadows(shadows[i]),
CornerRadius = MaskCornerRadius,
Width = _maskSize.Width,
Height = _maskSize.Height
};
Canvas.SetLeft(renderer, offsetX);
Canvas.SetTop(renderer, offsetY);
renderers.Add(renderer);
}
return renderers;
}
protected override Size MeasureOverride(Size availableSize)
{
base.MeasureOverride(availableSize);
@ -155,7 +174,7 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
if (_ghostBitmap is null) {
var scaling = TopLevel.GetTopLevel(this)?.RenderScaling ?? 1.0;
_ghostBitmap = new RenderTargetBitmap(new PixelSize((int)(DesiredSize.Width * scaling), (int)(DesiredSize.Height * scaling)),
new Vector(96, 96));
new Vector(96 * scaling, 96 * scaling));
_ghostBitmap.Render(this);
_layout!.Children.Clear();
}

View File

@ -1,6 +1,8 @@
using System.Reactive.Disposables;
using AtomUI.MotionScene;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Styling;
using Avalonia.Threading;
namespace AtomUI.Controls.MotionScene;
@ -38,12 +40,11 @@ public class Director : IDirector
_compositeDisposable.Add(Disposable.Create((sceneLayer), state =>
{
if (sceneLayer is not null) {
//sceneLayer.Opacity = 0;
Dispatcher.UIThread.InvokeAsync(async () =>
{
// await Task.Delay(60);
// sceneLayer.Hide();
// sceneLayer.Dispose();
await Task.Delay(100);
sceneLayer.Hide();
sceneLayer.Dispose();
});
}
}));
@ -51,6 +52,7 @@ public class Director : IDirector
_states.Add(actor, state);
if (actor.DispatchInSceneLayer) {
state.SceneLayer = sceneLayer;
var ghost = actor.GetAnimatableGhost();
sceneLayer!.SetMotionTarget(ghost);
actor.NotifyMotionTargetAddedToScene(ghost);
@ -136,9 +138,12 @@ public class Director : IDirector
private void HandleMotionCompleted(MotionActor actor)
{
actor.NotifyMotionCompleted();
MotionCompleted?.Invoke(this, new MotionEventArgs(actor));
if (_states.TryGetValue(actor, out var state)) {
if (state.SceneLayer is not null) {
state.SceneLayer.Opacity = 0;
}
actor.NotifyMotionCompleted();
MotionCompleted?.Invoke(this, new MotionEventArgs(actor));
state.Dispose();
_states.Remove(actor);
}

View File

@ -5,7 +5,6 @@ using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Transformation;
using Avalonia.VisualTree;
namespace AtomUI.MotionScene;

View File

@ -1,6 +1,7 @@
using System.Reflection;
using AtomUI.Platform.Windows;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Media;
@ -152,6 +153,6 @@ public class SceneLayer : WindowBase, IHostedVisualTreeRoot, IDisposable
public override void Render(DrawingContext context)
{
//context.FillRectangle(new SolidColorBrush(Colors.Coral), new Rect(new Point(0, 0), DesiredSize));
//context.FillRectangle(new SolidColorBrush(Colors.Coral, 0.2), new Rect(new Point(0, 0), DesiredSize));
}
}