diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index e96df62f2f..8d3d97b94f 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/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. /// - /// - /// public class AdornerLayer : Canvas { /// diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index 4d3659c96d..3e9967f235 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives /// /// A popup host can be either be a popup window created by the operating system /// () or an which is created - /// on an . + /// on an . /// internal interface IPopupHost : IDisposable, IFocusScope { diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs index 6b1c611949..3337288a13 100644 --- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs +++ b/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 + /// + /// Represents a surface for showing overlays. + /// Overlays are displayed on top of other elements, but behind popups. + /// + public class OverlayLayer : Canvas { protected override bool BypassFlowDirectionPolicies => true; + public Size AvailableSize { get; private set; } + + internal OverlayLayer() + { + } + + /// + /// Retrieves the overlay layer associated with the specified visual, if any. + /// + /// The visual for which to retrieve the associated overlay layer. + /// The associated with the visual, or null if no overlay layer exists. 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().FirstOrDefault(); return layers?.OverlayLayer; @@ -23,13 +37,15 @@ namespace Avalonia.Controls.Primitives return null; } + /// protected override Size MeasureOverride(Size availableSize) { - foreach (Control child in Children) + 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 diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index daab1d8539..4e46406dd3 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TransformProperty = PopupRoot.TransformProperty.AddOwner(); - 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(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); } diff --git a/src/Avalonia.Controls/Primitives/PopupOverlayLayer.cs b/src/Avalonia.Controls/Primitives/PopupOverlayLayer.cs new file mode 100644 index 0000000000..4ca54e3d8f --- /dev/null +++ b/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().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); + } + } +} diff --git a/src/Avalonia.Controls/Primitives/TextSelectorLayer.cs b/src/Avalonia.Controls/Primitives/TextSelectorLayer.cs index fad7718917..d6b080e588 100644 --- a/src/Avalonia.Controls/Primitives/TextSelectorLayer.cs +++ b/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().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; } diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 932cd033a3..6630f1e09c 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/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 _layers = new(); @@ -26,6 +27,19 @@ namespace Avalonia.Controls.Primitives } } + internal PopupOverlayLayer? PopupOverlayLayer + { + get + { + if (IsPopup) + return null; + var rv = FindLayer(); + if (rv == null) + AddLayer(rv = new PopupOverlayLayer(), PopupOverlayZIndex); + return rv; + } + } + internal OverlayLayer? OverlayLayer { get diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 8672013b81..ad0662311b 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests _toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver>(e => { if (e.Sender is Visual visual && TopLevel.GetTopLevel(visual) is {} root) - OverlayLayer.GetOverlayLayer(visual)?.Measure(root.ClientSize); + PopupOverlayLayer.GetPopupOverlayLayer(visual)?.Measure(root.ClientSize); })); }