tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Sutichaykuna

⎇ main llimphi-ui/examples/counter_web.rs Rust · 145 siq'ikuna · 4.5 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1//! El `counter` corriendo en un navegador: el consumidor de [`run_web`].
2//!
3//! Misma app Elm que `counter.rs` —modelo `u32`, dos botones, `view` pura—
4//! con el único cambio que el web impone: en vez de `fn main()` que llama a
5//! `run::<Counter>()`, un `#[wasm_bindgen(start)]` que llama a
6//! `run_web::<Counter>(Some("llimphi"))`. **La app no sabe que está en un
7//! navegador**; eso es todo el punto.
8//!
9//! Es un `cdylib` (ver el `[[example]]` en Cargo.toml) porque wasm-bindgen
10//! necesita ese crate-type. En targets nativos queda como una biblioteca
11//! vacía, así que `cargo check --workspace --examples` lo cubre sin ruido.
12//!
13//! Construir y servir:
14//!
15//! ```sh
16//! ./scripts/build-llimphi-web.sh          # → /tmp/llimphi-web
17//! python3 -m http.server 8000 -d /tmp/llimphi-web
18//! ```
19
20#![cfg(target_arch = "wasm32")]
21
22use llimphi_ui::llimphi_layout::taffy::{
23    prelude::{length, percent, Dimension, FlexDirection, Size, Style},
24    AlignItems, JustifyContent,
25};
26use llimphi_ui::llimphi_raster::peniko::Color;
27use llimphi_ui::{App, Handle, View};
28use wasm_bindgen::prelude::wasm_bindgen;
29
30#[derive(Clone)]
31enum Msg {
32    Increment,
33    Reset,
34}
35
36struct Counter;
37
38impl App for Counter {
39    type Model = u32;
40    type Msg = Msg;
41
42    fn title() -> &'static str {
43        "llimphi · counter web"
44    }
45
46    fn init(_: &Handle<Self::Msg>) -> Self::Model {
47        0
48    }
49
50    fn update(model: Self::Model, msg: Self::Msg, _: &Handle<Self::Msg>) -> Self::Model {
51        match msg {
52            Msg::Increment => model.saturating_add(1),
53            Msg::Reset => 0,
54        }
55    }
56
57    fn view(model: &Self::Model) -> View<Self::Msg> {
58        let number = View::new(Style {
59            size: Size {
60                width: percent(1.0_f32),
61                height: Dimension::auto(),
62            },
63            flex_grow: 1.0,
64            align_items: Some(AlignItems::CENTER),
65            justify_content: Some(JustifyContent::CENTER),
66            ..Default::default()
67        })
68        .text(model.to_string(), 160.0, Color::from_rgba8(230, 240, 250, 255));
69
70        let boton = |etiqueta: &str, ancho: f32, fondo: Color, tinta: Color, msg: Msg| {
71            View::new(Style {
72                size: Size {
73                    width: length(ancho),
74                    height: length(56.0_f32),
75                },
76                align_items: Some(AlignItems::CENTER),
77                justify_content: Some(JustifyContent::CENTER),
78                ..Default::default()
79            })
80            .fill(fondo)
81            .radius(12.0)
82            .text(etiqueta.to_string(), 26.0, tinta)
83            .on_click(msg)
84        };
85
86        let botones = View::new(Style {
87            flex_direction: FlexDirection::Row,
88            size: Size {
89                width: percent(1.0_f32),
90                height: length(56.0_f32),
91            },
92            gap: Size {
93                width: length(16.0_f32),
94                height: length(0.0_f32),
95            },
96            justify_content: Some(JustifyContent::CENTER),
97            ..Default::default()
98        })
99        .children(vec![
100            boton(
101                "+1",
102                160.0,
103                Color::from_rgba8(60, 200, 130, 255),
104                Color::from_rgba8(10, 30, 20, 255),
105                Msg::Increment,
106            ),
107            boton(
108                "reset",
109                120.0,
110                Color::from_rgba8(220, 80, 80, 255),
111                Color::from_rgba8(30, 10, 10, 255),
112                Msg::Reset,
113            ),
114        ]);
115
116        View::new(Style {
117            flex_direction: FlexDirection::Column,
118            size: Size {
119                width: percent(1.0_f32),
120                height: percent(1.0_f32),
121            },
122            gap: Size {
123                width: length(0.0_f32),
124                height: length(24.0_f32),
125            },
126            padding: llimphi_ui::llimphi_layout::taffy::Rect {
127                left: length(32.0_f32),
128                right: length(32.0_f32),
129                top: length(32.0_f32),
130                bottom: length(32.0_f32),
131            },
132            ..Default::default()
133        })
134        .fill(Color::from_rgba8(20, 24, 32, 255))
135        .children(vec![number, botones])
136    }
137}
138
139/// `start` de wasm-bindgen: el navegador lo llama al terminar de instanciar el
140/// módulo. Retorna en el acto — el bucle sigue vivo por `requestAnimationFrame`
141/// (ver [`llimphi_ui::run_web`]).
142#[wasm_bindgen(start)]
143pub fn arrancar() {
144    llimphi_ui::run_web::<Counter>(Some("llimphi"));
145}