tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
1// =============================================================================
2// escena_patron.rs — el árbol de prueba que se pinta por LOS DOS caminos.
3// -----------------------------------------------------------------------------
4// Llimphi tiene dos rasterizadores: `vello` (GPU, lo que corre en Linux) y
5// `vello_cpu` vía `llimphi-cpu` (lo que corre en el kernel bare-metal de wawa).
6// El compositor es el MISMO —`paint` emite las mismas órdenes— pero **nadie
7// había comparado nunca la salida**. O sea que la UI de wawa podía verse
8// distinta de la de Linux y no había forma de enterarse sin bootear y mirar.
9//
10// Este archivo se **incluye con `include!`** desde los dos lados, y ésa no es
11// una maña sino la única forma: los dos backends de `llimphi-text` son
12// excluyentes (`cpu` vs `vello`), así que un solo binario no puede linkear los
13// dos y `llimphi-wawa` vive en un workspace aparte justamente por eso. Un
14// archivo compartido es lo que garantiza que las dos corridas pinten el mismo
15// árbol y no dos árboles parecidos.
16//
17// Sólo se usa la API de `llimphi-compositor`, que compila igual en los dos
18// perfiles. Los dos sitios que lo incluyen corren en el anfitrión (el volcador
19// de wawa con `--features std`), así que `Vec` de `std` está bien: lo que se
20// comparte es el ÁRBOL, no el perfil sin sistema operativo.
21// =============================================================================
22
23use llimphi_compositor::llimphi_layout::taffy::prelude::{
24 length, percent, FlexDirection, Size, Style,
25};
26use llimphi_compositor::llimphi_layout::taffy::Rect as Insets;
27use llimphi_compositor::peniko::Color;
28use llimphi_compositor::{Shadow, View};
29
30/// Tamaño de todos los patrones. Chico a propósito: lo que se compara es el
31/// **rasterizado**, y un lienzo grande sólo multiplica los bytes del volcado.
32pub const ANCHO: u32 = 200;
33pub const ALTO: u32 = 150;
34
35fn caja<Msg: Clone>(w: f32, h: f32) -> View<Msg> {
36 View::<Msg>::new(Style {
37 size: Size { width: length(w), height: length(h) },
38 ..Default::default()
39 })
40}
41
42fn raiz<Msg: Clone>(hijos: Vec<View<Msg>>) -> View<Msg> {
43 View::<Msg>::new(Style {
44 flex_direction: FlexDirection::Column,
45 size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
46 gap: Size { width: length(0.0_f32), height: length(6.0_f32) },
47 padding: Insets {
48 left: length(8.0_f32),
49 right: length(8.0_f32),
50 top: length(8.0_f32),
51 bottom: length(8.0_f32),
52 },
53 ..Default::default()
54 })
55 .fill(Color::from_rgba8(18, 18, 24, 255))
56 .children(hijos)
57}
58
59/// Los patrones, **separados por rasgo**. Que vayan sueltos y no todos en un
60/// cuadro es deliberado: si los dos caminos divergen, el número por patrón dice
61/// **en qué** divergen. Un solo número global diría «difieren» y nada más.
62pub fn patrones<Msg: Clone>() -> Vec<(&'static str, View<Msg>)> {
63 let mut v = Vec::new();
64
65 // 1. Rectángulos alineados a la grilla: sin AA de por medio. Si esto ya
66 // difiere, el problema no es el antialiasing sino el color o el volcado.
67 v.push((
68 "rects",
69 raiz(vec![
70 caja(80.0, 30.0).fill(Color::from_rgba8(220, 60, 60, 255)),
71 caja(120.0, 25.0).fill(Color::from_rgba8(60, 200, 120, 255)),
72 caja(40.0, 40.0).fill(Color::from_rgba8(80, 120, 240, 255)),
73 ]),
74 ));
75
76 // 2. Esquinas redondeadas y borde: acá sí entra el antialiasing, que es
77 // donde dos rasterizadores distintos tienen todo el derecho a diferir.
78 v.push((
79 "radios",
80 raiz(vec![
81 caja(90.0, 40.0)
82 .fill(Color::from_rgba8(200, 200, 220, 255))
83 .radius(12.0),
84 caja(90.0, 40.0)
85 .fill(Color::from_rgba8(40, 40, 60, 255))
86 .radius(6.0)
87 .border(2.0, Color::from_rgba8(240, 180, 60, 255)),
88 ]),
89 ));
90
91 // 3. Alpha y capas: `alpha` abre una capa en los dos backends, y componer
92 // capas es donde una diferencia de orden se nota entera.
93 v.push((
94 "alpha",
95 raiz(vec![
96 caja(150.0, 50.0)
97 .fill(Color::from_rgba8(240, 80, 40, 255))
98 .alpha(0.5)
99 .children(vec![caja(60.0, 30.0)
100 .fill(Color::from_rgba8(40, 240, 200, 255))]),
101 caja(150.0, 40.0).fill(Color::from_rgba8(90, 90, 240, 128)),
102 ]),
103 ));
104
105 // 4. Sombra: un blur, o sea el rasgo con más aritmética de los cuatro.
106 v.push((
107 "sombra",
108 raiz(vec![caja(110.0, 60.0)
109 .fill(Color::from_rgba8(230, 230, 240, 255))
110 .radius(10.0)
111 .shadow(Shadow {
112 color: Color::from_rgba8(0, 0, 0, 160),
113 blur: 10.0,
114 dx: 3.0,
115 dy: 4.0,
116 spread: 0.0,
117 })]),
118 ));
119
120 // 5. Texto. La fuente va embebida (`include_bytes!` en `llimphi-text`), así
121 // que los dos caminos shapean con el MISMO archivo y lo que quede es
122 // diferencia de rasterizado de glifos, no de tipografía.
123 v.push((
124 "texto",
125 raiz(vec![
126 View::<Msg>::new(Style::default()).text(
127 "Hola wawa — jgpqy",
128 18.0,
129 Color::from_rgba8(240, 240, 250, 255),
130 ),
131 View::<Msg>::new(Style::default()).text(
132 "renglón chico 0123456789",
133 11.0,
134 Color::from_rgba8(180, 200, 240, 255),
135 ),
136 ]),
137 ));
138
139 // 6. **La escalera de tamaños de texto.** Existe para decidir una pregunta
140 // concreta: la divergencia del texto entre los dos rasterizadores, ¿es
141 // de la ORLA o es SISTEMÁTICA?
142 //
143 // La tinta de un glifo crece con el área (≈ s²) y su borde con el
144 // perímetro (≈ s). Si la diferencia viene sólo del antialiasing del
145 // contorno, su peso relativo tiene que caer como 1/s al agrandar la
146 // letra. Si en cambio se mantiene, no es el borde: es que un lado pinta
147 // la cobertura distinto en todo el glifo, y eso sí sería un problema.
148 //
149 // Una sola letra por patrón, y con curvas: menos glifos, menos ruido.
150 for tam in [11.0f32, 18.0, 48.0, 96.0] {
151 v.push((
152 match tam as u32 {
153 11 => "texto_11",
154 18 => "texto_18",
155 48 => "texto_48",
156 _ => "texto_96",
157 },
158 raiz(vec![View::<Msg>::new(Style::default()).text(
159 "Ooo",
160 tam,
161 Color::from_rgba8(255, 255, 255, 255),
162 )]),
163 ));
164 }
165
166 // 7. Texto separado por REPERTORIO, no por tamaño: ASCII puro contra
167 // caracteres que pueden no estar en las fuentes embebidas.
168 for (nombre, cadena) in [
169 ("texto_ascii", "Hamburgefonstiv 0123"),
170 ("texto_acentos", "renglon ñáéíóú"),
171 ("texto_raya", "guion — largo"),
172 ] {
173 v.push((
174 nombre,
175 raiz(vec![View::<Msg>::new(Style::default()).text(
176 cadena,
177 18.0,
178 Color::from_rgba8(255, 255, 255, 255),
179 )]),
180 ));
181 }
182
183 v
184}