This document captures design and implementation notes for TooltipHost and the tooltip overlay system.
For end-user usage and examples, see Tooltip.
src/XenoAtom.Terminal.UI/Controls/TooltipHost.cssrc/XenoAtom.Terminal.UI/Controls/TooltipWindow.cs (internal overlay window)src/XenoAtom.Terminal.UI/Styling/TooltipStyle.cssrc/XenoAtom.Terminal.UI/VisualExtensions.cs (.Tooltip(...) convenience)src/XenoAtom.Terminal.UI/TerminalApp.cs owns a single active tooltip window and routes it through the window layer.src/XenoAtom.Terminal.UI/Controls/BreakdownChart.cs uses TooltipWindow directly for segment hover tooltips (with
a pointer-based anchor rectangle).src/XenoAtom.Terminal.UI.Tests/TooltipTests.cssamples/ControlsDemo/Demos/TooltipDemo.csTooltipHost is the public wrapper control:
TooltipHost : ContentVisual, IAnimatedVisual (sealed)Properties:
Content : Visual? (inherited from ContentVisual)TooltipContent : Visual?ShowDelayMilliseconds : int (default 500)Placement : PopupPlacement (default Below)OffsetX : int (default 0)OffsetY : int (default 1)Convenience API:
VisualExtensions.Tooltip(this Visual visual, Visual tooltip) -> TooltipHost
TooltipContent.TooltipWindow is an internal ContentVisual that renders a tooltip surface in window-layer coordinates.
It is created and managed by TooltipHost (and some other controls, e.g. BreakdownChart).
Tooltips require the window layer, therefore:
TerminalApp.ShowTooltipWindowTooltips are implemented as "windows" via the window layer, but with non-interactive characteristics:
TooltipWindow.IsHitTestVisible = false (so it is excluded from pointer hit testing)TooltipWindow.IsEnabled = false (so even if hit-tested, it does not receive input)TooltipWindow.Focusable = false (so it does not steal focus when shown)TooltipHost implements IAnimatedVisual and is automatically registered/unregistered when attached/detached because
Visual.AttachToApp registers IAnimatedVisual instances.
Implementation detail:
TooltipHost.NextAnimationTick currently returns 0, which means the app considers it eligible to run on every
animation pass. The actual logic is lightweight and exits early when no tooltip work is needed.On each animation pass:
TooltipContent is null: close tooltip and clear any scheduled show ticknow + delaynow >= scheduled, open the tooltip window and clear the scheduleWhen TooltipContent changes:
On detach from app:
TerminalApp keeps a single _activeTooltipWindow.
When a new tooltip window is shown:
Additionally, when other windows (dialogs/popups/context menus) are shown, the app closes the active tooltip window so tooltips do not remain visible behind/over modal UI.
TooltipWindow measures the content to determine the tooltip size, but it always returns flexible hints so the window can be arranged within the fullscreen root rectangle.
Content measurement rules:
TooltipStyle.Padding is applied inside the borderTooltipStyle.MaxWidth optionally caps the max width used for measuring contentInner content constraints:
innerMaxWidth = maxWidth - padding.Horizontal - 2innerMaxHeight = maxHeight - padding.Vertical - 2 (when finite)Arrange computes a popup rectangle:
desiredWidth = desiredContentWidth + padding.Horizontal + 2desiredHeight = desiredContentHeight + padding.Vertical + 2Default position (no anchor): centered in the final rect.
Anchoring:
AnchorRect (explicit rectangle in UI coordinates), orAnchor.Bounds (when AnchorRect is null)Placement is based on PopupPlacement:
Offsets:
OffsetX and OffsetY are applied after placement selection.Final clamping:
Tooltip content is arranged inside:
popupRect minus a 1-cell border and minus TooltipStyle.Padding.TooltipWindow renders:
TooltipStyle.ResolveSurfaceStyle(theme))TooltipStyle.Glyphs and TooltipStyle.ResolveBorderStyle(theme)The tooltip window does not render the tooltip content itself; child rendering draws content on top.
TooltipStyle controls:
PaddingMaxWidth (defaults to 60)SurfaceStyle and BorderStyle overridesGlyphs : LineGlyphs (default: Rounded; glyphs are non-ASCII in code)Default style resolution is theme-driven:
theme.PopupSurface, then theme.SurfaceAlt, then theme.Surfacetheme.BorderStyle(focused: false) and matches the chosen surface backgroundImportant: to prevent style leakage from the underlay (e.g. underline), both ResolveSurfaceStyle and
ResolveBorderStyle explicitly return styles with an explicit text style (via WithTextStyle(style.TextStyle)).
This ensures the tooltip surface/border establishes a stable baseline for any nested content that renders with
Style.None.
TooltipTests validate:
IAnimatedVisual.NextAnimationTick usage for TooltipHost so the app can schedule show ticks more efficiently.