Flyout¶
Header: winui/ui/dialogs/Flyout.hpp. Spec: winui/ui/dialogs/Flyout.hpp.
Arbitrary content floating in a popup next to an owned anchor.
The content sits on a surface with the theme's elevated menu background and a 1 DIP frame. The padding is 16, 15, 16 and 17 DIPs (left, top, right, bottom). The surface is at least 96x40 and at most 456x758 DIPs, so long text wraps at 456. Opening, placement and focus work as in FlyoutBase. A Button anchor opens it on click, and any other anchor on a click nothing inside it handled.
Example:
// Declarative:
ui::ObservableVector<std::string> cart;
ui::Control<ui::Flyout> flyout;
window.set_content(ui::Flyout{
.placement = ui::PopupPlacement::Right,
.control = flyout,
.anchor = ui::Button{.text = "Empty cart"},
.content = ui::VerticalStack{
.spacing = 12.0f,
.content = {
ui::Text{.text = "All items will be removed. Continue?"},
ui::Button{.text = "Yes, empty my cart", .on_click = [&] { cart.clear(); flyout->hide(); }},
},
},
});
// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto owned = std::make_unique<ui::FlyoutControl>();
owned->set_anchor(std::make_unique<ui::ButtonControl>("Open"));
owned->set_content(std::make_unique<ui::TextControl>("Flyout content"));
stack->add(std::move(owned));
ui::Flyout¶
An anchor element with content that floats above the tree when opened. Builds a ui::FlyoutControl. A Button anchor opens it on click, Enter or Space by itself. Any other anchor opens it on a click that nothing inside it handled, by pointer only. From code, call show() on the control from .control. It opens below the anchor, and an outside click or Escape closes it.
Example:
ui::ObservableVector<std::string> cart;
ui::Control<ui::Flyout> confirm;
window.set_content(ui::Flyout{
.control = confirm,
.anchor = ui::Button{ .text = "Empty cart" },
.content = ui::VerticalStack{
.spacing = 12,
.content = {
ui::Text{ .text = "All items will be removed. Do you want to continue?" },
ui::Button{ .text = "Yes, empty my cart", .on_click = [&] { cart.clear(); confirm->hide(); } },
},
},
});
Every field, in the order a designated initializer names them:
ui::Flyout{
.is_open, .placement, .placement_alignment, .show_mode, .light_dismiss, .style, .margin,
.width, .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
.vertical_alignment, .visible, .tool_tip, .accessibility, .on_opened, .on_closing,
.on_closed, .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 | |
|---|---|---|---|
is_open |
ui::TwoWayField<bool> |
{false} |
Whether the flyout is showing. Two-way with ui::bind(): closing it writes false, and a false that on_closing cancels is put back to true. |
placement |
ui::Binding<ui::PopupPlacement> |
{ui::PopupPlacement::Below} |
Which side of the anchor it opens on; PopupPlacement::Below by default. |
placement_alignment |
ui::Binding<ui::PopupPlacementAlignment> |
{ui::PopupPlacementAlignment::Center} |
How the flyout lines up with the anchor along that side. |
show_mode |
ui::Binding<ui::FlyoutShowMode> |
{ui::FlyoutShowMode::Standard} |
Standard takes keyboard focus while open, Transient leaves it where it is. |
light_dismiss |
ui::Binding<bool> |
{true} |
Whether an outside click or Escape closes it; true by default. Off, it stays open until is_open turns false, hide() runs or a command in it runs. |
on_opened |
ui::Action |
The flyout is showing. A two-way is_open has been written by then. |
|
on_closing |
ui::Callback<ui::Decision(const ui::PopupClosingArgs&)> |
Gets why it is about to close; return ui::Cancel to keep it open. | |
on_closed |
ui::Callback<void(ui::PopupCloseReason)> |
Gets why it closed, once the close fade has finished. A two-way is_open has been written by then. |
|
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::Flyout> |
Out: the handle that receives the built control. | |
anchor |
ui::Spec |
The in-tree element the flyout hangs from. | |
content |
ui::Spec |
The floating content. It is not part of the main tree. The window lays it out and draws it separately, in its popup layer. |
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¶
Consumes the spec and returns the built ui::FlyoutControl; the caller owns it.
ui::FlyoutControl¶
Derives from ui::FlyoutBase.
Arbitrary content floating in a popup next to an owned anchor.
The content sits on a surface with the theme's elevated menu background and a 1 DIP frame. The padding is 16, 15, 16 and 17 DIPs (left, top, right, bottom). The surface is at least 96x40 and at most 456x758 DIPs, so long text wraps at 456. Opening, placement and focus work as in FlyoutBase. A Button anchor opens it on click, and any other anchor on a click nothing inside it handled.
Example:
// Declarative:
ui::ObservableVector<std::string> cart;
ui::Control<ui::Flyout> flyout;
window.set_content(ui::Flyout{
.placement = ui::PopupPlacement::Right,
.control = flyout,
.anchor = ui::Button{.text = "Empty cart"},
.content = ui::VerticalStack{
.spacing = 12.0f,
.content = {
ui::Text{.text = "All items will be removed. Continue?"},
ui::Button{.text = "Yes, empty my cart", .on_click = [&] { cart.clear(); flyout->hide(); }},
},
},
});
// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto owned = std::make_unique<ui::FlyoutControl>();
owned->set_anchor(std::make_unique<ui::ButtonControl>("Open"));
owned->set_content(std::make_unique<ui::TextControl>("Flyout content"));
stack->add(std::move(owned));
FlyoutControl¶
A flyout with no anchor and no content.
set_content¶
Takes ownership of the floating content, replacing any previous one, and returns a non-owning observer. The content shows while is_open(). It is not part of the main tree. The window lays it out and draws it separately, in its popup layer.
content¶
The floating content (non-owning), or nullptr.
class FlyoutBase¶
Derives from ui::Element.
What every flyout shares: an owned anchor element it opens from, opening and closing, placement against the anchor or at a point, focus handling, and the open and close fade.
A FlyoutBase wraps its anchor. Layout, rendering, input, focus and automation pass through to the anchor, and the flyout manages the popup. A Button anchor opens the flyout on click and with Enter or Space by itself (ButtonControl::set_flyout()). Any other anchor opens it on a left click that nothing inside the anchor handled. So a Text, an Image or a card opens it by pointer, while a CheckBox or a TextBox inside still does only its own thing. Such an anchor has no keyboard shortcut to open it, so call show() or toggle() from its own handler for that.
By default the popup opens below the anchor, centered and 4 DIPs away from it, and flips when there is no room. show_at() puts its top-left corner at a point instead, as a context menu does. FlyoutBase can't be constructed directly. Use Flyout, MenuFlyout or CommandBarFlyout.
Example:
auto flyout = std::make_unique<ui::FlyoutControl>();
flyout->set_anchor(std::make_unique<ui::ButtonControl>("Options")); // clicks toggle the flyout
flyout->set_content(std::make_unique<ui::TextControl>("Hello"));
flyout->set_placement(ui::PopupPlacement::Above);
flyout->set_placement_alignment(ui::PopupPlacementAlignment::Leading);
flyout->on_closed([](ui::PopupCloseReason) { std::println("flyout closed"); });
window.set_content(std::move(flyout));
set_anchor¶
Takes ownership of the anchor element, replacing any previous one, and returns a non-owning observer, as ScrollControl::set_child() does. A Button anchor toggles the flyout on click.
anchor¶
The anchor element (non-owning), or nullptr.
is_open¶
True while the flyout is shown or fading in. It turns false as soon as hide(), an outside click or Escape commits to closing, though the popup keeps drawing while it fades out (transition_opacity()).
show¶
Opens the flyout against the anchor, on the side placement() names, and raises on_opened(). Does nothing while already open.
show_at¶
Opens the flyout with its top-left corner at point (window-client DIPs), as a context menu opens at the pointer.
hide¶
Closes with PopupCloseReason::Programmatic. on_closing() can cancel it. Otherwise the close fade starts, is_open() turns false at once, and on_closed() runs once the fade ends. Does nothing while already closed.
toggle¶
hide() when open, show() otherwise.
placement¶
Which side of the anchor the content opens against; PopupPlacement::Below by default. It flips to the opposite side when there is no room.
set_placement¶
Sets the side; applies the next time the popup is placed.
placement_alignment¶
The alignment across the placement axis; Center by default.
set_placement_alignment¶
Sets the alignment across the placement axis.
show_mode¶
Whether an open flyout takes keyboard focus; Standard (it does) by default.
set_show_mode¶
Sets whether the next open takes keyboard focus.
light_dismiss¶
Whether an outside click or Escape closes the flyout; true by default. Off, it stays open until hide() or a command in it closes it, and an outside click reaches what is below.
set_light_dismiss¶
Turns light dismiss on or off.
on_opened¶
Raised when show() or show_at() opens the flyout.
on_closing¶
Raised with the reason before an open flyout closes; return ui::Cancel to keep it open.
on_closed¶
Raised with the reason once the flyout has closed and its close fade has finished (not when on_closing() canceled). The handler may destroy the flyout.
focusable¶
Whether this element can take keyboard focus (a leaf control opts in).
handle_global_shortcut¶
The shortcuts that fire wherever focus is. The ones a command surface carries are of this kind (a MenuBar's Ctrl+S, a MenuFlyout item's Delete, an AppBarButton's Ctrl+N), so those controls answer here. Window offers a key that no scoped shortcut took to every element in the window, in tree order, when the focused element and its parents did not handle the key themselves (key_override()). Default: none.
raise_context_requested¶
A right-click lands on the anchor, though the flyout holds its place in the tab order. The anchor's context request handler runs first, then the flyout's own.
for_each_child¶
Visits the anchor, if any. The popup is not a child.
collect_focusables¶
The flyout stands in for its anchor as one tab stop. Focus itself goes to the anchor.
automation_children¶
No automation children: the anchor is not listed under the flyout.
transition_opacity¶
A linear fade over the open and close transitions by default. A plain Flyout has no opening slide.
automation_role¶
Passes through to the anchor, like every Element override above. Screen readers see the anchor, with the open popup's content as its child.
automation_name_fallback¶
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.
automation_providers¶
Lists what a client can do with this node: set out's field for each pattern this control implements now to this, through the provider interface it inherits privately (ui/Automation.hpp). A pattern that only applies in some states is an if. out arrives empty, and nothing is listed by default.
Example:
class Chip final : public ui::Element, private ui::InvokeProvider
{
protected:
void automation_providers(ui::AutomationProviders& out) override
{
out.invoke = this;
}
private:
bool invoke() override
{
on_click_();
return true;
}
ui::Callback<void()> on_click_;
};
FlyoutBase¶
phase¶
The current phase, for a derived flyout's rendering. The popup stays open through Closing, so it fades out.
transition_progress¶
How far the current Opening or Closing transition is, 0 to 1 (1 once Open or Closed).
set_transition_durations¶
The open and close transitions' lengths; 250 ms each by default. A derived flyout with its own motion sets them in its constructor.
positioned_at_point¶
Whether the current or last open was show_at(), placed at a point, instead of show().
close¶
Closes for reason: raises on_closing(), and unless a handler cancels, starts the close fade. A derived flyout closes with PopupCloseReason::Invoked once a command in it ran.
opening_override¶
Called as show() or show_at() commits to opening, before the Opening transition starts. Reset the popup's own state here.
closing_override¶
Called as a close commits, once no on_closing() handler canceled it.
flyout_surface¶
The element the flyout floats while it shows: its surface, built on first call. Null floats nothing.
popup_element¶
The popup's element, which the flyout ticks and clears of dirty flags itself because it is not in the window's tree. flyout_surface() by default.
flyout_chrome¶
The shadow the window draws behind the surface; the theme's flyout shadow by default.
flyout_dismissed¶
An outside click or Escape: closes with PopupCloseReason::LightDismiss by default.
sync_popup¶
Opens, updates or closes the popup to match the phase, the anchor and the settings. A derived flyout calls it when its surface or chrome changes.
measure_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¶
Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.
render_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().
clear_dirty_override¶
Clears the anchor, and the popup too, which is outside the tree Window clears.
tick_override¶
Ticks the anchor, the popup while it shows, and the open/close transition.
pointer_override¶
A left click on the anchor that nothing inside it handled opens the flyout while it is closed. Presses on the open popup's content never open it again.
key_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¶
Typed text for this element, routed like key_override(). Default: false.
focus_changed¶
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¶
focus_visible() changed; the element is redrawn either way. No-op by default.
draws_focus_ring¶
None of its own: the anchor inside it draws its own focus ring.
Enumerations¶
FlyoutShowMode¶
Whether an open flyout takes keyboard focus.
| Value | |
|---|---|
Standard |
takes keyboard focus while open |
Transient |
leaves focus where it is |
FlyoutPhase¶
Where a flyout is in its open and close cycle (FlyoutBase::phase()). During Closing, is_open() is already false, but the popup keeps drawing and fades out until the close transition ends.
| Value | |
|---|---|
Closed |
not shown |
Opening |
shown, fading in |
Open |
shown |
Closing |
committed to closing, fading out |
Tests¶
tests/test_flyout.cpp