// Artist carousel — title, "View all" link, prev/next page controls, and // a clipped sliding track of artist cards. Same paging mechanism as // Carousel. Used by the search results All tab. import { Theme } from "../foundation/semantic-colors.slint"; import { Typography } from "../foundation/typography.slint"; import { SlimItem } from "../state.slint"; import { ArtistCard } from "ArtistCard.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint"; component NavButton inherits Rectangle { in property icon; in property enabled: true; callback clicked(); width: 28px; height: 28px; border-radius: 14px; opacity: root.enabled ? 1.0 : 0.4; background: ta.has-hover && root.enabled ? Theme.surface-hover : Theme.surface-elevated; QbzIcon { source: root.icon; width: 15px; height: 15px; x: Math.round((parent.width - self.width) / 2 / 1px) * 1px; y: Math.round((parent.height - self.height) / 2 / 1px) * 1px; tint: root.enabled ? Theme.text-primary : Theme.text-muted; } ta := TouchArea { enabled: root.enabled; mouse-cursor: root.enabled ? pointer : default; clicked => { root.clicked(); } } } component ViewAllLink inherits Rectangle { callback clicked(); width: link-text.preferred-width + 16px; height: 26px; border-radius: 4px; background: ta.has-hover ? Theme.surface-hover : transparent; link-text := Text { text: @tr("View all"); color: Theme.accent; font-size: 14px; font-weight: Typography.medium; x: Math.round((parent.width - self.width) / 2 / 1px) * 1px; y: Math.round((parent.height - self.height) / 2 / 1px) * 1px; } ta := TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } } } export component ArtistCarousel inherits VerticalLayout { in property title; in property <[SlimItem]> artists; in property show-view-all: false; // Follow-chip behavior + kind for the cards (see ArtistCard). // Defaults keep the artist-search usage unchanged. in property card-follow-mode: "auto"; in property card-follow-kind: "artist"; callback artist-clicked(string); callback media-action(string, string, string); callback view-all-clicked(); property card-width: 160px; property card-height: 220px; property gap: 16px; property count: root.artists.length; property per-page: Math.max( 1, Math.floor((viewport.width + root.gap) / (root.card-width + root.gap))); property page-count: Math.max(1, Math.ceil(root.count / root.per-page)); property page: 0; property current-page: Math.min(Math.max(0, root.page), root.page-count - 1); property step: root.per-page * (root.card-width + root.gap); property content-width: root.count * root.card-width + Math.max(0, root.count - 1) * root.gap; spacing: 12px; HorizontalLayout { spacing: 8px; Text { text: root.title; color: Theme.text-primary; font-size: 18px; font-weight: Typography.semibold; vertical-alignment: center; horizontal-stretch: 1; } VerticalLayout { alignment: center; NavButton { icon: @image-url("../assets/icons/chevron-left.svg"); enabled: root.current-page > 0; clicked => { root.page = root.current-page - 1; } } } VerticalLayout { alignment: center; NavButton { icon: @image-url("../assets/icons/chevron-right.svg"); enabled: root.current-page < root.page-count - 1; clicked => { root.page = root.current-page + 1; } } } if root.show-view-all: VerticalLayout { alignment: center; ViewAllLink { clicked => { root.view-all-clicked(); } } } } viewport := Rectangle { height: root.card-height; clip: true; Rectangle { x: -root.current-page * root.step; y: 0px; width: root.content-width; height: root.card-height; animate x { duration: 220ms; easing: ease-in-out; } HorizontalLayout { spacing: root.gap; for artist in root.artists: ArtistCard { artist: artist; follow-mode: root.card-follow-mode; follow-kind: root.card-follow-kind; clicked(id) => { root.artist-clicked(id); } media-action(k, i, a) => { root.media-action(k, i, a); } } } } Rectangle { x: 0px; width: 56px; background: @linear-gradient(90deg, Theme.surface-main 0%, transparent 100%); opacity: root.current-page > 0 ? 1.0 : 0.0; animate opacity { duration: 150ms; } } Rectangle { x: parent.width - self.width; width: 56px; background: @linear-gradient(90deg, transparent 0%, Theme.surface-main 100%); opacity: root.current-page < root.page-count - 1 ? 1.0 : 0.0; animate opacity { duration: 150ms; } } } }