Browse Source

Use ContextRequested event to show ContextMenu

Max Katz 4 years ago
parent
commit
72c975ac0b

+ 22 - 15
src/Avalonia.Controls/ContextMenu.cs

@@ -1,12 +1,15 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Linq;
+
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
+using Avalonia.Input.Platform;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Styling;
@@ -220,7 +223,7 @@ namespace Avalonia.Controls
 
             if (e.OldValue is ContextMenu oldMenu)
             {
-                control.PointerReleased -= ControlPointerReleased;
+                control.ContextRequested -= ControlContextRequested;
                 oldMenu._attachedControls?.Remove(control);
                 ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null);
             }
@@ -229,7 +232,7 @@ namespace Avalonia.Controls
             {
                 newMenu._attachedControls ??= new List<Control>();
                 newMenu._attachedControls.Add(control);
-                control.PointerReleased += ControlPointerReleased;
+                control.ContextRequested += ControlContextRequested;
             }
         }
 
@@ -330,6 +333,7 @@ namespace Avalonia.Controls
                 _popup.Opened += PopupOpened;
                 _popup.Closed += PopupClosed;
                 _popup.Closing += PopupClosing;
+                _popup.KeyUp += PopupKeyUp;
             }
 
             if (_popup.Parent != control)
@@ -389,25 +393,28 @@ namespace Avalonia.Controls
             });
         }
 
-        private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e)
+        private void PopupKeyUp(object sender, KeyEventArgs e)
         {
-            var control = (Control)sender;
-            var contextMenu = control.ContextMenu;
-
-            if (control.ContextMenu.IsOpen)
+            if (IsOpen)
             {
-                if (contextMenu.CancelClosing())
-                    return;
+                var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
 
-                control.ContextMenu.Close();
-                e.Handled = true;
+                if (keymap.OpenContextMenu.Any(k => k.Matches(e))
+                    && !CancelClosing())
+                {
+                    Close();
+                    e.Handled = true;
+                }
             }
+        }
 
-            if (e.InitialPressMouseButton == MouseButton.Right)
+        private static void ControlContextRequested(object sender, ContextRequestedEventArgs e)
+        {
+            var control = (Control)sender;
+            if (!e.Handled
+                && control.ContextMenu is ContextMenu contextMenu
+                && !contextMenu.CancelOpening())
             {
-                if (contextMenu.CancelOpening())
-                    return;
-
                 contextMenu.Open(control, e.Source as Control ?? control);
                 e.Handled = true;
             }

+ 4 - 2
src/Avalonia.Controls/Control.cs

@@ -231,7 +231,8 @@ namespace Avalonia.Controls
         {
             base.OnPointerReleased(e);
 
-            if (!e.Handled
+            if (e.Source == this
+                && !e.Handled
                 && e.InitialPressMouseButton == MouseButton.Right)
             {
                 var args = new ContextRequestedEventArgs(e);
@@ -244,7 +245,8 @@ namespace Avalonia.Controls
         {
             base.OnKeyUp(e);
 
-            if (!e.Handled)
+            if (e.Source == this
+                && !e.Handled)
             {
                 var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>().OpenContextMenu;
                 var matches = false;

+ 101 - 4
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@@ -1,11 +1,11 @@
 using System;
+
+using Avalonia.Controls.Primitives;
 using Avalonia.Input;
-using Avalonia.LogicalTree;
 using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.MarkupExtensions;
 using Avalonia.Platform;
+using Avalonia.Rendering;
 using Avalonia.UnitTests;
-using Castle.DynamicProxy.Generators;
 using Moq;
 using Xunit;
 
@@ -123,6 +123,90 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
+        {
+            using (Application())
+            {
+                var renderer = new Mock<IRenderer>();
+                var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
+                var windowImpl = Mock.Get(platform.CreateWindow());
+                windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
+
+                var window = new Window(windowImpl.Object);
+                window.Width = 100;
+                window.Height = 100;
+
+                var button = new Button
+                {
+                    Height = 10,
+                    Width = 10,
+                    HorizontalAlignment = Layout.HorizontalAlignment.Left,
+                    VerticalAlignment = Layout.VerticalAlignment.Top
+                };
+                window.Content = button;
+
+                window.ApplyTemplate();
+                window.Show();
+
+                var tracker = 0;
+
+                var c = new ContextMenu();
+                c.ContextMenuClosing += (s, e) =>
+                {
+                    tracker++;
+                    e.Cancel = true;
+                };
+                button.ContextMenu = c;
+                c.Open(button);
+
+                var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
+                var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
+                overlay.RaiseEvent(e);
+
+                Assert.Equal(1, tracker);
+                Assert.True(c.IsOpen);
+            }
+        }
+
+        [Fact]
+        public void Light_Dismiss_Closes_Flyout()
+        {
+            using (Application())
+            {
+                var renderer = new Mock<IRenderer>();
+                var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
+                var windowImpl = Mock.Get(platform.CreateWindow());
+                windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
+
+                var window = new Window(windowImpl.Object);
+                window.Width = 100;
+                window.Height = 100;
+
+                var button = new Button
+                {
+                    Height = 10,
+                    Width = 10,
+                    HorizontalAlignment = Layout.HorizontalAlignment.Left,
+                    VerticalAlignment = Layout.VerticalAlignment.Top
+                };
+                window.Content = button;
+
+                window.ApplyTemplate();
+                window.Show();
+
+                var c = new ContextMenu();
+                c.PlacementMode = PlacementMode.Bottom;
+                c.Open(button);
+
+                var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
+                var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
+                overlay.RaiseEvent(e);
+
+                Assert.False(c.IsOpen);
+            }
+        }
+
         [Fact]
         public void Clicking_On_Control_Toggles_ContextMenu()
         {
@@ -341,7 +425,7 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        [Fact(Skip = "The only reason this test was 'passing' before was that the author forgot to call Window.ApplyTemplate()")]
+        [Fact]
         public void Cancelling_Closing_Leaves_ContextMenuOpen()
         {
             using (Application())
@@ -396,5 +480,18 @@ namespace Avalonia.Controls.UnitTests
 
             return UnitTestApplication.Start(services);
         }
+
+        private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
+        {
+            var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
+            return new PointerPressedEventArgs(
+                source,
+                pointer,
+                source,
+                p,
+                0,
+                new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
+                KeyModifiers.None);
+        }
     }
 }