diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml
index 29df11510c..79114bc9de 100644
--- a/samples/ControlCatalog/Pages/ToolTipPage.xaml
+++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml
@@ -1,22 +1,34 @@
-
- ToolTip
- A control which pops up a hint when a control is hovered
+
+ ToolTip
+ A control which pops up a hint when a control is hovered
-
-
-
-
- ToolTip
- A control which pops up a hint when a control is hovered
-
-
- Hover Here
-
+
+
+
+
+
+ ToolTip
+ A control which pops up a hint when a control is hovered
+
+
+ Hover Here
+
+
+ And Here
+
+
-
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index daea187a69..5cd3b22fc9 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -277,7 +277,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDetachedFromLogicalTree(e);
_topLevel = null;
-
+
if (_popupRoot != null)
{
((ISetLogicalParent)_popupRoot).SetParent(null);
@@ -327,34 +327,40 @@ namespace Avalonia.Controls.Primitives
///
/// The popup's position in screen coordinates.
protected virtual Point GetPosition()
+ {
+ return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot,
+ HorizontalOffset, VerticalOffset);
+ }
+
+ internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var zero = default(Point);
- var mode = PlacementMode;
- var target = PlacementTarget ?? this.GetVisualParent();
+ var mode = placement;
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
- }
+ }
switch (mode)
{
case PlacementMode.Pointer:
- if(PopupRoot != null)
+ if (popupRoot != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
- var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
- return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
+ var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling,
+ verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling);
+ return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
}
return default(Point);
case PlacementMode.Bottom:
-
- return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero;
+ return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ??
+ zero;
case PlacementMode.Right:
- return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero;
+ return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index e1b69637af..e45f30f818 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -3,11 +3,7 @@
using System;
using System.Reactive.Linq;
-using System.Reactive.Subjects;
using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.Threading;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -29,29 +25,50 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterAttached("Tip");
///
- /// The popup window used to display the active tooltip.
+ /// Defines the ToolTip.IsOpen attached property.
///
- private static PopupRoot s_popup;
+ public static readonly AttachedProperty IsOpenProperty =
+ AvaloniaProperty.RegisterAttached("IsOpen");
///
- /// The control that the currently visible tooltip is attached to.
+ /// Defines the ToolTip.Placement property.
///
- private static Control s_current;
+ public static readonly AttachedProperty PlacementProperty =
+ AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer);
///
- /// Observable fired when a tooltip should be displayed for a control. The output from this
- /// observable is throttled and calls when the time
- /// period expires.
+ /// Defines the ToolTip.HorizontalOffset property.
///
- private static readonly Subject s_show = new Subject();
+ 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 curernt instance in the control.
+ ///
+ private static readonly AttachedProperty ToolTipProperty =
+ AvaloniaProperty.RegisterAttached("ToolTip");
+
+ private PopupRoot _popup;
///
/// Initializes static members of the class.
///
static ToolTip()
{
- TipProperty.Changed.Subscribe(TipChanged);
- s_show.Throttle(TimeSpan.FromSeconds(0.5), AvaloniaScheduler.Instance).Subscribe(ShowToolTip);
+ TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged);
+ IsOpenProperty.Changed.Subscribe(IsOpenChanged);
}
///
@@ -77,101 +94,160 @@ namespace Avalonia.Controls
}
///
- /// called when the property changes on a control.
+ /// Gets the value of the ToolTip.IsOpen attached property.
///
- /// The event args.
- private static void TipChanged(AvaloniaPropertyChangedEventArgs e)
+ /// The control to get the property from.
+ ///
+ /// A value indicating whether the tool tip is visible.
+ ///
+ public static bool GetIsOpen(Control element)
{
- var control = (Control)e.Sender;
+ return element.GetValue(IsOpenProperty);
+ }
- if (e.OldValue != null)
- {
- control.PointerEnter -= ControlPointerEnter;
- control.PointerLeave -= ControlPointerLeave;
- }
+ ///
+ /// 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);
+ }
- if (e.NewValue != null)
- {
- control.PointerEnter += ControlPointerEnter;
- control.PointerLeave += ControlPointerLeave;
- }
+ ///
+ /// 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);
}
///
- /// Shows a tooltip for the specified control.
+ /// Sets the value of the ToolTip.Placement attached property.
///
- /// The control.
- private static void ShowToolTip(Control control)
+ /// The control to get the property from.
+ /// A value indicating how the tool tip is positioned.
+ public static void SetPlacement(Control element, PlacementMode value)
{
- if (control != null && control.IsVisible && control.GetVisualRoot() != null)
- {
- var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
+ element.SetValue(PlacementProperty, value);
+ }
- if (cp.HasValue && control.IsVisible && new Rect(control.Bounds.Size).Contains(cp.Value))
- {
- var position = control.PointToScreen(cp.Value) + new Vector(0, 22);
-
- if (s_popup == null)
- {
- s_popup = new PopupRoot();
- s_popup.Content = new ToolTip();
- }
- else
- {
- ((ISetLogicalParent)s_popup).SetParent(null);
- }
-
- ((ISetLogicalParent)s_popup).SetParent(control);
- ((ToolTip)s_popup.Content).Content = GetTip(control);
- s_popup.Position = position;
- s_popup.Show();
-
- s_current = control;
- }
- }
+ ///
+ /// 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);
}
///
- /// Called when the pointer enters a control with an attached tooltip.
+ /// Gets the value of the ToolTip.ShowDelay attached property.
///
- /// The event sender.
- /// The event args.
- private static void ControlPointerEnter(object sender, PointerEventArgs e)
+ /// 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)
{
- s_current = (Control)sender;
- s_show.OnNext(s_current);
+ return element.GetValue(ShowDelayProperty);
}
///
- /// Called when the pointer leaves a control with an attached tooltip.
+ /// Sets the value of the ToolTip.ShowDelay attached property.
///
- /// The event sender.
- /// The event args.
- private static void ControlPointerLeave(object sender, PointerEventArgs e)
+ /// 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)
{
- var control = (Control)sender;
+ element.SetValue(ShowDelayProperty, value);
+ }
+
+ private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var control = (Control)e.Sender;
- if (control == s_current)
+ if ((bool)e.NewValue)
{
- if (s_popup != null)
+ var tip = GetTip(control);
+ if (tip == null) return;
+
+ var toolTip = control.GetValue(ToolTipProperty);
+ if (toolTip == null || (tip != toolTip && tip != toolTip.Content))
{
- DisposeTooltip();
- s_show.OnNext(null);
+ toolTip?.Close();
+
+ toolTip = tip as ToolTip ?? new ToolTip { Content = tip };
+ control.SetValue(ToolTipProperty, toolTip);
}
+
+ toolTip.Open(control);
+ }
+ else
+ {
+ var toolTip = control.GetValue(ToolTipProperty);
+ toolTip?.Close();
}
}
- private static void DisposeTooltip()
+ private void Open(Control control)
{
- if (s_popup != null)
- {
- // Clear the ToolTip's Content in case it has control content: this will
- // reset its visual parent allowing it to be used again.
- ((ToolTip)s_popup.Content).Content = null;
+ Close();
+
+ _popup = new PopupRoot { Content = this };
+ ((ISetLogicalParent)_popup).SetParent(control);
+ _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
+ GetHorizontalOffset(control), GetVerticalOffset(control));
+ _popup.Show();
+ }
- // Dispose of the popup.
- s_popup.Dispose();
- s_popup = null;
+ private void Close()
+ {
+ if (_popup != null)
+ {
+ _popup.Content = null;
+ _popup.Hide();
+ _popup = null;
}
}
}
diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs
new file mode 100644
index 0000000000..bfd7ef0f33
--- /dev/null
+++ b/src/Avalonia.Controls/ToolTipService.cs
@@ -0,0 +1,98 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Handeles interaction with controls.
+ ///
+ internal sealed class ToolTipService
+ {
+ public static ToolTipService Instance { get; } = new ToolTipService();
+
+ private DispatcherTimer _timer;
+
+ private ToolTipService() { }
+
+ ///
+ /// called when the property changes on a control.
+ ///
+ /// The event args.
+ internal void TipChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var control = (Control)e.Sender;
+
+ if (e.OldValue != null)
+ {
+ control.PointerEnter -= ControlPointerEnter;
+ control.PointerLeave -= ControlPointerLeave;
+ }
+
+ if (e.NewValue != null)
+ {
+ control.PointerEnter += ControlPointerEnter;
+ control.PointerLeave += ControlPointerLeave;
+ }
+ }
+
+ ///
+ /// Called when the pointer enters a control with an attached tooltip.
+ ///
+ /// The event sender.
+ /// The event args.
+ private void ControlPointerEnter(object sender, PointerEventArgs e)
+ {
+ StopTimer();
+
+ var control = (Control)sender;
+ var showDelay = ToolTip.GetShowDelay(control);
+ if (showDelay == 0)
+ {
+ Open(control);
+ }
+ else
+ {
+ StartShowTimer(showDelay, control);
+ }
+ }
+
+ ///
+ /// Called when the pointer leaves a control with an attached tooltip.
+ ///
+ /// The event sender.
+ /// The event args.
+ private void ControlPointerLeave(object sender, PointerEventArgs e)
+ {
+ var control = (Control)sender;
+ Close(control);
+ }
+
+ private void StartShowTimer(int showDelay, Control control)
+ {
+ _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) };
+ _timer.Tick += (o, e) => Open(control);
+ _timer.Start();
+ }
+
+ private void Open(Control control)
+ {
+ StopTimer();
+
+ ToolTip.SetIsOpen(control, true);
+ }
+
+ private void Close(Control control)
+ {
+ StopTimer();
+
+ ToolTip.SetIsOpen(control, false);
+ }
+
+ private void StopTimer()
+ {
+ _timer?.Stop();
+ _timer = null;
+ }
+ }
+}
\ No newline at end of file