Skip to content
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

ToolTip should use Popup internally + fix Popups not closing when placement target is closed #15358

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 2 additions & 6 deletions src/Avalonia.Controls/ContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,7 @@ private void Open(Control control, Control placementTarget, PlacementMode placem
_popup.KeyUp += PopupKeyUp;
}

if (_popup.Parent != control)
{
((ISetLogicalParent)_popup).SetParent(null);
((ISetLogicalParent)_popup).SetParent(control);
}
_popup.SetPopupParent(control);

_popup.Placement = placement;

Expand Down Expand Up @@ -383,7 +379,7 @@ private void PopupClosed(object? sender, EventArgs e)

if (_attachedControls is null || _attachedControls.Count == 0)
{
((ISetLogicalParent)_popup!).SetParent(null);
_popup!.SetPopupParent(null);
}

RaiseEvent(new RoutedEventArgs
Expand Down
19 changes: 7 additions & 12 deletions src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ protected virtual bool HideCore(bool canCancel = true)
IsOpen = false;
Popup.IsOpen = false;

((ISetLogicalParent)Popup).SetParent(null);
Popup.PlacementTarget = null;
Popup.SetPopupParent(null);

// Ensure this isn't active
_transientDisposable?.Dispose();
Expand All @@ -205,6 +206,9 @@ protected virtual bool HideCore(bool canCancel = true)

OnClosed();

// Set to null after all other events.
Target = null;

return true;
}

Expand All @@ -228,17 +232,8 @@ protected virtual bool ShowAtCore(Control placementTarget, bool showAtPointer =
}
}

if (Popup.Parent != null && Popup.Parent != placementTarget)
{
((ISetLogicalParent)Popup).SetParent(null);
}

if (Popup.Parent == null || Popup.PlacementTarget != placementTarget)
{
Popup.PlacementTarget = Target = placementTarget;
((ISetLogicalParent)Popup).SetParent(placementTarget);
Popup.TemplatedParent = placementTarget.TemplatedParent;
}
Popup.PlacementTarget = Target = placementTarget;
Popup.SetPopupParent(placementTarget);

if (Popup.Child == null)
{
Expand Down
5 changes: 4 additions & 1 deletion src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.VisualTree;

Expand Down Expand Up @@ -135,7 +136,9 @@ void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualS
}

double IManagedPopupPositionerPopup.Scaling => 1;


// TODO12: mark PrivateAPI or internal.
[Unstable("PopupHost is consireded an internal API. Use Popup or any Popup-based controls (Flyout, Tooltip) instead.")]
public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver)
{
if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
Expand Down
17 changes: 17 additions & 0 deletions src/Avalonia.Controls/Primitives/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,23 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
}
}

/// <summary>
/// Helper method to set popup's styling and templated parent.
/// </summary>
internal void SetPopupParent(Control? newParent)
{
if (Parent != null && Parent != newParent)
{
((ISetLogicalParent)this).SetParent(null);
}

if (Parent == null || PlacementTarget != newParent)
{
((ISetLogicalParent)this).SetParent(newParent);
TemplatedParent = newParent?.TemplatedParent;
}
}

private void UpdateHostPosition(IPopupHost popupHost, Control placementTarget)
{
popupHost.ConfigurePosition(
Expand Down
81 changes: 37 additions & 44 deletions src/Avalonia.Controls/ToolTip.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
Expand Down Expand Up @@ -79,19 +80,16 @@ public class ToolTip : ContentControl, IPopupHostProvider
internal static readonly AttachedProperty<ToolTip?> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip?>("ToolTip");

private IPopupHost? _popupHost;
private Popup? _popup;
private Action<IPopupHost?>? _popupHostChangedHandler;
private CompositeDisposable? _subscriptions;

/// <summary>
/// Initializes static members of the <see cref="ToolTip"/> class.
/// </summary>
static ToolTip()
{
IsOpenProperty.Changed.Subscribe(IsOpenChanged);

HorizontalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
VerticalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
PlacementProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
}

internal Control? AdornedControl { get; private set; }
Expand Down Expand Up @@ -309,69 +307,64 @@ private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
}
}

private static void RecalculatePositionOnPropertyChanged(AvaloniaPropertyChangedEventArgs args)
{
var control = (Control)args.Sender;
var tooltip = control.GetValue(ToolTipProperty);
if (tooltip == null)
{
return;
}
IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host;

tooltip.RecalculatePosition(control);
}

IPopupHost? IPopupHostProvider.PopupHost => _popupHost;

