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 preserves the immediate-mode feel while using stateful widget structs with pointer-derived widget identity, a row/column layout helper API, and backend-agnostic rendering hooks.
Compared to microui-rs, this crate embraces std types, closure-based window/panel/column scopes, 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.

Key Concepts
- Context: owns the renderer handle, user input, and root windows. The atlas is provided by the renderer and accessed through the context. Each frame starts by feeding input into the context, then calling
context.window(...)for every visible window or popup. - Container: describes one layout surface. Every window, panel, popup or custom widget receives a mutable
Containerthat exposes high-level widgets (buttons, sliders, etc.) and lower-level drawing helpers. - Layout engine + flows: the engine tracks scope stack, scroll-adjusted coordinates, and content extents, while flows control placement behavior. Use
Container::with_rowfor row tracks,Container::stack/Container::stack_directionfor one-item-per-line sections, andcontainer.column(|ui| { ... })for nested scopes. Widget helpers (button,textbox,slider, etc.) query each widget'spreferred_sizebefore allocating the cell, 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. - 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 mut name = new;
ctx.window;
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:
ui.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
- Every built-in widget now reports its own intrinsic preferred size from content metrics (text/icon/thumb/line layout).
Containerwidget helpers callWidget::preferred_sizefirst, then allocate the widget rectangle, then callWidget::handle.- Returning
<= 0for either axis still means "use layout fallback/defaults" for that axis. next_cell()is the raw layout helper that does not run widget preferred sizing.
Flow helpers
with_row(widths, height, ...)configures an explicit multi-slot row track.stack(height, ...)configures a vertical one-slot flow with widthSizePolicy::Remainder(0).stack_direction(height, direction, ...)is the same asstack, but allowsStackDirection::BottomToTop.stack_with_width(width, height, ...)is the same asstack, but with explicit width policy.stack_with_width_direction(width, height, direction, ...)combines explicit width policy with directional stacking.column(...)starts a nested scope; inside it you can choose row or stack flow independently.
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 mut image_button = with_image;
ui.button;
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.
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
- Container 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.
Container::text_with_wrapsupports explicit wrapping modes (TextWrap::NoneorTextWrap::Word) and renders wrapped lines back-to-back inside an internal column, so the block keeps the outer padding without adding extra spacing between lines.- Custom drawing code can call
Container::draw_textdirectly when precise placement is required, or usedraw_control_textto get automatic alignment/clip handling.
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::preferred_sizebefore rectangle allocation. - 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