tawasuyu

tawasuyu/llimphi

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

Rust 484Markdown 258TOML 131RON 38118 Definitions

⎇ main llimphi-test/tests/tabs_indicador.rs Rust · 111 lines · 3.9 KB History Who touched what raw

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

1//! El **indicador deslizante** de `tabs`, medido sobre el árbol montado.
2//!
3//! Los tests de `llimphi-widget-tabs` certifican la aritmética
4//! (`indicator_bounds_px`); esto certifica lo otro: que el nodo que se pinta
5//! aterrice **donde dice la aritmética** una vez que taffy repartió la tira. Es
6//! la junta donde un indicador se rompe de verdad —queda bien calculado y mal
7//! colocado— y no se ve en un test puro.
8//!
9//! Sin GPU: se monta y se mide (CLAUDE.md §8).
10
11use llimphi_test::query::Ui;
12use llimphi_ui::{llimphi_layout::Style, View};
13use llimphi_widget_tabs::{
14    indicator_bounds_px, tab_widths, tabs_view, tabs_view_sliding, TabsPalette, TabsSpec,
15};
16
17#[derive(Clone, Debug, PartialEq)]
18enum Msg {
19    Select(usize),
20}
21
22/// Labels de **anchos bien distintos**: es el caso que el `segmented` no tiene
23/// y por el que este indicador estaba pendiente.
24fn labels() -> Vec<String> {
25    vec![
26        "Red".to_string(),
27        "Configuración avanzada del sistema".to_string(),
28        "Logs".to_string(),
29    ]
30}
31
32fn spec() -> TabsSpec<Msg, impl Fn(usize) -> Msg> {
33    TabsSpec {
34        labels: labels(),
35        active: 1,
36        on_select: |i: usize| Msg::Select(i),
37        content: View::<Msg>::new(Style::default()),
38        tab_height: 32.0,
39        palette: TabsPalette::from_theme(&llimphi_theme::Theme::default()),
40        tab_width: None,
41        on_close: None,
42        on_reorder: None,
43        strip_offset: 0.0,
44        on_strip_scroll: None,
45    }
46}
47
48/// ¿Hay un nodo con este rect (tolerancia de medio píxel)?
49fn hay_rect(ui: &Ui<Msg>, x: f32, w: f32, h: f32) -> bool {
50    ui.all().any(|n| {
51        let r = n.rect();
52        (r.x - x).abs() < 0.5 && (r.w - w).abs() < 0.5 && (r.h - h).abs() < 0.1
53    })
54}
55
56#[test]
57fn el_realce_aterriza_donde_dice_la_aritmetica() {
58    let anchos = tab_widths(&labels(), None, false);
59    // A mitad de camino entre el tab corto y el largo: ni una posición ni un
60    // ancho que coincidan con ningún tab, así que si el nodo está ahí es porque
61    // interpoló de verdad.
62    let (left, w) = indicator_bounds_px(&anchos, 0.5);
63    let ui = Ui::new(tabs_view_sliding(spec(), 0.5), 1200.0, 300.0);
64    assert!(
65        hay_rect(&ui, left, w, 2.0),
66        "no hay realce en ({left:.1}, {w:.1}):\n{}",
67        ui.dump()
68    );
69}
70
71#[test]
72fn el_modo_clasico_no_pinta_ningun_realce_flotante() {
73    let anchos = tab_widths(&labels(), None, false);
74    let (left, w) = indicator_bounds_px(&anchos, 0.5);
75    let ui = Ui::new(tabs_view(spec()), 1200.0, 300.0);
76    assert!(
77        !hay_rect(&ui, left, w, 2.0),
78        "el modo clásico no debería tener nada a mitad de camino entre dos tabs"
79    );
80}
81
82#[test]
83fn con_t_en_el_tab_activo_el_realce_lo_cubre_exacto() {
84    // El caso del host que no anima: `indicator_t = active`. El realce tiene
85    // que quedar clavado sobre el tab, no aproximado.
86    let anchos = tab_widths(&labels(), None, false);
87    let (left, w) = indicator_bounds_px(&anchos, 1.0);
88    let ui = Ui::new(tabs_view_sliding(spec(), 1.0), 1200.0, 300.0);
89    assert!(hay_rect(&ui, left, w, 2.0), "{}", ui.dump());
90    // Y ese ancho es exactamente el del tab activo, no una fracción del track.
91    assert!((w - anchos[1]).abs() < 0.01);
92}
93
94#[test]
95fn con_overflow_el_realce_viaja_con_la_tira() {
96    // Con `strip_offset` la fila interna está desplazada; el realce vive
97    // adentro, así que se corre con ella. Si el host tuviera que restarle el
98    // offset por su cuenta, este test lo delataría.
99    let anchos = tab_widths(&labels(), None, false);
100    let (left, w) = indicator_bounds_px(&anchos, 1.0);
101    let desplazamiento = 40.0_f32;
102    let mut s = spec();
103    s.strip_offset = desplazamiento;
104    s.on_strip_scroll = Some(std::sync::Arc::new(|_d: f32| Msg::Select(0)));
105    let ui = Ui::new(tabs_view_sliding(s, 1.0), 600.0, 300.0);
106    assert!(
107        hay_rect(&ui, left - desplazamiento, w, 2.0),
108        "el realce tendría que haberse corrido {desplazamiento} px:\n{}",
109        ui.dump()
110    );
111}