Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MenuList

MenuList preview

MenuList — a themed vertical menu container with keyboard navigation.

Public types

KindName
structMenuSeparator — A 1 dp horizontal divider line between groups of menu items
structMenuList — A themed vertical dropdown menu panel with keyboard navigation and type-ahead

Public functions

Detailed description

MenuList is the dropdown panel used by MenuBar, MenuContext, and popover-style menus. It provides a themed surface (background, rounded border, drop shadow) and owns the full keyboard navigation stack: ArrowUp/Down moves focus, Enter activates, Escape bubbles to the enclosing overlay host, Home and End jump to the first/last visible item. Type-ahead search jumps to the next item whose stripped label starts with the accumulated keystrokes (500 ms reset window by default).

Items are added with .item(widget) (any impl Widget, but typically a MenuItem); separators with .separator(). Conditional rows use .item_when(widget, visible_prop) — a hidden row collapses to zero height and is skipped by keyboard navigation. For very long lists (recent files, etc.) call .max_visible_items(n) to cap the panel height and wrap the content in a ScrollArea.

Two knobs shape the surface. .inset(dp) sets the gap between the menu surface and its rows (default 4 dp; the max_visible_items scroll cap follows it). .surface_style(style) gives this one list its own PopoverStyle — frame, fill, shadow — ahead of the theme's popover slot, which a ComboBox dropdown shares; other lists are unaffected.

Safe-triangle hover gate. When the pointer leaves a submenu trigger's row with the submenu still up, the trigger arms the safe region (the apex and the cone live in teksilo_core::overlay) and publishes that submenu's id on a MenuList-wide shared state, so sibling items can skip their hover-switch while the cursor travels diagonally toward the submenu.

Accessibility

