gioser

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ main llimphi-drm/examples/lista.rs Rust · 127 lines · 4.3 KB History Who touched what raw

chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual d8edd9a9c · Sergio ·

1//! Una lista navegable en el framebuffer, **sin compositor y sin GPU**.
2//!
3//! No es una demo bonita: es el arnés que prueba que el camino entero llega al
4//! panel. Por eso los colores son planos y conocidos —se cuentan píxeles del
5//! otro lado de la frontera, con `screendump`, igual que hace
6//! `scripts/wawa-cromo-qemu.py` con el cromo del kernel.
7//!
8//!     cargo run -p llimphi-drm --example lista     (necesita una consola libre)
9//!
10//! Se sale con `q`.
11
12use llimphi_drm::peniko::Color;
13use llimphi_drm::taffy::prelude::{length, percent, FlexDirection, Size, Style};
14use llimphi_drm::{correr, Key, KeyEvent, NamedKey, Pantalla, Programa, View};
15
16/// Los tres colores del arnés. Planos y separados a propósito: el veredicto es
17/// un conteo de píxeles exactos, y dos tonos parecidos no se distinguirían del
18/// ruido de un rasterizador.
19const FONDO: Color = Color::from_rgba8(18, 18, 32, 255);
20const FILA: Color = Color::from_rgba8(40, 44, 60, 255);
21const ELEGIDA: Color = Color::from_rgba8(90, 160, 120, 255);
22
23struct Lista;
24
25#[derive(Clone)]
26enum Msg {
27    Arriba,
28    Abajo,
29    Salir,
30}
31
32struct Modelo {
33    elegido: usize,
34    salir: bool,
35}
36
37impl Programa for Lista {
38    type Model = Modelo;
39    type Msg = Msg;
40
41    fn init() -> Modelo {
42        Modelo { elegido: 0, salir: false }
43    }
44
45    fn update(mut m: Modelo, msg: Msg) -> Modelo {
46        match msg {
47            Msg::Arriba => m.elegido = m.elegido.saturating_sub(1),
48            Msg::Abajo => m.elegido = (m.elegido + 1).min(2),
49            Msg::Salir => m.salir = true,
50        }
51        m
52    }
53
54    fn view(m: &Modelo) -> View<Msg> {
55        let filas: Vec<View<Msg>> = (0..3)
56            .map(|i| {
57                View::new(Style {
58                    size: Size { width: percent(0.8_f32), height: length(80.0_f32) },
59                    ..Default::default()
60                })
61                .fill(if i == m.elegido { ELEGIDA } else { FILA })
62            })
63            .collect();
64        View::new(Style {
65            flex_direction: FlexDirection::Column,
66            size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
67            gap: Size { width: length(0.0_f32), height: length(20.0_f32) },
68            ..Default::default()
69        })
70        .fill(FONDO)
71        .children(filas)
72    }
73
74    fn on_key(_m: &Modelo, ev: &KeyEvent) -> Option<Msg> {
75        match &ev.key {
76            Key::Named(NamedKey::ArrowUp) => Some(Msg::Arriba),
77            Key::Named(NamedKey::ArrowDown) => Some(Msg::Abajo),
78            Key::Character(c) if c == "q" => Some(Msg::Salir),
79            _ => None,
80        }
81    }
82
83    fn termino(m: &Modelo) -> bool {
84        m.salir
85    }
86}
87
88/// Decir algo donde se pueda LEER desde afuera.
89///
90/// La salida estándar de un programa lanzado por el getty es su tty —acá tty1,
91/// la pantalla gráfica—, así que un `println!` no llega a la consola serie y
92/// quien mira el arranque desde afuera no ve nada. `/dev/kmsg` es el ring del
93/// kernel, y el kernel lo replica a la consola del `console=` del cmdline. Es
94/// el mismo camino que usa `arje-zero` para sus huellas de arranque.
95fn decir(linea: &str) {
96    use std::io::Write;
97    println!("{linea}");
98    // `/dev/console` es el que apunta el último `console=` del cmdline —en el
99    // medio, el puerto serie—, y es lo único que se lee desde afuera cuando la
100    // pantalla es justamente lo que se está probando.
101    for destino in ["/dev/console", "/dev/kmsg"] {
102        if let Ok(mut f) = std::fs::OpenOptions::new().write(true).open(destino) {
103            let _ = writeln!(f, "{linea}");
104        }
105    }
106}
107
108fn main() {
109    decir("lista :: arranco");
110    let mut pantalla = match Pantalla::abrir() {
111        Ok(p) => p,
112        Err(e) => {
113            // Se dice por qué y se sale con código: en un arranque headless esto
114            // es lo único que queda en el log del serial.
115            decir(&format!("lista :: ERROR no pude abrir la pantalla: {e}"));
116            std::process::exit(2);
117        }
118    };
119    let (w, h) = pantalla.tamano();
120    // El tamaño va al serial: es lo que permite saber, desde afuera, contra qué
121    // resolución hay que contar los píxeles del pantallazo.
122    decir(&format!("lista :: pantalla {w}x{h}"));
123    if let Err(e) = correr::<Lista>(&mut pantalla) {
124        decir(&format!("lista :: ERROR {e}"));
125        std::process::exit(1);
126    }
127}