mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-30 02:47:45 +08:00
完成 popup flip 功能
This commit is contained in:
parent
900a57f414
commit
f43a41f825
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否显示指示箭头
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsShowArrowProperty =
|
||||
ArrowDecoratedBox.IsShowArrowProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowArrowEffectiveProperty =
|
||||
ArrowDecoratedBox.IsShowArrowProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <summary>
|
||||
/// 箭头是否始终指向中心
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsPointAtCenterProperty =
|
||||
AvaloniaProperty.Register<PopupFlyoutBase, bool>(nameof(IsPointAtCenter), false);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Content"/> property
|
||||
/// </summary>
|
||||
@ -28,6 +46,27 @@ public class Flyout : PopupFlyoutBase
|
||||
public static readonly StyledProperty<ControlTheme?> FlyoutPresenterThemeProperty =
|
||||
AvaloniaProperty.Register<Flyout, ControlTheme?>(nameof(FlyoutPresenterTheme));
|
||||
|
||||
public bool IsShowArrow
|
||||
{
|
||||
get => GetValue(IsShowArrowProperty);
|
||||
set => SetValue(IsShowArrowProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否实际显示箭头
|
||||
/// </summary>
|
||||
public bool IsShowArrowEffective
|
||||
{
|
||||
get => GetValue(IsShowArrowEffectiveProperty);
|
||||
set => SetValue(IsShowArrowEffectiveProperty, value);
|
||||
}
|
||||
|
||||
public bool IsPointAtCenter
|
||||
{
|
||||
get => GetValue(IsPointAtCenterProperty);
|
||||
set => SetValue(IsPointAtCenterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for the flyout presenter.
|
||||
/// </summary>
|
||||
@ -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
|
||||
/// <summary>
|
||||
/// 判断是否可以启用箭头,有些组合是不能启用箭头绘制的,因为没有意义
|
||||
/// </summary>
|
||||
/// <param name="placementMode"></param>
|
||||
/// <param name="placement"></param>
|
||||
/// <param name="anchor"></param>
|
||||
/// <param name="gravity"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
/// 箭头是否始终指向中心
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsPointAtCenterProperty =
|
||||
PopupFlyoutBase.IsPointAtCenterProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ToolTip.Placement property.
|
||||
/// </summary>
|
||||
FlyoutControl.IsPointAtCenterProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
public static readonly StyledProperty<PlacementMode> PlacementProperty =
|
||||
Popup.PlacementProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
|
||||
public static readonly StyledProperty<PopupAnchor> PlacementAnchorProperty =
|
||||
Popup.PlacementAnchorProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
|
||||
|
||||
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
|
||||
Popup.PlacementGravityProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
@ -66,7 +65,7 @@ public class FlyoutHost : Control
|
||||
/// 还有些 anchor 和 gravity 的组合也没有用
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> MarginToAnchorProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, double>(nameof(MarginToAnchor), 0);
|
||||
Popup.MarginToAnchorProperty.AddOwner<FlyoutHost>();
|
||||
|
||||
public static readonly StyledProperty<int> ShowDelayProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, int>(nameof(ShowDelay), 400);
|
||||
@ -83,10 +82,7 @@ public class FlyoutHost : Control
|
||||
get => GetValue(AnchorTargetProperty);
|
||||
set => SetValue(AnchorTargetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Flyout that should be shown with this button.
|
||||
/// </summary>
|
||||
|
||||
public PopupFlyoutBase? Flyout
|
||||
{
|
||||
get => GetValue(FlyoutProperty);
|
||||
@ -116,15 +112,13 @@ public class FlyoutHost : Control
|
||||
get => GetValue(PlacementProperty);
|
||||
set => SetValue(PlacementProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementGravity"/>
|
||||
|
||||
public PopupGravity PlacementGravity
|
||||
{
|
||||
get => GetValue(PlacementGravityProperty);
|
||||
set => SetValue(PlacementGravityProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementAnchor"/>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 最基本得弹窗 Flyout,在这里不处理那种带箭头得
|
||||
/// </summary>
|
||||
public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否显示指示箭头
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsShowArrowProperty =
|
||||
ArrowDecoratedBox.IsShowArrowProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <summary>
|
||||
/// 箭头是否始终指向中心
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsPointAtCenterProperty =
|
||||
AvaloniaProperty.Register<PopupFlyoutBase, bool>(nameof(IsPointAtCenter), false);
|
||||
|
||||
/// <summary>
|
||||
/// 距离 anchor 的边距,根据垂直和水平进行设置
|
||||
/// 但是对某些组合无效,比如跟随鼠标的情况
|
||||
/// 还有些 anchor 和 gravity 的组合也没有用
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> MarginToAnchorProperty =
|
||||
AvaloniaProperty.Register<PopupFlyoutBase, double>(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<PopupFlyoutBase>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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<EventArgs>? PopupHostCreated;
|
||||
public event EventHandler<EventArgs>? AboutToClosing;
|
||||
private static readonly FieldInfo ManagedPopupPositionerPopupInfo;
|
||||
protected IManagedPopupPositionerPopup? _managedPopupPositioner; // 在弹窗有效期获取
|
||||
|
||||
protected internal WeakReference<IPopupHost>? _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<IPopupHost>(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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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.
|
||||
/// </summary>
|
||||
public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control that is hosting the popup root.
|
||||
/// </summary>
|
||||
@ -57,7 +57,6 @@ public class LiteWindow : WindowBase, IHostedVisualTreeRoot, IDisposable
|
||||
|
||||
public TopLevel ParentTopLevel { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
PlatformImpl?.Dispose();
|
||||
|
@ -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<BoxShadows> MaskShadowsProperty =
|
||||
Border.BoxShadowProperty.AddOwner<Popup>();
|
||||
|
||||
public static readonly StyledProperty<double> MarginToAnchorProperty =
|
||||
AvaloniaProperty.Register<Popup, double>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为标准的 anchor 类型
|
||||
/// </summary>
|
||||
/// <param name="placement"></param>
|
||||
/// <param name="anchor"></param>
|
||||
/// <param name="gravity"></param>
|
||||
/// <returns></returns>
|
||||
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")
|
||||
};
|
||||
}
|
||||
}
|
@ -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<BoxShadows> MaskShadowsProperty =
|
||||
Border.BoxShadowProperty.AddOwner<PopupShadowLayer>();
|
||||
public static readonly StyledProperty<BoxShadows> MaskShadowsProperty =
|
||||
Border.BoxShadowProperty.AddOwner<PopupShadowLayer>();
|
||||
|
||||
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, TEventHandler>(T target, TEventHandler handler,
|
||||
Action<T, TEventHandler> subscribe,
|
||||
Action<T, TEventHandler> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<bool> IsOpenProperty = AvaloniaProperty.Register<Popup, bool>(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<PopupRoot, EventHandler<PixelPointEventArgs>>(parentPopupRoot, ParentPopupPositionChanged,
|
||||
// (x, handler) => x.PositionChanged += handler,
|
||||
// (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup);
|
||||
//
|
||||
// if (parentPopupRoot.Parent is Popup popup)
|
||||
// {
|
||||
// SubscribeToEventHandler<Popup, EventHandler<EventArgs>>(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, TEventHandler>(T target, TEventHandler handler,
|
||||
Action<T, TEventHandler> subscribe,
|
||||
Action<T, TEventHandler> unsubscribe)
|
||||
{
|
||||
subscribe(target, handler);
|
||||
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
|
||||
}
|
||||
}
|
@ -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));
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user