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/progress/src/lib.rs Rust · 144 líneas · 5.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-progress` — progreso determinado, lineal o radial.
2//!
3//! Determinado = la app conoce el porcentaje (`0.0..=1.0`). Para
4//! progreso indeterminado (la op está corriendo, no sé cuánto falta),
5//! usar `llimphi-widget-spinner`.
6//!
7//! Dos formas:
8//! - [`linear_progress_view`] — barra horizontal con relleno proporcional.
9//! - [`radial_progress_view`] — anillo cuya porción llena indica el avance.
10
11#![forbid(unsafe_code)]
12
13
14// Sin `std`: este widget no usa nada del sistema —sólo `alloc` para
15// `String`/`Vec`/`Arc`—, y declararlo es lo que le permite cruzar a una
16// jaula WASM o al kernel. Antes enlazaba `std` por omisión (W4.6).
17#![no_std]
18
19extern crate alloc;
20
21use alloc::vec;
22use alloc::format;
23use llimphi_compositor::llimphi_layout::taffy::{
24    prelude::{length, percent, FlexDirection, Position, Size, Style},
25    Rect,
26};
27use llimphi_compositor::peniko::kurbo::{Affine, Arc, Cap, Stroke};
28use llimphi_compositor::peniko::Color;
29use llimphi_compositor::View;
30use llimphi_theme::radius;
31
32/// Barra horizontal: una pista (`track`) con un fill proporcional al
33/// `progress` (0.0..=1.0) pintado encima.
34pub fn linear_progress_view<Msg: Clone + 'static>(
35    progress: f32,
36    track_color: Color,
37    fill_color: Color,
38    height_px: f32,
39) -> View<Msg> {
40    let p = progress.clamp(0.0, 1.0);
41    let fill_radius = radius::XS;
42    let fill = View::new(Style {
43        position: Position::Absolute,
44        inset: Rect {
45            left: length(0.0_f32),
46            top: length(0.0_f32),
47            right: llimphi_compositor::llimphi_layout::taffy::prelude::auto(),
48            bottom: length(0.0_f32),
49        },
50        size: Size {
51            width: percent(p),
52            height: percent(1.0_f32),
53        },
54        ..Default::default()
55    })
56    .fill(fill_color)
57    .radius(fill_radius)
58    .paint_with(move |scene, _ts, rect| {
59        // Gloss superior sobre la porción rellena — la barra deja de
60        // leerse como un rect plano y se siente como una luz que avanza.
61        // Mismo patrón que button/badge (P6).
62        use llimphi_compositor::peniko::kurbo::{Affine, Point, RoundedRect};
63        use llimphi_compositor::peniko::{Fill, Gradient};
64        if rect.w <= 0.0 || rect.h <= 0.0 {
65            return;
66        }
67        let x0 = rect.x as f64;
68        let y0 = rect.y as f64;
69        let x1 = (rect.x + rect.w) as f64;
70        let y1 = (rect.y + rect.h) as f64;
71        let y_mid = y0 + (y1 - y0) * 0.5;
72        let rr = RoundedRect::new(x0, y0, x1, y1, fill_radius);
73        let top = Color::from_rgba8(255, 255, 255, 50);
74        let bot = Color::from_rgba8(255, 255, 255, 0);
75        let g = Gradient::new_linear(Point::new(x0, y0), Point::new(x0, y_mid))
76            .with_stops([top, bot].as_slice());
77        scene.fill(Fill::NonZero, Affine::IDENTITY, &g, None, &rr);
78    });
79
80    View::new(Style {
81        flex_direction: FlexDirection::Row,
82        size: Size {
83            width: percent(1.0_f32),
84            height: length(height_px),
85        },
86        ..Default::default()
87    })
88    .fill(track_color)
89    .radius(radius::XS)
90    // Sin esto la barra es una caja de color: el lector no tiene forma de
91    // enunciar el avance. El `value` es el porcentaje ya redondeado.
92    .role(llimphi_compositor::Role::ProgressBar)
93    .aria_value(format!("{}%", (p * 100.0 + 0.5) as i32))
94    .children(vec![fill])
95}
96
97/// Anillo cuya porción angular llena indica el avance. Empieza desde
98/// arriba (12 en punto) y gira en sentido horario, igual que la
99/// convención de relojes y muchos progress radiales.
100pub fn radial_progress_view<Msg: Clone + 'static>(
101    progress: f32,
102    track_color: Color,
103    fill_color: Color,
104    stroke_width_ratio: f32,
105) -> View<Msg> {
106    let p = progress.clamp(0.0, 1.0);
107    let sw = stroke_width_ratio;
108    View::new(Style {
109        position: Position::Absolute,
110        size: Size {
111            width: percent(1.0_f32),
112            height: percent(1.0_f32),
113        },
114        ..Default::default()
115    })
116    .paint_with(move |scene, _ts, rect| {
117        let side = rect.w.min(rect.h) as f64;
118        if side <= 0.0 {
119            return;
120        }
121        let cx = rect.x as f64 + rect.w as f64 * 0.5;
122        let cy = rect.y as f64 + rect.h as f64 * 0.5;
123        let stroke_w = (side * sw as f64).max(1.0);
124        let radius = (side - stroke_w) * 0.5;
125        let stroke = Stroke::new(stroke_w).with_caps(Cap::Round);
126
127        // Track completo (anillo gris).
128        let track = Arc::new((cx, cy), (radius, radius), 0.0, core::f64::consts::TAU, 0.0);
129        scene.stroke(&stroke, Affine::IDENTITY, track_color, None, &track);
130
131        // Arco lleno — arranca en -π/2 (12 en punto) y barre `p * 2π`
132        // en sentido horario (positivo en el sistema y-down de vello).
133        if p > 0.0 {
134            let theta0 = -core::f64::consts::FRAC_PI_2;
135            let sweep = core::f64::consts::TAU * p as f64;
136            let fill_arc = Arc::new((cx, cy), (radius, radius), theta0, sweep, 0.0);
137            scene.stroke(&stroke, Affine::IDENTITY, fill_color, None, &fill_arc);
138        }
139    })
140    // El anillo se pinta entero en un `paint_with`: sin semántica declarada
141    // sería un nodo vacío para el lector.
142    .role(llimphi_compositor::Role::ProgressBar)
143    .aria_value(format!("{}%", (p * 100.0 + 0.5) as i32))
144}