From f0eafa70d0d7e8b066cf01cccfe5c6c85774ec20 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Jul 2020 20:27:42 +0200 Subject: [PATCH] Added LightDismissOverlayLayer.InputPassThroughElement. Which implements something similar to UWP's `FlyoutBase.OverlayInputPassThroughElement`. With this feature, `Menu`/`MenuItem` can use `IsLightDismissEnabled="True"` fixing #3965 for top-level menus. --- src/Avalonia.Controls/Menu.cs | 17 ++++++++++++++ .../Primitives/LightDismissOverlayLayer.cs | 22 ++++++++++++++++++- src/Avalonia.Themes.Default/MenuItem.xaml | 2 ++ src/Avalonia.Themes.Fluent/MenuItem.xaml | 5 +++-- .../Rendering/ICustomSimpleHitTest.cs | 11 ++++++++++ .../Rendering/SceneGraph/Scene.cs | 6 +++++ 6 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 3733137714..f74daa791d 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; @@ -16,6 +17,8 @@ namespace Avalonia.Controls private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); + private LightDismissOverlayLayer? _overlay; + /// /// Initializes a new instance of the class. /// @@ -45,6 +48,13 @@ namespace Avalonia.Controls return; } + if (_overlay?.InputPassThroughElement == this) + { + _overlay.InputPassThroughElement = null; + } + + _overlay = null; + foreach (var i in ((IMenu)this).SubItems) { i.Close(); @@ -68,6 +78,13 @@ namespace Avalonia.Controls return; } + _overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(this); + + if (_overlay is object) + { + _overlay.InputPassThroughElement = this; + } + IsOpen = true; RaiseEvent(new RoutedEventArgs diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index ede9a9b635..752eedb68a 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.VisualTree; @@ -11,8 +13,10 @@ namespace Avalonia.Controls.Primitives /// /// A layer that is used to dismiss a when the user clicks outside. /// - public class LightDismissOverlayLayer : Border + public class LightDismissOverlayLayer : Border, ICustomHitTest { + public IInputElement? InputPassThroughElement { get; set; } + /// /// Returns the light dismiss overlay for a specified visual. /// @@ -37,5 +41,21 @@ namespace Avalonia.Controls.Primitives return manager?.LightDismissOverlayLayer; } + + public bool HitTest(Point point) + { + if (InputPassThroughElement is object) + { + var p = point.Transform(this.TransformToVisual(VisualRoot)!.Value); + var hit = VisualRoot.GetVisualAt(p, x => x != this); + + if (hit is object) + { + return !InputPassThroughElement.IsVisualAncestorOf(hit); + } + } + + return true; + } } } diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 0eb87166b2..b1f536a704 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -59,6 +59,7 @@ Grid.Column="4"/> + IsLightDismissEnabled="True" + IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"> + /// Allows customization of hit-testing for all renderers. + /// + /// + /// Note that this interface can only used to make a portion of a control non-hittable, it + /// cannot expand the hittable area of a control. + /// + public interface ICustomHitTest : ICustomSimpleHitTest + { + } + public static class CustomSimpleHitTestExtensions { public static bool HitTestCustom(this IVisual visual, Point point) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 0f6001516d..4f5c97cdff 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -304,6 +304,12 @@ namespace Avalonia.Rendering.SceneGraph clipped = !node.GeometryClip.FillContains(controlPoint.Value); } + if (!clipped && node.Visual is ICustomHitTest custom) + { + var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual); + clipped = !custom.HitTest(controlPoint.Value); + } + return !clipped; }