App::open_window and App::menu_bar have been callable since OxiUI shipped. Until this release, calling them stored a value that nothing ever read.
Today we released OxiUI 0.2.2 — a release that finishes wiring two APIs that previously compiled, chained, and silently did nothing: the multi-window registry and the menu bar now reach a real rendering path in every backend that can support them. Alongside that, two backends (oxiui-slint, oxiui-dioxus) stop returning a fabricated Ok(()) for a window that never opened, oxiui-compute-wgpu stops panicking on GPU device loss, and oxiui-web compiles for wasm32-unknown-unknown again.
No GTK. No Qt. No SDL. The dependency shape hasn’t changed — OxiUI still builds in a clean rust:slim container on the same egui/iced/slint/dioxus facades and the same wgpu/softbuffer rendering paths. What changed in 0.2.2 is how honestly each backend reports what it actually did.
Why OxiUI 0.2.2 is a game changer
The previous release (0.2.1) fixed lifecycle hooks that compiled but never fired. This release closes the same category of bug in three more places, plus two real crashes:
App::open_windowregistered a secondary window;App::menu_barregistered aMenuBar. Both were stored on theAppstruct and read by nobody — no backend consumed either one, so calling them had zero visible effect.oxiui_slint::run_slintandoxiui_dioxus::run_dioxusexecuted the content closure in headless collection mode and returnedOk(())— indistinguishable, from the caller’s side, from a real window that opened, ran, and closed normally.OxiIcedWidget::drawhad an empty body: a placed widget was a correctly-sized, completely invisible hole in the layout.oxiui-web::set_themeencoded an inert"__theme:<name>"sentinel throughinject_eventthat no code path decoded — worse, it risked that literal string landing inside a focused text field.oxiui-compute-wgpu’s buffer readback and fiveDispatchermethods (map_f32,zip_map_f32,reduce_sum_f32,sph_density,sort_f32) called.expect()on GPU device-poll and buffer-mapping results — a device loss or out-of-memory condition took down the whole process instead of returning an error.oxiui-webdidn’t compile forwasm32-unknown-unknownat all: two missingweb-sysfeatures and several call sites that had drifted from the currentweb-sys0.3.103 API shape.
OxiUI 0.2.2 ends all of that.
- Multi-window and the menu bar reach real widgets now. A new
oxiui::shell::ShellConfigbundles registered windows, their content closures, the menu bar, and the runtimeWindowHandle;App::run()hands it to the backend through a newBackendRunner::set_shelltrait method whose default implementation rejects a non-empty shell withUiError::Unsupported— a runner can no longer swallow a window or menu bar in silence, it has to say so. - The egui backend opens real OS windows. One
egui::Context::show_viewport_deferredviewport per registered secondary window, torn down cleanly when the user closes it. render_menu_bar+MenuBarStatedraw through anyUiCtx— egui above the primary frame, iced above the primary view, and bothrun_headless_onceandbuild_a11y_snapshotrender it too, so accessibility and headless testing see the same menu structure a real window does.OxiIcedWidget::drawrenders. It now materializes into a real iced element through the samebuild_onepipelineIcedUiCtx::into_iced_elementalready used, and delegates everyWidgetmethod — layout, draw, events, children, overlay.oxiui-web::set_themechanges the theme. Dark/light apply directly viaegui::Context::set_theme; high-contrast maps the COOLJAPAN WCAG-AAA palette throughoxiui_egui::palette_to_egui_visuals.oxiui-compute-wgpureturnsResultinstead of panicking on device loss or OOM across buffer readback and all fiveDispatchermethods; the render/text integration helpers keep their infallible public signatures but now fall back to their CPU sibling implementation instead of aborting.oxiui-webcompiles forwasm32-unknown-unknownagain, with the missingweb-sysfeatures added and every drifted call site (exec_command,FontFace::new_with_str_and_descriptors,request_fullscreen/exit_fullscreen,ServiceWorkerRegistration::unregister) updated to the current API.- 2,024 tests passing across 16 crates, zero clippy warnings, Pure Rust default features, MSRV 1.89.
Technical Deep Dive: shell, widget IDs, and honest failure
- The shell pipeline (
oxiui::shell,oxiui::menu).ShellConfigis the single value that carries everything a backend needs to actually show windows and menus — secondary windows plus their content closures, the menu bar, and aWindowHandle.BackendRunner::set_shellis the seam every backend implements against; a backend that can’t support part of the shell (iced can’t do multi-window — its 0.14applicationruntime is single-window,iced::daemonwould be required) rejects it explicitly withUiError::Unsupportedrather than accepting and ignoring it. - Disjoint widget-id ranges (
EguiUiCtx::with_id_base,IcedUiCtx::with_id_base/next_widget_id). Rendering the menu bar and the content into the same frame means twoUiCtxs drawing widgets that need stable, non-colliding IDs. The menu bar’s widget count changes as its dropdown opens and closes; without a reserved high ID range, every content widget’s ID would shift underneath it, silently rerouting iced clicks and invalidating egui’s persistent widget state (dropdown selection, popup position, grid column widths). - New
WindowHandleand headless frame driving.App::open_window_with(config, F)attaches a per-window content closure;App::window_handle()returns a cloneable, thread-safe handle withopen/close/focus, drained each frame into aWindowSessionstate machine.App::run_headless_frame(&mut dyn UiCtx) -> usizedrives one complete frame — menu bar, init, content, per-frame hooks — and reports how many menu actions fired, the display-free way to exercise the whole shell path in tests. - A new fuzz harness (
fuzz/) foroxiui-render-soft.fuzz_fill_polygon/fuzz_composite_intogeneralize the 0.2.1 security fixes to arbitrary finite input, and a newfuzz_bezier_flattentarget found two real, still-open crashes on its first run: a subtract-overflow panic inscanline.rs’s fast-forward loop and a stack-overflow inpath.rs’s adaptive bezier flattening from finite-input float overflow. Both repro inputs are checked intofuzz/regressions/(not gitignored, unlike the corpus/artifact dirs) with decoded parameters infuzz/regressions/README.md— tracked inTODO.md’s backlog, not yet fixed.
Getting Started
cargo add oxiui
use oxiui::{App, theme};
use oxiui::menu::{MenuBar, MenuItem};
fn main() -> Result<(), Box<dyn std::error::Error>> {
App::new("My App")
.theme(theme::cooljapan_default())
.menu_bar(MenuBar::new().item(MenuItem::action("Quit", |_| std::process::exit(0))))
.content(|ui| {
ui.heading("Hello from OxiUI — the menu bar above this actually renders now");
})
.run()?;
Ok(())
}
Open a second window and hold onto a handle for it:
use oxiui::{App, WindowConfig};
let app = App::new("Main")
.open_window_with(WindowConfig::new("settings"), |ui| {
ui.label("Settings window content");
});
let handle = app.window_handle();
handle.open("settings")?;
What’s New in 0.2.2
- Added: multi-window registry and menu bar wired into a real rendering path (
oxiui::shell::ShellConfig,BackendRunner::set_shell,oxiui::menu::render_menu_bar/MenuBarState); real OS viewports for secondary windows on the egui backend;App::open_window_with,App::window_handle()/WindowHandle,App::run_headless_frame;EguiUiCtx::with_id_base/IcedUiCtx::with_id_base/next_widget_idfor disjoint menu/content widget-id ranges; afuzz/cargo-fuzz harness foroxiui-render-soft;hello_headlessexample smoke test wired intoDockerfile.ffi-audit; rootrustfmt.toml/clippy.toml. - Changed:
oxiui-slint/oxiui-dioxusnow returnErr(UiError::Unsupported(..))instead of fabricatingOk(())for a window that never opened;OxiIcedWidget::drawrenders instead of being an invisible placeholder;oxiui-web::set_themegenuinely applies the theme;oxiui-compute-wgpureturnsResultinstead of panicking on GPU device-loss/OOM, falling back to CPU where its public signature stays infallible;pollster0.4.0 → 1.0.1 (major, re-exported);oxifft0.4.1 → 0.4.2,oxicode0.2.5 → 0.2.6,oxifont0.2.1 → 0.2.2,oxitext/oxitext-sdf0.2.1 → 0.2.2. - Fixed:
oxiui-webcompiles forwasm32-unknown-unknownagain — missingweb-sysfeatures (HtmlHeadElement,FontFaceDescriptors) added, andexec_command,FontFace::new_with_str_and_descriptors,request_fullscreen/exit_fullscreen, andServiceWorkerRegistration::unregisterupdated to the currentweb-sys0.3.103 API shape. - Known Issues: the new fuzz harness found two pre-existing, still-open crashes in
oxiui-render-soft— a subtract-overflow inscanline.rsand a stack-overflow inpath.rs’s bezier flattening — repro inputs checked intofuzz/regressions/, not yet fixed.
Tips
- If you called
App::open_window/menu_barbefore 0.2.2, check what actually shows up now. Both always compiled and chained; neither reached a backend. Re-verify the window/menu you configured actually behaves the way your code implies. - Building a custom
BackendRunner? Implementset_shellexplicitly. The default implementation rejects any non-empty shell withUiError::Unsupported— you’ll get a clear error instead of a silently ignored window or menu bar. - iced apps: multi-window still isn’t supported, and now says so.
IcedRunner::set_shellaccepts the menu bar but returnsUiError::Unsupportedfor secondary windows (iced 0.14’sapplicationruntime is single-window). Handle that error rather than assuming success. oxiui-slint/oxiui-dioxuscallers: check yourrun()error handling. A window that doesn’t actually open now returnsErr, notOk(())— if you were relying on the old behavior to mean “it worked,” that assumption just became visible.- GPU code against
oxiui-compute-wgpu: handle the newResultreturns.read_back/read_back_range/TypedBuffer::downloadand all fiveDispatchermethods can now returnComputeErroron device loss or OOM instead of aborting your process. - Fuzzing your own geometry inputs?
cargo +nightly fuzz run fuzz_bezier_flattenfrom the workspace root reproduces the two known-open crashes directly — useful as a starting corpus if you’re hardening a downstream consumer before OxiUI’s own fix lands.
This is the foundation
OxiUI is part of NoFFI — the COOLJAPAN initiative to replace every C/C++/Fortran/-sys FFI dependency in the Rust ecosystem with a clean, memory-safe, 100% Pure Rust implementation. It’s built directly on its NoFFI siblings OxiText (text shaping) and OxiFont (font loading + raster), and OxiAero depends on oxiui-compute-wgpu for its GPU compute path — exactly the crate this release made fail gracefully instead of panicking on device loss.
Repository: https://github.com/cool-japan/oxiui
Star the repo if you’d rather get UiError::Unsupported than a window that silently never opens.
Pure Rust UI — sovereign, safe, and honest about what it can’t do yet.
— KitaSan at COOLJAPAN OÜ August 6, 2026