Embedding lui
lui is designed to be embedded inside a Rust application. The core library is windowing-system agnostic — you connect it to any window via the Driver trait or use the provided drivers.
Architecture
Your Application
│
├── Tree (DOM + fonts + callbacks)
├── Runtime<YourDriver> (engine + GPU state)
└── YourDriver (winit / egui / Bevy / custom)
Step 1: Create a Tree
use lui_parser::parse;
let mut tree = parse(r#"<html><body>
<div style="padding: 20px; font-family: sans-serif;">
<h1>Hello, World!</h1>
</div>
</body></html>"#);
tree.register_font(FontFace {
family: "sans-serif".into(),
file: "fonts/Roboto-Regular.ttf".into(),
..Default::default()
});
Step 2: Choose a Driver
winit (desktop)
use lui_driver_winit::WinitDriver;
let event_loop = EventLoop::new()?;
let window = Arc::new(event_loop.create_window(Window::default_attributes())?);
let mut driver = WinitDriver::bind(window, tree);
// In your event loop:
driver.handle_event(&event); // dispatches + renders
egui / eframe
use lui_driver_egui::EguiRunner;
let mut runner = EguiRunner::new(window, 800, 600);
egui::CentralPanel::default().show(ctx, |ui| {
let output = runner.show(ui, &mut tree, ui.available_size());
if output.layout_available {
// Submit output.display_list to GPU
}
});
Bevy
use lui_driver_bevy::{LuiPlugin, HtmlOverlay};
app.add_plugins(LuiPlugin);
fn setup(mut commands: Commands) {
commands.insert_resource(HtmlOverlay::new());
}
Step 3: Wire Input
// winit example: translate WindowEvents to engine input
match event {
WindowEvent::CursorMoved { position, .. } => {
runtime.on_pointer_move(&mut tree, position.x, position.y);
}
WindowEvent::MouseInput { button, state, .. } => {
let pressed = state == ElementState::Pressed;
runtime.on_mouse_button(&mut tree, button.into(), pressed);
}
WindowEvent::KeyboardInput { event, .. } => {
runtime.on_key(&mut tree, key, code, pressed, repeat, text);
}
WindowEvent::MouseWheel { delta, .. } => {
runtime.on_wheel_event(&mut tree, x, y, dx, dy, mode);
}
}
The WinitDriver::handle_event() does all of this automatically for winit windows.
Step 4: Render
Each frame, the engine runs cascade → layout → paint → GPU render:
let timings = runtime.render_frame(&mut tree);
println!("Frame: cascade={:.2}ms layout={:.2}ms paint={:.2}ms",
timings.cascade_ms, timings.layout_ms, timings.paint_ms);
Custom Drivers
Implement the Driver trait from lui-driver:
impl Driver for MyWindow {
type Surface = MySurface;
fn surface(&self) -> &Arc<Self::Surface> { &self.surface }
fn inner_size(&self) -> (u32, u32) { (self.width, self.height) }
fn scale_factor(&self) -> f64 { self.dpi }
fn request_redraw(&self) { /* request new frame */ }
fn set_cursor(&self, cursor: Cursor) { /* set OS cursor */ }
// Optional: clipboard, profiling
}
Then wrap with Runtime<MyWindow>:
let mut runtime = Runtime::new(my_window, 800, 600);
runtime.render_frame(&mut tree);