26 changed files with 675 additions and 223 deletions
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue