Browse Source

IPopupImpl is now optional advanced feature

pull/2778/head
Nikita Tsukanov 7 years ago
parent
commit
f9561260a3
  1. 2
      src/Avalonia.Controls/ComboBox.cs
  2. 2
      src/Avalonia.Controls/MenuItem.cs
  3. 2
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  4. 42
      src/Avalonia.Controls/Primitives/AdornerDecorator.cs
  5. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  6. 22
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  7. 145
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  8. 60
      src/Avalonia.Controls/Primitives/Popup.cs
  9. 160
      src/Avalonia.Controls/Primitives/PopupHost.cs
  10. 64
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  11. 91
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  12. 97
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  13. 5
      src/Avalonia.Controls/ToolTip.cs
  14. 1
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  15. 3
      src/Avalonia.Native/WindowImpl.cs
  16. 4
      src/Avalonia.Themes.Default/ComboBox.xaml
  17. 6
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  18. 4
      src/Avalonia.Themes.Default/Window.xaml
  19. 1
      src/Avalonia.X11/X11Platform.cs
  20. 3
      src/Avalonia.X11/X11Window.cs
  21. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  22. 5
      src/Windows/Avalonia.Win32/WindowImpl.cs
  23. 41
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  24. 117
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  25. 13
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  26. 4
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

2
src/Avalonia.Controls/ComboBox.cs

@ -202,7 +202,7 @@ namespace Avalonia.Controls
{ {
if (!e.Handled) if (!e.Handled)
{ {
if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot) if (_popup?.IsInsidePopup((IVisual)e.Source) == true)
{ {
if (UpdateSelectionFromEventSource(e.Source)) if (UpdateSelectionFromEventSource(e.Source))
{ {

2
src/Avalonia.Controls/MenuItem.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls
public bool IsTopLevel => Parent is Menu; public bool IsTopLevel => Parent is Menu;
/// <inheritdoc/> /// <inheritdoc/>
bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false; bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
/// <inheritdoc/> /// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement; IMenuElement IMenuItem.Parent => Parent as IMenuElement;

2
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -150,7 +150,7 @@ namespace Avalonia.Controls.Notifications
private void Install(Window host) private void Install(Window host)
{ {
var adornerLayer = host.GetVisualDescendants() var adornerLayer = host.GetVisualDescendants()
.OfType<AdornerDecorator>() .OfType<VisualLayerManager>()
.FirstOrDefault() .FirstOrDefault()
?.AdornerLayer; ?.AdornerLayer;

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

@ -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);
}
}
}

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

@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
public static AdornerLayer GetAdornerLayer(IVisual visual) public static AdornerLayer GetAdornerLayer(IVisual visual)
{ {
return visual.GetVisualAncestors() return visual.GetVisualAncestors()
.OfType<AdornerDecorator>() .OfType<VisualLayerManager>()
.FirstOrDefault() .FirstOrDefault()
?.AdornerLayer; ?.AdornerLayer;
} }

22
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<double> widthProperty,
StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty,
StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty);
}
}

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

@ -0,0 +1,145 @@
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public class OverlayLayer : Control
{
/// <summary>
/// Defines the Left attached property.
/// </summary>
public static readonly AttachedProperty<double> LeftProperty =
AvaloniaProperty.RegisterAttached<OverlayLayer, Control, double>("Left", 0);
/// <summary>
/// Defines the Top attached property.
/// </summary>
public static readonly AttachedProperty<double> TopProperty =
AvaloniaProperty.RegisterAttached<OverlayLayer, Control, double>("Top", 0);
/// <summary>
/// Defines the InfiniteAvailableSize attached property.
/// </summary>
public static readonly AttachedProperty<bool> InfiniteAvailableSizeProperty =
AvaloniaProperty.RegisterAttached<OverlayLayer, Control, bool>("InfiniteAvailableSize", false);
static OverlayLayer()
{
foreach (var p in new []{LeftProperty, TopProperty})
{
p.Changed.AddClassHandler<Control>((target, e) =>
{
if (target.GetVisualParent() is OverlayLayer layer)
layer.InvalidateArrange();
});
}
}
public Size AvailableSize { get; private set; }
/// <summary>
/// Gets the value of the Left attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static double GetLeft(AvaloniaObject element)
{
return element.GetValue(LeftProperty);
}
/// <summary>
/// Sets the value of the Left attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="value">The left value.</param>
public static void SetLeft(AvaloniaObject element, double value)
{
element.SetValue(LeftProperty, value);
}
/// <summary>
/// Gets the value of the Top attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <returns>The control's top coordinate.</returns>
public static double GetTop(AvaloniaObject element)
{
return element.GetValue(TopProperty);
}
/// <summary>
/// Sets the value of the Top attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="value">The top value.</param>
public static void SetTop(AvaloniaObject element, double value)
{
element.SetValue(TopProperty, value);
}
/// <summary>
/// Gets the value of the Top attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <returns>The control's top coordinate.</returns>
public static bool GetInfiniteAvailableSize(AvaloniaObject element)
{
return element.GetValue(InfiniteAvailableSizeProperty);
}
/// <summary>
/// Sets the value of the Top attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="value">The top value.</param>
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<VisualLayerManager>().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;
}
}
}

