Expand description
§muri — Menu Utilities for Rust Interfaces
muri is a cross-platform, fully-styleable tray-icon + popup-menu system
for Rust: a custom-drawn replacement for the muda + tray-icon pairing.
Unlike native menus (which delegate pixels to AppKit / Win32 USER / GTK and
can’t be restyled), muri draws one consistent custom appearance on every
OS, enabling true alignment, arbitrary colors/fonts, embedded logos, and
flush right-aligned values with no reserved chevron column.
muri owns the whole stack: the tray icon, the styled popup, and the anchoring (where the popup appears relative to the icon). A consuming app drives it with a small builder API:
use muri::{Tray, Menu, Row, Icon};
let menu = Menu::new()
.row(Row::new("open").label("Open"))
.separator()
.row(Row::new("quit").label("Quit"));
let _tray = Tray::new(Icon::from_png(icon_png))
.tooltip("My App")
.menu(menu)
.on_click(|id| println!("clicked {}", id.as_str()));
// let _m = muri::MainThreadMarker::new().unwrap(); // call this on the real main thread
// _tray.run(_m); // installs the tray icon and enters the platform event loop§Native API: the one obvious way
The native surface deliberately has one canonical call per task, with alternatives kept only as clearly-labeled thin sugar or full-control escape hatches (issue #62). When in doubt, reach for the canonical path:
| Task | Canonical native call | Escape hatch |
|---|---|---|
| Build a menu | Menu::new + Menu::row / separator / section_header / submenu / content | Menu::item with a hand-built Item |
| An interactive row | Row::new(id) | — |
| A header / label / info row | Row::label_only(text) | Row::default + segments |
| Row text | Row::label / Row::label_value | Row::segments (hand-built Segments) |
| Bold a row’s label | Row::bold | a whole-label StyleRun with Weight::Bold |
| Color a row’s value | Row::value_color | per-substring StyleRuns |
| An icon | Icon::from_png / Icon::from_rgba / Icon::from_svg | — |
| Choose the look | MenuOptions (carrying a ThemeSource) | Tray::theme / TrayHandle::set_theme (derived conveniences that set the MenuOptions theme) |
Per-run StyleRun styling (via Segment::run/Segment::runs) is the
one styling system; Row::bold/Row::value_color are ergonomic
front doors onto it and render identically to hand-built runs.
MenuOptions is the single source of truth for “which look”.
§Status
0.9.0 testing release — all three backends implemented. macOS draws
a real styled popup (NSStatusItem + non-activating NSPanel), with
flyout submenus, keyboard navigation (keynav), and an accessibility
tree (a11y) exposed to VoiceOver via AccessKit (a11y feature).
Windows anchors a WS_EX_NOACTIVATE layered popup and exposes UIA via
accesskit_windows. Linux installs an SNI/AppIndicator native menu and
supports pointer-anchored popups via X11 override-redirect. ContextMenu::open_at
/ Popup::anchored_to / TrayHandle::open work on all three
platforms; on-device verification (real hardware, real screen readers) is
what this testing release is for. See the README for the honest platform
matrix.
§Crate layout
menu— the declarativeSegment→Row→Item→Menutree, identifiers, events, and icons (pure data model).style/theme— visual primitives (Color,Font) and theThemesurface with pure semantic-color resolution.layout— pureFlex/Alignwidth resolution (the flush-right layout).flyout— pure flyout-submenu placement (right/left flip, clamp) and hover-stack transitions.geometry— logical points/sizes/rects andInsets/Edge.render— theSceneDrawerinterface shared by the one CPU-raster backend on every OS.platform— the single per-OSPlatformseam (tray anchor, popup event loop, environment) selected once.error—Error/Unsupported.
§Rendering stack
Text is shaped/rasterized with fontdb + harfrust + swash; everything
else is composited by muri’s own CPU Framebuffer
blitter — a tiny binary with no GPU warm-up. Windowing is native per-OS:
macOS uses a non-activating NSPanel/CALayer; Windows a
WS_EX_NOACTIVATE layered window; Linux an SNI tray plus an X11
override-redirect window for ContextMenu::open_at.
§Platform support (honest matrix)
Legend: ✅ working (automated-tested / live) · 🔬 code-complete, on-device verification pending (this is what the 0.9.0 testing release is for) · ❌ not offered (by design).
| OS | Tray icon | Styled anchored popup | Context menu (open_at) | Screen reader |
|---|---|---|---|---|
| macOS | 🔬 NSStatusItem | 🔬 non-activating NSPanel, N-level flyouts, mouse + keyboard nav | 🔬 open_at + Popup (shared PopupSession) | 🔬 VoiceOver (per-window AccessKit adapters wired) |
| Windows | 🔬 Shell_NotifyIcon | 🔬 WS_EX_NOACTIVATE layered popup, WH_MOUSE_LL dismiss | 🔬 open_at + Popup (reuses the layered popup) | 🔬 NVDA + Narrator (UIA via accesskit_windows) |
| Linux | 🔬 SNI/AppIndicator native menu | ❌ tray-anchored (by design — see below); use pointer ContextMenu | 🔬 X11 override-redirect open_at (Wayland: Unsupported::ClientPositioning) | 🔬 Orca (AT-SPI via the native menu) |
The 🔬 cells are muri code that builds and is clippy-clean on its target in CI but hasn’t been exercised on real hardware yet — verifying them, and the four screen readers, is exactly what the 0.9.0 testing release is for; each flips to ✅ as it’s confirmed on the road to 1.0. The Linux tray-anchored styled popup stays ❌ permanently.
Linux caveat: the SNI/AppIndicator tray host owns the icon in its own
process, so the app never gets the icon’s rect or click coordinate; Wayland
also forbids a client from positioning its own toplevel. A tray-anchored
styled popup is therefore architecturally impossible there. Tray::run
still installs a native SNI/AppIndicator menu, but reports
Error::Unsupported(Unsupported::TrayAnchor) for the anchor rect
— use that native menu or a pointer-anchored ContextMenu instead. See
Unsupported.
Re-exports§
pub use a11y::announcement;pub use a11y::build_tree;pub use a11y::focused_id;pub use a11y::locate;pub use a11y::AxId;pub use a11y::AxNode;pub use a11y::AxRole;pub use a11y::AxTree;pub use anchor::place_popup;pub use error::Error;pub use error::Result;pub use error::Unsupported;pub use event::MenuEventReceiver;pub use flyout::next_flyout;pub use flyout::place_flyout;pub use flyout::FlyoutPlacement;pub use flyout::FlyoutSide;pub use flyout::HoverTarget;pub use geometry::Edge;pub use geometry::Insets;pub use geometry::LogicalPoint;pub use geometry::LogicalRect;pub use geometry::LogicalSize;pub use keynav::handle_key;pub use keynav::FlyoutFocus;pub use keynav::MenuFocus;pub use menu::Align;pub use menu::Axis;pub use menu::ClickHandler;pub use menu::Content;pub use menu::Flex;pub use menu::Icon;pub use menu::Item;pub use menu::Menu;pub use menu::MenuEvent;pub use menu::MenuId;pub use menu::Row;pub use menu::Segment;pub use menu::Stack;pub use menu::StyleRun;pub use menu::TextContent;pub use platform::Appearance;pub use platform::Platform;pub use platform::PlatformEvent;pub use style::Color;pub use style::Font;pub use style::FontFamily;pub use style::Rgba;pub use style::Weight;pub use theme::GutterPolicy;pub use theme::MenuOptions;pub use theme::OsFamily;pub use theme::Preset;pub use theme::Theme;pub use theme::ThemeMode;pub use theme::ThemeSource;pub use theme::TrailingGutterPolicy;
Modules§
- a11y
- Pure accessibility-tree model: the parallel structure a screen reader walks.
- anchor
- Pure popup-placement math shared by every OS backend.
- compat
- muda +
tray-iconcompatibility facade (spec02,60) — behind themuda-compatfeature. - error
- Error types for muri’s fallible operations.
- event
- The process-global
MenuEventchannel — muri’s single, unified event source (spec03§3). - flyout
- Pure, platform-independent flyout-submenu geometry and hover-stack logic.
- geometry
- DPI-independent (“logical”) geometry primitives shared by the layout engine, the scene drawer, and the per-OS anchoring shims.
- keynav
- Pure keyboard-navigation state machine for the menu and its N-level flyout stack.
- layout
- Pure, platform-independent row layout: distributing a row’s available width
across its segments according to their
Flexbehavior, then placing each segment’s text within its box according to itsAlign. - menu
- The declarative menu data model: identifiers and events, icons, and the
Segment→Row→Item→Menutree a consumer builds. This layer is entirely pure (no I/O, no platform calls) and is what both the renderer and the accessibility tree are derived from. - platform
- The single platform seam: one
Platformtrait, one implementation module per OS behind it, selected exactly once here. - render
- The scene-drawer interface and its one real implementation — the CPU raster
backend (a muri-owned raster blitter plus a muri-owned text layer built
directly on
fontdb/harfrust/swash) described in the design. The same drawer paints the menu on macOS, Windows, and Linux; per-OS code is confined to the anchoring/dismiss shims (seecrate::platform), never to drawing. - style
- Visual style primitives: colors, fonts, and weights. These are the building
blocks a consumer attaches to segments and rows; they are resolved against a
Themeinto concrete pixels by the renderer. - theme
- Theming: the
Themesurface, where it comes from (ThemeSource), the per-popupMenuOptions, and the pure resolution of a semanticColorinto a concreteRgba.
Structs§
- Context
Menu - A free-standing styled menu shown at an explicit screen point. Unlike a
tray-anchored popup, this works anywhere a pointer coordinate is available —
including Linux/Wayland via
xdg_positionerrelative to the caller’s own surface — so it is muri’s portable styled-menu primitive. - Main
Thread Marker - A zero-cost,
!Send + !Syncproof that the calling code is running on the thread that obtained it — required byTray::runandTray::spawnbecause installing anNSStatusItemis only safe from AppKit’s main thread on macOS (issue #46); Windows/Linux take the same proof for one uniform contract across backends. - Popup
- A styled dropdown popup anchored to an arbitrary caller rectangle (e.g. a
toolbar button), rather than the tray icon or a bare point. It reuses the exact
place_popupmath the tray uses;Tray,ContextMenu, andPopupdiffer only in how the anchor rectangle is obtained, and all funnel into one sharedPopupSession(spec 01 §5.3, spec 20 §3). - Surface
Id - A process-global, monotonically increasing identifier for one surface
instance — a
Tray,ContextMenu, orPopup— so aMenuEventconsumer can tell which surface an activation came from (issue #51). - Tray
- A live tray icon with an attached styled menu.
- Tray
Handle - A cheap,
Clone + Sendremote control for a runningTray.