The frame loop
What to call each frame, in what order, and how to forward input.
Masque does not own a window or a loop. You call it from yours.
Each frame
// 1. Pick up saved edits (with Masque::watch).
if ui.check_for_changes() {
if let Err(err) = ui.reload() {
eprintln!("reload failed: {err}");
}
}
// 2. Re-run the document if its state changed, then reconcile.
let rerendered = ui.update()?;
// 3. Advance springs and exits. Cap dt so a stalled frame
// doesn't skip a whole animation.
ui.get_ui_mut().tick_animations(dt.min(0.033));
// 4. Lay out at the window's logical size.
ui.get_ui_mut().layout(width, height);
// 5. Paint.
surface.draw(ui.get_ui_mut());
// 6. Flush unmount hooks queued by settled exits.
ui.process_drain_queues();
Call
tick_animationsevery frame, not only whenupdatere-rendered. Animations are ticked independently of state, and skipping ticks freezes transitions in flight.
Updating host state
ui.with_runtime_mut(|rt| {
rt.inject_host_state_if_changed("player_count".into(), Value::Integer(4));
rt.request_rerender();
});
inject_host_state_if_changed skips the re-render when the value is unchanged.
Forwarding input
use masque::widget::event::Event;
use masque::widget::point::Point;
let event = Event::with_point("mouse_down".into(), Point::new(x, y));
ui.get_ui_mut().call_event(&event);
For drags and context menus, your input code owns the dead-zone logic and calls the dedicated dispatch_drag_* and dispatch_contextmenu methods.
Asking what the UI wants
wants_cursor_free(): the UI is showing something that needs the pointer, such as a menu.consumes_character_key(): a text field has focus, so don't treat letters as shortcuts.has_input_blocking_portal(): a modal is open.
TODO: a complete winit + Skia host, kept compiling in CI.