mirui 0.2.0

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation

mirui

Crates.io docs.rs License: MIT

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 DSLui! macro powered by 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

[dependencies]
mirui = "0.1"
mirui-macros = "0.1"
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

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

Attribute Type Description
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

// 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

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:

Mode FPS Notes
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 for ESP32-C3 demos including three-body physics simulation with partial refresh.

License

MIT