|
@@ -14,6 +14,8 @@ using Avalonia.LogicalTree;
|
|
|
using Avalonia.Metadata;
|
|
using Avalonia.Metadata;
|
|
|
using Avalonia.Platform;
|
|
using Avalonia.Platform;
|
|
|
using Avalonia.VisualTree;
|
|
using Avalonia.VisualTree;
|
|
|
|
|
+using Avalonia.Media;
|
|
|
|
|
+using Avalonia.Utilities;
|
|
|
|
|
|
|
|
namespace Avalonia.Controls.Primitives
|
|
namespace Avalonia.Controls.Primitives
|
|
|
{
|
|
{
|
|
@@ -33,6 +35,12 @@ namespace Avalonia.Controls.Primitives
|
|
|
public static readonly StyledProperty<Control?> ChildProperty =
|
|
public static readonly StyledProperty<Control?> ChildProperty =
|
|
|
AvaloniaProperty.Register<Popup, Control?>(nameof(Child));
|
|
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>
|
|
/// <summary>
|
|
|
/// Defines the <see cref="IsOpen"/> property.
|
|
/// Defines the <see cref="IsOpen"/> property.
|
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -196,6 +204,16 @@ namespace Avalonia.Controls.Primitives
|
|
|
set;
|
|
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>
|
|
/// <summary>
|
|
|
/// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
|
|
/// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
|
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -395,24 +413,29 @@ namespace Avalonia.Controls.Primitives
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_isOpenRequested = false;
|
|
_isOpenRequested = false;
|
|
|
- var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
|
|
|
|
|
|
|
|
|
|
|
|
+ var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
|
|
|
var handlerCleanup = new CompositeDisposable(7);
|
|
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);
|
|
popupHost.SetChild(Child);
|
|
|
((ISetLogicalParent)popupHost).SetParent(this);
|
|
((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,
|
|
SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied,
|
|
|
(x, handler) => x.TemplateApplied += handler,
|
|
(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);
|
|
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
|
|
|
|
|
|
|
@@ -542,7 +565,93 @@ namespace Avalonia.Controls.Primitives
|
|
|
base.OnDetachedFromLogicalTree(e);
|
|
base.OnDetachedFromLogicalTree(e);
|
|
|
Close();
|
|
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()
|
|
private void HandlePositionChange()
|
|
|
{
|
|
{
|
|
|
if (_openState != null)
|
|
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()
|
|
private void WindowLostFocus()
|
|
|
{
|
|
{
|
|
|
if (IsLightDismissEnabled)
|
|
if (IsLightDismissEnabled)
|
|
@@ -862,15 +979,16 @@ namespace Avalonia.Controls.Primitives
|
|
|
private readonly IDisposable _cleanup;
|
|
private readonly IDisposable _cleanup;
|
|
|
private IDisposable? _presenterCleanup;
|
|
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;
|
|
TopLevel = topLevel;
|
|
|
PopupHost = popupHost;
|
|
PopupHost = popupHost;
|
|
|
_cleanup = cleanup;
|
|
_cleanup = cleanup;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public TopLevel TopLevel { get; }
|
|
public TopLevel TopLevel { get; }
|
|
|
-
|
|
|
|
|
|
|
+ public IControl PlacementTarget { get; set; }
|
|
|
public IPopupHost PopupHost { get; }
|
|
public IPopupHost PopupHost { get; }
|
|
|
|
|
|
|
|
public void SetPresenterSubscription(IDisposable? presenterCleanup)
|
|
public void SetPresenterSubscription(IDisposable? presenterCleanup)
|