Files
sergio e65e9cc623 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>
2026-06-04 04:23:42 +00:00

137 lines
3.6 KiB
Rust

//! Showcase de `llimphi-widget-tabs`: 3 tabs con contenido distinto
//! cada uno. Hover en los tabs inactivos cambia el bg.
//!
//! Corré con: `cargo run -p llimphi-widget-tabs --example showcase --release`.
use llimphi_ui::llimphi_layout::taffy::{
prelude::{length, percent, Size, Style},
AlignItems, Rect,
};
use llimphi_ui::llimphi_raster::peniko::Color;
use llimphi_ui::llimphi_text::Alignment;
use llimphi_ui::{App, Handle, View};
use llimphi_widget_tabs::{tabs_view, TabsPalette, TabsSpec};
#[derive(Clone)]
enum Msg {
SelectTab(usize),
}
struct Model {
active: usize,
}
struct Showcase;
impl App for Showcase {
type Model = Model;
type Msg = Msg;
fn title() -> &'static str {
"llimphi · tabs showcase"
}
fn initial_size() -> (u32, u32) {
(900, 600)
}
fn init(_: &Handle<Msg>) -> Model {
Model { active: 0 }
}
fn update(model: Model, msg: Msg, _: &Handle<Msg>) -> Model {
let mut m = model;
match msg {
Msg::SelectTab(i) => m.active = i,
}
m
}
fn view(model: &Model) -> View<Msg> {
let body = match model.active {
0 => content_pane(
"General",
"Acá vivirían los settings principales del módulo.\n\
El click cambia de tab; el hover sobre tabs inactivos\n\
ilumina el fondo levemente.",
Color::from_rgba8(220, 230, 245, 255),
),
1 => content_pane(
"Avanzado",
"Variables esotéricas, banderas experimentales.\n\
Probablemente no las toques.",
Color::from_rgba8(200, 220, 240, 255),
),
_ => content_pane(
"Logs",
"[12:01:33] arranqué\n[12:01:34] cargué config\n\
[12:01:35] esperando eventos…",
Color::from_rgba8(180, 195, 215, 255),
),
};
tabs_view(TabsSpec {
labels: vec!["General".into(), "Avanzado".into(), "Logs".into()],
active: model.active,
on_select: Msg::SelectTab,
content: body,
tab_height: 36.0,
palette: TabsPalette::default(),
tab_width: Some(160.0),
})
}
}
fn content_pane(title: &str, body: &str, fg: Color) -> View<Msg> {
let header = View::new(Style {
size: Size {
width: percent(1.0_f32),
height: length(36.0_f32),
},
padding: Rect {
left: length(20.0_f32),
right: length(20.0_f32),
top: length(8.0_f32),
bottom: length(0.0_f32),
},
align_items: Some(AlignItems::Start),
..Default::default()
})
.text_aligned(
format!("# {title}"),
18.0,
Color::from_rgba8(220, 230, 245, 255),
Alignment::Start,
);
let body_view = View::new(Style {
size: Size {
width: percent(1.0_f32),
height: percent(1.0_f32),
},
flex_grow: 1.0,
padding: Rect {
left: length(20.0_f32),
right: length(20.0_f32),
top: length(0.0_f32),
bottom: length(20.0_f32),
},
..Default::default()
})
.text_aligned(body.to_string(), 13.0, fg, Alignment::Start);
View::new(Style {
flex_direction: llimphi_ui::llimphi_layout::taffy::FlexDirection::Column,
size: Size {
width: percent(1.0_f32),
height: percent(1.0_f32),
},
..Default::default()
})
.children(vec![header, body_view])
}
fn main() {
llimphi_ui::run::<Showcase>();
}