mirui 0.45.0

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation
pub mod animation;
pub mod blur_filter;
pub mod book_flip;
pub mod builder_form;
pub mod butterfly;
pub mod clip_path;
mod compact_layout;
pub mod composite;
pub mod cover_flow;
pub mod curve_text;
pub mod curve_text_compact;
pub mod custom_view;
pub mod effect_glass;
pub mod effect_panels;
pub mod fill_rules;
pub mod flip_card;
pub mod gradient;
pub mod i18n;
pub mod icon;
pub mod image_flip;
pub mod interaction_lab;
pub mod kinetic_console;
pub mod layout_lab;
pub mod lazy_list;
pub mod life;
pub mod life_compact;
mod motion;
pub mod nested_scroll;
pub mod niche;
pub mod offscreen;
pub mod offscreen_modal;
pub mod orbit_console;
pub mod particles;
pub mod persistence_counter;
pub mod pinch_rotate;
pub mod render_showcase;
pub mod scroll;
pub mod shapes;
pub mod slider_value_changed;
pub mod spatial_anim;
pub mod state_computed;
pub mod state_counter;
pub mod state_effect;
pub mod state_form;
pub mod state_keyed;
pub mod state_list;
pub mod state_show;
pub mod state_todo;
pub mod stroke_styles;
pub mod subpixel;
pub mod tabbar;
pub mod text_input;
pub mod theme_swap;
pub mod three_body;
pub mod transform;
pub mod typography_lab;
pub mod vector_mandala;
pub mod widgets;
pub mod widgets_compact;

pub(super) const PROJECTIVE_SPIN_PHASE: crate::types::Fixed = crate::types::Fixed::from_ratio(1, 4);

#[cfg(test)]
pub(super) fn assert_text_layouts_fit(world: &crate::ecs::World) {
    use alloc::vec::Vec;

    use crate::text::TextLayoutHandle;
    use crate::types::fixed::from_textflow;
    use crate::ui::ComputedRect;
    use crate::ui::widgets::{Text, TextOverflow, TextWrap};

    let entities: Vec<_> = world.query::<Text>().collect();
    let layouts = world
        .resource::<crate::text::layout::TextLayoutResource>()
        .expect("text layout resource")
        .borrow();
    for entity in entities {
        if world.has::<crate::text::TextPath>(entity) {
            continue;
        }
        let Some(rect) = world.get::<ComputedRect>(entity).map(|value| value.0) else {
            continue;
        };
        let Some(handle) = world.get::<TextLayoutHandle>(entity).copied() else {
            continue;
        };
        let text = world.get::<Text>(entity).expect("text entity");
        let layout = layouts.get(handle).expect("live text layout");
        let paragraph = text.paragraph();
        if let Some(max_lines) = paragraph.max_lines {
            assert!(
                layout.lines().len() <= usize::from(max_lines),
                "{:?} exceeds {max_lines} lines: {}",
                entity,
                text.resolve(world)
            );
        }
        if paragraph.wrap != TextWrap::NoWrap || paragraph.overflow == TextOverflow::Ellipsis {
            assert!(
                from_textflow(layout.measure().width) <= rect.w,
                "{:?} exceeds its width {:?}: {}",
                entity,
                rect.w,
                text.resolve(world)
            );
        }
        assert!(
            from_textflow(layout.measure().height) <= rect.h,
            "{:?} exceeds its height {:?}: {}",
            entity,
            rect.h,
            text.resolve(world)
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn projective_spin_phase_avoids_singular_edge_on_frames() {
        use crate::types::Fixed;
        use crate::types::Transform3D;

        for half_step in 0..720 {
            let angle = PROJECTIVE_SPIN_PHASE + Fixed::from_ratio(half_step, 2);
            assert!(!Fixed::cos_deg(angle).is_zero(), "angle {angle:?}");
            assert!(!Fixed::cos_deg(-angle).is_zero(), "angle -{angle:?}");
            assert!(
                Transform3D::rotate_y_perspective(angle, Fixed::from_int(400))
                    .inverse()
                    .is_some(),
                "angle {angle:?}"
            );
        }
    }
}