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
NSStatusItembutton; AppKit computes the on-screen rect and which display the menu bar is on. - Windows: anchored via
Shell_NotifyIconGetRect, positioning aWS_EX_NOACTIVATElayered window toward screen center. - Linux:
Tray::runinstalls 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 reportsError::Unsupported(Unsupported::TrayAnchor)). Use that native menu or a pointer-anchoredContextMenu.
§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
impl Tray
Sourcepub fn new(icon: Icon) -> Self
pub fn new(icon: Icon) -> Self
Create a tray with the given status-bar icon.
Examples found in repository?
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}Sourcepub fn surface_id(&self) -> SurfaceId
pub fn surface_id(&self) -> SurfaceId
Sourcepub fn handle(&self) -> TrayHandle
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().
Sourcepub fn anchor_rect(&self) -> Result<LogicalRect>
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.
Attach the menu shown when the icon is clicked.
Examples found in repository?
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}Sourcepub fn tooltip(self, text: impl Into<String>) -> Self
pub fn tooltip(self, text: impl Into<String>) -> Self
Set the tray icon tooltip / accessible name.
Examples found in repository?
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}Sourcepub fn title(self, text: impl Into<String>) -> Self
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.
Sourcepub fn options(self, options: MenuOptions) -> Self
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.
Sourcepub fn theme(self, theme: ThemeSource) -> Self
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).
Sourcepub fn on_click(self, handler: impl Fn(&MenuId) + Send + 'static) -> Self
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?
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}Replace the menu content at runtime (cheap; re-rendered on next open).
Sourcepub fn set_icon(&mut self, icon: Icon)
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).
Sourcepub fn set_title(&mut self, title: Option<String>)
pub fn set_title(&mut self, title: Option<String>)
Replace the status-item text title at runtime (macOS menu-bar text).
Sourcepub fn title_text(&self) -> Option<&str>
pub fn title_text(&self) -> Option<&str>
The status-item text title, if set.
Sourcepub fn tooltip_text(&self) -> Option<&str>
pub fn tooltip_text(&self) -> Option<&str>
The tooltip, if set.
Borrow the current menu.
Borrow the options.
Sourcepub fn accessibility_tree(&self) -> AxTree
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).
Sourcepub fn dispatch(&self, id: &MenuId)
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.
Sourcepub fn run(self, _m: MainThreadMarker) -> Result<()>
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?
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}Sourcepub fn spawn(self, _m: MainThreadMarker) -> Result<TrayHandle>
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.