tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1//! Mide cuánto tarda el ajuste blando del text-area y el hit-testing del mouse
2//! según el largo del texto. Diagnóstico de T11 (COLA-SHUMA): el input se cuelga
3//! al clickear con mucho texto. Evidencia en TEXTO (Regla 8 del repo), no en
4//! pantalla.
5//!
6//! Tres columnas, tres caminos distintos:
7//! - `wrap` — partir el texto en renglones visuales (`renglones_visuales`).
8//! - `click2d` — click en el text-area: wrap + mapear x→columna dentro del
9//! renglón (`place_caret_2d`).
10//! - `click1L` — click en el campo de **una línea**, donde el texto llega
11//! entero al mapeo x→columna (`Press`). No pasa por el wrap. Se mide con el
12//! caret **al final** —como queda tras tipear o pegar—, que es el caso caro:
13//! ahí el campo está desplazado (`scroll_x`) y el punto clickeado cae al
14//! fondo del texto, no en los primeros caracteres.
15//!
16//! Lo que se mira no es el número absoluto (depende de la máquina) sino cómo
17//! crece: duplicar el texto tiene que duplicar el costo, no cuadruplicarlo.
18
19use llimphi_widget_text_input::{MemClipboard, TextInputEvent, TextInputState};
20use std::time::Instant;
21
22fn main() {
23 let mut clip = MemClipboard::new();
24 // Ancho típico del input del cajón, y un texto de palabras normales.
25 const ANCHO: f32 = 900.0;
26 let palabra = "cargo ";
27
28 // Calentar el typesetter: la primera medición de todo el proceso paga la
29 // carga de la fuente (~150 ms) y, sin esto, se la come la primera fila de
30 // la tabla y parece un costo del texto corto.
31 {
32 let mut st = TextInputState::new();
33 st.fijar_ancho_caja(ANCHO);
34 st.set_text("cargo cargo cargo");
35 let _ = st.renglones_visuales();
36 st.pointer_click(100.0, 13.0);
37 }
38
39 println!(
40 "{:>8} {:>10} {:>9} {:>9} {:>9}",
41 "chars", "renglones", "wrap ms", "click2d ms", "click1L ms"
42 );
43 for n in [200usize, 500, 1_000, 2_000, 4_000, 8_000, 16_000, 32_000] {
44 let texto: String = palabra.repeat(n / palabra.len() + 1);
45 let texto = &texto[..n];
46
47 let mut st = TextInputState::new();
48 st.fijar_ancho_caja(ANCHO);
49 st.set_text(texto);
50
51 let t0 = Instant::now();
52 let filas = st.renglones_visuales();
53 let ms = t0.elapsed().as_secs_f64() * 1000.0;
54
55 // El gesto del bug: un click en una posición puntual del texto.
56 let t1 = Instant::now();
57 st.place_caret_2d(430.0, 40.0);
58 let ms_click = t1.elapsed().as_secs_f64() * 1000.0;
59
60 // El mismo gesto en el campo de una línea: el texto no está envuelto,
61 // así que el mapeo x→columna lo ve entero. Con el caret al final el
62 // campo está desplazado y el click cae al fondo del texto.
63 let mut una = TextInputState::new();
64 una.fijar_ancho_caja(ANCHO);
65 una.set_text(texto);
66 una.editor_mut().set_caret_at(0, n);
67 let t2 = Instant::now();
68 una.handle(TextInputEvent::Press(430.0), &mut clip);
69 let ms_1l = t2.elapsed().as_secs_f64() * 1000.0;
70
71 println!("{n:>8} {filas:>10} {ms:>9.1} {ms_click:>10.1} {ms_1l:>10.1}");
72
73 if ms > 4_000.0 || ms_1l > 4_000.0 {
74 println!(" (corto acá: ya es inusable)");
75 break;
76 }
77 }
78}