aura-gui 0.3.0

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Adaptador interno que convierte una closure `|ui| ...` en una vista del motor.
//!
//! El usuario no ve este tipo: `run_app` lo crea, guarda el estado por clave y vuelve a llamar a la closure en
//! cada frame. La closure no necesita `impl` ni nombrar el tipo de la vista.

use crate::view::element::IntoElement;
use crate::view::render::{Component, ViewContext};
use crate::view::state::StateStore;
use crate::view::ui::Ui;
use crate::view::widget_store::WidgetStore;
use std::marker::PhantomData;

pub(crate) struct FnApp<F, R> {
    build: F,
    state: StateStore,
    widgets: WidgetStore,
    marker: PhantomData<R>,
}

impl<F, R> FnApp<F, R> {
    pub(crate) fn new(build: F) -> Self {
        Self { build, state: StateStore::default(), widgets: WidgetStore::default(), marker: PhantomData }
    }
}

impl<F, R> Component for FnApp<F, R>
where
    F: FnMut(&mut Ui) -> R + 'static,
    R: IntoElement + 'static,
{
    fn view(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let theme = cx.theme;
        let viewport = cx.viewport;
        let entities = cx.entities_mut();
        let mut ui = Ui::new(theme, viewport, entities, &mut self.state, &mut self.widgets);
        (self.build)(&mut ui).into_element()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::view::prelude::*;
    use crate::view::test_context::TestWindow;
    use std::cell::Cell;
    use std::rc::Rc;

    #[test]
    fn una_closure_lee_el_estado_y_se_redibuja_al_pulsar() {
        let seen = Rc::new(Cell::new(0u32));
        let capture = seen.clone();
        let app = FnApp::new(move |ui: &mut Ui| {
            let count = ui.state("count", 0u32);
            capture.set(count.get());
            ui.page().center().child(ui.button("Sumar").on_click(count.update(|n| *n += 1)))
        });

        let (mut test, _) = TestWindow::new(|_| app);
        assert_eq!(seen.get(), 0);

        let [x, y, width, height] = test.window.scene().quads.last().expect("el botón pinta").bounds;
        test.simulate_click(x + width / 2.0, y + height / 2.0);
        assert_eq!(seen.get(), 1, "el handler mutó el estado y pidió otro frame");
    }

    #[test]
    fn un_componente_conserva_su_estado_entre_frames() {
        let seen = Rc::new(Cell::new(0u32));
        let capture = seen.clone();
        let app = FnApp::new(move |ui: &mut Ui| {
            let capture = capture.clone();
            ui.component(
                "panel",
                || 0u32,
                move |ui, value, handle| {
                    capture.set(*value);
                    ui.page().center().child(ui.button("Sumar").on_click(handle.update(|n| *n += 1)))
                },
            )
        });

        let (mut test, _) = TestWindow::new(|_| app);
        assert_eq!(seen.get(), 0);
        let [x, y, width, height] = test.window.scene().quads.last().expect("el botón pinta").bounds;
        test.simulate_click(x + width / 2.0, y + height / 2.0);
        assert_eq!(seen.get(), 1, "el componente conserva su valor y se redibuja");
    }
}