# mirui
[](https://crates.io/crates/mirui)
[](https://docs.rs/mirui)
[](LICENSE)
A lightweight, `no_std` ECS-driven UI framework for embedded, desktop, and WebAssembly.
## Features
- **ECS architecture** — entities, components, systems, resources, queries
- **`no_std` + `alloc`** — runs on bare-metal MCUs (ESP32-C3, STM32) with a global allocator
- **Declarative DSL** — `ui!` macro powered by [xrune](https://github.com/W-Mai/xrune)
- **Flexbox + absolute positioning** — familiar layout model
- **HiDPI support** — automatic scale factor handling
- **Dirty-flag partial refresh** — only re-renders changed regions (160fps on ESP32-C3)
- **ScrollView** — inertia, elastic bounce, scroll chaining, spring resistance
- **Components** — Button, Checkbox, ProgressBar, Image, ScrollView
- **Pluggable backends** — SDL2 (desktop), FramebufBackend (embedded)
## Quick Start
```toml
[dependencies]
mirui = "0.1"
mirui-macros = "0.1"
```
```rust
use mirui::app::App;
use mirui::backend::sdl::SdlBackend;
use mirui::layout::*;
use mirui::types::Color;
use mirui::widget::builder::WidgetBuilder;
use mirui_macros::ui;
fn main() {
let backend = SdlBackend::new("hello mirui", 480, 320);
let mut app = App::new(backend);
let root = WidgetBuilder::new(&mut app.world)
.bg_color(Color::rgb(30, 30, 46))
.layout(LayoutStyle {
direction: FlexDirection::Column,
width: Some(480),
height: Some(320),
..Default::default()
})
.id();
ui! {
:(
parent: root
world: &mut app.world
:)
content (direction: FlexDirection::Column, grow: 1.0) {
header (bg_color: Color::rgb(88, 166, 255), height: 40, text: "Hello mirui!") {}
body (grow: 1.0, bg_color: Color::rgb(40, 40, 60)) {}
footer (bg_color: Color::rgb(210, 168, 255), height: 30, text: "ECS + DSL") {}
}
};
app.set_root(root);
app.run();
}
```
## DSL Syntax
```rust
ui! {
// Context: parent entity + world reference
:(
parent: root
world: &mut world
:)
// Widgets with attributes
widget_name (attr: value, attr: value) {
child1 (attr: value) {}
child2 (attr: value) {}
}
// Enchants: attach arbitrary components
img (width: 16, height: 16, image: Image::new(data, 16, 16)) [
PhysicsBody { x: 0, y: 0 },
Velocity { vx: 1, vy: 0 },
] {}
// Iteration
walk items.iter() with item {
row (text: item.name, bg_color: item.color) {}
}
// Conditional
if show_footer {
footer (text: "visible") {}
}
}
```
## Supported Attributes
| `bg_color` | `Color` | Background color |
| `text` | `&str` | Text content |
| `text_color` | `Color` | Text color |
| `border_radius` | `u16` | Corner radius |
| `border_color` | `Color` | Border color |
| `width` / `height` | `u16` | Fixed size |
| `grow` | `f32` | Flex grow factor |
| `direction` | `FlexDirection` | Row / Column |
| `justify` | `JustifyContent` | Main axis alignment |
| `align` | `AlignItems` | Cross axis alignment |
| `padding` | `Padding` | Inner padding |
| `position` | `Position` | Flex / Absolute |
| `left` / `top` | `i32` | Absolute position |
| `image` | `Image` | Image component |
## ECS
```rust
// Spawn entities
let e = world.spawn();
world.insert(e, MyComponent { ... });
// Query
let mut buf = Vec::new();
world.query::<PhysicsBody>().and::<Velocity>().without::<Disabled>().collect_into(&mut buf);
for e in &buf {
world.get_mut::<Velocity>(*e).unwrap().vx += 1;
}
// Resources (global singletons)
world.insert_resource(DeltaTime(0.016));
let dt = world.resource::<DeltaTime>().unwrap().0;
// Systems
app.add_system(physics_system);
app.systems.add_fn(|world| { /* closure system */ });
```
## ScrollView
```rust
ui! {
:(
parent: root
world: &mut world
:)
scroll_container (direction: FlexDirection::Column, grow: 1.0) [
ScrollOffset { x: 0, y: 0 },
ScrollConfig { direction: ScrollAxis::Vertical, elastic: true, content_height: 800, content_width: 0 }
] {
walk items.iter() with item {
row (height: 60, bg_color: item.color, text: item.label) {}
}
}
};
```
Features: drag scrolling, inertia, elastic bounce with spring resistance, scroll chaining for nested scroll views.
## Performance
Tested on ESP32-C3 (RISC-V 160MHz, no FPU) + ST7735S 128×128 SPI display:
| Full-screen refresh | 60 | SPI 26MHz bottleneck |
| Partial refresh (dirty rect) | 160 | Only changed regions transmitted |
| Binary size (.text) | ~36KB | mirui + app + esp-hal |
## Hardware Examples
See [mirui-examples](https://github.com/W-Mai/mirui-examples) for ESP32-C3 demos including three-body physics simulation with partial refresh.
## License
MIT