Skip to content

MenuFlyout

Headers: winui/ui/menus/MenuFlyout.hpp, winui/ui/menus/MenuItem.hpp. Spec: winui/ui/menus/MenuFlyout.hpp.

A list of commands in a popup: hung from an anchor element (a click on a Button anchor toggles it) or opened at a point with show_at() for a context menu.

The flyout sits in the tree in its anchor's place and owns the anchor (set_anchor()); the menu floats in the Window's overlay layer, cascades into submenus, and closes on invoke, light dismiss or Escape. Placement and show()/hide() come from FlyoutBase. The declarative form is ui::MenuFlyout.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto menu  = std::make_unique<ui::MenuFlyoutControl>();
ui::ButtonControl* options = menu->set_anchor(std::make_unique<ui::ButtonControl>("Options"));
menu->set_items({
    {.text = "Open", .on_click = [&] { open_file(); }},
    ui::MenuItem::sub_item("Send to", {{.text = "Bluetooth"}, {.text = "Desktop"}}),
    ui::MenuItem::separator(),
    ui::MenuItem::toggle("Repeat", /*checked=*/true),
});
ui::MenuFlyoutControl* flyout = stack->add(std::move(menu)); // the stack owns flyout and anchor

// Also a context menu: a right-click on the anchor opens it at the pointer.
options->on_context_requested([flyout, options](std::optional<ui::Point> at) {
    flyout->show_at(at.value_or(options->bounds().center()));
});

// Or declaratively:
stack->add(ui::MenuFlyout{
    .items   = {{.text = "Reset"}, ui::MenuItem::separator(),
                ui::MenuItem::toggle("Repeat", true)},
    .anchor  = ui::Button{.text = "Options"},
}.build());

ui::MenuFlyout

A list of commands in a popup, hung from an anchor. Builds a ui::MenuFlyoutControl. A Button anchor opens it on click. To use it as a context menu, call show_at(point) on its .control handle from an on_context_requested handler. The built element takes the anchor's place in the tree, and the menu floats in the window's popup layer.

Example:

bool repeat = true;
window.set_content(ui::MenuFlyout{
    .items   = { { .text = "Reset", .on_click = [&] { repeat = true; } },
                 ui::MenuItem::separator(),
                 ui::MenuItem::toggle("Repeat", true, [&](bool on) { repeat = on; }),
                 ui::MenuItem::sub_item("Sort by", {
                     ui::MenuItem::radio("Name", true),
                     ui::MenuItem::radio("Date") }) },
    .anchor    = ui::Button{ .text = "Options" },
});

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

ui::MenuFlyout{
    .items, .is_open, .placement, .placement_alignment, .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
}

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

Field Type Default
items ui::Binding<std::vector<ui::MenuItem>> The menu's entries: items, toggles, radio items, separators and submenus.
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, centered on the anchor; PopupPlacement::Below by default.
placement_alignment ui::Binding<ui::PopupPlacementAlignment> {ui::PopupPlacementAlignment::Center} How the menu lines up with the anchor along that side.
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::MenuFlyout> Out: the handle that receives the built control.
anchor ui::Spec The in-tree element the menu hangs from.

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::MenuFlyoutControl; the caller owns it.

ui::MenuFlyoutControl

Derives from ui::FlyoutBase.

A list of commands in a popup: hung from an anchor element (a click on a Button anchor toggles it) or opened at a point with show_at() for a context menu.

The flyout sits in the tree in its anchor's place and owns the anchor (set_anchor()); the menu floats in the Window's overlay layer, cascades into submenus, and closes on invoke, light dismiss or Escape. Placement and show()/hide() come from FlyoutBase. The declarative form is ui::MenuFlyout.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto menu  = std::make_unique<ui::MenuFlyoutControl>();
ui::ButtonControl* options = menu->set_anchor(std::make_unique<ui::ButtonControl>("Options"));
menu->set_items({
    {.text = "Open", .on_click = [&] { open_file(); }},
    ui::MenuItem::sub_item("Send to", {{.text = "Bluetooth"}, {.text = "Desktop"}}),
    ui::MenuItem::separator(),
    ui::MenuItem::toggle("Repeat", /*checked=*/true),
});
ui::MenuFlyoutControl* flyout = stack->add(std::move(menu)); // the stack owns flyout and anchor

