|
|
|
@ -14,6 +14,8 @@ using Avalonia.LogicalTree; |
|
|
|
using Avalonia.Metadata; |
|
|
|
using Avalonia.Platform; |
|
|
|
using Avalonia.VisualTree; |
|
|
|
using Avalonia.Media; |
|
|
|
using Avalonia.Utilities; |
|
|
|
|
|
|
|
namespace Avalonia.Controls.Primitives |
|
|
|
{ |
|
|
|
@ -33,6 +35,12 @@ namespace Avalonia.Controls.Primitives |
|
|
|
public static readonly StyledProperty<Control?> ChildProperty = |
|
|
|
AvaloniaProperty.Register<Popup, Control?>(nameof(Child)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="InheritsTransform"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> InheritsTransformProperty = |
|
|
|
AvaloniaProperty.Register<Popup, bool>(nameof(InheritsTransform)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="IsOpen"/> property.
|
|
|
|
/// </summary>
|
|
|
|
@ -196,6 +204,16 @@ namespace Avalonia.Controls.Primitives |
|
|
|
set; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value that determines whether the popup inherits the render transform
|
|
|
|
/// from its <see cref="PlacementTarget"/>. Defaults to false.
|
|
|
|
/// </summary>
|
|
|
|
public bool InheritsTransform |
|
|
|
{ |
|
|
|
get => GetValue(InheritsTransformProperty); |
|
|
|
set => SetValue(InheritsTransformProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
|
|
|
|
/// </summary>
|
|
|
|
@ -395,24 +413,29 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
|
|
|
|
_isOpenRequested = false; |
|
|
|
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); |
|
|
|
|
|
|
|
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); |
|
|
|
var handlerCleanup = new CompositeDisposable(7); |
|
|
|
|
|
|
|
popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty, |
|
|
|
HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty).DisposeWith(handlerCleanup); |
|
|
|
|
|
|
|
UpdateHostSizing(popupHost, topLevel, placementTarget); |
|
|
|
popupHost.Topmost = Topmost; |
|
|
|
popupHost.SetChild(Child); |
|
|
|
((ISetLogicalParent)popupHost).SetParent(this); |
|
|
|
|
|
|
|
popupHost.ConfigurePosition( |
|
|
|
placementTarget, |
|
|
|
PlacementMode, |
|
|
|
new Point(HorizontalOffset, VerticalOffset), |
|
|
|
PlacementAnchor, |
|
|
|
PlacementGravity, |
|
|
|
PlacementConstraintAdjustment, |
|
|
|
PlacementRect); |
|
|
|
if (InheritsTransform && placementTarget is Control c) |
|
|
|
{ |
|
|
|
SubscribeToEventHandler<Control, EventHandler<AvaloniaPropertyChangedEventArgs>>( |
|
|
|
c, |
|
|
|
PlacementTargetPropertyChanged, |
|
|
|
(x, handler) => x.PropertyChanged += handler, |
|
|
|
(x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
popupHost.Transform = null; |
|
|
|
} |
|
|
|
|
|
|
|
UpdateHostPosition(popupHost, placementTarget); |
|
|
|
|
|
|
|
SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied, |
|
|
|
(x, handler) => x.TemplateApplied += handler, |
|
|
|
@ -494,7 +517,7 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_openState = new PopupOpenState(topLevel, popupHost, cleanupPopup); |
|
|
|
_openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup); |
|
|
|
|
|
|
|
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint); |
|
|
|
|
|
|
|
@ -542,7 +565,93 @@ namespace Avalonia.Controls.Primitives |
|
|
|
base.OnDetachedFromLogicalTree(e); |
|
|
|
Close(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|
|
|
{ |
|
|
|
if (_openState is not null) |
|
|
|
{ |
|
|
|
if (change.Property == WidthProperty || |
|
|
|
change.Property == MinWidthProperty || |
|
|
|
change.Property == MaxWidthProperty || |
|
|
|
change.Property == HeightProperty || |
|
|
|
change.Property == MinHeightProperty || |
|
|
|
change.Property == MaxHeightProperty) |
|
|
|
{ |
|
|
|
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget); |
|
|
|
} |
|
|
|
else if (change.Property == PlacementTargetProperty || |
|
|
|
change.Property == PlacementModeProperty || |
|
|
|
change.Property == HorizontalOffsetProperty || |
|
|
|
change.Property == VerticalOffsetProperty || |
|
|
|
change.Property == PlacementAnchorProperty || |
|
|
|
change.Property == PlacementConstraintAdjustmentProperty || |
|
|
|
change.Property == PlacementRectProperty) |
|
|
|
{ |
|
|
|
if (change.Property == PlacementTargetProperty) |
|
|
|
{ |
|
|
|
var newTarget = change.GetNewValue<Control?>() ?? this.FindLogicalAncestorOfType<IControl>(); |
|
|
|
|
|
|
|
if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel) |
|
|
|
{ |
|
|
|
Close(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_openState.PlacementTarget = newTarget; |
|
|
|
} |
|
|
|
|
|
|
|
UpdateHostPosition(_openState.PopupHost, _openState.PlacementTarget); |
|
|
|
} |
|
|
|
else if (change.Property == TopmostProperty) |
|
|
|
{ |
|
|
|
_openState.PopupHost.Topmost = change.GetNewValue<bool>(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void UpdateHostPosition(IPopupHost popupHost, IControl placementTarget) |
|
|
|
{ |
|
|
|
popupHost.ConfigurePosition( |
|
|
|
placementTarget, |
|
|
|
PlacementMode, |
|
|
|
new Point(HorizontalOffset, VerticalOffset), |
|
|
|
PlacementAnchor, |
|
|
|
PlacementGravity, |
|
|
|
PlacementConstraintAdjustment, |
|
|
|
PlacementRect ?? new Rect(default, placementTarget.Bounds.Size)); |
|
|
|
} |
|
|
|
|
|
|
|
private void UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, IControl placementTarget) |
|
|
|
{ |
|
|
|
var scaleX = 1.0; |
|
|
|
var scaleY = 1.0; |
|
|
|
|
|
|
|
if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m) |
|
|
|
{ |
|
|
|
scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); |
|
|
|
scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); |
|
|
|
|
|
|
|
// Ideally we'd only assign a ScaleTransform here when the scale != 1, but there's
|
|
|
|
// an issue with LayoutTransformControl in that it sets its LayoutTransform property
|
|
|
|
// with LocalValue priority in ArrangeOverride in certain cases when LayoutTransform
|
|
|
|
// is null, which breaks TemplateBindings to this property. Offending commit/line:
|
|
|
|
//
|
|
|
|
// https://github.com/AvaloniaUI/Avalonia/commit/6fbe1c2180ef45a940e193f1b4637e64eaab80ed#diff-5344e793df13f462126a8153ef46c44194f244b6890f25501709bae51df97f82R54
|
|
|
|
popupHost.Transform = new ScaleTransform(scaleX, scaleY); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
popupHost.Transform = null; |
|
|
|
} |
|
|
|
|
|
|
|
popupHost.Width = Width * scaleX; |
|
|
|
popupHost.MinWidth = MinWidth * scaleX; |
|
|
|
popupHost.MaxWidth = MaxWidth * scaleX; |
|
|
|
popupHost.Height = Height * scaleY; |
|
|
|
popupHost.MinHeight = MinHeight * scaleY; |
|
|
|
popupHost.MaxHeight = MaxHeight * scaleY; |
|
|
|
} |
|
|
|
|
|
|
|
private void HandlePositionChange() |
|
|
|
{ |
|
|
|
if (_openState != null) |
|
|
|
@ -824,6 +933,14 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void PlacementTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
|
|
|
{ |
|
|
|
if (_openState is not null && e.Property == Visual.TransformedBoundsProperty) |
|
|
|
{ |
|
|
|
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void WindowLostFocus() |
|
|
|
{ |
|
|
|
if (IsLightDismissEnabled) |
|
|
|
@ -862,15 +979,16 @@ namespace Avalonia.Controls.Primitives |
|
|
|
private readonly IDisposable _cleanup; |
|
|
|
private IDisposable? _presenterCleanup; |
|
|
|
|
|
|
|
public PopupOpenState(TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup) |
|
|
|
public PopupOpenState(IControl placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup) |
|
|
|
{ |
|
|
|
PlacementTarget = placementTarget; |
|
|
|
TopLevel = topLevel; |
|
|
|
PopupHost = popupHost; |
|
|
|
_cleanup = cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
public TopLevel TopLevel { get; } |
|
|
|
|
|
|
|
public IControl PlacementTarget { get; set; } |
|
|
|
public IPopupHost PopupHost { get; } |
|
|
|
|
|
|
|
public void SetPresenterSubscription(IDisposable? presenterCleanup) |
|
|
|
|