//! Showcase de `llimphi-widget-carousel`: 4 páginas de color con dots, //! flechas, **swipe** (arrastra la página y suelta), **teclado** (←/→) y //! **auto-play** (avanza sola cada 3 s; cualquier interacción no la detiene, //! es un demo). //! //! Corre con: `cargo run -p llimphi-widget-carousel --example carousel_demo --release`. use std::sync::Arc; use std::time::Duration; use llimphi_ui::llimphi_layout::taffy::{ prelude::{percent, Size, Style}, AlignItems, JustifyContent, }; use llimphi_ui::llimphi_raster::peniko::Color; use llimphi_ui::{App, Handle, Key, KeyEvent, KeyState, NamedKey, View}; use llimphi_widget_carousel::{ carousel_view, navigate, CarouselPalette, CarouselSpec, CarouselTransition, CarouselWrap, }; const N: usize = 4; /// Cuánto avanza la transición por tick (~60fps → ~180ms de deslizamiento). const TRANS_STEP: f32 = 0.12; #[derive(Clone)] enum Msg { Goto(usize), Swipe(i32), Tick, TransTick, } struct Model { current: usize, transition: Option, } /// Arranca una transición desde `from` hacia la página actual. fn begin(m: &mut Model, from: usize) { if from != m.current { m.transition = Some(CarouselTransition { from, t: 0.0 }); } } struct Showcase; impl App for Showcase { type Model = Model; type Msg = Msg; fn title() -> &'static str { "llimphi · carousel (swipe · ←/→ · auto-play)" } fn initial_size() -> (u32, u32) { (720, 480) } fn init(handle: &Handle) -> Model { // Auto-play: un tick cada 3 s que avanza una página. handle.spawn_periodic(Duration::from_secs(3), || Msg::Tick); // Animación de la transición (~60 fps). handle.spawn_periodic(Duration::from_millis(16), || Msg::TransTick); Model { current: 0, transition: None } } fn on_key(_model: &Model, event: &KeyEvent) -> Option { if event.state != KeyState::Pressed { return None; } match &event.key { Key::Named(NamedKey::ArrowLeft) => Some(Msg::Swipe(-1)), Key::Named(NamedKey::ArrowRight) => Some(Msg::Swipe(1)), _ => None, } } fn update(model: Model, msg: Msg, _: &Handle) -> Model { let mut m = model; match msg { Msg::Goto(i) => { let from = m.current; m.current = i.min(N - 1); begin(&mut m, from); } Msg::Swipe(d) => { let from = m.current; m.current = navigate(m.current, N, d, CarouselWrap::Wrap); begin(&mut m, from); } Msg::Tick => { let from = m.current; m.current = navigate(m.current, N, 1, CarouselWrap::Wrap); begin(&mut m, from); } Msg::TransTick => { if let Some(mut tr) = m.transition { tr.t += TRANS_STEP; m.transition = if tr.t >= 1.0 { None } else { Some(tr) }; } } } m } fn view(model: &Model) -> View { let colors = [ Color::from_rgba8(58, 90, 140, 255), Color::from_rgba8(140, 72, 82, 255), Color::from_rgba8(70, 130, 100, 255), Color::from_rgba8(120, 100, 150, 255), ]; let pages: Vec> = (0..N) .map(|i| { View::new(Style { size: Size { width: percent(1.0_f32), height: percent(1.0_f32) }, align_items: Some(AlignItems::CENTER), justify_content: Some(JustifyContent::CENTER), ..Default::default() }) .fill(colors[i]) .text( format!("Página {}", i + 1), 28.0, Color::from_rgba8(240, 244, 250, 255), ) }) .collect(); carousel_view(CarouselSpec { pages, current: model.current, wrap: CarouselWrap::Wrap, show_arrows: true, palette: CarouselPalette::default(), on_change: Arc::new(Msg::Goto), on_swipe: Some(Arc::new(Msg::Swipe)), transition: model.transition, }) } } fn main() { llimphi_ui::run::(); }