Skip to content

TeachingTip

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

A card that points at a control (or sits against the window) to teach something about it.

The element itself takes no room where it is placed in the tree. While open, its card floats above the window, placed against target() by placement() and placement_alignment(). It falls back to the other sides when there is no room. The card holds an optional icon, the title and subtitle, optional content, optional hero content, and up to two buttons. The tip owns its content and hero content. The target is a non-owning pointer that must outlive the tip or be cleared first. The target usually exists only once the tree is built, so set it right before opening, or give the spec a .target handle, which links in either build order.

Example:

ui::Control<ui::Button> show;
ui::Control<ui::TeachingTip> tip;

window.set_content(ui::VerticalStack{
    .content = {
        ui::Button{.text = "Show TeachingTip", .on_click = [&] { tip->set_open(true); }, .control = show},
        ui::TeachingTip{.title                 = "Save automatically",
                        .subtitle              = "Your work is saved to the cloud as you go.",
                        .action_button_text = "Got it",
                        .close_button_text  = "Close",
                        .target                = show,
                        .placement             = ui::PopupPlacement::Below,
                        .on_closed             = [](ui::PopupCloseReason) { /* remember it */ },
                        .control               = tip,
                        .content               = ui::Text{.text     = "Turn it off in Settings.",
                                                          .wrapping = ui::TextWrapping::WrapWholeWords}},
    },
});

// Or imperatively, next to the target in the same window:
auto  stack  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* target = stack->add(std::make_unique<ui::ButtonControl>("Show TeachingTip"));
auto* t      = stack->add(std::make_unique<ui::TeachingTipControl>());
t->set_title("This is the title");
t->set_subtitle("And this is the subtitle");
t->set_light_dismiss(true);
target->on_click([t, target] {
    t->set_target(target);
    t->set_open(true);
});

ui::TeachingTip

A card pointing at a control, or at the window, to teach something. Builds a ui::TeachingTipControl, which takes no room in layout. Set its target with .target, or through .control once the target exists. Without a target it sits against the window's edge.

Example:

ui::Control<ui::Button> save_button;
ui::Control<ui::TeachingTip> tip;

window.set_content(ui::VerticalStack{ .content = {
    ui::Button{ .text = "Save", .control = save_button },
    ui::TeachingTip{ .title                = "Saving automatically",
                     .subtitle             = "We save your changes as you go.",
                     .close_button_text = "Got it",
                     .placement            = ui::PopupPlacement::Below,
                     .control              = tip },
}});
tip->set_target(save_button);
tip->set_open(true);

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

ui::TeachingTip{
    .title, .subtitle, .is_open, .icon, .action_button_text, .close_button_text,
    .light_dismiss, .target, .placement, .placement_alignment, .placement_margin,
    .hero_content_placement, .tail_visibility, .style, .margin, .width, .height, .min_width,
    .min_height, .max_width, .max_height, .horizontal_alignment, .vertical_alignment, .visible,
    .tool_tip, .enabled, .accessibility, .on_action_button_click, .on_close_button_click,
    .on_opened, .on_closing, .on_closed, .on_pointer_entered, .on_pointer_exited,
    .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control, .content, .hero_content
}

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

Field Type Default
title ui::Binding<std::string> The semibold first line.
subtitle ui::Binding<std::string> The line under the title.
is_open ui::TwoWayField<bool> {false} Whether the tip is open. ui::bind(prop) is two-way. Closing the tip writes false into the property, and a false that on_closing cancels is put back to true.
icon ui::Binding<ui::IconSource> The icon left of the title: a Symbol, a glyph, a path or a picture; empty draws none.
action_button_text ui::Binding<std::string> The accent button's caption; empty shows no action button.
close_button_text ui::Binding<std::string> A text close button; empty (with light dismiss off) shows a corner "X" instead.
light_dismiss ui::Binding<bool> {false} Whether an outside click or Escape closes it.
target ui::ElementLink The element the tail points at, by its handle (.target = save with a ui::Control<ui::Button>), built before or after the tip. Unset for an untargeted tip.
placement ui::Binding<std::optional<ui::PopupPlacement>> The side of the target tried first; the tip moves to another side when there is no room. Unset tries above first, and puts an untargeted tip at the bottom of the window.
placement_alignment ui::Binding<ui::PopupPlacementAlignment> {ui::PopupPlacementAlignment::Center} How the card lines up with the tail: Leading runs it right (or down) from the tail, Trailing left (or up).
placement_margin ui::Binding<ui::Thickness> The gap in DIPs to the target on the side the card is on, or for an untargeted tip the gap from the window's edges (ui::TeachingTipControl::placement_margin()).
hero_content_placement ui::Binding<ui::TeachingTipHeroPlacement> {ui::TeachingTipHeroPlacement::Auto} Whether .hero_content goes above or below the text.
tail_visibility ui::Binding<ui::AutoVisibility> {ui::AutoVisibility::Auto} When the tail pointing at the target is drawn.
on_action_button_click ui::Action Fires when the action button is clicked. Does not close the tip by itself.
on_close_button_click ui::Action Fires when the close button is clicked, before the tip closes.
on_opened ui::Action The tip has opened. A two-way is_open has been written by then.
on_closing ui::Callback<ui::Decision(const ui::PopupClosingArgs&)> Gets why the tip is about to close; return ui::Cancel to keep it open.
on_closed ui::Callback<void(ui::PopupCloseReason)> Raised however it closed, which is where a bound is_open learns about it.
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::TeachingTip> Out: the handle that receives the built control, through which to set_target().
content ui::Spec Extra content under the subtitle.
hero_content ui::Spec An edge-to-edge picture at the top or bottom of the card.

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

