tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ main widgets/scaffold/src/lib.rs Rust · 187 siq'ikuna · 6.4 KB Ñawpa willay Pi imata llamkarqan ch'uya

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

1//! `llimphi-widget-scaffold` — chasis de página.
2//!
3//! Inspiración Flutter `Scaffold`/Material 3 layout. Compone un layout
4//! de página común:
5//!
6//! ```text
7//! ┌──────────────────────────────┐
8//! │  app bar (opcional)          │  ← 48 px
9//! ├──────────────────────────────┤
10//! │                              │
11//! │   body (flex-grow 1)         │
12//! │                          ╭──╮│
13//! │                          │+ ││  ← FAB opcional (bottom-end)
14//! │                          ╰──╯│
15//! ├──────────────────────────────┤
16//! │  bottom bar (opcional)       │  ← 56 px
17//! └──────────────────────────────┘
18//! ```
19//!
20//! Los drawers laterales se ofrecen como **overlays** (la app los pasa
21//! por `view_overlay`, no por el body — así no roban el layout cuando
22//! están cerrados). Este widget sólo aporta el chasis central; los
23//! drawers son responsabilidad de quien los abre.
24
25#![forbid(unsafe_code)]
26
27
28// Sin `std`: este widget no usa nada del sistema —sólo `alloc` para
29// `String`/`Vec`—, y declararlo es lo que le permite cruzar a una jaula
30// WASM o al kernel. Antes enlazaba `std` por omisión (W4.6).
31#![no_std]
32
33extern crate alloc;
34
35use alloc::string::String;
36use alloc::vec::Vec;
37use alloc::vec;
38use llimphi_compositor::llimphi_layout::taffy::{
39    prelude::{length, percent, FlexDirection, Size, Style},
40    AlignItems, JustifyContent, Position,
41};
42use llimphi_compositor::peniko::Color;
43use llimphi_compositor::View;
44use llimphi_theme::Theme;
45
46#[derive(Debug, Clone, Copy)]
47pub struct ScaffoldPalette {
48    pub bg: Color,
49}
50
51impl ScaffoldPalette {
52    pub fn from_theme(t: &Theme) -> Self {
53        Self { bg: t.bg_app }
54    }
55}
56
57/// Opciones del scaffold — todas las superficies son opcionales (un
58/// scaffold con sólo `body` es válido y degenera en `View` con bg).
59pub struct ScaffoldSpec<Msg: Clone + 'static> {
60    pub app_bar: Option<View<Msg>>,
61    pub body: View<Msg>,
62    pub bottom_bar: Option<View<Msg>>,
63    pub fab: Option<View<Msg>>,
64}
65
66/// Compone el scaffold. Llamado típicamente desde el `view(model)` de la
67/// app como root. Para drawers, pasarlos por `view_overlay`.
68pub fn scaffold_view<Msg: Clone + 'static>(
69    spec: ScaffoldSpec<Msg>,
70    palette: &ScaffoldPalette,
71) -> View<Msg> {
72    let mut col_children: Vec<View<Msg>> = Vec::with_capacity(3);
73
74    if let Some(bar) = spec.app_bar {
75        col_children.push(
76            View::new(Style {
77                size: Size { width: percent(1.0_f32), height: length(48.0_f32) },
78                flex_shrink: 0.0,
79                ..Default::default()
80            })
81            .children(vec![bar]),
82        );
83    }
84
85    // Body con FAB anclado.
86    let mut body_layer_children: Vec<View<Msg>> = vec![spec.body];
87    if let Some(f) = spec.fab {
88        body_layer_children.push(
89            View::new(Style {
90                position: Position::Absolute,
91                inset: llimphi_compositor::llimphi_layout::taffy::Rect {
92                    left: llimphi_compositor::llimphi_layout::taffy::prelude::auto(),
93                    right: length(16.0_f32),
94                    top: llimphi_compositor::llimphi_layout::taffy::prelude::auto(),
95                    bottom: length(16.0_f32),
96                },
97                ..Default::default()
98            })
99            .children(vec![f]),
100        );
101    }
102    let body_layer = View::new(Style {
103        size: Size {
104            width: percent(1.0_f32),
105            height: llimphi_compositor::llimphi_layout::taffy::prelude::Dimension::auto(),
106        },
107        flex_grow: 1.0,
108        ..Default::default()
109    })
110    .children(body_layer_children);
111    col_children.push(body_layer);
112
113    if let Some(bar) = spec.bottom_bar {
114        col_children.push(
115            View::new(Style {
116                size: Size { width: percent(1.0_f32), height: length(56.0_f32) },
117                flex_shrink: 0.0,
118                ..Default::default()
119            })
120            .children(vec![bar]),
121        );
122    }
123
124    View::new(Style {
125        flex_direction: FlexDirection::Column,
126        size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
127        ..Default::default()
128    })
129    .fill(palette.bg)
130    .children(col_children)
131}
132
133/// App bar estándar — barra superior 48 px con título a la izquierda y
134/// slot de acciones a la derecha. El caller pasa el `View` de las
135/// acciones (botones de ícono típicamente).
136pub fn app_bar_view<Msg: Clone + 'static>(
137    title: impl Into<String>,
138    actions: Vec<View<Msg>>,
139    palette: &ScaffoldPalette,
140    theme: &Theme,
141) -> View<Msg> {
142    use llimphi_compositor::llimphi_text::Alignment;
143    let _ = palette;
144    let title_view = View::new(Style {
145        size: Size {
146            width: llimphi_compositor::llimphi_layout::taffy::prelude::auto(),
147            height: percent(1.0_f32),
148        },
149        flex_grow: 1.0,
150        align_items: Some(AlignItems::CENTER),
151        justify_content: Some(JustifyContent::FLEX_START),
152        padding: llimphi_compositor::llimphi_layout::taffy::Rect {
153            left: length(16.0_f32),
154            right: length(0.0_f32),
155            top: length(0.0_f32),
156            bottom: length(0.0_f32),
157        },
158        ..Default::default()
159    })
160    .text_aligned(title.into(), 16.0, theme.fg_text, Alignment::Start)
161    .bold();
162    let actions_view = View::new(Style {
163        flex_direction: FlexDirection::Row,
164        size: Size {
165            width: llimphi_compositor::llimphi_layout::taffy::prelude::auto(),
166            height: percent(1.0_f32),
167        },
168        align_items: Some(AlignItems::CENTER),
169        gap: Size { width: length(4.0_f32), height: length(0.0_f32) },
170        padding: llimphi_compositor::llimphi_layout::taffy::Rect {
171            left: length(0.0_f32),
172            right: length(8.0_f32),
173            top: length(0.0_f32),
174            bottom: length(0.0_f32),
175        },
176        ..Default::default()
177    })
178    .children(actions);
179    View::new(Style {
180        flex_direction: FlexDirection::Row,
181        size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
182        align_items: Some(AlignItems::CENTER),
183        ..Default::default()
184    })
185    .fill(theme.bg_panel)
186    .children(vec![title_view, actions_view])
187}