Skip to content

Implement <MenuBar> / <Menu> / <MenuButton> (egui menus) #28

Description

@ZhukMax

Provide a declarative API for menus: a horizontal <MenuBar> that hosts one or more dropdown <Menu text="..."> groups, each containing actionable <MenuButton> items (and optional nested submenus). Map directly to ui.menu_bar(...) and ui.menu_button(...), with ergonomic events and diagnostics.

  • Menus are a staple of desktop-style UIs (File/Edit/View…); EFx should support them first-class.
  • Reduce imperative glue by letting templates define full menu structures.
  • Enable nested submenus and disabled items with predictable behavior.

Scope

  • New tags:

    • <MenuBar>: container rendering a horizontal menubar.
    • <Menu text="..."> ... </Menu>: dropdown menu opener (label in the bar; content shown on open).
    • <MenuButton onClick={|| ...}>Label</MenuButton>: clickable action inside a menu.
  • Optional attributes:

    • <MenuBar>: id, class, tooltip (applied to the bar area).
    • <Menu>: text (required), id, enabled=bool, tooltip.
    • <MenuButton>: text (alt to children), enabled=bool, shortcut="Ctrl+O", closeOnClick=bool (default: true), tooltip.
  • Supported children:

    • <MenuBar> → one or more <Menu> (and optionally <Separator/> between menus is ignored visually).
    • <Menu><MenuButton>, nested <Menu> (submenu), <Separator/>.
    • <MenuButton> → text as children (or text="...", mutually exclusive).

Non-goals (here):

  • Automatic accelerator handling (keyboard shortcuts are displayed textually only).
  • Iconography and custom item widgets (can be added later).
  • Platform-level menu integration (native menus).

Proposed API (sketch)

Basic menubar

efx!(ui, r#"
<MenuBar>
  <Menu text="File">
    <MenuButton onClick={|| new_file()}>New</MenuButton>
    <MenuButton onClick={|| open_file()} shortcut="Ctrl+O">Open…</MenuButton>
    <Separator/>
    <MenuButton onClick={|| quit()} shortcut="Ctrl+Q">Quit</MenuButton>
  </Menu>
  <Menu text="Edit">
    <MenuButton onClick={|| undo()} shortcut="Ctrl+Z">Undo</MenuButton>
    <MenuButton onClick={|| redo()} shortcut="Ctrl+Shift+Z">Redo</MenuButton>
  </Menu>
</MenuBar>
"#);

Nested submenu & disabled item

efx!(ui, r#"
<MenuBar>
  <Menu text="View">
    <Menu text="Theme">
      <MenuButton onClick={|| set_theme("Light")}>Light</MenuButton>
      <MenuButton onClick={|| set_theme("Dark")}>Dark</MenuButton>
    </Menu>
    <Separator/>
    <MenuButton enabled="false" tooltip="No layout available">Reset Layout</MenuButton>
  </Menu>
</MenuBar>
"#);

Conceptual mapping to egui

ui.menu_bar(|ui| {
    // <Menu text="...">
    ui.menu_button(text, |ui| {
        // <MenuButton> → if ui.button(label).clicked() { if close_on_click { ui.close_menu(); } onClick(); }
        // <Menu> (nested) → ui.menu_button(...)
        // <Separator/> → ui.separator();
    });
});

Tasks

  • AST & validation

    • Add nodes for <MenuBar>, <Menu>, <MenuButton>.
    • Enforce structure: <Menu> only inside <MenuBar> or another <Menu>; <MenuButton> only inside <Menu>.
    • Mutually exclusive text sources for <MenuButton>: children or text=....
  • Codegen

    • <MenuBar>ui.menu_bar(|ui| { ... }).
    • <Menu text="...">ui.menu_button(text, |ui| { ... }) with enabled/tooltip.
    • <MenuButton>:
      • Render as ui.button(label); honor enabled via ui.add_enabled(...).
      • If shortcut present, append right-aligned hint to the label (e.g., "Open…\tCtrl+O" or two-column layout if feasible).
      • On click: call onClick; if closeOnClick != false, call ui.close_menu().
      • Apply tooltip via Response::on_hover_text.
  • Diagnostics

    • Wrong nesting (e.g., <MenuButton> outside <Menu>) → clear error with expected parents.
    • Missing text on <Menu> → required attribute message.
    • Both child text and text= on <MenuButton> → actionable error.
    • Unknown attributes per tag → list allowed attrs.
  • Examples & docs

    • examples/menu_bar.rs: File/Edit/View with nested Theme submenu; disabled item; shortcuts.
    • Sandbox scene “Menus” mirroring the example.
    • Docs: section “Menus” with usage notes and best practices (place in a top panel for classic UI).
  • Tests

    • trybuild UI tests: invalid structure, missing/duplicate attributes, dual text sources.
    • Ensure examples build for wasm32-unknown-unknown (no runtime launch).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions