tawasuyu

tawasuyu/llimphi

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

Rust 238TOML 102Markdown 83Shell 24178 Definitions

⎇ c1b11b2c01d7623a4f346519b9d1ec435be30b95 widgets/app-header/src/lib.rs Rust · 145 lines · 4.4 KB History Who touched what raw

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

1//! `llimphi-widget-app-header` — tira superior estándar de las apps.
2//!
3//! Reproduce el contrato del `nahual-widget-app-header` GPUI: label
4//! dinámico a la izquierda con `flex_grow`, slot a la derecha para
5//! acciones (theme switcher, botones de toolbar, etc.). bg = `bg_panel`,
6//! line-bottom como `border` del theme.
7//!
8//! Uso típico:
9//!
10//! ```ignore
11//! app_header(format!("Log: {} · {} entries", path, n), vec![], &palette)
12//! ```
13
14#![forbid(unsafe_code)]
15
16use llimphi_ui::llimphi_layout::taffy::{
17    prelude::{length, percent, FlexDirection, Size, Style},
18    AlignItems, Rect,
19};
20use llimphi_ui::llimphi_raster::peniko::Color;
21use llimphi_ui::llimphi_text::Alignment;
22use llimphi_ui::View;
23use llimphi_widget_panel::{panel_signature_painter, PanelStyle};
24
25/// Paleta del header. Defaults desde el theme global.
26#[derive(Debug, Clone, Copy)]
27pub struct AppHeaderPalette {
28    pub bg: Color,
29    pub border_bottom: Color,
30    pub fg_text: Color,
31    pub height: f32,
32    /// Firma visual: gradient sutil + hairline accent en el top edge. Se
33    /// activa por defecto al construir desde theme. `None` cae al fill
34    /// plano de `bg` (modo back-compat para sitios que arman la palette
35    /// a mano sin theme).
36    pub signature: Option<PanelStyle>,
37}
38
39impl Default for AppHeaderPalette {
40    fn default() -> Self {
41        Self::from_theme(&llimphi_theme::Theme::dark())
42    }
43}
44
45impl AppHeaderPalette {
46    pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
47        Self {
48            bg: t.bg_panel,
49            border_bottom: t.border,
50            fg_text: t.fg_text,
51            height: 40.0,
52            signature: Some(PanelStyle {
53                radius: 0.0,
54                ..PanelStyle::from_theme(t)
55            }),
56        }
57    }
58}
59
60/// Header con `label` a la izquierda y `actions` a la derecha. `actions`
61/// es vacío para apps sin toolbar; viene como Vec para que la app meta
62/// botones / switcher / status pill / lo que necesite.
63pub fn app_header<Msg: Clone + 'static>(
64    label: impl Into<String>,
65    actions: Vec<View<Msg>>,
66    palette: &AppHeaderPalette,
67) -> View<Msg> {
68    let label_view = View::new(Style {
69        size: Size {
70            width: percent(1.0_f32),
71            height: length(palette.height),
72        },
73        flex_grow: 1.0,
74        padding: Rect {
75            left: length(16.0_f32),
76            right: length(16.0_f32),
77            top: length(0.0_f32),
78            bottom: length(0.0_f32),
79        },
80        align_items: Some(AlignItems::Center),
81        ..Default::default()
82    })
83    .text_aligned(label.into(), 14.0, palette.fg_text, Alignment::Start);
84
85    let actions_view = View::new(Style {
86        flex_direction: FlexDirection::Row,
87        size: Size {
88            width: llimphi_ui::llimphi_layout::taffy::prelude::Dimension::auto(),
89            height: length(palette.height),
90        },
91        align_items: Some(AlignItems::Center),
92        padding: Rect {
93            left: length(8.0_f32),
94            right: length(8.0_f32),
95            top: length(0.0_f32),
96            bottom: length(0.0_f32),
97        },
98        gap: Size {
99            width: length(6.0_f32),
100            height: length(0.0_f32),
101        },
102        ..Default::default()
103    })
104    .children(actions);
105
106    // Bottom border: el header rellena `bg` (o aplica la firma si está
107    // habilitada), y debajo va una línea 1px de `border_bottom`. Lo
108    // metemos como un wrapper column.
109    let bar_style = Style {
110        flex_direction: FlexDirection::Row,
111        size: Size {
112            width: percent(1.0_f32),
113            height: length(palette.height),
114        },
115        align_items: Some(AlignItems::Center),
116        ..Default::default()
117    };
118    let bar = match palette.signature {
119        Some(style) => View::new(bar_style)
120            .paint_with(panel_signature_painter(style))
121            .children(vec![label_view, actions_view]),
122        None => View::new(bar_style)
123            .fill(palette.bg)
124            .children(vec![label_view, actions_view]),
125    };
126
127    let underline = View::new(Style {
128        size: Size {
129            width: percent(1.0_f32),
130            height: length(1.0_f32),
131        },
132        ..Default::default()
133    })
134    .fill(palette.border_bottom);
135
136    View::new(Style {
137        flex_direction: FlexDirection::Column,
138        size: Size {
139            width: percent(1.0_f32),
140            height: length(palette.height + 1.0),
141        },
142        ..Default::default()
143    })
144    .children(vec![bar, underline])
145}