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