Skip to content

MenuBar

Header: winui/ui/menus/MenuBar.hpp. Spec: winui/ui/menus/MenuBar.hpp.

A horizontal menu bar: a row of headers, each dropping down a cascading MenuFlyout-style menu that floats in the overlay layer under the header's left edge.

The bar is one tab stop. Left/Right move between headers and wrap around. Down, Enter and Space open the focused header's menu with its first item focused. While a menu is open the bar keeps focus and passes the keys on to the menu. Every item's shortcut fires app-wide, whether its menu is open or not. The declarative form is ui::MenuBar.

Example:

bool show_status_bar = true;
auto menu = std::make_unique<ui::MenuBarControl>();
menu->set_items({
    ui::MenuBarItem{.text = "File", .items = {
        {.text = "Open", .shortcut = ui::Ctrl + ui::Key::O, .on_click = [&] { open_file(); }},
        ui::MenuItem::separator(),
        {.text = "Exit", .on_click = [&] { quit(); }},
    }},
    ui::MenuBarItem{.text = "View", .items = {
        ui::MenuItem::toggle("Status bar", /*checked=*/true,
                                      [&](bool on) { show_status_bar = on; }),
    }},
});

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::MenuBarControl* bar = stack->add(std::move(menu)); // observer; the stack owns it
window.set_content(std::move(stack));

// Or declaratively:
window.set_content(ui::MenuBar{ .items = {
    { .text = "File", .items = { { .text = "Open", .on_click = [&] { open_file(); } },
                                 ui::MenuItem::separator(),
                                 { .text = "Exit", .on_click = [&] { quit(); } } } },
}});

ui::MenuBar

A menu bar. Builds a ui::MenuBarControl. Each menu is a list of ui::MenuItem, so its items may be toggles, radio items, separators or submenus with icons and shortcuts. Every item's shortcut fires app-wide, whether its menu is open or not.

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

Example:

ui::Property<bool> status_bar{true};
window.set_content(ui::VerticalStack{ .content = {
    ui::MenuBar{ .items = {
        { .text  = "File",
          .items = { { .text = "Open", .shortcut = ui::Ctrl + ui::Key::O,
                       .on_click = [&] { open_file(); } },
                     ui::MenuItem::separator(),
                     { .text = "Exit", .on_click = [&] { quit(); } } } },
        { .text  = "View",
          .items = { ui::MenuItem::toggle("Status bar", ui::bind(status_bar)) } },
    }},
    ui::Text{ .text = "Document" },
}});

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

ui::MenuBar{
    .items, .style, .margin, .width, .height, .min_width, .min_height, .max_width, .max_height,
    .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control
}

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

Field Type Default
items ui::Binding<std::vector<ui::MenuBarItem>> The bar's headers, left to right, each with the menu it drops down.
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::MenuBar> Out: the handle that receives the built control.

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

ui::MenuBarControl

Derives from ui::Element.

A horizontal menu bar: a row of headers, each dropping down a cascading MenuFlyout-style menu that floats in the overlay layer under the header's left edge.

The bar is one tab stop. Left/Right move between headers and wrap around. Down, Enter and Space open the focused header's menu with its first item focused. While a menu is open the bar keeps focus and passes the keys on to the menu. Every item's shortcut fires app-wide, whether its menu is open or not. The declarative form is ui::MenuBar.

Example:

bool show_status_bar = true;
auto menu = std::make_unique<ui::MenuBarControl>();
menu->set_items({
    ui::MenuBarItem{.text = "File", .items = {
        {.text = "Open", .shortcut = ui::Ctrl + ui::Key::O, .on_click = [&] { open_file(); }},
        ui::MenuItem::separator(),
        {.text = "Exit", .on_click = [&] { quit(); }},
    }},
    ui::MenuBarItem{.text = "View", .items = {
        ui::MenuItem::toggle("Status bar", /*checked=*/true,
                                      [&](bool on) { show_status_bar = on; }),
    }},
});

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::MenuBarControl* bar = stack->add(std::move(menu)); // observer; the stack owns it
window.set_content(std::move(stack));

// Or declaratively:
window.set_content(ui::MenuBar{ .items = {
    { .text = "File", .items = { { .text = "Open", .on_click = [&] { open_file(); } },
                                 ui::MenuItem::separator(),
                                 { .text = "Exit", .on_click = [&] { quit(); } } } },
}});

