From f43a41f825f8e59dbd0d2f16387746643fb9c83a Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 30 Jun 2024 16:39:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20popup=20flip=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArrowDecoratedBoxStyle.cs | 2 +- src/AtomUI.Controls/Flyouts/Flyout.cs | 103 ++++- src/AtomUI.Controls/Flyouts/FlyoutHost.cs | 35 +- .../Flyouts/FlyoutPresenter.cs | 3 +- src/AtomUI.Controls/Flyouts/InfoFlyout.cs | 4 +- .../Flyouts/PopupFlyoutBase.cs | 52 +-- .../PopupFlyoutBaseInterceptor.cs | 26 +- src/AtomUI.Controls/Popup/AbstractPopup.cs | 51 ++- src/AtomUI.Controls/Popup/IShadowLayer.cs | 2 - src/AtomUI.Controls/Popup/LiteWindow.cs | 5 +- src/AtomUI.Controls/Popup/Popup.cs | 374 +++++++++++++++++- src/AtomUI.Controls/Popup/PopupShadowLayer.cs | 165 ++++---- src/AtomUI.Controls/Popup/ShadowPopupHost.cs | 62 --- src/AtomUI.Controls/Popup/ShadowRenderer.cs | 7 +- 14 files changed, 650 insertions(+), 241 deletions(-) delete mode 100644 src/AtomUI.Controls/Popup/ShadowPopupHost.cs diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs index d8ced27..39ca4d4 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs @@ -56,7 +56,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle _controlTokenBinder.AddControlBinding(MinHeightProperty, GlobalResourceKey.ControlHeight); _controlTokenBinder.AddControlBinding(PaddingProperty, GlobalResourceKey.PaddingXS); _controlTokenBinder.AddControlBinding(ArrowSizeTokenProperty, ArrowDecoratedBoxResourceKey.ArrowSize); - _controlTokenBinder.AddControlBinding(BackgroundProperty, GlobalResourceKey.ColorPrimary); + _controlTokenBinder.AddControlBinding(BackgroundProperty, GlobalResourceKey.ColorBgContainer); _controlTokenBinder.AddControlBinding(CornerRadiusProperty, GlobalResourceKey.BorderRadius); } diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index eeb88fd..df893a8 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives.PopupPositioning; @@ -7,8 +8,25 @@ using Avalonia.Styling; namespace AtomUI.Controls; +using PopupControl = Popup; + public class Flyout : PopupFlyoutBase { + /// + /// 是否显示指示箭头 + /// + public static readonly StyledProperty IsShowArrowProperty = + ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); + + public static readonly StyledProperty IsShowArrowEffectiveProperty = + ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); + + /// + /// 箭头是否始终指向中心 + /// + public static readonly StyledProperty IsPointAtCenterProperty = + AvaloniaProperty.Register(nameof(IsPointAtCenter), false); + /// /// Defines the property /// @@ -28,6 +46,27 @@ public class Flyout : PopupFlyoutBase public static readonly StyledProperty FlyoutPresenterThemeProperty = AvaloniaProperty.Register(nameof(FlyoutPresenterTheme)); + public bool IsShowArrow + { + get => GetValue(IsShowArrowProperty); + set => SetValue(IsShowArrowProperty, value); + } + + /// + /// 是否实际显示箭头 + /// + public bool IsShowArrowEffective + { + get => GetValue(IsShowArrowEffectiveProperty); + set => SetValue(IsShowArrowEffectiveProperty, value); + } + + public bool IsPointAtCenter + { + get => GetValue(IsPointAtCenterProperty); + set => SetValue(IsPointAtCenterProperty, value); + } + /// /// Gets or sets the that is applied to the container element generated for the flyout presenter. /// @@ -49,10 +88,12 @@ public class Flyout : PopupFlyoutBase protected override Control CreatePresenter() { - return new FlyoutPresenter + var presenter = new FlyoutPresenter { [!BorderedStyleControl.ChildProperty] = this[!ContentProperty] }; + BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, IsShowArrowProperty); + return presenter; } protected override void OnOpening(CancelEventArgs args) @@ -73,12 +114,66 @@ public class Flyout : PopupFlyoutBase /// /// 判断是否可以启用箭头,有些组合是不能启用箭头绘制的,因为没有意义 /// - /// + /// /// /// /// - private bool CanEnabledArrow(PlacementMode placementMode, PopupAnchor? anchor, PopupGravity? gravity) + private bool CanEnabledArrow(PlacementMode placement, PopupAnchor? anchor, PopupGravity? gravity) { - return true; + if (placement == PlacementMode.Center || + placement == PlacementMode.Pointer) { + return false; + } + + return PopupControl.IsCanonicalAnchorType(placement, anchor, gravity); + } + + private Point CalculatePopupPositionDelta(Control anchorTarget, PlacementMode placement, PopupAnchor? anchor = null, + PopupGravity? gravity = null) + { + var offsetX = 0d; + var offsetY = 0d; + if (IsShowArrow && IsPointAtCenter) { + if (CanEnabledArrow(placement, anchor, gravity)) { + if (Popup.Child is ArrowDecoratedBox arrowDecoratedBox) { + var arrowVertexPoint = arrowDecoratedBox.ArrowVertexPoint; + var anchorSize = anchorTarget.Bounds.Size; + var centerX = anchorSize.Width / 2; + var centerY = anchorSize.Height / 2; + // 这里计算不需要全局坐标 + if (placement == PlacementMode.TopEdgeAlignedLeft || + placement == PlacementMode.BottomEdgeAlignedLeft) { + offsetX += centerX - arrowVertexPoint.Item1; + } else if (placement == PlacementMode.TopEdgeAlignedRight || + placement == PlacementMode.BottomEdgeAlignedRight) { + offsetX -= centerX - arrowVertexPoint.Item2; + } else if (placement == PlacementMode.RightEdgeAlignedTop || + placement == PlacementMode.LeftEdgeAlignedTop) { + offsetY += centerY - arrowVertexPoint.Item1; + } else if (placement == PlacementMode.RightEdgeAlignedBottom || + placement == PlacementMode.LeftEdgeAlignedBottom) { + offsetY -= centerY - arrowVertexPoint.Item2; + } + } + } + } + + return new Point(offsetX, offsetY); + } + + // 因为在某些 placement 下箭头是不能显示的 + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + if (e.Property == IsShowArrowProperty || + e.Property == PlacementProperty || + e.Property == PlacementAnchorProperty || + e.Property == PlacementGravityProperty) { + if (IsShowArrow == false) { + IsShowArrowEffective = false; + } else { + IsShowArrowEffective = CanEnabledArrow(Placement, PlacementAnchor, PlacementGravity); + } + } } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs index 94aa24f..c5b0189 100644 --- a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs +++ b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs @@ -1,4 +1,6 @@ using System.Reactive.Disposables; +using AtomUI.Data; +using AtomUI.Styling; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; @@ -10,6 +12,8 @@ using Avalonia.Metadata; namespace AtomUI.Controls; +using FlyoutControl = Flyout; + public enum FlyoutTriggerType { Hover, @@ -44,19 +48,14 @@ public class FlyoutHost : Control /// 箭头是否始终指向中心 /// public static readonly StyledProperty IsPointAtCenterProperty = - PopupFlyoutBase.IsPointAtCenterProperty.AddOwner(); - - /// - /// Defines the ToolTip.Placement property. - /// + FlyoutControl.IsPointAtCenterProperty.AddOwner(); + public static readonly StyledProperty PlacementProperty = Popup.PlacementProperty.AddOwner(); - /// public static readonly StyledProperty PlacementAnchorProperty = Popup.PlacementAnchorProperty.AddOwner(); - - /// + public static readonly StyledProperty PlacementGravityProperty = Popup.PlacementGravityProperty.AddOwner(); @@ -66,7 +65,7 @@ public class FlyoutHost : Control /// 还有些 anchor 和 gravity 的组合也没有用 /// public static readonly StyledProperty MarginToAnchorProperty = - AvaloniaProperty.Register(nameof(MarginToAnchor), 0); + Popup.MarginToAnchorProperty.AddOwner(); public static readonly StyledProperty ShowDelayProperty = AvaloniaProperty.Register(nameof(ShowDelay), 400); @@ -83,10 +82,7 @@ public class FlyoutHost : Control get => GetValue(AnchorTargetProperty); set => SetValue(AnchorTargetProperty, value); } - - /// - /// Gets or sets the Flyout that should be shown with this button. - /// + public PopupFlyoutBase? Flyout { get => GetValue(FlyoutProperty); @@ -116,15 +112,13 @@ public class FlyoutHost : Control get => GetValue(PlacementProperty); set => SetValue(PlacementProperty, value); } - - /// + public PopupGravity PlacementGravity { get => GetValue(PlacementGravityProperty); set => SetValue(PlacementGravityProperty, value); } - /// public PopupAnchor PlacementAnchor { get => GetValue(PlacementAnchorProperty); @@ -151,6 +145,7 @@ public class FlyoutHost : Control private bool _initialized = false; private CompositeDisposable _compositeDisposable; + private GlobalTokenBinder _globalTokenBinder; static FlyoutHost() { @@ -160,11 +155,7 @@ public class FlyoutHost : Control public FlyoutHost() { _compositeDisposable = new CompositeDisposable(); - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - base.OnPropertyChanged(e); + _globalTokenBinder = new GlobalTokenBinder(); } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) @@ -175,6 +166,7 @@ public class FlyoutHost : Control ((ISetLogicalParent)AnchorTarget).SetParent(this); VisualChildren.Add(AnchorTarget); } + _globalTokenBinder.AddGlobalBinding(this, MarginToAnchorProperty, GlobalResourceKey.MarginXXS); _initialized = true; } } @@ -200,6 +192,7 @@ public class FlyoutHost : Control _compositeDisposable.Add(BindUtils.RelayBind(this, PlacementGravityProperty, Flyout)); _compositeDisposable.Add(BindUtils.RelayBind(this, IsShowArrowProperty, Flyout)); _compositeDisposable.Add(BindUtils.RelayBind(this, IsPointAtCenterProperty, Flyout)); + _compositeDisposable.Add(BindUtils.RelayBind(this, MarginToAnchorProperty, Flyout)); } } diff --git a/src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs b/src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs index 370c6ab..db17da4 100644 --- a/src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs +++ b/src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs @@ -1,5 +1,4 @@ -using Avalonia.Controls.Primitives; -using Avalonia.Input; +using Avalonia.Input; using Avalonia.LogicalTree; namespace AtomUI.Controls; diff --git a/src/AtomUI.Controls/Flyouts/InfoFlyout.cs b/src/AtomUI.Controls/Flyouts/InfoFlyout.cs index 06a5a8e..084fb4e 100644 --- a/src/AtomUI.Controls/Flyouts/InfoFlyout.cs +++ b/src/AtomUI.Controls/Flyouts/InfoFlyout.cs @@ -1,6 +1,4 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Metadata; +using Avalonia.Controls; namespace AtomUI.Controls; diff --git a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs index f1ba3b1..25d2611 100644 --- a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs @@ -1,49 +1,26 @@ -using Avalonia; +using AtomUI.Utils; +using Avalonia; using Avalonia.Controls; using Avalonia.Layout; - namespace AtomUI.Controls; using AvaloniaPopupFlyoutBase = Avalonia.Controls.Primitives.PopupFlyoutBase; +using PopupControl = Popup; /// /// 最基本得弹窗 Flyout,在这里不处理那种带箭头得 /// public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase { - /// - /// 是否显示指示箭头 - /// - public static readonly StyledProperty IsShowArrowProperty = - ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); - - /// - /// 箭头是否始终指向中心 - /// - public static readonly StyledProperty IsPointAtCenterProperty = - AvaloniaProperty.Register(nameof(IsPointAtCenter), false); - /// /// 距离 anchor 的边距,根据垂直和水平进行设置 /// 但是对某些组合无效,比如跟随鼠标的情况 /// 还有些 anchor 和 gravity 的组合也没有用 /// public static readonly StyledProperty MarginToAnchorProperty = - AvaloniaProperty.Register(nameof(MarginToAnchor), 0); - - public bool IsShowArrow - { - get => GetValue(IsShowArrowProperty); - set => SetValue(IsShowArrowProperty, value); - } - - public bool IsPointAtCenter - { - get => GetValue(IsPointAtCenterProperty); - set => SetValue(IsPointAtCenterProperty, value); - } - + PopupControl.MarginToAnchorProperty.AddOwner(); + public double MarginToAnchor { get => GetValue(MarginToAnchorProperty); @@ -68,8 +45,11 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase presenter.Classes.AddRange(classes); } - protected internal virtual void NotifyPopupCreated(Popup popup) { } - + protected internal virtual void NotifyPopupCreated(Popup popup) + { + BindUtils.RelayBind(this, MarginToAnchorProperty, popup); + } + protected internal virtual void NotifyPositionPopup(bool showAtPointer) { Size sz; @@ -83,8 +63,10 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase Popup.VerticalOffset = VerticalOffset; Popup.HorizontalOffset = HorizontalOffset; + Popup.PlacementAnchor = PlacementAnchor; Popup.PlacementGravity = PlacementGravity; + if (showAtPointer) { Popup.Placement = PlacementMode.Pointer; } else { @@ -92,14 +74,4 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase Popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment; } } - - protected override void OnOpened() - { - base.OnOpened(); - } - - protected override void OnClosed() - { - base.OnClosed(); - } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Interceptors/PopupFlyoutBaseInterceptor.cs b/src/AtomUI.Controls/Interceptors/PopupFlyoutBaseInterceptor.cs index c07fbb5..c293b68 100644 --- a/src/AtomUI.Controls/Interceptors/PopupFlyoutBaseInterceptor.cs +++ b/src/AtomUI.Controls/Interceptors/PopupFlyoutBaseInterceptor.cs @@ -63,9 +63,15 @@ internal static class PopupFlyoutBaseInterceptor return false; } - public static void UpdateHostPositionInterceptor(AbstractPopup __instance, IPopupHost popupHost, Control placementTarget) + public static void UpdateHostPositionPostfixInterceptor(AbstractPopup __instance, IPopupHost popupHost, Control placementTarget) { - __instance.NotifyPopupHostPositionUpdated(popupHost, placementTarget); + __instance.NotifyHostPositionUpdated(popupHost, placementTarget); + } + + public static bool UpdateHostPositionPrefixInterceptor(AbstractPopup __instance, IPopupHost popupHost, Control placementTarget) + { + __instance.NotifyAboutToUpdateHostPosition(popupHost, placementTarget); + return true; } public static bool PositionPopupInterceptor(PopupFlyoutBase __instance, bool showAtPointer) @@ -80,7 +86,8 @@ internal static class PopupFlyoutBaseInterceptorRegister public static void Register(Harmony harmony) { RegisterPopupFlyoutBaseCreatePopup(harmony); - RegisterPopupUpdateHostPosition(harmony); + RegisterPopupUpdateHostPositionPrefix(harmony); + RegisterPopupUpdateHostPositionPostfix(harmony); RegisterPopupPositionPopup(harmony); } @@ -93,11 +100,20 @@ internal static class PopupFlyoutBaseInterceptorRegister harmony.Patch(origin, prefix: new HarmonyMethod(prefixInterceptor)); } - private static void RegisterPopupUpdateHostPosition(Harmony harmony) + private static void RegisterPopupUpdateHostPositionPrefix(Harmony harmony) + { + var origin = typeof(AvaloniaPopup).GetMethod("UpdateHostPosition", BindingFlags.Instance | BindingFlags.NonPublic); + var prefixInterceptor = typeof(PopupFlyoutBaseInterceptor) + .GetMethod(nameof(PopupFlyoutBaseInterceptor.UpdateHostPositionPrefixInterceptor), + BindingFlags.Static | BindingFlags.Public); + harmony.Patch(origin, prefix: new HarmonyMethod(prefixInterceptor)); + } + + private static void RegisterPopupUpdateHostPositionPostfix(Harmony harmony) { var origin = typeof(AvaloniaPopup).GetMethod("UpdateHostPosition", BindingFlags.Instance | BindingFlags.NonPublic); var postfixInterceptor = typeof(PopupFlyoutBaseInterceptor) - .GetMethod(nameof(PopupFlyoutBaseInterceptor.UpdateHostPositionInterceptor), + .GetMethod(nameof(PopupFlyoutBaseInterceptor.UpdateHostPositionPostfixInterceptor), BindingFlags.Static | BindingFlags.Public); harmony.Patch(origin, postfix: new HarmonyMethod(postfixInterceptor)); } diff --git a/src/AtomUI.Controls/Popup/AbstractPopup.cs b/src/AtomUI.Controls/Popup/AbstractPopup.cs index 677b5ce..3dbbd9a 100644 --- a/src/AtomUI.Controls/Popup/AbstractPopup.cs +++ b/src/AtomUI.Controls/Popup/AbstractPopup.cs @@ -1,6 +1,8 @@ -using Avalonia.Controls; +using System.Reflection; +using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; namespace AtomUI.Controls; @@ -8,7 +10,19 @@ using AvaloniaPopup = Avalonia.Controls.Primitives.Popup; public abstract class AbstractPopup : AvaloniaPopup { + public event EventHandler? PopupHostCreated; + public event EventHandler? AboutToClosing; + private static readonly FieldInfo ManagedPopupPositionerPopupInfo; + protected IManagedPopupPositionerPopup? _managedPopupPositioner; // 在弹窗有效期获取 + protected internal WeakReference? _popupHost; + + static AbstractPopup() + { + ManagedPopupPositionerPopupInfo = typeof(ManagedPopupPositioner).GetField("_popup", + BindingFlags.Instance | BindingFlags.NonPublic)!; + } + public AbstractPopup() { (this as IPopupHostProvider).PopupHostChanged += HandlePopupChanged; @@ -22,15 +36,44 @@ public abstract class AbstractPopup : AvaloniaPopup } } - protected internal virtual void NotifyPopupHostPositionUpdated(IPopupHost popupHost, Control placementTarget) + protected internal virtual void NotifyHostPositionUpdated(IPopupHost popupHost, Control placementTarget) { if (_popupHost is null) { _popupHost = new WeakReference(popupHost); NotifyPopupHostCreated(popupHost); } } + + // 开始定位 Host 窗口 + protected internal virtual void NotifyAboutToUpdateHostPosition(IPopupHost popupHost, Control placementTarget) + { + if (popupHost is PopupRoot popupRoot) { + if (popupRoot.PlatformImpl?.PopupPositioner is ManagedPopupPositioner managedPopupPositioner) { + _managedPopupPositioner = + ManagedPopupPositionerPopupInfo.GetValue(managedPopupPositioner) as IManagedPopupPositionerPopup; + } + } + } - protected virtual void NotifyPopupHostCreated(IPopupHost popupHost) {} - protected internal virtual void NotifyAboutToClosing() {} + protected virtual void NotifyPopupHostCreated(IPopupHost popupHost) + { + PopupHostCreated?.Invoke(this, new PopupHostCreatedEventArgs(popupHost)); + } + + protected internal virtual void NotifyAboutToClosing() + { + AboutToClosing?.Invoke(this, EventArgs.Empty); + } + protected virtual void NotifyClosed() {} +} + +public class PopupHostCreatedEventArgs : EventArgs +{ + public IPopupHost PopupHost { get; } + + public PopupHostCreatedEventArgs(IPopupHost host) + { + PopupHost = host; + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/IShadowLayer.cs b/src/AtomUI.Controls/Popup/IShadowLayer.cs index 79039bd..e533926 100644 --- a/src/AtomUI.Controls/Popup/IShadowLayer.cs +++ b/src/AtomUI.Controls/Popup/IShadowLayer.cs @@ -7,6 +7,4 @@ public interface IShadowDecorator public BoxShadows MaskShadows { get; set; } public void AttachToTarget(Popup host); public void DetachedFromTarget(Popup host); - public void ShowShadows(); - public void HideShadows(); } \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/LiteWindow.cs b/src/AtomUI.Controls/Popup/LiteWindow.cs index 7edeb42..c714886 100644 --- a/src/AtomUI.Controls/Popup/LiteWindow.cs +++ b/src/AtomUI.Controls/Popup/LiteWindow.cs @@ -13,7 +13,7 @@ public class LiteWindow : WindowBase, IHostedVisualTreeRoot, IDisposable { BackgroundProperty.OverrideDefaultValue(typeof(LiteWindow), Brushes.White); } - + public LiteWindow(TopLevel parent, IPopupImpl impl) : this(parent, impl, null) { } @@ -36,7 +36,7 @@ public class LiteWindow : WindowBase, IHostedVisualTreeRoot, IDisposable /// Gets the platform-specific window implementation. /// public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl; - + /// /// Gets the control that is hosting the popup root. /// @@ -57,7 +57,6 @@ public class LiteWindow : WindowBase, IHostedVisualTreeRoot, IDisposable public TopLevel ParentTopLevel { get; } - /// public void Dispose() { PlatformImpl?.Dispose(); diff --git a/src/AtomUI.Controls/Popup/Popup.cs b/src/AtomUI.Controls/Popup/Popup.cs index 729c6b1..5c9dc0c 100644 --- a/src/AtomUI.Controls/Popup/Popup.cs +++ b/src/AtomUI.Controls/Popup/Popup.cs @@ -1,9 +1,13 @@ using System.Reactive.Disposables; +using System.Reflection; using AtomUI.Data; using AtomUI.Styling; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -14,44 +18,390 @@ public class Popup : AbstractPopup public static readonly StyledProperty MaskShadowsProperty = Border.BoxShadowProperty.AddOwner(); + public static readonly StyledProperty MarginToAnchorProperty = + AvaloniaProperty.Register(nameof(MarginToAnchor)); + public BoxShadows MaskShadows { get => GetValue(MaskShadowsProperty); set => SetValue(MaskShadowsProperty, value); } + + public double MarginToAnchor + { + get => GetValue(MarginToAnchorProperty); + set => SetValue(MarginToAnchorProperty, value); + } - private PopupShadowLayer _shadowLayer; + private static readonly MethodInfo ConfigurePositionMethodInfo; + private PopupShadowLayer? _shadowLayer; private GlobalTokenBinder _globalTokenBinder; - private CompositeDisposable _compositeDisposable; + private CompositeDisposable? _compositeDisposable; private bool _initialized; + static Popup() + { + var type = typeof(IPopupPositioner).Assembly.GetType("Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerExtensions")!; + ConfigurePositionMethodInfo = type.GetMethod("ConfigurePosition", BindingFlags.Public | BindingFlags.Static)!; + } + public Popup() { IsLightDismissEnabled = false; - _shadowLayer = new PopupShadowLayer(); - _shadowLayer.AttachToTarget(this); _globalTokenBinder = new GlobalTokenBinder(); - _compositeDisposable = new CompositeDisposable(); } - + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); if (!_initialized) { - _globalTokenBinder.AddGlobalBinding(this, MaskShadowsProperty, GlobalResourceKey.BoxShadowsSecondary); - _compositeDisposable.Add(BindUtils.RelayBind(this, MaskShadowsProperty, _shadowLayer)); + _globalTokenBinder.AddGlobalBinding(this, MaskShadowsProperty, GlobalResourceKey.BoxShadowsSecondary); _initialized = true; } } - + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); - _compositeDisposable.Dispose(); + _compositeDisposable?.Dispose(); } - private PopupShadowLayer CreateShadowLayer() + protected override void NotifyPopupHostCreated(IPopupHost popupHost) { - return default!; + base.NotifyPopupHostCreated(popupHost); + + if (PlacementTarget is not null) { + var toplevel = TopLevel.GetTopLevel(PlacementTarget); + if (toplevel is null) { + throw new InvalidOperationException( + "Unable to create shadow layer, top level for PlacementTarget is null."); + } + + _compositeDisposable = new CompositeDisposable(); + _shadowLayer = new PopupShadowLayer(toplevel); + _compositeDisposable?.Add(BindUtils.RelayBind(this, MaskShadowsProperty, _shadowLayer!)); + _shadowLayer.AttachToTarget(this); + } + } + + protected override void NotifyClosed() + { + base.NotifyClosed(); + _compositeDisposable?.Dispose(); + _shadowLayer = null; + } + + internal (bool, bool) CalculateFlipInfo(Size translatedSize, Rect anchorRect, PopupAnchor anchor, + PopupGravity gravity, + Point offset) + { + var result = (false, false); + var bounds = GetBounds(anchorRect); + offset *= _managedPopupPositioner!.Scaling; + + bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask) + { + if (edge.HasFlag(PopupAnchor.Left) && rc.X < bounds.X || + edge.HasFlag(PopupAnchor.Top) && rc.Y < bounds.Y || + edge.HasFlag(PopupAnchor.Right) && rc.Right > bounds.Right || + edge.HasFlag(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom) { + return false; + } + + return true; + } + + Rect GetUnconstrained(PopupAnchor a, PopupGravity g) => + new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize); + + var geo = GetUnconstrained(anchor, gravity); + // If flipping geometry and anchor is allowed and helps, use the flipped one, + // otherwise leave it as is + if (!FitsInBounds(geo, PopupAnchor.HorizontalMask)) { + result.Item1 = true; + } + + if (!FitsInBounds(geo, PopupAnchor.VerticalMask)) { + result.Item2 = true; + } + + return result; + } + + private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity) + { + double x, y; + if (gravity.HasFlag(PopupGravity.Left)) { + x = -size.Width; + } else if (gravity.HasFlag(PopupGravity.Right)) { + x = 0; + } else { + x = -size.Width / 2; + } + + if (gravity.HasFlag(PopupGravity.Top)) { + y = -size.Height; + } else if (gravity.HasFlag(PopupGravity.Bottom)) { + y = 0; + } else { + y = -size.Height / 2; + } + + return anchorPoint + new Point(x, y); + } + + private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge) + { + double x, y; + if (edge.HasFlag(PopupAnchor.Left)) { + x = anchorRect.X; + } else if (edge.HasFlag(PopupAnchor.Right)) { + x = anchorRect.Right; + } else { + x = anchorRect.X + anchorRect.Width / 2; + } + + if (edge.HasFlag(PopupAnchor.Top)) { + y = anchorRect.Y; + } else if (edge.HasFlag(PopupAnchor.Bottom)) { + y = anchorRect.Bottom; + } else { + y = anchorRect.Y + anchorRect.Height / 2; + } + + return new Point(x, y); + } + + private Rect GetBounds(Rect anchorRect) + { + // 暂时只支持窗口的方式 + if (_managedPopupPositioner is null) { + throw new InvalidOperationException("ManagedPopupPositioner is null"); + } + + var parentGeometry = _managedPopupPositioner.ParentClientAreaScreenGeometry; + var screens = _managedPopupPositioner.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); + } + + public static void ConfigurePosition(ref PopupPositionerParameters positionerParameters, + TopLevel topLevel, + Visual target, PlacementMode placement, Point offset, + PopupAnchor anchor, PopupGravity gravity, + PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect, + FlowDirection flowDirection) + { + + var arguments = new object?[] + { + positionerParameters, + topLevel, + target, + placement, + offset, + anchor, + gravity, + constraintAdjustment, + rect, + flowDirection + }; + ConfigurePositionMethodInfo.Invoke(null, arguments); + positionerParameters = (PopupPositionerParameters)arguments[0]!; + } + + internal static Direction GetDirection(PlacementMode placement) + { + return placement switch + { + PlacementMode.Left => Direction.Left, + PlacementMode.LeftEdgeAlignedBottom => Direction.Left, + PlacementMode.LeftEdgeAlignedTop => Direction.Left, + + PlacementMode.Top => Direction.Top, + PlacementMode.TopEdgeAlignedLeft => Direction.Top, + PlacementMode.TopEdgeAlignedRight => Direction.Top, + + PlacementMode.Right => Direction.Right, + PlacementMode.RightEdgeAlignedBottom => Direction.Right, + PlacementMode.RightEdgeAlignedTop => Direction.Right, + + PlacementMode.Bottom => Direction.Bottom, + PlacementMode.BottomEdgeAlignedLeft => Direction.Bottom, + PlacementMode.BottomEdgeAlignedRight => Direction.Bottom, + _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, "Invalid value for PlacementMode") + }; + } + + private Point CalculateMarginToAnchorOffset(PlacementMode placement) + { + var offsetX = 0d; + var offsetY = 0d; + if (placement != PlacementMode.Center && + placement != PlacementMode.Pointer && + IsCanonicalAnchorType(placement, PlacementAnchor, PlacementGravity)) { + var direction = GetDirection(placement); + if (direction == Direction.Bottom) { + offsetY += MarginToAnchor; + } else if (direction == Direction.Top) { + offsetY += -MarginToAnchor; + } else if (direction == Direction.Left) { + offsetX += -MarginToAnchor; + } else { + offsetX += MarginToAnchor; + } + } else if (placement == PlacementMode.Pointer) { + offsetX += MarginToAnchor; + offsetY += MarginToAnchor; + } + return new Point(offsetX, offsetY); + } + + /// + /// 是否为标准的 anchor 类型 + /// + /// + /// + /// + /// + internal static bool IsCanonicalAnchorType(PlacementMode placement, PopupAnchor? anchor, PopupGravity? gravity) + { + if (placement == PlacementMode.AnchorAndGravity) { + switch (anchor, gravity) { + case (PopupAnchor.Bottom, PopupGravity.Bottom): + case (PopupAnchor.Right, PopupGravity.Right): + case (PopupAnchor.Left, PopupGravity.Left): + case (PopupAnchor.Top, PopupGravity.Top): + case (PopupAnchor.TopRight, PopupGravity.TopLeft): + case (PopupAnchor.TopLeft, PopupGravity.TopRight): + case (PopupAnchor.BottomLeft, PopupGravity.BottomRight): + case (PopupAnchor.BottomRight, PopupGravity.BottomLeft): + case (PopupAnchor.TopLeft, PopupGravity.BottomLeft): + case (PopupAnchor.BottomLeft, PopupGravity.TopLeft): + case (PopupAnchor.TopRight, PopupGravity.BottomRight): + case (PopupAnchor.BottomRight, PopupGravity.TopRight): + break; + default: + return false; + } + } + + return true; + } + + protected internal override void NotifyAboutToUpdateHostPosition(IPopupHost popupHost, Control placementTarget) + { + base.NotifyAboutToUpdateHostPosition(popupHost, placementTarget); + var offsetX = HorizontalOffset; + var offsetY = VerticalOffset; + var marginToAnchorOffset = CalculateMarginToAnchorOffset(Placement); + offsetX += marginToAnchorOffset.X; + offsetY += marginToAnchorOffset.Y; + HorizontalOffset = offsetX; + VerticalOffset = offsetY; + + if (Placement != PlacementMode.Center && + Placement != PlacementMode.Pointer) { + // 计算是否 flip + PopupPositionerParameters parameters = new PopupPositionerParameters(); + if (popupHost is PopupRoot popupRoot) { + var offset = new Point(HorizontalOffset, VerticalOffset); + ConfigurePosition(ref parameters, popupRoot.ParentTopLevel, + PlacementTarget!, + Placement, + offset, + PlacementAnchor, + PlacementGravity, + PopupPositionerConstraintAdjustment.All, + null, + FlowDirection); + + Size popupSize; + // Popup.Child can't be null here, it was set in ShowAtCore. + if (Child!.DesiredSize == default) { + // Popup may not have been shown yet. Measure content + popupSize = LayoutHelper.MeasureChild(Child, Size.Infinity, new Thickness()); + } else { + popupSize = Child.DesiredSize; + } + var scaling = _managedPopupPositioner!.Scaling; + var anchorRect = new Rect( + parameters.AnchorRectangle.TopLeft * scaling, + parameters.AnchorRectangle.Size * scaling); + anchorRect = anchorRect.Translate(_managedPopupPositioner.ParentClientAreaScreenGeometry.TopLeft); + + var flipInfo = CalculateFlipInfo(popupSize * scaling, + anchorRect, + parameters.Anchor, + parameters.Gravity, + offset); + if (flipInfo.Item1 || flipInfo.Item2) { + var flipPlacement = GetFlipPlacement(Placement); + var flipAnchorAndGravity = GetAnchorAndGravity(flipPlacement); + var flipOffset = CalculateMarginToAnchorOffset(flipPlacement); + Placement = flipPlacement; + PlacementAnchor = flipAnchorAndGravity.Item1; + PlacementGravity = flipAnchorAndGravity.Item2; + HorizontalOffset = flipOffset.X; + VerticalOffset = flipOffset.Y; + } + } + } + } + + private (PopupAnchor, PopupGravity) GetAnchorAndGravity(PlacementMode placement) + { + return placement switch + { + PlacementMode.Bottom => (PopupAnchor.Bottom, PopupGravity.Bottom), + PlacementMode.Right => (PopupAnchor.Right, PopupGravity.Right), + PlacementMode.Left => (PopupAnchor.Left, PopupGravity.Left), + PlacementMode.Top => (PopupAnchor.Top, PopupGravity.Top), + PlacementMode.TopEdgeAlignedRight => (PopupAnchor.TopRight, PopupGravity.TopLeft), + PlacementMode.TopEdgeAlignedLeft => (PopupAnchor.TopLeft, PopupGravity.TopRight), + PlacementMode.BottomEdgeAlignedLeft => (PopupAnchor.BottomLeft, PopupGravity.BottomRight), + PlacementMode.BottomEdgeAlignedRight => (PopupAnchor.BottomRight, PopupGravity.BottomLeft), + PlacementMode.LeftEdgeAlignedTop => (PopupAnchor.TopLeft, PopupGravity.BottomLeft), + PlacementMode.LeftEdgeAlignedBottom => (PopupAnchor.BottomLeft, PopupGravity.TopLeft), + PlacementMode.RightEdgeAlignedTop => (PopupAnchor.TopRight, PopupGravity.BottomRight), + PlacementMode.RightEdgeAlignedBottom => (PopupAnchor.BottomRight, PopupGravity.TopRight), + _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, "Invalid value for PlacementMode") + }; + } + + protected PlacementMode GetFlipPlacement(PlacementMode placement) + { + return placement switch + { + PlacementMode.Left => PlacementMode.Right, + PlacementMode.LeftEdgeAlignedTop => PlacementMode.RightEdgeAlignedTop, + PlacementMode.LeftEdgeAlignedBottom => PlacementMode.RightEdgeAlignedBottom, + + PlacementMode.Top => PlacementMode.Bottom, + PlacementMode.TopEdgeAlignedLeft => PlacementMode.BottomEdgeAlignedLeft, + PlacementMode.TopEdgeAlignedRight => PlacementMode.BottomEdgeAlignedRight, + + PlacementMode.Right => PlacementMode.Left, + PlacementMode.RightEdgeAlignedTop => PlacementMode.LeftEdgeAlignedTop, + PlacementMode.RightEdgeAlignedBottom => PlacementMode.LeftEdgeAlignedBottom, + + PlacementMode.Bottom => PlacementMode.Top, + PlacementMode.BottomEdgeAlignedLeft => PlacementMode.TopEdgeAlignedLeft, + PlacementMode.BottomEdgeAlignedRight => PlacementMode.TopEdgeAlignedRight, + + _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, "Invalid value for PlacementMode") + }; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/PopupShadowLayer.cs b/src/AtomUI.Controls/Popup/PopupShadowLayer.cs index 4bb9667..7466d79 100644 --- a/src/AtomUI.Controls/Popup/PopupShadowLayer.cs +++ b/src/AtomUI.Controls/Popup/PopupShadowLayer.cs @@ -2,7 +2,6 @@ using System.Reflection; using AtomUI.Media; using AtomUI.Platform.Windows; -using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -11,76 +10,65 @@ using Avalonia.Media; namespace AtomUI.Controls; -internal class PopupShadowLayer : AbstractPopup, IShadowDecorator +internal class PopupShadowLayer : LiteWindow, IShadowDecorator { - public static readonly StyledProperty MaskShadowsProperty = - Border.BoxShadowProperty.AddOwner(); + public static readonly StyledProperty MaskShadowsProperty = + Border.BoxShadowProperty.AddOwner(); public BoxShadows MaskShadows { get => GetValue(MaskShadowsProperty); set => SetValue(MaskShadowsProperty, value); } - + private static readonly FieldInfo ManagedPopupPositionerPopupInfo; - private IManagedPopupPositionerPopup? _managedPopupPositionerPopup; static PopupShadowLayer() { - ManagedPopupPositionerPopupInfo = typeof(ManagedPopupPositioner).GetField("_popup", + ManagedPopupPositionerPopupInfo = typeof(ManagedPopupPositioner).GetField("_popup", BindingFlags.Instance | BindingFlags.NonPublic)!; } - public PopupShadowLayer() - { - WindowManagerAddShadowHint = false; - IsLightDismissEnabled = false; - _compositeDisposable = new CompositeDisposable(); - } - private Popup? _target; private ShadowRenderer? _shadowRenderer; - private CompositeDisposable _compositeDisposable; + private CompositeDisposable? _compositeDisposable; + private IManagedPopupPositionerPopup? _managedPopupPositionerPopup; + private TopLevel? _topLevel; + private bool _isOpened = false; + + public PopupShadowLayer(TopLevel topLevel) + : base(topLevel, topLevel.PlatformImpl?.CreatePopup()!) + { + _topLevel = topLevel; + Background = new SolidColorBrush(Colors.Transparent); + if (this is WindowBase window) { + window.SetTransparentForMouseEvents(true); + } + + if (PlatformImpl?.PopupPositioner is ManagedPopupPositioner managedPopupPositioner) { + _managedPopupPositionerPopup = + ManagedPopupPositionerPopupInfo.GetValue(managedPopupPositioner) as IManagedPopupPositionerPopup; + } + } public void AttachToTarget(Popup popup) { - if (_target is not null && _target != popup) { - // 释放资源 - _compositeDisposable.Dispose(); - } _target = popup; ConfigureShadowPopup(); } public void DetachedFromTarget(Popup popup) { } - - public void ShowShadows() { } - public void HideShadows() {} private void ConfigureShadowPopup() { - var offset = CalculatePopupOffset(); - HorizontalOffset = offset.X; - VerticalOffset = offset.Y; - // // 绑定资源要管理起来 if (_target is not null) { _target.Opened += HandleTargetOpened; _target.Closed += HandleTargetClosed; - SetupRelayBindings(); } + if (_shadowRenderer is null) { _shadowRenderer ??= new ShadowRenderer(); - Child = _shadowRenderer; - } - } - - private void SetupRelayBindings() - { - if (_target is not null) { - // _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementProperty, this)); - // _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementGravityProperty, this)); - // _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementAnchorProperty, this)); - _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementTargetProperty, this)); + SetChild(_shadowRenderer); } } @@ -89,22 +77,62 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator SetupShadowRenderer(); Open(); } - - private void HandleTargetClosed(object? sender, EventArgs args) + + private void Open() { - _compositeDisposable.Dispose(); - Close(); + if (_isOpened) { + return; + } + + _compositeDisposable = new CompositeDisposable(); + if (_topLevel is Avalonia.Controls.Window window && window.PlatformImpl != null) { + + } + + if (_target?.Host is PopupRoot popupRoot) { + popupRoot.PositionChanged += TargetPopupPositionChanged; + } + + _compositeDisposable.Add(Disposable.Create(this, state => + { + state.SetChild(null); + Hide(); + ((ISetLogicalParent)state).SetParent(null); + Dispose(); + })); + ((ISetLogicalParent)this).SetParent(_target); + SetupPositionAndSize(); + _isOpened = true; + Show(); } - protected override void NotifyClosed() + private void TargetPopupPositionChanged(object? sender, PixelPointEventArgs e) { - base.NotifyClosed(); - _managedPopupPositionerPopup = null; + SetupPositionAndSize(); } - + + private static IDisposable SubscribeToEventHandler(T target, TEventHandler handler, + Action subscribe, + Action unsubscribe) + { + subscribe(target, handler); + return Disposable.Create((unsubscribe, target, handler), + state => state.unsubscribe(state.target, state.handler)); + } + + private void HandleTargetClosed(object? sender, EventArgs args) + { + if (_target is not null) { + _target.Opened -= HandleTargetOpened; + _target.Closed -= HandleTargetClosed; + } + + _compositeDisposable?.Dispose(); + _compositeDisposable = null; + } + private void SetupShadowRenderer() { - SetupRelayBindings(); if (_target?.Child is not null && _shadowRenderer is not null) { // 理论上现在已经有大小了 var content = _target?.Child!; @@ -123,22 +151,6 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator } } - protected override void NotifyPopupHostCreated(IPopupHost popupHost) - { - base.NotifyPopupHostCreated(popupHost); - if (popupHost is WindowBase window) { - window.Background = new SolidColorBrush(Colors.Transparent); - window.SetTransparentForMouseEvents(true); - if (_managedPopupPositionerPopup is null) { - if (popupHost is PopupRoot popupRoot) { - if (popupRoot.PlatformImpl?.PopupPositioner is ManagedPopupPositioner managedPopupPositioner) { - _managedPopupPositionerPopup = ManagedPopupPositionerPopupInfo.GetValue(managedPopupPositioner) as IManagedPopupPositionerPopup; - } - } - } - } - } - private Size CalculateShadowRendererSize(Control content) { var shadowThickness = MaskShadows.Thickness(); @@ -147,18 +159,19 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator return new Size(targetWidth, targetHeight); } - private Point CalculatePopupOffset() + private void SetupPositionAndSize() { - return default; + if (_target?.Host is PopupRoot popupRoot) { + var impl = popupRoot.PlatformImpl!; + var targetPosition = impl.Position; + double offsetX = targetPosition.X; + double offsetY = targetPosition.Y; + double scaling = _managedPopupPositionerPopup!.Scaling; + var shadowThickness = MaskShadows.Thickness(); + offsetX -= shadowThickness.Left * scaling; + offsetY -= shadowThickness.Top * scaling; + _managedPopupPositionerPopup?.MoveAndResize(new Point(offsetX, offsetY), + new Size(_shadowRenderer!.Width, _shadowRenderer.Height)); + } } - - protected internal override void NotifyPopupHostPositionUpdated(IPopupHost popupHost, Control placementTarget) - { - base.NotifyPopupHostPositionUpdated(popupHost, placementTarget); - popupHost.ConfigurePosition(placementTarget, - PlacementMode.Pointer, - offset: new Point(-40, 1), - anchor: PopupAnchor.Top, - gravity: PopupGravity.Top); - } -} +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/ShadowPopupHost.cs b/src/AtomUI.Controls/Popup/ShadowPopupHost.cs deleted file mode 100644 index c9a086c..0000000 --- a/src/AtomUI.Controls/Popup/ShadowPopupHost.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Reactive.Disposables; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; - -namespace AtomUI.Controls; - -public class ShadowPopupHost : LiteWindow -{ - public static readonly StyledProperty IsOpenProperty = AvaloniaProperty.Register(nameof(IsOpen)); - - public bool IsOpen - { - get => GetValue(IsOpenProperty); - set => SetValue(IsOpenProperty, value); - } - - private CompositeDisposable? _compositeDisposable; - private PopupRoot _popupRoot; - - public ShadowPopupHost(PopupRoot popupRoot) - : base(popupRoot, popupRoot.PlatformImpl!) - { - _popupRoot = popupRoot; - } - - private void Open() - { - if (_compositeDisposable != null) { - return; - } - - Topmost = true; - // SubscribeToEventHandler>(parentPopupRoot, ParentPopupPositionChanged, - // (x, handler) => x.PositionChanged += handler, - // (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup); - // - // if (parentPopupRoot.Parent is Popup popup) - // { - // SubscribeToEventHandler>(popup, ParentClosed, - // (x, handler) => x.Closed += handler, - // (x, handler) => x.Closed -= handler).DisposeWith(handlerCleanup); - // } - _compositeDisposable = new CompositeDisposable(); - _compositeDisposable.Add(Disposable.Create(this, state => - { - state.SetChild(null); - Hide(); - ((ISetLogicalParent)state).SetParent(null); - Dispose(); - })); - Show(); - } - - private static IDisposable SubscribeToEventHandler(T target, TEventHandler handler, - Action subscribe, - Action unsubscribe) - { - subscribe(target, handler); - return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); - } -} \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/ShadowRenderer.cs b/src/AtomUI.Controls/Popup/ShadowRenderer.cs index 94e87fb..ea1917a 100644 --- a/src/AtomUI.Controls/Popup/ShadowRenderer.cs +++ b/src/AtomUI.Controls/Popup/ShadowRenderer.cs @@ -66,7 +66,7 @@ internal class ShadowRenderer : Control var maskContent = new Border { BorderThickness = new Thickness(0), - Background = new SolidColorBrush(Colors.Red), + Background = new SolidColorBrush(Colors.Transparent), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch }; @@ -98,9 +98,4 @@ internal class ShadowRenderer : Control Canvas.SetTop(_maskContent, shadowThickness.Top); } } - - // public sealed override void Render(DrawingContext context) - // { - // context.FillRectangle(new SolidColorBrush(Colors.Bisque), new Rect(new Point(0, 0), DesiredSize)); - // } } \ No newline at end of file