Skip to content

Fixed overlay popups not automatically closing #16564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Avalonia.Controls/ToolTipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void Update(IInputRoot root, Visual? candidateToolTipHost)
{
var currentToolTip = _tipControl?.GetValue(ToolTip.ToolTipProperty);

if (root == currentToolTip?.VisualRoot)
if (root == currentToolTip?.PopupHost?.HostedVisualTreeRoot)
{
// Don't update while the pointer is over a tooltip
return;
Expand Down
97 changes: 77 additions & 20 deletions tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Reactive;
using System.Runtime.CompilerServices;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.UnitTests;
Expand All @@ -15,18 +18,65 @@ namespace Avalonia.Controls.UnitTests
public class ToolTipTests_Popup : ToolTipTests
{
protected override TestServices ConfigureServices(TestServices baseServices) => baseServices;

protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl) { }

protected override void VerifyToolTipType(Control control)
{
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
Assert.IsType<PopupRoot>(toolTip.PopupHost);
Assert.Same(toolTip.VisualRoot, toolTip.PopupHost);
}
}

public class ToolTipTests_Overlay : ToolTipTests
public class ToolTipTests_Overlay : ToolTipTests, IDisposable
{
private readonly IDisposable _toolTipOpenSubscription;

public ToolTipTests_Overlay()
{
_toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<bool>>(e =>
{
if (e.Sender is Visual { VisualRoot: {} root } visual)
OverlayLayer.GetOverlayLayer(visual).Measure(root.ClientSize);
}));
}

public void Dispose()
{
_toolTipOpenSubscription.Dispose();
}

protected override TestServices ConfigureServices(TestServices baseServices) =>
baseServices.With(windowingPlatform: new MockWindowingPlatform(popupImpl: window => null));

protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl)
{
windowImpl.Setup(x => x.CreatePopup()).Returns(default(IPopupImpl));
}

protected override void VerifyToolTipType(Control control)
{
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
Assert.IsType<OverlayPopupHost>(toolTip.PopupHost);
Assert.Same(toolTip.VisualRoot, control.VisualRoot);
}
}

public abstract class ToolTipTests
{
protected abstract TestServices ConfigureServices(TestServices baseServices);

protected abstract void SetupWindowMock(Mock<IWindowImpl> windowImpl);

protected abstract void VerifyToolTipType(Control control);

private void AssertToolTipOpen(Control control)
{
Assert.True(ToolTip.GetIsOpen(control));
VerifyToolTipType(control);
}

private static readonly MouseDevice s_mouseDevice = new(new Pointer(0, PointerType.Mouse, true));

[Fact]
Expand All @@ -46,7 +96,7 @@ public void Should_Close_When_Control_Detaches()

SetupWindowAndActivateToolTip(panel, target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

panel.Children.Remove(target);

Expand Down Expand Up @@ -74,7 +124,7 @@ public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree()

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

panel.Children.Remove(target);

Expand All @@ -95,7 +145,7 @@ public void Should_Open_On_Pointer_Enter()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -112,7 +162,7 @@ public void Content_Should_Update_When_Tip_Property_Changes_And_Already_Open()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
Assert.Equal("Tip", target.GetValue(ToolTip.ToolTipProperty).Content);

ToolTip.SetTip(target, "Tip1");
Expand All @@ -139,7 +189,7 @@ public void Should_Open_On_Pointer_Enter_With_Delay()

timer.ForceFire();

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand Down Expand Up @@ -188,6 +238,7 @@ public void Setting_IsOpen_Should_Add_Open_Class()
ToolTip.SetIsOpen(decorator, true);

Assert.Equal(new[] { ":open" }, toolTip.Classes);
VerifyToolTipType(decorator);
}
}

Expand All @@ -197,8 +248,11 @@ public void Clearing_IsOpen_Should_Remove_Open_Class()
using (UnitTestApplication.Start(ConfigureServices(TestServices.StyledWindow)))
{
var toolTip = new ToolTip();
var window = new Window();

var windowImpl = MockWindowingPlatform.CreateWindowMock();
SetupWindowMock(windowImpl);
var window = new Window(windowImpl.Object);

var decorator = new Decorator()
{
[ToolTip.TipProperty] = toolTip
Expand All @@ -211,6 +265,7 @@ public void Clearing_IsOpen_Should_Remove_Open_Class()
window.Presenter.ApplyTemplate();

ToolTip.SetIsOpen(decorator, true);
AssertToolTipOpen(decorator);
ToolTip.SetIsOpen(decorator, false);

Assert.Empty(toolTip.Classes);
Expand All @@ -230,7 +285,7 @@ public void Should_Close_On_Null_Tip()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand All @@ -253,13 +308,13 @@ public void Should_Not_Close_When_Pointer_Is_Moved_Over_ToolTip()

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));

mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -277,16 +332,16 @@ public void Should_Not_Close_When_Pointer_Is_Moved_From_ToolTip_To_Original_Cont
var mouseEnter = SetupWindowAndGetMouseEnterAction(target);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -311,12 +366,12 @@ public void Should_Close_When_Pointer_Is_Moved_From_ToolTip_To_Another_Control()
var mouseEnter = SetupWindowAndGetMouseEnterAction(panel);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

mouseEnter(other);

Expand Down Expand Up @@ -352,15 +407,15 @@ public void New_ToolTip_Replaces_Other_ToolTip_Immediately()
Assert.False(ToolTip.GetIsOpen(other)); // long delay

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target)); // no delay
AssertToolTipOpen(target); // no delay

mouseEnter(other);
Assert.True(ToolTip.GetIsOpen(other)); // delay skipped, a tooltip was already open

// Now disable the between-show system

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

ToolTip.SetBetweenShowDelay(other, -1);

Expand Down Expand Up @@ -389,7 +444,7 @@ public void ToolTip_Events_Order_Is_Defined()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand Down Expand Up @@ -442,7 +497,7 @@ public void ToolTip_Can_Be_Replaced_On_The_Fly_Via_Opening_Event()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand All @@ -463,7 +518,7 @@ public void Should_Close_When_Pointer_Leaves_Window()
var mouseEnter = SetupWindowAndGetMouseEnterAction(target);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var topLevel = TopLevel.GetTopLevel(target);
topLevel.PlatformImpl.Input(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel,
Expand All @@ -476,6 +531,8 @@ public void Should_Close_When_Pointer_Leaves_Window()
private Action<Control> SetupWindowAndGetMouseEnterAction(Control windowContent, [CallerMemberName] string testName = null)
{
var windowImpl = MockWindowingPlatform.CreateWindowMock();
SetupWindowMock(windowImpl);

var hitTesterMock = new Mock<IHitTester>();

var window = new Window(windowImpl.Object)
Expand Down
Loading