Skip to content

Commit

Permalink
Add Popup.ShouldUseOverlayLayer property (#5629)
Browse files Browse the repository at this point in the history
* Implement Popup.ShouldUseOverlayLayer property

* Add ShouldUseOverlayLayer tests
  • Loading branch information
maxkatz6 authored Jan 28, 2025
1 parent 2e6c571 commit 98a388d
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
10 changes: 8 additions & 2 deletions src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,16 @@ void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualS
// TODO12: mark PrivateAPI or internal.
[Unstable("PopupHost is considered an internal API. Use Popup or any Popup-based controls (Flyout, Tooltip) instead.")]
public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver)
=> CreatePopupHost(target, dependencyResolver, false);

internal static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver, bool shouldUseOverlayLayer)
{
if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
if (!shouldUseOverlayLayer)
{
return new PopupRoot(topLevel, popupImpl, dependencyResolver);
if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
{
return new PopupRoot(topLevel, popupImpl, dependencyResolver);
}
}

if (OverlayLayer.GetOverlayLayer(target) is { } overlayLayer)
Expand Down
39 changes: 38 additions & 1 deletion src/Avalonia.Controls/Primitives/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,21 @@ public class Popup : Control, IPopupHostProvider
public static readonly AttachedProperty<bool> TakesFocusFromNativeControlProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, bool>(nameof(TakesFocusFromNativeControl), true);

/// <summary>
/// Defines the <see cref="ShouldUseOverlayLayer"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShouldUseOverlayLayerProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ShouldUseOverlayLayer));

/// <summary>
/// Defines the <see cref="IsUsingOverlayLayer"/> property.
/// </summary>
public static readonly DirectProperty<Popup, bool> IsUsingOverlayLayerProperty = AvaloniaProperty.RegisterDirect<Popup, bool>(
nameof(IsUsingOverlayLayer), o => o.IsUsingOverlayLayer);

private bool _isOpenRequested;
private bool _ignoreIsOpenChanged;
private bool _isUsingOverlayLayer;
private PopupOpenState? _openState;
private Action<IPopupHost?>? _popupHostChangedHandler;

Expand Down Expand Up @@ -386,6 +399,29 @@ public bool TakesFocusFromNativeControl
set => SetValue(TakesFocusFromNativeControlProperty, value);
}

/// <summary>
/// Gets or sets a value that indicates whether the popup should be shown in the overlay layer of the parent window.
/// </summary>
/// <remarks>
/// When <see cref="ShouldUseOverlayLayer"/> is "false" implementation depends on the platform.
/// Use <see cref="IsUsingOverlayLayer"/> to get actual popup behavior.
/// This is an equvalent of `OverlayPopups` property of the platform options, but settable independently per each popup.
/// </remarks>
public bool ShouldUseOverlayLayer
{
get => GetValue(ShouldUseOverlayLayerProperty);
set => SetValue(ShouldUseOverlayLayerProperty, value);
}

/// <summary>
/// Gets a value that indicates whether the popup is shown in the overlay layer of the parent window.
/// </summary>
public bool IsUsingOverlayLayer
{
get => _isUsingOverlayLayer;
private set => SetAndRaise(IsUsingOverlayLayerProperty, ref _isUsingOverlayLayer, value);
}

IPopupHost? IPopupHostProvider.PopupHost => Host;

event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged
Expand Down Expand Up @@ -423,7 +459,7 @@ public void Open()

_isOpenRequested = false;

var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver, ShouldUseOverlayLayer);
var handlerCleanup = new CompositeDisposable(7);

UpdateHostSizing(popupHost, topLevel, placementTarget);
Expand Down Expand Up @@ -541,6 +577,7 @@ public void Open()
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);

popupHost.Show();
IsUsingOverlayLayer = popupHost is OverlayPopupHost;

if (TakesFocusFromNativeControl)
popupHost.TakeFocus();
Expand Down
32 changes: 32 additions & 0 deletions tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,38 @@ private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null
return result;
}

[Fact]
public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Disabled_OverlayLayer()
{
using (CreateServices())
{
var target = new Popup();
target.IsOpen = true;
target.ShouldUseOverlayLayer = false;

var window = PreparedWindow(target);
window.Show();

Assert.Equal(UsePopupHost, target.IsUsingOverlayLayer);
}
}

[Fact]
public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Enabled_OverlayLayer()
{
using (CreateServices())
{
var target = new Popup();
target.IsOpen = true;
target.ShouldUseOverlayLayer = true;

var window = PreparedWindow(target);
window.Show();

Assert.Equal(true, target.IsUsingOverlayLayer);
}
}

private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(
Expand Down

0 comments on commit 98a388d

Please sign in to comment.