1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Display-free, one-call offscreen rendering of a built [`Menu`] to pixels
//! (issue #59).
//!
//! [`render_menu_to_png`] / [`render_menu_to_rgba`] rasterize a menu popup to a
//! bitmap **without** creating a tray, a window, or requiring any display, TCC
//! prompt, or Accessibility permission — the exact same layout + paint pass a
//! live popup runs ([`render_menu`](super::paint::render_menu) over a
//! [`RasterDrawer`]), driven straight into an in-memory
//! [`Framebuffer`](super::Framebuffer) and read back out. That makes them
//! suitable for generating screenshots and cross-OS golden images on a headless
//! CI runner.
//!
//! These entry points are always available: they need no platform/tray feature
//! and carry no `#[cfg(target_os)]` — they render the same way on every OS.
//!
//! ## Theme resolution is handled for you
//!
//! The caller passes only `&Menu`, `&MenuOptions`, and a device `scale`; the
//! concrete [`Theme`](crate::Theme) is resolved internally from
//! [`MenuOptions::theme`](crate::MenuOptions) exactly as a real popup resolves
//! it, and the matching font tier is selected the same way
//! ([`RasterDrawer::for_menu_options`]): a forced OS look pins the *target* OS's
//! UI font via the forced-theme / bundled-font resolution path, so the offscreen
//! pixels match what the on-screen popup would draw.
//!
//! ## Cross-OS looks from a single host
//!
//! Set [`MenuOptions::theme`](crate::MenuOptions) to a forced source
//! ([`ThemeSource::MacOs`](crate::ThemeSource::MacOs) /
//! [`Windows`](crate::ThemeSource::Windows) /
//! [`Gnome`](crate::ThemeSource::Gnome)) to render any OS's OEM menu look from
//! any host — the basis for a single-runner cross-OS golden suite. With the
//! `bundled-fonts` feature on, a forced look also renders in a vendored OSS
//! substitute face when the real target font is absent, so the output is
//! byte-identical across macOS/Windows/Linux runners.
//!
//! ## Appearance and hovered rows
//!
//! The render is deterministic and headless, so it does **not** query the live
//! system appearance or accent: a `System(..)`/forced source with
//! [`ThemeMode::Auto`](crate::ThemeMode) resolves to the *light* look. Pass an
//! explicit dark mode (e.g. `ThemeSource::MacOs(ThemeMode::Dark)`) for the dark
//! variant. No row is highlighted (no hovered state); to render a specific
//! hovered row, drive [`render_menu`](super::paint::render_menu) over a
//! [`RasterDrawer`] directly with a `highlight` index.
use crateMenu;
use crate;
use render_menu;
use RasterDrawer;
/// The host OS family, mapped from [`std::env::consts::OS`] (a compile-time
/// target constant, *not* a `#[cfg(target_os)]` branch — this module is
/// OS-agnostic by contract). Used only to resolve a `System(..)` theme source
/// against the host look; forced sources ignore it entirely.
/// Resolve the concrete [`Theme`] from `options` the same way a live popup does,
/// but display-free: forced sources (`MacOs`/`Windows`/`Gnome`) yield the target
/// OS look on any host; `System`/`Preset`/`Custom` resolve against the host
/// family. No live OS accent/palette/menu-font is injected (that path needs a
/// display/TCC), and the appearance defaults to light, so the output is
/// deterministic on a headless runner.
/// Render `menu` into a fresh [`RasterDrawer`] using the theme and font tier
/// resolved from `options` — the shared core of both public entry points.
/// Render a built [`Menu`] to PNG bytes, display-free — no tray, no window, no
/// display/TCC/Accessibility permission required.
///
/// The popup is laid out and painted exactly as a live one would be
/// ([`render_menu`](super::paint::render_menu) over a [`RasterDrawer`]), then
/// encoded to a straight-alpha RGBA8 PNG. `scale` is the device scale factor
/// (device pixels per logical pixel, e.g. `2.0` for a Retina-density capture).
///
/// The [`Theme`](crate::Theme) is resolved internally from
/// [`options.theme`](crate::MenuOptions) — the caller never builds a `Theme`.
/// Force a cross-OS look with
/// [`ThemeSource::MacOs`](crate::ThemeSource::MacOs) /
/// [`Windows`](crate::ThemeSource::Windows) /
/// [`Gnome`](crate::ThemeSource::Gnome) to render any OS's OEM menu from any
/// host (ideal for CI screenshots and cross-OS golden tests).
///
/// The render is deterministic and headless: it does not query the live system
/// appearance or accent, so a `System(..)`/forced source with
/// [`ThemeMode::Auto`](crate::ThemeMode) resolves to the *light* look (pass an
/// explicit dark mode for the dark variant), and no row is highlighted.
///
/// # Examples
///
/// ```
/// use muri::{Menu, MenuOptions, Row};
///
/// let menu = Menu::new().row(Row::new("quit").label("Quit"));
/// let png = muri::render_menu_to_png(&menu, &MenuOptions::default(), 2.0);
/// assert!(!png.is_empty());
/// ```
/// Render a built [`Menu`] to straight-alpha RGBA8 pixels, returning
/// `(rgba, width, height)` in device pixels — the same display-free pass as
/// [`render_menu_to_png`] without the PNG encode, for callers that want the raw
/// buffer (diffing, custom encoding, feeding another rasterizer).
///
/// `rgba` is `width * height * 4` bytes, row-major, un-premultiplied
/// (`R, G, B, A`). `scale` is the device scale factor. The
/// [`Theme`](crate::Theme) is resolved internally from
/// [`options.theme`](crate::MenuOptions); force a cross-OS look with
/// [`ThemeSource::MacOs`](crate::ThemeSource::MacOs) /
/// [`Windows`](crate::ThemeSource::Windows) /
/// [`Gnome`](crate::ThemeSource::Gnome). Like [`render_menu_to_png`], the render
/// is deterministic and headless (light look for an `Auto` appearance, no
/// highlighted row).
///
/// # Examples
///
/// ```
/// use muri::{Menu, MenuOptions, Row};
///
/// let menu = Menu::new().row(Row::new("quit").label("Quit"));
/// let (rgba, w, h) = muri::render_menu_to_rgba(&menu, &MenuOptions::default(), 2.0);
/// assert_eq!(rgba.len(), (w * h * 4) as usize);
/// ```