tawasuyu

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definiciones

⎇ main widgets/empty/src/lib.rs Rust · 129 líneas · 3.9 KB Historia Quién tocó qué crudo

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

1//! `llimphi-widget-empty` — empty state con icono, título y descripción.
2//!
3//! Patrón para reemplazar pantallas en blanco con orientación: cuando
4//! una lista no tiene items, un editor no tiene archivo abierto, una
5//! búsqueda no arrojó resultados — en vez de fondo plano, mostrar
6//! un icono grande apagado + título + descripción corta + (opcional)
7//! botón de acción primaria.
8
9#![forbid(unsafe_code)]
10
11
12// Sin `std`: este widget no usa nada del sistema —sólo `alloc` para
13// `String`/`Vec`—, y declararlo es lo que le permite cruzar a una jaula
14// WASM o al kernel. Antes enlazaba `std` por omisión (W4.6).
15#![no_std]
16
17extern crate alloc;
18
19use alloc::string::String;
20use alloc::vec;
21use alloc::string::ToString;
22use llimphi_compositor::llimphi_layout::taffy::{
23    prelude::{length, percent, FlexDirection, Size, Style},
24    AlignItems, JustifyContent,
25};
26use llimphi_compositor::peniko::Color;
27use llimphi_compositor::llimphi_text::Alignment;
28use llimphi_compositor::View;
29use llimphi_icons::{icon_view, Icon};
30use llimphi_theme::{alpha, Theme};
31
32/// Paleta del empty state — colores apagados para no competir con la
33/// UI principal.
34#[derive(Debug, Clone, Copy)]
35pub struct EmptyPalette {
36    pub fg_icon: Color,
37    pub fg_title: Color,
38    pub fg_desc: Color,
39}
40
41impl EmptyPalette {
42    pub fn from_theme(t: &Theme) -> Self {
43        Self {
44            fg_icon: with_alpha8(t.fg_muted, alpha::HINT),
45            fg_title: t.fg_muted,
46            fg_desc: with_alpha8(t.fg_muted, alpha::DISABLED),
47        }
48    }
49}
50
51fn with_alpha8(c: Color, a: u8) -> Color {
52    let [r, g, b, _] = c.components;
53    use llimphi_compositor::peniko::color::AlphaColor;
54    AlphaColor::new([r, g, b, a as f32 / 255.0])
55}
56
57/// Construye el empty state. La app llama desde su `view()` cuando
58/// detecta el caso vacío:
59///
60/// ```ignore
61/// if model.items.is_empty() {
62///     return empty_view(Icon::File, "Sin archivos abiertos",
63///                       Some("Abrí uno con Ctrl+O para empezar."),
64///                       &palette);
65/// }
66/// ```
67pub fn empty_view<Msg: Clone + 'static>(
68    icon: Icon,
69    title: impl Into<String>,
70    description: Option<&str>,
71    palette: &EmptyPalette,
72) -> View<Msg> {
73    let icon_cell = View::new(Style {
74        size: Size {
75            width: length(72.0_f32),
76            height: length(72.0_f32),
77        },
78        flex_shrink: 0.0,
79        ..Default::default()
80    })
81    .children(vec![icon_view(icon, palette.fg_icon, 1.4)]);
82
83    // El estado vacío es lo único que hay en pantalla: sin rol el lector
84    // recorre tres textos sueltos sin saber que son UN mensaje.
85    let aria = title.into();
86    let title_view = View::new(Style {
87        size: Size {
88            width: percent(1.0_f32),
89            height: length(28.0_f32),
90        },
91        flex_shrink: 0.0,
92        ..Default::default()
93    })
94    .text_aligned(aria.clone(), 15.5, palette.fg_title, Alignment::Center)
95    .role(llimphi_compositor::Role::Heading);
96
97    let mut children = vec![icon_cell, title_view];
98    if let Some(desc) = description {
99        children.push(
100            View::new(Style {
101                size: Size {
102                    width: length(360.0_f32),
103                    height: length(40.0_f32),
104                },
105                flex_shrink: 0.0,
106                ..Default::default()
107            })
108            .text_aligned(desc.to_string(), 12.0, palette.fg_desc, Alignment::Center),
109        );
110    }
111
112    View::new(Style {
113        flex_direction: FlexDirection::Column,
114        size: Size {
115            width: percent(1.0_f32),
116            height: percent(1.0_f32),
117        },
118        align_items: Some(AlignItems::CENTER),
119        justify_content: Some(JustifyContent::CENTER),
120        gap: Size {
121            width: length(0.0_f32),
122            height: length(14.0_f32),
123        },
124        ..Default::default()
125    })
126    .role(llimphi_compositor::Role::Status)
127    .aria_label(aria)
128    .children(children)
129}