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
therefore can’t be restyled), muri draws one consistent custom appearance on
every OS. That single owned drawing surface is what makes true
left/right/center alignment, arbitrary colors and fonts, embedded logos, and
flush right-aligned values with no reserved chevron column actually
possible.
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_bytes(icon_png))
.tooltip("My App")
.menu(menu)
.on_click(|id| println!("clicked {}", id.as_str()));
// _tray.run(); // installs the tray icon and enters the platform event loop§Status
0.9.0 testing release — all three backends implemented. On macOS
the crate draws a real styled popup: Tray::run installs the
NSStatusItem, opens the custom-drawn menu anchored to it in a
non-activating NSPanel, opens flyout submenu panels beside submenu
rows, and supports keyboard navigation (keynav) over the same
hover-stack the mouse drives. muri also publishes a parallel
accessibility tree (a11y) — Tray::accessibility_tree — mapping
the menu onto menu/menuitem roles, with an AccessKit TreeUpdate bridge
behind the a11y feature — and (also behind a11y) a per-window AccessKit
adapter, so the tree is exposed to NSAccessibility / VoiceOver. The shared
scene drawer, the Flex/Align flush-right layout, the flyout
placement/hover-stack logic, the keyboard-nav state machine, and the
a11y-tree construction are all pure and unit-tested. The Windows
backend installs the notification-area icon, anchors a WS_EX_NOACTIVATE
layered popup via UpdateLayeredWindow, and exposes UIA through
accesskit_windows. The Linux backend installs an SNI/AppIndicator
native menu and supports pointer-anchored popups via an X11
override-redirect open_at. ContextMenu::open_at / Popup::anchored_to
and, once a tray loop is running, TrayHandle::open, work on all three
platforms; on-device verification (real hardware, real screen readers) is
exactly what the 0.9.0 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 and rasterized with fontdb (font discovery) + harfrust
(shaping) + swash (glyph rendering). Everything else — glyphs, fills,
strokes, images — is composited by muri’s own in-house CPU
Framebuffer blitter. CPU raster means a tiny
binary, no GPU warm-up, and
instant popups with full pixel control. Windowing is native per-OS: macOS
uses a non-activating NSPanel presented via CALayer; Windows uses a
WS_EX_NOACTIVATE layered window presented via UpdateLayeredWindow;
Linux uses 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 and draws the icon
in its own process, so the app is never told the icon’s on-screen rectangle
and never receives the click coordinate; Wayland additionally forbids a client
from positioning its own toplevel. A tray-anchored styled popup is therefore
architecturally impossible on Linux/Wayland. muri does not pretend otherwise:
Tray::run still works there (it installs an SNI/AppIndicator native
menu), but the tray anchor rect is unavailable — the backend reports
Error::Unsupported(Unsupported::TrayAnchor) — so a styled
tray-anchored popup is not offered; render the same Menu through that
native menu, or show it as a pointer-anchored ContextMenu. 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::ClickHandler;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::StyleRun;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::MenuOptions;pub use theme::Theme;pub use theme::ThemeSource;
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 for fills/blits, plus a muri-owned text
layer built directly on the
fontdb/harfrust/swashengines) 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. - 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). - Tray
- A live tray icon with an attached styled menu.
- Tray
Handle - A cheap,
Clone + Sendremote control for a runningTray.