Browse Source

Add PopupOverlayLayer (#20751)

* Add PopupOverlayLayer

* Address review

* Move LightDismissOverlayLayer above OverlayLayer
pull/20807/head
Julien Lebosquain 8 months ago
committed by GitHub
parent
commit
7460649a70
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  2. 2
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  3. 32
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  4. 6
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  5. 42
      src/Avalonia.Controls/Primitives/PopupOverlayLayer.cs
  6. 13
      src/Avalonia.Controls/Primitives/TextSelectorLayer.cs
  7. 20
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  8. 2
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

2
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -11,8 +11,6 @@ namespace Avalonia.Controls.Primitives
/// Represents a surface for showing adorners.
/// Adorners are always on top of the adorned element and are positioned to stay relative to the adorned element.
/// </summary>
/// <remarks>
/// </remarks>
public class AdornerLayer : Canvas
{
/// <summary>

2
src/Avalonia.Controls/Primitives/IPopupHost.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives
/// <remarks>
/// A popup host can be either be a popup window created by the operating system
/// (<see cref="PopupRoot"/>) or an <see cref="OverlayPopupHost"/> which is created
/// on an <see cref="OverlayLayer"/>.
/// on an <see cref="PopupOverlayLayer"/>.
/// </remarks>
internal interface IPopupHost : IDisposable, IFocusScope
{

32
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@ -1,20 +1,34 @@
using System.Linq;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
internal class OverlayLayer : Canvas
/// <summary>
/// Represents a surface for showing overlays.
/// Overlays are displayed on top of other elements, but behind popups.
/// </summary>
public class OverlayLayer : Canvas
{
protected override bool BypassFlowDirectionPolicies => true;
public Size AvailableSize { get; private set; }
internal OverlayLayer()
{
}
/// <summary>
/// Retrieves the overlay layer associated with the specified visual, if any.
/// </summary>
/// <param name="visual">The visual for which to retrieve the associated overlay layer.</param>
/// <returns>The <see cref="OverlayLayer"/> associated with the visual, or null if no overlay layer exists.</returns>
public static OverlayLayer? GetOverlayLayer(Visual visual)
{
foreach(var v in visual.GetVisualAncestors())
if(v is VisualLayerManager vlm)
if (vlm.OverlayLayer != null)
return vlm.OverlayLayer;
if (TopLevel.GetTopLevel(visual) is {} tl)
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager { OverlayLayer: { } layer })
return layer;
if (TopLevel.GetTopLevel(visual) is { } tl)
{
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
return layers?.OverlayLayer;
@ -23,13 +37,15 @@ namespace Avalonia.Controls.Primitives
return null;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
foreach (Control child in Children)
foreach (var child in Children)
child.Measure(availableSize);
return availableSize;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
// We are saving it here since child controls might need to know the entire size of the overlay

6
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<Transform?> TransformProperty =
PopupRoot.TransformProperty.AddOwner<OverlayPopupHost>();
private readonly OverlayLayer _overlayLayer;
private readonly PopupOverlayLayer _overlayLayer;
private readonly ManagedPopupPositioner _positioner;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!;
@ -31,7 +31,7 @@ namespace Avalonia.Controls.Primitives
static OverlayPopupHost()
=> KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<OverlayPopupHost>(KeyboardNavigationMode.Cycle);
internal OverlayPopupHost(OverlayLayer overlayLayer)
internal OverlayPopupHost(PopupOverlayLayer overlayLayer)
{
_overlayLayer = overlayLayer;
_positioner = new ManagedPopupPositioner(this);
@ -161,7 +161,7 @@ namespace Avalonia.Controls.Primitives
}
}
if (OverlayLayer.GetOverlayLayer(target) is { } overlayLayer)
if (PopupOverlayLayer.GetPopupOverlayLayer(target) is { } overlayLayer)
{
return new OverlayPopupHost(overlayLayer);
}

42
src/Avalonia.Controls/Primitives/PopupOverlayLayer.cs

@ -0,0 +1,42 @@
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
internal sealed class PopupOverlayLayer : Canvas
{
protected override bool BypassFlowDirectionPolicies => true;
public Size AvailableSize { get; private set; }
public static PopupOverlayLayer? GetPopupOverlayLayer(Visual visual)
{
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager { PopupOverlayLayer: { } layer })
return layer;
if (TopLevel.GetTopLevel(visual) is { } tl)
{
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
return layers?.PopupOverlayLayer;
}
return null;
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children)
child.Measure(availableSize);
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
// We are saving it here since child controls might need to know the entire size of the overlay
// and Bounds won't be updated in time
AvailableSize = finalSize;
return base.ArrangeOverride(finalSize);
}
}
}

13
src/Avalonia.Controls/Primitives/TextSelectorLayer.cs

@ -1,5 +1,4 @@
using System.Linq;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -7,14 +6,16 @@ namespace Avalonia.Controls.Primitives
public class TextSelectorLayer : Canvas
{
protected override bool BypassFlowDirectionPolicies => true;
public Size AvailableSize { get; private set; }
public static TextSelectorLayer? GetTextSelectorLayer(Visual visual)
{
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager vlm)
if (vlm.TextSelectorLayer != null)
return vlm.TextSelectorLayer;
if (visual is TopLevel tl)
if (v is VisualLayerManager { TextSelectorLayer: { } textSelectorLayer })
return textSelectorLayer;
if (TopLevel.GetTopLevel(visual) is { } tl)
{
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
return layers?.TextSelectorLayer;
@ -25,7 +26,7 @@ namespace Avalonia.Controls.Primitives
protected override Size MeasureOverride(Size availableSize)
{
foreach (Control child in Children)
foreach (var child in Children)
child.Measure(availableSize);
return default;
}

20
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -6,9 +6,10 @@ namespace Avalonia.Controls.Primitives
public sealed class VisualLayerManager : Decorator
{
private const int AdornerZIndex = int.MaxValue - 100;
private const int LightDismissOverlayZIndex = int.MaxValue - 98;
private const int OverlayZIndex = int.MaxValue - 97;
private const int TextSelectorLayerZIndex = int.MaxValue - 96;
private const int OverlayZIndex = int.MaxValue - 98;
private const int LightDismissOverlayZIndex = int.MaxValue - 97;
private const int PopupOverlayZIndex = int.MaxValue - 96;
private const int TextSelectorLayerZIndex = int.MaxValue - 95;
private ILogicalRoot? _logicalRoot;
private readonly List<Control> _layers = new();
@ -26,6 +27,19 @@ namespace Avalonia.Controls.Primitives
}
}
internal PopupOverlayLayer? PopupOverlayLayer
{
get
{
if (IsPopup)
return null;
var rv = FindLayer<PopupOverlayLayer>();
if (rv == null)
AddLayer(rv = new PopupOverlayLayer(), PopupOverlayZIndex);
return rv;
}
}
internal OverlayLayer? OverlayLayer
{
get

2
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests
_toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<bool>>(e =>
{
if (e.Sender is Visual visual && TopLevel.GetTopLevel(visual) is {} root)
OverlayLayer.GetOverlayLayer(visual)?.Measure(root.ClientSize);
PopupOverlayLayer.GetPopupOverlayLayer(visual)?.Measure(root.ClientSize);
}));
}

Loading…
Cancel
Save