limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/Layout

    CarouselNew

    A shelf of items that scrolls sideways inside a page that scrolls down, under an optional heading: the rows of a music app's home page. It has no arrow buttons; the pointer moves it.

    Native componentGuide source

    Preview

    Examples

    Each one is the library drawing it. Press, type and hover.

    Eyebrow and title
    CodeSource
    Carousel::new("carousel-mixes", albums("carousel-mix", 10), palette)
        .eyebrow("Made for Ana")
        .title("Your top mixes")
        .trailing(all("carousel-mixes-all"))
        .render()
    Beside a picture
    CodeSource
    Carousel::new("carousel-artists", artists, palette)
        .leading(Avatar::new("Nina Simone", palette).render())
        .eyebrow("More like")
        .title("Nina Simone")
        .render()
    A long shelf
    CodeSource
    Carousel::new("carousel-long", albums("carousel-long-album", 60), palette)
        .title("Sixty albums")
        .gap(16.0)
        .inset(16.0)
        .render()
    While it loads
    CodeSource
    Carousel::new("carousel-loading", coming, palette).title("Recently played").render()
    Long title
    CodeSource
    Carousel::new("carousel-long-title", albums("carousel-long-title-album", 6), palette)
        .title("The albums everyone in the house keeps putting on when friends come over")
        .trailing(all("carousel-long-title-all"))
        .render()

    Carousel

    A shelf of items that scrolls sideways inside a page that scrolls down, under an optional heading: the rows of a music app’s home page. It has no arrow buttons; the pointer moves it.

    Import

    use limn::{Carousel, Component, MediaTile};

    Usage

    A carousel takes an ID, the items in order and the palette. The items are the host’s own elements and report their own actions.

    fn view(state: &State, palette: ComponentPalette) -> Element {
        let tiles = state
            .albums
            .iter()
            .map(|album| MediaTile::new(format!("album={}", album.id), &album.title, palette).subtitle(&album.artist).render())
            .collect();
        Carousel::new("home-albums", tiles, palette).title("Jump back in").render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        if let UiAction::Clicked { id } = action {
            if let Some(album) = id.strip_prefix("album=") {
                state.open_album(album);
            }
        }
    }

    The row fills the width it is given and is as tall as its tallest item, with 8 px under the items for its indicator.

    Moving it

    InputEffect
    A drag anywhere on the row, on an item tooThe row follows the pointer. A press on an item becomes a drag after 4 px of travel sideways, and the item is then not clicked.
    A press that does not travelClicks the item under it, as anywhere else.
    Letting go of a drag with speedThe row glides on and slows to rest, stopping at either end.
    A press while it glidesCatches the row where it is, and clicks nothing.
    Trackpad sideways, or Shift with the wheelPans the row.
    The wheelScrolls the page; the row does not move.

    The tree keeps where the row stands under the carousel’s ID, across rebuilds and routes. tree.horizontal_scroll_offset(id) reads it, and tree.set_horizontal_scroll_offset(id, 0.0) puts it back to the start, for a row whose content was replaced.

    Heading

    Carousel::new("mixes", tiles, palette)
        .eyebrow("Made for Ana")
        .title("Your top mixes")
        .trailing(Link::new("mixes:all", "Show all", palette).render())
        .render()
    BuilderWhat it adds
    title(text)The heading, 24 px bold on one line, cut with an ellipsis when it does not fit.
    eyebrow(text)A muted line of 14 px over the title: what the row is to the reader, over its name.
    leading(element)A picture beside the heading, held round in 48 × 48, such as the artist a row is about.
    trailing(element)An element at the far end of the heading, such as a “Show all” link.

    Without any of them the carousel is the row alone.

    Gap and inset

    Carousel::new("long", tiles, palette).gap(16.0).inset(32.0)

    gap is the space between items, 24 unless set. inset is the space before the first item and after the last, and on both sides of the heading, zero unless set. Give the carousel the page’s full width and the page’s gutter as its inset: the first item lines up with the rest of the page, and the row still scrolls out under the gutter to the page’s edges. Both take logical pixels; a negative value is zero and one that is not finite is ignored.

    Edges

    Where items are hidden past an edge, that edge fades into the palette’s surface over the inset and two gaps, 48 px at the defaults. An edge with nothing past it does not fade. Give the carousel the color of the page it stands on:

    Carousel::new("home-albums", tiles, ComponentPalette { surface: page_color, ..palette })

    A thin indicator in the palette’s muted color shows where the row stands while it moves and fades out after it stops.

    Keyboard

    KeyEffect
    Tab, Shift+TabMoves through the items, as through any controls. A focused item is scrolled clear of the edges’ fades.
    Enter, SpaceActivates the focused item.

    Motion

    WhatHow
    A released dragGlides on at the pointer’s speed over its last 100 ms, at most 8000 px/s, slowing as e^(-t/0.325 s) until under 20 px/s. It follows elapsed time, so it covers the same ground at any frame rate. The tree asks for a frame on every refresh while it glides.
    The indicatorHolds for four move_ms after the row stops, then fades over exit_ms.

    Under reduced motion a released row stays where it was let go, and the indicator hides without fading.

    Actions

    The carousel reports nothing of its own: each item reports its own, such as UiAction::Clicked { id } from a MediaTile.

    Accessibility

    The title is a heading for the platform, with the eyebrow as its description. Each item says what it is itself.

    API reference

    The guide covers composition, options and actions. Generate the complete Rust type and builder reference from the checkout:

    cargo doc -p limn --open

    Component API overview · State and actions

    The example is drawn by crates/limn-gallery/src/pages/carousel.rs.