// Also a context menu: a right-click on the anchor opens it at the pointer.
options->on_context_requested([flyout, options](std::optional<ui::Point> at) {
    flyout->show_at(at.value_or(options->bounds().center()));
});

// Or declaratively:
stack->add(ui::MenuFlyout{
    .items   = {{.text = "Reset"}, ui::MenuItem::separator(),
                ui::MenuItem::toggle("Repeat", true)},
    .anchor  = ui::Button{.text = "Options"},
}.build());

MenuFlyoutControl()

An empty menu with no anchor.

~MenuFlyoutControl() override

items

const std::vector<MenuItem>& items() const

The items. Toggle and radio items' is_checked() track clicks made in the menu and the properties their checked is bound to.

set_items

void set_items(std::vector<MenuItem> items)

Replaces the items; an open menu closes first. The menu redraws when the command of any item changes, submenus included, and follows every bound checked while it holds the items.

handle_global_shortcut

bool handle_global_shortcut(const KeyEvent& e) override

The shortcut of an item, in the menu or in a submenu, invokes that item whether the menu is open or not.

transition_opacity

float transition_opacity() const override

The menu's opacity during its open and close transitions. A menu that slides in (Theme::popup_motion) opens fully opaque, and one that fades in blends in. Closing fades it out over the theme's close duration.

tick_override

void tick_override(Duration delta) override

Picks up the theme's menu open and close durations, then advances the flyout's transition.

struct MenuItem

One entry of a menu. The plain case is {.text = "Open", .on_click = [&] { open(); }}; the factories below spell out the other kinds. The fields follow the spec order: what the item shows, then the command and the handlers, then the submenu.

on_click is a copyable std::function, since items live in a std::vector built from a brace list, which only copies. A handler must not capture a move-only value.

