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

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

1//! `llimphi-widget-gauge` — medidor radial.
2//!
3//! Arco de 270° (de 7:30 a 4:30, manecillas de reloj) con un track
4//! gris fino y un arco activo del color `accent` que crece con el
5//! valor. En el centro, una etiqueta opcional con el valor formateado.
6//! Pensado para dashboards (CPU, RAM, throughput) y métricas con
7//! contexto (lleno / vacío / objetivo).
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::format;
21use llimphi_compositor::llimphi_layout::taffy::{
22    prelude::{length, Size, Style},
23    AlignItems, JustifyContent,
24};
25use llimphi_compositor::peniko::Color;
26use llimphi_compositor::llimphi_text::Alignment;
27use llimphi_compositor::View;
28use llimphi_theme::Theme;
29
30#[derive(Debug, Clone, Copy)]
31pub struct GaugePalette {
32    pub track: Color,
33    pub active: Color,
34    pub fg: Color,
35}
36
37impl GaugePalette {
38    pub fn from_theme(t: &Theme) -> Self {
39        Self {
40            track: t.border,
41            active: t.accent,
42            fg: t.fg_text,
43        }
44    }
45}
46
47/// Render del gauge. `value` es la fracción 0..=1 que representa la
48/// progresión del arco. `label` es texto centrado opcional.
49pub fn gauge_view<Msg: Clone + 'static>(
50    value: f32,
51    size_px: f32,
52    label: Option<String>,
53    palette: &GaugePalette,
54) -> View<Msg> {
55    let v = value.clamp(0.0, 1.0);
56    let track = palette.track;
57    let active = palette.active;
58    let mut node = View::new(Style {
59        size: Size { width: length(size_px), height: length(size_px) },
60        align_items: Some(AlignItems::CENTER),
61        justify_content: Some(JustifyContent::CENTER),
62        ..Default::default()
63    })
64    .paint_with(move |scene, _ts, rect| {
65        paint_gauge(scene, rect, v, track, active);
66    })
67    // El arco lo pinta un `paint_with`: sin esto es un nodo vacío para el
68    // lector. El label visible (si lo hay) suele ser el valor formateado por
69    // el caller, pero el porcentaje crudo va igual en `value`.
70    .role(llimphi_compositor::Role::ProgressBar)
71    .aria_value(format!("{}%", (v * 100.0).round() as i32));
72    if let Some(lbl) = label {
73        node = node
74            .aria_label(lbl.clone())
75            .text_aligned(lbl, (size_px * 0.22).max(11.0), palette.fg, Alignment::Center);
76    }
77    node
78}
79
80fn paint_gauge(
81    scene: &mut llimphi_compositor::Lienzo,
82    rect: llimphi_compositor::PaintRect,
83    value: f32,
84    track: Color,
85    active: Color,
86) {
87    use llimphi_compositor::peniko::kurbo::{Affine, Arc, Point, Stroke};
88    if rect.w <= 0.0 || rect.h <= 0.0 {
89        return;
90    }
91    let cx = (rect.x + rect.w * 0.5) as f64;
92    let cy = (rect.y + rect.h * 0.5) as f64;
93    let s = rect.w.min(rect.h) as f64;
94    let stroke_w = (s * 0.10).max(2.0);
95    let r = (s * 0.5) - stroke_w * 0.6;
96
97    // Arco completo (270° = 1.5π), empieza en 135° (7:30) y avanza
98    // sentido horario. En kurbo, `start_angle` está en radianes; CCW
99    // positivo. Tomamos start = 135° (back of bottom-left), sweep
100    // negativo para ir CW.
101    let start_deg = 135.0_f64;
102    let total_sweep_deg = -270.0_f64; // CW
103    let active_sweep_deg = total_sweep_deg * (value as f64);
104
105    let center = Point::new(cx, cy);
106    let radii = (r, r);
107
108    let track_arc = Arc {
109        center,
110        radii: radii.into(),
111        start_angle: start_deg.to_radians(),
112        sweep_angle: total_sweep_deg.to_radians(),
113        x_rotation: 0.0,
114    };
115    let stroke = Stroke::new(stroke_w).with_caps(llimphi_compositor::peniko::kurbo::Cap::Round);
116    scene.stroke(&stroke, Affine::IDENTITY, track, None, &track_arc);
117
118    if value > 0.001 {
119        let active_arc = Arc {
120            center,
121            radii: radii.into(),
122            start_angle: start_deg.to_radians(),
123            sweep_angle: active_sweep_deg.to_radians(),
124            x_rotation: 0.0,
125        };
126        scene.stroke(&stroke, Affine::IDENTITY, active, None, &active_arc);
127    }
128}