Skip to content

ToolTip

Header: winui/ui/dialogs/ToolTip.hpp. Spec: winui/ui/dialogs/ToolTip.hpp.

Hover text for an element, opened after the pointer rests on the anchor and closed when the pointer leaves or clicks.

A ToolTip wraps its anchor. It takes the anchor's place in the tree and passes layout, rendering, input, focus and automation through, so the anchor behaves as it would unwrapped. The tip content lives outside the main tree. The window lays it out and draws it on the theme's tooltip surface. The open delay counts down in tick_override(), so the host must tick the Window, as the built-in window host does. A tip opened by the mouse docks to the pointer, inflated by the offsets. For plain text on any element, Element::set_tool_tip() is shorter.

Example:

auto  toolbar = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
auto  tip     = std::make_unique<ui::ToolTipControl>();
auto* save    = tip->set_anchor(std::make_unique<ui::ButtonControl>("Save"));
tip->set_content(std::make_unique<ui::TextControl>("Saves the current document"));
tip->set_placement(ui::PopupPlacement::Below);
save->on_click([] { save_document(); });
toolbar->add(std::move(tip)); // the ToolTip takes the Button's place in the tree

// Declaratively:
toolbar->add(ui::ToolTip{
    .anchor  = ui::Button{.text = "Save"},
    .content = ui::Text{.text = "Saves the current document"},
}.build());

ui::ToolTip

A hover tip over an anchor. Builds a ui::ToolTipControl.

The built element wraps the anchor, which lays out and takes input as usual; the tip shows after the pointer rests on it for .delay and hides on a press, a key or the pointer leaving.

Example:

window.set_content(ui::ToolTip{
    .placement = ui::PopupPlacement::Below,
    .anchor    = ui::Button{ .text = "Open", .on_click = [&] { open_file(); } },
    .content   = ui::Text{ .text = "Open a document from disk" },
});

Every field, in the order a designated initializer names them:

ui::ToolTip{
    .delay, .placement, .horizontal_offset, .vertical_offset, .placement_rect, .style, .margin,
    .width, .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .accessibility, .on_pointer_entered,
    .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control,
    .anchor, .content
}

Fields, in declaration order (designators must follow it):

Field Type Default
delay ui::Binding<ui::Duration> {ui::Duration{800.0}} How long the pointer must rest on the anchor before the tip opens; 800 ms by default.
placement ui::Binding<ui::PopupPlacement> {ui::PopupPlacement::Above} Which side of the pointer (or placement_rect) the tip sits on.
horizontal_offset ui::Binding<float> {20.0f} How far the dock rect is inflated on the left and right, in DIPs.
vertical_offset ui::Binding<float> {20.0f} How far the dock rect is inflated on the top and bottom, in DIPs.
placement_rect ui::Binding<std::optional<ui::Rect>> A rect in the anchor's coordinates to dock to instead of the pointer.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::ToolTip> Out: the handle that receives the built control.
anchor ui::Spec The element the tip belongs to; it takes the ToolTip's place in the layout.
content ui::Spec What the tip shows, usually a ui::Text.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::ToolTipControl; the caller owns it.

ui::ToolTipControl

Derives from ui::Element.

Hover text for an element, opened after the pointer rests on the anchor and closed when the pointer leaves or clicks.

A ToolTip wraps its anchor. It takes the anchor's place in the tree and passes layout, rendering, input, focus and automation through, so the anchor behaves as it would unwrapped. The tip content lives outside the main tree. The window lays it out and draws it on the theme's tooltip surface. The open delay counts down in tick_override(), so the host must tick the Window, as the built-in window host does. A tip opened by the mouse docks to the pointer, inflated by the offsets. For plain text on any element, Element::set_tool_tip() is shorter.

Example:

auto  toolbar = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
auto  tip     = std::make_unique<ui::ToolTipControl>();
auto* save    = tip->set_anchor(std::make_unique<ui::ButtonControl>("Save"));
tip->set_content(std::make_unique<ui::TextControl>("Saves the current document"));
tip->set_placement(ui::PopupPlacement::Below);
save->on_click([] { save_document(); });
toolbar->add(std::move(tip)); // the ToolTip takes the Button's place in the tree

// Declaratively:
toolbar->add(ui::ToolTip{
    .anchor  = ui::Button{.text = "Save"},
    .content = ui::Text{.text = "Saves the current document"},
}.build());

ToolTipControl

ToolTipControl() = default

An empty tooltip: no anchor, no content, 800 ms delay, placed Above.

set_anchor

template <typename T>
T* set_anchor(std::unique_ptr<T> anchor)

Takes ownership of the anchor element, the element the tip describes, and returns a non-owning observer, as FlyoutControl::set_anchor() and ScrollControl::set_child() do. Replaces and destroys any previous anchor.

anchor

Element* anchor() const

The anchor, or null before set_anchor(). Owned by this ToolTip.

set_content

