Skip to main content

Tray

Struct Tray 

Source
pub struct Tray { /* private fields */ }
Expand description

A live tray icon with an attached styled menu.

Construct with Tray::new, configure via the builder methods, then call Tray::run to install the icon and enter the platform event loop. Content can be swapped at runtime with Tray::set_menu (or from another thread via a TrayHandle) — usagio rebuilds its menu on a ~0.75s tick.

§Anchoring, per OS

  • macOS: anchored to the NSStatusItem button; AppKit computes the on-screen rect and which display the menu bar is on.
  • Windows: anchored via Shell_NotifyIconGetRect, positioning a WS_EX_NOACTIVATE layered window toward screen center.
  • Linux: Tray::run installs an SNI/AppIndicator native menu (it does not block on anchoring); a styled tray-anchored popup is not offered (the anchor rect is unavailable — the backend reports Error::Unsupported(Unsupported::TrayAnchor)). Use that native menu or a pointer-anchored ContextMenu.

§Shutdown

Dropping a Tray itself does not post TrayCommand::Shutdown — it’s normally consumed by Tray::run/Tray::spawn first. What auto-shuts-down the tray is dropping the last outstanding TrayHandle from a given Tray::handle call: its Drop (issue #47) posts Shutdown exactly once, matching tray-icon’s drop-removes contract with no explicit TrayHandle::shutdown call needed.

Implementations§

Source§

impl Tray

Source

pub fn new(icon: Icon) -> Self

Create a tray with the given status-bar icon.

Examples found in repository?
examples/demo_tray.rs (line 124)
122fn main() {
123    let icon = Icon::from_png(swatch_png(120, 170, 255));
124    let tray = Tray::new(icon)
125        .tooltip("muri demo")
126        .menu(demo_menu())
127        .on_click(|id| {
128            println!("clicked: {}", id.as_str());
129            if id.as_str() == "quit" {
130                std::process::exit(0);
131            }
132        });
133
134    println!("muri demo_tray: click the status-bar icon to open the styled popup.");
135    // The tray owns the platform status item, which has main-thread affinity on
136    // macOS; `Tray::run` proves that at compile time by taking a MainThreadMarker
137    // (issue #46). `main` runs on the main thread, so this is always `Some`.
138    let Some(mtm) = muri::MainThreadMarker::new() else {
139        eprintln!("tray error: must be started on the main thread");
140        return;
141    };
142    if let Err(e) = tray.run(mtm) {
143        eprintln!("tray error: {e}");
144    }
145}
Source

pub fn surface_id(&self) -> SurfaceId

This tray’s process-global SurfaceId (issue #51), assigned once in Tray::new. Lets a MenuEvent consumer correlate an activation back to the surface it came from.

Source

pub fn handle(&self) -> TrayHandle

A cheap, Clone + Send TrayHandle that can drive this tray from any thread once Tray::run is live (posts made earlier buffer). Obtain it before run consumes the tray.

Each call starts a fresh, independently-tracked handle family: dropping every handle/clone in one handle() call’s family auto-shuts-down the tray (issue #47). Calling handle() again creates a separate family — prefer calling it once and fanning out with .clone().

Source

pub fn anchor_rect(&self) -> Result<LogicalRect>

The tray icon’s current on-screen rectangle in logical coordinates (issue #48) — the native, main-thread counterpart of TrayHandle::anchor_rect.

Honest limitation: Tray::run/Tray::spawn consume the Tray, so there’s no &self left once a live icon exists. This queries a fresh platform::current() instance that never had install_tray called, so on macOS/Windows it reliably returns Err until the engine can query a running backend’s live state. Provided for API symmetry with TrayHandle::anchor_rect; Linux’s answer (Unsupported::TrayAnchor) doesn’t depend on installation state.

Source

pub fn menu(self, menu: Menu) -> Self

Attach the menu shown when the icon is clicked.

Examples found in repository?
examples/demo_tray.rs (line 126)
122fn main() {
123    let icon = Icon::from_png(swatch_png(120, 170, 255));
124    let tray = Tray::new(icon)
125        .tooltip("muri demo")
126        .menu(demo_menu())
127        .on_click(|id| {
128            println!("clicked: {}", id.as_str());
129            if id.as_str() == "quit" {
130                std::process::exit(0);
131            }
132        });
133
134    println!("muri demo_tray: click the status-bar icon to open the styled popup.");
135    // The tray owns the platform status item, which has main-thread affinity on
136    // macOS; `Tray::run` proves that at compile time by taking a MainThreadMarker
137    // (issue #46). `main` runs on the main thread, so this is always `Some`.
138    let Some(mtm) = muri::MainThreadMarker::new() else {
139        eprintln!("tray error: must be started on the main thread");
140        return;
141    };
142    if let Err(e) = tray.run(mtm) {
143        eprintln!("tray error: {e}");
144    }
145}
Source

pub fn tooltip(self, text: impl Into<String>) -> Self

Set the tray icon tooltip / accessible name.

Examples found in repository?
examples/demo_tray.rs (line 125)
122fn main() {
123    let icon = Icon::from_png(swatch_png(120, 170, 255));
124    let tray = Tray::new(icon)
125        .tooltip("muri demo")
126        .menu(demo_menu())
127        .on_click(|id| {
128            println!("clicked: {}", id.as_str());
129            if id.as_str() == "quit" {
130                std::process::exit(0);
131            }
132        });
133
134    println!("muri demo_tray: click the status-bar icon to open the styled popup.");
135    // The tray owns the platform status item, which has main-thread affinity on
136    // macOS; `Tray::run` proves that at compile time by taking a MainThreadMarker
137    // (issue #46). `main` runs on the main thread, so this is always `Some`.
138    let Some(mtm) = muri::MainThreadMarker::new() else {
139        eprintln!("tray error: must be started on the main thread");
140        return;
141    };
142    if let Err(e) = tray.run(mtm) {
143        eprintln!("tray error: {e}");
144    }
145}
Source

pub fn title(self, text: impl Into<String>) -> Self

Set the status-item text title — the macOS menu-bar text shown beside (or instead of) the icon, e.g. a live “45%”. Rendered on the NSStatusItem button; on Windows/Linux the notification area has no text label, so this is retained but not drawn.

Source

pub fn options(self, options: MenuOptions) -> Self

Set popup options (width bounds, theme source). MenuOptions is the canonical, single source of truth for a tray’s look and layout (issue #62); theme below is a derived convenience over it.

Source

pub fn theme(self, theme: ThemeSource) -> Self

Convenience: set just the theme source. A thin wrapper over options — it sets the MenuOptions::theme field, which is the canonical “which look” source of truth (issue #62).

Source

pub fn on_click(self, handler: impl Fn(&MenuId) + Send + 'static) -> Self

Register a click handler invoked with the activated row’s MenuId.

Examples found in repository?
examples/demo_tray.rs (lines 127-132)
122fn main() {
123    let icon = Icon::from_png(swatch_png(120, 170, 255));
124    let tray = Tray::new(icon)
125        .tooltip("muri demo")
126        .menu(demo_menu())
127        .on_click(|id| {
128            println!("clicked: {}", id.as_str());
129            if id.as_str() == "quit" {
130                std::process::exit(0);
131            }
132        });
133
134    println!("muri demo_tray: click the status-bar icon to open the styled popup.");
135    // The tray owns the platform status item, which has main-thread affinity on
136    // macOS; `Tray::run` proves that at compile time by taking a MainThreadMarker
137    // (issue #46). `main` runs on the main thread, so this is always `Some`.
138    let Some(mtm) = muri::MainThreadMarker::new() else {
139        eprintln!("tray error: must be started on the main thread");
140        return;
141    };
142    if let Err(e) = tray.run(mtm) {
143        eprintln!("tray error: {e}");
144    }
145}
Source

pub fn set_menu(&mut self, menu: Menu)

Replace the menu content at runtime (cheap; re-rendered on next open).

Source

pub fn set_icon(&mut self, icon: Icon)

Replace the status-bar icon at runtime (re-published on next backend update; on Linux this re-registers the SNI icon_pixmap).

Source

pub fn set_title(&mut self, title: Option<String>)

Replace the status-item text title at runtime (macOS menu-bar text).

Source

pub fn title_text(&self) -> Option<&str>

The status-item text title, if set.

Source

pub fn tooltip_text(&self) -> Option<&str>

The tooltip, if set.

Source

pub fn current_menu(&self) -> &Menu

Borrow the current menu.

Source

pub fn icon(&self) -> &Icon

Borrow the icon.

Source

pub fn menu_options(&self) -> &MenuOptions

Borrow the options.

Source

pub fn accessibility_tree(&self) -> AxTree

Build the AxTree the platform screen reader walks over the current menu. The backend rebuilds this whenever the menu is (re)opened or swapped and feeds it to the platform accessibility API (via AccessKit).

Source

pub fn dispatch(&self, id: &MenuId)

Dispatch a click to the registered handler, if any. Used by the backend when a row is activated; exposed so the data flow is testable without a live event loop.

Order (spec 03 §3): the per-surface on_click closure runs first (synchronously), then the same activation is projected onto the global MenuEvent channel and any set_event_handler. An inert MenuId::none fires neither.

Source

pub fn run(self, _m: MainThreadMarker) -> Result<()>

Install the tray icon and run the platform event loop, dispatching row activations to the registered handler and the global MenuEvent channel. Consumes the Tray and blocks for its lifetime; obtain a TrayHandle with Tray::handle before calling this to drive it from any thread.

Per-OS backend, selected once in platform::current: macOS runs the native NSApplication loop, Windows the Win32 message pump, Linux its SNI/AppIndicator worker loop. Takes a MainThreadMarker (issue #46) since installing the NSStatusItem is only safe on AppKit’s main thread.

Examples found in repository?
examples/demo_tray.rs (line 142)
122fn main() {
123    let icon = Icon::from_png(swatch_png(120, 170, 255));
124    let tray = Tray::new(icon)
125        .tooltip("muri demo")
126        .menu(demo_menu())
127        .on_click(|id| {
128            println!("clicked: {}", id.as_str());
129            if id.as_str() == "quit" {
130                std::process::exit(0);
131            }
132        });
133
134    println!("muri demo_tray: click the status-bar icon to open the styled popup.");
135    // The tray owns the platform status item, which has main-thread affinity on
136    // macOS; `Tray::run` proves that at compile time by taking a MainThreadMarker
137    // (issue #46). `main` runs on the main thread, so this is always `Some`.
138    let Some(mtm) = muri::MainThreadMarker::new() else {
139        eprintln!("tray error: must be started on the main thread");
140        return;
141    };
142    if let Err(e) = tray.run(mtm) {
143        eprintln!("tray error: {e}");
144    }
145}
Source

pub fn spawn(self, _m: MainThreadMarker) -> Result<TrayHandle>

Install the tray icon and begin driving it without blocking, returning a TrayHandle to mutate it from any thread. The non-blocking counterpart to Tray::run, for hosts that own their own event loop (e.g. the tray-icon compat facade).

On Windows/Linux the UI pump runs on a dedicated background thread. On macOS this is best-effort: spawn must be called from the main thread and relies on the host’s existing NSApplication loop (see Platform::spawn_tray). Takes a MainThreadMarker (issue #46) for the same reason as Tray::run.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Tray

§

impl !Sync for Tray

§

impl !UnwindSafe for Tray

§

impl Freeze for Tray

§

impl Send for Tray

§

impl Unpin for Tray

§

impl UnsafeUnpin for Tray

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AutoreleaseSafe for T
where T: ?Sized,

Source§

impl<T> AutoreleaseSafe for T
where T: ?Sized,

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.