60
src/Avalonia.Controls/Primitives/Popup.cs

@ -79,7 +79,7 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost)); AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpen; private bool _isOpen;
private PopupRoot _popupRoot; private IPopupHost _popupRoot;
private TopLevel _topLevel; private TopLevel _topLevel;
private IDisposable _nonClientListener; private IDisposable _nonClientListener;
bool _ignoreIsOpenChanged = false; bool _ignoreIsOpenChanged = false;
@ -94,7 +94,6 @@ namespace Avalonia.Controls.Primitives
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false); IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged); ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged); IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
} }
public Popup() public Popup()
@ -112,10 +111,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public event EventHandler Opened; public event EventHandler Opened;
/// <summary> public IPopupHost Host => _popupRoot;
/// Raised when the popup root has been created, but before it has been shown.
/// </summary>
public event EventHandler PopupRootCreated;
/// <summary> /// <summary>
/// Gets or sets the control to display in the popup. /// Gets or sets the control to display in the popup.
@ -192,11 +188,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementTargetProperty, value); } set { SetValue(PlacementTargetProperty, value); }
} }
/// <summary>
/// Gets the root of the popup window.
/// </summary>
public PopupRoot PopupRoot => _popupRoot;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the popup should stay open when the popup is /// Gets or sets a value indicating whether the popup should stay open when the popup is
/// pressed or loses focus. /// pressed or loses focus.
@ -219,7 +210,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets the root of the popup window. /// Gets the root of the popup window.
/// </summary> /// </summary>
IVisual IVisualTreeHost.Root => _popupRoot; IVisual IVisualTreeHost.Root => _popupRoot?.VisualRoot;
/// <summary> /// <summary>
/// Opens the popup. /// Opens the popup.
@ -242,28 +233,13 @@ namespace Avalonia.Controls.Primitives
"Attempted to open a popup not attached to a TopLevel"); "Attempted to open a popup not attached to a TopLevel");
} }
_popupRoot = PopupHost.CreatePopupHost(placementTarget, DependencyResolver);
_popupRoot = new PopupRoot(_topLevel, DependencyResolver) _bindings.Add(_popupRoot.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
{ HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty));
[~WidthProperty] = this[~WidthProperty], _bindings.Add(_decorator.Bind(PopupContentHost.ChildProperty, this[~ChildProperty]));
[~HeightProperty] = this[~HeightProperty],
[~MinWidthProperty] = this[~MinWidthProperty], _popupRoot.SetContent(_decorator);
[~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;
((ISetLogicalParent)_popupRoot).SetParent(this); ((ISetLogicalParent)_popupRoot).SetParent(this);
@ -287,7 +263,6 @@ namespace Avalonia.Controls.Primitives
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick); _nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick);
PopupRootCreated?.Invoke(this, EventArgs.Empty);
_popupRoot.Show(); _popupRoot.Show();
@ -338,7 +313,7 @@ namespace Avalonia.Controls.Primitives
foreach(var b in _bindings) foreach(var b in _bindings)
b.Dispose(); b.Dispose();
_bindings.Clear(); _bindings.Clear();
_popupRoot.Content = null; _popupRoot.SetContent(null);
_popupRoot.Hide(); _popupRoot.Hide();
((ISetLogicalParent)_popupRoot).SetParent(null); ((ISetLogicalParent)_popupRoot).SetParent(null);
_popupRoot.Dispose(); _popupRoot.Dispose();
@ -425,14 +400,15 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child) private bool IsChildOrThis(IVisual child)
{ {
IVisual root = child.GetVisualRoot(); return _decorator.FindCommonVisualAncestor(child) == _decorator;
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
} }
public bool IsInsidePopup(IVisual visual)
{
return _decorator.FindCommonVisualAncestor(visual) == _decorator;
}
public bool IsPointerOverPopup => _decorator.IsPointerOver;
private void WindowDeactivated(object sender, EventArgs e) private void WindowDeactivated(object sender, EventArgs e)
{ {

160
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;
/// <inheritdoc/>
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<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
// Topmost property is not supported
var bindings = new List<IDisposable>();
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<ManagedPopupPositionerScreenInfo> 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));
}
}
}

