tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1//! Showcase de `llimphi-widget-carousel`: 4 páginas de color con dots,
2//! flechas, **swipe** (arrastra la página y suelta), **teclado** (←/→) y
3//! **auto-play** (avanza sola cada 3 s; cualquier interacción no la detiene,
4//! es un demo).
5//!
6//! Corre con: `cargo run -p llimphi-widget-carousel --example carousel_demo --release`.
7
8use std::sync::Arc;
9use std::time::Duration;
10
11use llimphi_ui::llimphi_layout::taffy::{
12 prelude::{percent, Size, Style},
13 AlignItems, JustifyContent,
14};
15use llimphi_ui::llimphi_raster::peniko::Color;
16use llimphi_ui::{App, Handle, Key, KeyEvent, KeyState, NamedKey, View};
17use llimphi_widget_carousel::{
18 carousel_view, navigate, CarouselPalette, CarouselSpec, CarouselTransition, CarouselWrap,
19};
20
21const N: usize = 4;
22/// Cuánto avanza la transición por tick (~60fps → ~180ms de deslizamiento).
23const TRANS_STEP: f32 = 0.12;
24
25#[derive(Clone)]
26enum Msg {
27 Goto(usize),
28 Swipe(i32),
29 Tick,
30 TransTick,
31}
32
33struct Model {
34 current: usize,
35 transition: Option<CarouselTransition>,
36}
37
38/// Arranca una transición desde `from` hacia la página actual.
39fn begin(m: &mut Model, from: usize) {
40 if from != m.current {
41 m.transition = Some(CarouselTransition { from, t: 0.0 });
42 }
43}
44
45struct Showcase;
46
47impl App for Showcase {
48 type Model = Model;
49 type Msg = Msg;
50
51 fn title() -> &'static str {
52 "llimphi · carousel (swipe · ←/→ · auto-play)"
53 }
54
55 fn initial_size() -> (u32, u32) {
56 (720, 480)
57 }
58
59 fn init(handle: &Handle<Msg>) -> Model {
60 // Auto-play: un tick cada 3 s que avanza una página.
61 handle.spawn_periodic(Duration::from_secs(3), || Msg::Tick);
62 // Animación de la transición (~60 fps).
63 handle.spawn_periodic(Duration::from_millis(16), || Msg::TransTick);
64 Model { current: 0, transition: None }
65 }
66
67 fn on_key(_model: &Model, event: &KeyEvent) -> Option<Msg> {
68 if event.state != KeyState::Pressed {
69 return None;
70 }
71 match &event.key {
72 Key::Named(NamedKey::ArrowLeft) => Some(Msg::Swipe(-1)),
73 Key::Named(NamedKey::ArrowRight) => Some(Msg::Swipe(1)),
74 _ => None,
75 }
76 }
77
78 fn update(model: Model, msg: Msg, _: &Handle<Msg>) -> Model {
79 let mut m = model;
80 match msg {
81 Msg::Goto(i) => {
82 let from = m.current;
83 m.current = i.min(N - 1);
84 begin(&mut m, from);
85 }
86 Msg::Swipe(d) => {
87 let from = m.current;
88 m.current = navigate(m.current, N, d, CarouselWrap::Wrap);
89 begin(&mut m, from);
90 }
91 Msg::Tick => {
92 let from = m.current;
93 m.current = navigate(m.current, N, 1, CarouselWrap::Wrap);
94 begin(&mut m, from);
95 }
96 Msg::TransTick => {
97 if let Some(mut tr) = m.transition {
98 tr.t += TRANS_STEP;
99 m.transition = if tr.t >= 1.0 { None } else { Some(tr) };
100 }
101 }
102 }
103 m
104 }
105
106 fn view(model: &Model) -> View<Msg> {
107 let colors = [
108 Color::from_rgba8(58, 90, 140, 255),
109 Color::from_rgba8(140, 72, 82, 255),
110 Color::from_rgba8(70, 130, 100, 255),
111 Color::from_rgba8(120, 100, 150, 255),
112 ];
113 let pages: Vec<View<Msg>> = (0..N)
114 .map(|i| {
115 View::new(Style {
116 size: Size { width: percent(1.0_f32), height: percent(1.0_f32) },
117 align_items: Some(AlignItems::CENTER),
118 justify_content: Some(JustifyContent::CENTER),
119 ..Default::default()
120 })
121 .fill(colors[i])
122 .text(
123 format!("Página {}", i + 1),
124 28.0,
125 Color::from_rgba8(240, 244, 250, 255),
126 )
127 })
128 .collect();
129
130 carousel_view(CarouselSpec {
131 pages,
132 current: model.current,
133 wrap: CarouselWrap::Wrap,
134 show_arrows: true,
135 palette: CarouselPalette::default(),
136 on_change: Arc::new(Msg::Goto),
137 on_swipe: Some(Arc::new(Msg::Swipe)),
138 transition: model.transition,
139 })
140 }
141}
142
143fn main() {
144 llimphi_ui::run::<Showcase>();
145}