Browse Source

Merge pull request #8448 from pr8x/fix-button-flyout

Fix `Button.Flyout` not toggling
Max Katz 3 năm trước cách đây
mục cha
commit
2911b37fe0

+ 38 - 18
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
         /// <inheritdoc/>
         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);
         }
 
+        /// <summary>
+        /// Closes the button's flyout.
+        /// </summary>
+        protected virtual void CloseFlyout()
+        {
+            Flyout?.Hide();
+        }
+
         /// <summary>
         /// Invoked when the button's flyout is opened.
         /// </summary>
@@ -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();
                 }

+ 41 - 16
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);
-        }
-
         /// <summary>
         /// Defines the <see cref="IsOpen"/> property
         /// </summary>
         public static readonly DirectProperty<FlyoutBase, bool> IsOpenProperty =
-           AvaloniaProperty.RegisterDirect<FlyoutBase, bool>(nameof(IsOpen),
-               x => x.IsOpen);
+            AvaloniaProperty.RegisterDirect<FlyoutBase, bool>(nameof(IsOpen),
+                x => x.IsOpen);
 
         /// <summary>
         /// Defines the <see cref="Target"/> property
@@ -43,6 +38,14 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.RegisterDirect<FlyoutBase, FlyoutShowMode>(nameof(ShowMode),
                 x => x.ShowMode, (x, v) => x.ShowMode = v);
 
+        /// <summary>
+        /// Defines the <see cref="OverlayInputPassThroughElement"/> property
+        /// </summary>
+        public static readonly DirectProperty<FlyoutBase, IInputElement?> OverlayInputPassThroughElementProperty =
+            Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>(
+                o => o._overlayInputPassThroughElement,
+                (o, v) => o._overlayInputPassThroughElement = v);
+
         /// <summary>
         /// Defines the AttachedFlyout property
         /// </summary>
@@ -57,6 +60,12 @@ namespace Avalonia.Controls.Primitives
         private PixelRect? _enlargePopupRectScreenPixelRect;
         private IDisposable? _transientDisposable;
         private Action<IPopupHost?>? _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);
         }
 
+        /// <summary>
+        /// Gets or sets an element that should receive pointer input events even when underneath
+        /// the flyout's overlay.
+        /// </summary>
+        public IInputElement? OverlayInputPassThroughElement
+        {
+            get => _overlayInputPassThroughElement;
+            set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);
+        }
+
         IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
 
-        event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged 
-        { 
-            add => _popupHostChangedHandler += value; 
+        event Action<IPopupHost?>? 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)

+ 1 - 1
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(() =>
                     {