Items are plain values: the menu that holds them (MenuFlyout, MenuBar, a context menu, the window's system menu) keeps its own copy, so a toggle's or radio item's current state is read back from that owner's items() with is_checked(), or followed through on_checked_changed. text, icon, shortcut, enabled and checked take a value or a property: bound to a property, every menu built from the item follows it, and a click writes a two-way checked.

Radio items form a group in one of two ways. Bound with .checked = ui::selects(choice, value), the items bound to the same property are the group, wherever they sit in the menu. Unbound, a run of radio items next to each other in one list is a group, so a separator or any other item between two runs starts a new one.

Example:

enum class Size { Small, Large };
ui::Property<std::string> note;
ui::Property<bool>        word_wrap{true};
ui::Property<Size>        size{Size::Small};

std::vector<ui::MenuItem> items{
    {.text = "Copy", .icon = ui::Symbol::Copy, .shortcut = ui::Ctrl + ui::Key::C,
     .on_click = [&] { ui::clipboard().set_text(note.get()); }},
    ui::MenuItem::separator(),
    ui::MenuItem::toggle("Word wrap", ui::bind(word_wrap)),
    ui::MenuItem::radio("Small", ui::selects(size, Size::Small)),
    ui::MenuItem::radio("Large", ui::selects(size, Size::Large)),
    ui::MenuItem::sub_item("Send to", {{.text = "Desktop"}, {.text = "Mail recipient"}}),
    ui::MenuItem::split("Paste", [&] { note = ui::clipboard().text().value_or(""); }, {{.text = "Paste as text"}}),
};
auto flyout = std::make_unique<ui::MenuFlyoutControl>();
flyout->set_items(std::move(items)); // or a ui::MenuBarItem's items

text

Binding<std::string> text {}

The text. Falls back to the command's text.

kind

MenuItemKind kind = MenuItemKind::Item

Which kind of menu item this entry is; the factories below set it. Defaults to a plain Item.

icon

Binding<IconSource> icon {}

The icon, drawn 16x16. Falls back to the command's icon.

shortcut

Binding<KeyCombo> shortcut {}

The keyboard shortcut. It fires app-wide and invokes the item, and its text is shown in the shortcut column. Falls back to the command's.

shortcut_text

std::string shortcut_text {}

Shown instead of the shortcut's own text.

enabled

Binding<bool> enabled {true}

A disabled item is drawn dimmed, skipped by the keyboard, and its shortcut does not fire; a disabled command disables it too (is_enabled()).

checked

Binding<bool> checked {false}

The checked state of a Toggle or Radio item. Two-way from a ui::Property<bool> or ui::selects(): the item follows the property in every menu built from it, and a click writes the new state back before on_checked_changed runs.

command

CommandRef command {}

The command, run after on_click with command_parameter. The command also supplies the text, icon and shortcut the item does not set, and disables the item while it is disabled.

command_parameter

std::string command_parameter {}

Passed to the command's execute.

on_click

std::function<void()> on_click {}

Runs on a click, after the menu has closed, and after a toggle or radio item has changed.

on_checked_changed

std::function<void(bool checked)> on_checked_changed {}

Called when a Toggle or Radio item's checked changes through the menu (a click, a key, a shortcut, or a group-mate being checked), after a two-way checked has been written. A change of the bound property does not call it.

items

std::vector<MenuItem> items {}

The submenu of a SubItem or Split item.

toggle

static MenuItem toggle(std::string text, Binding<bool> checked = false, std::function<void(bool)> on_checked_changed = {})

A toggle item: a click flips checked (drawn as a check mark) and reports the new state to on_checked_changed before on_click runs.

radio

static MenuItem radio(std::string text, Binding<bool> checked = false, std::function<void(bool)> on_checked_changed = {})

A radio item: a click checks it and unchecks the rest of its group (see above); clicking a checked one leaves it checked. Each item unchecked this way writes false into its own two-way checked.

separator

static MenuItem separator()

A 1px line between groups of items; never focused or invoked.

sub_item

static MenuItem sub_item(std::string text, std::vector<MenuItem> items)

A submenu item: invoking it (click, Right, Enter, Space, or a 400 ms hover) opens items as a cascading submenu beside it. It has no on_click of its own.

split

static MenuItem split(std::string text, std::function<void()> on_click, std::vector<MenuItem> items)

A split item: the item runs on_click, and the chevron button at its right edge opens items as a submenu.

from_command

static MenuItem from_command(const Command& command, std::string parameter = {})

An item built entirely from command: its text, icon, shortcut and action.

display_text

std::string display_text() const

text, or the command's text when text is empty.

display_icon

const IconSource& display_icon() const

icon, or the command's icon when icon is empty (an empty IconSource when neither is set).

display_shortcut

KeyCombo display_shortcut() const

shortcut, or the command's shortcut when it is empty.

display_shortcut_text

std::string display_shortcut_text() const

What the shortcut column shows: shortcut_text if set, else display_shortcut()'s "Ctrl+S" text (empty when there is no shortcut).

is_checked

bool is_checked() const

The check state: checked's value. In a menu's items() it is the current state, whether a click or the bound property set it last.

is_enabled

bool is_enabled() const

Enabled, and its command (if any) is too.

focusable

bool focusable() const

Can hold focus in a menu: not a separator, and enabled.

has_submenu

bool has_submenu() const

Opens a submenu (a SubItem, or a Split item with items).

Enumerations

enum class MenuItemKind

Which kind of menu item a MenuItem stands for.

Value
Item a plain command
Toggle a check item
Radio one of a group of exclusive items
Separator a line between groups
SubItem items open as a submenu
Split invokes like an Item, and items open from its chevron

Tests

  • tests/test_menu_flyout.cpp