Role::Menu, named after what opened it (a MenuBar trigger, the MenuItem::submenu row, a PopoverButton's button), with the keyboard highlight as its active descendant: the menu keeps keyboard focus, and a screen reader is told of each move of the highlight as a focus change to the row. Each row is Role::MenuItem / Role::MenuItemCheckBox / Role::MenuItemRadio as declared by the item, and carries its position among the menu's shown rows, whose count the menu carries. Radio items in the same list also auto-group via push_to_radio_group.

#![allow(unused)]
fn main() {
use teksilo_widgets::{MenuList, MenuItem};
use teksilo_i18n::lit;
use teksilo_core::Intent;
let _w = MenuList::new()
    .item(MenuItem::new(lit!("Cut")).on_activate_fn(|ctx| ctx.send_intent(Intent::new("app.cut"))))
    .item(MenuItem::new(lit!("Copy")).on_activate_fn(|ctx| ctx.send_intent(Intent::new("app.copy"))))
    .separator()
    .item(MenuItem::new(lit!("Paste")).on_activate_fn(|ctx| ctx.send_intent(Intent::new("app.paste"))));
}

Density

The picture above is the widget at TargetDensity::Compact, the mouse-and-keyboard ladder. Below is the same subject on the same canvas with only the ladder changed, so what moves is the density and nothing else — where the subject no longer fits, that is what the denser targets cost it at that size. See docs/density-and-targets.md.

Touch

MenuList at Touch density

API reference

📖 Full rustdoc API for this module

pub struct MenuSeparator

Import: use teksilo::widgets::MenuSeparator;

A 1 dp horizontal divider line between groups of menu items.

#![allow(unused)]
fn main() {
pub struct MenuSeparator;
}

pub struct MenuList

Import: use teksilo::widgets::MenuList;

A themed vertical dropdown menu panel with keyboard navigation and type-ahead.

See the module documentation for the full feature description.

#![allow(unused)]
fn main() {
pub struct MenuList { /* fields */ }
}

Methods

pub fn new() -> Self

Create an empty menu list with no items, no height cap, and the default 500 ms type-ahead reset window.

pub fn inset(mut self, inset: f32) -> Self

Set the gap, in dp, between the menu surface and its rows, on every side. Defaults to 4 dp. A smaller value lets the rows (and their hover / highlight bands) reach nearer the surface's frame, which suits a thin or accent-coloured border. Negative values are treated as zero. The scroll cap of max_visible_items follows the same value.

pub fn surface_style(mut self, style: impl PopoverStyle) -> Self

Give this list its own surface: background, border, corner radius and shadow come from style instead of the theme's popover slot.

Precedence, highest first: this per-list style, the theme's style_slots.popover, the built-in RecipePopoverStyle. Only this list is affected; a ComboBox dropdown or another menu keeps the theme's. The style is asked for PopoverVariant::Menu.

pub fn type_ahead_timeout(mut self, d: Duration) -> Self

Override the type-ahead buffer reset window. Defaults to 500ms to match Windows' menubar convention. Tests use Duration::ZERO to force every keypress to start a fresh search.

pub fn attached_side(mut self, side: crate::shadow::AttachedSide) -> Self (default: None)

Suppress drop-shadow drawing on the side that visually merges with the menu's trigger. See crate::shadow::AttachedSide for the available edges.

pub fn item(mut self, widget: impl Widget + 'static) -> Self

Add a menu item (typically a MenuItem).

pub fn items(self, iter: impl IntoIterator<Item = impl Widget + 'static>) -> Self

Add several menu items from an iterator, in order.

The loop form of item: reach for it when the rows come from data rather than being written out one call at a time.

pub fn item_when( self, widget: impl Widget + 'static, visible: impl Into<teksilo_core::signal::Prop<bool>>, ) -> Self

Add a menu item that is shown only while visible is true. When the gate is false the row collapses to zero height (no gap) and keyboard navigation skips it — arrows, Home/End, Enter, type-ahead, and mnemonic activation all ignore it. Used e.g. by a Toolbar's overflow menu, where each row is present only while its inline twin is collapsed.

Because a hidden row never claims its mnemonic letter, two gated rows that are mutually exclusive may share one — the letter resolves to whichever is visible when it is pressed.

pub fn items_when<W, V>(self, iter: impl IntoIterator<Item = (W, V)>) -> Self where W: Widget + 'static, V: Into<teksilo_core::signal::Prop<bool>>,

Add several gated rows from an iterator of (widget, visible) pairs.

The loop form of item_when, for a gated row set built from data. Each pair carries its own gate, so the rows appear and disappear independently.

pub fn item_boxed_when( mut self, widget: Box<dyn Widget>, visible: impl Into<teksilo_core::signal::Prop<bool>>, ) -> Self

item_when for an already-boxed widget — used when the row type is decided at runtime (e.g. a menu row that is either a MenuItem or an embedded control).

pub fn items_boxed_when<V>(self, iter: impl IntoIterator<Item = (Box<dyn Widget>, V)>) -> Self where V: Into<teksilo_core::signal::Prop<bool>>,

Add several gated rows from an iterator of (widget, visible) pairs.

The loop form of item_boxed_when, for a gated row set built from data. Each pair carries its own gate, so the rows may appear and disappear independently.

pub fn separator(mut self) -> Self

Add a separator line.

pub fn header(mut self, widget: impl Widget + 'static) -> Self

Add a non-interactive section caption (typically a crate::GroupHeader). Skipped by Arrow/Home/End navigation and type-ahead, exactly like separator. The caller passes any impl Widget, but it must expose its own accessible name/role via accessibility() (as GroupHeader does) or it is silently pruned from the AT tree as a content-free container.

pub fn max_visible_items(mut self, n: usize) -> Self

Cap the panel height to roughly n * item_height and make the content scrollable when that height is exceeded. Clamped to at least 1. Useful for long menus (e.g. a "Recent files" list) — without this, a very long menu grows to exceed the window.

Note: items are still materialized eagerly; this is a viewport cap, not virtualization. See the module-level note.