Skip to main content

Crate muri

Crate muri 

Source
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 declarative SegmentRowItemMenu tree, identifiers, events, and icons (pure data model).
  • style / theme — visual primitives (Color, Font) and the Theme surface with pure semantic-color resolution.
  • layout — pure Flex/Align width resolution (the flush-right layout).
  • flyout — pure flyout-submenu placement (right/left flip, clamp) and hover-stack transitions.
  • geometry — logical points/sizes/rects and Insets/Edge.
  • render — the SceneDrawer interface shared by the one CPU-raster backend on every OS.
  • platform — the single per-OS Platform seam (tray anchor, popup event loop, environment) selected once.
  • errorError / 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).

OSTray iconStyled anchored popupContext 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 keynav::NavAction;
pub use keynav::NavKey;
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-icon compatibility facade (spec 02, 60) — behind the muda-compat feature.
error
Error types for muri’s fallible operations.
event
The process-global MenuEvent channel — muri’s single, unified event source (spec 03 §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 Flex behavior, then placing each segment’s text within its box according to its Align.
menu
The declarative menu data model: identifiers and events, icons, and the SegmentRowItemMenu tree 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 Platform trait, 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 / swash engines) 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 (see crate::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 Theme into concrete pixels by the renderer.
theme
Theming: the Theme surface, where it comes from (ThemeSource), the per-popup MenuOptions, and the pure resolution of a semantic Color into a concrete Rgba.

Structs§

ContextMenu
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_positioner relative 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_popup math the tray uses; Tray, ContextMenu, and Popup differ only in how the anchor rectangle is obtained, and all funnel into one shared PopupSession (spec 01 §5.3, spec 20 §3).
Tray
A live tray icon with an attached styled menu.
TrayHandle
A cheap, Clone + Send remote control for a running Tray.