64
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -45,6 +45,7 @@ Copyright © 2019 Nikita Tsukanov
*/ */
using System; using System;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives.PopupPositioning namespace Avalonia.Controls.Primitives.PopupPositioning
{ {
@ -290,5 +291,68 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
void Update(PopupPositionerParameters parameters); 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");
}
}
}
} }

91
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Disposables;
using System.Text; using System.Text;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
@ -21,7 +23,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// The root window of a <see cref="Popup"/>. /// The root window of a <see cref="Popup"/>.
/// </summary> /// </summary>
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
{ {
private readonly TopLevel _parent; private readonly TopLevel _parent;
private IDisposable _presenterSubscription; private IDisposable _presenterSubscription;
@ -38,8 +40,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PopupRoot"/> class. /// Initializes a new instance of the <see cref="PopupRoot"/> class.
/// </summary> /// </summary>
public PopupRoot(TopLevel parent) public PopupRoot(TopLevel parent, IPopupImpl impl)
: this(parent, null) : this(parent, impl,null)
{ {
} }
@ -49,8 +51,8 @@ namespace Avalonia.Controls.Primitives
/// <param name="dependencyResolver"> /// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used. /// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param> /// </param>
public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver) public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver dependencyResolver)
: base(parent.PlatformImpl.CreatePopup(), dependencyResolver) : base(impl, dependencyResolver)
{ {
_parent = parent; _parent = parent;
} }
@ -133,65 +135,36 @@ namespace Avalonia.Controls.Primitives
PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None) PopupPositioningEdge gravity = PopupPositioningEdge.None)
{ {
// We need a better way for tracking the last pointer position _positionerParameters.ConfigurePosition(_parent, target,
var pointer = _parent.PointToClient(_parent.PlatformImpl.MouseDevice.Position); placement, offset, anchor, gravity);
_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");
}
if (_positionerParameters.Size != default) if (_positionerParameters.Size != default)
UpdatePosition(); UpdatePosition();
} }
IVisual IPopupHost.VisualRoot => this;
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
var bindings = new List<IDisposable>();
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();
});
}
/// <summary> /// <summary>
/// Carries out the arrange pass of the window. /// Carries out the arrange pass of the window.
/// </summary> /// </summary>

97
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<Control> _layers = new List<Control>();
public AdornerLayer AdornerLayer
{
get
{
var rv = FindLayer<AdornerLayer>();
if (rv == null)
AddLayer(rv = new AdornerLayer(), AdornerZIndex);
return rv;
}
}
public OverlayLayer OverlayLayer
{
get
{
if (IsPopup)
return null;
var rv = FindLayer<OverlayLayer>();
if(rv == null)
AddLayer(rv = new OverlayLayer(), OverlayZIndex);
return rv;
}
}
T FindLayer<T>() 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);
}
}
}

5
src/Avalonia.Controls/ToolTip.cs

