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

Motor gráfico Llimphi como workspace independiente: bucle Elm
(input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley.
Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets
+ módulos, sin dependencias al resto del monorepo. cargo check --workspace
pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 04:23:42 +00:00
commit e65e9cc623
286 changed files with 46136 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "llimphi-widget-app-header"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
publish.workspace = true
description = "llimphi-widget-app-header — tira superior estándar para apps Llimphi: label dinámico a la izquierda + slot de acciones opcional a la derecha. Análogo Llimphi al `nahual-widget-app-header` GPUI."
[dependencies]
llimphi-ui = { workspace = true }
llimphi-theme = { workspace = true }
llimphi-widget-panel = { workspace = true }
+5
View File
@@ -0,0 +1,5 @@
# llimphi-widget-app-header
> Header común de app para [llimphi](../../README.md).
Barra superior estándar: logo/icono · título · acciones a la derecha · breadcrumb opcional. Cualquier app del monorepo lo usa para coherencia visual.
+5
View File
@@ -0,0 +1,5 @@
# llimphi-widget-app-header
> Common app header for [llimphi](../../README.md).
Standard top bar: logo/icon · title · right-side actions · optional breadcrumb. Any monorepo app uses it for visual coherence.
+145
View File
@@ -0,0 +1,145 @@
//! `llimphi-widget-app-header` — tira superior estándar de las apps.
//!
//! Reproduce el contrato del `nahual-widget-app-header` GPUI: label
//! dinámico a la izquierda con `flex_grow`, slot a la derecha para
//! acciones (theme switcher, botones de toolbar, etc.). bg = `bg_panel`,
//! line-bottom como `border` del theme.
//!
//! Uso típico:
//!
//! ```ignore
//! app_header(format!("Log: {} · {} entries", path, n), vec![], &palette)
//! ```
#![forbid(unsafe_code)]
use llimphi_ui::llimphi_layout::taffy::{
prelude::{length, percent, FlexDirection, Size, Style},
AlignItems, Rect,
};
use llimphi_ui::llimphi_raster::peniko::Color;
use llimphi_ui::llimphi_text::Alignment;
use llimphi_ui::View;
use llimphi_widget_panel::{panel_signature_painter, PanelStyle};
/// Paleta del header. Defaults desde el theme global.
#[derive(Debug, Clone, Copy)]
pub struct AppHeaderPalette {
pub bg: Color,
pub border_bottom: Color,
pub fg_text: Color,
pub height: f32,
/// Firma visual: gradient sutil + hairline accent en el top edge. Se
/// activa por defecto al construir desde theme. `None` cae al fill
/// plano de `bg` (modo back-compat para sitios que arman la palette
/// a mano sin theme).
pub signature: Option<PanelStyle>,
}
impl Default for AppHeaderPalette {
fn default() -> Self {
Self::from_theme(&llimphi_theme::Theme::dark())
}
}
impl AppHeaderPalette {
pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
Self {
bg: t.bg_panel,
border_bottom: t.border,
fg_text: t.fg_text,
height: 40.0,
signature: Some(PanelStyle {
radius: 0.0,
..PanelStyle::from_theme(t)
}),
}
}
}
/// Header con `label` a la izquierda y `actions` a la derecha. `actions`
/// es vacío para apps sin toolbar; viene como Vec para que la app meta
/// botones / switcher / status pill / lo que necesite.
pub fn app_header<Msg: Clone + 'static>(
label: impl Into<String>,
actions: Vec<View<Msg>>,
palette: &AppHeaderPalette,
) -> View<Msg> {
let label_view = View::new(Style {
size: Size {
width: percent(1.0_f32),
height: length(palette.height),
},
flex_grow: 1.0,
padding: Rect {
left: length(16.0_f32),
right: length(16.0_f32),
top: length(0.0_f32),
bottom: length(0.0_f32),
},
align_items: Some(AlignItems::Center),
..Default::default()
})
.text_aligned(label.into(), 14.0, palette.fg_text, Alignment::Start);
let actions_view = View::new(Style {
flex_direction: FlexDirection::Row,
size: Size {
width: llimphi_ui::llimphi_layout::taffy::prelude::Dimension::auto(),
height: length(palette.height),
},
align_items: Some(AlignItems::Center),
padding: Rect {
left: length(8.0_f32),
right: length(8.0_f32),
top: length(0.0_f32),
bottom: length(0.0_f32),
},
gap: Size {
width: length(6.0_f32),
height: length(0.0_f32),
},
..Default::default()
})
.children(actions);
// Bottom border: el header rellena `bg` (o aplica la firma si está
// habilitada), y debajo va una línea 1px de `border_bottom`. Lo
// metemos como un wrapper column.
let bar_style = Style {
flex_direction: FlexDirection::Row,
size: Size {
width: percent(1.0_f32),
height: length(palette.height),
},
align_items: Some(AlignItems::Center),
..Default::default()
};
let bar = match palette.signature {
Some(style) => View::new(bar_style)
.paint_with(panel_signature_painter(style))
.children(vec![label_view, actions_view]),
None => View::new(bar_style)
.fill(palette.bg)
.children(vec![label_view, actions_view]),
};
let underline = View::new(Style {
size: Size {
width: percent(1.0_f32),
height: length(1.0_f32),
},
..Default::default()
})
.fill(palette.border_bottom);
View::new(Style {
flex_direction: FlexDirection::Column,
size: Size {
width: percent(1.0_f32),
height: length(palette.height + 1.0),
},
..Default::default()
})
.children(vec![bar, underline])
}