//! `llimphi-widget-card` — container card-shape para entries de //! timeline, info cards, dashboards, etc. //! //! Aporta la **forma**: padding consistente (12/8), `radius` 4, gap //! pequeño entre children, y opcionalmente un accent vertical //! (4 px) pegado a la izquierda para entries semánticas (verde = //! OK, rojo = error, ámbar = warning, etc). //! //! Análogo Llimphi al `nahual-widget-card` GPUI. #![forbid(unsafe_code)] use llimphi_ui::llimphi_layout::taffy::{ prelude::{length, percent, FlexDirection, Size, Style}, Rect, }; use llimphi_ui::llimphi_raster::peniko::Color; use llimphi_ui::{Shadow, View}; use llimphi_theme::elevation; use llimphi_widget_panel::{panel_signature_painter, PanelStyle}; #[derive(Debug, Clone, Copy)] pub struct CardPalette { pub bg: Color, } impl Default for CardPalette { fn default() -> Self { Self::from_theme(&llimphi_theme::Theme::dark()) } } impl CardPalette { pub fn from_theme(t: &llimphi_theme::Theme) -> Self { Self { bg: t.bg_panel } } } /// Opciones del card. #[derive(Debug, Clone, Copy)] pub struct CardOptions { /// Accent vertical a la izquierda (4 px). `None` = sin accent. pub accent: Option, pub padding: f32, pub gap: f32, pub radius: f64, /// Firma visual del panel (gradient sutil + hairline accent en el /// top). `Some(style)` reemplaza el fill plano del body por el /// painter de la firma — usar para cards prominentes (dashboards, /// timeline entries grandes) donde se nota el "tallado". `None` /// mantiene el fill sólido del `CardPalette` (default). pub signature: Option, } impl Default for CardOptions { fn default() -> Self { Self { accent: None, padding: 12.0, gap: 4.0, radius: 4.0, signature: None, } } } impl CardOptions { /// Variante con firma visual derivada del theme. El `radius` del /// card se alinea al del `PanelStyle` para que la silueta del /// gradiente coincida con las esquinas del nodo. pub fn with_signature(t: &llimphi_theme::Theme) -> Self { let style = PanelStyle::from_theme(t); Self { accent: None, padding: 12.0, gap: 4.0, radius: style.radius, signature: Some(style), } } } /// Compone un card: bg + radius + padding + flex-column con gap entre /// children. Si `opts.accent` está presente, hay una franja vertical /// de 4 px del color del accent pegada al borde izquierdo. pub fn card_view( children: Vec>, opts: CardOptions, palette: &CardPalette, ) -> View { let pad = opts.padding; let body_style = Style { flex_direction: FlexDirection::Column, size: Size { width: percent(1.0_f32), height: llimphi_ui::llimphi_layout::taffy::prelude::Dimension::auto(), }, flex_grow: 1.0, padding: Rect { left: length(pad), right: length(pad), top: length(pad * 0.66), bottom: length(pad * 0.66), }, gap: Size { width: length(0.0_f32), height: length(opts.gap), }, ..Default::default() }; let body = if let Some(style) = opts.signature { View::new(body_style) .paint_with(panel_signature_painter(style)) .radius(opts.radius) .clip(true) .children(children) } else { View::new(body_style) .fill(palette.bg) .radius(opts.radius) .children(children) }; let Some(accent) = opts.accent else { return body; }; let accent_strip = View::new(Style { size: Size { width: length(4.0_f32), height: percent(1.0_f32), }, ..Default::default() }) .fill(accent) .radius(opts.radius); View::new(Style { flex_direction: FlexDirection::Row, size: Size { width: percent(1.0_f32), height: llimphi_ui::llimphi_layout::taffy::prelude::Dimension::auto(), }, ..Default::default() }) .children(vec![accent_strip, body]) } /// Variante elevada — la misma forma de `card_view` pero con sombra /// del nivel `elev` (token de [`llimphi_theme::elevation`]). Default /// pensado: `elevation::E2` (card flotante sobre el panel). El accent /// strip — si está presente — queda detrás de la sombra; el visual /// dominante es el body. Útil para dashboards y entries destacadas /// que necesitan separación clara del fondo del panel. pub fn card_elevated_view( children: Vec>, opts: CardOptions, palette: &CardPalette, elev: elevation::Elev, ) -> View { let (a, blur, dy) = elev; let shadow = Shadow { color: Color::from_rgba8(0, 0, 0, a), blur, dx: 0.0, dy, spread: 0.0, }; card_view(children, opts, palette).shadow(shadow) }