@ -61,7 +61,7 @@ namespace Avalonia.Controls
private static readonly AttachedProperty<ToolTip> ToolTipProperty = private static readonly AttachedProperty<ToolTip> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip"); AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
private PopupRoot _popup; private IPopupHost _popup;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ToolTip"/> class. /// Initializes static members of the <see cref="ToolTip"/> class.
@ -235,7 +235,8 @@ namespace Avalonia.Controls
{ {
Close(); Close();
_popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this}; _popup = PopupHost.CreatePopupHost(control, null);
_popup.Content = this;
((ISetLogicalParent)_popup).SetParent(control); ((ISetLogicalParent)_popup).SetParent(control);
_popup.ConfigurePosition(control, GetPlacement(control), _popup.ConfigurePosition(control, GetPlacement(control),

1
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -24,6 +24,7 @@ namespace Avalonia
{ {
public bool UseDeferredRendering { get; set; } = true; public bool UseDeferredRendering { get; set; } = true;
public bool UseGpu { get; set; } = true; public bool UseGpu { get; set; } = true;
public bool OverlayPopups { get; set; }
public string AvaloniaNativeLibraryPath { get; set; } public string AvaloniaNativeLibraryPath { get; set; }
} }

3
src/Avalonia.Native/WindowImpl.cs

@ -106,6 +106,7 @@ namespace Avalonia.Native
public Func<bool> Closing { get; set; } public Func<bool> Closing { get; set; }
public void Move(PixelPoint point) => Position = point; 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);
} }
} }

4
src/Avalonia.Themes.Default/ComboBox.xaml

@ -39,7 +39,7 @@
StaysOpen="False"> StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<AdornerDecorator Margin="-1 -1 0 0"> <VisualLayerManager IsPopup="True" Margin="-1 -1 0 0">
<ScrollViewer> <ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
@ -48,7 +48,7 @@
VirtualizationMode="{TemplateBinding VirtualizationMode}" VirtualizationMode="{TemplateBinding VirtualizationMode}"
/> />
</ScrollViewer> </ScrollViewer>
</AdornerDecorator> </VisualLayerManager>
</Border> </Border>
</Popup> </Popup>
</Grid> </Grid>

6
src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml

@ -4,13 +4,13 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<AdornerDecorator> <VisualLayerManager>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/> Margin="{TemplateBinding Padding}"/>
</AdornerDecorator> </VisualLayerManager>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

4
src/Avalonia.Themes.Default/Window.xaml

@ -5,14 +5,14 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<AdornerDecorator> <VisualLayerManager>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</AdornerDecorator> </VisualLayerManager>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

1
src/Avalonia.X11/X11Platform.cs

