diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/DropdownButtonShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/DropdownButtonShowCase.axaml index ee7273b..39a9a88 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/DropdownButtonShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/DropdownButtonShowCase.axaml @@ -11,12 +11,19 @@ - + Hover me - - The most basic example. - + + + + + + + + + diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/InfoFlyoutShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/InfoFlyoutShowCase.axaml index 90a3a48..8ac25e8 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/InfoFlyoutShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/InfoFlyoutShowCase.axaml @@ -35,14 +35,6 @@ Hover me - - - - The most basic example. - - - Focus me - diff --git a/src/AtomUI.Controls/DropdownButton/DropdownButton.cs b/src/AtomUI.Controls/DropdownButton/DropdownButton.cs index d1f78d0..b048402 100644 --- a/src/AtomUI.Controls/DropdownButton/DropdownButton.cs +++ b/src/AtomUI.Controls/DropdownButton/DropdownButton.cs @@ -15,14 +15,12 @@ using Avalonia.Threading; namespace AtomUI.Controls; -using FlyoutControl = Flyout; - public class DropdownButton : Button { #region 公共属性定义 - public static readonly StyledProperty DropdownFlyoutProperty = - AvaloniaProperty.Register(nameof(DropdownFlyout)); + public static readonly StyledProperty DropdownFlyoutProperty = + AvaloniaProperty.Register(nameof(DropdownFlyout)); public static readonly StyledProperty TriggerTypeProperty = AvaloniaProperty.Register(nameof(TriggerType), FlyoutTriggerType.Click); @@ -51,7 +49,7 @@ public class DropdownButton : Button public static readonly StyledProperty MouseLeaveDelayProperty = AvaloniaProperty.Register(nameof(MouseLeaveDelay), 100); - public FlyoutControl? DropdownFlyout + public MenuFlyout? DropdownFlyout { get => GetValue(DropdownFlyoutProperty); set => SetValue(DropdownFlyoutProperty, value); @@ -117,6 +115,7 @@ public class DropdownButton : Button private DispatcherTimer? _mouseLeaveDelayTimer; private CompositeDisposable? _subscriptions; private PathIcon? _openIndicatorIcon; + private IDisposable? _flyoutCloseDetectDisposable; static DropdownButton() { @@ -136,7 +135,7 @@ public class DropdownButton : Button base.OnApplyTemplate(e); TokenResourceBinder.CreateGlobalTokenBinding(this, MarginToAnchorProperty, GlobalTokenResourceKey.MarginXXS); - SetupTriggerHandler(); + SetupFlyoutProperties(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) @@ -162,6 +161,26 @@ public class DropdownButton : Button BindUtils.RelayBind(this, IsShowArrowProperty, DropdownFlyout); BindUtils.RelayBind(this, IsPointAtCenterProperty, DropdownFlyout); BindUtils.RelayBind(this, MarginToAnchorProperty, DropdownFlyout); + + DropdownFlyout.Opened += HandleFlyoutOpened; + } + } + + private void HandleFlyoutOpened(object? sender, EventArgs e) + { + if (DropdownFlyout is IPopupHostProvider popupHostProvider) { + var host = popupHostProvider.PopupHost; + if (host is PopupRoot popupRoot) { + // 这里 PopupRoot 关闭的时候会被关闭,所以这里的事件处理器是不是不需要删除 + popupRoot.PointerMoved += (o, args) => + { + StopMouseLeaveTimer(); + if (_flyoutCloseDetectDisposable is null) { + var inputManager = AvaloniaLocator.Current.GetService()!; + _flyoutCloseDetectDisposable = inputManager.Process.Subscribe(DetectWhenToClosePopup); + } + }; + } } } @@ -175,13 +194,6 @@ public class DropdownButton : Button HandleAnchorTargetHover(args); } })); - } else if (TriggerType == FlyoutTriggerType.Focus) { - _subscriptions.Add(IsFocusedProperty.Changed.Subscribe(args => - { - if (args.Sender == this) { - HandleAnchorTargetFocus(args); - } - })); } else if (TriggerType == FlyoutTriggerType.Click) { var inputManager = AvaloniaLocator.Current.GetService()!; _subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick)); @@ -199,33 +211,46 @@ public class DropdownButton : Button } } - private void HandleAnchorTargetFocus(AvaloniaPropertyChangedEventArgs e) + private void DetectWhenToClosePopup(RawInputEventArgs args) { - if (DropdownFlyout is not null) { - if (e.GetNewValue()) { - if (!DropdownFlyout.IsOpen) { - ShowFlyout(); + if (args is RawPointerEventArgs pointerEventArgs) { + if (DropdownFlyout is null) { + return; + } + if (DropdownFlyout.IsOpen) { + var found = false; + if (pointerEventArgs.Root is PopupRoot popupRoot) { + var current = popupRoot.Parent; + while (current is not null) { + if (current == this) { + found = true; + } + current = current.Parent; + } + } else if (object.Equals(pointerEventArgs.Root, this)) { + found = true; + } + if (!found) { + HideFlyout(); } - } else { - HideFlyout(); } } } - + private void HandleAnchorTargetClick(RawInputEventArgs args) { if (args is RawPointerEventArgs pointerEventArgs) { + if (DropdownFlyout is null) { + return; + } + if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp) { - if (DropdownFlyout is null) { - return; - } - if (!DropdownFlyout.IsOpen) { var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); if (!pos.HasValue) { return; } - + var bounds = new Rect(pos.Value, Bounds.Size); if (bounds.Contains(pointerEventArgs.Position)) { ShowFlyout(); @@ -246,7 +271,8 @@ public class DropdownButton : Button if (DropdownFlyout is null) { return; } - + // 防止干扰打开 + _flyoutCloseDetectDisposable?.Dispose(); StopMouseEnterTimer(); StopMouseLeaveTimer(); DropdownFlyout.Hide(); @@ -262,7 +288,8 @@ public class DropdownButton : Button if (DropdownFlyout is null) { return; } - + _flyoutCloseDetectDisposable?.Dispose(); + _flyoutCloseDetectDisposable = null; StopMouseEnterTimer(); if (MouseLeaveDelay == 0) { diff --git a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs index f2a5234..012fa1e 100644 --- a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs +++ b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs @@ -18,8 +18,7 @@ using FlyoutControl = Flyout; public enum FlyoutTriggerType { Hover, - Click, - Focus + Click } public class FlyoutHost : Control @@ -212,13 +211,6 @@ public class FlyoutHost : Control HandleAnchorTargetHover(args); } }); - } else if (Trigger == FlyoutTriggerType.Focus) { - _subscriptions.Add(IsFocusedProperty.Changed.Subscribe(args => - { - if (args.Sender == AnchorTarget) { - HandleAnchorTargetFocus(args); - } - })); } else if (Trigger == FlyoutTriggerType.Click) { var inputManager = AvaloniaLocator.Current.GetService()!; _subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick)); @@ -236,17 +228,6 @@ public class FlyoutHost : Control } } - private void HandleAnchorTargetFocus(AvaloniaPropertyChangedEventArgs e) - { - if (Flyout is not null) { - if (e.GetNewValue()) { - ShowFlyout(); - } else { - HideFlyout(); - } - } - } - private void HandleAnchorTargetClick(RawInputEventArgs args) { if (args is RawPointerEventArgs pointerEventArgs) { diff --git a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs index ef0dded..be0b51b 100644 --- a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs @@ -526,4 +526,19 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider //Add new classes presenter.Classes.AddRange(classes); } + + internal bool InPopupRootBounds(Point position) + { + if (!IsOpen) { + return false; + } + // TODO 后期需要加入对 Overlay 的支持 + if (Popup?.Host is PopupRoot root) { + // Get the popup root bounds and convert to screen coordinates + + Console.WriteLine(position); + } + + return true; + } } \ No newline at end of file