Core API -- Ui, Canvas, Widget¶
Widget trait¶
Every GUI element implements Widget:
pub trait Widget {
fn collect(&self, cmds: &mut Vec<RenderCommand>);
fn hit(&self, _mx: f64, _my: f64) -> bool { false }
fn mouse_input(&mut self, _mx: f64, _my: f64, _pressed: bool) {}
fn mouse_move(&mut self, _mx: f64, _my: f64) {}
fn keyboard_input(&mut self, _text: Option<&str>,
_key: Option<GuiKey>, _pressed: bool) {}
fn bounding_rect(&self) -> Option<[f32; 4]> { None }
fn tooltip(&self) -> Option<&str> { None }
// Reactive layout (all have default no-op implementations)
fn apply_constraint(&mut self, container_w: f32, container_h: f32) {}
fn apply_constraint_with(&mut self, c: &Constraint, cw: f32, ch: f32) {}
fn shift(&mut self, dx: f32, dy: f32) { /* nudges position via apply_constraint_with */ }
}
collect— push draw commands for this framehit— point-in-widget test used for focus trackingmouse_input/mouse_move— update hover/press/drag statekeyboard_input— handle text and key events when focusedbounding_rect— optional[x, y, w, h]used by containers for auto-sizingtooltip— optional string; callers query hovered widgets and render itapply_constraint— resolve the widget’s storedConstraintgiven container dims; called automatically byUi::resolve_constraintseach frameapply_constraint_with— apply an external constraint (used by the shift helper)shift— nudge position by(dx, dy); called when a parentContainer/Panelmoves
Rc<RefCell<T>> where T: Widget also implements Widget, so shared handles
can be added directly to a Ui or Canvas.
You can implement this trait to create completely custom widgets.
See constraint.md for the full reactive layout reference.
Canvas¶
Canvas holds a heterogeneous collection of widgets and manages focus.
| Method | Description |
|---|---|
Canvas::new() |
Create empty canvas |
.add(widget) |
Push any Widget + 'static |
.mouse_move(mx, my) |
Forward cursor movement to all children |
.mouse_input(mx, my, pressed) |
Update focus on press; deliver to all children |
.keyboard_input(text, key, pressed) |
Deliver to focused child only |
.collect(cmds) |
Aggregate draw commands from all children |
Ui¶
Ui wraps a Canvas and is the object you hold in your application.
Construction and widget registration¶
Input routing¶
The runner calls these automatically via Ui::handle_window_event. You do not
need to call them manually unless you manage your own event loop.
ui.mouse_move(mx, my);
ui.mouse_input(mx, my, pressed);
ui.keyboard_input(Some("a"), None, true);
ui.keyboard_input(None, Some(GuiKey::Backspace), true);
Viewport helper¶
ui.register_viewport(vp_ref.clone()); // store + add to canvas
ui.set_viewport_rect(x, y, w, h); // update rect on resize
ui.viewport_focused() // bool -- use to capture mouse for 3D camera
Drawing¶
Called by the runner each frame. If you need manual control:
Reactive constraint resolution¶
// Resolve all Constraint expressions against the current window size.
// The engine runner calls this automatically before draw_ui.
ui.resolve_constraints(window_w, window_h);
Every widget that carries a Constraint has its rect (or pos for Label)
updated to match the new dimensions. Widgets without a constraint are untouched.
See constraint.md for the full reference.
In your draw_ui callback you receive a DrawContext
which already holds gui, text, font, and ctx — use dc.gui and dc.text directly.
GuiKey¶
A lightweight key enum used in keyboard_input, avoiding a direct dependency
on winit:
pub enum GuiKey {
Backspace,
Delete,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
Home,
End,
Enter,
Escape,
Tab,
}
When the winit-backend feature is enabled, impl From<winit::keyboard::KeyCode>
is provided. All variants above are mapped; unrecognised keys fall through to
Backspace (compile-time exhaustiveness requirement).
RenderCommand¶
Widgets produce RenderCommand values that are later converted to GPU draw
calls by UiPass. You only need this when writing custom widgets.
pub enum RenderCommand {
Quad {
rect: Rect,
color: [f32; 4],
radii: [f32; 4], // per-corner radii [TL, TR, BL, BR]
flags: u32, // bit 0 = colour-wheel gradient
},
/// Draw a textured image. This variant is only available when the
/// `assets` feature is enabled and is used by the `Image` widget.
#[cfg(feature = "assets")]
Image {
rect: Rect,
texture: std::sync::Arc<ferrous_assets::Texture2d>,
uv0: [f32; 2],
uv1: [f32; 2],
color: [f32; 4],
},
Text {
rect: Rect, // origin; width/height informational only
text: String,
color: [f32; 4],
font_size: f32,
},
/// Signal the renderer to begin scissoring to `rect`.
PushClip { rect: Rect },
/// End the most recent scissor region.
PopClip,
## `GuiBatch`
`GuiBatch` is the low-level container that holds a sequence of `GuiQuad`
instances prepared by a `Widget` tree. It is normally consumed by `UiPass`
and you rarely need to interact with it directly, but a few helpers are
available:
```rust
impl GuiBatch {
pub fn new() -> Self;
pub fn clear(&mut self);
pub fn push(&mut self, quad: GuiQuad);
pub fn rect(&mut self, x,y,w,h, color);
pub fn rect_radii(&mut self, ...);
// when `assets` is enabled:
pub fn reserve_texture_slot(&mut self, Arc<Texture2d>) -> u32;
pub fn rect_textured(&mut self, ... , tex_index: u32);
pub fn image(&mut self, ..., texture: Arc<Texture2d>, uv0, uv1, color);
}
```
Texture slots are deduplicated automatically; attempting to exceed
`MAX_TEXTURE_SLOTS` will panic. The batch may also be queried for its
length with `.len()` and `.is_empty()`.
}
PushClip/PopClip are emitted by Container when clip = true.
The GuiBatch/TextBatch conversion layer ignores them; a renderer pass that
wants clipping must consume them and set the GPU scissor rect.