Skip to main content

Icon

Enum Icon 

Source
pub enum Icon {
    Png(Arc<[u8]>),
    Svg(Arc<[u8]>),
    Checkmark,
    Symbol(&'static str),
}
Expand description

A leading/trailing icon or logo. Raster (PNG) bytes are decoded at load; SVG bytes are rasterized by muri’s own zeno-backed restricted-subset rasterizer (paths, basic shapes, solid fills, transform; no filters/text/ gradients — those should ship as PNG). Both feed the same icon path.

Variants§

§

Png(Arc<[u8]>)

A PNG (or other auto-detected raster format) from raw bytes.

§

Svg(Arc<[u8]>)

An SVG from raw bytes, rasterized per target size via muri’s restricted SVG subset (see the Icon note for what’s supported).

§

Checkmark

The themed checkmark glyph, drawn in the leading column.

§

Symbol(&'static str)

A named symbol: an SF Symbol on macOS, with a bundled fallback elsewhere.

Implementations§

Source§

impl Icon

Source

pub fn from_png(bytes: impl Into<Arc<[u8]>>) -> Self

The one obvious way to build a raster icon: from encoded PNG (or any auto-detected raster format) bytes. The 90% path for a logo/avatar.

Examples found in repository?
examples/usagio_menu.rs (line 99)
94fn capture_submenu(groups: &[Group]) -> Menu {
95    let mut menu = Menu::new();
96    for group in groups {
97        menu = menu.row(
98            Row::new(format!("capture:{}", group.display_name.to_lowercase()))
99                .leading(Icon::from_png(group.icon_png))
100                .label(format!("Capture {} login", group.display_name)),
101        );
102    }
103    menu
104}
105
106/// The Settings submenu: a checkable toggle, an auto-swap threshold flyout, and
107/// a backup flyout — exercising nested submenus, `checked`, and an SVG icon.
108fn settings_submenu() -> Menu {
109    let autoswap = Menu::new()
110        .row(Row::new("autoswap:off").checked(false).label("Off"))
111        .row(Row::new("autoswap:80").checked(false).label("At 80%"))
112        .row(Row::new("autoswap:90").checked(true).label("At 90%"))
113        .separator()
114        .row(Row::new("autoswap:now").label("Swap now"));
115
116    let backup = Menu::new()
117        .row(Row::new("backup:save").label("Save backup\u{2026}"))
118        .row(Row::new("backup:restore").label("Restore backup\u{2026}"));
119
120    Menu::new()
121        .row(
122            Row::new("notifications:limits")
123                .checked(true)
124                .label("Notify near limits"),
125        )
126        .submenu(Row::new("autoswap").label("Auto-swap threshold"), autoswap)
127        .submenu(
128            Row::new("backup")
129                // An SVG leading icon, rasterized per-DPI at draw time.
130                .leading(Icon::from_svg(
131                    br#"<svg xmlns="http://www.w3.org/2000/svg"/>"#.as_slice(),
132                ))
133                .label("Backup"),
134            backup,
135        )
136        .separator()
137        .row(Row::new("refresh:now").label("Refresh now"))
138}
139
140fn build(groups: &[Group]) -> Menu {
141    let mut menu = Menu::new();
142
143    for group in groups {
144        // Bold, logo'd, non-interactive group header (Claude, then Codex).
145        menu = menu.section_header(
146            Row::default()
147                .leading(Icon::from_png(group.icon_png))
148                .segment(Segment::new(group.display_name).font(Font::system(13.0, Weight::Bold))),
149        );
150
151        for acct in &group.accounts {
152            // The label Grows to eat the leftover width, so the value segment
153            // sits flush at the true right edge — no reserved chevron column.
154            let label = Segment::new(acct.display)
155                .flex(Flex::Grow)
156                .font(if acct.active {
157                    Font::system(13.0, Weight::Bold)
158                } else {
159                    Font::system(13.0, Weight::Regular)
160                });
161
162            let value = Segment::new(acct.trailing)
163                .align(Align::Right)
164                .runs(severity_runs(&acct.severity));
165
166            let mut label_row = Row::new(format!("switch:{}:{}", acct.provider, acct.key))
167                .segments(vec![label, value])
168                .checked(acct.active);
169            if acct.active {
170                label_row = label_row.leading(Icon::Checkmark);
171            }
172
173            menu = menu.submenu(label_row, account_submenu(acct));
174        }
175
176        menu = menu.separator();
177    }
178
179    // Bottom actions — now with populated submenus.
180    menu = menu
181        .submenu(
182            Row::new("capture").label("Capture current login"),
183            capture_submenu(groups),
184        )
185        .submenu(Row::new("settings").label("Settings"), settings_submenu());
186
187    // "Quit ........ usagio vX" — label Grows, version tail is greyed + flush-right.
188    menu = menu.row(Row::new("quit").segments(vec![
189        Segment::new("Quit").flex(Flex::Grow),
190        Segment::new(format!("usagio v{}", env!("CARGO_PKG_VERSION")))
191            .align(Align::Right)
192            .color(Color::SecondaryLabel),
193    ]));
194
195    menu
196}
More examples
Hide additional examples
examples/demo_tray.rs (line 67)
60fn demo_menu() -> Menu {
61    let claude_logo = swatch_png(217, 119, 87);
62    let codex_logo = swatch_png(80, 80, 90);
63
64    Menu::new()
65        .section_header(
66            Row::default()
67                .leading(Icon::from_png(claude_logo))
68                .segment(Segment::new("Claude").font(Font::system(13.0, Weight::Bold))),
69        )
70        .submenu(
71            Row::new("acct:claude:me")
72                .leading(Icon::Checkmark)
73                .checked(true)
74                .segments(vec![
75                    Segment::new("me@example.com")
76                        .flex(Flex::Grow)
77                        .font(Font::system(13.0, Weight::Bold)),
78                    Segment::new("47% / 89%")
79                        .align(Align::Right)
80                        .runs(vec![StyleRun::new(6, 3, Color::SystemRed)]),
81                ]),
82            account_submenu("claude:me"),
83        )
84        .submenu(
85            Row::new("acct:claude:work").segments(vec![
86                Segment::new("work@example.com").flex(Flex::Grow),
87                Segment::new("12% / 30%")
88                    .align(Align::Right)
89                    .runs(vec![StyleRun::new(0, 3, Color::SystemGreen)]),
90            ]),
91            account_submenu("claude:work"),
92        )
93        .separator()
94        .section_header(
95            Row::default()
96                .leading(Icon::from_png(codex_logo))
97                .segment(Segment::new("Codex").font(Font::system(13.0, Weight::Bold))),
98        )
99        .row(Row::new("switch:codex:me").segments(vec![
100            Segment::new("me@example.com").flex(Flex::Grow),
101            Segment::new("3h 12m")
102                .align(Align::Right)
103                .runs(vec![StyleRun::new(0, 6, Color::SystemOrange)]),
104        ]))
105        .separator()
106        .submenu(
107            Row::new("settings").label("Settings"),
108            Menu::new()
109                .row(Row::new("settings:apikey").label("API key…"))
110                .row(Row::new("settings:autoswap").label("Auto-swap accounts"))
111                .separator()
112                .row(Row::new("settings:notifications").label("Notifications…")),
113        )
114        .row(Row::new("quit").segments(vec![
115            Segment::new("Quit").flex(Flex::Grow),
116            Segment::new(concat!("usagio v", env!("CARGO_PKG_VERSION")))
117                .align(Align::Right)
118                .color(Color::SecondaryLabel),
119        ]))
120}
121
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 from_rgba(rgba: &[u8], width: u32, height: u32) -> Result<Self>

The one obvious way to build an icon from raw straight-alpha RGBA8 pixels: width * height * 4 bytes, row-major. muri keeps a single encoded-bytes representation internally (the pixels are encoded to PNG), so a consumer never has to choose a representation — hence this returns a plain Icon, indistinguishable at the type level from one built with Icon::from_png. Returns Error::BadIcon when the buffer length doesn’t equal width * height * 4 (or either dimension is zero).

Source

pub fn from_svg(bytes: impl Into<Arc<[u8]>>) -> Self

The one obvious way to build an icon from raw SVG bytes (rasterized per target size via muri’s restricted SVG subset; see the Icon note).

Examples found in repository?
examples/usagio_menu.rs (lines 130-132)
108fn settings_submenu() -> Menu {
109    let autoswap = Menu::new()
110        .row(Row::new("autoswap:off").checked(false).label("Off"))
111        .row(Row::new("autoswap:80").checked(false).label("At 80%"))
112        .row(Row::new("autoswap:90").checked(true).label("At 90%"))
113        .separator()
114        .row(Row::new("autoswap:now").label("Swap now"));
115
116    let backup = Menu::new()
117        .row(Row::new("backup:save").label("Save backup\u{2026}"))
118        .row(Row::new("backup:restore").label("Restore backup\u{2026}"));
119
120    Menu::new()
121        .row(
122            Row::new("notifications:limits")
123                .checked(true)
124                .label("Notify near limits"),
125        )
126        .submenu(Row::new("autoswap").label("Auto-swap threshold"), autoswap)
127        .submenu(
128            Row::new("backup")
129                // An SVG leading icon, rasterized per-DPI at draw time.
130                .leading(Icon::from_svg(
131                    br#"<svg xmlns="http://www.w3.org/2000/svg"/>"#.as_slice(),
132                ))
133                .label("Backup"),
134            backup,
135        )
136        .separator()
137        .row(Row::new("refresh:now").label("Refresh now"))
138}

Trait Implementations§

Source§

impl Clone for Icon

Source§

fn clone(&self) -> Icon

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Icon

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Icon

§

impl RefUnwindSafe for Icon

§

impl Send for Icon

§

impl Sync for Icon

§

impl Unpin for Icon

§

impl UnsafeUnpin for Icon

§

impl UnwindSafe for Icon

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.