build

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

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

ui::TeachingTipControl

Derives from ui::Element.

A card that points at a control (or sits against the window) to teach something about it.

The element itself takes no room where it is placed in the tree. While open, its card floats above the window, placed against target() by placement() and placement_alignment(). It falls back to the other sides when there is no room. The card holds an optional icon, the title and subtitle, optional content, optional hero content, and up to two buttons. The tip owns its content and hero content. The target is a non-owning pointer that must outlive the tip or be cleared first. The target usually exists only once the tree is built, so set it right before opening, or give the spec a .target handle, which links in either build order.

Example:

ui::Control<ui::Button> show;
ui::Control<ui::TeachingTip> tip;

window.set_content(ui::VerticalStack{
    .content = {
        ui::Button{.text = "Show TeachingTip", .on_click = [&] { tip->set_open(true); }, .control = show},
        ui::TeachingTip{.title                 = "Save automatically",
                        .subtitle              = "Your work is saved to the cloud as you go.",
                        .action_button_text = "Got it",
                        .close_button_text  = "Close",
                        .target                = show,
                        .placement             = ui::PopupPlacement::Below,
                        .on_closed             = [](ui::PopupCloseReason) { /* remember it */ },
                        .control               = tip,
                        .content               = ui::Text{.text     = "Turn it off in Settings.",
                                                          .wrapping = ui::TextWrapping::WrapWholeWords}},
    },
});

// Or imperatively, next to the target in the same window:
auto  stack  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* target = stack->add(std::make_unique<ui::ButtonControl>("Show TeachingTip"));
auto* t      = stack->add(std::make_unique<ui::TeachingTipControl>());
t->set_title("This is the title");
t->set_subtitle("And this is the subtitle");
t->set_light_dismiss(true);
target->on_click([t, target] {
    t->set_target(target);
    t->set_open(true);
});

TeachingTipControl

TeachingTipControl()

A closed, untargeted tip with no text, no content and no buttons (only the corner "X"). Not hit-test visible: it draws nothing where it sits, so a press there goes to what is under it.

~TeachingTipControl

~TeachingTipControl() override

set_title

void set_title(std::string title)

Sets the title.

title

const std::string& title() const

The semibold first line of the card, wrapping at whole words; hidden when empty. Screen readers also use it as the name when none is set.

set_subtitle

void set_subtitle(std::string subtitle)

Sets the subtitle.

subtitle

const std::string& subtitle() const

The line under the title, wrapping at whole words; hidden when empty.

set_icon

void set_icon(IconSource icon)

Sets the icon, in its own column left of the titles. A glyph shows at its own size, which is 20 DIPs for a Symbol. Anything else fills a 20 DIP square.

icon

const IconSource& icon() const

The icon left of the titles; empty (the default) drops the icon column.

set_content

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

Takes ownership of the content under the titles and returns a non-owning observer. Replaces and destroys any previous content; nullptr clears it.

content

Element* content() const

The content under the titles, owned by the tip; nullptr when none.

set_hero_content

template <typename T>
T* set_hero_content(std::unique_ptr<T> hero)

Takes ownership of the hero content, typically an image, drawn edge to edge across the card's top or bottom, and returns a non-owning observer. Replaces any previous hero.

hero_content

Element* hero_content() const

The hero content, owned by the tip; nullptr when none.

set_hero_content_placement

void set_hero_content_placement(TeachingTipHeroPlacement placement)

Sets which edge the hero sits at.

hero_content_placement

TeachingTipHeroPlacement hero_content_placement() const

Which edge the hero sits at; Auto (the default) puts the hero away from the tail.

set_action_button_text

void set_action_button_text(std::string text)

Sets the action button's text; empty hides the button. The same goes for the close button. The action button only raises on_action_button_click; the close button also closes the tip.

action_button_text

const std::string& action_button_text() const

The action button's text; empty (the default) hides it.

set_close_button_text

void set_close_button_text(std::string text)

Sets the close button's text; a non-empty one also hides the corner "X".

close_button_text

const std::string& close_button_text() const

The close button's text; empty (the default) hides it.

action_button

ButtonControl* action_button() const

The action button, owned by the tip (for styling); shown only when action_button_text() is non-empty.

close_button

ButtonControl* close_button() const

The close button, owned by the tip; shown only when close_button_text() is non-empty.

alternate_close_button

ButtonControl* alternate_close_button() const

