mirui 0.28.2

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation
extern crate alloc;

#[cfg(feature = "std")]
use crate::app::{App, RendererFactory};
use crate::ecs::{Entity, World};
use crate::prelude::*;
#[cfg(feature = "std")]
use crate::surface::Surface;
use crate::widget;
use crate::widget::{Children, Parent};
use alloc::vec::Vec;

pub struct BarState {
    pub y: Fixed,
    pub speed: Fixed,
    pub snap: bool,
    pub x: Fixed,
}

pub struct BarBounds {
    pub h: i32,
}

#[mirui_macros::system(order = ANIMATION)]
pub fn bar_move_system(world: &mut World) {
    let bound_h = world.resource::<BarBounds>().map(|b| b.h).unwrap_or(128);
    let mut buf = Vec::new();
    world.query::<BarState>().collect_into(&mut buf);
    for e in buf {
        let (new_y, changed) = {
            let Some(bar) = world.get_mut::<BarState>(e) else {
                continue;
            };
            let old_display = if bar.snap { bar.y.floor() } else { bar.y };
            bar.y += bar.speed;
            if bar.y > Fixed::from_int(bound_h - 18) {
                bar.y = Fixed::from_int(20);
            }
            let new_display = if bar.snap { bar.y.floor() } else { bar.y };
            (new_display, new_display != old_display)
        };
        if changed {
            let bx = world.get::<BarState>(e).unwrap().x;
            widget::set_position(world, e, bx, new_y);
        }
    }
}

pub fn build_widgets(world: &mut World, parent: Entity, view_w: u16, view_h: u16) {
    let bw = view_w as i32;
    let bh = view_h as i32;
    world.insert_resource(BarBounds { h: bh });

    let bar1 = WidgetBuilder::new(world)
        .bg_color(Color::rgb(255, 100, 100))
        .layout(LayoutStyle {
            position: Position::Absolute,
            left: Dimension::px(10),
            top: Dimension::px(20),
            width: Dimension::px(50),
            height: Dimension::px(8),
            ..Default::default()
        })
        .id();
    world.insert(
        bar1,
        BarState {
            y: Fixed::from_int(20),
            speed: Fixed::from_raw(9),
            snap: true,
            x: Fixed::from_int(10),
        },
    );

    let bar2 = WidgetBuilder::new(world)
        .bg_color(Color::rgb(100, 200, 255))
        .layout(LayoutStyle {
            position: Position::Absolute,
            left: Dimension::px(bw - 60),
            top: Dimension::px(20),
            width: Dimension::px(50),
            height: Dimension::px(8),
            ..Default::default()
        })
        .id();
    world.insert(
        bar2,
        BarState {
            y: Fixed::from_int(20),
            speed: Fixed::from_raw(9),
            snap: false,
            x: Fixed::from_int(bw - 60),
        },
    );

    world.insert(bar1, Parent(parent));
    world.insert(bar2, Parent(parent));
    if let Some(children) = world.get_mut::<Children>(parent) {
        children.0.push(bar1);
        children.0.push(bar2);
    }
}

#[cfg(feature = "std")]
pub fn setup_app<B, F>(app: &mut App<B, F>, parent: Entity)
where
    B: Surface,
    F: RendererFactory<B>,
{
    let info = app.backend.display_info();
    app.add_system(bar_move_system::system());
    build_widgets(&mut app.world, parent, info.width, info.height);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::widget::IdMap;
    use crate::widget::builder::WidgetBuilder;

    #[test]
    fn build_widgets_smoke() {
        let mut world = World::new();
        world.insert_resource(IdMap::new());
        let parent = WidgetBuilder::new(&mut world).id();
        build_widgets(&mut world, parent, 128, 128);
        assert!(
            world
                .get::<Children>(parent)
                .is_some_and(|c| !c.0.is_empty()),
        );
    }
}