diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 6dba33516b..8e5d4e1e06 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Windows.Input; using Avalonia.Automation.Peers; @@ -281,24 +282,29 @@ namespace Avalonia.Controls /// protected override void OnKeyDown(KeyEventArgs e) { - if (e.Key == Key.Enter) + switch (e.Key) { - OnClick(); - e.Handled = true; - } - else if (e.Key == Key.Space) - { - if (ClickMode == ClickMode.Press) - { + case Key.Enter: OnClick(); + e.Handled = true; + break; + + case Key.Space: + { + if (ClickMode == ClickMode.Press) + { + OnClick(); + } + + IsPressed = true; + e.Handled = true; + break; } - IsPressed = true; - e.Handled = true; - } - else if (e.Key == Key.Escape && Flyout != null) - { - // If Flyout doesn't have focusable content, close the flyout here - Flyout.Hide(); + + case Key.Escape when Flyout != null: + // If Flyout doesn't have focusable content, close the flyout here + CloseFlyout(); + break; } base.OnKeyDown(e); @@ -327,7 +333,14 @@ namespace Avalonia.Controls { if (IsEffectivelyEnabled) { - OpenFlyout(); + if (_isFlyoutOpen) + { + CloseFlyout(); + } + else + { + OpenFlyout(); + } var e = new RoutedEventArgs(ClickEvent); RaiseEvent(e); @@ -348,6 +361,14 @@ namespace Avalonia.Controls Flyout?.ShowAt(this); } + /// + /// Closes the button's flyout. + /// + protected virtual void CloseFlyout() + { + Flyout?.Hide(); + } + /// /// Invoked when the button's flyout is opened. /// @@ -494,8 +515,7 @@ namespace Avalonia.Controls // If flyout is changed while one is already open, make sure we // close the old one first - if (oldFlyout != null && - oldFlyout.IsOpen) + if (oldFlyout != null && oldFlyout.IsOpen) { oldFlyout.Hide(); } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 1504d2b25f..00ebcab70e 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -12,17 +12,12 @@ namespace Avalonia.Controls.Primitives { public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider { - static FlyoutBase() - { - Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); - } - /// /// Defines the property /// public static readonly DirectProperty IsOpenProperty = - AvaloniaProperty.RegisterDirect(nameof(IsOpen), - x => x.IsOpen); + AvaloniaProperty.RegisterDirect(nameof(IsOpen), + x => x.IsOpen); /// /// Defines the property @@ -43,6 +38,14 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.RegisterDirect(nameof(ShowMode), x => x.ShowMode, (x, v) => x.ShowMode = v); + /// + /// Defines the property + /// + public static readonly DirectProperty OverlayInputPassThroughElementProperty = + Popup.OverlayInputPassThroughElementProperty.AddOwner( + o => o._overlayInputPassThroughElement, + (o, v) => o._overlayInputPassThroughElement = v); + /// /// Defines the AttachedFlyout property /// @@ -57,6 +60,12 @@ namespace Avalonia.Controls.Primitives private PixelRect? _enlargePopupRectScreenPixelRect; private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + private IInputElement? _overlayInputPassThroughElement; + + static FlyoutBase() + { + Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); + } public FlyoutBase() { @@ -101,11 +110,21 @@ namespace Avalonia.Controls.Primitives private set => SetAndRaise(TargetProperty, ref _target, value); } + /// + /// Gets or sets an element that should receive pointer input events even when underneath + /// the flyout's overlay. + /// + public IInputElement? OverlayInputPassThroughElement + { + get => _overlayInputPassThroughElement; + set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); + } + IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; - event Action? IPopupHostProvider.PopupHostChanged - { - add => _popupHostChangedHandler += value; + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; remove => _popupHostChangedHandler -= value; } @@ -175,8 +194,9 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; + ((ISetLogicalParent)Popup).SetParent(null); - + // Ensure this isn't active _transientDisposable?.Dispose(); _transientDisposable = null; @@ -231,6 +251,8 @@ namespace Avalonia.Controls.Primitives Popup.Child = CreatePresenter(); } + Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement; + if (CancelOpening()) { return false; @@ -356,10 +378,13 @@ namespace Avalonia.Controls.Primitives private Popup CreatePopup() { - var popup = new Popup(); - popup.WindowManagerAddShadowHint = false; - popup.IsLightDismissEnabled = true; - popup.OverlayDismissEventPassThrough = true; + var popup = new Popup + { + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = true, + //Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss. + OverlayDismissEventPassThrough = false + }; popup.Opened += OnPopupOpened; popup.Closed += OnPopupClosed; @@ -372,7 +397,7 @@ namespace Avalonia.Controls.Primitives { IsOpen = true; - _popupHostChangedHandler?.Invoke(Popup!.Host); + _popupHostChangedHandler?.Invoke(Popup.Host); } private void OnPopupClosing(object? sender, CancelEventArgs e) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1501d97470..3573ad9aaa 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -501,7 +501,7 @@ namespace Avalonia.Controls.Primitives if (dismissLayer != null) { dismissLayer.IsVisible = true; - dismissLayer.InputPassThroughElement = _overlayInputPassThroughElement; + dismissLayer.InputPassThroughElement = OverlayInputPassThroughElement; Disposable.Create(() => {