template <typename T>
T* set_content(std::unique_ptr<T> content)

Takes ownership of the floating hover content and returns a non-owning observer. The content is not part of the main tree. The window lays it out and draws it separately. The tooltip's background, border and padding come from the theme, so the content needs none.

content

Element* content() const

The floating content, or null before set_content(). Owned by this ToolTip.

is_open

bool is_open() const

Whether the tip is showing. It opens once the hover delay elapses or show_at() is called. It closes when the pointer leaves, on a click, on close(), or when the popup is dismissed.

delay

Duration delay() const

How long the pointer must stay over the anchor before the tooltip opens; 800 ms by default.

set_delay

void set_delay(Duration delay)

Sets the delay. It also applies to a countdown already running, which keeps its elapsed time.

placement

PopupPlacement placement() const

Which side of the dock rect the tooltip opens against; Above by default. It flips to Below near the top edge of the window.

set_placement

void set_placement(PopupPlacement placement)

Sets the side. Takes effect the next time the popup is placed.

horizontal_offset

float horizontal_offset() const

How far the dock rect is inflated on the left and right before the tooltip is placed against it, in DIPs; 20 by default. vertical_offset() does the same for the top and bottom.

set_horizontal_offset

void set_horizontal_offset(float offset)

Sets the horizontal inflation in DIPs. A negative value shrinks the dock rect, pulling the tip over it.

vertical_offset

float vertical_offset() const

The vertical inflation in DIPs; 20 by default. See horizontal_offset().

set_vertical_offset

void set_vertical_offset(float offset)

Sets the vertical inflation in DIPs.

placement_rect

const std::optional<Rect>& placement_rect() const

The placement rect, in the anchor's own coordinates. The tooltip docks to it instead of the pointer, so it opens beside the rect and doesn't cover what it describes.

set_placement_rect

void set_placement_rect(std::optional<Rect> rect)

Sets or clears the placement rect. nullopt docks to the pointer again.

focusable

bool focusable() const override

Focusable exactly when the anchor is; focus itself goes to the anchor.

for_each_child

void for_each_child(ChildVisitor visit) const override

The anchor, if any. The tip's content is popup content, outside the tree.

collect_focusables

void collect_focusables(std::vector<Element*>& out) override

The tip stands in for its anchor as one tab stop. Focus itself goes to the anchor.

automation_children

void automation_children(std::vector<Element*>&) const override

No automation children: the anchor is not listed under the tip.

automation_role

AutomationRole automation_role() const override

The anchor's role, name and providers, so screen readers see the anchor. With no anchor, a Group with nothing to act on.

automation_name_fallback

std::string automation_name_fallback() const override

automation_name()'s fallback when accessibility().name is empty. Default: no inherent name, correct for controls with no visible text of their own (Slider, ProgressBar), which need an explicit accessibility().name from the caller. A control with its own visible text (Button, CheckBox, TextControl, ...) overrides this to return it.

show_at

void show_at(Point pointer)

Opens the tip now, docked to pointer (window DIPs) as a tip opened by the mouse is, whatever the pointer does over the anchor. The window shows Element::set_tool_tip() text this way. Calling it while open moves the dock point.

close

void close()

Closes the tip and resets the hover countdown. Nothing changes on screen when it is already closed.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

Implement these instead of measure() and arrange(). The base class has already applied margin, size limits and alignment by the time these run. measure_override() is pure (every concrete Element must size itself); arrange_override() defaults to a no-op since most leaves need nothing beyond having bounds() set, which the base already did.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

What the control draws, in layout coordinates; render() has already applied the visual layer. A container draws its children through their render(), never their render_override().

pointer_override

bool pointer_override(const PointerEvent& e) override

Forwards every event to the anchor and tracks hover. A move starts the countdown, leaving cancels it or closes the tip, and a press closes the tip.

handled_pointer_override

void handled_pointer_override(const PointerEvent& e) override

A press the anchor took closes the tip too.

tick_override

void tick_override(Duration delta) override

Ticks the anchor, then advances the hover countdown and opens the tip once it reaches delay().

key_override

bool key_override(const KeyEvent& e) override

A key for this element: the focused one, or one inside it that did not handle it. Return true when the key did something here, and it goes no further; false lets it bubble on to the parent and then to the shortcuts, a dialog's default button and arrow-key focus moves. Default: false.

text_override

bool text_override(const TextInputEvent& text_event) override

Typed text for this element, routed like key_override(). Default: false.

focus_changed

void focus_changed(bool focused) override

Keyboard focus arrived (true) or left (false); is_focused() already says so. The element is redrawn either way. No-op by default.

focus_visible_changed

void focus_visible_changed(bool visible) override

focus_visible() changed; the element is redrawn either way. No-op by default.

draws_focus_ring

bool draws_focus_ring() const override

None of its own: the anchor inside it draws its own focus ring.

Tests

  • tests/test_tool_tip.cpp