diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index f32b8fabc6..a70d26624c 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -202,7 +202,7 @@ namespace Avalonia.Controls
{
if (!e.Handled)
{
- if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
+ if (_popup?.IsInsidePopup((IVisual)e.Source) == true)
{
if (UpdateSelectionFromEventSource(e.Source))
{
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index bd558af5ef..38cc3f6daf 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -224,7 +224,7 @@ namespace Avalonia.Controls
public bool IsTopLevel => Parent is Menu;
///
- bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false;
+ bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
///
IMenuElement IMenuItem.Parent => Parent as IMenuElement;
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 93873cbf7d..aa91224572 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -150,7 +150,7 @@ namespace Avalonia.Controls.Notifications
private void Install(Window host)
{
var adornerLayer = host.GetVisualDescendants()
- .OfType()
+ .OfType()
.FirstOrDefault()
?.AdornerLayer;
diff --git a/src/Avalonia.Controls/Primitives/AdornerDecorator.cs b/src/Avalonia.Controls/Primitives/AdornerDecorator.cs
deleted file mode 100644
index 4608d64806..0000000000
--- a/src/Avalonia.Controls/Primitives/AdornerDecorator.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Avalonia.LogicalTree;
-
-namespace Avalonia.Controls.Primitives
-{
- public class AdornerDecorator : Decorator
- {
- public AdornerDecorator()
- {
- AdornerLayer = new AdornerLayer();
- ((ISetLogicalParent)AdornerLayer).SetParent(this);
- AdornerLayer.ZIndex = int.MaxValue;
- VisualChildren.Add(AdornerLayer);
- }
-
- protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- base.OnAttachedToLogicalTree(e);
-
- ((ILogical)AdornerLayer).NotifyAttachedToLogicalTree(e);
- }
-
- public AdornerLayer AdornerLayer
- {
- get;
- }
-
- protected override Size MeasureOverride(Size availableSize)
- {
- AdornerLayer.Measure(availableSize);
- return base.MeasureOverride(availableSize);
- }
-
- protected override Size ArrangeOverride(Size finalSize)
- {
- AdornerLayer.Arrange(new Rect(finalSize));
- return base.ArrangeOverride(finalSize);
- }
- }
-}
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index d198570909..ebe5e0a93e 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
public static AdornerLayer GetAdornerLayer(IVisual visual)
{
return visual.GetVisualAncestors()
- .OfType()
+ .OfType()
.FirstOrDefault()
?.AdornerLayer;
}
diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs
new file mode 100644
index 0000000000..ca0f723893
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs
@@ -0,0 +1,22 @@
+using System;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+ public interface IPopupHost : IDisposable
+ {
+ object Content { get; set; }
+ IVisual VisualRoot { get; }
+
+ void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
+ PopupPositioningEdge anchor = PopupPositioningEdge.None,
+ PopupPositioningEdge gravity = PopupPositioningEdge.None);
+ void Show();
+ void Hide();
+ IDisposable BindConstraints(AvaloniaObject popup, StyledProperty widthProperty,
+ StyledProperty minWidthProperty, StyledProperty maxWidthProperty,
+ StyledProperty heightProperty, StyledProperty minHeightProperty,
+ StyledProperty maxHeightProperty, StyledProperty topmostProperty);
+ }
+}
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
new file mode 100644
index 0000000000..32dcf9f797
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -0,0 +1,145 @@
+using System.Linq;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+ public class OverlayLayer : Control
+ {
+ ///
+ /// Defines the Left attached property.
+ ///
+ public static readonly AttachedProperty LeftProperty =
+ AvaloniaProperty.RegisterAttached("Left", 0);
+
+ ///
+ /// Defines the Top attached property.
+ ///
+ public static readonly AttachedProperty TopProperty =
+ AvaloniaProperty.RegisterAttached("Top", 0);
+
+ ///
+ /// Defines the InfiniteAvailableSize attached property.
+ ///
+ public static readonly AttachedProperty InfiniteAvailableSizeProperty =
+ AvaloniaProperty.RegisterAttached("InfiniteAvailableSize", false);
+
+
+ static OverlayLayer()
+ {
+ foreach (var p in new []{LeftProperty, TopProperty})
+ {
+ p.Changed.AddClassHandler((target, e) =>
+ {
+ if (target.GetVisualParent() is OverlayLayer layer)
+ layer.InvalidateArrange();
+ });
+ }
+ }
+
+ public Size AvailableSize { get; private set; }
+
+ ///
+ /// Gets the value of the Left attached property for a control.
+ ///
+ /// The control.
+ /// The control's left coordinate.
+ public static double GetLeft(AvaloniaObject element)
+ {
+ return element.GetValue(LeftProperty);
+ }
+
+ ///
+ /// Sets the value of the Left attached property for a control.
+ ///
+ /// The control.
+ /// The left value.
+ public static void SetLeft(AvaloniaObject element, double value)
+ {
+ element.SetValue(LeftProperty, value);
+ }
+
+ ///
+ /// Gets the value of the Top attached property for a control.
+ ///
+ /// The control.
+ /// The control's top coordinate.
+ public static double GetTop(AvaloniaObject element)
+ {
+ return element.GetValue(TopProperty);
+ }
+
+ ///
+ /// Sets the value of the Top attached property for a control.
+ ///
+ /// The control.
+ /// The top value.
+ public static void SetTop(AvaloniaObject element, double value)
+ {
+ element.SetValue(TopProperty, value);
+ }
+
+ ///
+ /// Gets the value of the Top attached property for a control.
+ ///
+ /// The control.
+ /// The control's top coordinate.
+ public static bool GetInfiniteAvailableSize(AvaloniaObject element)
+ {
+ return element.GetValue(InfiniteAvailableSizeProperty);
+ }
+
+ ///
+ /// Sets the value of the Top attached property for a control.
+ ///
+ /// The control.
+ /// The top value.
+ public static void SetInfiniteAvailableSize(AvaloniaObject element, bool value)
+ {
+ element.SetValue(InfiniteAvailableSizeProperty, value);
+ }
+
+
+ public static OverlayLayer GetOverlayLayer(IVisual visual)
+ {
+ foreach(var v in visual.GetVisualAncestors())
+ if(v is VisualLayerManager vlm)
+ if (vlm.OverlayLayer != null)
+ return vlm.OverlayLayer;
+ if (visual is TopLevel tl)
+ {
+ var layers = tl.GetVisualDescendants().OfType().FirstOrDefault();
+ return layers?.OverlayLayer;
+ }
+
+ return null;
+ }
+
+ public void Add(Control v)
+ {
+ VisualChildren.Add(v);
+ InvalidateArrange();
+ }
+
+ public void Remove(Control v) => VisualChildren.Remove(v);
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+
+ var infinite = new Size(double.PositiveInfinity, double.PositiveInfinity);
+ foreach (Control v in VisualChildren)
+ v.Measure(GetInfiniteAvailableSize(v) ? infinite : availableSize);
+
+ return new Size();
+ }
+
+ 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;
+ foreach (Control v in VisualChildren)
+ v.Arrange(new Rect(GetLeft(v), GetTop(v), v.DesiredSize.Width, v.DesiredSize.Height));
+ return finalSize;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 201566831d..dc92347c9d 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -79,7 +79,7 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register(nameof(Topmost));
private bool _isOpen;
- private PopupRoot _popupRoot;
+ private IPopupHost _popupRoot;
private TopLevel _topLevel;
private IDisposable _nonClientListener;
bool _ignoreIsOpenChanged = false;
@@ -94,7 +94,6 @@ namespace Avalonia.Controls.Primitives
IsHitTestVisibleProperty.OverrideDefaultValue(false);
ChildProperty.Changed.AddClassHandler(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler(x => x.IsOpenChanged);
- TopmostProperty.Changed.AddClassHandler((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
}
public Popup()
@@ -112,10 +111,7 @@ namespace Avalonia.Controls.Primitives
///
public event EventHandler Opened;
- ///
- /// Raised when the popup root has been created, but before it has been shown.
- ///
- public event EventHandler PopupRootCreated;
+ public IPopupHost Host => _popupRoot;
///
/// Gets or sets the control to display in the popup.
@@ -192,11 +188,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementTargetProperty, value); }
}
- ///
- /// Gets the root of the popup window.
- ///
- public PopupRoot PopupRoot => _popupRoot;
-
///
/// Gets or sets a value indicating whether the popup should stay open when the popup is
/// pressed or loses focus.
@@ -219,7 +210,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the root of the popup window.
///
- IVisual IVisualTreeHost.Root => _popupRoot;
+ IVisual IVisualTreeHost.Root => _popupRoot?.VisualRoot;
///
/// Opens the popup.
@@ -242,28 +233,13 @@ namespace Avalonia.Controls.Primitives
"Attempted to open a popup not attached to a TopLevel");
}
+ _popupRoot = PopupHost.CreatePopupHost(placementTarget, DependencyResolver);
- _popupRoot = new PopupRoot(_topLevel, DependencyResolver)
- {
- [~WidthProperty] = this[~WidthProperty],
- [~HeightProperty] = this[~HeightProperty],
- [~MinWidthProperty] = this[~MinWidthProperty],
- [~MaxWidthProperty] = this[~MaxWidthProperty],
- [~MinHeightProperty] = this[~MinHeightProperty],
- [~MaxHeightProperty] = this[~MaxHeightProperty],
- };
-
- void Bind(AvaloniaProperty prop) => _bindings.Add(_popupRoot.Bind(prop, this[~prop]));
-
- Bind(WidthProperty);
- Bind(MinWidthProperty);
- Bind(MaxWidthProperty);
- Bind(HeightProperty);
- Bind(MinHeightProperty);
- Bind(MaxHeightProperty);
- _decorator.Bind(PopupContentHost.ChildProperty, this[~ChildProperty]);
-
- _popupRoot.Content = _decorator;
+ _bindings.Add(_popupRoot.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
+ HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty));
+ _bindings.Add(_decorator.Bind(PopupContentHost.ChildProperty, this[~ChildProperty]));
+
+ _popupRoot.SetContent(_decorator);
((ISetLogicalParent)_popupRoot).SetParent(this);
@@ -287,7 +263,6 @@ namespace Avalonia.Controls.Primitives
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick);
- PopupRootCreated?.Invoke(this, EventArgs.Empty);
_popupRoot.Show();
@@ -338,7 +313,7 @@ namespace Avalonia.Controls.Primitives
foreach(var b in _bindings)
b.Dispose();
_bindings.Clear();
- _popupRoot.Content = null;
+ _popupRoot.SetContent(null);
_popupRoot.Hide();
((ISetLogicalParent)_popupRoot).SetParent(null);
_popupRoot.Dispose();
@@ -425,14 +400,15 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child)
{
- IVisual root = child.GetVisualRoot();
- while (root is PopupRoot)
- {
- if (root == PopupRoot) return true;
- root = ((PopupRoot)root).Parent.GetVisualRoot();
- }
- return false;
+ return _decorator.FindCommonVisualAncestor(child) == _decorator;
}
+
+ public bool IsInsidePopup(IVisual visual)
+ {
+ return _decorator.FindCommonVisualAncestor(visual) == _decorator;
+ }
+
+ public bool IsPointerOverPopup => _decorator.IsPointerOver;
private void WindowDeactivated(object sender, EventArgs e)
{
diff --git a/src/Avalonia.Controls/Primitives/PopupHost.cs b/src/Avalonia.Controls/Primitives/PopupHost.cs
new file mode 100644
index 0000000000..3d6809c061
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/PopupHost.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+ public class PopupHost : Control, IPopupHost, IInteractive, IManagedPopupPositionerPopup
+ {
+ private readonly OverlayLayer _overlayLayer;
+ private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
+ private ManagedPopupPositioner _positioner;
+ private bool _shown;
+ private IControl _content;
+
+ public PopupHost(OverlayLayer overlayLayer)
+ {
+ _overlayLayer = overlayLayer;
+ _positioner = new ManagedPopupPositioner(this);
+ }
+
+ public void SetContent(IControl control)
+ {
+ if (_content == control)
+ return;
+ if (_content != null)
+ VisualChildren.Remove(_content);
+ _content = control;
+ if (_content != null)
+ VisualChildren.Add(_content);
+ }
+
+ public IVisual VisualRoot => null;
+
+ ///
+ IInteractive IInteractive.InteractiveParent => Parent;
+
+ public void Dispose() => Hide();
+
+
+ public void Show()
+ {
+ _overlayLayer.Add(this);
+ _shown = true;
+ }
+
+ public void Hide()
+ {
+ _overlayLayer.Remove(this);
+ _shown = false;
+ }
+
+ public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty widthProperty, StyledProperty minWidthProperty,
+ StyledProperty maxWidthProperty, StyledProperty heightProperty, StyledProperty minHeightProperty,
+ StyledProperty maxHeightProperty, StyledProperty topmostProperty)
+ {
+ // Topmost property is not supported
+ var bindings = new List();
+
+ void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
+ Bind(WidthProperty, widthProperty);
+ Bind(MinWidthProperty, minWidthProperty);
+ Bind(MaxWidthProperty, maxWidthProperty);
+ Bind(HeightProperty, heightProperty);
+ Bind(MinHeightProperty, minHeightProperty);
+ Bind(MaxHeightProperty, maxHeightProperty);
+
+ return Disposable.Create(() =>
+ {
+ foreach (var x in bindings)
+ x.Dispose();
+ });
+ }
+
+ public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
+ PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None)
+ {
+ _positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot(), target, placement, offset, anchor,
+ gravity);
+ UpdatePosition();
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ if (_positionerParameters.Size != finalSize)
+ {
+ _positionerParameters.Size = finalSize;
+ UpdatePosition();
+ }
+ return base.ArrangeOverride(finalSize);
+ }
+
+
+ void UpdatePosition()
+ {
+ // Don't bother the positioner with layout system artifacts
+ if (_positionerParameters.Size.Width == 0 || _positionerParameters.Size.Height == 0)
+ return;
+ if (_shown)
+ {
+ _positioner.Update(_positionerParameters);
+ }
+ }
+
+ IReadOnlyList IManagedPopupPositionerPopup.Screens
+ {
+ get
+ {
+ var rc = new Rect(default, _overlayLayer.AvailableSize);
+ return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)};
+ }
+ }
+
+ Rect IManagedPopupPositionerPopup.ParentClientAreaScreenGeometry =>
+ new Rect(default, _overlayLayer.Bounds.Size);
+
+ private Point _lastRequestedPosition;
+ void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize)
+ {
+ _lastRequestedPosition = devicePoint;
+ Dispatcher.UIThread.Post(() =>
+ {
+ OverlayLayer.SetLeft(this, _lastRequestedPosition.X);
+ OverlayLayer.SetTop(this, _lastRequestedPosition.Y);
+ }, DispatcherPriority.Layout);
+ }
+
+ Point IManagedPopupPositionerPopup.TranslatePoint(Point pt) => pt;
+
+ Size IManagedPopupPositionerPopup.TranslateSize(Size size) => size;
+
+ public static IPopupHost CreatePopupHost(IVisual target, IAvaloniaDependencyResolver dependencyResolver)
+ {
+ var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup();
+ if (platform != null)
+ return new PopupRoot((TopLevel)target.GetVisualRoot(), platform, dependencyResolver);
+ {
+ var overlayLayer = OverlayLayer.GetOverlayLayer(target);
+ if (overlayLayer == null)
+ throw new InvalidOperationException(
+ "Unable to create IPopupImpl and no overlay layer is found for the target control");
+
+
+ return new PopupHost(overlayLayer);
+ }
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size));
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index af78483b7f..3010a3d8a8 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -45,6 +45,7 @@ Copyright © 2019 Nikita Tsukanov
*/
using System;
+using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
@@ -290,5 +291,68 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
void Update(PopupPositionerParameters parameters);
}
+ static class PopupPositionerExtensions
+ {
+ public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters,
+ TopLevel topLevel,
+ IVisual target, PlacementMode placement, Point offset,
+ PopupPositioningEdge anchor, PopupPositioningEdge gravity)
+ {
+ // We need a better way for tracking the last pointer position
+ var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position);
+
+ positionerParameters.Offset = offset;
+ positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All;
+ if (placement == PlacementMode.Pointer)
+ {
+ positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
+ positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
+ positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+ }
+ else
+ {
+ if (target == null)
+ throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
+ var matrix = target.TransformToVisual(topLevel);
+ if (matrix == null)
+ {
+ if (target.GetVisualRoot() == null)
+ throw new InvalidCastException("Target control is not attached to the visual tree");
+ throw new InvalidCastException("Target control is not in the same tree as the popup parent");
+ }
+
+ positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size)
+ .TransformToAABB(matrix.Value);
+
+ if (placement == PlacementMode.Right)
+ {
+ positionerParameters.Anchor = PopupPositioningEdge.TopRight;
+ positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+ }
+ else if (placement == PlacementMode.Bottom)
+ {
+ positionerParameters.Anchor = PopupPositioningEdge.BottomLeft;
+ positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
+ }
+ else if (placement == PlacementMode.Left)
+ {
+ positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
+ positionerParameters.Gravity = PopupPositioningEdge.BottomLeft;
+ }
+ else if (placement == PlacementMode.Top)
+ {
+ positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
+ positionerParameters.Gravity = PopupPositioningEdge.TopRight;
+ }
+ else if (placement == PlacementMode.AnchorAndGravity)
+ {
+ positionerParameters.Anchor = anchor;
+ positionerParameters.Gravity = gravity;
+ }
+ else
+ throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
+ }
+ }
+ }
}
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 0437d4a550..36595f69c4 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Disposables;
using System.Text;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
@@ -21,7 +23,7 @@ namespace Avalonia.Controls.Primitives
///
/// The root window of a .
///
- public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
+ public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
{
private readonly TopLevel _parent;
private IDisposable _presenterSubscription;
@@ -38,8 +40,8 @@ namespace Avalonia.Controls.Primitives
///
/// Initializes a new instance of the class.
///
- public PopupRoot(TopLevel parent)
- : this(parent, null)
+ public PopupRoot(TopLevel parent, IPopupImpl impl)
+ : this(parent, impl,null)
{
}
@@ -49,8 +51,8 @@ namespace Avalonia.Controls.Primitives
///
/// The dependency resolver to use. If null the default dependency resolver will be used.
///
- public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver)
- : base(parent.PlatformImpl.CreatePopup(), dependencyResolver)
+ public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver dependencyResolver)
+ : base(impl, dependencyResolver)
{
_parent = parent;
}
@@ -133,65 +135,36 @@ namespace Avalonia.Controls.Primitives
PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None)
{
- // We need a better way for tracking the last pointer position
- var pointer = _parent.PointToClient(_parent.PlatformImpl.MouseDevice.Position);
-
- _positionerParameters.Offset = offset;
- _positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All;
- if (placement == PlacementMode.Pointer)
- {
- _positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
- _positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
- _positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
- }
- else
- {
- if (target == null)
- throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
- var matrix = target.TransformToVisual(_parent);
- if (matrix == null)
- {
- if (target.GetVisualRoot() == null)
- throw new InvalidCastException("Target control is not attached to the visual tree");
- throw new InvalidCastException("Target control is not in the same tree as the popup parent");
- }
-
- _positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size)
- .TransformToAABB(matrix.Value);
-
- if (placement == PlacementMode.Right)
- {
- _positionerParameters.Anchor = PopupPositioningEdge.TopRight;
- _positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
- }
- else if (placement == PlacementMode.Bottom)
- {
- _positionerParameters.Anchor = PopupPositioningEdge.BottomLeft;
- _positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
- }
- else if (placement == PlacementMode.Left)
- {
- _positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
- _positionerParameters.Gravity = PopupPositioningEdge.BottomLeft;
- }
- else if (placement == PlacementMode.Top)
- {
- _positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
- _positionerParameters.Gravity = PopupPositioningEdge.TopRight;
- }
- else if (placement == PlacementMode.AnchorAndGravity)
- {
- _positionerParameters.Anchor = anchor;
- _positionerParameters.Gravity = gravity;
- }
- else
- throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
- }
+ _positionerParameters.ConfigurePosition(_parent, target,
+ placement, offset, anchor, gravity);
if (_positionerParameters.Size != default)
UpdatePosition();
}
+
+ IVisual IPopupHost.VisualRoot => this;
+ public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty widthProperty, StyledProperty minWidthProperty,
+ StyledProperty maxWidthProperty, StyledProperty heightProperty, StyledProperty minHeightProperty,
+ StyledProperty maxHeightProperty, StyledProperty topmostProperty)
+ {
+ var bindings = new List();
+
+ void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
+ Bind(WidthProperty, widthProperty);
+ Bind(MinWidthProperty, minWidthProperty);
+ Bind(MaxWidthProperty, maxWidthProperty);
+ Bind(HeightProperty, heightProperty);
+ Bind(MinHeightProperty, minHeightProperty);
+ Bind(MaxHeightProperty, maxHeightProperty);
+ Bind(TopmostProperty, topmostProperty);
+ return Disposable.Create(() =>
+ {
+ foreach (var x in bindings)
+ x.Dispose();
+ });
+ }
+
///
/// Carries out the arrange pass of the window.
///
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
new file mode 100644
index 0000000000..7354f2788f
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using Avalonia.LogicalTree;
+using Avalonia.Styling;
+
+namespace Avalonia.Controls.Primitives
+{
+ public class VisualLayerManager : Decorator
+ {
+ private const int AdornerZIndex = int.MaxValue - 100;
+ private const int OverlayZIndex = int.MaxValue - 99;
+
+ private bool _isAttachedToLogicalTree;
+ private IStyleHost _styleHost;
+ public bool IsPopup { get; set; }
+
+ List _layers = new List();
+
+
+ public AdornerLayer AdornerLayer
+ {
+ get
+ {
+ var rv = FindLayer();
+ if (rv == null)
+ AddLayer(rv = new AdornerLayer(), AdornerZIndex);
+ return rv;
+ }
+ }
+
+ public OverlayLayer OverlayLayer
+ {
+ get
+ {
+ if (IsPopup)
+ return null;
+ var rv = FindLayer();
+ if(rv == null)
+ AddLayer(rv = new OverlayLayer(), OverlayZIndex);
+ return rv;
+ }
+ }
+
+ T FindLayer() where T : class
+ {
+ foreach (var layer in _layers)
+ if (layer is T match)
+ return match;
+ return null;
+ }
+
+ void AddLayer(Control layer, int zindex)
+ {
+ _layers.Add(layer);
+ ((ISetLogicalParent)layer).SetParent(this);
+ layer.ZIndex = zindex;
+ VisualChildren.Add(layer);
+ if (_isAttachedToLogicalTree)
+ ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleHost));
+ InvalidateArrange();
+ }
+
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ _isAttachedToLogicalTree = true;
+ _styleHost = e.Root;
+
+ foreach (var l in _layers)
+ ((ILogical)l).NotifyAttachedToLogicalTree(e);
+ }
+
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ _styleHost = null;
+ _isAttachedToLogicalTree = false;
+ base.OnDetachedFromLogicalTree(e);
+ foreach (var l in _layers)
+ ((ILogical)l).NotifyDetachedFromLogicalTree(e);
+ }
+
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ foreach (var l in _layers)
+ l.Measure(availableSize);
+ return base.MeasureOverride(availableSize);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ foreach (var l in _layers)
+ l.Arrange(new Rect(finalSize));
+ return base.ArrangeOverride(finalSize);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index da537a2e65..1bfcb47bb9 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Controls
private static readonly AttachedProperty ToolTipProperty =
AvaloniaProperty.RegisterAttached("ToolTip");
- private PopupRoot _popup;
+ private IPopupHost _popup;
///
/// Initializes static members of the class.
@@ -235,7 +235,8 @@ namespace Avalonia.Controls
{
Close();
- _popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this};
+ _popup = PopupHost.CreatePopupHost(control, null);
+ _popup.Content = this;
((ISetLogicalParent)_popup).SetParent(control);
_popup.ConfigurePosition(control, GetPlacement(control),
diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
index 09f822cf46..02810ed155 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
@@ -24,6 +24,7 @@ namespace Avalonia
{
public bool UseDeferredRendering { get; set; } = true;
public bool UseGpu { get; set; } = true;
+ public bool OverlayPopups { get; set; }
public string AvaloniaNativeLibraryPath { get; set; }
}
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index e4c158eeb3..490d5688a8 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -106,6 +106,7 @@ namespace Avalonia.Native
public Func Closing { get; set; }
public void Move(PixelPoint point) => Position = point;
- public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this);
+ public override IPopupImpl CreatePopup() =>
+ _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, this);
}
}
diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml
index 6227962a48..ffc96d5a2c 100644
--- a/src/Avalonia.Themes.Default/ComboBox.xaml
+++ b/src/Avalonia.Themes.Default/ComboBox.xaml
@@ -39,7 +39,7 @@
StaysOpen="False">
-
+
-
+
diff --git a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
index bc06ab010e..1fd168c009 100644
--- a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
@@ -4,13 +4,13 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml
index 2514422ce8..2a8b5d0fca 100644
--- a/src/Avalonia.Themes.Default/Window.xaml
+++ b/src/Avalonia.Themes.Default/Window.xaml
@@ -5,14 +5,14 @@
-
+
-
+
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 9bdcaab82b..e88a7d8db2 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -91,6 +91,7 @@ namespace Avalonia
{
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
+ public bool OverlayPopups { get; set; }
public List GlxRendererBlacklist { get; set; } = new List
{
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index a3c00abda9..5481862f23 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -812,7 +812,8 @@ namespace Avalonia.X11
}
public IMouseDevice MouseDevice => _mouse;
- public IPopupImpl CreatePopup() => new X11Window(_platform, this);
+ public IPopupImpl CreatePopup()
+ => _platform.Options.OverlayPopups ? null : new X11Window(_platform, this);
public void Activate()
{
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 56a7e356b6..f20cf394bb 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -41,6 +41,7 @@ namespace Avalonia
public bool UseDeferredRendering { get; set; } = true;
public bool AllowEglInitialization { get; set; }
public bool? EnableMultitouch { get; set; }
+ public bool OverlayPopups { get; set; }
}
}
@@ -61,6 +62,7 @@ namespace Avalonia.Win32
}
public static bool UseDeferredRendering => Options.UseDeferredRendering;
+ internal static bool UseOverlayPopups => Options.OverlayPopups;
public static Win32PlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 21625af84a..e33e1f11dc 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -250,10 +250,7 @@ namespace Avalonia.Win32
UnmanagedMethods.SetActiveWindow(_hwnd);
}
- public IPopupImpl CreatePopup()
- {
- return new PopupImpl(this);
- }
+ public IPopupImpl CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this);
public void Dispose()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
index 944bf1e642..e840e7b530 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
@@ -54,10 +54,47 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Popup.Open();
- Assert.Equal(target.Popup, ((IStyleHost)target.Popup.PopupRoot).StylingParent);
+ Assert.Equal(target.Popup, ((IStyleHost)target.Popup.Host).StylingParent);
}
}
+ [Fact]
+ public void PopupRoot_Should_Have_Template_Applied()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+ var target = new Popup {PlacementMode = PlacementMode.Pointer};
+ var child = new Control();
+
+ window.Content = target;
+ window.ApplyTemplate();
+ target.Open();
+
+
+ Assert.Single(((Visual)target.Host).GetVisualChildren());
+
+ var templatedChild = ((Visual)target.Host).GetVisualChildren().Single();
+ Assert.IsType(templatedChild);
+
+
+ Assert.Equal((PopupRoot)target.Host, ((IControl)templatedChild).TemplatedParent);
+ }
+ }
+
+ [Fact]
+ public void PopupRoot_Should_Have_Null_VisualParent()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Popup() {PlacementTarget = new Window()};
+
+ target.Open();
+
+ Assert.Null(((Visual)target.Host).GetVisualParent());
+ }
+ }
+
[Fact]
public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
{
@@ -134,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
private PopupRoot CreateTarget(TopLevel popupParent)
{
- var result = new PopupRoot(popupParent)
+ var result = new PopupRoot(popupParent, popupParent.PlatformImpl.CreatePopup())
{
Template = new FuncControlTemplate((parent, scope) =>
new ContentPresenter
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 3df4de6b68..ccdfe8af33 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
public class PopupTests
{
+ protected bool UsePopupHost;
+
[Fact]
public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
{
@@ -137,20 +139,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new Popup();
- Assert.Null(target.PopupRoot);
- }
- }
-
- [Fact]
- public void PopupRoot_Should_Have_Null_VisualParent()
- {
- using (CreateServices())
- {
- var target = new Popup() {PlacementTarget = new Window()};
-
- target.Open();
-
- Assert.Null(target.PopupRoot.GetVisualParent());
+ Assert.Null(((Visual)target.Host));
}
}
@@ -159,12 +148,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
using (CreateServices())
{
- var target = new Popup() {PlacementTarget = new Window()};
+ var target = new Popup() {PlacementTarget = PreparedWindow()};
target.Open();
- Assert.Equal(target, target.PopupRoot.Parent);
- Assert.Equal(target, target.PopupRoot.GetLogicalParent());
+ Assert.Equal(target, ((Visual)target.Host).Parent);
+ Assert.Equal(target, ((Visual)target.Host).GetLogicalParent());
}
}
@@ -174,11 +163,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (CreateServices())
{
var target = new Popup() {PlacementMode = PlacementMode.Pointer};
- var root = new Window() { Content = target };
+ var root = PreparedWindow(target);
target.Open();
- var popupRoot = (ILogical)target.PopupRoot;
+ var popupRoot = (ILogical)((Visual)target.Host);
Assert.True(popupRoot.IsAttachedToLogicalTree);
root.Content = null;
@@ -191,7 +180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
using (CreateServices())
{
- var window = new Window();
+ var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer};
window.Content = target;
@@ -214,7 +203,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
using (CreateServices())
{
- var window = new Window();
+ var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer};
window.Content = target;
@@ -234,48 +223,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
- [Fact]
- public void PopupRoot_Should_Have_Template_Applied()
- {
- using (CreateServices())
- {
- var window = new Window();
- var target = new Popup {PlacementMode = PlacementMode.Pointer};
- var child = new Control();
-
- window.Content = target;
- window.ApplyTemplate();
- target.Open();
-
- Assert.Single(target.PopupRoot.GetVisualChildren());
-
- var templatedChild = target.PopupRoot.GetVisualChildren().Single();
- Assert.IsType(templatedChild);
- Assert.Equal(target.PopupRoot, ((IControl)templatedChild).TemplatedParent);
- }
- }
-
+
[Fact]
public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent()
{
+ if(UsePopupHost)
+ // For some reason with overlay popups templates don't get applied in test mode but
+ // everything works perfectly fine at runtime. I leave this one to you @grokys
+ return;
using (CreateServices())
{
PopupContentControl target;
- var root = new Window()
+ var root = PreparedWindow(target = new PopupContentControl
{
- Content = target = new PopupContentControl
- {
- Content = new Border(),
- Template = new FuncControlTemplate(PopupContentControlTemplate),
- },
- //StylingParent = AvaloniaLocator.Current.GetService()
- };
- root.ApplyTemplate();
+ Content = new Border(),
+ Template = new FuncControlTemplate(PopupContentControlTemplate),
+ });
+ root.Show();
target.ApplyTemplate();
var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
popup.Open();
- var popupRoot = popup.PopupRoot;
+ var popupRoot = (Visual)popup.Host;
var children = popupRoot.GetVisualDescendants().ToList();
var types = children.Select(x => x.GetType().Name).ToList();
@@ -306,6 +275,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
+ Window PreparedWindow(object content = null)
+ {
+ var w = new Window {Content = content};
+ w.ApplyTemplate();
+ return w;
+ }
+
[Fact]
public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
{
@@ -316,7 +292,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = child = new TestControl(),
DataContext = "foo",
- PlacementTarget = new Window()
+ PlacementTarget = PreparedWindow()
};
var beginCalled = false;
@@ -336,9 +312,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.False(beginCalled);
}
}
+
+ [Fact]
+ public void Popup_Host_Type_Should_Match_Platform_Preference()
+ {
+ using (CreateServices())
+ {
+ var target = new Popup() {PlacementTarget = PreparedWindow()};
+
+ target.Open();
+ if (UsePopupHost)
+ Assert.IsType(target.Host);
+ else
+ Assert.IsType(target.Host);
+ }
+ }
- private static IDisposable CreateServices() => UnitTestApplication.Start(TestServices.StyledWindow);
+ private IDisposable CreateServices()
+ {
+ return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
+ new MockWindowingPlatform(null,
+ () =>
+ {
+ if(UsePopupHost)
+ return null;
+ return MockWindowingPlatform.CreatePopupMock().Object;
+ })));
+ }
private static IControl PopupRootTemplate(PopupRoot control, INameScope scope)
{
@@ -379,4 +380,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
}
+
+ public class PopupTestsWithPopupRoot : PopupTests
+ {
+ public PopupTestsWithPopupRoot()
+ {
+ UsePopupHost = true;
+ }
+ }
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
index dcecfe3b22..b8d41d5a87 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Styling;
using Avalonia.UnitTests;
@@ -59,11 +60,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
new Setter(
Window.TemplateProperty,
new FuncControlTemplate((x, scope) =>
- new ContentPresenter
+ new VisualLayerManager
{
- Name = "PART_ContentPresenter",
- [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
- }.RegisterInNameScope(scope)))
+ Child =
+ new ContentPresenter
+ {
+ Name = "PART_ContentPresenter",
+ [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
+ }.RegisterInNameScope(scope)
+ }))
}
};
}
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index 1b47318fe1..c33ec72141 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -23,7 +23,9 @@ namespace Avalonia.UnitTests
var mock = Mock.Get(win);
mock.Setup(x => x.CreatePopup()).Returns(() =>
{
- return popupImpl?.Invoke() ?? CreatePopupMock().Object;
+ if (popupImpl != null)
+ return popupImpl();
+ return CreatePopupMock().Object;
});
PixelPoint pos = default;