Rxi's Microui Port to Idiomatic Rust
This project started as a C2Rust conversion of Rxi's MicroUI and has since grown into a Rust-first UI toolkit. It keeps Microui's compact rendering model while moving UI authoring onto retained WidgetTree values, stateful widget structs with pointer-derived identity, and backend-agnostic rendering hooks.
Compared to microui-rs, this crate embraces std types, reusable retained trees, and richer widgets such as custom rendering callbacks, dialogs, and a file dialog.
Demo
Clone and build the demo (enable exactly one backend feature):
$ cargo run --example demo-full --features example-vulkan # Vulkan backend
# or
$ cargo run --example demo-full --features example-glow # Glow backend
# or
$ cargo run --example demo-full --features example-wgpu # WGPU backend
example-backend is only a shared gate for example code paths; it is not runnable by itself.
Running with only --features example-backend will fail intentionally at compile time.
demo-full now loads examples/FACEPALM.png and assets/suzane.obj from disk at runtime (no include_bytes! for those files).
For a smaller release executable, use nightly + rebuilt std:
RUSTFLAGS="-C strip=symbols -C link-arg=-s -Zlocation-detail=none -Zfmt-debug=none" \
Replace example-wgpu with example-glow or example-vulkan if needed.

Key Concepts
- Context: owns the renderer handle, user input, frame results, and root windows. Each frame starts by feeding input into the context, then calling
context.window(...),context.dialog(...), orcontext.popup(...)with retained trees for every visible surface. - Container: the internal execution object behind windows, panels, popups, and retained tree nodes. Application code should normally work through
Context,WindowHandle,ContainerHandle, andWidgetTreeBuilderinstead of authoring widgets directly on a container. - Layout engine + flows: the engine tracks scope stack, scroll-adjusted coordinates, and content extents, while flows control placement behavior.
WidgetTreeBuilderexposes retained row/grid/column/stack structure, and widget layout uses each widget'smeasureresult soSizePolicy::Autocan follow per-widget intrinsic sizing. - Widget: stateful UI element implementing the
Widgettrait (for exampleButton,Textbox,Slider). These structs hold interaction state and use pointer-derived IDs from their current address. - WidgetTree: retained widget/layout hierarchy built once with
WidgetTreeBuilderand replayed each frame throughContext::window(...),Context::dialog(...), orContext::popup(...). Tree nodes cover widgets, panels, headers/tree nodes, row/grid/column/stack layout groups, and custom rendering, so UI structure stays representable as retained data instead of traversal-time callbacks. - Graphics: widget-local primitive drawing exposed through
WidgetCtx::graphics(...)and theGraphicsbuilder. It covers rectangles, frames, text/icons/images, thick line strokes, filled polygons, and nested local clip scopes. - Typography: atlases can now bake multiple named fonts and sizes.
Styleresolves semantic roles (body,small,title,heading,mono) throughFontRole, while individual text-bearing widgets can override their ownfont: FontChoice. - Renderer: any backend that implements the
Renderertrait can be used. The included SDL2 + glow example demonstrates how to batch the commands produced by a container and upload them to the GPU.
let name = widget_handle;
let tree = build;
ctx.window;
if ctx.committed_results.state_of_handle.is_submitted
Retained trees are the supported public authoring path. Post-render business logic lives alongside the window call and reads from ctx.committed_results(), which intentionally exposes the previous frame's published interaction generation:
ctx.window;
let results = ctx.committed_results;
if results.state_of_handle.is_submitted
Widget IDs
Widget IDs default to the address of the widget state. This is stable as long as the state stays at a fixed address, but it can change if the state lives inside a Vec that grows/shrinks (reallocation moves items). If that happens, focus/hover continuity follows the new addresses.
When setting focus manually, pass a widget pointer ID from widget_id_of or widget_id_of_handle:
my_window.set_focus;
Window, dialog, and popup builders now accept a WidgetBehaviourOption to control scroll behavior. Use WidgetBehaviourOption::NO_SCROLL
for popups that should not scroll, WidgetBehaviourOption::GRAB_SCROLL for widgets that want to consume scroll, and
WidgetBehaviourOption::NONE for default behavior. Custom widgets receive consumed scroll in CustomRenderArgs::scroll_delta.
Preferred sizing and retained layout
- Every built-in widget reports its own intrinsic preferred size from content metrics (text/icon/thumb/line layout).
- Retained traversal measures committed widget state, allocates the widget rectangle, then calls
Widget::runto sample interaction and update widget-local state. WidgetTreeBuilderexposes retainedrow,grid,column,stack,header,tree_node,container, andcustom_renderstructure so layout stays declarative instead of closure-driven.SizePolicy::Weight(value)distributes available track space by sibling weight ratio (spacing accounted for). In single-track flows, it uses a0..=100scale.- Returning
<= 0for either axis fromWidget::measurestill means "use layout fallback/defaults" for that axis.
Images and textures
Some widgets can render an Image, which can reference either a slot or an uploaded texture at runtime:
let texture = ctx.load_image_from?;
let image_button = widget_handle;
let tree = build;
ctx.window;
if ctx.committed_results.state_of_handle.is_submitted
Image::Slotrenders an entry from the atlas and benefits from batching.Image::Texturetargets renderer-owned textures (the backend handles binding when drawing).WidgetFillOptioncontrols which interaction states draw a filled background; useWidgetFillOption::ALLto keep the default normal/hover/click fills.- Use
Context::load_image_rgba/load_image_fromandContext::free_imageto manage the lifetime of external textures.
Graphics primitives
WidgetCtx::graphics(...)exposes a widget-localGraphicsbuilder for custom widgets and paint code.- The builder provides
draw_rect,draw_box,draw_text,draw_icon,draw_image,draw_frame,draw_widget_frame,draw_control_text,stroke_line,fill_polygon, and local clip helpers such aswith_clip. - Filled shapes and strokes are tessellated into retained triangles and clipped in software before replay, so primitive rendering stays consistent across glow, Vulkan, and WGPU backends.
examples/demo-fullincludes a dedicated graphics window that exercises the primitive API.
Fonts and typography
- Atlas building supports multiple baked fonts and sizes through
builder::FontAsset, and the same config can drive both runtime atlas construction and offline/prebuilt atlas export. Context::new(...)binds the conventional atlas keysbody,small,title,heading, andmonoonto the defaultStyle.Context::set_style(...)also rebinds any font fields that are still left at their default/unset values, so tweaking colors or spacing on top ofStyle::default()keeps the intended body/title sizes.- Text-bearing widgets expose
font: FontChoice, so you can either select a semantic role (FontRole::Heading.into()) or a concrete baked font ID (atlas.font_id("caption").unwrap().into()). - Font sizes are selected by choosing another baked font variant, not by scaling one bitmap font at runtime.
examples/demo-fulluses this directly:NORMAL.ttffor control/body text,BOLD.ttffor window titles, andCONSOLE.ttffor the log window’s input/output text.
use ;
const FONTS: & = &;
let config = Config ;
let mut title = new;
title.font = Heading.into;
If fonts is empty, builder::Config falls back to default_font + default_font_size for the old single-font atlas layout.
Cargo features
builder(default) – enables the runtime atlas builder and PNG decoding helpers used by the examples.png_source– allows serialized atlases andImageSource::Png { .. }uploads to stay compressed.save-to-rust– enablesAtlasHandle::to_rust_filesto emit the current atlas as Rust code for embedding.example-backend– shared internal gate used by examples; pair it with exactly one concrete backend.example-glow/example-vulkan/example-wgpu– concrete example backends; choose exactly one when running examples.
Disabling default features leaves only the raw RGBA upload path (ImageSource::Raw { .. }):
cargo build --no-default-features
The demos require builder, so run them with --no-default-features plus builder:
cargo run --example demo-full --no-default-features --features "example-vulkan builder"
Equivalent command using the shared gate explicitly:
cargo run --example demo-full --no-default-features --features "example-backend example-vulkan builder"
To export an atlas as Rust, enable save-to-rust (optionally png_source for PNG bytes) and call AtlasHandle::to_rust_files, or use the helper binary:
cargo run --bin atlas_export --features "builder save-to-rust" -- --output path/to/atlas.rs
Text rendering and layout
- Retained text widgets automatically center the font’s baseline inside each cell, and every line gets a small vertical pad so glyphs never touch the widget borders.
TextBlocksupports wrapped multi-line content while preserving outer padding without adding extra spacing between lines.- Custom rendering still goes through retained
custom_rendernodes, which receive layout, input, and clip information throughCustomRenderArgs.
Version 0.6
Version 0.6.0 is the retained-tree release. Compared to 0.5.0, it replaces the public immediate/closure authoring path with retained widget trees and committed interaction results.
- Replaced the v0.5 public immediate/closure authoring path with retained widget trees.
-
Context::window,dialog, andpopupnow take&WidgetTreeinstead of UI-building closures. -
WidgetTreenodes cover widgets, embedded containers, headers/tree nodes, row/grid/column/stack groups, and custom render leaves. - The public immediate widget-helper surface and
tree.run(...)escape hatch are gone from the supported API.
-
- Added a retained composition API with stable node identity and a smaller builder surface.
-
WidgetTree/WidgetTreeBuilderprovide reusable retained widget/layout hierarchies with stableNodeIds. -
WidgetTreeBuilderis centered on oneNodeOptionsvalue that carries optional keys and optional placement metadata. - The old
keyed_*/*_with_policybuilder matrix was collapsed into one default insertion method plus one*_with(NodeOptions, ...)overload per structural concept.
-
- Reworked retained execution around explicit layout and interaction generations.
- Retained traversal now runs a layout pass and a
Widget::runpass, reusing cached geometry instead of advancing layout while rendering. -
WidgetTreeCachestores layout and interaction separately across previous/current generations. - The temporary runtime adapter tree was removed; retained traversal now walks
WidgetTreeNodevalues directly.
- Retained traversal now runs a layout pass and a
- Tightened the widget/runtime contract compared to v0.5.
- Widgets now implement
measure+run; the intermediatereconcile/ frame-commit design was removed. - Persistent widget state stays inside the widget handle and mutates during
run. - Pointer-derived widget IDs remain the source of focus, hover, and result lookup, with
widget_id_ofandwidget_id_of_handleas the public helpers.
- Widgets now implement
- Added widget-local graphics primitives as a first-class paint path.
-
WidgetCtx::graphics(...)andGraphicsexpose rectangles, frames, text/icons/images, thick line strokes, polygon fills, and nested local clip scopes. - Primitives are tessellated into retained triangles and software-clipped before replay, keeping backend behavior consistent without fragmenting batches on clip changes.
-
- Added multi-font atlas support with semantic typography roles.
- Runtime atlas building and offline/prebuilt atlas export now share the same multi-font config surface.
-
Stylebinds semantic roles (body,small,title,heading,mono) from atlas font names, and text-bearing widgets can override their font per instance throughFontChoice. - Different text sizes are represented as separate baked font variants instead of runtime bitmap scaling.
- Made committed retained results the strict public business-logic contract.
- Per-frame widget results are recorded internally by widget ID and published as the previous frame's committed generation.
-
Context::committed_results()is the public app-facing results API, includingFrameResultGeneration::state_of_handle. - Current-frame results stay internal;
FrameResultsandcurrent_results()are no longer part of the public surface.
- Migrated shipped UI and supporting widgets to the retained-only model.
-
examples/simple,examples/calculator,examples/demo-full, andFileDialogStatenow build retained trees and react through committed results. - Retained display widgets such as
TextBlockreplaced callback-only display glue. - Demo/file-dialog lists now reuse persistent
ListItemstate instead of rebuilding transient labels every frame.
-
- Hardened retained interaction, layout, and renderer integration.
- Mouse input delivered to widgets and custom render callbacks is localized to the widget rectangle.
- Root windows and nested panels now share the retained scrollbar/clip/resize ordering needed for correct scrolling and bottom-right resize behavior.
- Weight-based sizing, directional stacks, wrapped text blocks, retained custom rendering, and the glow/vulkan/wgpu example backends were all kept aligned with the retained execution path.
Version 0.5
- Widget identity moved fully to pointer-based IDs.
- Removed
with_id; focus/hover now use widget trait-object/state pointers.
- Removed
- Layout refactor: introduced
LayoutEngine+ specialized flows (RowFlow,StackFlow) instead of a one-size-fits-all manager.- Preferred sizing pipeline: widget helpers now call
Widget::measure, allocate rectangles, then run widgets directly against the current frame input. - Directional stack support:
StackDirection::{TopToBottom, BottomToTop}plusstack_directionandstack_with_width_direction.
- Preferred sizing pipeline: widget helpers now call
- Context/container API cleanup:
Contextmodule split, input forwarding helpers, container state encapsulation, and handle views. - Widget internals cleanup: helper macroization/simplification, node/widget scaffolding unification, and text widget module split.
- Text and input fixes: shared text layout/edit paths, textbox delete/end fixes, centralized widget input fallback.
- Scrollbar behavior cleanup: unified sizing, layout, and drag handling.
- File dialog and atlas fixes, including file dialog layout redesign and footer/button spacing corrections.
- Added WGPU example backend and migrated demo-full to new layout flow APIs.
- Added directional stack demo window and expanded documentation/comments for layout and WGPU renderer.
Version 0.4
- Stateful widgets
- Stateful widgets for core controls (button, list item, checkbox, textbox, slider, number, custom).
- Pointer-based widget IDs; InputSnapshot threaded through widgets and cached per frame.
- IdManager removed; widget IDs now derive from state pointers.
- Widget API redesign requires stateful widget instances; trait/type renames applied.
- Legacy
button_ex*shims removed. - DrawCtx extracted into its own module and shared via WidgetCtx.
- WidgetState/WidgetCtx pipeline with ControlState returned from
update_control.
- File dialog UX fixes (close on OK/cancel, path-aware browsing).
- Expanded unit tests for scrollbars, sliders, and PNG decoding paths.
- Style shared via
Rc<Style>across containers/panels; window chrome state moved intoWindow. -
Container::stylenow usesRc<Style>.
Version 0.3
- Use
std(Vec,parse, ...) - Containers contain clip stack and command list
- Move
begin_*,end_*functions to closures - Move to AtlasRenderer Trait
- Remove/Refactor
Pool - Change layout code
- Treenode as tree
- Manage windows lifetime & ownership outside of context (use root windows)
- Manage containers lifetime & ownership outside of contaienrs
- Software based textured rectangle clipping
- Add Atlasser to the code
- Runtime atlasser
- Icon
- Font (Hash Table)
- Separate Atlas Builder from the Atlas
- Builder feature
- Save Atlas to rust
- Atlas loader from const rust
- Runtime atlasser
- Image widget
- Png Atlas source
- Pass-Through rendering command (for 3D viewports)
- Custom Rendering widget
- Mouse input event
- Keyboard event
- Text event
- Drag outside of the region
- Rendering
- Dialog support
- File dialog
- API/Examples loop/iterations
- Simple example
- Full api use example (3d/dialog/..)
- Documentation