Carousel
A carousel with motion and swipe built using Embla-style semantics.
Overview
视口 overflow-hidden + flex track 平移,两侧圆形 outline 按钮(起始位 prev 禁用),点按发射 Slide X of N。
Installation
Carousel 是单个 Rust 文件(src/carousel.rs)—— track/viewport/位移全自绘,导航按钮复用 button.rs。
[dependencies]gpui-kit = "0.6.6"# carousel.rs composes button.rs for the nav controls —# both ship in source.zip.# Download the runnable example crate, then:cargo run --locked -- --carousel# Other demos:cargo run --locked -- --carousel sizescargo run --locked -- --carousel spacingcargo run --locked -- --carousel verticalcargo run --locked -- --carousel loopUsage
Carousel 是 entity:cx.new(|cx| Carousel::new(cx)) 拿到 focus handle 后配置 slide 数量、每张占比(per_view)、间距与轴向。滚动状态由组件自持,选中变化通过 CarouselSelectEvent 订阅。
use justdo_command::carousel::{Carousel, CarouselSelectEvent};let carousel = cx.new(|cx| { Carousel::new(cx) .dark(dark) .count(5) // demo slides 1..=5 .per_view(1) // basis-full — one slide per viewport .gap(16.) .slide_px(336.) // extent along the scroll axis .cross_px(200.) // cross-axis extent .loop_(false)});// Current-slide API — emitted by nav buttons and arrow keys.cx.subscribe(&carousel, |_, _, e: &CarouselSelectEvent, _| { println!("slide {} of {}", e.index + 1, e.count);}).detach();网页预览与桌面端运行同一份 Rust / GPUI 组件。
Composition
组合结构对齐 shadcn/ui 的 Carousel / CarouselContent / CarouselItem / CarouselPrevious / CarouselNext(Embla)。
Carousel root — relative, focusable, arrow-key scroll├── viewport overflow-hidden clip box│ └── track flex-row (or flex-col) + gap, offset by -scroll px│ ├── slide flex_none, slide × cross, rounded card│ └── … rendered twice when loop_(true)├── prev circular outline button, -left-44px, disabled at 0└── next circular outline button, -right-44px, disabled at endNotes
Scroll & loop. 位移是 track 上的单个动画 px offset(负 margin),ease-out cubic 320ms。loop_(true) 会把 slide 列表渲染两份:回绕时动画先滚进复制 lap,落地后 offset 按一圈取模归位 —— 视觉上连续无缝。
// Scroll model: one animated px offset on the flex track// (negative margin), eased cubic-out over 320ms like Embla.// loop_(true) renders the slide list twice; a wrap-around// animates into the duplicated lap then snaps the offset// back by one lap — visually continuous, no blank space.car.go(-1, cx); // previous slidecar.go(1, cx); // next slide (clamped or wrapped)Keyboard. 根容器可聚焦(点击视口聚焦):横排响应 ←/→,竖排(.vertical(true))响应 ↑/↓,与 Embla 的键盘映射一致。
Boundaries. 非 loop 模式首尾按钮禁用(can_prev / can_next);per_view > 1 时末位自动停在最后完整视口(count - per_view)。
API Reference
Carousel 实体与事件。
| API | Signature | Notes |
|---|---|---|
Carousel::new(cx) | -> Carousel | Entity; needs cx for the focus handle. |
.count(..) | usize | Slide count (demo slides 1..=n). |
.per_view(..) | usize | Slides per viewport — Embla basis-1/n. |
.gap(..) | f32 | Slide gutter — Embla pl-*. |
.slide_px(..) / .cross_px(..) | f32 | Slide extent on the scroll axis / cross axis. |
.vertical(..) | bool | Vertical axis: column track + top/bottom buttons. |
.loop_(..) | bool | Embla opts.loop — seamless wrap, buttons never disable. |
.dark(..) | bool | Dark theme. |
CarouselSelectEvent | { index, count } | Emitted on every slide change — the current-slide API. |
Source & inspiration
结构与交互参考 shadcn/ui Carousel (React Aria / Embla) 。本页交互由 Rust / GPUI 绘制,基础样式来自 gpui-kit 。Autoplay/插件体系与 RTL 未移植。