tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ dba855e446ca2bd4e7d146bdc168fb232cad15a4 widgets/stat-card/src/lib.rs Rust · 144 lines · 4.1 KB History Who touched what raw

feat: llimphi standalone — framework UI soberano extraído del monorepo e65e9cc62 · Sergio ·

1//! `llimphi-widget-stat-card` — tarjeta de dashboard con accent.
2//!
3//! Compone (sobre `llimphi-widget-card`):
4//! - **Border-l-4** con un color de accent que el caller decide.
5//! - **Label** chico arriba en el color del accent.
6//! - **Value** grande (28 px) en el color principal del texto.
7//! - **Description** chica en el color tenue.
8//! - **Listing opcional** de items recientes con sub-header
9//!   `"recent (N):"`.
10//!
11//! Análogo Llimphi al `nahual-widget-stat-card` GPUI. Pensado para
12//! dashboards estilo `minga-explorer`, `brahman-broker-explorer`.
13
14#![forbid(unsafe_code)]
15
16use llimphi_ui::llimphi_layout::taffy::{
17    prelude::{length, percent, Size, Style},
18    Rect,
19};
20use llimphi_ui::llimphi_raster::peniko::Color;
21use llimphi_ui::llimphi_text::Alignment;
22use llimphi_ui::View;
23use llimphi_widget_card::{card_view, CardOptions, CardPalette};
24
25/// Paleta del stat-card. `accent` se setea por instancia (verde/rojo/
26/// ámbar etc.), los otros vienen del theme.
27#[derive(Debug, Clone, Copy)]
28pub struct StatCardPalette {
29    pub bg: Color,
30    pub fg_text: Color,
31    pub fg_muted: Color,
32}
33
34impl Default for StatCardPalette {
35    fn default() -> Self {
36        Self::from_theme(&llimphi_theme::Theme::dark())
37    }
38}
39
40impl StatCardPalette {
41    pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
42        Self {
43            bg: t.bg_panel,
44            fg_text: t.fg_text,
45            fg_muted: t.fg_muted,
46        }
47    }
48}
49
50/// Compone un stat-card.
51///
52/// - `label`: header chico en color `accent`.
53/// - `value`: texto principal grande.
54/// - `description`: línea chica tenue debajo del value.
55/// - `accent`: color del border-l + del label.
56/// - `recent_items`: si no vacío, agrega "recent (N):" + una fila por
57///   item.
58pub fn stat_card_view<Msg: Clone + 'static>(
59    label: &str,
60    value: impl Into<String>,
61    description: &str,
62    accent: Color,
63    recent_items: &[String],
64    palette: &StatCardPalette,
65) -> View<Msg> {
66    let label_row = View::new(Style {
67        size: Size {
68            width: percent(1.0_f32),
69            height: length(14.0_f32),
70        },
71        ..Default::default()
72    })
73    .text_aligned(label.to_string(), 11.0, accent, Alignment::Start);
74
75    let value_row = View::new(Style {
76        size: Size {
77            width: percent(1.0_f32),
78            height: length(36.0_f32),
79        },
80        ..Default::default()
81    })
82    .text_aligned(value.into(), 28.0, palette.fg_text, Alignment::Start);
83
84    let desc_row = View::new(Style {
85        size: Size {
86            width: percent(1.0_f32),
87            height: length(14.0_f32),
88        },
89        ..Default::default()
90    })
91    .text_aligned(
92        description.to_string(),
93        11.0,
94        palette.fg_muted,
95        Alignment::Start,
96    );
97
98    let mut children: Vec<View<Msg>> = vec![label_row, value_row, desc_row];
99
100    if !recent_items.is_empty() {
101        children.push(
102            View::new(Style {
103                size: Size {
104                    width: percent(1.0_f32),
105                    height: length(16.0_f32),
106                },
107                padding: Rect {
108                    left: length(0.0_f32),
109                    right: length(0.0_f32),
110                    top: length(6.0_f32),
111                    bottom: length(0.0_f32),
112                },
113                ..Default::default()
114            })
115            .text_aligned(
116                format!("recent ({}):", recent_items.len()),
117                10.0,
118                palette.fg_muted,
119                Alignment::Start,
120            ),
121        );
122        for it in recent_items {
123            children.push(
124                View::new(Style {
125                    size: Size {
126                        width: percent(1.0_f32),
127                        height: length(14.0_f32),
128                    },
129                    ..Default::default()
130                })
131                .text_aligned(it.clone(), 11.0, palette.fg_text, Alignment::Start),
132            );
133        }
134    }
135
136    card_view(
137        children,
138        CardOptions {
139            accent: Some(accent),
140            ..Default::default()
141        },
142        &CardPalette { bg: palette.bg },
143    )
144}