gioser

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ 14d76bd4af13b219f0b162a54f09e784dea9aa8d widgets/button/src/lib.rs Rust · 124 lines · 4.4 KB History Who touched what raw

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

1//! `llimphi-widget-button` — botón clicable con estado hover.
2//!
3//! Reusable entre apps Llimphi: `button_view(label, palette, on_click)`
4//! devuelve una vista que cambia de color cuando el cursor pasa por
5//! encima y emite `on_click` al ser apretada. El caller controla las
6//! dimensiones envolviendo el `View` retornado en un contenedor flex
7//! con el tamaño que necesite (botón ancho completo, chip 80×30, etc).
8//!
9//! No expone estado interno — todo el estado vive en el `Model` del App
10//! (el hover lo trackea llimphi-ui automáticamente vía `hover_fill`).
11
12#![forbid(unsafe_code)]
13
14use llimphi_ui::llimphi_layout::taffy::{
15    prelude::{length, percent, Size, Style},
16    AlignItems, JustifyContent, Rect,
17};
18use llimphi_ui::llimphi_raster::peniko::Color;
19use llimphi_ui::llimphi_text::Alignment;
20use llimphi_ui::View;
21
22/// Paleta del botón. Por default un chip dark con highlight tenue al
23/// hover — similar al patrón `bg_panel_alt` + `bg_row_hover` de
24/// `nahual-theme`.
25#[derive(Debug, Clone, Copy)]
26pub struct ButtonPalette {
27    pub bg: Color,
28    pub bg_hover: Color,
29    pub fg: Color,
30    pub radius: f64,
31}
32
33impl Default for ButtonPalette {
34    fn default() -> Self {
35        Self::from_theme(&llimphi_theme::Theme::dark())
36    }
37}
38
39impl ButtonPalette {
40    /// Construye la paleta desde un `Theme` semántico.
41    pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
42        Self {
43            bg: t.bg_button,
44            bg_hover: t.bg_button_hover,
45            fg: t.fg_text,
46            radius: 5.0,
47        }
48    }
49}
50
51/// Compone un botón rectangular: bg + texto + on_click + hover. Por
52/// default ocupa ancho 100% del padre y alto 30 px; sobre-escribir
53/// pasando un `Style` propio vía [`button_styled`].
54pub fn button_view<Msg: Clone + 'static>(
55    label: impl Into<String>,
56    palette: &ButtonPalette,
57    on_click: Msg,
58) -> View<Msg> {
59    button_styled(
60        label,
61        Style {
62            size: Size {
63                width: percent(1.0_f32),
64                height: length(30.0_f32),
65            },
66            padding: Rect {
67                left: length(10.0_f32),
68                right: length(10.0_f32),
69                top: length(0.0_f32),
70                bottom: length(0.0_f32),
71            },
72            align_items: Some(AlignItems::Center),
73            justify_content: Some(JustifyContent::Center),
74            ..Default::default()
75        },
76        Alignment::Center,
77        palette,
78        on_click,
79    )
80}
81
82/// Variante con `Style` y alineación de texto explícitos — útil cuando
83/// la app necesita un botón con dimensiones particulares o el texto a
84/// la izquierda.
85pub fn button_styled<Msg: Clone + 'static>(
86    label: impl Into<String>,
87    style: Style,
88    text_alignment: Alignment,
89    palette: &ButtonPalette,
90    on_click: Msg,
91) -> View<Msg> {
92    // Gloss superior: gradient blanco alpha 28 → 0 sobre la mitad de
93    // arriba. `paint_with` corre entre el fill (que respeta hover_fill)
94    // y el texto, así que la luz se suma al color de base sin sustituirlo
95    // — el hover sigue funcionando idéntico. El RoundedRect cubre el
96    // botón completo y `Extend::Pad` (default de peniko) deja la mitad
97    // inferior en alpha 0. Match: chrome/splash — superficie con luz
98    // descendente desde el edge superior.
99    let radius = palette.radius;
100    View::new(style)
101        .fill(palette.bg)
102        .hover_fill(palette.bg_hover)
103        .radius(radius)
104        .paint_with(move |scene, _ts, rect| {
105            use llimphi_ui::llimphi_raster::kurbo::{Affine, Point, RoundedRect};
106            use llimphi_ui::llimphi_raster::peniko::{Fill, Gradient};
107            if rect.w <= 0.0 || rect.h <= 0.0 {
108                return;
109            }
110            let x0 = rect.x as f64;
111            let y0 = rect.y as f64;
112            let x1 = (rect.x + rect.w) as f64;
113            let y1 = (rect.y + rect.h) as f64;
114            let y_mid = y0 + (y1 - y0) * 0.5;
115            let rr = RoundedRect::new(x0, y0, x1, y1, radius);
116            let top = Color::from_rgba8(255, 255, 255, 28);
117            let bot = Color::from_rgba8(255, 255, 255, 0);
118            let gradient = Gradient::new_linear(Point::new(x0, y0), Point::new(x0, y_mid))
119                .with_stops([top, bot].as_slice());
120            scene.fill(Fill::NonZero, Affine::IDENTITY, &gradient, None, &rr);
121        })
122        .text_aligned(label.into(), 13.0, palette.fg, text_alignment)
123        .on_click(on_click)
124}