#nullable enable using System; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; namespace Avalonia.Controls { /// /// A control which pops up a hint when a control is hovered. /// /// /// You will probably not want to create a control directly: if added to /// the tree it will act as a simple styled to look like a tooltip. /// To add a tooltip to a control, use the attached property, /// assigning the content that you want displayed. /// [PseudoClasses(":open")] public class ToolTip : ContentControl, IPopupHostProvider { /// /// Defines the ToolTip.Tip attached property. /// public static readonly AttachedProperty TipProperty = AvaloniaProperty.RegisterAttached("Tip"); /// /// Defines the ToolTip.IsOpen attached property. /// public static readonly AttachedProperty IsOpenProperty = AvaloniaProperty.RegisterAttached("IsOpen"); /// /// Defines the ToolTip.Placement property. /// public static readonly AttachedProperty PlacementProperty = AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer); /// /// Defines the ToolTip.HorizontalOffset property. /// public static readonly AttachedProperty HorizontalOffsetProperty = AvaloniaProperty.RegisterAttached("HorizontalOffset"); /// /// Defines the ToolTip.VerticalOffset property. /// public static readonly AttachedProperty VerticalOffsetProperty = AvaloniaProperty.RegisterAttached("VerticalOffset", 20); /// /// Defines the ToolTip.ShowDelay property. /// public static readonly AttachedProperty ShowDelayProperty = AvaloniaProperty.RegisterAttached("ShowDelay", 400); /// /// Stores the current instance in the control. /// internal static readonly AttachedProperty ToolTipProperty = AvaloniaProperty.RegisterAttached("ToolTip"); private IPopupHost? _popupHost; private Action? _popupHostChangedHandler; /// /// Initializes static members of the class. /// static ToolTip() { TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged); IsOpenProperty.Changed.Subscribe(IsOpenChanged); HorizontalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); VerticalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); PlacementProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged); } /// /// Gets the value of the ToolTip.Tip attached property. /// /// The control to get the property from. /// /// The content to be displayed in the control's tooltip. /// public static object? GetTip(Control element) { return element.GetValue(TipProperty); } /// /// Sets the value of the ToolTip.Tip attached property. /// /// The control to get the property from. /// The content to be displayed in the control's tooltip. public static void SetTip(Control element, object? value) { element.SetValue(TipProperty, value); } /// /// Gets the value of the ToolTip.IsOpen attached property. /// /// The control to get the property from. /// /// A value indicating whether the tool tip is visible. /// public static bool GetIsOpen(Control element) { return element.GetValue(IsOpenProperty); } /// /// Sets the value of the ToolTip.IsOpen attached property. /// /// The control to get the property from. /// A value indicating whether the tool tip is visible. public static void SetIsOpen(Control element, bool value) { element.SetValue(IsOpenProperty, value); } /// /// Gets the value of the ToolTip.Placement attached property. /// /// The control to get the property from. /// /// A value indicating how the tool tip is positioned. /// public static PlacementMode GetPlacement(Control element) { return element.GetValue(PlacementProperty); } /// /// Sets the value of the ToolTip.Placement attached property. /// /// The control to get the property from. /// A value indicating how the tool tip is positioned. public static void SetPlacement(Control element, PlacementMode value) { element.SetValue(PlacementProperty, value); } /// /// Gets the value of the ToolTip.HorizontalOffset attached property. /// /// The control to get the property from. /// /// A value indicating how the tool tip is positioned. /// public static double GetHorizontalOffset(Control element) { return element.GetValue(HorizontalOffsetProperty); } /// /// Sets the value of the ToolTip.HorizontalOffset attached property. /// /// The control to get the property from. /// A value indicating how the tool tip is positioned. public static void SetHorizontalOffset(Control element, double value) { element.SetValue(HorizontalOffsetProperty, value); } /// /// Gets the value of the ToolTip.VerticalOffset attached property. /// /// The control to get the property from. /// /// A value indicating how the tool tip is positioned. /// public static double GetVerticalOffset(Control element) { return element.GetValue(VerticalOffsetProperty); } /// /// Sets the value of the ToolTip.VerticalOffset attached property. /// /// The control to get the property from. /// A value indicating how the tool tip is positioned. public static void SetVerticalOffset(Control element, double value) { element.SetValue(VerticalOffsetProperty, value); } /// /// Gets the value of the ToolTip.ShowDelay attached property. /// /// The control to get the property from. /// /// A value indicating the time, in milliseconds, before a tool tip opens. /// public static int GetShowDelay(Control element) { return element.GetValue(ShowDelayProperty); } /// /// Sets the value of the ToolTip.ShowDelay attached property. /// /// The control to get the property from. /// A value indicating the time, in milliseconds, before a tool tip opens. public static void SetShowDelay(Control element, int value) { element.SetValue(ShowDelayProperty, value); } private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) { var control = (Control)e.Sender; var newValue = (bool)e.NewValue!; ToolTip? toolTip; if (newValue) { var tip = GetTip(control); if (tip == null) return; toolTip = control.GetValue(ToolTipProperty); if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) { toolTip?.Close(); toolTip = tip as ToolTip ?? new ToolTip { Content = tip }; control.SetValue(ToolTipProperty, toolTip); } toolTip.Open(control); } else { toolTip = control.GetValue(ToolTipProperty); toolTip?.Close(); } toolTip?.UpdatePseudoClasses(newValue); } private static void RecalculatePositionOnPropertyChanged(AvaloniaPropertyChangedEventArgs args) { var control = (Control)args.Sender; var tooltip = control.GetValue(ToolTipProperty); if (tooltip == null) { return; } tooltip.RecalculatePosition(control); } IPopupHost? IPopupHostProvider.PopupHost => _popupHost; event Action? 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(); _popupHost = OverlayPopupHost.CreatePopupHost(control, null); _popupHost.SetChild(this); ((ISetLogicalParent)_popupHost).SetParent(control); _popupHost.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); WindowManagerAddShadowHintChanged(_popupHost, false); _popupHost.Show(); _popupHostChangedHandler?.Invoke(_popupHost); } private void Close() { if (_popupHost != null) { _popupHost.SetChild(null); _popupHost.Dispose(); _popupHost = null; _popupHostChangedHandler?.Invoke(null); } } private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) { if (host is PopupRoot pr) { pr.PlatformImpl.SetWindowManagerAddShadowHint(hint); } } private void UpdatePseudoClasses(bool newValue) { PseudoClasses.Set(":open", newValue); } } }