tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! Formulario WASM Tier 3: texto + checkbox + slider + dropdown + multiline +
2//! radio + saludo vivo.
3//!
4//! Ejercita todos los eventos con payload: `Text` (nombre y notas multilínea),
5//! `Toggle` (mayúsculas), `Value` (slider edad), `Select` (dropdown color y
6//! grupo de radio prioridad) y `Click` (limpiar). El estado vive en el guest;
7//! el host sólo pinta y notifica.
8
9use llimphi_wasm_app_sdk::{
10 col, export_wasm_app, row, text, Align, Dim, Justify, TextAlign, Ui, WasmApp, WireNode,
11};
12
13const COLORES: [&str; 3] = ["rojo", "verde", "azul"];
14const PRIORIDADES: [&str; 3] = ["baja", "media", "alta"];
15
16#[derive(Clone)]
17pub enum Msg {
18 SetNombre(String),
19 SetMayus(bool),
20 SetEdad(f32),
21 SetColor(u32),
22 SetNotas(String),
23 SetPrioridad(u32),
24 Limpiar,
25}
26
27pub struct Form {
28 nombre: String,
29 mayus: bool,
30 edad: f32,
31 color: u32,
32 notas: String,
33 prioridad: u32,
34}
35
36impl Default for Form {
37 fn default() -> Self {
38 Form {
39 nombre: String::new(),
40 mayus: false,
41 edad: 30.0,
42 color: 1,
43 notas: String::new(),
44 prioridad: 0,
45 }
46 }
47}
48
49impl Form {
50 fn saludo(&self) -> String {
51 if self.nombre.is_empty() {
52 return "Hola, …".into();
53 }
54 let s = format!("Hola, {}!", self.nombre);
55 if self.mayus {
56 s.to_uppercase()
57 } else {
58 s
59 }
60 }
61}
62
63impl WasmApp for Form {
64 type Msg = Msg;
65
66 fn init() -> Self {
67 Form::default()
68 }
69
70 fn update(&mut self, msg: Msg) {
71 match msg {
72 Msg::SetNombre(s) => self.nombre = s,
73 Msg::SetMayus(b) => self.mayus = b,
74 Msg::SetEdad(v) => self.edad = v,
75 Msg::SetColor(i) => self.color = i,
76 Msg::SetNotas(s) => self.notas = s,
77 Msg::SetPrioridad(i) => self.prioridad = i,
78 Msg::Limpiar => {
79 self.nombre.clear();
80 self.mayus = false;
81 self.edad = 30.0;
82 self.color = 1;
83 self.notas.clear();
84 self.prioridad = 0;
85 }
86 }
87 }
88
89 fn view(&self, ui: &mut Ui<Msg>) -> WireNode {
90 let titulo = text("Saludo", 32.0, [230, 240, 250, 255]);
91
92 // EventId 0: campo de nombre.
93 let campo = ui
94 .text_input(self.nombre.clone(), "tu nombre…", Msg::SetNombre)
95 .fill([28, 34, 44, 255])
96 .radius(8.0)
97 .padding(10.0, 12.0, 10.0, 12.0)
98 .width(Dim::Pct(1.0))
99 .height(Dim::Px(44.0));
100
101 // EventId 1: checkbox.
102 let check = row(vec![
103 ui.checkbox(self.mayus, Msg::SetMayus),
104 text("MAYÚSCULAS", 20.0, [200, 210, 225, 255]).align(Align::Center),
105 ])
106 .gap(10.0)
107 .align(Align::Center);
108
109 // EventId 2: slider de edad (0..100).
110 let edad_row = row(vec![
111 text(format!("Edad {}", self.edad as i32), 20.0, [200, 210, 225, 255])
112 .width(Dim::Px(90.0)),
113 ui.slider(self.edad, 0.0, 100.0, Msg::SetEdad)
114 .grow(1.0)
115 .height(Dim::Px(20.0)),
116 ])
117 .gap(12.0)
118 .align(Align::Center);
119
120 // EventId 3: dropdown de color.
121 let color = ui
122 .select(
123 COLORES.iter().map(|s| s.to_string()).collect(),
124 self.color,
125 Msg::SetColor,
126 )
127 .width(Dim::Pct(1.0));
128
129 let saludo = text(self.saludo(), 28.0, [120, 220, 170, 255]);
130 let detalle = text(
131 format!(
132 "Edad: {} · Color: {} · Prioridad: {} · Notas: {} líneas",
133 self.edad as i32,
134 COLORES[self.color as usize % COLORES.len()],
135 PRIORIDADES[self.prioridad as usize % PRIORIDADES.len()],
136 self.notas.lines().count(),
137 ),
138 18.0,
139 [150, 160, 180, 255],
140 )
141 .grow(1.0);
142
143 // EventId 4: notas multilínea (Enter inserta salto de línea).
144 let notas = ui
145 .multiline_input(self.notas.clone(), "notas (Enter = nueva línea)…", Msg::SetNotas)
146 .fill([28, 34, 44, 255])
147 .radius(8.0)
148 .padding(10.0, 12.0, 10.0, 12.0)
149 .width(Dim::Pct(1.0))
150 .height(Dim::Px(72.0));
151
152 // EventId 5: grupo de radio de prioridad (el nodo es el contenedor; el
153 // host pinta una fila por opción).
154 let prioridad = ui
155 .radio(
156 PRIORIDADES.iter().map(|s| s.to_string()).collect(),
157 self.prioridad,
158 Msg::SetPrioridad,
159 )
160 .width(Dim::Pct(1.0))
161 .gap(2.0);
162
163 // EventId 6: botón limpiar.
164 let limpiar = ui
165 .button("limpiar", 20.0, [30, 10, 10, 255], Msg::Limpiar)
166 .text_align(TextAlign::Center)
167 .fill([200, 120, 90, 255])
168 .radius(8.0)
169 .size(Dim::Px(120.0), Dim::Px(40.0))
170 .align(Align::Center)
171 .justify(Justify::Center);
172
173 col(vec![
174 titulo, campo, check, edad_row, color, saludo, detalle, notas, prioridad, limpiar,
175 ])
176 .gap(16.0)
177 .pad(28.0)
178 .fill([18, 22, 30, 255])
179 .width(Dim::Pct(1.0))
180 .height(Dim::Pct(1.0))
181 }
182}
183
184export_wasm_app!(Form);