MenuBarControl()

An enabled, empty bar; it is not focusable until it has items.

~MenuBarControl() override
MenuBarControl(MenuBarControl&&) noexcept

Moves a bar that has not been parented yet.

operator=

MenuBarControl& operator=(MenuBarControl&&) = delete

items

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

The menus, in header order. Toggle and radio items' is_checked() reflect clicks made in the open menu and the properties their checked is bound to, so read them back from here.

set_items

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

Replaces the menus, closes any that is open and lays out the header row again.

open_index

std::optional<std::size_t> open_index() const

Which header's menu is open; unset when none is.

active_command

std::optional<std::size_t> active_command() const

The item holding focus in the open menu (an index into its top level); unset when there is none.

focused_header

std::size_t focused_header() const

The header the keyboard is on (Left/Right move it); 0 for a fresh bar.

open

void open(std::size_t index, bool from_keyboard = false)

Opens header index's menu, as a click on it would, closing any other. With from_keyboard the menu's first focusable item is focused, as Down/Enter/Space do. Does nothing for an out-of-range index or a disabled bar; a header with an empty menu only takes the keyboard focus.

close

void close()

Closes the open menu and its submenus. The menu fades out over the theme's close duration.

focused

bool focused() const

Whether the bar holds keyboard focus; the focus ring is then drawn around focused_header().

focusable

bool focusable() const override

One tab stop for the whole bar, while it is enabled and has at least one header.

handle_global_shortcut

bool handle_global_shortcut(const KeyEvent& e) override

Every menu's shortcuts, open or not.

take_menu_access

bool take_menu_access(char32_t letter) override

Alt or F10 alone moves keyboard focus to the first header. Alt with a letter opens the menu whose header starts with that letter, ignoring case, with its first item focused. Escape and running an item give focus back (Window::end_menu_access()).

transition_opacity

float transition_opacity() const override

The menu's opacity: fully opaque while it slides in, then fading out as it closes.

automation_role

AutomationRole automation_role() const override

AutomationRole::MenuBar.

automation_children

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

One MenuItem node per header, which expands to open its menu.

automation_parent_of

Element* automation_parent_of(const Element& child) const override

The open menu belongs to its header's node.

automation_focus

Element* automation_focus() const override

The open menu's item the keyboard is on, else the header it is on.

measure_override

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

The header row. Each header is its title plus padding and margin, and the bar is at least 40 DIPs tall. An open menu floats and never changes this size.

arrange_override

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

Keeps an open menu hanging from its header.

pointer_override

bool pointer_override(const PointerEvent& e) override

Hover highlights a header; a click opens its menu, and a second click on the same header closes it. While a menu is open, moving onto another header opens that one instead.

render_override

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

Draws the header row: subtle header backgrounds, the open menu's header pressed-in, and the focus ring around the focused header. The menu itself is a popup the window draws.

tick_override

void tick_override(Duration delta) override

Advances the menu's open slide, submenu hover delays and the close fade.

clear_dirty_override

void clear_dirty_override(DirtyFlags flags) override

Clears the bar's dirty flags and those of the floating menu with it.

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.

focus_changed

void focus_changed(bool focused) override

Called by the Window as focus arrives or leaves; losing focus closes the open menu.

key_override

bool key_override(const KeyEvent& e) override

Left/Right move between headers. With a menu open, they move into the neighboring menu when the open menu has no use for the key. Down, Enter and Space open the focused header's menu, and while a menu is open its keys go to it.

focus_ring_rect

Rect focus_ring_rect(const Theme&) const override

Around the focused header.

struct MenuBarItem

One top-level entry of a Menu bar: the header title and the items of the menu that drops down from it.

Example:

auto menu = std::make_unique<ui::MenuBarControl>();
ui::MenuBarItem file{.text = "File", .items = {
    {.text = "Open", .shortcut = ui::Ctrl + ui::Key::O, .on_click = [&] { open_file(); }},
    ui::MenuItem::separator(),
    {.text = "Exit", .on_click = [&] { quit(); }},
}};
menu->set_items({std::move(file)});

Field Type Default
text std::string The header title shown in the bar.
items std::vector<MenuItem> The menu's entries; they may be toggles, radio items, separators and submenus.

Tests

  • tests/test_menu_bar.cpp