Browse Source

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.
pull/4099/head
Steven Kirk 6 years ago
parent
commit
f0eafa70d0
  1. 17
      src/Avalonia.Controls/Menu.cs
  2. 22
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  3. 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  4. 5
      src/Avalonia.Themes.Fluent/MenuItem.xaml
  5. 11
      src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
  6. 6
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

17
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<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Horizontal });
private LightDismissOverlayLayer? _overlay;
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.
/// </summary>
@ -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

22
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
/// <summary>
/// A layer that is used to dismiss a <see cref="Popup"/> when the user clicks outside.
/// </summary>
public class LightDismissOverlayLayer : Border
public class LightDismissOverlayLayer : Border, ICustomHitTest
{
public IInputElement? InputPassThroughElement { get; set; }
/// <summary>
/// Returns the light dismiss overlay for a specified visual.
/// </summary>
@ -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;
}
}
}

2
src/Avalonia.Themes.Default/MenuItem.xaml

@ -59,6 +59,7 @@
Grid.Column="4"/>
<Popup Name="PART_Popup"
PlacementMode="Right"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
@ -107,6 +108,7 @@
</ContentPresenter.DataTemplates>
</ContentPresenter>
<Popup Name="PART_Popup"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"

5
src/Avalonia.Themes.Fluent/MenuItem.xaml

@ -108,8 +108,8 @@
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="True"
PlacementMode="Right"
IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}">
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
BorderThickness="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}"
@ -154,6 +154,7 @@
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"

11
src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs

@ -14,6 +14,17 @@ namespace Avalonia.Rendering
bool HitTest(Point point);
}
/// <summary>
/// Allows customization of hit-testing for all renderers.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public interface ICustomHitTest : ICustomSimpleHitTest
{
}
public static class CustomSimpleHitTestExtensions
{
public static bool HitTestCustom(this IVisual visual, Point point)

6
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;
}

Loading…
Cancel
Save