diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 8e6173a6cb..63e8234919 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -2269,6 +2269,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer
@@ -2287,6 +2293,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)
@@ -3871,6 +3883,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.get_IsPopup
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Primitives.VisualLayerManager.get_LightDismissOverlayLayer
@@ -3889,6 +3907,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Primitives.VisualLayerManager.set_IsPopup(System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Screens.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index d4e5488019..59718c3e3f 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -4,6 +4,7 @@ using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Platform;
+using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Platform;
@@ -54,6 +55,12 @@ namespace Avalonia.Controls.Embedding
protected override Type StyleKeyOverride => typeof(EmbeddableControlRoot);
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ EnableVisualLayerManagerLayers();
+ }
+
protected override AutomationPeer OnCreateAutomationPeer()
{
return new EmbeddableControlRootAutomationPeer(this);
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 8d3d97b94f..1c8b24f627 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Specialized;
+using System.Linq;
using Avalonia.Input.TextInput;
using Avalonia.Media;
using Avalonia.Reactive;
@@ -71,7 +72,32 @@ namespace Avalonia.Controls.Primitives
public static AdornerLayer? GetAdornerLayer(Visual visual)
{
- return visual.FindAncestorOfType()?.AdornerLayer;
+ // Check if the visual is inside an OverlayLayer with a dedicated AdornerLayer
+ foreach (var ancestor in visual.GetVisualAncestors())
+ {
+ if (GetDirectAdornerLayer(ancestor) is { } adornerLayer)
+ return adornerLayer;
+ }
+
+ if (TopLevel.GetTopLevel(visual) is { } topLevel)
+ {
+ foreach (var descendant in topLevel.GetVisualDescendants())
+ {
+ if (GetDirectAdornerLayer(descendant) is { } adornerLayer)
+ return adornerLayer;
+ }
+ }
+
+ return null;
+
+ static AdornerLayer? GetDirectAdornerLayer(Visual visual)
+ {
+ if (visual is OverlayLayer { AdornerLayer: { } adornerLayer })
+ return adornerLayer;
+ if (visual is VisualLayerManager vlm)
+ return vlm.AdornerLayer;
+ return null;
+ }
}
public static bool GetIsClipEnabled(Visual adorner)
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 3337288a13..a9d9b072f2 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -13,6 +13,11 @@ namespace Avalonia.Controls.Primitives
public Size AvailableSize { get; private set; }
+ ///
+ /// Gets the dedicated adorner layer for this overlay layer.
+ ///
+ internal AdornerLayer? AdornerLayer { get; set; }
+
internal OverlayLayer()
{
}
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
index 6630f1e09c..eb912d2cf8 100644
--- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -3,6 +3,9 @@ using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
+ ///
+ /// A control that manages multiple layers such as adorners, overlays, text selectors, and popups.
+ ///
public sealed class VisualLayerManager : Decorator
{
private const int AdornerZIndex = int.MaxValue - 100;
@@ -13,13 +16,37 @@ namespace Avalonia.Controls.Primitives
private ILogicalRoot? _logicalRoot;
private readonly List _layers = new();
-
- public bool IsPopup { get; set; }
-
- internal AdornerLayer AdornerLayer
+ private OverlayLayer? _overlayLayer;
+
+ ///
+ /// Gets or sets a value indicating whether an is
+ /// created for this . When enabled, the adorner layer is added to the
+ /// visual tree, providing a dedicated layer for rendering adorners.
+ ///
+ public bool EnableAdornerLayer { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether an is
+ /// created for this . When enabled, the overlay layer is added to the
+ /// visual tree, providing a dedicated layer for rendering overlay visuals.
+ ///
+ public bool EnableOverlayLayer { get; set; }
+
+ internal bool EnablePopupOverlayLayer { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a is
+ /// created for this . When enabled, the overlay layer is added to the
+ /// visual tree, providing a dedicated layer for rendering text selection handles.
+ ///
+ public bool EnableTextSelectorLayer { get; set; }
+
+ internal AdornerLayer? AdornerLayer
{
get
{
+ if (!EnableAdornerLayer)
+ return null;
var rv = FindLayer();
if (rv == null)
AddLayer(rv = new AdornerLayer(), AdornerZIndex);
@@ -31,7 +58,7 @@ namespace Avalonia.Controls.Primitives
{
get
{
- if (IsPopup)
+ if (!EnablePopupOverlayLayer)
return null;
var rv = FindLayer();
if (rv == null)
@@ -44,12 +71,21 @@ namespace Avalonia.Controls.Primitives
{
get
{
- if (IsPopup)
+ if (!EnableOverlayLayer)
return null;
- var rv = FindLayer();
- if (rv == null)
- AddLayer(rv = new OverlayLayer(), OverlayZIndex);
- return rv;
+ if (_overlayLayer == null)
+ {
+ _overlayLayer = new OverlayLayer();
+ var adorner = new AdornerLayer();
+ _overlayLayer.AdornerLayer = adorner;
+
+ var panel = new Panel();
+ panel.Children.Add(_overlayLayer);
+ panel.Children.Add(adorner);
+
+ AddLayer(panel, OverlayZIndex);
+ }
+ return _overlayLayer;
}
}
@@ -57,7 +93,7 @@ namespace Avalonia.Controls.Primitives
{
get
{
- if (IsPopup)
+ if (!EnableTextSelectorLayer)
return null;
var rv = FindLayer();
if (rv == null)
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 8556d03d91..ceb9590564 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -38,6 +38,7 @@ namespace Avalonia.Controls
/// tracking the widget's .
///
[TemplatePart("PART_TransparencyFallback", typeof(Border))]
+ [TemplatePart("PART_VisualLayerManager", typeof(VisualLayerManager))]
public abstract class TopLevel : ContentControl,
ICloseable,
IStyleHost,
@@ -125,6 +126,7 @@ namespace Avalonia.Controls
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel;
private Border? _transparencyFallbackBorder;
+ private VisualLayerManager? _visualLayerManager;
private TargetWeakEventSubscriber? _resourcesChangesSubscriber;
private IStorageProvider? _storageProvider;
private Screens? _screens;
@@ -133,6 +135,18 @@ namespace Avalonia.Controls
internal TopLevelHost TopLevelHost => _topLevelHost;
internal new PresentationSource PresentationSource => _source;
internal IInputRoot InputRoot => _source;
+
+ private protected VisualLayerManager? VisualLayerManager => _visualLayerManager;
+
+ private protected void EnableVisualLayerManagerLayers()
+ {
+ if (_visualLayerManager is { } vlm)
+ {
+ vlm.EnableOverlayLayer = true;
+ vlm.EnablePopupOverlayLayer = true;
+ vlm.EnableTextSelectorLayer = true;
+ }
+ }
///
/// Initializes static members of the class.
@@ -723,6 +737,8 @@ namespace Avalonia.Controls
{
base.OnApplyTemplate(e);
+ _visualLayerManager = e.NameScope.Find("PART_VisualLayerManager");
+
if (PlatformImpl is null)
return;
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index db3ec6a077..e7a4ce953e 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform;
+using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
@@ -795,6 +796,12 @@ namespace Avalonia.Controls
ShowCore