From 72c975ac0b44d745f529c8b2a5ff8b44deec1a30 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 13 Jun 2021 02:43:51 -0400 Subject: [PATCH] Use ContextRequested event to show ContextMenu --- src/Avalonia.Controls/ContextMenu.cs | 37 +++--- src/Avalonia.Controls/Control.cs | 6 +- .../ContextMenuTests.cs | 105 +++++++++++++++++- 3 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index ca55fd0bc7..242df8e357 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/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(); 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(); - 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; } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 845efd3279..fe731b9d4b 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/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().OpenContextMenu; var matches = false; diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index f3a1316c7d..9490f27f47 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/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(); + var platform = AvaloniaLocator.Current.GetService(); + var windowImpl = Mock.Get(platform.CreateWindow()); + windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).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(); + var platform = AvaloniaLocator.Current.GetService(); + var windowImpl = Mock.Get(platform.CreateWindow()); + windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).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); + } } }