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(null, false); } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + EnableVisualLayerManagerLayers(); + } + protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { if (!IgnoreVisibilityChanges) diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml index 1fc931db36..2ea83ec6a9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml @@ -12,7 +12,7 @@ - + - + - + - + - + - + - + - +