internal IPopupHost? PopupHost => _popupHost;
internal IPopupHost? PopupHost => _popup?.Host;

event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged
{
add => _popupHostChangedHandler += value;
remove => _popupHostChangedHandler -= value;
}

internal void RecalculatePosition(Control control)
{
_popupHost?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
}

private void Open(Control control)
{
Close();

if (_popup is null)
{
_popup = new Popup();
_popup.Child = this;
_popup.WindowManagerAddShadowHint = false;

_popupHost = OverlayPopupHost.CreatePopupHost(control, null);
_popupHost.SetChild(this);
((ISetLogicalParent)_popupHost).SetParent(control);
ApplyTemplatedParent(this, control.TemplatedParent);
_popup.Opened += OnPopupOpened;
_popup.Closed += OnPopupClosed;
}

_popupHost.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
_subscriptions = new CompositeDisposable(new[]
{
_popup.Bind(Popup.HorizontalOffsetProperty, control.GetBindingObservable(HorizontalOffsetProperty)),
_popup.Bind(Popup.VerticalOffsetProperty, control.GetBindingObservable(VerticalOffsetProperty)),
_popup.Bind(Popup.PlacementProperty, control.GetBindingObservable(PlacementProperty))
});

WindowManagerAddShadowHintChanged(_popupHost, false);
_popup.PlacementTarget = control;
_popup.SetPopupParent(control);

_popupHost.Show();
_popupHostChangedHandler?.Invoke(_popupHost);
_popup.IsOpen = true;
}

private void Close()
{
if (_popupHost != null)
_subscriptions?.Dispose();

if (_popup is not null)
{
_popupHost.SetChild(null);
_popupHost.Dispose();
_popupHost = null;
_popupHostChangedHandler?.Invoke(null);
Closed?.Invoke(this, EventArgs.Empty);
_popup.IsOpen = false;
_popup.SetPopupParent(null);
_popup.PlacementTarget = null;
}
}

private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint)
private void OnPopupClosed(object? sender, EventArgs e)
{
if (host is PopupRoot pr)
{
pr.WindowManagerAddShadowHint = hint;
}
_popupHostChangedHandler?.Invoke(null);
Closed?.Invoke(this, EventArgs.Empty);
}

private void OnPopupOpened(object? sender, EventArgs e)
{
_popupHostChangedHandler?.Invoke(((Popup)sender!).Host);
}

private void UpdatePseudoClasses(bool newValue)
Expand Down
34 changes: 1 addition & 33 deletions src/Avalonia.Controls/ToolTipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public ToolTipService(IInputManager inputManager)
_subscriptions = new CompositeDisposable(
inputManager.Process.Subscribe(InputManager_OnProcess),
ToolTip.ServiceEnabledProperty.Changed.Subscribe(ServiceEnabledChanged),
ToolTip.TipProperty.Changed.Subscribe(TipChanged),
ToolTip.IsOpenProperty.Changed.Subscribe(TipOpenChanged));
ToolTip.TipProperty.Changed.Subscribe(TipChanged));
}

public void Dispose()
Expand Down Expand Up @@ -122,30 +121,6 @@ private void TipChanged(AvaloniaPropertyChangedEventArgs e)
}
}

private void TipOpenChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;

if (e.OldValue is false && e.NewValue is true)
{
control.DetachedFromVisualTree += ControlDetaching;
control.EffectiveViewportChanged += ControlEffectiveViewportChanged;
}
else if (e.OldValue is true && e.NewValue is false)
{
control.DetachedFromVisualTree -= ControlDetaching;
control.EffectiveViewportChanged -= ControlEffectiveViewportChanged;
}
}

private void ControlDetaching(object? sender, VisualTreeAttachmentEventArgs e)
{
var control = (Control)sender!;
control.DetachedFromVisualTree -= ControlDetaching;
control.EffectiveViewportChanged -= ControlEffectiveViewportChanged;
Close(control);
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
}

private void OnTipControlChanged(Control? oldValue, Control? newValue)
{
StopTimer();
Expand Down Expand Up @@ -184,13 +159,6 @@ private void OnTipControlChanged(Control? oldValue, Control? newValue)
}
}

private void ControlEffectiveViewportChanged(object? sender, Layout.EffectiveViewportChangedEventArgs e)
{
var control = (Control)sender!;
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
toolTip?.RecalculatePosition(control);
}

private void ToolTipClosed(object? sender, EventArgs e)
{
_lastTipCloseTime = DateTime.UtcNow.Ticks;
Expand Down
Loading