Expand description
§pane_ui
A RON-driven, hot-reloadable UI library for wgpu.
Define your entire UI in a .ron file. No layout code, no style code, no wiring code.
Edit the file while your app runs and the UI updates instantly — menus, styles, and
shaders alike. Your game loop stays untouched.
§Modes
Choose the integration that fits your architecture:
| Mode | Owns window? | GPU required? | Use when |
|---|---|---|---|
run | ✅ | ✅ | Pane IS the app |
overlay / PaneOverlay | ❌ | ✅ | Compositing onto your renderer |
headless / PaneHeadless | ❌ | ❌ | Tests, servers, CI |
§Standalone
Pane owns the window and event loop entirely.
// Simplest — one line, no callbacks
pane::run("assets/menu.ron");
// With callback — handle actions and call write/toast each frame
pane::run_with("assets/menu.ron", |ui, action| {
if let pane::PaneAction::Custom(ref id) = action {
if id == "save" { ui.push_toast("Saved!", 2.0, 0.0, -400.0, 300.0, 60.0); }
}
});§Overlay
Composites onto your existing wgpu renderer. You keep your device, queue, and loop.
// Startup — pass Some(gilrs) to share your gamepad context, or None to auto-create one
let mut ui = pane::overlay("assets/hud.ron", &device, &queue, format, None);
// In your event loop
ui.handle_event(&event, window_width, window_height);
// In your render pass — call after your own draw commands
for action in ui.draw(&mut encoder, &view, pw, ph) {
match action {
pane::PaneAction::Custom(tag) => println!("button: {tag}"),
pane::PaneAction::Slider(id, val) => println!("{id} = {val:.2}"),
pane::PaneAction::Toggle(id, checked) => println!("{id} = {checked}"),
pane::PaneAction::Quit => std::process::exit(0),
_ => {}
}
}§Headless
No GPU, no window. Useful for testing UI logic in CI or driving a server-side state machine.
let mut menu = pane::headless("assets/menu.ron");
menu.press("play_button");
let actions = menu.update(1.0 / 60.0);§Coordinate System
Pane uses a centered, resolution-independent coordinate system:
(0.0, 0.0)is always the center of the screen- The screen height is always
1080units regardless of actual resolution - Width scales proportionally — widescreen just adds space on the sides
- Positive Y is downward
A button at x: -170.0, y: 0.0 is centered vertically on every screen.
You never write resolution-specific layout.
§Widgets
15 built-in widget types, all defined in RON:
Button · Toggle · Slider · TextBox · Dropdown · RadioGroup ·
ScrollList · ScrollPane · Bar · Popout · Label · Divider ·
Image · ProgressBar · Tabs
Plus Actor — a non-interactive animated element that follows the cursor
or moves to programmatic targets.
§Actions
PaneOverlay::draw and PaneHeadless::update return a vector of PaneAction
each frame. Match on these to drive your application:
for action in actions {
match action {
PaneAction::Custom(tag) => { /* button pressed */ }
PaneAction::Slider(id, value) => { /* slider moved */ }
PaneAction::Toggle(id, checked) => { /* toggle flipped */ }
PaneAction::Dropdown(id, idx, label) => { /* selection changed */ }
PaneAction::Radio(id, idx, label) => { /* selection changed */ }
PaneAction::TextChanged(id, text) => { /* keystroke */ }
PaneAction::TextSubmitted(id, text) => { /* enter pressed */ }
PaneAction::SwitchRoot(name) => { /* root changed */ }
PaneAction::Quit => { std::process::exit(0); }
}
}§Runtime API
Read or write any widget’s value from code at any time. Useful for syncing UI to app state on startup, or driving a progress bar mid-game.
// Read — returns the UiItem definition + current visual state
if let Some((pane::api::UiItem::Slider(s), _)) = ui.read("volume") {
println!("volume = {}", s.value);
}
// Write — silently sets value without firing PaneActions
ui.write("volume", &WriteValue::Slider(0.8));
ui.write("mute", &WriteValue::Toggle(true));
ui.write("name", &WriteValue::Text("Player 1".into()));
ui.write("quality", &WriteValue::Selected(2));
// Toast notification (also triggerable directly from RON via on_press: Toast(...))
ui.push_toast("Settings saved", 2.0, 0.0, -400.0, 300.0, 60.0);§Hot Reload
Set hot_reload: true in your root .ron file. While your app runs:
- Save a menu
.ron→ layout and behaviour update instantly - Save a style
.ron→ visual design updates instantly - Save a
.wgslshader → shader recompiles and updates instantly
No restart. No recompile. Just save.
Requires the dev feature for runtime style loading:
pane_ui = { version = "0.1", features = ["dev"] }§Controller Support
All widgets support gamepad navigation with no extra configuration:
- D-pad / left stick — moves focus to the nearest widget in that direction
- South (A / Cross) — confirms; activates focused widget
- East (B / Circle) — cancels; closes popout panels
- Sliders — left/right adjusts value continuously
- Scroll lists & panes — focus scrolls to keep the active item visible
- Popouts — confirm opens, up/down navigates inside, cancel closes
Pass your existing gilrs::Gilrs to share it with your game:
// Share your game's gilrs context
let ui = pane::overlay("assets/hud.ron", &device, &queue, format, Some(gilrs));
// Or let pane_ui manage its own
let ui = pane::overlay("assets/hud.ron", &device, &queue, format, None);§Custom Styles & Shaders
Drop .ron style files in your style directory and .wgsl shaders in your shader
directory. Reference them by filename (without extension). Users and modders can
reskin your entire UI without touching your code.
(
shader_dirs: ["shaders"],
style_dirs: ["styles"],
roots: [
(
name: "main_menu",
buttons: [
(
id: "my_btn",
style: "my_custom_style", // loads styles/my_custom_style.ron
on_press: Custom("clicked"),
// ...
),
],
),
],
)6 built-in styles are always available: frosted_glass · retro · plain ·
glass_pill · emboss · sharp_outline
§Debugging
Enable debug logging to print every internal message to stdout:
// Overlay or headless
ui.set_debug(true);// Standalone — set the environment variable before running
PANE_DEBUG=1 cargo runRe-exports§
pub use api::PaneAction;pub use api::PaneHeadless;pub use api::PaneOverlay;pub use api::StandaloneHandle;pub use api::WriteValue;pub use api::headless;pub use api::overlay;pub use api::run;pub use api::run_with;pub use gilrs;