@ -91,6 +91,7 @@ namespace Avalonia
{ {
public bool UseEGL { get; set; } public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true; public bool UseGpu { get; set; } = true;
public bool OverlayPopups { get; set; }
public List<string> GlxRendererBlacklist { get; set; } = new List<string> public List<string> GlxRendererBlacklist { get; set; } = new List<string>
{ {

3
src/Avalonia.X11/X11Window.cs

@ -812,7 +812,8 @@ namespace Avalonia.X11
} }
public IMouseDevice MouseDevice => _mouse; 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() public void Activate()
{ {

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -41,6 +41,7 @@ namespace Avalonia
public bool UseDeferredRendering { get; set; } = true; public bool UseDeferredRendering { get; set; } = true;
public bool AllowEglInitialization { get; set; } public bool AllowEglInitialization { get; set; }
public bool? EnableMultitouch { 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; public static bool UseDeferredRendering => Options.UseDeferredRendering;
internal static bool UseOverlayPopups => Options.OverlayPopups;
public static Win32PlatformOptions Options { get; private set; } public static Win32PlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size( public Size DoubleClickSize => new Size(

5
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -250,10 +250,7 @@ namespace Avalonia.Win32
UnmanagedMethods.SetActiveWindow(_hwnd); UnmanagedMethods.SetActiveWindow(_hwnd);
} }
public IPopupImpl CreatePopup() public IPopupImpl CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this);
{
return new PopupImpl(this);
}
public void Dispose() public void Dispose()
{ {

41
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -54,10 +54,47 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate(); target.ApplyTemplate();
target.Popup.Open(); 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<ContentPresenter>(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] [Fact]
public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree() 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) private PopupRoot CreateTarget(TopLevel popupParent)
{ {
var result = new PopupRoot(popupParent) var result = new PopupRoot(popupParent, popupParent.PlatformImpl.CreatePopup())
{ {
Template = new FuncControlTemplate<PopupRoot>((parent, scope) => Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter new ContentPresenter

117
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
public class PopupTests public class PopupTests
{ {
protected bool UsePopupHost;
[Fact] [Fact]
public void Setting_Child_Should_Set_Child_Controls_LogicalParent() public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
{ {
@ -137,20 +139,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new Popup(); var target = new Popup();
Assert.Null(target.PopupRoot); Assert.Null(((Visual)target.Host));
}
}
[Fact]
public void PopupRoot_Should_Have_Null_VisualParent()
{
using (CreateServices())
{
var target = new Popup() {PlacementTarget = new Window()};
target.Open();
Assert.Null(target.PopupRoot.GetVisualParent());
} }
} }
@ -159,12 +148,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
using (CreateServices()) using (CreateServices())
{ {
var target = new Popup() {PlacementTarget = new Window()}; var target = new Popup() {PlacementTarget = PreparedWindow()};
target.Open(); target.Open();
Assert.Equal(target, target.PopupRoot.Parent); Assert.Equal(target, ((Visual)target.Host).Parent);
Assert.Equal(target, target.PopupRoot.GetLogicalParent()); Assert.Equal(target, ((Visual)target.Host).GetLogicalParent());
} }
} }
@ -174,11 +163,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (CreateServices()) using (CreateServices())
{ {
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {PlacementMode = PlacementMode.Pointer};
var root = new Window() { Content = target }; var root = PreparedWindow(target);
target.Open(); target.Open();
var popupRoot = (ILogical)target.PopupRoot; var popupRoot = (ILogical)((Visual)target.Host);
Assert.True(popupRoot.IsAttachedToLogicalTree); Assert.True(popupRoot.IsAttachedToLogicalTree);
root.Content = null; root.Content = null;
@ -191,7 +180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
using (CreateServices()) using (CreateServices())
{ {
var window = new Window(); var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {PlacementMode = PlacementMode.Pointer};
window.Content = target; window.Content = target;
@ -214,7 +203,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
using (CreateServices()) using (CreateServices())
{ {
var window = new Window(); var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {PlacementMode = PlacementMode.Pointer};
window.Content = target; 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<ContentPresenter>(templatedChild);
Assert.Equal(target.PopupRoot, ((IControl)templatedChild).TemplatedParent);
}
}
[Fact] [Fact]
public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent() 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()) using (CreateServices())
{ {
PopupContentControl target; PopupContentControl target;
var root = new Window() var root = PreparedWindow(target = new PopupContentControl
{ {
Content = target = new PopupContentControl Content = new Border(),
{ Template = new FuncControlTemplate<PopupContentControl>(PopupContentControlTemplate),
Content = new Border(), });
Template = new FuncControlTemplate<PopupContentControl>(PopupContentControlTemplate), root.Show();
},
//StylingParent = AvaloniaLocator.Current.GetService<IGlobalStyles>()
};
root.ApplyTemplate();
target.ApplyTemplate(); target.ApplyTemplate();
var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
popup.Open(); popup.Open();
var popupRoot = popup.PopupRoot; var popupRoot = (Visual)popup.Host;
var children = popupRoot.GetVisualDescendants().ToList(); var children = popupRoot.GetVisualDescendants().ToList();
var types = children.Select(x => x.GetType().Name).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] [Fact]
public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit() 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(), Child = child = new TestControl(),
DataContext = "foo", DataContext = "foo",
PlacementTarget = new Window() PlacementTarget = PreparedWindow()
}; };
var beginCalled = false; var beginCalled = false;
@ -336,9 +312,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.False(beginCalled); 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<PopupHost>(target.Host);
else
Assert.IsType<PopupRoot>(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) 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;
}
}
} }

13
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -59,11 +60,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
new Setter( new Setter(
Window.TemplateProperty, Window.TemplateProperty,
new FuncControlTemplate<Window>((x, scope) => new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter new VisualLayerManager
{ {
Name = "PART_ContentPresenter", Child =
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], new ContentPresenter
}.RegisterInNameScope(scope))) {
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
}.RegisterInNameScope(scope)
}))
} }
}; };
} }

4
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -23,7 +23,9 @@ namespace Avalonia.UnitTests
var mock = Mock.Get(win); var mock = Mock.Get(win);
mock.Setup(x => x.CreatePopup()).Returns(() => mock.Setup(x => x.CreatePopup()).Returns(() =>
{ {
return popupImpl?.Invoke() ?? CreatePopupMock().Object; if (popupImpl != null)
return popupImpl();
return CreatePopupMock().Object;
}); });
PixelPoint pos = default; PixelPoint pos = default;

Loading…
Cancel
Save