gioser

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/banner/src/lib.rs Rust · 127 líneas · 4.2 KB Historia Quién tocó qué crudo

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

1//! `llimphi-widget-banner` — tiras horizontales de status.
2//!
3//! Cuatro variants con paleta consistente entre apps:
4//!
5//! - [`BannerKind::Info`] — azul tenue, mensajes neutros.
6//! - [`BannerKind::Success`] — verde, confirmaciones de op exitosa.
7//! - [`BannerKind::Warning`] — amber, llamadas de atención.
8//! - [`BannerKind::Error`] — rojo, errores fatales o de carga.
9//!
10//! Análogo Llimphi al `nahual-widget-banner` GPUI. Los colores son
11//! **semánticos** y no cambian con el theme (un Error en dark y en
12//! light tiene que seguir leyéndose como rojo).
13
14#![forbid(unsafe_code)]
15
16
17// Sin `std`: este widget no usa nada del sistema —sólo `alloc` para
18// `String`/`Vec`—, y declararlo es lo que le permite cruzar a una jaula
19// WASM o al kernel. Antes enlazaba `std` por omisión (W4.6).
20#![no_std]
21
22extern crate alloc;
23
24use alloc::string::String;
25use alloc::vec;
26use llimphi_compositor::llimphi_layout::taffy::{
27    prelude::{length, percent, Size, Style},
28    AlignItems, Rect,
29};
30use llimphi_compositor::peniko::Color;
31use llimphi_compositor::llimphi_text::Alignment;
32use llimphi_compositor::View;
33
34/// Severidad / tono del banner.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum BannerKind {
37    Info,
38    Success,
39    Warning,
40    Error,
41}
42
43impl BannerKind {
44    pub fn bg(self) -> Color {
45        match self {
46            BannerKind::Info => Color::from_rgba8(0x1d, 0x2a, 0x3a, 0xff),
47            BannerKind::Success => Color::from_rgba8(0x2d, 0x3a, 0x2a, 0xff),
48            BannerKind::Warning => Color::from_rgba8(0x4a, 0x3a, 0x1a, 0xff),
49            BannerKind::Error => Color::from_rgba8(0x4a, 0x20, 0x20, 0xff),
50        }
51    }
52
53    pub fn fg(self) -> Color {
54        match self {
55            BannerKind::Info => Color::from_rgba8(0xc0, 0xd0, 0xe0, 0xff),
56            BannerKind::Success => Color::from_rgba8(0xc0, 0xe0, 0xa0, 0xff),
57            BannerKind::Warning => Color::from_rgba8(0xf0, 0xe0, 0xa0, 0xff),
58            BannerKind::Error => Color::from_rgba8(0xff, 0xd0, 0xd0, 0xff),
59        }
60    }
61}
62
63/// Ancho del rail de severidad en el edge izquierdo. Mismo valor que
64/// `llimphi-widget-toast` — banner y toast son las versiones persistente
65/// y efímera del mismo lenguaje (P5 → P8).
66const RAIL_W: f32 = 3.0;
67
68/// Banner simple: una fila con `message` centrado verticalmente y
69/// alineado a la izquierda. bg/fg vienen del `kind`.
70pub fn banner_view<Msg: Clone + 'static>(
71    kind: BannerKind,
72    message: impl Into<String>,
73) -> View<Msg> {
74    use llimphi_compositor::llimphi_layout::taffy::prelude::FlexDirection;
75
76    // El texto vive en un hijo; el nodo `Alert` es la raíz, así que necesita
77    // el mensaje como nombre accesible.
78    let message: String = message.into();
79
80    // Rail de severidad en el edge izquierdo — stripe del color fg
81    // semántico, visible al pasar el ojo. Mismo patrón que toast P5.
82    let rail = View::new(Style {
83        size: Size {
84            width: length(RAIL_W),
85            height: percent(1.0_f32),
86        },
87        flex_shrink: 0.0,
88        ..Default::default()
89    })
90    .fill(kind.fg());
91
92    // Contenedor del mensaje: padding original ahora vive aquí para que
93    // el rail pegue al borde sin offset y el texto arranque después.
94    let body = View::new(Style {
95        size: Size {
96            width: percent(1.0_f32),
97            height: percent(1.0_f32),
98        },
99        padding: Rect {
100            left: length(12.0_f32),
101            right: length(12.0_f32),
102            top: length(6.0_f32),
103            bottom: length(6.0_f32),
104        },
105        align_items: Some(AlignItems::CENTER),
106        ..Default::default()
107    })
108    .text_aligned(message.clone(), 11.0, kind.fg(), Alignment::Start);
109
110    View::new(Style {
111        flex_direction: FlexDirection::Row,
112        size: Size {
113            width: percent(1.0_f32),
114            height: length(28.0_f32),
115        },
116        align_items: Some(AlignItems::CENTER),
117        ..Default::default()
118    })
119    .fill(kind.bg())
120    .radius(3.0)
121    .clip(true)
122    // Alert: los lectores lo anuncian solos al aparecer, sin esperar que el
123    // usuario navegue hasta él — que es justo el punto de un banner.
124    .role(llimphi_compositor::Role::Alert)
125    .aria_label(message)
126    .children(vec![rail, body])
127}