COOLJAPAN
← All posts

OxiUI 0.2.1 Released — Lifecycle Hooks Go Live, Persistent State Finally Persists

OxiUI 0.2.1 wires on_close/on_resize/on_focus into the real egui and iced event loops for the first time, makes with_persistent_state actually write to disk via oxicode, and closes an integer-overflow bounds bypass plus an unbounded-iteration DoS in the CPU rasterizer. The sovereign GUI layer for the COOLJAPAN ecosystem.

release oxiui pure-rust cooljapan noffi ui gui security cross-platform

OxiUI’s on_close, on_resize, and on_focus hooks have been part of the public API since the framework shipped — and until this release, none of them ever fired.

Today we released OxiUI 0.2.1 — a fix release that wires lifecycle hooks into the real eframe/iced event loops for the first time, makes with_persistent_state actually write your app’s state to disk instead of silently discarding it, and closes two bugs in the CPU software rasterizer: an integer-overflow bounds-check bypass and an unbounded-iteration denial-of-service.

No GTK. No Qt. No SDL. The dependency tree hasn’t changed — OxiUI still builds with a single cargo build in a clean rust:slim container, on the same egui/iced facade, the same wgpu and softbuffer rendering paths, and the same winit windowing. What changed in 0.2.1 is what the API actually does once your app is running.

Why OxiUI 0.2.1 is a game changer

The previous release had a class of bug that’s worse than a crash — silent no-ops behind a compiling, chainable API:

OxiUI 0.2.1 ends all of that:

Technical Deep Dive: wiring three hooks into two different event loops

  1. Lifecycle detection (runner::LifecycleTracker). Both backends poll or receive raw state every tick — egui reads the viewport size and focus flag each frame; iced receives raw winit-style window events. The tracker’s job is purely to de-duplicate: it holds the last-seen size/focus/close state and only emits a LifecycleEvent when something actually changed, so on_resize doesn’t fire every frame just because the window happened to redraw.
  2. Runner ownership. Previously, EguiRunner/IcedRunner existed as thin constructors that didn’t retain the app’s hooks. Now they’re real BackendRunner implementations: EguiRunner::new() / IcedRunner::new() (plus a theme setter) take ownership of the theme, hook vectors, and plugin list, and App::run() simply hands its state to the matching runner and lets it drive the native event loop.
  3. Persistence. with_persistent_state decodes State from storage_path via oxicode on startup (falling back to the caller’s initial value on any decode error, with a stderr warning — never a panic), wraps the live value in Arc<Mutex<State>> shared between the per-frame content closure and a new on_close hook, and encodes it back to disk when that hook fires.
  4. Hardening the CPU rasterizer. oxiui-render-soft’s composite_into guard and its scanline fill/paint routines both took raw, potentially attacker-shaped geometry as trusted input. The fix in both cases is the same shape: replace unchecked arithmetic (u32 multiplication, unclamped iteration bounds) with checked arithmetic and framebuffer-relative clamping, so malformed or extreme input degrades to “draw nothing” instead of “read out of bounds” or “spin forever.”

Getting Started

cargo add oxiui
use oxiui::{App, theme};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    App::new("My App")
        .theme(theme::cooljapan_default())
        .on_close(|_ui| {
            println!("window closing — this now actually fires");
        })
        .content(|ui| {
            ui.heading("Hello from OxiUI");
        })
        .run()?;
    Ok(())
}

With the persist feature on, the same builder can carry real state across runs:

use oxicode::{Encode, Decode};
use oxiui::App;

#[derive(Encode, Decode, Default)]
struct Counter {
    n: u32,
}

let app = oxiui::App::new("Counter").with_persistent_state(
    Counter::default(),
    std::env::temp_dir().join("my_app_state.oxi"),
    |ui, state| {
        if ui.button("Click me").clicked() {
            state.n += 1;
        }
        ui.label(&format!("Clicks: {}", state.n));
    },
);
app.run()?;

Close the window, run it again, and state.n picks up where it left off — for real, this time.

What’s New in 0.2.1

Tips

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 this release matters most for anything that keeps state across runs or reacts to window lifecycle: settings panels, data UIs, and any app that used to lose its state on every restart without ever throwing an error.

Repository: https://github.com/cool-japan/oxiui

Star the repo if you’ve ever shipped a hook that quietly did nothing and want a framework that tells you when it does.

Pure Rust UI — sovereign, safe, and FFI-free.

KitaSan at COOLJAPAN OÜ July 30, 2026

↑ Back to all posts