The corner "X". It shows only when there is no close button text and light dismiss is off (alternate_close_visible()).

alternate_close_visible

bool alternate_close_visible() const

Whether the corner "X" is on screen: no close button text and light dismiss off.

enabled

bool enabled() const

A disabled tip grays out its action, close and corner buttons, which then take no input. Its content keeps its own state, and light dismiss and Escape still close it.

set_target

void set_target(Element* target)

The element the tail points at; nullptr for an untargeted tip. Non-owning: a target that is destroyed while the tip points at it leaves the tip untargeted. Setting it makes the next layout re-decide the placement.

target

Element* target() const

The current target (non-owning); nullptr (the default) for an untargeted tip.

set_placement

void set_placement(std::optional<PopupPlacement> placement)

Sets the side tried first; nullopt (the default) tries above the target first, and puts an untargeted tip at the bottom of the window. The next layout re-decides the placement.

placement

std::optional<PopupPlacement> placement() const

The side tried first, before the other sides; nullopt by default. For an untargeted tip, the window edge the card sits against.

set_placement_alignment

void set_placement_alignment(PopupPlacementAlignment alignment)

Sets how the card lines up with the tail; the next layout re-decides the placement.

placement_alignment

PopupPlacementAlignment placement_alignment() const

How the card lines up with the tail on its side; Center by default. Leading runs the card right (or down) from the tail, Trailing left (or up). For an untargeted tip, which end of the window edge the card sits at.

set_placement_margin

void set_placement_margin(Thickness margin)

Sets the placement margin; takes effect at the next layout.

placement_margin

Thickness placement_margin() const

The placement margin, in DIPs; zero by default. For a targeted tip, it is the gap between the target and the card. Only the side the card is on counts, such as top for a card above the target or left for one to its left. For an untargeted tip, it is the gap from the window's edges, added to the fixed edge inset. A card centered on an axis moves by left - right or top - bottom.

set_tail_visibility

void set_tail_visibility(AutoVisibility visibility)

Sets when the tail is drawn.

tail_visibility

AutoVisibility tail_visibility() const

When the tail is drawn; Auto (the default) draws the tail only for a targeted tip.

effective_placement

PopupPlacement effective_placement() const

The side the open tip settled on, Below for an untargeted one. Only meaningful after a layout pass with the tip open. When no side fits, the card is centered over the target and this reports Above.

effective_placement_alignment

PopupPlacementAlignment effective_placement_alignment() const

How the open tip lines up with its tail on that side.

tail_shown

bool tail_shown() const

Whether the tail is drawn for the current placement.

set_light_dismiss

void set_light_dismiss(bool enabled)

Turns light dismiss on or off; false by default. With it on, a click outside or Escape closes the tip with LightDismiss, and the card fills with in-app acrylic instead of its solid background. The tip also takes keyboard focus while open, and the corner "X" is hidden.

light_dismiss

bool light_dismiss() const

Whether light dismiss is on; see set_light_dismiss().

is_open

bool is_open() const

Whether the tip is open; false by default. Stays true when an on_closing() handler cancels.

set_open

void set_open(bool open)

Opens or closes the tip. Opening fires on_opened() at once and places the card at the next layout. A tip that fits nowhere then closes with Programmatic. Closing this way reports Programmatic, and on_closing() can cancel it. Setting the current state does nothing.

on_action_button_click

void on_action_button_click(Callback<void()> handler)

Fires when the action button is clicked; the tip stays open.

on_close_button_click

void on_close_button_click(Callback<void()> handler)

Fires when the close button or the corner "X" is clicked, before the tip closes with CloseButton.

on_closing

void on_closing(Callback<Decision(const PopupClosingArgs&)> handler)

Fires with the reason before an open tip closes; return ui::Cancel to keep the tip open.

on_closed

void on_closed(Callback<void(PopupCloseReason)> handler)

Fires with the reason once the tip has closed (not when canceled).

on_opened

void on_opened(Callback<void()> handler)

Fires from set_open(true) as the tip opens. There is no opening animation to wait for.

automation_role

AutomationRole automation_role() const override

Always AutomationRole::Pane. Screen readers reach the floating card the way they reach any popup's content.

automation_name_fallback

std::string automation_name_fallback() const override

The title, when no accessibility name is set.

measure_override

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

Always Size: the tip takes no room where it sits in the tree.

arrange_override

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

Nothing to arrange in place; the card is placed by the popup.

render_override

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

Draws nothing in place. The card draws above the window.

clear_dirty_override

void clear_dirty_override(DirtyFlags flags) override

Also clears the content, hero and buttons, which are not ordinary children.

tick_override

void tick_override(Duration delta) override

Forwards the tick to the card's children (button animations) while open.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

Enumerations

TeachingTipHeroPlacement

enum class TeachingTipHeroPlacement

Which edge of the card the hero content sits at: Auto puts it at the edge away from the tail.

Value
Auto The edge away from the tail; the top when there is no tail at the top.
Top Always across the top of the card.
Bottom Always across the bottom of the card.

Tests

  • tests/test_teaching_tip.cpp